From a1d6a062a951419d41e1837c2a312c9c490bea69 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Fri, 12 Sep 2025 12:44:11 -0700 Subject: [PATCH 001/681] 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 189aa7ad54..7c0b967abd 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/681] =?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 7b87846748..cb5a6b3a47 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/681] 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 582a44454e..57ba2a245f 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 4af9a102ac..f3da6e3e6f 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/681] 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 3390fed2e1..eb4e4ef584 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/681] 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 54ff8e97e9..ae12809eb9 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/681] 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 cc1605a132..152dbfe456 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 5b13bd635a..a788b9a290 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 cf169f4dc6..c87db4232b 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 c6a4b12e63..ae7196684d 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 0d20b6487c..0d5b3d2213 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 0000000000..a87d3323ce --- /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 0000000000..b7c48f7961 --- /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 0000000000..17985d73b6 --- /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 0000000000..27375793a9 --- /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 0000000000..f8812d6f17 --- /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 0000000000..c8acedae83 --- /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 0000000000..031f10320a --- /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 0000000000..82fc7d1bd1 --- /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 0000000000..1a57c43007 --- /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 0000000000..f54c365c1c --- /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 0000000000..54fc91b112 --- /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 0000000000..b8c3841727 --- /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 0000000000..108aa120d9 --- /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 0000000000..72002e6892 --- /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 0000000000..2c2f42e885 --- /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 0000000000..aa372312b4 --- /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 0000000000..6467ef6639 --- /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 0000000000..8bf7c4a0d3 --- /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 0000000000..738746f3fc --- /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 0000000000..aa41fbba83 --- /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 0000000000..942690da84 --- /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 0000000000..2afacbd1dc --- /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 0000000000..7c11f362ec --- /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 0000000000..02c4c961f3 --- /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 0000000000..6ccba34dbc --- /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 0000000000..9f2069af2d --- /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 0000000000..7781faab45 --- /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 0000000000..59f788e025 --- /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 0000000000..d578829d44 --- /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 0000000000..abe162a93b --- /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 0000000000..a4244e9137 --- /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 0000000000..af9666e186 --- /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 0000000000..07fe55f955 --- /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 0000000000..0efde349bb --- /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 0000000000..19f01b3441 --- /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 0000000000..6d81ffa43e --- /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 0000000000..1a156aed36 --- /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 0000000000..42a94d1941 --- /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 0000000000..2a47842614 --- /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 0000000000..28a3364eee --- /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 0000000000..40ecca50e9 --- /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 0000000000..4e1eedcf99 --- /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 0000000000..ce048414e3 --- /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 0000000000..7f3e2654eb --- /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 0000000000..db8221772a --- /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 0000000000..c16f03bfd2 --- /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 0000000000..9a55285676 --- /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 0000000000..b8e017531e --- /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 0000000000..fd462ec6b8 --- /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 0000000000..8d6fdaca6c --- /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 0000000000..2dfac6f5b2 --- /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 0000000000..02549bd9a9 --- /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 0000000000..8f2dfcaebb --- /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 0000000000..3181cd0896 --- /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 0000000000..9cc23bb941 --- /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 0000000000..f4c8c5652c --- /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 0000000000..5df3354f97 --- /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 0000000000..f81ef0a64a --- /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 0000000000..e01fbf8bb3 --- /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 0000000000..6bca44f9f8 --- /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 0000000000..0d545564b2 --- /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 0000000000..cfebdff5be --- /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 0000000000..490da787ff --- /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 0000000000..30e492756d --- /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 0000000000..49448a05c7 --- /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 0000000000..91edf0541f --- /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 0000000000..7fe4e44534 --- /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 0000000000..8e4cff8600 --- /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 0000000000..44cae78190 --- /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 0000000000..8d052ff6d2 --- /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 0000000000..013f70247f --- /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 0000000000..db71ca59f5 --- /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 0000000000..f920e30d6c --- /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 0000000000..64e00109fe --- /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 0000000000..919dc1ec2a --- /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 0000000000..5ccfbbe916 --- /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 0000000000..c596a2781d --- /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 0000000000..efdaa88006 --- /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 0000000000..fa6732af71 --- /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 0000000000..d871979e67 --- /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 0000000000..efee54fea8 --- /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 0000000000..812b29caaf --- /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 0000000000..a5053d51de --- /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 0000000000..b205bc5fcf --- /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 0000000000..8cd234bf7b --- /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 0000000000..db959d5fc1 --- /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 0000000000..fce1192137 --- /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 0000000000..9636ebc81b --- /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 0000000000..278a2a6311 --- /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 0000000000..e6ae02efbf --- /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 0000000000..4e9488eaa0 --- /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 0000000000..a3dd2c72af --- /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 0000000000..2e7db3c19d --- /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 0000000000..57932eaeb0 --- /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 0000000000..90983df170 --- /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 0000000000..e707131c8f --- /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 0000000000..33a63e05cb --- /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 0000000000..5a81cd674a --- /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 0000000000..6c61741e5e --- /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 0000000000..353b5a763f --- /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 0000000000..ef2b874b26 --- /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 0000000000..de2f938c60 --- /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 0000000000..ade8e6906c --- /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 0000000000..2734d292c9 --- /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 0000000000..459bb057c9 --- /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 0000000000..a4e8933e50 --- /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 0000000000..32a39febc7 --- /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 0000000000..a79ce5a1b4 --- /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 0000000000..760b289ee1 --- /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 0000000000..34cd2714a6 --- /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 0000000000..c37f4ace9d --- /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 0000000000..58b5ea658e --- /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 0000000000..4e62f7fb63 --- /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 0000000000..2e14e4f293 --- /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 0000000000..952eb6cf79 --- /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 0000000000..5b3094167b --- /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 0000000000..70c8112bad --- /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 0000000000..ef77ac592e --- /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 0000000000..5b54b01bcf --- /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 0000000000..828043fe81 --- /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 0000000000..ca3f72fff0 --- /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 0000000000..96cf965ab3 --- /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 0000000000..223ffc4951 --- /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 0000000000..3bc380c31a --- /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 0000000000..1f0fa95e08 --- /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 0000000000..7ce1b57024 --- /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 0000000000..46ad47b113 --- /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 0000000000..99bdc08b36 --- /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 0000000000..17c5741777 --- /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 0000000000..0b4ef0a07f --- /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 0000000000..7e094e1813 --- /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 0000000000..0cfd4d2df4 --- /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 0000000000..0270811548 --- /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 0000000000..5a95720c7f --- /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 0000000000..00a4f4b48f --- /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 0000000000..497fc2a34f --- /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 0000000000..0fc4523171 --- /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 0000000000..84b9d47425 --- /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 89be3cceb2..eb6f15817d 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 f33569704a..6abc10c8e8 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 19ef77e01c..b5ee988cd6 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 136c4119f6..013cf76584 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/681] 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 0000000000..6f8388b24d --- /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 917c156f9f..b73b7467a8 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 57ba2a245f..41bf520cd9 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 f3da6e3e6f..cb993acf0d 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/681] 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 100dfe544d..14d54e6bea 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/681] 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 b73b7467a8..13fbee54e2 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 0000000000..5503be9e64 --- /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 41bf520cd9..61b0a09718 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 cb993acf0d..09bba74930 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/681] 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 61b0a09718..b831a2f154 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 09bba74930..1c4d9a8f3c 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/681] 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 b831a2f154..b9cafa60d7 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/681] 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 14d54e6bea..cf27857d1f 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 b9cafa60d7..986df3d7bf 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/681] 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 986df3d7bf..879b677514 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/681] 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 eb4e4ef584..4f3219dc1b 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/681] 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 0000000000..c5b43dbd3a --- /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/681] [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 bd6a9c920c..7d3f7a9442 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 5483a02de3..89096d90d2 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 5483a02de303d40cb2632d59f3f3a54dabfb5965 +Subproject commit 89096d90d2f0f71be63a4af0152fe3b2aa55cf9d diff --git a/opendbc_repo b/opendbc_repo index 4170d7d876..c2ba07083c 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 4170d7d876a87904dab4b351aa8139ec3d400430 +Subproject commit c2ba07083c1cfbb0c5b86469bd7b87090291a0d3 diff --git a/panda b/panda index 819fa5854e..a2064b86f3 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 8289df5491..2931eb4acd 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 6a7359fd85..88d3939a6a 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/681] 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 ce97bf294d..17d0ec1614 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 0f50acdc38..36e65ad91c 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/681] pin pycapnp (#36160) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1655b565e2..489876f78c 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/681] 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 71b5291dfb..3f9b846e82 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/681] 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 152dbfe456..cc1605a132 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 a788b9a290..5b13bd635a 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 c87db4232b..cf169f4dc6 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 ae7196684d..c6a4b12e63 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 0d5b3d2213..0d20b6487c 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 a87d3323ce..0000000000 --- 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 b7c48f7961..0000000000 --- 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 17985d73b6..0000000000 --- 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 27375793a9..0000000000 --- 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 f8812d6f17..0000000000 --- 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 c8acedae83..0000000000 --- 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 031f10320a..0000000000 --- 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 82fc7d1bd1..0000000000 --- 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 1a57c43007..0000000000 --- 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 f54c365c1c..0000000000 --- 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 54fc91b112..0000000000 --- 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 b8c3841727..0000000000 --- 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 108aa120d9..0000000000 --- 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 72002e6892..0000000000 --- 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 2c2f42e885..0000000000 --- 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 aa372312b4..0000000000 --- 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 6467ef6639..0000000000 --- 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 8bf7c4a0d3..0000000000 --- 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 738746f3fc..0000000000 --- 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 aa41fbba83..0000000000 --- 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 942690da84..0000000000 --- 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 2afacbd1dc..0000000000 --- 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 7c11f362ec..0000000000 --- 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 02c4c961f3..0000000000 --- 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 6ccba34dbc..0000000000 --- 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 9f2069af2d..0000000000 --- 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 7781faab45..0000000000 --- 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 59f788e025..0000000000 --- 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 d578829d44..0000000000 --- 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 abe162a93b..0000000000 --- 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 a4244e9137..0000000000 --- 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 af9666e186..0000000000 --- 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 07fe55f955..0000000000 --- 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 0efde349bb..0000000000 --- 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 19f01b3441..0000000000 --- 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 6d81ffa43e..0000000000 --- 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 1a156aed36..0000000000 --- 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 42a94d1941..0000000000 --- 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 2a47842614..0000000000 --- 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 28a3364eee..0000000000 --- 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 40ecca50e9..0000000000 --- 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 4e1eedcf99..0000000000 --- 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 ce048414e3..0000000000 --- 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 7f3e2654eb..0000000000 --- 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 db8221772a..0000000000 --- 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 c16f03bfd2..0000000000 --- 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 9a55285676..0000000000 --- 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 b8e017531e..0000000000 --- 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 fd462ec6b8..0000000000 --- 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 8d6fdaca6c..0000000000 --- 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 2dfac6f5b2..0000000000 --- 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 02549bd9a9..0000000000 --- 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 8f2dfcaebb..0000000000 --- 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 3181cd0896..0000000000 --- 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 9cc23bb941..0000000000 --- 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 f4c8c5652c..0000000000 --- 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 5df3354f97..0000000000 --- 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 f81ef0a64a..0000000000 --- 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 e01fbf8bb3..0000000000 --- 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 6bca44f9f8..0000000000 --- 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 0d545564b2..0000000000 --- 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 cfebdff5be..0000000000 --- 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 490da787ff..0000000000 --- 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 30e492756d..0000000000 --- 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 49448a05c7..0000000000 --- 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 91edf0541f..0000000000 --- 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 7fe4e44534..0000000000 --- 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 8e4cff8600..0000000000 --- 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 44cae78190..0000000000 --- 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 8d052ff6d2..0000000000 --- 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 013f70247f..0000000000 --- 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 db71ca59f5..0000000000 --- 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 f920e30d6c..0000000000 --- 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 64e00109fe..0000000000 --- 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 919dc1ec2a..0000000000 --- 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 5ccfbbe916..0000000000 --- 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 c596a2781d..0000000000 --- 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 efdaa88006..0000000000 --- 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 fa6732af71..0000000000 --- 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 d871979e67..0000000000 --- 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 efee54fea8..0000000000 --- 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 812b29caaf..0000000000 --- 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 a5053d51de..0000000000 --- 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 b205bc5fcf..0000000000 --- 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 8cd234bf7b..0000000000 --- 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 db959d5fc1..0000000000 --- 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 fce1192137..0000000000 --- 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 9636ebc81b..0000000000 --- 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 278a2a6311..0000000000 --- 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 e6ae02efbf..0000000000 --- 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 4e9488eaa0..0000000000 --- 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 a3dd2c72af..0000000000 --- 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 2e7db3c19d..0000000000 --- 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 57932eaeb0..0000000000 --- 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 90983df170..0000000000 --- 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 e707131c8f..0000000000 --- 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 33a63e05cb..0000000000 --- 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 5a81cd674a..0000000000 --- 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 6c61741e5e..0000000000 --- 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 353b5a763f..0000000000 --- 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 ef2b874b26..0000000000 --- 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 de2f938c60..0000000000 --- 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 ade8e6906c..0000000000 --- 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 2734d292c9..0000000000 --- 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 459bb057c9..0000000000 --- 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 a4e8933e50..0000000000 --- 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 32a39febc7..0000000000 --- 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 a79ce5a1b4..0000000000 --- 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 760b289ee1..0000000000 --- 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 34cd2714a6..0000000000 --- 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 c37f4ace9d..0000000000 --- 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 58b5ea658e..0000000000 --- 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 4e62f7fb63..0000000000 --- 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 2e14e4f293..0000000000 --- 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 952eb6cf79..0000000000 --- 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 5b3094167b..0000000000 --- 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 70c8112bad..0000000000 --- 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 ef77ac592e..0000000000 --- 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 5b54b01bcf..0000000000 --- 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 828043fe81..0000000000 --- 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 ca3f72fff0..0000000000 --- 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 96cf965ab3..0000000000 --- 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 223ffc4951..0000000000 --- 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 3bc380c31a..0000000000 --- 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 1f0fa95e08..0000000000 --- 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 7ce1b57024..0000000000 --- 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 46ad47b113..0000000000 --- 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 99bdc08b36..0000000000 --- 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 17c5741777..0000000000 --- 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 0b4ef0a07f..0000000000 --- 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 7e094e1813..0000000000 --- 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 0cfd4d2df4..0000000000 --- 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 0270811548..0000000000 --- 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 5a95720c7f..0000000000 --- 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 00a4f4b48f..0000000000 --- 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 497fc2a34f..0000000000 --- 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 0fc4523171..0000000000 --- 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 84b9d47425..0000000000 --- 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 eb6f15817d..89be3cceb2 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 6abc10c8e8..f33569704a 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 b5ee988cd6..19ef77e01c 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 013cf76584..136c4119f6 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/681] 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 dbc1919221..f012e8b06b 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 cca2cec7ad..6372b1813d 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 a5dab19789..eb38f20597 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/681] 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 a80a663ec9..f9fad5f6f5 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 5e4fcf5c33..e32c3d6033 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/681] 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 f33569704a..6ab9b30abb 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/681] fix mac build (#36168) --- SConstruct | 1 + 1 file changed, 1 insertion(+) diff --git a/SConstruct b/SConstruct index 5b13bd635a..d8a390f5df 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/681] 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 6ab9b30abb..facc9eb9f6 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 19ef77e01c..430fc95222 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/681] 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 0000000000..2579d30f65 --- /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 430fc95222..0ae0b35359 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/681] 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 facc9eb9f6..97e06bcc6b 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/681] 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 ac231400a4..005655b21d 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 63298914ea..79d05eaf42 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/681] 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 39bc0d5aa4..d03ad5d8c6 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/681] 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 7c0b967abd..f5cf630607 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 7d3f7a9442..22cb0d46c8 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 c2ba07083c..c9e7868cb8 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/681] 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 d4a00115d4..c865cc94a6 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/681] 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 c9e7868cb8..0c8676f74c 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit c9e7868cb830978c2254ef9f012b843bb4f0ddd7 +Subproject commit 0c8676f74cf386ac914faa80a6b14303693ccde9 diff --git a/pyproject.toml b/pyproject.toml index 489876f78c..3d2c2a6f74 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 c6a4b12e63..d63d7d84eb 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 1bca909294..ffa1565cbf 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 fb93e565ff..34abe8ab4b 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 25d2b626cf..0b2bca29d4 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/681] 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 8361befb53..d65fc5b760 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/681] 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 0c8676f74c..c70bd060c6 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 0c8676f74cf386ac914faa80a6b14303693ccde9 +Subproject commit c70bd060c6a410c1083186a1e4165e43a4eda0df diff --git a/pyproject.toml b/pyproject.toml index 3d2c2a6f74..489876f78c 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 d63d7d84eb..c6a4b12e63 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 ffa1565cbf..1bca909294 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 34abe8ab4b..fb93e565ff 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 0b2bca29d4..25d2b626cf 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/681] [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 a2064b86f3..1289337ceb 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit a2064b86f3c9908883033a953503f150cedacbc7 +Subproject commit 1289337ceb6205ad985a5469baa950b319329327 diff --git a/pyproject.toml b/pyproject.toml index 489876f78c..9c8a6148a2 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 6802b45e6e..1a6df4bb9b 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 215056e6e6..5aeb035bdc 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 e08fc30c2e..006eeef6f5 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 965ea59b16..73c8dae60d 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 965ea59b16679793b8f48368ac24c4a0ef587e71 +Subproject commit 73c8dae60d0eaa3a27a252f7967d2dab8378109e diff --git a/uv.lock b/uv.lock index 25d2b626cf..c24d9dbbbe 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/681] 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 f9fad5f6f5..a80a663ec9 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 e32c3d6033..5e4fcf5c33 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/681] 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 a80a663ec9..a4a1f8f34f 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 5e4fcf5c33..e32c3d6033 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/681] [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 c70bd060c6..6894a012cd 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit c70bd060c6a410c1083186a1e4165e43a4eda0df +Subproject commit 6894a012cd7ec36678b8abd3d95dc79975d5dbe2 diff --git a/tinygrad_repo b/tinygrad_repo index 73c8dae60d..a6fd96f620 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 73c8dae60d0eaa3a27a252f7967d2dab8378109e +Subproject commit a6fd96f62050efd4a2fe7c885d1a11f87e3c5b0a diff --git a/uv.lock b/uv.lock index c24d9dbbbe..9c791b8110 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/681] 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 f3a63d3dec..ad8e85136b 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 e32c3d6033..9c5a8348f9 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/681] 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 f5cf630607..966d5d3809 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 22cb0d46c8..3ea12f651e 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 6894a012cd..2eec1af104 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/681] 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 3140f419f5..8f33124b5c 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/681] 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 0429799a2e..e454afb691 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/681] 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 a8d81a8ba2..e4ff4133aa 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/681] cleanup dead build flags --- SConstruct | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/SConstruct b/SConstruct index d8a390f5df..6f035e3045 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/681] 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 a731a9158c..d43382f199 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 cdbfa4c04c..4d47a9878c 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 3f433e1fcb..5b35f7ac9d 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 07b5d42f4b..ffa2ff4db9 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 af9ae943ea..15aeb94ede 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 eb38f20597..1e3f28eb8e 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 25843d1ce8..70f06f6b9d 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 e4ff4133aa..648ce47a6f 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 3e6317a49c..7a7215b48e 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 eba5ef9e58..968afda9c8 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/681] 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 15aeb94ede..3ce5c839e8 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/681] 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 6f035e3045..80273db106 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 3cdb6fc5a2..0891b79039 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 1a6df4bb9b..f20855c2cb 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 8d3d7cf72e..cdd817ae2f 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 6f16e00957..b9783477eb 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 81ef613393..5f533cd090 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 29aa8493c7..598603a08a 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 734f748a2a..e288c6d8b0 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 8c4602bb31..85f358977c 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 d2083a5985..a9058401ce 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 3237d13074..9d4b81a3f9 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/681] 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 cc55fefd25..0000000000 --- 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 cfc34e5848..631ea04840 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 c594fb53d3..0832b3964e 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/681] 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 3ce5c839e8..e24686566d 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 7a7215b48e..119901da40 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/681] 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 df8bba030c..14847df102 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 be2132c1b7..30ad0f763a 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 bb2b431e03..0230e16a4f 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 79d05eaf42..55d53125d8 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 35a7c4101c..bf6d113f62 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 5b35f7ac9d..911891f10b 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 f7304bf6a6..757500a71c 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/681] 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 e8817f24b4..c529694df4 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 932773755d..89bdb48ac9 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 c4a2c0ca11..13532620cb 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/681] 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 89bdb48ac9..d84e16cb54 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/681] 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 9ea6fe3070..8a7105063d 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 777d2f4c3f..0a8fadea90 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 d84e16cb54..4c036873d0 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 13532620cb..39a65a9191 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 911891f10b..481fd98045 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 119901da40..986d7158de 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/681] 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 8a7105063d..9ea6fe3070 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 0a8fadea90..777d2f4c3f 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 4c036873d0..d84e16cb54 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 39a65a9191..13532620cb 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 481fd98045..911891f10b 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 986d7158de..119901da40 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/681] 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 911891f10b..132d78e5a2 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/681] 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 9ea6fe3070..8a7105063d 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 777d2f4c3f..0a8fadea90 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 d84e16cb54..4c036873d0 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 13532620cb..39a65a9191 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 132d78e5a2..481fd98045 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 119901da40..986d7158de 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/681] 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 0a8fadea90..ffb45f821d 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 f012e8b06b..0a4c45163b 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 27f5763077..175233c5ba 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 536d993389..c813d852bc 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 6372b1813d..0157dd3ef4 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/681] 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 f102481c3a..9ee533d2a4 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 c8b4e62030..6c5508ce7d 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 f54bd9116c..39fef3182c 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/681] 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 0000000000..11e745a96a --- /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 beb426c669..57b1158be2 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 9ee533d2a4..320e7477f1 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 0000000000..7fb22e4484 --- /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/681] 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 9ec7a59223..b5c3b21dc0 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/681] 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 11e745a96a..ff9655d155 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 b5c3b21dc0..9ec7a59223 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/681] 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/681] 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 39fef3182c..da0ea287cf 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/681] [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 2eec1af104..aa3d32a63b 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 2eec1af104972b7784644bf38c4c5afb52fc070a +Subproject commit aa3d32a63b7d05e69777fa90147192220abde8a3 diff --git a/panda b/panda index 1289337ceb..48c776a3df 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/681] 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 0230e16a4f..5fb6fd5737 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/681] 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 481fd98045..ed2a20dd11 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 247b7a5492..d68ea58990 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/681] 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 ffb45f821d..97f9d6b222 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 0000000000..ea6fedebf7 --- /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 14847df102..b34042d43e 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/681] bump panda (#36226) bump --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 48c776a3df..615009cf0f 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/681] 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 8a7105063d..9ea6fe3070 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 97f9d6b222..6628ab4d60 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 4c036873d0..d84e16cb54 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 39a65a9191..13532620cb 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 ed2a20dd11..4973efa063 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 986d7158de..119901da40 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/681] 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 6628ab4d60..1f56122241 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/681] =?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 b98c1f5242..019fbbe10b 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 5aeb035bdc..2851a3e7da 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 255f28d80e..04b69c61c3 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 121871ef2b..0000000000 --- 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 dcc727510e..5ae91f67a3 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 a9cb21a3f1..c2e5bc3fe4 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 2a20b20dc1..1cc7101880 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/681] 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 d84e16cb54..a770c0f92f 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 13532620cb..bb3abcddb6 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 4973efa063..a16f0b0bda 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 119901da40..03385609d6 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/681] 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 b3db1fa5f0..7e1d3b61ba 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 da0ea287cf..6ca0ca2c40 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 e2296fd5ed..5dacae8210 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 a985e783be..e0d737cb1c 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 61ac043b72..3db930eb26 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 d68ea58990..bb7eeb7c5c 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 03385609d6..3dd2a4d654 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 8f33124b5c..cbab024f09 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 757500a71c..c76f30d196 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/681] 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 1f56122241..a2401ef8be 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 58afcec5ef..1e0e7bfd53 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 4d47a9878c..5987d062ff 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 bb3abcddb6..9c45519b4b 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/681] 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 4f4a8dcdff..2fc13d4c96 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 1e3f28eb8e..32d63952a2 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 3dd2a4d654..9eaac6030b 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/681] 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 b34042d43e..892853ca3b 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 4361725a1b..0349070010 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 2fc13d4c96..5611be8f70 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 1021b5452b..b1dc54bf7a 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/681] 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 892853ca3b..cb705d46f8 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 5611be8f70..370141bd64 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 3db930eb26..707b30983b 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 b3cdc82cf5..48903fa5bd 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 32d63952a2..141f682db0 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 bb7eeb7c5c..b870227854 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 648ce47a6f..f9cdf7f2b4 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 cbab024f09..604cd59fd0 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/681] 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 9eaac6030b..5dfdff9b96 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/681] 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 5dacae8210..d0d59df1ff 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/681] 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 5dfdff9b96..35ceef1ff4 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/681] 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 0349070010..7440512a62 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 22f159e891..55e2812efd 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 f9cdf7f2b4..c9ccff8210 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/681] 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 55e2812efd..22f159e891 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/681] 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 d85be77121..14c1e64774 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 44d8d95e95..e3bb03de07 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/681] 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 9c8a6148a2..a0d135db65 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 55d53125d8..7676635e18 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 a16f0b0bda..6c703a516e 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 c9ccff8210..7877325492 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 35ceef1ff4..8a7bd9f512 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/681] 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 7fb22e4484..1ac5083fc6 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 6ca0ca2c40..7ad96bc9a5 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/681] 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 320e7477f1..c33b16fac4 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 1ac5083fc6..81eef60d69 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 7ad96bc9a5..586d90dc4b 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/681] 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 586d90dc4b..8ff8f27bed 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/681] 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 a0d135db65..9a8de2b746 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 c33b16fac4..39b39d33d9 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 8ff8f27bed..e26b5d313e 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 7676635e18..64e1b701f1 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/681] 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 cb705d46f8..c41c8b0a26 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 e26b5d313e..444688b7a0 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 b870227854..db30833f19 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/681] 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 14c1e64774..106a06e3a2 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 e3bb03de07..44d8d95e95 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/681] 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 b1dc54bf7a..606b04b6e9 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/681] 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 966d5d3809..568be6c353 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/681] 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 81eef60d69..acb317cbaa 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/681] 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 acb317cbaa..d7e08112b2 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/681] 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 d7e08112b2..f570a701ba 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/681] 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 0000000000..1e88ce57d3 --- /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/681] 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 f570a701ba..36d4e1f504 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/681] 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 36d4e1f504..ecd1302589 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/681] 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 ff9655d155..3986c8592f 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 9ec7a59223..79ab373c2e 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/681] 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 9c791b8110..3246221299 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/681] 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 3986c8592f..ff9655d155 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 79ab373c2e..9ec7a59223 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/681] 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 db30833f19..b032df4d9f 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/681] 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 35dc5ac401..f6caa3c5f0 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 b032df4d9f..f7dc9e9344 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/681] 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 ecd1302589..e77a2d4d7e 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/681] 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 7440512a62..7aebc609f8 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 444688b7a0..1045971636 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 6c703a516e..a42b1d1129 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 f7dc9e9344..2c3eeb3793 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 7877325492..056aa2d90b 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/681] 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 57b1158be2..35ced1e38b 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/681] 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 056aa2d90b..509c49be35 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/681] 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 39b39d33d9..f7f57c680d 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/681] 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 e24686566d..5594742cb3 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 8a7bd9f512..85b98f10ac 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/681] 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 c529694df4..e81129b137 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/681] 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 36055d8635..d97aa8b66b 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 22f159e891..55e2812efd 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/681] 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 6c5508ce7d..a3bd2e23e0 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 a42b1d1129..473eaa5fa8 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/681] 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 5987d062ff..34354ecbab 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 9618768957..6fe7b6843c 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 473eaa5fa8..755a335e4d 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 c172f94251..fcb7b25ccd 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 2c3eeb3793..91a1ccbbc4 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/681] 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 bf6d113f62..e4e44b7d04 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/681] 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 d97aa8b66b..36055d8635 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 55e2812efd..22f159e891 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/681] 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 544feb1c59..f786213da7 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/681] 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 ba9fa8b7a6..2b57be7227 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/681] 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 c41c8b0a26..62abd2b998 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/681] 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 2b57be7227..7ede8c394a 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/681] 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 a770c0f92f..3877da81c5 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 d03ad5d8c6..44c26c80e5 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/681] 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 34354ecbab..5987d062ff 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 6fe7b6843c..9618768957 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 755a335e4d..473eaa5fa8 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 fcb7b25ccd..c172f94251 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 91a1ccbbc4..2c3eeb3793 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/681] 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 e28fa3021c..bf6dd04f60 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 2a8b873e2e..7cd04240d0 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/681] 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 c40443d7e7..24d2faa0db 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 029d16e59e..4442e6cbe4 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 7cd04240d0..a97ed3e54e 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 ac35151487..cea9f74814 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 00a083509f..d8eae451ed 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 5a2814e089..bcaf934586 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 0ce06dc996..c564e9d3e5 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/681] 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 5987d062ff..8fa38145d3 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 e81129b137..2802363f81 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/681] 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 4442e6cbe4..9e31ac1526 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 a97ed3e54e..00cf0dab91 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 cea9f74814..808c9a659a 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 d8eae451ed..4cc1c47f61 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 bcaf934586..e50d91351d 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 c564e9d3e5..354c7f00ad 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 a833fadb94..f55bcc3787 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/681] 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 967caec867..8e3a74d2d9 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 1bca909294..50feab4f4a 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 113fa5e7e6..e31fda3728 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 fb93e565ff..c19f1bf9dd 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/681] 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 e50d91351d..4878bde3af 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/681] 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 00cf0dab91..d69796738f 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/681] 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 99142280ca..ff5084781f 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/681] bump opendbc (#36289) --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index aa3d32a63b..dfa6807ebf 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/681] =?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 cb5a6b3a47..89444c3ea7 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 4b4fa05df8..0411ddb744 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/681] 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 4878bde3af..2a1b666efd 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 f55bcc3787..538aa34f2a 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/681] 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 2c3eeb3793..8d713ee7ea 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/681] 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 1e0e7bfd53..01f663d4b9 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 9c45519b4b..be83517980 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 509c49be35..bbf66c6555 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 968afda9c8..0fbf3c844a 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/681] 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 cfef0f84d1..143bb2c262 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 01f663d4b9..fddcf32fbd 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 e77a2d4d7e..fb42b94d6d 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/681] 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 606b04b6e9..e9d0cb53bd 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/681] 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 a5cf1731dc..85ce4a858b 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 e0d737cb1c..4068af01e7 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 48903fa5bd..4184c5cb12 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/681] 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 4184c5cb12..38a93687e0 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/681] 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 143bb2c262..32d278ee9b 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 fddcf32fbd..8fdbd6f9a6 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 be83517980..f4b9e1a9be 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 e9d0cb53bd..789ae53f3d 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 8d713ee7ea..938867f748 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/681] 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 32d278ee9b..d3a829df81 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 62abd2b998..758200c0ad 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 789ae53f3d..6d3e143b53 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 bbf66c6555..d203ec8139 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/681] 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 a25d9bd316..69ed5f458f 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 17e25f78b4df38dcf68d5661b33b07b0e0af6829 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 10 Oct 2025 17:00:44 -0400 Subject: [PATCH 142/681] ui: dynamic ICBM status (#1357) * ui: dynamic ICBM status * straight up pls --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 18 ++++++++++++++++-- selfdrive/ui/sunnypilot/qt/onroad/hud.h | 4 ++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index 251140ad17..9ce32002b8 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -127,6 +127,9 @@ void HudRendererSP::updateState(const UIState &s) { leftBlindspot = car_state.getLeftBlindspot(); rightBlindspot = car_state.getRightBlindspot(); showTurnSignals = s.scene.turn_signals; + + carControlEnabled = car_control.getEnabled(); + speedCluster = car_state.getCruiseState().getSpeedCluster() * speedConv; } void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) { @@ -685,10 +688,21 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) { } } - // Draw "MAX" text + // Draw "MAX" or carState.cruiseState.speedCluster (when ICBM is active) text + if (carControlEnabled) { + if (std::nearbyint(set_speed) != std::nearbyint(speedCluster)) { + icbm_active_counter = 3 * UI_FREQ; + } else if (icbm_active_counter > 0) { + icbm_active_counter--; + } + } else { + icbm_active_counter = 0; + } + QString max_str = (icbm_active_counter != 0) ? QString::number(std::nearbyint(speedCluster)) : tr("MAX"); + 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")); + p.drawText(set_speed_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, max_str); // Draw set speed QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(set_speed)) : "–"; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.h b/selfdrive/ui/sunnypilot/qt/onroad/hud.h index c50e2d641d..4a3f90827d 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.h @@ -115,4 +115,8 @@ private: bool rightBlindspot; int blinkerFrameCounter; bool showTurnSignals; + + bool carControlEnabled; + float speedCluster = 0; + int icbm_active_counter = 0; }; From fdcf8b592e5596386c5639875ce260b19267cc2f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 10 Oct 2025 21:07:36 -0700 Subject: [PATCH 143/681] 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 f7f57c680d..9243d8ea1f 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 d3a829df81..bc8fc953c4 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 758200c0ad..286ba36f07 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 7e1d3b61ba..353a9d976f 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 7aebc609f8..a337b9034d 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 8fdbd6f9a6..c75cc7574d 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 1045971636..49edafa0f1 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 d0d59df1ff..f0031138fe 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 d203ec8139..b1c4b44df1 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 c76f30d196..f19a6fbfdb 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 fe6edda23a87f478ec30e3b4cc876c9a625835e4 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 11 Oct 2025 01:52:33 -0400 Subject: [PATCH 144/681] Revert "UI: Make Always Offroad more accessible (#1327)" (#1358) Reverts #1327 --- .../qt/offroad/settings/device_panel.cc | 17 +++++------------ .../qt/offroad/settings/device_panel.h | 2 -- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc index 43e6067451..0abbb5ba34 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc @@ -83,7 +83,7 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { connect(maxTimeOffroad, &OptionControlSP::updateLabels, maxTimeOffroad, &MaxTimeOffroad::refresh); addItem(maxTimeOffroad); - toggleDeviceBootMode = new ButtonParamControlSP("DeviceBootMode", tr("Wake-Up Behavior"), "", "", {"Default", "Offroad"}, 375, true); + toggleDeviceBootMode = new ButtonParamControlSP("DeviceBootMode", tr("Wake-Up Behavior"), "", "", {"Default", "Offroad"}, 375, true); addItem(toggleDeviceBootMode); connect(toggleDeviceBootMode, &ButtonParamControlSP::buttonClicked, this, [=](int index) { @@ -116,8 +116,9 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { offroadBtn->setFixedWidth(power_layout->sizeHint().width()); QObject::connect(offroadBtn, &PushButtonSP::clicked, this, &DevicePanelSP::setOffroadMode); - power_group_layout = new QVBoxLayout(); + QVBoxLayout *power_group_layout = new QVBoxLayout(); power_group_layout->setSpacing(25); + power_group_layout->addWidget(offroadBtn, 0, Qt::AlignHCenter); power_group_layout->addLayout(power_layout); addItem(power_group_layout); @@ -130,7 +131,7 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { buttons["onroadUploadsBtn"], }; - QObject::connect(uiState(), &UIState::offroadTransition, [=](bool _offroad) { + QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { for (auto btn : findChildren()) { bool always_enabled = std::find(always_enabled_btns.begin(), always_enabled_btns.end(), btn) != always_enabled_btns.end(); @@ -138,8 +139,6 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { btn->setEnabled(offroad); } } - offroad = _offroad; - updateState(); }); } @@ -190,7 +189,7 @@ void DevicePanelSP::updateState() { } bool offroad_mode_param = params.getBool("OffroadMode"); - offroadBtn->setText(offroad_mode_param ? tr("Exit Always Offroad") : tr("Enable Always Offroad")); + offroadBtn->setText(offroad_mode_param ? tr("Exit Always Offroad") : tr("Always Offroad")); offroadBtn->setStyleSheet(offroad_mode_param ? alwaysOffroadStyle : autoOffroadStyle); DeviceSleepModeStatus currStatus = DeviceSleepModeStatus::DEFAULT; @@ -198,10 +197,4 @@ void DevicePanelSP::updateState() { currStatus = DeviceSleepModeStatus::OFFROAD; } toggleDeviceBootMode->setDescription(deviceSleepModeDescription(currStatus)); - - if (offroad and not offroad_mode_param) { - power_group_layout->insertWidget(0, offroadBtn, 0, Qt::AlignHCenter); - } else { - AddWidgetAt(0, offroadBtn); - } } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h index ce4acfc7cf..f6d21699ff 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h @@ -31,8 +31,6 @@ private: PushButtonSP *offroadBtn; MaxTimeOffroad *maxTimeOffroad; ButtonParamControlSP *toggleDeviceBootMode; - QVBoxLayout *power_group_layout; - bool offroad; const QString alwaysOffroadStyle = R"( PushButtonSP { From 974a7a3f7d95253f75ea915fa393000927121eb1 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Fri, 10 Oct 2025 23:47:45 -0700 Subject: [PATCH 145/681] ui: improve toggle states in Developer panel (#1352) ui: quickboot toggle race condition Co-authored-by: Jason Wen --- .../ui/sunnypilot/qt/offroad/settings/developer_panel.cc | 7 +++---- .../ui/sunnypilot/qt/offroad/settings/developer_panel.h | 3 --- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc index 9c36097f1b..021f5b3776 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc @@ -56,14 +56,13 @@ DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(pare addItem(errorLogBtn); QObject::connect(uiState(), &UIState::offroadTransition, this, &DeveloperPanelSP::updateToggles); - - is_release = params.getBool("IsReleaseBranch"); - is_tested = params.getBool("IsTestedBranch"); - is_development = params.getBool("IsDevelopmentBranch"); } void DeveloperPanelSP::updateToggles(bool offroad) { bool disable_updates = params.getBool("DisableUpdates"); + bool is_release = params.getBool("IsReleaseBranch"); + bool is_tested = params.getBool("IsTestedBranch"); + bool is_development = params.getBool("IsDevelopmentBranch"); prebuiltToggle->setVisible(!is_release && !is_tested && !is_development); prebuiltToggle->setEnabled(disable_updates); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h index 7f67512b5d..ed64964a56 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h @@ -22,9 +22,6 @@ private: ParamControlSP *prebuiltToggle; Params params; ParamControlSP *showAdvancedControls; - bool is_development; - bool is_release; - bool is_tested; private slots: void updateToggles(bool offroad); From b6dbb0fd8d79915f6f67d70500d5c971f72f33df Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 11 Oct 2025 03:29:24 -0700 Subject: [PATCH 146/681] 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 9243d8ea1f..70960ee22f 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 a2401ef8be..702854f98a 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 353a9d976f..e8eaaa44f2 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 8fa38145d3..c499d35d4d 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 9618768957..40a649899d 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 49edafa0f1..0b3ca8176f 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 64e1b701f1..252a7dd94d 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 6b601f6dff..1e31ac8a1a 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 e4e44b7d04..dfaa3e4008 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 370141bd64..dc3c5a4a76 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 473eaa5fa8..755a335e4d 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 f0031138fe..a5b9fc70d3 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 c172f94251..fcb7b25ccd 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 938867f748..7ca62409d8 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 239d63037e..f53e3f0ebb 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 2da9a9f8df..33c4ef29d3 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 147/681] 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 bc8fc953c4..ec4d1fe989 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 c75cc7574d..01195142e3 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 6d3e143b53..274307395c 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 7ca62409d8..f90f78fe1b 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 148/681] 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 c499d35d4d..4ba7f2aa13 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 f4b9e1a9be..3ac9dbea17 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 149/681] 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 9a8de2b746..7248caa4f5 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 3246221299..476945120c 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 150/681] 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 755a335e4d..d3bc3d494c 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 151/681] 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 286ba36f07..35021492e0 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 a337b9034d..0c17a54fbe 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 152/681] add earcut python package --- pyproject.toml | 1 + uv.lock | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 7248caa4f5..78bf8c22ee 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 476945120c..c34a6d9b71 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 153/681] 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 ec4d1fe989..fac00b60a2 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 35021492e0..66341db37b 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 b1c4b44df1..aaa67472dd 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 154/681] 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 85b98f10ac..a50881c8cb 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 155/681] 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 ca031e2f17..c8ee9b140c 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 156/681] 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 c813d852bc..98c3d686fa 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 157/681] 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 2802363f81..02b015315b 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 158/681] 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 0b49e69116..43bf7d0476 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 b4b9b9dbbe..9fc76489b8 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 0a4c45163b..cac2f9c968 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 3f23301972..69ba946dcd 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 fb42b94d6d..bd40b89624 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 b6153ee8a4..c88befd454 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 22f159e891..b8a1e09889 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 1e5758e7126b528741ce1c9d98b36065590bc8e5 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sun, 12 Oct 2025 00:21:38 -0400 Subject: [PATCH 159/681] ui: Better E2E Alert UI Positioning (#1355) Better E2E Alert UI Positioning --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index 9ce32002b8..c00be966a3 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -714,9 +714,8 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) { void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const QString &alert_alt_text) { int size = devUiInfo > 0 ? e2e_alert_small : e2e_alert_large; int x = surface_rect.center().x() + surface_rect.width() / 4; - int y = surface_rect.center().y() + 40; + int y = surface_rect.center().y() + 20; x += devUiInfo > 0 ? 0 : 50; - y += devUiInfo > 0 ? 0 : 80; QRect alertRect(x - size, y - size, size * 2, size * 2); // Alert Circle From b89393a5a214a24fe45f49630029749598996562 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sun, 12 Oct 2025 00:34:15 -0400 Subject: [PATCH 160/681] UI: Blinker Size & State Fix (#1363) Blinker Size & State Fix Co-authored-by: Jason Wen --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 22 ++++++++++++++++++---- selfdrive/ui/sunnypilot/qt/onroad/hud.h | 1 + 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index c00be966a3..718bfb514b 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -776,19 +776,33 @@ void HudRendererSP::drawCurrentSpeedSP(QPainter &p, const QRect &surface_rect) { } void HudRendererSP::drawBlinker(QPainter &p, const QRect &surface_rect) { + const bool hazard = leftBlinkerOn && rightBlinkerOn; + int blinkerStatus = hazard ? 2 : (leftBlinkerOn or rightBlinkerOn) ? 1 : 0; + if (!leftBlinkerOn && !rightBlinkerOn) { blinkerFrameCounter = 0; + lastBlinkerStatus = 0; return; } + + if (blinkerStatus != lastBlinkerStatus) { + blinkerFrameCounter = 0; + lastBlinkerStatus = blinkerStatus; + } + ++blinkerFrameCounter; - const int circleRadius = 44; - const int arrowLength = 44; - const int x_gap = 180; + const int BLINKER_COOLDOWN_FRAMES = UI_FREQ / 10; + if (blinkerFrameCounter < BLINKER_COOLDOWN_FRAMES) { + return; + } + + const int circleRadius = 60; + const int arrowLength = 60; + const int x_gap = 160; const int y_offset = 272; const int centerX = surface_rect.center().x(); - const bool hazard = leftBlinkerOn && rightBlinkerOn; const QPen bgBorder(Qt::white, 5); const QPen arrowPen(Qt::NoPen); diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.h b/selfdrive/ui/sunnypilot/qt/onroad/hud.h index 4a3f90827d..554c9c5d0d 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.h @@ -114,6 +114,7 @@ private: bool leftBlindspot; bool rightBlindspot; int blinkerFrameCounter; + int lastBlinkerStatus; bool showTurnSignals; bool carControlEnabled; From 6f42bbab18ae9a35a5ace15a67f995b282c1c333 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sun, 12 Oct 2025 01:59:03 -0400 Subject: [PATCH 161/681] Reapply "UI: Make Always Offroad more accessible" (#1327) (#1361) * UI: Make Always Offroad more accessible * conditional - based on offroad * no need to delete * account for always offroad * fix offroad transition * do this inside updateState on every invoke --------- Co-authored-by: Jason Wen --- .../qt/offroad/settings/device_panel.cc | 43 ++++++++++--------- .../qt/offroad/settings/device_panel.h | 6 ++- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc index 0abbb5ba34..8295789f0a 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc @@ -83,12 +83,12 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { connect(maxTimeOffroad, &OptionControlSP::updateLabels, maxTimeOffroad, &MaxTimeOffroad::refresh); addItem(maxTimeOffroad); - toggleDeviceBootMode = new ButtonParamControlSP("DeviceBootMode", tr("Wake-Up Behavior"), "", "", {"Default", "Offroad"}, 375, true); + toggleDeviceBootMode = new ButtonParamControlSP("DeviceBootMode", tr("Wake-Up Behavior"), "", "", {"Default", "Offroad"}, 375, true); addItem(toggleDeviceBootMode); connect(toggleDeviceBootMode, &ButtonParamControlSP::buttonClicked, this, [=](int index) { params.put("DeviceBootMode", QString::number(index).toStdString()); - updateState(); + updateState(offroad); }); addItem(device_grid_layout); @@ -116,14 +116,13 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { offroadBtn->setFixedWidth(power_layout->sizeHint().width()); QObject::connect(offroadBtn, &PushButtonSP::clicked, this, &DevicePanelSP::setOffroadMode); - QVBoxLayout *power_group_layout = new QVBoxLayout(); + power_group_layout = new QVBoxLayout(); power_group_layout->setSpacing(25); - power_group_layout->addWidget(offroadBtn, 0, Qt::AlignHCenter); power_group_layout->addLayout(power_layout); addItem(power_group_layout); - std::vector always_enabled_btns = { + always_enabled_btns = { rebootBtn, poweroffBtn, offroadBtn, @@ -131,15 +130,7 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { buttons["onroadUploadsBtn"], }; - QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { - for (auto btn : findChildren()) { - bool always_enabled = std::find(always_enabled_btns.begin(), always_enabled_btns.end(), btn) != always_enabled_btns.end(); - - if (!always_enabled) { - btn->setEnabled(offroad); - } - } - }); + QObject::connect(uiState(), &UIState::offroadTransition, this, &DevicePanelSP::updateState); } void DevicePanelSP::setOffroadMode() { @@ -163,7 +154,7 @@ void DevicePanelSP::setOffroadMode() { ConfirmationDialog::alert(tr("Disengage to Enter Always Offroad Mode"), this); } - updateState(); + updateState(offroad); } void DevicePanelSP::resetSettings() { @@ -180,16 +171,20 @@ void DevicePanelSP::resetSettings() { } void DevicePanelSP::showEvent(QShowEvent *event) { - updateState(); + updateState(offroad); } -void DevicePanelSP::updateState() { - if (!isVisible()) { - return; +void DevicePanelSP::updateState(bool _offroad) { + for (auto btn : findChildren()) { + bool always_enabled = std::find(always_enabled_btns.begin(), always_enabled_btns.end(), btn) != always_enabled_btns.end(); + + if (!always_enabled) { + btn->setEnabled(_offroad); + } } bool offroad_mode_param = params.getBool("OffroadMode"); - offroadBtn->setText(offroad_mode_param ? tr("Exit Always Offroad") : tr("Always Offroad")); + offroadBtn->setText(offroad_mode_param ? tr("Exit Always Offroad") : tr("Enable Always Offroad")); offroadBtn->setStyleSheet(offroad_mode_param ? alwaysOffroadStyle : autoOffroadStyle); DeviceSleepModeStatus currStatus = DeviceSleepModeStatus::DEFAULT; @@ -197,4 +192,12 @@ void DevicePanelSP::updateState() { currStatus = DeviceSleepModeStatus::OFFROAD; } toggleDeviceBootMode->setDescription(deviceSleepModeDescription(currStatus)); + + if (offroad and not offroad_mode_param) { + power_group_layout->insertWidget(0, offroadBtn, 0, Qt::AlignHCenter); + } else { + AddWidgetAt(0, offroadBtn); + } + + offroad = _offroad; } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h index f6d21699ff..425c4528a1 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h @@ -23,7 +23,7 @@ public: explicit DevicePanelSP(SettingsWindowSP *parent = 0); void showEvent(QShowEvent *event) override; void setOffroadMode(); - void updateState(); + void updateState(bool _offroad); void resetSettings(); private: @@ -31,6 +31,10 @@ private: PushButtonSP *offroadBtn; MaxTimeOffroad *maxTimeOffroad; ButtonParamControlSP *toggleDeviceBootMode; + QVBoxLayout *power_group_layout; + bool offroad; + + std::vector always_enabled_btns = {}; const QString alwaysOffroadStyle = R"( PushButtonSP { From 49d9b8bb001a4739cfd91d7b1039e7d8cd5bb8f2 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Sat, 11 Oct 2025 23:06:06 -0700 Subject: [PATCH 162/681] 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 3ac9dbea17..dab01f9245 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 d3bc3d494c..f0bcdcdc6d 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 163/681] 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 fac00b60a2..21eb44efe7 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 164/681] 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 c8ee9b140c..744fdbf135 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 165/681] 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 02b015315b..9754d488ed 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 166/681] 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 4068af01e7..99a3569fbe 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 5b29fd0f2ce9e52c40f303054990b18a0d276b19 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sun, 12 Oct 2025 12:12:52 -0400 Subject: [PATCH 167/681] UI: Onroad Screen Timeout Fixes (#1364) * Screen Timeout Fixes * rename * auto type --------- Co-authored-by: Jason Wen --- selfdrive/ui/qt/window.cc | 10 ++++++++++ selfdrive/ui/qt/window.h | 4 ++++ .../ui/sunnypilot/qt/onroad/annotated_camera.cc | 6 ++++++ selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h | 1 + selfdrive/ui/sunnypilot/qt/onroad/onroad_home.cc | 2 -- selfdrive/ui/sunnypilot/ui.cc | 12 +++++++----- selfdrive/ui/sunnypilot/ui.h | 8 +++++++- 7 files changed, 35 insertions(+), 8 deletions(-) diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 36f12c266b..d0806e4379 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -92,6 +92,16 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) { // ignore events when device is awakened by resetInteractiveTimeout ignore = !device()->isAwake(); device()->resetInteractiveTimeout(); + +#ifdef SUNNYPILOT + auto *s_sp = uiStateSP(); + bool onroadScreenControl = s_sp->scene.onroadScreenOffControl; + bool started = s_sp->scene.started; + bool timerExpired = (s_sp->scene.onroadScreenOffTimer == 0); + ignore |= (onroadScreenControl and started and timerExpired); + s_sp->reset_onroad_sleep_timer(); +#endif + break; } default: diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h index 8a118b0bb6..63293d4177 100644 --- a/selfdrive/ui/qt/window.h +++ b/selfdrive/ui/qt/window.h @@ -7,6 +7,10 @@ #include "selfdrive/ui/qt/offroad/onboarding.h" #include "selfdrive/ui/qt/offroad/settings.h" +#ifdef SUNNYPILOT +#include "selfdrive/ui/sunnypilot/ui.h" +#endif + class MainWindow : public QWidget { Q_OBJECT diff --git a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc index 1d5567161a..f9b62b0ae7 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.cc @@ -18,4 +18,10 @@ void AnnotatedCameraWidgetSP::updateState(const UIState &s) { void AnnotatedCameraWidgetSP::showEvent(QShowEvent *event) { AnnotatedCameraWidget::showEvent(event); ui_update_params_sp(uiState()); + uiStateSP()->reset_onroad_sleep_timer(OnroadTimerStatusToggle::RESUME); +} + +void AnnotatedCameraWidgetSP::hideEvent(QHideEvent *event) { + AnnotatedCameraWidget::hideEvent(event); + uiStateSP()->reset_onroad_sleep_timer(OnroadTimerStatusToggle::PAUSE); } diff --git a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h index 8c0a385657..2e7609dae0 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/annotated_camera.h @@ -18,4 +18,5 @@ public: protected: void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent* event) override; }; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.cc b/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.cc index 3f853e3363..b26d1b828a 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/onroad_home.cc @@ -25,10 +25,8 @@ void OnroadWindowSP::updateState(const UIStateSP &s) { void OnroadWindowSP::mousePressEvent(QMouseEvent *e) { OnroadWindow::mousePressEvent(e); - uiStateSP()->reset_onroad_sleep_timer(); } void OnroadWindowSP::offroadTransition(bool offroad) { OnroadWindow::offroadTransition(offroad); - uiStateSP()->reset_onroad_sleep_timer(); } diff --git a/selfdrive/ui/sunnypilot/ui.cc b/selfdrive/ui/sunnypilot/ui.cc index c021e0e95b..2244e23199 100644 --- a/selfdrive/ui/sunnypilot/ui.cc +++ b/selfdrive/ui/sunnypilot/ui.cc @@ -71,17 +71,19 @@ void ui_update_params_sp(UIStateSP *s) { s->scene.onroadScreenOffBrightness = std::atoi(params.get("OnroadScreenOffBrightness").c_str()); s->scene.onroadScreenOffControl = params.getBool("OnroadScreenOffControl"); s->scene.onroadScreenOffTimerParam = std::atoi(params.get("OnroadScreenOffTimer").c_str()); - s->reset_onroad_sleep_timer(); s->scene.turn_signals = params.getBool("ShowTurnSignals"); } -void UIStateSP::reset_onroad_sleep_timer() { - if (scene.onroadScreenOffTimerParam >= 0 and scene.onroadScreenOffControl) { - scene.onroadScreenOffTimer = scene.onroadScreenOffTimerParam * UI_FREQ; - } else { +void UIStateSP::reset_onroad_sleep_timer(OnroadTimerStatusToggle toggleTimerStatus) { + // Toggling from active state to inactive + if (toggleTimerStatus == OnroadTimerStatusToggle::PAUSE and scene.onroadScreenOffTimer != -1) { scene.onroadScreenOffTimer = -1; } + // Toggling from a previously inactive state or resetting an active timer + else if ((scene.onroadScreenOffTimerParam >= 0 and scene.onroadScreenOffControl and scene.onroadScreenOffTimer != -1) or toggleTimerStatus == OnroadTimerStatusToggle::RESUME) { + scene.onroadScreenOffTimer = scene.onroadScreenOffTimerParam * UI_FREQ; + } } DeviceSP::DeviceSP(QObject *parent) : Device(parent) { diff --git a/selfdrive/ui/sunnypilot/ui.h b/selfdrive/ui/sunnypilot/ui.h index 55dd43ad52..ba5de83ef6 100644 --- a/selfdrive/ui/sunnypilot/ui.h +++ b/selfdrive/ui/sunnypilot/ui.h @@ -15,6 +15,12 @@ #include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" +enum OnroadTimerStatusToggle { + NONE, + PAUSE, + RESUME +}; + class UIStateSP : public UIState { Q_OBJECT @@ -61,7 +67,7 @@ public: return user.user_id.toLower() != "unregisteredsponsor" && user.user_id.toLower() != "temporarysponsor"; }); } - void reset_onroad_sleep_timer(); + void reset_onroad_sleep_timer(OnroadTimerStatusToggle toggleTimerStatus = OnroadTimerStatusToggle::NONE); signals: void sunnylinkRoleChanged(bool subscriber); From cb6fa622ee484a1b51345958c0d9bed0aa8335d8 Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Sun, 12 Oct 2025 20:11:23 -0700 Subject: [PATCH 168/681] Visuals: Move custom chevron info to `sunnypilot/qt` (#1066) * refactor: move to sp ui * pr suggestion * no need to check pcm just oplong * no color, big front, long check * Fix Rainbow Mode & Y Positioning * Move param to uiscene --------- Co-authored-by: Jason Wen Co-authored-by: nayan --- selfdrive/ui/qt/onroad/model.cc | 167 ------------------ selfdrive/ui/qt/onroad/model.h | 10 -- .../qt/offroad/settings/visuals_panel.cc | 34 ++++ .../qt/offroad/settings/visuals_panel.h | 3 + selfdrive/ui/sunnypilot/qt/onroad/model.cc | 135 ++++++++++++++ selfdrive/ui/sunnypilot/qt/onroad/model.h | 11 ++ selfdrive/ui/sunnypilot/ui.cc | 1 + selfdrive/ui/sunnypilot/ui_scene.h | 1 + 8 files changed, 185 insertions(+), 177 deletions(-) diff --git a/selfdrive/ui/qt/onroad/model.cc b/selfdrive/ui/qt/onroad/model.cc index a1e3756cb6..a4165be216 100644 --- a/selfdrive/ui/qt/onroad/model.cc +++ b/selfdrive/ui/qt/onroad/model.cc @@ -34,7 +34,6 @@ void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) { drawLead(painter, lead_two, lead_vertices[1], surface_rect); } } - drawLeadStatus(painter, surface_rect.height(), surface_rect.width()); painter.restore(); } @@ -175,172 +174,6 @@ QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float } -void ModelRenderer::drawLeadStatus(QPainter &painter, int height, int width) { - auto *s = uiState(); - auto &sm = *(s->sm); - - if (!sm.alive("radarState")) return; - - const auto &radar_state = sm["radarState"].getRadarState(); - const auto &lead_one = radar_state.getLeadOne(); - const auto &lead_two = radar_state.getLeadTwo(); - - // Check if we have any active leads - bool has_lead_one = lead_one.getStatus(); - bool has_lead_two = lead_two.getStatus(); - - if (!has_lead_one && !has_lead_two) { - // Fade out status display - lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f); - if (lead_status_alpha <= 0.0f) return; - } else { - // Fade in status display - lead_status_alpha = std::min(1.0f, lead_status_alpha + 0.1f); - } - - // Draw status for each lead vehicle under its chevron - if (true) { - drawLeadStatusAtPosition(painter, lead_one, lead_vertices[0], height, width, "L1"); - } - - if (has_lead_two && std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0) { - drawLeadStatusAtPosition(painter, lead_two, lead_vertices[1], height, width, "L2"); - } -} - -void ModelRenderer::drawLeadStatusAtPosition(QPainter &painter, - const cereal::RadarState::LeadData::Reader &lead_data, - const QPointF &chevron_pos, - int height, int width, - const QString &label) { - - float d_rel = lead_data.getDRel(); - float v_rel = lead_data.getVRel(); - auto *s = uiState(); - auto &sm = *(s->sm); - float v_ego = sm["carState"].getCarState().getVEgo(); - - int chevron_data = std::atoi(Params().get("ChevronInfo").c_str()); - - // Calculate chevron size (same logic as drawLead) - float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35; - - QFont content_font = painter.font(); - content_font.setPixelSize(35); - content_font.setBold(true); - painter.setFont(content_font); - - QFontMetrics fm(content_font); - bool is_metric = s->scene.is_metric; - - QStringList text_lines; - - const int chevron_types = 3; - const int chevron_all = chevron_types + 1; // All metrics (value 4) - QStringList chevron_text[chevron_types]; - int position; - float val; - - // Distance display (chevron_data == 1 or all) - if (chevron_data == 1 || chevron_data == chevron_all) { - position = 0; - val = std::max(0.0f, d_rel); - QString distance_unit = is_metric ? "m" : "ft"; - if (!is_metric) { - val *= 3.28084f; // Convert meters to feet - } - chevron_text[position].append(QString::number(val, 'f', 0) + " " + distance_unit); - } - - // Absolute velocity display (chevron_data == 2 or all) - if (chevron_data == 2 || chevron_data == chevron_all) { - position = (chevron_data == 2) ? 0 : 1; - val = std::max(0.0f, (v_rel + v_ego) * (is_metric ? static_cast(MS_TO_KPH) : static_cast(MS_TO_MPH))); - chevron_text[position].append(QString::number(val, 'f', 0) + " " + (is_metric ? "km/h" : "mph")); - } - - // Time-to-contact display (chevron_data == 3 or all) - if (chevron_data == 3 || chevron_data == chevron_all) { - position = (chevron_data == 3) ? 0 : 2; - val = (d_rel > 0 && v_ego > 0) ? std::max(0.0f, d_rel / v_ego) : 0.0f; - QString ttc_str = (val > 0 && val < 200) ? QString::number(val, 'f', 1) + "s" : "---"; - chevron_text[position].append(ttc_str); - } - - // Collect all non-empty text lines - for (int i = 0; i < chevron_types; ++i) { - if (!chevron_text[i].isEmpty()) { - text_lines.append(chevron_text[i]); - } - } - - // If no text to display, return early - if (text_lines.isEmpty()) { - return; - } - - // Text box dimensions - float str_w = 150; // Width of text area - float str_h = 45; // Height per line - - // Position text below chevron, centered horizontally - float text_x = chevron_pos.x() - str_w / 2; - float text_y = chevron_pos.y() + sz + 15; - - // Clamp to screen bounds - text_x = std::clamp(text_x, 10.0f, (float)width - str_w - 10); - - // Shadow offset - QPoint shadow_offset(2, 2); - - // Draw each line of text with shadow - for (int i = 0; i < text_lines.size(); ++i) { - if (!text_lines[i].isEmpty()) { - QRect textRect(text_x, text_y + (i * str_h), str_w, str_h); - - // Draw shadow - painter.setPen(QColor(0x0, 0x0, 0x0, (int)(200 * lead_status_alpha))); - painter.drawText(textRect.translated(shadow_offset.x(), shadow_offset.y()), - Qt::AlignBottom | Qt::AlignHCenter, text_lines[i]); - - // Determine text color based on content and danger level - QColor text_color; - - // Check if this is a distance line (contains 'm' or 'ft') - if (text_lines[i].contains("m") || text_lines[i].contains("ft")) { - if (d_rel < 20.0f) { - text_color = QColor(255, 80, 80, (int)(255 * lead_status_alpha)); // Red - danger - } else if (d_rel < 40.0f) { - text_color = QColor(255, 200, 80, (int)(255 * lead_status_alpha)); // Yellow - caution - } else { - text_color = QColor(80, 255, 120, (int)(255 * lead_status_alpha)); // Green - safe - } - } - // Enhanced color coding for time-to-contact - else if (text_lines[i].contains("s") && !text_lines[i].contains("---")) { - float ttc_val = text_lines[i].left(text_lines[i].length() - 1).toFloat(); - if (ttc_val < 3.0f) { - text_color = QColor(255, 80, 80, (int)(255 * lead_status_alpha)); // Red - urgent - } else if (ttc_val < 6.0f) { - text_color = QColor(255, 200, 80, (int)(255 * lead_status_alpha)); // Yellow - caution - } else { - text_color = QColor(0xff, 0xff, 0xff, (int)(255 * lead_status_alpha)); // White - safe - } - } - else { - text_color = QColor(0xff, 0xff, 0xff, (int)(255 * lead_status_alpha)); // White for other lines - } - - // Draw main text - painter.setPen(text_color); - painter.drawText(textRect, Qt::AlignBottom | Qt::AlignHCenter, text_lines[i]); - } - } - - // Reset pen - painter.setPen(Qt::NoPen); -} - void ModelRenderer::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, const QRect &surface_rect) { const float speedBuff = 10.; diff --git a/selfdrive/ui/qt/onroad/model.h b/selfdrive/ui/qt/onroad/model.h index e40f832c37..3ef8ba5320 100644 --- a/selfdrive/ui/qt/onroad/model.h +++ b/selfdrive/ui/qt/onroad/model.h @@ -34,12 +34,6 @@ protected: 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 drawLeadStatus(QPainter &painter, int height, int width); - void drawLeadStatusAtPosition(QPainter &painter, - const cereal::RadarState::LeadData::Reader &lead_data, - const QPointF &chevron_pos, - int height, int width, - const QString &label); 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); virtual void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead); @@ -65,8 +59,4 @@ protected: Eigen::Matrix3f car_space_transform = Eigen::Matrix3f::Zero(); QRectF clip_region; - float lead_status_alpha = 0.0f; - QPointF lead_status_pos; - QString lead_status_text; - QColor lead_status_color; }; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc index 0324cd70f0..0e42f777cd 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc @@ -140,6 +140,40 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) { vlayout->addWidget(sunnypilotScroller); main_layout->addWidget(sunnypilotScreen); + + QObject::connect(uiState(), &UIState::offroadTransition, this, &VisualsPanel::refreshLongitudinalStatus); + + refreshLongitudinalStatus(); +} + +void VisualsPanel::refreshLongitudinalStatus() { + 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(); + + has_longitudinal_control = hasLongitudinalControl(CP); + } else { + has_longitudinal_control = false; + } + + if (chevron_info_settings) { + QString chevronEnabledDescription = tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control)."); + QString chevronNoLongDescription = tr("This feature requires openpilot longitudinal control to be available."); + + if (has_longitudinal_control) { + chevron_info_settings->setDescription(chevronEnabledDescription); + } else { + // Reset to "Off" when longitudinal not available + params.put("ChevronInfo", "0"); + chevron_info_settings->setDescription(chevronNoLongDescription); + } + + // Enable only when longitudinal is available + chevron_info_settings->setEnabled(has_longitudinal_control); + chevron_info_settings->refresh(); + } } void VisualsPanel::paramsRefresh() { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h index 30ff31c301..76a846dd45 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h @@ -19,6 +19,7 @@ public: explicit VisualsPanel(QWidget *parent = nullptr); void paramsRefresh(); + void refreshLongitudinalStatus(); protected: QStackedLayout* main_layout = nullptr; @@ -29,4 +30,6 @@ protected: ParamWatcher * param_watcher; ButtonParamControlSP *chevron_info_settings; ButtonParamControlSP *dev_ui_settings; + + bool has_longitudinal_control = false; }; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/model.cc b/selfdrive/ui/sunnypilot/qt/onroad/model.cc index 5d92838f8a..875ec6f0b8 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/model.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/model.cc @@ -87,4 +87,139 @@ void ModelRendererSP::drawPath(QPainter &painter, const cereal::ModelDataV2::Rea // Normal path rendering ModelRenderer::drawPath(painter, model, surface_rect.height()); } + + drawLeadStatus(painter, surface_rect.height(), surface_rect.width()); +} + +void ModelRendererSP::drawLeadStatus(QPainter &painter, int height, int width) { + auto *s = uiState(); + auto &sm = *(s->sm); + + bool longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl(); + if (!longitudinal_control) { + lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f); + return; + } + + if (!sm.alive("radarState")) { + lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f); + return; + } + + const auto &radar_state = sm["radarState"].getRadarState(); + const auto &lead_one = radar_state.getLeadOne(); + const auto &lead_two = radar_state.getLeadTwo(); + + bool has_lead_one = lead_one.getStatus(); + bool has_lead_two = lead_two.getStatus(); + + if (!has_lead_one && !has_lead_two) { + lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f); + if (lead_status_alpha <= 0.0f) return; + } else { + lead_status_alpha = std::min(1.0f, lead_status_alpha + 0.1f); + } + + if (has_lead_one) { + drawLeadStatusAtPosition(painter, lead_one, lead_vertices[0], height, width, "L1"); + } + + if (has_lead_two && std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0) { + drawLeadStatusAtPosition(painter, lead_two, lead_vertices[1], height, width, "L2"); + } +} + +void ModelRendererSP::drawLeadStatusAtPosition(QPainter &painter, + const cereal::RadarState::LeadData::Reader &lead_data, + const QPointF &chevron_pos, + int height, int width, + const QString &label) { + float d_rel = lead_data.getDRel(); + float v_rel = lead_data.getVRel(); + auto *s = uiState(); + auto &sm = *(s->sm); + float v_ego = sm["carState"].getCarState().getVEgo(); + + int chevron_data = s->scene.chevron_info; + float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35; + + QFont content_font = painter.font(); + content_font.setPixelSize(50); + content_font.setBold(true); + painter.setFont(content_font); + + bool is_metric = s->scene.is_metric; + QStringList text_lines; + const int chevron_all = 4; + QStringList chevron_text[3]; + + // Distance display + if (chevron_data == 1 || chevron_data == chevron_all) { + int pos = 0; + float val = std::max(0.0f, d_rel); + QString unit = is_metric ? "m" : "ft"; + if (!is_metric) val *= 3.28084f; + chevron_text[pos].append(QString::number(val, 'f', 0) + " " + unit); + } + + // Speed display + if (chevron_data == 2 || chevron_data == chevron_all) { + int pos = (chevron_data == 2) ? 0 : 1; + float multiplier = is_metric ? static_cast(MS_TO_KPH) : static_cast(MS_TO_MPH); + float val = std::max(0.0f, (v_rel + v_ego) * multiplier); + QString unit = is_metric ? "km/h" : "mph"; + chevron_text[pos].append(QString::number(val, 'f', 0) + " " + unit); + } + + // Time to contact + if (chevron_data == 3 || chevron_data == chevron_all) { + int pos = (chevron_data == 3) ? 0 : 2; + float val = (d_rel > 0 && v_ego > 0) ? std::max(0.0f, d_rel / v_ego) : 0.0f; + QString ttc = (val > 0 && val < 200) ? QString::number(val, 'f', 1) + "s" : "---"; + chevron_text[pos].append(ttc); + } + + for (int i = 0; i < 3; ++i) { + if (!chevron_text[i].isEmpty()) text_lines.append(chevron_text[i]); + } + + if (text_lines.isEmpty()) return; + + QFontMetrics fm(content_font); + float text_width = 120.0f; + for (const QString &line : text_lines) { + text_width = std::max(text_width, fm.horizontalAdvance(line) + 20.0f); + } + text_width = std::min(text_width, 250.0f); + + float line_height = 50.0f; + float total_height = text_lines.size() * line_height; + float margin = 20.0f; + + float text_y = chevron_pos.y() + sz + 15; + if (text_y + total_height > height - margin) { + float y_max = chevron_pos.y() > (height - margin) ? (height - margin) : chevron_pos.y(); + text_y = y_max - 15 - total_height; + text_y = std::max(margin, text_y); + } + + float text_x = chevron_pos.x() - text_width / 2; + text_x = std::clamp(text_x, margin, (float)width - text_width - margin); + + QPoint shadow_offset(2, 2); + QColor text_color = QColor(255, 255, 255, (int)(255 * lead_status_alpha)); + for (int i = 0; i < text_lines.size(); ++i) { + float y = text_y + (i * line_height); + if (y + line_height > height - margin) break; + + QRect rect(text_x, y, text_width, line_height); + + // Draw shadow + painter.setPen(QColor(0, 0, 0, (int)(200 * lead_status_alpha))); + painter.drawText(rect.translated(shadow_offset), Qt::AlignCenter, text_lines[i]); + painter.setPen(text_color); + painter.drawText(rect, Qt::AlignCenter, text_lines[i]); + } + + painter.setPen(Qt::NoPen); } diff --git a/selfdrive/ui/sunnypilot/qt/onroad/model.h b/selfdrive/ui/sunnypilot/qt/onroad/model.h index 24404f32f0..73999f0059 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/model.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/model.h @@ -17,6 +17,17 @@ private: void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead) override; void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &rect) override; + // Lead status display methods + void drawLeadStatus(QPainter &painter, int height, int width); + void drawLeadStatusAtPosition(QPainter &painter, + const cereal::RadarState::LeadData::Reader &lead_data, + const QPointF &chevron_pos, + int height, int width, + const QString &label); + QPolygonF left_blindspot_vertices; QPolygonF right_blindspot_vertices; + + // Lead status animation + float lead_status_alpha = 0.0f; }; diff --git a/selfdrive/ui/sunnypilot/ui.cc b/selfdrive/ui/sunnypilot/ui.cc index 2244e23199..16d2bac49d 100644 --- a/selfdrive/ui/sunnypilot/ui.cc +++ b/selfdrive/ui/sunnypilot/ui.cc @@ -73,6 +73,7 @@ void ui_update_params_sp(UIStateSP *s) { s->scene.onroadScreenOffTimerParam = std::atoi(params.get("OnroadScreenOffTimer").c_str()); s->scene.turn_signals = params.getBool("ShowTurnSignals"); + s->scene.chevron_info = std::atoi(params.get("ChevronInfo").c_str()); } void UIStateSP::reset_onroad_sleep_timer(OnroadTimerStatusToggle toggleTimerStatus) { diff --git a/selfdrive/ui/sunnypilot/ui_scene.h b/selfdrive/ui/sunnypilot/ui_scene.h index 233f1e15de..353f4ed8da 100644 --- a/selfdrive/ui/sunnypilot/ui_scene.h +++ b/selfdrive/ui/sunnypilot/ui_scene.h @@ -18,4 +18,5 @@ typedef struct UISceneSP : UIScene { bool trueVEgoUI; bool hideVEgoUI; bool turn_signals = false; + int chevron_info; } UISceneSP; From 728108f97f3d094d8502e1334fe62e8dfe52a360 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sun, 12 Oct 2025 23:36:58 -0400 Subject: [PATCH 169/681] ui: Optimize Param Read for Onroad UI (#1365) * Move params to uiscene * more --------- Co-authored-by: Jason Wen --- .../ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc | 4 ++-- selfdrive/ui/sunnypilot/qt/onroad/model.cc | 4 ++-- selfdrive/ui/sunnypilot/ui.cc | 2 ++ selfdrive/ui/sunnypilot/ui_scene.h | 2 ++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc index 96d945ab53..4bbf894572 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc @@ -21,7 +21,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) { paramsRefresh(param_name, param_value); }); - is_sunnylink_enabled = Params().getBool("SunnylinkEnabled"); + is_sunnylink_enabled = params.getBool("SunnylinkEnabled"); connect(uiStateSP(), &UIStateSP::sunnylinkRolesChanged, this, &SunnylinkPanel::updatePanel); connect(uiStateSP(), &UIStateSP::sunnylinkDeviceUsersChanged, this, &SunnylinkPanel::updatePanel); connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) { @@ -272,7 +272,7 @@ void SunnylinkPanel::updatePanel() { const auto sunnylinkDongleId = getSunnylinkDongleId().value_or(tr("N/A")); sunnylinkEnabledBtn->setEnabled(!is_onroad); - is_sunnylink_enabled = Params().getBool("SunnylinkEnabled"); + is_sunnylink_enabled = params.getBool("SunnylinkEnabled"); bool is_sub = uiStateSP()->isSunnylinkSponsor() && is_sunnylink_enabled; auto max_current_sponsor_rule = uiStateSP()->sunnylinkSponsorRole(); auto role_name = max_current_sponsor_rule.getSponsorTierString(); diff --git a/selfdrive/ui/sunnypilot/qt/onroad/model.cc b/selfdrive/ui/sunnypilot/qt/onroad/model.cc index 875ec6f0b8..14943b1d48 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/model.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/model.cc @@ -24,7 +24,7 @@ void ModelRendererSP::update_model(const cereal::ModelDataV2::Reader &model, con void ModelRendererSP::drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &surface_rect) { auto *s = uiState(); auto &sm = *(s->sm); - bool blindspot = Params().getBool("BlindSpot"); + bool blindspot = s->scene.blindspot_ui; if (blindspot) { bool left_blindspot = sm["carState"].getCarState().getLeftBlindspot(); @@ -49,7 +49,7 @@ void ModelRendererSP::drawPath(QPainter &painter, const cereal::ModelDataV2::Rea } } - bool rainbow = Params().getBool("RainbowMode"); + bool rainbow = s->scene.rainbow_mode; //float v_ego = sm["carState"].getCarState().getVEgo(); if (rainbow) { diff --git a/selfdrive/ui/sunnypilot/ui.cc b/selfdrive/ui/sunnypilot/ui.cc index 16d2bac49d..ab1ca0e6a6 100644 --- a/selfdrive/ui/sunnypilot/ui.cc +++ b/selfdrive/ui/sunnypilot/ui.cc @@ -74,6 +74,8 @@ void ui_update_params_sp(UIStateSP *s) { s->scene.turn_signals = params.getBool("ShowTurnSignals"); s->scene.chevron_info = std::atoi(params.get("ChevronInfo").c_str()); + s->scene.blindspot_ui = params.getBool("BlindSpot"); + s->scene.rainbow_mode = params.getBool("RainbowMode"); } void UIStateSP::reset_onroad_sleep_timer(OnroadTimerStatusToggle toggleTimerStatus) { diff --git a/selfdrive/ui/sunnypilot/ui_scene.h b/selfdrive/ui/sunnypilot/ui_scene.h index 353f4ed8da..da98931e6b 100644 --- a/selfdrive/ui/sunnypilot/ui_scene.h +++ b/selfdrive/ui/sunnypilot/ui_scene.h @@ -19,4 +19,6 @@ typedef struct UISceneSP : UIScene { bool hideVEgoUI; bool turn_signals = false; int chevron_info; + bool blindspot_ui; + bool rainbow_mode; } UISceneSP; From 68d059fd5d3a2503ddb1096518f11fcc3df183d5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 12 Oct 2025 23:48:58 -0400 Subject: [PATCH 170/681] ui: ensure Cruise panel widget is reset when `hideEvent` (#1366) --- .../settings/longitudinal/speed_limit/speed_limit_settings.cc | 4 ++++ .../settings/longitudinal/speed_limit/speed_limit_settings.h | 1 + .../ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc | 4 ++++ .../ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h | 1 + 4 files changed, 10 insertions(+) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc index ff5d6221ce..3efbfeed86 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc @@ -158,3 +158,7 @@ void SpeedLimitSettings::refresh() { void SpeedLimitSettings::showEvent(QShowEvent *event) { refresh(); } + +void SpeedLimitSettings::hideEvent(QHideEvent *event) { + setCurrentWidget(subPanelFrame); +} diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h index f76f9bea2a..23fa7b4ece 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h @@ -21,6 +21,7 @@ public: SpeedLimitSettings(QWidget *parent = nullptr); void refresh(); void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent *event) override; signals: void backPress(); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc index 3e3977ff64..31124a3242 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc @@ -86,6 +86,10 @@ void LongitudinalPanel::showEvent(QShowEvent *event) { refresh(offroad); } +void LongitudinalPanel::hideEvent(QHideEvent *event) { + main_layout->setCurrentWidget(cruisePanelScreen); +} + void LongitudinalPanel::refresh(bool _offroad) { auto cp_bytes = params.get("CarParamsPersistent"); auto cp_sp_bytes = params.get("CarParamsSPPersistent"); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h index 0b7f39c645..a94369a560 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h @@ -18,6 +18,7 @@ class LongitudinalPanel : public QWidget { public: explicit LongitudinalPanel(QWidget *parent = nullptr); void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent *event) override; void refresh(bool _offroad); private: From 7d54b58b8d3bb83e20299602a3d748ed8baf3763 Mon Sep 17 00:00:00 2001 From: Kirito3481 <47619491+Kirito3481@users.noreply.github.com> Date: Mon, 13 Oct 2025 13:19:24 +0900 Subject: [PATCH 171/681] ui: Update Korean translations (#1359) Update ko-kr translations Co-authored-by: Jason Wen --- selfdrive/ui/translations/main_ko.ts | 265 ++++++++++++++------------- 1 file changed, 135 insertions(+), 130 deletions(-) diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index f3ec6b69e8..d86468ae6e 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -144,11 +144,11 @@ Please use caution when using this feature. Only use the blinker when traffic an Global Brightness - + 전역 밝기 Overrides the brightness of the device. This applies to both onroad and offroad screens. - + 기기의 밝기를 강제로 설정합니다. 이 설정은 주행 중 화면과 비주행 화면(설정 등) 모두에 적용됩니다. @@ -256,11 +256,11 @@ This only toggles the visibility of the controls; it does not toggle the actual Enable Copyparty service - + Copyparty 서비스 사용 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 it's IP. - + Copyparty는 매우 유능한 파일 서버로, 이를 통해 주행 기록을 다운로드하고, 로그 파일을 확인하며, 심지어 브라우저에서 일부 파일을 편집할 수도 있습니다. 이 기능을 사용하려면 comma 기기의 로컬 IP를 통해 접속해야 합니다. @@ -538,32 +538,32 @@ Steering lag calibration is complete. Onroad Uploads - + 주행 중 업로드 Enable Always Offroad - + 항상 오프로드 사용 DisplayPanel Onroad Screen: Reduced Brightness - + 주행 중 화면: 밝기 감소 Turn off device screen or reduce brightness after driving starts. It automatically brightens again when screen is touched or a visible alert is displayed. - + 운전이 시작된 후 기기 화면을 끄거나 밝기를 줄입니다. 화면을 터치하거나 시각적 알림이 표시되면 자동으로 다시 밝아집니다. Interactivity Timeout - 상호작용 타임아웃 + 상호작용 타임아웃 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. - 설정 화면에 사용자 지정 타임아웃을 적용하세요. -사용자가 화면과 상호작용하지 않으면 설정 화면이 자동으로 닫히는 시간입니다. + 설정 UI에 사용자 지정 타임아웃을 적용하세요. +이는 사용자가 화면과 상호 작용하지 않으면 설정 UI가 자동으로 닫히는 시간입니다. @@ -630,55 +630,55 @@ This is the time after which settings UI closes automatically if user is not int ExternalStorageControl External Storage - + 외부 저장소 Extend your comma device's storage by inserting a USB drive into the aux port. - + USB 드라이브를 AUX 포트에 삽입하여 comma 기기의 저장 공간을 확장합니다. CHECK - 확인 + 확인 MOUNT - + 마운트 UNMOUNT - + 마운트 해제 FORMAT - + 포맷 Are you sure you want to format this drive? This will erase all data. - + 이 드라이브를 포맷하시겠습니까? 이 작업은 모든 데이터를 지웁니다. Format - + 포맷 formatting - + 포맷 중 insert drive - + 드라이브 삽입 needs format - + 포맷 필요 mounting - + 마운트 중 unmounting - + 마운트 해제 중 @@ -737,57 +737,58 @@ Firehose 모드를 사용하면 훈련 데이터 업로드를 극대화하여 op HudRendererSP km/h - km/h + km/h mph - mph + mph GREEN LIGHT - + 녹색 신호 LEAD VEHICLE DEPARTING - + 전방 차량이 +출발하였습니다 SPEED - + SPEED LIMIT - + LIMIT Near - + 근처 km - + km m - 분 + m mi - + mi ft - + ft AHEAD - + 전방 MAX - 최대 + 최대 @@ -905,15 +906,15 @@ DEPARTING Enforce Torque Lateral Control - + 토크 조향 제어 강제 적용 Enable this to enforce sunnypilot to steer with Torque lateral control. - + sunnypilot이 토크 조향 제어를 통해 조향하도록 강제하려면 이 기능을 활성화하세요. Customize Params - + 매개변수 사용자 지정 @@ -940,31 +941,31 @@ DEPARTING Intelligent Cruise Button Management (ICBM) (Alpha) - + 지능형 크루즈 버튼 관리 (ICBM) (알파) When enabled, sunnypilot will attempt to manage the built-in cruise control buttons by emulating button presses for limited longitudinal control. - + 활성화하면, sunnypilot은 제한적인 가감속 제어를 위해 버튼 조작을 재현하여 내장 크루즈 컨트롤 버튼을 관리하려고 시도합니다. Smart Cruise Control - Vision - + 스마트 크루즈 컨트롤 - 비전 Use vision path predictions to estimate the appropriate speed to drive through turns ahead. - + 전방 커브 구간을 통과하기 위한 적절한 속도를 예측하기 위해 시각 경로 예측 기능을 사용합니다. Smart Cruise Control - Map - + 스마트 크루즈 컨트롤 - 지도 Use map data to estimate the appropriate speed to drive through turns ahead. - + 전방 커브 구간을 통과하기 위한 적절한 속도를 예측하기 위해 지도 데이터를 사용합니다. Speed Limit - + 속도 제한 @@ -1195,47 +1196,47 @@ The default software delay value is 0.2 Refresh Model List - + 모델 목록 새로고침 REFRESH - 새로 고침 + 새로고침 Fetching Latest Models - + 최신 모델 불러오는 중 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. - + 이 기능을 켜면 차량이 스스로 조향 응답 속도를 학습하고 맞춥니다. 끄면 고정된 조향 응답 속도를 사용합니다. 이 기능을 켜두는 것이 기본 openpilot 경험을 제공합니다. Live Steer Delay: - + 실시간 조향 지연 시간: Actuator Delay: - + 액츄에이터 지연 시간: Software Delay: - + 소프트웨어 지연 시간: Total Delay: - + 전체 지연 시간: Use Lane Turn Desires - + 차로 회전 의도 사용 Adjust Lane Turn Speed - + 차로 회전 속도 조정 Set the maximum speed for lane turn desires. Default is 19 %1. - + 차로 회전 의도의 최대 속도를 설정합니다. 기본값은 19 %1 입니다. @@ -1400,7 +1401,7 @@ The default software delay value is 0.2 <b>Unsupported branch detected</b> - The current version of <b><u>%1</u></b> branch is no longer supported on the comma three. Please go to <b>[Device > Software]</b> and install a supported branch with <b><u>-tici</u></b> in the branch name for the comma three. - + <b>지원되지 않는 브랜치 감지</b> - 현재 사용 중인 <b><u>%1</u></b> 브랜치는 comma three 기기에서 더 이상 지원되지 않습니다. <b>[기기 > 소프트웨어]</b>로 이동하여 브랜치 이름에 <b><u>-tici</u></b> 가 포함된 comma three용 브랜치로 설치하세요. @@ -1779,56 +1780,59 @@ Warning: You are on a metered connection! None - + 없음 Fixed - + 고정 Percent - + 비율 Car Only - + 차량만 Map Only - + 지도만 Car First - + 차량 +우선 Map First - + 지도 +우선 Combined Data - + 결합 +데이터 Off - + 끄기 Information - + 정보 Warning - + 경고 Assist - + 보조 @@ -1926,7 +1930,7 @@ Data Display - + 화면 @@ -2168,78 +2172,78 @@ Data SpeedLimitPolicy Back - 뒤로 + 뒤로 Speed Limit Source - + 속도 제한 출처 ⦿ Car Only: Use Speed Limit data only from Car - + ⦿ 차량만: 차량에서 제공되는 속도 제한 데이터만 사용 ⦿ Map Only: Use Speed Limit data only from OpenStreetMaps - + ⦿ 지도만: OpenStreetMaps에서 제공되는 속도 제한 데이터만 사용 ⦿ Car First: Use Speed Limit data from Car if available, else use from OpenStreetMaps - + ⦿ 차량 우선: 사용 가능한 경우 차량의 속도 제한 데이터를 사용하고, 그렇지 않은 경우 OpenStreetMaps의 데이터 사용 ⦿ Map First: Use Speed Limit data from OpenStreetMaps if available, else use from Car - + ⦿ 지도 우선: 사용 가능한 경우 OpenStreetMaps의 속도 제한 데이터를 사용하고, 그렇지 않은 경우 차량의 데이터 사용 ⦿ Combined: Use combined Speed Limit data from Car & OpenStreetMaps - + ⦿ 결합: 차량 및 OpenStreetMaps의 속도 제한 결합 데이터 사용 SpeedLimitSettings Back - 뒤로 + 뒤로 Speed Limit - + 속도 제한 Customize Source - + 출처 사용자 지정 Speed Limit Offset - + 속도 제한 오프셋 ⦿ None: No Offset - + ⦿ 없음: 오프셋 없음 ⦿ Fixed: Adds a fixed offset [Speed Limit + Offset] - + ⦿ 고정: 고정 오프셋을 추가합니다. [제한 속도 + 오프셋] ⦿ Percent: Adds a percent offset [Speed Limit + (Offset % Speed Limit)] - + ⦿ 비율: 백분율 오프셋을 추가합니다. [제한 속도 + (오프셋 % 제한 속도)] ⦿ Off: Disables the Speed Limit functions. - + ⦿ 끄기: 속도 제한 기능을 비활성화합니다. ⦿ Information: Displays the current road's speed limit. - + ⦿ 정보: 현재 도로의 제한 속도를 표시합니다. ⦿ Warning: Provides a warning when exceeding the current road's speed limit. - + ⦿ 경고: 현재 도로의 제한 속도를 초과할 경우 경고합니다. ⦿ Assist: Adjusts the vehicle's cruise speed based on the current road's speed limit when operating the +/- buttons. - + ⦿ 보조: +/- 버튼을 조작할 때 현재 도로의 제한 속도를 기준으로 차량의 크루즈 속도를 조정합니다. @@ -2412,27 +2416,27 @@ Data Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.) - + sunnylink 업로더를 활성화하여 sunnypilot이 귀하의 주행 데이터를 sunnypilot 서버로 업로드하도록 허용합니다. (가장 높은 등급에서만 해당하며, 사용자에게는 어떠한 이점도 제공하지 않습니다. 저희는 그냥 데이터 용량을 테스트하는 중입니다.) [Don't use] Enable sunnylink uploader - + [사용하지 마세요] sunnylink 업로더 사용 🚀 sunnylink 🚀 - + 🚀 sunnylink 🚀 For secure backup, restore, and remote configuration - + 안전한 백업, 복원 및 원격 설정을 위해 Sponsorship isn't required for basic backup/restore - + 기본적인 백업/복원은 후원이 필요하지 않습니다 Click the sponsor button for more details - + 후원 버튼을 눌러 더 자세한 정보를 확인하세요 @@ -2628,54 +2632,54 @@ Data TorqueLateralControlCustomParams Manual Real-Time Tuning - + 실시간 수동 튜닝 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. - + 토크 조향 제어기가 셀프 튜닝에서 학습된 값 대신 고정된 값을 사용하도록 강제합니다. 이 토글을 활성화하면 셀프 튜닝 값이 무시됩니다. Lateral Acceleration Factor - + 조향 가속 계수 Friction - + 마찰 Real-time and Offline - + 실시간 및 오프라인 Offline Only - + 오프라인만 TorqueLateralControlSettings Self-Tune - + 셀프 튜닝 Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default. - + 기본적으로 토크 조향 제어를 사용하지 않는 플랫폼에 대해 토크 조향 제어의 셀프 튜닝 기능을 활성화합니다. Less Restrict Settings for Self-Tune (Beta) - + 셀프 튜닝 제한 완화 설정 (베타) Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values. - + 셀프 튜닝 사용 시 덜 엄격한 설정을 사용합니다. 이는 학습 값에 대해 torqued가 더 관대하도록 허용합니다. Enable Custom Tuning - + 사용자 지정 튜닝 사용 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. - + 토크 조향 제어에 대한 사용자 지정 튜닝을 활성화합니다. 아래에서 조향 가속 계수 및 마찰을 수정하면 "opendbc/car/torque_data" 내의 YAML 파일에 명시된 오프라인 값이 무시됩니다. 이 값들은 "실시간 수동 튜닝" 토글이 활성화되면 실시간으로도 사용됩니다. @@ -2690,7 +2694,7 @@ Data Favorites - + 즐겨찾기 @@ -2737,88 +2741,89 @@ Data Enable Tesla Rainbow Mode - + 테슬라 무지개 모드 사용 A beautiful rainbow effect on the path the model wants to take. - + 모델이 가고자 하는 경로에 아름다운 무지개 효과를 추가합니다. It - + 이는 운전에 does not - + 어떠한 affect driving in any way. - + 영향도 미치지 않습니다. Enable Standstill Timer - + 정차 타이머 사용 Show a timer on the HUD when the car is at a standstill. - + 차량이 정차 상태일 때 HUD에 타이머를 표시합니다. Display Road Name - + 도로 이름 표시 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. - + 차량이 주행 중인 도로의 이름을 표시합니다. 도로 이름을 가져오려면 OSM 패널에서 해당 위치의 OpenStreetMap 데이터베이스를 다운로드해야 합니다. Green Traffic Light Alert (Beta) - + 녹색 신호 감지 알림 (베타) 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. - + 참고: 이 알림음은 단순한 알림 목적으로만 설계되었습니다. 주변 환경을 살피고 그에 따라 결정하는 것은 운전자의 책임입니다. Lead Departure Alert (Beta) - + 전방 차량 출발 알림 (베타) A chime and on-screen alert will play when you are stopped, and the vehicle in front of you start moving. - + 당신이 정차 중이고 전방 차량이 움직이기 시작할 때, 알림음과 화면 경고가 표시됩니다. Speedometer: Always Display True Speed - + 속도계: 항상 실제 속도 표시 Always display the true vehicle current speed from wheel speed sensors. - + 항상 휠 스피드 센서에서 얻은 실제 차량 속도를 표시합니다. Speedometer: Hide from Onroad Screen - + 속도계: 주행 화면에서 숨기기 Right - + 오른쪽 Right && Bottom - + 오른쪽 && +아래 Developer UI - + 개발자 UI Display real-time parameters and metrics from various sources. - + 다양한 출처의 실시간 매개변수 및 측정 지표를 표시합니다. From bd9bb74d03d4143b65f1a218848ec1f7a195dfe4 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 13 Oct 2025 01:40:22 -0400 Subject: [PATCH 172/681] custom cereal: fix formatting (#1367) --- cereal/custom.capnp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 44b8bd69fa..833845d36d 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -406,11 +406,11 @@ struct ModelDataV2SP @0xa1680744031fdb2d { laneTurnDirection @0 :TurnDirection; } - enum TurnDirection { - none @0; - turnLeft @1; - turnRight @2; - } +enum TurnDirection { + none @0; + turnLeft @1; + turnRight @2; +} struct CustomReserved10 @0xcb9fd56c7057593a { } From 7e03277962091dc9081a97f89673a86705dcffb7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 13 Oct 2025 03:06:39 -0400 Subject: [PATCH 173/681] ui: bigger cluster set speed fonts when ICBM is active (#1369) --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index 718bfb514b..3ed8feb993 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -698,11 +698,13 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) { } else { icbm_active_counter = 0; } + int max_str_size = (icbm_active_counter != 0) ? 60 : 40; + int max_str_y = (icbm_active_counter != 0) ? 15 : 27; QString max_str = (icbm_active_counter != 0) ? QString::number(std::nearbyint(speedCluster)) : tr("MAX"); - p.setFont(InterFont(40, QFont::DemiBold)); + p.setFont(InterFont(max_str_size, QFont::DemiBold)); p.setPen(max_color); - p.drawText(set_speed_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, max_str); + p.drawText(set_speed_rect.adjusted(0, max_str_y, 0, 0), Qt::AlignTop | Qt::AlignHCenter, max_str); // Draw set speed QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(set_speed)) : "–"; From de0a1e66d828f5723c4ce00957d5adae842e1a4b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 13 Oct 2025 13:54:50 -0700 Subject: [PATCH 174/681] 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 bd40b89624..20d0f2ecba 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 141f682db0..ef28df8112 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 175/681] 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 20d0f2ecba..a65f198ad6 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 176/681] 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 e468d88e0e..b577b74b00 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 e5f1f86ac2d19550dcd6238635811a0a2efa09b7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 13 Oct 2025 19:37:58 -0400 Subject: [PATCH 177/681] params: helper to clamp out-of-range int params (#1373) * params: helpers to clamp out-of-range values * lint * inline * fix access * actually fix the param * inherit them * more lint --- selfdrive/selfdrived/selfdrived.py | 8 ++++++- sunnypilot/__init__.py | 21 +++++++++++++++++++ .../controls/lib/speed_limit/common.py | 9 ++++---- .../lib/speed_limit/speed_limit_resolver.py | 15 +++++++++++-- 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index 4bc75021f7..0a4ea749e4 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -24,6 +24,7 @@ from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroa from openpilot.system.version import get_build_metadata from openpilot.sunnypilot.mads.mads import ModularAssistiveDrivingSystem +from openpilot.sunnypilot import get_sanitize_int_param from openpilot.sunnypilot.selfdrive.car.car_specific import CarSpecificEventsSP from openpilot.sunnypilot.selfdrive.car.cruise_helpers import CruiseHelper from openpilot.sunnypilot.selfdrive.car.intelligent_cruise_button_management.controller import IntelligentCruiseButtonManagement @@ -130,7 +131,12 @@ class SelfdriveD(CruiseHelper): self.logged_comm_issue = None self.not_running_prev = None self.experimental_mode = False - self.personality = self.params.get("LongitudinalPersonality", return_default=True) + self.personality = get_sanitize_int_param( + "LongitudinalPersonality", + min(log.LongitudinalPersonality.schema.enumerants.values()), + max(log.LongitudinalPersonality.schema.enumerants.values()), + self.params + ) self.recalibrating_seen = False self.state_machine = StateMachine() self.rk = Ratekeeper(100, print_delay_threshold=None) diff --git a/sunnypilot/__init__.py b/sunnypilot/__init__.py index dd9870597d..ccacd0be0a 100644 --- a/sunnypilot/__init__.py +++ b/sunnypilot/__init__.py @@ -5,6 +5,7 @@ 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 hashlib PARAMS_UPDATE_PERIOD = 3 # seconds @@ -16,3 +17,23 @@ def get_file_hash(path: str) -> str: for byte_block in iter(lambda: f.read(4096), b""): sha256_hash.update(byte_block) return sha256_hash.hexdigest() + + +class IntEnumBase(IntEnum): + @classmethod + def min(cls): + return min(cls) + + @classmethod + def max(cls): + return max(cls) + + +def get_sanitize_int_param(key: str, min_val: int, max_val: int, params) -> int: + val: int = params.get(key, return_default=True) + clipped_val = max(min_val, min(max_val, val)) + + if clipped_val != val: + params.put(key, clipped_val) + + return clipped_val diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/common.py b/sunnypilot/selfdrive/controls/lib/speed_limit/common.py index baf9328032..c46768464e 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/common.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/common.py @@ -4,10 +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 enum import IntEnum + +from openpilot.sunnypilot import IntEnumBase -class Policy(IntEnum): +class Policy(IntEnumBase): car_state_only = 0 map_data_only = 1 car_state_priority = 2 @@ -15,13 +16,13 @@ class Policy(IntEnum): combined = 4 -class OffsetType(IntEnum): +class OffsetType(IntEnumBase): off = 0 fixed = 1 percentage = 2 -class Mode(IntEnum): +class Mode(IntEnumBase): off = 0 information = 1 warning = 2 diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py index 51fb8f6d64..459334d156 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py @@ -12,7 +12,7 @@ from openpilot.common.constants import CV from openpilot.common.gps import get_gps_location_service from openpilot.common.params import Params from openpilot.common.realtime import DT_MDL -from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD +from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD, get_sanitize_int_param from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import LIMIT_MAX_MAP_DATA_AGE, LIMIT_ADAPT_ACC from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Policy, OffsetType @@ -42,6 +42,12 @@ class SpeedLimitResolver: self.distance_solutions = {} # Store for distance to current speed limit start for different sources self.policy = self.params.get("SpeedLimitPolicy", return_default=True) + self.policy = get_sanitize_int_param( + "SpeedLimitPolicy", + Policy.min().value, + Policy.max().value, + self.params + ) self._policy_to_sources_map = { Policy.car_state_only: [SpeedLimitSource.car], Policy.map_data_only: [SpeedLimitSource.map], @@ -54,7 +60,12 @@ class SpeedLimitResolver: self._reset_limit_sources(source) self.is_metric = self.params.get_bool("IsMetric") - self.offset_type = self.params.get("SpeedLimitOffsetType", return_default=True) + self.offset_type = get_sanitize_int_param( + "SpeedLimitOffsetType", + OffsetType.min().value, + OffsetType.max().value, + self.params + ) self.offset_value = self.params.get("SpeedLimitValueOffset", return_default=True) self.speed_limit = 0. From 8e3757ac875a244adb1cef4d029cb8c59b345982 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 13 Oct 2025 16:49:39 -0700 Subject: [PATCH 178/681] 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 ea6fedebf7..bccb36712c 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 f0bcdcdc6d..1925122d0b 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 285fd9760698673d99a51cf407d480d2bc4e0531 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 13 Oct 2025 20:36:24 -0400 Subject: [PATCH 179/681] ui: only draw speedCluster speed over "MAX" when ICBM is enabled (#1374) --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 4 +++- selfdrive/ui/sunnypilot/qt/onroad/hud.h | 1 + selfdrive/ui/sunnypilot/ui.cc | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index 3ed8feb993..8c913cec17 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -35,6 +35,7 @@ void HudRendererSP::updateState(const UIState &s) { const auto gpsLocation = is_gps_location_external ? sm["gpsLocationExternal"].getGpsLocationExternal() : sm["gpsLocation"].getGpsLocation(); const auto ltp = sm["liveTorqueParameters"].getLiveTorqueParameters(); const auto car_params = sm["carParams"].getCarParams(); + const auto car_params_sp = sm["carParamsSP"].getCarParamsSP(); const auto lp_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP(); const auto lmd = sm["liveMapDataSP"].getLiveMapDataSP(); @@ -130,6 +131,7 @@ void HudRendererSP::updateState(const UIState &s) { carControlEnabled = car_control.getEnabled(); speedCluster = car_state.getCruiseState().getSpeedCluster() * speedConv; + pcmCruiseSpeed = car_params_sp.getPcmCruiseSpeed(); } void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) { @@ -689,7 +691,7 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) { } // Draw "MAX" or carState.cruiseState.speedCluster (when ICBM is active) text - if (carControlEnabled) { + if (!pcmCruiseSpeed && carControlEnabled) { if (std::nearbyint(set_speed) != std::nearbyint(speedCluster)) { icbm_active_counter = 3 * UI_FREQ; } else if (icbm_active_counter > 0) { diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.h b/selfdrive/ui/sunnypilot/qt/onroad/hud.h index 554c9c5d0d..bc7ee81de3 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.h @@ -120,4 +120,5 @@ private: bool carControlEnabled; float speedCluster = 0; int icbm_active_counter = 0; + bool pcmCruiseSpeed; }; diff --git a/selfdrive/ui/sunnypilot/ui.cc b/selfdrive/ui/sunnypilot/ui.cc index ab1ca0e6a6..df7b3ce5ca 100644 --- a/selfdrive/ui/sunnypilot/ui.cc +++ b/selfdrive/ui/sunnypilot/ui.cc @@ -29,7 +29,7 @@ UIStateSP::UIStateSP(QObject *parent) : UIState(parent) { "wideRoadCameraState", "managerState", "selfdriveState", "longitudinalPlan", "modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP", "carControl", "gpsLocationExternal", "gpsLocation", "liveTorqueParameters", - "carStateSP", "liveParameters", "liveMapDataSP" + "carStateSP", "liveParameters", "liveMapDataSP", "carParamsSP" }); // update timer From 39e73cc46ea666787ff49bb1926d9d5625c1ae29 Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Mon, 13 Oct 2025 18:45:59 -0700 Subject: [PATCH 180/681] ui: add ModelRendererSP::draw (#1372) * ModelRendererSP::draw * match * less * huh? * unused --------- Co-authored-by: Jason Wen --- selfdrive/ui/qt/onroad/model.cc | 3 +- selfdrive/ui/qt/onroad/model.h | 4 - selfdrive/ui/sunnypilot/qt/onroad/model.cc | 143 ++++++++++++--------- selfdrive/ui/sunnypilot/qt/onroad/model.h | 14 +- 4 files changed, 88 insertions(+), 76 deletions(-) diff --git a/selfdrive/ui/qt/onroad/model.cc b/selfdrive/ui/qt/onroad/model.cc index a4165be216..176af56613 100644 --- a/selfdrive/ui/qt/onroad/model.cc +++ b/selfdrive/ui/qt/onroad/model.cc @@ -22,7 +22,7 @@ void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) { update_model(model, lead_one); drawLaneLines(painter); - drawPath(painter, model, surface_rect); + drawPath(painter, model, surface_rect.height()); if (longitudinal_control && sm.alive("radarState")) { update_leads(radar_state, model.getPosition()); @@ -173,7 +173,6 @@ QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float (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.; diff --git a/selfdrive/ui/qt/onroad/model.h b/selfdrive/ui/qt/onroad/model.h index 3ef8ba5320..0a58d345c4 100644 --- a/selfdrive/ui/qt/onroad/model.h +++ b/selfdrive/ui/qt/onroad/model.h @@ -39,9 +39,6 @@ protected: virtual 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); - virtual void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &surface_rect) {; - drawPath(painter, model, surface_rect.height()); - } void updatePathGradient(QLinearGradient &bg); QColor blendColors(const QColor &start, const QColor &end, float t); @@ -58,5 +55,4 @@ protected: QPointF lead_vertices[2] = {}; Eigen::Matrix3f car_space_transform = Eigen::Matrix3f::Zero(); QRectF clip_region; - }; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/model.cc b/selfdrive/ui/sunnypilot/qt/onroad/model.cc index 14943b1d48..086b703e10 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/model.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/model.cc @@ -21,74 +21,63 @@ void ModelRendererSP::update_model(const cereal::ModelDataV2::Reader &model, con mapLineToPolygon(model.getLaneLines()[2], 0.2, -0.05, &right_blindspot_vertices, max_idx_barrier); } -void ModelRendererSP::drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &surface_rect) { +void ModelRendererSP::draw(QPainter &painter, const QRect &surface_rect) { + ModelRenderer::draw(painter, surface_rect); auto *s = uiState(); auto &sm = *(s->sm); - bool blindspot = s->scene.blindspot_ui; - if (blindspot) { - bool left_blindspot = sm["carState"].getCarState().getLeftBlindspot(); - bool right_blindspot = sm["carState"].getCarState().getRightBlindspot(); - - //painter.setBrush(QColor::fromRgbF(1.0, 0.0, 0.0, 0.4)); // Red with alpha for blind spot - - if (left_blindspot && !left_blindspot_vertices.isEmpty()) { - QLinearGradient gradient(0, 0, surface_rect.width(), 0); // Horizontal gradient from left to right - gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha - gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha - painter.setBrush(gradient); - painter.drawPolygon(left_blindspot_vertices); - } - - if (right_blindspot && !right_blindspot_vertices.isEmpty()) { - QLinearGradient gradient(surface_rect.width(), 0, 0, 0); // Horizontal gradient from right to left - gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha - gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha - painter.setBrush(gradient); - painter.drawPolygon(right_blindspot_vertices); - } + if (sm.rcv_frame("liveCalibration") < s->scene.started_frame || + sm.rcv_frame("modelV2") < s->scene.started_frame) { + return; } + painter.save(); + + const auto &model = sm["modelV2"].getModelV2(); + const auto &radar_state = sm["radarState"].getRadarState(); + const auto &lead_one = radar_state.getLeadOne(); + const auto &car_state = sm["carState"].getCarState(); + + update_model(model, lead_one); + drawLaneLines(painter); + + bool blindspot = s->scene.blindspot_ui; bool rainbow = s->scene.rainbow_mode; - //float v_ego = sm["carState"].getCarState().getVEgo(); + + bool left_blindspot = car_state.getLeftBlindspot(); + bool right_blindspot = car_state.getRightBlindspot(); + + if (blindspot) { + drawBlindspot(painter, surface_rect, left_blindspot, right_blindspot); + } if (rainbow) { - // Simple time-based animation - float time_offset = std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count() / 1000.0f; - - // simple linear gradient from bottom to top - QLinearGradient bg(0, surface_rect.height(), 0, 0); - - // evenly spaced colors across the spectrum - // The animation shifts the entire spectrum smoothly - float animation_speed = 40.0f; // speed vroom vroom - float hue_offset = fmod(time_offset * animation_speed, 360.0f); - - // 6-8 color stops for smooth transitions more color makes it laggy - const int num_stops = 7; - for (int i = 0; i < num_stops; i++) { - float position = static_cast(i) / (num_stops - 1); - - float hue = fmod(hue_offset + position * 360.0f, 360.0f); - float saturation = 0.9f; - float lightness = 0.6f; - - // Alpha fades out towards the far end of the path - float alpha = 0.8f * (1.0f - position * 0.3f); - - QColor color = QColor::fromHslF(hue / 360.0f, saturation, lightness, alpha); - bg.setColorAt(position, color); - } - - painter.setBrush(bg); - painter.drawPolygon(track_vertices); + drawRainbowPath(painter, surface_rect); } else { - // Normal path rendering ModelRenderer::drawPath(painter, model, surface_rect.height()); } drawLeadStatus(painter, surface_rect.height(), surface_rect.width()); + + painter.restore(); +} + +void ModelRendererSP::drawBlindspot(QPainter &painter, const QRect &surface_rect, bool left_blindspot, bool right_blindspot) { + if (left_blindspot && !left_blindspot_vertices.isEmpty()) { + QLinearGradient gradient(0, 0, surface_rect.width(), 0); // Horizontal gradient from left to right + gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha + gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha + painter.setBrush(gradient); + painter.drawPolygon(left_blindspot_vertices); + } + + if (right_blindspot && !right_blindspot_vertices.isEmpty()) { + QLinearGradient gradient(surface_rect.width(), 0, 0, 0); // Horizontal gradient from right to left + gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha + gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha + painter.setBrush(gradient); + painter.drawPolygon(right_blindspot_vertices); + } } void ModelRendererSP::drawLeadStatus(QPainter &painter, int height, int width) { @@ -121,19 +110,16 @@ void ModelRendererSP::drawLeadStatus(QPainter &painter, int height, int width) { } if (has_lead_one) { - drawLeadStatusAtPosition(painter, lead_one, lead_vertices[0], height, width, "L1"); + drawLeadStatusPosition(painter, lead_one, lead_vertices[0], height, width); } if (has_lead_two && std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0) { - drawLeadStatusAtPosition(painter, lead_two, lead_vertices[1], height, width, "L2"); + drawLeadStatusPosition(painter, lead_two, lead_vertices[1], height, width); } } -void ModelRendererSP::drawLeadStatusAtPosition(QPainter &painter, - const cereal::RadarState::LeadData::Reader &lead_data, - const QPointF &chevron_pos, - int height, int width, - const QString &label) { +void ModelRendererSP::drawLeadStatusPosition(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, + const QPointF &chevron_pos, int height, int width) { float d_rel = lead_data.getDRel(); float v_rel = lead_data.getVRel(); auto *s = uiState(); @@ -223,3 +209,36 @@ void ModelRendererSP::drawLeadStatusAtPosition(QPainter &painter, painter.setPen(Qt::NoPen); } + +void ModelRendererSP::drawRainbowPath(QPainter &painter, const QRect &surface_rect) { + // Simple time-based animation + float time_offset = std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count() / 1000.0f; + + // simple linear gradient from bottom to top + QLinearGradient bg(0, surface_rect.height(), 0, 0); + + // evenly spaced colors across the spectrum + // The animation shifts the entire spectrum smoothly + float animation_speed = 40.0f; // speed vroom vroom + float hue_offset = fmod(time_offset * animation_speed, 360.0f); + + // 6-8 color stops for smooth transitions more color makes it laggy + const int num_stops = 7; + for (int i = 0; i < num_stops; i++) { + float position = static_cast(i) / (num_stops - 1); + + float hue = fmod(hue_offset + position * 360.0f, 360.0f); + float saturation = 0.9f; + float lightness = 0.6f; + + // Alpha fades out towards the far end of the path + float alpha = 0.8f * (1.0f - position * 0.3f); + + QColor color = QColor::fromHslF(hue / 360.0f, saturation, lightness, alpha); + bg.setColorAt(position, color); + } + + painter.setBrush(bg); + painter.drawPolygon(track_vertices); +} diff --git a/selfdrive/ui/sunnypilot/qt/onroad/model.h b/selfdrive/ui/sunnypilot/qt/onroad/model.h index 73999f0059..68068f2068 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/model.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/model.h @@ -13,17 +13,15 @@ class ModelRendererSP : public ModelRenderer { public: ModelRendererSP() = default; + void draw(QPainter &painter, const QRect &surface_rect); + private: void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead) override; - void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &rect) override; - - // Lead status display methods void drawLeadStatus(QPainter &painter, int height, int width); - void drawLeadStatusAtPosition(QPainter &painter, - const cereal::RadarState::LeadData::Reader &lead_data, - const QPointF &chevron_pos, - int height, int width, - const QString &label); + void drawLeadStatusPosition(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, + const QPointF &chevron_pos, int height, int width); + void drawBlindspot(QPainter &painter, const QRect &surface_rect, bool left_blindspot, bool right_blindspot); + void drawRainbowPath(QPainter &painter, const QRect &surface_rect); QPolygonF left_blindspot_vertices; QPolygonF right_blindspot_vertices; From 7229c7541eac0fc321252d4c53a98534ec1fbb07 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon, 13 Oct 2025 19:02:53 -0700 Subject: [PATCH 181/681] capnp: consolidate TurnDirection enum (#1370) Co-authored-by: Jason Wen --- cereal/custom.capnp | 10 +++++----- selfdrive/controls/lib/desire_helper.py | 10 +++++----- sunnypilot/selfdrive/controls/lib/lane_turn_desire.py | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 833845d36d..20f0984620 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -404,12 +404,12 @@ struct LiveMapDataSP @0xf416ec09499d9d19 { struct ModelDataV2SP @0xa1680744031fdb2d { laneTurnDirection @0 :TurnDirection; -} -enum TurnDirection { - none @0; - turnLeft @1; - turnRight @2; + enum TurnDirection { + none @0; + turnLeft @1; + turnRight @2; + } } struct CustomReserved10 @0xcb9fd56c7057593a { diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index e72d464d06..bf24fe2602 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -32,9 +32,9 @@ DESIRES = { } TURN_DESIRES = { - custom.TurnDirection.none: log.Desire.none, - custom.TurnDirection.turnLeft: log.Desire.turnLeft, - custom.TurnDirection.turnRight: log.Desire.turnRight, + custom.ModelDataV2SP.TurnDirection.none: log.Desire.none, + custom.ModelDataV2SP.TurnDirection.turnLeft: log.Desire.turnLeft, + custom.ModelDataV2SP.TurnDirection.turnRight: log.Desire.turnRight, } @@ -49,7 +49,7 @@ class DesireHelper: self.desire = log.Desire.none self.alc = AutoLaneChangeController(self) self.lane_turn_controller = LaneTurnController(self) - self.lane_turn_direction = custom.TurnDirection.none + self.lane_turn_direction = custom.ModelDataV2SP.TurnDirection.none @staticmethod def get_lane_change_direction(CS): @@ -126,7 +126,7 @@ class DesireHelper: self.prev_one_blinker = one_blinker - if self.lane_turn_direction != custom.TurnDirection.none: + if self.lane_turn_direction != custom.ModelDataV2SP.TurnDirection.none: self.desire = TURN_DESIRES[self.lane_turn_direction] else: self.desire = DESIRES[self.lane_change_direction][self.lane_change_state] diff --git a/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py b/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py index 00ce026abb..7767fdae74 100644 --- a/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py +++ b/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py @@ -15,7 +15,7 @@ LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS class LaneTurnController: def __init__(self, desire_helper): self.DH = desire_helper - self.turn_direction = custom.TurnDirection.none + self.turn_direction = custom.ModelDataV2SP.TurnDirection.none self.params = Params() self.lane_turn_value = float(self.params.get("LaneTurnValue", return_default=True)) * CV.MPH_TO_MS self.param_read_counter = 0 @@ -33,13 +33,13 @@ class LaneTurnController: def update_lane_turn(self, blindspot_left: bool, blindspot_right: bool, left_blinker: bool, right_blinker: bool, v_ego: float) -> None: if left_blinker and not right_blinker and v_ego < self.lane_turn_value and not blindspot_left: - self.turn_direction = custom.TurnDirection.turnLeft + self.turn_direction = custom.ModelDataV2SP.TurnDirection.turnLeft elif right_blinker and not left_blinker and v_ego < self.lane_turn_value and not blindspot_right: - self.turn_direction = custom.TurnDirection.turnRight + self.turn_direction = custom.ModelDataV2SP.TurnDirection.turnRight else: - self.turn_direction = custom.TurnDirection.none + self.turn_direction = custom.ModelDataV2SP.TurnDirection.none def get_turn_direction(self): if not self.enabled: - return custom.TurnDirection.none + return custom.ModelDataV2SP.TurnDirection.none return self.turn_direction From 59c64acc2901e7156fddadc9194e99e3fa44ebd5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 13 Oct 2025 22:26:47 -0400 Subject: [PATCH 182/681] Subaru: Stop and Go support (beta) (#1375) * Subaru: Stop and Go auto-resume support * bump * bump * fix * bump * fix init * wat * use just standstill for now * Revert "use just standstill for now" This reverts commit f72cce68921ffe15b8914ce42d213758a1807619. * bump * bump * fix it * only send at 10 * bump * fix type * forget about planner resume, it sucks * try to send off_accel * still need it * always send * disable safety checks for now * same * more * all the time for both * don't need i guess * bump * try 15 frames per try * all should have it * try 3 for all * use throttle for all preglobal? * bump * bump * separate thresholds between preglobal and global * longer wait before sending * shorter time but immediately resend * quick * new timeout * about to cry * same thing but another try * no need * round 3 * try 1.4 * lower! * 1.2 * last try * beta asf * bump --- common/params_keys.h | 2 + opendbc_repo | 2 +- .../settings/vehicle/subaru_settings.cc | 45 +++++++++++++++++++ .../settings/vehicle/subaru_settings.h | 31 +++++++++++++ sunnypilot/selfdrive/car/interfaces.py | 6 +++ 5 files changed, 85 insertions(+), 1 deletion(-) diff --git a/common/params_keys.h b/common/params_keys.h index 3b5d02a429..ecb73a9456 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -205,6 +205,8 @@ inline static std::unordered_map keys = { // sunnypilot car specific params {"HyundaiLongitudinalTuning", {PERSISTENT | BACKUP, INT, "0"}}, + {"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}}, + {"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}}, {"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}}, {"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}}, diff --git a/opendbc_repo b/opendbc_repo index b592ecdd3b..b8a00bddda 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit b592ecdd3b571a1acee0c04726117a137cec5832 +Subproject commit b8a00bddda562f981b24e099a3850209579e890a diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.cc index 47c4057f44..302740a94a 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.cc @@ -8,7 +8,52 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.h" SubaruSettings::SubaruSettings(QWidget *parent) : BrandSettingsInterface(parent) { + stopAndGoToggle = new ParamControl("SubaruStopAndGo", tr("Stop and Go (Beta)"), "", ""); + stopAndGoToggle->setConfirmation(true, false); + list->addItem(stopAndGoToggle); + + stopAndGoManualParkingBrakeToggle = new ParamControl( + "SubaruStopAndGoManualParkingBrake", + tr("Stop and Go for Manual Parking Brake (Beta)"), + "", + "" + ); + stopAndGoManualParkingBrakeToggle->setConfirmation(true, false); + list->addItem(stopAndGoManualParkingBrakeToggle); } void SubaruSettings::updateSettings() { + 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(); + + is_subaru = CP.getBrand() == "subaru"; + + if (is_subaru) { + if (!(CP.getFlags() & (SUBARU_FLAG_GLOBAL_GEN2 | SUBARU_FLAG_HYBRID))) { + has_stop_and_go = true; + } + } + } else { + is_subaru = false; + has_stop_and_go = false; + } + + bool stop_and_go_disabled = !offroad || !has_stop_and_go; + QString stop_and_go_desc = stopAndGoDescriptionBuilder(stopAndGoDesc); + QString stop_and_go_manual_parking_brake_desc = stopAndGoDescriptionBuilder(stopAndGoManualParkingBrakeDesc); + if (stop_and_go_disabled) { + stop_and_go_desc = stopAndGoDescriptionBuilder(stopAndGoDesc, stopAndGoDisabledMsg()); + stop_and_go_manual_parking_brake_desc = stopAndGoDescriptionBuilder(stopAndGoManualParkingBrakeDesc, stopAndGoDisabledMsg()); + } + + stopAndGoToggle->setEnabled(has_stop_and_go); + stopAndGoToggle->setDescription(stop_and_go_desc); + stopAndGoToggle->showDescription(); + + stopAndGoManualParkingBrakeToggle->setEnabled(has_stop_and_go); + stopAndGoManualParkingBrakeToggle->setDescription(stop_and_go_manual_parking_brake_desc); + stopAndGoManualParkingBrakeToggle->showDescription(); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.h index a715951ad9..2bb160beba 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.h @@ -14,6 +14,9 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h" #include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" +const int SUBARU_FLAG_GLOBAL_GEN2 = 4; +const int SUBARU_FLAG_HYBRID = 32; + class SubaruSettings : public BrandSettingsInterface { Q_OBJECT @@ -23,4 +26,32 @@ public: private: bool offroad = false; + bool is_subaru; + bool has_stop_and_go; + + ParamControl* stopAndGoToggle; + ParamControl* stopAndGoManualParkingBrakeToggle; + + QString stopAndGoDesc = tr("Experimental feature to enable auto-resume during stop-and-go for certain supported Subaru platforms."); + QString stopAndGoManualParkingBrakeDesc = 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!"); + + QString stopAndGoDisabledMsg() const { + if (is_subaru && !has_stop_and_go) { + return tr("This feature is currently not available on this platform."); + } + + if (!is_subaru) { + return tr("Start the car to check car compatibility."); + } + + if (!offroad) { + return tr("Enable \"Always Offroad\" in Device panel, or turn vehicle off to toggle."); + } + + return QString(); + } + + static QString stopAndGoDescriptionBuilder(const QString &base_description, const QString &custom_description = "") { + return "" + custom_description + "

" + base_description; + } }; diff --git a/sunnypilot/selfdrive/car/interfaces.py b/sunnypilot/selfdrive/car/interfaces.py index ed0c11fb19..3072cde8cd 100644 --- a/sunnypilot/selfdrive/car/interfaces.py +++ b/sunnypilot/selfdrive/car/interfaces.py @@ -84,4 +84,10 @@ def initialize_params(params) -> list[dict[str, Any]]: "HyundaiLongitudinalTuning" ]) + # subaru + keys.extend([ + "SubaruStopAndGo", + "SubaruStopAndGoManualParkingBrake", + ]) + return [{k: params.get(k, return_default=True)} for k in keys] From 339bc0b8b3c4a192aa4e982039bc2ac28246c080 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 14 Oct 2025 00:19:21 -0400 Subject: [PATCH 183/681] Revert "capnp: consolidate TurnDirection enum" (#1376) Revert "capnp: consolidate TurnDirection enum (#1370)" This reverts commit 7229c7541eac0fc321252d4c53a98534ec1fbb07. --- cereal/custom.capnp | 10 +++++----- selfdrive/controls/lib/desire_helper.py | 10 +++++----- sunnypilot/selfdrive/controls/lib/lane_turn_desire.py | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 20f0984620..833845d36d 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -404,12 +404,12 @@ struct LiveMapDataSP @0xf416ec09499d9d19 { struct ModelDataV2SP @0xa1680744031fdb2d { laneTurnDirection @0 :TurnDirection; +} - enum TurnDirection { - none @0; - turnLeft @1; - turnRight @2; - } +enum TurnDirection { + none @0; + turnLeft @1; + turnRight @2; } struct CustomReserved10 @0xcb9fd56c7057593a { diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index bf24fe2602..e72d464d06 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -32,9 +32,9 @@ DESIRES = { } TURN_DESIRES = { - custom.ModelDataV2SP.TurnDirection.none: log.Desire.none, - custom.ModelDataV2SP.TurnDirection.turnLeft: log.Desire.turnLeft, - custom.ModelDataV2SP.TurnDirection.turnRight: log.Desire.turnRight, + custom.TurnDirection.none: log.Desire.none, + custom.TurnDirection.turnLeft: log.Desire.turnLeft, + custom.TurnDirection.turnRight: log.Desire.turnRight, } @@ -49,7 +49,7 @@ class DesireHelper: self.desire = log.Desire.none self.alc = AutoLaneChangeController(self) self.lane_turn_controller = LaneTurnController(self) - self.lane_turn_direction = custom.ModelDataV2SP.TurnDirection.none + self.lane_turn_direction = custom.TurnDirection.none @staticmethod def get_lane_change_direction(CS): @@ -126,7 +126,7 @@ class DesireHelper: self.prev_one_blinker = one_blinker - if self.lane_turn_direction != custom.ModelDataV2SP.TurnDirection.none: + if self.lane_turn_direction != custom.TurnDirection.none: self.desire = TURN_DESIRES[self.lane_turn_direction] else: self.desire = DESIRES[self.lane_change_direction][self.lane_change_state] diff --git a/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py b/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py index 7767fdae74..00ce026abb 100644 --- a/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py +++ b/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py @@ -15,7 +15,7 @@ LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS class LaneTurnController: def __init__(self, desire_helper): self.DH = desire_helper - self.turn_direction = custom.ModelDataV2SP.TurnDirection.none + self.turn_direction = custom.TurnDirection.none self.params = Params() self.lane_turn_value = float(self.params.get("LaneTurnValue", return_default=True)) * CV.MPH_TO_MS self.param_read_counter = 0 @@ -33,13 +33,13 @@ class LaneTurnController: def update_lane_turn(self, blindspot_left: bool, blindspot_right: bool, left_blinker: bool, right_blinker: bool, v_ego: float) -> None: if left_blinker and not right_blinker and v_ego < self.lane_turn_value and not blindspot_left: - self.turn_direction = custom.ModelDataV2SP.TurnDirection.turnLeft + self.turn_direction = custom.TurnDirection.turnLeft elif right_blinker and not left_blinker and v_ego < self.lane_turn_value and not blindspot_right: - self.turn_direction = custom.ModelDataV2SP.TurnDirection.turnRight + self.turn_direction = custom.TurnDirection.turnRight else: - self.turn_direction = custom.ModelDataV2SP.TurnDirection.none + self.turn_direction = custom.TurnDirection.none def get_turn_direction(self): if not self.enabled: - return custom.ModelDataV2SP.TurnDirection.none + return custom.TurnDirection.none return self.turn_direction From 7f5342f3784c841b275fcbf213ef5e339f1fe991 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 14 Oct 2025 01:13:20 -0400 Subject: [PATCH 184/681] soundd: custom audible alerts (#1377) * Revert "capnp: consolidate TurnDirection enum (#1370)" This reverts commit 7229c7541eac0fc321252d4c53a98534ec1fbb07. * soundd: custom audible alerts * comment --- cereal/custom.capnp | 39 +++++++++++++++++++++++++++++++++++++++ selfdrive/ui/soundd.py | 9 ++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 833845d36d..5243380d31 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -69,6 +69,45 @@ struct LeadData { struct SelfdriveStateSP @0x81c2f05a394cf4af { mads @0 :ModularAssistiveDrivingSystem; intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement; + + enum AudibleAlert { + none @0; + + engage @1; + disengage @2; + refuse @3; + + warningSoft @4; + warningImmediate @5; + + prompt @6; + promptRepeat @7; + promptDistracted @8; + + # unused, these are reserved for upstream events so we don't collide + reserved9 @9; + reserved10 @10; + reserved11 @11; + reserved12 @12; + reserved13 @13; + reserved14 @14; + reserved15 @15; + reserved16 @16; + reserved17 @17; + reserved18 @18; + reserved19 @19; + reserved20 @20; + reserved21 @21; + reserved22 @22; + reserved23 @23; + reserved24 @24; + reserved25 @25; + reserved26 @26; + reserved27 @27; + reserved28 @28; + reserved29 @29; + reserved30 @30; + } } struct ModelManagerSP @0xaedffd8f31e7b55d { diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index a94456efe0..485c406266 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -4,7 +4,7 @@ import time import wave -from cereal import car, messaging +from cereal import car, messaging, custom from openpilot.common.basedir import BASEDIR from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import Ratekeeper @@ -26,8 +26,13 @@ AMBIENT_DB = 30 # DB where MIN_VOLUME is applied DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied AudibleAlert = car.CarControl.HUDControl.AudibleAlert +AudibleAlertSP = custom.SelfdriveStateSP.AudibleAlert +sound_list_sp: dict[int, tuple[str, int | None, float]] = { + # AudibleAlertSP, file name, play count (none for infinite) +} + sound_list: dict[int, tuple[str, int | None, float]] = { # AudibleAlert, file name, play count (none for infinite) AudibleAlert.engage: ("engage.wav", 1, MAX_VOLUME), @@ -40,6 +45,8 @@ 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), + + **sound_list_sp, } def check_selfdrive_timeout_alert(sm): From f4041dc1f0ffbdd261fdc342d312e6a0f96c7c1e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 14 Oct 2025 00:33:38 -0700 Subject: [PATCH 185/681] 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 4ba7f2aa13..9337b3c239 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 186/681] 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 40a649899d..97c399cdab 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 1e31ac8a1a..e779ead334 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 dfaa3e4008..f786d1ab2f 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 187/681] 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 70960ee22f..519e0e020a 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 97c399cdab..23031be306 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 188/681] 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 bccb36712c..f4ab1a05c6 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 1925122d0b..4a62c996c7 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 4bd020e92b0ee7fe678985efdb7cd2efd6ecf9cd Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 14 Oct 2025 09:19:26 -0400 Subject: [PATCH 189/681] Speed Limit Assist: audible alerts for certain states (#1378) --- cereal/custom.capnp | 3 +++ selfdrive/assets/sounds/prompt_single_high.wav | 3 +++ selfdrive/assets/sounds/prompt_single_low.wav | 3 +++ selfdrive/ui/soundd.py | 2 ++ sunnypilot/selfdrive/selfdrived/events.py | 9 +++++---- 5 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 selfdrive/assets/sounds/prompt_single_high.wav create mode 100644 selfdrive/assets/sounds/prompt_single_low.wav diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 5243380d31..e81749dbac 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -107,6 +107,9 @@ struct SelfdriveStateSP @0x81c2f05a394cf4af { reserved28 @28; reserved29 @29; reserved30 @30; + + promptSingleLow @31; + promptSingleHigh @32; } } diff --git a/selfdrive/assets/sounds/prompt_single_high.wav b/selfdrive/assets/sounds/prompt_single_high.wav new file mode 100644 index 0000000000..202483d17f --- /dev/null +++ b/selfdrive/assets/sounds/prompt_single_high.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbfa5858c0a672411ffdc691efdecb06d01ae458cc1df409bcf3fdeaa4756f72 +size 34638 diff --git a/selfdrive/assets/sounds/prompt_single_low.wav b/selfdrive/assets/sounds/prompt_single_low.wav new file mode 100644 index 0000000000..925401ea27 --- /dev/null +++ b/selfdrive/assets/sounds/prompt_single_low.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db9671bb03e01f119bba1eb6cc0507e0f039ac4e5b7f9f839a87071c52e86e56 +size 44416 diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index 485c406266..01c7c68949 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -31,6 +31,8 @@ AudibleAlertSP = custom.SelfdriveStateSP.AudibleAlert sound_list_sp: dict[int, tuple[str, int | None, float]] = { # AudibleAlertSP, file name, play count (none for infinite) + AudibleAlertSP.promptSingleLow: ("prompt_single_low.wav", 1, MAX_VOLUME), + AudibleAlertSP.promptSingleHigh: ("prompt_single_high.wav", 1, MAX_VOLUME), } sound_list: dict[int, tuple[str, int | None, float]] = { diff --git a/sunnypilot/selfdrive/selfdrived/events.py b/sunnypilot/selfdrive/selfdrived/events.py index e9f4b29bae..5cd7255bc3 100644 --- a/sunnypilot/selfdrive/selfdrived/events.py +++ b/sunnypilot/selfdrive/selfdrived/events.py @@ -11,6 +11,7 @@ AlertSize = log.SelfdriveState.AlertSize AlertStatus = log.SelfdriveState.AlertStatus VisualAlert = car.CarControl.HUDControl.VisualAlert AudibleAlert = car.CarControl.HUDControl.AudibleAlert +AudibleAlertSP = custom.SelfdriveStateSP.AudibleAlert EventNameSP = custom.OnroadEventSP.EventName @@ -58,7 +59,7 @@ def speed_limit_pre_active_alert(CP: car.CarParams, CS: car.CarState, sm: messag "Speed Limit Assist: Activation Required", alert_2_str, AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.none, .1) + Priority.LOW, VisualAlert.none, AudibleAlertSP.promptSingleLow, .1) class EventsSP(EventsBase): @@ -202,7 +203,7 @@ EVENTS_SP: dict[int, dict[str, Alert | AlertCallbackType]] = { "Automatically adjusting to the posted speed limit", "", AlertStatus.normal, AlertSize.small, - Priority.LOW, VisualAlert.none, AudibleAlert.none, 5.), + Priority.LOW, VisualAlert.none, AudibleAlertSP.promptSingleHigh, 5.), }, EventNameSP.speedLimitChanged: { @@ -210,7 +211,7 @@ EVENTS_SP: dict[int, dict[str, Alert | AlertCallbackType]] = { "Set speed changed", "", AlertStatus.normal, AlertSize.small, - Priority.LOW, VisualAlert.none, AudibleAlert.none, 5.), + Priority.LOW, VisualAlert.none, AudibleAlertSP.promptSingleHigh, 5.), }, EventNameSP.speedLimitPreActive: { @@ -222,7 +223,7 @@ EVENTS_SP: dict[int, dict[str, Alert | AlertCallbackType]] = { "Automatically adjusting to the last speed limit", "", AlertStatus.normal, AlertSize.small, - Priority.LOW, VisualAlert.none, AudibleAlert.none, 5.), + Priority.LOW, VisualAlert.none, AudibleAlertSP.promptSingleHigh, 5.), }, EventNameSP.e2eChime: { From fec6382b964c304d6729ad1b26913377760aa949 Mon Sep 17 00:00:00 2001 From: Nayan Date: Tue, 14 Oct 2025 11:29:27 -0400 Subject: [PATCH 190/681] UI: Fix Speed Limit Assist (SLA) Translations (#1379) Fix SLA Translations --- .../longitudinal/speed_limit/helpers.h | 24 ++-- .../speed_limit/speed_limit_policy.cc | 10 +- .../speed_limit/speed_limit_settings.cc | 14 +-- selfdrive/ui/translations/main_ko.ts | 112 +++++++++--------- 4 files changed, 80 insertions(+), 80 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/helpers.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/helpers.h index 295072d1ef..6c02d627fa 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/helpers.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/helpers.h @@ -13,9 +13,9 @@ enum class SpeedLimitOffsetType { }; inline const QString SpeedLimitOffsetTypeTexts[]{ - QObject::tr("None"), - QObject::tr("Fixed"), - QObject::tr("Percent"), + QT_TRANSLATE_NOOP("SpeedLimitSettings", "None"), + QT_TRANSLATE_NOOP("SpeedLimitSettings", "Fixed"), + QT_TRANSLATE_NOOP("SpeedLimitSettings", "Percent"), }; enum class SpeedLimitSourcePolicy { @@ -27,11 +27,11 @@ enum class SpeedLimitSourcePolicy { }; inline const QString SpeedLimitSourcePolicyTexts[]{ - QObject::tr("Car\nOnly"), - QObject::tr("Map\nOnly"), - QObject::tr("Car\nFirst"), - QObject::tr("Map\nFirst"), - QObject::tr("Combined\nData") + QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Car\nOnly"), + QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Map\nOnly"), + QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Car\nFirst"), + QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Map\nFirst"), + QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Combined\nData") }; enum class SpeedLimitMode { @@ -42,8 +42,8 @@ enum class SpeedLimitMode { }; inline const QString SpeedLimitModeTexts[]{ - QObject::tr("Off"), - QObject::tr("Information"), - QObject::tr("Warning"), - QObject::tr("Assist"), + QT_TRANSLATE_NOOP("SpeedLimitSettings", "Off"), + QT_TRANSLATE_NOOP("SpeedLimitSettings", "Information"), + QT_TRANSLATE_NOOP("SpeedLimitSettings", "Warning"), + QT_TRANSLATE_NOOP("SpeedLimitSettings", "Assist"), }; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_policy.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_policy.cc index 764a8e0208..c54ff11f69 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_policy.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_policy.cc @@ -23,11 +23,11 @@ SpeedLimitPolicy::SpeedLimitPolicy(QWidget *parent) : QWidget(parent) { ListWidgetSP *list = new ListWidgetSP(this); std::vector speed_limit_policy_texts{ - SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::CAR_ONLY)], - SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::MAP_ONLY)], - SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::CAR_FIRST)], - SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::MAP_FIRST)], - SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::COMBINED)] + tr(SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::CAR_ONLY)].toStdString().c_str()), + tr(SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::MAP_ONLY)].toStdString().c_str()), + tr(SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::CAR_FIRST)].toStdString().c_str()), + tr(SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::MAP_FIRST)].toStdString().c_str()), + tr(SpeedLimitSourcePolicyTexts[static_cast(SpeedLimitSourcePolicy::COMBINED)].toStdString().c_str()) }; speed_limit_policy = new ButtonParamControlSP( "SpeedLimitPolicy", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc index 3efbfeed86..f64198f97e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc @@ -25,10 +25,10 @@ SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent) speedLimitPolicyScreen = new SpeedLimitPolicy(this); std::vector speed_limit_mode_texts{ - SpeedLimitModeTexts[static_cast(SpeedLimitMode::OFF)], - SpeedLimitModeTexts[static_cast(SpeedLimitMode::INFORMATION)], - SpeedLimitModeTexts[static_cast(SpeedLimitMode::WARNING)], - SpeedLimitModeTexts[static_cast(SpeedLimitMode::ASSIST)], + tr(SpeedLimitModeTexts[static_cast(SpeedLimitMode::OFF)].toStdString().c_str()), + tr(SpeedLimitModeTexts[static_cast(SpeedLimitMode::INFORMATION)].toStdString().c_str()), + tr(SpeedLimitModeTexts[static_cast(SpeedLimitMode::WARNING)].toStdString().c_str()), + tr(SpeedLimitModeTexts[static_cast(SpeedLimitMode::ASSIST)].toStdString().c_str()) }; speed_limit_mode_settings = new ButtonParamControlSP( "SpeedLimitMode", @@ -64,9 +64,9 @@ SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent) QVBoxLayout *offsetLayout = new QVBoxLayout(offsetFrame); std::vector speed_limit_offset_texts{ - SpeedLimitOffsetTypeTexts[static_cast(SpeedLimitOffsetType::NONE)], - SpeedLimitOffsetTypeTexts[static_cast(SpeedLimitOffsetType::FIXED)], - SpeedLimitOffsetTypeTexts[static_cast(SpeedLimitOffsetType::PERCENT)] + tr(SpeedLimitOffsetTypeTexts[static_cast(SpeedLimitOffsetType::NONE)].toStdString().c_str()), + tr(SpeedLimitOffsetTypeTexts[static_cast(SpeedLimitOffsetType::FIXED)].toStdString().c_str()), + tr(SpeedLimitOffsetTypeTexts[static_cast(SpeedLimitOffsetType::PERCENT)].toStdString().c_str()) }; speed_limit_offset_settings = new ButtonParamControlSP( "SpeedLimitOffsetType", diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index d86468ae6e..164ce09f07 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -1778,62 +1778,6 @@ Warning: You are on a metered connection! sunnypilot sunnypilot - - None - 없음 - - - Fixed - 고정 - - - Percent - 비율 - - - Car -Only - 차량만 - - - Map -Only - 지도만 - - - Car -First - 차량 -우선 - - - Map -First - 지도 -우선 - - - Combined -Data - 결합 -데이터 - - - Off - 끄기 - - - Information - 정보 - - - Warning - 경고 - - - Assist - 보조 -
SettingsWindow @@ -2198,6 +2142,34 @@ Data ⦿ Combined: Use combined Speed Limit data from Car & OpenStreetMaps ⦿ 결합: 차량 및 OpenStreetMaps의 속도 제한 결합 데이터 사용 + + Car +Only + 차량만 + + + Map +Only + 지도만 + + + Car +First + 차량 +우선 + + + Map +First + 지도 +우선 + + + Combined +Data + 결합 +데이터 + SpeedLimitSettings @@ -2245,6 +2217,34 @@ Data ⦿ Assist: Adjusts the vehicle's cruise speed based on the current road's speed limit when operating the +/- buttons. ⦿ 보조: +/- 버튼을 조작할 때 현재 도로의 제한 속도를 기준으로 차량의 크루즈 속도를 조정합니다. + + None + 없음 + + + Fixed + 고정 + + + Percent + 비율 + + + Off + 끄기 + + + Information + 정보 + + + Warning + 경고 + + + Assist + 보조 + SshControl From d3e3628a9586f131b23a1c1cac9520c21a3626ff Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 14 Oct 2025 11:40:42 -0400 Subject: [PATCH 191/681] ui: only draw ahead speed limit if it's parsed from OSM (#1380) --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 4 +++- selfdrive/ui/sunnypilot/qt/onroad/hud.h | 1 + .../controls/lib/speed_limit/speed_limit_resolver.py | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index 8c913cec17..fa57e2b334 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -46,6 +46,7 @@ void HudRendererSP::updateState(const UIState &s) { speedLimitValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitValid(); speedLimitLastValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLastValid(); speedLimitFinalLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitFinalLast() * speedConv; + speedLimitSource = lp_sp.getSpeedLimit().getResolver().getSource(); speedLimitMode = static_cast(s.scene.speed_limit_mode); speedLimitAssistState = lp_sp.getSpeedLimit().getAssist().getState(); speedLimitAssistActive = lp_sp.getSpeedLimit().getAssist().getActive(); @@ -547,7 +548,8 @@ void HudRendererSP::drawSpeedLimitSigns(QPainter &p, QRect &sign_rect) { } void HudRendererSP::drawUpcomingSpeedLimit(QPainter &p) { - bool speed_limit_ahead = speedLimitAheadValid && speedLimitAhead > 0 && speedLimitAhead != speedLimit && speedLimitAheadValidFrame > 0; + bool speed_limit_ahead = speedLimitAheadValid && speedLimitAhead > 0 && speedLimitAhead != speedLimit && speedLimitAheadValidFrame > 0 && + speedLimitSource == cereal::LongitudinalPlanSP::SpeedLimit::Source::MAP; if (!speed_limit_ahead) { return; } diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.h b/selfdrive/ui/sunnypilot/qt/onroad/hud.h index bc7ee81de3..f9135ee9ef 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.h @@ -83,6 +83,7 @@ private: bool speedLimitValid; bool speedLimitLastValid; float speedLimitFinalLast; + cereal::LongitudinalPlanSP::SpeedLimit::Source speedLimitSource; bool speedLimitAheadValid; float speedLimitAhead; float speedLimitAheadDistance; diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py index 459334d156..35965c0e18 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_resolver.py @@ -142,6 +142,7 @@ class SpeedLimitResolver: self.limit_solutions[SpeedLimitSource.map] = speed_limit self.distance_solutions[SpeedLimitSource.map] = 0. + # FIXME-SP: this is not working as expected if 0. < next_speed_limit < self.v_ego: adapt_time = (next_speed_limit - self.v_ego) / LIMIT_ADAPT_ACC adapt_distance = self.v_ego * adapt_time + 0.5 * LIMIT_ADAPT_ACC * adapt_time ** 2 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 192/681] 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 2a1b666efd..664e866636 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 9a14baac4deac77ef4d9171a3ce4af2754317035 Mon Sep 17 00:00:00 2001 From: Nayan Date: Tue, 14 Oct 2025 20:01:42 -0400 Subject: [PATCH 193/681] Green Light and Lead Departure alerts improvements (#1381) --- .../controls/lib/e2e_alerts_helper.py | 64 +++++++++++++++---- sunnypilot/selfdrive/selfdrived/events.py | 2 +- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py b/sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py index 0135b2806c..5a92d878d6 100644 --- a/sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py +++ b/sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py @@ -12,47 +12,83 @@ from openpilot.common.realtime import DT_MDL from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP -TRIGGER_THRESHOLD = 30 +GREEN_LIGHT_X_THRESHOLD = 30 class E2EAlertsHelper: def __init__(self): self._params = Params() - self._frame = -1 + self.frame = -1 self.green_light_alert = False self.green_light_alert_enabled = self._params.get_bool("GreenLightAlert") self.lead_depart_alert = False self.lead_depart_alert_enabled = self._params.get_bool("LeadDepartAlert") + self.alert_allowed = False + self.green_light_alert_count = 0 + self.last_lead_distance = -1 + self.last_moving_frame = -1 + def _read_params(self) -> None: - if self._frame % int(PARAMS_UPDATE_PERIOD / DT_MDL) == 0: + if self.frame % int(PARAMS_UPDATE_PERIOD / DT_MDL) == 0: self.green_light_alert_enabled = self._params.get_bool("GreenLightAlert") self.lead_depart_alert_enabled = self._params.get_bool("LeadDepartAlert") - self._frame += 1 - def update(self, sm: messaging.SubMaster, events_sp: EventsSP) -> None: self._read_params() - if not (self.green_light_alert_enabled or self.lead_depart_alert_enabled): - return - CS = sm['carState'] CC = sm['carControl'] model_x = sm['modelV2'].position.x max_idx = len(model_x) - 1 has_lead = sm['radarState'].leadOne.status - lead_vRel: float = sm['radarState'].leadOne.vRel + lead_dRel = sm['radarState'].leadOne.dRel + standstill = CS.standstill + moving = not standstill and CS.vEgo > 0.1 + _allowed = standstill and not CS.gasPressed and not CC.enabled - # Green light alert - self.green_light_alert = (self.green_light_alert_enabled and model_x[max_idx] > TRIGGER_THRESHOLD - and not has_lead and CS.standstill and not CS.gasPressed and not CC.enabled) + if moving: + self.last_moving_frame = self.frame + recent_moving = self.last_moving_frame == -1 or (self.frame - self.last_moving_frame) * DT_MDL < 2.0 + + if standstill and not recent_moving: + self.alert_allowed = True + elif not standstill: + self.alert_allowed = False + self.green_light_alert_count = 0 + self.last_lead_distance = -1 + + # Green Light Alert + _green_light_alert = False + if self.green_light_alert_enabled and _allowed and not has_lead and model_x[max_idx] > GREEN_LIGHT_X_THRESHOLD: + if self.alert_allowed: + self.green_light_alert_count += 1 + else: + self.green_light_alert_count = 0 + + if self.green_light_alert_count > 2 and self.alert_allowed: + _green_light_alert = True + self.alert_allowed = False + else: + self.green_light_alert_count = 0 + + self.green_light_alert = _green_light_alert # Lead Departure Alert - self.lead_depart_alert = (self.lead_depart_alert_enabled and CS.standstill and model_x[max_idx] > 30 - and has_lead and lead_vRel > 1 and not CS.gasPressed) + _lead_depart_alert = False + if self.lead_depart_alert_enabled and _allowed and has_lead: + if self.last_lead_distance == -1 or lead_dRel < self.last_lead_distance: + self.last_lead_distance = lead_dRel + + if self.last_lead_distance != -1 and (lead_dRel - self.last_lead_distance > 1.0) and self.alert_allowed: + _lead_depart_alert = True + self.alert_allowed = False + + self.lead_depart_alert = _lead_depart_alert if self.green_light_alert or self.lead_depart_alert: events_sp.add(custom.OnroadEventSP.EventName.e2eChime) + + self.frame += 1 diff --git a/sunnypilot/selfdrive/selfdrived/events.py b/sunnypilot/selfdrive/selfdrived/events.py index 5cd7255bc3..5d5424bb16 100644 --- a/sunnypilot/selfdrive/selfdrived/events.py +++ b/sunnypilot/selfdrive/selfdrived/events.py @@ -231,6 +231,6 @@ EVENTS_SP: dict[int, dict[str, Alert | AlertCallbackType]] = { "", "", AlertStatus.normal, AlertSize.none, - Priority.MID, VisualAlert.none, AudibleAlert.prompt, 0.1), + Priority.MID, VisualAlert.none, AudibleAlert.prompt, 3.), }, } From 734151f59bf535054719e0ffc9a9621bbe963da3 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Tue, 14 Oct 2025 20:32:10 -0700 Subject: [PATCH 194/681] Reapply "capnp: consolidate TurnDirection enum" (#1376) (#1382) * Reapply "capnp: consolidate TurnDirection enum" (#1376) This reverts commit 339bc0b8b3c4a192aa4e982039bc2ac28246c080. * cache it * format --------- Co-authored-by: Jason Wen --- cereal/custom.capnp | 10 +- selfdrive/controls/lib/desire_helper.py | 11 +- selfdrive/selfdrived/selfdrived.py | 5 +- .../controls/lib/lane_turn_desire.py | 12 +- .../lib/tests/test_lane_turn_desire.py | 160 +++++++++--------- 5 files changed, 101 insertions(+), 97 deletions(-) diff --git a/cereal/custom.capnp b/cereal/custom.capnp index e81749dbac..5c0a004fa6 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -446,12 +446,12 @@ struct LiveMapDataSP @0xf416ec09499d9d19 { struct ModelDataV2SP @0xa1680744031fdb2d { laneTurnDirection @0 :TurnDirection; -} -enum TurnDirection { - none @0; - turnLeft @1; - turnRight @2; + enum TurnDirection { + none @0; + turnLeft @1; + turnRight @2; + } } struct CustomReserved10 @0xcb9fd56c7057593a { diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index e72d464d06..16908fa3e3 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -6,6 +6,7 @@ from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTur LaneChangeState = log.LaneChangeState LaneChangeDirection = log.LaneChangeDirection +TurnDirection = custom.ModelDataV2SP.TurnDirection LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS LANE_CHANGE_TIME_MAX = 10. @@ -32,9 +33,9 @@ DESIRES = { } TURN_DESIRES = { - custom.TurnDirection.none: log.Desire.none, - custom.TurnDirection.turnLeft: log.Desire.turnLeft, - custom.TurnDirection.turnRight: log.Desire.turnRight, + TurnDirection.none: log.Desire.none, + TurnDirection.turnLeft: log.Desire.turnLeft, + TurnDirection.turnRight: log.Desire.turnRight, } @@ -49,7 +50,7 @@ class DesireHelper: self.desire = log.Desire.none self.alc = AutoLaneChangeController(self) self.lane_turn_controller = LaneTurnController(self) - self.lane_turn_direction = custom.TurnDirection.none + self.lane_turn_direction = TurnDirection.none @staticmethod def get_lane_change_direction(CS): @@ -126,7 +127,7 @@ class DesireHelper: self.prev_one_blinker = one_blinker - if self.lane_turn_direction != custom.TurnDirection.none: + if self.lane_turn_direction != TurnDirection.none: self.desire = TURN_DESIRES[self.lane_turn_direction] else: self.desire = DESIRES[self.lane_change_direction][self.lane_change_state] diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index 0a4ea749e4..3bc616fb04 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -44,6 +44,7 @@ LaneChangeDirection = log.LaneChangeDirection EventName = log.OnroadEvent.EventName ButtonType = car.CarState.ButtonEvent.Type SafetyModel = car.CarParams.SafetyModel +TurnDirection = custom.ModelDataV2SP.TurnDirection IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput) @@ -305,9 +306,9 @@ class SelfdriveD(CruiseHelper): # Handle lane turn lane_turn_direction = self.sm['modelDataV2SP'].laneTurnDirection - if lane_turn_direction == custom.TurnDirection.turnLeft: + if lane_turn_direction == TurnDirection.turnLeft: self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnLeft) - elif lane_turn_direction == custom.TurnDirection.turnRight: + elif lane_turn_direction == TurnDirection.turnRight: self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnRight) for i, pandaState in enumerate(self.sm['pandaStates']): diff --git a/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py b/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py index 00ce026abb..fa35ebb125 100644 --- a/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py +++ b/sunnypilot/selfdrive/controls/lib/lane_turn_desire.py @@ -9,13 +9,15 @@ from cereal import custom from openpilot.common.constants import CV from openpilot.common.params import Params +TurnDirection = custom.ModelDataV2SP.TurnDirection + LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS class LaneTurnController: def __init__(self, desire_helper): self.DH = desire_helper - self.turn_direction = custom.TurnDirection.none + self.turn_direction = TurnDirection.none self.params = Params() self.lane_turn_value = float(self.params.get("LaneTurnValue", return_default=True)) * CV.MPH_TO_MS self.param_read_counter = 0 @@ -33,13 +35,13 @@ class LaneTurnController: def update_lane_turn(self, blindspot_left: bool, blindspot_right: bool, left_blinker: bool, right_blinker: bool, v_ego: float) -> None: if left_blinker and not right_blinker and v_ego < self.lane_turn_value and not blindspot_left: - self.turn_direction = custom.TurnDirection.turnLeft + self.turn_direction = TurnDirection.turnLeft elif right_blinker and not left_blinker and v_ego < self.lane_turn_value and not blindspot_right: - self.turn_direction = custom.TurnDirection.turnRight + self.turn_direction = TurnDirection.turnRight else: - self.turn_direction = custom.TurnDirection.none + self.turn_direction = TurnDirection.none def get_turn_direction(self): if not self.enabled: - return custom.TurnDirection.none + return TurnDirection.none return self.turn_direction diff --git a/sunnypilot/selfdrive/controls/lib/tests/test_lane_turn_desire.py b/sunnypilot/selfdrive/controls/lib/tests/test_lane_turn_desire.py index 5633ed6efc..57fe7b684f 100644 --- a/sunnypilot/selfdrive/controls/lib/tests/test_lane_turn_desire.py +++ b/sunnypilot/selfdrive/controls/lib/tests/test_lane_turn_desire.py @@ -1,113 +1,113 @@ import pytest -from cereal import log +from cereal import log, custom from openpilot.common.params import Params from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTurnController, LANE_CHANGE_SPEED_MIN from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeMode - -class TurnDirection: - none = 0 - turnLeft = 1 - turnRight = 2 +TurnDirection = custom.ModelDataV2SP.TurnDirection @pytest.mark.parametrize("left_blinker,right_blinker,v_ego,blindspot_left,blindspot_right,expected", [ - (True, False, 5, False, False, TurnDirection.turnLeft), - (False, True, 6, False, False, TurnDirection.turnRight), - (True, False, 9, False, False, TurnDirection.none), - (True, False, 7, True, False, TurnDirection.none), - (False, True, 6, False, True, TurnDirection.none), - (False, False, 5, False, False, TurnDirection.none), - (True, True, 5, False, False, TurnDirection.none), + (True, False, 5, False, False, TurnDirection.turnLeft), + (False, True, 6, False, False, TurnDirection.turnRight), + (True, False, 9, False, False, TurnDirection.none), + (True, False, 7, True, False, TurnDirection.none), + (False, True, 6, False, True, TurnDirection.none), + (False, False, 5, False, False, TurnDirection.none), + (True, True, 5, False, False, TurnDirection.none), ]) def test_lane_turn_desire_conditions(left_blinker, right_blinker, v_ego, blindspot_left, blindspot_right, expected): - dh = DesireHelper() - controller = LaneTurnController(dh) - controller.enabled = True - controller.lane_turn_value = LANE_CHANGE_SPEED_MIN - controller.turn_direction = TurnDirection.none - controller.update_lane_turn(blindspot_left, blindspot_right, left_blinker, right_blinker, v_ego) - assert controller.get_turn_direction() == expected + dh = DesireHelper() + controller = LaneTurnController(dh) + controller.enabled = True + controller.lane_turn_value = LANE_CHANGE_SPEED_MIN + controller.turn_direction = TurnDirection.none + controller.update_lane_turn(blindspot_left, blindspot_right, left_blinker, right_blinker, v_ego) + assert controller.get_turn_direction() == expected def test_lane_turn_desire_disabled(): - dh = DesireHelper() - controller = LaneTurnController(dh) - controller.enabled = False - controller.lane_turn_value = LANE_CHANGE_SPEED_MIN - controller.turn_direction = TurnDirection.none - controller.update_lane_turn(False, False, True, False, 7) - assert controller.get_turn_direction() == TurnDirection.none + dh = DesireHelper() + controller = LaneTurnController(dh) + controller.enabled = False + controller.lane_turn_value = LANE_CHANGE_SPEED_MIN + controller.turn_direction = TurnDirection.none + controller.update_lane_turn(False, False, True, False, 7) + assert controller.get_turn_direction() == TurnDirection.none def test_lane_turn_overrides_lane_change(): - dh = DesireHelper() - controller = LaneTurnController(dh) - controller.enabled = True - controller.lane_turn_value = LANE_CHANGE_SPEED_MIN - controller.turn_direction = TurnDirection.none - # left turn desire - controller.update_lane_turn(False, False, True, False, 5) - assert controller.get_turn_direction() == TurnDirection.turnLeft - # right turn desire - controller.update_lane_turn(False, False, False, True, 6) - assert controller.get_turn_direction() == TurnDirection.turnRight - # no turn - controller.update_lane_turn(False, False, False, False, 7) - assert controller.get_turn_direction() == TurnDirection.none + dh = DesireHelper() + controller = LaneTurnController(dh) + controller.enabled = True + controller.lane_turn_value = LANE_CHANGE_SPEED_MIN + controller.turn_direction = TurnDirection.none + # left turn desire + controller.update_lane_turn(False, False, True, False, 5) + assert controller.get_turn_direction() == TurnDirection.turnLeft + # right turn desire + controller.update_lane_turn(False, False, False, True, 6) + assert controller.get_turn_direction() == TurnDirection.turnRight + # no turn + controller.update_lane_turn(False, False, False, False, 7) + assert controller.get_turn_direction() == TurnDirection.none @pytest.mark.parametrize("v_ego,expected", [ - (8.93, TurnDirection.turnLeft), # just below threshold - (8.96, TurnDirection.none), # above threshold - (8.95, TurnDirection.none), # just above threshold + (8.93, TurnDirection.turnLeft), # just below threshold + (8.96, TurnDirection.none), # above threshold + (8.95, TurnDirection.none), # just above threshold ]) def test_lane_turn_desire_speed_boundary(v_ego, expected): - dh = DesireHelper() - controller = LaneTurnController(dh) - controller.enabled = True - controller.lane_turn_value = LANE_CHANGE_SPEED_MIN - controller.turn_direction = TurnDirection.none - controller.update_lane_turn(False, True, True, False, v_ego) - assert controller.get_turn_direction() == expected + dh = DesireHelper() + controller = LaneTurnController(dh) + controller.enabled = True + controller.lane_turn_value = LANE_CHANGE_SPEED_MIN + controller.turn_direction = TurnDirection.none + controller.update_lane_turn(False, True, True, False, v_ego) + assert controller.get_turn_direction() == expected class DummyCarState: - def __init__(self, vEgo=0, leftBlinker=False, rightBlinker=False, leftBlindspot=False, rightBlindspot=False, - steeringPressed=False, steeringTorque=0, brakePressed=False): - self.vEgo = vEgo - self.leftBlinker = leftBlinker - self.rightBlinker = rightBlinker - self.leftBlindspot = leftBlindspot - self.rightBlindspot = rightBlindspot - self.steeringPressed = steeringPressed - self.steeringTorque = steeringTorque - self.brakePressed = brakePressed + def __init__(self, vEgo=0, leftBlinker=False, rightBlinker=False, leftBlindspot=False, rightBlindspot=False, + steeringPressed=False, steeringTorque=0, brakePressed=False): + self.vEgo = vEgo + self.leftBlinker = leftBlinker + self.rightBlinker = rightBlinker + self.leftBlindspot = leftBlindspot + self.rightBlindspot = rightBlindspot + self.steeringPressed = steeringPressed + self.steeringTorque = steeringTorque + self.brakePressed = brakePressed + @pytest.fixture def set_lane_turn_params(): - params = Params() - params.put("LaneTurnDesire", True) - params.put("LaneTurnValue", 20.0) + params = Params() + params.put("LaneTurnDesire", True) + params.put("LaneTurnValue", 20.0) + @pytest.mark.parametrize("carstate, lateral_active, lane_change_prob, expected_desire", [ - # Lane turn desire overrides lane change desire - (DummyCarState(vEgo=5, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False), True, 1.0, log.Desire.turnLeft), - (DummyCarState(vEgo=7, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False), True, 1.0, log.Desire.turnRight), - # Lane change desire only (no turn desires) - (DummyCarState(vEgo=9, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False, - steeringPressed=True, steeringTorque=1), True, 1.0, log.Desire.laneChangeLeft), - (DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False, - steeringPressed=True, steeringTorque=-1), True, 1.0, log.Desire.laneChangeRight), - # No desire (inactive) - (DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=False), False, 1.0, log.Desire.none), - (DummyCarState(vEgo=4, leftBlinker=False, rightBlinker=False), True, 1.0, log.Desire.none), # No blinkers? no desire! + # Lane turn desire overrides lane change desire + (DummyCarState(vEgo=5, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False), True, 1.0, + log.Desire.turnLeft), + (DummyCarState(vEgo=7, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False), True, 1.0, + log.Desire.turnRight), + # Lane change desire only (no turn desires) + (DummyCarState(vEgo=9, leftBlinker=True, rightBlinker=False, leftBlindspot=False, rightBlindspot=False, + steeringPressed=True, steeringTorque=1), True, 1.0, log.Desire.laneChangeLeft), + (DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=True, leftBlindspot=False, rightBlindspot=False, + steeringPressed=True, steeringTorque=-1), True, 1.0, log.Desire.laneChangeRight), + # No desire (inactive) + (DummyCarState(vEgo=9, leftBlinker=False, rightBlinker=False), False, 1.0, log.Desire.none), + (DummyCarState(vEgo=4, leftBlinker=False, rightBlinker=False), True, 1.0, log.Desire.none), # No blinkers? no desire! ]) def test_desire_helper_integration(carstate, lateral_active, lane_change_prob, expected_desire, set_lane_turn_params): - dh = DesireHelper() - dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE - for _ in range(10): - dh.update(carstate, lateral_active, lane_change_prob) - assert dh.desire == expected_desire # The first four tests were unit tests to test the controller, where this tests the integration in desire helpers + dh = DesireHelper() + dh.alc.lane_change_set_timer = AutoLaneChangeMode.NUDGE + for _ in range(10): + dh.update(carstate, lateral_active, lane_change_prob) + assert dh.desire == expected_desire # The first four tests were unit tests to test the controller, where this tests the integration in desire helpers From 8a1fcd8991cde9c122bfe5d01b835dee85b1b681 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 14 Oct 2025 21:17:36 -0700 Subject: [PATCH 195/681] 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 9754d488ed..362f49a51e 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 cac2f9c968..bffa30ad22 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 69ed5f458f..8aaef1bcfb 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 196/681] 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 bffa30ad22..0e0adceaf6 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 197/681] 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 85f358977c..f2a064c606 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 198/681] 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 a65f198ad6..ea3f9c4802 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 e0ccc175e4b49dc389ede1bc522b7de64056e450 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 15 Oct 2025 00:59:40 -0400 Subject: [PATCH 199/681] liveMapDataSP: improve speed limit validation logic (#1383) --- sunnypilot/mapd/live_map_data/base_map_data.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sunnypilot/mapd/live_map_data/base_map_data.py b/sunnypilot/mapd/live_map_data/base_map_data.py index 25925937f5..723864dd2a 100644 --- a/sunnypilot/mapd/live_map_data/base_map_data.py +++ b/sunnypilot/mapd/live_map_data/base_map_data.py @@ -8,8 +8,12 @@ from abc import abstractmethod, ABC import cereal.messaging as messaging from openpilot.common.params import Params +from openpilot.common.constants import CV +from openpilot.selfdrive.car.cruise import V_CRUISE_UNSET from openpilot.sunnypilot.navd.helpers import coordinate_from_param +MAX_SPEED_LIMIT = V_CRUISE_UNSET * CV.KPH_TO_MS + class BaseMapData(ABC): def __init__(self): @@ -46,9 +50,9 @@ class BaseMapData(ABC): mapd_sp_send.valid = self.sm['liveLocationKalman'].gpsOK live_map_data = mapd_sp_send.liveMapDataSP - live_map_data.speedLimitValid = bool(speed_limit > 0) + live_map_data.speedLimitValid = bool(MAX_SPEED_LIMIT > speed_limit > 0) live_map_data.speedLimit = speed_limit - live_map_data.speedLimitAheadValid = bool(next_speed_limit > 0) + live_map_data.speedLimitAheadValid = bool(MAX_SPEED_LIMIT > next_speed_limit > 0) live_map_data.speedLimitAhead = next_speed_limit live_map_data.speedLimitAheadDistance = next_speed_limit_distance live_map_data.roadName = self.get_current_road_name() 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 200/681] 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 70f06f6b9d..85bc477bf6 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 201/681] 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 2579d30f65..0000000000 --- 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 0000000000..778e821ce3 --- /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 28139158a1..519f567d3f 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 202/681] 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 ea3f9c4802..3f3958f9d3 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 203/681] 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 0e0adceaf6..661496a032 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 204/681] 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 f786d1ab2f..b0f122227e 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 33c4ef29d3..65b579209e 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 205/681] 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 85ce4a858b..8f6466ce46 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 99a3569fbe..a7fa4369a9 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 38a93687e0..5dd5a69c69 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 65b579209e..d995ec1b52 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 206/681] 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 3877da81c5..125053f17e 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 207/681] 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 ef28df8112..141f682db0 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 aaa67472dd..1f4f69a6bf 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 6d51d64285fdfef07f09845d8d96425bc0a769ab Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 15 Oct 2025 09:46:53 -0400 Subject: [PATCH 208/681] interfaces: clean up unsupported params during initialization (#1385) * interfaces: clean up unsupported params during initialization * fix * logging and no DEC when no long * ui * ui --- .../speed_limit/speed_limit_settings.cc | 6 ++++- .../qt/offroad/settings/longitudinal_panel.cc | 13 ++++++++-- sunnypilot/selfdrive/car/interfaces.py | 24 +++++++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc index f64198f97e..95aa4ef26f 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc @@ -122,6 +122,10 @@ void SpeedLimitSettings::refresh() { has_longitudinal_control = hasLongitudinalControl(CP); intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable(); + + if (!has_longitudinal_control && CP_SP.getPcmCruiseSpeed()) { + params.put("SpeedLimitMode", std::to_string(static_cast(SpeedLimitMode::WARNING))); + } } else { has_longitudinal_control = false; intelligent_cruise_button_management_available = false; @@ -148,7 +152,7 @@ void SpeedLimitSettings::refresh() { speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues(getSpeedLimitModeValues())); } else { speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues( - {SpeedLimitMode::OFF,SpeedLimitMode::INFORMATION, SpeedLimitMode::WARNING})); + {SpeedLimitMode::OFF, SpeedLimitMode::INFORMATION, SpeedLimitMode::WARNING})); } speed_limit_mode_settings->showDescription(); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc index 31124a3242..a5098a13e6 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc @@ -104,6 +104,17 @@ void LongitudinalPanel::refresh(bool _offroad) { has_longitudinal_control = hasLongitudinalControl(CP); is_pcm_cruise = CP.getPcmCruise(); intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable(); + + if (!intelligent_cruise_button_management_available || has_longitudinal_control) { + params.remove("IntelligentCruiseButtonManagement"); + } + + if (!has_longitudinal_control && CP_SP.getPcmCruiseSpeed()) { + params.remove("CustomAccIncrementsEnabled"); + params.remove("DynamicExperimentalControl"); + params.remove("SmartCruiseControlVision"); + params.remove("SmartCruiseControlMap"); + } } else { has_longitudinal_control = false; is_pcm_cruise = false; @@ -127,11 +138,9 @@ void LongitudinalPanel::refresh(bool _offroad) { customAccIncrement->setDescription(accEnabledDescription); } } else { - params.remove("CustomAccIncrementsEnabled"); customAccIncrement->toggleFlipped(false); customAccIncrement->setDescription(accNoLongDescription); customAccIncrement->showDescription(); - params.remove("IntelligentCruiseButtonManagement"); intelligentCruiseButtonManagement->toggleFlipped(false); } } diff --git a/sunnypilot/selfdrive/car/interfaces.py b/sunnypilot/selfdrive/car/interfaces.py index 3072cde8cd..5467639a63 100644 --- a/sunnypilot/selfdrive/car/interfaces.py +++ b/sunnypilot/selfdrive/car/interfaces.py @@ -11,6 +11,7 @@ from opendbc.car.interfaces import CarInterfaceBase from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog from openpilot.sunnypilot.selfdrive.controls.lib.nnlc.helpers import get_nn_model_path +from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode as SpeedLimitMode import openpilot.system.sentry as sentry @@ -66,6 +67,28 @@ def _initialize_torque_lateral_control(CI: CarInterfaceBase, CP: structs.CarPara CI.configure_torque_tune(CP.carFingerprint, CP.lateralTuning) +def _cleanup_unsupported_params(CP: structs.CarParams, CP_SP: structs.CarParamsSP, params: Params = None) -> None: + if params is None: + params = Params() + + if CP.steerControlType == structs.CarParams.SteerControlType.angle: + cloudlog.warning("SteerControlType is angle, cleaning up params") + params.remove("NeuralNetworkLateralControl") + params.remove("EnforceTorqueControl") + + if not CP_SP.intelligentCruiseButtonManagementAvailable or CP.openpilotLongitudinalControl: + cloudlog.warning("ICBM not available or openpilot Longitudinal Control enabled, cleaning up params") + params.remove("IntelligentCruiseButtonManagement") + + if not CP.openpilotLongitudinalControl and CP_SP.pcmCruiseSpeed: + cloudlog.warning("openpilot Longitudinal Control and ICBM not available, cleaning up params") + params.remove("DynamicExperimentalControl") + params.remove("CustomAccIncrementsEnabled") + params.remove("SmartCruiseControlVision") + params.remove("SmartCruiseControlMap") + params.put("SpeedLimitMode", int(SpeedLimitMode.warning)) + + def setup_interfaces(CI: CarInterfaceBase, params: Params = None) -> None: CP = CI.CP CP_SP = CI.CP_SP @@ -74,6 +97,7 @@ def setup_interfaces(CI: CarInterfaceBase, params: Params = None) -> None: nnlc_enabled = _initialize_neural_network_lateral_control(CP, CP_SP, params) _initialize_intelligent_cruise_button_management(CP, CP_SP, params) _initialize_torque_lateral_control(CI, CP, enforce_torque, nnlc_enabled) + _cleanup_unsupported_params(CP, CP_SP) def initialize_params(params) -> list[dict[str, Any]]: From d7e1c42c2b73a3c96fa17908ee1118a5171f4183 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 15 Oct 2025 12:25:23 -0400 Subject: [PATCH 209/681] ui: move Dynamic Experimental Control (DEC) toggle to Longitudinal panel (#1388) - Implemented a new toggle for enabling Dynamic Experimental Control (DEC) in longitudinal settings. - Removed previous implementation for DEC from general settings. - Updated accessibility based on longitudinal control status. --- selfdrive/ui/qt/offroad/settings.cc | 7 ------- .../qt/offroad/settings/longitudinal_panel.cc | 10 ++++++++++ .../qt/offroad/settings/longitudinal_panel.h | 1 + 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 205a14624a..6f594d939a 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -33,13 +33,6 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { "../assets/icons/experimental_white.svg", false, }, - { - "DynamicExperimentalControl", - tr("Enable Dynamic Experimental Control"), - tr("Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal."), - "../assets/offroad/icon_blank.png", - false, - }, { "DisengageOnAccelerator", tr("Disengage on Accelerator Pedal"), diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc index a5098a13e6..f2c7ea3825 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc @@ -43,6 +43,15 @@ LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) { intelligentCruiseButtonManagement->setConfirmation(true, false); list->addItem(intelligentCruiseButtonManagement); + dynamicExperimentalControl = new ParamControlSP( + "DynamicExperimentalControl", + tr("Dynamic Experimental Control (DEC)"), + tr("Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal."), + "", + this + ); + list->addItem(dynamicExperimentalControl); + SmartCruiseControlVision = new ParamControl( "SmartCruiseControlVision", tr("Smart Cruise Control - Vision"), @@ -153,6 +162,7 @@ void LongitudinalPanel::refresh(bool _offroad) { customAccIncrement->setEnabled(cai_allowed && !offroad); customAccIncrement->refresh(); + dynamicExperimentalControl->setEnabled(has_longitudinal_control); SmartCruiseControlVision->setEnabled(has_longitudinal_control || icbm_allowed); SmartCruiseControlMap->setEnabled(has_longitudinal_control || icbm_allowed); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h index a94369a560..127b7871eb 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h @@ -35,6 +35,7 @@ private: ParamControl *SmartCruiseControlVision; ParamControl *SmartCruiseControlMap; ParamControl *intelligentCruiseButtonManagement = nullptr; + ParamControl *dynamicExperimentalControl = nullptr; SpeedLimitSettings *speedLimitScreen; PushButtonSP *speedLimitSettings; }; From f1ca81debf97153a0b531bfe6d774869af83bc77 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 15 Oct 2025 16:20:31 -0400 Subject: [PATCH 210/681] ui: chevron should always be on top of driving path (#1391) --- selfdrive/ui/sunnypilot/qt/onroad/model.cc | 34 ++++++++++++++-------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/model.cc b/selfdrive/ui/sunnypilot/qt/onroad/model.cc index 086b703e10..590ade24a1 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/model.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/model.cc @@ -22,7 +22,6 @@ void ModelRendererSP::update_model(const cereal::ModelDataV2::Reader &model, con } void ModelRendererSP::draw(QPainter &painter, const QRect &surface_rect) { - ModelRenderer::draw(painter, surface_rect); auto *s = uiState(); auto &sm = *(s->sm); @@ -31,6 +30,11 @@ void ModelRendererSP::draw(QPainter &painter, const QRect &surface_rect) { 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(); @@ -41,22 +45,28 @@ void ModelRendererSP::draw(QPainter &painter, const QRect &surface_rect) { update_model(model, lead_one); drawLaneLines(painter); - bool blindspot = s->scene.blindspot_ui; - bool rainbow = s->scene.rainbow_mode; - - bool left_blindspot = car_state.getLeftBlindspot(); - bool right_blindspot = car_state.getRightBlindspot(); - - if (blindspot) { - drawBlindspot(painter, surface_rect, left_blindspot, right_blindspot); - } - - if (rainbow) { + if (s->scene.rainbow_mode) { drawRainbowPath(painter, surface_rect); } else { ModelRenderer::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); + } + } + + if (s->scene.blindspot_ui) { + const bool left_blindspot = car_state.getLeftBlindspot(); + const bool right_blindspot = car_state.getRightBlindspot(); + drawBlindspot(painter, surface_rect, left_blindspot, right_blindspot); + } drawLeadStatus(painter, surface_rect.height(), surface_rect.width()); painter.restore(); From c438aeb5a5ce4d4e5c8aee5c68a5eaac08ce3afd Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 15 Oct 2025 16:47:00 -0400 Subject: [PATCH 211/681] ui: check for updated message before updating states in HUD (#1392) --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 105 ++++++++++++++--------- selfdrive/ui/sunnypilot/qt/onroad/hud.h | 2 +- 2 files changed, 64 insertions(+), 43 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index fa57e2b334..d8176051fb 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -26,31 +26,54 @@ HudRendererSP::HudRendererSP() { void HudRendererSP::updateState(const UIState &s) { HudRenderer::updateState(s); + float speedConv = is_metric ? MS_TO_KPH : MS_TO_MPH; + devUiInfo = s.scene.dev_ui_info; + roadName = s.scene.road_name; + showTurnSignals = s.scene.turn_signals; + speedLimitMode = static_cast(s.scene.speed_limit_mode); + speedUnit = is_metric ? tr("km/h") : tr("mph"); + standstillTimer = s.scene.standstill_timer; + const SubMaster &sm = *(s.sm); const auto cs = sm["controlsState"].getControlsState(); const auto car_state = sm["carState"].getCarState(); const auto car_control = sm["carControl"].getCarControl(); const auto radar_state = sm["radarState"].getRadarState(); const auto is_gps_location_external = sm.rcv_frame("gpsLocationExternal") > 1; - const auto gpsLocation = is_gps_location_external ? sm["gpsLocationExternal"].getGpsLocationExternal() : sm["gpsLocation"].getGpsLocation(); + const char *gps_source = is_gps_location_external ? "gpsLocationExternal" : "gpsLocation"; + const auto gpsLocation = is_gps_location_external ? sm[gps_source].getGpsLocationExternal() : sm[gps_source].getGpsLocation(); const auto ltp = sm["liveTorqueParameters"].getLiveTorqueParameters(); const auto car_params = sm["carParams"].getCarParams(); const auto car_params_sp = sm["carParamsSP"].getCarParamsSP(); const auto lp_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP(); const auto lmd = sm["liveMapDataSP"].getLiveMapDataSP(); - float speedConv = is_metric ? MS_TO_KPH : MS_TO_MPH; - speedLimit = lp_sp.getSpeedLimit().getResolver().getSpeedLimit() * speedConv; - speedLimitLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLast() * speedConv; - speedLimitOffset = lp_sp.getSpeedLimit().getResolver().getSpeedLimitOffset() * speedConv; - speedLimitValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitValid(); - speedLimitLastValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLastValid(); - speedLimitFinalLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitFinalLast() * speedConv; - speedLimitSource = lp_sp.getSpeedLimit().getResolver().getSource(); - speedLimitMode = static_cast(s.scene.speed_limit_mode); - speedLimitAssistState = lp_sp.getSpeedLimit().getAssist().getState(); - speedLimitAssistActive = lp_sp.getSpeedLimit().getAssist().getActive(); - roadName = s.scene.road_name; + if (sm.updated("carParams")) { + steerControlType = car_params.getSteerControlType(); + } + + if (sm.updated("carParamsSP")) { + pcmCruiseSpeed = car_params_sp.getPcmCruiseSpeed(); + } + + if (sm.updated("longitudinalPlanSP")) { + speedLimit = lp_sp.getSpeedLimit().getResolver().getSpeedLimit() * speedConv; + speedLimitLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLast() * speedConv; + speedLimitOffset = lp_sp.getSpeedLimit().getResolver().getSpeedLimitOffset() * speedConv; + speedLimitValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitValid(); + speedLimitLastValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLastValid(); + speedLimitFinalLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitFinalLast() * speedConv; + speedLimitSource = lp_sp.getSpeedLimit().getResolver().getSource(); + speedLimitAssistState = lp_sp.getSpeedLimit().getAssist().getState(); + speedLimitAssistActive = lp_sp.getSpeedLimit().getAssist().getActive(); + smartCruiseControlVisionEnabled = lp_sp.getSmartCruiseControl().getVision().getEnabled(); + smartCruiseControlVisionActive = lp_sp.getSmartCruiseControl().getVision().getActive(); + smartCruiseControlMapEnabled = lp_sp.getSmartCruiseControl().getMap().getEnabled(); + smartCruiseControlMapActive = lp_sp.getSmartCruiseControl().getMap().getActive(); + greenLightAlert = lp_sp.getE2eAlerts().getGreenLightAlert(); + leadDepartAlert = lp_sp.getE2eAlerts().getLeadDepartAlert(); + } + if (sm.updated("liveMapDataSP")) { roadNameStr = QString::fromStdString(lmd.getRoadName()); speedLimitAheadValid = lmd.getSpeedLimitAheadValid(); @@ -66,7 +89,7 @@ void HudRendererSP::updateState(const UIState &s) { static int reverse_delay = 0; bool reverse_allowed = false; - if (int(car_state.getGearShifter()) != 4) { + if (car_state.getGearShifter() != cereal::CarState::GearShifter::REVERSE) { reverse_delay = 0; reverse_allowed = false; } else { @@ -78,46 +101,47 @@ void HudRendererSP::updateState(const UIState &s) { reversing = reverse_allowed; + if (sm.updated("liveParameters")) { + roll = sm["liveParameters"].getLiveParameters().getRoll(); + } + + if (sm.updated("deviceState")) { + memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent(); + } + + if (sm.updated(gps_source)) { + gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0; // External reports accuracy, internal does not. + altitude = gpsLocation.getAltitude(); + bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg(); + bearingDeg = gpsLocation.getBearingDeg(); + } + + if (sm.updated("liveTorqueParameters")) { + torquedUseParams = ltp.getUseParams(); + latAccelFactorFiltered = ltp.getLatAccelFactorFiltered(); + frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered(); + liveValid = ltp.getLiveValid(); + } + latActive = car_control.getLatActive(); + actuators = car_control.getActuators(); + longOverride = car_control.getCruiseControl().getOverride(); + carControlEnabled = car_control.getEnabled(); + steerOverride = car_state.getSteeringPressed(); - - devUiInfo = s.scene.dev_ui_info; - - speedUnit = is_metric ? tr("km/h") : tr("mph"); lead_d_rel = radar_state.getLeadOne().getDRel(); lead_v_rel = radar_state.getLeadOne().getVRel(); lead_status = radar_state.getLeadOne().getStatus(); - steerControlType = car_params.getSteerControlType(); - actuators = car_control.getActuators(); torqueLateral = steerControlType == cereal::CarParams::SteerControlType::TORQUE; angleSteers = car_state.getSteeringAngleDeg(); desiredCurvature = cs.getDesiredCurvature(); curvature = cs.getCurvature(); - roll = sm["liveParameters"].getLiveParameters().getRoll(); - memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent(); - gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0; // External reports accuracy, internal does not. - altitude = gpsLocation.getAltitude(); vEgo = car_state.getVEgo(); aEgo = car_state.getAEgo(); steeringTorqueEps = car_state.getSteeringTorqueEps(); - bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg(); - bearingDeg = gpsLocation.getBearingDeg(); - torquedUseParams = ltp.getUseParams(); - latAccelFactorFiltered = ltp.getLatAccelFactorFiltered(); - frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered(); - liveValid = ltp.getLiveValid(); - standstillTimer = s.scene.standstill_timer; isStandstill = car_state.getStandstill(); if (not s.scene.started) standstillElapsedTime = 0.0; - longOverride = car_control.getCruiseControl().getOverride(); - smartCruiseControlVisionEnabled = lp_sp.getSmartCruiseControl().getVision().getEnabled(); - smartCruiseControlVisionActive = lp_sp.getSmartCruiseControl().getVision().getActive(); - smartCruiseControlMapEnabled = lp_sp.getSmartCruiseControl().getMap().getEnabled(); - smartCruiseControlMapActive = lp_sp.getSmartCruiseControl().getMap().getActive(); - - greenLightAlert = lp_sp.getE2eAlerts().getGreenLightAlert(); - leadDepartAlert = lp_sp.getE2eAlerts().getLeadDepartAlert(); // override stock current speed values float v_ego = (v_ego_cluster_seen && !s.scene.trueVEgoUI) ? car_state.getVEgoCluster() : car_state.getVEgo(); @@ -128,11 +152,8 @@ void HudRendererSP::updateState(const UIState &s) { rightBlinkerOn = car_state.getRightBlinker(); leftBlindspot = car_state.getLeftBlindspot(); rightBlindspot = car_state.getRightBlindspot(); - showTurnSignals = s.scene.turn_signals; - carControlEnabled = car_control.getEnabled(); speedCluster = car_state.getCruiseState().getSpeedCluster() * speedConv; - pcmCruiseSpeed = car_params_sp.getPcmCruiseSpeed(); } void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) { diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.h b/selfdrive/ui/sunnypilot/qt/onroad/hud.h index f9135ee9ef..a32fdb28a7 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.h @@ -121,5 +121,5 @@ private: bool carControlEnabled; float speedCluster = 0; int icbm_active_counter = 0; - bool pcmCruiseSpeed; + bool pcmCruiseSpeed = true; }; From 99bd9075d539b3f74af8610626a02d22ce53d931 Mon Sep 17 00:00:00 2001 From: Nayan Date: Wed, 15 Oct 2025 17:18:31 -0400 Subject: [PATCH 212/681] ui: Fix Onroad Screen-Off default param (#1389) Change defaults Co-authored-by: Jason Wen --- common/params_keys.h | 4 ++-- .../qt/offroad/settings/display/onroad_screen_brightness.cc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index ecb73a9456..dd58462a95 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -160,9 +160,9 @@ inline static std::unordered_map keys = { {"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}}, {"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}}, {"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}}, - {"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "100"}}, + {"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "0"}}, {"OnroadScreenOffControl", {PERSISTENT | BACKUP, BOOL}}, - {"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "0"}}, + {"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "15"}}, {"OnroadUploads", {PERSISTENT | BACKUP, BOOL, "1"}}, {"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}}, {"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}}, diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/display/onroad_screen_brightness.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/display/onroad_screen_brightness.cc index f28d9e45e8..e29d06cb82 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/display/onroad_screen_brightness.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/display/onroad_screen_brightness.cc @@ -29,7 +29,7 @@ OnroadScreenBrightnessControl::OnroadScreenBrightnessControl(const QString ¶ "Onroad Brightness", "", "", - {0, 100}, 10, true); + {0, 90}, 10, true); connect(onroadScreenOffTimer, &OptionControlSP::updateLabels, this, &OnroadScreenBrightnessControl::refresh); connect(onroadScreenBrightness, &OptionControlSP::updateLabels, this, &OnroadScreenBrightnessControl::refresh); From 9e6af5ba740eecd78dec2af302ea2bbc8d969f8f Mon Sep 17 00:00:00 2001 From: Nayan Date: Wed, 15 Oct 2025 17:40:52 -0400 Subject: [PATCH 213/681] ui: Adjust UI Elements to account for Sidebar & Dev UI (#1390) * resize & reposition * Apply suggestion from @sunnyhaibin * sir, this is Wendy's * this is still a Wendy's --------- Co-authored-by: Jason Wen --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 32 ++++++++++++------------ selfdrive/ui/sunnypilot/qt/onroad/hud.h | 10 +++----- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index d8176051fb..5db7ba1814 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -12,15 +12,12 @@ HudRendererSP::HudRendererSP() { - plus_arrow_up_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_plus_arrow_up", {105, 105}); - minus_arrow_down_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_minus_arrow_down", {105, 105}); + plus_arrow_up_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_plus_arrow_up", {90, 90}); + minus_arrow_down_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_minus_arrow_down", {90, 90}); - int small_max = e2e_alert_small * 2 - 40; - int large_max = e2e_alert_large * 2 - 40; - green_light_alert_small_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/green_light.png", {small_max, small_max}); - green_light_alert_large_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/green_light.png", {large_max, large_max}); - lead_depart_alert_small_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/lead_depart.png", {small_max, small_max}); - lead_depart_alert_large_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/lead_depart.png", {large_max, large_max}); + int size = e2e_alert_size * 2 - 40; + green_light_alert_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/green_light.png", {size, size}); + lead_depart_alert_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/lead_depart.png", {size, size}); } void HudRendererSP::updateState(const UIState &s) { @@ -154,6 +151,9 @@ void HudRendererSP::updateState(const UIState &s) { rightBlindspot = car_state.getRightBlindspot(); speedCluster = car_state.getCruiseState().getSpeedCluster() * speedConv; + + allow_e2e_alerts = sm["selfdriveState"].getSelfdriveState().getAlertSize() == cereal::SelfdriveState::AlertSize::NONE && + sm.rcv_frame("driverStateV2") > s.scene.started_frame && !reversing; } void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) { @@ -256,11 +256,11 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) { e2eAlertFrame++; if (greenLightAlert) { alert_text = tr("GREEN\nLIGHT"); - alert_img = devUiInfo > 0 ? green_light_alert_small_img : green_light_alert_large_img; + alert_img = green_light_alert_img; } else if (leadDepartAlert) { alert_text = tr("LEAD VEHICLE\nDEPARTING"); - alert_img = devUiInfo > 0 ? lead_depart_alert_small_img : lead_depart_alert_large_img; + alert_img = lead_depart_alert_img; } drawE2eAlert(p, surface_rect); } @@ -664,7 +664,7 @@ void HudRendererSP::drawRoadName(QPainter &p, const QRect &surface_rect) { void HudRendererSP::drawSpeedLimitPreActiveArrow(QPainter &p, QRect &sign_rect) { const int sign_margin = 12; - const int arrow_spacing = sign_margin * 3; + const int arrow_spacing = sign_margin * 1.4; int arrow_x = sign_rect.right() + arrow_spacing; int _set_speed = std::nearbyint(set_speed); @@ -739,11 +739,11 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) { } void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const QString &alert_alt_text) { - int size = devUiInfo > 0 ? e2e_alert_small : e2e_alert_large; - int x = surface_rect.center().x() + surface_rect.width() / 4; + if (!allow_e2e_alerts) return; + + int x = surface_rect.right() - e2e_alert_size - (devUiInfo > 0 ? 180 : 100) - (UI_BORDER_SIZE * 3); int y = surface_rect.center().y() + 20; - x += devUiInfo > 0 ? 0 : 50; - QRect alertRect(x - size, y - size, size * 2, size * 2); + QRect alertRect(x - e2e_alert_size, y - e2e_alert_size, e2e_alert_size * 2, e2e_alert_size * 2); // Alert Circle QPoint center = alertRect.center(); @@ -752,7 +752,7 @@ void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const Q else frameColor = pulseElement(e2eAlertFrame) ? QColor(255, 255, 255, 75) : QColor(0, 255, 0, 75); p.setPen(QPen(frameColor, 15)); p.setBrush(QColor(0, 0, 0, 190)); - p.drawEllipse(center, size, size); + p.drawEllipse(center, e2e_alert_size, e2e_alert_size); // Alert Text QColor txtColor; diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.h b/selfdrive/ui/sunnypilot/qt/onroad/hud.h index a32fdb28a7..bc80a5e8bc 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.h @@ -97,16 +97,14 @@ private: int speedLimitAssistFrame; QPixmap plus_arrow_up_img; QPixmap minus_arrow_down_img; - int e2e_alert_small = 250; - int e2e_alert_large = 300; - QPixmap green_light_alert_small_img; - QPixmap green_light_alert_large_img; + int e2e_alert_size = 300; + QPixmap green_light_alert_img; bool greenLightAlert; int e2eAlertFrame; int e2eAlertDisplayTimer = 0; + bool allow_e2e_alerts; bool leadDepartAlert; - QPixmap lead_depart_alert_small_img; - QPixmap lead_depart_alert_large_img; + QPixmap lead_depart_alert_img; QString alert_text; QPixmap alert_img; bool hideVEgoUI; From 437726b3486c2e214205577ad009b11c79668cdc Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 15 Oct 2025 18:05:50 -0400 Subject: [PATCH 214/681] Speed Limit Mode: only cleanup param if Assist was selected (#1393) Speed Limit Mode: only cleanup param if it was Assist --- .../settings/longitudinal/speed_limit/speed_limit_settings.cc | 4 +++- sunnypilot/selfdrive/car/interfaces.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc index 95aa4ef26f..5c3b03d2af 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc @@ -124,7 +124,9 @@ void SpeedLimitSettings::refresh() { intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable(); if (!has_longitudinal_control && CP_SP.getPcmCruiseSpeed()) { - params.put("SpeedLimitMode", std::to_string(static_cast(SpeedLimitMode::WARNING))); + if (speed_limit_mode_param == SpeedLimitMode::ASSIST) { + params.put("SpeedLimitMode", std::to_string(static_cast(SpeedLimitMode::WARNING))); + } } } else { has_longitudinal_control = false; diff --git a/sunnypilot/selfdrive/car/interfaces.py b/sunnypilot/selfdrive/car/interfaces.py index 5467639a63..6a868ab85b 100644 --- a/sunnypilot/selfdrive/car/interfaces.py +++ b/sunnypilot/selfdrive/car/interfaces.py @@ -86,7 +86,9 @@ def _cleanup_unsupported_params(CP: structs.CarParams, CP_SP: structs.CarParamsS params.remove("CustomAccIncrementsEnabled") params.remove("SmartCruiseControlVision") params.remove("SmartCruiseControlMap") - params.put("SpeedLimitMode", int(SpeedLimitMode.warning)) + + if params.get("SpeedLimitMode", return_default=True) == SpeedLimitMode.assist: + params.put("SpeedLimitMode", int(SpeedLimitMode.warning)) def setup_interfaces(CI: CarInterfaceBase, params: Params = None) -> None: From 530ad2925df5f29cb213a2907ae5841ac6400042 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Wed, 15 Oct 2025 17:03:49 -0700 Subject: [PATCH 215/681] 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 4a62c996c7..ff7163793d 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 216/681] 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 f4ab1a05c6..a817fc53ad 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 a7fa4369a9..b41e4e7cd3 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 141f682db0..fa6e5ed7dc 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 85bc477bf6..69aab81bc0 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 d995ec1b52..a3cabcac30 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 a50881c8cb..3bbf2e6402 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 604cd59fd0..813e9cca8c 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 217/681] 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 b41e4e7cd3..e362703cd7 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 69aab81bc0..6663c4e0ee 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 a3cabcac30..6407d17d53 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 218/681] 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 6407d17d53..32cc7063d2 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 219/681] 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 e779ead334..bbbd52a8cd 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 b0f122227e..46a5877355 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 519f567d3f..dc85c0fafa 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 fcb7b25ccd..b91090e051 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 50462a1d01e639c8f823f5ed550b2fd29e01f667 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 16 Oct 2025 00:55:17 -0400 Subject: [PATCH 220/681] E2E Alert: universal state machine (#1395) * E2E Helper: universal state machine * not used * rename * 10 frames for both * time based * magic * lead depart: only arm if we have a confirmed close lead for over a second after allowing alert * less * shorter trigger * lol * always update --- selfdrive/ui/sunnypilot/qt/onroad/hud.cc | 18 +-- .../controls/lib/e2e_alerts_helper.py | 138 ++++++++++++++---- 2 files changed, 116 insertions(+), 40 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc index 5db7ba1814..2fc9eb98fc 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.cc +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.cc @@ -67,9 +67,9 @@ void HudRendererSP::updateState(const UIState &s) { smartCruiseControlVisionActive = lp_sp.getSmartCruiseControl().getVision().getActive(); smartCruiseControlMapEnabled = lp_sp.getSmartCruiseControl().getMap().getEnabled(); smartCruiseControlMapActive = lp_sp.getSmartCruiseControl().getMap().getActive(); - greenLightAlert = lp_sp.getE2eAlerts().getGreenLightAlert(); - leadDepartAlert = lp_sp.getE2eAlerts().getLeadDepartAlert(); } + greenLightAlert = lp_sp.getE2eAlerts().getGreenLightAlert(); + leadDepartAlert = lp_sp.getE2eAlerts().getLeadDepartAlert(); if (sm.updated("liveMapDataSP")) { roadNameStr = QString::fromStdString(lmd.getRoadName()); @@ -138,7 +138,7 @@ void HudRendererSP::updateState(const UIState &s) { steeringTorqueEps = car_state.getSteeringTorqueEps(); isStandstill = car_state.getStandstill(); - if (not s.scene.started) standstillElapsedTime = 0.0; + if (!s.scene.started) standstillElapsedTime = 0.0; // override stock current speed values float v_ego = (v_ego_cluster_seen && !s.scene.trueVEgoUI) ? car_state.getVEgoCluster() : car_state.getVEgo(); @@ -246,7 +246,7 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) { drawRoadName(p, surface_rect); // Green Light & Lead Depart Alerts - if (greenLightAlert or leadDepartAlert) { + if (greenLightAlert || leadDepartAlert) { e2eAlertDisplayTimer = 3 * UI_FREQ; // reset onroad sleep timer for e2e alerts uiStateSP()->reset_onroad_sleep_timer(); @@ -278,7 +278,7 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) { // No Alerts displayed else { e2eAlertFrame = 0; - if (not isStandstill) standstillElapsedTime = 0.0; + if (!isStandstill) standstillElapsedTime = 0.0; } // Blinker @@ -748,7 +748,7 @@ void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const Q // Alert Circle QPoint center = alertRect.center(); QColor frameColor; - if (not alert_alt_text.isEmpty()) frameColor = QColor(255, 255, 255, 75); + if (!alert_alt_text.isEmpty()) frameColor = QColor(255, 255, 255, 75); else frameColor = pulseElement(e2eAlertFrame) ? QColor(255, 255, 255, 75) : QColor(0, 255, 0, 75); p.setPen(QPen(frameColor, 15)); p.setBrush(QColor(0, 0, 0, 190)); @@ -758,7 +758,7 @@ void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const Q QColor txtColor; QFont font; int alert_bottom_adjustment; - if (not alert_alt_text.isEmpty()) { + if (!alert_alt_text.isEmpty()) { font = InterFont(100, QFont::Bold); alert_bottom_adjustment = 5; txtColor = QColor(255, 255, 255, 255); @@ -775,7 +775,7 @@ void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const Q textRect.moveBottom(alertRect.bottom() - alertRect.height() / alert_bottom_adjustment); p.drawText(textRect, Qt::AlignCenter, alert_text); - if (not alert_alt_text.isEmpty()) { + if (!alert_alt_text.isEmpty()) { // Alert Alternate Text p.setFont(InterFont(80, QFont::Bold)); p.setPen(QColor(255, 175, 3, 240)); @@ -804,7 +804,7 @@ void HudRendererSP::drawCurrentSpeedSP(QPainter &p, const QRect &surface_rect) { void HudRendererSP::drawBlinker(QPainter &p, const QRect &surface_rect) { const bool hazard = leftBlinkerOn && rightBlinkerOn; - int blinkerStatus = hazard ? 2 : (leftBlinkerOn or rightBlinkerOn) ? 1 : 0; + int blinkerStatus = hazard ? 2 : (leftBlinkerOn || rightBlinkerOn) ? 1 : 0; if (!leftBlinkerOn && !rightBlinkerOn) { blinkerFrameCounter = 0; diff --git a/sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py b/sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py index 5a92d878d6..944bf617e9 100644 --- a/sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py +++ b/sunnypilot/selfdrive/controls/lib/e2e_alerts_helper.py @@ -13,80 +13,156 @@ from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP GREEN_LIGHT_X_THRESHOLD = 30 +LEAD_DEPART_DIST_THRESHOLD = 1.0 +TRIGGER_TIMER_THRESHOLD = 0.3 + + +class E2EStates: + INACTIVE = 0 + ARMED = 1 + CONSUMED = 2 class E2EAlertsHelper: def __init__(self): self._params = Params() self.frame = -1 + self.green_light_state = E2EStates.INACTIVE + self.prev_green_light_state = E2EStates.INACTIVE + self.lead_depart_state = E2EStates.INACTIVE + self.prev_lead_depart_state = E2EStates.INACTIVE self.green_light_alert = False self.green_light_alert_enabled = self._params.get_bool("GreenLightAlert") self.lead_depart_alert = False self.lead_depart_alert_enabled = self._params.get_bool("LeadDepartAlert") - self.alert_allowed = False - self.green_light_alert_count = 0 + self.green_light_trigger_timer = 0 + self.lead_depart_trigger_timer = 0 self.last_lead_distance = -1 self.last_moving_frame = -1 + self.allowed = False + self.last_allowed = False + self.has_lead = False + + self.lead_depart_arm_timer = 0 + self.lead_depart_confirmed_lead = False + self.lead_depart_armed = False + def _read_params(self) -> None: if self.frame % int(PARAMS_UPDATE_PERIOD / DT_MDL) == 0: self.green_light_alert_enabled = self._params.get_bool("GreenLightAlert") self.lead_depart_alert_enabled = self._params.get_bool("LeadDepartAlert") - def update(self, sm: messaging.SubMaster, events_sp: EventsSP) -> None: - self._read_params() - + def update_alert_trigger(self, sm: messaging.SubMaster): CS = sm['carState'] CC = sm['carControl'] model_x = sm['modelV2'].position.x max_idx = len(model_x) - 1 - has_lead = sm['radarState'].leadOne.status + self.has_lead = sm['radarState'].leadOne.status lead_dRel = sm['radarState'].leadOne.dRel + standstill = CS.standstill moving = not standstill and CS.vEgo > 0.1 - _allowed = standstill and not CS.gasPressed and not CC.enabled if moving: self.last_moving_frame = self.frame recent_moving = self.last_moving_frame == -1 or (self.frame - self.last_moving_frame) * DT_MDL < 2.0 - if standstill and not recent_moving: - self.alert_allowed = True - elif not standstill: - self.alert_allowed = False - self.green_light_alert_count = 0 - self.last_lead_distance = -1 + self.allowed = not moving and not CS.gasPressed and not CC.enabled and not recent_moving # Green Light Alert - _green_light_alert = False - if self.green_light_alert_enabled and _allowed and not has_lead and model_x[max_idx] > GREEN_LIGHT_X_THRESHOLD: - if self.alert_allowed: - self.green_light_alert_count += 1 + green_light_trigger = False + if self.green_light_state == E2EStates.ARMED: + if model_x[max_idx] > GREEN_LIGHT_X_THRESHOLD: + self.green_light_trigger_timer += 1 else: - self.green_light_alert_count = 0 + self.green_light_trigger_timer = 0 - if self.green_light_alert_count > 2 and self.alert_allowed: - _green_light_alert = True - self.alert_allowed = False - else: - self.green_light_alert_count = 0 - - self.green_light_alert = _green_light_alert + if self.green_light_trigger_timer * DT_MDL > TRIGGER_TIMER_THRESHOLD: + green_light_trigger = True + elif self.green_light_state != E2EStates.ARMED: + self.green_light_trigger_timer = 0 # Lead Departure Alert - _lead_depart_alert = False - if self.lead_depart_alert_enabled and _allowed and has_lead: + close_lead_valid = self.has_lead and lead_dRel < 8.0 + if self.allowed and not self.last_allowed and close_lead_valid: + self.lead_depart_confirmed_lead = True + elif not self.allowed: + self.lead_depart_confirmed_lead = False + + if self.allowed and self.lead_depart_confirmed_lead and close_lead_valid: + self.lead_depart_arm_timer += 1 + + if self.lead_depart_arm_timer * DT_MDL >= 1.0: + self.lead_depart_armed = True + else: + self.lead_depart_arm_timer = 0 + self.lead_depart_armed = False + + lead_depart_trigger = False + if self.lead_depart_state == E2EStates.ARMED: if self.last_lead_distance == -1 or lead_dRel < self.last_lead_distance: self.last_lead_distance = lead_dRel - if self.last_lead_distance != -1 and (lead_dRel - self.last_lead_distance > 1.0) and self.alert_allowed: - _lead_depart_alert = True - self.alert_allowed = False + if self.last_lead_distance != -1 and (lead_dRel - self.last_lead_distance > LEAD_DEPART_DIST_THRESHOLD): + self.lead_depart_trigger_timer += 1 + else: + self.lead_depart_trigger_timer = 0 - self.lead_depart_alert = _lead_depart_alert + if self.lead_depart_trigger_timer * DT_MDL > TRIGGER_TIMER_THRESHOLD: + lead_depart_trigger = True + elif self.lead_depart_state != E2EStates.ARMED: + self.last_lead_distance = -1 + self.lead_depart_trigger_timer = 0 + + self.last_allowed = self.allowed + + return green_light_trigger, lead_depart_trigger + + @staticmethod + def update_state_machine(state: int, enabled: bool, allowed: bool, triggered: bool) -> tuple[int, bool]: + if state != E2EStates.INACTIVE: + if not allowed or not enabled: + state = E2EStates.INACTIVE + + else: + if state == E2EStates.ARMED: + if triggered: + state = E2EStates.CONSUMED + + elif state == E2EStates.CONSUMED: + pass + + elif state == E2EStates.INACTIVE: + if allowed and enabled: + state = E2EStates.ARMED + + return state, triggered + + def update(self, sm: messaging.SubMaster, events_sp: EventsSP) -> None: + self._read_params() + + green_light_trigger, lead_depart_trigger = self.update_alert_trigger(sm) + + self.prev_green_light_state = self.green_light_state + self.prev_lead_depart_state = self.lead_depart_state + + self.green_light_state, self.green_light_alert = self.update_state_machine( + self.green_light_state, + self.green_light_alert_enabled, + self.allowed and not self.has_lead, + green_light_trigger + ) + + self.lead_depart_state, self.lead_depart_alert = self.update_state_machine( + self.lead_depart_state, + self.lead_depart_alert_enabled, + self.allowed and self.lead_depart_armed, + lead_depart_trigger + ) if self.green_light_alert or self.lead_depart_alert: events_sp.add(custom.OnroadEventSP.EventName.e2eChime) From 063aa994d267d730f1400eebde18992be50e4883 Mon Sep 17 00:00:00 2001 From: Nayan Date: Thu, 16 Oct 2025 01:03:45 -0400 Subject: [PATCH 221/681] ui: Resize E2E Alerts (#1396) because people be enabling ALL THE UI Co-authored-by: Jason Wen --- selfdrive/ui/sunnypilot/qt/onroad/hud.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/qt/onroad/hud.h b/selfdrive/ui/sunnypilot/qt/onroad/hud.h index bc80a5e8bc..f021a949e4 100644 --- a/selfdrive/ui/sunnypilot/qt/onroad/hud.h +++ b/selfdrive/ui/sunnypilot/qt/onroad/hud.h @@ -97,7 +97,7 @@ private: int speedLimitAssistFrame; QPixmap plus_arrow_up_img; QPixmap minus_arrow_down_img; - int e2e_alert_size = 300; + int e2e_alert_size = 250; QPixmap green_light_alert_img; bool greenLightAlert; int e2eAlertFrame; From b29b1964ba232f407f164212ddfe84d96395dfb7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 15 Oct 2025 22:10:55 -0700 Subject: [PATCH 222/681] 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 3f3958f9d3..013287aaeb 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 223/681] 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 362f49a51e..0f944bac52 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 013287aaeb..6de774648a 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 fa6e5ed7dc..15d48b4e13 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 32cc7063d2..99aed529a7 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 224/681] 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 cc1605a132..50ac49dd7c 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 67dd5ee795..74bcf1bae6 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 9fc76489b8..40f13e11cb 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 d93963cf2c..bd731f5dd5 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 ebffc01dfd..38b5ebe7c1 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 23cdc140f4..69ce323a10 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 0000000000..2487973507 --- /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 0000000000..23cdc140f4 --- /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 f786213da7..870f4d5975 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 e1c5c19307..a1c5e903e1 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 225/681] 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 a9b84b5c06..5cb0a38e0b 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 e362703cd7..5dbf597484 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 226/681] build new staging branch --- Jenkinsfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index ad8e85136b..73fa74c1cd 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 227/681] 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 125053f17e..1e4fbbb783 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 44c26c80e5..28585b08ba 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 228/681] 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 175233c5ba..e5d8171413 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 229/681] 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 40f13e11cb..00abc525a0 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 744fdbf135..5098b6a06c 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 230/681] 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 9c5a8348f9..f59509715f 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 231/681] 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 870f4d5975..0b50244482 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 a1c5e903e1..954aa0d486 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 232/681] 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 98909bfb52..d56f11bcc2 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 233/681] 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 d56f11bcc2..2a1442a20c 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 234/681] 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 5fb6fd5737..3eb72ec104 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 b8a1e09889..0c35a3d3c9 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 235/681] 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 00abc525a0..0c3bdea4c8 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 236/681] 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 74bcf1bae6..5c14af6dcb 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 bd731f5dd5..25cbe9f321 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 38b5ebe7c1..298571f31c 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 237/681] 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 5c14af6dcb..74bcf1bae6 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 25cbe9f321..bd731f5dd5 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 298571f31c..38b5ebe7c1 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 238/681] 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 74bcf1bae6..07ec162f0b 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 bd731f5dd5..035d8aa011 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 38b5ebe7c1..6b99df1c38 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 239/681] 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 6de774648a..2e96b3bd43 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 240/681] 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 46a5877355..0538570e4a 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 241/681] 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 252a7dd94d..3778586f89 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 2825c00fcc72c30f4e8768e7e60c6db02513b1a0 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 17 Oct 2025 22:53:31 -0400 Subject: [PATCH 242/681] controlsd: update lateral delay param in a separate thread (#1402) --- selfdrive/controls/controlsd.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 3601ae310e..1694afee23 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -99,7 +99,6 @@ class Controls(ControlsExt, ModelStateBase): self.LaC.extension.update_model_v2(self.sm['modelV2']) - self.lat_delay = get_lat_delay(self.params, self.sm["liveDelay"].lateralDelay) self.LaC.extension.update_lateral_lag(self.lat_delay) long_plan = self.sm['longitudinalPlan'] @@ -234,6 +233,9 @@ class Controls(ControlsExt, ModelStateBase): 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): From 72282f2d2e0ffeab3b49fc82e1504e08f55d279f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 17 Oct 2025 23:30:06 -0400 Subject: [PATCH 243/681] Speed Limit Assist: update events handling (#1400) * Speed Limit Assist: update active event handling * ok no more for non pcm long it was annoying * 5 seconds preActive for non pcm long now * Revert "5 seconds preActive for non pcm long now" This reverts commit dfcc601035f4c34c8ce213cb221e9148b96fdcc3. * dynamic alert size * do the same here * lint --- .../lib/speed_limit/speed_limit_assist.py | 18 +++++++++++---- sunnypilot/selfdrive/selfdrived/events.py | 23 +++++++------------ 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py index 302d8cd14d..e2e1125a79 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py @@ -109,6 +109,16 @@ class SpeedLimitAssist: def target_set_speed_confirmed(self) -> bool: return bool(self.v_cruise_cluster_conv == self.target_set_speed_conv) + @property + def v_cruise_cluster_below_confirm_speed_threshold(self) -> bool: + return bool(self.v_cruise_cluster_conv < CONFIRM_SPEED_THRESHOLD[self.is_metric]) + + def update_active_event(self, events_sp: EventsSP) -> None: + if self.v_cruise_cluster_below_confirm_speed_threshold: + events_sp.add(EventNameSP.speedLimitChanged) + else: + events_sp.add(EventNameSP.speedLimitActive) + def get_v_target_from_control(self) -> float: if self._has_speed_limit: if self.pcm_op_long and self.is_enabled: @@ -175,7 +185,7 @@ class SpeedLimitAssist: @property def apply_confirm_speed_threshold(self) -> bool: # below CST: always require user confirmation - if self.v_cruise_cluster_conv < CONFIRM_SPEED_THRESHOLD[self.is_metric]: + if self.v_cruise_cluster_below_confirm_speed_threshold: return True # at/above CST: @@ -351,15 +361,15 @@ class SpeedLimitAssist: if self.is_active: if self._state_prev not in ACTIVE_STATES: - events_sp.add(EventNameSP.speedLimitActive) + self.update_active_event(events_sp) # only notify if we acquire a valid speed limit # do not check has_speed_limit here elif self._speed_limit != self.speed_limit_prev: if self.speed_limit_prev <= 0: - events_sp.add(EventNameSP.speedLimitActive) + self.update_active_event(events_sp) elif self.speed_limit_prev > 0 and self._speed_limit > 0: - events_sp.add(EventNameSP.speedLimitChanged) + self.update_active_event(events_sp) def update(self, long_enabled: bool, long_override: bool, v_ego: float, a_ego: float, v_cruise_cluster: float, speed_limit: float, speed_limit_final_last: float, has_speed_limit: bool, distance: float, events_sp: EventsSP) -> None: diff --git a/sunnypilot/selfdrive/selfdrived/events.py b/sunnypilot/selfdrive/selfdrived/events.py index 5d5424bb16..4edc0bd470 100644 --- a/sunnypilot/selfdrive/selfdrived/events.py +++ b/sunnypilot/selfdrive/selfdrived/events.py @@ -4,7 +4,6 @@ from openpilot.common.constants import CV from openpilot.sunnypilot.selfdrive.selfdrived.events_base import EventsBase, Priority, ET, Alert, \ NoEntryAlert, ImmediateDisableAlert, EngagementAlert, NormalPermanentAlert, AlertCallbackType, wrong_car_mode_alert from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import PCM_LONG_REQUIRED_MAX_SET_SPEED, CONFIRM_SPEED_THRESHOLD -from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.helpers import compare_cluster_target AlertSize = log.SelfdriveState.AlertSize @@ -34,6 +33,9 @@ def speed_limit_pre_active_alert(CP: car.CarParams, CS: car.CarState, sm: messag speed_conv = CV.MS_TO_KPH if metric else CV.MS_TO_MPH speed_limit_final_last = sm['longitudinalPlanSP'].speedLimit.resolver.speedLimitFinalLast speed_limit_final_last_conv = round(speed_limit_final_last * speed_conv) + alert_1_str = "" + alert_2_str = "" + alert_size = AlertSize.none if CP.openpilotLongitudinalControl and CP.pcmCruise: # PCM long @@ -41,24 +43,15 @@ def speed_limit_pre_active_alert(CP: car.CarParams, CS: car.CarState, sm: messag pcm_long_required_max = cst_low if speed_limit_final_last_conv < CONFIRM_SPEED_THRESHOLD[metric] else cst_high pcm_long_required_max_set_speed_conv = round(pcm_long_required_max * speed_conv) speed_unit = "km/h" if metric else "mph" + + alert_1_str = "Speed Limit Assist: Activation Required" alert_2_str = f"Manually change set speed to {pcm_long_required_max_set_speed_conv} {speed_unit} to activate" - else: - # Non PCM long - v_cruise_cluster = CS.vCruiseCluster * CV.KPH_TO_MS - - req_plus, req_minus = compare_cluster_target(v_cruise_cluster, speed_limit_final_last, metric) - arrow_str = "" - if req_plus: - arrow_str = "RES/+" - elif req_minus: - arrow_str = "SET/-" - - alert_2_str = f"Operate the {arrow_str} cruise control button to activate" + alert_size = AlertSize.mid return Alert( - "Speed Limit Assist: Activation Required", + alert_1_str, alert_2_str, - AlertStatus.normal, AlertSize.mid, + AlertStatus.normal, alert_size, Priority.LOW, VisualAlert.none, AudibleAlertSP.promptSingleLow, .1) From 523c92c6fe06a3564fa5d0208f24fcd21c6d3628 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 17 Oct 2025 23:41:33 -0400 Subject: [PATCH 244/681] Speed Limit Assist: lower `preActive` timer for Non PCM Longitudinal and ICBM cars (#1403) 5 seconds preActive for non pcm long now --- .../lib/speed_limit/speed_limit_assist.py | 20 +++++++++++-------- .../tests/test_speed_limit_assist.py | 4 ++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py index e2e1125a79..b948452059 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py @@ -27,7 +27,11 @@ ACTIVE_STATES = (SpeedLimitAssistState.active, SpeedLimitAssistState.adapting) ENABLED_STATES = (SpeedLimitAssistState.preActive, SpeedLimitAssistState.pending, *ACTIVE_STATES) DISABLED_GUARD_PERIOD = 0.5 # secs. -PRE_ACTIVE_GUARD_PERIOD = 15 # secs. Time to wait after activation before considering temp deactivation signal. +# secs. Time to wait after activation before considering temp deactivation signal. +PRE_ACTIVE_GUARD_PERIOD = { + True: 15, + False: 5, +} SPEED_LIMIT_CHANGED_HOLD_PERIOD = 1 # secs. Time to wait after speed limit change before switching to preActive. LIMIT_MIN_ACC = -1.5 # m/s^2 Maximum deceleration allowed for limit controllers to provide. @@ -241,7 +245,7 @@ class SpeedLimitAssist: self.state = SpeedLimitAssistState.inactive elif self.speed_limit_changed and self.apply_confirm_speed_threshold: self.state = SpeedLimitAssistState.preActive - self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) + self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL) elif self._has_speed_limit and self.v_offset < LIMIT_SPEED_OFFSET_TH: self.state = SpeedLimitAssistState.adapting @@ -251,7 +255,7 @@ class SpeedLimitAssist: self.state = SpeedLimitAssistState.inactive elif self.speed_limit_changed and self.apply_confirm_speed_threshold: self.state = SpeedLimitAssistState.preActive - self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) + self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL) elif self.v_offset >= LIMIT_SPEED_OFFSET_TH: self.state = SpeedLimitAssistState.active @@ -261,7 +265,7 @@ class SpeedLimitAssist: self._update_confirmed_state() elif self.speed_limit_changed: self.state = SpeedLimitAssistState.preActive - self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) + self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL) # PRE_ACTIVE elif self.state == SpeedLimitAssistState.preActive: @@ -287,7 +291,7 @@ class SpeedLimitAssist: self._update_confirmed_state() elif self._has_speed_limit: self.state = SpeedLimitAssistState.preActive - self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) + self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL) else: self.state = SpeedLimitAssistState.pending @@ -313,7 +317,7 @@ class SpeedLimitAssist: elif self.speed_limit_changed and self.apply_confirm_speed_threshold: self.state = SpeedLimitAssistState.preActive - self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) + self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL) # PRE_ACTIVE elif self.state == SpeedLimitAssistState.preActive: @@ -327,7 +331,7 @@ class SpeedLimitAssist: elif self.state == SpeedLimitAssistState.inactive: if self.speed_limit_changed: self.state = SpeedLimitAssistState.preActive - self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) + self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL) elif self._update_non_pcm_long_confirmed_state(): self.state = SpeedLimitAssistState.active @@ -343,7 +347,7 @@ class SpeedLimitAssist: self.state = SpeedLimitAssistState.active elif self._has_speed_limit: self.state = SpeedLimitAssistState.preActive - self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) + self.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.pcm_op_long] / DT_MDL) else: self.state = SpeedLimitAssistState.inactive diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py index d2c7a4716d..168c53016b 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py @@ -39,7 +39,7 @@ class TestSpeedLimitAssist: self.events_sp = EventsSP() CI = self._setup_platform(TOYOTA.TOYOTA_RAV4_TSS2) self.sla = SpeedLimitAssist(CI.CP) - self.sla.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL) + self.sla.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.sla.pcm_op_long] / DT_MDL) self.pcm_long_max_set_speed = PCM_LONG_REQUIRED_MAX_SET_SPEED[self.sla.is_metric][1] # use 80 MPH for now self.speed_conv = CV.MS_TO_KPH if self.sla.is_metric else CV.MS_TO_MPH @@ -114,7 +114,7 @@ class TestSpeedLimitAssist: self.sla.state = SpeedLimitAssistState.preActive self.sla.update(True, False, SPEED_LIMITS['city'], 0, SPEED_LIMITS['highway'], SPEED_LIMITS['city'], SPEED_LIMITS['city'], True, 0, self.events_sp) - for _ in range(int(PRE_ACTIVE_GUARD_PERIOD / DT_MDL)): + for _ in range(int(PRE_ACTIVE_GUARD_PERIOD[self.sla.pcm_op_long] / DT_MDL)): self.sla.update(True, False, SPEED_LIMITS['city'], 0, SPEED_LIMITS['highway'], SPEED_LIMITS['city'], SPEED_LIMITS['city'], True, 0, self.events_sp) assert self.sla.state == SpeedLimitAssistState.inactive From 025a930ce8f4cbf1650ac5bfc12dd7cdd194fd37 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 18 Oct 2025 04:04:48 -0400 Subject: [PATCH 245/681] ui: update longitudinal-related settings handling (#1401) * ui: update ICBM-related settings handling * oops * oops * single location * some more * fix cruise toggles * always init true * check this * nah * should be this --- .../speed_limit/speed_limit_settings.cc | 13 +++-- .../speed_limit/speed_limit_settings.h | 1 + .../qt/offroad/settings/longitudinal_panel.cc | 50 +++++++++++-------- .../qt/offroad/settings/longitudinal_panel.h | 2 +- selfdrive/ui/sunnypilot/qt/util.cc | 4 ++ selfdrive/ui/sunnypilot/qt/util.h | 1 + 6 files changed, 45 insertions(+), 26 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc index 5c3b03d2af..1696c7f63a 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc @@ -7,6 +7,8 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h" +#include "selfdrive/ui/sunnypilot/qt/util.h" + SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent) { subPanelFrame = new QFrame(); QVBoxLayout *subPanelLayout = new QVBoxLayout(subPanelFrame); @@ -109,7 +111,7 @@ void SpeedLimitSettings::refresh() { QString offsetLabel = QString::fromStdString(params.get("SpeedLimitValueOffset")); bool has_longitudinal_control; - bool intelligent_cruise_button_management_available; + bool has_icbm; auto cp_bytes = params.get("CarParamsPersistent"); auto cp_sp_bytes = params.get("CarParamsSPPersistent"); if (!cp_bytes.empty() && !cp_sp_bytes.empty()) { @@ -121,16 +123,16 @@ void SpeedLimitSettings::refresh() { cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot(); has_longitudinal_control = hasLongitudinalControl(CP); - intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable(); + has_icbm = hasIntelligentCruiseButtonManagement(CP_SP); - if (!has_longitudinal_control && CP_SP.getPcmCruiseSpeed()) { + if (!has_longitudinal_control && !has_icbm) { if (speed_limit_mode_param == SpeedLimitMode::ASSIST) { params.put("SpeedLimitMode", std::to_string(static_cast(SpeedLimitMode::WARNING))); } } } else { has_longitudinal_control = false; - intelligent_cruise_button_management_available = false; + has_icbm = false; } speed_limit_mode_settings->setDescription(modeDescription(speed_limit_mode_param)); @@ -150,13 +152,14 @@ void SpeedLimitSettings::refresh() { speed_limit_offset->showDescription(); } - if (has_longitudinal_control || intelligent_cruise_button_management_available) { + if (has_longitudinal_control || has_icbm) { speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues(getSpeedLimitModeValues())); } else { speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues( {SpeedLimitMode::OFF, SpeedLimitMode::INFORMATION, SpeedLimitMode::WARNING})); } + speed_limit_mode_settings->refresh(); speed_limit_mode_settings->showDescription(); speed_limit_offset->showDescription(); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h index 23fa7b4ece..69e85814c2 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h @@ -35,6 +35,7 @@ private: SpeedLimitPolicy *speedLimitPolicyScreen; ButtonParamControlSP *speed_limit_offset_settings; OptionControlSP *speed_limit_offset; + bool icbm_available = false; static QString offsetDescription(SpeedLimitOffsetType type = SpeedLimitOffsetType::NONE) { QString none_str = tr("⦿ None: No Offset"); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc index f2c7ea3825..2cc59ea333 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc @@ -7,6 +7,8 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h" +#include "selfdrive/ui/sunnypilot/qt/util.h" + LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) { setStyleSheet(R"( #back_btn { @@ -40,7 +42,9 @@ LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) { "", this ); - intelligentCruiseButtonManagement->setConfirmation(true, false); + QObject::connect(intelligentCruiseButtonManagement, &ParamControlSP::toggleFlipped, this, [=](bool) { + refresh(offroad); + }); list->addItem(intelligentCruiseButtonManagement); dynamicExperimentalControl = new ParamControlSP( @@ -112,22 +116,41 @@ void LongitudinalPanel::refresh(bool _offroad) { has_longitudinal_control = hasLongitudinalControl(CP); is_pcm_cruise = CP.getPcmCruise(); - intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable(); + has_icbm = hasIntelligentCruiseButtonManagement(CP_SP); - if (!intelligent_cruise_button_management_available || has_longitudinal_control) { + if (CP_SP.getIntelligentCruiseButtonManagementAvailable() && !has_longitudinal_control) { + intelligentCruiseButtonManagement->setEnabled(offroad); + } else { params.remove("IntelligentCruiseButtonManagement"); + intelligentCruiseButtonManagement->setEnabled(false); } - if (!has_longitudinal_control && CP_SP.getPcmCruiseSpeed()) { + if (has_longitudinal_control || has_icbm) { + // enable Custom ACC Increments when long is available and is not PCM cruise + customAccIncrement->setEnabled(((has_longitudinal_control && !is_pcm_cruise) || has_icbm) && offroad); + dynamicExperimentalControl->setEnabled(has_longitudinal_control); + SmartCruiseControlVision->setEnabled(true); + SmartCruiseControlMap->setEnabled(true); + } else { params.remove("CustomAccIncrementsEnabled"); params.remove("DynamicExperimentalControl"); params.remove("SmartCruiseControlVision"); params.remove("SmartCruiseControlMap"); + customAccIncrement->setEnabled(false); + dynamicExperimentalControl->setEnabled(false); + SmartCruiseControlVision->setEnabled(false); + SmartCruiseControlMap->setEnabled(false); } + + intelligentCruiseButtonManagement->refresh(); + customAccIncrement->refresh(); + dynamicExperimentalControl->refresh(); + SmartCruiseControlVision->refresh(); + SmartCruiseControlMap->refresh(); } else { has_longitudinal_control = false; is_pcm_cruise = false; - intelligent_cruise_button_management_available = false; + has_icbm = false; } QString accEnabledDescription = tr("Enable custom Short & Long press increments for cruise speed increase/decrease."); @@ -139,8 +162,8 @@ void LongitudinalPanel::refresh(bool _offroad) { customAccIncrement->setDescription(onroadOnlyDescription); customAccIncrement->showDescription(); } else { - if (has_longitudinal_control || intelligent_cruise_button_management_available) { - if (is_pcm_cruise) { + if (has_longitudinal_control || has_icbm) { + if (has_longitudinal_control && is_pcm_cruise) { customAccIncrement->setDescription(accPcmCruiseDisabledDescription); customAccIncrement->showDescription(); } else { @@ -150,21 +173,8 @@ void LongitudinalPanel::refresh(bool _offroad) { customAccIncrement->toggleFlipped(false); customAccIncrement->setDescription(accNoLongDescription); customAccIncrement->showDescription(); - intelligentCruiseButtonManagement->toggleFlipped(false); } } - bool icbm_allowed = intelligent_cruise_button_management_available && !has_longitudinal_control; - intelligentCruiseButtonManagement->setEnabled(icbm_allowed && offroad); - - // enable toggle when long is available and is not PCM cruise - bool cai_allowed = (has_longitudinal_control && !is_pcm_cruise) || icbm_allowed; - customAccIncrement->setEnabled(cai_allowed && !offroad); - customAccIncrement->refresh(); - - dynamicExperimentalControl->setEnabled(has_longitudinal_control); - SmartCruiseControlVision->setEnabled(has_longitudinal_control || icbm_allowed); - SmartCruiseControlMap->setEnabled(has_longitudinal_control || icbm_allowed); - offroad = _offroad; } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h index 127b7871eb..2b9c0b8968 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h @@ -25,7 +25,7 @@ private: Params params; bool has_longitudinal_control = false; bool is_pcm_cruise = false; - bool intelligent_cruise_button_management_available = false;; + bool has_icbm = false; bool offroad = false; QStackedLayout *main_layout = nullptr; diff --git a/selfdrive/ui/sunnypilot/qt/util.cc b/selfdrive/ui/sunnypilot/qt/util.cc index be39b297d7..eaa1f4bd12 100644 --- a/selfdrive/ui/sunnypilot/qt/util.cc +++ b/selfdrive/ui/sunnypilot/qt/util.cc @@ -122,3 +122,7 @@ std::optional loadCerealEvent(Params& params, const std:: return std::nullopt; } } + +bool hasIntelligentCruiseButtonManagement(const cereal::CarParamsSP::Reader &car_params_sp) { + return car_params_sp.getIntelligentCruiseButtonManagementAvailable() && Params().getBool("IntelligentCruiseButtonManagement"); +} diff --git a/selfdrive/ui/sunnypilot/qt/util.h b/selfdrive/ui/sunnypilot/qt/util.h index 4b9d615ce5..60a73615ba 100644 --- a/selfdrive/ui/sunnypilot/qt/util.h +++ b/selfdrive/ui/sunnypilot/qt/util.h @@ -23,3 +23,4 @@ std::optional getParamIgnoringDefault(const std::string ¶m_name, co QMap loadPlatformList(); QStringList searchFromList(const QString &query, const QStringList &list); std::optional loadCerealEvent(Params& params, const std::string& _param); +bool hasIntelligentCruiseButtonManagement(const cereal::CarParamsSP::Reader &car_params_sp); 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 246/681] 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 ff5084781f..e3fa8afdf4 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 4cc1c47f61..14ab9f21b5 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 664e866636..fbef5828e6 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 6d4f922461..62dbc842c5 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 4c7adc0a3e..365688429a 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 247/681] 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 38fc0e9209..bc19572507 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 248/681] =?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 89444c3ea7..1e764af9ba 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 0411ddb744..441c4a16af 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 249/681] 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 813e9cca8c..b7e30b696b 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 250/681] 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 ae12809eb9..8b5062ad9b 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 43c8e07de6..2fe2873a3d 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 251/681] 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 8996ad6460..94f5b33231 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 252/681] 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 dfa6807ebf..b59f8bdcca 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 fbef5828e6..f2b1e0b669 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 538aa34f2a..dd73ed12f7 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 253/681] 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 211b4d550b..f6e8d781c8 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 7e9eaf932f..5e7b02a1a3 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 ed0890d946..1587a88205 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 c8cc0d3e05..4bde921696 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 2ae3356bb8..edd9a30412 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 c2e4bbc552..6251e03397 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 132b5088d7..b0674dee82 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 65880bdad9..424b78851c 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 254/681] 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 b7e30b696b..593a371fd9 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 255/681] 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 dc85c0fafa..54f742952e 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 256/681] 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 519e0e020a..34a7558cdc 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 a817fc53ad..df259a8fb5 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 21eb44efe7..91268960cb 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 66341db37b..e9bdf59e26 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 e8eaaa44f2..f4d70eaa47 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 d43382f199..7f431d3a77 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 0c17a54fbe..8e0cfdbc3c 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 01195142e3..1e4d5c6c85 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 9337b3c239..48b577ea59 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 0f944bac52..a81fbfc440 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 a3bd2e23e0..543ea35e81 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 98c3d686fa..a2459c27e2 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 424b78851c..643d246012 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 23031be306..faa3bf877f 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 0b3ca8176f..0bd5b161b9 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 3778586f89..85b42d1a7a 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 bbbd52a8cd..49a0e56cdc 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 0538570e4a..ea88180ef8 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 dc3c5a4a76..e44c4aad5d 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 0000000000..c020b33d5e --- /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 274307395c..8c5ae0aa01 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 f90f78fe1b..368d02cdfc 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 6663c4e0ee..ac006c2545 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 1f4f69a6bf..55abe02fe1 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 3bbf2e6402..a59030363b 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 593a371fd9..8c63ca3f9f 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 c85b6a0d1c62537fd7e8341d7cf884deb359f504 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 21 Oct 2025 00:53:16 -0400 Subject: [PATCH 257/681] branches: track sunnypilot release branches separately (#1409) * branches: track sunnypilot release branches separately * more remotes for legacy support * bruh * revert --- common/params_keys.h | 1 + selfdrive/ui/qt/offroad/developer_panel.h | 2 +- .../qt/offroad/settings/developer_panel.cc | 5 ++++- system/manager/manager.py | 1 + system/version.py | 14 ++++++++++---- 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index dd58462a95..d8485be157 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -154,6 +154,7 @@ inline static std::unordered_map keys = { {"IntelligentCruiseButtonManagement", {PERSISTENT | BACKUP , BOOL}}, {"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}}, {"IsDevelopmentBranch", {CLEAR_ON_MANAGER_START, BOOL}}, + {"IsReleaseSpBranch", {CLEAR_ON_MANAGER_START, BOOL}}, {"LastGPSPositionLLK", {PERSISTENT, STRING}}, {"LeadDepartAlert", {PERSISTENT | BACKUP, BOOL, "0"}}, {"MaxTimeOffroad", {PERSISTENT | BACKUP, INT, "1800"}}, diff --git a/selfdrive/ui/qt/offroad/developer_panel.h b/selfdrive/ui/qt/offroad/developer_panel.h index 51b1b0b6f0..699fa1edcf 100644 --- a/selfdrive/ui/qt/offroad/developer_panel.h +++ b/selfdrive/ui/qt/offroad/developer_panel.h @@ -12,7 +12,7 @@ public: explicit DeveloperPanel(SettingsWindow *parent); void showEvent(QShowEvent *event) override; -private: +protected: Params params; ParamControl* adbToggle; ParamControl* joystickToggle; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc index 021f5b3776..9eb8da71ec 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc @@ -60,7 +60,7 @@ DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(pare void DeveloperPanelSP::updateToggles(bool offroad) { bool disable_updates = params.getBool("DisableUpdates"); - bool is_release = params.getBool("IsReleaseBranch"); + bool is_release = params.getBool("IsReleaseBranch") || params.getBool("IsReleaseSpBranch"); bool is_tested = params.getBool("IsTestedBranch"); bool is_development = params.getBool("IsDevelopmentBranch"); @@ -79,6 +79,9 @@ void DeveloperPanelSP::updateToggles(bool offroad) { enableGithubRunner->setVisible(!is_release); errorLogBtn->setVisible(!is_release); showAdvancedControls->setEnabled(true); + + joystickToggle->setVisible(!is_release); + longManeuverToggle->setVisible(!is_release); } void DeveloperPanelSP::showEvent(QShowEvent *event) { diff --git a/system/manager/manager.py b/system/manager/manager.py index 1186c25114..23e8ff7255 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -67,6 +67,7 @@ def manager_init() -> None: params.put_bool("IsDevelopmentBranch", build_metadata.development_channel) params.put_bool("IsTestedBranch", build_metadata.tested_channel) params.put_bool("IsReleaseBranch", build_metadata.release_channel) + params.put_bool("IsReleaseSpBranch", build_metadata.release_sp_channel) params.put("HardwareSerial", serial) # set dongle id diff --git a/system/version.py b/system/version.py index 9719311b7e..6ff290e032 100755 --- a/system/version.py +++ b/system/version.py @@ -13,8 +13,8 @@ from openpilot.common.git import get_commit, get_origin, get_branch, get_short_b RELEASE_SP_BRANCHES = ['release-c3', 'release'] TESTED_SP_BRANCHES = ['staging-c3', 'staging-c3-new', 'staging'] MASTER_SP_BRANCHES = ['master'] -RELEASE_BRANCHES = ['release3-staging', 'release3', 'release-tici', 'nightly'] + RELEASE_SP_BRANCHES -TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging', 'nightly-dev'] + TESTED_SP_BRANCHES +RELEASE_BRANCHES = ['release3-staging', 'release3', 'release-tici', 'nightly'] +TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging', 'nightly-dev'] + RELEASE_SP_BRANCHES + TESTED_SP_BRANCHES SP_BRANCH_MIGRATIONS = { ("tici", "staging-c3-new"): "staging-tici", @@ -96,7 +96,9 @@ class OpenpilotMetadata: @property def sunnypilot_remote(self) -> bool: return self.git_normalized_origin in ("github.com/sunnypilot/sunnypilot", - "github.com/sunnypilot/openpilot") + "github.com/sunnypilot/openpilot", + "github.com/sunnyhaibin/sunnypilot", + "github.com/sunnyhaibin/openpilot") @property def git_normalized_origin(self) -> str: @@ -120,6 +122,10 @@ class BuildMetadata: def release_channel(self) -> bool: return self.channel in RELEASE_BRANCHES + @property + def release_sp_channel(self) -> bool: + return self.channel in RELEASE_SP_BRANCHES + @property def canonical(self) -> str: return f"{self.openpilot.version}-{self.openpilot.git_commit}-{self.openpilot.build_style}" @@ -146,7 +152,7 @@ class BuildMetadata: return "staging" elif self.master_channel: return "master" - elif self.release_channel: + elif self.release_channel or self.release_sp_channel: return "release" else: return "feature" From cb5d12013683055f1b9b2361e1b80275431a5e1b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 21 Oct 2025 14:22:37 -0400 Subject: [PATCH 258/681] FCA: update minEnableSpeed and LKAS control logic (#1386) * FCA: update minEnableSpeed and LKAS control logic * bump --- opendbc_repo | 2 +- sunnypilot/selfdrive/car/car_specific.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index b8a00bddda..0a2315efd1 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit b8a00bddda562f981b24e099a3850209579e890a +Subproject commit 0a2315efd175176c318aa34d634d55d9dcd9350a diff --git a/sunnypilot/selfdrive/car/car_specific.py b/sunnypilot/selfdrive/car/car_specific.py index 97ce26429c..bce496ed28 100644 --- a/sunnypilot/selfdrive/car/car_specific.py +++ b/sunnypilot/selfdrive/car/car_specific.py @@ -37,7 +37,7 @@ class CarSpecificEventsSP: # TODO-SP: add 1 m/s hysteresis if CS.vEgo >= self.CP.minEnableSpeed: self.low_speed_alert = False - if CS.gearShifter != GearShifter.drive: + if self.CP.minEnableSpeed >= 14.5 and CS.gearShifter != GearShifter.drive: self.low_speed_alert = True if self.low_speed_alert: events.add(EventName.belowSteerSpeed) From cc8f6eadfe0e80ab23b5c281779dc5df7868a319 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Tue, 21 Oct 2025 13:58:48 -0700 Subject: [PATCH 259/681] =?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 5ae91f67a3..1b6a8c3e93 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 f57de1c5b2b19807ac65f349e3c29546bef92fff Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 21 Oct 2025 17:12:57 -0400 Subject: [PATCH 260/681] version: a new beginning (#1411) * version: a new beginning * changelog * singular * show ours * actual * readjust * updated * more * official spelling * more * sync * fix * send it * push * we never had this lol * syncs --- .codespellignore | 1 + .../workflows/sunnypilot-build-prebuilt.yaml | 2 +- CHANGELOG.md | 1066 +++++++++++++++++ common/swaglog.cc | 4 +- common/tests/test_markdown.py | 2 +- common/tests/test_swaglog.cc | 4 +- release/build_release.sh | 2 +- release/build_stripped.sh | 2 +- release/ci/publish.sh | 4 +- selfdrive/ui/tests/cycle_offroad_alerts.py | 2 +- selfdrive/ui/tests/test_ui/run.py | 2 +- sunnypilot/common/version.h | 1 + system/loggerd/logger.cc | 4 +- system/updated/updated.py | 4 +- system/version.py | 4 +- tools/replay/api.cc | 4 +- tools/replay/consoleui.cc | 4 +- 17 files changed, 1095 insertions(+), 17 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 sunnypilot/common/version.h diff --git a/.codespellignore b/.codespellignore index 9328517d4a..ab63c0cd29 100644 --- a/.codespellignore +++ b/.codespellignore @@ -3,3 +3,4 @@ REGIST PullRequest cancelled FOF +NoO diff --git a/.github/workflows/sunnypilot-build-prebuilt.yaml b/.github/workflows/sunnypilot-build-prebuilt.yaml index 00ae1e28bf..f7719709ae 100644 --- a/.github/workflows/sunnypilot-build-prebuilt.yaml +++ b/.github/workflows/sunnypilot-build-prebuilt.yaml @@ -79,7 +79,7 @@ jobs: is_stable_branch="$(echo "$CONFIG" | jq -r '.stable_branch // false')"; echo "is_stable_branch=$is_stable_branch" >> $GITHUB_OUTPUT - stable_version=$(cat common/version.h | grep COMMA_VERSION | sed -e 's/[^0-9|.]//g'); + stable_version=$(cat sunnypilot/common/version.h | grep SUNNYPILOT_VERSION | sed -e 's/[^0-9|.]//g'); echo "version=$([ "$is_stable_branch" = "true" ] && echo "$stable_version" || echo "$BUILD")" >> $GITHUB_OUTPUT echo "extra_version_identifier=${environment}" >> $GITHUB_OUTPUT fi diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..687484affe --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1066 @@ +sunnypilot Version 2025.001.000 (2025-10-25) +======================== +* 🛠️ Major rewrite + * Most features are intended to be identical to previous versions with slight improvements + * Fully adopts upstream commaai’s openpilot, opendbc (car interface and safety), and panda test suites to ensure consistent safety compliance and reliability across all systems + * Added regression testing to verify expected behavior and maintain stability across core modules + * Aligns with comma.ai’s safety policy: preserving driver monitoring, actuation checks, and safety test suite coverage + * Some features have not yet been reimplemented in this rewrite and are temporarily disabled in this release. They may return in future releases once fully ported and validated. See the end of the changelog to get a list of what's not going to be present. +* 🌟 Major Features & Systems + * Modular Assistive Driving System (MADS) + * Complete driving assistance framework + * Driving Model Manager + * Custom driving model selection with support for about 86 models (as of writing), from Night Strike (October 2023) up to The Cool People’s Models (October 2025) + * Neural Network Lateral Control (NNLC) (Formerly NNFF) + * Advanced torque-based lateral control + * Dynamic Experimental Control (DEC) + * Intelligent longitudinal control adaptation + * Speed Limit Assist (SLA) + * Comprehensive speed limit integration featuring @pfeiferj's `mapd` for offline map limits downloads, a Speed Limit Resolver for sourcing data (from car, map, combined, etc), on-screen UI for Speed Limit Information/Warning, and Speed Limit Assist (SLA) to adjust cruise speed automatically. + * Intelligent Cruise Button Management (ICBM) + * System designed to manage the vehicle’s speed by sending cruise control button commands to the car’s ECU. + * Smart Cruise Control Map & Vision (SCC-M / SCC-V) + * When using any form of long control (openpilot longitudinal or ICBM) it will control the speed at which you enter and perform a turn by leveraging map data (SCC-M) and/or by leveraging what the model sees about the curve ahead (SCC-V) + * Vehicle Selector + * If your vehicle isn’t fingerprinted automatically, you can still use the vehicle selector to get it working + * sunnylink Integration + * Cloud connectivity and settings backup/restore + * PENDING: The infrastructure is ready for remote setting management, including remote driving model switching. An announcement will be made when this is ready to use in current and future releases. + * External Storage Support + * Expanded storage options + * mapd Integration (thanks to @pfeiferj) + * Allow downloading OpenStreetMap databases for your area, which could be useful for Speed Limit Assist (SLA) +* User Interface Enhancements + * Complete UI Redesign from Default openpilot Experience + * A total overhaul of the sunnypilot offroad user interface for a modern and intuitive experience. + * New Settings Panels + * Reorganized settings into dedicated panels: Steering, Longitudinal, Vehicle, Models, Visuals, Display, and Trips. + * Advanced Controls Toggle + * Out of the box experience has a slightly reduced set of settings for a lower barrier of entry, once you are ready, you can get a few extra settings by toggling on the Advanced Controls. + * Models Panel + * A dedicated panel for model management, featuring a download manager, model folders, a favorites system, fuzzy search, and a cache refresh button. + * Visuals & Display + * Extensive customization options including brightness controls, custom interactivity timeouts, green light indicator, lead vehicle indicator, on-screen turn signals, blind spot indicators, lead chevron info, standstill timer, road name display, and a Tesla-like 🌈 rainbow road path. + * Screen Off while driving + * Options to turn the screen off while driving and customize wake-up behavior for alerts. + * Branch & Platform Selectors + * Improved software management with a searchable branch selector and a platform selector that displays the current fingerprint. + * Developer UI + * An enhanced developer UI with better alert positioning and an integrated error log viewer. + * Convenience Features + * Added an “Exit Offroad” button, “Always Offroad” mode, Quiet Mode, and customizable max time offroad settings. + * OpenStreetMap Database Downloader + * The OpenStreetMap database downloader now includes a search feature for easily finding areas. +* Model and AI Improvements + * Modular Model Backend + * Major refactor of `modeld` to support modular runners (SNPE, thneed, tinygrad) and dynamic model inputs. + * Enhanced Model Outputs + * Models now provide additional outputs like “turn desires” for improved control. + * Live Parameter Adjustments + * Support for live delay adjustments and software delay controls directly from the UI. + * Model Management + * Added model caching, automatic refresh capabilities, and shape inference from inputs for better compatibility. +* Control Systems + * Pause Lateral on Blinker + * Option to temporarily pause lateral control when the turn signal is active. + * Custom ACC Setpoint Increments + * Configure custom increments for adjusting the ACC set speed for applicable vehicle platforms. + * Steering on Brake Press + * Customizable steering behavior when the brake pedal is pressed. + * Enforce Torque Lateral Control + * New customized settings for fine-tuning torque-based steering. + * Automatic Lane Change + * Support for automatic lane changes, including a mode to disable it. +* Technical Infrastructure + * Custom Cereal Implementation + * Migrated sunnypilot-specific events, car parameters, and car controls to a dedicated cereal for better compatibility and performance. + * Car Interface Abstractions + * Refactored car interfaces to support brand-specific settings and easier integration. + * Param Store Caching + * Implemented a cache for the parameter store to reduce startup times, with support for live parameter updates. + * Enhanced Error Handling + * Improved exception management and Sentry logging for better stability and debugging. + * Docker & CI/CD + * Full Docker image support, a dedicated GitHub runner service, and comprehensive improvements to the entire CI/CD pipeline for automated testing, building, and releasing. +* Bug Fixes and Stability + * Registration Requirement Removed + * No longer necessary to register the device to go onroad. + * Panda Firmware Checks + * Improved firmware checks to gracefully handle deprecated Panda devices. + * Numerous Fixes + * Addressed a wide range of bugs across the system for a more stable and reliable experience. +* Developer Experience + * CLion IDE integration and external tools + * Comprehensive testing and build automation + * Model building and publishing automation + * UI preview generation and testing + * Release drafting and version management + * Code quality and maintenance workflows +* Translations and Localization + * Korean translation updates + * Automated translation management system +* ❌ Removed + * Navigate on openpilot (NoO) + * Navigate on openpilot (NoO) has been removed as upstream is prioritizing improving the driving model’s capabilities and simplifying the training stack. + * The feature may return in a future upstream release by comma.ai once model improvements from upstream make it more reliable. + * Visuals: Rocket Fuel + * Visuals: Displaying Braking Status + * Vehicle: Toyota - Enforce Stock Longitudinal Control + * Subaru: Increase Steering Torque + * Longitudinal: Acceleration Personality + * UI: Display CPU Temperature on Sidebar + * Lateral: Block Lane Change with Road Edge Detection + * UI: Display DM Camera in Reverse Gear + * UI: Auto-hide Selected UI Elements + * Visuals: Display End-to-End Longitudinal Status + * Toyota: Stop and Go Hack (alpha) + * Visuals: Onroad Settings + * Honda: Serial Steering Support + * Volkswagen: Non-ACC Platforms Support + * Longitudinal: Dynamic Personality + * Honda Nidec: Allow Stock Longitudinal Control + * Lateral Planner: Dynamic Lane Profile + * Lateral Planner: Laneful Mode + * Lateral: Custom Camera and Path Offsets + * Toyota: Door Controls +* New Contributors + * @discountchubbs made their first contribution in "ui: Error log button to Developer panel (#627)" + * @First made their first contribution in "NNLC: bump max similarity for higher accuracy (#704)" + * @nayan made their first contribution in "UI: Update AbstractControlSP_SELECTOR and OptionControlSP (#800)" + * @wtogamiwtogami made their first contribution in "Add TOYOTA_RAV4_PRIME NNLC tuning gen 1 (#850)" + * @dparring made their first contribution in "FCA: Ram 1500 improvements (#797)" + * @Kirito3481 made their first contribution in "Update ko-kr translation (#1167)" + * @michael-was-taken made their first contributio@dzid26 in "Reorder README tables: show -new branches first (#1191)" + * @dzid26 made their first contribution in "docs: clarify pedal press (#1289)" + * @HazZelnutz made their first contribution in "Visuals: Turn signals on screen when blinker is used (#1291)" +************************ +* Synced with commaai's openpilot (v0.10.1) + * master commit c9dbf97649a27117be6d5955a49e2d4253337288 (September 12, 2025) +* New driving model + * World Model: removed global localization inputs + * 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 +* 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! + +sunnypilot - 0.9.7.1 (2024-06-13) +======================== +* New driving model + * Inputs the past curvature for smoother and more accurate lateral control + * Simplified neural network architecture in the model's last layers + * Minor fixes to desire augmentation and weight decay +* New driver monitoring model + * Improved end-to-end bit for phone detection +* Adjust driving personality with the follow distance button +* Support for hybrid variants of supported Ford models +* Fingerprinting without the OBD-II port on all cars +* Improved fuzzy fingerprinting for Ford and Volkswagen +************************ +* UPDATED: Synced with commaai's openpilot + * master commit f8cb04e (June 10, 2024) +* NEW❗: sunnylink (Alpha early access) + * NEW❗: Config/Settings Backup + * Remotely back up and restore sunnypilot settings easily + * Device registration with sunnylink ensures a secure, integrated experience across services + * AES encryption derived from the device's RSA private key is used for utmost security + * Settings are encrypted on-device, transmitted securely via HTTPS, and stored encrypted on sunnylink + * Prevents loss of settings after device resets, offering peace of mind through end-to-end encryption + * Early alpha access to all current and previous GitHub Sponsors and Patreon supporters + * GitHub account pairing from device settings scanning QR code + * Pairing your account will allow you to access features via our API (still WIP but accessible if you dig a little on our code 😉) + * Allow inheritance of your sponsorship status, allowing you to get extra features and early access whenever applicable +* NEW❗: iOS Siri Shortcuts Navigation support thanks to twilsonco and mike86437! + * iOS and macOS Shortcuts to quickly set navigation destinations from your iOS device + * comma Prime support + * Personal Mapbox/Amap/Google Maps token support + * Instructions on how to set up your iOS Siri Shortcuts: https://routinehub.co/shortcut/17677/ +* NEW❗: Forced Offroad mode + * Force sunnypilot in the offroad state even when the car is on + * When Forced Offroad mode is on, allows changing offroad-only settings even when the car is turned on + * To engage/disengage Force Offroad, go to Settings -> Device panel +* UPDATED: Auto Lane Change Timer -> Auto Lane Change by Blinker + * NEW❗: New "Off" option to disable lane change by blinker +* UPDATED: Pause Lateral Below Speed with Blinker + * NEW❗: Customizable Pause Lateral Speed + * Pause lateral actuation with blinker when traveling below the desired speed selected. Default is 20 MPH or 32 km/h. +* UPDATED: Hyundai CAN Longitudinal + * Auto-enable radar tracks on platforms with applicable Mando radar +* UPDATED: Hyundai CAN-FD Camera-based SCC + * NEW❗: Parse lead info for camera-based SCC platforms with longitudinal support + * Improve lead tracking when using openpilot longitudinal +* RE-ENABLED: Map-based Turn Speed Control (M-TSC) for supported platforms + * openpilot Longitudinal Control available cars + * Custom Stock Longitudinal Control available cars +* UPDATED: Continued support for comma Pedal + * In response to the official deprecation of support for comma Pedal in the upstream, sunnypilot will continue maintaining software support for comma Pedal +* UPDATED: Driving Model Selector v4 + * NEW❗: Driving Model additions + * North Dakota (April 29, 2024) - NDv2 + * WD40 (April 09, 2024) - WD40 + * Duck Amigo (March 18, 2024) - DA + * Recertified Herbalist (March 01, 2024) - CHLR + * Legacy Driving Models with Navigate on openpilot (NoO) support + * Includes Duck Amigo and all preceding models +* UPDATED: Bumping mapd by [@pfeiferj](https://github.com/pfeiferj) to version [v1.9.0](https://github.com/pfeiferj/mapd/releases/tag/v1.9.0) thanks to pfeiferj! +* UPDATED: Reset Mapbox Access Token -> Reset Access Tokens for Map Services + * Reset self-service access tokens for Mapbox, Amap, and Google Maps +* UPDATED: Upstream native support for Gap Adjust Cruise +* UPDATED: Neural Network Lateral Control (NNLC) + * Due to upstream changes with platform simplifications, most platforms will match and fallback to combined platform model + * This will be updated when the new mapping of platforms are restructured (thanks @twilsonco 😉) +* UI Updates + * Display Metrics Below Chevron + * NEW❗: Metrics is now being displayed below the chevron instead of above + * NEW❗: Display both Distance and Speed simultaneously + * NEW❗: View sunnylink connectivity status on the left sidebar! + +sunnypilot - 0.9.6.2 (2024-05-29) +======================== +* REMOVED: Screen Recorder + * Screen Recorder is removed due to unnecessary resource usage + * An improved version will be available in the near future. Stay tuned! + +sunnypilot - 0.9.6.1 (2024-02-27) +======================== +* New driving model + * Vision model trained on more data + * Improved driving performance + * Directly outputs curvature for lateral control +* New driver monitoring model + * Trained on larger dataset +* AGNOS 9 +* comma body streaming and controls over WebRTC +* Improved fuzzy fingerprinting for many makes and models +* Alpha longitudinal support for new Toyota models +* Chevrolet Equinox 2019-22 support thanks to JasonJShuler and nworb-cire! +* Dodge Durango 2020-21 support +* Hyundai Staria 2023 support thanks to sunnyhaibin! +* Kia Niro Plug-in Hybrid 2022 support thanks to sunnyhaibin! +* Lexus LC 2024 support thanks to nelsonjchen! +* Toyota RAV4 2023-24 support +* Toyota RAV4 Hybrid 2023-24 support +************************ +* UPDATED: Synced with commaai's openpilot + * master commit db57a21 (February 22, 2024) + * v0.9.6 release (February 27, 2024) +* UPDATED: Dynamic Experimental Control (DEC) + * Synced with dragonpilot-community/dragonpilot:beta3 commit f4ee52f +* NEW❗: Default Driving Model: Certified Herbalist v2 (February 13, 2024) +* UPDATED: Driving Model Selector v3 + * NEW❗: Driving Model additions + * Certified Herbalist v2 (February 13, 2024) - CHv2 + * Certified Herbalist (February 5, 2024) - CH + * Los Angeles v2 (January 24, 2024) - LAv2 + * Los Angeles (January 22, 2024) - LAv1 + * NEW❗: Model Caching thanks to DevTekVE! + * Model caching allows the selection of previously downloaded Driving Model + * Users can now access cached versions of selected models, eliminating redundant downloads for previously fetched models + * Legacy Driving Models support + * New Delhi (December 21, 2023) - ND + * Blue Diamond v2 (December 11, 2023) - BDv2 + * Blue Diamond (November 18, 2023) - BDv1 + * Farmville (November 7, 2023) - FV + * Night Strike (October 3, 2023) - NS + * Certain features are deprecated with newer Driving Models + * Dynamic Lane Profile (DLP) + * Custom Offsets +* UPDATED: Dynamic Lane Profile (DLP) + * Continued support for Legacy Driving Models (e.g., ND, BDv2, BDv1, FV, NS) + * Deprecated support for newer Driving Models (e.g., CHv2, CH, LAv2, LAv1) +* UPDATED: Custom Offsets + * Continued support for Legacy Driving Models (e.g., ND, BDv2, BDv1, FV, NS) + * Deprecated support for newer Driving Models (e.g., CHv2, CH, LAv2, LAv1) +* UPDATED: Hyundai/Kia/Genesis - ESCC Radar Interceptor + * Message parsing improvements with the latest firmware update: https://github.com/sunnypilot/panda/tree/test-escc-smdps +* UI Updates + * NEW❗: Visuals: Display Feature Status toggle + * Display the statuses of certain features on the driving screen + * NEW❗: Visuals: Enable Onroad Settings toggle + * Display the Onroad Settings button on the driving screen to adjust feature options on the driving screen, without navigating into the settings menu + * REMOVED: "Device ambient" temperature option on the sidebar +* FIXED: New comma 3X support +* FIXED: New comma eSIM support +* Bug fixes and performance improvements + +sunnypilot - 0.9.5.3 (2023-12-24) +======================== +* UPDATED: Dynamic Experimental Control (DEC) + * Synced with dragonpilot-community/dragonpilot:lp-dp-beta2 commit 578d38b +* UPDATED: Driving Model Selector v2 + * Driving models sort in descending order based on availability date + * Experimental/unmerged driving models are only available in "dev-c3" branch + * To select and use experimental driving models, navigate to "Software" panel, select the "dev-c3" branch, and check for update +* UPDATED: Vision-based Turn Speed Control (V-TSC) implementation + * Refactored implementation thanks to pfeiferj! + * More accurate and consistent velocity calculation to achieve smoother longitudinal control in curves +* NEW❗: Speed Limit Warning + * Display alert and/or chime to warn the driver when the cruising speed is faster than the speed limit plus the Warning Offset + * Customizable Warning Offset, independent of Speed Limit Control (SLC)'s Limit Offset +* UPDATED: Speed Limit Source Policy + * Selectable speed limit source for Speed Limit Control and Speed Limit Warning + * Applicable to: Speed Limit Control, Speed Limit Warning +* UPDATED: Speed Limit Control (SLC) + * Engage Mode: Removed "Warning Only" mode - this has been replaced by the new Speed Limit Warning sub-menu +* UPDATED: OpenStreetMap (OSM) implementation + * Refactored implementation thanks to pfeiferj! + * Less resource impact + * Significantly smaller sizes with databases + * All regions are available to download + * Weekly map updates thanks to pfeiferj! + * Increased the font size of the road name + * C3X-specific changes + * Altitude (ALT.) display on Developer UI + * Current street name on top of driving screen when "OSM Debug UI" is enabled +* UPDATED: Map-based Turn Speed Control (M-TSC) implementation + * Only available in "staging-c3" and "dev-c3" branches. If you are using "release-c3" branch, navigate to "Software" panel, select the desired target branch, and check for update + * Refactored implementation thanks to pfeiferj! + * Based on the new OpenStreetMap implementation + * Improved predicted curvature calculations from OpenStreetMap data +* UI updates + * RE-ENABLED: Navigation: Full screen support + * Display the map view in full screen + * To switch back to driving view, tap on the border edge +* Hyundai Bayon Non-SCC 2019 support thanks to polein78! + +sunnypilot - 0.9.5.2 (2023-12-07) +======================== +* NEW❗: MADS: Allow Navigate on openpilot in Chill Mode + * Allow navigation to feed map view into the driving model while using Chill Mode + * Support all platforms, including platforms that do not support openpilot longitudinal control & Experimental Mode +* NEW❗: Neural Network Lateral Controller + * Formerly known as "NNFF", this replaces the lateral "torque" controller with one using a neural network trained on each car's (actually, each separate EPS firmware) driving data for increased controls accuracy + * Contact @twilsonco in the sunnypilot Discord server with feedback, or to provide log data for your car if your car is currently unsupported +* NEW❗: Driving Model Selector + * Easily switch between driving models without reinstalling branches. Offering immediate access to the latest models upon release + * An internet connection is required for downloading models. Each model switch currently involves downloading the model again. Future updates may allow for offline switching + * Warning is displayed for metered connections to avoid unexpected data usage if on cellular data + * Change driving models via **Settings -> Software -> Current Driving Model**. +* NEW❗: Hyundai CAN longitudinal: + * NEW❗: Enable radar tracks for certain Santa Fe platforms + * Internal Combustion Engine (ICE) 2021-23 + * Hybrid 2022-23 + * Plug-in Hybrid 2022-23 +* NEW❗: Lane Change: When manually braking with steering engaged, turning on the turn signal will default to Nudge mode +* Volkswagen MQB CC only platforms (radar or no radar) support thanks to jyoung8607! + +sunnypilot - 0.9.5.1 (2023-11-17) +======================== +* UPDATED: Synced with commaai's master commit e94c3c5 +* NEW❗: Farmville driving model +* NEW❗: Onroad Settings Panel + * Onroad buttons (i.e., DLP, GAC) moved to its dedicated panel + * Driving Personality + * Dynamic Lane Profile (DLP) + * Dynamic Experimental Control (DEC) + * Speed Limit Control (SLC) +* NEW❗: Display main feature status on onroad view in real-time + * GAP - Driving Personality + * DLP - Dynamic Lane Profile + * DEC - Dynamic Experimental Control + * SLC - Speed Limit Control +* NEW❗: Dynamic Experimental Control (DEC) thanks to dragonpilot-community! + * Automatically determines and selects between openpilot ACC and openpilot End to End longitudinal based on conditions for a more natural drive + * Dynamic Experimental Control is only active while in Experimental Mode + * When Dynamic Experimental Control is ON, initially setting cruise speed will set to the vehicle's current speed +* NEW❗: Hyundai CAN longitudinal: + * NEW❗: Parse lead info for camera-based SCC platforms + * Improve lead tracking when using openpilot longitudinal + * NEW❗: Parse lead distance to display on car cluster + * Introduced better lead distance calculation to display on the car's cluster, replacing the binary "lead visible" indication on the SCC cluster + * Lead distance is now categorized into different ranges for more detailed and comprehensive information to the driver similar to how stock ACC does it + * NEW❗: Parse speed limit sign recognition from camera for certain supported platforms +* NEW❗: Subaru - Stop and Go auto-resume support thanks to martinl! + * Global (excluding Gen 2 and Hybrid) and Pre-Global support +* NEW❗: Toyota - Stop and Go hack + * Allow some Toyota/Lexus cars to auto resume during stop and go traffic + * Only applicable to certain models and model years +* NEW❗: Toyota: ZSS support thanks to dragonpilot-community and ErichMoraga! +* NEW❗: MSPA (Cereal structs refactor) + * Make sunnypilot Parsable Again - @sshane + * sunnypilot is now parsable with stock openpilot tools +* NEW❗: Display 3D buildings on map thanks to jakethesnake420! +* openpilot Longitudianl Control capable cars only + * UPDATED: Gap Adjust Cruise is now a part of Driving Personality + * [DISTANCE/FOLLOW DISTANCE/GAP DISTANCE] physical button on the steering wheel to select Driving Personality on by default + * Status now viewable in onroad view or Onroad Settings Panel + * REMOVED: Gap Adjust Cruise toggle +* UPDATED: Speed Limit Control (SLC) + * NEW❗: Speed Limit Engage Mode + * Select the desired mode to set the cruising speed to the speed limit + * Warning Only: Warn the driver when the vehicle is driven faster than the speed limit + * Auto: Automatic speed adjustment on motorways based on speed limit data + * User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit + * Supported platforms + * openpilot Longitudinal Control available cars (Excluding certain Toyota/Lexus, Ford, explained below) + * Custom Stock Longitudinal Control available cars + * Unsupported platforms + * Toyota/Lexus and Ford - most platforms do not allow us to control the PCM's set speed, requires testers to verify + * NEW❗: Speed limit source selector + * Select the desired precedence order of sources used to adapt cruise speed to road limits +* UPDATED: Custom Stock Longitudinal Control + * RE-ENABLED: Hyundai/Kia/Genesis CAN-FD platforms +* UPDATED: Custom Offsets reimplementation + * Camera Offset only works in Laneful (Laneful Only or Laneful in Auto mode when using Dynamic Lane Profile) + * Path Offset can be applied to both Laneless and Laneful +* UPDATED: Refactored Torque Lateral Control custom tuning menu + * NEW❗: Less Restrict Settings for Self-Tune (Beta) + * NEW❗: Custom Tuning for setting offline and live values in real-time +* UPDATED: Auto-detect custom Mapbox token if a personal Mapbox token is provided + * REMOVED: "Enable Mapbox Navigation" toggle +* UI updates + * New Settings menu redesign and improved interactions +* FIXED: Retain hotspot/tethering state was not consistently saved +* FIXED: Map stuck in "Map Loading" if comma Prime is active +* FIXED: OpenStreetMap implementation on C3X devices + * M-TSC + * Altitude (ALT.) display on Developer UI + * Current street name on top of driving screen when "OSM Debug UI" is enabled +* Hyundai Kona Non-SCC 2019 support thanks to Quex! +* Kia Seltos Non-SCC 2023-24 support thanks to Moodkiller and jeroid_! + +sunnypilot - 0.9.4.1 (2023-08-11) +======================== +* UPDATED: Synced with commaai's 0.9.4 release +* NEW❗: Moonrise driving model +* NEW❗: Ford upstream models support +* UPDATED: Dynamic Lane Profile selector in the "SP - Controls" menu +* REMOVED: Dynamic Lane Profile driving screen UI button +* FIXED: Disallow torque lateral control for angle control platforms (e.g. Ford, Nissan, Tesla) + * Torque lateral control cannot be used by angle control platforms, and would cause a "Controls Unresponsive" error if Torque lateral control is enforced in settings +* REMOVED: Speed Limit Style override +* Honda Accord 2016-17 support thanks to mlocoteta! + * Serial Steering hardware required. For more information, see https://github.com/mlocoteta/serialSteeringHardware +* mapd: utilize advisory speed limit in curves (#142) thanks to pfeiferj! + +sunnypilot - 0.9.3.1 (2023-07-09) +======================== +* UPDATED: Synced with commaai's 0.9.3 release +* NEW❗: Display Temperature on Sidebar toggle + * Display Ambient temperature, memory temperature, CPU core with the highest temperature, GPU temperature, or max of Memory/CPU/GPU on the sidebar + * Replace "Display CPU Temperature on Sidebar" toggle +* NEW❗: Hot Coffee driving model +* NEW❗: HKG CAN: Smoother Stopping Performance (Beta) toggle + * Smoother stopping behind a stopped car or desired stopping event. + * This is only applicable to HKG CAN platforms using openpilot longitudinal control +* NEW❗: Toyota: TSS2 longitudinal: Custom Tuning + * Smoother longitudinal performance for Toyota/Lexus TSS2/LSS2 cars thanks to dragonpilot-community! +* NEW❗: Enable Screen Recorder toggle + * Enable this will display a button on the onroad screen to toggle on or off real-time screen recording with UI elements. +* IMPROVED: Dynamic Lane Profile: when using Laneline planner via Laneline Mode or Auto Mode, enforce Laneless planner while traveling below 10 MPH or 16 km/h +* REMOVED: Display CPU Temperature on Sidebar + +sunnypilot - 0.9.2.3 (2023-06-18) +======================== +* NEW❗: Auto Lane Change: Delay with Blind Spot + * Toggle to enable a delay timer for seamless lane changes when blind spot monitoring (BSM) detects an obstructing vehicle, ensuring safe maneuvering +* NEW❗: Driving Screen Off: Wake with Non-Critical Events + * When Driving Screen Off Timer is not set to "Always On": + * Enabled: Wake the brightness of the screen to display all events + * Disabled: Wake the brightness of the screen to display critical events + * Currently, all non-nudge modes are default to continue lane change after 1 seconds of blind spot detection +* NEW❗: Fleet Manager PIN Requirement toggle + * User can now enable or disable PIN requirement on the comma device before accessing Fleet Manager +* NEW❗: Reset all sunnypilot settings toggle +* NEW❗: Turn signals display on screen when blinker is used + * Green: Blinker is on + * Red: Blinker is on, car detected in the adjacent blind spot or road edge detected +* IMPROVED: mapd: better exceptions handling when loading dependencies +* UPDATED: Green Traffic Light Chime no longer displays an orange border when executed +* FIXED: mapd: Road name flashing caused by desync with last GPS timestamp +* FIXED: Ram HD (2500/3500): Ignore paramsd sanity check + * Live parameters have trouble with self-tuning on this platform with upstream openpilot 0.9.2 +* Hyundai: Longitudinal support for CAN-based Camera SCC cars thanks to Zack1010OP's Patreon sponsor! + +sunnypilot - 0.9.2.2 (2023-06-13) +======================== +* NEW❗: Toyota: Allow M.A.D.S. toggling with LKAS Button (Beta) +* IMPROVED: Ram: cruise button handling + +sunnypilot - 0.9.2.1 (2023-06-10) +======================== +* UPDATED: Synced with commaai's 0.9.2 release +* UPDATED: feature revamp with better stability +* UPDATED: + * M.A.D.S. + * Path color becomes LIGHT ORANGE during Driver Steering Override + * Gap Adjust Cruise (now known as Driving Personality in upstream openpilot 0.9.3): + * Updated profiles and jerk changes + * Experimental Mode support + * Three settings: Stock, Aggressive, and Maniac + * Stock is recommended and the default + * In Aggressive/Maniac mode, lead follow distance is shorter and quicker gas/brake response + * Dynamic Lane Profile + * Display blue borders on both sides of the driving path when Laneline mode is being used in the planner + * Auto Mode optimization + * Permanent: Laneless during Auto Lane Change execution + * Mapd + * OpenStreetMap Database: new regions added + * Developer UI (Dev UI) + * REMOVED: 2-column design + * NEW❗: 1-column + 1-row design + * Custom Stock Longitudinal Control + * NEW❗: Chrysler/Jeep/Ram support + * NEW❗: Mazda support + * NEW❗: Volkswagen PQ support + * DISABLED: Hyundai/Kia/Genesis CAN-FD platforms +* NEW❗: Switch between Chill (openpilot ACC) and Experimental (E2E longitudinal) with DISTANCE button on the steering wheel + * To switch between Chill and Experimental Mode: press and hold the DISTANCE button on the steering wheel for over 0.5 second + * All openpilot longitudinal capable cars support +* NEW❗: Nicki Minaj driving model +* NEW❗: Nissan and Mazda upstream models support +* NEW❗: Pre-Global Subaru upstream models support +* NEW❗: Display End-to-end Longitudinal Status (Beta) + * Display an icon that appears when the End-to-end model decides to start or stop +* NEW❗: Green Traffic Light Chime (Beta) + * A chime will play when the traffic light you are waiting for turns green, and you have no vehicle in front of you. +* NEW❗: Lead Vehicle Departure Alert + * Notify when the leading vehicle drives away +* NEW❗: Speedometer: Display True Speed + * Display the true vehicle current speed from wheel speed sensors. +* NEW❗: Speedometer: Hide from Onroad Screen +* NEW❗: Auto-Hide UI Buttons + * Hide UI buttons on driving screen after a 30-second timeout. Tap on the screen at anytime to reveal the UI buttons + * Applicable to Dynamic Lane Profile (DLP) and Gap Adjust Cruise (GAC) +* NEW❗: Display DM Camera in Reverse Gear + * Show Driver Monitoring camera while the car is in reverse gear +* NEW❗: Block Lane Change: Road Edge Detection (Beta) + * Block lane change when road edge is detected on the stalk actuated side +* NEW❗: Display CPU Temperature on Sidebar + * Display the CPU core with the highest temperature on the sidebar +* NEW❗: Display current driving model in Software settings +* NEW❗: HKG: smartMDPS automatic detection (installed with applicable firmware) +* FIXED: Unintended siren/alarm from the comma device if the vehicle is turned off too quickly in PARK gear +* FIXED: mapd: Exception handling for loading dependencies +* Fleet Manager via Browser support thanks to actuallylemoncurd, AlexandreSato, ntegan1, and royjr! + * Access your dashcam footage, screen recordings, and error logs when the car is turned off + * Connect to the device via Wi-Fi, mobile hotspot, or tethering on the comma device, then navigate to http://ipAddress:5050 to access. +* Honda Clarity 2018-22 support thanks to mcallbosco, vanillagorillaa and wirelessnet2! +* Ram: Steer to 0/7 MPH support thanks to vincentw56! +* Retain hotspot/tethering state across reboots thanks to rogerioaguas! + +sunnypilot - Version Latest (2023-02-22) +======================== +* UPDATED: Synced with commaai's master branch - 2023.02.19-04:52:00:GMT - 0.9.2 +* Refactor sunnypilot features to be more stable + +sunnypilot - Version Latest (2022-12-16) +======================== +* UPDATED: Synced with commaai's master branch - 2022.12.16-06:31:00:GMT - 0.9.1 +* NEW❗: GM: + * NEW❗: Gap Adjust Cruise support - Chill, Normal, Aggressive + * NEW❗: Experimental Mode: Hold DISTANCE button on the steering wheel for 0.5 second to switch between Experimental Mode and Chill Mode +* REMOVED❌: Toytoa: SnG Hack + * This method is not recommended and may cause some cars to not behave as expected + * SDSU is strongly recommended to enable SnG for Toyota vehicles without SnG from factory +* commaai: radard: add missing accel data for vision-only leads (commaai/openpilot#26619) - pending PR + * VOACC performance is drastically improved when using Chill Mode +* IMPROVED: M.A.D.S. events handling +* IMPROVED: UI: screen recorder button change +* IMPROVED: OpenStreetMap Offline Database optimization +* FIXED: Toyota: vehicles' LKAS button no longer has a delay with toggling M.A.D.S. +* FIXED: Toyota: brake pedal press at standstill causing Cruise Fault +* FIXED: Volkswagen MQB: reduce Camera Malfunction occurrences (requires testing) +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-12-10) +======================== +* IMPROVED: NEW❗ Developer UI design + * Second column metrics is now moved to the bottom of the screen + * ACC. = Acceleration + * L.S. = Lead Speed + * E.T. = EPS Torque + * B.D. = Bearing Degree + * FRI. = Friction + * L.A. = Lateral Acceleration + * ALT. = Altitude +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-12-07) +======================== +* NEW❗: Screen Recorder support thanks to neokii and Kumar! +* NEW❗: End-to-end longitudinal start/stop status icon + * Only appears when Experimental Mode is enabled +* NEW❗: End-to-end longitudinal car chime when starting + * Hyundai/Kia/Genesis CAN platform, Honda/Acura Bosch/Nidec, Toyota/Lexus + * i.e. Traffic light turns green, stop sign ready to go, etc. + * Only appears when Experimental Mode is enabled AND longitudinal control is disengaged +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-12-05) +======================== +* UPDATED: Synced with commaai's master branch - 2022.12.04-22:46:00:GMT - 0.9.1 +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-11-12) +======================== +* UPDATED: Synced with commaai's master branch - 2022.11.12-10:02:00:GMT - 0.8.17 +* FIXED: CAN Error for CAN HKG cars that do not have navigation from the factory +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-11-11) +======================== +* UPDATED: Synced with commaai's master branch - 2022.11.11-21:22:00:GMT - 0.8.17 +* commaai: AGNOS 6.2 (commaai/openpilot#26441) +* NEW❗: Speed Limit Control - HKG - add speed limit from car's navigation head unit + * Compatible with certain models, trims, and model years +* DISABLED: FCA: RAM HD - steer down to 0 +* FIXED: UI: End-to-end longitudinal button on driving screen synchronization +* FIXED: Honda: Longitudinal status with set cruise speed now displays properly in the car's dashboard +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-11-08) +======================== +* ADDED: New Zealand offline OpenStreetMap database + +sunnypilot - Version Latest (2022-11-04) +======================== +* UPDATED: Synced with commaai's master branch - 2022.11.05-01:44:00:GMT - 0.8.17 +* RE-ENABLED: Dynamic Lane Profile - preserves lanelines + * Can be found in "SP - Controls" menu +* NEW❗: DLP: switch to laneless for current/future curves thanks to @twilsonco! + * Can be found in "SP - Controls" menu +* NEW❗: UI: Road Camera Selector + * Enable this will display a button on the driving screen to select the driving camera + * Can be found in "SP - Visuals" menu +* NEW❗: Controls: Camera & Path Custom Offsets + * Only applicable to laneline mode when using Dynamic Lane Profile +* NEW❗: Buttons on driving screen are now sorted based on priority and availability +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-28) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.28-03:53:00:GMT - 0.8.17 +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-26) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.26-06:20:00:GMT - 0.8.17 +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-25) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.25-23:53:00:GMT - 0.8.17 +* Pre-Global Subaru support thanks to @martinl! +* NEW❗: Speed Limit values turn red when current speed is higher than posted speed limit +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-23) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.22-23:15:00:GMT - 0.8.17 +* IMPROVED: Custom Stock Longitudinal Control - HKG - only allow engagement on user button press +* IMPROVED: Custom Stock Longitudinal Control - Volkswagen MQB & PQ - more consistent set speed change +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-21) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.21-17:33:00:GMT - 0.8.17 +* IMPROVED: Custom Stock Longitudinal Control - Volkswagen MQB & PQ - more predictable button send logic +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-20) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.20-20:25:00:GMT - 0.8.17 +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-19) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.19-08:31:00:GMT - 0.8.17 +* IMPROVED: Controls: Speed Limit Control - accelerator press only disengage if "Disengage on Accelerator Pedal" is enabled +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-18) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.18-04:44:00:GMT - 0.8.17 +* RE-ENABLED: Volkswagen MQB & PQ with Custom Stock Longitudinal Control +* NEW❗: Steering Rate Cost Live Tune + * Enables live tune for Steering Rate Cost. Lower value allows steering wheel to move more freely at low speed + * Can be found in "SP - Controls" menu +* FIXED: MADS: GM - include Regen Paddle logic thanks to @twilsonco! +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-17) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.17-23:54:00:GMT+1 - 0.8.17 +* ENABLED: "Custom Stock Longitudinal Control" toggle for CAN-FD cars +* FIXED: HKG CAN-FD: Could not engage when openpilot longitudinal is enabled +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-13) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.13-19:43:00:GMT+1 - 0.8.17 +* ADDED: Live Tmux toggle + * Can be found in "SP - General" menu +* IMPROVED: OpenStreetMap Database Update - only check for database update with explicit user decision +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-11) +======================== +* ADDED: Hyundai openpilot longitudinal improvements - huge thanks to @aragon7777! +* ADDED: Check for OpenStreetMap Database Update button +* UPDATED: commaai: Low speed lateral control improvements (commaai:openpilot#26022, bbcd448) - pending PR +* FIXED: MUTCD speed limit spacing adjusts dynamically when no subtext is shown (i.e., speed limit offset, distance to next speed limit) +* FIXED: MADS: Intermittent CAN Error when engaging for Toyota Prius TSS-P +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-09) +======================== +* ADDED: commaai: Low speed lateral control improvements (commaai:openpilot#26022, bca288bb) - pending PR +* FIXED: MADS: Intermittent CAN Error when engaging for Toyota Prius TSS-P +* IMPROVED: mapd: stop signs and other supported traffic_calming tags are now slowing/stopping as expected +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-08) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.08-12:07:00:GMT+1 - 0.8.17 +* FIXED: MADS: Intermittent CAN Error when engaging for Toyota Prius TSS-P +* IMPROVED: mapd: Speed Humps are now set at 20 MPH or 32 km/h +* IMPROVED: OpenStreetMap Offline Database download experience +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-10-07) +======================== +* UPDATED: Synced with commaai's master branch - 2022.10.07-08:16:00:GMT - 0.8.17 +* NEW❗: OpenStreetMap database can now be downloaded locally for offline use + * Now offering US South, US West, US Northeast, US Florida, Taiwan, and South Africa + * Databases updated - 2022.10.05-03:30:00:GMT +* NEW❗: mapd: Stop Sign, Yield, Speed Bump, Speed Hump, Sharp Curve support - huge thanks to @move-fast and @dragonpilot-community! + * Go to https://openstreetmap.org and start mapping out your area! +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-09-30) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.30-22:43:00:GMT - 0.8.17 +* RE-ADDED: Torque Lateral Controller Live Tune Menu +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-09-23) +======================== +* ADDED: Developer UI: latAccelFactorFiltered & frictionCoefficientFiltered values displays in green if Torque is using live params +* Bug fixes and performance improvements + +sunnypilot - Version Latest (2022-09-22) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.19-22:19:00:GMT - 0.8.17 +* NEW❗: Toggle to explicitly enable Custom Stock Longitudinal Control + * Applicable cars only: Honda, Hyundai/Kia/Genesis + * Settings -> Toggles menu + +sunnypilot - Version Latest (2022-09-21) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.19-22:19:00:GMT - 0.8.17 +* ADDED: Toggle to enable Live Torque (self/auto tune) with Torque lateral controller + * To enable, first enable "Enforce Torque Lateral Controller" toggle +* UPDATED: New metrics in Developer UI (when Live Torque is enabled) + * REMOVED: latAccelFactorRaw & frictionCoefficientRaw from torqued + * ADDED: latAccelFactorFiltered & frictionCoefficientFiltered from torqued +* REMOVED: Temporary remove Torque Lateral Controller Live Tune Menu + +sunnypilot - Version Latest (2022-09-20) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.19-22:19:00:GMT - 0.8.17 +* ADDED: Toggle to enable Live Torque (self/auto tune) with Torque lateral controller + * To enable, first enable "Enforce Torque Lateral Controller" toggle +* REMOVED: Temporary remove Torque Lateral Controller Live Tune Menu + +sunnypilot - Version Latest (2022-09-18) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.17-11:23:00:GMT - 0.8.17 +* ADDED: Kia Forte Non-SCC 2019 support for @askalice +* FIXED: Torque Lateral Control Live Tune now syncs with commaai:openpilot#25822 +* FIXED: mapd dependencies no longer need to be re-downloaded after unknown reboots + +sunnypilot - Version Latest (2022-09-17) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.17-11:23:00:GMT - 0.8.17 +* NEW❗: Non SCC HKG support + * Custom Stock Longitudinal Control + * ❗No❗ openpilot longitudinal control +* FIXED: Honda Bosch random low-value set speed changes + +sunnypilot - Version Latest (2022-09-16) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.16-20:23:00:GMT - 0.8.17 + +sunnypilot - Version Latest (2022-09-15) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.16-02:00:00:GMT - 0.8.17 +* FIXED: Block additional auto lane change actions if blinker stays on after the first lane change +* REVERTED: Some Toyota with LKAS button no longer requires double press to engage/disengage M.A.D.S. + +sunnypilot - Version Latest (2022-09-14)u +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.11-02:47:00:GMT - 0.8.17 +* NEW❗: GM models supported in Force Car Recognition (FCR) + * Under "SP - Vehicles" +* NEW❗: Prompt to select car in "SP - Vehicles" if car unrecognized on startup +* FIXED: Some Toyota with LKAS button no longer requires double press to engage/disengage M.A.D.S. +* UPDATED: ESCC: Use radar tracks from radar if available + +sunnypilot - Version Latest (2022-09-13) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.11-02:47:00:GMT - 0.8.17 +* NEW❗: New metric in Developer UI + * Actual Lateral Acceleration (Roll Compensated) + +sunnypilot - Version Latest (2022-09-12) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.11-02:47:00:GMT - 0.8.17 +* FIXED: Honda Nidec models not gaining speed when longitudinal engaged + +sunnypilot - Version Latest (2022-09-11) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.11-02:47:00:GMT - 0.8.17 +* NEW❗: Hyundai Enhanced SCC now forwards FCW and AEB signals and commands from radar to car +* RE-ENABLED: MADS Status Icon toggle + +sunnypilot - Version Latest (2022-09-10) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.11-02:47:00:GMT - 0.8.17 +* NEW❗: RAM improvement implementation thanks to realfast! +* DISABLED: Chrysler/Jeep/Ram with Custom Stock Longitudinal Control +* DISABLED: Volkswagen MQB & PQ with Custom Stock Longitudinal Control + +sunnypilot - Version Latest (2022-09-09) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.09-07:35:00:GMT - 0.8.17 +* NEW❗: MADS now supporting General Motors (GM) +* ADDED: Custom Stock Longitudinal Control - Volkswagen + * MQB & PQ +* ADDED: Reverse ACC Change + * ACC +/-: Short=5, Long=1 +* ADDED: Custom Stock Longitudinal Control + * Hyundai/Kia/Genesis + * Honda Bosch +* ADDED: Hyundai: 2015-16 Genesis resume from standstill fix (commaai:openpilot#25579) - pending PR +* Vision Turn Speed Control re-enabled +* Disable Onroad Uploads toggle re-enabled + +sunnypilot - Version Latest (2022-09-08) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.08-04:05:00:GMT - 0.8.17 +* NEW❗: Block lane change initiation while brake is pressed + +sunnypilot - Version Latest (2022-09-07) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.08-04:05:00:GMT - 0.8.17 +* NEW❗: Display End-to-end longitudinal 🌮 on screen + * NEW❗: Hold DISTANCE button on the steering wheel for 1 second to switch between E2E Long and ACC mode + * Enable toggle on the driving screen to switch between modes with End-to-end longitudinal + * Only applicable to cars with openpilot longitudinal control +* NEW❗: Block lane change initiation while brake is pressed +* REMOVED: Dynamic Lane Profile - upstream laneless model is now on by default +* REMOVED: hyundai: consistent start from stop (commaai:openpilot#25672) - pending PR + +sunnypilot - Version Latest (2022-09-06) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.06 - 0.8.17 +* NEW❗: Display useful metrics above the chevron that tracks the lead car + * Under "SP - Visuals" menu + * Only applicable to cars with openpilot longitudinal control +* ADDED: hyundai: consistent start from stop (commaai:openpilot#25672) - pending PR +* FIXED: Vienna speed limit interface now scales properly with the outer box +* REMOVED: Hyundai long improvements (commaai:openpilot#25604) - closed PR + +sunnypilot - Version Latest (2022-09-05) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.03 - 0.8.17 +* NEW❗: Speed Limit Control (SLC) interface integrated with upstream +* NEW❗: Speed limit from active navigation is now prioritized for Speed Limit Control +* NEW❗: MUTCD (U.S.) or Vienna (E.U.) speed limit interfaces can now be selected under "SP - Controls" + +sunnypilot - Version Latest (2022-09-04) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.03 - 0.8.17 +* FIXED: Gap Adjust Cruise status now displays properly on screen +* FIXED: mapd - missing index in list caused mapd to crash +* REMOVED: Temporary removed Vision Turn Speed Control + +sunnypilot - Version Latest (2022-09-03) +======================== +* UPDATED: Synced with commaai's master branch - 2022.09.03 - 0.8.17 +* ADDED: New border colors for different operation engagements +* ADDED: UI: Show barrier when car detected in blind spot + * Only applicable to cars that have BSM detection with openpilot +* FIXED: Cruise Cancel button no longer display prompt if cruise not engaged +* TWEAKED: Update changelogs on startup in Settings -> Software -> Version +* REMOVED: Upload Raw Logs and Full Resolution Videos toggles + +sunnypilot - Version Latest (2022-08-31) +======================== +* UPDATED: Synced with commaai's master branch - 2022.08.31 - 0.8.17 +* ADDED: New border colors for different operation engagements +* ADDED: UI: Show barrier when car detected in blind spot + * Only applicable to cars that have BSM detection with openpilot +* FIXED: Cruise Cancel button no longer display prompt if cruise not engaged +* REMOVED: Upload Raw Logs and Full Resolution Videos toggles + +sunnypilot - Version 0.8.16 (2022-07-16) +======================== +* Sync with commaai's master branches +* NEW❗: Add toggle to pause lateral actuation below 30 MPH / 50 KM/H +* IMPROVED: Better controls mismatch handling +* IMPROVED: Less frequent Low Memory alert +* IMPROVED: Only allow lateral control when in forward gears +* IMPROVED: Better alerts handling on gear changes + +sunnypilot - Version 0.8.14-1.3 (2022-06-29) +======================== +* Hyundai/Kia/Genesis + * NEW❗: MADS: Add GAP/Distance button on the steering wheel to engage/disengage + * To engage/disengage MADS: Hold the button for 0.5 second +* NEW❗: Dynamic Lane Profile: Add toggle to enable "Laneless for Curves in Auto Lane" +* HOTFIX🛠: Improve Torque lateral control and reduce ping pong for some Toyota cars + * Torque control: higher low speed gains and better steering angle deadzone logic +* Developer UI: Remove Distance Traveled, replace with Memory Usage % + * This may have a potential to fix the Low Memory alert that may appear + +sunnypilot - Version 0.8.14-1 (2022-06-27) +======================== +* HOTFIX🛠: Honda, Toyota, Volkswagen now initialized correctly with Torque Lateral Live Tune + +sunnypilot - Version 0.8.14-1 (2022-06-27) +======================== +* NEW❗: Added toggle to enable updates for sunnypilot +* HOTFIX🛠: Volkswagen car list now displays properly in Force Car Recognition menu +* REVERTED: Honda - temporary removes CRUISE (MAIN) for MADS engagement + * LKAS button continues to be used for MADS engagement/disengagement + +sunnypilot - Version 0.8.14-1 (2022-06-26) +======================== +Visit https://bit.ly/sunnyreadme for more details +* sunnypilot 0.8.14 release - based on openpilot 0.8.14 devel +* "0.8.14-prod-c3" branch only supports comma three + * If you have a comma two, EON, or other devices than a comma three, visit sunnyhaibin's discord server for more details: https://discord.gg/wRW3meAgtx +* Mono-branch support + * Honda/Acura + * Hyundai/Kia/Genesis + * Toyota/Lexus + * Volkswagen MQB +* Modified Assistive Driving Safety (MADS) Mode + * NEW❗: CRUISE (MAIN) now engages MADS for all supported car makes + * NEW❗: Added toggle to disable disengaging Automatic Lane Centering (ALC) on the brake pedal +* Dynamic Lane Profile (DLP) +* NEW❗: Gap Adjust Cruise (GAC) + * openpilot longitudinal cars can now adjust between the lead car's following distance gap via 3 modes: + * Steering Wheel (SW) | User Interface (UI) | Steering Wheel + User Interface (SW+UI) +* NEW❗: Custom Camera & Path Offsets +* NEW❗: Torque Lateral Control from openpilot 0.8.15 master (as of 2022-06-15) +* NEW❗: Torque Lateral Control Live Tune Menu +* NEW❗: Speed Limit Sign from openpilot 0.8.15 master (as of 2022-06-22) +* NEW❗: Mapbox Speed Limit data will now be utilized in Speed Limit Control (SLC) + * Speed limit data will be utilized in the following availability: + * Mapbox (active navigation) -> OpenStreetMap -> Car Interface (Toyota's TSR) +* Custom Stock Longitudinal Control + * NEW❗: Volkswagen MQB + * Honda + * Hyundai/Kia/Genesis +* NEW❗: Mapbox navigation support for non-Prime users + * Visit sunnyhaibin's discord server for more details: https://discord.gg/wRW3meAgtx +* Hyundai/Kia/Genesis + * NEW❗: Enhanced SCC (ESCC) Support + * Requires hardware modification. Visit sunnyhaibin's discord server for more details: https://discord.gg/wRW3meAgtx + * NEW❗: Smart MDPS (SMDPS) Support - Auto-detection + * Requires hardware modification and custom firmware for the SMDPS. Visit sunnyhaibin's discord server for more details: https://discord.gg/wRW3meAgtx +* Toyota/Lexus + * NEW❗: Added toggle to enforce stock longitudinal control + +sunnypilot - Version 0.8.12-4 +======================== +* NEW❗: Custom Stock Longitudinal Control by setting the target speed via openpilot's "MAX" speed thanks to multikyd! + * Speed Limit Control + * Vision-based Turn Control + * Map-based Turn Control +* NEW❗: HDA status integration with Custom Stock Longitudinal Control on applicable HKG cars only +* NEW❗: Roll Compensation and SteerRatio fix from comma's 0.8.13 +* NEW❗: Dev UI to display different metrics on screen + * Click on the "MAX" box on the top left of the openpilot display to toggle different metrics display + * Lead car relative distance; Lead car relative speed; Actual steering degree; Desired steering degree; Engine RPM; Longitudinal acceleration; Lead car actual speed; EPS torque; Current altitude; Compass direction +* NEW❗: Stand Still Timer to display time spent at a stop with M.A.D.S engaged (i.e., stop lights, stop signs, traffic congestions) +* NEW❗: Current car speed text turns red when the car is braking +* NEW❗: Export GPS tracks into GPX files and upload to OSM thanks to eFini! +* NEW❗: Enable ACC and M.A.D.S with a single press of the RES+/SET- button +* NEW❗: ACC +/-: Short=5, Long=1 + * Change the ACC +/- buttons behavior with cruise speed change in openpilot + * Disabled (Stock): Short=1, Long=5 + * Enabled: Short=5, Long=1 +* NEW❗: Speed Limit Value Offset (not %)* + * Set speed limit higher or lower than actual speed limit for a more personalized drive. + * *To use this feature, turn off "Enable Speed Limit % Offset"* +* NEW❗: Dedicated icon to show the status of M.A.D.S. +* NEW❗: No Offroad Fix for non-official devices that cannot shut down after the car is turned off +* NEW❗: Stop N' Go Resume Alternative + * Offer alternative behavior to auto resume when stopped behind a lead car using stock SCC/ACC. This feature removes the repeating prompt chime when stopped and/or allows some cars to use auto resume (i.e., Genesis) +* IMPROVED: Show the lead car icon in the car's dashboard when a lead car is detected by openpilot's camera vision +* FIXED: MADS button unintentionally set MAX when using stock longitudinal control thanks to Spektor56! + +sunnypilot - Version 0.8.12-3 +======================== +* NEW❗: Bypass "System Malfunction" alert toggle + * Prevent openpilot from returning the "System Malfunction" alert that hinders the ability use openpilot +* FIXED: Hyundai/Kia/Genesis Brake Hold Active now outputs the correct events on screen with M.A.D.S. engaged + +sunnypilot - Version 0.8.12-2 +======================== +* NEW❗: Disable M.A.D.S. toggle to disable the beloved M.A.D.S. feature + * Enable Stock openpilot engagement/disengagement +* ADJUST: Initialize Driving Screen Off Brightness at 50% + +sunnypilot - Version 0.8.12-1 +======================== +* sunnypilot 0.8.12 release - based on openpilot 0.8.12 devel +* Dedicated Hyundai/Kia/Genesis branch support +* NEW❗: OpenStreetMap integration thanks to the Move Fast team! + * NEW❗: Vision-based Turn Control + * NEW❗: Map-Data-based Turn Control + * NEW❗: Speed Limit Control w/ optional Speed Limit Offset + * NEW❗: OpenStreetMap integration debug UI + * Only available to openpilot longitudinal enabled cars +* NEW❗: Hands on Wheel Monitoring according to EU r079r4e regulation +* NEW❗: Disable Onroad Uploads for data-limited Wi-Fi hotspots when using OpenStreetMap related features +* NEW❗: Fast Boot (Prebuilt) +* NEW❗: Auto Lane Change Timer +* NEW❗: Screen Brightness Control (Global) +* NEW❗: Driving Screen Off Timer +* NEW❗: Driving Screen Off Brightness (%) +* NEW❗: Max Time Offroad +* Improved user feedback with M.A.D.S. operations thanks to Spektor56! + * Lane Path + * Green🟢 (Laneful), Red🔴 (Laneless): M.A.D.S. engaged + * White⚪: M.A.D.S. suspended or disengaged + * Black⚫: M.A.D.S. engaged, steering is being manually override by user + * Screen border now only illuminates Green when SCC/ACC is engaged + +sunnypilot - Version 0.8.10-1 (Unreleased) +======================== +* sunnypilot 0.8.10 release - based on openpilot 0.8.10 `devel` +* Add Toyota cars to Force Car Recognition + +sunnypilot - Version 0.8.9-4 +======================== +* Hyundai: Fix Ioniq Hybrid signals + +sunnypilot - Version 0.8.9-3 +======================== +* Update home screen brand and version structure + +sunnypilot - Version 0.8.9-2 +======================== +* Added additional Sonata Hybrid Firmware Versions +* Features + * Modified Assistive Driving Safety (MADS) Mode + * Dynamic Lane Profile (DLP) + * Quiet Drive 🤫 + * Force Car Recognition (FCR) + * PID Controller: add kd into the stock PID controller + +sunnypilot - Version 0.8.9-1 +======================== +* First changelog! +* Features + * Modified Assistive Driving Safety (MADS) Mode + * Dynamic Lane Profile (DLP) + * Quiet Drive 🤫 + * Force Car Recognition (FCR) + * PID Controller: add kd into the stock PID controller diff --git a/common/swaglog.cc b/common/swaglog.cc index 62a405a2b6..d73f0c1a59 100644 --- a/common/swaglog.cc +++ b/common/swaglog.cc @@ -15,6 +15,8 @@ #include "common/version.h" #include "system/hardware/hw.h" +#include "sunnypilot/common/version.h" + class SwaglogState { public: SwaglogState() { @@ -56,7 +58,7 @@ public: if (char* daemon_name = getenv("MANAGER_DAEMON")) { ctx_j["daemon"] = daemon_name; } - ctx_j["version"] = COMMA_VERSION; + ctx_j["version"] = SUNNYPILOT_VERSION; ctx_j["dirty"] = !getenv("CLEAN"); ctx_j["device"] = Hardware::get_name(); } diff --git a/common/tests/test_markdown.py b/common/tests/test_markdown.py index d3c7e02c69..7e04bf2920 100644 --- a/common/tests/test_markdown.py +++ b/common/tests/test_markdown.py @@ -6,7 +6,7 @@ from openpilot.common.markdown import parse_markdown class TestMarkdown: def test_all_release_notes(self): - with open(os.path.join(BASEDIR, "RELEASES.md")) as f: + with open(os.path.join(BASEDIR, "CHANGELOG.md")) as f: release_notes = f.read().split("\n\n") assert len(release_notes) > 10 diff --git a/common/tests/test_swaglog.cc b/common/tests/test_swaglog.cc index 09bc4c3795..c97f907c24 100644 --- a/common/tests/test_swaglog.cc +++ b/common/tests/test_swaglog.cc @@ -9,6 +9,8 @@ #include "system/hardware/hw.h" #include "third_party/json11/json11.hpp" +#include "sunnypilot/common/version.h" + std::string daemon_name = "testy"; std::string dongle_id = "test_dongle_id"; int LINE_NO = 0; @@ -53,7 +55,7 @@ void recv_log(int thread_cnt, int thread_msg_cnt) { REQUIRE(ctx["dongle_id"].string_value() == dongle_id); REQUIRE(ctx["dirty"].bool_value() == true); - REQUIRE(ctx["version"].string_value() == COMMA_VERSION); + REQUIRE(ctx["version"].string_value() == SUNNYPILOT_VERSION); std::string device = Hardware::get_name(); REQUIRE(ctx["device"].string_value() == device); diff --git a/release/build_release.sh b/release/build_release.sh index 5ada9c5ecc..9bd4c81f6d 100755 --- a/release/build_release.sh +++ b/release/build_release.sh @@ -39,7 +39,7 @@ cd $BUILD_DIR rm -f panda/board/obj/panda.bin.signed rm -f panda/board/obj/panda_h7.bin.signed -VERSION=$(cat common/version.h | awk -F[\"-] '{print $2}') +VERSION=$(cat sunnypilot/common/version.h | awk -F[\"-] '{print $2}') echo "[-] committing version $VERSION T=$SECONDS" git add -f . git commit -a -m "openpilot v$VERSION release" diff --git a/release/build_stripped.sh b/release/build_stripped.sh index 88c0101a45..df5b2a3dd6 100755 --- a/release/build_stripped.sh +++ b/release/build_stripped.sh @@ -49,7 +49,7 @@ rm -f panda/board/obj/panda.bin.signed GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD) GIT_COMMIT_DATE=$(git --git-dir=$SOURCE_DIR/.git show --no-patch --format='%ct %ci' HEAD) DATETIME=$(date '+%Y-%m-%dT%H:%M:%S') -VERSION=$(cat $SOURCE_DIR/common/version.h | awk -F\" '{print $2}') +VERSION=$(cat $SOURCE_DIR/sunnypilot/common/version.h | awk -F\" '{print $2}') echo -n "$GIT_HASH" > git_src_commit echo -n "$GIT_COMMIT_DATE" > git_src_commit_date diff --git a/release/ci/publish.sh b/release/ci/publish.sh index 4aecbacc4a..315ae22bfe 100755 --- a/release/ci/publish.sh +++ b/release/ci/publish.sh @@ -30,7 +30,7 @@ if [ -z "$GIT_ORIGIN" ]; then fi # "Tagging" -echo "#define COMMA_VERSION \"$VERSION\"" > ${OUTPUT_DIR}/common/version.h +echo "#define SUNNYPILOT_VERSION \"$VERSION\"" > ${OUTPUT_DIR}/sunnypilot/common/version.h ## set git identity #source $DIR/identity.sh @@ -55,7 +55,7 @@ git add -f . # include source commit hash and build date in commit GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD) DATETIME=$(date '+%Y-%m-%dT%H:%M:%S') -SP_VERSION=$(awk -F\" '{print $2}' $SOURCE_DIR/common/version.h) +SP_VERSION=$(awk -F\" '{print $2}' $SOURCE_DIR/sunnypilot/common/version.h) # Commit with detailed message git commit -a -m "sunnypilot v$VERSION diff --git a/selfdrive/ui/tests/cycle_offroad_alerts.py b/selfdrive/ui/tests/cycle_offroad_alerts.py index e468d88e0e..04fcd4017f 100755 --- a/selfdrive/ui/tests/cycle_offroad_alerts.py +++ b/selfdrive/ui/tests/cycle_offroad_alerts.py @@ -18,7 +18,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 = open(os.path.join(BASEDIR, "CHANGELOG.md")).read() r = r[:r.find('\n\n')] # Slice latest release notes params.put("UpdaterNewReleaseNotes", r + "\n") diff --git a/selfdrive/ui/tests/test_ui/run.py b/selfdrive/ui/tests/test_ui/run.py index 68d1f0c185..d254ca8733 100755 --- a/selfdrive/ui/tests/test_ui/run.py +++ b/selfdrive/ui/tests/test_ui/run.py @@ -188,7 +188,7 @@ def setup_offroad_alert(click, pm: PubMaster, scroll=None): def setup_update_available(click, pm: PubMaster, scroll=None): Params().put_bool("UpdateAvailable", True) - release_notes_path = os.path.join(BASEDIR, "RELEASES.md") + release_notes_path = os.path.join(BASEDIR, "CHANGELOG.md") with open(release_notes_path) as file: release_notes = file.read().split('\n\n', 1)[0] Params().put("UpdaterNewReleaseNotes", release_notes + "\n") diff --git a/sunnypilot/common/version.h b/sunnypilot/common/version.h new file mode 100644 index 0000000000..5086f103f8 --- /dev/null +++ b/sunnypilot/common/version.h @@ -0,0 +1 @@ +#define SUNNYPILOT_VERSION "2025.001.000" diff --git a/system/loggerd/logger.cc b/system/loggerd/logger.cc index 0ebe323939..e8aeb96d02 100644 --- a/system/loggerd/logger.cc +++ b/system/loggerd/logger.cc @@ -11,6 +11,8 @@ #include "common/swaglog.h" #include "common/version.h" +#include "sunnypilot/common/version.h" + // ***** log metadata ***** kj::Array logger_build_init_data() { uint64_t wall_time = nanos_since_epoch(); @@ -19,7 +21,7 @@ kj::Array logger_build_init_data() { auto init = msg.initEvent().initInitData(); init.setWallTimeNanos(wall_time); - init.setVersion(COMMA_VERSION); + init.setVersion(SUNNYPILOT_VERSION); init.setDirty(!getenv("CLEAN")); init.setDeviceType(Hardware::get_device_type()); diff --git a/system/updated/updated.py b/system/updated/updated.py index 7ab9e070dc..181e410fa6 100755 --- a/system/updated/updated.py +++ b/system/updated/updated.py @@ -82,7 +82,7 @@ def set_consistent_flag(consistent: bool) -> None: def parse_release_notes(basedir: str) -> bytes: try: - with open(os.path.join(basedir, "RELEASES.md"), "rb") as f: + with open(os.path.join(basedir, "CHANGELOG.md"), "rb") as f: r = f.read().split(b'\n\n', 1)[0] # Slice latest release notes try: return bytes(parse_markdown(r.decode("utf-8")), encoding="utf-8") @@ -294,7 +294,7 @@ class Updater: try: branch = self.get_branch(basedir) commit = self.get_commit_hash(basedir)[:7] - with open(os.path.join(basedir, "common", "version.h")) as f: + with open(os.path.join(basedir, "sunnypilot", "common", "version.h")) as f: version = f.read().split('"')[1] commit_unix_ts = run(["git", "show", "-s", "--format=%ct", "HEAD"], basedir).rstrip() diff --git a/system/version.py b/system/version.py index 6ff290e032..268aaf11ea 100755 --- a/system/version.py +++ b/system/version.py @@ -33,13 +33,13 @@ terms_version: str = "2" def get_version(path: str = BASEDIR) -> str: - with open(os.path.join(path, "common", "version.h")) as _versionf: + with open(os.path.join(path, "sunnypilot", "common", "version.h")) as _versionf: version = _versionf.read().split('"')[1] return version def get_release_notes(path: str = BASEDIR) -> str: - with open(os.path.join(path, "RELEASES.md")) as f: + with open(os.path.join(path, "CHANGELOG.md")) as f: return f.read().split('\n\n', 1)[0] diff --git a/tools/replay/api.cc b/tools/replay/api.cc index 85e4e52b28..49923913de 100644 --- a/tools/replay/api.cc +++ b/tools/replay/api.cc @@ -14,6 +14,8 @@ #include "common/version.h" #include "system/hardware/hw.h" +#include "sunnypilot/common/version.h" + namespace CommaApi2 { // Base64 URL-safe character set (uses '-' and '_' instead of '+' and '/') @@ -141,7 +143,7 @@ std::string httpGet(const std::string &url, long *response_code) { // Handle headers struct curl_slist *headers = nullptr; - headers = curl_slist_append(headers, "User-Agent: openpilot-" COMMA_VERSION); + headers = curl_slist_append(headers, "User-Agent: openpilot-" SUNNYPILOT_VERSION); if (!token.empty()) { headers = curl_slist_append(headers, ("Authorization: JWT " + token).c_str()); } diff --git a/tools/replay/consoleui.cc b/tools/replay/consoleui.cc index 185c7d6c36..bdbdb3e841 100644 --- a/tools/replay/consoleui.cc +++ b/tools/replay/consoleui.cc @@ -10,6 +10,8 @@ #include "common/util.h" #include "common/version.h" +#include "sunnypilot/common/version.h" + namespace { const int BORDER_SIZE = 3; @@ -119,7 +121,7 @@ void ConsoleUI::initWindows() { // set the title bar wbkgd(w[Win::Title], A_REVERSE); - mvwprintw(w[Win::Title], 0, 3, "sunnypilot replay %s", COMMA_VERSION); + mvwprintw(w[Win::Title], 0, 3, "sunnypilot replay %s", SUNNYPILOT_VERSION); // show windows on the real screen refresh(); From 641af6d7e7f7fe882b1bee5ce5b52133cf76ee9b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 21 Oct 2025 17:55:27 -0400 Subject: [PATCH 261/681] changelog: more new contributors! (#1413) --- CHANGELOG.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 687484affe..00be668571 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -123,16 +123,24 @@ sunnypilot Version 2025.001.000 (2025-10-25) * Lateral Planner: Laneful Mode * Lateral: Custom Camera and Path Offsets * Toyota: Door Controls -* New Contributors - * @discountchubbs made their first contribution in "ui: Error log button to Developer panel (#627)" - * @First made their first contribution in "NNLC: bump max similarity for higher accuracy (#704)" - * @nayan made their first contribution in "UI: Update AbstractControlSP_SELECTOR and OptionControlSP (#800)" - * @wtogamiwtogami made their first contribution in "Add TOYOTA_RAV4_PRIME NNLC tuning gen 1 (#850)" +* New Contributors (sunnypilot/sunnypilot) + * @royjr made their first contribution in "NNLC: bump max similarity for higher accuracy (#704)" + * @nayan8teen made their first contribution in "UI: Update AbstractControlSP_SELECTOR and OptionControlSP (#800)" + * @wtogami made their first contribution in "TOYOTA_RAV4_PRIME NNLC tuning gen 1 (#850)" * @dparring made their first contribution in "FCA: Ram 1500 improvements (#797)" * @Kirito3481 made their first contribution in "Update ko-kr translation (#1167)" - * @michael-was-taken made their first contributio@dzid26 in "Reorder README tables: show -new branches first (#1191)" - * @dzid26 made their first contribution in "docs: clarify pedal press (#1289)" + * @michael-was-taken made their first contribution in "Reorder README tables: show -new branches first (#1191)" + * @dzid26 made their first contribution in "params: Fix loading delay on startup (#1297)" * @HazZelnutz made their first contribution in "Visuals: Turn signals on screen when blinker is used (#1291)" +* New Contributors (sunnypilot/opendbc) + * @chrispypatt made their first contribution in "Toyota: SecOC Longitudinal Control (sunnypilot/opendbc#93)" + * @Discountchubbs made their first contribution in "Hyundai: EPS FW For 2022 KIA_NIRO_EV SCC (sunnypilot/opendbc#118)" + * @lukasloetkolben made their first contribution in "Tesla: enableBsm is always true (sunnypilot/opendbc#163)" + * @roenthomas made their first contribution in "Honda: int flag for modified EPS configs (sunnypilot/opendbc#254)" + * @AmyJeanes made their first contribution in "Tesla: Fix stock LKAS being blocked when MADS is enabled (sunnypilot/opendbc#286)" + * @mvl-boston made their first contribution in "Honda: Update Clarity brake to renamed DBC message name (sunnypilot/opendbc#282)" + * @dzid26 made their first contribution in "Tesla: Parse speed limit from CAN (sunnypilot/opendbc#308)" + * @firestar5683 made their first contribution in "GM: Non-ACC platforms with steering only support (sunnypilot/opendbc#229)" ************************ * Synced with commaai's openpilot (v0.10.1) * master commit c9dbf97649a27117be6d5955a49e2d4253337288 (September 12, 2025) From 657ff0f8ecd6de0c3a1b81657aa56b5fab907e2c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 22 Oct 2025 00:09:10 -0400 Subject: [PATCH 262/681] ui: refine ICBM description handling and availability logic (#1414) * ui: refine ICBM description handling and availability logic * car -> platform * retain --- .../qt/offroad/settings/longitudinal_panel.cc | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc index 2cc59ea333..9c51ff8c67 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc @@ -38,7 +38,7 @@ LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) { intelligentCruiseButtonManagement = new ParamControlSP( "IntelligentCruiseButtonManagement", tr("Intelligent Cruise Button Management (ICBM) (Alpha)"), - tr("When enabled, sunnypilot will attempt to manage the built-in cruise control buttons by emulating button presses for limited longitudinal control."), + "", "", this ); @@ -104,6 +104,8 @@ void LongitudinalPanel::hideEvent(QHideEvent *event) { } void LongitudinalPanel::refresh(bool _offroad) { + const QString icbm_description = tr("When enabled, sunnypilot will attempt to manage the built-in cruise control buttons by emulating button presses for limited longitudinal control."); + auto cp_bytes = params.get("CarParamsPersistent"); auto cp_sp_bytes = params.get("CarParamsSPPersistent"); if (!cp_bytes.empty() && !cp_sp_bytes.empty()) { @@ -120,9 +122,24 @@ void LongitudinalPanel::refresh(bool _offroad) { if (CP_SP.getIntelligentCruiseButtonManagementAvailable() && !has_longitudinal_control) { intelligentCruiseButtonManagement->setEnabled(offroad); + intelligentCruiseButtonManagement->setDescription(icbm_description); } else { params.remove("IntelligentCruiseButtonManagement"); intelligentCruiseButtonManagement->setEnabled(false); + + const QString icbm_unavaialble = tr("Intelligent Cruise Button Management is currently unavailable on this platform."); + + QString long_desc = icbm_unavaialble; + if (has_longitudinal_control) { + if (CP.getAlphaLongitudinalAvailable()) { + long_desc = icbm_unavaialble + " " + tr("Disable the openpilot Longitudinal Control (alpha) toggle to allow Intelligent Cruise Button Management."); + } else { + long_desc = icbm_unavaialble + " " + tr("openpilot Longitudinal Control is the default longitudinal control for this platform."); + } + } + + intelligentCruiseButtonManagement->setDescription("" + long_desc + "

" + icbm_description); + intelligentCruiseButtonManagement->showDescription(); } if (has_longitudinal_control || has_icbm) { @@ -151,6 +168,7 @@ void LongitudinalPanel::refresh(bool _offroad) { has_longitudinal_control = false; is_pcm_cruise = false; has_icbm = false; + intelligentCruiseButtonManagement->setDescription("" + tr("Start the vehicle to check vehicle compatibility.") + "
" + icbm_description); } QString accEnabledDescription = tr("Enable custom Short & Long press increments for cruise speed increase/decrease."); From 5289b08bcf0b136ef1829f4951d48aa22bdb1945 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Tue, 21 Oct 2025 21:54:40 -0700 Subject: [PATCH 263/681] 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 13f3b22091..44c463b18c 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 02ef82390b..b3558a15a8 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 7097e69aa320e269b9c59d70756da23588a8aeef Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 22 Oct 2025 11:17:02 -0400 Subject: [PATCH 264/681] Speed Limit Assist: generalize availability helper (#1416) * init infra to disable sla in certain conditions * a bit more * in another PR * in another PR * since when? * start here --- .../controls/lib/longitudinal_planner.py | 4 ++-- selfdrive/controls/plannerd.py | 8 +++++-- .../test/longitudinal_maneuvers/plant.py | 4 +++- .../speed_limit/speed_limit_settings.cc | 24 ++++++++++--------- sunnypilot/selfdrive/car/interfaces.py | 5 ++-- .../controls/lib/longitudinal_planner.py | 4 ++-- .../controls/lib/speed_limit/helpers.py | 17 +++++++++++++ .../lib/speed_limit/speed_limit_assist.py | 9 ++++--- .../tests/test_speed_limit_assist.py | 2 +- 9 files changed, 52 insertions(+), 25 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 53eca38356..0501f669f1 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -51,12 +51,12 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP): class LongitudinalPlanner(LongitudinalPlannerSP): - def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL): + def __init__(self, CP, CP_SP, 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' - LongitudinalPlannerSP.__init__(self, self.CP, self.mpc) + LongitudinalPlannerSP.__init__(self, self.CP, CP_SP, self.mpc) self.fcw = False self.dt = dt self.allow_throttle = True diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 4565a9c047..f7d3370f90 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from cereal import car +from cereal import car, custom from openpilot.common.gps import get_gps_location_service from openpilot.common.params import Params from openpilot.common.realtime import Priority, config_realtime_process @@ -17,10 +17,14 @@ def main(): CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) cloudlog.info("plannerd got CarParams: %s", CP.brand) + cloudlog.info("plannerd is waiting for CarParamsSP") + CP_SP = messaging.log_from_bytes(params.get("CarParamsSP", block=True), custom.CarParamsSP) + cloudlog.info("plannerd got CarParamsSP") + gps_location_service = get_gps_location_service(params) ldw = LaneDepartureWarning() - longitudinal_planner = LongitudinalPlanner(CP) + longitudinal_planner = LongitudinalPlanner(CP, CP_SP) pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP']) sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState', 'liveMapDataSP', 'carStateSP', gps_location_service], diff --git a/selfdrive/test/longitudinal_maneuvers/plant.py b/selfdrive/test/longitudinal_maneuvers/plant.py index e466472432..e82ef2f65e 100755 --- a/selfdrive/test/longitudinal_maneuvers/plant.py +++ b/selfdrive/test/longitudinal_maneuvers/plant.py @@ -51,7 +51,9 @@ class Plant: from opendbc.car.honda.values import CAR from opendbc.car.honda.interface import CarInterface - self.planner = LongitudinalPlanner(CarInterface.get_non_essential_params(CAR.HONDA_CIVIC), init_v=self.speed) + CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC) + CP_SP = CarInterface.get_non_essential_params_sp(CP, CAR.HONDA_CIVIC) + self.planner = LongitudinalPlanner(CP, CP_SP, init_v=self.speed) @property def current_time(self): diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc index 1696c7f63a..50d9ccbb41 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc @@ -110,8 +110,7 @@ void SpeedLimitSettings::refresh() { SpeedLimitOffsetType offset_type_param = static_cast(std::atoi(params.get("SpeedLimitOffsetType").c_str())); QString offsetLabel = QString::fromStdString(params.get("SpeedLimitValueOffset")); - bool has_longitudinal_control; - bool has_icbm; + bool sla_available; auto cp_bytes = params.get("CarParamsPersistent"); auto cp_sp_bytes = params.get("CarParamsSPPersistent"); if (!cp_bytes.empty() && !cp_sp_bytes.empty()) { @@ -122,17 +121,20 @@ void SpeedLimitSettings::refresh() { cereal::CarParams::Reader CP = cmsg.getRoot(); cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot(); - has_longitudinal_control = hasLongitudinalControl(CP); - has_icbm = hasIntelligentCruiseButtonManagement(CP_SP); + bool has_longitudinal_control = hasLongitudinalControl(CP); + bool has_icbm = hasIntelligentCruiseButtonManagement(CP_SP); - if (!has_longitudinal_control && !has_icbm) { - if (speed_limit_mode_param == SpeedLimitMode::ASSIST) { - params.put("SpeedLimitMode", std::to_string(static_cast(SpeedLimitMode::WARNING))); - } + /* + * Speed Limit Assist is available when: + * - has_longitudinal_control or has_icbm + */ + sla_available = has_longitudinal_control || has_icbm; + + if (!sla_available && speed_limit_mode_param == SpeedLimitMode::ASSIST) { + params.put("SpeedLimitMode", std::to_string(static_cast(SpeedLimitMode::WARNING))); } } else { - has_longitudinal_control = false; - has_icbm = false; + sla_available = false; } speed_limit_mode_settings->setDescription(modeDescription(speed_limit_mode_param)); @@ -152,7 +154,7 @@ void SpeedLimitSettings::refresh() { speed_limit_offset->showDescription(); } - if (has_longitudinal_control || has_icbm) { + if (sla_available) { speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues(getSpeedLimitModeValues())); } else { speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues( diff --git a/sunnypilot/selfdrive/car/interfaces.py b/sunnypilot/selfdrive/car/interfaces.py index 6a868ab85b..7d63d3c5ba 100644 --- a/sunnypilot/selfdrive/car/interfaces.py +++ b/sunnypilot/selfdrive/car/interfaces.py @@ -11,7 +11,7 @@ from opendbc.car.interfaces import CarInterfaceBase from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog from openpilot.sunnypilot.selfdrive.controls.lib.nnlc.helpers import get_nn_model_path -from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode as SpeedLimitMode +from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.helpers import set_speed_limit_assist_availability import openpilot.system.sentry as sentry @@ -87,8 +87,7 @@ def _cleanup_unsupported_params(CP: structs.CarParams, CP_SP: structs.CarParamsS params.remove("SmartCruiseControlVision") params.remove("SmartCruiseControlMap") - if params.get("SpeedLimitMode", return_default=True) == SpeedLimitMode.assist: - params.put("SpeedLimitMode", int(SpeedLimitMode.warning)) + set_speed_limit_assist_availability(CP, CP_SP, params) def setup_interfaces(CI: CarInterfaceBase, params: Params = None) -> None: diff --git a/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py b/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py index 4c2443f253..68b50c3e19 100644 --- a/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py +++ b/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py @@ -22,13 +22,13 @@ LongitudinalPlanSource = custom.LongitudinalPlanSP.LongitudinalPlanSource class LongitudinalPlannerSP: - def __init__(self, CP: structs.CarParams, mpc): + def __init__(self, CP: structs.CarParams, CP_SP: structs.CarParamsSP, mpc): self.events_sp = EventsSP() self.resolver = SpeedLimitResolver() self.dec = DynamicExperimentalController(CP, mpc) self.scc = SmartCruiseControl() self.resolver = SpeedLimitResolver() - self.sla = SpeedLimitAssist(CP) + self.sla = SpeedLimitAssist(CP, CP_SP) self.generation = int(model_bundle.generation) if (model_bundle := get_active_bundle()) else None self.source = LongitudinalPlanSource.cruise self.e2e_alerts_helper = E2EAlertsHelper() diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py b/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py index 40e8f653ff..c209e98dce 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py @@ -5,7 +5,10 @@ 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 custom, car from openpilot.common.constants import CV +from openpilot.common.params import Params +from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode as SpeedLimitMode def compare_cluster_target(v_cruise_cluster: float, target_set_speed: float, is_metric: bool) -> tuple[bool, bool]: @@ -17,3 +20,17 @@ def compare_cluster_target(v_cruise_cluster: float, target_set_speed: float, is_ req_minus = v_cruise_cluster_conv > target_set_speed_conv return req_plus, req_minus + + +def set_speed_limit_assist_availability(CP: car.CarParams, CP_SP: custom.CarParamsSP, params: Params = None) -> None: + if params is None: + params = Params() + + allowed = True + + if not CP.openpilotLongitudinalControl and CP_SP.pcmCruiseSpeed: + allowed = False + + if not allowed: + if params.get("SpeedLimitMode", return_default=True) == SpeedLimitMode.assist: + params.put("SpeedLimitMode", int(SpeedLimitMode.warning)) diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py index b948452059..fcb5a98a2a 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py @@ -15,7 +15,7 @@ from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import PCM_LONG_REQUIRED_MAX_SET_SPEED, CONFIRM_SPEED_THRESHOLD from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode -from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.helpers import compare_cluster_target +from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.helpers import compare_cluster_target, set_speed_limit_assist_availability from openpilot.selfdrive.modeld.constants import ModelConstants ButtonType = car.CarState.ButtonEvent.Type @@ -38,7 +38,7 @@ LIMIT_MIN_ACC = -1.5 # m/s^2 Maximum deceleration allowed for limit controllers LIMIT_MAX_ACC = 1.0 # m/s^2 Maximum acceleration allowed for limit controllers to provide while active. LIMIT_MIN_SPEED = 8.33 # m/s, Minimum speed limit to provide as solution on limit controllers. LIMIT_SPEED_OFFSET_TH = -1. # m/s Maximum offset between speed limit and current speed for adapting state. -V_CRUISE_UNSET = 255 +V_CRUISE_UNSET = 255. CRUISE_BUTTONS_PLUS = (ButtonType.accelCruise, ButtonType.resumeCruise) CRUISE_BUTTONS_MINUS = (ButtonType.decelCruise, ButtonType.setCruise) @@ -52,13 +52,15 @@ class SpeedLimitAssist: a_ego: float v_offset: float - def __init__(self, CP): + def __init__(self, CP: car.CarParams, CP_SP: custom.CarParamsSP): self.params = Params() self.CP = CP + self.CP_SP = CP_SP self.frame = -1 self.long_engaged_timer = 0 self.pre_active_timer = 0 self.is_metric = self.params.get_bool("IsMetric") + set_speed_limit_assist_availability(self.CP, self.CP_SP, self.params) self.enabled = self.params.get("SpeedLimitMode", return_default=True) == Mode.assist self.long_enabled = False self.long_enabled_prev = False @@ -140,6 +142,7 @@ class SpeedLimitAssist: def update_params(self) -> None: if self.frame % int(PARAMS_UPDATE_PERIOD / DT_MDL) == 0: self.is_metric = self.params.get_bool("IsMetric") + set_speed_limit_assist_availability(self.CP, self.CP_SP, self.params) self.enabled = self.params.get("SpeedLimitMode", return_default=True) == Mode.assist def update_car_state(self, CS: car.CarState) -> None: diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py index 168c53016b..8a3376a05c 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py @@ -38,7 +38,7 @@ class TestSpeedLimitAssist: self.reset_custom_params() self.events_sp = EventsSP() CI = self._setup_platform(TOYOTA.TOYOTA_RAV4_TSS2) - self.sla = SpeedLimitAssist(CI.CP) + self.sla = SpeedLimitAssist(CI.CP, CI.CP_SP) self.sla.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.sla.pcm_op_long] / DT_MDL) self.pcm_long_max_set_speed = PCM_LONG_REQUIRED_MAX_SET_SPEED[self.sla.is_metric][1] # use 80 MPH for now self.speed_conv = CV.MS_TO_KPH if self.sla.is_metric else CV.MS_TO_MPH 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 265/681] 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 f2b1e0b669..31d982859b 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 266/681] 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 019fbbe10b..6cd8196ae0 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 31d982859b..c46b0965c5 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 dd73ed12f7..732b441da9 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 267/681] 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 15d48b4e13..baf45ce95a 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 268/681] 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 ac006c2545..80b87483de 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 269/681] 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 c46b0965c5..443fd1851c 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 732b441da9..cdd4301fcf 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 270/681] 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 f2a064c606..d741e13cf3 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 b853b0f2f2..6f89e04800 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 271/681] 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 0891b79039..c771ee78b7 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 44e8c83e6d..0000000000 --- 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 12dd2ca035..0000000000 --- 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 ddb6f744e9..0000000000 --- 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 9ec61b9b81..ed851a41c8 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 5e86e87c76..e6b6a44c40 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 0c35a3d3c9..5c02227ca5 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 272/681] 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 07ec162f0b..9bbe0916f7 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 035d8aa011..61ba1d38ee 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 6b99df1c38..434c0567d1 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 273/681] 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 368d02cdfc..b7e28a8d52 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 274/681] 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 b7e28a8d52..7d90d56925 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 275/681] 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 55abe02fe1..a02c7a1ebc 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 276/681] 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 8e0cfdbc3c..1b78b5c9e8 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 277/681] 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 0832b3964e..d3306a11ba 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 78bf8c22ee..6cc6298eb3 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 43bf7d0476..55f347c44e 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 7ede8c394a..a7670b2482 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 91268960cb..4b8f087968 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 e9bdf59e26..3f762a8842 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 f4d70eaa47..bbd4aef532 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 7f431d3a77..f13383fa55 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 1b78b5c9e8..73c8946f55 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 1e4d5c6c85..e4441edec3 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 48b577ea59..d468442b15 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 0000000000..b68a197870 --- /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 0000000000..34b3d8321d --- /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 0000000000..55554e9d9c --- /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 0000000000..a7424c0915 --- /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 0000000000..12bdcb3a8c --- /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 0000000000..1829dccd83 --- /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 0000000000..29d3b9b9fe --- /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 0000000000..e91820f242 --- /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 0bd5b161b9..802243ff3e 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 ea88180ef8..fcc5a14103 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 e44c4aad5d..88389cb053 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 ff7163793d..476a33a99d 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 c020b33d5e..7670808924 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 baf45ce95a..84969d032e 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 80b87483de..b0e1035687 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 99aed529a7..756495436b 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 a02c7a1ebc..a604c8e16d 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 a59030363b..e705c55f7c 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 278/681] 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 73c8946f55..6f4629c75f 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 279/681] 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 0157dd3ef4..562cde39e1 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 280/681] 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 6f4629c75f..09b09ddec1 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 2e96b3bd43..29fae5b61c 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 84969d032e..bd6517ad49 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 756495436b..10f6f1400d 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 a604c8e16d..e5f234ed39 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 8c63ca3f9f..fc42b48081 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 281/681] update RELEASES.md --- RELEASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 568be6c353..558d322094 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 282/681] 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 50ac49dd7c..41a7367d84 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 0000000000..b85597b1f4 --- /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 3f762a8842..f5f37fbd3c 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 09b09ddec1..8166a8a9e4 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 b68a197870..e6a0e880db 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 0000000000..5860c94ddd --- /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 34b3d8321d..cac0faf887 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 55554e9d9c..66fecf358d 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 a7424c0915..04dffbf3e6 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 12bdcb3a8c..4b0370403e 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 0000000000..0ae8869634 --- /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 0000000000..c07d449013 --- /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 1829dccd83..a14304bb8b 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 0000000000..87420ed07a --- /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 29d3b9b9fe..022621f579 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 0000000000..d09add97d8 --- /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 0000000000..1e9e2c0948 --- /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 476a33a99d..68e1fdc44a 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 7670808924..03d519e601 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 b91090e051..ea6fa63365 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 f6caa3c5f0..d51975087a 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 10f6f1400d..4259208107 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 fc42b48081..3b2201164a 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 283/681] 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 bd6517ad49..5b9c51886d 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 284/681] 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 89be3cceb2..e3674452d5 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 97f178b1f3..bc50afc03a 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 2e213988bc..4eda46f37b 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 47304fbdc5..6df164b442 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 ee50712212..045dc67220 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 0000000000..99e904c490 --- /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 0000000000..ad64d7e722 --- /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 0000000000..629a108b16 --- /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 0000000000..577eea12a0 --- /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 f27df4bf1e..ca069b358d 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 5c1cbe2d69..f839ffe7fe 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 c552567ada51b0189d5f1d742553731054aa102b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 23 Oct 2025 01:08:19 -0400 Subject: [PATCH 285/681] ui: increase minimum button width in ButtonParamControlSP (#1419) --- selfdrive/ui/sunnypilot/qt/widgets/controls.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/qt/widgets/controls.h b/selfdrive/ui/sunnypilot/qt/widgets/controls.h index e111ce2fb5..71041556b7 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/controls.h @@ -390,7 +390,7 @@ class ButtonParamControlSP : public MultiButtonControlSP { Q_OBJECT public: ButtonParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, - const std::vector &button_texts, const int minimum_button_width = 225, const bool inline_layout = false, bool advancedControl = false) : MultiButtonControlSP(title, desc, icon, + const std::vector &button_texts, const int minimum_button_width = 380, const bool inline_layout = false, bool advancedControl = false) : MultiButtonControlSP(title, desc, icon, button_texts, minimum_button_width, inline_layout, advancedControl) { key = param.toStdString(); int value = atoi(params.get(key).c_str()); From 1e73025f86d7f6cbf745a1b8507dde4bf47ec0d5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 22 Oct 2025 22:18:07 -0700 Subject: [PATCH 286/681] 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 35ced1e38b..3f60fab73d 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 9ec7a59223..0000000000 --- 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 d3306a11ba..b700df0c88 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 80273db106..648afdae57 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 6cc6298eb3..8cb877c351 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 a7670b2482..a9b40f0d24 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 4903a3db3d..0000000000 --- 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 6889b40e51..0000000000 --- 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 ad64d7e722..0000000000 --- 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 e01adbe063..0000000000 --- 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 567a54d49b..0000000000 --- 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 8b1fdf474c..0000000000 --- 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 a19b70ac3f..0000000000 --- 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 f19786a46f..0000000000 --- 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 0bdf64e459..0000000000 --- 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 8bdeaf3bbd..0000000000 --- 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 d4ad8974b0..0000000000 --- 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 cab932a388..0000000000 --- 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 a095228da2..0000000000 --- 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 c73421b185..0000000000 --- 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 9010227f18..0000000000 --- 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 f6eb752fe6..0000000000 --- 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 e255073f39..0000000000 --- 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 bfb7638bbe..0000000000 --- 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 aa158b4c7b..0000000000 --- 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 9082cb0f99..0000000000 --- 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 1390566671..0000000000 --- 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 db229c5fa6..0000000000 --- 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 6eb8693a1a..0000000000 --- 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 d52cf16bb7..0000000000 --- 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 9bc3fad3c9..0000000000 --- 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 0236e20f16..0000000000 --- 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 de38d8ffc3..0000000000 --- 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 f504ad69f1..0000000000 --- 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 d205579f6c..0000000000 --- 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 32e58c9dba..0000000000 --- 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 9c91bc3c7b..0000000000 --- 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 49f2c950b4..0000000000 --- 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 47db151c81..0000000000 --- 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 4cfa3d0e3c..0000000000 --- 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 b2ac379dbe..0000000000 --- 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 52902abdc8..0000000000 --- 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 79547e4b83..0000000000 --- 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 080f9bd50f..0000000000 --- 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 c321d2d44f..0000000000 --- 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 f12daf1e3c..0000000000 --- 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 0e2e3bb043..0000000000 --- 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 cdd817ae2f..0000000000 --- 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 b9783477eb..0000000000 --- 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 7aa731898c..0000000000 --- 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 c0e2758273..0000000000 --- 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 e9c6a2f7c0..0000000000 --- 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 6a2b7b6951..0000000000 --- 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 493c203977..0000000000 --- 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 2bf1a70a62..0000000000 --- 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 5f533cd090..0000000000 --- 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 598603a08a..0000000000 --- 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 40dda971f5..0000000000 --- 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 3342de5324..0000000000 --- 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 0cbf14931b..0000000000 --- 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 089e54e4a0..0000000000 --- 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 9ead27b8d5..0000000000 --- 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 e61617283a..0000000000 --- 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 3a4828a2a8..0000000000 --- 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 2dcf4f9d8c..0000000000 --- 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 ee820c46a3..0000000000 --- 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 266a90a92c..0000000000 --- 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 978bf83a63..0000000000 --- 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 024331aa39..0000000000 --- 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 26743952de..0000000000 --- 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 920bd651e2..0000000000 --- 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 82302ad5bc..0000000000 --- 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 e7263a008f..0000000000 --- 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 d7eb8beeb3..0000000000 --- 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 3e68a15b7b..0000000000 --- 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 6b579fcc5d..0000000000 --- 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 05b61e1f76..0000000000 --- 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 1587a88205..0000000000 --- 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 4bde921696..0000000000 --- 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 fcefc5784f..0000000000 --- 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 edd9a30412..5308a44ea5 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 3e14c33255..0000000000 --- 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 28b07029eb..0000000000 --- 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 fbccbedb20..0000000000 --- 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 e8d57dddbe..0000000000 --- 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 4efb92d0c8..0000000000 --- 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 4307cee91f..0000000000 --- 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 9ab43dd9b8..0000000000 --- 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 10651a4160..0000000000 --- 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 4f8b03ef50..0000000000 --- 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 77aa5e07c1..0000000000 --- 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 9bd6822086..0000000000 --- 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 7c6c692d54..0000000000 --- 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 - Ağ - - - 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 9481e62f10..0000000000 --- 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 c3ef55d3d9..0000000000 --- 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 ed851a41c8..0000000000 --- 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 b3c482aafe..0000000000 --- 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 643d246012..e91820f242 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 e91820f242..0000000000 --- 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 5c02227ca5..940e7f9125 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 5891f85851..0000000000 --- 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 0055aae229..0000000000 --- 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 99e904c490..2ed461fdf6 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 1c89e2b885f0c24372e095360db8896663e32e78 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 23 Oct 2025 03:26:32 -0400 Subject: [PATCH 287/681] Speed Limit Assist: Disable for Tesla in release (#1418) * Speed Limit Assist: Disable for Tesla in release * add test * unused * use constant * eh * flip * universal it * check release state and align in tests * use this * eh * update changelog --- CHANGELOG.md | 2 + .../speed_limit/speed_limit_settings.cc | 7 ++- .../controls/lib/speed_limit/helpers.py | 9 +++- .../lib/speed_limit/speed_limit_assist.py | 4 +- .../tests/test_speed_limit_assist.py | 43 +++++++++++++++++-- 5 files changed, 56 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00be668571..66f5f432b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ sunnypilot Version 2025.001.000 (2025-10-25) * Intelligent longitudinal control adaptation * Speed Limit Assist (SLA) * Comprehensive speed limit integration featuring @pfeiferj's `mapd` for offline map limits downloads, a Speed Limit Resolver for sourcing data (from car, map, combined, etc), on-screen UI for Speed Limit Information/Warning, and Speed Limit Assist (SLA) to adjust cruise speed automatically. + * Currently disabled for Tesla with sunnypilot Longitudinal Control in release + * May return in future releases * Intelligent Cruise Button Management (ICBM) * System designed to manage the vehicle’s speed by sending cruise control button commands to the car’s ECU. * Smart Cruise Control Map & Vision (SCC-M / SCC-V) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc index 50d9ccbb41..8724e68651 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc @@ -105,6 +105,7 @@ SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent) } void SpeedLimitSettings::refresh() { + bool is_release = params.getBool("IsReleaseSpBranch"); bool is_metric_param = params.getBool("IsMetric"); SpeedLimitMode speed_limit_mode_param = static_cast(std::atoi(params.get("SpeedLimitMode").c_str())); SpeedLimitOffsetType offset_type_param = static_cast(std::atoi(params.get("SpeedLimitOffsetType").c_str())); @@ -126,9 +127,11 @@ void SpeedLimitSettings::refresh() { /* * Speed Limit Assist is available when: - * - has_longitudinal_control or has_icbm + * - has_longitudinal_control or has_icbm, and + * - is not a release branch or not a disallowed brand */ - sla_available = has_longitudinal_control || has_icbm; + bool sla_disallow_in_release = CP.getBrand() == "tesla" && is_release; + sla_available = (has_longitudinal_control || has_icbm) && !sla_disallow_in_release; if (!sla_available && speed_limit_mode_param == SpeedLimitMode::ASSIST) { params.put("SpeedLimitMode", std::to_string(static_cast(SpeedLimitMode::WARNING))); diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py b/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py index c209e98dce..04a85f57c4 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py @@ -22,15 +22,22 @@ def compare_cluster_target(v_cruise_cluster: float, target_set_speed: float, is_ return req_plus, req_minus -def set_speed_limit_assist_availability(CP: car.CarParams, CP_SP: custom.CarParamsSP, params: Params = None) -> None: +def set_speed_limit_assist_availability(CP: car.CarParams, CP_SP: custom.CarParamsSP, params: Params = None) -> bool: if params is None: params = Params() + is_release = params.get_bool("IsReleaseSpBranch") + disallow_in_release = CP.brand == "tesla" and is_release allowed = True + if disallow_in_release: + allowed = False + if not CP.openpilotLongitudinalControl and CP_SP.pcmCruiseSpeed: allowed = False if not allowed: if params.get("SpeedLimitMode", return_default=True) == SpeedLimitMode.assist: params.put("SpeedLimitMode", int(SpeedLimitMode.warning)) + + return allowed diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py index fcb5a98a2a..ff7be8a8be 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/speed_limit_assist.py @@ -10,13 +10,13 @@ from cereal import custom, car from openpilot.common.params import Params from openpilot.common.constants import CV from openpilot.common.realtime import DT_MDL -from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N +from openpilot.selfdrive.modeld.constants import ModelConstants +from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import PCM_LONG_REQUIRED_MAX_SET_SPEED, CONFIRM_SPEED_THRESHOLD from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.helpers import compare_cluster_target, set_speed_limit_assist_availability -from openpilot.selfdrive.modeld.constants import ModelConstants ButtonType = car.CarState.ButtonEvent.Type EventNameSP = custom.OnroadEventSP.EventName diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py index 8a3376a05c..5dc89cfd80 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py @@ -4,17 +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 cereal import custom +import pytest + +from cereal import custom from opendbc.car.car_helpers import interfaces +from opendbc.car.tesla.values import CAR as TESLA from opendbc.car.toyota.values import CAR as TOYOTA from openpilot.common.constants import CV from openpilot.common.params import Params from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.car.cruise import V_CRUISE_UNSET +from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfaces -from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import PCM_LONG_REQUIRED_MAX_SET_SPEED +from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.speed_limit_assist import SpeedLimitAssist, \ PRE_ACTIVE_GUARD_PERIOD, ACTIVE_STATES from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP @@ -30,14 +34,28 @@ SPEED_LIMITS = { 'freeway': 80 * CV.MPH_TO_MS, # 80 mph } +DEFAULT_CAR = TOYOTA.TOYOTA_RAV4_TSS2 + + +@pytest.fixture +def car_name(request): + return getattr(request, "param", DEFAULT_CAR) + + +@pytest.fixture(autouse=True) +def set_car_name_on_instance(request, car_name): + instance = getattr(request, "instance", None) + if instance: + instance.car_name = car_name + class TestSpeedLimitAssist: - def setup_method(self): + def setup_method(self, method): self.params = Params() self.reset_custom_params() self.events_sp = EventsSP() - CI = self._setup_platform(TOYOTA.TOYOTA_RAV4_TSS2) + CI = self._setup_platform(self.car_name) self.sla = SpeedLimitAssist(CI.CP, CI.CP_SP) self.sla.pre_active_timer = int(PRE_ACTIVE_GUARD_PERIOD[self.sla.pcm_op_long] / DT_MDL) self.pcm_long_max_set_speed = PCM_LONG_REQUIRED_MAX_SET_SPEED[self.sla.is_metric][1] # use 80 MPH for now @@ -51,10 +69,12 @@ class TestSpeedLimitAssist: CP = CarInterface.get_non_essential_params(car_name) CP_SP = CarInterface.get_non_essential_params_sp(CP, car_name) CI = CarInterface(CP, CP_SP) + CI.CP.openpilotLongitudinalControl = True # always assume it's openpilot longitudinal sunnypilot_interfaces.setup_interfaces(CI, self.params) return CI def reset_custom_params(self): + self.params.put("IsReleaseSpBranch", True) self.params.put("SpeedLimitMode", int(Mode.assist)) self.params.put_bool("IsMetric", False) self.params.put("SpeedLimitOffsetType", 0) @@ -84,6 +104,21 @@ class TestSpeedLimitAssist: assert not self.sla.is_active assert V_CRUISE_UNSET == self.sla.get_v_target_from_control() + @pytest.mark.parametrize("car_name", [TESLA.TESLA_MODEL_Y], indirect=True) + def test_disallowed_brands(self, car_name): + """ + Speed Limit Assist is disabled for the following brands and conditions: + - All Tesla and is a release branch + """ + assert not self.sla.enabled + + # stay disallowed even when the param may have changed from somewhere else + self.params.put("SpeedLimitMode", int(Mode.assist)) + for _ in range(int(PARAMS_UPDATE_PERIOD / DT_MDL)): + self.sla.update(True, False, SPEED_LIMITS['city'], 0, SPEED_LIMITS['highway'], SPEED_LIMITS['city'], + SPEED_LIMITS['city'], True, 0, self.events_sp) + assert not self.sla.enabled + def test_disabled(self): self.params.put("SpeedLimitMode", int(Mode.off)) for _ in range(int(10. / DT_MDL)): From 4861d150566bf58d1ce205ba20ce575596226178 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 23 Oct 2025 00:45:51 -0700 Subject: [PATCH 288/681] 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 a9b40f0d24..37bf4974df 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 5d47ffdb8ab2bbc6a842dd346fdeb8e9b13c737f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 23 Oct 2025 03:48:04 -0400 Subject: [PATCH 289/681] Speed Limit Assist: Disable for Rivian (#1421) * Speed Limit Assist: Disable for Tesla in release * add test * unused * use constant * eh * flip * universal it * check release state and align in tests * use this * eh * update changelog * Speed Limit Assist: Disable for Rivian * desc * changelog --- CHANGELOG.md | 2 +- .../longitudinal/speed_limit/speed_limit_settings.cc | 6 ++++-- sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py | 3 ++- .../lib/speed_limit/tests/test_speed_limit_assist.py | 6 ++++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66f5f432b1..5b79d67826 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ sunnypilot Version 2025.001.000 (2025-10-25) * Intelligent longitudinal control adaptation * Speed Limit Assist (SLA) * Comprehensive speed limit integration featuring @pfeiferj's `mapd` for offline map limits downloads, a Speed Limit Resolver for sourcing data (from car, map, combined, etc), on-screen UI for Speed Limit Information/Warning, and Speed Limit Assist (SLA) to adjust cruise speed automatically. - * Currently disabled for Tesla with sunnypilot Longitudinal Control in release + * Currently disabled for Tesla with sunnypilot Longitudinal Control in release and Rivian with sunnypilot Longitudinal Control in all branches * May return in future releases * Intelligent Cruise Button Management (ICBM) * System designed to manage the vehicle’s speed by sending cruise control button commands to the car’s ECU. diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc index 8724e68651..99c227d233 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.cc @@ -128,10 +128,12 @@ void SpeedLimitSettings::refresh() { /* * Speed Limit Assist is available when: * - has_longitudinal_control or has_icbm, and - * - is not a release branch or not a disallowed brand + * - is not a release branch or not a disallowed brand, and + * - is not always disallowed */ bool sla_disallow_in_release = CP.getBrand() == "tesla" && is_release; - sla_available = (has_longitudinal_control || has_icbm) && !sla_disallow_in_release; + bool sla_always_disallow = CP.getBrand() == "rivian"; + sla_available = (has_longitudinal_control || has_icbm) && !sla_disallow_in_release && !sla_always_disallow; if (!sla_available && speed_limit_mode_param == SpeedLimitMode::ASSIST) { params.put("SpeedLimitMode", std::to_string(static_cast(SpeedLimitMode::WARNING))); diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py b/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py index 04a85f57c4..4f388befc7 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/helpers.py @@ -28,9 +28,10 @@ def set_speed_limit_assist_availability(CP: car.CarParams, CP_SP: custom.CarPara is_release = params.get_bool("IsReleaseSpBranch") disallow_in_release = CP.brand == "tesla" and is_release + always_disallow = CP.brand == "rivian" allowed = True - if disallow_in_release: + if disallow_in_release or always_disallow: allowed = False if not CP.openpilotLongitudinalControl and CP_SP.pcmCruiseSpeed: diff --git a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py index 5dc89cfd80..aa6650adda 100644 --- a/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py +++ b/sunnypilot/selfdrive/controls/lib/speed_limit/tests/test_speed_limit_assist.py @@ -9,6 +9,7 @@ import pytest from cereal import custom from opendbc.car.car_helpers import interfaces +from opendbc.car.rivian.values import CAR as RIVIAN from opendbc.car.tesla.values import CAR as TESLA from opendbc.car.toyota.values import CAR as TOYOTA from openpilot.common.constants import CV @@ -104,11 +105,12 @@ class TestSpeedLimitAssist: assert not self.sla.is_active assert V_CRUISE_UNSET == self.sla.get_v_target_from_control() - @pytest.mark.parametrize("car_name", [TESLA.TESLA_MODEL_Y], indirect=True) + @pytest.mark.parametrize("car_name", [RIVIAN.RIVIAN_R1_GEN1, TESLA.TESLA_MODEL_Y], indirect=True) def test_disallowed_brands(self, car_name): """ Speed Limit Assist is disabled for the following brands and conditions: - - All Tesla and is a release branch + - All Tesla and is a release branch; + - All Rivian """ assert not self.sla.enabled From 485c7b2725081f9375b789c6b617458fd86d681d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 23 Oct 2025 00:54:31 -0700 Subject: [PATCH 290/681] 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 e6a0e880db..abb6940a54 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 5860c94ddd..608389fc07 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 cac0faf887..f32c27a9ef 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 66fecf358d..6fbb537aff 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 04dffbf3e6..59b9e6dfdb 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 4b0370403e..f883d4d485 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 0ae8869634..ca8aac1515 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 c07d449013..5a3e891b87 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 a14304bb8b..8a388d0da0 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 87420ed07a..f2e56f2882 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 022621f579..10191234a1 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 d09add97d8..16e4369476 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 1e9e2c0948..85cfb77401 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 e91820f242..779beb5ba2 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 291/681] 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 29fae5b61c..3080b55b95 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 292/681] 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 e705c55f7c..e9d7a1b09e 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 293/681] 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 558d322094..9ab60e6cb7 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 669f303211..ef20670781 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 294/681] 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 68e1fdc44a..4ed59f1942 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 4e3b1f1f6b2193fb090ec777c1ffdcd502635b93 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 24 Oct 2025 14:56:24 -0400 Subject: [PATCH 295/681] interface: add `is_release` flag to `get_params_sp` (#1426) * interface: add `is_release` flag to `get_params_sp` * split and rename * debump --- selfdrive/car/card.py | 3 ++- selfdrive/car/tests/test_models.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 4c44e43281..6758432b68 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -88,6 +88,7 @@ class Car: self.can_callbacks = can_comm_callbacks(self.can_sock, self.pm.sock['sendcan']) is_release = self.params.get_bool("IsReleaseBranch") + is_release_sp = self.params.get_bool("IsReleaseSpBranch") if CI is None: # wait for one pandaState and one CAN packet @@ -110,7 +111,7 @@ class Car: init_params_list_sp = sunnypilot_interfaces.initialize_params(self.params) self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, num_pandas, cached_params, - fixed_fingerprint, init_params_list_sp) + fixed_fingerprint, init_params_list_sp, is_release_sp) sunnypilot_interfaces.setup_interfaces(self.CI, self.params) self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP, self.CI.CP_SP) self.CP = self.CI.CP diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 4f61ad3c35..48da4b1ce2 100644 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -151,7 +151,7 @@ class TestCarModelBase(unittest.TestCase): cls.CarInterface = interfaces[cls.platform] cls.CP = cls.CarInterface.get_params(cls.platform, cls.fingerprint, car_fw, alpha_long, False, docs=False) - cls.CP_SP = cls.CarInterface.get_params_sp(cls.CP, cls.platform, cls.fingerprint, car_fw, alpha_long, docs=False) + cls.CP_SP = cls.CarInterface.get_params_sp(cls.CP, cls.platform, cls.fingerprint, car_fw, alpha_long, False, docs=False) assert cls.CP assert cls.CP_SP assert cls.CP.carFingerprint == cls.platform From 432c6050edee69bfd718a3da9e96c6cf2153d2bd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 15:28:08 -0400 Subject: [PATCH 296/681] [bot] Update Python packages (#1338) Update Python packages Co-authored-by: github-actions[bot] --- docs/CARS.md | 30 ++++++++++++++++-------------- opendbc_repo | 2 +- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 25f84b389e..695c2589e3 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. -# 337 Supported Cars +# 339 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -21,7 +21,10 @@ 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,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
||| @@ -236,20 +239,20 @@ A supported vehicle is one that just works when you install a comma device. All |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 2017-18|EyeSight Driver Assistance[8](#footnotes)|Stock|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|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 2015-18|EyeSight Driver Assistance[8](#footnotes)|Stock|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|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-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|Outback 2018-19|EyeSight Driver Assistance[8](#footnotes)|Stock|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|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-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)
||| +|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
||| @@ -308,7 +311,6 @@ 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 Toyota A connector
- 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
||| -|Toyota|Wildlander PHEV 2021|All|openpilot|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
||| diff --git a/opendbc_repo b/opendbc_repo index 0a2315efd1..952a061397 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 0a2315efd175176c318aa34d634d55d9dcd9350a +Subproject commit 952a0613977eee8cb5180f4562121ec2291c92b5 From 43e7d8717657a062ef0b6535cb43506668469341 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 24 Oct 2025 15:34:50 -0400 Subject: [PATCH 297/681] version: more release branches (#1427) --- system/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/version.py b/system/version.py index 268aaf11ea..a3f2e36da5 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_SP_BRANCHES = ['release-c3', 'release'] +RELEASE_SP_BRANCHES = ['release-c3', 'release', 'release-tizi', 'release-tici', 'release-tizi-staging', 'release-tici-staging'] TESTED_SP_BRANCHES = ['staging-c3', 'staging-c3-new', 'staging'] MASTER_SP_BRANCHES = ['master'] RELEASE_BRANCHES = ['release3-staging', 'release3', 'release-tici', 'nightly'] From ae9bd39883fab51a850f92191f45f05cdb9089fa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 17:04:27 -0400 Subject: [PATCH 298/681] [bot] Update Python packages (#1428) Update Python packages Co-authored-by: github-actions[bot] --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 952a061397..efe4ff137f 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 952a0613977eee8cb5180f4562121ec2291c92b5 +Subproject commit efe4ff137f839bbd416ac8ff8f5b78550a7d6eec From 3a45fff1b973ad291115dc5e2a97754b4664f1b6 Mon Sep 17 00:00:00 2001 From: MuskratGG <67509251+sirmuskrat@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:09:01 -0400 Subject: [PATCH 299/681] =?UTF-8?q?ui:=20openpilot=20Longitudinal=20Contro?= =?UTF-8?q?l=20=E2=86=92=20sunnypilot=20Longitudinal=20Control=20(#1422)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update developer_panel.cc Changed mentions of "openpilot Longitudinal Control" to "sunnypilot Longitudinal Control" to align with other UI elements pointing users towards enabling "sunnypilot Longitudinal Control" * Update warning message for longitudinal control * more * a bit more * slightly more --------- Co-authored-by: Jason Wen --- CHANGELOG.md | 2 +- selfdrive/ui/qt/offroad/developer_panel.cc | 8 ++++---- selfdrive/ui/qt/offroad/settings.cc | 2 +- .../sunnypilot/qt/offroad/settings/longitudinal_panel.cc | 6 +++--- .../qt/offroad/settings/vehicle/hyundai_settings.h | 4 ++-- .../ui/sunnypilot/qt/offroad/settings/visuals_panel.cc | 6 +++--- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b79d67826..a36cca7cda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ sunnypilot Version 2025.001.000 (2025-10-25) * Intelligent Cruise Button Management (ICBM) * System designed to manage the vehicle’s speed by sending cruise control button commands to the car’s ECU. * Smart Cruise Control Map & Vision (SCC-M / SCC-V) - * When using any form of long control (openpilot longitudinal or ICBM) it will control the speed at which you enter and perform a turn by leveraging map data (SCC-M) and/or by leveraging what the model sees about the curve ahead (SCC-V) + * When using any form of long control (sunnypilot longitudinal control or ICBM) it will control the speed at which you enter and perform a turn by leveraging map data (SCC-M) and/or by leveraging what the model sees about the curve ahead (SCC-V) * Vehicle Selector * If your vehicle isn’t fingerprinted automatically, you can still use the vehicle selector to get it working * sunnylink Integration diff --git a/selfdrive/ui/qt/offroad/developer_panel.cc b/selfdrive/ui/qt/offroad/developer_panel.cc index fbfb16294b..37c5d19272 100644 --- a/selfdrive/ui/qt/offroad/developer_panel.cc +++ b/selfdrive/ui/qt/offroad/developer_panel.cc @@ -32,11 +32,11 @@ DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) { experimentalLongitudinalToggle = new ParamControl( "AlphaLongitudinalEnabled", - tr("openpilot Longitudinal Control (Alpha)"), + tr("sunnypilot 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, sunnypilot 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.")), + .arg(tr("WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).")) + .arg(tr("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.")), "" ); experimentalLongitudinalToggle->setConfirmation(true, false); diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 6f594d939a..9a909ff2d2 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -188,7 +188,7 @@ void TogglesPanel::updateToggles() { 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."); + 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."); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc index 9c51ff8c67..ab357a2c16 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc @@ -132,9 +132,9 @@ void LongitudinalPanel::refresh(bool _offroad) { QString long_desc = icbm_unavaialble; if (has_longitudinal_control) { if (CP.getAlphaLongitudinalAvailable()) { - long_desc = icbm_unavaialble + " " + tr("Disable the openpilot Longitudinal Control (alpha) toggle to allow Intelligent Cruise Button Management."); + long_desc = icbm_unavaialble + " " + tr("Disable the sunnypilot Longitudinal Control (alpha) toggle to allow Intelligent Cruise Button Management."); } else { - long_desc = icbm_unavaialble + " " + tr("openpilot Longitudinal Control is the default longitudinal control for this platform."); + long_desc = icbm_unavaialble + " " + tr("sunnypilot Longitudinal Control is the default longitudinal control for this platform."); } } @@ -172,7 +172,7 @@ void LongitudinalPanel::refresh(bool _offroad) { } QString accEnabledDescription = tr("Enable custom Short & Long press increments for cruise speed increase/decrease."); - QString accNoLongDescription = tr("This feature can only be used with openpilot longitudinal control enabled."); + QString accNoLongDescription = tr("This feature can only be used with sunnypilot longitudinal control enabled."); QString accPcmCruiseDisabledDescription = tr("This feature is not supported on this platform due to vehicle limitations."); QString onroadOnlyDescription = tr("Start the vehicle to check vehicle compatibility."); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.h index c94d40cfde..b1ae95a01e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.h @@ -33,7 +33,7 @@ private: static QString toggleDisableMsg(bool _offroad, bool _has_longitudinal_control) { if (!_has_longitudinal_control) { - return tr("This feature can only be used with openpilot longitudinal control enabled."); + return tr("This feature can only be used with sunnypilot longitudinal control enabled."); } if (!_offroad) { @@ -57,7 +57,7 @@ private: } return QString("%1

%2
%3
%4
") - .arg(tr("Fine-tune your driving experience by adjusting acceleration smoothness with openpilot longitudinal control.")) + .arg(tr("Fine-tune your driving experience by adjusting acceleration smoothness with sunnypilot longitudinal control.")) .arg(off_str) .arg(dynamic_str) .arg(predictive_str); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc index 0e42f777cd..37e982a982 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc @@ -119,7 +119,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) { // Visuals: Display Metrics below Chevron std::vector chevron_info_settings_texts{tr("Off"), tr("Distance"), tr("Speed"), tr("Time"), tr("All")}; chevron_info_settings = new ButtonParamControlSP( - "ChevronInfo", tr("Display Metrics Below Chevron"), tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control)."), + "ChevronInfo", tr("Display Metrics Below Chevron"), tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with sunnypilot longitudinal control)."), "", chevron_info_settings_texts, 200); @@ -159,8 +159,8 @@ void VisualsPanel::refreshLongitudinalStatus() { } if (chevron_info_settings) { - QString chevronEnabledDescription = tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control)."); - QString chevronNoLongDescription = tr("This feature requires openpilot longitudinal control to be available."); + QString chevronEnabledDescription = tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with sunnypilot longitudinal control)."); + QString chevronNoLongDescription = tr("This feature requires sunnypilot longitudinal control to be available."); if (has_longitudinal_control) { chevron_info_settings->setDescription(chevronEnabledDescription); 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 300/681] 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 4ed59f1942..54a18d96d3 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 301/681] 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 54f742952e..37228e2d45 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 302/681] 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 4b8f087968..0419729a4d 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 303/681] 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 e5f234ed39..ed087c3f13 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 304/681] 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 5098b6a06c..98dd00ebaa 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 305/681] 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 661496a032..05e2f094ab 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 306/681] 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 03d519e601..bb9461864c 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 307/681] 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 62cff63b58..0000000000 --- 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 4406bf96b1..2296fa7088 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 9bd4ac9522..0000000000 --- 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 75395ead1f..0000000000 --- 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 a9977c2362..c7fe1984c5 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 b0d889f163..89c0601f06 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 6d494b6f75..2b54cfeebf 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 bf1c557128..eff70d2544 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 44c463b18c..44116a2329 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 6ed53b759c..50c4f5408f 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 1048acfe0a..1dce99273a 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 bc19572507..5b6234e1d5 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 b3558a15a8..9b3ccc8d29 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 819b17f113..59f5ac0b50 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 d60064fc91..c4216f5e76 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 5dbf597484..da8c8d81fb 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 8fa0e8eda3..9045a4381b 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 02f5fd1b95..ee9ee294bb 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 e80ba1399d..31c1e0ff11 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 308/681] 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 3f60fab73d..5ca020424c 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 309/681] 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 df9700a017..3eded659ac 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 ed8a7e7d17..8a0c066942 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 e9cd090446..a5f0f748c7 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 310/681] 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 1dce99273a..4d52d78417 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 311/681] 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 0c3bdea4c8..a842483857 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 3eb72ec104..f2ca4e7700 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 312/681] 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 5e7b02a1a3..945928f617 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 313/681] 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 05e2f094ab..fa0528444d 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 c1e15e5544b6c9cc32ee7d8488baa7d1c86d5ea6 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 25 Oct 2025 01:00:46 -0400 Subject: [PATCH 314/681] changelog: add new contributor entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a36cca7cda..2c899f18d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -134,6 +134,7 @@ sunnypilot Version 2025.001.000 (2025-10-25) * @michael-was-taken made their first contribution in "Reorder README tables: show -new branches first (#1191)" * @dzid26 made their first contribution in "params: Fix loading delay on startup (#1297)" * @HazZelnutz made their first contribution in "Visuals: Turn signals on screen when blinker is used (#1291)" + * @sirmuskrat made their first contribution in "ui: openpilot Longitudinal Control → sunnypilot Longitudinal Control (#1422)" * New Contributors (sunnypilot/opendbc) * @chrispypatt made their first contribution in "Toyota: SecOC Longitudinal Control (sunnypilot/opendbc#93)" * @Discountchubbs made their first contribution in "Hyundai: EPS FW For 2022 KIA_NIRO_EV SCC (sunnypilot/opendbc#118)" From 5e2f1427046c02669e0fbf766176f05e0873169f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 24 Oct 2025 22:02:25 -0700 Subject: [PATCH 315/681] 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 a842483857..f90627e10c 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 316/681] 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 54a18d96d3..f09344a9e2 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 317/681] 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 97e06bcc6b..fdd21067ec 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 318/681] 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 f09344a9e2..f7450cbd72 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 1a4ea669875ffef3bffa27f355bc764c761cbfc8 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 25 Oct 2025 22:45:17 -0400 Subject: [PATCH 319/681] version: bump to 2025.002.000 --- CHANGELOG.md | 3 +++ sunnypilot/common/version.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c899f18d4..5cb25994be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +sunnypilot Version 2025.002.000 (2025-xx-xx) +======================== + sunnypilot Version 2025.001.000 (2025-10-25) ======================== * 🛠️ Major rewrite diff --git a/sunnypilot/common/version.h b/sunnypilot/common/version.h index 5086f103f8..b52d984b4d 100644 --- a/sunnypilot/common/version.h +++ b/sunnypilot/common/version.h @@ -1 +1 @@ -#define SUNNYPILOT_VERSION "2025.001.000" +#define SUNNYPILOT_VERSION "2025.002.000" From eecb8e5c19e1862ad71ffa0e47812ac1e3864323 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 25 Oct 2025 19:55:26 -0700 Subject: [PATCH 320/681] models: bump model json to v8 (#1430) models: bump model json to v8 post release Co-authored-by: Jason Wen --- sunnypilot/models/fetcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sunnypilot/models/fetcher.py b/sunnypilot/models/fetcher.py index 3be6e0b46c..8c9c4eac71 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://docs.sunnypilot.ai/driving_models_v7.json" + MODEL_URL = "https://docs.sunnypilot.ai/driving_models_v8.json" def __init__(self, params: Params): self.params = params From b460d5804c9c6f9ee7e314f94ae9af0eab5b1883 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 25 Oct 2025 20:47:37 -0700 Subject: [PATCH 321/681] LiveLocationKalman: skip tests on unsupported msgq (#1407) * locationd llk: skip tests on unsupported msgq * Update sunnypilot/selfdrive/locationd/tests/test_locationd.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Jason Wen Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- sunnypilot/selfdrive/locationd/tests/test_locationd.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sunnypilot/selfdrive/locationd/tests/test_locationd.py b/sunnypilot/selfdrive/locationd/tests/test_locationd.py index c409f5b5a5..877bc821da 100644 --- a/sunnypilot/selfdrive/locationd/tests/test_locationd.py +++ b/sunnypilot/selfdrive/locationd/tests/test_locationd.py @@ -1,4 +1,5 @@ import pytest +import platform import json import random import time @@ -12,6 +13,10 @@ from openpilot.common.transformations.coordinates import ecef2geodetic from openpilot.system.manager.process_config import managed_processes +if platform.system() == 'Darwin': + pytest.skip("Skipping locationd test on macOS due to unsupported msgq.", allow_module_level=True) + + class TestLocationdProc: LLD_MSGS = ['gpsLocationExternal', 'cameraOdometry', 'carState', 'liveCalibration', 'accelerometer', 'gyroscope', 'magnetometer'] From f0dd0b5c8c32045f93f0a22523ed29b2f45f160b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 26 Oct 2025 00:47:07 -0700 Subject: [PATCH 322/681] 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 b83efedb60..8ed1c22a63 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 323/681] make ruff happy :) --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8cb877c351..f2d8c5cad0 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 324/681] 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 1f90a2a932..fffd4b4ed9 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 0000000000..f3633f3710 --- /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 37bf4974df..8a5f5793a5 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 f7450cbd72..4d0ae5fa49 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 325/681] 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 f3633f3710..06bd4e8f0e 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 326/681] 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 f13383fa55..72d3a4bafe 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 327/681] 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 4d0ae5fa49..39f27ea1a4 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 328/681] 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 4d52d78417..8ddc4da2f2 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 e4aada10a400552f63ba4cda11371841071bdf24 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sun, 26 Oct 2025 21:43:30 -0400 Subject: [PATCH 329/681] Bug: Model UI Crash Fix (#1431) Model UI Crash Fix --- selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc index c3f795e18c..4d89ea58b0 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc @@ -310,9 +310,8 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() { QList sortedModels; QSet modelFolders; QRegularExpression re("\\(([^)]*)\\)[^(]*$"); - const auto bundles = model_manager.getAvailableBundles(); - for (const auto &bundle : bundles) { + for (const auto &bundle : model_manager.getAvailableBundles()) { auto overrides = bundle.getOverrides(); QString folder; for (const auto &override : overrides) { @@ -392,7 +391,7 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() { showResetParamsDialog(); } else { // Find selected bundle and initiate download - for (const auto &bundle: bundles) { + for (const auto &bundle: model_manager.getAvailableBundles()) { if (QString::fromStdString(bundle.getRef()) == selectedBundleRef) { params.put("ModelManager_DownloadIndex", std::to_string(bundle.getIndex())); if (bundle.getGeneration() != model_manager.getActiveBundle().getGeneration()) { 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 330/681] 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 127a04e9e4..63d41d5b73 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 ff9655d155..18880e8a17 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 331/681] 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 d8bf91116d..acd24de163 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 9f1819afbf..19a30599bd 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 332/681] 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 fa0528444d..259551954d 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 333/681] 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 3080b55b95..ef3c9fe02d 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 334/681] 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 5594742cb3..b6305a03f5 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 e9d7a1b09e..592c9de971 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 335/681] 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 e4441edec3..3a1265e0fe 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 336/681] 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 30ad0f763a..fc72b4f9c6 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 dab01f9245..5d770fb91d 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 337/681] 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 e69de29bb2..b07e842f1a 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 259551954d..1f202141c3 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 8aaef1bcfb..7b3181d1ac 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 5d770fb91d..a947e5406b 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 85b42d1a7a..f960cf723e 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 49a0e56cdc..e98e4c1e1c 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 fcc5a14103..3c9406688f 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 39f27ea1a4..8ec417159f 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 5b9c51886d..df7a52d1c0 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 338/681] 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 ef3c9fe02d..f36ad1badb 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 339/681] 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 1cb1a47a48..92ff68fe76 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 5308a44ea5..3177814f9f 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: Tue, 28 Oct 2025 16:01:21 +0100 Subject: [PATCH 340/681] ci: integrate Discourse notifications and refactor notification logic (#1435) * ci: integrate Discourse notifications and refactor notification logic - Replaced Discord webhook notifications with Discourse topic updates. - Introduced reusable `post-to-discourse` composite action. - Added `test-discourse.yaml` workflow for debugging and verification. * ci: adjust notification dependencies and prepare_strategy reference - Updated `notify` step to depend on `prepare_strategy` instead of `build`. - Adjusted variable references to use `prepare_strategy` outputs. * Forcing debug * ci: update environment variable references and add commit information - Switched `PUBLIC_REPO_URL` source to environment variable for consistency. - Added commit SHA variables to enhance template generation logic. * more tweaks! * more tweaks! * bad bot lmao * Test? * i mean.... * i mean.... * getting there * testing the if * testing the if * ci: re-enable notify steps for prebuilt workflow - Uncommented `build` and `publish` dependencies. - Restored conditional logic to trigger only for relevant events. * ci: enhance Discourse action to support new topic creation - Added support for creating new topics with `category-id` and `title`. - Improved input validation and response handling for flexibility. * ci: improve conditions for prebuilt workflow notifications - Refined `if` clause to ensure branches in `DEV_FEEDBACK_NOTIFICATION_BRANCHES` are targeted. - Adjusted logic for accurate topic ID mapping in Discourse integration. * forgot to rename --- .../workflows/post-to-discourse/action.yml | 105 ++++++++++++++++++ .../workflows/sunnypilot-build-prebuilt.yaml | 63 +++++++---- .github/workflows/test-discourse.yaml.yml | 78 +++++++++++++ 3 files changed, 222 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/post-to-discourse/action.yml create mode 100644 .github/workflows/test-discourse.yaml.yml diff --git a/.github/workflows/post-to-discourse/action.yml b/.github/workflows/post-to-discourse/action.yml new file mode 100644 index 0000000000..55232ce0e1 --- /dev/null +++ b/.github/workflows/post-to-discourse/action.yml @@ -0,0 +1,105 @@ +name: 'Post to Discourse' +description: 'Posts a message to a Discourse topic (existing or new)' + +inputs: + discourse-url: + description: 'Discourse instance URL (e.g., https://discourse.example.com)' + required: true + api-key: + description: 'Discourse API key' + required: true + api-username: + description: 'Discourse API username' + required: true + topic-id: + description: 'Discourse topic ID to post to (use this OR category-id + title)' + required: false + category-id: + description: 'Category ID for new topic (required if topic-id not provided)' + required: false + title: + description: 'Title for new topic (required if topic-id not provided)' + required: false + message: + description: 'Message content (markdown supported)' + required: true + +outputs: + post-number: + description: 'The post number in the topic' + value: ${{ steps.post.outputs.post_number }} + post-url: + description: 'Direct URL to the post' + value: ${{ steps.post.outputs.post_url }} + topic-id: + description: 'The topic ID (useful when creating a new topic)' + value: ${{ steps.post.outputs.topic_id }} + +runs: + using: "composite" + steps: + - name: Post to Discourse + id: post + shell: bash + run: | + # Validate inputs + if [ -z "${{ inputs.topic-id }}" ] && ([ -z "${{ inputs.category-id }}" ] || [ -z "${{ inputs.title }}" ]); then + echo "❌ Error: Must provide either topic-id OR both category-id and title" + exit 1 + fi + + if [ -n "${{ inputs.topic-id }}" ] && ([ -n "${{ inputs.category-id }}" ] || [ -n "${{ inputs.title }}" ]); then + echo "⚠️ Warning: Both topic-id and category-id/title provided. Will post to existing topic." + fi + + # Determine if creating new topic or posting to existing + if [ -n "${{ inputs.topic-id }}" ]; then + echo "📝 Posting to existing topic ID: ${{ inputs.topic-id }}" + + # Create JSON payload for posting to existing topic + PAYLOAD=$(jq -n \ + --arg content '${{ inputs.message }}' \ + --arg topic_id "${{ inputs.topic-id }}" \ + '{topic_id: $topic_id, raw: $content}') + else + echo "✨ Creating new topic: ${{ inputs.title }}" + + # Create JSON payload for new topic + PAYLOAD=$(jq -n \ + --arg content '${{ inputs.message }}' \ + --arg title "${{ inputs.title }}" \ + --arg category "${{ inputs.category-id }}" \ + '{title: $title, category: ($category | tonumber), raw: $content}') + fi + + # Post to Discourse + RESPONSE=$(curl -s -w "\n%{http_code}" \ + -X POST "${{ inputs.discourse-url }}/posts.json" \ + -H "Content-Type: application/json" \ + -H "Api-Key: ${{ inputs.api-key }}" \ + -H "Api-Username: ${{ inputs.api-username }}" \ + -d "$PAYLOAD") + + HTTP_CODE=$(echo "$RESPONSE" | tail -n1) + BODY=$(echo "$RESPONSE" | sed '$d') + + if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then + echo "✅ Successfully posted to Discourse!" + + POST_NUMBER=$(echo "$BODY" | jq -r '.post_number // "unknown"') + TOPIC_ID=$(echo "$BODY" | jq -r '.topic_id // "${{ inputs.topic-id }}"') + POST_URL="${{ inputs.discourse-url }}/t/${TOPIC_ID}/${POST_NUMBER}" + + echo "post_number=${POST_NUMBER}" >> $GITHUB_OUTPUT + echo "post_url=${POST_URL}" >> $GITHUB_OUTPUT + echo "topic_id=${TOPIC_ID}" >> $GITHUB_OUTPUT + + echo "Topic ID: ${TOPIC_ID}" + echo "Post number: ${POST_NUMBER}" + echo "URL: ${POST_URL}" + else + echo "❌ Failed to post to Discourse" + echo "HTTP Code: ${HTTP_CODE}" + echo "Response: ${BODY}" + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/sunnypilot-build-prebuilt.yaml b/.github/workflows/sunnypilot-build-prebuilt.yaml index f7719709ae..12fb01cbd1 100644 --- a/.github/workflows/sunnypilot-build-prebuilt.yaml +++ b/.github/workflows/sunnypilot-build-prebuilt.yaml @@ -302,36 +302,51 @@ jobs: git push -f origin ${TAG} notify: - needs: [ build, publish ] + needs: + - prepare_strategy + - build + - publish runs-on: ubuntu-24.04 - if: ${{ (always() && !cancelled() && !failure()) && needs.publish.result == 'success' && !failure() && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }} + if: ${{ (always() && !cancelled() && !failure()) + && needs.publish.result == 'success' + && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) + && (fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES_V2)[github.head_ref || github.ref_name] != null) }} steps: - uses: actions/checkout@v4 - - name: Setup Alpine Linux environment - uses: jirutka/setup-alpine@v1.2.0 - with: - packages: 'jq gettext curl' - - name: Send Discord Notification - env: - DISCORD_WEBHOOK: ${{ contains(fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES), env.SOURCE_BRANCH) && secrets.DISCORD_DEV_FEEDBACK_CHANNEL_WEBHOOK || secrets.DISCORD_DEV_PRIVATE_CHANNEL_WEBHOOK }} + - name: Prepare notification message + id: message run: | - TEMPLATE='${{ vars.DISCORD_GENERAL_UPDATE_NOTICE }}' - export EXTRA_VERSION_IDENTIFIER="${{ needs.build.outputs.extra_version_identifier }}" - export VERSION="${{ needs.build.outputs.version }}" - export branch_name=${{ env.SOURCE_BRANCH }} - export new_branch=${{ needs.build.outputs.new_branch }} - export extra_version_identifier=${{ needs.build.outputs.extra_version_identifier || github.run_number}} - echo ${TEMPLATE} | envsubst | jq -c '.' | tee payload.json - curl -X POST -H "Content-Type: application/json" -d @payload.json $DISCORD_WEBHOOK + TEMPLATE='${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}' + export VERSION="${{ needs.prepare_strategy.outputs.version }}" + export branch_name="${{ env.SOURCE_BRANCH }}" + export new_branch="${{ needs.prepare_strategy.outputs.new_branch }}" + export commit_sha="${{ github.sha }}" + export commit_short_sha="${{ github.sha }}" + export commit_short_sha="${commit_short_sha:0:7}" + export extra_version_identifier="${{ needs.prepare_strategy.outputs.extra_version_identifier || github.run_number }}" + export PUBLIC_REPO_URL="${{ env.PUBLIC_REPO_URL }}" - echo "" - echo "---- ℹ️ To update the list of branches that notify to dev-feedback -----" - echo "" - echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/DEV_FEEDBACK_NOTIFICATION_BRANCHES" - echo "2. Current value: ${{ vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES }}" - echo "3. Update as needed (JSON array with no spaces)" - shell: alpine.sh {0} + MESSAGE=$(cat << 'EOF' | envsubst + ${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }} + EOF + ) + + { + echo 'content<> $GITHUB_OUTPUT + shell: bash + + - name: Post to Discourse + uses: ./.github/workflows/post-to-discourse + with: + discourse-url: ${{ vars.DISCOURSE_URL }} + api-key: ${{ secrets.DISCOURSE_API_KEY }} + api-username: "system" + topic-id: ${{ fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES_V2)[github.head_ref || github.ref_name].topic_id }} + message: ${{ steps.message.outputs.content }} manage-pr-labels: name: Remove prebuilt label diff --git a/.github/workflows/test-discourse.yaml.yml b/.github/workflows/test-discourse.yaml.yml new file mode 100644 index 0000000000..fadaec4eaa --- /dev/null +++ b/.github/workflows/test-discourse.yaml.yml @@ -0,0 +1,78 @@ +name: Debug Discourse Posting + +on: + push: + +jobs: + test-discourse-post: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Post test message to Discourse + uses: ./.github/workflows/post-to-discourse + with: + discourse-url: ${{ vars.DISCOURSE_URL }} + api-key: ${{ secrets.DISCOURSE_API_KEY }} + api-username: ${{ secrets.DISCOURSE_API_USERNAME }} + topic-id: ${{ vars.DISCOURSE_UPDATES_TOPIC_ID }} + message: | + ## 🧪 Test Post from GitHub Actions + + **This is a test post to verify Discourse integration** + + - **Workflow**: ${{ github.workflow }} + - **Run Number**: #${{ github.run_number }} + - **Branch**: `${{ github.ref_name }}` + - **Commit**: ${{ github.sha }} + - **Actor**: @${{ github.actor }} + - **Timestamp**: ${{ github.event.head_commit.timestamp }} + + --- + + ### Fake Build Info (for testing) + - **Version**: 0.9.8-test + - **Build**: #42 + - **Branch**: release-test + + [View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + + *This is an automated test message. Drive safe! 🚗💨* + + + - name: Create topic on Discourse + uses: ./.github/workflows/post-to-discourse + with: + discourse-url: ${{ vars.DISCOURSE_URL }} + api-key: ${{ secrets.DISCOURSE_API_KEY }} + api-username: ${{ secrets.DISCOURSE_API_USERNAME }} + #topic-id: ${{ vars.DISCOURSE_UPDATES_TOPIC_ID }} + category-id: 4 + title: "This is a test of a new topic instead of a reply" + message: | + ## 🧪 Test Post from GitHub Actions + + **This is a test post to verify Discourse integration** + + - **Workflow**: ${{ github.workflow }} + - **Run Number**: #${{ github.run_number }} + - **Branch**: `${{ github.ref_name }}` + - **Commit**: ${{ github.sha }} + - **Actor**: @${{ github.actor }} + - **Timestamp**: ${{ github.event.head_commit.timestamp }} + + --- + + ### Fake Build Info (for testing) + - **Version**: 0.9.8-test + - **Build**: #42 + - **Branch**: release-test + + [View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + + *This is an automated test message. Drive safe! 🚗💨* + - name: Display results + if: always() + run: | + echo "::notice::Discourse post test completed" + echo "Check your Discourse topic to verify the post appeared correctly" \ No newline at end of file From 55147d8a55e23293a95b353039bc9caeb866ec04 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 28 Oct 2025 18:58:04 +0100 Subject: [PATCH 341/681] ci: use environment variable for PR label in query (#1436) * ci: use environment variable for PR label in query - Replaced static `PR_LABEL` references with `${{ env.PR_LABEL }}` for consistency. - Ensures flexibility and reduces hardcoded values in the workflow. * does this work better? * fuck this * aight --- .github/workflows/sunnypilot-master-dev-prep.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sunnypilot-master-dev-prep.yaml b/.github/workflows/sunnypilot-master-dev-prep.yaml index 4aafd4a4a4..968540955f 100644 --- a/.github/workflows/sunnypilot-master-dev-prep.yaml +++ b/.github/workflows/sunnypilot-master-dev-prep.yaml @@ -3,7 +3,6 @@ name: Build dev env: DEFAULT_SOURCE_BRANCH: "master" DEFAULT_TARGET_BRANCH: "master-dev" - PR_LABEL: "dev" LFS_URL: 'https://gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git/info/lfs' LFS_PUSH_URL: 'ssh://git@gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git' @@ -119,7 +118,7 @@ jobs: # Use GitHub API to get PRs with specific label, ordered by creation date PR_LIST=$(gh api graphql -f query=' query($search_query:String!) { - search(query: $search_query, type:ISSUE, first:100) { + search(query: $search_query, type:ISSUE, first:40) { nodes { ... on PullRequest { number @@ -149,7 +148,7 @@ jobs: } } } - }' -F search_query="repo:${{ github.repository }} is:pr is:open label:${PR_LABEL},${PR_LABEL}-c3 draft:false sort:created-asc") + }' -F search_query="repo:${{ github.repository }} is:pr is:open label:${{ vars.PREBUILT_PR_LABEL }},${{ vars.PREBUILT_PR_LABEL }}-c3 draft:false sort:created-asc") PR_LIST=${PR_LIST//\'/} echo "PR_LIST=${PR_LIST}" >> $GITHUB_OUTPUT From ef9683ee7945986bc90260e07865247b84e19ca7 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 29 Oct 2025 04:09:49 +0800 Subject: [PATCH 342/681] 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 f2ca4e7700..4a1f03fb87 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 a947e5406b..b08b8ef28c 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 23fa8a6775..698ed3650b 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 8f6466ce46..3922c27aac 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 da8c8d81fb..f6d2853fa0 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 5dd5a69c69..2e1a8687e1 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 343/681] 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 ed087c3f13..20a5f77911 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 344/681] 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 06bd4e8f0e..a0d01af148 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 698ed3650b..8e45191bf6 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 345/681] 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 2fe2873a3d..ad65693722 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 707e2aedae4ddc872c609df1ae7c9e3f1e8738cd Mon Sep 17 00:00:00 2001 From: THERoenPR Date: Tue, 28 Oct 2025 22:13:39 -0400 Subject: [PATCH 346/681] controlsd: add `CP_SP` to `get_pid_accel_limits` (#1410) * Add CP_SP to get_pid_accel_limits() call in controlsd Match input parameters of CP_SP commit * bump * bump --------- Co-authored-by: roenthomas <43324106+roenthomas@users.noreply.github.com> Co-authored-by: Jason Wen --- opendbc_repo | 2 +- selfdrive/controls/controlsd.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index efe4ff137f..e0e1626820 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit efe4ff137f839bbd416ac8ff8f5b78550a7d6eec +Subproject commit e0e1626820d6a18a984ae69eebc012180699d41c diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 1694afee23..b381879a7a 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -132,7 +132,7 @@ class Controls(ControlsExt, ModelStateBase): self.LoC.reset() # accel PID loop - pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, CS.vCruise * CV.KPH_TO_MS) + pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, self.CP_SP, CS.vEgo, CS.vCruise * CV.KPH_TO_MS) actuators.accel = float(self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits)) # Steering PID loop and lateral MPC From 9f20eb8ce612fadfa7331e0d50e5619eaaf853b8 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Tue, 28 Oct 2025 19:15:43 -0700 Subject: [PATCH 347/681] 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 f6d2853fa0..0045b45417 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 348/681] 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 9bbe0916f7..3715ad2486 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 61ba1d38ee..d6a6ab51c9 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 434c0567d1..c51453f4c6 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 2487973507..b4dfa9be2e 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 349/681] 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 b9de86215e..0000000000 --- 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 7341e41029..0000000000 --- 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 350/681] 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 443fd1851c..1e3d80c2d5 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 cdd4301fcf..37d1e8f8d3 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 351/681] 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 1e3d80c2d5..f36aabc1d2 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 37d1e8f8d3..f313edbc62 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 352/681] 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 f6e8d781c8..badc162149 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 0419729a4d..cb7b0f9e91 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 8e45191bf6..bdd103c5c1 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 353/681] 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 bdd103c5c1..6f67139c5a 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 354/681] 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 7dbafb4be5..2f7498a07c 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 72216e2cc8..794c08f6a3 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 f833819143926bf987070632588a0dc271e84c55 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 31 Oct 2025 17:54:39 +0100 Subject: [PATCH 355/681] ci: update trigger for prebuilt (#1439) Updated workflow `if` conditions to use `vars.PREBUILT_PR_LABEL`. --- .github/workflows/sunnypilot-master-dev-prep.yaml | 4 ++-- .../ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sunnypilot-master-dev-prep.yaml b/.github/workflows/sunnypilot-master-dev-prep.yaml index 968540955f..e93e778aa6 100644 --- a/.github/workflows/sunnypilot-master-dev-prep.yaml +++ b/.github/workflows/sunnypilot-master-dev-prep.yaml @@ -42,7 +42,7 @@ jobs: if: ( (github.event_name == 'workflow_dispatch') || (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)) - || (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev')))) + || (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)))) ) steps: - uses: actions/checkout@v4 @@ -54,7 +54,7 @@ jobs: uses: ./.github/workflows/wait-for-action # Path to where you place the action if: ( (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)) - || (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev')))) + || (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 diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc index 4bbf894572..86b5b93e27 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc @@ -290,7 +290,7 @@ void SunnylinkPanel::updatePanel() { pairSponsorBtn->setEnabled(!is_onroad && is_sunnylink_enabled); pairSponsorBtn->setValue(is_paired ? tr("Paired") : tr("Not Paired")); - sunnylinkUploaderEnabledBtn->setEnabled(max_current_sponsor_rule.roleTier == SponsorTier::Guardian && is_sunnylink_enabled); + sunnylinkUploaderEnabledBtn->setEnabled(max_current_sponsor_rule.roleTier >= SponsorTier::Novice && is_sunnylink_enabled); if (!is_sunnylink_enabled) { sunnylinkEnabledBtn->setValue(""); From f60c2b6a835201ac06fa109ae98b14c36afabe18 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 1 Nov 2025 12:14:57 +0100 Subject: [PATCH 356/681] sunnylink: update uploader button logic to support novice tier and above (#1438) * sunnylink: update uploader button logic to support novice tier and above - Adjusted the enable condition to include SponsorTier::Novice and above. * sunnylink: improve uploader button visibility and accessibility logic - Made uploader button conditionally visible based on user tier and settings. - Clarified button label to specify testing purposes only. --- .../ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc index 86b5b93e27..924d1d3c09 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc @@ -90,7 +90,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) { QString sunnylinkUploaderDesc = tr("Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)"); sunnylinkUploaderEnabledBtn = new ParamControlSP( "EnableSunnylinkUploader", - tr("[Don't use] Enable sunnylink uploader"), + tr("Enable sunnylink uploader (just for testing infrastructure)"), sunnylinkUploaderDesc, "", nullptr, true); list->addItem(sunnylinkUploaderEnabledBtn); @@ -290,7 +290,10 @@ void SunnylinkPanel::updatePanel() { pairSponsorBtn->setEnabled(!is_onroad && is_sunnylink_enabled); pairSponsorBtn->setValue(is_paired ? tr("Paired") : tr("Not Paired")); - sunnylinkUploaderEnabledBtn->setEnabled(max_current_sponsor_rule.roleTier >= SponsorTier::Novice && is_sunnylink_enabled); + bool can_do_uploads = max_current_sponsor_rule.roleTier >= SponsorTier::Novice && is_sunnylink_enabled; + sunnylinkUploaderEnabledBtn->setVisible(can_do_uploads); + sunnylinkUploaderEnabledBtn->setEnabled(can_do_uploads); + if (!is_sunnylink_enabled) { sunnylinkEnabledBtn->setValue(""); From 682d738ffac3432549643a9889e1424192f88291 Mon Sep 17 00:00:00 2001 From: Amy Jeanes Date: Sun, 2 Nov 2025 02:47:30 +0000 Subject: [PATCH 357/681] Tesla: Coop Steering (#1283) * Tesla: Coop Steering * bump * bump * sync with opendbc/master * resolve comment * add oscillation warning and add confirmation * styling desc * beta --------- Co-authored-by: Jason Wen --- common/params_keys.h | 1 + opendbc_repo | 2 +- .../settings/vehicle/tesla_settings.cc | 34 +++++++++++++++++++ .../offroad/settings/vehicle/tesla_settings.h | 2 +- sunnypilot/selfdrive/car/interfaces.py | 5 +++ 5 files changed, 42 insertions(+), 2 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index d8485be157..df53261eb2 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -208,6 +208,7 @@ inline static std::unordered_map keys = { {"HyundaiLongitudinalTuning", {PERSISTENT | BACKUP, INT, "0"}}, {"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}}, {"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}}, + {"TeslaCoopSteering", {PERSISTENT | BACKUP, BOOL, "0"}}, {"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}}, {"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}}, diff --git a/opendbc_repo b/opendbc_repo index e0e1626820..c7126f8ba6 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit e0e1626820d6a18a984ae69eebc012180699d41c +Subproject commit c7126f8ba620eb06fe345013081e97503331bbe1 diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.cc index 50ab023021..2fffdb8ad4 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.cc @@ -8,7 +8,41 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.h" TeslaSettings::TeslaSettings(QWidget *parent) : BrandSettingsInterface(parent) { + constexpr int coopSteeringMinKmh = 23; // minimum speed for cooperative steering (enforced by Tesla firmware) + constexpr int oemSteeringMinKmh = 48; // minimum speed for OEM lane departure avoidance (enforced by Tesla firmware) + bool is_metric = params.getBool("IsMetric"); + QString unit = is_metric ? "km/h" : "mph"; + int display_value_coop; + int display_value_oem; + if (is_metric) { + display_value_coop = coopSteeringMinKmh; + display_value_oem = oemSteeringMinKmh; + } else { + display_value_coop = static_cast(std::round(coopSteeringMinKmh * KM_TO_MILE)); + display_value_oem = static_cast(std::round(oemSteeringMinKmh * KM_TO_MILE)); + } + const QString coop_desc = QString("%1

" + "%2
" + "%3
") + .arg(tr("Warning: May experience steering oscillations below %5 %6 during turns, recommend disabling this feature if you experience these.")) + .arg(tr("Allows the driver to provide limited steering input while openpilot is engaged.")) + .arg(tr("Only works above %4 %6.")) + .arg(display_value_coop) + .arg(display_value_oem) + .arg(unit); + + coopSteeringToggle = new ParamControlSP( + "TeslaCoopSteering", + tr("Cooperative Steering (Beta)"), + coop_desc, + "", + this + ); + list->addItem(coopSteeringToggle); + coopSteeringToggle->showDescription(); + coopSteeringToggle->setConfirmation(true, false); } void TeslaSettings::updateSettings() { + coopSteeringToggle->setEnabled(offroad); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.h index 37f2936cdf..a1294513ee 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.h @@ -22,5 +22,5 @@ public: void updateSettings() override; private: - bool offroad = false; + ParamControlSP *coopSteeringToggle = nullptr; }; diff --git a/sunnypilot/selfdrive/car/interfaces.py b/sunnypilot/selfdrive/car/interfaces.py index 7d63d3c5ba..59cfeabd6e 100644 --- a/sunnypilot/selfdrive/car/interfaces.py +++ b/sunnypilot/selfdrive/car/interfaces.py @@ -115,4 +115,9 @@ def initialize_params(params) -> list[dict[str, Any]]: "SubaruStopAndGoManualParkingBrake", ]) + # tesla + keys.extend([ + "TeslaCoopSteering", + ]) + return [{k: params.get(k, return_default=True)} for k in keys] From b81d5bca3c4418fad601f103f070f309077c16ed Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 2 Nov 2025 06:50:41 +0100 Subject: [PATCH 358/681] ui: update discord references and add forum widget (#1440) * sunnylink: introduce community popup with QR code embedding - Added `SunnylinkCommunityPopup` widget to promote the sunnypilot Community Forum. - Integrated a QR code generator and display for quick access. - Updated `WiFiPromptWidget` to include a "Learn More" button triggering the community popup. * sunnylink: adjust community popup styling for better layout - Reduced font size of description text slightly for consistency. - Decreased QR code dimensions to improve visual balance. * Making more space out of thin air * sunnylink: update community references to use forum links - Replaced Discord links with Community Forum URLs for better alignment. - Improved clarity in sponsorship instructions. --- selfdrive/ui/qt/widgets/wifi.cc | 11 +- selfdrive/ui/qt/widgets/wifi.h | 5 + selfdrive/ui/sunnypilot/SConscript | 1 + .../settings/sunnylink/community_widget.cc | 139 ++++++++++++++++++ .../settings/sunnylink/community_widget.h | 40 +++++ .../settings/sunnylink/sponsor_widget.cc | 4 +- 6 files changed, 193 insertions(+), 7 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.cc create mode 100644 selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.h diff --git a/selfdrive/ui/qt/widgets/wifi.cc b/selfdrive/ui/qt/widgets/wifi.cc index d7eb8beeb3..8958e48c93 100644 --- a/selfdrive/ui/qt/widgets/wifi.cc +++ b/selfdrive/ui/qt/widgets/wifi.cc @@ -11,17 +11,18 @@ WiFiPromptWidget::WiFiPromptWidget(QWidget *parent) : QFrame(parent) { 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;"); + community_popup = new SunnylinkCommunityPopup(this); + QLabel *title = new QLabel(tr("sunnypilot Community")); + title->setStyleSheet("font-size: 56px; font-weight: 500;"); main_layout->addWidget(title); - QLabel *desc = new QLabel(tr("Maximize your training data uploads to improve openpilot's driving models.")); + QLabel *desc = new QLabel(tr("Need help or have ideas?
Join our community now!")); 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"); }); + QPushButton *settings_btn = new QPushButton(tr("Learn More")); + connect(settings_btn, &QPushButton::clicked, [=]() { community_popup->exec(); }); settings_btn->setStyleSheet(R"( QPushButton { font-size: 48px; diff --git a/selfdrive/ui/qt/widgets/wifi.h b/selfdrive/ui/qt/widgets/wifi.h index 3e68a15b7b..8ef9dca91a 100644 --- a/selfdrive/ui/qt/widgets/wifi.h +++ b/selfdrive/ui/qt/widgets/wifi.h @@ -3,12 +3,17 @@ #include #include +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.h" + class WiFiPromptWidget : public QFrame { Q_OBJECT public: explicit WiFiPromptWidget(QWidget* parent = 0); +private: + SunnylinkCommunityPopup *community_popup; + signals: void openSettings(int index = 0, const QString ¶m = ""); }; diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index be92c14261..449093886a 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -35,6 +35,7 @@ qt_src = [ "sunnypilot/qt/offroad/settings/software_panel.cc", "sunnypilot/qt/offroad/settings/sunnylink_panel.cc", "sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.cc", + "sunnypilot/qt/offroad/settings/sunnylink/community_widget.cc", "sunnypilot/qt/offroad/settings/trips_panel.cc", "sunnypilot/qt/offroad/settings/vehicle_panel.cc", "sunnypilot/qt/offroad/settings/visuals_panel.cc", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.cc new file mode 100644 index 0000000000..e29e89ee98 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.cc @@ -0,0 +1,139 @@ +/** + * Copyright (c) 2025-, sunnypilot 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/sunnypilot/qt/offroad/settings/sunnylink/community_widget.h" + +#include "selfdrive/ui/sunnypilot/ui.h" +#include "selfdrive/ui/sunnypilot/qt/util.h" + +using qrcodegen::QrCode; + +// --- SunnylinkCommunityQRWidget --- + +SunnylinkCommunityQRWidget::SunnylinkCommunityQRWidget(QWidget* parent) + : QWidget(parent) {} + +void SunnylinkCommunityQRWidget::showEvent(QShowEvent *event) { + updateQrCode(SUNNYLINK_COMMUNITY_URL); + update(); +} + +void SunnylinkCommunityQRWidget::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); + } + } + + int final_sz = ((width() / sz) - 1) * sz; + img = QPixmap::fromImage(im.scaled(final_sz, final_sz, Qt::KeepAspectRatio), Qt::MonoOnly); +} + +void SunnylinkCommunityQRWidget::paintEvent(QPaintEvent *e) { + QPainter p(this); + p.fillRect(rect(), Qt::white); + + if (!img.isNull()) { + QSize s = (size() - img.size()) / 2; + p.drawPixmap(s.width(), s.height(), img); + } +} + +// --- SunnylinkCommunityPopup --- + +QStringList SunnylinkCommunityPopup::getInstructions() { + QStringList instructions; + instructions << tr("Scan the QR code and join us!"); + return instructions; +} + +SunnylinkCommunityPopup::SunnylinkCommunityPopup(QWidget* parent) + : DialogBase(parent) { + auto *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + // Solarized Light base3 background + setStyleSheet("SunnylinkCommunityPopup { background-color: #FDF6E3; }"); + + // Header spanning full width + auto headerWidget = new QWidget(this); + auto headerLayout = new QHBoxLayout(headerWidget); + headerLayout->setContentsMargins(85, 50, 85, 30); + headerLayout->setSpacing(30); + + auto close = new QPushButton(QIcon(":/icons/close.svg"), "", this); + close->setIconSize(QSize(80, 80)); + close->setStyleSheet("border: none;"); + connect(close, &QPushButton::clicked, this, &QDialog::reject); + headerLayout->addWidget(close, 0, Qt::AlignLeft | Qt::AlignVCenter); + + const auto title = new QLabel(tr("Join the sunnypilot Community Forum"), this); + // Solarized base02 for text + title->setStyleSheet("font-size: 65px; color: #073642;"); + title->setWordWrap(false); + title->setAlignment(Qt::AlignCenter); + headerLayout->addWidget(title, 1); + + // Spacer to balance the close button on the right + auto spacer = new QWidget(this); + spacer->setFixedSize(80, 80); + headerLayout->addWidget(spacer, 0); + + mainLayout->addWidget(headerWidget); + + // Two-column content layout + auto contentLayout = new QHBoxLayout(); + contentLayout->setContentsMargins(0, 0, 0, 0); + contentLayout->setSpacing(0); + mainLayout->addLayout(contentLayout, 66); + + // Left side: description + auto leftLayout = new QVBoxLayout(); + leftLayout->setContentsMargins(85, 40, 50, 70); + leftLayout->setSpacing(35); + contentLayout->addLayout(leftLayout, 40); + + // Hype / intro paragraph + const auto desc = new QLabel(tr( + "We're excited to announce our sunnypilot Community Forum

" + "Over the years, Discord just hasn't scaled well for our growing community.
" + "It's noisy, unsearchable, and great discussions disappear too easily.
" + "Our new community forum aims to fix that by making it easier to find answers, share ideas, track feedback, report bugs, help newcomers and more!

" + "Here's what's waiting for you:
" + "• Fully indexable and discoverable through search engines 🔎
" + "• AI-powered🤖 topic and chat summaries, spam detection, and more
" + "• A trust-level system✅ that rewards meaningful contributions
" + "• Designed to work on your own time.🧘

" + "Scan the QR code on the right and join the discussion!" + ), this); + // Solarized base01 for body text + desc->setStyleSheet("font-size: 40px; color: #586E75;"); + desc->setWordWrap(true); + leftLayout->addWidget(desc); + + leftLayout->addStretch(); + + // Right side: QR code and instructions + auto rightLayout = new QVBoxLayout(); + rightLayout->setContentsMargins(50, 40, 85, 70); + rightLayout->setSpacing(40); + contentLayout->addLayout(rightLayout, 1); + + // QR code (smaller, fixed size) + auto *qr = new SunnylinkCommunityQRWidget(this); + qr->setFixedSize(500, 500); + rightLayout->addStretch(); + rightLayout->addWidget(qr, 0, Qt::AlignCenter); + rightLayout->addStretch(); +} \ No newline at end of file diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.h new file mode 100644 index 0000000000..613375d12c --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.h @@ -0,0 +1,40 @@ +/** +* Copyright (c) 2025-, sunnypilot 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 +#include + +#include "common/util.h" +#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h" + +const QString SUNNYLINK_COMMUNITY_URL = "https://community.sunnypilot.ai/sp-qr"; + +class SunnylinkCommunityQRWidget : public QWidget { + Q_OBJECT + +public: + explicit SunnylinkCommunityQRWidget(QWidget* parent = nullptr); + void paintEvent(QPaintEvent*) override; + +private: + QPixmap img; + void updateQrCode(const QString &text); + void showEvent(QShowEvent *event) override; +}; + +// Popup widget +class SunnylinkCommunityPopup : public DialogBase { + Q_OBJECT + +public: + explicit SunnylinkCommunityPopup(QWidget* parent = nullptr); + +private: + static QStringList getInstructions(); +}; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.cc index 7b5ce48238..a014eddacb 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.cc @@ -79,11 +79,11 @@ QStringList SunnylinkSponsorPopup::getInstructions(bool sponsor_pair) { 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 Discord at https://discord.gg/sunnypilot"); + << tr("If sponsorship status was not updated, please contact a moderator on our 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 on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status"); + << tr("Join our Community Forum at https://community.sunnypilot.ai and reach out to a moderator if you have issues"); } return instructions; } From c7b115b68e9a7fbee55a7fbd01ebc8e28d4588b2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 2 Nov 2025 04:30:08 -0800 Subject: [PATCH 359/681] 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 ea6fa63365..544ba5b870 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 360/681] 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 d51975087a..745d37b468 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 18af4d6ad6db657bcdbe892614851d24a33f9dcc Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 2 Nov 2025 20:26:17 +0100 Subject: [PATCH 361/681] ui: Fix spacing in sunnylink panel (#1450) Fix spacing --- selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc index 924d1d3c09..1b170bb0c2 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc @@ -90,7 +90,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) { QString sunnylinkUploaderDesc = tr("Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)"); sunnylinkUploaderEnabledBtn = new ParamControlSP( "EnableSunnylinkUploader", - tr("Enable sunnylink uploader (just for testing infrastructure)"), + tr("Enable sunnylink uploader (infrastructure test)"), sunnylinkUploaderDesc, "", nullptr, true); list->addItem(sunnylinkUploaderEnabledBtn); From 5ea5f6f267301d5bc83bdc17004807740608abd0 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 2 Nov 2025 21:48:33 -0800 Subject: [PATCH 362/681] 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 6f67139c5a..d1d74d3ac6 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 071147baafae09c0a3f8305187596f8af16f800b Mon Sep 17 00:00:00 2001 From: Matt Purnell <65473602+mpurnell1@users.noreply.github.com> Date: Sun, 2 Nov 2025 23:52:17 -0600 Subject: [PATCH 363/681] docs: Update README installation branches and discord links (#1453) * Use sunnypilot CARS.md, update number of supported cars, add comma * Update device reference * Update discord links to forum links * Update references to -c3-new branches and release * Update broken link to branches table * Update README.md --------- Co-authored-by: DevTekVE --- README.md | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 7e686cef5c..598e1273a7 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,9 @@ ## 🌞 What is sunnypilot? [sunnypilot](https://github.com/sunnyhaibin/sunnypilot) is a fork of comma.ai's openpilot, an open source driver assistance system. sunnypilot offers the user a unique driving experience for over 300+ supported car makes and models with modified behaviors of driving assist engagements. sunnypilot complies with comma.ai's safety rules as accurately as possible. -## 💭 Join our Discord -Join the official sunnypilot Discord server to stay up to date with all the latest features and be a part of shaping the future of sunnypilot! -* https://discord.gg/sunnypilot - - ![](https://dcbadge.vercel.app/api/server/wRW3meAgtx?style=flat) ![Discord Shield](https://discordapp.com/api/guilds/880416502577266699/widget.png?style=shield) +## 💭 Join our Community Forum +Join the official sunnypilot community forum to stay up to date with all the latest features and be a part of shaping the future of sunnypilot! +* https://community.sunnypilot.ai/ ## Documentation https://docs.sunnypilot.ai/ is your one stop shop for everything from features to installation to FAQ about the sunnypilot @@ -16,13 +14,13 @@ https://docs.sunnypilot.ai/ is your one stop shop for everything from features t * 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 300+ supported cars](https://github.com/commaai/openpilot/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. +* 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). ## 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-c3-new` branch. +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] @@ -31,28 +29,28 @@ Please refer to [Recommended Branches](#-recommended-branches) to find your pref * 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-c3-new.sunnypilot.ai```. + 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, go to `Settings` ▶️ `Software`. + 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-c3-new` + 4. Scroll to select the desired branch per Recommended Branches (see below). Example: `staging` - -| Branch | Installation URL | -|:----------------:|:---------------------------------------------:| -| `staging-c3-new` | `https://staging-c3-new.sunnypilot.ai` | -| `dev-c3-new` | `https://dev-c3-new.sunnypilot.ai` | -| `custom-branch` | `https://install.sunnypilot.ai/{branch_name}` | -| `release-c3-new` | **Not yet available**. | +### 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-c3-new'. +> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging'. > [!NOTE] -> Do you require further assistance with software installation? Join the [sunnypilot Discord server](https://discord.sunnypilot.com) and message us in the `#installation-help` channel. +> 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.
From 9bf904e8a644a6d642c387b05f6a66fcc305e802 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 4 Nov 2025 00:55:05 +0800 Subject: [PATCH 364/681] 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 d1d74d3ac6..97aaaa27b4 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 365/681] 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 f36aabc1d2..1e3d80c2d5 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 f313edbc62..37d1e8f8d3 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 366/681] 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 1e3d80c2d5..443fd1851c 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 37d1e8f8d3..cdd4301fcf 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 367/681] 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 97aaaa27b4..bca836334f 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 368/681] 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 20a5f77911..cfb8ab58a3 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 369/681] 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 b0e1035687..4ec92f507a 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 370/681] 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 4259208107..59d6a5947b 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 371/681] 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 59d6a5947b..7d76802565 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 372/681] 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 f19a6fbfdb..a843010d56 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 373/681] 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 f90627e10c..69d920c1a0 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 4a1f03fb87..222a25b87b 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 374/681] 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 36e65ad91c..ff2622e963 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 375/681] 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 b6305a03f5..4cf2ccebc8 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 376/681] 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 bca836334f..f9d7b0c9c1 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 377/681] 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 005655b21d..656c05ed59 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 80c087188d..81aee1ebf9 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 4b5de0eddbf34080106e1ee1e8bf424aa1ce82ca Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Tue, 4 Nov 2025 22:53:31 +0100 Subject: [PATCH 378/681] stats: sunnylink integration (#1454) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * sunnylink: add statsd process and related telemetry logging infrastructure - Introduced `statsd_sp` process for handling Sunnylink-specific stats. - Enhanced metrics logging with improved directory structure and data handling. * sunnylink: re-enable and refine stat_handler for telemetry processing - Reactivated `stat_handler` thread with improved path handling. - Made `stat_handler` more flexible by allowing directory injection. * statsd: fix formatting issue in telemetry string generation - Corrected missing comma between `sunnylink_dongle_id` and `comma_dongle_id`. * update statsd_sp process configuration for enhanced readiness logic - Modified `statsd_sp` initialization to include `always_run` alongside `sunnylink_ready_shim`. - Ensures robust process activation conditions. * refactor(statsd): enhance and unify StatLogSP implementation - Replaced custom `StatLogSP` in sunnylink with centralized implementation from `system.statsd`. - Ensures consistent logic for StatLogSP handling across modules. * fix * refactor(statsd): add intercept parameter to StatLogSP for configurable logging - Introduced optional `intercept` parameter to `StatLogSP` to manage `comma_statlog` initialization. - Updated usage in `sunnylink` to disable interception where unnecessary. * Dont complain * feat(statsd): add raw metric type and SunnyPilot-specific stats collection - Introduced `METRIC_TYPE.RAW` for base64-encoded raw data metrics. - Added `sp_stats` thread to export SunnyPilot params as raw metrics. - Enhanced telemetry handling with decoding and serialization updates. * refactor(statsd): improve `sp_stats` error handling and param processing - Enhanced exception handling for `params.get` to prevent crashes. - Added support for nested dict values to be included in stats. * refactor(statsd): adjust imports and minor code formatting updates - Updated `Ratekeeper` import path for consistency with the `openpilot` module structure. - Fixed minor formatting for improved readability. * refactor(statsd): update typings and remove unused NoReturn annotation - Removed unnecessary `NoReturn` typing for `stats_main` to simplify function definition. - Adjusted `get_influxdb_line_raw` to refine typing for `value` parameter. * cleanup * init * init * slightly more * staticmethod * handle them all * get them models * log with route * more * car * Revert "car" This reverts commit fe1c90cf4d422864388e4a73d5cd25c9e585a9b3. * handle capnp * Revert "handle capnp" This reverts commit c5aea6880333863838a7c258541fb7a2812f2e2b. * 1 more time * Revert "1 more time" This reverts commit a364474fa580d9ef9760a8896f9db76d526abead. * Cleaning to expose wider * feat(interfaces, statsd): log car params to stats system - Added `STATSLOGSP` import and logging to capture `carFingerprint` in metrics. - Improved error handling in `get_influxdb_line_raw` for robust metric generation. * refactor(interfaces): streamline car params logging to stats - Simplified logging by directly converting `CP` to a dictionary. - Removed legacy stats aggregation for clarity. * feat(sunnylink): enable compression for stats in SunnyLink - Added optional compression for stats payload to support large data. - Updated `stat_handler` to handle compression and base64 encoding. * fix(statsd): filter complex types in `get_influxdb_line_raw` - Skips unsupported types (dict, list, bytes) to prevent formatting errors. - Simplifies type annotation for `value` parameter. * fix(statsd): use `json.dumps` for string conversion in `get_influxdb_line_raw` - Ensures proper handling of special characters in values. - Prevents potential formatting issues with raw `str()` conversion. * refactor(interfaces, statsd): update parameter keys for stats logging - Renamed logged keys for better clarity (`sunnypilot_params` → `sunnypilot.car_params`, `device_params`). - Ensures consistency across data logs. * bet --------- Co-authored-by: Jason Wen --- sunnypilot/selfdrive/car/interfaces.py | 5 + sunnypilot/sunnylink/athena/sunnylinkd.py | 5 +- sunnypilot/sunnylink/statsd.py | 278 ++++++++++++++++++++++ system/athena/athenad.py | 24 +- system/hardware/hw.py | 7 + system/manager/process_config.py | 1 + system/statsd.py | 59 ++++- 7 files changed, 368 insertions(+), 11 deletions(-) create mode 100755 sunnypilot/sunnylink/statsd.py diff --git a/sunnypilot/selfdrive/car/interfaces.py b/sunnypilot/selfdrive/car/interfaces.py index 59cfeabd6e..2cfbd1eec8 100644 --- a/sunnypilot/selfdrive/car/interfaces.py +++ b/sunnypilot/selfdrive/car/interfaces.py @@ -15,6 +15,8 @@ from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.helpers import set_ import openpilot.system.sentry as sentry +from sunnypilot.sunnylink.statsd import STATSLOGSP + def log_fingerprint(CP: structs.CarParams) -> None: if CP.carFingerprint == "MOCK": @@ -100,6 +102,9 @@ 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()) + # STATSLOGSP.raw('sunnypilot_params.car_params_sp', CP_SP.to_dict()) # https://github.com/sunnypilot/opendbc/pull/361 + def initialize_params(params) -> list[dict[str, Any]]: keys: list = [] diff --git a/sunnypilot/sunnylink/athena/sunnylinkd.py b/sunnypilot/sunnylink/athena/sunnylinkd.py index d1b38d656f..4c431c5d34 100755 --- a/sunnypilot/sunnylink/athena/sunnylinkd.py +++ b/sunnypilot/sunnylink/athena/sunnylinkd.py @@ -16,8 +16,9 @@ from functools import partial from openpilot.common.params import Params from openpilot.common.realtime import set_core_affinity from openpilot.common.swaglog import cloudlog +from openpilot.system.hardware.hw import Paths from openpilot.system.athena.athenad import ws_send, jsonrpc_handler, \ - recv_queue, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler, start_local_proxy_shim, upload_handler + recv_queue, UploadQueueCache, upload_queue, cur_upload_items, backoff, ws_manage, log_handler, start_local_proxy_shim, upload_handler, stat_handler from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutException, create_connection, WebSocketConnectionClosedException) @@ -51,7 +52,7 @@ def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: 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=stat_handler, args=(end_event,), name='stat_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}') for x in range(HANDLER_THREADS) diff --git a/sunnypilot/sunnylink/statsd.py b/sunnypilot/sunnylink/statsd.py new file mode 100755 index 0000000000..eefff63516 --- /dev/null +++ b/sunnypilot/sunnylink/statsd.py @@ -0,0 +1,278 @@ +#!/usr/bin/env python3 +import base64 +import json +import os +import threading +import traceback + +import zmq +import time +import uuid +from pathlib import Path +from collections import defaultdict +from datetime import datetime, UTC + +from openpilot.common.params import Params +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.system.version import get_build_metadata +from openpilot.system.loggerd.config import STATS_DIR_FILE_LIMIT, STATS_SOCKET, STATS_FLUSH_TIME_S +from openpilot.system.statsd import METRIC_TYPE, StatLogSP +from openpilot.common.realtime import Ratekeeper + +STATSLOGSP = StatLogSP(intercept=False) + +def sp_stats(end_event): + """Collect sunnypilot-specific statistics and send as raw metrics.""" + rk = Ratekeeper(.1, print_delay_threshold=None) + statlogsp = STATSLOGSP + params = Params() + + def flatten_dict(d, parent_key='', sep='.'): + items = {} + if isinstance(d, dict): + for k, v in d.items(): + new_key = f"{parent_key}{sep}{k}" if parent_key else k + items.update(flatten_dict(v, new_key, sep=sep)) + elif isinstance(d, (list, tuple)): + for i, v in enumerate(d): + new_key = f"{parent_key}[{i}]" + items.update(flatten_dict(v, new_key, sep=sep)) + else: + items[parent_key] = d + return items + + # Collect sunnypilot parameters + stats_dict = {} + + param_keys = [ + 'SunnylinkEnabled', + 'AutoLaneChangeBsmDelay', + 'AutoLaneChangeTimer', + 'CarPlatformBundle', + 'CurrentRoute', + 'DevUIInfo', + 'EnableCopyparty', + 'IntelligentCruiseButtonManagement', + 'QuietMode', + 'RainbowMode', + 'ShowAdvancedControls', + 'Mads', + 'MadsMainCruiseAllowed', + 'MadsSteeringMode', + 'MadsUnifiedEngagementMode', + 'ModelManager_ActiveBundle', + 'ModelManager_Favs', + 'EnableSunnylinkUploader', + 'SunnylinkEnabled', + 'InstallDate', + 'UptimeOffroad', + 'UptimeOnroad', + ] + + while not end_event.is_set(): + try: + for key in param_keys: + + try: + value = params.get(key) + except Exception as e: + stats_dict[key] = e + continue + + if value is None: + continue + + if isinstance(value, (dict, list, tuple)): + stats_dict.update(flatten_dict(value, key)) + else: + stats_dict[key] = value + + if stats_dict: + statlogsp.raw('sunnypilot.device_params', stats_dict) + except Exception as e: + cloudlog.error(f"Exception {e}") + finally: + rk.keep_time() + + +def stats_main(end_event): + comma_dongle_id = Params().get("DongleId") + sunnylink_dongle_id = Params().get("SunnylinkDongleId") + + def get_influxdb_line(measurement: str, value: float | dict[str, float], timestamp: datetime, tags: dict) -> str: + res = f"{measurement}" + for k, v in tags.items(): + res += f",{k}={str(v)}" + res += " " + + if isinstance(value, float): + value = {'value': value} + + for k, v in value.items(): + res += f"{k}={str(v)}," + + res += f"sunnylink_dongle_id=\"{sunnylink_dongle_id}\",comma_dongle_id=\"{comma_dongle_id}\" {int(timestamp.timestamp() * 1e9)}\n" + return res + + def get_influxdb_line_raw(measurement: str, value: dict, timestamp: datetime, tags: dict) -> str: + res = f"{measurement}" + try: + custom_tags = "" + for k, v in tags.items(): + custom_tags += f",{k}={str(v)}" + res += custom_tags + + fields = "" + for k, v in value.items(): + # Skip complex types - only keep simple scalar values + if isinstance(v, (dict, list, bytes, bytearray)): + continue + + fields += f"{k}={json.dumps(v)}," + + res += f" {fields}" + except Exception as e: + cloudlog.error(f"Unable to get influxdb line for: {value}") + res += f",invalid=1 reason={e}," + + res += f"sunnylink_dongle_id=\"{sunnylink_dongle_id}\",comma_dongle_id=\"{comma_dongle_id}\" {int(timestamp.timestamp() * 1e9)}\n" + return res + + # open statistics socket + ctx = zmq.Context.instance() + sock = ctx.socket(zmq.PULL) + sock.bind(f"{STATS_SOCKET}_sp") + + STATS_DIR = Paths.stats_sp_root() + + # initialize stats directory + Path(STATS_DIR).mkdir(parents=True, exist_ok=True) + + build_metadata = get_build_metadata() + + # initialize tags + tags = { + 'started': False, + 'version': build_metadata.openpilot.version, + 'branch': build_metadata.channel, + 'dirty': build_metadata.openpilot.is_dirty, + 'origin': build_metadata.openpilot.git_normalized_origin, + 'deviceType': HARDWARE.get_device_type(), + } + + # subscribe to deviceState for started state + sm = SubMaster(['deviceState']) + + idx = 0 + boot_uid = str(uuid.uuid4())[:8] + last_flush_time = time.monotonic() + gauges = {} + samples: dict[str, list[float]] = defaultdict(list) + raws: dict = defaultdict() + try: + while not end_event.is_set(): + started_prev = sm['deviceState'].started + sm.update() + + # Update metrics + while True: + try: + metric = sock.recv_string(zmq.NOBLOCK) + try: + metric_type = metric.split('|')[1] + metric_name = metric.split(':')[0] + metric_value_raw = metric.split('|')[0].split(':')[1] + + if metric_type == METRIC_TYPE.GAUGE: + metric_value = float(metric_value_raw) + gauges[metric_name] = metric_value + elif metric_type == METRIC_TYPE.SAMPLE: + metric_value = float(metric_value_raw) + samples[metric_name].append(metric_value) + elif metric_type == METRIC_TYPE.RAW: + raws[metric_name] = metric_value_raw + else: + cloudlog.event("unknown metric type", metric_type=metric_type) + except Exception: + print(traceback.format_exc()) + cloudlog.event("malformed metric", metric=metric) + except zmq.error.Again: + break + + # flush when started state changes or after FLUSH_TIME_S + if (time.monotonic() > last_flush_time + STATS_FLUSH_TIME_S) or (sm['deviceState'].started != started_prev): + result = "" + current_time = datetime.now(UTC) + tags['started'] = sm['deviceState'].started + + for key, value in raws.items(): + decoded_value = json.loads(base64.b64decode(value).decode('utf-8')) + result += get_influxdb_line_raw(key, decoded_value, current_time, tags) + + for key, value in gauges.items(): + result += get_influxdb_line(f"gauge.{key}", value, current_time, tags) + + for key, values in samples.items(): + values.sort() + sample_count = len(values) + sample_sum = sum(values) + + stats = { + 'count': sample_count, + 'min': values[0], + 'max': values[-1], + 'mean': sample_sum / sample_count, + } + for percentile in [0.05, 0.5, 0.95]: + value = values[int(round(percentile * (sample_count - 1)))] + stats[f"p{int(percentile * 100)}"] = value + + result += get_influxdb_line(f"sample.{key}", stats, current_time, tags) + + # clear intermediate data + gauges.clear() + samples.clear() + last_flush_time = time.monotonic() + + # check that we aren't filling up the drive + 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: + f.write(result) + idx += 1 + else: + cloudlog.error("stats dir full") + finally: + sock.close() + ctx.term() + + +def main(): + rk = Ratekeeper(1, print_delay_threshold=None) + end_event = threading.Event() + + threads = [ + threading.Thread(target=stats_main, args=(end_event,)), + threading.Thread(target=sp_stats, args=(end_event,)), + ] + + for t in threads: + t.start() + + try: + while all(t.is_alive() for t in threads): + rk.keep_time() + finally: + end_event.set() + + for t in threads: + t.join() + + +if __name__ == "__main__": + main() diff --git a/system/athena/athenad.py b/system/athena/athenad.py index 42c9cf8a1c..716733021a 100755 --- a/system/athena/athenad.py +++ b/system/athena/athenad.py @@ -744,26 +744,40 @@ def log_handler(end_event: threading.Event, log_attr_name=LOG_ATTR_NAME) -> None cloudlog.exception("athena.log_handler.exception") -def stat_handler(end_event: threading.Event) -> None: - STATS_DIR = Paths.stats_root() +def stat_handler(end_event: threading.Event, stats_dir=None, is_sunnylink=False) -> None: + stats_dir = stats_dir or Paths.stats_root() last_scan = 0.0 while not end_event.is_set(): curr_scan = time.monotonic() try: if curr_scan - last_scan > 10: - stat_filenames = list(filter(lambda name: not name.startswith(tempfile.gettempprefix()), os.listdir(STATS_DIR))) + stat_filenames = list(filter(lambda name: not name.startswith(tempfile.gettempprefix()), os.listdir(stats_dir))) if len(stat_filenames) > 0: - stat_path = os.path.join(STATS_DIR, stat_filenames[0]) + stat_path = os.path.join(stats_dir, stat_filenames[0]) with open(stat_path) as f: + payload = f.read() + is_compressed = False + + # Log the current size of the file + if is_sunnylink: + # Compress and encode the data if it exceeds the maximum size + compressed_data = gzip.compress(payload.encode()) + payload = base64.b64encode(compressed_data).decode() + is_compressed = True + jsonrpc = { "method": "storeStats", "params": { - "stats": f.read() + "stats": payload }, "jsonrpc": "2.0", "id": stat_filenames[0] } + + if is_sunnylink and is_compressed: + jsonrpc["params"]["compressed"] = is_compressed + low_priority_send_queue.put_nowait(json.dumps(jsonrpc)) os.remove(stat_path) last_scan = curr_scan diff --git a/system/hardware/hw.py b/system/hardware/hw.py index d24857e8bd..3527aac872 100644 --- a/system/hardware/hw.py +++ b/system/hardware/hw.py @@ -55,6 +55,13 @@ class Paths: else: return "/data/stats/" + @staticmethod + def stats_sp_root() -> str: + if PC: + return str(Path(Paths.comma_home()) / "stats") + else: + return "/data/stats_sp/" + @staticmethod def config_root() -> str: if PC: diff --git a/system/manager/process_config.py b/system/manager/process_config.py index c5f20e511b..6e52635a64 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -164,6 +164,7 @@ procs = [ # sunnylink <3 DaemonProcess("manage_sunnylinkd", "sunnypilot.sunnylink.athena.manage_sunnylinkd", "SunnylinkdPid"), PythonProcess("sunnylink_registration_manager", "sunnypilot.sunnylink.registration_manager", sunnylink_need_register_shim), + PythonProcess("statsd_sp", "sunnypilot.sunnylink.statsd", and_(always_run, sunnylink_ready_shim)), ] # sunnypilot diff --git a/system/statsd.py b/system/statsd.py index d60064fc91..89ffa0d6fc 100755 --- a/system/statsd.py +++ b/system/statsd.py @@ -1,11 +1,15 @@ #!/usr/bin/env python3 +import base64 +import json import os +from decimal import Decimal + import zmq import time import uuid from pathlib import Path from collections import defaultdict -from datetime import datetime, UTC +from datetime import datetime, UTC, date from typing import NoReturn from openpilot.common.params import Params @@ -21,18 +25,21 @@ from openpilot.system.loggerd.config import STATS_DIR_FILE_LIMIT, STATS_SOCKET, class METRIC_TYPE: GAUGE = 'g' SAMPLE = 'sa' + RAW = 'r' + class StatLog: def __init__(self): self.pid = None self.zctx = None self.sock = None + self.stats_socket = STATS_SOCKET def connect(self) -> None: - self.zctx = zmq.Context() + self.zctx = zmq.Context.instance() or zmq.Context() self.sock = self.zctx.socket(zmq.PUSH) self.sock.setsockopt(zmq.LINGER, 10) - self.sock.connect(STATS_SOCKET) + self.sock.connect(self.stats_socket) self.pid = os.getpid() def __del__(self): @@ -60,6 +67,50 @@ class StatLog: self._send(f"{name}:{value}|{METRIC_TYPE.SAMPLE}") +class StatLogSP(StatLog): + def __init__(self, intercept=True): + """ + Initializes the class instance with an optional parameter to determine + if statistical logging should be configured or not. + + :param intercept: A boolean flag that indicates whether to initialize + the `comma_statlog`. If True, the `comma_statlog` attribute is + instantiated as a `StatLog` object. Defaults to True. + """ + super().__init__() + self.comma_statlog = StatLog() if intercept else None + self.stats_socket = f"{STATS_SOCKET}_sp" + + def connect(self) -> None: + super().connect() + if self.comma_statlog: + self.comma_statlog.connect() + + def __del__(self): + super().__del__() + if self.comma_statlog: + self.comma_statlog.__del__() + + def _send(self, metric: str) -> None: + super()._send(metric) + if self.comma_statlog: + self.comma_statlog._send(metric) + + @staticmethod + def default_converter(obj): + if isinstance(obj, (datetime, date)): + return obj.isoformat() + if isinstance(obj, set): + return list(obj) + if isinstance(obj, Decimal): + return float(obj) + return str(obj) # fallback for unknown types + + def raw(self, name: str, value: dict) -> None: + encoded_dict = base64.b64encode(json.dumps(value, default=self.default_converter).encode("utf-8")).decode("utf-8") + self._send(f"{name}:{encoded_dict}|{METRIC_TYPE.RAW}") + + def main() -> NoReturn: dongle_id = Params().get("DongleId") def get_influxdb_line(measurement: str, value: float | dict[str, float], timestamp: datetime, tags: dict) -> str: @@ -180,4 +231,4 @@ def main() -> NoReturn: if __name__ == "__main__": main() else: - statlog = StatLog() + statlog = StatLogSP(intercept=True) From 38eb400e41c18bfdb6fbde00f1217661aec22e81 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Tue, 4 Nov 2025 17:00:15 -0700 Subject: [PATCH 379/681] 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 c2e5bc3fe4..83904e2fb8 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 380/681] bump panda --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 615009cf0f..1c44035563 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 381/681] 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 91898ac59a..d926a7ae86 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 382/681] 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 b700df0c88..e4992a3d05 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 0000000000..0294125ceb --- /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 8d0870171f..ee75a8b1ce 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 383/681] make github LFS work if you want it --- SConstruct | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SConstruct b/SConstruct index 648afdae57..094503cfa7 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 384/681] 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 84a926dd78..6882ad0955 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 385/681] 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 ad6530de9a..54c7393986 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 3715ad2486..a7ac013c51 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 222a25b87b..33d05f89e8 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 ff2622e963..9791f15f34 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 a6fd96f620..7296c74cbd 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 386/681] 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 0b50244482..0a9fffc1d4 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 954aa0d486..e7a435b93e 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 387/681] 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 a7ac013c51..ee4d860e98 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 d6a6ab51c9..e664f31e87 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 c51453f4c6..04a690e32d 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 cde88fd8ed6dfa0f2218745acf53ff8170ffd086 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Thu, 6 Nov 2025 12:13:15 +0100 Subject: [PATCH 388/681] bug: Fix initial registration for sunnylink (#1457) refactor(sunnylink): defer `SunnylinkApi` initialization to function scope - Moved `SunnylinkApi` object creation into individual functions as needed. - Prevents unnecessary initialization when the object isn't used. --- sunnypilot/sunnylink/athena/sunnylinkd.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sunnypilot/sunnylink/athena/sunnylinkd.py b/sunnypilot/sunnylink/athena/sunnylinkd.py index 4c431c5d34..911739c312 100755 --- a/sunnypilot/sunnylink/athena/sunnylinkd.py +++ b/sunnypilot/sunnylink/athena/sunnylinkd.py @@ -34,9 +34,6 @@ SUNNYLINK_RECONNECT_TIMEOUT_S = 70 # FYI changing this will also would require DISALLOW_LOG_UPLOAD = threading.Event() params = Params() -sunnylink_dongle_id = params.get("SunnylinkDongleId") -sunnylink_api = SunnylinkApi(sunnylink_dongle_id) - def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: cloudlog.info("sunnylinkd.handle_long_poll started") @@ -133,6 +130,8 @@ def ws_ping(ws: WebSocket, end_event: threading.Event) -> None: def ws_queue(end_event: threading.Event) -> None: + sunnylink_dongle_id = params.get("SunnylinkDongleId") + sunnylink_api = SunnylinkApi(sunnylink_dongle_id) resume_requested = False tries = 0 @@ -234,6 +233,9 @@ def saveParams(params_to_update: dict[str, str], compression: bool = False) -> N def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local_port: int) -> dict[str, int]: + sunnylink_dongle_id = params.get("SunnylinkDongleId") + sunnylink_api = SunnylinkApi(sunnylink_dongle_id) + cloudlog.debug("athena.startLocalProxy.starting") ws = create_connection( remote_ws_uri, @@ -255,6 +257,8 @@ def main(exit_event: threading.Event = None): cloudlog.info("Waiting for sunnylink registration to complete") time.sleep(10) + sunnylink_dongle_id = params.get("SunnylinkDongleId") + sunnylink_api = SunnylinkApi(sunnylink_dongle_id) UploadQueueCache.initialize(upload_queue) ws_uri = f"{SUNNYLINK_ATHENA_HOST}" From b6bcc8cca32f9df8e63afe7c98f5d738b5383da5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 6 Nov 2025 09:42:23 -0800 Subject: [PATCH 389/681] 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 33d05f89e8..a4b99825e9 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 8c1d59fecd43d0e4b7d3c3742023fa0be0ff91f2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:47:55 -0500 Subject: [PATCH 390/681] [bot] Update Python packages (#1434) Update Python packages Co-authored-by: github-actions[bot] --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index c7126f8ba6..c32e79f3c6 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit c7126f8ba620eb06fe345013081e97503331bbe1 +Subproject commit c32e79f3c6a6170bec9f879a0ffdf901caeb4ab0 From 2ab45b552d8ceae61647f6ac64b238be1b7e5a06 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 6 Nov 2025 23:10:03 -0500 Subject: [PATCH 391/681] Update CHANGELOG.md --- CHANGELOG.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cb25994be..ea7199a854 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ -sunnypilot Version 2025.002.000 (2025-xx-xx) +sunnypilot Version 2025.002.000 (2025-11-06) ======================== +* What's Changed (sunnypilot/sunnypilot) + * models: bump model json to v8 by @Discountchubbs + * Bug: Model UI Crash Fix by @nayan8teen + * controlsd: add `CP_SP` to `get_pid_accel_limits` by @THERoenPR + * sunnylink: update uploader button logic to support novice tier and above by @devtekve + * Tesla: Coop Steering by @AmyJeanes + * ui: update discord references and add forum widget by @devtekve + * ui: Fix spacing in sunnylink panel by @devtekve + * docs: Update README installation branches and discord links by @mpurnell1 in + * stats: sunnylink integration by @devtekve + * bug: Fix initial registration for sunnylink by @devtekve +* What's Changed (sunnypilot/opendbc) + * Honda: add brake hold messages for Clarity by @mvl-boston + * interface: add `CP_SP` to `get_pid_accel_limits` method signature by @roenthomas + * Honda: use fixed accel min/max constants for Gas Interceptor by @roenthomas + * Tesla: Coop Steering by @AmyJeanes +* New Contributors (sunnypilot/sunnypilot) + * @THERoenPR made their first contribution in "controlsd: add `CP_SP` to `get_pid_accel_limits`" + * @AmyJeanes made their first contribution in "Tesla: Coop Steering" + * @mpurnell1 made their first contribution in "docs: Update README installation branches and discord links" +* Full Changelog: https://github.com/sunnypilot/sunnypilot/compare/v2025.001.000...v2025.002.000 sunnypilot Version 2025.001.000 (2025-10-25) ======================== From c1d3ae427bd773edbf3d4bd73f06ab52659e638f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 6 Nov 2025 23:12:41 -0500 Subject: [PATCH 392/681] version: bump to 2025.003.000 --- CHANGELOG.md | 3 +++ sunnypilot/common/version.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea7199a854..61bc81bafc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +sunnypilot Version 2025.003.000 (20xx-xx-xx) +======================== + sunnypilot Version 2025.002.000 (2025-11-06) ======================== * What's Changed (sunnypilot/sunnypilot) diff --git a/sunnypilot/common/version.h b/sunnypilot/common/version.h index b52d984b4d..6833a508de 100644 --- a/sunnypilot/common/version.h +++ b/sunnypilot/common/version.h @@ -1 +1 @@ -#define SUNNYPILOT_VERSION "2025.002.000" +#define SUNNYPILOT_VERSION "2025.003.000" From fb34601d5a414e832a68e4357dc5b1653f0807cb Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 6 Nov 2025 20:46:04 -0800 Subject: [PATCH 393/681] Revert "bump panda" (#36594) This reverts commit 36e53c7394730be54c8e2e28c811d8e16d5e54ea. --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 1c44035563..615009cf0f 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 394/681] 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 1e4fbbb783..b9f601f8fb 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 395/681] 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 0a9fffc1d4..ab6bbb977b 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 e7a435b93e..a0f7734d27 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 396/681] 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 ee4d860e98..6fc51c5b84 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 e664f31e87..1143f4f08a 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 04a690e32d..5082647a1c 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 397/681] 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 6cd8196ae0..981cfd468f 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 badc162149..8d4c8d9e4b 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 83904e2fb8..02d8ff5c71 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 1cc7101880..1f8babe029 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 3d97135f60..b52dfa4d88 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 398/681] 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 0000000000..669bd0ee23 --- /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 0000000000..128ec8e776 --- /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 dd074cb6ef907d6bc8e913feacfe4bdf489de853 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon, 10 Nov 2025 07:50:43 -0800 Subject: [PATCH 399/681] ci: efficient model building (#1456) * new new * Simplify model removal * use a var --- .github/workflows/build-all-tinygrad-models.yaml | 4 ++-- .github/workflows/build-single-tinygrad-model.yaml | 2 +- .github/workflows/sunnypilot-build-model.yaml | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-all-tinygrad-models.yaml b/.github/workflows/build-all-tinygrad-models.yaml index 1c7812ee47..00864d38a0 100644 --- a/.github/workflows/build-all-tinygrad-models.yaml +++ b/.github/workflows/build-all-tinygrad-models.yaml @@ -74,7 +74,7 @@ jobs: env: GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts' run: | - git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/docs.sunnypilot.ai2.git gitlab_docs + git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs cd gitlab_docs git checkout main git sparse-checkout set --no-cone models/ @@ -191,7 +191,7 @@ jobs: GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts' run: | echo "Cloning GitLab" - git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/docs.sunnypilot.ai2.git gitlab_docs + git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs cd gitlab_docs echo "checkout models/${RECOMPILED_DIR}" git sparse-checkout set --no-cone models/${RECOMPILED_DIR} diff --git a/.github/workflows/build-single-tinygrad-model.yaml b/.github/workflows/build-single-tinygrad-model.yaml index 76f39fb6fe..fae9d6aa01 100644 --- a/.github/workflows/build-single-tinygrad-model.yaml +++ b/.github/workflows/build-single-tinygrad-model.yaml @@ -109,7 +109,7 @@ jobs: GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=~/.ssh/known_hosts' run: | echo "Cloning GitLab" - git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/docs.sunnypilot.ai2.git gitlab_docs + git clone --depth 1 --filter=tree:0 --sparse git@gitlab.com:sunnypilot/public/${{ vars.MODELS_GITLAB }} gitlab_docs cd gitlab_docs echo "checkout models/${RECOMPILED_DIR}" git sparse-checkout set --no-cone models/${RECOMPILED_DIR} diff --git a/.github/workflows/sunnypilot-build-model.yaml b/.github/workflows/sunnypilot-build-model.yaml index dfe84e7853..bc4ecfe04b 100644 --- a/.github/workflows/sunnypilot-build-model.yaml +++ b/.github/workflows/sunnypilot-build-model.yaml @@ -156,6 +156,8 @@ jobs: with: name: models-${{ env.REF }}${{ inputs.artifact_suffix }} path: ${{ github.workspace }}/selfdrive/modeld/models + - run: | + rm -f ${{ github.workspace }}/selfdrive/modeld/models/{dmonitoring_model,big_driving_policy,big_driving_vision}.onnx - name: Build Model run: | From 8fceb9d957e7c16045a0ed655862ec3be485df19 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 11 Nov 2025 05:58:23 +0800 Subject: [PATCH 400/681] 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 e3674452d5..d4cfc67280 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 505b2b8053..bc2380e550 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 35537d75e3..0a0c07a155 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 2ed461fdf6..89379a8434 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 c857c9ffbe..55018f28f0 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 401/681] 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 34a7558cdc..7f477d4241 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 402/681] 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 f9d7b0c9c1..3c5d3a6936 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 403/681] 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 63ee736dca..cd30e4f370 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 0000000000..2948c4857d --- /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 b692552fb8..bded80b2e5 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 404/681] 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 98dd00ebaa..87db7cc636 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 405/681] 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 fdd21067ec..5c2131d4bf 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 406/681] 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 cb7b0f9e91..9ea1019f54 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 407/681] 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 3c5d3a6936..ddee0f41b5 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 408/681] 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 ddee0f41b5..af254f4b57 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 409/681] 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 6fc51c5b84..0fba08bb24 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 1143f4f08a..58c3d2a4e6 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 5082647a1c..bac2dfc594 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 410/681] 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 f950985075..4609012194 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 7ecad82873..c9ab7e7e2b 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 411/681] 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 ab6bbb977b..368e27b449 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 412/681] 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 af254f4b57..352a8cfa9a 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 413/681] 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 352a8cfa9a..e9f5484a17 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 414/681] 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 368e27b449..7f2ce5951f 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 a0f7734d27..4e810c8b7b 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 415/681] 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 54c7393986..ad6530de9a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,4 +15,4 @@ url = ../../commaai/teleoprtc [submodule "tinygrad"] path = tinygrad_repo - url = https://github.com/commaai/tinygrad.git + url = https://github.com/tinygrad/tinygrad.git diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index f20855c2cb..ae549f3a76 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 7296c74cbd..547304c471 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 7296c74cbd2666da7dce95d7ca6dab5340653a5c +Subproject commit 547304c471b26ada0b34f400ccba67f3e1eb5965 From 90cbb094829501706b79eaab356a623328a4f23f Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:03:00 -0800 Subject: [PATCH 416/681] tinygrad: sync tinygrad and recompile models (#1464) * ref * add in the fixes * Update fetcher.py * Update helpers.py * force push same commit so we can restart CI --- selfdrive/modeld/SConscript | 4 ++-- selfdrive/modeld/dmonitoringmodeld.py | 2 +- selfdrive/modeld/modeld.py | 2 +- sunnypilot/models/fetcher.py | 2 +- sunnypilot/models/helpers.py | 4 ++-- sunnypilot/models/runners/model_runner.py | 7 ++----- tinygrad_repo | 2 +- 7 files changed, 10 insertions(+), 13 deletions(-) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 6802b45e6e..1a6df4bb9b 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 215056e6e6..5aeb035bdc 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 00b49afaba..9ac21a18dc 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/sunnypilot/models/fetcher.py b/sunnypilot/models/fetcher.py index 8c9c4eac71..0c14315ff8 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://docs.sunnypilot.ai/driving_models_v8.json" + MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-docs/refs/heads/gh-pages/docs/driving_models_v9.json" def __init__(self, params: Params): self.params = params diff --git a/sunnypilot/models/helpers.py b/sunnypilot/models/helpers.py index 20b94fb611..51a3a24598 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 = 10 -REQUIRED_MIN_SELECTOR_VERSION = 9 +CURRENT_SELECTOR_VERSION = 11 +REQUIRED_MIN_SELECTOR_VERSION = 11 USE_ONNX = os.getenv('USE_ONNX', PC) diff --git a/sunnypilot/models/runners/model_runner.py b/sunnypilot/models/runners/model_runner.py index 7124662791..6902ed09b8 100644 --- a/sunnypilot/models/runners/model_runner.py +++ b/sunnypilot/models/runners/model_runner.py @@ -14,13 +14,10 @@ CUSTOM_MODEL_PATH = Paths.model_root() # Set QCOM environment variable for TICI devices, potentially enabling hardware acceleration USBGPU = "USBGPU" in os.environ if USBGPU: - os.environ['AMD'] = '1' + os.environ['DEV'] = 'AMD' os.environ['AMD_IFACE'] = 'USB' -elif TICI: - os.environ['QCOM'] = '1' else: - os.environ['LLVM'] = '1' - os.environ['JIT'] = '2' # TODO: This may cause issues + os.environ['DEV'] = 'QCOM' if TICI else 'CPU' class ModelData: diff --git a/tinygrad_repo b/tinygrad_repo index d2bb1bcb97..7296c74cbd 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit d2bb1bcb976f106a41928f2d66d354ab7afd6f59 +Subproject commit 7296c74cbd2666da7dce95d7ca6dab5340653a5c From b778da1d7c4ebcfaec7d57787efec064f53a211d Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Fri, 14 Nov 2025 14:29:04 -0800 Subject: [PATCH 417/681] 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 981cfd468f..86774b8d42 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 ae549f3a76..8b33a457f2 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 2851a3e7da..dc2de6f998 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 1b6a8c3e93..9b1c4a1834 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 02d8ff5c71..f405eba537 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 1f8babe029..67234550f7 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 59b8cf8250..9ba599bac9 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 418/681] 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 823df2b580..1ecd114dc4 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 419/681] 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 b124ae4d85..3f2562aae2 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 e9f5484a17..1d085a5a05 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 28585b08ba..3cc480b33b 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 420/681] 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 c6e2d98914..f84fe75869 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 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 421/681] 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 8a388d0da0..84b53c6e8d 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 b8a845fe93374087135dfab6f526089ebf32eb17 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 17 Nov 2025 20:46:34 -0800 Subject: [PATCH 422/681] 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 3f2562aae2..6e43c20d12 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 1d085a5a05..16b924e442 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 423/681] 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 b08b8ef28c..8871e5de2c 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 424/681] 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 f137b406b4..293904a8ee 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 f405eba537..463bf2b7fa 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 425/681] mici fcc --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f2d8c5cad0..2faa4f8ace 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 426/681] 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 2faa4f8ace..3f3804ba6a 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 3cc480b33b..7be6638af1 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 7d76802565..8d33ac2fd0 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 427/681] 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 2b6467fa78..cc0366881b 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 428/681] 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 940e7f9125..8cf3e8c14c 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 429/681] 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 c865cc94a6..a9b1683c51 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 e9c62fe327..997c7e3770 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 430/681] 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 9ea6fe3070..212e1a8f40 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 431/681] 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 d468442b15..050cd795bf 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 432/681] 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 b52dfa4d88..0fc11b9636 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 433/681] 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 b4dfa9be2e..ec586dbcb3 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 434/681] 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 a0d01af148..ddc8b3a868 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 0000000000..af2122cd9a --- /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 0000000000..ad9afc3a3e --- /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 0000000000..b6f4cc9d12 --- /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 0000000000..d2104df4e1 --- /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 0000000000..5cae152106 --- /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 0000000000..45027a372e --- /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 0000000000..68ae400b1e --- /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 0000000000..3696334d5e --- /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 0000000000..08b2e318d4 --- /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 0000000000..230c537d6d --- /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 0000000000..76e75d5421 --- /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 0000000000..a9fd28cc35 --- /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 0000000000..779c219fcb --- /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 0000000000..3d648d34f1 --- /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 0000000000..e431cb0c73 --- /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 0000000000..820b236066 --- /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 0000000000..6c38508af9 --- /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 0000000000..99c5ea6509 --- /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 0000000000..9164f74bad --- /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 0000000000..0e21bc1b5a --- /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 0000000000..5bb4d778f8 --- /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 0000000000..bf8559ec87 --- /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 0000000000..555c16e095 --- /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 0000000000..d95039da92 --- /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 0000000000..246fc015ec --- /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 0000000000..e0138bfd65 --- /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 0000000000..db2953b690 --- /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 0000000000..11197fcf57 --- /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 0000000000..9718a6b135 --- /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 0000000000..e4aa33a135 --- /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 0000000000..500f48e36f --- /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 0000000000..72b64f0732 --- /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 0000000000..fbe70ee826 --- /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 0000000000..e0f061a015 --- /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 0000000000..8acab1854f --- /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 0000000000..11197fcf57 --- /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 0000000000..017da6c8c9 --- /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 0000000000..09d0d798d8 --- /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 0000000000..ec4741f7ff --- /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 0000000000..df25984030 --- /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 0000000000..f96de2e304 --- /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 0000000000..62f6dac634 --- /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 0000000000..31fcf11a6a --- /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 0000000000..81cd5566fe --- /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 0000000000..00b358c81c --- /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 0000000000..142367d0e6 --- /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 0000000000..2ff01024d9 --- /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 0000000000..6a8351f6ee --- /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 0000000000..91a7b43c5a --- /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 0000000000..09ca2d08d5 --- /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 0000000000..13af475c6d --- /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 0000000000..83c3595b29 --- /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 0000000000..0a50b6a1ca --- /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 0000000000..865355ef01 --- /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 0000000000..5d3b1e5d7b --- /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 0000000000..67216078d9 --- /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 0000000000..207182276e --- /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 0000000000..4d83ed5cd9 --- /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 0000000000..a8a68b372c --- /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 0000000000..ec2f948998 --- /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 0000000000..7aa7f0542a --- /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 0000000000..8f0e8ebfb1 --- /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 0000000000..b61b9b063c --- /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 0000000000..1ac4442f49 --- /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 0000000000..bc12e57e17 --- /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 0000000000..15e502d617 --- /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 0000000000..48f52ff9ce --- /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 0000000000..87ca979fbe --- /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 0000000000..e668ed1fe4 --- /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 0000000000..72a7c8c8f9 --- /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 0000000000..b3a7801467 --- /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 0000000000..2849d2733c --- /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 0000000000..cd86937aea --- /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 0000000000..af16c02912 --- /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 0000000000..c44c511275 --- /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 0000000000..6da29ad66f --- /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 0000000000..2db5856989 --- /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 0000000000..f29b24fd09 --- /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 0000000000..cb16320693 --- /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 0000000000..f6d57b3134 --- /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 0000000000..186ea78fb9 --- /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 0000000000..f072b2363f --- /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 0000000000..a2de14a4e8 --- /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 0000000000..6c89cd9fc2 --- /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 0000000000..f9173711eb --- /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 0000000000..ee925458d3 --- /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 0000000000..cc05931b03 --- /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 0000000000..0caf0d07ce --- /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 0000000000..fccc2484df --- /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 0000000000..342f8e28da --- /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 0000000000..d63cc56fbc --- /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 0000000000..eb38934302 --- /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 0000000000..4a2cae6c8a --- /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 0000000000..09b180e97f --- /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 0000000000..8c2c068d41 --- /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 0000000000..778d1847d7 --- /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 0000000000..100b29da45 --- /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 0000000000..4bf0cd8726 --- /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 0000000000..df6d009335 --- /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 0000000000..c3323a9fea --- /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 0000000000..64ab947c53 --- /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 0000000000..6cdef706bd --- /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 0000000000..f7beaf3923 --- /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 0000000000..0563668bed --- /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 0000000000..aef242b6d8 --- /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 0000000000..a80de07634 --- /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 0000000000..a1240481e5 --- /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 0000000000..eae5af77f0 --- /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 0000000000..0da6c384d9 --- /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 0000000000..541433be76 --- /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 0000000000..26cc8b4fca --- /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 0000000000..905170fd10 --- /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 0000000000..88eb4ac2a3 --- /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 0000000000..0a0b18c7a9 --- /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 0000000000..99e1a2e246 --- /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 0000000000..2a3e837138 --- /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 0000000000..9e7b90be41 --- /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 0000000000..99e1a2e246 --- /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 0000000000..1a1655fddc --- /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 0000000000..4d64d8062f --- /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 0000000000..2d53a20cef --- /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 0000000000..482a0e1042 --- /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 0000000000..38ddff84b7 --- /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 0000000000..ccb343e8ed --- /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 0000000000..77d9a77d6f --- /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 0000000000..403aaedfb2 --- /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 0000000000..554c63e099 --- /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 0000000000..c4834a5649 --- /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 0000000000..9708cfe284 --- /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 0000000000..030ce61d5b --- /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 0000000000..867cadbbd6 --- /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 0000000000..d41edd4c2a --- /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 0000000000..309e56e6ee --- /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 0000000000..2dd18f489a --- /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 0000000000..e79dc2eb58 --- /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 0000000000..e52fb0c17d --- /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 0000000000..5633f2b499 --- /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 0000000000..ed0634079b --- /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 0000000000..e3f58b1078 --- /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 0000000000..5b502e00aa --- /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 0000000000..3892f6eb8c --- /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 0000000000..3a34af8846 --- /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 0000000000..6aa6c6b851 --- /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 0000000000..4d74d86075 --- /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 0000000000..1ee01aeac2 --- /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 0000000000..5028a8cd21 --- /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 0000000000..6e30f47fba --- /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 0000000000..4a7db930a0 --- /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 0000000000..a8d51960c4 --- /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 0000000000..bbf1d96254 --- /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 0000000000..1515867a40 --- /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 0000000000..43c10a54ad --- /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 0000000000..11c3ae2d3f --- /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 0000000000..683587a060 --- /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 0000000000..9ebff76b50 --- /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 0000000000..541433be76 --- /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 0000000000..9b4851c568 --- /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 0000000000..6514791de7 --- /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 0000000000..64235b3a2f --- /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 0000000000..806eea28b7 --- /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 0000000000..529b7c80cc --- /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 0000000000..5028a8cd21 --- /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 0000000000..1095d4fc23 --- /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 0000000000..6c2c47e882 --- /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 0000000000..03a7245e76 --- /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 0000000000..f122349b82 --- /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 0000000000..c0e5e8619e --- /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 0000000000..e6e4189128 --- /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 f3b5f21a27..8983884b25 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 0000000000..f3b5f21a27 --- /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 fc24a23c2f..39d4c749c8 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 0000000000..fc24a23c2f --- /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 8a5f5793a5..0de3e13c01 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 5cb0a38e0b..38dd1ce25c 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 7f477d4241..cd6ae600ef 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 0000000000..5e87a6447a --- /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 9ea1019f54..646c817508 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 f5f37fbd3c..00ae6a188e 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 8166a8a9e4..4b8b7015f8 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 3a1265e0fe..7fae2dfd24 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 0000000000..e69de29bb2 diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py new file mode 100644 index 0000000000..014fc0c45f --- /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 0000000000..b52f9ed39a --- /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 0000000000..2e9a8bee3c --- /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 0000000000..6036393aa5 --- /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 0000000000..8fc63e8963 --- /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 0000000000..de2e11caf2 --- /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 0000000000..303d976b07 --- /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 0000000000..a62c1d153a --- /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 0000000000..75238d581a --- /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 0000000000..8efb516a42 --- /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 0000000000..bb45117b94 --- /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 0000000000..eb5555660a --- /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 0000000000..ab55f392f7 --- /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 0000000000..f962210afb --- /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 0000000000..de792282c5 --- /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 0000000000..f2fa5e8fe8 --- /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 0000000000..369055846e --- /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 0000000000..bb5171d6e3 --- /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 0000000000..3f1badfe84 --- /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 0000000000..1f6dffe879 --- /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 0000000000..be08e0fee3 --- /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 0000000000..d64ab65ef2 --- /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 0000000000..e064205d59 --- /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 0000000000..4803b6d208 --- /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 44116a2329..d88410ada3 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 0294125ceb..b1fa4acc48 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 f36ad1badb..3d476c4319 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 a4b99825e9..7fe0dfbbc9 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 8871e5de2c..4a6ff9ebd9 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 6e43c20d12..21c8ab974a 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 16b924e442..e3370a5f74 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 0000000000..8d9caadfdd --- /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 7be6638af1..94af35e157 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 544ba5b870..dee4b419ff 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 4cf2ccebc8..217ac5e89a 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 745d37b468..3fabfbb66b 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 0000000000..d9bb45d99a --- /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 0000000000..4afd69bfa7 --- /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 0000000000..2ae2f7cc19 --- /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 3922c27aac..c32504a5b8 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 0045b45417..23ffc26aa2 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 eb33f08349..2a48b3889b 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 707b30983b..17e8a507cb 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 0000000000..3922c27aac --- /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 0000000000..bf64361bed --- /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 0000000000..2e1a8687e1 --- /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 2e1a8687e1..42d12d9090 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 562cde39e1..95858ec1b3 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 df7a52d1c0..34b2a51a42 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 8c5ae0aa01..97618660bd 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 8d33ac2fd0..35e2708e62 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 0000000000..2eb24cdb97 --- /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 592c9de971..f41a04c249 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 3b2201164a..62578d1cfb 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 a843010d56..fb7f635be0 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 0000000000..a843010d56 --- /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 0000000000..b17d8f3b7c --- /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 435/681] 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 de2e11caf2..b5e0ea838d 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 436/681] small release --- RELEASES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 9ab60e6cb7..58044dc694 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 437/681] 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 6036393aa5..a3f8592080 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 438/681] 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 45027a372e..0000000000 --- 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 08b2e318d4..0000000000 --- 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 99c5ea6509..0000000000 --- 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 bf8559ec87..0000000000 --- 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 db2953b690..0000000000 --- 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 11197fcf57..0000000000 --- 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 e4aa33a135..0000000000 --- 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 500f48e36f..0000000000 --- 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 72b64f0732..0000000000 --- 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 fbe70ee826..0000000000 --- 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 e0f061a015..0000000000 --- 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 8acab1854f..0000000000 --- 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 11197fcf57..0000000000 --- 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 017da6c8c9..0000000000 --- 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 09d0d798d8..0000000000 --- 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 ec4741f7ff..0000000000 --- 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 df25984030..0000000000 --- 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 f96de2e304..0000000000 --- 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 62f6dac634..0000000000 --- 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 31fcf11a6a..0000000000 --- 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 81cd5566fe..0000000000 --- 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 00b358c81c..0000000000 --- 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 15e502d617..0000000000 --- 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 b3a7801467..0000000000 --- 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 2849d2733c..0000000000 --- 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 6da29ad66f..0000000000 --- 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 2db5856989..0000000000 --- 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 fccc2484df..0000000000 --- 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 f7beaf3923..0000000000 --- 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 0563668bed..0000000000 --- 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 aef242b6d8..0000000000 --- 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 a80de07634..0000000000 --- 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 a1240481e5..0000000000 --- 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 99e1a2e246..0000000000 --- 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 403aaedfb2..0000000000 --- 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 554c63e099..0000000000 --- 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 5633f2b499..0000000000 --- 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 5028a8cd21..0000000000 --- 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 1515867a40..0000000000 --- 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 439/681] 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 0fba08bb24..fcbee2ff8d 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 58c3d2a4e6..5a2a092aa8 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 bac2dfc594..3abf66cdd4 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 440/681] 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 dc2de6f998..fca762c69b 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 441/681] 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 69d920c1a0..27cc17624e 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 442/681] 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 2eb24cdb97..a4f4c7d09b 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 443/681] 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 463bf2b7fa..208fc1676a 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 67234550f7..6ea9b80283 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 444/681] 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 217ac5e89a..28bd58f226 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 445/681] bump msgq --- msgq_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msgq_repo b/msgq_repo index 89096d90d2..a16cf1f608 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 446/681] 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 8d4c8d9e4b..bf410825f1 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 df259a8fb5..5d61c1c95a 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 a3f8592080..d140a17441 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 3d476c4319..481ac111be 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 4a6ff9ebd9..ef0696a22c 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 36055d8635..8db13346e3 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 4afd69bfa7..d7395f9b7a 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 f59509715f..1f01b181cd 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 447/681] 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 f962210afb..0f425b10da 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 448/681] 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 f2fa5e8fe8..2f7f10b951 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 fa56d539a7cb29b695d101f2d90554989b0ff82c Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Thu, 20 Nov 2025 10:28:19 -0800 Subject: [PATCH 449/681] 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 86774b8d42..5fce839577 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 208fc1676a..4b216a496c 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 450/681] 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 5fce839577..686771e284 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 4b216a496c..4d9e2e7b0b 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 451/681] 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 293904a8ee..1dc256d467 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 4d9e2e7b0b..9b9282bf49 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 452/681] 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 2f7f10b951..f2fa5e8fe8 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 453/681] 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 d140a17441..66a0919b09 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 454/681] 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 21c8ab974a..f81cb5573a 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 455/681] 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 fb7f635be0..9a04e84257 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 456/681] 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 2931eb4acd..a76cbc46e3 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 457/681] 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 9b9282bf49..7697e68b98 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 458/681] 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 38dd1ce25c..072fa4e24b 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 459/681] 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 66a0919b09..afc7bfce17 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 460/681] 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 de792282c5..a5c95470f5 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 461/681] msgq: point back to comma's (#1498) --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index b9f0336a0e..cd6cf2168f 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 462/681] 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 12fb01cbd1..d3ad2d2419 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 e93e778aa6..e7a9663743 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 9cde4cf076..01bc614618 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 463/681] [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 695c2589e3..4c1e90e18d 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 62f6f9e4d4..61bf5a90c5 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 464/681] 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 014fc0c45f..75fdfe3e99 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 8184cd8a6ab72a4c6a57971f330ab739c21020a9 Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 21 Nov 2025 14:59:54 -0500 Subject: [PATCH 465/681] 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 dc49230818..33f4543c3e 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 7440d224ca..0000000000 --- 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 0ba5cbea914ed437f760e8644ac6800aacbe5a4a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 21 Nov 2025 15:55:18 -0500 Subject: [PATCH 466/681] 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 0000000000..b58b1bf1b7 --- /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 0000000000..6c231c83f8 --- /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 467/681] 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 75fdfe3e99..6102265a87 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 2e9a8bee3c..60f64b31b0 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 1f01b181cd..0cea616d23 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 468/681] ci: fix upstream merge conflicts with prebuilts (#1503) --- SConstruct | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SConstruct b/SConstruct index 48dd044709..f5d5ec6fb8 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 469/681] 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 afc7bfce17..52dbb785d6 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 470/681] 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 19a30599bd..a77a80935d 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 471/681] 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 f81cb5573a..796d298626 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 e3370a5f74..7271127838 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 0000000000..b39bf31e9d --- /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 472/681] 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 646c817508..f47cd310ca 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 7fae2dfd24..b7f176e24f 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 481ac111be..d3de616cc0 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 0000000000..a232ac192d --- /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 0000000000..dcf8f6019d --- /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 0000000000..46b390f773 --- /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 f41a04c249..fa47d35536 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 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 473/681] 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 f5d5ec6fb8..b2abae8d1b 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 dc6f6700d1..dfab658b20 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 3f4507e4eb..d105685bd8 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 84d99fd10f..55e703ef97 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 ce06ba39f7..0000000000 --- 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 bd76624213..0000000000 --- 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 f19501d37d..0000000000 --- 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 056ba9d4fe..0000000000 --- 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 1e451a7e3e..a917d6cbb8 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 51a3a24598..a581185371 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 a1c6fed910..0000000000 --- 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 54c0c32e41..0000000000 --- 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 2154203026..0000000000 --- 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 1d81abd3a5..0000000000 --- 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 40e3d5af74..0000000000 --- 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 9e9e9221be..0000000000 --- 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 3f1b7a8b8c..0000000000 --- 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 aa42b5e830..0000000000 --- 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 9423da1b94..0000000000 --- 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 018b567256..0000000000 --- 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 798fa3f124..0000000000 --- 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 4e29b39bb4..0000000000 --- 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 c9b3ef970a..0000000000 --- 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 57592f01ba..0000000000 --- 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 4f83e6b4e4..0000000000 --- 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 beb8d75f21..0000000000 --- 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 f893e0a237..0000000000 --- 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 ac33c84af8..0000000000 --- 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 2f006c857f..0000000000 --- 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 57d2c8ea99..0000000000 --- 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 4ce12a9f11..0000000000 --- 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 069e7a1d69..0000000000 --- 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 a171ac7e81..0000000000 --- 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 ea491a545b..0000000000 --- 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 59803fbd8f..0000000000 --- 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 c7b85f43c1..0000000000 --- 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 1088a5b31f..0000000000 --- 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 c1eba3bad9..0000000000 --- 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 a9a7d5ed89..0000000000 --- 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 feecec6667..0000000000 --- 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 99583dccb4..0000000000 --- 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 cef946e2e6..0000000000 --- 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 21fce1b04a..0000000000 --- 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 6a95eef170..0000000000 --- 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 a03ddfe1d1..0000000000 --- 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 3685a8ddab..0000000000 --- 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 92eb786d31..0000000000 --- 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 66097ba569..0000000000 --- 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 380a3e0e6e..0000000000 --- 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 a823c0c7fa..0000000000 --- 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 837dba092a..0000000000 --- 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 5ab45b82ee..0000000000 --- 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 5fc7846ced..0000000000 --- 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 2d78e81b17..0000000000 --- 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 a660bca0f6..0000000000 --- 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 7b2567920e..0000000000 --- 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 a0cb648cf7..0000000000 --- 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 3bbe0638e6..0000000000 --- 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 b97a649320..0000000000 --- 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 1af654d110..0000000000 --- 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 a5d2398833..0000000000 --- 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 418d9db9e2..0000000000 --- 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 2e2a1c315b..0000000000 --- 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 700025efab..0000000000 --- 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 20d1377a01..0000000000 --- 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 0367b1106b..0000000000 --- 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 db98cba162..0000000000 --- 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 9edc36ca66e9ab533d80d32329f9e4b3f272d06b Mon Sep 17 00:00:00 2001 From: Nayan Date: Mon, 24 Nov 2025 11:18:12 -0500 Subject: [PATCH 474/681] 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 a5b9fc70d3..6dd9ceaadc 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 475/681] 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 702854f98a..a020a63b55 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 0000000000..9439c588d3 --- /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 0000000000..081969cf10 --- /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 0000000000..d1d0a661b1 --- /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 0000000000..add437b127 --- /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 0000000000..1f44775bb3 --- /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 0000000000..d57a0de9d8 --- /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 0000000000..bf174de90b --- /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 0000000000..ac67eb9163 --- /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 0000000000..b1e12c17ab --- /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 0000000000..a318cebeb0 --- /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 0000000000..d04816a411 --- /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 0000000000..1036af3e5f --- /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 d3de616cc0..cd383e4bc3 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 0000000000..d579937f3f --- /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 0000000000..1229af846d --- /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 a232ac192d..4880ad58de 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 c67afb45aeb7b0e5e37f48adb8f0c1da1edeaf2f Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Mon, 24 Nov 2025 14:20:20 -0800 Subject: [PATCH 476/681] 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 e69de29bb2..0000000000 diff --git a/selfdrive/modeld/tests/test_modeld.py b/selfdrive/modeld/tests/test_modeld.py deleted file mode 100644 index 6927c9e473..0000000000 --- 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 477/681] 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 330011eb78..26c3f3462d 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 478/681] 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 b59f8bdcca..6171d1a976 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 443fd1851c..0ba38736db 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 cdd4301fcf..4a58e321fc 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 479/681] 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 aab16ffbbe..8d6449cb4b 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 480/681] 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 cd383e4bc3..d9254b58c6 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 481/681] 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 d9254b58c6..cd383e4bc3 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 482/681] 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 f47cd310ca..e3e462c549 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 7271127838..e4850e9cbc 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 b39bf31e9d..481886e37a 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 483/681] 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 cd6ae600ef..c99c8fe12c 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 5d61c1c95a..b19cebb266 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 e3e462c549..2ff94aa52d 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 00ae6a188e..078623c882 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 bbd4aef532..5ab82fd8f5 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 b7f176e24f..cd233aa3ac 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 014fc0c45f..fd4de9a717 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 2e9a8bee3c..cdde40d4a6 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 afc7bfce17..5f20cf7c78 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 b5e0ea838d..1d5e4989ec 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 303d976b07..18731d675e 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 8efb516a42..82a78ce37c 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 eb5555660a..b7f0098073 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 ab55f392f7..64d57e8958 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 a81fbfc440..1e5f99cc66 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 cd383e4bc3..e64398d227 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 28bd58f226..cd809f2699 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 d7395f9b7a..316e6c4a8b 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 484/681] 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 1dc256d467..1ac2c2dcba 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 7697e68b98..5b5e16dde3 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 485/681] 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 bbd4aef532..9b9b51b18c 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 303d976b07..f2f8bcb1cb 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 75238d581a..a452777748 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 486/681] 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 9b9b51b18c..bbd4aef532 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 f2f8bcb1cb..303d976b07 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 a452777748..75238d581a 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 487/681] 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 bbd4aef532..ea83e962e6 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 303d976b07..f2f8bcb1cb 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 75238d581a..a452777748 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 488/681] 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 95858ec1b3..18a63a736d 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 489/681] 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 bf410825f1..fc7f410e40 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 592bf2c4b2..e0f213e02c 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 8ed1c22a63..31e844dd0a 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 fc72b4f9c6..f3adebf4b6 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 f2f8bcb1cb..5cc43322d0 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 490/681] 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 18a63a736d..546c682f33 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 491/681] 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 3f3804ba6a..3f3c8a72bb 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 7a3303a9e9..79ac26f1c6 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 492/681] 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 eb6e75afa2..644c35e0ab 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 493/681] 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 35e2708e62..b6e67d03a0 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 494/681] 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 9a04e84257..3cb6a1e188 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 495/681] 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 9821283cb5..37a46b2096 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 496/681] 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 b6e67d03a0..c90b111de3 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 497/681] 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 eb5555660a..bdf85acc38 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 bb5171d6e3..524eb11637 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 498/681] 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 369055846e..080083c3e2 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 499/681] 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 8d9caadfdd..8677c32f81 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 500/681] 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 b5e0ea838d..988c823a99 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 5cc43322d0..ff75e6f8ed 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 8677c32f81..ab93be9453 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 d7395f9b7a..9792c51e7c 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 3cb6a1e188..72f76d90c4 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 501/681] 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 1f6dffe879..d7c9f27a92 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 502/681] 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 ab55f392f7..f1f4e66f5a 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 4914445415f1b5ae5adc61ff672323a05ec3f8e5 Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 28 Nov 2025 01:02:31 -0500 Subject: [PATCH 503/681] 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 cd233aa3ac..f5f3a4e9c5 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 4880ad58de..b942f79d33 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 dcf8f6019d..4ecb9a4881 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 fa47d35536..71755cc284 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 504/681] 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 3ea12f651e..223f452e16 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 505/681] 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 d3ad2d2419..8af877963e 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 506/681] 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 31c1e0ff11..f988fa9db1 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 507/681] 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 9ca41e5567..0000000000 --- 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 cac37d58cd..0000000000 --- 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 508/681] 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 8af877963e..50456f93d3 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 509/681] 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 0000000000..017dead1bb --- /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 a62c1d153a..2ab46d1695 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 510/681] 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 017dead1bb..8c27d87d4e 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 511/681] 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 b942f79d33..0fb4a5c309 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 4ecb9a4881..955adaa73e 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 0000000000..91e9650ebd --- /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 512/681] 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 0000000000..e69de29bb2 diff --git a/system/ui/sunnypilot/widgets/input_dialog.py b/system/ui/sunnypilot/widgets/input_dialog.py new file mode 100644 index 0000000000..88ab0b1a66 --- /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 513/681] 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 0000000000..76f4243411 --- /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 514/681] 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 1e3713c7ef..a57b6ef55f 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 0000000000..83ec3c7af8 --- /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 0000000000..f4f1fbc4b1 --- /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 0000000000..26bdca42d6 --- /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 0000000000..ac2ef556e6 --- /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 515/681] 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 8c27d87d4e..d085fdf55f 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 516/681] 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 eaaf5a51e9..9d0e5c9f15 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 8caeeaeabc..3f6053d158 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 517/681] 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 0000000000..8d0f6bd59b --- /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 518/681] 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 c90b111de3..432f21e598 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 519/681] 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 d64ab65ef2..950d71319f 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 520/681] 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 6102265a87..9152bdc7fa 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 2ab46d1695..eec16d884f 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 950d71319f..b11056f993 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 432f21e598..fd0516a986 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 521/681] 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 524eb11637..cb9f7c6fcf 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 fd0516a986..91f05c3551 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 522/681] 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 ab93be9453..00ef95cc8b 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 523/681] 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 a4f4c7d09b..7459dc5731 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 524/681] 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 0f425b10da..995c4618f7 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 525/681] 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 cb9f7c6fcf..76b443842e 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 526/681] 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 0000000000..18e35508b7 --- /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 ef0696a22c..c78fdccb59 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 527/681] 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 0fb4a5c309..eb254166eb 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 0000000000..e69de29bb2 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 0000000000..14666d49b6 --- /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 0000000000..aa2904424f --- /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 528/681] 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 c88befd454..d79e7fd2ad 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 529/681] 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 f81cb5573a..79a4dd32ea 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 e3370a5f74..79e68aa67f 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 530/681] 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 58044dc694..fabe635c71 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 ef20670781..c489ecc578 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 531/681] 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 17d0ec1614..4163b33791 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 909ad41e03..1c98bb1e4e 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 391ba45531..8896117694 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 532/681] 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 223f452e16..fcb44154bc 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 463683fd3c..cd352b2ede 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 533/681] 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 c7fe1984c5..c2b880f873 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 89c0601f06..684c7aeb75 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 c4216f5e76..33e9e9912d 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 f988fa9db1..2bf3ba8209 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 534/681] 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 684c7aeb75..71b29a0c4e 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 8db13346e3..2d80c78ff5 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 535/681] 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 fc7f410e40..c496ad2854 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 536/681] 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 b1fa4acc48..fde4f25ffe 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 537/681] 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 3f3c8a72bb..b2acf1c09b 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 538/681] 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 aa2904424f..6458ebf001 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 539/681] 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 c9ab7e7e2b..fb13ead034 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 ee034fb083..f4e865d476 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 540/681] 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 6458ebf001..5685a6c27e 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 541/681] 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 88ab0b1a66..ed67302fcb 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 5685a6c27e..4691f7f8f9 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 542/681] 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 4691f7f8f9..a8947a9b18 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 543/681] 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 a8947a9b18..fd2a576d58 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 544/681] 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 c496ad2854..d6104e7497 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 e0f213e02c..592bf2c4b2 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 31e844dd0a..8ed1c22a63 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 f3adebf4b6..fc72b4f9c6 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 ff75e6f8ed..dcb4b6c84d 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 545/681] 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 0000000000..e69de29bb2 diff --git a/selfdrive/ui/sunnypilot/layouts/__init__.py b/selfdrive/ui/sunnypilot/layouts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selfdrive/ui/sunnypilot/layouts/settings/__init__.py b/selfdrive/ui/sunnypilot/layouts/settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selfdrive/ui/sunnypilot/layouts/settings/vehicle.py b/selfdrive/ui/sunnypilot/layouts/settings/vehicle.py deleted file mode 100644 index d04816a411..0000000000 --- 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 0000000000..23b861f8c3 --- /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 0000000000..60bc335ce4 --- /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 eb254166eb..68e68d41a9 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 955adaa73e..0c87a06a16 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 546/681] 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 fc72b4f9c6..e1ef387bf7 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 dcb4b6c84d..10e52bb3b4 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 547/681] 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 d119a8a832..69236482b0 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 548/681] 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 4163b33791..17d0ec1614 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 1c98bb1e4e..909ad41e03 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 8896117694..391ba45531 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 549/681] 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 2d80c78ff5..15f8a2b793 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 550/681] 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 ee9ee294bb..f5418be81a 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 2bf3ba8209..01c6c5dc47 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 551/681] 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 0c87a06a16..f05de81397 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 552/681] 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 23b861f8c3..1b6c35df2c 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 553/681] 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 1b6c35df2c..86d9e61695 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 0000000000..e69de29bb2 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 0000000000..8d83fdf916 --- /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 0000000000..d1c9ea5d64 --- /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 0000000000..ad62dba56f --- /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 0000000000..678732296f --- /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 0000000000..8871087e03 --- /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 0000000000..edcd17cdb8 --- /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 0000000000..fec68795a6 --- /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 0000000000..f6849eb201 --- /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 0000000000..d354f0f34b --- /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 0000000000..7b3446a1a7 --- /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 0000000000..6b767d332a --- /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 0000000000..876aa2d2ea --- /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 0000000000..66e7ec1d5a --- /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 0000000000..46d536c651 --- /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 0000000000..e061a8a22a --- /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 0000000000..a6d44c5e4d --- /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 60bc335ce4..db90595274 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 554/681] 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 f1f4e66f5a..ab55f392f7 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 555/681] 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 87db7cc636..881a916df7 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 556/681] 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 4b8b7015f8..e0df8f2705 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 557/681] 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 5b5e16dde3..1ed04e705a 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 558/681] 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 8d84cdbd5d..f9a90490b9 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 559/681] 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 15f8a2b793..2d80c78ff5 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 560/681] 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 e6b6a44c40..1e24198267 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 8cf3e8c14c..0b99183193 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 561/681] 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 18e35508b7..1db5ad613e 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 0000000000..4d0b397e03 --- /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 562/681] 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 e64398d227..164358bfce 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 563/681] 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 0000000000..14f573c628 --- /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 bf174de90b..905338552f 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 564/681] 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 8d6449cb4b..aab16ffbbe 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 565/681] 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 68e68d41a9..3597509dc5 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 fd2a576d58..cfc0a5caf1 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 566/681] 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 1e764af9ba..ec451ba736 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 441c4a16af..e5ef27810a 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 567/681] 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 f2fa5e8fe8..9179e9e463 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 568/681] 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 9179e9e463..624a659ebc 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 569/681] 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 79e68aa67f..26e446612d 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 570/681] 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 624a659ebc..e5399b85d3 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 571/681] 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 0000000000..1b6913947b --- /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 e4850e9cbc..60a110293d 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 572/681] 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 995c4618f7..f3e0ef409e 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 573/681] 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 b11056f993..26845765c3 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 546c682f33..efa35bc016 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 574/681] 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 52dbb785d6..a196d20c4a 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 575/681] 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 a196d20c4a..abf772ce58 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 576/681] 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 e5399b85d3..9adb660d8b 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 577/681] 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 8983884b25..7bfd97ad71 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 39d4c749c8..8633b5ac2d 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 0000000000..6161e80e74 --- /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 578/681] 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 3597509dc5..c1f8c3926e 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 f05de81397..315840f7ea 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 579/681] 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 26845765c3..b11056f993 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 efa35bc016..546c682f33 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 580/681] 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 078623c882..8830ef946f 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 4b8b7015f8..c4f899d798 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 c1f8c3926e..3e1d2dc408 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 315840f7ea..bd68d35a48 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 71755cc284..2aeefd5444 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 581/681] 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 4d0b397e03..13b5ad81ec 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 582/681] 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 905338552f..45c9b93483 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 b1e12c17ab..7125fdf3f3 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 164358bfce..e209ab8060 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 0000000000..4fab79caf1 --- /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 583/681] 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 add437b127..43f0d25b47 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 1db5ad613e..af625ee619 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 c236643a06..2d3d670bfd 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 584/681] 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 7125fdf3f3..316d4d2240 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 4fab79caf1..af6b9cf45d 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 585/681] 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 615009cf0f..1ffad74f88 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 586/681] 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 91f05c3551..97b293083d 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 587/681] 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 bd68d35a48..ac646df5bf 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 588/681] 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 46b390f773..2924aec2b4 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 589/681] 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 cfc0a5caf1..27a978153e 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 590/681] 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 27a978153e..2dd5e3a2dc 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 591/681] 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 45c9b93483..4de2cacafd 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 0000000000..b890dd5b5b --- /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 592/681] 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 5a3e891b87..f12aebaeb3 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 593/681] 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 eb0af5b64a..b5a68c6b26 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 3e9e452b90..aba25dcf83 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 46e7f546b0..f87b1276c5 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 d2b25bc5f2..134d88a919 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 4eda46f37b..35492c8efa 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 6df164b442..0fe1535c7a 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 d65fc5b760..2d070acff6 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 9bc94c090f..1da59f93e3 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 cccc9b6d9a..e7b1129a30 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 e75c519ac7..7ab50d1494 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 594/681] 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 2d070acff6..1ea3733ed0 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 1da59f93e3..92c2714ae7 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 fb13ead034..cc105dd10e 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 595/681] 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 a4f3677ff3..4d43df2da8 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 596/681] 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 0000000000..cf78fb5a33 --- /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 b0674dee82..47e673ce89 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 597/681] 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 699a0f0bd3..c4894f2711 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 598/681] 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 0000000000..707825b1ac --- /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 5ca020424c..c5802b5cb2 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 b2acf1c09b..b45a808f1d 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 d926a7ae86..98f2a5e8ce 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 0000000000..be7af5438a --- /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 0000000000..44df75fa5d --- /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 c34a6d9b71..650220c875 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 599/681] 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 76b443842e..7f489ccf98 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 a2459c27e2..79f150deea 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 600/681] 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 caf7871573..0d93b70465 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 601/681] 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 44df75fa5d..cbb2d606e0 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 602/681] 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 38be4ecca4..62c26ca809 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 7cd9e97be5..28d6b3310c 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 6f7e658f48..05d58f03c6 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 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 603/681] 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 01c6c5dc47..c791444f74 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 604/681] 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 b45a808f1d..d5dc95e1b1 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 650220c875..b179517e0b 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 605/681] 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 ab55f392f7..71ca03cccf 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 606/681] 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 cbb2d606e0..9da157660e 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 607/681] 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 27cc17624e..b9506d0806 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 f3e0ef409e..89a4926ce9 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 881a916df7..5443948465 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 608/681] 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 5319d286b0..9e166a44d7 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 609/681] 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 8a0c066942..d59b45efcb 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 610/681] 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 1ed04e705a..3377ce6c68 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 611/681] 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 cdbaca32cf..c2db249cf2 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 e0a9a4f6a6..fd7efcee90 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 612/681] 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 0000000000..04a76d054e --- /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 613/681] 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 656c05ed59..7ea278038d 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 50c4f5408f..3b71a9c31f 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 81aee1ebf9..405b2423f2 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 614/681] 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 c6a4b12e63..1cac16adcd 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 615/681] 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 bf4588a40c..2a3ac8b861 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 616/681] 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 ec451ba736..1e764af9ba 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 e5ef27810a..441c4a16af 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 617/681] 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 ee0eb48684..57e968d02f 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 0fd45940d8..82eb099e7e 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 9cf321a1b6..a099facd15 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 a581185371..ce6625a1f0 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 618/681] 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 bdf85acc38..7ee83ff880 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 619/681] 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 43f0d25b47..16d406ea98 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 0000000000..09230da14b --- /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 620/681] 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 d57a0de9d8..3726a820f5 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 1211c1ecc6..9304f8f0b7 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 621/681] 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 0000000000..259067723e --- /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 622/681] 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 1e764af9ba..e0eb918125 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 441c4a16af..76c96670a9 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 623/681] 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 eec16d884f..18c4dd5d6d 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 624/681] 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 0000000000..f3a2433373 --- /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 4de2cacafd..103dd99efd 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 83ec3c7af8..4cc6b18089 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 0dd59d04047b8bf52b9e07e25b93bedcee6b42e9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 13 Dec 2025 01:32:06 -0800 Subject: [PATCH 625/681] 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 d085fdf55f..1faf49311a 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 626/681] 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 18c4dd5d6d..99e41431e1 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 627/681] 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 72f76d90c4..4858569d2f 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 628/681] 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 546c682f33..097ac74c7e 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 629/681] 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 28bd58f226..7e5f04ef6f 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 630/681] 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 b11056f993..e445c0c9c7 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 631/681] 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 99e41431e1..5a79b8a639 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 e445c0c9c7..2118b62ed6 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 632/681] 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 5a79b8a639..3129cd791b 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 633/681] 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 3129cd791b..ba9da7aaac 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 2118b62ed6..b5374791e8 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 4858569d2f..2074de00b0 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 634/681] 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 ba9da7aaac..9f0c134406 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 635/681] 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 9f0c134406..7137169784 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 636/681] 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 097ac74c7e..a3fed6d962 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 2074de00b0..f33ba941bf 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 637/681] 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 7137169784..ed5454d8e0 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 b5374791e8..4021a11c23 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 638/681] 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 00ef95cc8b..0859071dac 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 639/681] 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 ed5454d8e0..793bdcf4a0 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 4021a11c23..3d9aa3f9e2 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 640/681] 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 50456f93d3..dacbacbe26 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 641/681] 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 e5a7a3ffde..0000000000 --- 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 9c2fd8d2be4eff9ffcf31f05c5967d8f9573623e Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 14 Dec 2025 22:44:51 -0500 Subject: [PATCH 642/681] 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 16d406ea98..211aad6a22 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 3e1d2dc408..84fe4efb04 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 ac646df5bf..b79ed92718 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 643/681] 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 84fe4efb04..7a29b5bb13 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 b79ed92718..2d7239ae66 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 644/681] 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 38bb7435e2..7ae393bb5b 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 0000000000..af610be75d --- /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 0ae0b35359..ae8a1974ac 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 9d9e5aa02db229c56538301bcac69bc8fc988de8 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 15 Dec 2025 15:36:28 -0800 Subject: [PATCH 645/681] 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 673a5bc1d0..789dad5623 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 646/681] 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 fcb44154bc..e0d61cd415 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 6171d1a976..4bd6ffea21 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 5868ca4c1c..59e1ae054e 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 647/681] 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 686771e284..3a6432c84d 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 edeca412ce..f7269b79ce 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 1dab652386..5e98e12243 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 648/681] 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 4bd6ffea21..39773a987e 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 9e166a44d7..6210983d97 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 649/681] 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 a16cf1f608..92999f6bc1 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 b9506d0806..972f09be30 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 650/681] 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 b03285f80a..0ad846f0f4 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 7f7e2795c4..dfeeb807ee 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 f7269b79ce..e7350aceac 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 92999f6bc1..345878d914 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 0000000000..69015a6be2 --- /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 a76cbc46e3..2fd4a4def2 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 972f09be30..ed3fca5fb0 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 21de1ff33f..47da321024 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 1cac16adcd..9703ac2f5f 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 6de63dfbbc..462dd7a361 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 651/681] 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 8efb516a42..c16504fac8 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 652/681] 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 207f8477d7..b19ce1472b 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 345878d914..6abe47bc98 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 653/681] 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 9152bdc7fa..af97de92f4 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 a3fed6d962..5b3b4a691c 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 654/681] 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 a9b1683c51..c6dbcccbee 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 655/681] 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 ed3fca5fb0..f57751c067 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 656/681] 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 fabe635c71..5f2a3459a0 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 657/681] Update RELEASES.md --- RELEASES.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 5f2a3459a0..618b28dc5a 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 658/681] 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 abf772ce58..29bc55c9c8 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 9adb660d8b..af7fc33a47 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 ef0696a22c..30a6565095 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 9792c51e7c..84be5bc3da 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 659/681] 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 26e446612d..719d38284d 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 660/681] 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 29bc55c9c8..81109fb25b 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 af7fc33a47..150e436569 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 080083c3e2..3862c299a9 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 661/681] 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 4d83ed5cd9..04ffc24356 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 7aa7f0542a..540b2029a0 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 0000000000..92993e3e00 --- /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 0000000000..53a837afbe --- /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 0000000000..74cce9d975 --- /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 0000000000..da8bb3eefd --- /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 81109fb25b..d358813411 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 150e436569..bab3d6e6f1 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 3862c299a9..356d7ac832 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 84be5bc3da..fda35be6eb 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 34b2a51a42..9c0ea75b42 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 b17d8f3b7c..455cdeef71 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 662/681] 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 793bdcf4a0..374539c4ce 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 663/681] 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 543ea35e81..f69ad8c49c 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 664/681] 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 af97de92f4..9152bdc7fa 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 5b3b4a691c..a3fed6d962 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 665/681] 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 719d38284d..501a7ff371 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 fda35be6eb..2c6090b4ac 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 666/681] 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 c5802b5cb2..f0028841a0 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 667/681] lil more release notes --- RELEASES.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 618b28dc5a..3028351400 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 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 668/681] 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 1890dbf299..a76d28a231 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 48d174bfa0..47d105f993 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 4a58e321fc..cdd4301fcf 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 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 669/681] 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 666bd0d27d..a2fd5f7dce 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 211aad6a22..5820b34cc0 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 3726a820f5..f8a3a85042 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 670/681] 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 e7a9663743..10794bf0f7 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 671/681] 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 a5f0ae29f2..1b06711a95 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 0000000000..5e25f6da9a --- /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 a57b6ef55f..3aeacf6a39 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 3228fd0d62..707e0620f9 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 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 672/681] 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 b534b7e37e..83114ac551 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 b52d0df6e3bf0a155656add339c22584e9ab33a5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 18 Dec 2025 02:08:20 -0500 Subject: [PATCH 673/681] 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 707825b1ac..5552586529 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 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 674/681] [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 1f202141c3..a47c04053d 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 0000000000..e69de29bb2 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 0000000000..14a224ae77 --- /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 0000000000..e8daca8868 --- /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 0000000000..33582df191 --- /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 af625ee619..8a0bc24ad9 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 57e7c0b2c1cee42b7e56ee0420714d048fc8c6df Mon Sep 17 00:00:00 2001 From: Nayan Date: Thu, 18 Dec 2025 23:15:52 -0500 Subject: [PATCH 675/681] [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 b52f9ed39a..a83ebd1969 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 0000000000..e69de29bb2 diff --git a/selfdrive/ui/sunnypilot/mici/layouts/settings.py b/selfdrive/ui/sunnypilot/mici/layouts/settings.py new file mode 100644 index 0000000000..c6a2d58257 --- /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 0000000000..2ab035c1cf --- /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 0000000000..e69de29bb2 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 0000000000..e2cef2fa07 --- /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 5578b7e7540e194b36e78a8033aa7964875d3af7 Mon Sep 17 00:00:00 2001 From: royjr Date: Fri, 19 Dec 2025 14:36:10 -0500 Subject: [PATCH 676/681] 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 a5c95470f5..54699eab54 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 3f1badfe84..db316aa636 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 d7c9f27a92..c8485a3101 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 a47c04053d..1d66379f93 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 0000000000..e69de29bb2 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 0000000000..4a1aa92241 --- /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 0000000000..5a718947cf --- /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 0000000000..0a5739cc00 --- /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 8a0bc24ad9..aca0644c1d 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 378731390a..f44dc152bb 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 677/681] 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 68dd5617a0..14fa6e1bd5 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 678/681] 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 1b3c623fc4..cc38476041 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 1734a7efcf..a81a13b2c7 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 679/681] 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 3aeacf6a39..9f70aead59 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 0000000000..616bff037e --- /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 680/681] [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 b9f601f8fb..bf38f8e553 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 0000000000..214a855fb3 --- /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 0000000000..cd76261f89 --- /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 aca0644c1d..2e817090b4 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 681/681] [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 bf38f8e553..cae9765341 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 0000000000..a8a342c129 --- /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 214a855fb3..5d78997662 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 2e817090b4..fe90e3bdfa 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")