From 713c02cc3f2fdf99bcf5d6080223410245496d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Fri, 27 Jun 2025 22:28:59 -0700 Subject: [PATCH 01/74] Torque control: more integrator (#35610) * Torque control: more integrator * bump opendbc * update ref --- opendbc_repo | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index bedcb89664..fb5ac6d304 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit bedcb896644528aed6af448e63eeadb3dd8b2c77 +Subproject commit fb5ac6d304fb0688763f20868039e4d8444d5dd0 diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index f839238098..87a679cfba 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -f440c9e0469d32d350aa99ddaa8f44591a2ce690 \ No newline at end of file +f6e0fd7806bc903b5f82123eccaf42e2b9a38891 \ No newline at end of file From 0f1a9d5c8ccaec3916aa801f9ef02d82bd1af27f Mon Sep 17 00:00:00 2001 From: Andrei Radulescu Date: Sat, 28 Jun 2025 21:30:13 +0300 Subject: [PATCH 02/74] webcam: changes for comma zero (#35464) * Revert "webcam: remove other cv2 usage (#33236)" This reverts commit 0cade540158f1dadfb045f9fb40b06b7c48c545d. * Revert "remove cv2 usage (#33101)" This reverts commit 144e9e271c155c3a7702f62445ad2303ae057c50. * Revert "remove opencv-python-headless (#33082)" This reverts commit 488e08507a6a86921d9ef4546a36e3d6ef85a8f8. * webcam: set width, height, fps; flip image; use av reformat * 1280x720 @ 25fps --- tools/webcam/camera.py | 9 +++++++++ tools/webcam/camerad.py | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/webcam/camera.py b/tools/webcam/camera.py index 45f9379038..7926766c6f 100644 --- a/tools/webcam/camera.py +++ b/tools/webcam/camera.py @@ -11,7 +11,14 @@ class Camera: self.stream_type = stream_type self.cur_frame_id = 0 + print(f"Opening {cam_type_state} at {camera_id}") + self.cap = cv.VideoCapture(camera_id) + + self.cap.set(cv.CAP_PROP_FRAME_WIDTH, 1280.0) + self.cap.set(cv.CAP_PROP_FRAME_HEIGHT, 720.0) + self.cap.set(cv.CAP_PROP_FPS, 25.0) + self.W = self.cap.get(cv.CAP_PROP_FRAME_WIDTH) self.H = self.cap.get(cv.CAP_PROP_FRAME_HEIGHT) @@ -25,6 +32,8 @@ class Camera: ret, frame = self.cap.read() if not ret: break + # Rotate the frame 180 degrees (flip both axes) + frame = cv.flip(frame, -1) yuv = Camera.bgr2nv12(frame) yield yuv.data.tobytes() self.cap.release() diff --git a/tools/webcam/camerad.py b/tools/webcam/camerad.py index cb096dc00b..f1e70d948a 100755 --- a/tools/webcam/camerad.py +++ b/tools/webcam/camerad.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import threading import os +import platform from collections import namedtuple from msgq.visionipc import VisionIpcServer, VisionStreamType @@ -30,8 +31,7 @@ class Camerad: self.cameras = [] for c in CAMERAS: - cam_device = f"/dev/video{c.cam_id}" - print(f"opening {c.msg_name} at {cam_device}") + cam_device = f"/dev/video{c.cam_id}" if platform.system() != "Darwin" else c.cam_id cam = Camera(c.msg_name, c.stream_type, cam_device) self.cameras.append(cam) self.vipc_server.create_buffers(c.stream_type, 20, cam.W, cam.H) From 9e2fc078cb1fbcfcbf8a25813da4eb943112ec4e Mon Sep 17 00:00:00 2001 From: Andrei Radulescu Date: Sun, 29 Jun 2025 18:13:28 +0300 Subject: [PATCH 03/74] remove thneed from gitignore (#35615) --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 40438f5fd0..ac4021529b 100644 --- a/.gitignore +++ b/.gitignore @@ -70,8 +70,6 @@ flycheck_* cppcheck_report.txt comma*.sh -selfdrive/modeld/thneed/compile -selfdrive/modeld/models/*.thneed selfdrive/modeld/models/*.pkl *.bz2 From c3c5992f88bccfec377c0470bc35b5b0ee2d3739 Mon Sep 17 00:00:00 2001 From: eFini Date: Mon, 30 Jun 2025 05:37:51 +0800 Subject: [PATCH 04/74] modeld: avoid using USB GPU on a AMD laptop (#35602) modeld: avoid using usb GPU if 'USBGPU' is not in os.environ Co-authored-by: Adeeb Shihadeh --- selfdrive/modeld/SConscript | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 545fcdcff7..0da3c24e19 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -56,17 +56,15 @@ for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: tg_compile(flags, model_name) # Compile BIG model if USB GPU is available -import subprocess -from tinygrad import Device - -# because tg doesn't support multi-process -devs = subprocess.check_output('python3 -c "from tinygrad import Device; print(list(Device.get_available_devices()))"', shell=True, cwd=env.Dir('#').abspath) -if b"AMD" in devs: - del Device - print("USB GPU detected... building") - flags = "AMD=1 AMD_IFACE=USB AMD_LLVM=1 NOLOCALS=0 IMAGE=0" - bp = tg_compile(flags, "big_driving_policy") - bv = tg_compile(flags, "big_driving_vision") - lenv.SideEffect('lock', [bp, bv]) # tg doesn't support multi-process so build serially -else: - print("USB GPU not detected... skipping") +if "USBGPU" in os.environ: + import subprocess + # because tg doesn't support multi-process + devs = subprocess.check_output('python3 -c "from tinygrad import Device; print(list(Device.get_available_devices()))"', shell=True, cwd=env.Dir('#').abspath) + if b"AMD" in devs: + print("USB GPU detected... building") + flags = "AMD=1 AMD_IFACE=USB AMD_LLVM=1 NOLOCALS=0 IMAGE=0" + bp = tg_compile(flags, "big_driving_policy") + bv = tg_compile(flags, "big_driving_vision") + lenv.SideEffect('lock', [bp, bv]) # tg doesn't support multi-process so build serially + else: + print("USB GPU not detected... skipping") From 41f95dc5815e42e6bc884c0ace42084f178dc558 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 30 Jun 2025 09:09:24 -0700 Subject: [PATCH 05/74] separate stale thresholds for draft PRs --- .github/workflows/stale.yaml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index d0dead5126..581f915f1d 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -7,6 +7,7 @@ on: env: DAYS_BEFORE_PR_CLOSE: 2 DAYS_BEFORE_PR_STALE: 9 + DAYS_BEFORE_PR_STALE: 30 jobs: stale: @@ -24,6 +25,28 @@ jobs: exempt-pr-labels: "ignore stale,needs testing" # if wip or it needs testing from the community, don't mark as stale days-before-pr-stale: ${{ env.DAYS_BEFORE_PR_STALE }} days-before-pr-close: ${{ env.DAYS_BEFORE_PR_CLOSE }} + exempt-draft-pr: false + + # issue config + days-before-issue-stale: -1 # ignore issues for now + + # same as above, but give draft PRs more time + stale_drafts: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + exempt-all-milestones: true + + # pull request config + stale-pr-message: 'This PR has had no activity for ${{ env.DAYS_BEFORE_PR_STALE_DRAFT }} days. It will be automatically closed in ${{ env.DAYS_BEFORE_PR_CLOSE }} days if there is no activity.' + close-pr-message: 'This PR has been automatically closed due to inactivity. Feel free to re-open once activity resumes.' + stale-pr-label: stale + delete-branch: ${{ github.event.pull_request.head.repo.full_name == 'commaai/openpilot' }} # only delete branches on the main repo + exempt-pr-labels: "ignore stale,needs testing" # if wip or it needs testing from the community, don't mark as stale + days-before-pr-stale: ${{ env.DAYS_BEFORE_PR_STALE_DRAFT }} + days-before-pr-close: ${{ env.DAYS_BEFORE_PR_CLOSE }} + exempt-draft-pr: true # issue config days-before-issue-stale: -1 # ignore issues for now From 082f4c0aee65ed59d9692f6484f3bd618f79c57f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 30 Jun 2025 11:50:34 -0700 Subject: [PATCH 06/74] micd: deprecate unused field (#35618) deprecate --- cereal/log.capnp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index c569eeeaf8..4dc6f103f3 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2476,7 +2476,8 @@ struct Microphone { # uncalibrated, A-weighted soundPressureWeighted @3 :Float32; soundPressureWeightedDb @1 :Float32; - filteredSoundPressureWeightedDb @2 :Float32; + + filteredSoundPressureWeightedDbDEPRECATED @2 :Float32; } struct Touch { From dcd56ae09a672e4edc8b0b0fd4040b9b913a4eae Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:42:21 -0700 Subject: [PATCH 07/74] store mic audio with toggle (#35595) * store/send mic audio with toggle * script to extract audio from logs * change description and add translation placeholders * microphone icon * apply toggle in loggerd * add legnth and counter * startFrameIdx counter * Revert "change description and add translation placeholders" This reverts commit 7baa1f6de99c6ebe9f9906193da7e83dad79511a. * send mic data first and then calc * restore changed description/icon after revert * adjust fft samples to keep old time window * remove extract_audio.py since audio is now stored in qcam isntead of rlog * qt microphone recording icon * Revert "remove extract_audio.py since audio is now stored in qcam isntead of rlog" This reverts commit 7a3a75bd8db5376d1e442a3ba931c67550b5f132. * move extract_audio script and output file by default * remove length field * recording indicator swaps sides based on lhd/rhd * use record icon from comma body * Update toggle description Co-authored-by: Adeeb Shihadeh * update raylib toggle desc cause I did earlier * microphone --> soundPressure, audioData --> rawAudioData * cleanup unused var * update README * sidebar mic indicator instead of annotated camera * improve logic readability * remove startFrameIdx and sequenceNum * use Q_PROPERTY/setProperty so that update() is actually called on value change * specify old id for SoundPressure * fix typo --------- Co-authored-by: Adeeb Shihadeh --- README.md | 2 +- cereal/log.capnp | 10 ++- cereal/services.py | 3 +- common/params_keys.h | 1 + selfdrive/assets/icons/microphone.png | 3 + selfdrive/ui/layouts/settings/toggles.py | 7 +++ selfdrive/ui/qt/offroad/settings.cc | 7 +++ selfdrive/ui/qt/sidebar.cc | 22 ++++++- selfdrive/ui/qt/sidebar.h | 6 +- selfdrive/ui/soundd.py | 6 +- selfdrive/ui/ui.cc | 3 + selfdrive/ui/ui.h | 2 +- system/loggerd/loggerd.cc | 26 ++++---- system/micd.py | 24 +++++--- tools/scripts/extract_audio.py | 77 ++++++++++++++++++++++++ 15 files changed, 165 insertions(+), 34 deletions(-) create mode 100644 selfdrive/assets/icons/microphone.png create mode 100755 tools/scripts/extract_audio.py diff --git a/README.md b/README.md index 250f9d36d5..17238b5c3f 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ By default, openpilot uploads the driving data to our servers. You can also acce openpilot is open source software: the user is free to disable data collection if they wish to do so. openpilot logs the road-facing cameras, CAN, GPS, IMU, magnetometer, thermal sensors, crashes, and operating system logs. -The driver-facing camera is only logged if you explicitly opt-in in settings. The microphone is not recorded. +The driver-facing camera and microphone are only logged if you explicitly opt-in in settings. By using openpilot, you agree to [our Privacy Policy](https://comma.ai/privacy). You understand that use of this software or its related services will generate certain types of user data, which may be logged and stored at the sole discretion of comma. By accepting this agreement, you grant an irrevocable, perpetual, worldwide right to comma for the use of this data. diff --git a/cereal/log.capnp b/cereal/log.capnp index 4dc6f103f3..a754b9ef00 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2470,7 +2470,7 @@ struct DebugAlert { struct UserFlag { } -struct Microphone { +struct SoundPressure @0xdc24138990726023 { soundPressure @0 :Float32; # uncalibrated, A-weighted @@ -2480,6 +2480,11 @@ struct Microphone { filteredSoundPressureWeightedDbDEPRECATED @2 :Float32; } +struct AudioData { + data @0 :Data; + sampleRate @1 :UInt32; +} + struct Touch { sec @0 :Int64; usec @1 :Int64; @@ -2557,7 +2562,8 @@ struct Event { livestreamDriverEncodeIdx @119 :EncodeIndex; # microphone data - microphone @103 :Microphone; + soundPressure @103 :SoundPressure; + rawAudioData @147 :AudioData; # systems stuff androidLog @20 :AndroidLogEntry; diff --git a/cereal/services.py b/cereal/services.py index 82fc04bd00..a13fc810f4 100755 --- a/cereal/services.py +++ b/cereal/services.py @@ -73,7 +73,8 @@ _services: dict[str, tuple] = { "navThumbnail": (True, 0.), "qRoadEncodeIdx": (False, 20.), "userFlag": (True, 0., 1), - "microphone": (True, 10., 10), + "soundPressure": (True, 10., 10), + "rawAudioData": (False, 20.), # debug "uiDebug": (True, 0., 1), diff --git a/common/params_keys.h b/common/params_keys.h index 3fd4e1b6ab..fa6146f3c9 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -99,6 +99,7 @@ inline static std::unordered_map keys = { {"PandaSomResetTriggered", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"PandaSignatures", CLEAR_ON_MANAGER_START}, {"PrimeType", PERSISTENT}, + {"RecordAudio", PERSISTENT}, {"RecordFront", PERSISTENT}, {"RecordFrontLock", PERSISTENT}, // for the internal fleet {"SecOCKey", PERSISTENT | DONT_LOG}, diff --git a/selfdrive/assets/icons/microphone.png b/selfdrive/assets/icons/microphone.png new file mode 100644 index 0000000000..6cb9cc0254 --- /dev/null +++ b/selfdrive/assets/icons/microphone.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9fc1f7f31d41f26ea7d6f52b3096f7a91844a3b897bc233a8489253c46f0403b +size 6324 diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 5c17082769..a5e383cc02 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -22,6 +22,7 @@ DESCRIPTIONS = { "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.", } @@ -77,6 +78,12 @@ class TogglesLayout(Widget): toggle_item( "Use Metric System", DESCRIPTIONS["IsMetric"], self._params.get_bool("IsMetric"), icon="monitoring.png" ), + toggle_item( + "Record Microphone Audio", + DESCRIPTIONS["RecordAudio"], + self._params.get_bool("RecordAudio"), + icon="microphone.png", + ), ] self._scroller = Scroller(items, line_separator=True, spacing=0) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index d6338063f3..788672822a 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -68,6 +68,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { "../assets/icons/metric.png", false, }, + { + "RecordAudio", + tr("Record 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, + }, }; diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index 966396edc2..e3b83573da 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -24,10 +24,11 @@ void Sidebar::drawMetric(QPainter &p, const QPair &label, QCol 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) { +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)); connect(this, &Sidebar::valueChanged, [=] { update(); }); @@ -47,12 +48,15 @@ void Sidebar::mousePressEvent(QMouseEvent *event) { } 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) { - flag_pressed = settings_pressed = false; + if (flag_pressed || settings_pressed || mic_indicator_pressed) { + flag_pressed = settings_pressed = mic_indicator_pressed = false; update(); } if (onroad && home_btn.contains(event->pos())) { @@ -61,6 +65,8 @@ void Sidebar::mouseReleaseEvent(QMouseEvent *event) { pm->send("userFlag", msg); } else if (settings_btn.contains(event->pos())) { emit openSettings(); + } else if (recording_audio && mic_indicator_btn.contains(event->pos())) { + emit openSettings(2, "RecordAudio"); } } @@ -106,6 +112,8 @@ void Sidebar::updateState(const UIState &s) { pandaStatus = {{tr("NO"), tr("PANDA")}, danger_color}; } setProperty("pandaStatus", QVariant::fromValue(pandaStatus)); + + setProperty("recordingAudio", s.scene.recording_audio); } void Sidebar::paintEvent(QPaintEvent *event) { @@ -120,6 +128,14 @@ void Sidebar::paintEvent(QPaintEvent *event) { 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 diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index 2091418e52..f05a4ab201 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -18,6 +18,7 @@ class Sidebar : public QFrame { 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); @@ -36,8 +37,8 @@ protected: void mouseReleaseEvent(QMouseEvent *event) override; void drawMetric(QPainter &p, const QPair &label, QColor c, int y); - QPixmap home_img, flag_img, settings_img; - bool onroad, flag_pressed, settings_pressed; + QPixmap home_img, flag_img, settings_img, mic_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")}, @@ -50,6 +51,7 @@ protected: 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); diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index 8b5fc8bd04..13f3b22091 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -135,7 +135,7 @@ class Soundd: # sounddevice must be imported after forking processes import sounddevice as sd - sm = messaging.SubMaster(['selfdriveState', 'microphone']) + sm = messaging.SubMaster(['selfdriveState', 'soundPressure']) with self.get_stream(sd) as stream: rk = Ratekeeper(20) @@ -144,8 +144,8 @@ class Soundd: while True: sm.update(0) - if sm.updated['microphone'] and self.current_alert == AudibleAlert.none: # only update volume filter when not playing alert - self.spl_filter_weighted.update(sm["microphone"].soundPressureWeightedDb) + if sm.updated['soundPressure'] and self.current_alert == AudibleAlert.none: # only update volume filter when not playing alert + self.spl_filter_weighted.update(sm["soundPressure"].soundPressureWeightedDb) self.current_volume = self.calculate_volume(float(self.spl_filter_weighted.x)) self.get_audible_alert(sm) diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 79a245a0e7..4f8bd7ddfc 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -60,6 +60,9 @@ static void update_state(UIState *s) { 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) { diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index fd2aee771e..b3c482aafe 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -58,7 +58,7 @@ typedef struct UIScene { cereal::LongitudinalPersonality personality; float light_sensor = -1; - bool started, ignition, is_metric; + bool started, ignition, is_metric, recording_audio; uint64_t started_frame; } UIScene; diff --git a/system/loggerd/loggerd.cc b/system/loggerd/loggerd.cc index 2d2d4640ed..898216e5b6 100644 --- a/system/loggerd/loggerd.cc +++ b/system/loggerd/loggerd.cc @@ -226,19 +226,21 @@ void loggerd_thread() { for (const auto& [_, it] : services) { const bool encoder = util::ends_with(it.name, "EncodeData"); const bool livestream_encoder = util::starts_with(it.name, "livestream"); - if (!it.should_log && (!encoder || livestream_encoder)) continue; - LOGD("logging %s", it.name.c_str()); + const bool record_audio = (it.name == "rawAudioData") && Params().getBool("RecordAudio"); + if (it.should_log || (encoder && !livestream_encoder) || record_audio) { + LOGD("logging %s", it.name.c_str()); - SubSocket * sock = SubSocket::create(ctx.get(), it.name); - assert(sock != NULL); - poller->registerSocket(sock); - service_state[sock] = { - .name = it.name, - .counter = 0, - .freq = it.decimation, - .encoder = encoder, - .user_flag = it.name == "userFlag", - }; + SubSocket * sock = SubSocket::create(ctx.get(), it.name); + assert(sock != NULL); + poller->registerSocket(sock); + service_state[sock] = { + .name = it.name, + .counter = 0, + .freq = it.decimation, + .encoder = encoder, + .user_flag = it.name == "userFlag", + }; + } } LoggerdState s; diff --git a/system/micd.py b/system/micd.py index 38f3225f55..02ef82390b 100755 --- a/system/micd.py +++ b/system/micd.py @@ -9,10 +9,10 @@ from openpilot.common.retry import retry from openpilot.common.swaglog import cloudlog RATE = 10 -FFT_SAMPLES = 4096 +FFT_SAMPLES = 1600 # 100ms REFERENCE_SPL = 2e-5 # newtons/m^2 -SAMPLE_RATE = 44100 -SAMPLE_BUFFER = 4096 # approx 100ms +SAMPLE_RATE = 16000 +SAMPLE_BUFFER = 800 # 50ms @cache @@ -45,7 +45,7 @@ def apply_a_weighting(measurements: np.ndarray) -> np.ndarray: class Mic: def __init__(self): self.rk = Ratekeeper(RATE) - self.pm = messaging.PubMaster(['microphone']) + self.pm = messaging.PubMaster(['soundPressure', 'rawAudioData']) self.measurements = np.empty(0) @@ -61,12 +61,12 @@ class Mic: sound_pressure_weighted = self.sound_pressure_weighted sound_pressure_level_weighted = self.sound_pressure_level_weighted - msg = messaging.new_message('microphone', valid=True) - msg.microphone.soundPressure = float(sound_pressure) - msg.microphone.soundPressureWeighted = float(sound_pressure_weighted) - msg.microphone.soundPressureWeightedDb = float(sound_pressure_level_weighted) + msg = messaging.new_message('soundPressure', valid=True) + msg.soundPressure.soundPressure = float(sound_pressure) + msg.soundPressure.soundPressureWeighted = float(sound_pressure_weighted) + msg.soundPressure.soundPressureWeightedDb = float(sound_pressure_level_weighted) - self.pm.send('microphone', msg) + self.pm.send('soundPressure', msg) self.rk.keep_time() def callback(self, indata, frames, time, status): @@ -76,6 +76,12 @@ class Mic: Logged A-weighted equivalents are rough approximations of the human-perceived loudness. """ + msg = messaging.new_message('rawAudioData', valid=True) + audio_data_int_16 = (indata[:, 0] * 32767).astype(np.int16) + msg.rawAudioData.data = audio_data_int_16.tobytes() + msg.rawAudioData.sampleRate = SAMPLE_RATE + self.pm.send('rawAudioData', msg) + with self.lock: self.measurements = np.concatenate((self.measurements, indata[:, 0])) diff --git a/tools/scripts/extract_audio.py b/tools/scripts/extract_audio.py new file mode 100755 index 0000000000..2a6ad9f9e3 --- /dev/null +++ b/tools/scripts/extract_audio.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +import os +import sys +import wave +import argparse +import numpy as np + +from openpilot.tools.lib.logreader import LogReader, ReadMode + + +def extract_audio(route_or_segment_name, output_file=None, play=False): + lr = LogReader(route_or_segment_name, default_mode=ReadMode.AUTO_INTERACTIVE) + audio_messages = list(lr.filter("rawAudioData")) + if not audio_messages: + print("No rawAudioData messages found in logs") + return + sample_rate = audio_messages[0].sampleRate + + audio_chunks = [] + total_frames = 0 + for msg in audio_messages: + audio_array = np.frombuffer(msg.data, dtype=np.int16) + audio_chunks.append(audio_array) + total_frames += len(audio_array) + full_audio = np.concatenate(audio_chunks) + + print(f"Found {total_frames} frames from {len(audio_messages)} audio messages at {sample_rate} Hz") + + if output_file: + if write_wav_file(output_file, full_audio, sample_rate): + print(f"Audio written to {output_file}") + else: + print("Audio extraction canceled.") + if play: + play_audio(full_audio, sample_rate) + + +def write_wav_file(filename, audio_data, sample_rate): + if os.path.exists(filename): + if input(f"File '{filename}' exists. Overwrite? (y/N): ").lower() not in ['y', 'yes']: + return False + + with wave.open(filename, 'wb') as wav_file: + wav_file.setnchannels(1) # Mono + wav_file.setsampwidth(2) # 16-bit + wav_file.setframerate(sample_rate) + wav_file.writeframes(audio_data.tobytes()) + return True + + +def play_audio(audio_data, sample_rate): + try: + import sounddevice as sd + + print("Playing audio... Press Ctrl+C to stop") + sd.play(audio_data, sample_rate) + sd.wait() + except KeyboardInterrupt: + print("\nPlayback stopped") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Extract audio data from openpilot logs") + parser.add_argument("-o", "--output", help="Output WAV file path") + parser.add_argument("--play", action="store_true", help="Play audio with sounddevice") + parser.add_argument("route_or_segment_name", nargs='?', help="The route or segment name") + + if len(sys.argv) == 1: + parser.print_help() + sys.exit() + args = parser.parse_args() + + output_file = args.output + if not args.output and not args.play: + output_file = "extracted_audio.wav" + + extract_audio(args.route_or_segment_name.strip(), output_file, args.play) From 08be179b8f9d641315087a987a88e3eb5eb460bf Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 30 Jun 2025 13:44:02 -0700 Subject: [PATCH 08/74] update release notes --- RELEASES.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 91652d6dc7..e4ed90c171 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,9 @@ -Version 0.9.10 (2025-06-30) +Version 0.9.10 (2025-07-07) ======================== +* New driving model + * Lead car ground-truth fixes + * Ported over VAE from the MLSIM stack +* Opt-in audio recording for dashcam video Version 0.9.9 (2025-05-23) ======================== From f79f7b6584c3df41d5d3c2d67df1ae5aef2b0544 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 30 Jun 2025 14:50:25 -0700 Subject: [PATCH 09/74] sensord: fix temp scale (#35621) --- system/sensord/sensors/lsm6ds3_temp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/sensord/sensors/lsm6ds3_temp.py b/system/sensord/sensors/lsm6ds3_temp.py index 7d7b1c2ce1..b9bb9fe3da 100644 --- a/system/sensord/sensors/lsm6ds3_temp.py +++ b/system/sensord/sensors/lsm6ds3_temp.py @@ -7,10 +7,10 @@ from openpilot.system.sensord.sensors.i2c_sensor import Sensor class LSM6DS3_Temp(Sensor): @property def device_address(self) -> int: - return 0x6A # Default I2C address for LSM6DS3 + return 0x6A def _read_temperature(self) -> float: - scale = 16.0 if log.SensorEventData.SensorSource.lsm6ds3 else 256.0 + scale = 16.0 if self.source == log.SensorEventData.SensorSource.lsm6ds3 else 256.0 data = self.read(0x20, 2) return 25 + (self.parse_16bit(data[0], data[1]) / scale) From 5c560377428bc0b28fa2fbb3ba60d8f6dda2c231 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:07:53 -0700 Subject: [PATCH 10/74] [bot] Update Python packages (#35617) Update Python packages Co-authored-by: Vehicle Researcher --- opendbc_repo | 2 +- tinygrad_repo | 2 +- uv.lock | 1254 ++++++++++++++++++++++++------------------------- 3 files changed, 626 insertions(+), 632 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index fb5ac6d304..fdb4fb1935 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit fb5ac6d304fb0688763f20868039e4d8444d5dd0 +Subproject commit fdb4fb1935d494497c94f334e9a0fc51c2e0a142 diff --git a/tinygrad_repo b/tinygrad_repo index eceb7a00d2..710d734ce7 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit eceb7a00d2108c063ab00f7ed9fad581b47dc492 +Subproject commit 710d734ce7e1b5e47d58d19206fd8f3e3854794c diff --git a/uv.lock b/uv.lock index 530db4ec9d..b25bf8a9f5 100644 --- a/uv.lock +++ b/uv.lock @@ -511,7 +511,7 @@ version = "0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-xlib", marker = "sys_platform == 'linux'" }, - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "sys_platform != 'darwin'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/2f/3a/46ca34abf0725a754bc44ef474ad34aedcc3ea23b052d97b18b76715a6a9/EWMHlib-0.2-py3-none-any.whl", hash = "sha256:f5b07d8cfd4c7734462ee744c32d490f2f3233fa7ab354240069344208d2f6f5", size = 46657, upload-time = "2024-04-17T08:15:56.338Z" }, @@ -655,7 +655,7 @@ wheels = [ [[package]] name = "gymnasium" -version = "1.1.1" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cloudpickle" }, @@ -663,9 +663,9 @@ dependencies = [ { name = "numpy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/69/70cd29e9fc4953d013b15981ee71d4c9ef4d8b2183e6ef2fe89756746dce/gymnasium-1.1.1.tar.gz", hash = "sha256:8bd9ea9bdef32c950a444ff36afc785e1d81051ec32d30435058953c20d2456d", size = 829326, upload-time = "2025-03-06T16:30:36.428Z" } +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 = [ - { url = "https://files.pythonhosted.org/packages/f9/68/2bdc7b46b5f543dd865575f9d19716866bdb76e50dd33b71ed1a3dd8bb42/gymnasium-1.1.1-py3-none-any.whl", hash = "sha256:9c167ec0a2b388666e37f63b2849cd2552f7f5b71938574c637bb36487eb928a", size = 965410, upload-time = "2025-03-06T16:30:34.443Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e2/a111dbb8625af467ea4760a1373d6ef27aac3137931219902406ccc05423/gymnasium-1.2.0-py3-none-any.whl", hash = "sha256:fc4a1e4121a9464c29b4d7dc6ade3fbeaa36dea448682f5f71a6d2c17489ea76", size = 944301, upload-time = "2025-06-27T08:21:18.83Z" }, ] [[package]] @@ -816,44 +816,38 @@ wheels = [ [[package]] name = "lxml" -version = "5.4.0" +version = "6.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/3d/14e82fc7c8fb1b7761f7e748fd47e2ec8276d137b6acfe5a4bb73853e08f/lxml-5.4.0.tar.gz", hash = "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd", size = 3679479, upload-time = "2025-04-23T01:50:29.322Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/ed/60eb6fa2923602fba988d9ca7c5cdbd7cf25faa795162ed538b527a35411/lxml-6.0.0.tar.gz", hash = "sha256:032e65120339d44cdc3efc326c9f660f5f7205f3a535c1fdbf898b29ea01fb72", size = 4096938, upload-time = "2025-06-26T16:28:19.373Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/2d/67693cc8a605a12e5975380d7ff83020dcc759351b5a066e1cced04f797b/lxml-5.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:98a3912194c079ef37e716ed228ae0dcb960992100461b704aea4e93af6b0bb9", size = 8083240, upload-time = "2025-04-23T01:45:18.566Z" }, - { url = "https://files.pythonhosted.org/packages/73/53/b5a05ab300a808b72e848efd152fe9c022c0181b0a70b8bca1199f1bed26/lxml-5.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ea0252b51d296a75f6118ed0d8696888e7403408ad42345d7dfd0d1e93309a7", size = 4387685, upload-time = "2025-04-23T01:45:21.387Z" }, - { url = "https://files.pythonhosted.org/packages/d8/cb/1a3879c5f512bdcd32995c301886fe082b2edd83c87d41b6d42d89b4ea4d/lxml-5.4.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b92b69441d1bd39f4940f9eadfa417a25862242ca2c396b406f9272ef09cdcaa", size = 4991164, upload-time = "2025-04-23T01:45:23.849Z" }, - { url = "https://files.pythonhosted.org/packages/f9/94/bbc66e42559f9d04857071e3b3d0c9abd88579367fd2588a4042f641f57e/lxml-5.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20e16c08254b9b6466526bc1828d9370ee6c0d60a4b64836bc3ac2917d1e16df", size = 4746206, upload-time = "2025-04-23T01:45:26.361Z" }, - { url = "https://files.pythonhosted.org/packages/66/95/34b0679bee435da2d7cae895731700e519a8dfcab499c21662ebe671603e/lxml-5.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7605c1c32c3d6e8c990dd28a0970a3cbbf1429d5b92279e37fda05fb0c92190e", size = 5342144, upload-time = "2025-04-23T01:45:28.939Z" }, - { url = "https://files.pythonhosted.org/packages/e0/5d/abfcc6ab2fa0be72b2ba938abdae1f7cad4c632f8d552683ea295d55adfb/lxml-5.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecf4c4b83f1ab3d5a7ace10bafcb6f11df6156857a3c418244cef41ca9fa3e44", size = 4825124, upload-time = "2025-04-23T01:45:31.361Z" }, - { url = "https://files.pythonhosted.org/packages/5a/78/6bd33186c8863b36e084f294fc0a5e5eefe77af95f0663ef33809cc1c8aa/lxml-5.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cef4feae82709eed352cd7e97ae062ef6ae9c7b5dbe3663f104cd2c0e8d94ba", size = 4876520, upload-time = "2025-04-23T01:45:34.191Z" }, - { url = "https://files.pythonhosted.org/packages/3b/74/4d7ad4839bd0fc64e3d12da74fc9a193febb0fae0ba6ebd5149d4c23176a/lxml-5.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:df53330a3bff250f10472ce96a9af28628ff1f4efc51ccba351a8820bca2a8ba", size = 4765016, upload-time = "2025-04-23T01:45:36.7Z" }, - { url = "https://files.pythonhosted.org/packages/24/0d/0a98ed1f2471911dadfc541003ac6dd6879fc87b15e1143743ca20f3e973/lxml-5.4.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:aefe1a7cb852fa61150fcb21a8c8fcea7b58c4cb11fbe59c97a0a4b31cae3c8c", size = 5362884, upload-time = "2025-04-23T01:45:39.291Z" }, - { url = "https://files.pythonhosted.org/packages/48/de/d4f7e4c39740a6610f0f6959052b547478107967362e8424e1163ec37ae8/lxml-5.4.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ef5a7178fcc73b7d8c07229e89f8eb45b2908a9238eb90dcfc46571ccf0383b8", size = 4902690, upload-time = "2025-04-23T01:45:42.386Z" }, - { url = "https://files.pythonhosted.org/packages/07/8c/61763abd242af84f355ca4ef1ee096d3c1b7514819564cce70fd18c22e9a/lxml-5.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d2ed1b3cb9ff1c10e6e8b00941bb2e5bb568b307bfc6b17dffbbe8be5eecba86", size = 4944418, upload-time = "2025-04-23T01:45:46.051Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c5/6d7e3b63e7e282619193961a570c0a4c8a57fe820f07ca3fe2f6bd86608a/lxml-5.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:72ac9762a9f8ce74c9eed4a4e74306f2f18613a6b71fa065495a67ac227b3056", size = 4827092, upload-time = "2025-04-23T01:45:48.943Z" }, - { url = "https://files.pythonhosted.org/packages/71/4a/e60a306df54680b103348545706a98a7514a42c8b4fbfdcaa608567bb065/lxml-5.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f5cb182f6396706dc6cc1896dd02b1c889d644c081b0cdec38747573db88a7d7", size = 5418231, upload-time = "2025-04-23T01:45:51.481Z" }, - { url = "https://files.pythonhosted.org/packages/27/f2/9754aacd6016c930875854f08ac4b192a47fe19565f776a64004aa167521/lxml-5.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:3a3178b4873df8ef9457a4875703488eb1622632a9cee6d76464b60e90adbfcd", size = 5261798, upload-time = "2025-04-23T01:45:54.146Z" }, - { url = "https://files.pythonhosted.org/packages/38/a2/0c49ec6941428b1bd4f280650d7b11a0f91ace9db7de32eb7aa23bcb39ff/lxml-5.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e094ec83694b59d263802ed03a8384594fcce477ce484b0cbcd0008a211ca751", size = 4988195, upload-time = "2025-04-23T01:45:56.685Z" }, - { url = "https://files.pythonhosted.org/packages/7a/75/87a3963a08eafc46a86c1131c6e28a4de103ba30b5ae903114177352a3d7/lxml-5.4.0-cp311-cp311-win32.whl", hash = "sha256:4329422de653cdb2b72afa39b0aa04252fca9071550044904b2e7036d9d97fe4", size = 3474243, upload-time = "2025-04-23T01:45:58.863Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f9/1f0964c4f6c2be861c50db380c554fb8befbea98c6404744ce243a3c87ef/lxml-5.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd3be6481ef54b8cfd0e1e953323b7aa9d9789b94842d0e5b142ef4bb7999539", size = 3815197, upload-time = "2025-04-23T01:46:01.096Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4c/d101ace719ca6a4ec043eb516fcfcb1b396a9fccc4fcd9ef593df34ba0d5/lxml-5.4.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b5aff6f3e818e6bdbbb38e5967520f174b18f539c2b9de867b1e7fde6f8d95a4", size = 8127392, upload-time = "2025-04-23T01:46:04.09Z" }, - { url = "https://files.pythonhosted.org/packages/11/84/beddae0cec4dd9ddf46abf156f0af451c13019a0fa25d7445b655ba5ccb7/lxml-5.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942a5d73f739ad7c452bf739a62a0f83e2578afd6b8e5406308731f4ce78b16d", size = 4415103, upload-time = "2025-04-23T01:46:07.227Z" }, - { url = "https://files.pythonhosted.org/packages/d0/25/d0d93a4e763f0462cccd2b8a665bf1e4343dd788c76dcfefa289d46a38a9/lxml-5.4.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:460508a4b07364d6abf53acaa0a90b6d370fafde5693ef37602566613a9b0779", size = 5024224, upload-time = "2025-04-23T01:46:10.237Z" }, - { url = "https://files.pythonhosted.org/packages/31/ce/1df18fb8f7946e7f3388af378b1f34fcf253b94b9feedb2cec5969da8012/lxml-5.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529024ab3a505fed78fe3cc5ddc079464e709f6c892733e3f5842007cec8ac6e", size = 4769913, upload-time = "2025-04-23T01:46:12.757Z" }, - { url = "https://files.pythonhosted.org/packages/4e/62/f4a6c60ae7c40d43657f552f3045df05118636be1165b906d3423790447f/lxml-5.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ca56ebc2c474e8f3d5761debfd9283b8b18c76c4fc0967b74aeafba1f5647f9", size = 5290441, upload-time = "2025-04-23T01:46:16.037Z" }, - { url = "https://files.pythonhosted.org/packages/9e/aa/04f00009e1e3a77838c7fc948f161b5d2d5de1136b2b81c712a263829ea4/lxml-5.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a81e1196f0a5b4167a8dafe3a66aa67c4addac1b22dc47947abd5d5c7a3f24b5", size = 4820165, upload-time = "2025-04-23T01:46:19.137Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/e0b2f61fa2404bf0f1fdf1898377e5bd1b74cc9b2cf2c6ba8509b8f27990/lxml-5.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00b8686694423ddae324cf614e1b9659c2edb754de617703c3d29ff568448df5", size = 4932580, upload-time = "2025-04-23T01:46:21.963Z" }, - { url = "https://files.pythonhosted.org/packages/24/a2/8263f351b4ffe0ed3e32ea7b7830f845c795349034f912f490180d88a877/lxml-5.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c5681160758d3f6ac5b4fea370495c48aac0989d6a0f01bb9a72ad8ef5ab75c4", size = 4759493, upload-time = "2025-04-23T01:46:24.316Z" }, - { url = "https://files.pythonhosted.org/packages/05/00/41db052f279995c0e35c79d0f0fc9f8122d5b5e9630139c592a0b58c71b4/lxml-5.4.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:2dc191e60425ad70e75a68c9fd90ab284df64d9cd410ba8d2b641c0c45bc006e", size = 5324679, upload-time = "2025-04-23T01:46:27.097Z" }, - { url = "https://files.pythonhosted.org/packages/1d/be/ee99e6314cdef4587617d3b3b745f9356d9b7dd12a9663c5f3b5734b64ba/lxml-5.4.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:67f779374c6b9753ae0a0195a892a1c234ce8416e4448fe1e9f34746482070a7", size = 4890691, upload-time = "2025-04-23T01:46:30.009Z" }, - { url = "https://files.pythonhosted.org/packages/ad/36/239820114bf1d71f38f12208b9c58dec033cbcf80101cde006b9bde5cffd/lxml-5.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:79d5bfa9c1b455336f52343130b2067164040604e41f6dc4d8313867ed540079", size = 4955075, upload-time = "2025-04-23T01:46:32.33Z" }, - { url = "https://files.pythonhosted.org/packages/d4/e1/1b795cc0b174efc9e13dbd078a9ff79a58728a033142bc6d70a1ee8fc34d/lxml-5.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3d3c30ba1c9b48c68489dc1829a6eede9873f52edca1dda900066542528d6b20", size = 4838680, upload-time = "2025-04-23T01:46:34.852Z" }, - { url = "https://files.pythonhosted.org/packages/72/48/3c198455ca108cec5ae3662ae8acd7fd99476812fd712bb17f1b39a0b589/lxml-5.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1af80c6316ae68aded77e91cd9d80648f7dd40406cef73df841aa3c36f6907c8", size = 5391253, upload-time = "2025-04-23T01:46:37.608Z" }, - { url = "https://files.pythonhosted.org/packages/d6/10/5bf51858971c51ec96cfc13e800a9951f3fd501686f4c18d7d84fe2d6352/lxml-5.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4d885698f5019abe0de3d352caf9466d5de2baded00a06ef3f1216c1a58ae78f", size = 5261651, upload-time = "2025-04-23T01:46:40.183Z" }, - { url = "https://files.pythonhosted.org/packages/2b/11/06710dd809205377da380546f91d2ac94bad9ff735a72b64ec029f706c85/lxml-5.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea53d51859b6c64e7c51d522c03cc2c48b9b5d6172126854cc7f01aa11f52bc", size = 5024315, upload-time = "2025-04-23T01:46:43.333Z" }, - { url = "https://files.pythonhosted.org/packages/f5/b0/15b6217834b5e3a59ebf7f53125e08e318030e8cc0d7310355e6edac98ef/lxml-5.4.0-cp312-cp312-win32.whl", hash = "sha256:d90b729fd2732df28130c064aac9bb8aff14ba20baa4aee7bd0795ff1187545f", size = 3486149, upload-time = "2025-04-23T01:46:45.684Z" }, - { url = "https://files.pythonhosted.org/packages/91/1e/05ddcb57ad2f3069101611bd5f5084157d90861a2ef460bf42f45cced944/lxml-5.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1dc4ca99e89c335a7ed47d38964abcb36c5910790f9bd106f2a8fa2ee0b909d2", size = 3817095, upload-time = "2025-04-23T01:46:48.521Z" }, + { url = "https://files.pythonhosted.org/packages/7c/23/828d4cc7da96c611ec0ce6147bbcea2fdbde023dc995a165afa512399bbf/lxml-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ee56288d0df919e4aac43b539dd0e34bb55d6a12a6562038e8d6f3ed07f9e36", size = 8438217, upload-time = "2025-06-26T16:25:34.349Z" }, + { url = "https://files.pythonhosted.org/packages/f1/33/5ac521212c5bcb097d573145d54b2b4a3c9766cda88af5a0e91f66037c6e/lxml-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8dd6dd0e9c1992613ccda2bcb74fc9d49159dbe0f0ca4753f37527749885c25", size = 4590317, upload-time = "2025-06-26T16:25:38.103Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2e/45b7ca8bee304c07f54933c37afe7dd4d39ff61ba2757f519dcc71bc5d44/lxml-6.0.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:d7ae472f74afcc47320238b5dbfd363aba111a525943c8a34a1b657c6be934c3", size = 5221628, upload-time = "2025-06-26T16:25:40.878Z" }, + { url = "https://files.pythonhosted.org/packages/32/23/526d19f7eb2b85da1f62cffb2556f647b049ebe2a5aa8d4d41b1fb2c7d36/lxml-6.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5592401cdf3dc682194727c1ddaa8aa0f3ddc57ca64fd03226a430b955eab6f6", size = 4949429, upload-time = "2025-06-28T18:47:20.046Z" }, + { url = "https://files.pythonhosted.org/packages/ac/cc/f6be27a5c656a43a5344e064d9ae004d4dcb1d3c9d4f323c8189ddfe4d13/lxml-6.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58ffd35bd5425c3c3b9692d078bf7ab851441434531a7e517c4984d5634cd65b", size = 5087909, upload-time = "2025-06-28T18:47:22.834Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e6/8ec91b5bfbe6972458bc105aeb42088e50e4b23777170404aab5dfb0c62d/lxml-6.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f720a14aa102a38907c6d5030e3d66b3b680c3e6f6bc95473931ea3c00c59967", size = 5031713, upload-time = "2025-06-26T16:25:43.226Z" }, + { url = "https://files.pythonhosted.org/packages/33/cf/05e78e613840a40e5be3e40d892c48ad3e475804db23d4bad751b8cadb9b/lxml-6.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2a5e8d207311a0170aca0eb6b160af91adc29ec121832e4ac151a57743a1e1e", size = 5232417, upload-time = "2025-06-26T16:25:46.111Z" }, + { url = "https://files.pythonhosted.org/packages/ac/8c/6b306b3e35c59d5f0b32e3b9b6b3b0739b32c0dc42a295415ba111e76495/lxml-6.0.0-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:2dd1cc3ea7e60bfb31ff32cafe07e24839df573a5e7c2d33304082a5019bcd58", size = 4681443, upload-time = "2025-06-26T16:25:48.837Z" }, + { url = "https://files.pythonhosted.org/packages/59/43/0bd96bece5f7eea14b7220476835a60d2b27f8e9ca99c175f37c085cb154/lxml-6.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2cfcf84f1defed7e5798ef4f88aa25fcc52d279be731ce904789aa7ccfb7e8d2", size = 5074542, upload-time = "2025-06-26T16:25:51.65Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3d/32103036287a8ca012d8518071f8852c68f2b3bfe048cef2a0202eb05910/lxml-6.0.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a52a4704811e2623b0324a18d41ad4b9fabf43ce5ff99b14e40a520e2190c851", size = 4729471, upload-time = "2025-06-26T16:25:54.571Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a8/7be5d17df12d637d81854bd8648cd329f29640a61e9a72a3f77add4a311b/lxml-6.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c16304bba98f48a28ae10e32a8e75c349dd742c45156f297e16eeb1ba9287a1f", size = 5256285, upload-time = "2025-06-26T16:25:56.997Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d0/6cb96174c25e0d749932557c8d51d60c6e292c877b46fae616afa23ed31a/lxml-6.0.0-cp311-cp311-win32.whl", hash = "sha256:f8d19565ae3eb956d84da3ef367aa7def14a2735d05bd275cd54c0301f0d0d6c", size = 3612004, upload-time = "2025-06-26T16:25:59.11Z" }, + { url = "https://files.pythonhosted.org/packages/ca/77/6ad43b165dfc6dead001410adeb45e88597b25185f4479b7ca3b16a5808f/lxml-6.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b2d71cdefda9424adff9a3607ba5bbfc60ee972d73c21c7e3c19e71037574816", size = 4003470, upload-time = "2025-06-26T16:26:01.655Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bc/4c50ec0eb14f932a18efc34fc86ee936a66c0eb5f2fe065744a2da8a68b2/lxml-6.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:8a2e76efbf8772add72d002d67a4c3d0958638696f541734304c7f28217a9cab", size = 3682477, upload-time = "2025-06-26T16:26:03.808Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/d01d735c298d7e0ddcedf6f028bf556577e5ab4f4da45175ecd909c79378/lxml-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78718d8454a6e928470d511bf8ac93f469283a45c354995f7d19e77292f26108", size = 8429515, upload-time = "2025-06-26T16:26:06.776Z" }, + { url = "https://files.pythonhosted.org/packages/06/37/0e3eae3043d366b73da55a86274a590bae76dc45aa004b7042e6f97803b1/lxml-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:84ef591495ffd3f9dcabffd6391db7bb70d7230b5c35ef5148354a134f56f2be", size = 4601387, upload-time = "2025-06-26T16:26:09.511Z" }, + { url = "https://files.pythonhosted.org/packages/a3/28/e1a9a881e6d6e29dda13d633885d13acb0058f65e95da67841c8dd02b4a8/lxml-6.0.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:2930aa001a3776c3e2601cb8e0a15d21b8270528d89cc308be4843ade546b9ab", size = 5228928, upload-time = "2025-06-26T16:26:12.337Z" }, + { url = "https://files.pythonhosted.org/packages/9a/55/2cb24ea48aa30c99f805921c1c7860c1f45c0e811e44ee4e6a155668de06/lxml-6.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:219e0431ea8006e15005767f0351e3f7f9143e793e58519dc97fe9e07fae5563", size = 4952289, upload-time = "2025-06-28T18:47:25.602Z" }, + { url = "https://files.pythonhosted.org/packages/31/c0/b25d9528df296b9a3306ba21ff982fc5b698c45ab78b94d18c2d6ae71fd9/lxml-6.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bd5913b4972681ffc9718bc2d4c53cde39ef81415e1671ff93e9aa30b46595e7", size = 5111310, upload-time = "2025-06-28T18:47:28.136Z" }, + { url = "https://files.pythonhosted.org/packages/e9/af/681a8b3e4f668bea6e6514cbcb297beb6de2b641e70f09d3d78655f4f44c/lxml-6.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:390240baeb9f415a82eefc2e13285016f9c8b5ad71ec80574ae8fa9605093cd7", size = 5025457, upload-time = "2025-06-26T16:26:15.068Z" }, + { url = "https://files.pythonhosted.org/packages/69/f8/693b1a10a891197143c0673fcce5b75fc69132afa81a36e4568c12c8faba/lxml-6.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ca50bd612438258a91b5b3788c6621c1f05c8c478e7951899f492be42defc0da", size = 5257565, upload-time = "2025-06-26T16:26:17.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/96/e08ff98f2c6426c98c8964513c5dab8d6eb81dadcd0af6f0c538ada78d33/lxml-6.0.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:c24b8efd9c0f62bad0439283c2c795ef916c5a6b75f03c17799775c7ae3c0c9e", size = 4713390, upload-time = "2025-06-26T16:26:20.292Z" }, + { url = "https://files.pythonhosted.org/packages/a8/83/6184aba6cc94d7413959f6f8f54807dc318fdcd4985c347fe3ea6937f772/lxml-6.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afd27d8629ae94c5d863e32ab0e1d5590371d296b87dae0a751fb22bf3685741", size = 5066103, upload-time = "2025-06-26T16:26:22.765Z" }, + { url = "https://files.pythonhosted.org/packages/ee/01/8bf1f4035852d0ff2e36a4d9aacdbcc57e93a6cd35a54e05fa984cdf73ab/lxml-6.0.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:54c4855eabd9fc29707d30141be99e5cd1102e7d2258d2892314cf4c110726c3", size = 4791428, upload-time = "2025-06-26T16:26:26.461Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f7/5495829a864bc5f8b0798d2b52a807c89966523140f3d6fa3a58ab6720ea/lxml-6.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36531f81c8214e293097cd2b7873f178997dae33d3667caaae8bdfb9666b76c0", size = 5281290, upload-time = "2025-06-26T16:26:29.406Z" }, + { url = "https://files.pythonhosted.org/packages/79/56/6b8edb79d9ed294ccc4e881f4db1023af56ba451909b9ce79f2a2cd7c532/lxml-6.0.0-cp312-cp312-win32.whl", hash = "sha256:690b20e3388a7ec98e899fd54c924e50ba6693874aa65ef9cb53de7f7de9d64a", size = 3613495, upload-time = "2025-06-26T16:26:31.588Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1e/cc32034b40ad6af80b6fd9b66301fc0f180f300002e5c3eb5a6110a93317/lxml-6.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:310b719b695b3dd442cdfbbe64936b2f2e231bb91d998e99e6f0daf991a3eba3", size = 4014711, upload-time = "2025-06-26T16:26:33.723Z" }, + { url = "https://files.pythonhosted.org/packages/55/10/dc8e5290ae4c94bdc1a4c55865be7e1f31dfd857a88b21cbba68b5fea61b/lxml-6.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:8cb26f51c82d77483cdcd2b4a53cda55bbee29b3c2f3ddeb47182a2a9064e4eb", size = 3674431, upload-time = "2025-06-26T16:26:35.959Z" }, ] [[package]] @@ -1073,47 +1067,47 @@ wheels = [ [[package]] name = "multidict" -version = "6.5.0" +version = "6.6.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/46/b5/59f27b4ce9951a4bce56b88ba5ff5159486797ab18863f2b4c1c5e8465bd/multidict-6.5.0.tar.gz", hash = "sha256:942bd8002492ba819426a8d7aefde3189c1b87099cdf18aaaefefcf7f3f7b6d2", size = 98512, upload-time = "2025-06-17T14:15:56.556Z" } +sdist = { url = "https://files.pythonhosted.org/packages/03/5d/d72502cd6dd64b0c5a5117b1701f05c38e94ffb4a1b4ab65ff0cd9b974e8/multidict-6.6.2.tar.gz", hash = "sha256:c1e8b8b0523c0361a78ce9b99d9850c51cf25e1fa3c5686030ce75df6fdf2918", size = 100939, upload-time = "2025-06-28T14:38:20.828Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/ba/484f8e96ee58ec4fef42650eb9dbbedb24f9bc155780888398a4725d2270/multidict-6.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8b4bf6bb15a05796a07a248084e3e46e032860c899c7a9b981030e61368dba95", size = 73283, upload-time = "2025-06-17T14:13:50.406Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/01d62ea6199d76934c87746695b3ed16aeedfdd564e8d89184577037baac/multidict-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46bb05d50219655c42a4b8fcda9c7ee658a09adbb719c48e65a20284e36328ea", size = 42937, upload-time = "2025-06-17T14:13:51.45Z" }, - { url = "https://files.pythonhosted.org/packages/da/cf/bb462d920f26d9e2e0aff8a78aeb06af1225b826e9a5468870c57591910a/multidict-6.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:54f524d73f4d54e87e03c98f6af601af4777e4668a52b1bd2ae0a4d6fc7b392b", size = 42748, upload-time = "2025-06-17T14:13:52.505Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b1/d5c11ea0fdad68d3ed45f0e2527de6496d2fac8afe6b8ca6d407c20ad00f/multidict-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529b03600466480ecc502000d62e54f185a884ed4570dee90d9a273ee80e37b5", size = 236448, upload-time = "2025-06-17T14:13:53.562Z" }, - { url = "https://files.pythonhosted.org/packages/fc/69/c3ceb264994f5b338c812911a8d660084f37779daef298fc30bd817f75c7/multidict-6.5.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69ad681ad7c93a41ee7005cc83a144b5b34a3838bcf7261e2b5356057b0f78de", size = 228695, upload-time = "2025-06-17T14:13:54.775Z" }, - { url = "https://files.pythonhosted.org/packages/81/3d/c23dcc0d34a35ad29974184db2878021d28fe170ecb9192be6bfee73f1f2/multidict-6.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fe9fada8bc0839466b09fa3f6894f003137942984843ec0c3848846329a36ae", size = 247434, upload-time = "2025-06-17T14:13:56.039Z" }, - { url = "https://files.pythonhosted.org/packages/06/b3/06cf7a049129ff52525a859277abb5648e61d7afae7fb7ed02e3806be34e/multidict-6.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f94c6ea6405fcf81baef1e459b209a78cda5442e61b5b7a57ede39d99b5204a0", size = 239431, upload-time = "2025-06-17T14:13:57.33Z" }, - { url = "https://files.pythonhosted.org/packages/8a/72/b2fe2fafa23af0c6123aebe23b4cd23fdad01dfe7009bb85624e4636d0dd/multidict-6.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ca75ad8a39ed75f079a8931435a5b51ee4c45d9b32e1740f99969a5d1cc2ee", size = 231542, upload-time = "2025-06-17T14:13:58.597Z" }, - { url = "https://files.pythonhosted.org/packages/a1/c9/a52ca0a342a02411a31b6af197a6428a5137d805293f10946eeab614ec06/multidict-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4c08f3a2a6cc42b414496017928d95898964fed84b1b2dace0c9ee763061f9", size = 233069, upload-time = "2025-06-17T14:13:59.834Z" }, - { url = "https://files.pythonhosted.org/packages/9b/55/a3328a3929b8e131e2678d5e65f552b0a6874fab62123e31f5a5625650b0/multidict-6.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:046a7540cfbb4d5dc846a1fd9843f3ba980c6523f2e0c5b8622b4a5c94138ae6", size = 250596, upload-time = "2025-06-17T14:14:01.178Z" }, - { url = "https://files.pythonhosted.org/packages/6c/b8/aa3905a38a8287013aeb0a54c73f79ccd8b32d2f1d53e5934643a36502c2/multidict-6.5.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:64306121171d988af77d74be0d8c73ee1a69cf6f96aea7fa6030c88f32a152dd", size = 237858, upload-time = "2025-06-17T14:14:03.232Z" }, - { url = "https://files.pythonhosted.org/packages/d3/eb/f11d5af028014f402e5dd01ece74533964fa4e7bfae4af4824506fa8c398/multidict-6.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b4ac1dd5eb0ecf6f7351d5a9137f30a83f7182209c5d37f61614dfdce5714853", size = 249175, upload-time = "2025-06-17T14:14:04.561Z" }, - { url = "https://files.pythonhosted.org/packages/ac/57/d451905a62e5ef489cb4f92e8190d34ac5329427512afd7f893121da4e96/multidict-6.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bab4a8337235365f4111a7011a1f028826ca683834ebd12de4b85e2844359c36", size = 259532, upload-time = "2025-06-17T14:14:05.798Z" }, - { url = "https://files.pythonhosted.org/packages/d3/90/ff82b5ac5cabe3c79c50cf62a62f3837905aa717e67b6b4b7872804f23c8/multidict-6.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a05b5604c5a75df14a63eeeca598d11b2c3745b9008539b70826ea044063a572", size = 250554, upload-time = "2025-06-17T14:14:07.382Z" }, - { url = "https://files.pythonhosted.org/packages/d5/5a/0cabc50d4bc16e61d8b0a8a74499a1409fa7b4ef32970b7662a423781fc7/multidict-6.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:67c4a640952371c9ca65b6a710598be246ef3be5ca83ed38c16a7660d3980877", size = 248159, upload-time = "2025-06-17T14:14:08.65Z" }, - { url = "https://files.pythonhosted.org/packages/c0/1d/adeabae0771544f140d9f42ab2c46eaf54e793325999c36106078b7f6600/multidict-6.5.0-cp311-cp311-win32.whl", hash = "sha256:fdeae096ca36c12d8aca2640b8407a9d94e961372c68435bef14e31cce726138", size = 40357, upload-time = "2025-06-17T14:14:09.91Z" }, - { url = "https://files.pythonhosted.org/packages/e1/fe/bbd85ae65c96de5c9910c332ee1f4b7be0bf0fb21563895167bcb6502a1f/multidict-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:e2977ef8b7ce27723ee8c610d1bd1765da4f3fbe5a64f9bf1fd3b4770e31fbc0", size = 44432, upload-time = "2025-06-17T14:14:11.013Z" }, - { url = "https://files.pythonhosted.org/packages/96/af/f9052d9c4e65195b210da9f7afdea06d3b7592b3221cc0ef1b407f762faa/multidict-6.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:82d0cf0ea49bae43d9e8c3851e21954eff716259ff42da401b668744d1760bcb", size = 41408, upload-time = "2025-06-17T14:14:12.112Z" }, - { url = "https://files.pythonhosted.org/packages/0a/fa/18f4950e00924f7e84c8195f4fc303295e14df23f713d64e778b8fa8b903/multidict-6.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1bb986c8ea9d49947bc325c51eced1ada6d8d9b4c5b15fd3fcdc3c93edef5a74", size = 73474, upload-time = "2025-06-17T14:14:13.528Z" }, - { url = "https://files.pythonhosted.org/packages/6c/66/0392a2a8948bccff57e4793c9dde3e5c088f01e8b7f8867ee58a2f187fc5/multidict-6.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:03c0923da300120830fc467e23805d63bbb4e98b94032bd863bc7797ea5fa653", size = 43741, upload-time = "2025-06-17T14:14:15.188Z" }, - { url = "https://files.pythonhosted.org/packages/98/3e/f48487c91b2a070566cfbab876d7e1ebe7deb0a8002e4e896a97998ae066/multidict-6.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4c78d5ec00fdd35c91680ab5cf58368faad4bd1a8721f87127326270248de9bc", size = 42143, upload-time = "2025-06-17T14:14:16.612Z" }, - { url = "https://files.pythonhosted.org/packages/3f/49/439c6cc1cd00365cf561bdd3579cc3fa1a0d38effb3a59b8d9562839197f/multidict-6.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadc3cb78be90a887f8f6b73945b840da44b4a483d1c9750459ae69687940c97", size = 239303, upload-time = "2025-06-17T14:14:17.707Z" }, - { url = "https://files.pythonhosted.org/packages/c4/24/491786269e90081cb536e4d7429508725bc92ece176d1204a4449de7c41c/multidict-6.5.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5b02e1ca495d71e07e652e4cef91adae3bf7ae4493507a263f56e617de65dafc", size = 236913, upload-time = "2025-06-17T14:14:18.981Z" }, - { url = "https://files.pythonhosted.org/packages/e8/76/bbe2558b820ebeca8a317ab034541790e8160ca4b1e450415383ac69b339/multidict-6.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7fe92a62326eef351668eec4e2dfc494927764a0840a1895cff16707fceffcd3", size = 250752, upload-time = "2025-06-17T14:14:20.297Z" }, - { url = "https://files.pythonhosted.org/packages/3e/e3/3977f2c1123f553ceff9f53cd4de04be2c1912333c6fabbcd51531655476/multidict-6.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7673ee4f63879ecd526488deb1989041abcb101b2d30a9165e1e90c489f3f7fb", size = 243937, upload-time = "2025-06-17T14:14:21.935Z" }, - { url = "https://files.pythonhosted.org/packages/b6/b8/7a6e9c13c79709cdd2f22ee849f058e6da76892d141a67acc0e6c30d845c/multidict-6.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa097ae2a29f573de7e2d86620cbdda5676d27772d4ed2669cfa9961a0d73955", size = 237419, upload-time = "2025-06-17T14:14:23.215Z" }, - { url = "https://files.pythonhosted.org/packages/84/9d/8557f5e88da71bc7e7a8ace1ada4c28197f3bfdc2dd6e51d3b88f2e16e8e/multidict-6.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:300da0fa4f8457d9c4bd579695496116563409e676ac79b5e4dca18e49d1c308", size = 237222, upload-time = "2025-06-17T14:14:24.516Z" }, - { url = "https://files.pythonhosted.org/packages/a3/3b/8f023ad60e7969cb6bc0683738d0e1618f5ff5723d6d2d7818dc6df6ad3d/multidict-6.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9a19bd108c35877b57393243d392d024cfbfdefe759fd137abb98f6fc910b64c", size = 247861, upload-time = "2025-06-17T14:14:25.839Z" }, - { url = "https://files.pythonhosted.org/packages/af/1c/9cf5a099ce7e3189906cf5daa72c44ee962dcb4c1983659f3a6f8a7446ab/multidict-6.5.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f32a1777465a35c35ddbbd7fc1293077938a69402fcc59e40b2846d04a120dd", size = 243917, upload-time = "2025-06-17T14:14:27.164Z" }, - { url = "https://files.pythonhosted.org/packages/6c/bb/88ee66ebeef56868044bac58feb1cc25658bff27b20e3cfc464edc181287/multidict-6.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9cc1e10c14ce8112d1e6d8971fe3cdbe13e314f68bea0e727429249d4a6ce164", size = 249214, upload-time = "2025-06-17T14:14:28.795Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ec/a90e88cc4a1309f33088ab1cdd5c0487718f49dfb82c5ffc845bb17c1973/multidict-6.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e95c5e07a06594bdc288117ca90e89156aee8cb2d7c330b920d9c3dd19c05414", size = 258682, upload-time = "2025-06-17T14:14:30.066Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d8/16dd69a6811920a31f4e06114ebe67b1cd922c8b05c9c82b050706d0b6fe/multidict-6.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:40ff26f58323795f5cd2855e2718a1720a1123fb90df4553426f0efd76135462", size = 254254, upload-time = "2025-06-17T14:14:31.323Z" }, - { url = "https://files.pythonhosted.org/packages/ac/a8/90193a5f5ca1bdbf92633d69a25a2ef9bcac7b412b8d48c84d01a2732518/multidict-6.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76803a29fd71869a8b59c2118c9dcfb3b8f9c8723e2cce6baeb20705459505cf", size = 247741, upload-time = "2025-06-17T14:14:32.717Z" }, - { url = "https://files.pythonhosted.org/packages/cd/43/29c7a747153c05b41d1f67455426af39ed88d6de3f21c232b8f2724bde13/multidict-6.5.0-cp312-cp312-win32.whl", hash = "sha256:df7ecbc65a53a2ce1b3a0c82e6ad1a43dcfe7c6137733f9176a92516b9f5b851", size = 41049, upload-time = "2025-06-17T14:14:33.941Z" }, - { url = "https://files.pythonhosted.org/packages/1e/e8/8f3fc32b7e901f3a2719764d64aeaf6ae77b4ba961f1c3a3cf3867766636/multidict-6.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ec1c3fbbb0b655a6540bce408f48b9a7474fd94ed657dcd2e890671fefa7743", size = 44700, upload-time = "2025-06-17T14:14:35.016Z" }, - { url = "https://files.pythonhosted.org/packages/24/e4/e250806adc98d524d41e69c8d4a42bc3513464adb88cb96224df12928617/multidict-6.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:2d24a00d34808b22c1f15902899b9d82d0faeca9f56281641c791d8605eacd35", size = 41703, upload-time = "2025-06-17T14:14:36.168Z" }, - { url = "https://files.pythonhosted.org/packages/44/d8/45e8fc9892a7386d074941429e033adb4640e59ff0780d96a8cf46fe788e/multidict-6.5.0-py3-none-any.whl", hash = "sha256:5634b35f225977605385f56153bd95a7133faffc0ffe12ad26e10517537e8dfc", size = 12181, upload-time = "2025-06-17T14:15:55.156Z" }, + { url = "https://files.pythonhosted.org/packages/20/33/e9f6a8e960087721722e1bb7948f87647c12b2ed0526d8f5b062d4673cbb/multidict-6.6.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f89ce85a8f949e4dde3a4fb374500630cf425a32d8c53a2892b8fbfc556720dc", size = 76259, upload-time = "2025-06-28T14:36:12.137Z" }, + { url = "https://files.pythonhosted.org/packages/e5/41/9924607eee3b1a133e1890be63759378c086ecd34f8c6573552d1043d503/multidict-6.6.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d6c5bed784d70e790b6f014c1a9456e76401ab32bdacbf8bf4a6c2f1654b12", size = 44529, upload-time = "2025-06-28T14:36:13.205Z" }, + { url = "https://files.pythonhosted.org/packages/10/43/29c7765474941cfc2739706bd0ebac056edc3ae091654a605440beabc03f/multidict-6.6.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a94cff417557f5a665e81c642e88d0062d19203a6b470b84fd62007d9c4c23c6", size = 44188, upload-time = "2025-06-28T14:36:14.616Z" }, + { url = "https://files.pythonhosted.org/packages/01/24/8a016db31ca3664ddcf312c2671309068b040022366bf81f3dfa8a55e161/multidict-6.6.2-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:ebe5fc33f384c0277d3e9ecfc2f1fb3a5d2e6ac03deda39b84cf97611857241a", size = 228403, upload-time = "2025-06-28T14:36:15.697Z" }, + { url = "https://files.pythonhosted.org/packages/c8/15/d88e1f74daaa18be5544a718885bcbffc8b351a72fb019a12d44effb4f7c/multidict-6.6.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9723d0ba4d4eb50e7abcc235d4db5ce562c6cc20caa902967d8d915ccd73cd75", size = 248429, upload-time = "2025-06-28T14:36:16.949Z" }, + { url = "https://files.pythonhosted.org/packages/66/83/5e23f9c457d5b0995ce161cc26afdb62b73471e56cea104c0759022e9d6c/multidict-6.6.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fcb85c7bedeaace74fbb631186789a23706bf22a44224b36340576370b3d9fd5", size = 225137, upload-time = "2025-06-28T14:36:18.609Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3d/8c5ef319927e179193f4fead913617f9c8e83722dcd3da5fcf34c8c41948/multidict-6.6.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d037a8777504c29cb65b90b6bff6c5bf4e5129487a3f7e4517ae97c3dcf675e", size = 257370, upload-time = "2025-06-28T14:36:19.938Z" }, + { url = "https://files.pythonhosted.org/packages/6d/6b/68773e727bba1d3a3aa42c18e9f8000c2d928da39de979c9816d801e8952/multidict-6.6.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d082bf71da51352247b94622c9c8fd905b22bd4a7e94e252d4336492f595abe5", size = 256199, upload-time = "2025-06-28T14:36:21.262Z" }, + { url = "https://files.pythonhosted.org/packages/32/d7/8bcf5147c3c9a6581d80778757ec2f21c1e52f13cd1e268548d6f79e43c5/multidict-6.6.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce75c8986b17bc35f22240b7a4d9561530551e1f715b6cc23e136e7c06e6922a", size = 246823, upload-time = "2025-06-28T14:36:22.688Z" }, + { url = "https://files.pythonhosted.org/packages/49/83/2c7cfc6057ad234d5708d738718c086c17265eadcd17a42f6e6c22b494d1/multidict-6.6.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f38e7b738de36f7caa707add650555d9bc01af9276000ba0427fda08153861f7", size = 244391, upload-time = "2025-06-28T14:36:24.335Z" }, + { url = "https://files.pythonhosted.org/packages/23/36/4a4ea1064705c4f5f1a8edecd825bd09529d8b6877283ab97f123c4eb324/multidict-6.6.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3e88f5cf86bbe5303d056cec9ccc854408571a3f6d41ce90ca9fdc2df47e3810", size = 238816, upload-time = "2025-06-28T14:36:25.541Z" }, + { url = "https://files.pythonhosted.org/packages/a3/e5/b25ebe8dbfe577eb03b877b167446426e69ae33da10fa74fe68518f974e2/multidict-6.6.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2b62ab7680362cfaf03059f7cb680d3bacd7d8adf176b0a86045e882486bbffc", size = 246514, upload-time = "2025-06-28T14:36:27.154Z" }, + { url = "https://files.pythonhosted.org/packages/4f/3a/642cb671e1563013327d56821cedacb132489c89a29e72c10c74acb5575d/multidict-6.6.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5b39f3c6c22f4f227fe8994eb916ea12e4d46f65fdbc2d977d20750a85be581b", size = 254082, upload-time = "2025-06-28T14:36:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/2f/cc/10b69b681cd2ef752506eb83295cbb151ebaf5716ae53a50626db0b6ffea/multidict-6.6.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b256569fc1f8b52d0c3280503f2b5031e90cdb4469f67bfef32f53ef2b07922d", size = 248122, upload-time = "2025-06-28T14:36:30.533Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/e34dc495dc7501ffa936758d69f9268868f0fa1a99d059bd3bce7c97fc90/multidict-6.6.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:32791619e6b41cce97b6510fb68ee215c75012aa3c53778057a538ae473d3abd", size = 242454, upload-time = "2025-06-28T14:36:31.886Z" }, + { url = "https://files.pythonhosted.org/packages/71/38/2140871da7664088d6ddbf4597d07024e29e8f88a96348fff90f0c8aaf42/multidict-6.6.2-cp311-cp311-win32.whl", hash = "sha256:7eb520abcf9f0e983fa4c666586161a2137d78096546ea19744b50ceae1bc401", size = 41247, upload-time = "2025-06-28T14:36:33.471Z" }, + { url = "https://files.pythonhosted.org/packages/09/b5/b913cd1eaf8ef9eb19580a9699ed86a8f7f945248bfe09ca06ce5878caea/multidict-6.6.2-cp311-cp311-win_amd64.whl", hash = "sha256:d2f765d6793815a82e517a1095c02ccbeab47f0a98fc244fe551625c519ade4d", size = 45877, upload-time = "2025-06-28T14:36:34.912Z" }, + { url = "https://files.pythonhosted.org/packages/90/34/8c041a172b032ec05da5476998876863b9d1eb9029925883e2e27fb48d69/multidict-6.6.2-cp311-cp311-win_arm64.whl", hash = "sha256:1d465e6cbbcadcbb8b3b08827fe9af889cc035500764b313aafb82c7e8e4b0a9", size = 42858, upload-time = "2025-06-28T14:36:36.003Z" }, + { url = "https://files.pythonhosted.org/packages/76/67/244bc9038eb05bae87a07d494ff48e43a4be7417c3fd538e0ea65c1beebf/multidict-6.6.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6e5e426aff6b5b32167b1185909ea390e51e59c7a6adfe65de16746e5739d8c1", size = 76357, upload-time = "2025-06-28T14:36:37.071Z" }, + { url = "https://files.pythonhosted.org/packages/61/3c/03a4d33683ffa9851a14e14cafa76130be99101b2a1b446d47967f47f68e/multidict-6.6.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0c4617af352d3e03b5febd040100d1bba67ac844e0f7780c8a124358883119dd", size = 45313, upload-time = "2025-06-28T14:36:38.121Z" }, + { url = "https://files.pythonhosted.org/packages/50/3c/5eca9c3be9ccb31c26ad144b5fb5160c29d853cd8bc52c1ce53ffd838a0a/multidict-6.6.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:65854da6c2f065f7e52c4385727494d72b25eaf4e901b15fb3f61e21bb0b52eb", size = 43528, upload-time = "2025-06-28T14:36:39.169Z" }, + { url = "https://files.pythonhosted.org/packages/3a/d8/6707b7ac3fd336b034b89e9ac5fdcca045e8f6b84ee4163c1857795366b4/multidict-6.6.2-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:c477d3bc9a1aa0214f5639a8c1b4a6b3cd9faea5a861b4001a6df62294dcc952", size = 238181, upload-time = "2025-06-28T14:36:40.703Z" }, + { url = "https://files.pythonhosted.org/packages/a8/24/b822b9f9bceed4f22008172717d601d6209bbe7daca2d35828be60208ba9/multidict-6.6.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d19a4bc7c5c1a25424812a26e8dccb18fff65a5f24515d2f3b85302ca3f3914f", size = 257172, upload-time = "2025-06-28T14:36:42.402Z" }, + { url = "https://files.pythonhosted.org/packages/76/e6/7995824cc95a15daebb15da87fc9509cc3c35027885d534d80718c55d10e/multidict-6.6.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:54ffaf44483b082602c1e1831472e3369c005f092271dbbcad2f7b12d1e84019", size = 242147, upload-time = "2025-06-28T14:36:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/f8/44/23c9b50461423766d9f32b013a49ce07b358a1188d43cfa977385a872d03/multidict-6.6.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a526df4fe21d2dc68265c046685042fc89187dc40754489f32f7efc05e264b0f", size = 267431, upload-time = "2025-06-28T14:36:44.964Z" }, + { url = "https://files.pythonhosted.org/packages/28/e4/72cc549230e7d93f9eca0206fac402af239058d8a9f0fb95f348762e8fdd/multidict-6.6.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:73e8763061f0a38cec6d084b696964ee7b7e50c10c89a64b20be7044dca36a74", size = 269480, upload-time = "2025-06-28T14:36:46.569Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e3/a809cf2e624cb37f29f4569e756bd708cd96a93d3d940143464d9079a2f5/multidict-6.6.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81a13031b6831e4de5a89fe391d7f8b60a929f2d22dad403c69d60853e5ba1ca", size = 256759, upload-time = "2025-06-28T14:36:47.859Z" }, + { url = "https://files.pythonhosted.org/packages/03/85/ad1127e662ed20d8ba2751bf67d874380a817577cd486a7309dd50d116a1/multidict-6.6.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6421f4d5acd632af56ae90906755b242e518d59f5313a7b41cd55fb59adfcd74", size = 252393, upload-time = "2025-06-28T14:36:49.169Z" }, + { url = "https://files.pythonhosted.org/packages/36/b3/67c331269372e38c435dff4c4b3b5ca8aba958dd58936153c5e64d07a515/multidict-6.6.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3fed2cfff9d8e3316fc4c5aca40f33d7cd29b5a9a4cbf4aa17dfcae592ccb17c", size = 249848, upload-time = "2025-06-28T14:36:50.88Z" }, + { url = "https://files.pythonhosted.org/packages/06/56/54d51eb89cdcb5518828081cb396219699468f70266ef0fcacf57a339319/multidict-6.6.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eb97a4eed516fb3d63028fc0a8a8661e1acdf7925eace9c85153ff967926331c", size = 249993, upload-time = "2025-06-28T14:36:52.562Z" }, + { url = "https://files.pythonhosted.org/packages/78/8e/afc23d4d59ac2969743fdabb7fbd722c0b8bf333c31b02e8594e21661755/multidict-6.6.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9bbef50bfefe84213b791c9a326d3221fa31181ba39576049a55c1eef9768109", size = 262437, upload-time = "2025-06-28T14:36:54.251Z" }, + { url = "https://files.pythonhosted.org/packages/3c/ec/74586ce0ebb48a7394719d5d2fda019ec7cc41e3fc01cb50ecd82cf80f6e/multidict-6.6.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d7d15b9285d439c3ca80b97b0ed6cc98a2df22c481de1848b77117563ddba14", size = 259363, upload-time = "2025-06-28T14:36:55.514Z" }, + { url = "https://files.pythonhosted.org/packages/ae/82/1fa2fbdc85d98b6c764b4a49e22e118b8d987f1fb5936cadfbdf091f06ef/multidict-6.6.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5244c5c12889d84b9b7bf22f875e52c5ba4daa89c8ab92719863a14cd76dd04d", size = 252014, upload-time = "2025-06-28T14:36:56.734Z" }, + { url = "https://files.pythonhosted.org/packages/59/bc/21e7c4bb6e7911cac9fb41d4b295abb2d98c2123196d7c692e9e6e9f1ac4/multidict-6.6.2-cp312-cp312-win32.whl", hash = "sha256:a2ec0e52d7b298d53983cc4987fe76a25e033305f58d597fbcc1ff139b5e417e", size = 41826, upload-time = "2025-06-28T14:36:57.924Z" }, + { url = "https://files.pythonhosted.org/packages/61/57/5bd2019d7b2a5846c75372b1a5d994358739649d8863c73c37f2f7a418a6/multidict-6.6.2-cp312-cp312-win_amd64.whl", hash = "sha256:96d2d55c01ce4ec000a1b6eadbaa22971c91ec512819abee8b5b13f4af3fd566", size = 45920, upload-time = "2025-06-28T14:36:59.352Z" }, + { url = "https://files.pythonhosted.org/packages/91/c1/b1038c82ccc2e2ae3c40c912b8ee6a45ed0c9349dffdd1c3fc073f733ee9/multidict-6.6.2-cp312-cp312-win_arm64.whl", hash = "sha256:a0af3b15eab84e0d4f62a365927070d7f200db7efb8bb1e17de7c14fab5183bb", size = 43207, upload-time = "2025-06-28T14:37:00.872Z" }, + { url = "https://files.pythonhosted.org/packages/0c/30/7b7d121f76ea3ea7561814531e5cc19e75e9b6646818491179c2c875b591/multidict-6.6.2-py3-none-any.whl", hash = "sha256:a7d14275ff2f85a8ff3c2a32e30f94b9fc8a2125b59a4ecc32271a347fad6e78", size = 12312, upload-time = "2025-06-28T14:38:19.677Z" }, ] [[package]] @@ -1845,164 +1839,164 @@ name = "pyobjc" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-accessibility", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-accounts", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-addressbook" }, - { name = "pyobjc-framework-adservices", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-adsupport", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-applescriptkit" }, - { name = "pyobjc-framework-applescriptobjc", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-applicationservices" }, - { name = "pyobjc-framework-apptrackingtransparency", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-audiovideobridging", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-authenticationservices", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-automaticassessmentconfiguration", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-automator" }, - { name = "pyobjc-framework-avfoundation", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-avkit", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-avrouting", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-backgroundassets", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-browserenginekit", marker = "platform_release >= '23.4'" }, - { name = "pyobjc-framework-businesschat", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-calendarstore", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-callkit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-carbon" }, - { name = "pyobjc-framework-cfnetwork" }, - { name = "pyobjc-framework-cinematic", marker = "platform_release >= '23.0'" }, - { name = "pyobjc-framework-classkit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-cloudkit", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-collaboration", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-colorsync", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-contacts", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-contactsui", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-coreaudio" }, - { name = "pyobjc-framework-coreaudiokit" }, - { name = "pyobjc-framework-corebluetooth", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-coredata" }, - { name = "pyobjc-framework-corehaptics", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-corelocation", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-coremedia", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-coremediaio", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-coremidi" }, - { name = "pyobjc-framework-coreml", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-coremotion", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-coreservices" }, - { name = "pyobjc-framework-corespotlight", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-coretext" }, - { name = "pyobjc-framework-corewlan", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-cryptotokenkit", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-datadetection", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-devicecheck", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-devicediscoveryextension", marker = "platform_release >= '24.0'" }, - { name = "pyobjc-framework-dictionaryservices", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-discrecording" }, - { name = "pyobjc-framework-discrecordingui" }, - { name = "pyobjc-framework-diskarbitration" }, - { name = "pyobjc-framework-dvdplayback" }, - { name = "pyobjc-framework-eventkit", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-exceptionhandling" }, - { name = "pyobjc-framework-executionpolicy", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-extensionkit", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-externalaccessory", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-fileprovider", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-fileproviderui", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-findersync", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-fsevents", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-fskit", marker = "platform_release >= '24.4'" }, - { name = "pyobjc-framework-gamecenter", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-gamecontroller", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-gamekit", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-gameplaykit", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-healthkit", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-imagecapturecore", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-inputmethodkit", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-installerplugins" }, - { name = "pyobjc-framework-instantmessage", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-intents", marker = "platform_release >= '16.0'" }, - { name = "pyobjc-framework-intentsui", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-iobluetooth" }, - { name = "pyobjc-framework-iobluetoothui" }, - { name = "pyobjc-framework-iosurface", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-ituneslibrary", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-kernelmanagement", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-latentsemanticmapping" }, - { name = "pyobjc-framework-launchservices" }, - { name = "pyobjc-framework-libdispatch", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-libxpc", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-linkpresentation", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-localauthentication", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-localauthenticationembeddedui", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-mailkit", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-mapkit", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-mediaaccessibility", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-mediaextension", marker = "platform_release >= '24.0'" }, - { name = "pyobjc-framework-medialibrary", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-mediaplayer", marker = "platform_release >= '16.0'" }, - { name = "pyobjc-framework-mediatoolbox", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-metal", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-metalfx", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-metalkit", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-metalperformanceshaders", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-metalperformanceshadersgraph", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-metrickit", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-mlcompute", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-modelio", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-multipeerconnectivity", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-naturallanguage", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-netfs", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-network", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-networkextension", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-notificationcenter", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-opendirectory", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-osakit" }, - { name = "pyobjc-framework-oslog", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-passkit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-pencilkit", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-phase", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-photos", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-photosui", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-preferencepanes" }, - { name = "pyobjc-framework-pushkit", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-quartz" }, - { name = "pyobjc-framework-quicklookthumbnailing", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-replaykit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-safariservices", marker = "platform_release >= '16.0'" }, - { name = "pyobjc-framework-safetykit", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-scenekit", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-screencapturekit", marker = "platform_release >= '21.4'" }, - { name = "pyobjc-framework-screensaver" }, - { name = "pyobjc-framework-screentime", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-scriptingbridge", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-searchkit" }, - { name = "pyobjc-framework-security" }, - { name = "pyobjc-framework-securityfoundation" }, - { name = "pyobjc-framework-securityinterface" }, - { name = "pyobjc-framework-securityui", marker = "platform_release >= '24.4'" }, - { name = "pyobjc-framework-sensitivecontentanalysis", marker = "platform_release >= '23.0'" }, - { name = "pyobjc-framework-servicemanagement", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-sharedwithyou", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-sharedwithyoucore", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-shazamkit", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-social", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-soundanalysis", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-speech", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-spritekit", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-storekit", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-symbols", marker = "platform_release >= '23.0'" }, - { name = "pyobjc-framework-syncservices" }, - { name = "pyobjc-framework-systemconfiguration" }, - { name = "pyobjc-framework-systemextensions", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-threadnetwork", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-uniformtypeidentifiers", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-usernotifications", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-usernotificationsui", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-videosubscriberaccount", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-videotoolbox", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-virtualization", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-vision", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-webkit" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-accessibility", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-accounts", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-addressbook", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-adservices", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-adsupport", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-applescriptkit", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-applescriptobjc", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-applicationservices", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-apptrackingtransparency", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-audiovideobridging", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-authenticationservices", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-automaticassessmentconfiguration", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-automator", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-avfoundation", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-avkit", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-avrouting", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-backgroundassets", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-browserenginekit", marker = "platform_release >= '23.4' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-businesschat", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-calendarstore", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-callkit", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-carbon", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cfnetwork", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cinematic", marker = "platform_release >= '23.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-classkit", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cloudkit", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-collaboration", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-colorsync", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-contacts", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-contactsui", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreaudiokit", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-corebluetooth", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coredata", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-corehaptics", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-corelocation", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremedia", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremediaio", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremidi", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreml", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremotion", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-corespotlight", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coretext", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-corewlan", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cryptotokenkit", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-datadetection", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-devicecheck", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-devicediscoveryextension", marker = "platform_release >= '24.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-dictionaryservices", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-discrecording", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-discrecordingui", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-diskarbitration", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-dvdplayback", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-eventkit", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-exceptionhandling", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-executionpolicy", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-extensionkit", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-externalaccessory", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-fileprovider", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-fileproviderui", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-findersync", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-fsevents", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-fskit", marker = "platform_release >= '24.4' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-gamecenter", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-gamecontroller", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-gamekit", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-gameplaykit", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-healthkit", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-imagecapturecore", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-inputmethodkit", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-installerplugins", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-instantmessage", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-intents", marker = "platform_release >= '16.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-intentsui", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-iobluetooth", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-iobluetoothui", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-iosurface", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-ituneslibrary", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-kernelmanagement", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-latentsemanticmapping", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-launchservices", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-libdispatch", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-libxpc", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-linkpresentation", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-localauthentication", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-localauthenticationembeddedui", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-mailkit", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-mapkit", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-mediaaccessibility", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-mediaextension", marker = "platform_release >= '24.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-medialibrary", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-mediaplayer", marker = "platform_release >= '16.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-mediatoolbox", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metal", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metalfx", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metalkit", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metalperformanceshaders", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metalperformanceshadersgraph", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metrickit", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-mlcompute", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-modelio", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-multipeerconnectivity", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-naturallanguage", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-netfs", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-network", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-networkextension", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-notificationcenter", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-opendirectory", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-osakit", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-oslog", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-passkit", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-pencilkit", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-phase", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-photos", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-photosui", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-preferencepanes", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-pushkit", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quicklookthumbnailing", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-replaykit", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-safariservices", marker = "platform_release >= '16.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-safetykit", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-scenekit", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-screencapturekit", marker = "platform_release >= '21.4' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-screensaver", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-screentime", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-scriptingbridge", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-searchkit", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-securityfoundation", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-securityinterface", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-securityui", marker = "platform_release >= '24.4' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-sensitivecontentanalysis", marker = "platform_release >= '23.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-servicemanagement", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-sharedwithyou", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-sharedwithyoucore", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-shazamkit", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-social", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-soundanalysis", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-speech", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-spritekit", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-storekit", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-symbols", marker = "platform_release >= '23.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-syncservices", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-systemconfiguration", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-systemextensions", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-threadnetwork", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-uniformtypeidentifiers", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-usernotifications", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-usernotificationsui", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-videosubscriberaccount", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-videotoolbox", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-virtualization", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-vision", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-webkit", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/db/5e/16bc372806790d295c76b5c7851767cc9ee3787b3e581f5d7cc44158e4e0/pyobjc-11.1.tar.gz", hash = "sha256:a71b14389657811d658526ba4d5faba4ef7eadbddcf9fe8bf4fb3a6261effba3", size = 11161, upload-time = "2025-06-14T20:56:32.819Z" } wheels = [ @@ -2024,9 +2018,9 @@ name = "pyobjc-framework-accessibility" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/78/b4/10c16e9d48568a68da2f61866b19468d4ac7129c377d4b1333ee936ae5d0/pyobjc_framework_accessibility-11.1.tar.gz", hash = "sha256:c0fa5f1e00906ec002f582c7d3d80463a46d19f672bf5ec51144f819eeb40656", size = 45098, upload-time = "2025-06-14T20:56:35.287Z" } wheels = [ @@ -2039,8 +2033,8 @@ name = "pyobjc-framework-accounts" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/12/45/ca21003f68ad0f13b5a9ac1761862ad2ddd83224b4314a2f7d03ca437c8d/pyobjc_framework_accounts-11.1.tar.gz", hash = "sha256:384fec156e13ff75253bb094339013f4013464f6dfd47e2f7de3e2ae7441c030", size = 17086, upload-time = "2025-06-14T20:56:36.035Z" } wheels = [ @@ -2052,8 +2046,8 @@ name = "pyobjc-framework-addressbook" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/d3/f5bb5c72be5c6e52224f43e23e5a44e86d2c35ee9af36939e5514c6c7a0f/pyobjc_framework_addressbook-11.1.tar.gz", hash = "sha256:ce2db3be4a3128bf79d5c41319a6d16b73754785ce75ac694d0d658c690922fc", size = 97609, upload-time = "2025-06-14T20:56:37.324Z" } wheels = [ @@ -2066,8 +2060,8 @@ name = "pyobjc-framework-adservices" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2a/3f/af76eab6eee0a405a4fdee172e7181773040158476966ecd757b0a98bfc5/pyobjc_framework_adservices-11.1.tar.gz", hash = "sha256:44c72f8163705c9aa41baca938fdb17dde257639e5797e6a5c3a2b2d8afdade9", size = 12473, upload-time = "2025-06-14T20:56:38.147Z" } wheels = [ @@ -2079,8 +2073,8 @@ name = "pyobjc-framework-adsupport" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7f/03/9c51edd964796a97def4e1433d76a128dd7059b685fb4366081bf4e292ba/pyobjc_framework_adsupport-11.1.tar.gz", hash = "sha256:78b9667c275785df96219d205bd4309731869c3298d0931e32aed83bede29096", size = 12556, upload-time = "2025-06-14T20:56:38.741Z" } wheels = [ @@ -2092,8 +2086,8 @@ name = "pyobjc-framework-applescriptkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bc/63/1bcfcdca53bf5bba3a7b4d73d24232ae1721a378a32fd4ebc34a35549df2/pyobjc_framework_applescriptkit-11.1.tar.gz", hash = "sha256:477707352eaa6cc4a5f8c593759dc3227a19d5958481b1482f0d59394a4601c3", size = 12392, upload-time = "2025-06-14T20:56:39.331Z" } wheels = [ @@ -2105,8 +2099,8 @@ name = "pyobjc-framework-applescriptobjc" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a3/27/687b55b575367df045879b786f358355e40e41f847968e557d0718a6c4a4/pyobjc_framework_applescriptobjc-11.1.tar.gz", hash = "sha256:c8a0ec975b64411a4f16a1280c5ea8dbe949fd361e723edd343102f0f95aba6e", size = 12445, upload-time = "2025-06-14T20:56:39.976Z" } wheels = [ @@ -2118,10 +2112,10 @@ name = "pyobjc-framework-applicationservices" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coretext" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coretext", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/3f/b33ce0cecc3a42f6c289dcbf9ff698b0d9e85f5796db2e9cb5dadccffbb9/pyobjc_framework_applicationservices-11.1.tar.gz", hash = "sha256:03fcd8c0c600db98fa8b85eb7b3bc31491701720c795e3f762b54e865138bbaf", size = 224842, upload-time = "2025-06-14T20:56:40.648Z" } wheels = [ @@ -2134,8 +2128,8 @@ name = "pyobjc-framework-apptrackingtransparency" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/49/68/7aa3afffd038dd6e5af764336bca734eb910121013ca71030457b61e5b99/pyobjc_framework_apptrackingtransparency-11.1.tar.gz", hash = "sha256:796cc5f83346c10973806cfb535d4200b894a5d2626ff2eeb1972d594d14fed4", size = 13135, upload-time = "2025-06-14T20:56:41.494Z" } wheels = [ @@ -2147,8 +2141,8 @@ name = "pyobjc-framework-audiovideobridging" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c3/25/6c5a7b1443d30139cc722029880284ea9dfa575f0436471b9364fcd499f5/pyobjc_framework_audiovideobridging-11.1.tar.gz", hash = "sha256:12756b3aa35083b8ad5c9139b6a0e2f4792e217096b5bf6b702d499038203991", size = 72913, upload-time = "2025-06-14T20:56:42.128Z" } wheels = [ @@ -2161,8 +2155,8 @@ name = "pyobjc-framework-authenticationservices" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8f/b7/3e9ad0ed3625dc02e495615ea5dbf55ca95cbd25b3e31f25092f5caad640/pyobjc_framework_authenticationservices-11.1.tar.gz", hash = "sha256:8fd801cdb53d426b4e678b0a8529c005d0c44f5a17ccd7052a7c3a1a87caed6a", size = 115266, upload-time = "2025-06-14T20:56:42.889Z" } wheels = [ @@ -2175,8 +2169,8 @@ name = "pyobjc-framework-automaticassessmentconfiguration" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3d/39/d4c94e0245d290b83919854c4f205851cc0b2603f843448fdfb8e74aad71/pyobjc_framework_automaticassessmentconfiguration-11.1.tar.gz", hash = "sha256:70eadbf8600101901a56fcd7014d8941604e14f3b3728bc4fb0178a9a9420032", size = 24933, upload-time = "2025-06-14T20:56:43.984Z" } wheels = [ @@ -2189,8 +2183,8 @@ name = "pyobjc-framework-automator" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/63/9f/097ed9f4de9e9491a1b08bb7d85d35a95d726c9e9f5f5bf203b359a436b6/pyobjc_framework_automator-11.1.tar.gz", hash = "sha256:9b46c55a4f9ae2b3c39ff560f42ced66bdd18c093188f0b5fc4060ad911838e4", size = 201439, upload-time = "2025-06-14T20:56:44.767Z" } wheels = [ @@ -2203,11 +2197,11 @@ name = "pyobjc-framework-avfoundation" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coreaudio" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3c/1f/90cdbce1d3b4861cbb17c12adf57daeec32477eb1df8d3f9ab8551bdadfb/pyobjc_framework_avfoundation-11.1.tar.gz", hash = "sha256:6663056cc6ca49af8de6d36a7fff498f51e1a9a7f1bde7afba718a8ceaaa7377", size = 832178, upload-time = "2025-06-14T20:56:46.329Z" } wheels = [ @@ -2220,9 +2214,9 @@ name = "pyobjc-framework-avkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/61/ff/9f41f2b8de786871184b48c4e5052cb7c9fcc204e7fee06687fa32b08bed/pyobjc_framework_avkit-11.1.tar.gz", hash = "sha256:d948204a7b94e0e878b19a909f9b33342e19d9ea519571d66a21fce8f72e3263", size = 46825, upload-time = "2025-06-14T20:56:47.494Z" } wheels = [ @@ -2235,8 +2229,8 @@ name = "pyobjc-framework-avrouting" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cf/42/94bc18b968a4ee8b6427257f907ffbfc97f8ba6a6202953da149b649d638/pyobjc_framework_avrouting-11.1.tar.gz", hash = "sha256:7db1291d9f53cc58d34b2a826feb721a85f50ceb5e71952e8762baacd3db3fc0", size = 21069, upload-time = "2025-06-14T20:56:48.57Z" } wheels = [ @@ -2249,8 +2243,8 @@ name = "pyobjc-framework-backgroundassets" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/08/76/21e1632a212f997d7a5f26d53eb997951978916858039b79f43ebe3d10b2/pyobjc_framework_backgroundassets-11.1.tar.gz", hash = "sha256:2e14b50539d96d5fca70c49f21b69fdbad81a22549e3630f5e4f20d5c0204fc2", size = 24803, upload-time = "2025-06-14T20:56:49.566Z" } wheels = [ @@ -2263,11 +2257,11 @@ name = "pyobjc-framework-browserenginekit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coreaudio" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/30/75/087270d9f81e913b57c7db58eaff8691fa0574b11faf9302340b3b8320f1/pyobjc_framework_browserenginekit-11.1.tar.gz", hash = "sha256:918440cefb10480024f645169de3733e30ede65e41267fa12c7b90c264a0a479", size = 31944, upload-time = "2025-06-14T20:56:50.195Z" } wheels = [ @@ -2280,8 +2274,8 @@ name = "pyobjc-framework-businesschat" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/85/be/9d9d9d9383c411a58323ea510d768443287ca21610af652b815b3205ea80/pyobjc_framework_businesschat-11.1.tar.gz", hash = "sha256:69589d2f0cb4e7892e5ecc6aed79b1abd1ec55c099a7faacae6a326bc921259d", size = 12698, upload-time = "2025-06-14T20:56:51.173Z" } wheels = [ @@ -2293,8 +2287,8 @@ name = "pyobjc-framework-calendarstore" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/41/df/7ca8ee65b16d5fc862d7e8664289472eed918cf4d76921de6bdaa1461c65/pyobjc_framework_calendarstore-11.1.tar.gz", hash = "sha256:858ee00e6a380d9c086c2d7db82c116a6c406234038e0ec8fc2ad02e385dc437", size = 68215, upload-time = "2025-06-14T20:56:51.799Z" } wheels = [ @@ -2306,8 +2300,8 @@ name = "pyobjc-framework-callkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/51/d5/4f0b62ab35be619e8c8d96538a03cf56fde6fd53540e1837e0fa588b3f6c/pyobjc_framework_callkit-11.1.tar.gz", hash = "sha256:b84d5ea38dff0cbe0754f5f9f6f33c742e216f12e7166179a8ec2cf4b0bfca94", size = 46648, upload-time = "2025-06-14T20:56:52.579Z" } wheels = [ @@ -2320,8 +2314,8 @@ name = "pyobjc-framework-carbon" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/39/a4/d751851865d9a78405cfec0c8b2931b1e96b9914e9788cd441fa4e8290d0/pyobjc_framework_carbon-11.1.tar.gz", hash = "sha256:047f098535479efa3ab89da1ebdf3cf9ec0b439a33a4f32806193886e9fcea71", size = 37291, upload-time = "2025-06-14T20:56:53.642Z" } wheels = [ @@ -2333,8 +2327,8 @@ name = "pyobjc-framework-cfnetwork" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6f/49/7b24172e3d6eb0ddffc33a7498a2bea264aa2958c3fecaeb463bef88f0b8/pyobjc_framework_cfnetwork-11.1.tar.gz", hash = "sha256:ad600163eeadb7bf71abc51a9b6f2b5462a018d3f9bb1510c5ce3fdf2f22959d", size = 79069, upload-time = "2025-06-14T20:56:54.615Z" } wheels = [ @@ -2347,11 +2341,11 @@ name = "pyobjc-framework-cinematic" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-avfoundation" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-metal" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/57/6f/c2d0b49e01e654496a1781bafb9da72a6fbd00f5abb39dc4a3a0045167c7/pyobjc_framework_cinematic-11.1.tar.gz", hash = "sha256:efde39a6a2379e1738dbc5434b2470cd187cf3114ffb81390b3b1abda470b382", size = 25522, upload-time = "2025-06-14T20:56:55.379Z" } wheels = [ @@ -2363,8 +2357,8 @@ name = "pyobjc-framework-classkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/8b/5150b4faddd15d5dd795bc62b2256c4f7dafc983cfa694fcf88121ea0016/pyobjc_framework_classkit-11.1.tar.gz", hash = "sha256:ee1e26395eb00b3ed5442e3234cdbfe925d2413185af38eca0477d7166651df4", size = 39831, upload-time = "2025-06-14T20:56:56.036Z" } wheels = [ @@ -2377,11 +2371,11 @@ name = "pyobjc-framework-cloudkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-accounts" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coredata" }, - { name = "pyobjc-framework-corelocation" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-accounts", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coredata", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-corelocation", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/a6/bfe5be55ed95704efca0e86b218155a9c801735107cedba3af8ea4580a05/pyobjc_framework_cloudkit-11.1.tar.gz", hash = "sha256:40d2dc4bf28c5be9b836b01e4d267a15d847d756c2a65530e1fcd79b2825e86d", size = 122778, upload-time = "2025-06-14T20:56:56.73Z" } wheels = [ @@ -2393,7 +2387,7 @@ name = "pyobjc-framework-cocoa" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4b/c5/7a866d24bc026f79239b74d05e2cf3088b03263da66d53d1b4cf5207f5ae/pyobjc_framework_cocoa-11.1.tar.gz", hash = "sha256:87df76b9b73e7ca699a828ff112564b59251bb9bbe72e610e670a4dc9940d038", size = 5565335, upload-time = "2025-06-14T20:56:59.683Z" } wheels = [ @@ -2406,8 +2400,8 @@ name = "pyobjc-framework-collaboration" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/49/9dbe8407d5dd663747267c1234d1b914bab66e1878d22f57926261a3063b/pyobjc_framework_collaboration-11.1.tar.gz", hash = "sha256:4564e3931bfc51773623d4f57f2431b58a39b75cb964ae5c48d27ee4dde2f4ea", size = 16839, upload-time = "2025-06-14T20:57:01.101Z" } wheels = [ @@ -2419,8 +2413,8 @@ name = "pyobjc-framework-colorsync" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b5/97/7613b6041f62c52f972e42dd5d79476b56b84d017a8b5e4add4d9cfaca36/pyobjc_framework_colorsync-11.1.tar.gz", hash = "sha256:7a346f71f34b2ccd1b020a34c219b85bf8b6f6e05283d503185aeb7767a269dd", size = 38999, upload-time = "2025-06-14T20:57:01.761Z" } wheels = [ @@ -2432,8 +2426,8 @@ name = "pyobjc-framework-contacts" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a6/85/34868b6447d552adf8674bac226b55c2baacacee0d67ee031e33805d6faa/pyobjc_framework_contacts-11.1.tar.gz", hash = "sha256:752036e7d8952a4122296d7772f274170a5f35a53ee6454a27f3e1d9603222cc", size = 84814, upload-time = "2025-06-14T20:57:02.582Z" } wheels = [ @@ -2446,9 +2440,9 @@ name = "pyobjc-framework-contactsui" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-contacts" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-contacts", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3f/57/8765b54a30edaa2a56df62e11e7c32e41b6ea300513256adffa191689368/pyobjc_framework_contactsui-11.1.tar.gz", hash = "sha256:5bc29ea2b10a342018e1b96be6b140c10ebe3cfb6417278770feef5e88026a1f", size = 20031, upload-time = "2025-06-14T20:57:03.603Z" } wheels = [ @@ -2461,8 +2455,8 @@ name = "pyobjc-framework-coreaudio" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/39/c0/4ab6005cf97e534725b0c14b110d4864b367c282b1c5b0d8f42aad74a83f/pyobjc_framework_coreaudio-11.1.tar.gz", hash = "sha256:b7b89540ae7efc6c1e3208ac838ef2acfc4d2c506dd629d91f6b3b3120e55c1b", size = 141032, upload-time = "2025-06-14T20:57:04.348Z" } wheels = [ @@ -2475,9 +2469,9 @@ name = "pyobjc-framework-coreaudiokit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coreaudio" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f1/4e/c49b26c60047c511727efe994b412276c487dfe90f1ee0fced0bddbdf8a3/pyobjc_framework_coreaudiokit-11.1.tar.gz", hash = "sha256:0b461c3d6123fda4da6b6aaa022efc918c1de2e126a5cf07d2189d63fa54ba40", size = 21955, upload-time = "2025-06-14T20:57:05.218Z" } wheels = [ @@ -2490,8 +2484,8 @@ name = "pyobjc-framework-corebluetooth" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3d/fe/2081dfd9413b7b4d719935c33762fbed9cce9dc06430f322d1e2c9dbcd91/pyobjc_framework_corebluetooth-11.1.tar.gz", hash = "sha256:1deba46e3fcaf5e1c314f4bbafb77d9fe49ec248c493ad00d8aff2df212d6190", size = 60337, upload-time = "2025-06-14T20:57:05.919Z" } wheels = [ @@ -2504,8 +2498,8 @@ name = "pyobjc-framework-coredata" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/00/e3/af497da7a7c895b6ff529d709d855a783f34afcc4b87ab57a1a2afb3f876/pyobjc_framework_coredata-11.1.tar.gz", hash = "sha256:fe9fd985f8e06c70c0fb1e6bbea5b731461f9e76f8f8d8e89c7c72667cdc6adf", size = 260628, upload-time = "2025-06-14T20:57:06.729Z" } wheels = [ @@ -2518,8 +2512,8 @@ name = "pyobjc-framework-corehaptics" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5f/83/cc997ec4687a68214dd3ad1bdf64353305f5c7e827fad211adac4c28b39f/pyobjc_framework_corehaptics-11.1.tar.gz", hash = "sha256:e5da3a97ed6aca9b7268c8c5196c0a339773a50baa72d1502d3435dc1a2a80f1", size = 42722, upload-time = "2025-06-14T20:57:08.019Z" } wheels = [ @@ -2531,8 +2525,8 @@ name = "pyobjc-framework-corelocation" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/95/ef/fbd2e01ec137208af7bfefe222773748d27f16f845b0efa950d65e2bd719/pyobjc_framework_corelocation-11.1.tar.gz", hash = "sha256:46a67b99925ee3d53914331759c6ee110b31bb790b74b05915acfca41074c206", size = 104508, upload-time = "2025-06-14T20:57:08.731Z" } wheels = [ @@ -2545,8 +2539,8 @@ name = "pyobjc-framework-coremedia" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/95/5d/81513acd219df77a89176f1574d936b81ad6f6002225cabb64d55efb7e8d/pyobjc_framework_coremedia-11.1.tar.gz", hash = "sha256:82cdc087f61e21b761e677ea618a575d4c0dbe00e98230bf9cea540cff931db3", size = 216389, upload-time = "2025-06-14T20:57:09.546Z" } wheels = [ @@ -2559,8 +2553,8 @@ name = "pyobjc-framework-coremediaio" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/64/68/9cef2aefba8e69916049ff43120e8794df8051bdf1f690a55994bbe4eb57/pyobjc_framework_coremediaio-11.1.tar.gz", hash = "sha256:bccd69712578b177144ded398f4695d71a765ef61204da51a21f0c90b4ad4c64", size = 108326, upload-time = "2025-06-14T20:57:10.435Z" } wheels = [ @@ -2573,8 +2567,8 @@ name = "pyobjc-framework-coremidi" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/ca/2ae5149966ccd78290444f88fa62022e2b96ed2fddd47e71d9fd249a9f82/pyobjc_framework_coremidi-11.1.tar.gz", hash = "sha256:095030c59d50c23aa53608777102bc88744ff8b10dfb57afe24b428dcd12e376", size = 107817, upload-time = "2025-06-14T20:57:11.245Z" } wheels = [ @@ -2587,8 +2581,8 @@ name = "pyobjc-framework-coreml" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0d/5d/4309f220981d769b1a2f0dcb2c5c104490d31389a8ebea67e5595ce1cb74/pyobjc_framework_coreml-11.1.tar.gz", hash = "sha256:775923eefb9eac2e389c0821b10564372de8057cea89f1ea1cdaf04996c970a7", size = 82005, upload-time = "2025-06-14T20:57:12.004Z" } wheels = [ @@ -2601,8 +2595,8 @@ name = "pyobjc-framework-coremotion" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a5/95/e469dc7100ea6b9c29a074a4f713d78b32a78d7ec5498c25c83a56744fc2/pyobjc_framework_coremotion-11.1.tar.gz", hash = "sha256:5884a568521c0836fac39d46683a4dea3d259a23837920897042ffb922d9ac3e", size = 67050, upload-time = "2025-06-14T20:57:12.705Z" } wheels = [ @@ -2615,9 +2609,9 @@ name = "pyobjc-framework-coreservices" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-fsevents" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-fsevents", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/a9/141d18019a25776f507992f9e7ffc051ca5a734848d8ea8d848f7c938efc/pyobjc_framework_coreservices-11.1.tar.gz", hash = "sha256:cf8eb5e272c60a96d025313eca26ff2487dcd02c47034cc9db39f6852d077873", size = 1245086, upload-time = "2025-06-14T20:57:13.914Z" } wheels = [ @@ -2630,8 +2624,8 @@ name = "pyobjc-framework-corespotlight" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/31/c7/b67ebfb63b7ccbfda780d583056d1fd4b610ba3839c8ebe3435b86122c61/pyobjc_framework_corespotlight-11.1.tar.gz", hash = "sha256:4dd363c8d3ff7619659b63dd31400f135b03e32435b5d151459ecdacea14e0f2", size = 87161, upload-time = "2025-06-14T20:57:14.934Z" } wheels = [ @@ -2644,9 +2638,9 @@ name = "pyobjc-framework-coretext" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/65/e9/d3231c4f87d07b8525401fd6ad3c56607c9e512c5490f0a7a6abb13acab6/pyobjc_framework_coretext-11.1.tar.gz", hash = "sha256:a29bbd5d85c77f46a8ee81d381b847244c88a3a5a96ac22f509027ceceaffaf6", size = 274702, upload-time = "2025-06-14T20:57:16.059Z" } wheels = [ @@ -2659,8 +2653,8 @@ name = "pyobjc-framework-corewlan" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c6/d8/03aff3c75485fc999e260946ef1e9adf17640a6e08d7bf603d31cfcf73fc/pyobjc_framework_corewlan-11.1.tar.gz", hash = "sha256:4a8afea75393cc0a6fe696e136233aa0ed54266f35a47b55a3583f4cb078e6ce", size = 65792, upload-time = "2025-06-14T20:57:16.931Z" } wheels = [ @@ -2673,8 +2667,8 @@ name = "pyobjc-framework-cryptotokenkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/92/7fab6fcc6bb659d6946cfb2f670058180bcc4ca1626878b0f7c95107abf0/pyobjc_framework_cryptotokenkit-11.1.tar.gz", hash = "sha256:5f82f44d9ab466c715a7c8ad4d5ec47c68aacd78bd67b5466a7b8215a2265328", size = 59223, upload-time = "2025-06-14T20:57:17.658Z" } wheels = [ @@ -2687,8 +2681,8 @@ name = "pyobjc-framework-datadetection" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7d/4d/65c61d8878b44689e28d5729be9edbb73e20b1b0500d1095172cfd24aea6/pyobjc_framework_datadetection-11.1.tar.gz", hash = "sha256:cbe0080b51e09b2f91eaf2a9babec3dcf2883d7966bc0abd8393ef7abfcfc5db", size = 13485, upload-time = "2025-06-14T20:57:18.829Z" } wheels = [ @@ -2700,8 +2694,8 @@ name = "pyobjc-framework-devicecheck" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f3/f2/b1d263f8231f815a9eeff15809f4b7428dacdc0a6aa267db5ed907445066/pyobjc_framework_devicecheck-11.1.tar.gz", hash = "sha256:8b05973eb2673571144d81346336e749a21cec90bd7fcaade76ffd3b147a0741", size = 13954, upload-time = "2025-06-14T20:57:19.782Z" } wheels = [ @@ -2713,8 +2707,8 @@ name = "pyobjc-framework-devicediscoveryextension" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9a/b8/102863bfa2f1e414c88bb9f51151a9a58b99c268a841b59d46e0dcc5fe6d/pyobjc_framework_devicediscoveryextension-11.1.tar.gz", hash = "sha256:ae160ea40f25d3ee5e7ce80ac9c1b315f94d0a4c7ccb86920396f71c6bf799a0", size = 14298, upload-time = "2025-06-14T20:57:20.738Z" } wheels = [ @@ -2726,8 +2720,8 @@ name = "pyobjc-framework-dictionaryservices" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-coreservices" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d6/13/c46f6db61133fee15e3471f33a679da2af10d63fa2b4369e0cd476988721/pyobjc_framework_dictionaryservices-11.1.tar.gz", hash = "sha256:39c24452d0ddd037afeb73a1742614c94535f15b1c024a8a6cc7ff081e1d22e7", size = 10578, upload-time = "2025-06-14T20:57:21.392Z" } wheels = [ @@ -2739,8 +2733,8 @@ name = "pyobjc-framework-discrecording" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a5/b2/d8d1a28643c2ab681b517647bacb68496c98886336ffbd274f0b2ad28cdc/pyobjc_framework_discrecording-11.1.tar.gz", hash = "sha256:37585458e363b20bb28acdb5cc265dfca934d8a07b7baed2584953c11c927a87", size = 123004, upload-time = "2025-06-14T20:57:22.01Z" } wheels = [ @@ -2753,9 +2747,9 @@ name = "pyobjc-framework-discrecordingui" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-discrecording" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-discrecording", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/25/53/d71717f00332b8fc3d8a5c7234fdc270adadfeb5ca9318a55986f5c29c44/pyobjc_framework_discrecordingui-11.1.tar.gz", hash = "sha256:a9f10e2e7ee19582c77f0755ae11a64e3d61c652cbd8a5bf52756f599be24797", size = 19370, upload-time = "2025-06-14T20:57:22.791Z" } wheels = [ @@ -2767,8 +2761,8 @@ name = "pyobjc-framework-diskarbitration" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/da/2a/68fa0c99e04ec1ec24b0b7d6f5b7ec735d5e8a73277c5c0671438a69a403/pyobjc_framework_diskarbitration-11.1.tar.gz", hash = "sha256:a933efc6624779a393fafe0313e43378bcae2b85d6d15cff95ac30048c1ef490", size = 19866, upload-time = "2025-06-14T20:57:23.435Z" } wheels = [ @@ -2780,8 +2774,8 @@ name = "pyobjc-framework-dvdplayback" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b8/76/77046325b1957f0cbcdf4f96667496d042ed4758f3413f1d21df5b085939/pyobjc_framework_dvdplayback-11.1.tar.gz", hash = "sha256:b44c36a62c8479e649133216e22941859407cca5796b5f778815ef9340a838f4", size = 64558, upload-time = "2025-06-14T20:57:24.118Z" } wheels = [ @@ -2793,8 +2787,8 @@ name = "pyobjc-framework-eventkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b4/c4/cbba8f2dce13b9be37ecfd423ba2b92aa3f209dbb58ede6c4ce3b242feee/pyobjc_framework_eventkit-11.1.tar.gz", hash = "sha256:5643150f584243681099c5e9435efa833a913e93fe9ca81f62007e287349b561", size = 75177, upload-time = "2025-06-14T20:57:24.81Z" } wheels = [ @@ -2806,8 +2800,8 @@ name = "pyobjc-framework-exceptionhandling" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/19/0d/c72a885b40d28a99b586447f9ea6f400589f13d554fcd6f13a2c841bb6d2/pyobjc_framework_exceptionhandling-11.1.tar.gz", hash = "sha256:e010f56bf60ab4e9e3225954ebb53e9d7135d37097043ac6dd2a3f35770d4efa", size = 17890, upload-time = "2025-06-14T20:57:25.521Z" } wheels = [ @@ -2819,8 +2813,8 @@ name = "pyobjc-framework-executionpolicy" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0b/cf/54431846508c5d5bb114a415ebb96187da5847105918169e42f4ca3b00e6/pyobjc_framework_executionpolicy-11.1.tar.gz", hash = "sha256:3280ad2f4c5eaf45901f310cee0c52db940c0c63e959ad082efb8df41055d986", size = 13496, upload-time = "2025-06-14T20:57:26.173Z" } wheels = [ @@ -2832,8 +2826,8 @@ name = "pyobjc-framework-extensionkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ce/7d/89adf16c7de4246477714dce8fcffae4242778aecd0c5f0ad9904725f42c/pyobjc_framework_extensionkit-11.1.tar.gz", hash = "sha256:c114a96f13f586dbbab8b6219a92fa4829896a645c8cd15652a6215bc8ff5409", size = 19766, upload-time = "2025-06-14T20:57:27.106Z" } wheels = [ @@ -2846,8 +2840,8 @@ name = "pyobjc-framework-externalaccessory" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d9/a3/519242e6822e1ddc9e64e21f717529079dbc28a353474420da8315d0a8b1/pyobjc_framework_externalaccessory-11.1.tar.gz", hash = "sha256:50887e948b78a1d94646422c243ac2a9e40761675e38b9184487870a31e83371", size = 23123, upload-time = "2025-06-14T20:57:27.845Z" } wheels = [ @@ -2860,8 +2854,8 @@ name = "pyobjc-framework-fileprovider" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1b/80/3ebba2c1e5e3aeae989fe038c259a93e7e7e18fd56666ece514d000d38ea/pyobjc_framework_fileprovider-11.1.tar.gz", hash = "sha256:748ca1c75f84afdf5419346a24bf8eec44dca071986f31f00071dc191b3e9ca8", size = 91696, upload-time = "2025-06-14T20:57:28.546Z" } wheels = [ @@ -2874,8 +2868,8 @@ name = "pyobjc-framework-fileproviderui" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-fileprovider" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-fileprovider", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/75/ed/0f5af06869661822c4a70aacd674da5d1e6b6661240e2883bbc7142aa525/pyobjc_framework_fileproviderui-11.1.tar.gz", hash = "sha256:162a23e67f59e1bb247e84dda88d513d7944d815144901a46be6fe051b6c7970", size = 13163, upload-time = "2025-06-14T20:57:29.568Z" } wheels = [ @@ -2887,8 +2881,8 @@ name = "pyobjc-framework-findersync" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2a/82/c6b670494ac0c4cf14cf2db0dfbe0df71925d20595404939383ddbcc56d3/pyobjc_framework_findersync-11.1.tar.gz", hash = "sha256:692364937f418f0e4e4abd395a09a7d4a0cdd55fd4e0184de85ee59642defb6e", size = 15045, upload-time = "2025-06-14T20:57:30.173Z" } wheels = [ @@ -2900,8 +2894,8 @@ name = "pyobjc-framework-fsevents" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8e/83/ec0b9ba355dbc34f27ed748df9df4eb6dbfdd9bbd614b0f193752f36f419/pyobjc_framework_fsevents-11.1.tar.gz", hash = "sha256:d29157d04124503c4dfa9dcbbdc8c34d3bab134d3db3a48d96d93f26bd94c14d", size = 29587, upload-time = "2025-06-14T20:57:30.796Z" } wheels = [ @@ -2914,8 +2908,8 @@ name = "pyobjc-framework-fskit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/46/47/d1f04c6115fa78936399a389cc5e0e443f8341c9a6c1c0df7f6fdbe51286/pyobjc_framework_fskit-11.1.tar.gz", hash = "sha256:9ded1eab19b4183cb04381e554bbbe679c1213fd58599d6fc6e135e93b51136f", size = 42091, upload-time = "2025-06-14T20:57:31.504Z" } wheels = [ @@ -2928,8 +2922,8 @@ name = "pyobjc-framework-gamecenter" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1b/8e/b594fd1dc32a59462fc68ad502be2bd87c70e6359b4e879a99bcc4beaf5b/pyobjc_framework_gamecenter-11.1.tar.gz", hash = "sha256:a1c4ed54e11a6e4efba6f2a21ace92bcf186e3fe5c74a385b31f6b1a515ec20c", size = 31981, upload-time = "2025-06-14T20:57:32.192Z" } wheels = [ @@ -2942,8 +2936,8 @@ name = "pyobjc-framework-gamecontroller" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/70/4c/1dd62103092a182f2ab8904c8a8e3922d2b0a80a7adab0c20e5fd0207d75/pyobjc_framework_gamecontroller-11.1.tar.gz", hash = "sha256:4d5346faf90e1ebe5602c0c480afbf528a35a7a1ad05f9b49991fdd2a97f105b", size = 115783, upload-time = "2025-06-14T20:57:32.879Z" } wheels = [ @@ -2956,9 +2950,9 @@ name = "pyobjc-framework-gamekit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5b/7b/ba141ec0f85ca816f493d1f6fe68c72d01092e5562e53c470a0111d9c34b/pyobjc_framework_gamekit-11.1.tar.gz", hash = "sha256:9b8db075da8866c4ef039a165af227bc29393dc11a617a40671bf6b3975ae269", size = 165397, upload-time = "2025-06-14T20:57:33.711Z" } wheels = [ @@ -2971,9 +2965,9 @@ name = "pyobjc-framework-gameplaykit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-spritekit" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-spritekit", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e0/07/f38b1d83eac10ea4f75c605ffc4850585740db89b90842d311e586ee36cd/pyobjc_framework_gameplaykit-11.1.tar.gz", hash = "sha256:9ae2bee69b0cc1afa0e210b4663c7cdbb3cc94be1374808df06f98f992e83639", size = 73399, upload-time = "2025-06-14T20:57:34.538Z" } wheels = [ @@ -2986,8 +2980,8 @@ name = "pyobjc-framework-healthkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/af/66/fa76f7c8e36e4c10677d42d91a8e220c135c610a06b759571db1abe26a32/pyobjc_framework_healthkit-11.1.tar.gz", hash = "sha256:20f59bd9e1ffafe5893b4eff5867fdfd20bd46c3d03bc4009219d82fc6815f76", size = 202009, upload-time = "2025-06-14T20:57:35.285Z" } wheels = [ @@ -3000,8 +2994,8 @@ name = "pyobjc-framework-imagecapturecore" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7b/3b/f4edbc58a8c7394393f8d00d0e764f655545e743ee4e33917f27b8c68e7b/pyobjc_framework_imagecapturecore-11.1.tar.gz", hash = "sha256:a610ceb6726e385b132a1481a68ce85ccf56f94667b6d6e1c45a2cfab806a624", size = 100398, upload-time = "2025-06-14T20:57:36.503Z" } wheels = [ @@ -3014,8 +3008,8 @@ name = "pyobjc-framework-inputmethodkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/02/32/6a90bba682a31960ba1fc2d3b263e9be26043c4fb7aed273c13647c8b7d9/pyobjc_framework_inputmethodkit-11.1.tar.gz", hash = "sha256:7037579524041dcee71a649293c2660f9359800455a15e6a2f74a17b46d78496", size = 27203, upload-time = "2025-06-14T20:57:37.246Z" } wheels = [ @@ -3028,8 +3022,8 @@ name = "pyobjc-framework-installerplugins" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4d/89/9a881e466476ca21f3ff3e8e87ccfba1aaad9b88f7eea4be6d3f05b07107/pyobjc_framework_installerplugins-11.1.tar.gz", hash = "sha256:363e59c7e05553d881f0facd41884f17b489ff443d7856e33dd0312064c746d9", size = 27451, upload-time = "2025-06-14T20:57:37.915Z" } wheels = [ @@ -3041,9 +3035,9 @@ name = "pyobjc-framework-instantmessage" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/b9/5cec4dd0053b5f63c01211a60a286c47464d9f3e0c81bd682e6542dbff00/pyobjc_framework_instantmessage-11.1.tar.gz", hash = "sha256:c222aa61eb009704b333f6e63df01a0e690136e7e495907e5396882779bf9525", size = 33774, upload-time = "2025-06-14T20:57:38.553Z" } wheels = [ @@ -3055,8 +3049,8 @@ name = "pyobjc-framework-intents" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4c/af/d7f260d06b79acca8028e373c2fe30bf0be014388ba612f538f40597d929/pyobjc_framework_intents-11.1.tar.gz", hash = "sha256:13185f206493f45d6bd2d4903c2136b1c4f8b9aa37628309ace6ff4a906b4695", size = 448459, upload-time = "2025-06-14T20:57:39.589Z" } wheels = [ @@ -3069,8 +3063,8 @@ name = "pyobjc-framework-intentsui" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-intents" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-intents", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/86/46/20aae4a71efb514b096f36273a6129b48b01535bf501e5719d4a97fcb3a5/pyobjc_framework_intentsui-11.1.tar.gz", hash = "sha256:c8182155af4dce369c18d6e6ed9c25bbd8110c161ed5f1b4fb77cf5cdb99d135", size = 21305, upload-time = "2025-06-14T20:57:40.477Z" } wheels = [ @@ -3083,8 +3077,8 @@ name = "pyobjc-framework-iobluetooth" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/93/e0/74b7b10c567b66c5f38b45ab240336325a4c889f43072d90f2b90aaeb7c0/pyobjc_framework_iobluetooth-11.1.tar.gz", hash = "sha256:094fd4be60cd1371b17cb4b33a3894e0d88a11b36683912be0540a7d51de76f1", size = 300992, upload-time = "2025-06-14T20:57:41.256Z" } wheels = [ @@ -3097,8 +3091,8 @@ name = "pyobjc-framework-iobluetoothui" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-iobluetooth" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-iobluetooth", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dd/32/872272faeab6fe471eac6962c75db72ce65c3556e00b4edebdb41aaab7cb/pyobjc_framework_iobluetoothui-11.1.tar.gz", hash = "sha256:060c721f1cd8af4452493e8153b72b572edcd2a7e3b635d79d844f885afee860", size = 22835, upload-time = "2025-06-14T20:57:42.119Z" } wheels = [ @@ -3110,8 +3104,8 @@ name = "pyobjc-framework-iosurface" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c5/ce/38ec17d860d0ee040bb737aad8ca7c7ff46bef6c9cffa47382d67682bb2d/pyobjc_framework_iosurface-11.1.tar.gz", hash = "sha256:a468b3a31e8cd70a2675a3ddc7176ab13aa521c035f11188b7a3af8fff8b148b", size = 20275, upload-time = "2025-06-14T20:57:42.742Z" } wheels = [ @@ -3123,8 +3117,8 @@ name = "pyobjc-framework-ituneslibrary" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ee/43/aebefed774b434965752f9001685af0b19c02353aa7a12d2918af0948181/pyobjc_framework_ituneslibrary-11.1.tar.gz", hash = "sha256:e2212a9340e4328056ade3c2f9d4305c71f3f6af050204a135f9fa9aa3ba9c5e", size = 47388, upload-time = "2025-06-14T20:57:43.383Z" } wheels = [ @@ -3136,8 +3130,8 @@ name = "pyobjc-framework-kernelmanagement" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1a/b6/708f10ac16425834cb5f8b71efdbe39b42c3b1009ac0c1796a42fc98cd36/pyobjc_framework_kernelmanagement-11.1.tar.gz", hash = "sha256:e934d1638cd89e38d6c6c5d4d9901b4295acee2d39cbfe0bd91aae9832961b44", size = 12543, upload-time = "2025-06-14T20:57:44.046Z" } wheels = [ @@ -3149,8 +3143,8 @@ name = "pyobjc-framework-latentsemanticmapping" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/db/8a/4e54ee2bc77d59d770b287daf73b629e2715a2b3b31264d164398131cbad/pyobjc_framework_latentsemanticmapping-11.1.tar.gz", hash = "sha256:c6c3142301e4d375c24a47dfaeebc2f3d0fc33128a1c0a755794865b9a371145", size = 17444, upload-time = "2025-06-14T20:57:44.643Z" } wheels = [ @@ -3162,8 +3156,8 @@ name = "pyobjc-framework-launchservices" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-coreservices" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2b/0a/a76b13109b8ab563fdb2d7182ca79515f132f82ac6e1c52351a6b02896a8/pyobjc_framework_launchservices-11.1.tar.gz", hash = "sha256:80b55368b1e208d6c2c58395cc7bc12a630a2a402e00e4930493e9bace22b7bb", size = 20446, upload-time = "2025-06-14T20:57:45.258Z" } wheels = [ @@ -3175,8 +3169,8 @@ name = "pyobjc-framework-libdispatch" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/89/7830c293ba71feb086cb1551455757f26a7e2abd12f360d375aae32a4d7d/pyobjc_framework_libdispatch-11.1.tar.gz", hash = "sha256:11a704e50a0b7dbfb01552b7d686473ffa63b5254100fdb271a1fe368dd08e87", size = 53942, upload-time = "2025-06-14T20:57:45.903Z" } wheels = [ @@ -3189,8 +3183,8 @@ name = "pyobjc-framework-libxpc" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6a/c9/7e15e38ac23f5bfb4e82bdf3b7ef88e2f56a8b4ad884009bc2d5267d2e1f/pyobjc_framework_libxpc-11.1.tar.gz", hash = "sha256:8fd7468aa520ff19915f6d793070b84be1498cb87224bee2bad1f01d8375273a", size = 49135, upload-time = "2025-06-14T20:57:46.59Z" } wheels = [ @@ -3203,9 +3197,9 @@ name = "pyobjc-framework-linkpresentation" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/76/22873be73f12a3a11ae57af13167a1d2379e4e7eef584de137156a00f5ef/pyobjc_framework_linkpresentation-11.1.tar.gz", hash = "sha256:a785f393b01fdaada6d7d6d8de46b7173babba205b13b44f1dc884b3695c2fc9", size = 14987, upload-time = "2025-06-14T20:57:47.277Z" } wheels = [ @@ -3217,9 +3211,9 @@ name = "pyobjc-framework-localauthentication" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-security" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e5/27/9e3195f3561574140e9b9071a36f7e0ebd18f50ade9261d23b5b9df8fccd/pyobjc_framework_localauthentication-11.1.tar.gz", hash = "sha256:3cd48907c794bd414ac68b8ac595d83c7e1453b63fc2cfc2d2035b690d31eaa1", size = 40700, upload-time = "2025-06-14T20:57:47.931Z" } wheels = [ @@ -3232,9 +3226,9 @@ name = "pyobjc-framework-localauthenticationembeddedui" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-localauthentication" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-localauthentication", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/29/7b/08c1e52487b07e9aee4c24a78f7c82a46695fa883113e3eece40f8e32d40/pyobjc_framework_localauthenticationembeddedui-11.1.tar.gz", hash = "sha256:22baf3aae606e5204e194f02bb205f244e27841ea7b4a4431303955475b4fa56", size = 14076, upload-time = "2025-06-14T20:57:48.557Z" } wheels = [ @@ -3246,8 +3240,8 @@ name = "pyobjc-framework-mailkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7e/7e/f22d733897e7618bd70a658b0353f5f897c583df04e7c5a2d68b99d43fbb/pyobjc_framework_mailkit-11.1.tar.gz", hash = "sha256:bf97dc44cb09b9eb9d591660dc0a41f077699976144b954caa4b9f0479211fd7", size = 32012, upload-time = "2025-06-14T20:57:49.173Z" } wheels = [ @@ -3259,10 +3253,10 @@ name = "pyobjc-framework-mapkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-corelocation" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-corelocation", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/57/f0/505e074f49c783f2e65ca82174fd2d4348568f3f7281c1b81af816cf83bb/pyobjc_framework_mapkit-11.1.tar.gz", hash = "sha256:f3a5016f266091be313a118a42c0ea4f951c399b5259d93639eb643dacc626f1", size = 165614, upload-time = "2025-06-14T20:57:50.362Z" } wheels = [ @@ -3275,8 +3269,8 @@ name = "pyobjc-framework-mediaaccessibility" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8d/81/60412b423c121de0fa0aa3ef679825e1e2fe8b00fceddec7d72333ef564b/pyobjc_framework_mediaaccessibility-11.1.tar.gz", hash = "sha256:52479a998fec3d079d2d4590a945fc78c41fe7ac8c76f1964c9d8156880565a4", size = 18440, upload-time = "2025-06-14T20:57:51.126Z" } wheels = [ @@ -3288,10 +3282,10 @@ name = "pyobjc-framework-mediaextension" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-avfoundation" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e1/09/fd214dc0cf3f3bc3f528815af4799c0cb7b4bf4032703b19ea63486a132b/pyobjc_framework_mediaextension-11.1.tar.gz", hash = "sha256:85a1c8a94e9175fb364c453066ef99b95752343fd113f08a3805cad56e2fa709", size = 58489, upload-time = "2025-06-14T20:57:51.796Z" } wheels = [ @@ -3304,9 +3298,9 @@ name = "pyobjc-framework-medialibrary" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2b/06/11ff622fb5fbdd557998a45cedd2b0a1c7ea5cc6c5cb015dd6e42ebd1c41/pyobjc_framework_medialibrary-11.1.tar.gz", hash = "sha256:102f4326f789734b7b2dfe689abd3840ca75a76fb8058bd3e4f85398ae2ce29d", size = 18706, upload-time = "2025-06-14T20:57:52.474Z" } wheels = [ @@ -3318,8 +3312,8 @@ name = "pyobjc-framework-mediaplayer" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-avfoundation" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/80/d5/daba26eb8c70af1f3823acfd7925356acc4dd75eeac4fc86dc95d94d0e15/pyobjc_framework_mediaplayer-11.1.tar.gz", hash = "sha256:d07a634b98e1b9eedd82d76f35e616525da096bd341051ea74f0971e0f2f2ddd", size = 93749, upload-time = "2025-06-14T20:57:53.165Z" } wheels = [ @@ -3331,8 +3325,8 @@ name = "pyobjc-framework-mediatoolbox" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e1/68/cc230d2dfdeb974fdcfa828de655a43ce2bf4962023fd55bbb7ab0970100/pyobjc_framework_mediatoolbox-11.1.tar.gz", hash = "sha256:97834addc5179b3165c0d8cd74cc97ad43ed4c89547724216426348aca3b822a", size = 23568, upload-time = "2025-06-14T20:57:53.913Z" } wheels = [ @@ -3345,8 +3339,8 @@ name = "pyobjc-framework-metal" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/af/cf/29fea96fd49bf72946c5dac4c43ef50f26c15e9f76edd6f15580d556aa23/pyobjc_framework_metal-11.1.tar.gz", hash = "sha256:f9fd3b7574a824632ee9b7602973da30f172d2b575dd0c0f5ef76b44cfe9f6f9", size = 446549, upload-time = "2025-06-14T20:57:54.731Z" } wheels = [ @@ -3359,8 +3353,8 @@ name = "pyobjc-framework-metalfx" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-metal" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/10/20/4c839a356b534c161fb97e06589f418fc78cc5a0808362bdecf4f9a61a8d/pyobjc_framework_metalfx-11.1.tar.gz", hash = "sha256:555c1b895d4ba31be43930f45e219a5d7bb0e531d148a78b6b75b677cc588fd8", size = 27002, upload-time = "2025-06-14T20:57:55.949Z" } wheels = [ @@ -3373,9 +3367,9 @@ name = "pyobjc-framework-metalkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-metal" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/45/cb/7e01bc61625c7a6fea9c9888c9ed35aa6bbc47cda2fcd02b6525757bc2b8/pyobjc_framework_metalkit-11.1.tar.gz", hash = "sha256:8811cd81ee9583b9330df4f2499a73dcc53f3359cb92767b409acaec9e4faa1e", size = 45135, upload-time = "2025-06-14T20:57:56.601Z" } wheels = [ @@ -3388,8 +3382,8 @@ name = "pyobjc-framework-metalperformanceshaders" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-metal" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d0/11/5df398a158a6efe2c87ac5cae121ef2788242afe5d4302d703147b9fcd91/pyobjc_framework_metalperformanceshaders-11.1.tar.gz", hash = "sha256:8a312d090a0f51651e63d9001e6cc7c1aa04ceccf23b494cbf84b7fd3d122071", size = 302113, upload-time = "2025-06-14T20:57:57.407Z" } wheels = [ @@ -3402,8 +3396,8 @@ name = "pyobjc-framework-metalperformanceshadersgraph" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-metalperformanceshaders" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metalperformanceshaders", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/32/c3/8d98661f7eecd1f1b0d80a80961069081b88efd3a82fbbed2d7e6050c0ad/pyobjc_framework_metalperformanceshadersgraph-11.1.tar.gz", hash = "sha256:d25225aab4edc6f786b29fe3d9badc4f3e2d0caeab1054cd4f224258c1b6dbe2", size = 105098, upload-time = "2025-06-14T20:57:58.273Z" } wheels = [ @@ -3415,8 +3409,8 @@ name = "pyobjc-framework-metrickit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bd/48/8ae969a51a91864000e39c1de74627b12ff587b1dbad9406f7a30dfe71f8/pyobjc_framework_metrickit-11.1.tar.gz", hash = "sha256:a79d37575489916c35840e6a07edd958be578d3be7a3d621684d028d721f0b85", size = 40952, upload-time = "2025-06-14T20:57:58.996Z" } wheels = [ @@ -3429,8 +3423,8 @@ name = "pyobjc-framework-mlcompute" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8b/e6/f064dec650fb1209f41aba0c3074416cb9b975a7cf4d05d93036e3d917f0/pyobjc_framework_mlcompute-11.1.tar.gz", hash = "sha256:f6c4c3ea6a62e4e3927abf9783c40495aa8bb9a8c89def744b0822da58c2354b", size = 89021, upload-time = "2025-06-14T20:57:59.997Z" } wheels = [ @@ -3442,9 +3436,9 @@ name = "pyobjc-framework-modelio" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a0/27/140bf75706332729de252cc4141e8c8afe16a0e9e5818b5a23155aa3473c/pyobjc_framework_modelio-11.1.tar.gz", hash = "sha256:fad0fa2c09d468ac7e49848e144f7bbce6826f2178b3120add8960a83e5bfcb7", size = 123203, upload-time = "2025-06-14T20:58:01.035Z" } wheels = [ @@ -3457,8 +3451,8 @@ name = "pyobjc-framework-multipeerconnectivity" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/73/99/75bf6170e282d9e546b353b65af7859de8b1b27ddc431fc4afbf15423d01/pyobjc_framework_multipeerconnectivity-11.1.tar.gz", hash = "sha256:a3dacca5e6e2f1960dd2d1107d98399ff81ecf54a9852baa8ec8767dbfdbf54b", size = 26149, upload-time = "2025-06-14T20:58:01.793Z" } wheels = [ @@ -3471,8 +3465,8 @@ name = "pyobjc-framework-naturallanguage" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a2/e9/5352fbf09c5d5360405dea49fb77e53ed55acd572a94ce9a0d05f64d2b70/pyobjc_framework_naturallanguage-11.1.tar.gz", hash = "sha256:ab1fc711713aa29c32719774fc623bf2d32168aed21883970d4896e901ff4b41", size = 46120, upload-time = "2025-06-14T20:58:02.808Z" } wheels = [ @@ -3484,8 +3478,8 @@ name = "pyobjc-framework-netfs" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/68/5d/d68cc59a1c1ea61f227ed58e7b185a444d560655320b53ced155076f5b78/pyobjc_framework_netfs-11.1.tar.gz", hash = "sha256:9c49f050c8171dc37e54d05dd12a63979c8b6b565c10f05092923a2250446f50", size = 15910, upload-time = "2025-06-14T20:58:03.811Z" } wheels = [ @@ -3497,8 +3491,8 @@ name = "pyobjc-framework-network" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0a/ee/5ea93e48eca341b274027e1532bd8629fd55d609cd9c39c2c3acf26158c3/pyobjc_framework_network-11.1.tar.gz", hash = "sha256:f6df7a58a1279bbc976fd7e2efe813afbbb18427df40463e6e2ee28fba07d2df", size = 124670, upload-time = "2025-06-14T20:58:05.491Z" } wheels = [ @@ -3511,8 +3505,8 @@ name = "pyobjc-framework-networkextension" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/71/30/d1eee738d702bbca78effdaa346a2b05359ab8a96d961b7cb44838e236ca/pyobjc_framework_networkextension-11.1.tar.gz", hash = "sha256:2b74b430ca651293e5aa90a1e7571b200d0acbf42803af87306ac8a1c70b0d4b", size = 217252, upload-time = "2025-06-14T20:58:06.311Z" } wheels = [ @@ -3525,8 +3519,8 @@ name = "pyobjc-framework-notificationcenter" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/4a/d3529b9bd7aae2c89d258ebc234673c5435e217a5136abd8c0aba37b916b/pyobjc_framework_notificationcenter-11.1.tar.gz", hash = "sha256:0b938053f2d6b1cea9db79313639d7eb9ddd5b2a5436a346be0887e75101e717", size = 23389, upload-time = "2025-06-14T20:58:07.136Z" } wheels = [ @@ -3539,8 +3533,8 @@ name = "pyobjc-framework-opendirectory" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9d/02/ac56c56fdfbc24cdf87f4a624f81bbe2e371d0983529b211a18c6170e932/pyobjc_framework_opendirectory-11.1.tar.gz", hash = "sha256:319ac3424ed0350be458b78148914468a8fc13a069d62e7869e3079108e4f118", size = 188880, upload-time = "2025-06-14T20:58:08.003Z" } wheels = [ @@ -3552,8 +3546,8 @@ name = "pyobjc-framework-osakit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/22/f9cdfb5de255b335f99e61a3284be7cb1552a43ed1dfe7c22cc868c23819/pyobjc_framework_osakit-11.1.tar.gz", hash = "sha256:920987da78b67578367c315d208f87e8fab01dd35825d72242909f29fb43c820", size = 22290, upload-time = "2025-06-14T20:58:09.103Z" } wheels = [ @@ -3565,10 +3559,10 @@ name = "pyobjc-framework-oslog" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/79/93/3feb7f6150b50165524750a424f5434448392123420cb4673db766c3f54a/pyobjc_framework_oslog-11.1.tar.gz", hash = "sha256:b2af409617e6b68fa1f1467c5a5679ebf59afd0cdc4b4528e1616059959a7979", size = 24689, upload-time = "2025-06-14T20:58:09.739Z" } wheels = [ @@ -3581,8 +3575,8 @@ name = "pyobjc-framework-passkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5c/05/063db500e7df70e39cbb5518a5a03c2acc06a1ca90b057061daea00129f3/pyobjc_framework_passkit-11.1.tar.gz", hash = "sha256:d2408b58960fca66607b483353c1ffbd751ef0bef394a1853ec414a34029566f", size = 144859, upload-time = "2025-06-14T20:58:10.761Z" } wheels = [ @@ -3595,8 +3589,8 @@ name = "pyobjc-framework-pencilkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/75/d0/bbbe9dadcfc37e33a63d43b381a8d9a64eca27559df38efb74d524fa6260/pyobjc_framework_pencilkit-11.1.tar.gz", hash = "sha256:9c173e0fe70179feadc3558de113a8baad61b584fe70789b263af202bfa4c6be", size = 22570, upload-time = "2025-06-14T20:58:11.538Z" } wheels = [ @@ -3608,8 +3602,8 @@ name = "pyobjc-framework-phase" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-avfoundation" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c6/d2/e9384b5b3fbcc79e8176cb39fcdd48b77f60cd1cb64f9ee4353762b037dc/pyobjc_framework_phase-11.1.tar.gz", hash = "sha256:a940d81ac5c393ae3da94144cf40af33932e0a9731244e2cfd5c9c8eb851e3fc", size = 58986, upload-time = "2025-06-14T20:58:12.196Z" } wheels = [ @@ -3621,8 +3615,8 @@ name = "pyobjc-framework-photos" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/78/b0/576652ecd05c26026ab4e75e0d81466edd570d060ce7df3d6bd812eb90d0/pyobjc_framework_photos-11.1.tar.gz", hash = "sha256:c8c3b25b14a2305047f72c7c081ff3655b3d051f7ed531476c03246798f8156d", size = 92569, upload-time = "2025-06-14T20:58:12.939Z" } wheels = [ @@ -3635,8 +3629,8 @@ name = "pyobjc-framework-photosui" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/20/bb/e6de720efde2e9718677c95c6ae3f97047be437cda7a0f050cd1d6d2a434/pyobjc_framework_photosui-11.1.tar.gz", hash = "sha256:1c7ffab4860ce3e2b50feeed4f1d84488a9e38546db0bec09484d8d141c650df", size = 48443, upload-time = "2025-06-14T20:58:13.626Z" } wheels = [ @@ -3649,8 +3643,8 @@ name = "pyobjc-framework-preferencepanes" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/34/ac/9324602daf9916308ebf1935b8a4b91c93b9ae993dcd0da731c0619c2836/pyobjc_framework_preferencepanes-11.1.tar.gz", hash = "sha256:6e4a55195ec9fc921e0eaad6b3038d0ab91f0bb2f39206aa6fccd24b14a0f1d8", size = 26212, upload-time = "2025-06-14T20:58:14.361Z" } wheels = [ @@ -3662,8 +3656,8 @@ name = "pyobjc-framework-pushkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/f0/92d0eb26bf8af8ebf6b5b88df77e70b807de11f01af0162e0a429fcfb892/pyobjc_framework_pushkit-11.1.tar.gz", hash = "sha256:540769a4aadc3c9f08beca8496fe305372501eb28fdbca078db904a07b8e10f4", size = 21362, upload-time = "2025-06-14T20:58:15.642Z" } wheels = [ @@ -3676,8 +3670,8 @@ name = "pyobjc-framework-quartz" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c7/ac/6308fec6c9ffeda9942fef72724f4094c6df4933560f512e63eac37ebd30/pyobjc_framework_quartz-11.1.tar.gz", hash = "sha256:a57f35ccfc22ad48c87c5932818e583777ff7276605fef6afad0ac0741169f75", size = 3953275, upload-time = "2025-06-14T20:58:17.924Z" } wheels = [ @@ -3690,9 +3684,9 @@ name = "pyobjc-framework-quicklookthumbnailing" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/aa/98/6e87f360c2dfc870ae7870b8a25fdea8ddf1d62092c755686cebe7ec1a07/pyobjc_framework_quicklookthumbnailing-11.1.tar.gz", hash = "sha256:1614dc108c1d45bbf899ea84b8691288a5b1d25f2d6f0c57dfffa962b7a478c3", size = 16527, upload-time = "2025-06-14T20:58:20.811Z" } wheels = [ @@ -3704,8 +3698,8 @@ name = "pyobjc-framework-replaykit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c8/4f/014e95f0fd6842d7fcc3d443feb6ee65ac69d06c66ffa9327fc33ceb7c27/pyobjc_framework_replaykit-11.1.tar.gz", hash = "sha256:6919baa123a6d8aad769769fcff87369e13ee7bae11b955a8185a406a651061b", size = 26132, upload-time = "2025-06-14T20:58:21.853Z" } wheels = [ @@ -3718,8 +3712,8 @@ name = "pyobjc-framework-safariservices" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1a/fc/c47d2abf3c1de6db21d685cace76a0931d594aa369e3d090260295273f6e/pyobjc_framework_safariservices-11.1.tar.gz", hash = "sha256:39a17df1a8e1c339457f3acbff0dc0eae4681d158f9d783a11995cf484aa9cd0", size = 34905, upload-time = "2025-06-14T20:58:22.492Z" } wheels = [ @@ -3732,9 +3726,9 @@ name = "pyobjc-framework-safetykit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/28/cc/f6aa5d6f45179bd084416511be4e5b0dd0752cb76daa93869e6edb806096/pyobjc_framework_safetykit-11.1.tar.gz", hash = "sha256:c6b44e0cf69e27584ac3ef3d8b771d19a7c2ccd9c6de4138d091358e036322d4", size = 21240, upload-time = "2025-06-14T20:58:23.132Z" } wheels = [ @@ -3747,9 +3741,9 @@ name = "pyobjc-framework-scenekit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/64/cf/2d89777120d2812e7ee53c703bf6fc8968606c29ddc1351bc63f0a2a5692/pyobjc_framework_scenekit-11.1.tar.gz", hash = "sha256:82941f1e5040114d6e2c9fd35507244e102ef561c637686091b71a7ad0f31306", size = 214118, upload-time = "2025-06-14T20:58:24.003Z" } wheels = [ @@ -3762,9 +3756,9 @@ name = "pyobjc-framework-screencapturekit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/32/a5/9bd1f1ad1773a1304ccde934ff39e0f0a0b0034441bf89166aea649606de/pyobjc_framework_screencapturekit-11.1.tar.gz", hash = "sha256:11443781a30ed446f2d892c9e6642ca4897eb45f1a1411136ca584997fa739e0", size = 53548, upload-time = "2025-06-14T20:58:24.837Z" } wheels = [ @@ -3777,8 +3771,8 @@ name = "pyobjc-framework-screensaver" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7c/f6/f2d48583b29fc67b64aa1f415fd51faf003d045cdb1f3acab039b9a3f59f/pyobjc_framework_screensaver-11.1.tar.gz", hash = "sha256:d5fbc9dc076cc574ead183d521840b56be0c160415e43cb8e01cfddd6d6372c2", size = 24302, upload-time = "2025-06-14T20:58:25.52Z" } wheels = [ @@ -3791,8 +3785,8 @@ name = "pyobjc-framework-screentime" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/82/33/ebed70a1de134de936bb9a12d5c76f24e1e335ff4964f9bb0af9b09607f1/pyobjc_framework_screentime-11.1.tar.gz", hash = "sha256:9bb8269456bbb674e1421182efe49f9168ceefd4e7c497047c7bf63e2f510a34", size = 14875, upload-time = "2025-06-14T20:58:26.179Z" } wheels = [ @@ -3804,8 +3798,8 @@ name = "pyobjc-framework-scriptingbridge" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8e/c1/5b1dd01ff173df4c6676f97405113458918819cb2064c1735b61948e8800/pyobjc_framework_scriptingbridge-11.1.tar.gz", hash = "sha256:604445c759210a35d86d3e0dfcde0aac8e5e3e9d9e35759e0723952138843699", size = 23155, upload-time = "2025-06-14T20:58:26.812Z" } wheels = [ @@ -3818,8 +3812,8 @@ name = "pyobjc-framework-searchkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-coreservices" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6e/20/61b73fddae0d1a94f5defb0cd4b4f391ec03bfcce7ebe830cb827d5e208a/pyobjc_framework_searchkit-11.1.tar.gz", hash = "sha256:13a194eefcf1359ce9972cd92f2aadddf103f3efb1b18fd578ba5367dff3c10c", size = 30918, upload-time = "2025-06-14T20:58:27.447Z" } wheels = [ @@ -3831,8 +3825,8 @@ name = "pyobjc-framework-security" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ee/6f/ba50ed2d9c1192c67590a7cfefa44fc5f85c776d1e25beb224dec32081f6/pyobjc_framework_security-11.1.tar.gz", hash = "sha256:dabcee6987c6bae575e2d1ef0fcbe437678c4f49f1c25a4b131a5e960f31a2da", size = 302291, upload-time = "2025-06-14T20:58:28.506Z" } wheels = [ @@ -3845,9 +3839,9 @@ name = "pyobjc-framework-securityfoundation" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-security" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5c/d4/19591dd0938a45b6d8711ef9ae5375b87c37a55b45d79c52d6f83a8d991f/pyobjc_framework_securityfoundation-11.1.tar.gz", hash = "sha256:b3c4cf70735a93e9df40f3a14478143959c415778f27be8c0dc9ae0c5b696b92", size = 13270, upload-time = "2025-06-14T20:58:29.304Z" } wheels = [ @@ -3859,9 +3853,9 @@ name = "pyobjc-framework-securityinterface" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-security" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a1/be/c846651c3e7f38a637c40ae1bcda9f14237c2395637c3a188df4f733c727/pyobjc_framework_securityinterface-11.1.tar.gz", hash = "sha256:e7aa6373e525f3ae05d71276e821a6348c53fec9f812b90eec1dbadfcb507bc9", size = 37648, upload-time = "2025-06-14T20:58:29.932Z" } wheels = [ @@ -3874,9 +3868,9 @@ name = "pyobjc-framework-securityui" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-security" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/07/5b/3b5585d56e0bcaba82e0661224bbc7aaf29fba6b10498971dbe08b2b490a/pyobjc_framework_securityui-11.1.tar.gz", hash = "sha256:e80c93e8a56bf89e4c0333047b9f8219752dd6de290681e9e2e2b2e26d69e92d", size = 12179, upload-time = "2025-06-14T20:58:30.928Z" } wheels = [ @@ -3888,9 +3882,9 @@ name = "pyobjc-framework-sensitivecontentanalysis" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/7b/e28f6b30d99e9d464427a07ada82b33cd3292f310bf478a1824051d066b9/pyobjc_framework_sensitivecontentanalysis-11.1.tar.gz", hash = "sha256:5b310515c7386f7afaf13e4632d7d9590688182bb7b563f8026c304bdf317308", size = 12796, upload-time = "2025-06-14T20:58:31.488Z" } wheels = [ @@ -3902,8 +3896,8 @@ name = "pyobjc-framework-servicemanagement" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/20/c6/32e11599d9d232311607b79eb2d1d21c52eaaf001599ea85f8771a933fa2/pyobjc_framework_servicemanagement-11.1.tar.gz", hash = "sha256:90a07164da49338480e0e135b445acc6ae7c08549a2037d1e512d2605fedd80a", size = 16645, upload-time = "2025-06-14T20:58:32.062Z" } wheels = [ @@ -3915,8 +3909,8 @@ name = "pyobjc-framework-sharedwithyou" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-sharedwithyoucore" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-sharedwithyoucore", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fe/a5/e299fbd0c13d4fac9356459f21372f6eef4279d0fbc99ba316d88dfbbfb4/pyobjc_framework_sharedwithyou-11.1.tar.gz", hash = "sha256:ece3a28a3083d0bcad0ac95b01f0eb699b9d2d0c02c61305bfd402678753ff6e", size = 34216, upload-time = "2025-06-14T20:58:32.75Z" } wheels = [ @@ -3929,8 +3923,8 @@ name = "pyobjc-framework-sharedwithyoucore" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/79/a3/1ca6ff1b785772c7c5a38a7c017c6f971b1eda638d6a0aab3bbde18ac086/pyobjc_framework_sharedwithyoucore-11.1.tar.gz", hash = "sha256:790050d25f47bda662a9f008b17ca640ac2460f2559a56b17995e53f2f44ed73", size = 29459, upload-time = "2025-06-14T20:58:33.422Z" } wheels = [ @@ -3943,8 +3937,8 @@ name = "pyobjc-framework-shazamkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/de/08/ba739b97f1e441653bae8da5dd1e441bbbfa43940018d21edb60da7dd163/pyobjc_framework_shazamkit-11.1.tar.gz", hash = "sha256:c6e3c9ab8744d9319a89b78ae6f185bb5704efb68509e66d77bcd1f84a9446d6", size = 25797, upload-time = "2025-06-14T20:58:34.086Z" } wheels = [ @@ -3957,8 +3951,8 @@ name = "pyobjc-framework-social" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/07/2e/cc7707b7a40df392c579087947049f3e1f0e00597e7151ec411f654d8bef/pyobjc_framework_social-11.1.tar.gz", hash = "sha256:fbc09d7b00dad45b547f9b2329f4dcee3f5a50e2348de1870de0bd7be853a5b7", size = 14540, upload-time = "2025-06-14T20:58:35.116Z" } wheels = [ @@ -3970,8 +3964,8 @@ name = "pyobjc-framework-soundanalysis" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e0/d4/b9497dbb57afdf0d22f61bb6e776a6f46cf9294c890448acde5b46dd61f3/pyobjc_framework_soundanalysis-11.1.tar.gz", hash = "sha256:42cd25b7e0f343d8b59367f72b5dae96cf65696bdb8eeead8d7424ed37aa1434", size = 16539, upload-time = "2025-06-14T20:58:35.813Z" } wheels = [ @@ -3983,8 +3977,8 @@ name = "pyobjc-framework-speech" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/67/76/2a1fd7637b2c662349ede09806e159306afeebfba18fb062ad053b41d811/pyobjc_framework_speech-11.1.tar.gz", hash = "sha256:d382977208c3710eacea89e05eae4578f1638bb5a7b667c06971e3d34e96845c", size = 41179, upload-time = "2025-06-14T20:58:36.43Z" } wheels = [ @@ -3997,9 +3991,9 @@ name = "pyobjc-framework-spritekit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/16/02/2e253ba4f7fad6efe05fd5fcf44aede093f6c438d608d67c6c6623a1846d/pyobjc_framework_spritekit-11.1.tar.gz", hash = "sha256:914da6e846573cac8db5e403dec9a3e6f6edf5211f9b7e429734924d00f65108", size = 130297, upload-time = "2025-06-14T20:58:37.113Z" } wheels = [ @@ -4012,8 +4006,8 @@ name = "pyobjc-framework-storekit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/44/a0/58cab9ebc9ac9282e1d4734b1987d1c3cd652b415ec3e678fcc5e735d279/pyobjc_framework_storekit-11.1.tar.gz", hash = "sha256:85acc30c0bfa120b37c3c5ac693fe9ad2c2e351ee7a1f9ea6f976b0c311ff164", size = 76421, upload-time = "2025-06-14T20:58:37.86Z" } wheels = [ @@ -4026,8 +4020,8 @@ name = "pyobjc-framework-symbols" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cd/af/7191276204bd3e7db1d0a3e490a869956606f77f7a303a04d92a5d0c3f7b/pyobjc_framework_symbols-11.1.tar.gz", hash = "sha256:0e09b7813ef2ebdca7567d3179807444dd60f3f393202b35b755d4e1baf99982", size = 13377, upload-time = "2025-06-14T20:58:38.542Z" } wheels = [ @@ -4039,9 +4033,9 @@ name = "pyobjc-framework-syncservices" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coredata" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coredata", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/69/45/cd9fa83ed1d75be7130fb8e41c375f05b5d6621737ec37e9d8da78676613/pyobjc_framework_syncservices-11.1.tar.gz", hash = "sha256:0f141d717256b98c17ec2eddbc983c4bd39dfa00dc0c31b4174742e73a8447fe", size = 57996, upload-time = "2025-06-14T20:58:39.146Z" } wheels = [ @@ -4054,8 +4048,8 @@ name = "pyobjc-framework-systemconfiguration" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e2/3d/41590c0afc72e93d911348fbde0c9c1071ff53c6f86df42df64b21174bb9/pyobjc_framework_systemconfiguration-11.1.tar.gz", hash = "sha256:f30ed0e9a8233fecb06522e67795918ab230ddcc4a18e15494eff7532f4c3ae1", size = 143410, upload-time = "2025-06-14T20:58:39.917Z" } wheels = [ @@ -4068,8 +4062,8 @@ name = "pyobjc-framework-systemextensions" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b4/57/4609fd9183383616b1e643c2489ad774335f679523a974b9ce346a6d4d5b/pyobjc_framework_systemextensions-11.1.tar.gz", hash = "sha256:8ff9f0aad14dcdd07dd47545c1dd20df7a286306967b0a0232c81fcc382babe6", size = 23062, upload-time = "2025-06-14T20:58:40.686Z" } wheels = [ @@ -4082,8 +4076,8 @@ name = "pyobjc-framework-threadnetwork" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e7/a4/5400a222ced0e4f077a8f4dd0188e08e2af4762e72ed0ed39f9d27feefc9/pyobjc_framework_threadnetwork-11.1.tar.gz", hash = "sha256:73a32782f44b61ca0f8a4a9811c36b1ca1cdcf96c8a3ba4de35d8e8e58a86ad5", size = 13572, upload-time = "2025-06-14T20:58:41.311Z" } wheels = [ @@ -4095,8 +4089,8 @@ name = "pyobjc-framework-uniformtypeidentifiers" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c5/4f/066ed1c69352ccc29165f45afb302f8c9c2b5c6f33ee3abfa41b873c07e5/pyobjc_framework_uniformtypeidentifiers-11.1.tar.gz", hash = "sha256:86c499bec8953aeb0c95af39b63f2592832384f09f12523405650b5d5f1ed5e9", size = 20599, upload-time = "2025-06-14T20:58:41.945Z" } wheels = [ @@ -4108,8 +4102,8 @@ name = "pyobjc-framework-usernotifications" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b4/4c/e7e180fcd06c246c37f218bcb01c40ea0213fde5ace3c09d359e60dcaafd/pyobjc_framework_usernotifications-11.1.tar.gz", hash = "sha256:38fc763afa7854b41ddfca8803f679a7305d278af8a7ad02044adc1265699996", size = 55428, upload-time = "2025-06-14T20:58:42.572Z" } wheels = [ @@ -4122,9 +4116,9 @@ name = "pyobjc-framework-usernotificationsui" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-usernotifications" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-usernotifications", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d2/c4/03d97bd3adcee9b857533cb42967df0d019f6a034adcdbcfca2569d415b2/pyobjc_framework_usernotificationsui-11.1.tar.gz", hash = "sha256:18e0182bddd10381884530d6a28634ebb3280912592f8f2ad5bac2a9308c6a65", size = 14123, upload-time = "2025-06-14T20:58:43.267Z" } wheels = [ @@ -4136,8 +4130,8 @@ name = "pyobjc-framework-videosubscriberaccount" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/aa/00/cd9d93d06204bbb7fe68fb97022b0dd4ecdf8af3adb6d70a41e22c860d55/pyobjc_framework_videosubscriberaccount-11.1.tar.gz", hash = "sha256:2dd78586260fcee51044e129197e8bf2e157176e02babeec2f873afa4235d8c6", size = 28856, upload-time = "2025-06-14T20:58:43.903Z" } wheels = [ @@ -4149,10 +4143,10 @@ name = "pyobjc-framework-videotoolbox" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e5/e3/df9096f54ae1f27cab8f922ee70cbda5d80f8c1d12734c38580829858133/pyobjc_framework_videotoolbox-11.1.tar.gz", hash = "sha256:a27985656e1b639cdb102fcc727ebc39f71bb1a44cdb751c8c80cc9fe938f3a9", size = 88551, upload-time = "2025-06-14T20:58:44.566Z" } wheels = [ @@ -4165,8 +4159,8 @@ name = "pyobjc-framework-virtualization" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f1/ff/57214e8f42755eeaad516a7e673dae4341b8742005d368ecc22c7a790b0b/pyobjc_framework_virtualization-11.1.tar.gz", hash = "sha256:4221ee5eb669e43a2ff46e04178bec149af2d65205deb5d4db5fa62ea060e022", size = 78633, upload-time = "2025-06-14T20:58:45.358Z" } wheels = [ @@ -4179,10 +4173,10 @@ name = "pyobjc-framework-vision" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coreml" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreml", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/40/a8/7128da4d0a0103cabe58910a7233e2f98d18c590b1d36d4b3efaaedba6b9/pyobjc_framework_vision-11.1.tar.gz", hash = "sha256:26590512ee7758da3056499062a344b8a351b178be66d4b719327884dde4216b", size = 133721, upload-time = "2025-06-14T20:58:46.095Z" } wheels = [ @@ -4195,8 +4189,8 @@ name = "pyobjc-framework-webkit" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/92/04/fb3d0b68994f7e657ef00c1ac5fc1c04ae2fc7ea581d647f5ae1f6739b14/pyobjc_framework_webkit-11.1.tar.gz", hash = "sha256:27e701c7aaf4f24fc7e601a128e2ef14f2773f4ab071b9db7438dc5afb5053ae", size = 717102, upload-time = "2025-06-14T20:58:47.461Z" } wheels = [ @@ -4425,7 +4419,7 @@ name = "python-xlib" version = "0.33" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "six" }, + { name = "six", marker = "sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/86/f5/8c0653e5bb54e0cbdfe27bf32d41f27bc4e12faa8742778c17f2a71be2c0/python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32", size = 269068, upload-time = "2022-12-25T18:53:00.824Z" } wheels = [ @@ -4630,7 +4624,7 @@ wheels = [ [[package]] name = "rerun-sdk" -version = "0.23.3" +version = "0.23.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -4640,11 +4634,11 @@ dependencies = [ { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/00/54/710685ea836d91f14422de515262a9db5c2a17777e59ae4c1c19b46c1fa0/rerun_sdk-0.23.3-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8a048ace69443aeb4040046d4bc28ba17be2c6862df7c2161e21b0608d5d6e0f", size = 59486089, upload-time = "2025-05-26T16:08:08.395Z" }, - { url = "https://files.pythonhosted.org/packages/26/81/b94c23cde1b9ed54900f47e337f8e156d9fdec7494eaa11bbb592c26a5d9/rerun_sdk-0.23.3-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:a3ef05acfa9315d956c69eb680a50e46c802ba27eb8025bab71e14a989299383", size = 55128863, upload-time = "2025-05-26T16:08:13.328Z" }, - { url = "https://files.pythonhosted.org/packages/02/bc/c32f71969a5d981087fded194012ef162391e49dba71ba5115a17c642065/rerun_sdk-0.23.3-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:0cc9c60f1180edca1a4d70dbe591eb108e4dbe599c31136949b8d4106410cd14", size = 61603766, upload-time = "2025-05-26T16:08:17.476Z" }, - { url = "https://files.pythonhosted.org/packages/fa/ef/93b6a8932747842e5f7c27f3ce7ef82bbc046aad3b7d5b2e905ffc5614a7/rerun_sdk-0.23.3-cp39-abi3-manylinux_2_31_x86_64.whl", hash = "sha256:49833bf3a4854eaf287a8bfe7dfa4cd8f67b0fd7917a469c8bb7061895fb1cf8", size = 64875203, upload-time = "2025-05-26T16:08:21.95Z" }, - { url = "https://files.pythonhosted.org/packages/c5/36/759287d69301a3ada86ee102369a9521dfef0bb91b215e98a6a7b262a311/rerun_sdk-0.23.3-cp39-abi3-win_amd64.whl", hash = "sha256:88639632116b3761e2a45f4e5ac39eaf3406b615f516baccb508d93c6b42cc4e", size = 50862558, upload-time = "2025-05-26T16:08:26.98Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7f/d77608b6c73e43bbc67a86510e43573b8c03abac80b7341d15f677818e9c/rerun_sdk-0.23.4-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3d18fdbf6f7d4fb2bb271dbbeecf1c9c2c8cdbf348b9e59a2bcd36f9c2f44df1", size = 59487147, upload-time = "2025-06-26T13:58:06.987Z" }, + { url = "https://files.pythonhosted.org/packages/2c/39/64ea3c404239bd709e76bc36e321dbb1343dbf84ec918480a1b87ce63a85/rerun_sdk-0.23.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:e2fbded870b50058052a6e03fe890d268f77dc19d5dec171b48b524c61199256", size = 55131456, upload-time = "2025-06-26T13:58:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d2/9809c34f486cd402ad34287efaf1f546052e7876d66821aa152a23e9cd84/rerun_sdk-0.23.4-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2aec8c65d2f1fee0df4b145389fab23fe91c7f07a3ed0b6d2003c177c3437ca4", size = 61577323, upload-time = "2025-06-26T13:58:38.33Z" }, + { url = "https://files.pythonhosted.org/packages/13/5c/5c194619bbdc9545832f50bf94634fc9f548cf8f08eada76446dffce8d8d/rerun_sdk-0.23.4-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:229b5f12a4f6499e2575ace3a9bbc3d164245a65224789d7e3ebc5656b01538d", size = 64877348, upload-time = "2025-06-26T13:58:57.528Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e6/98923b167df0388da2acea1a4a8cc5fdd4557c0ada25fd79a704d614ce09/rerun_sdk-0.23.4-cp39-abi3-win_amd64.whl", hash = "sha256:1c3a2480892a09165183d38788ea238a4689aaf61f3c388539aa74e1514df23c", size = 50872538, upload-time = "2025-06-26T13:59:12.107Z" }, ] [[package]] @@ -4696,27 +4690,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.12.0" +version = "0.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/90/5255432602c0b196a0da6720f6f76b93eb50baef46d3c9b0025e2f9acbf3/ruff-0.12.0.tar.gz", hash = "sha256:4d047db3662418d4a848a3fdbfaf17488b34b62f527ed6f10cb8afd78135bc5c", size = 4376101, upload-time = "2025-06-17T15:19:26.217Z" } +sdist = { url = "https://files.pythonhosted.org/packages/97/38/796a101608a90494440856ccfb52b1edae90de0b817e76bfade66b12d320/ruff-0.12.1.tar.gz", hash = "sha256:806bbc17f1104fd57451a98a58df35388ee3ab422e029e8f5cf30aa4af2c138c", size = 4413426, upload-time = "2025-06-26T20:34:14.784Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/fd/b46bb20e14b11ff49dbc74c61de352e0dc07fb650189513631f6fb5fc69f/ruff-0.12.0-py3-none-linux_armv6l.whl", hash = "sha256:5652a9ecdb308a1754d96a68827755f28d5dfb416b06f60fd9e13f26191a8848", size = 10311554, upload-time = "2025-06-17T15:18:45.792Z" }, - { url = "https://files.pythonhosted.org/packages/e7/d3/021dde5a988fa3e25d2468d1dadeea0ae89dc4bc67d0140c6e68818a12a1/ruff-0.12.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:05ed0c914fabc602fc1f3b42c53aa219e5736cb030cdd85640c32dbc73da74a6", size = 11118435, upload-time = "2025-06-17T15:18:49.064Z" }, - { url = "https://files.pythonhosted.org/packages/07/a2/01a5acf495265c667686ec418f19fd5c32bcc326d4c79ac28824aecd6a32/ruff-0.12.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:07a7aa9b69ac3fcfda3c507916d5d1bca10821fe3797d46bad10f2c6de1edda0", size = 10466010, upload-time = "2025-06-17T15:18:51.341Z" }, - { url = "https://files.pythonhosted.org/packages/4c/57/7caf31dd947d72e7aa06c60ecb19c135cad871a0a8a251723088132ce801/ruff-0.12.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7731c3eec50af71597243bace7ec6104616ca56dda2b99c89935fe926bdcd48", size = 10661366, upload-time = "2025-06-17T15:18:53.29Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ba/aa393b972a782b4bc9ea121e0e358a18981980856190d7d2b6187f63e03a/ruff-0.12.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:952d0630eae628250ab1c70a7fffb641b03e6b4a2d3f3ec6c1d19b4ab6c6c807", size = 10173492, upload-time = "2025-06-17T15:18:55.262Z" }, - { url = "https://files.pythonhosted.org/packages/d7/50/9349ee777614bc3062fc6b038503a59b2034d09dd259daf8192f56c06720/ruff-0.12.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c021f04ea06966b02614d442e94071781c424ab8e02ec7af2f037b4c1e01cc82", size = 11761739, upload-time = "2025-06-17T15:18:58.906Z" }, - { url = "https://files.pythonhosted.org/packages/04/8f/ad459de67c70ec112e2ba7206841c8f4eb340a03ee6a5cabc159fe558b8e/ruff-0.12.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d235618283718ee2fe14db07f954f9b2423700919dc688eacf3f8797a11315c", size = 12537098, upload-time = "2025-06-17T15:19:01.316Z" }, - { url = "https://files.pythonhosted.org/packages/ed/50/15ad9c80ebd3c4819f5bd8883e57329f538704ed57bac680d95cb6627527/ruff-0.12.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0758038f81beec8cc52ca22de9685b8ae7f7cc18c013ec2050012862cc9165", size = 12154122, upload-time = "2025-06-17T15:19:03.727Z" }, - { url = "https://files.pythonhosted.org/packages/76/e6/79b91e41bc8cc3e78ee95c87093c6cacfa275c786e53c9b11b9358026b3d/ruff-0.12.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:139b3d28027987b78fc8d6cfb61165447bdf3740e650b7c480744873688808c2", size = 11363374, upload-time = "2025-06-17T15:19:05.875Z" }, - { url = "https://files.pythonhosted.org/packages/db/c3/82b292ff8a561850934549aa9dc39e2c4e783ab3c21debe55a495ddf7827/ruff-0.12.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68853e8517b17bba004152aebd9dd77d5213e503a5f2789395b25f26acac0da4", size = 11587647, upload-time = "2025-06-17T15:19:08.246Z" }, - { url = "https://files.pythonhosted.org/packages/2b/42/d5760d742669f285909de1bbf50289baccb647b53e99b8a3b4f7ce1b2001/ruff-0.12.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3a9512af224b9ac4757f7010843771da6b2b0935a9e5e76bb407caa901a1a514", size = 10527284, upload-time = "2025-06-17T15:19:10.37Z" }, - { url = "https://files.pythonhosted.org/packages/19/f6/fcee9935f25a8a8bba4adbae62495c39ef281256693962c2159e8b284c5f/ruff-0.12.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b08df3d96db798e5beb488d4df03011874aff919a97dcc2dd8539bb2be5d6a88", size = 10158609, upload-time = "2025-06-17T15:19:12.286Z" }, - { url = "https://files.pythonhosted.org/packages/37/fb/057febf0eea07b9384787bfe197e8b3384aa05faa0d6bd844b94ceb29945/ruff-0.12.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6a315992297a7435a66259073681bb0d8647a826b7a6de45c6934b2ca3a9ed51", size = 11141462, upload-time = "2025-06-17T15:19:15.195Z" }, - { url = "https://files.pythonhosted.org/packages/10/7c/1be8571011585914b9d23c95b15d07eec2d2303e94a03df58294bc9274d4/ruff-0.12.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e55e44e770e061f55a7dbc6e9aed47feea07731d809a3710feda2262d2d4d8a", size = 11641616, upload-time = "2025-06-17T15:19:17.6Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ef/b960ab4818f90ff59e571d03c3f992828d4683561095e80f9ef31f3d58b7/ruff-0.12.0-py3-none-win32.whl", hash = "sha256:7162a4c816f8d1555eb195c46ae0bd819834d2a3f18f98cc63819a7b46f474fb", size = 10525289, upload-time = "2025-06-17T15:19:19.688Z" }, - { url = "https://files.pythonhosted.org/packages/34/93/8b16034d493ef958a500f17cda3496c63a537ce9d5a6479feec9558f1695/ruff-0.12.0-py3-none-win_amd64.whl", hash = "sha256:d00b7a157b8fb6d3827b49d3324da34a1e3f93492c1f97b08e222ad7e9b291e0", size = 11598311, upload-time = "2025-06-17T15:19:21.785Z" }, - { url = "https://files.pythonhosted.org/packages/d0/33/4d3e79e4a84533d6cd526bfb42c020a23256ae5e4265d858bd1287831f7d/ruff-0.12.0-py3-none-win_arm64.whl", hash = "sha256:8cd24580405ad8c1cc64d61725bca091d6b6da7eb3d36f72cc605467069d7e8b", size = 10724946, upload-time = "2025-06-17T15:19:23.952Z" }, + { url = "https://files.pythonhosted.org/packages/06/bf/3dba52c1d12ab5e78d75bd78ad52fb85a6a1f29cc447c2423037b82bed0d/ruff-0.12.1-py3-none-linux_armv6l.whl", hash = "sha256:6013a46d865111e2edb71ad692fbb8262e6c172587a57c0669332a449384a36b", size = 10305649, upload-time = "2025-06-26T20:33:39.242Z" }, + { url = "https://files.pythonhosted.org/packages/8c/65/dab1ba90269bc8c81ce1d499a6517e28fe6f87b2119ec449257d0983cceb/ruff-0.12.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b3f75a19e03a4b0757d1412edb7f27cffb0c700365e9d6b60bc1b68d35bc89e0", size = 11120201, upload-time = "2025-06-26T20:33:42.207Z" }, + { url = "https://files.pythonhosted.org/packages/3f/3e/2d819ffda01defe857fa2dd4cba4d19109713df4034cc36f06bbf582d62a/ruff-0.12.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9a256522893cb7e92bb1e1153283927f842dea2e48619c803243dccc8437b8be", size = 10466769, upload-time = "2025-06-26T20:33:44.102Z" }, + { url = "https://files.pythonhosted.org/packages/63/37/bde4cf84dbd7821c8de56ec4ccc2816bce8125684f7b9e22fe4ad92364de/ruff-0.12.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:069052605fe74c765a5b4272eb89880e0ff7a31e6c0dbf8767203c1fbd31c7ff", size = 10660902, upload-time = "2025-06-26T20:33:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/0e/3a/390782a9ed1358c95e78ccc745eed1a9d657a537e5c4c4812fce06c8d1a0/ruff-0.12.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a684f125a4fec2d5a6501a466be3841113ba6847827be4573fddf8308b83477d", size = 10167002, upload-time = "2025-06-26T20:33:47.81Z" }, + { url = "https://files.pythonhosted.org/packages/6d/05/f2d4c965009634830e97ffe733201ec59e4addc5b1c0efa035645baa9e5f/ruff-0.12.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdecdef753bf1e95797593007569d8e1697a54fca843d78f6862f7dc279e23bd", size = 11751522, upload-time = "2025-06-26T20:33:49.857Z" }, + { url = "https://files.pythonhosted.org/packages/35/4e/4bfc519b5fcd462233f82fc20ef8b1e5ecce476c283b355af92c0935d5d9/ruff-0.12.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:70d52a058c0e7b88b602f575d23596e89bd7d8196437a4148381a3f73fcd5010", size = 12520264, upload-time = "2025-06-26T20:33:52.199Z" }, + { url = "https://files.pythonhosted.org/packages/85/b2/7756a6925da236b3a31f234b4167397c3e5f91edb861028a631546bad719/ruff-0.12.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84d0a69d1e8d716dfeab22d8d5e7c786b73f2106429a933cee51d7b09f861d4e", size = 12133882, upload-time = "2025-06-26T20:33:54.231Z" }, + { url = "https://files.pythonhosted.org/packages/dd/00/40da9c66d4a4d51291e619be6757fa65c91b92456ff4f01101593f3a1170/ruff-0.12.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cc32e863adcf9e71690248607ccdf25252eeeab5193768e6873b901fd441fed", size = 11608941, upload-time = "2025-06-26T20:33:56.202Z" }, + { url = "https://files.pythonhosted.org/packages/91/e7/f898391cc026a77fbe68dfea5940f8213622474cb848eb30215538a2dadf/ruff-0.12.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fd49a4619f90d5afc65cf42e07b6ae98bb454fd5029d03b306bd9e2273d44cc", size = 11602887, upload-time = "2025-06-26T20:33:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/f6/02/0891872fc6aab8678084f4cf8826f85c5d2d24aa9114092139a38123f94b/ruff-0.12.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ed5af6aaaea20710e77698e2055b9ff9b3494891e1b24d26c07055459bb717e9", size = 10521742, upload-time = "2025-06-26T20:34:00.465Z" }, + { url = "https://files.pythonhosted.org/packages/2a/98/d6534322c74a7d47b0f33b036b2498ccac99d8d8c40edadb552c038cecf1/ruff-0.12.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:801d626de15e6bf988fbe7ce59b303a914ff9c616d5866f8c79eb5012720ae13", size = 10149909, upload-time = "2025-06-26T20:34:02.603Z" }, + { url = "https://files.pythonhosted.org/packages/34/5c/9b7ba8c19a31e2b6bd5e31aa1e65b533208a30512f118805371dbbbdf6a9/ruff-0.12.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2be9d32a147f98a1972c1e4df9a6956d612ca5f5578536814372113d09a27a6c", size = 11136005, upload-time = "2025-06-26T20:34:04.723Z" }, + { url = "https://files.pythonhosted.org/packages/dc/34/9bbefa4d0ff2c000e4e533f591499f6b834346025e11da97f4ded21cb23e/ruff-0.12.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:49b7ce354eed2a322fbaea80168c902de9504e6e174fd501e9447cad0232f9e6", size = 11648579, upload-time = "2025-06-26T20:34:06.766Z" }, + { url = "https://files.pythonhosted.org/packages/6f/1c/20cdb593783f8f411839ce749ec9ae9e4298c2b2079b40295c3e6e2089e1/ruff-0.12.1-py3-none-win32.whl", hash = "sha256:d973fa626d4c8267848755bd0414211a456e99e125dcab147f24daa9e991a245", size = 10519495, upload-time = "2025-06-26T20:34:08.718Z" }, + { url = "https://files.pythonhosted.org/packages/cf/56/7158bd8d3cf16394928f47c637d39a7d532268cd45220bdb6cd622985760/ruff-0.12.1-py3-none-win_amd64.whl", hash = "sha256:9e1123b1c033f77bd2590e4c1fe7e8ea72ef990a85d2484351d408224d603013", size = 11547485, upload-time = "2025-06-26T20:34:11.008Z" }, + { url = "https://files.pythonhosted.org/packages/91/d0/6902c0d017259439d6fd2fd9393cea1cfe30169940118b007d5e0ea7e954/ruff-0.12.1-py3-none-win_arm64.whl", hash = "sha256:78ad09a022c64c13cc6077707f036bab0fac8cd7088772dcd1e5be21c5002efc", size = 10691209, upload-time = "2025-06-26T20:34:12.928Z" }, ] [[package]] @@ -4730,15 +4724,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.30.0" +version = "2.32.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/4c/af31e0201b48469786ddeb1bf6fd3dfa3a291cc613a0fe6a60163a7535f9/sentry_sdk-2.30.0.tar.gz", hash = "sha256:436369b02afef7430efb10300a344fb61a11fe6db41c2b11f41ee037d2dd7f45", size = 326767, upload-time = "2025-06-12T10:34:34.733Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/59/eb90c45cb836cf8bec973bba10230ddad1c55e2b2e9ffa9d7d7368948358/sentry_sdk-2.32.0.tar.gz", hash = "sha256:9016c75d9316b0f6921ac14c8cd4fb938f26002430ac5be9945ab280f78bec6b", size = 334932, upload-time = "2025-06-27T08:10:02.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/99/31ac6faaae33ea698086692638f58d14f121162a8db0039e68e94135e7f1/sentry_sdk-2.30.0-py2.py3-none-any.whl", hash = "sha256:59391db1550662f746ea09b483806a631c3ae38d6340804a1a4c0605044f6877", size = 343149, upload-time = "2025-06-12T10:34:32.896Z" }, + { url = "https://files.pythonhosted.org/packages/01/a1/fc4856bd02d2097324fb7ce05b3021fb850f864b83ca765f6e37e92ff8ca/sentry_sdk-2.32.0-py2.py3-none-any.whl", hash = "sha256:6cf51521b099562d7ce3606da928c473643abe99b00ce4cb5626ea735f4ec345", size = 356122, upload-time = "2025-06-27T08:10:01.424Z" }, ] [[package]] From f726717c725b89492b1f1b37f4dd2dd68a3c0a5e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 30 Jun 2025 20:34:47 -0700 Subject: [PATCH 11/74] Update stale.yaml --- .github/workflows/stale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 581f915f1d..823df2b580 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -7,7 +7,7 @@ on: env: DAYS_BEFORE_PR_CLOSE: 2 DAYS_BEFORE_PR_STALE: 9 - DAYS_BEFORE_PR_STALE: 30 + DAYS_BEFORE_PR_STALE_DRAFT: 30 jobs: stale: From a5630eb7b7b868e82d9cd6e5041736dccb0acc78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Wed, 2 Jul 2025 21:25:08 -0700 Subject: [PATCH 12/74] Bump opendbc * bronco special * ignore low speed * fixes * update ref * bump * update ref --- opendbc_repo | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index fdb4fb1935..33842a09ac 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit fdb4fb1935d494497c94f334e9a0fc51c2e0a142 +Subproject commit 33842a09ac54d2b6dc53a2afcc97841495a795cd diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 87a679cfba..ea3b0bfb7e 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -f6e0fd7806bc903b5f82123eccaf42e2b9a38891 \ No newline at end of file +502c90d4859d2529ae2adf7097d575852673be74 \ No newline at end of file From 64fd3f986081ba6cb488d02e799845367add29e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Wed, 2 Jul 2025 21:50:55 -0700 Subject: [PATCH 13/74] Tomb Raider 14 (#35620) * f7db6a09-43c5-4db9-b856-7fe1a1c231eb/400 * bd99d079-9afb-4af5-9f31-236d5c9ff15f/400 * aggressive tr: 7707a4ca-7d5e-47a2-8760-93b5004695cd/400 * bd99d079-9afb-4af5-9f31-236d5c9ff15f/400 * ae82d7a8-b74d-43b5-ab6d-d72e6040dab3/400 * revert stop distance * comments --- selfdrive/controls/lib/longitudinal_planner.py | 18 ++++++++++++++---- selfdrive/modeld/fill_model_msg.py | 7 ------- selfdrive/modeld/modeld.py | 14 +++++++++----- selfdrive/modeld/models/driving_policy.onnx | 4 ++-- selfdrive/modeld/models/driving_vision.onnx | 4 ++-- selfdrive/modeld/parse_model_outputs.py | 13 ++++++------- 6 files changed, 33 insertions(+), 27 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 9d71faeca2..f5c273cf1c 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -52,6 +52,7 @@ class LongitudinalPlanner: def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL): self.CP = CP self.mpc = LongitudinalMpc(dt=dt) + # TODO remove mpc modes when TR released self.mpc.mode = 'acc' self.fcw = False self.dt = dt @@ -90,7 +91,7 @@ class LongitudinalPlanner: return x, v, a, j, throttle_prob def update(self, sm): - self.mpc.mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc' + self.mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc' if len(sm['carControl'].orientationNED) == 3: accel_coast = get_coast_accel(sm['carControl'].orientationNED[1]) @@ -113,7 +114,7 @@ class LongitudinalPlanner: # No change cost when user is controlling the speed, or when standstill prev_accel_constraint = not (reset_state or sm['carState'].standstill) - if self.mpc.mode == 'acc': + if self.mode == 'acc': accel_clip = [ACCEL_MIN, get_max_accel(v_ego)] steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg accel_clip = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_clip, self.CP) @@ -127,7 +128,7 @@ class LongitudinalPlanner: # Prevent divergence, smooth in current v_ego self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego)) - # Compute model v_ego error + # TODO v_model_error is deprecated with TR self.v_model_error = get_speed_error(sm['modelV2'], v_ego) x, v, a, j, throttle_prob = self.parse_model(sm['modelV2'], self.v_model_error) # Don't clip at low speeds since throttle_prob doesn't account for creep @@ -160,8 +161,17 @@ class LongitudinalPlanner: self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.a_desired + a_prev) / 2.0 action_t = self.CP.longitudinalActuatorDelay + DT_MDL - output_a_target, self.output_should_stop = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX, + output_a_target_mpc, output_should_stop_mpc = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX, action_t=action_t, vEgoStopping=self.CP.vEgoStopping) + output_a_target_e2e = sm['modelV2'].action.desiredAcceleration + output_should_stop_e2e = sm['modelV2'].action.shouldStop + + if self.mode == 'acc': + output_a_target = output_a_target_mpc + self.output_should_stop = output_should_stop_mpc + else: + output_a_target = min(output_a_target_mpc, output_a_target_e2e) + self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc for idx in range(2): accel_clip[idx] = np.clip(accel_clip[idx], self.prev_accel_clip[idx] - 0.05, self.prev_accel_clip[idx] + 0.05) diff --git a/selfdrive/modeld/fill_model_msg.py b/selfdrive/modeld/fill_model_msg.py index a91c6395c7..a2b54b420e 100644 --- a/selfdrive/modeld/fill_model_msg.py +++ b/selfdrive/modeld/fill_model_msg.py @@ -89,13 +89,6 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D fill_xyzt(modelV2.orientation, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.T_FROM_CURRENT_EULER].T) fill_xyzt(modelV2.orientationRate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T) - # temporal pose - temporal_pose = modelV2.temporalPose - temporal_pose.trans = net_output_data['sim_pose'][0,:ModelConstants.POSE_WIDTH//2].tolist() - temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:ModelConstants.POSE_WIDTH//2].tolist() - temporal_pose.rot = net_output_data['sim_pose'][0,ModelConstants.POSE_WIDTH//2:].tolist() - temporal_pose.rotStd = net_output_data['sim_pose_stds'][0,ModelConstants.POSE_WIDTH//2:].tolist() - # poly path fill_xyz_poly(driving_model_data.path, ModelConstants.POLY_PATH_DEGREE, *net_output_data['plan'][0,:,Plan.POSITION].T) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index ac88dfbb5f..365cfd33ce 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -31,7 +31,7 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS from openpilot.common.transformations.model import get_warp_matrix from openpilot.system import sentry from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper -from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value +from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState from openpilot.selfdrive.modeld.constants import ModelConstants, Plan @@ -46,8 +46,8 @@ POLICY_PKL_PATH = Path(__file__).parent / 'models/driving_policy_tinygrad.pkl' VISION_METADATA_PATH = Path(__file__).parent / 'models/driving_vision_metadata.pkl' POLICY_METADATA_PATH = Path(__file__).parent / 'models/driving_policy_metadata.pkl' -LAT_SMOOTH_SECONDS = 0.0 -LONG_SMOOTH_SECONDS = 0.0 +LAT_SMOOTH_SECONDS = 0.1 +LONG_SMOOTH_SECONDS = 0.3 MIN_LAT_CONTROL_SPEED = 0.3 @@ -60,7 +60,11 @@ def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log. action_t=long_action_t) desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS) - desired_curvature = model_output['desired_curvature'][0, 0] + desired_curvature = get_curvature_from_plan(plan[:,Plan.T_FROM_CURRENT_EULER][:,2], + plan[:,Plan.ORIENTATION_RATE][:,2], + ModelConstants.T_IDXS, + v_ego, + lat_action_t) if v_ego > MIN_LAT_CONTROL_SPEED: desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, LAT_SMOOTH_SECONDS) else: @@ -174,7 +178,7 @@ class ModelState: # TODO model only uses last value now self.full_prev_desired_curv[0,:-1] = self.full_prev_desired_curv[0,1:] self.full_prev_desired_curv[0,-1,:] = policy_outputs_dict['desired_curvature'][0, :] - self.numpy_inputs['prev_desired_curv'][:] = self.full_prev_desired_curv[0, self.temporal_idxs] + self.numpy_inputs['prev_desired_curv'][:] = 0*self.full_prev_desired_curv[0, self.temporal_idxs] combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} if SEND_RAW_PRED: diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index 343c3a0aa8..2fb716997d 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:5f714fb38bcd44b5d9f44e3a8e1595e1dfdf7558f0eec3485cf3f2dbb6dc7d8d -size 15971805 +oid sha256:1741cad23f6f451782b5db6182218749ee12072e393d57eac36d8d5c55d9358a +size 15583374 diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx index 924d11304d..fe636fb682 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:3ac4867fbc618037e8d03143edbfeeae960f2025644b5dcf36c6665271b4f874 -size 34883375 +oid sha256:3d2bd82ba42341dba1bda5426e45c4c646db604c9ac422156eaa2b9ef26194f9 +size 46265993 diff --git a/selfdrive/modeld/parse_model_outputs.py b/selfdrive/modeld/parse_model_outputs.py index 810c44ccb9..783572d436 100644 --- a/selfdrive/modeld/parse_model_outputs.py +++ b/selfdrive/modeld/parse_model_outputs.py @@ -88,6 +88,12 @@ class Parser: self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,)) self.parse_mdn('road_transform', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) + self.parse_mdn('lane_lines', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_LANE_LINES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) + self.parse_mdn('road_edges', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_ROAD_EDGES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) + self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION, + out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH)) + for k in ['lead_prob', 'lane_lines_prob']: + self.parse_binary_crossentropy(k, outs) self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH)) self.parse_binary_crossentropy('meta', outs) return outs @@ -95,17 +101,10 @@ class Parser: def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: self.parse_mdn('plan', outs, in_N=ModelConstants.PLAN_MHP_N, out_N=ModelConstants.PLAN_MHP_SELECTION, out_shape=(ModelConstants.IDX_N,ModelConstants.PLAN_WIDTH)) - self.parse_mdn('lane_lines', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_LANE_LINES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) - self.parse_mdn('road_edges', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_ROAD_EDGES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) - self.parse_mdn('sim_pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) - self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION, - out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH)) if 'lat_planner_solution' in outs: self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(ModelConstants.IDX_N,ModelConstants.LAT_PLANNER_SOLUTION_WIDTH)) if 'desired_curvature' in outs: self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,)) - for k in ['lead_prob', 'lane_lines_prob']: - self.parse_binary_crossentropy(k, outs) self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,)) return outs From e503e657bc06bd6c47a290f62fe0984af58b1b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Wed, 2 Jul 2025 23:36:41 -0700 Subject: [PATCH 14/74] Model error deprecated with TR (#35628) * Model error deprecated with TR * no get speed error * import --- cereal/log.capnp | 2 +- selfdrive/controls/lib/drive_helpers.py | 9 --------- selfdrive/controls/lib/longitudinal_planner.py | 13 +++++-------- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index a754b9ef00..7f41cffa15 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -1083,7 +1083,7 @@ struct ModelDataV2 { confidence @23: ConfidenceClass; # Model perceived motion - temporalPose @21 :Pose; + temporalPoseDEPRECATED @21 :Pose; # e2e lateral planner action @26: Action; diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 56eeac3bdf..51eb694b4c 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -1,5 +1,4 @@ import numpy as np -from cereal import log from opendbc.car.vehicle_model import ACCELERATION_DUE_TO_GRAVITY from openpilot.common.realtime import DT_CTRL, DT_MDL @@ -40,14 +39,6 @@ def clip_curvature(v_ego, prev_curvature, new_curvature, roll): return float(new_curvature), limited_accel or limited_max_curv -def get_speed_error(modelV2: log.ModelDataV2, v_ego: float) -> float: - # ToDo: Try relative error, and absolute speed - if len(modelV2.temporalPose.trans): - vel_err = np.clip(modelV2.temporalPose.trans[0] - v_ego, -MAX_VEL_ERR, MAX_VEL_ERR) - return float(vel_err) - return 0.0 - - def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL, vEgoStopping=0.05): if len(speeds) == len(t_idxs): v_now = speeds[0] diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index f5c273cf1c..ba622416ac 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -11,7 +11,7 @@ from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC -from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_speed_error, get_accel_from_plan +from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_accel_from_plan from openpilot.selfdrive.car.cruise import V_CRUISE_MAX, V_CRUISE_UNSET from openpilot.common.swaglog import cloudlog @@ -61,7 +61,6 @@ class LongitudinalPlanner: self.a_desired = init_a self.v_desired_filter = FirstOrderFilter(init_v, 2.0, self.dt) self.prev_accel_clip = [ACCEL_MIN, ACCEL_MAX] - self.v_model_error = 0.0 self.output_a_target = 0.0 self.output_should_stop = False @@ -71,12 +70,12 @@ class LongitudinalPlanner: self.solverExecutionTime = 0.0 @staticmethod - def parse_model(model_msg, model_error): + def parse_model(model_msg): if (len(model_msg.position.x) == ModelConstants.IDX_N and len(model_msg.velocity.x) == ModelConstants.IDX_N and len(model_msg.acceleration.x) == ModelConstants.IDX_N): - x = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.position.x) - model_error * T_IDXS_MPC - v = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.velocity.x) - model_error + x = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.position.x) + v = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.velocity.x) a = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.acceleration.x) j = np.zeros(len(T_IDXS_MPC)) else: @@ -128,9 +127,7 @@ class LongitudinalPlanner: # Prevent divergence, smooth in current v_ego self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego)) - # TODO v_model_error is deprecated with TR - self.v_model_error = get_speed_error(sm['modelV2'], v_ego) - x, v, a, j, throttle_prob = self.parse_model(sm['modelV2'], self.v_model_error) + x, v, a, j, throttle_prob = self.parse_model(sm['modelV2']) # Don't clip at low speeds since throttle_prob doesn't account for creep self.allow_throttle = throttle_prob > ALLOW_THROTTLE_THRESHOLD or v_ego <= MIN_ALLOW_THROTTLE_SPEED From 2183b4ca7baa4c09f8d0625df64e332ee6354ae2 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Thu, 3 Jul 2025 19:06:55 +0200 Subject: [PATCH 15/74] Add support for generating clang compilation database by default (#35629) compilation db by default --- SConstruct | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/SConstruct b/SConstruct index d5051c8fb2..83ff9cbf02 100644 --- a/SConstruct +++ b/SConstruct @@ -39,10 +39,6 @@ AddOption('--clazy', action='store_true', help='build with clazy') -AddOption('--compile_db', - action='store_true', - help='build clang compilation database') - AddOption('--ccflags', action='store', type='string', @@ -214,8 +210,7 @@ if arch == "Darwin": darwin_rpath_link_flags = [f"-Wl,-rpath,{path}" for path in env["RPATH"]] env["LINKFLAGS"] += darwin_rpath_link_flags -if GetOption('compile_db'): - env.CompilationDatabase('compile_commands.json') +env.CompilationDatabase('compile_commands.json') # Setup cache dir cache_dir = '/data/scons_cache' if AGNOS else '/tmp/scons_cache' From 7e4c9ee612d4d1b3acd380379126e949bf89bba9 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 3 Jul 2025 12:57:08 -0700 Subject: [PATCH 16/74] tici: reduce GPU pwrlevel (#35630) --- system/hardware/tici/hardware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 8e4ba722c4..20a23bd67e 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -415,8 +415,8 @@ class Tici(HardwareBase): # *** GPU config *** # https://github.com/commaai/agnos-kernel-sdm845/blob/master/arch/arm64/boot/dts/qcom/sdm845-gpu.dtsi#L216 - sudo_write("0", "/sys/class/kgsl/kgsl-3d0/min_pwrlevel") - sudo_write("0", "/sys/class/kgsl/kgsl-3d0/max_pwrlevel") + 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") sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_clk_on") sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_rail_on") From 1209c2a6c0581a837cf096a4e033af3eaca839c1 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 3 Jul 2025 17:14:24 -0700 Subject: [PATCH 17/74] ci: format tests yaml (#35632) format --- .github/workflows/selfdrive_tests.yaml | 48 +++++++++++++------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 8403bdd9cc..cab1344a00 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -32,9 +32,9 @@ env: jobs: build_release: name: build release - runs-on: ${{ - (github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || + 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"]') }} @@ -70,9 +70,9 @@ jobs: run: release/check-submodules.sh build: - runs-on: ${{ - (github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || + 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"]') }} @@ -124,9 +124,9 @@ jobs: static_analysis: name: static analysis - runs-on: ${{ - (github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || + 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"]') }} @@ -143,9 +143,9 @@ jobs: unit_tests: name: unit tests - runs-on: ${{ - (github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || + 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"]') }} @@ -173,9 +173,9 @@ jobs: process_replay: name: process replay - runs-on: ${{ - (github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || + 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"]') }} @@ -229,9 +229,9 @@ jobs: test_cars: name: cars - runs-on: ${{ - (github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || + 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"]') }} @@ -324,9 +324,9 @@ jobs: simulator_driving: name: simulator driving - runs-on: ${{ - (github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || + 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"]') }} @@ -349,9 +349,9 @@ jobs: 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') || + 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"]') }} From ebe9ab85af974c692578bd3797b31b0c478d5b95 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 3 Jul 2025 18:28:57 -0700 Subject: [PATCH 18/74] ci: remove `codecov` (#35631) * debug * coverage?? * remove all coverage * remove old hack * Revert "remove old hack" This reverts commit 32ee5f589f98f548afac46a539a4b5ab095630e5. * remove * read --- .github/workflows/selfdrive_tests.yaml | 26 +++----------------------- README.md | 1 - 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index cab1344a00..a78b51950d 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -27,7 +27,7 @@ env: RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c - PYTEST: pytest --continue-on-collection-errors --cov --cov-report=xml --cov-append --durations=0 --durations-min=5 --hypothesis-seed 0 -n logical + PYTEST: pytest --continue-on-collection-errors --durations=0 --durations-min=5 -n logical jobs: build_release: @@ -164,12 +164,6 @@ jobs: ./selfdrive/ui/tests/create_test_translations.sh && \ QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ chmod -R 777 /tmp/comma_download_cache" - - name: "Upload coverage to Codecov" - uses: codecov/codecov-action@v4 - with: - name: ${{ github.job }} - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} process_replay: name: process replay @@ -196,10 +190,8 @@ jobs: - name: Run replay timeout-minutes: ${{ contains(runner.name, 'nsc') && (steps.dependency-cache.outputs.cache-hit == 'true') && 1 || 20 }} run: | - ${{ env.RUN }} "coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ - chmod -R 777 /tmp/comma_download_cache && \ - coverage combine && \ - coverage xml" + ${{ env.RUN }} "selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ + chmod -R 777 /tmp/comma_download_cache" - name: Print diff id: print-diff if: always() @@ -220,12 +212,6 @@ jobs: run: | ${{ env.RUN }} "ONNXCPU=1 $PYTEST selfdrive/test/process_replay/test_regen.py && \ chmod -R 777 /tmp/comma_download_cache" - - name: "Upload coverage to Codecov" - uses: codecov/codecov-action@v4 - with: - name: ${{ github.job }} - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} test_cars: name: cars @@ -260,12 +246,6 @@ jobs: env: NUM_JOBS: 4 JOB_ID: ${{ matrix.job }} - - name: "Upload coverage to Codecov" - uses: codecov/codecov-action@v4 - with: - name: ${{ github.job }}-${{ matrix.job }} - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} car_docs_diff: name: PR comments diff --git a/README.md b/README.md index 17238b5c3f..dcf645ff61 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,6 @@ 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) -[![codecov](https://codecov.io/gh/commaai/openpilot/branch/master/graph/badge.svg)](https://codecov.io/gh/commaai/openpilot) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![X Follow](https://img.shields.io/twitter/follow/comma_ai)](https://x.com/comma_ai) [![Discord](https://img.shields.io/discord/469524606043160576)](https://discord.comma.ai) From 8ae0026d8d03bdff8798ef52e9385cdf879e7ec8 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 3 Jul 2025 18:49:06 -0700 Subject: [PATCH 19/74] remove `coverage` package (#35634) cov --- codecov.yml | 13 ----- pyproject.toml | 5 -- uv.lock | 150 +++++++++++-------------------------------------- 3 files changed, 34 insertions(+), 134 deletions(-) delete mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 519e7d1a38..0000000000 --- a/codecov.yml +++ /dev/null @@ -1,13 +0,0 @@ -comment: false -coverage: - status: - project: - default: - informational: true - patch: off - -ignore: - - "**/test_*.py" - - "selfdrive/test/**" - - "system/version.py" # codecov changes depending on if we are in a branch or not - - "tools" diff --git a/pyproject.toml b/pyproject.toml index 98c9bace92..25d0c455f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,11 +81,9 @@ docs = [ ] testing = [ - "coverage", "hypothesis ==6.47.*", "mypy", "pytest", - "pytest-cov", "pytest-cpp", "pytest-subtests", # https://github.com/pytest-dev/pytest-xdist/issues/1215 @@ -265,8 +263,5 @@ lint.flake8-implicit-str-concat.allow-multiline = false "unittest".msg = "Use pytest" "pyray.measure_text_ex".msg = "Use openpilot.system.ui.lib.text_measure" -[tool.coverage.run] -concurrency = ["multiprocessing", "thread"] - [tool.ruff.format] quote-style = "preserve" diff --git a/uv.lock b/uv.lock index b25bf8a9f5..7cd67d7959 100644 --- a/uv.lock +++ b/uv.lock @@ -368,43 +368,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, ] -[[package]] -name = "coverage" -version = "7.9.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/e0/98670a80884f64578f0c22cd70c5e81a6e07b08167721c7487b4d70a7ca0/coverage-7.9.1.tar.gz", hash = "sha256:6cf43c78c4282708a28e466316935ec7489a9c487518a77fa68f716c67909cec", size = 813650, upload-time = "2025-06-13T13:02:28.627Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/34/fa69372a07d0903a78ac103422ad34db72281c9fc625eba94ac1185da66f/coverage-7.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95c765060e65c692da2d2f51a9499c5e9f5cf5453aeaf1420e3fc847cc060582", size = 212146, upload-time = "2025-06-13T13:00:48.496Z" }, - { url = "https://files.pythonhosted.org/packages/27/f0/da1894915d2767f093f081c42afeba18e760f12fdd7a2f4acbe00564d767/coverage-7.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba383dc6afd5ec5b7a0d0c23d38895db0e15bcba7fb0fa8901f245267ac30d86", size = 212536, upload-time = "2025-06-13T13:00:51.535Z" }, - { url = "https://files.pythonhosted.org/packages/10/d5/3fc33b06e41e390f88eef111226a24e4504d216ab8e5d1a7089aa5a3c87a/coverage-7.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37ae0383f13cbdcf1e5e7014489b0d71cc0106458878ccde52e8a12ced4298ed", size = 245092, upload-time = "2025-06-13T13:00:52.883Z" }, - { url = "https://files.pythonhosted.org/packages/0a/39/7aa901c14977aba637b78e95800edf77f29f5a380d29768c5b66f258305b/coverage-7.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69aa417a030bf11ec46149636314c24c8d60fadb12fc0ee8f10fda0d918c879d", size = 242806, upload-time = "2025-06-13T13:00:54.571Z" }, - { url = "https://files.pythonhosted.org/packages/43/fc/30e5cfeaf560b1fc1989227adedc11019ce4bb7cce59d65db34fe0c2d963/coverage-7.9.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a4be2a28656afe279b34d4f91c3e26eccf2f85500d4a4ff0b1f8b54bf807338", size = 244610, upload-time = "2025-06-13T13:00:56.932Z" }, - { url = "https://files.pythonhosted.org/packages/bf/15/cca62b13f39650bc87b2b92bb03bce7f0e79dd0bf2c7529e9fc7393e4d60/coverage-7.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:382e7ddd5289f140259b610e5f5c58f713d025cb2f66d0eb17e68d0a94278875", size = 244257, upload-time = "2025-06-13T13:00:58.545Z" }, - { url = "https://files.pythonhosted.org/packages/cd/1a/c0f2abe92c29e1464dbd0ff9d56cb6c88ae2b9e21becdb38bea31fcb2f6c/coverage-7.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e5532482344186c543c37bfad0ee6069e8ae4fc38d073b8bc836fc8f03c9e250", size = 242309, upload-time = "2025-06-13T13:00:59.836Z" }, - { url = "https://files.pythonhosted.org/packages/57/8d/c6fd70848bd9bf88fa90df2af5636589a8126d2170f3aade21ed53f2b67a/coverage-7.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a39d18b3f50cc121d0ce3838d32d58bd1d15dab89c910358ebefc3665712256c", size = 242898, upload-time = "2025-06-13T13:01:02.506Z" }, - { url = "https://files.pythonhosted.org/packages/c2/9e/6ca46c7bff4675f09a66fe2797cd1ad6a24f14c9c7c3b3ebe0470a6e30b8/coverage-7.9.1-cp311-cp311-win32.whl", hash = "sha256:dd24bd8d77c98557880def750782df77ab2b6885a18483dc8588792247174b32", size = 214561, upload-time = "2025-06-13T13:01:04.012Z" }, - { url = "https://files.pythonhosted.org/packages/a1/30/166978c6302010742dabcdc425fa0f938fa5a800908e39aff37a7a876a13/coverage-7.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b55ad10a35a21b8015eabddc9ba31eb590f54adc9cd39bcf09ff5349fd52125", size = 215493, upload-time = "2025-06-13T13:01:05.702Z" }, - { url = "https://files.pythonhosted.org/packages/60/07/a6d2342cd80a5be9f0eeab115bc5ebb3917b4a64c2953534273cf9bc7ae6/coverage-7.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ad935f0016be24c0e97fc8c40c465f9c4b85cbbe6eac48934c0dc4d2568321e", size = 213869, upload-time = "2025-06-13T13:01:09.345Z" }, - { url = "https://files.pythonhosted.org/packages/68/d9/7f66eb0a8f2fce222de7bdc2046ec41cb31fe33fb55a330037833fb88afc/coverage-7.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8de12b4b87c20de895f10567639c0797b621b22897b0af3ce4b4e204a743626", size = 212336, upload-time = "2025-06-13T13:01:10.909Z" }, - { url = "https://files.pythonhosted.org/packages/20/20/e07cb920ef3addf20f052ee3d54906e57407b6aeee3227a9c91eea38a665/coverage-7.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5add197315a054e92cee1b5f686a2bcba60c4c3e66ee3de77ace6c867bdee7cb", size = 212571, upload-time = "2025-06-13T13:01:12.518Z" }, - { url = "https://files.pythonhosted.org/packages/78/f8/96f155de7e9e248ca9c8ff1a40a521d944ba48bec65352da9be2463745bf/coverage-7.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600a1d4106fe66f41e5d0136dfbc68fe7200a5cbe85610ddf094f8f22e1b0300", size = 246377, upload-time = "2025-06-13T13:01:14.87Z" }, - { url = "https://files.pythonhosted.org/packages/3e/cf/1d783bd05b7bca5c10ded5f946068909372e94615a4416afadfe3f63492d/coverage-7.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a876e4c3e5a2a1715a6608906aa5a2e0475b9c0f68343c2ada98110512ab1d8", size = 243394, upload-time = "2025-06-13T13:01:16.23Z" }, - { url = "https://files.pythonhosted.org/packages/02/dd/e7b20afd35b0a1abea09fb3998e1abc9f9bd953bee548f235aebd2b11401/coverage-7.9.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f34346dd63010453922c8e628a52ea2d2ccd73cb2487f7700ac531b247c8a5", size = 245586, upload-time = "2025-06-13T13:01:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/4e/38/b30b0006fea9d617d1cb8e43b1bc9a96af11eff42b87eb8c716cf4d37469/coverage-7.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:888f8eee13f2377ce86d44f338968eedec3291876b0b8a7289247ba52cb984cd", size = 245396, upload-time = "2025-06-13T13:01:19.164Z" }, - { url = "https://files.pythonhosted.org/packages/31/e4/4d8ec1dc826e16791f3daf1b50943e8e7e1eb70e8efa7abb03936ff48418/coverage-7.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9969ef1e69b8c8e1e70d591f91bbc37fc9a3621e447525d1602801a24ceda898", size = 243577, upload-time = "2025-06-13T13:01:22.433Z" }, - { url = "https://files.pythonhosted.org/packages/25/f4/b0e96c5c38e6e40ef465c4bc7f138863e2909c00e54a331da335faf0d81a/coverage-7.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:60c458224331ee3f1a5b472773e4a085cc27a86a0b48205409d364272d67140d", size = 244809, upload-time = "2025-06-13T13:01:24.143Z" }, - { url = "https://files.pythonhosted.org/packages/8a/65/27e0a1fa5e2e5079bdca4521be2f5dabf516f94e29a0defed35ac2382eb2/coverage-7.9.1-cp312-cp312-win32.whl", hash = "sha256:5f646a99a8c2b3ff4c6a6e081f78fad0dde275cd59f8f49dc4eab2e394332e74", size = 214724, upload-time = "2025-06-13T13:01:25.435Z" }, - { url = "https://files.pythonhosted.org/packages/9b/a8/d5b128633fd1a5e0401a4160d02fa15986209a9e47717174f99dc2f7166d/coverage-7.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:30f445f85c353090b83e552dcbbdad3ec84c7967e108c3ae54556ca69955563e", size = 215535, upload-time = "2025-06-13T13:01:27.861Z" }, - { url = "https://files.pythonhosted.org/packages/a3/37/84bba9d2afabc3611f3e4325ee2c6a47cd449b580d4a606b240ce5a6f9bf/coverage-7.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:af41da5dca398d3474129c58cb2b106a5d93bbb196be0d307ac82311ca234342", size = 213904, upload-time = "2025-06-13T13:01:29.202Z" }, - { url = "https://files.pythonhosted.org/packages/3e/e5/c723545c3fd3204ebde3b4cc4b927dce709d3b6dc577754bb57f63ca4a4a/coverage-7.9.1-pp39.pp310.pp311-none-any.whl", hash = "sha256:db0f04118d1db74db6c9e1cb1898532c7dcc220f1d2718f058601f7c3f499514", size = 204009, upload-time = "2025-06-13T13:02:25.787Z" }, - { url = "https://files.pythonhosted.org/packages/08/b8/7ddd1e8ba9701dea08ce22029917140e6f66a859427406579fd8d0ca7274/coverage-7.9.1-py3-none-any.whl", hash = "sha256:66b974b145aa189516b6bf2d8423e888b742517d37872f6ee4c5be0073bd9a3c", size = 204000, upload-time = "2025-06-13T13:02:27.173Z" }, -] - -[package.optional-dependencies] -toml = [ - { name = "tomli", marker = "python_full_version <= '3.11'" }, -] - [[package]] name = "crcmod" version = "1.7" @@ -658,10 +621,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 = [ @@ -840,10 +803,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/55/2cb24ea48aa30c99f805921c1c7860c1f45c0e811e44ee4e6a155668de06/lxml-6.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:219e0431ea8006e15005767f0351e3f7f9143e793e58519dc97fe9e07fae5563", size = 4952289, upload-time = "2025-06-28T18:47:25.602Z" }, { url = "https://files.pythonhosted.org/packages/31/c0/b25d9528df296b9a3306ba21ff982fc5b698c45ab78b94d18c2d6ae71fd9/lxml-6.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bd5913b4972681ffc9718bc2d4c53cde39ef81415e1671ff93e9aa30b46595e7", size = 5111310, upload-time = "2025-06-28T18:47:28.136Z" }, { url = "https://files.pythonhosted.org/packages/e9/af/681a8b3e4f668bea6e6514cbcb297beb6de2b641e70f09d3d78655f4f44c/lxml-6.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:390240baeb9f415a82eefc2e13285016f9c8b5ad71ec80574ae8fa9605093cd7", size = 5025457, upload-time = "2025-06-26T16:26:15.068Z" }, + { url = "https://files.pythonhosted.org/packages/99/b6/3a7971aa05b7be7dfebc7ab57262ec527775c2c3c5b2f43675cac0458cad/lxml-6.0.0-cp312-cp312-manylinux_2_27_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d6e200909a119626744dd81bae409fc44134389e03fbf1d68ed2a55a2fb10991", size = 5657016, upload-time = "2025-07-03T19:19:06.008Z" }, { url = "https://files.pythonhosted.org/packages/69/f8/693b1a10a891197143c0673fcce5b75fc69132afa81a36e4568c12c8faba/lxml-6.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ca50bd612438258a91b5b3788c6621c1f05c8c478e7951899f492be42defc0da", size = 5257565, upload-time = "2025-06-26T16:26:17.906Z" }, { url = "https://files.pythonhosted.org/packages/a8/96/e08ff98f2c6426c98c8964513c5dab8d6eb81dadcd0af6f0c538ada78d33/lxml-6.0.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:c24b8efd9c0f62bad0439283c2c795ef916c5a6b75f03c17799775c7ae3c0c9e", size = 4713390, upload-time = "2025-06-26T16:26:20.292Z" }, { url = "https://files.pythonhosted.org/packages/a8/83/6184aba6cc94d7413959f6f8f54807dc318fdcd4985c347fe3ea6937f772/lxml-6.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afd27d8629ae94c5d863e32ab0e1d5590371d296b87dae0a751fb22bf3685741", size = 5066103, upload-time = "2025-06-26T16:26:22.765Z" }, { url = "https://files.pythonhosted.org/packages/ee/01/8bf1f4035852d0ff2e36a4d9aacdbcc57e93a6cd35a54e05fa984cdf73ab/lxml-6.0.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:54c4855eabd9fc29707d30141be99e5cd1102e7d2258d2892314cf4c110726c3", size = 4791428, upload-time = "2025-06-26T16:26:26.461Z" }, + { url = "https://files.pythonhosted.org/packages/29/31/c0267d03b16954a85ed6b065116b621d37f559553d9339c7dcc4943a76f1/lxml-6.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c907516d49f77f6cd8ead1322198bdfd902003c3c330c77a1c5f3cc32a0e4d16", size = 5678523, upload-time = "2025-07-03T19:19:09.837Z" }, { url = "https://files.pythonhosted.org/packages/5c/f7/5495829a864bc5f8b0798d2b52a807c89966523140f3d6fa3a58ab6720ea/lxml-6.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36531f81c8214e293097cd2b7873f178997dae33d3667caaae8bdfb9666b76c0", size = 5281290, upload-time = "2025-06-26T16:26:29.406Z" }, { url = "https://files.pythonhosted.org/packages/79/56/6b8edb79d9ed294ccc4e881f4db1023af56ba451909b9ce79f2a2cd7c532/lxml-6.0.0-cp312-cp312-win32.whl", hash = "sha256:690b20e3388a7ec98e899fd54c924e50ba6693874aa65ef9cb53de7f7de9d64a", size = 3613495, upload-time = "2025-06-26T16:26:31.588Z" }, { url = "https://files.pythonhosted.org/packages/0b/1e/cc32034b40ad6af80b6fd9b66301fc0f180f300002e5c3eb5a6110a93317/lxml-6.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:310b719b695b3dd442cdfbbe64936b2f2e231bb91d998e99e6f0daf991a3eba3", size = 4014711, upload-time = "2025-06-26T16:26:33.723Z" }, @@ -932,22 +897,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" }, @@ -1294,13 +1259,11 @@ docs = [ ] testing = [ { name = "codespell" }, - { name = "coverage" }, { name = "hypothesis" }, { name = "mypy" }, { name = "pre-commit-hooks" }, { name = "pytest" }, { name = "pytest-asyncio" }, - { name = "pytest-cov" }, { name = "pytest-cpp" }, { name = "pytest-mock" }, { name = "pytest-randomly" }, @@ -1325,7 +1288,6 @@ 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'" }, @@ -1360,7 +1322,6 @@ requires-dist = [ { name = "pyserial" }, { name = "pytest", marker = "extra == 'testing'" }, { name = "pytest-asyncio", marker = "extra == 'testing'" }, - { name = "pytest-cov", marker = "extra == 'testing'" }, { name = "pytest-cpp", marker = "extra == 'testing'" }, { name = "pytest-mock", marker = "extra == 'testing'" }, { name = "pytest-randomly", marker = "extra == 'testing'" }, @@ -1426,8 +1387,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 = [ @@ -1439,8 +1400,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 = [ @@ -4203,9 +4164,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 = [ @@ -4306,20 +4267,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/05/ce271016e351fddc8399e546f6e23761967ee09c8c568bbfbecb0c150171/pytest_asyncio-1.0.0-py3-none-any.whl", hash = "sha256:4f024da9f1ef945e680dc68610b52550e36590a67fd31bb3b4943979a1f90ef3", size = 15976, upload-time = "2025-05-26T04:54:39.035Z" }, ] -[[package]] -name = "pytest-cov" -version = "6.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "coverage", extra = ["toml"] }, - { name = "pluggy" }, - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, -] - [[package]] name = "pytest-cpp" version = "2.6.0" @@ -4437,9 +4384,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 = [ @@ -4781,7 +4728,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 = [ @@ -4892,35 +4839,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, ] -[[package]] -name = "tomli" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, -] - [[package]] name = "tqdm" version = "4.67.1" @@ -5039,7 +4957,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 c807ecd7e173908ef0ae6ce0e72102a629369957 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 3 Jul 2025 18:57:59 -0700 Subject: [PATCH 20/74] revert fixed pytest seed --- conftest.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/conftest.py b/conftest.py index 9eda5d612a..7e40ec3ed7 100644 --- a/conftest.py +++ b/conftest.py @@ -2,7 +2,6 @@ import contextlib import gc import os import pytest -import random from openpilot.common.prefix import OpenpilotPrefix from openpilot.system.manager import manager @@ -48,8 +47,6 @@ def clean_env(): @pytest.fixture(scope="function", autouse=True) def openpilot_function_fixture(request): - random.seed(0) - with clean_env(): # setup a clean environment for each test with OpenpilotPrefix(shared_download_cache=request.node.get_closest_marker("shared_download_cache") is not None) as prefix: From a4e4a8afef51af60d75ddf22ac2236b89a22022a Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Sat, 5 Jul 2025 11:03:13 -0700 Subject: [PATCH 21/74] include audio in qcamera.ts (#35608) * encode/store audio as part of video file * better match write_audio() with write() * handle different FFmpeg versions, flush audio encoder, suppress encoder QAvg/info messages * use audio_buffer.size() instead of keeping track of size separately * no more for loops * save to qcam and rlog * assert audio support check * microphone --> soundPressure, audioData --> rawAudioData * deque much more efficient if buffer ever >> frame_size, ~ same performance for defaults * cleanup and fix time scaling * initialize audio separately and pass sample_rate in * update comments * ensure header is written before writing audio * buffer audio frame but do not process before header written * handle buffer overflow now that we are using as an actual buffer * spelling --- system/loggerd/loggerd.cc | 51 ++++++++++++--- system/loggerd/loggerd.h | 2 + system/loggerd/video_writer.cc | 111 +++++++++++++++++++++++++++++++++ system/loggerd/video_writer.h | 16 +++++ 4 files changed, 170 insertions(+), 10 deletions(-) diff --git a/system/loggerd/loggerd.cc b/system/loggerd/loggerd.cc index 898216e5b6..144ab9f349 100644 --- a/system/loggerd/loggerd.cc +++ b/system/loggerd/loggerd.cc @@ -62,6 +62,7 @@ struct RemoteEncoder { bool recording = false; bool marked_ready_to_rotate = false; bool seen_first_packet = false; + bool audio_initialized = false; }; size_t write_encode_data(LoggerdState *s, cereal::Event::Reader event, RemoteEncoder &re, const EncoderInfo &encoder_info) { @@ -78,12 +79,7 @@ size_t write_encode_data(LoggerdState *s, cereal::Event::Reader event, RemoteEnc LOGW("%s: dropped %d non iframe packets before init", encoder_info.publish_name, re.dropped_frames); re.dropped_frames = 0; } - // if we aren't actually recording, don't create the writer if (encoder_info.record) { - assert(encoder_info.filename != NULL); - re.writer.reset(new VideoWriter(s->logger.segmentPath().c_str(), - encoder_info.filename, idx.getType() != cereal::EncodeIndex::Type::FULL_H_E_V_C, - edata.getWidth(), edata.getHeight(), encoder_info.fps, idx.getType())); // write the header auto header = edata.getHeader(); re.writer->write((uint8_t *)header.begin(), header.size(), idx.getTimestampEof() / 1000, true, false); @@ -138,12 +134,19 @@ int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct // if this is a new segment, we close any possible old segments, move to the new, and process any queued packets if (re.current_segment != s->logger.segment()) { - if (re.recording) { - re.writer.reset(); + // if we aren't actually recording, don't create the writer + if (encoder_info.record) { + assert(encoder_info.filename != NULL); + re.writer.reset(new VideoWriter(s->logger.segmentPath().c_str(), + encoder_info.filename, idx.getType() != cereal::EncodeIndex::Type::FULL_H_E_V_C, + edata.getWidth(), edata.getHeight(), encoder_info.fps, idx.getType())); re.recording = false; + re.audio_initialized = false; } re.current_segment = s->logger.segment(); re.marked_ready_to_rotate = false; + } + if (re.audio_initialized || !encoder_info.include_audio) { // we are in this segment now, process any queued messages before this one if (!re.q.empty()) { for (auto qmsg : re.q) { @@ -153,9 +156,14 @@ int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct } re.q.clear(); } + bytes_count += write_encode_data(s, event, re, encoder_info); + delete msg; + } else if (re.q.size() > MAIN_FPS*10) { + LOGE_100("%s: dropping frame waiting for audio initialization, queue is too large", name.c_str()); + delete msg; + } else { + re.q.push_back(msg); // queue up all the new segment messages, they go in after audio is initialized } - bytes_count += write_encode_data(s, event, re, encoder_info); - delete msg; } else if (offset_segment_num > s->logger.segment()) { // encoderd packet has a newer segment, this means encoderd has rolled over if (!re.marked_ready_to_rotate) { @@ -214,7 +222,7 @@ void loggerd_thread() { typedef struct ServiceState { std::string name; int counter, freq; - bool encoder, user_flag; + bool encoder, user_flag, record_audio; } ServiceState; std::unordered_map service_state; std::unordered_map remote_encoders; @@ -239,6 +247,7 @@ void loggerd_thread() { .freq = it.decimation, .encoder = encoder, .user_flag = it.name == "userFlag", + .record_audio = record_audio, }; } } @@ -249,6 +258,7 @@ void loggerd_thread() { Params().put("CurrentRoute", s.logger.routeName()); std::map encoder_infos_dict; + std::vector encoders_with_audio; for (const auto &cam : cameras_logged) { for (const auto &encoder_info : cam.encoder_infos) { encoder_infos_dict[encoder_info.publish_name] = encoder_info; @@ -256,6 +266,13 @@ void loggerd_thread() { } } + for (auto &[sock, service] : service_state) { + auto it = encoder_infos_dict.find(service.name); + if (it != encoder_infos_dict.end() && it->second.include_audio) { + encoders_with_audio.push_back(&remote_encoders[sock]); + } + } + uint64_t msg_count = 0, bytes_count = 0; double start_ts = millis_since_boot(); while (!do_exit) { @@ -273,6 +290,20 @@ void loggerd_thread() { Message *msg = nullptr; while (!do_exit && (msg = sock->receive(true))) { const bool in_qlog = service.freq != -1 && (service.counter++ % service.freq == 0); + + if (service.record_audio) { + capnp::FlatArrayMessageReader cmsg(kj::ArrayPtr((capnp::word *)msg->getData(), msg->getSize() / sizeof(capnp::word))); + auto event = cmsg.getRoot(); + auto audio_data = event.getRawAudioData().getData(); + auto sample_rate = event.getRawAudioData().getSampleRate(); + for (auto* encoder : encoders_with_audio) { + if (encoder && encoder->writer) { + encoder->writer->write_audio((uint8_t*)audio_data.begin(), audio_data.size(), event.getLogMonoTime() / 1000, sample_rate); + encoder->audio_initialized = true; + } + } + } + if (service.encoder) { s.last_camera_seen_tms = millis_since_boot(); bytes_count += handle_encoder_msg(&s, msg, service.name, remote_encoders[sock], encoder_infos_dict[service.name]); diff --git a/system/loggerd/loggerd.h b/system/loggerd/loggerd.h index 27d2d37fc4..5dfb178fd5 100644 --- a/system/loggerd/loggerd.h +++ b/system/loggerd/loggerd.h @@ -35,6 +35,7 @@ public: const char *thumbnail_name = NULL; const char *filename = NULL; bool record = true; + bool include_audio = false; int frame_width = -1; int frame_height = -1; int fps = MAIN_FPS; @@ -106,6 +107,7 @@ const EncoderInfo qcam_encoder_info = { .encode_type = cereal::EncodeIndex::Type::QCAMERA_H264, .frame_width = 526, .frame_height = 330, + .include_audio = Params().getBool("RecordAudio"), INIT_ENCODE_FUNCTIONS(QRoadEncode), }; diff --git a/system/loggerd/video_writer.cc b/system/loggerd/video_writer.cc index 90b5f1af3d..68e870982f 100644 --- a/system/loggerd/video_writer.cc +++ b/system/loggerd/video_writer.cc @@ -50,6 +50,45 @@ VideoWriter::VideoWriter(const char *path, const char *filename, bool remuxing, } } +void VideoWriter::initialize_audio(int sample_rate) { + assert(this->ofmt_ctx->oformat->audio_codec != AV_CODEC_ID_NONE); // check output format supports audio streams + const AVCodec *audio_avcodec = avcodec_find_encoder(AV_CODEC_ID_AAC); + assert(audio_avcodec); + this->audio_codec_ctx = avcodec_alloc_context3(audio_avcodec); + assert(this->audio_codec_ctx); + this->audio_codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP; + this->audio_codec_ctx->sample_rate = sample_rate; + #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 28, 100) // FFmpeg 5.1+ + av_channel_layout_default(&this->audio_codec_ctx->ch_layout, 1); + #else + this->audio_codec_ctx->channel_layout = AV_CH_LAYOUT_MONO; + #endif + this->audio_codec_ctx->bit_rate = 32000; + this->audio_codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + this->audio_codec_ctx->time_base = (AVRational){1, audio_codec_ctx->sample_rate}; + int err = avcodec_open2(this->audio_codec_ctx, audio_avcodec, NULL); + assert(err >= 0); + av_log_set_level(AV_LOG_WARNING); // hide "QAvg" info msgs at the end of every segment + + this->audio_stream = avformat_new_stream(this->ofmt_ctx, NULL); + assert(this->audio_stream); + err = avcodec_parameters_from_context(this->audio_stream->codecpar, this->audio_codec_ctx); + assert(err >= 0); + + this->audio_frame = av_frame_alloc(); + assert(this->audio_frame); + this->audio_frame->format = this->audio_codec_ctx->sample_fmt; + #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 28, 100) // FFmpeg 5.1+ + av_channel_layout_copy(&this->audio_frame->ch_layout, &this->audio_codec_ctx->ch_layout); + #else + this->audio_frame->channel_layout = this->audio_codec_ctx->channel_layout; + #endif + this->audio_frame->sample_rate = this->audio_codec_ctx->sample_rate; + this->audio_frame->nb_samples = this->audio_codec_ctx->frame_size; + err = av_frame_get_buffer(this->audio_frame, 0); + assert(err >= 0); +} + void VideoWriter::write(uint8_t *data, int len, long long timestamp, bool codecconfig, bool keyframe) { if (of && data) { size_t written = util::safe_fwrite(data, 1, len, of); @@ -67,8 +106,10 @@ void VideoWriter::write(uint8_t *data, int len, long long timestamp, bool codecc } int err = avcodec_parameters_from_context(out_stream->codecpar, codec_ctx); assert(err >= 0); + // if there is an audio stream, it must be initialized before this point err = avformat_write_header(ofmt_ctx, NULL); assert(err >= 0); + header_written = true; } else { // input timestamps are in microseconds AVRational in_timebase = {1, 1000000}; @@ -77,6 +118,7 @@ void VideoWriter::write(uint8_t *data, int len, long long timestamp, bool codecc av_init_packet(&pkt); pkt.data = data; pkt.size = len; + pkt.stream_index = this->out_stream->index; enum AVRounding rnd = static_cast(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); pkt.pts = pkt.dts = av_rescale_q_rnd(timestamp, in_timebase, ofmt_ctx->streams[0]->time_base, rnd); @@ -95,11 +137,80 @@ void VideoWriter::write(uint8_t *data, int len, long long timestamp, bool codecc } } +void VideoWriter::write_audio(uint8_t *data, int len, long long timestamp, int sample_rate) { + if (!remuxing) return; + if (!audio_initialized) { + initialize_audio(sample_rate); + audio_initialized = true; + } + if (!audio_codec_ctx) return; + // sync logMonoTime of first audio packet with the timestampEof of first video packet + if (audio_pts == 0) { + audio_pts = (timestamp * audio_codec_ctx->sample_rate) / 1000000ULL; + } + + // convert s16le samples to fltp and add to buffer + const int16_t *raw_samples = reinterpret_cast(data); + int sample_count = len / sizeof(int16_t); + constexpr float normalizer = 1.0f / 32768.0f; + + const size_t max_buffer_size = sample_rate * 10; // 10 seconds + if (audio_buffer.size() + sample_count > max_buffer_size) { + size_t samples_to_drop = (audio_buffer.size() + sample_count) - max_buffer_size; + LOGE("Audio buffer overflow, dropping %zu oldest samples", samples_to_drop); + audio_buffer.erase(audio_buffer.begin(), audio_buffer.begin() + samples_to_drop); + audio_pts += samples_to_drop; + } + + // Add new samples to the buffer + const size_t original_size = audio_buffer.size(); + audio_buffer.resize(original_size + sample_count); + std::transform(raw_samples, raw_samples + sample_count, audio_buffer.begin() + original_size, + [](int16_t sample) { return sample * normalizer; }); + + if (!header_written) return; // header not written yet, process audio frame after header is written + while (audio_buffer.size() >= audio_codec_ctx->frame_size) { + audio_frame->pts = audio_pts; + float *f_samples = reinterpret_cast(audio_frame->data[0]); + std::copy(audio_buffer.begin(), audio_buffer.begin() + audio_codec_ctx->frame_size, f_samples); + audio_buffer.erase(audio_buffer.begin(), audio_buffer.begin() + audio_codec_ctx->frame_size); + encode_and_write_audio_frame(audio_frame); + } +} + +void VideoWriter::encode_and_write_audio_frame(AVFrame* frame) { + if (!remuxing || !audio_codec_ctx) return; + int send_result = avcodec_send_frame(audio_codec_ctx, frame); // encode frame + if (send_result >= 0) { + AVPacket *pkt = av_packet_alloc(); + while (avcodec_receive_packet(audio_codec_ctx, pkt) == 0) { + av_packet_rescale_ts(pkt, audio_codec_ctx->time_base, audio_stream->time_base); + pkt->stream_index = audio_stream->index; + + int err = av_interleaved_write_frame(ofmt_ctx, pkt); // write encoded frame + if (err < 0) { + LOGW("AUDIO: Write frame failed - error: %d", err); + } + av_packet_unref(pkt); + } + av_packet_free(&pkt); + } else { + LOGW("AUDIO: Failed to send audio frame to encoder: %d", send_result); + } + audio_pts += audio_codec_ctx->frame_size; +} + + VideoWriter::~VideoWriter() { if (this->remuxing) { + if (this->audio_codec_ctx) { + encode_and_write_audio_frame(NULL); // flush encoder + avcodec_free_context(&this->audio_codec_ctx); + } int err = av_write_trailer(this->ofmt_ctx); if (err != 0) LOGE("av_write_trailer failed %d", err); avcodec_free_context(&this->codec_ctx); + if (this->audio_frame) av_frame_free(&this->audio_frame); err = avio_closep(&this->ofmt_ctx->pb); if (err != 0) LOGE("avio_closep failed %d", err); avformat_free_context(this->ofmt_ctx); diff --git a/system/loggerd/video_writer.h b/system/loggerd/video_writer.h index 1aa758b42b..25e6484d58 100644 --- a/system/loggerd/video_writer.h +++ b/system/loggerd/video_writer.h @@ -1,6 +1,7 @@ #pragma once #include +#include extern "C" { #include @@ -13,13 +14,28 @@ class VideoWriter { public: VideoWriter(const char *path, const char *filename, bool remuxing, int width, int height, int fps, cereal::EncodeIndex::Type codec); void write(uint8_t *data, int len, long long timestamp, bool codecconfig, bool keyframe); + void write_audio(uint8_t *data, int len, long long timestamp, int sample_rate); + ~VideoWriter(); + private: + void initialize_audio(int sample_rate); + void encode_and_write_audio_frame(AVFrame* frame); + std::string vid_path, lock_path; FILE *of = nullptr; AVCodecContext *codec_ctx; AVFormatContext *ofmt_ctx; AVStream *out_stream; + + bool audio_initialized = false; + bool header_written = false; + AVStream *audio_stream = nullptr; + AVCodecContext *audio_codec_ctx = nullptr; + AVFrame *audio_frame = nullptr; + uint64_t audio_pts = 0; + std::deque audio_buffer; + bool remuxing; }; From baaa502b553e26a684d2160dfdc8a07691b20bc5 Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Sat, 5 Jul 2025 11:33:35 -0700 Subject: [PATCH 22/74] move record mic toggle (#35622) move toggle --- selfdrive/ui/layouts/settings/toggles.py | 6 +++--- selfdrive/ui/qt/offroad/settings.cc | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index a5e383cc02..7374d0f2ae 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -75,15 +75,15 @@ class TogglesLayout(Widget): self._params.get_bool("RecordFront"), icon="monitoring.png", ), - toggle_item( - "Use Metric System", DESCRIPTIONS["IsMetric"], self._params.get_bool("IsMetric"), icon="monitoring.png" - ), toggle_item( "Record Microphone Audio", DESCRIPTIONS["RecordAudio"], self._params.get_bool("RecordAudio"), icon="microphone.png", ), + toggle_item( + "Use Metric System", DESCRIPTIONS["IsMetric"], self._params.get_bool("IsMetric"), icon="monitoring.png" + ), ] self._scroller = Scroller(items, line_separator=True, spacing=0) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 788672822a..e242aa0878 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -61,6 +61,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { "../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"), @@ -68,13 +75,6 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { "../assets/icons/metric.png", false, }, - { - "RecordAudio", - tr("Record 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, - }, }; From 9bdfd46b8ffcdb3f5cef3c7be0f21f3a2ac0c3a1 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 5 Jul 2025 20:55:27 +0200 Subject: [PATCH 23/74] ci: updating gh runner and adjust restore process (#1022) * chore(ci): parameterize and update GitHub runner version to 2.325.0 * ci: refactor GitHub runner service installation logic Improved service installation workflow by introducing service template creation and cleaning up redundant checks. Enhanced restoration logic with clearer system state assessment and simplified flow for better maintainability. --- release/ci/install_github_runner.sh | 44 ++++++++++++++--------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/release/ci/install_github_runner.sh b/release/ci/install_github_runner.sh index f6b1e217c5..9f11e4841c 100755 --- a/release/ci/install_github_runner.sh +++ b/release/ci/install_github_runner.sh @@ -5,6 +5,7 @@ set -e DEFAULT_REPO_URL="https://github.com/sunnypilot" START_AT_BOOT=false RESTORE_MODE=false +RUNNER_VERSION="2.325.0" # Parse command line arguments while [[ $# -gt 0 ]]; do @@ -108,9 +109,9 @@ setup_system_configs() { install_runner() { echo "Downloading and setting up runner..." cd "$RUNNER_DIR" - curl -o actions-runner-linux-arm64-2.322.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.322.0/actions-runner-linux-arm64-2.322.0.tar.gz - sudo -u ${RUNNER_USER} tar -xzf ./actions-runner-linux-arm64-2.322.0.tar.gz - sudo rm ./actions-runner-linux-arm64-2.322.0.tar.gz + curl -o actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz -L https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz + sudo -u ${RUNNER_USER} tar -xzf ./actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz + sudo rm ./actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz sudo chmod +x ./config.sh } @@ -149,25 +150,32 @@ EOL } install_service() { + local service_name + if [ -f "${RUNNER_DIR}/.service" ]; then + service_name=$(cat "${RUNNER_DIR}/.service") + else + service_name="actions.runner.sunnypilot.$(uname -n)" + fi + + create_service_template remount_rw + local service_path="/etc/systemd/system/${service_name}" echo "Installing systemd service..." + if [ -f "${service_path}" ]; then + echo "Service ${service_path} found in systemd, we will delete it" + sudo rm -f "${service_path}" + fi + cd "$RUNNER_DIR" sudo ./svc.sh install $RUNNER_USER if [ "$START_AT_BOOT" = false ]; then - local service_name - if [ -f "${RUNNER_DIR}/.service" ]; then - service_name=$(cat "${RUNNER_DIR}/.service") - else - service_name="actions.runner.sunnypilot.$(uname -n)" - fi sudo systemctl disable "${service_name}" fi remount_ro } check_restore_prerequisites() { - local needs_restore=false local can_restore=false local service_name="" @@ -189,25 +197,16 @@ check_restore_prerequisites() { exit 1 fi - # Then check if restoration is needed (if either service or user is missing) - if ! systemctl list-unit-files "${service_name}" &>/dev/null; then - echo "Service ${service_name} not found in systemd" - needs_restore=true - fi - if ! id "${RUNNER_USER}" &>/dev/null; then echo "User ${RUNNER_USER} does not exist" - needs_restore=true fi # Only proceed if we can restore AND need to restore - if [ "$can_restore" = true ] && [ "$needs_restore" = true ]; then - echo "Restoration is needed and possible" + if [ "$can_restore" = true ]; then + echo "Restoration is possible" return 0 else - if [ "$needs_restore" = false ]; then - echo "System is already properly configured (user and service exist)" - fi + echo "No restoration possible" exit 0 fi } @@ -226,7 +225,6 @@ perform_install() { setup_system_configs install_runner set_directory_permissions - create_service_template configure_runner install_service echo "Installation completed successfully" From 00eb0e983dc149759d742266a8a9f1b2ae431e25 Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Sat, 5 Jul 2025 12:27:16 -0700 Subject: [PATCH 24/74] DEC: Kalman and the Magic Mode Switcheroo (#1026) * dec with kalmanfilter * updat pytest * Update sunnypilot/selfdrive/controls/lib/dec/constants.py Co-authored-by: DevTekVE --------- Co-authored-by: DevTekVE --- .../selfdrive/controls/lib/dec/constants.py | 32 +- sunnypilot/selfdrive/controls/lib/dec/dec.py | 568 +++++++++--------- .../dec/tests/pytest_dynamic_controller.py | 300 +++------ 3 files changed, 368 insertions(+), 532 deletions(-) diff --git a/sunnypilot/selfdrive/controls/lib/dec/constants.py b/sunnypilot/selfdrive/controls/lib/dec/constants.py index 48f4203e93..4586afbc9f 100644 --- a/sunnypilot/selfdrive/controls/lib/dec/constants.py +++ b/sunnypilot/selfdrive/controls/lib/dec/constants.py @@ -1,25 +1,17 @@ class WMACConstants: - LEAD_WINDOW_SIZE = 5 - LEAD_PROB = 0.5 + # Lead detection parameters + LEAD_WINDOW_SIZE = 6 # Stable detection window + LEAD_PROB = 0.45 # Balanced threshold for lead detection - SLOW_DOWN_WINDOW_SIZE = 5 - SLOW_DOWN_PROB = 0.6 + # Slow down detection parameters + SLOW_DOWN_WINDOW_SIZE = 5 # Responsive but stable + SLOW_DOWN_PROB = 0.3 # Balanced threshold for slow down scenarios + # Optimized slow down distance curve - smooth and progressive SLOW_DOWN_BP = [0., 10., 20., 30., 40., 50., 55., 60.] - SLOW_DOWN_DIST = [25., 38., 55., 75., 95., 115., 130., 150.] + SLOW_DOWN_DIST = [32., 46., 64., 86., 108., 130., 145., 165.] - SLOWNESS_WINDOW_SIZE = 12 - SLOWNESS_PROB = 0.5 - SLOWNESS_CRUISE_OFFSET = 1.05 - - DANGEROUS_TTC_WINDOW_SIZE = 3 - DANGEROUS_TTC = 2.3 - - MPC_FCW_WINDOW_SIZE = 10 - MPC_FCW_PROB = 0.5 - - -class SNG_State: - off = 0 - stopped = 1 - going = 2 + # Slowness detection parameters + SLOWNESS_WINDOW_SIZE = 10 # Stable slowness detection + SLOWNESS_PROB = 0.55 # Clear threshold for slowness + SLOWNESS_CRUISE_OFFSET = 1.025 # Conservative cruise speed offset diff --git a/sunnypilot/selfdrive/controls/lib/dec/dec.py b/sunnypilot/selfdrive/controls/lib/dec/dec.py index 812e5cc15f..46cad1b048 100644 --- a/sunnypilot/selfdrive/controls/lib/dec/dec.py +++ b/sunnypilot/selfdrive/controls/lib/dec/dec.py @@ -1,88 +1,133 @@ -# The MIT License -# -# Copyright (c) 2019-, Rick Lan, dragonpilot community, and a number of other of contributors. -# -# 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. -# -# Version = 2025-1-18 +""" +Copyright (c) 2021-, rav4kumar, Haibin Wen, sunnypilot, and a number of other contributors. -import numpy as np +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. +""" +# Version = 2025-6-30 from cereal import messaging from opendbc.car import structs from numpy import interp from openpilot.common.params import Params from openpilot.common.realtime import DT_MDL -from openpilot.sunnypilot.selfdrive.controls.lib.dec.constants import WMACConstants, SNG_State +from openpilot.sunnypilot.selfdrive.controls.lib.dec.constants import WMACConstants +from typing import Literal # d-e2e, from modeldata.h TRAJECTORY_SIZE = 33 +SET_MODE_TIMEOUT = 15 -HIGHWAY_CRUISE_KPH = 70 - -STOP_AND_GO_FRAME = 60 - -SET_MODE_TIMEOUT = 10 - -V_ACC_MIN = 9.72 +# Define the valid mode types +ModeType = Literal['acc', 'blended'] -class GenericMovingAverageCalculator: - def __init__(self, window_size): - self.window_size = window_size - self.data = [] - self.total = 0 +class SmoothKalmanFilter: + """Enhanced Kalman filter with smoothing for stable decision making.""" - def add_data(self, value: float) -> None: - if len(self.data) == self.window_size: - self.total -= self.data.pop(0) - self.data.append(value) - self.total += value + def __init__(self, initial_value=0, measurement_noise=0.1, process_noise=0.01, + alpha=1.0, smoothing_factor=0.85): + self.x = initial_value + self.P = 1.0 + self.R = measurement_noise + self.Q = process_noise + self.alpha = alpha + self.smoothing_factor = smoothing_factor + self.initialized = False + self.history = [] + self.max_history = 10 + self.confidence = 0.0 - def get_moving_average(self) -> float | None: - return None if len(self.data) == 0 else self.total / len(self.data) + def add_data(self, measurement): + if len(self.history) >= self.max_history: + self.history.pop(0) + self.history.append(measurement) - def reset_data(self) -> None: - self.data = [] - self.total = 0 + if not self.initialized: + self.x = measurement + self.initialized = True + self.confidence = 0.1 + return + + self.P = self.alpha * self.P + self.Q + + K = self.P / (self.P + self.R) + effective_K = K * (1.0 - self.smoothing_factor) + self.smoothing_factor * 0.1 + + innovation = measurement - self.x + self.x = self.x + effective_K * innovation + self.P = (1 - effective_K) * self.P + + if abs(innovation) < 0.1: + self.confidence = min(1.0, self.confidence + 0.05) + else: + self.confidence = max(0.1, self.confidence - 0.02) + + def get_value(self): + return self.x if self.initialized else None + + def get_confidence(self): + return self.confidence + + def reset_data(self): + self.initialized = False + self.history = [] + self.confidence = 0.0 -class WeightedMovingAverageCalculator: - def __init__(self, window_size): - self.window_size = window_size - self.data = [] - self.weights = np.linspace(1, 3, window_size) # Linear weights, adjust as needed +class ModeTransitionManager: + """Manages smooth transitions between driving modes with hysteresis.""" - def add_data(self, value: float) -> None: - if len(self.data) == self.window_size: - self.data.pop(0) - self.data.append(value) + def __init__(self): + self.current_mode: ModeType = 'acc' + self.mode_confidence = {'acc': 1.0, 'blended': 0.0} + self.transition_timeout = 0 + self.min_mode_duration = 10 + self.mode_duration = 0 + self.emergency_override = False - def get_weighted_average(self) -> float | None: - if len(self.data) == 0: - return None - weighted_sum: float = float(np.dot(self.data, self.weights[-len(self.data):])) - weight_total: float = float(np.sum(self.weights[-len(self.data):])) - return weighted_sum / weight_total + def request_mode(self, mode: ModeType, confidence: float = 1.0, emergency: bool = False): + # Emergency override for critical situations (stops, collisions) + if emergency: + self.emergency_override = True + self.current_mode = mode + self.transition_timeout = SET_MODE_TIMEOUT + self.mode_duration = 0 + return - def reset_data(self) -> None: - self.data = [] + self.mode_confidence[mode] = min(1.0, self.mode_confidence[mode] + 0.1 * confidence) + for m in self.mode_confidence: + if m != mode: + self.mode_confidence[m] = max(0.0, self.mode_confidence[m] - 0.05) + + # Require minimum duration in current mode (unless emergency) + if self.mode_duration < self.min_mode_duration and not self.emergency_override: + return + + # Hysteresis: higher threshold for mode changes + confidence_threshold = 0.6 if mode != self.current_mode else 0.3 # Lower threshold for faster response + + if self.mode_confidence[mode] > confidence_threshold: + if mode != self.current_mode and self.transition_timeout == 0: + self.transition_timeout = SET_MODE_TIMEOUT + self.current_mode = mode + self.mode_duration = 0 + + def update(self): + if self.transition_timeout > 0: + self.transition_timeout -= 1 + self.mode_duration += 1 + + # Reset emergency override after some time + if self.emergency_override and self.mode_duration > 20: + self.emergency_override = False + + # Gradual confidence decay + for mode in self.mode_confidence: + self.mode_confidence[mode] *= 0.98 + + def get_mode(self) -> ModeType: + return self.current_mode class DynamicExperimentalController: @@ -92,51 +137,59 @@ class DynamicExperimentalController: self._params = params or Params() self._enabled: bool = self._params.get_bool("DynamicExperimentalControl") self._active: bool = False - self._mode: str = 'acc' self._frame: int = 0 + self._urgency = 0.0 - # Use weighted moving average for filtering leads - self._lead_gmac = WeightedMovingAverageCalculator(window_size=WMACConstants.LEAD_WINDOW_SIZE) + self._mode_manager = ModeTransitionManager() + + # Smooth filters for stable decision making with faster response for critical scenarios + self._lead_filter = SmoothKalmanFilter( + measurement_noise=0.15, + process_noise=0.05, + alpha=1.02, + smoothing_factor=0.8 + ) + + self._slow_down_filter = SmoothKalmanFilter( + measurement_noise=0.1, + process_noise=0.1, + alpha=1.05, + smoothing_factor=0.7 + ) + + self._slowness_filter = SmoothKalmanFilter( + measurement_noise=0.1, + process_noise=0.06, + alpha=1.015, + smoothing_factor=0.92 + ) + + self._mpc_fcw_filter = SmoothKalmanFilter( + measurement_noise=0.2, + process_noise=0.1, + alpha=1.1, + smoothing_factor=0.5 + ) self._has_lead_filtered = False - self._has_lead_filtered_prev = False - - self._slow_down_gmac = WeightedMovingAverageCalculator(window_size=WMACConstants.SLOW_DOWN_WINDOW_SIZE) - self._has_slow_down: bool = False - self._slow_down_confidence: float = 0.0 - - self._has_blinkers = False - - self._slowness_gmac = WeightedMovingAverageCalculator(window_size=WMACConstants.SLOWNESS_WINDOW_SIZE) - self._has_slowness: bool = False - - self._has_nav_instruction = False - - self._dangerous_ttc_gmac = WeightedMovingAverageCalculator(window_size=WMACConstants.DANGEROUS_TTC_WINDOW_SIZE) - self._has_dangerous_ttc: bool = False - - self._v_ego_kph = 0. - self._v_cruise_kph = 0. - - self._has_lead = False - + self._has_slow_down = False + self._has_slowness = False + self._has_mpc_fcw = False + self._v_ego_kph = 0.0 + self._v_cruise_kph = 0.0 self._has_standstill = False - self._has_standstill_prev = False - - self._sng_transit_frame = 0 - self._sng_state = SNG_State.off - - self._mpc_fcw_gmac = WeightedMovingAverageCalculator(window_size=WMACConstants.MPC_FCW_WINDOW_SIZE) - self._has_mpc_fcw: bool = False self._mpc_fcw_crash_cnt = 0 - - self._set_mode_timeout = 0 + self._standstill_count = 0 + # debug + self._endpoint_x = float('inf') + self._expected_distance = 0.0 + self._trajectory_valid = False def _read_params(self) -> None: if self._frame % int(1. / DT_MDL) == 0: self._enabled = self._params.get_bool("DynamicExperimentalControl") def mode(self) -> str: - return str(self._mode) + return self._mode_manager.get_mode() def enabled(self) -> bool: return self._enabled @@ -144,46 +197,9 @@ class DynamicExperimentalController: def active(self) -> bool: return self._active - @staticmethod - def _anomaly_detection(recent_data: list[float], threshold: float = 2.0, context_check: bool = True) -> bool: - """ - Basic anomaly detection using standard deviation. - """ - if len(recent_data) < 5: - return False - mean: float = float(np.mean(recent_data)) - std_dev: float = float(np.std(recent_data)) - anomaly: bool = bool(recent_data[-1] > mean + threshold * std_dev) - - # Context check to ensure repeated anomaly - if context_check: - return np.count_nonzero(np.array(recent_data) > mean + threshold * std_dev) > 1 - return anomaly - - def _adaptive_slowdown_threshold(self) -> float: - """ - Adapts the slow-down threshold based on vehicle speed and recent behavior. - """ - slowdown_scaling_factor: float = (1.0 + 0.03 * np.log(1 + len(self._slow_down_gmac.data))) - adaptive_threshold: float = float( - interp(self._v_ego_kph, WMACConstants.SLOW_DOWN_BP, WMACConstants.SLOW_DOWN_DIST) * slowdown_scaling_factor - ) - return adaptive_threshold - - def _smoothed_lead_detection(self, lead_prob: float, smoothing_factor: float = 0.2): - """ - Smoothing the lead detection to avoid erratic behavior. - """ - lead_filtering: float = (1 - smoothing_factor) * self._has_lead_filtered + smoothing_factor * lead_prob - return lead_filtering > WMACConstants.LEAD_PROB - - def _adaptive_lead_prob_threshold(self) -> float: - """ - Adapts lead probability threshold based on driving conditions. - """ - if self._v_ego_kph > HIGHWAY_CRUISE_KPH: - return float(WMACConstants.LEAD_PROB + 0.1) # Increase the threshold on highways - return float(WMACConstants.LEAD_PROB) + def set_mpc_fcw_crash_cnt(self) -> None: + """Set MPC FCW crash count""" + self._mpc_fcw_crash_cnt = self._mpc.crash_cnt def _update_calculations(self, sm: messaging.SubMaster) -> None: car_state = sm['carState'] @@ -192,186 +208,168 @@ class DynamicExperimentalController: self._v_ego_kph = car_state.vEgo * 3.6 self._v_cruise_kph = car_state.vCruise - self._has_lead = lead_one.status self._has_standstill = car_state.standstill - # fcw detection - self._mpc_fcw_gmac.add_data(self._mpc_fcw_crash_cnt > 0) - if _mpc_fcw_weighted_average := self._mpc_fcw_gmac.get_weighted_average(): - self._has_mpc_fcw = _mpc_fcw_weighted_average > WMACConstants.MPC_FCW_PROB - else: - self._has_mpc_fcw = False - - # nav enable detection - # self._has_nav_instruction = md.navEnabledDEPRECATED and maneuver_distance / max(car_state.vEgo, 1) < 13 - - # lead detection with smoothing - self._lead_gmac.add_data(lead_one.status) - self._has_lead_filtered = (self._lead_gmac.get_weighted_average() or -1.) > WMACConstants.LEAD_PROB - #lead_prob = self._lead_gmac.get_weighted_average() or 0 - #self._has_lead_filtered = self._smoothed_lead_detection(lead_prob) - - # adaptive slow down detection - adaptive_threshold = self._adaptive_slowdown_threshold() - slow_down_trigger = len(md.orientation.x) == len(md.position.x) == TRAJECTORY_SIZE and md.position.x[TRAJECTORY_SIZE - 1] < adaptive_threshold - self._slow_down_gmac.add_data(slow_down_trigger) - if _has_slow_down_weighted_average := self._slow_down_gmac.get_weighted_average(): - self._has_slow_down = _has_slow_down_weighted_average > WMACConstants.SLOW_DOWN_PROB - self._slow_down_confidence = _has_slow_down_weighted_average # Store confidence level - else: - self._has_slow_down = False - self._slow_down_confidence = 0.0 # No confidence if no slowdown - - # anomaly detection for slow down events - if self._anomaly_detection(self._slow_down_gmac.data): - self._slow_down_confidence *= 0.85 # Reduce confidence - self._has_slow_down = self._slow_down_confidence > WMACConstants.SLOW_DOWN_PROB - - # blinker detection - self._has_blinkers = car_state.leftBlinker or car_state.rightBlinker - - # sng detection + # standstill detection if self._has_standstill: - self._sng_state = SNG_State.stopped - self._sng_transit_frame = 0 + self._standstill_count = min(20, self._standstill_count + 1) else: - if self._sng_transit_frame == 0: - if self._sng_state == SNG_State.stopped: - self._sng_state = SNG_State.going - self._sng_transit_frame = STOP_AND_GO_FRAME - elif self._sng_state == SNG_State.going: - self._sng_state = SNG_State.off - elif self._sng_transit_frame > 0: - self._sng_transit_frame -= 1 + self._standstill_count = max(0, self._standstill_count - 1) - # slowness detection - if not self._has_standstill: - self._slowness_gmac.add_data(self._v_ego_kph <= (self._v_cruise_kph * WMACConstants.SLOWNESS_CRUISE_OFFSET)) - if _slowness_weighted_average := self._slowness_gmac.get_weighted_average(): - self._has_slowness = _slowness_weighted_average > WMACConstants.SLOWNESS_PROB - else: - self._has_slowness = False + # Lead detection + self._lead_filter.add_data(float(lead_one.status)) + lead_value = self._lead_filter.get_value() or 0.0 + self._has_lead_filtered = lead_value > WMACConstants.LEAD_PROB - # dangerous TTC detection - if not self._has_lead_filtered and self._has_lead_filtered_prev: - self._dangerous_ttc_gmac.reset_data() - self._has_dangerous_ttc = False + # MPC FCW detection + fcw_filtered_value = self._mpc_fcw_filter.get_value() or 0.0 + self._mpc_fcw_filter.add_data(float(self._mpc_fcw_crash_cnt > 0)) + self._has_mpc_fcw = fcw_filtered_value > 0.5 - if self._has_lead and car_state.vEgo >= 0.01: - self._dangerous_ttc_gmac.add_data(lead_one.dRel / car_state.vEgo) + # Slow down detection + self._calculate_slow_down(md) - if _dangerous_ttc_weighted_average := self._dangerous_ttc_gmac.get_weighted_average(): - self._has_dangerous_ttc = _dangerous_ttc_weighted_average <= WMACConstants.DANGEROUS_TTC - else: - self._has_dangerous_ttc = False + # Slowness detection + if not (self._standstill_count > 5) and not self._has_slow_down: + current_slowness = float(self._v_ego_kph <= (self._v_cruise_kph * WMACConstants.SLOWNESS_CRUISE_OFFSET)) + self._slowness_filter.add_data(current_slowness) + slowness_value = self._slowness_filter.get_value() or 0.0 - # keep prev values - self._has_standstill_prev = self._has_standstill - self._has_lead_filtered_prev = self._has_lead_filtered + # Hysteresis for slowness + threshold = WMACConstants.SLOWNESS_PROB * (0.8 if self._has_slowness else 1.1) + self._has_slowness = slowness_value > threshold + + def _calculate_slow_down(self, md): + """Calculate urgency based on trajectory endpoint vs expected distance.""" + + # Reset to safe defaults + urgency = 0.0 + self._endpoint_x = float('inf') + self._trajectory_valid = False + + #Require exact trajectory size + position_valid = len(md.position.x) == TRAJECTORY_SIZE + orientation_valid = len(md.orientation.x) == TRAJECTORY_SIZE + + if not (position_valid and orientation_valid): + # Invalid trajectory - this itself might indicate a stop scenario + # Apply moderate urgency for incomplete trajectories at speed + if self._v_ego_kph > 20.0: + urgency = 0.3 + + self._slow_down_filter.add_data(urgency) + urgency_filtered = self._slow_down_filter.get_value() or 0.0 + self._has_slow_down = urgency_filtered > WMACConstants.SLOW_DOWN_PROB + self._urgency = urgency_filtered + return + + # We have a valid full trajectory + self._trajectory_valid = True + + # Use the exact endpoint (33rd point, index 32) + endpoint_x = md.position.x[TRAJECTORY_SIZE - 1] + self._endpoint_x = endpoint_x + + # Get expected distance based on current speed using tuned constants + expected_distance = interp(self._v_ego_kph, + WMACConstants.SLOW_DOWN_BP, + WMACConstants.SLOW_DOWN_DIST) + self._expected_distance = expected_distance + + # Calculate urgency based on trajectory shortage + if endpoint_x < expected_distance: + shortage = expected_distance - endpoint_x + shortage_ratio = shortage / expected_distance + + # Base urgency on shortage ratio + urgency = min(1.0, shortage_ratio * 2.0) + + # Increase urgency for very short trajectories (imminent stops) + critical_distance = expected_distance * 0.3 + if endpoint_x < critical_distance: + urgency = min(1.0, urgency * 2.0) + + # Speed-based urgency adjustment + if self._v_ego_kph > 25.0: + speed_factor = 1.0 + (self._v_ego_kph - 25.0) / 80.0 + urgency = min(1.0, urgency * speed_factor) + + # Apply filtering but with less smoothing for stops + self._slow_down_filter.add_data(urgency) + urgency_filtered = self._slow_down_filter.get_value() or 0.0 + + # Update state with lower threshold for better stop detection + self._has_slow_down = urgency_filtered > (WMACConstants.SLOW_DOWN_PROB * 0.8) + self._urgency = urgency_filtered def _radarless_mode(self) -> None: - # when mpc fcw crash prob is high - # use blended to slow down quickly + """Radarless mode decision logic with emergency handling.""" + + # EMERGENCY: MPC FCW - immediate blended mode if self._has_mpc_fcw: - self._set_mode('blended') + self._mode_manager.request_mode('blended', confidence=1.0, emergency=True) return - # Nav enabled and distance to upcoming turning is 300 or below - # if self._has_nav_instruction: - # self._set_mode('blended') - # return - - # when blinker is on and speed is driving below V_ACC_MIN: blended - # we don't want it to switch mode at higher speed, blended may trigger hard brake - # if self._has_blinkers and self._v_ego_kph < V_ACC_MIN: - # self._set_mode('blended') - # return - - # when at highway cruise and SNG: blended - # ensuring blended mode is used because acc is bad at catching SNG lead car - # especially those who accel very fast and then brake very hard. - # if self._sng_state == SNG_State.going and self._v_cruise_kph >= V_ACC_MIN: - # self._set_mode('blended') - # return - - # when standstill: blended - # in case of lead car suddenly move away under traffic light, acc mode won't brake at traffic light. - if self._has_standstill: - self._set_mode('blended') + # Standstill: use blended + if self._standstill_count > 3: + self._mode_manager.request_mode('blended', confidence=0.9) return - # when detecting slow down scenario: blended - # e.g. traffic light, curve, stop sign etc. + # Slow down scenarios: emergency for high urgency, normal for lower urgency if self._has_slow_down: - self._set_mode('blended') + if self._urgency > 0.7: + # Emergency: immediate blended mode for high urgency stops + self._mode_manager.request_mode('blended', confidence=1.0, emergency=True) + else: + # Normal: blended with urgency-based confidence + confidence = min(1.0, self._urgency * 1.5) + self._mode_manager.request_mode('blended', confidence=confidence) return - # when detecting lead slow down: blended - # use blended for higher braking capability - if self._has_dangerous_ttc: - self._set_mode('blended') + # Driving slow: use ACC (but not if actively slowing down) + if self._has_slowness and not self._has_slow_down: + self._mode_manager.request_mode('acc', confidence=0.8) return - # car driving at speed lower than set speed: acc - if self._has_slowness: - self._set_mode('acc') - return - - self._set_mode('acc') + # Default: ACC + self._mode_manager.request_mode('acc', confidence=0.7) def _radar_mode(self) -> None: - # when mpc fcw crash prob is high - # use blended to slow down quickly + """Radar mode with emergency handling.""" + + # EMERGENCY: MPC FCW - immediate blended mode if self._has_mpc_fcw: - self._set_mode('blended') + self._mode_manager.request_mode('blended', confidence=1.0, emergency=True) return - # If there is a filtered lead, the vehicle is not in standstill, and the lead vehicle's yRel meets the condition, - if self._has_lead_filtered and not self._has_standstill: - self._set_mode('acc') + # If lead detected and not in standstill: always use ACC + if self._has_lead_filtered and not (self._standstill_count > 3): + self._mode_manager.request_mode('acc', confidence=1.0) return - # when blinker is on and speed is driving below V_ACC_MIN: blended - # we don't want it to switch mode at higher speed, blended may trigger hard brake - # if self._has_blinkers and self._v_ego_kph < V_ACC_MIN: - # self._set_mode('blended') - # return - - # when standstill: blended - # in case of lead car suddenly move away under traffic light, acc mode won't brake at traffic light. - if self._has_standstill: - self._set_mode('blended') - return - - # when detecting slow down scenario: blended - # e.g. traffic light, curve, stop sign etc. + # Slow down scenarios: emergency for high urgency, normal for lower urgency if self._has_slow_down: - self._set_mode('blended') + if self._urgency > 0.7: + # Emergency: immediate blended mode for high urgency stops + self._mode_manager.request_mode('blended', confidence=1.0, emergency=True) + else: + # Normal: blended with urgency-based confidence + confidence = min(1.0, self._urgency * 1.3) + self._mode_manager.request_mode('blended', confidence=confidence) return - # car driving at speed lower than set speed: acc - if self._has_slowness: - self._set_mode('acc') + # Standstill: use blended + if self._standstill_count > 3: + self._mode_manager.request_mode('blended', confidence=0.9) return - # Nav enabled and distance to upcoming turning is 300 or below - # if self._has_nav_instruction: - # self._set_mode('blended') - # return + # Driving slow: use ACC (but not if actively slowing down) + if self._has_slowness and not self._has_slow_down: + self._mode_manager.request_mode('acc', confidence=0.8) + return - self._set_mode('acc') - - def set_mpc_fcw_crash_cnt(self) -> None: - self._mpc_fcw_crash_cnt = self._mpc.crash_cnt - - def _set_mode(self, mode: str) -> None: - if self._set_mode_timeout == 0: - self._mode = mode - if mode == 'blended': - self._set_mode_timeout = SET_MODE_TIMEOUT - - if self._set_mode_timeout > 0: - self._set_mode_timeout -= 1 + # Default: ACC + self._mode_manager.request_mode('acc', confidence=0.7) def update(self, sm: messaging.SubMaster) -> None: self._read_params() @@ -385,6 +383,6 @@ class DynamicExperimentalController: else: self._radar_mode() + self._mode_manager.update() self._active = sm['selfdriveState'].experimentalMode and self._enabled - self._frame += 1 diff --git a/sunnypilot/selfdrive/controls/lib/dec/tests/pytest_dynamic_controller.py b/sunnypilot/selfdrive/controls/lib/dec/tests/pytest_dynamic_controller.py index d15d88aefe..f9da39c03b 100644 --- a/sunnypilot/selfdrive/controls/lib/dec/tests/pytest_dynamic_controller.py +++ b/sunnypilot/selfdrive/controls/lib/dec/tests/pytest_dynamic_controller.py @@ -1,248 +1,94 @@ import pytest -import numpy as np -from openpilot.common.params import Params -from openpilot.sunnypilot.selfdrive.controls.lib.dec.constants import WMACConstants, SNG_State -from openpilot.sunnypilot.selfdrive.controls.lib.dec.dec import DynamicExperimentalController, TRAJECTORY_SIZE, STOP_AND_GO_FRAME - -class MockInterp: - def __call__(self, x, xp, fp): - return np.interp(x, xp, fp) - -class MockCarState: - def __init__(self, v_ego=0., standstill=False, left_blinker=False, right_blinker=False): - self.vEgo = v_ego - self.standstill = standstill - self.leftBlinker = left_blinker - self.rightBlinker = right_blinker +from openpilot.sunnypilot.selfdrive.controls.lib.dec.dec import DynamicExperimentalController class MockLeadOne: - def __init__(self, status=False, d_rel=0): + def __init__(self, status=0.0): self.status = status - self.dRel = d_rel + +class MockRadarState: + def __init__(self, status=0.0): + self.leadOne = MockLeadOne(status=status) + +class MockCarState: + def __init__(self, vEgo=0.0, vCruise=0.0, standstill=False): + self.vEgo = vEgo + self.vCruise = vCruise + self.standstill = standstill class MockModelData: - def __init__(self, x_vals=None, positions=None): - self.orientation = type('Orientation', (), {'x': x_vals})() - self.position = type('Position', (), {'x': positions})() + def __init__(self, valid=True): + size = 33 if valid else 10 # incomplete if invalid + self.position = type("Pos", (), {"x": [0.0] * size})() + self.orientation = type("Ori", (), {"x": [0.0] * size})() -class MockControlState: - def __init__(self, v_cruise=0): - self.vCruise = v_cruise +class MockSelfDriveState: + def __init__(self, experimentalMode=False): + self.experimentalMode = experimentalMode + +class MockParams: + def get_bool(self, name): + return True @pytest.fixture -def interp(monkeypatch): - mock_interp = MockInterp() - monkeypatch.setattr('numpy.interp', mock_interp) - return mock_interp +def default_sm(): + sm = { + 'carState': MockCarState(vEgo=10.0, vCruise=20.0), + 'radarState': MockRadarState(status=1.0), + 'modelV2': MockModelData(valid=True), + 'selfdriveState': MockSelfDriveState(experimentalMode=True), + } + return sm @pytest.fixture -def controller(interp): - params = Params() - params.put_bool("DynamicExperimentalControl", True) - return DynamicExperimentalController() +def mock_cp(): + class CP: + radarUnavailable = False + return CP() -def test_initial_state(controller): - """Test initial state of the controller""" - assert controller._mode == 'acc' - assert not controller._has_lead - assert not controller._has_standstill - assert controller._sng_state == SNG_State.off - assert not controller._has_lead_filtered - assert not controller._has_slow_down - assert not controller._has_dangerous_ttc - assert not controller._has_mpc_fcw +@pytest.fixture +def mock_mpc(): + class MPC: + crash_cnt = 0 + return MPC() -@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"]) -def test_standstill_detection(controller, has_radar): - """Test standstill detection and state transitions""" - car_state = MockCarState(standstill=True) - lead_one = MockLeadOne() - md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[150] * TRAJECTORY_SIZE) - controls_state = MockControlState() +# Fake Kalman Filter that always returns a given value +class FakeKalman: + def __init__(self, value=1.0): + self.value = value + def add_data(self, v): pass + def get_value(self): return self.value + def get_confidence(self): return 1.0 + def reset_data(self): pass - # Test transition to standstill - controller.update(not has_radar, car_state, lead_one, md, controls_state) - assert controller._sng_state == SNG_State.stopped - assert controller.get_mpc_mode() == 'blended' +def test_initial_mode_is_acc(mock_cp, mock_mpc): + controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams()) + assert controller.mode() == "acc" - # Test transition from standstill to moving - car_state.standstill = False - controller.update(not has_radar, car_state, lead_one, md, controls_state) - assert controller._sng_state == SNG_State.going +def test_standstill_triggers_blended(mock_cp, mock_mpc, default_sm): + controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams()) + default_sm['carState'].standstill = True + for _ in range(10): + controller.update(default_sm) + assert controller.mode() == "blended" - # Test complete transition to normal driving - for _ in range(STOP_AND_GO_FRAME + 1): - controller.update(not has_radar, car_state, lead_one, md, controls_state) - assert controller._sng_state == SNG_State.off +def test_emergency_blended_on_fcw(mock_cp, mock_mpc, default_sm): + controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams()) + mock_mpc.crash_cnt = 1 # simulate FCW + for _ in range(2): + controller.update(default_sm) + assert controller.mode() == "blended" -@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"]) -def test_lead_detection(controller, has_radar): - """Test lead vehicle detection and filtering""" - car_state = MockCarState(v_ego=20) # 72 kph - lead_one = MockLeadOne(status=True, d_rel=50) # Safe distance - md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[150] * TRAJECTORY_SIZE) - controls_state = MockControlState(v_cruise=72) +def test_radarless_slowdown_triggers_blended(mock_cp, mock_mpc, default_sm): + mock_cp.radarUnavailable = True + controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams()) - # Let moving average stabilize - for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1): - controller.update(not has_radar, car_state, lead_one, md, controls_state) + # Force conditions to simulate slowdown + controller._slow_down_filter = FakeKalman(value=1.0) # Ensure urgency triggers slowdown + controller._v_ego_kph = 35.0 + default_sm['modelV2'] = MockModelData(valid=False) # Incomplete trajectory - assert controller._has_lead_filtered - expected_mode = 'acc' if has_radar else 'blended' - assert controller.get_mpc_mode() == expected_mode + for _ in range(3): + controller.update(default_sm) - # Test lead loss detection - lead_one.status = False - for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1): - controller.update(not has_radar, car_state, lead_one, md, controls_state) - - assert not controller._has_lead_filtered - -@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"]) -def test_slow_down_detection(controller, has_radar): - """Test slow down detection based on trajectory""" - car_state = MockCarState(v_ego=10/3.6) # 10 kph - lead_one = MockLeadOne() - x_vals = [0] * TRAJECTORY_SIZE - positions = [20] * TRAJECTORY_SIZE # Position within slow down threshold - md = MockModelData(x_vals=x_vals, positions=positions) - controls_state = MockControlState(v_cruise=30) - - # Test slow down detection - for _ in range(WMACConstants.SLOW_DOWN_WINDOW_SIZE + 1): - controller.update(not has_radar, car_state, lead_one, md, controls_state) - - assert controller._has_slow_down - assert controller.get_mpc_mode() == 'blended' - - # Test slow down recovery - positions = [200] * TRAJECTORY_SIZE # Position outside slow down threshold - md = MockModelData(x_vals=x_vals, positions=positions) - for _ in range(WMACConstants.SLOW_DOWN_WINDOW_SIZE + 1): - controller.update(not has_radar, car_state, lead_one, md, controls_state) - - assert not controller._has_slow_down - -@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"]) -def test_dangerous_ttc_detection(controller, has_radar): - """Test Time-To-Collision detection and handling""" - car_state = MockCarState(v_ego=10) # 36 kph - lead_one = MockLeadOne(status=True) - md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[150] * TRAJECTORY_SIZE) - controls_state = MockControlState(v_cruise=36) - - # First establish normal conditions with lead - lead_one.dRel = 100 # Safe distance - for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1): # First establish lead detection - controller.update(not has_radar, car_state, lead_one, md, controls_state) - - assert controller._has_lead_filtered # Verify lead is detected - - # Now test dangerous TTC detection - lead_one.dRel = 10 # 10m distance - should trigger dangerous TTC - # TTC = dRel/vEgo = 10/10 = 1s (which is less than DANGEROUS_TTC = 2.3s) - - # Need to update multiple times to allow the weighted average to stabilize - for _ in range(WMACConstants.DANGEROUS_TTC_WINDOW_SIZE * 2): - controller.update(not has_radar, car_state, lead_one, md, controls_state) - - assert controller._has_dangerous_ttc, "TTC of 1s should be considered dangerous" - expected_mode = 'acc' if has_radar else 'blended' - assert controller.get_mpc_mode() == expected_mode, f"Should be in [{expected_mode}] mode with dangerous TTC" - -@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"]) -def test_mode_transitions(controller, has_radar): - """Test comprehensive mode transitions under different conditions""" - # Initialize with normal driving conditions - car_state = MockCarState(v_ego=25) # 90 kph - lead_one = MockLeadOne(status=False) - md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[200] * TRAJECTORY_SIZE) - controls_state = MockControlState(v_cruise=100) - - def stabilize_filters(): - """Helper to let all moving averages stabilize""" - for _ in range(max(WMACConstants.LEAD_WINDOW_SIZE, WMACConstants.SLOW_DOWN_WINDOW_SIZE, - WMACConstants.DANGEROUS_TTC_WINDOW_SIZE, WMACConstants.MPC_FCW_WINDOW_SIZE) + 1): - controller.update(not has_radar, car_state, lead_one, md, controls_state) - - # Test 1: Normal driving -> ACC mode - stabilize_filters() - assert controller.get_mpc_mode() == 'acc', "Should be in ACC mode under normal driving conditions" - - # Test 2: Standstill -> Blended mode - car_state.standstill = True - controller.update(not has_radar, car_state, lead_one, md, controls_state) - assert controller.get_mpc_mode() == 'blended', "Should be in blended mode during standstill" - - # Test 3: Lead car appears -> ACC mode - car_state = MockCarState(v_ego=20) # Reset car state - lead_one.status = True - lead_one.dRel = 50 # Safe distance - stabilize_filters() - assert not controller._has_dangerous_ttc, "Should not have dangerous TTC" - assert controller.get_mpc_mode() == 'acc', "Should be in ACC mode with safe lead distance" - - # Test 4: Dangerous TTC -> Blended mode - car_state = MockCarState(v_ego=20) # 72 kph - lead_one.status = True - lead_one.dRel = 50 # First establish normal lead detection - - # First establish lead detection - for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1): - controller.update(not has_radar, car_state, lead_one, md, controls_state) - - assert controller._has_lead_filtered # Verify lead is detected - - # Now create dangerous TTC condition - lead_one.dRel = 20 # This creates a TTC of 1s, well below DANGEROUS_TTC - - for _ in range(WMACConstants.DANGEROUS_TTC_WINDOW_SIZE * 2): - controller.update(not has_radar, car_state, lead_one, md, controls_state) - - assert controller._has_dangerous_ttc, "Should detect dangerous TTC condition" - expected_mode = 'acc' if has_radar else 'blended' - assert controller.get_mpc_mode() == expected_mode, f"Should be in [{expected_mode}] mode with dangerous TTC" - -@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"]) -def test_mpc_fcw_handling(controller, has_radar): - """Test MPC FCW crash count handling and mode transitions""" - car_state = MockCarState(v_ego=20) - lead_one = MockLeadOne() - md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[150] * TRAJECTORY_SIZE) - controls_state = MockControlState(v_cruise=72) - - # Test FCW activation - controller.set_mpc_fcw_crash_cnt(5) - for _ in range(WMACConstants.MPC_FCW_WINDOW_SIZE + 1): - controller.update(not has_radar, car_state, lead_one, md, controls_state) - - assert controller._has_mpc_fcw - assert controller.get_mpc_mode() == 'blended' - - # Test FCW recovery - controller.set_mpc_fcw_crash_cnt(0) - for _ in range(WMACConstants.MPC_FCW_WINDOW_SIZE + 1): - controller.update(not has_radar, car_state, lead_one, md, controls_state) - - assert not controller._has_mpc_fcw - -def test_radar_unavailable_handling(controller): - """Test behavior transitions between radar available and unavailable states""" - car_state = MockCarState(v_ego=27.78) # 100 kph - lead_one = MockLeadOne(status=True, d_rel=50) - md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[150] * TRAJECTORY_SIZE) - controls_state = MockControlState(v_cruise=100) - - # Test with radar available - for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1): - controller.update(False, car_state, lead_one, md, controls_state) - radar_mode = controller.get_mpc_mode() - - # Test with radar unavailable - for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1): - controller.update(True, car_state, lead_one, md, controls_state) - radarless_mode = controller.get_mpc_mode() - - assert radar_mode is not None - assert radarless_mode is not None + assert controller.mode() == "blended" From a8ec08e5bbc2be0a320fdb6853957588dae67048 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 5 Jul 2025 13:52:57 -0700 Subject: [PATCH 25/74] modeld: remove redundant process config (#35639) --- selfdrive/modeld/dmonitoringmodeld.py | 11 +---------- selfdrive/modeld/modeld.py | 10 +--------- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 2a3df3f652..7ab31ed2b4 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -14,7 +14,6 @@ import pickle import ctypes import numpy as np from pathlib import Path -from setproctitle import setproctitle from cereal import messaging from cereal.messaging import PubMaster, SubMaster @@ -25,7 +24,6 @@ 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.system import sentry MODEL_WIDTH, MODEL_HEIGHT = DM_INPUT_SIZE CALIB_LEN = 3 @@ -133,12 +131,8 @@ def get_driverstate_packet(model_output: np.ndarray, frame_id: int, location_ts: def main(): - setproctitle(PROCESS_NAME) config_realtime_process(7, 5) - sentry.set_tag("daemon", PROCESS_NAME) - cloudlog.bind(daemon=PROCESS_NAME) - cl_context = CLContext() model = ModelState(cl_context) cloudlog.warning("models loaded, dmonitoringmodeld starting") @@ -180,7 +174,4 @@ if __name__ == "__main__": try: main() except KeyboardInterrupt: - cloudlog.warning(f"child {PROCESS_NAME} got SIGINT") - except Exception: - sentry.capture_exception() - raise + cloudlog.warning("got SIGINT") diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 365cfd33ce..298abcd38a 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -19,7 +19,6 @@ import numpy as np import cereal.messaging as messaging from cereal import car, log from pathlib import Path -from setproctitle import setproctitle from cereal.messaging import PubMaster, SubMaster from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf from opendbc.car.car_helpers import get_demo_car_params @@ -29,7 +28,6 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.transformations.camera import DEVICE_CAMERAS from openpilot.common.transformations.model import get_warp_matrix -from openpilot.system import sentry from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan from openpilot.selfdrive.modeld.parse_model_outputs import Parser @@ -190,9 +188,6 @@ class ModelState: def main(demo=False): cloudlog.warning("modeld init") - sentry.set_tag("daemon", PROCESS_NAME) - cloudlog.bind(daemon=PROCESS_NAME) - setproctitle(PROCESS_NAME) if not USBGPU: # USB GPU currently saturates a core so can't do this yet, # also need to move the aux USB interrupts for good timings @@ -378,7 +373,4 @@ if __name__ == "__main__": args = parser.parse_args() main(demo=args.demo) except KeyboardInterrupt: - cloudlog.warning(f"child {PROCESS_NAME} got SIGINT") - except Exception: - sentry.capture_exception() - raise + cloudlog.warning("got SIGINT") From d0bd8cc4a3abe6a93fce2a605fad0b2eb0c7f16c Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 5 Jul 2025 14:42:36 -0700 Subject: [PATCH 26/74] liveDelay: Add live delay toggle to vehicles using torqued (#1001) * Add live delay toggle to torqued.py and twilsonco NNLC * Set this in init * Clean up * Live delay toggle refactor * ModeldLagd -> LagdToggle * This is for lagd_toggle.py * Add to NNLC * Lagd toggle: Display current values on UI * Add break * LagdToggleDelay Live edit software_delay when livedelay is toggled `off` * Always show description * Add description as to why values don't update offroad --------- Co-authored-by: Kumar <36933347+rav4kumar@users.noreply.github.com> --- common/params_keys.h | 2 + selfdrive/controls/controlsd.py | 3 +- selfdrive/locationd/torqued.py | 8 ++- selfdrive/modeld/modeld.py | 7 ++- .../qt/offroad/settings/models_panel.cc | 42 +++++++++++++-- .../qt/offroad/settings/models_panel.h | 2 + selfdrive/ui/sunnypilot/qt/widgets/controls.h | 25 +++++++-- sunnypilot/livedelay/__init__.py | 0 sunnypilot/livedelay/lagd_toggle.py | 51 +++++++++++++++++++ sunnypilot/modeld/modeld.py | 4 +- sunnypilot/modeld_v2/modeld.py | 4 +- sunnypilot/models/modeld_lagd.py | 26 ---------- .../lib/latcontrol_torque_ext_base.py | 5 +- system/manager/manager.py | 1 + 14 files changed, 136 insertions(+), 44 deletions(-) create mode 100644 sunnypilot/livedelay/__init__.py create mode 100644 sunnypilot/livedelay/lagd_toggle.py delete mode 100644 sunnypilot/models/modeld_lagd.py diff --git a/common/params_keys.h b/common/params_keys.h index 02dd8fee85..00e1763e16 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -178,6 +178,8 @@ inline static std::unordered_map keys = { // model panel params {"LagdToggle", PERSISTENT | BACKUP}, + {"LagdToggleDesc", PERSISTENT}, + {"LagdToggledelay", PERSISTENT | BACKUP}, // mapd {"MapAdvisorySpeedLimit", CLEAR_ON_ONROAD_TRANSITION}, diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 04d79d3330..9ef985fcc4 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -93,7 +93,8 @@ class Controls(ControlsExt): torque_params.frictionCoefficientFiltered) self.LaC.extension.update_model_v2(self.sm['modelV2']) - self.LaC.extension.update_lateral_lag(self.sm['liveDelay'].lateralDelay) + calculated_lag = self.LaC.extension.lagd_torqued_main(self.CP, self.sm['liveDelay']) + self.LaC.extension.update_lateral_lag(calculated_lag) long_plan = self.sm['longitudinalPlan'] model_v2 = self.sm['modelV2'] diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 23bd99931b..0066708319 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -11,6 +11,8 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator, PoseCalibrator, Pose +from openpilot.sunnypilot.livedelay.lagd_toggle import LagdToggle + HISTORY = 5 # secs POINTS_PER_BUCKET = 1500 MIN_POINTS_TOTAL = 4000 @@ -49,8 +51,10 @@ class TorqueBuckets(PointBuckets): break -class TorqueEstimator(ParameterEstimator): +class TorqueEstimator(ParameterEstimator, LagdToggle): def __init__(self, CP, decimated=False, track_all_points=False): + super().__init__() + self.CP = CP self.hist_len = int(HISTORY / DT_MDL) self.lag = 0.0 self.track_all_points = track_all_points # for offline analysis, without max lateral accel or max steer torque filters @@ -176,7 +180,7 @@ class TorqueEstimator(ParameterEstimator): elif which == "liveCalibration": self.calibrator.feed_live_calib(msg) elif which == "liveDelay": - self.lag = msg.lateralDelay + self.lag = self.lagd_torqued_main(self.CP, msg) # calculate lateral accel from past steering torque elif which == "livePose": if len(self.raw_points['steer_torque']) == self.hist_len: diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index ac88dfbb5f..3236edb53b 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -37,6 +37,8 @@ from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_ from openpilot.selfdrive.modeld.constants import ModelConstants, Plan from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext +from openpilot.sunnypilot.livedelay.lagd_toggle import LagdToggle + PROCESS_NAME = "selfdrive.modeld.modeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') @@ -86,6 +88,7 @@ class ModelState: prev_desire: np.ndarray # for tracking the rising edge of the pulse def __init__(self, context: CLContext): + self.LAT_SMOOTH_SECONDS = 0.0 with open(VISION_METADATA_PATH, 'rb') as f: vision_metadata = pickle.load(f) self.vision_input_shapes = vision_metadata['input_shapes'] @@ -251,6 +254,8 @@ def main(demo=False): CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) cloudlog.info("modeld got CarParams: %s", CP.brand) + modeld_lagd = LagdToggle() + # TODO this needs more thought, use .2s extra for now to estimate other delays # TODO Move smooth seconds to action function long_delay = CP.longitudinalActuatorDelay + LONG_SMOOTH_SECONDS @@ -296,7 +301,7 @@ def main(demo=False): is_rhd = sm["driverMonitoringState"].isRHD frame_id = sm["roadCameraState"].frameId v_ego = max(sm["carState"].vEgo, 0.) - lat_delay = sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS + lat_delay = modeld_lagd.lagd_main(CP, sm, model) lateral_control_params = np.array([v_ego, lat_delay], dtype=np.float32) if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']: device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc index b8237c3627..1a2e86622a 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc @@ -90,11 +90,25 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) { list->addItem(horizontal_line()); // LiveDelay toggle - list->addItem(new ParamControlSP("LagdToggle", - tr("Live Learning Steer Delay"), - 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."), - "../assets/offroad/icon_shell.png")); + lagd_toggle_control = new ParamControlSP("LagdToggle", tr("Live Learning Steer Delay"), "", "../assets/offroad/icon_shell.png"); + lagd_toggle_control->showDescription(); + list->addItem(lagd_toggle_control); + + // Software delay control + delay_control = new OptionControlSP("LagdToggledelay", tr("Adjust Software Delay"), + tr("Adjust the software delay when Live Learning Steer Delay is toggled off." + "\nThe default software delay value is 0.2"), + "", {10, 30}, 1, false, nullptr, true); + + connect(delay_control, &OptionControlSP::updateLabels, [=]() { + float value = QString::fromStdString(params.get("LagdToggledelay")).toFloat(); + delay_control->setLabel(QString::number(value, 'f', 2) + "s"); + }); + connect(lagd_toggle_control, &ParamControlSP::toggleFlipped, [=](bool state) { + delay_control->setVisible(!state); + }); + delay_control->showDescription(); + list->addItem(delay_control); } QProgressBar* ModelsPanel::createProgressBar(QWidget *parent) { @@ -290,6 +304,24 @@ void ModelsPanel::updateLabels() { handleBundleDownloadProgress(); currentModelLblBtn->setEnabled(!is_onroad && !isDownloading()); currentModelLblBtn->setValue(GetActiveModelInternalName()); + + // Update lagdToggle description with current value + QString 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. " + "The Current value is updated automatically when the vehicle is Onroad."); + QString current = QString::fromStdString(params.get("LagdToggleDesc", false)); + if (!current.isEmpty()) { + desc += "

" + tr("Current:") + " " + current + ""; + } + lagd_toggle_control->setDescription(desc); + lagd_toggle_control->showDescription(); + + delay_control->setVisible(!params.getBool("LagdToggle")); + if (delay_control->isVisible()) { + float value = QString::fromStdString(params.get("LagdToggledelay")).toFloat(); + delay_control->setLabel(QString::number(value, 'f', 2) + "s"); + delay_control->showDescription(); + } } /** diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h index 8b8cf69759..61f15e6d3c 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h @@ -64,6 +64,8 @@ private: bool is_onroad = false; ButtonControlSP *currentModelLblBtn; + ParamControlSP *lagd_toggle_control; + OptionControlSP *delay_control; QProgressBar *supercomboProgressBar; QFrame *supercomboFrame; QProgressBar *navigationProgressBar; diff --git a/selfdrive/ui/sunnypilot/qt/widgets/controls.h b/selfdrive/ui/sunnypilot/qt/widgets/controls.h index c8849faf53..29b6e6e6ea 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/controls.h @@ -480,6 +480,16 @@ private: return result.toInt(); } + int getParamValueScaled() { + const auto param_value = QString::fromStdString(params.get(key)); + return static_cast(param_value.toFloat() * 100); + } + + void setParamValueScaled(const int new_value) { + const float scaled_value = new_value / 100.0f; + params.put(key, QString::number(scaled_value, 'f', 2).toStdString()); + } + // Although the method is not static, and thus has access to the value property, I prefer to be explicit about the value. void setParamValue(const int new_value) { const auto value_str = valueMap != nullptr ? valueMap->value(QString::number(new_value)) : QString::number(new_value); @@ -488,7 +498,8 @@ private: public: OptionControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, - const MinMaxValue &range, const int per_value_change = 1, const bool inline_layout = false, const QMap *valMap = nullptr) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr), _title(title), valueMap(valMap), is_inline_layout(inline_layout) { + const MinMaxValue &range, const int per_value_change = 1, const bool inline_layout = false, + const QMap *valMap = nullptr, bool scale_float = false) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr), _title(title), valueMap(valMap), is_inline_layout(inline_layout), use_float_scaling(scale_float) { const QString style = R"( QPushButton { border-radius: 20px; @@ -528,7 +539,7 @@ public: const std::vector button_texts{"-", "+"}; key = param.toStdString(); - value = getParamValue(); + value = use_float_scaling ? getParamValueScaled() : getParamValue(); button_group = new QButtonGroup(this); button_group->setExclusive(true); @@ -546,10 +557,15 @@ public: QObject::connect(button, &QPushButton::clicked, [=]() { int change_value = (i == 0) ? -per_value_change : per_value_change; - value = getParamValue(); // in case it changed externally, we need to get the latest value. + value = use_float_scaling ? getParamValueScaled() : getParamValue(); value += change_value; value = std::clamp(value, range.min_value, range.max_value); - setParamValue(value); + + if (use_float_scaling) { + setParamValueScaled(value); + } else { + setParamValue(value); + } button_group->button(0)->setEnabled(!(value <= range.min_value)); button_group->button(1)->setEnabled(!(value >= range.max_value)); @@ -642,6 +658,7 @@ private: const QString label_disabled_style = "font-size: 50px; font-weight: 450; color: #5C5C5C;"; bool button_enabled = true; + bool use_float_scaling = false; }; class PushButtonSP : public QPushButton { diff --git a/sunnypilot/livedelay/__init__.py b/sunnypilot/livedelay/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sunnypilot/livedelay/lagd_toggle.py b/sunnypilot/livedelay/lagd_toggle.py new file mode 100644 index 0000000000..cc2f1e2504 --- /dev/null +++ b/sunnypilot/livedelay/lagd_toggle.py @@ -0,0 +1,51 @@ +""" +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.common.swaglog import cloudlog + + +class LagdToggle: + def __init__(self): + self.params = Params() + self.lag = 0.0 + self._last_desc = None + + @property + def software_delay(self): + return float(self.params.get("LagdToggledelay", encoding='utf8')) + + def _maybe_update_desc(self, desc): + if desc != self._last_desc: + self.params.put_nonblocking("LagdToggleDesc", desc) + self._last_desc = desc + + def lagd_main(self, CP, sm, model): + if self.params.get_bool("LagdToggle"): + lateral_delay = sm["liveDelay"].lateralDelay + lat_smooth = model.LAT_SMOOTH_SECONDS + result = lateral_delay + lat_smooth + desc = f"live steer delay learner ({lateral_delay:.3f}s) + model smoothing filter ({lat_smooth:.3f}s) = total delay ({result:.3f}s)" + self._maybe_update_desc(desc) + return result + + steer_actuator_delay = CP.steerActuatorDelay + lat_smooth = model.LAT_SMOOTH_SECONDS + delay = self.software_delay + result = (steer_actuator_delay + delay) + lat_smooth + desc = (f"Vehicle steering delay ({steer_actuator_delay:.3f}s) + software delay ({delay:.3f}s) + " + + f"model smoothing filter ({lat_smooth:.3f}s) = total delay ({result:.3f}s)") + self._maybe_update_desc(desc) + return result + + def lagd_torqued_main(self, CP, msg): + if self.params.get_bool("LagdToggle"): + self.lag = msg.lateralDelay + cloudlog.debug(f"TORQUED USING LIVE DELAY: {self.lag:.3f}") + else: + self.lag = CP.steerActuatorDelay + self.software_delay + cloudlog.debug(f"TORQUED USING STEER ACTUATOR: {self.lag:.3f}") + return self.lag diff --git a/sunnypilot/modeld/modeld.py b/sunnypilot/modeld/modeld.py index d4bd96ff66..86968ea26c 100755 --- a/sunnypilot/modeld/modeld.py +++ b/sunnypilot/modeld/modeld.py @@ -25,7 +25,7 @@ from openpilot.sunnypilot.modeld.parse_model_outputs import Parser from openpilot.sunnypilot.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState from openpilot.sunnypilot.modeld.constants import ModelConstants, Plan from openpilot.sunnypilot.models.helpers import get_active_bundle, get_model_path, load_metadata, prepare_inputs, load_meta_constants -from openpilot.sunnypilot.models.modeld_lagd import ModeldLagd +from openpilot.sunnypilot.livedelay.lagd_toggle import LagdToggle from openpilot.sunnypilot.modeld.models.commonmodel_pyx import ModelFrame, CLContext @@ -202,7 +202,7 @@ def main(demo=False): cloudlog.info("modeld got CarParams: %s", CP.brand) - modeld_lagd = ModeldLagd() + modeld_lagd = LagdToggle() # Enable lagd support for sunnypilot modeld long_delay = CP.longitudinalActuatorDelay + model.LONG_SMOOTH_SECONDS diff --git a/sunnypilot/modeld_v2/modeld.py b/sunnypilot/modeld_v2/modeld.py index 5b9c5ef9e5..58e79eceb5 100755 --- a/sunnypilot/modeld_v2/modeld.py +++ b/sunnypilot/modeld_v2/modeld.py @@ -23,7 +23,7 @@ from openpilot.sunnypilot.modeld_v2.models.commonmodel_pyx import DrivingModelFr from openpilot.sunnypilot.modeld_v2.meta_helper import load_meta_constants from openpilot.sunnypilot.models.helpers import get_active_bundle -from openpilot.sunnypilot.models.modeld_lagd import ModeldLagd +from openpilot.sunnypilot.livedelay.lagd_toggle import LagdToggle from openpilot.sunnypilot.models.runners.helpers import get_model_runner PROCESS_NAME = "selfdrive.modeld.modeld" @@ -239,7 +239,7 @@ def main(demo=False): cloudlog.info("modeld got CarParams: %s", CP.brand) - modeld_lagd = ModeldLagd() + modeld_lagd = LagdToggle() # TODO Move smooth seconds to action function long_delay = CP.longitudinalActuatorDelay + model.LONG_SMOOTH_SECONDS diff --git a/sunnypilot/models/modeld_lagd.py b/sunnypilot/models/modeld_lagd.py deleted file mode 100644 index 9e538123a8..0000000000 --- a/sunnypilot/models/modeld_lagd.py +++ /dev/null @@ -1,26 +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.common.swaglog import cloudlog - - -class ModeldLagd: - def __init__(self): - self.params = Params() - - def lagd_main(self, CP, sm, model): - if self.params.get_bool("LagdToggle"): - lateral_delay = sm["liveDelay"].lateralDelay - lat_smooth = model.LAT_SMOOTH_SECONDS - result = lateral_delay + lat_smooth - cloudlog.debug(f"LAGD USING LIVE DELAY: {lateral_delay:.3f} + {lat_smooth:.3f} = {result:.3f}") - return result - steer_actuator_delay = CP.steerActuatorDelay - lat_smooth = model.LAT_SMOOTH_SECONDS - result = (steer_actuator_delay + 0.2) + lat_smooth - cloudlog.debug(f"LAGD USING STEER ACTUATOR: {steer_actuator_delay:.3f} + 0.2 + {lat_smooth:.3f} = {result:.3f}") - return result diff --git a/sunnypilot/selfdrive/controls/lib/latcontrol_torque_ext_base.py b/sunnypilot/selfdrive/controls/lib/latcontrol_torque_ext_base.py index 8ccd81d8ff..b3c2d78ac6 100644 --- a/sunnypilot/selfdrive/controls/lib/latcontrol_torque_ext_base.py +++ b/sunnypilot/selfdrive/controls/lib/latcontrol_torque_ext_base.py @@ -10,6 +10,8 @@ import numpy as np from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N from openpilot.selfdrive.modeld.constants import ModelConstants +from openpilot.sunnypilot.livedelay.lagd_toggle import LagdToggle + LAT_PLAN_MIN_IDX = 5 LATERAL_LAG_MOD = 0.0 # seconds, modifies how far in the future we look ahead for the lateral plan @@ -41,8 +43,9 @@ def get_lookahead_value(future_vals, current_val): return min_val -class LatControlTorqueExtBase: +class LatControlTorqueExtBase(LagdToggle): def __init__(self, lac_torque, CP, CP_SP): + LagdToggle.__init__(self) self.model_v2 = None self.model_valid = False self.use_steering_angle = lac_torque.use_steering_angle diff --git a/system/manager/manager.py b/system/manager/manager.py index 9de332df47..692c7717ff 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -57,6 +57,7 @@ def manager_init() -> None: ("DynamicExperimentalControl", "0"), ("HyundaiLongitudinalTuning", "0"), ("LagdToggle", "1"), + ("LagdToggledelay", "0.2"), ("Mads", "1"), ("MadsMainCruiseAllowed", "1"), ("MadsSteeringMode", "0"), From 394603727e85ca12483ff06abe3af7aa7bd5e248 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 5 Jul 2025 14:58:34 -0700 Subject: [PATCH 27/74] Bug: Param Store Cache (#1025) * cache * Clear every 10 iterations, which means 100hz * 10 * Revert "Clear every 10 iterations, which means 100hz * 10" This reverts commit 4eda3079e6cfc83008b8df0d41094ce52237faf7. * Apply suggestion from @devtekve * Apply suggestion from @devtekve --------- Co-authored-by: DevTekVE --- sunnypilot/selfdrive/controls/lib/param_store.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/sunnypilot/selfdrive/controls/lib/param_store.py b/sunnypilot/selfdrive/controls/lib/param_store.py index b2701bc010..5ec2a1c8c9 100644 --- a/sunnypilot/selfdrive/controls/lib/param_store.py +++ b/sunnypilot/selfdrive/controls/lib/param_store.py @@ -22,14 +22,16 @@ class ParamStore: self.keys = universal_params + brand_params self.values = {} + self.cached_params_list: list[capnp.lib.capnp._DynamicStructBuilder] | None = None def update(self, params: Params) -> None: + old_values = dict(self.values) self.values = {k: params.get(k, encoding='utf8') or "0" for k in self.keys} + if old_values != self.values: + self.cached_params_list = None def publish(self) -> list[capnp.lib.capnp._DynamicStructBuilder]: - params_list: list[capnp.lib.capnp._DynamicStructBuilder] = [] - - for k in self.keys: - params_list.append(custom.CarControlSP.Param(key=k, value=self.values[k])) - - return params_list + if self.cached_params_list is None: + # TODO-SP: Why are we doing a list instead of a dictionary here? + self.cached_params_list = [custom.CarControlSP.Param(key=k, value=self.values[k]) for k in self.keys] + return self.cached_params_list From af53db3b07ae4236c71ad28595e39309cc0e8493 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 5 Jul 2025 21:23:32 -0700 Subject: [PATCH 28/74] Longitudinal Planner: Allow Non-MLSIM Models to Use MPC (#1012) * gen 11 only api limit exceeded maybe not * Try this for ModelDataV2.Action * mpc mode * Fix This * Revert "Try this for ModelDataV2.Action" This reverts commit e7db17980b3667de1399cac97ef92041f5d59eff. * fix logic flaw * Address comments for readability. --------- Co-authored-by: Kumar <36933347+rav4kumar@users.noreply.github.com> --- selfdrive/controls/lib/longitudinal_planner.py | 6 +++++- sunnypilot/selfdrive/controls/lib/longitudinal_planner.py | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 4e842f8d1b..6a26eadef4 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -94,9 +94,13 @@ class LongitudinalPlanner(LongitudinalPlannerSP): def update(self, sm): self.mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc' + if not self.mlsim: + self.mpc.mode = self.mode LongitudinalPlannerSP.update(self, sm) if dec_mpc_mode := self.get_mpc_mode(): self.mode = dec_mpc_mode + if not self.mlsim: + self.mpc.mode = dec_mpc_mode if len(sm['carControl'].orientationNED) == 3: accel_coast = get_coast_accel(sm['carControl'].orientationNED[1]) @@ -171,7 +175,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP): output_a_target_e2e = sm['modelV2'].action.desiredAcceleration output_should_stop_e2e = sm['modelV2'].action.shouldStop - if self.mode == 'acc': + if self.mode == 'acc' or not self.mlsim: output_a_target = output_a_target_mpc self.output_should_stop = output_should_stop_mpc else: diff --git a/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py b/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py index 56f32373d5..18bfbe925c 100644 --- a/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py +++ b/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py @@ -8,6 +8,7 @@ See the LICENSE.md file in the root directory for more details. from cereal import messaging, custom from opendbc.car import structs from openpilot.sunnypilot.selfdrive.controls.lib.dec.dec import DynamicExperimentalController +from openpilot.sunnypilot.models.helpers import get_active_bundle DecState = custom.LongitudinalPlanSP.DynamicExperimentalControl.DynamicExperimentalControlState @@ -15,6 +16,12 @@ DecState = custom.LongitudinalPlanSP.DynamicExperimentalControl.DynamicExperimen class LongitudinalPlannerSP: def __init__(self, CP: structs.CarParams, mpc): self.dec = DynamicExperimentalController(CP, mpc) + model_bundle = get_active_bundle() + self.generation = model_bundle.generation if model_bundle is not None else None + + @property + def mlsim(self) -> bool: + return self.generation == 11 def get_mpc_mode(self) -> str | None: if not self.dec.active(): From 44c8cc4cb0b107baf0eabd7885caee37b2f9cba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Mon, 7 Jul 2025 10:18:31 +0200 Subject: [PATCH 29/74] Revert "lagd: disable in release (#35426)" and "lagd calib: hide on release (#35523)" (#35633) * Revert "lagd: disable in release (#35426)" This reverts commit 6f6adc10a83aa5ab775a2b0aaebb7ab4396f4268. * Revert "lagd calib: hide on release (#35523)" This reverts commit e345f25ce4e82c342af1eadb145a80a3920db79f. * Fix conflict --- selfdrive/locationd/lagd.py | 11 +++----- selfdrive/locationd/test/test_lagd.py | 13 ---------- selfdrive/ui/qt/offroad/settings.cc | 37 ++++++++++++--------------- 3 files changed, 19 insertions(+), 42 deletions(-) diff --git a/selfdrive/locationd/lagd.py b/selfdrive/locationd/lagd.py index e6767db473..d7834f7f1f 100755 --- a/selfdrive/locationd/lagd.py +++ b/selfdrive/locationd/lagd.py @@ -157,8 +157,7 @@ class LateralLagEstimator: block_count: int = BLOCK_NUM, min_valid_block_count: int = BLOCK_NUM_NEEDED, block_size: int = BLOCK_SIZE, window_sec: float = MOVING_WINDOW_SEC, okay_window_sec: float = MIN_OKAY_WINDOW_SEC, min_recovery_buffer_sec: float = MIN_RECOVERY_BUFFER_SEC, min_vego: float = MIN_VEGO, min_yr: float = MIN_ABS_YAW_RATE, min_ncc: float = MIN_NCC, - max_lat_accel: float = MAX_LAT_ACCEL, max_lat_accel_diff: float = MAX_LAT_ACCEL_DIFF, min_confidence: float = MIN_CONFIDENCE, - enabled: bool = True): + max_lat_accel: float = MAX_LAT_ACCEL, max_lat_accel_diff: float = MAX_LAT_ACCEL_DIFF, min_confidence: float = MIN_CONFIDENCE): self.dt = dt self.window_sec = window_sec self.okay_window_sec = okay_window_sec @@ -173,7 +172,6 @@ class LateralLagEstimator: self.min_confidence = min_confidence self.max_lat_accel = max_lat_accel self.max_lat_accel_diff = max_lat_accel_diff - self.enabled = enabled self.t = 0.0 self.lat_active = False @@ -208,7 +206,7 @@ class LateralLagEstimator: liveDelay = msg.liveDelay valid_mean_lag, valid_std, current_mean_lag, current_std = self.block_avg.get() - if self.enabled and self.block_avg.valid_blocks >= self.min_valid_block_count and not np.isnan(valid_mean_lag) and not np.isnan(valid_std): + if self.block_avg.valid_blocks >= self.min_valid_block_count and not np.isnan(valid_mean_lag) and not np.isnan(valid_std): if valid_std > MAX_LAG_STD: liveDelay.status = log.LiveDelayData.Status.invalid else: @@ -371,10 +369,7 @@ def main(): params = Params() CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) - # TODO: remove me, lagd is in shadow mode on release - is_release = params.get_bool("IsReleaseBranch") - - lag_learner = LateralLagEstimator(CP, 1. / SERVICE_LIST['livePose'].frequency, enabled=not is_release) + lag_learner = LateralLagEstimator(CP, 1. / SERVICE_LIST['livePose'].frequency) if (initial_lag_params := retrieve_initial_lag(params, CP)) is not None: lag, valid_blocks = initial_lag_params lag_learner.reset(lag, valid_blocks) diff --git a/selfdrive/locationd/test/test_lagd.py b/selfdrive/locationd/test/test_lagd.py index 8fb829edd8..a3dfce9c29 100644 --- a/selfdrive/locationd/test/test_lagd.py +++ b/selfdrive/locationd/test/test_lagd.py @@ -110,19 +110,6 @@ class TestLagd: assert msg.liveDelay.validBlocks == BLOCK_NUM_NEEDED assert msg.liveDelay.calPerc == 100 - def test_disabled_estimator(self): - mocked_CP = car.CarParams(steerActuatorDelay=0.8) - estimator = LateralLagEstimator(mocked_CP, DT, min_recovery_buffer_sec=0.0, min_yr=0.0, enabled=False) - lag_frames = 5 - process_messages(estimator, lag_frames, int(MIN_OKAY_WINDOW_SEC / DT) + BLOCK_NUM_NEEDED * BLOCK_SIZE) - msg = estimator.get_msg(True) - assert msg.liveDelay.status == 'unestimated' - assert np.allclose(msg.liveDelay.lateralDelay, 1.0, atol=0.01) - assert np.allclose(msg.liveDelay.lateralDelayEstimate, lag_frames * DT, atol=0.01) - assert np.allclose(msg.liveDelay.lateralDelayEstimateStd, 0.0, atol=0.01) - assert msg.liveDelay.validBlocks == BLOCK_NUM_NEEDED - assert msg.liveDelay.calPerc == 100 - def test_estimator_masking(self): mocked_CP, lag_frames = car.CarParams(steerActuatorDelay=0.8), random.randint(1, 19) estimator = LateralLagEstimator(mocked_CP, DT, min_recovery_buffer_sec=0.0, min_yr=0.0, min_valid_block_count=1) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index e242aa0878..96734d69d9 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -333,26 +333,22 @@ void DevicePanel::updateCalibDescription() { } } - const bool is_release = params.getBool("IsReleaseBranch"); - if (!is_release) { - 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"; - } - } - desc += "\n\n"; - if (lag_perc < 100) { - desc += tr("Steering lag calibration is %1% complete.").arg(lag_perc); - } else { - desc += tr("Steering lag calibration is complete."); + 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()) { @@ -363,11 +359,10 @@ void DevicePanel::updateCalibDescription() { // don't add for non-torque cars if (torque.getUseParams()) { int torque_perc = torque.getCalPerc(); - desc += is_release ? "\n\n" : " "; if (torque_perc < 100) { - desc += tr("Steering torque response calibration is %1% complete.").arg(torque_perc); + desc += tr(" Steering torque response calibration is %1% complete.").arg(torque_perc); } else { - desc += tr("Steering torque response calibration is complete."); + desc += tr(" Steering torque response calibration is complete."); } } } catch (kj::Exception) { From 3238cd42cd9b725ec1adcfebd5cdca1dd1672195 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 14:45:48 -0700 Subject: [PATCH 30/74] [bot] Update translations (#35643) Update translations Co-authored-by: Vehicle Researcher --- selfdrive/ui/translations/main_ar.ts | 44 +++++++++++++++--------- selfdrive/ui/translations/main_de.ts | 44 +++++++++++++++--------- selfdrive/ui/translations/main_es.ts | 44 +++++++++++++++--------- selfdrive/ui/translations/main_fr.ts | 44 +++++++++++++++--------- selfdrive/ui/translations/main_ja.ts | 44 +++++++++++++++--------- selfdrive/ui/translations/main_ko.ts | 44 +++++++++++++++--------- selfdrive/ui/translations/main_pt-BR.ts | 44 +++++++++++++++--------- selfdrive/ui/translations/main_th.ts | 44 +++++++++++++++--------- selfdrive/ui/translations/main_tr.ts | 44 +++++++++++++++--------- selfdrive/ui/translations/main_zh-CHS.ts | 44 +++++++++++++++--------- selfdrive/ui/translations/main_zh-CHT.ts | 44 +++++++++++++++--------- 11 files changed, 308 insertions(+), 176 deletions(-) diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index df13a5d2e9..f632c063d5 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -310,26 +310,30 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - - Steering lag calibration is %1% complete. - - - - Steering lag calibration is complete. - - - - Steering torque response calibration is %1% complete. - - - - Steering torque response calibration is complete. - - 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 @@ -1182,6 +1186,14 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device 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. + + Updater diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 7cf794774f..098906f824 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -310,26 +310,30 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - - Steering lag calibration is %1% complete. - - - - Steering lag calibration is complete. - - - - Steering torque response calibration is %1% complete. - - - - Steering torque response calibration is complete. - - 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 @@ -1164,6 +1168,14 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device 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. + + Updater diff --git a/selfdrive/ui/translations/main_es.ts b/selfdrive/ui/translations/main_es.ts index 5941e53c73..1879268009 100644 --- a/selfdrive/ui/translations/main_es.ts +++ b/selfdrive/ui/translations/main_es.ts @@ -310,26 +310,30 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - - Steering lag calibration is %1% complete. - - - - Steering lag calibration is complete. - - - - Steering torque response calibration is %1% complete. - - - - Steering torque response calibration is complete. - - 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 @@ -1162,6 +1166,14 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device 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. + + Updater diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index 08b390701a..5148fce5f7 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -310,26 +310,30 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - - Steering lag calibration is %1% complete. - - - - Steering lag calibration is complete. - - - - Steering torque response calibration is %1% complete. - - - - Steering torque response calibration is complete. - - 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 @@ -1160,6 +1164,14 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device 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. + + Updater diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index f3243d8769..d3dda756b4 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -310,26 +310,30 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. openpilotの本体は左右4°以内、上5°下9°以内の角度で取付ける必要があります。 - - 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. - ステアリングトルク応答のキャリブレーション完了。 - 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. + + + + + +Steering lag calibration is complete. + + + + Steering torque response calibration is %1% complete. + + + + Steering torque response calibration is complete. + + DriverViewWindow @@ -1157,6 +1161,14 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device 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. + + Updater diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 9846093f2e..819392c8c8 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -310,26 +310,30 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - - Steering lag calibration is %1% complete. - - - - Steering lag calibration is complete. - - - - Steering torque response calibration is %1% complete. - - - - Steering torque response calibration is complete. - - 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 @@ -1157,6 +1161,14 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device 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. + + Updater diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 736f09d826..6c4270fd32 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -310,26 +310,30 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - - Steering lag calibration is %1% complete. - - - - Steering lag calibration is complete. - - - - Steering torque response calibration is %1% complete. - - - - Steering torque response calibration is complete. - - 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 @@ -1162,6 +1166,14 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device 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 + + + + Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. + + Updater diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index e8dd3c74a0..88f46303d6 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -310,26 +310,30 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - - Steering lag calibration is %1% complete. - - - - Steering lag calibration is complete. - - - - Steering torque response calibration is %1% complete. - - - - Steering torque response calibration is complete. - - 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 @@ -1157,6 +1161,14 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device 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. + + Updater diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts index 5243b0365a..eb3d8e1c39 100644 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -310,26 +310,30 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - - Steering lag calibration is %1% complete. - - - - Steering lag calibration is complete. - - - - Steering torque response calibration is %1% complete. - - - - Steering torque response calibration is complete. - - 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 @@ -1153,6 +1157,14 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device 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. + + Updater diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 7af7db1a27..806ccf27e9 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -310,26 +310,30 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - - Steering lag calibration is %1% complete. - - - - Steering lag calibration is complete. - - - - Steering torque response calibration is %1% complete. - - - - Steering torque response calibration is complete. - - 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 @@ -1157,6 +1161,14 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device 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. + + Updater diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 8eeebc78d5..664be84d9f 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -310,26 +310,30 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down. - - Steering lag calibration is %1% complete. - - - - Steering lag calibration is complete. - - - - Steering torque response calibration is %1% complete. - - - - Steering torque response calibration is complete. - - 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 @@ -1157,6 +1161,14 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device 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. + + Updater From 9ff322cff8b976ad0897ee1fb3fed216811152e1 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Mon, 7 Jul 2025 14:48:57 -0700 Subject: [PATCH 31/74] [bot] Update Python packages (#35644) * Update Python packages * revert for now --------- Co-authored-by: Vehicle Researcher Co-authored-by: Adeeb Shihadeh --- docs/CARS.md | 2 +- opendbc_repo | 2 +- panda | 2 +- tinygrad_repo | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index c53a30caed..e8ccdf7bb1 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -187,7 +187,7 @@ A supported vehicle is one that just works when you install a comma device. All |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|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
||| diff --git a/opendbc_repo b/opendbc_repo index 33842a09ac..51353491cb 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 33842a09ac54d2b6dc53a2afcc97841495a795cd +Subproject commit 51353491cbaee8f82b1f5edfdb0ab0de01bfa918 diff --git a/panda b/panda index 1020d35558..f849893bdc 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 1020d355584265391eb3acb556e4353b581fa9c0 +Subproject commit f849893bdcfc6cd05d4547d64d2802b5a83f409c diff --git a/tinygrad_repo b/tinygrad_repo index 710d734ce7..9a573a1d99 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 710d734ce7e1b5e47d58d19206fd8f3e3854794c +Subproject commit 9a573a1d99d53a85be3052a6db82eaf0ce9d84e9 From 74ebcd2249e1172850c0e2df46b326e5560e3239 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 7 Jul 2025 16:30:04 -0700 Subject: [PATCH 32/74] Missing NM params for raylib wifi manager (#35646) stuff missing --- system/ui/lib/wifi_manager.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index dd08b66373..75d1bdbfd3 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -204,7 +204,7 @@ class WifiManager: 'connection': { 'type': Variant('s', '802-11-wireless'), 'uuid': Variant('s', str(uuid.uuid4())), - 'id': Variant('s', ssid), + 'id': Variant('s', f'openpilot connection {ssid}'), 'autoconnect-retries': Variant('i', 0), }, '802-11-wireless': { @@ -212,7 +212,10 @@ class WifiManager: 'hidden': Variant('b', is_hidden), 'mode': Variant('s', 'infrastructure'), }, - 'ipv4': {'method': Variant('s', 'auto')}, + 'ipv4': { + 'method': Variant('s', 'auto'), + 'dns-priority': Variant('i', 600), + }, 'ipv6': {'method': Variant('s', 'ignore')}, } From 5b70c7890252b0e14a083aae242bbc919ed0653c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 7 Jul 2025 18:40:06 -0700 Subject: [PATCH 33/74] bump opendbc (#35649) * bump opendbc * update ref --- opendbc_repo | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 51353491cb..f4ec91dcb3 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 51353491cbaee8f82b1f5edfdb0ab0de01bfa918 +Subproject commit f4ec91dcb33990a9a6587aac93eb0677e4bac3ac diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index ea3b0bfb7e..2d68ea0eaa 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -502c90d4859d2529ae2adf7097d575852673be74 \ No newline at end of file +254f911c7c35c66eb12223fb0dc262d829a42e80 \ No newline at end of file From 53da5fa6b1fbee946fe6ac3b7cc22bc831fb8dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 7 Jul 2025 18:42:57 -0700 Subject: [PATCH 34/74] Update RELEASES.md --- RELEASES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASES.md b/RELEASES.md index e4ed90c171..37e7d02d34 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -3,6 +3,7 @@ Version 0.9.10 (2025-07-07) * New driving model * Lead car ground-truth fixes * Ported over VAE from the MLSIM stack + * New training architecture described in CVPR paper * Opt-in audio recording for dashcam video Version 0.9.9 (2025-05-23) From 13019da855647fd59e77488d78852f701a130df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 7 Jul 2025 18:43:30 -0700 Subject: [PATCH 35/74] Update RELEASES.md --- RELEASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 37e7d02d34..fda4cf4dbf 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,4 +1,4 @@ -Version 0.9.10 (2025-07-07) +Version 0.10.0 (2025-07-07) ======================== * New driving model * Lead car ground-truth fixes From 8e3b5f621007fff09e279c4b8560570f351d4679 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 7 Jul 2025 18:52:18 -0700 Subject: [PATCH 36/74] bump to 0.10 --- common/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/version.h b/common/version.h index 67b69da7c7..af827e20a6 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.9.10" +#define COMMA_VERSION "0.10.0" From 1b92dbb46f4aa2bf6a2662148b070d3fb9ab37db Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Mon, 7 Jul 2025 21:10:37 -0700 Subject: [PATCH 37/74] test: RecordFront and RecordAudio (#35650) record front and record audio tests --- system/loggerd/tests/test_loggerd.py | 103 ++++++++++++++++++--------- 1 file changed, 71 insertions(+), 32 deletions(-) diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index 179e237a65..46825219be 100644 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -97,6 +97,50 @@ class TestLoggerd: return sent_msgs + def _publish_camera_and_audio_messages(self, num_segs=1, segment_length=5): + d = DEVICE_CAMERAS[("tici", "ar0231")] + 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"), + ] + + pm = messaging.PubMaster([s for _, _, s in streams] + ["rawAudioData"]) + vipc_server = VisionIpcServer("camerad") + for stream_type, frame_spec, _ in streams: + vipc_server.create_buffers_with_sizes(stream_type, 40, *(frame_spec)) + vipc_server.start_listener() + + os.environ["LOGGERD_TEST"] = "1" + os.environ["LOGGERD_SEGMENT_LENGTH"] = str(segment_length) + managed_processes["loggerd"].start() + managed_processes["encoderd"].start() + assert pm.wait_for_readers_to_update("roadCameraState", timeout=5) + + fps = 20 + for n in range(1, int(num_segs * segment_length * fps) + 1): + # send video + for stream_type, frame_spec, state in streams: + dat = np.empty(frame_spec[2], dtype=np.uint8) + vipc_server.send(stream_type, dat[:].flatten().tobytes(), n, n / fps, n / fps) + + camera_state = messaging.new_message(state) + frame = getattr(camera_state, state) + frame.frameId = n + pm.send(state, camera_state) + + # send audio + msg = messaging.new_message('rawAudioData') + msg.rawAudioData.data = bytes(800 * 2) # 800 samples of int16 + msg.rawAudioData.sampleRate = 16000 + pm.send('rawAudioData', msg) + + for _, _, state in streams: + assert pm.wait_for_readers_to_update(state, timeout=5, dt=0.001) + + managed_processes["loggerd"].stop() + managed_processes["encoderd"].stop() + def test_init_data_values(self): os.environ["CLEAN"] = random.choice(["0", "1"]) @@ -138,51 +182,21 @@ class TestLoggerd: @pytest.mark.skip("FIXME: encoderd sometimes crashes in CI when running with pytest-xdist") def test_rotation(self): - os.environ["LOGGERD_TEST"] = "1" Params().put("RecordFront", "1") - d = DEVICE_CAMERAS[("tici", "ar0231")] expected_files = {"rlog.zst", "qlog.zst", "qcamera.ts", "fcamera.hevc", "dcamera.hevc", "ecamera.hevc"} - 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")] - - pm = messaging.PubMaster(["roadCameraState", "driverCameraState", "wideRoadCameraState"]) - vipc_server = VisionIpcServer("camerad") - for stream_type, frame_spec, _ in streams: - vipc_server.create_buffers_with_sizes(stream_type, 40, *(frame_spec)) - vipc_server.start_listener() num_segs = random.randint(2, 5) length = random.randint(1, 3) - os.environ["LOGGERD_SEGMENT_LENGTH"] = str(length) - managed_processes["loggerd"].start() - managed_processes["encoderd"].start() - assert pm.wait_for_readers_to_update("roadCameraState", timeout=5) - fps = 20.0 - for n in range(1, int(num_segs*length*fps)+1): - for stream_type, frame_spec, state in streams: - dat = np.empty(frame_spec[2], dtype=np.uint8) - vipc_server.send(stream_type, dat[:].flatten().tobytes(), n, n/fps, n/fps) - - camera_state = messaging.new_message(state) - frame = getattr(camera_state, state) - frame.frameId = n - pm.send(state, camera_state) - - for _, _, state in streams: - assert pm.wait_for_readers_to_update(state, timeout=5, dt=0.001) - - managed_processes["loggerd"].stop() - managed_processes["encoderd"].stop() + self._publish_camera_and_audio_messages(num_segs=num_segs, segment_length=length) route_path = str(self._get_latest_log_dir()).rsplit("--", 1)[0] for n in range(num_segs): p = Path(f"{route_path}--{n}") logged = {f.name for f in p.iterdir() if f.is_file()} diff = logged ^ expected_files - assert len(diff) == 0, f"didn't get all expected files. run={_} seg={n} {route_path=}, {diff=}\n{logged=} {expected_files=}" + assert len(diff) == 0, f"didn't get all expected files. seg={n} {route_path=}, {diff=}\n{logged=} {expected_files=}" def test_bootlog(self): # generate bootlog with fake launch log @@ -281,3 +295,28 @@ class TestLoggerd: segment_dir = self._get_latest_log_dir() assert getxattr(segment_dir, PRESERVE_ATTR_NAME) is None + + @pytest.mark.parametrize("record_front", [True, False]) + def test_record_front(self, record_front): + params = Params() + params.put_bool("RecordFront", record_front) + + self._publish_camera_and_audio_messages() + + dcamera_hevc_exists = os.path.exists(os.path.join(self._get_latest_log_dir(), 'dcamera.hevc')) + assert dcamera_hevc_exists == record_front + + @pytest.mark.parametrize("record_audio", [True, False]) + def test_record_audio(self, record_audio): + params = Params() + params.put_bool("RecordAudio", record_audio) + + 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'' + 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'))) + assert raw_audio_in_rlog == record_audio From 684f770435e58acffc3a59a4f525ff7645cd6c00 Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Mon, 7 Jul 2025 21:14:22 -0700 Subject: [PATCH 38/74] Revert "test: RecordFront and RecordAudio" (#35652) Revert "test: RecordFront and RecordAudio (#35650)" This reverts commit 1b92dbb46f4aa2bf6a2662148b070d3fb9ab37db. --- system/loggerd/tests/test_loggerd.py | 103 +++++++++------------------ 1 file changed, 32 insertions(+), 71 deletions(-) diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index 46825219be..179e237a65 100644 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -97,50 +97,6 @@ class TestLoggerd: return sent_msgs - def _publish_camera_and_audio_messages(self, num_segs=1, segment_length=5): - d = DEVICE_CAMERAS[("tici", "ar0231")] - 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"), - ] - - pm = messaging.PubMaster([s for _, _, s in streams] + ["rawAudioData"]) - vipc_server = VisionIpcServer("camerad") - for stream_type, frame_spec, _ in streams: - vipc_server.create_buffers_with_sizes(stream_type, 40, *(frame_spec)) - vipc_server.start_listener() - - os.environ["LOGGERD_TEST"] = "1" - os.environ["LOGGERD_SEGMENT_LENGTH"] = str(segment_length) - managed_processes["loggerd"].start() - managed_processes["encoderd"].start() - assert pm.wait_for_readers_to_update("roadCameraState", timeout=5) - - fps = 20 - for n in range(1, int(num_segs * segment_length * fps) + 1): - # send video - for stream_type, frame_spec, state in streams: - dat = np.empty(frame_spec[2], dtype=np.uint8) - vipc_server.send(stream_type, dat[:].flatten().tobytes(), n, n / fps, n / fps) - - camera_state = messaging.new_message(state) - frame = getattr(camera_state, state) - frame.frameId = n - pm.send(state, camera_state) - - # send audio - msg = messaging.new_message('rawAudioData') - msg.rawAudioData.data = bytes(800 * 2) # 800 samples of int16 - msg.rawAudioData.sampleRate = 16000 - pm.send('rawAudioData', msg) - - for _, _, state in streams: - assert pm.wait_for_readers_to_update(state, timeout=5, dt=0.001) - - managed_processes["loggerd"].stop() - managed_processes["encoderd"].stop() - def test_init_data_values(self): os.environ["CLEAN"] = random.choice(["0", "1"]) @@ -182,21 +138,51 @@ class TestLoggerd: @pytest.mark.skip("FIXME: encoderd sometimes crashes in CI when running with pytest-xdist") def test_rotation(self): + os.environ["LOGGERD_TEST"] = "1" Params().put("RecordFront", "1") + d = DEVICE_CAMERAS[("tici", "ar0231")] expected_files = {"rlog.zst", "qlog.zst", "qcamera.ts", "fcamera.hevc", "dcamera.hevc", "ecamera.hevc"} + 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")] + + pm = messaging.PubMaster(["roadCameraState", "driverCameraState", "wideRoadCameraState"]) + vipc_server = VisionIpcServer("camerad") + for stream_type, frame_spec, _ in streams: + vipc_server.create_buffers_with_sizes(stream_type, 40, *(frame_spec)) + vipc_server.start_listener() num_segs = random.randint(2, 5) length = random.randint(1, 3) + os.environ["LOGGERD_SEGMENT_LENGTH"] = str(length) + managed_processes["loggerd"].start() + managed_processes["encoderd"].start() + assert pm.wait_for_readers_to_update("roadCameraState", timeout=5) - self._publish_camera_and_audio_messages(num_segs=num_segs, segment_length=length) + fps = 20.0 + for n in range(1, int(num_segs*length*fps)+1): + for stream_type, frame_spec, state in streams: + dat = np.empty(frame_spec[2], dtype=np.uint8) + vipc_server.send(stream_type, dat[:].flatten().tobytes(), n, n/fps, n/fps) + + camera_state = messaging.new_message(state) + frame = getattr(camera_state, state) + frame.frameId = n + pm.send(state, camera_state) + + for _, _, state in streams: + assert pm.wait_for_readers_to_update(state, timeout=5, dt=0.001) + + managed_processes["loggerd"].stop() + managed_processes["encoderd"].stop() route_path = str(self._get_latest_log_dir()).rsplit("--", 1)[0] for n in range(num_segs): p = Path(f"{route_path}--{n}") logged = {f.name for f in p.iterdir() if f.is_file()} diff = logged ^ expected_files - assert len(diff) == 0, f"didn't get all expected files. seg={n} {route_path=}, {diff=}\n{logged=} {expected_files=}" + assert len(diff) == 0, f"didn't get all expected files. run={_} seg={n} {route_path=}, {diff=}\n{logged=} {expected_files=}" def test_bootlog(self): # generate bootlog with fake launch log @@ -295,28 +281,3 @@ class TestLoggerd: segment_dir = self._get_latest_log_dir() assert getxattr(segment_dir, PRESERVE_ATTR_NAME) is None - - @pytest.mark.parametrize("record_front", [True, False]) - def test_record_front(self, record_front): - params = Params() - params.put_bool("RecordFront", record_front) - - self._publish_camera_and_audio_messages() - - dcamera_hevc_exists = os.path.exists(os.path.join(self._get_latest_log_dir(), 'dcamera.hevc')) - assert dcamera_hevc_exists == record_front - - @pytest.mark.parametrize("record_audio", [True, False]) - def test_record_audio(self, record_audio): - params = Params() - params.put_bool("RecordAudio", record_audio) - - 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'' - 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'))) - assert raw_audio_in_rlog == record_audio From 2b8a956f41ffcbc3b41f394662cd3afa6481efae Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 7 Jul 2025 21:39:00 -0700 Subject: [PATCH 39/74] athena: lower upload DHCP priority (#35648) * try this * draft * works * caps * should only need https * fix test --- system/athena/athenad.py | 28 ++++++++++++++++++++++------ system/athena/tests/test_athenad.py | 6 +++--- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/system/athena/athenad.py b/system/athena/athenad.py index 5a02d45c43..5df7eec3f0 100755 --- a/system/athena/athenad.py +++ b/system/athena/athenad.py @@ -22,6 +22,7 @@ from typing import cast from collections.abc import Callable import requests +from requests.adapters import HTTPAdapter, DEFAULT_POOLBLOCK from jsonrpc import JSONRPCResponseManager, dispatcher from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutException, create_connection) @@ -55,6 +56,11 @@ WS_FRAME_SIZE = 4096 DEVICE_STATE_UPDATE_INTERVAL = 1.0 # in seconds DEFAULT_UPLOAD_PRIORITY = 99 # higher number = lower priority +# https://bytesolutions.com/dscp-tos-cos-precedence-conversion-chart, +# https://en.wikipedia.org/wiki/Differentiated_services +UPLOAD_TOS = 0x20 # CS1, low priority background traffic +SSH_TOS = 0x90 # AF42, DSCP of 36/HDD_LINUX_AC_VI with the minimum delay flag + NetworkType = log.DeviceState.NetworkType UploadFileDict = dict[str, str | int | float | bool] @@ -63,6 +69,17 @@ UploadItemDict = dict[str, str | bool | int | float | dict[str, str]] UploadFilesToUrlResponse = dict[str, int | list[UploadItemDict] | list[str]] +class UploadTOSAdapter(HTTPAdapter): + def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs): + pool_kwargs["socket_options"] = [(socket.IPPROTO_IP, socket.IP_TOS, UPLOAD_TOS)] + super().init_poolmanager(connections, maxsize, block, **pool_kwargs) + + +UPLOAD_SESS = requests.Session() +UPLOAD_SESS.mount("http://", UploadTOSAdapter()) +UPLOAD_SESS.mount("https://", UploadTOSAdapter()) + + @dataclass class UploadFile: fn: str @@ -309,10 +326,10 @@ def _do_upload(upload_item: UploadItem, callback: Callable = None) -> requests.R stream = None try: stream, content_length = get_upload_stream(path, compress) - response = requests.put(upload_item.url, - data=CallbackReader(stream, callback, content_length) if callback else stream, - headers={**upload_item.headers, 'Content-Length': str(content_length)}, - timeout=30) + response = UPLOAD_SESS.put(upload_item.url, + data=CallbackReader(stream, callback, content_length) if callback else stream, + headers={**upload_item.headers, 'Content-Length': str(content_length)}, + timeout=30) return response finally: if stream: @@ -482,8 +499,7 @@ def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local enable_multithread=True) # Set TOS to keep connection responsive while under load. - # DSCP of 36/HDD_LINUX_AC_VI with the minimum delay flag - ws.sock.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, 0x90) + ws.sock.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, SSH_TOS) ssock, csock = socket.socketpair() local_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) diff --git a/system/athena/tests/test_athenad.py b/system/athena/tests/test_athenad.py index dd82325815..e559e0c05c 100644 --- a/system/athena/tests/test_athenad.py +++ b/system/athena/tests/test_athenad.py @@ -19,7 +19,7 @@ from cereal import messaging from openpilot.common.params import Params from openpilot.common.timeout import Timeout from openpilot.system.athena import athenad -from openpilot.system.athena.athenad import MAX_RETRY_COUNT, dispatcher +from openpilot.system.athena.athenad import MAX_RETRY_COUNT, UPLOAD_SESS, dispatcher from openpilot.system.athena.tests.helpers import HTTPRequestHandler, MockWebsocket, MockApi, EchoSocket from openpilot.selfdrive.test.helpers import http_server_context from openpilot.system.hardware.hw import Paths @@ -29,7 +29,7 @@ def seed_athena_server(host, port): with Timeout(2, 'HTTP Server seeding failed'): while True: try: - requests.put(f'http://{host}:{port}/qlog.zst', data='', timeout=10) + UPLOAD_SESS.put(f'http://{host}:{port}/qlog.zst', data='', timeout=10) break except requests.exceptions.ConnectionError: time.sleep(0.1) @@ -239,7 +239,7 @@ class TestAthenadMethods: @pytest.mark.parametrize("status,retry", [(500,True), (412,False)]) @with_upload_handler def test_upload_handler_retry(self, mocker, host, status, retry): - mock_put = mocker.patch('requests.put') + mock_put = mocker.patch('openpilot.system.athena.athenad.UPLOAD_SESS.put') mock_put.return_value.__enter__.return_value.status_code = status fn = self._create_file('qlog.zst') item = athenad.UploadItem(path=fn, url=f"{host}/qlog.zst", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True) From 2a5c628d865d9a95a40b3ea425a3c6a189a8e79d Mon Sep 17 00:00:00 2001 From: programanichiro <99449198+programanichiro@users.noreply.github.com> Date: Wed, 9 Jul 2025 02:39:57 +0900 Subject: [PATCH 40/74] Multilang: update ja translation. (#35655) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 日本語訳、追加&調整。 * 調整。 --- selfdrive/ui/translations/main_ja.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index d3dda756b4..6310fd9ecd 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -318,21 +318,25 @@ 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. - + ステアリングトルク応答のキャリブレーション完了。
@@ -808,13 +812,15 @@ This may take up to a minute. WARNING: Custom Software - + 警告: カスタムソフトウェア Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle. If you'd like to proceed, use https://flash.comma.ai to restore your device to a factory state later. - + サードパーティ製ソフトウェアをインストールする際は注意してください。サードパーティ製ソフトウェアはcommaによってテストされておらず、あなたのデバイスや車両に損害を与える可能性があります。 + +続行したい場合は、後でデバイスを工場出荷時の状態に戻すために https://flash.comma.ai を使用してください。 @@ -1163,11 +1169,11 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device 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 のドライブレコーダー映像に含まれます。 From de16c6fbe14e121c5e74cd944ce03a0e4672540d Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Tue, 8 Jul 2025 13:20:00 -0700 Subject: [PATCH 41/74] test: RecordFront and RecordAudio (fixed) (#35654) * record front and record audio tests * set tests to same group so they are sequential * add comment --- system/loggerd/tests/test_loggerd.py | 105 +++++++++++++++++++-------- 1 file changed, 73 insertions(+), 32 deletions(-) diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index 179e237a65..d1d0887d40 100644 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -97,6 +97,50 @@ class TestLoggerd: return sent_msgs + def _publish_camera_and_audio_messages(self, num_segs=1, segment_length=5): + d = DEVICE_CAMERAS[("tici", "ar0231")] + 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"), + ] + + pm = messaging.PubMaster([s for _, _, s in streams] + ["rawAudioData"]) + vipc_server = VisionIpcServer("camerad") + for stream_type, frame_spec, _ in streams: + vipc_server.create_buffers_with_sizes(stream_type, 40, *(frame_spec)) + vipc_server.start_listener() + + os.environ["LOGGERD_TEST"] = "1" + os.environ["LOGGERD_SEGMENT_LENGTH"] = str(segment_length) + managed_processes["loggerd"].start() + managed_processes["encoderd"].start() + assert pm.wait_for_readers_to_update("roadCameraState", timeout=5) + + fps = 20 + for n in range(1, int(num_segs * segment_length * fps) + 1): + # send video + for stream_type, frame_spec, state in streams: + dat = np.empty(frame_spec[2], dtype=np.uint8) + vipc_server.send(stream_type, dat[:].flatten().tobytes(), n, n / fps, n / fps) + + camera_state = messaging.new_message(state) + frame = getattr(camera_state, state) + frame.frameId = n + pm.send(state, camera_state) + + # send audio + msg = messaging.new_message('rawAudioData') + msg.rawAudioData.data = bytes(800 * 2) # 800 samples of int16 + msg.rawAudioData.sampleRate = 16000 + pm.send('rawAudioData', msg) + + for _, _, state in streams: + assert pm.wait_for_readers_to_update(state, timeout=5, dt=0.001) + + managed_processes["loggerd"].stop() + managed_processes["encoderd"].stop() + def test_init_data_values(self): os.environ["CLEAN"] = random.choice(["0", "1"]) @@ -138,51 +182,21 @@ class TestLoggerd: @pytest.mark.skip("FIXME: encoderd sometimes crashes in CI when running with pytest-xdist") def test_rotation(self): - os.environ["LOGGERD_TEST"] = "1" Params().put("RecordFront", "1") - d = DEVICE_CAMERAS[("tici", "ar0231")] expected_files = {"rlog.zst", "qlog.zst", "qcamera.ts", "fcamera.hevc", "dcamera.hevc", "ecamera.hevc"} - 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")] - - pm = messaging.PubMaster(["roadCameraState", "driverCameraState", "wideRoadCameraState"]) - vipc_server = VisionIpcServer("camerad") - for stream_type, frame_spec, _ in streams: - vipc_server.create_buffers_with_sizes(stream_type, 40, *(frame_spec)) - vipc_server.start_listener() num_segs = random.randint(2, 5) length = random.randint(1, 3) - os.environ["LOGGERD_SEGMENT_LENGTH"] = str(length) - managed_processes["loggerd"].start() - managed_processes["encoderd"].start() - assert pm.wait_for_readers_to_update("roadCameraState", timeout=5) - fps = 20.0 - for n in range(1, int(num_segs*length*fps)+1): - for stream_type, frame_spec, state in streams: - dat = np.empty(frame_spec[2], dtype=np.uint8) - vipc_server.send(stream_type, dat[:].flatten().tobytes(), n, n/fps, n/fps) - - camera_state = messaging.new_message(state) - frame = getattr(camera_state, state) - frame.frameId = n - pm.send(state, camera_state) - - for _, _, state in streams: - assert pm.wait_for_readers_to_update(state, timeout=5, dt=0.001) - - managed_processes["loggerd"].stop() - managed_processes["encoderd"].stop() + self._publish_camera_and_audio_messages(num_segs=num_segs, segment_length=length) route_path = str(self._get_latest_log_dir()).rsplit("--", 1)[0] for n in range(num_segs): p = Path(f"{route_path}--{n}") logged = {f.name for f in p.iterdir() if f.is_file()} diff = logged ^ expected_files - assert len(diff) == 0, f"didn't get all expected files. run={_} seg={n} {route_path=}, {diff=}\n{logged=} {expected_files=}" + assert len(diff) == 0, f"didn't get all expected files. seg={n} {route_path=}, {diff=}\n{logged=} {expected_files=}" def test_bootlog(self): # generate bootlog with fake launch log @@ -281,3 +295,30 @@ class TestLoggerd: segment_dir = self._get_latest_log_dir() assert getxattr(segment_dir, PRESERVE_ATTR_NAME) is None + + @pytest.mark.xdist_group("camera_encoder_tests") # setting xdist group ensures tests are run in same worker, prevents encoderd from crashing + @pytest.mark.parametrize("record_front", [True, False]) + def test_record_front(self, record_front): + params = Params() + params.put_bool("RecordFront", record_front) + + self._publish_camera_and_audio_messages() + + dcamera_hevc_exists = os.path.exists(os.path.join(self._get_latest_log_dir(), 'dcamera.hevc')) + assert dcamera_hevc_exists == record_front + + @pytest.mark.xdist_group("camera_encoder_tests") # setting xdist group ensures tests are run in same worker, prevents encoderd from crashing + @pytest.mark.parametrize("record_audio", [True, False]) + def test_record_audio(self, record_audio): + params = Params() + params.put_bool("RecordAudio", record_audio) + + 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'' + 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'))) + assert raw_audio_in_rlog == record_audio From 9aed28a216d55adee291923d01f4632e15646532 Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Tue, 8 Jul 2025 14:07:16 -0700 Subject: [PATCH 42/74] bump opendbc --- opendbc_repo | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index f4ec91dcb3..3d341d0b7f 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit f4ec91dcb33990a9a6587aac93eb0677e4bac3ac +Subproject commit 3d341d0b7f0b20c57a0cc10e5d1485a734ff08af diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 2d68ea0eaa..79c03c65a8 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -254f911c7c35c66eb12223fb0dc262d829a42e80 \ No newline at end of file +de16c6fbe14e121c5e74cd944ce03a0e4672540d \ No newline at end of file From 147ce0217876ab0a81a28ddc52ec1021527688c0 Mon Sep 17 00:00:00 2001 From: Alexandre Nobuharu Sato <66435071+AlexandreSato@users.noreply.github.com> Date: Tue, 8 Jul 2025 18:12:32 -0300 Subject: [PATCH 43/74] Multilang: update pt-BR translation (#35657) --- selfdrive/ui/translations/main_pt-BR.ts | 28 +++++++++++++++---------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 6c4270fd32..0417b406a2 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -308,31 +308,35 @@ 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. @@ -388,7 +392,7 @@ O Modo Firehose permite maximizar o envio de dados de treinamento para melhorar Firehose Mode - + Modo Firehose @@ -813,13 +817,15 @@ Isso pode levar até um minuto. WARNING: Custom Software - + AVISO: Software Personalizado Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle. If you'd like to proceed, use https://flash.comma.ai to restore your device to a factory state later. - + Tenha cuidado ao instalar software de terceiros. Softwares de terceiros não foram testados pela comma e podem causar danos ao seu dispositivo e/ou veículo. + +Se quiser continuar, use https://flash.comma.ai para restaurar seu dispositivo ao estado de fábrica mais tarde. @@ -1168,11 +1174,11 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device 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. From 20fdb686ca050ab70da1dd306ee679ad116ad600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 8 Jul 2025 14:12:46 -0700 Subject: [PATCH 44/74] latcontrol torque: remove option to feed back on localizer (#35659) * Localizer is too laggy for control * typo * typo * fix test * fix imports * Revert "fix imports" This reverts commit 5074f8050170f974b451e00d9fdc752f09a47d57. * fix improt * import --- selfdrive/controls/controlsd.py | 2 +- selfdrive/controls/lib/latcontrol_angle.py | 2 +- selfdrive/controls/lib/latcontrol_pid.py | 2 +- selfdrive/controls/lib/latcontrol_torque.py | 14 +++----------- selfdrive/controls/lib/tests/test_latcontrol.py | 11 +++-------- 5 files changed, 9 insertions(+), 22 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 5ecff1ccdf..ebfd0c734e 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -121,7 +121,7 @@ class Controls: actuators.curvature = self.desired_curvature steer, steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp, self.steer_limited_by_controls, self.desired_curvature, - self.calibrated_pose, curvature_limited) # TODO what if not available + curvature_limited) # TODO what if not available actuators.torque = float(steer) actuators.steeringAngleDeg = float(steeringAngleDeg) # Ensure no NaNs/Infs diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py index f3f1bc5acf..807161735c 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -12,7 +12,7 @@ class LatControlAngle(LatControl): self.sat_check_min_speed = 5. self.use_steer_limited_by_controls = CP.brand == "tesla" - def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited): + def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, curvature_limited): 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 1f1199565e..4e4e0772fe 100644 --- a/selfdrive/controls/lib/latcontrol_pid.py +++ b/selfdrive/controls/lib/latcontrol_pid.py @@ -17,7 +17,7 @@ class LatControlPID(LatControl): super().reset() self.pid.reset() - def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited): + def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, curvature_limited): 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 3aef57baca..f8455b9bda 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -29,7 +29,6 @@ class LatControlTorque(LatControl): self.pid = PIDController(self.torque_params.kp, self.torque_params.ki, k_f=self.torque_params.kf, pos_limit=self.steer_max, neg_limit=-self.steer_max) self.torque_from_lateral_accel = CI.torque_from_lateral_accel() - self.use_steering_angle = self.torque_params.useSteeringAngle self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction): @@ -37,22 +36,15 @@ class LatControlTorque(LatControl): self.torque_params.latAccelOffset = latAccelOffset self.torque_params.friction = friction - def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited): + def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, curvature_limited): pid_log = log.ControlsState.LateralTorqueState.new_message() if not active: output_torque = 0.0 pid_log.active = False else: - actual_curvature_vm = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll) + actual_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll) roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY - if self.use_steering_angle: - actual_curvature = actual_curvature_vm - curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0)) - else: - assert calibrated_pose is not None - actual_curvature_pose = calibrated_pose.angular_velocity.yaw / CS.vEgo - actual_curvature = np.interp(CS.vEgo, [2.0, 5.0], [actual_curvature_vm, actual_curvature_pose]) - curvature_deadzone = 0.0 + 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 # desired rate is the desired rate of change in the setpoint, not the absolute desired curvature diff --git a/selfdrive/controls/lib/tests/test_latcontrol.py b/selfdrive/controls/lib/tests/test_latcontrol.py index a01f013ddd..f32e20b1e5 100644 --- a/selfdrive/controls/lib/tests/test_latcontrol.py +++ b/selfdrive/controls/lib/tests/test_latcontrol.py @@ -9,8 +9,6 @@ from opendbc.car.vehicle_model import VehicleModel 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 -from openpilot.selfdrive.locationd.helpers import Pose -from openpilot.common.mock.generators import generate_livePose class TestLatControl: @@ -30,18 +28,15 @@ class TestLatControl: params = log.LiveParametersData.new_message() - lp = generate_livePose() - pose = Pose.from_live_pose(lp.livePose) - # Saturate for curvature limited and controller limited for _ in range(1000): - _, _, lac_log = controller.update(True, CS, VM, params, False, 0, pose, True) + _, _, lac_log = controller.update(True, CS, VM, params, False, 0, True) assert lac_log.saturated for _ in range(1000): - _, _, lac_log = controller.update(True, CS, VM, params, False, 0, pose, False) + _, _, lac_log = controller.update(True, CS, VM, params, False, 0, False) assert not lac_log.saturated for _ in range(1000): - _, _, lac_log = controller.update(True, CS, VM, params, False, 1, pose, False) + _, _, lac_log = controller.update(True, CS, VM, params, False, 1, False) assert lac_log.saturated From c284edcd33bff645176b08fd21b52df9217fb7d2 Mon Sep 17 00:00:00 2001 From: Jimmy <9859727+Quantizr@users.noreply.github.com> Date: Tue, 8 Jul 2025 14:20:34 -0700 Subject: [PATCH 45/74] test: fix test_rotation in test_loggerd.py (#35658) fix test_rotation --- system/loggerd/tests/test_loggerd.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index d1d0887d40..8e2f52a731 100644 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -180,14 +180,14 @@ class TestLoggerd: assert getattr(initData, initData_key) == v assert logged_params[param_key].decode() == v - @pytest.mark.skip("FIXME: encoderd sometimes crashes in CI when running with pytest-xdist") + @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): Params().put("RecordFront", "1") expected_files = {"rlog.zst", "qlog.zst", "qcamera.ts", "fcamera.hevc", "dcamera.hevc", "ecamera.hevc"} - num_segs = random.randint(2, 5) - length = random.randint(1, 3) + num_segs = random.randint(2, 3) + length = random.randint(4, 5) # H264 encoder uses 40 lookahead frames and does B-frame reordering, so minimum 3 seconds before qcam output self._publish_camera_and_audio_messages(num_segs=num_segs, segment_length=length) From 12a5d8c2db790404043d4c6a6e3de340409ab88f Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Tue, 8 Jul 2025 18:31:44 -0400 Subject: [PATCH 46/74] tools: update to commaCarSegments v2 (#35660) --- tools/lib/comma_car_segments.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/lib/comma_car_segments.py b/tools/lib/comma_car_segments.py index fe9c350a9b..4d9eb08be2 100644 --- a/tools/lib/comma_car_segments.py +++ b/tools/lib/comma_car_segments.py @@ -4,7 +4,7 @@ import requests # Forks with additional car support can fork the commaCarSegments repo on huggingface or host the LFS files themselves COMMA_CAR_SEGMENTS_REPO = os.environ.get("COMMA_CAR_SEGMENTS_REPO", "https://huggingface.co/datasets/commaai/commaCarSegments") -COMMA_CAR_SEGMENTS_BRANCH = os.environ.get("COMMA_CAR_SEGMENTS_BRANCH", "main") +COMMA_CAR_SEGMENTS_BRANCH = os.environ.get("COMMA_CAR_SEGMENTS_BRANCH", "v2") COMMA_CAR_SEGMENTS_LFS_INSTANCE = os.environ.get("COMMA_CAR_SEGMENTS_LFS_INSTANCE", COMMA_CAR_SEGMENTS_REPO) def get_comma_car_segments_database(): @@ -86,5 +86,5 @@ def get_repo_url(path): return get_repo_raw_url(path) -def get_url(route, segment, file="rlog.bz2"): +def get_url(route, segment, file="rlog.zst"): return get_repo_url(f"segments/{route.replace('|', '/')}/{segment}/{file}") From f088b4320c9cca9129ead0ab2598dd5a9eb3af33 Mon Sep 17 00:00:00 2001 From: eFini Date: Wed, 9 Jul 2025 09:32:32 +0800 Subject: [PATCH 47/74] Multilang: update zh translation (#35662) --- selfdrive/ui/translations/main_zh-CHS.ts | 38 ++++++++++++---------- selfdrive/ui/translations/main_zh-CHT.ts | 40 ++++++++++++++---------- 2 files changed, 45 insertions(+), 33 deletions(-) diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 806ccf27e9..d9ec242e59 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -308,31 +308,35 @@ 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. - + 转向扭矩响应校准已完成。 @@ -361,11 +365,11 @@ Steering lag calibration is complete. 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 模式”允许您最大化上传训练数据,以改进 openpilot 的驾驶模型。更多数据意味着更强大的模型,也就意味着更优秀的“实验模式”。 Firehose Mode: ACTIVE - 训练数据上传模式:激活中 + Firehose 模式:激活中 ACTIVE @@ -387,7 +391,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Firehose Mode - + Firehose 模式 @@ -709,7 +713,7 @@ This may take up to a minute. Firehose - 训练上传 + Firehose @@ -808,13 +812,15 @@ This may take up to a minute. WARNING: Custom Software - + 警告:自定义软件 Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle. If you'd like to proceed, use https://flash.comma.ai to restore your device to a factory state later. - + 请谨慎安装第三方软件。第三方软件未经 comma 测试,可能会损害您的设备和车辆。 + +如果您决定继续,后续可通过 https://flash.comma.ai 将设备恢复到出厂状态。 @@ -1159,15 +1165,15 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device Changing this setting will restart openpilot if the car is powered on. - 如果车辆已通电,更改此设置将会重新启动 openpilot。 + 如果车辆已通电,更改此设置将会重新启动 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 的行车记录仪视频中。 @@ -1217,7 +1223,7 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device <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> + <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose 模式 <span style='font-family: Noto Color Emoji;'>🔥</span> diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 664be84d9f..9530f8920d 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -308,31 +308,35 @@ 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. - + 轉向扭矩反應校準已完成。 @@ -361,11 +365,11 @@ Steering lag calibration is complete. 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 模式」可讓您最大化上傳訓練數據,以改進 openpilot 的駕駛模型。更多數據代表更強大的模型,也就意味著更優秀的「實驗模式」。 Firehose Mode: ACTIVE - 訓練資料上傳模式:啟用中 + Firehose 模式:啟用中 ACTIVE @@ -373,7 +377,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp 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(以及特定的分支)可以用於訓練。 + 為了達到最佳效果,請每週將您的裝置帶回室內,並連接至優質的 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. @@ -387,7 +391,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Firehose Mode - + Firehose 模式 @@ -709,7 +713,7 @@ This may take up to a minute. Firehose - 訓練上傳 + Firehose @@ -808,13 +812,15 @@ This may take up to a minute. WARNING: Custom Software - + 警告:自訂軟體 Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle. If you'd like to proceed, use https://flash.comma.ai to restore your device to a factory state later. - + 請謹慎安裝第三方軟體。第三方軟體未經 comma 測試,可能會損壞您的裝置及車輛。 + +若您仍要繼續,日後可使用 https://flash.comma.ai 將您的裝置恢復至出廠狀態。 @@ -1159,15 +1165,15 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device Changing this setting will restart openpilot if the car is powered on. - 若車輛電源為開啟狀態,變更此設定將會重新啟動 openpilot。 + 若車輛電源為開啟狀態,變更此設定將會重新啟動 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 的行車記錄器影片中。 @@ -1217,7 +1223,7 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device <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> + <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose 模式 <span style='font-family: Noto Color Emoji;'>🔥</span> From 9f9940c5a33b16108142598f62dfbc2975f48da1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 8 Jul 2025 23:10:45 -0700 Subject: [PATCH 48/74] ui.py: add bg to radar points fix --- tools/replay/lib/ui_helpers.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tools/replay/lib/ui_helpers.py b/tools/replay/lib/ui_helpers.py index 39431eca10..b90cbd93b0 100644 --- a/tools/replay/lib/ui_helpers.py +++ b/tools/replay/lib/ui_helpers.py @@ -198,15 +198,12 @@ def maybe_update_radar_points(lt, lid_overlay): ar_pts = {} for track in lt: ar_pts[track.trackId] = [track.dRel, track.yRel, track.vRel, track.aRel] - for ids, pt in ar_pts.items(): + for pt in ar_pts.values(): # negative here since radar is left positive px, py = to_topdown_pt(pt[0], -pt[1]) if px != -1: - color = 255 - if int(ids) == 1: - lid_overlay[px - 2:px + 2, py - 10:py + 10] = 100 - else: - lid_overlay[px - 2:px + 2, py - 2:py + 2] = color + lid_overlay[px - 4:px + 4, py - 4:py + 4] = 0 + lid_overlay[px - 2:px + 2, py - 2:py + 2] = 255 def get_blank_lid_overlay(UP): lid_overlay = np.zeros((UP.lidar_x, UP.lidar_y), 'uint8') From a82ff7536da1918525058c915527207125f21d49 Mon Sep 17 00:00:00 2001 From: Lee Jong Mun <43285072+crwusiz@users.noreply.github.com> Date: Thu, 10 Jul 2025 00:34:11 +0900 Subject: [PATCH 49/74] Multilang: update kor translation (#35666) Multilang: kor translation update --- selfdrive/ui/translations/main_ko.ts | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 819392c8c8..9f45ca6971 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -308,31 +308,35 @@ 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. - + 조향 토크 응답 캘리브레이션이 완료되었습니다. @@ -387,7 +391,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Firehose Mode - + 파이어호스 모드 @@ -808,13 +812,15 @@ This may take up to a minute. WARNING: Custom Software - + 경고: 커스텀 소프트웨어 Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle. If you'd like to proceed, use https://flash.comma.ai to restore your device to a factory state later. - + 타사 소프트웨어를 설치할 때는 주의하십시오. 타사 소프트웨어는 comma에 의해 테스트되지 않았으며 장치나 차량에 손상을 줄 수 있습니다. + +진행하려면 https://flash.comma.ai를 사용하여 나중에 장치를 공장 초기화하세요. @@ -1163,11 +1169,11 @@ If you'd like to proceed, use https://flash.comma.ai to restore your device 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의 대시캠 비디오에 포함됩니다. From f5bbedb5c5ed93e0b97b54d7f73191d2de47cd41 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 9 Jul 2025 10:53:46 -0700 Subject: [PATCH 50/74] lagd enable in release --- RELEASES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index fda4cf4dbf..58d0d07a74 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -3,7 +3,8 @@ Version 0.10.0 (2025-07-07) * New driving model * Lead car ground-truth fixes * Ported over VAE from the MLSIM stack - * New training architecture described in CVPR paper + * New training architecture described in CVPR paper +* Enable live-learned steering actuation delay * Opt-in audio recording for dashcam video Version 0.9.9 (2025-05-23) From 2b56a6c37e2cc43a0c3fe27e8025e20ed8282d21 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 9 Jul 2025 10:58:47 -0700 Subject: [PATCH 51/74] Revert "tools: update to commaCarSegments v2 (#35660)" This reverts commit 12a5d8c2db790404043d4c6a6e3de340409ab88f. --- tools/lib/comma_car_segments.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/lib/comma_car_segments.py b/tools/lib/comma_car_segments.py index 4d9eb08be2..fe9c350a9b 100644 --- a/tools/lib/comma_car_segments.py +++ b/tools/lib/comma_car_segments.py @@ -4,7 +4,7 @@ import requests # Forks with additional car support can fork the commaCarSegments repo on huggingface or host the LFS files themselves COMMA_CAR_SEGMENTS_REPO = os.environ.get("COMMA_CAR_SEGMENTS_REPO", "https://huggingface.co/datasets/commaai/commaCarSegments") -COMMA_CAR_SEGMENTS_BRANCH = os.environ.get("COMMA_CAR_SEGMENTS_BRANCH", "v2") +COMMA_CAR_SEGMENTS_BRANCH = os.environ.get("COMMA_CAR_SEGMENTS_BRANCH", "main") COMMA_CAR_SEGMENTS_LFS_INSTANCE = os.environ.get("COMMA_CAR_SEGMENTS_LFS_INSTANCE", COMMA_CAR_SEGMENTS_REPO) def get_comma_car_segments_database(): @@ -86,5 +86,5 @@ def get_repo_url(path): return get_repo_raw_url(path) -def get_url(route, segment, file="rlog.zst"): +def get_url(route, segment, file="rlog.bz2"): return get_repo_url(f"segments/{route.replace('|', '/')}/{segment}/{file}") From 17d9b1269342166d5453c8cbe3412a803319ad87 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Wed, 9 Jul 2025 16:56:49 -0700 Subject: [PATCH 52/74] ci: don't push process replay diff automatically to Azure (#35670) remove --- .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 a78b51950d..72200a163f 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -203,7 +203,7 @@ jobs: name: process_replay_diff.txt path: selfdrive/test/process_replay/diff.txt - name: Upload reference logs - if: ${{ failure() && steps.print-diff.outcome == 'success' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }} + if: false # TODO: move this to github instead of azure run: | ${{ env.RUN }} "unset PYTHONWARNINGS && AZURE_TOKEN='$AZURE_TOKEN' python3 selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" - name: Run regen From c9731d6aa92255c6f52bb1b879f854b452f9a6be Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 9 Jul 2025 17:15:04 -0700 Subject: [PATCH 53/74] raylib: show touches flag for debugging (#35671) * show touches * type --- system/ui/lib/application.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 805fdcc71f..5faa51b640 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -4,19 +4,21 @@ import os import time import pyray as rl from collections.abc import Callable +from collections import deque from dataclasses import dataclass from enum import IntEnum from importlib.resources import as_file, files from openpilot.common.swaglog import cloudlog from openpilot.system.hardware import HARDWARE -DEFAULT_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 ENABLE_VSYNC = os.getenv("ENABLE_VSYNC", "1") == "1" 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")) @@ -61,6 +63,9 @@ class GuiApplication: self._trace_log_callback = None self._modal_overlay = ModalOverlay() + # Debug variables + self._mouse_history: deque[rl.Vector2] = deque(maxlen=DEFAULT_FPS * 2) + def request_close(self): self._window_close_requested = True @@ -190,6 +195,19 @@ class GuiApplication: if SHOW_FPS: rl.draw_fps(10, 10) + if SHOW_TOUCHES: + if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT): + self._mouse_history.clear() + self._mouse_history.append(rl.get_mouse_position()) + + 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) + rl.end_drawing() self._monitor_fps() except KeyboardInterrupt: From ccc2e9297bae4b6849f7881a25d3b10d4e1b224d Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Wed, 9 Jul 2025 19:42:39 -0700 Subject: [PATCH 54/74] pigeond: save almanac periodically (#35661) * save * save * check * comments * remove --- system/ubloxd/pigeond.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/system/ubloxd/pigeond.py b/system/ubloxd/pigeond.py index 2194a4b9d2..e6a03ffbdf 100755 --- a/system/ubloxd/pigeond.py +++ b/system/ubloxd/pigeond.py @@ -136,6 +136,17 @@ class TTYPigeon: return True return False +def save_almanac(pigeon: TTYPigeon) -> None: + # store almanac in flash + pigeon.send(b"\xB5\x62\x09\x14\x04\x00\x00\x00\x00\x00\x21\xEC") + try: + if pigeon.wait_for_ack(ack=UBLOX_SOS_ACK, nack=UBLOX_SOS_NACK): + cloudlog.info("Done storing almanac") + else: + cloudlog.error("Error storing almanac") + except TimeoutError: + pass + def init_baudrate(pigeon: TTYPigeon): # ublox default setting on startup is 9600 baudrate pigeon.set_baud(9600) @@ -245,16 +256,6 @@ def deinitialize_and_exit(pigeon: TTYPigeon | None): # controlled GNSS stop pigeon.send(b"\xB5\x62\x06\x04\x04\x00\x00\x00\x08\x00\x16\x74") - # store almanac in flash - pigeon.send(b"\xB5\x62\x09\x14\x04\x00\x00\x00\x00\x00\x21\xEC") - try: - if pigeon.wait_for_ack(ack=UBLOX_SOS_ACK, nack=UBLOX_SOS_NACK): - cloudlog.warning("Done storing almanac") - else: - cloudlog.error("Error storing almanac") - except TimeoutError: - pass - # turn off power and exit cleanly set_power(False) sys.exit(0) @@ -281,6 +282,7 @@ def run_receiving(pigeon: TTYPigeon, pm: messaging.PubMaster, duration: int = 0) def end_condition(): return True if duration == 0 else time.monotonic() - start_time < duration + last_almanac_save = time.monotonic() while end_condition(): dat = pigeon.receive() if len(dat) > 0: @@ -294,6 +296,11 @@ def run_receiving(pigeon: TTYPigeon, pm: messaging.PubMaster, duration: int = 0) msg = messaging.new_message('ubloxRaw', len(dat), valid=True) msg.ubloxRaw = dat[:] pm.send('ubloxRaw', msg) + + # save almanac every 5 minutes + if (time.monotonic() - last_almanac_save) > 60*5: + save_almanac(pigeon) + last_almanac_save = time.monotonic() else: # prevent locking up a CPU core if ublox disconnects time.sleep(0.001) From ddfda8a6ec3828b9d0fea1c0d857c4e7a4cf4231 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 20:22:40 -0700 Subject: [PATCH 55/74] [bot] Update translations (#35668) Update translations Co-authored-by: Vehicle Researcher --- selfdrive/ui/translations/main_ja.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 6310fd9ecd..c48b7bade6 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -326,7 +326,7 @@ Steering lag calibration is %1% complete. Steering lag calibration is complete. - + ステアリング遅延のキャリブレーション完了。 From 65381279f477e67db88e8a8cea8e7a174df67d45 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 9 Jul 2025 20:24:05 -0700 Subject: [PATCH 56/74] raylib: disable vsync (#35674) disable vsync --- system/ui/lib/application.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 5faa51b640..ff81fc69ad 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -16,10 +16,10 @@ FPS_LOG_INTERVAL = 5 # Seconds between logging FPS drops FPS_DROP_THRESHOLD = 0.9 # FPS drop threshold for triggering a warning FPS_CRITICAL_THRESHOLD = 0.5 # Critical threshold for triggering strict actions -ENABLE_VSYNC = os.getenv("ENABLE_VSYNC", "1") == "1" -SHOW_FPS = os.getenv("SHOW_FPS") == '1' -SHOW_TOUCHES = os.getenv("SHOW_TOUCHES") == '1' -STRICT_MODE = os.getenv("STRICT_MODE") == '1' +ENABLE_VSYNC = os.getenv("ENABLE_VSYNC", "0") == "1" +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")) DEFAULT_TEXT_SIZE = 60 From 13c5c4dacce48f47f60025bc29ac67a44e3038a2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 9 Jul 2025 21:33:19 -0700 Subject: [PATCH 57/74] raylib: don't drop touch events on device (#35672) * mouse thread * instanciate mouse * type that * pc handling * use mouse event list in widget * use events in scroll panel * no stop that * hack for now * typing * run * clean up --- selfdrive/ui/layouts/settings/settings.py | 4 +- selfdrive/ui/layouts/sidebar.py | 4 +- system/ui/lib/application.py | 97 +++++++++++++++++++++-- system/ui/lib/list_view.py | 4 +- system/ui/lib/scroll_panel.py | 39 +++++---- system/ui/lib/toggle.py | 3 +- system/ui/lib/widget.py | 23 +++--- 7 files changed, 134 insertions(+), 40 deletions(-) diff --git a/selfdrive/ui/layouts/settings/settings.py b/selfdrive/ui/layouts/settings/settings.py index 4a29f408ee..d4dc420b7f 100644 --- a/selfdrive/ui/layouts/settings/settings.py +++ b/selfdrive/ui/layouts/settings/settings.py @@ -7,7 +7,7 @@ from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout -from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.selfdrive.ui.layouts.network import NetworkLayout from openpilot.system.ui.lib.widget import Widget @@ -132,7 +132,7 @@ class SettingsLayout(Widget): if panel.instance: panel.instance.render(content_rect) - def _handle_mouse_release(self, mouse_pos: rl.Vector2) -> bool: + 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: diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index 13f69b79d4..cb8c6d472e 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 +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.widget import Widget @@ -135,7 +135,7 @@ class Sidebar(Widget): else: self._panda_status.update("VEHICLE", "ONLINE", Colors.GOOD) - def _handle_mouse_release(self, mouse_pos: rl.Vector2): + def _handle_mouse_release(self, mouse_pos: MousePos): if rl.check_collision_point_rec(mouse_pos, SETTINGS_BTN): if self._on_settings_click: self._on_settings_click() diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index ff81fc69ad..12d825b99f 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -3,18 +3,22 @@ import cffi import os import time import pyray as rl +import threading from collections.abc import Callable from collections import deque from dataclasses import dataclass from enum import IntEnum +from typing import NamedTuple from importlib.resources import as_file, files from openpilot.common.swaglog import cloudlog -from openpilot.system.hardware import HARDWARE +from openpilot.system.hardware import HARDWARE, PC +from openpilot.common.realtime import Ratekeeper 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 +MOUSE_THREAD_RATE = 140 # touch controller runs at 140Hz ENABLE_VSYNC = os.getenv("ENABLE_VSYNC", "0") == "1" SHOW_FPS = os.getenv("SHOW_FPS") == "1" @@ -47,6 +51,68 @@ class ModalOverlay: callback: Callable | None = None +class MousePos(NamedTuple): + x: float + y: float + + +class MouseEvent(NamedTuple): + pos: MousePos + left_pressed: bool + left_released: bool + left_down: bool + t: float + + +class MouseState: + def __init__(self): + self._events: deque[MouseEvent] = deque(maxlen=MOUSE_THREAD_RATE) # bound event list + self._prev_mouse_event: MouseEvent | None = None + + self._rk = Ratekeeper(MOUSE_THREAD_RATE) + self._lock = threading.Lock() + self._exit_event = threading.Event() + self._thread = None + + def get_events(self) -> list[MouseEvent]: + with self._lock: + events = list(self._events) + self._events.clear() + return events + + def start(self): + self._exit_event.clear() + if self._thread is None or not self._thread.is_alive(): + self._thread = threading.Thread(target=self._run_thread, daemon=True) + self._thread.start() + + def stop(self): + self._exit_event.set() + if self._thread is not None and self._thread.is_alive(): + self._thread.join() + + def _run_thread(self): + while not self._exit_event.is_set(): + rl.poll_input_events() + self._handle_mouse_event() + self._rk.keep_time() + + def _handle_mouse_event(self): + mouse_pos = rl.get_mouse_position() + ev = MouseEvent( + MousePos(mouse_pos.x, mouse_pos.y), + rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT), + rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT), + rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT), + time.monotonic(), + ) + # Only add changes + if self._prev_mouse_event is None or ev[:-1] != self._prev_mouse_event[:-1]: + with self._lock: + self._events.append(ev) + self._prev_mouse_event = ev + + class GuiApplication: def __init__(self, width: int, height: int): self._fonts: dict[FontWeight, rl.Font] = {} @@ -63,8 +129,11 @@ class GuiApplication: self._trace_log_callback = None self._modal_overlay = ModalOverlay() + self._mouse = MouseState() + self._mouse_events: list[MouseEvent] = [] + # Debug variables - self._mouse_history: deque[rl.Vector2] = deque(maxlen=DEFAULT_FPS * 2) + self._mouse_history: deque[MousePos] = deque(maxlen=MOUSE_THREAD_RATE) def request_close(self): self._window_close_requested = True @@ -94,6 +163,9 @@ class GuiApplication: self._set_styles() self._load_fonts() + if not PC: + self._mouse.start() + def set_modal_overlay(self, overlay, callback: Callable | None = None): self._modal_overlay = ModalOverlay(overlay=overlay, callback=callback) @@ -154,11 +226,25 @@ class GuiApplication: rl.unload_render_texture(self._render_texture) self._render_texture = None + if not PC: + self._mouse.stop() + rl.close_window() + @property + def mouse_events(self) -> list[MouseEvent]: + return self._mouse_events + def render(self): try: 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 + self._mouse._handle_mouse_event() + + # Store all mouse events for the current frame + self._mouse_events = self._mouse.get_events() + if self._render_texture: rl.begin_texture_mode(self._render_texture) rl.clear_background(rl.BLACK) @@ -196,9 +282,10 @@ class GuiApplication: rl.draw_fps(10, 10) if SHOW_TOUCHES: - if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT): - self._mouse_history.clear() - self._mouse_history.append(rl.get_mouse_position()) + for mouse_event in self._mouse_events: + if mouse_event.left_pressed: + self._mouse_history.clear() + self._mouse_history.append(mouse_event.pos) if self._mouse_history: mouse_pos = self._mouse_history[-1] diff --git a/system/ui/lib/list_view.py b/system/ui/lib/list_view.py index f5581be23e..4b6f434de3 100644 --- a/system/ui/lib/list_view.py +++ b/system/ui/lib/list_view.py @@ -2,7 +2,7 @@ import os import pyray as rl from collections.abc import Callable from abc import ABC -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.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.button import gui_button, ButtonStyle @@ -229,7 +229,7 @@ class ListItem(Widget): super().set_parent_rect(parent_rect) self._rect.width = parent_rect.width - def _handle_mouse_release(self, mouse_pos: rl.Vector2): + def _handle_mouse_release(self, mouse_pos: MousePos): if not self.is_visible: return diff --git a/system/ui/lib/scroll_panel.py b/system/ui/lib/scroll_panel.py index 32f3b7b575..9f01865b9a 100644 --- a/system/ui/lib/scroll_panel.py +++ b/system/ui/lib/scroll_panel.py @@ -1,6 +1,8 @@ +import time import pyray as rl from collections import deque from enum import IntEnum +from openpilot.system.ui.lib.application import gui_app, MouseEvent, MousePos # Scroll constants for smooth scrolling behavior MOUSE_WHEEL_SCROLL_SPEED = 30 @@ -38,51 +40,54 @@ class GuiScrollPanel: 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), False, False, False, time.monotonic())]: + 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 - # Calculate time delta - current_time = rl.get_time() - - mouse_pos = rl.get_mouse_position() max_scroll_y = max(content.height - bounds.height, 0) # Start dragging on mouse press - if rl.check_collision_point_rec(mouse_pos, bounds) and rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT): + if 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_pos.x >= scrollbar_x: + 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_pos.y - self._start_mouse_y = mouse_pos.y - self._last_drag_time = current_time + 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 rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT): - delta_y = mouse_pos.y - self._last_mouse_y + if mouse_event.left_down: + delta_y = mouse_event.pos.y - self._last_mouse_y # Track velocity for inertia - time_since_last_drag = current_time - self._last_drag_time + time_since_last_drag = mouse_event.t - self._last_drag_time if time_since_last_drag > 0: - drag_velocity = delta_y / time_since_last_drag / 60.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 = current_time + self._last_drag_time = mouse_event.t # Detect actual dragging - total_drag = abs(mouse_pos.y - self._start_mouse_y) + total_drag = abs(mouse_event.pos.y - self._start_mouse_y) if total_drag > DRAG_THRESHOLD: self._is_dragging = True @@ -96,9 +101,9 @@ class GuiScrollPanel: scroll_ratio = content.height / bounds.height self._offset.y -= delta_y * scroll_ratio - self._last_mouse_y = mouse_pos.y + self._last_mouse_y = mouse_event.pos.y - elif rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT): + elif mouse_event.left_released: # Calculate flick velocity if self._velocity_history: total_weight = 0 diff --git a/system/ui/lib/toggle.py b/system/ui/lib/toggle.py index 8ed9a655ec..2edf2ff11c 100644 --- a/system/ui/lib/toggle.py +++ b/system/ui/lib/toggle.py @@ -1,4 +1,5 @@ import pyray as rl +from openpilot.system.ui.lib.application import MousePos from openpilot.system.ui.lib.widget import Widget ON_COLOR = rl.Color(51, 171, 76, 255) @@ -23,7 +24,7 @@ class Toggle(Widget): def set_rect(self, rect: rl.Rectangle): self._rect = rl.Rectangle(rect.x, rect.y, WIDTH, HEIGHT) - def _handle_mouse_release(self, mouse_pos: rl.Vector2): + def _handle_mouse_release(self, mouse_pos: MousePos): if not self._enabled: return diff --git a/system/ui/lib/widget.py b/system/ui/lib/widget.py index 3539f5594b..7a436b8e9f 100644 --- a/system/ui/lib/widget.py +++ b/system/ui/lib/widget.py @@ -2,6 +2,7 @@ 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 class DialogResult(IntEnum): @@ -66,18 +67,18 @@ class Widget(abc.ABC): ret = self._render(self._rect) # Keep track of whether mouse down started within the widget's rectangle - mouse_pos = rl.get_mouse_position() - if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT) and self._touch_valid(): - if rl.check_collision_point_rec(mouse_pos, self._rect): - self._is_pressed = True + for mouse_event in gui_app.mouse_events: + if mouse_event.left_pressed and self._touch_valid(): + if rl.check_collision_point_rec(mouse_event.pos, self._rect): + self._is_pressed = True - elif not self._touch_valid(): - self._is_pressed = False + elif not self._touch_valid(): + self._is_pressed = False - elif rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT): - if self._is_pressed and rl.check_collision_point_rec(mouse_pos, self._rect): - self._handle_mouse_release(mouse_pos) - self._is_pressed = False + elif mouse_event.left_released: + if self._is_pressed and rl.check_collision_point_rec(mouse_event.pos, self._rect): + self._handle_mouse_release(mouse_event.pos) + self._is_pressed = False return ret @@ -91,6 +92,6 @@ class Widget(abc.ABC): def _update_layout_rects(self) -> None: """Optionally update any layout rects on Widget rect change.""" - def _handle_mouse_release(self, mouse_pos: rl.Vector2) -> bool: + def _handle_mouse_release(self, mouse_pos: MousePos) -> bool: """Optionally handle mouse release events.""" return False From a48a08bc80cd2f4668f17c4cd5a3f4d29e83e236 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 9 Jul 2025 21:45:52 -0700 Subject: [PATCH 58/74] scroll panel: remove useless return --- system/ui/lib/scroll_panel.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/system/ui/lib/scroll_panel.py b/system/ui/lib/scroll_panel.py index 9f01865b9a..21b6795a75 100644 --- a/system/ui/lib/scroll_panel.py +++ b/system/ui/lib/scroll_panel.py @@ -172,8 +172,6 @@ class GuiScrollPanel: elif self._offset.y < -(max_scroll_y + MAX_BOUNCE_DISTANCE): self._offset.y = -(max_scroll_y + MAX_BOUNCE_DISTANCE) - return self._offset - def is_touch_valid(self): return not self._is_dragging From 501fddac82f5cb09744375fb7c5cfc31fd65c1af Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 9 Jul 2025 22:04:22 -0700 Subject: [PATCH 59/74] raylib ui: add brightness and display power (#35676) * add constant * fix initial interaction time * from ui.cc --- selfdrive/ui/ui_state.py | 69 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 3738ea6d5d..c2bb5f77a4 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -1,12 +1,20 @@ import pyray as rl +import numpy as np import time +import threading from collections.abc import Callable from enum import Enum from cereal import messaging, log +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 UI_BORDER_SIZE = 30 +BACKLIGHT_OFFROAD = 50 class UIStatus(Enum): @@ -139,10 +147,15 @@ class UIState: class Device: def __init__(self): self._ignition = False - self._interaction_time: float = 0.0 + self._interaction_time: float = -1 self._interactive_timeout_callbacks: list[Callable] = [] self._prev_timed_out = False - self.reset_interactive_timeout() + self._awake = False + + self._offroad_brightness: int = BACKLIGHT_OFFROAD + self._last_brightness: int = 0 + 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: if timeout == -1: @@ -153,18 +166,64 @@ class Device: self._interactive_timeout_callbacks.append(callback) def update(self): + # do initial reset + if self._interaction_time <= 0: + self.reset_interactive_timeout() + + 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. + self._offroad_brightness = min(max(brightness, 0), 100) + + def _update_brightness(self): + clipped_brightness = self._offroad_brightness + + if ui_state.started and ui_state.light_sensor >= 0: + clipped_brightness = ui_state.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 = ((clipped_brightness + 16.0) / 116.0) ** 3.0 + + clipped_brightness = float(np.clip(100 * clipped_brightness, 10, 100)) + + brightness = round(self._brightness_filter.update(clipped_brightness)) + if not self._awake: + brightness = 0 + + 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 + + def _update_wakefulness(self): # Handle interactive timeout ignition_just_turned_off = not ui_state.ignition and self._ignition self._ignition = ui_state.ignition - interaction_timeout = time.monotonic() > self._interaction_time - if ignition_just_turned_off or rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT): + if ignition_just_turned_off or any(ev.left_down for ev in gui_app.mouse_events): self.reset_interactive_timeout() - elif interaction_timeout and not self._prev_timed_out: + + interaction_timeout = time.monotonic() > self._interaction_time + if interaction_timeout and not self._prev_timed_out: for callback in self._interactive_timeout_callbacks: callback() self._prev_timed_out = interaction_timeout + self._set_awake(ui_state.ignition or not interaction_timeout) + + def _set_awake(self, on: bool): + if on != self._awake: + self._awake = on + cloudlog.debug(f"setting display power {int(on)}") + HARDWARE.set_display_power(on) + # Global instance ui_state = UIState() From 92f1977666695939053062e49da834c25e2acf4f Mon Sep 17 00:00:00 2001 From: Nayan Date: Thu, 10 Jul 2025 01:30:10 -0400 Subject: [PATCH 60/74] UI: Custom Interactivity Timeout (#1032) custom interactivity timeout --- common/params_keys.h | 1 + .../qt/offroad/settings/device_panel.cc | 18 ++++++++++++++++++ .../qt/offroad/settings/device_panel.h | 1 + selfdrive/ui/ui.cc | 3 ++- system/manager/manager.py | 1 + 5 files changed, 23 insertions(+), 1 deletion(-) diff --git a/common/params_keys.h b/common/params_keys.h index 00e1763e16..e7c4e11745 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -136,6 +136,7 @@ inline static std::unordered_map keys = { {"CustomAccShortPressIncrement", PERSISTENT | BACKUP}, {"DeviceBootMode", PERSISTENT | BACKUP}, {"EnableGithubRunner", PERSISTENT | BACKUP}, + {"InteractivityTimeout", PERSISTENT | BACKUP}, {"MaxTimeOffroad", PERSISTENT | BACKUP}, {"Brightness", PERSISTENT | BACKUP}, {"ModelRunnerTypeCache", CLEAR_ON_ONROAD_TRANSITION}, diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc index b89116d424..dc2d7b318d 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc @@ -87,6 +87,17 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) { params.put("DeviceBootMode", QString::number(index).toStdString()); updateState(); }); + + interactivityTimeout = new OptionControlSP("InteractivityTimeout", tr("Interactivity Timeout"), + tr("Apply a custom timeout for settings UI." + "\nThis is the time after which settings UI closes automatically if user is not interacting with the screen."), + "", {0, 120}, 10, true, nullptr, false); + + connect(interactivityTimeout, &OptionControlSP::updateLabels, [=]() { + updateState(); + }); + + addItem(interactivityTimeout); // Brightness brightness = new Brightness(); @@ -198,4 +209,11 @@ void DevicePanelSP::updateState() { currStatus = DeviceSleepModeStatus::OFFROAD; } toggleDeviceBootMode->setDescription(deviceSleepModeDescription(currStatus)); + + QString timeoutValue = QString::fromStdString(params.get("InteractivityTimeout")); + if (timeoutValue == "0") { + interactivityTimeout->setLabel("DEFAULT"); + } else { + interactivityTimeout->setLabel(timeoutValue + "s"); + } } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h index 2cd3281d1f..899a942dae 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h @@ -33,6 +33,7 @@ private: MaxTimeOffroad *maxTimeOffroad; ButtonParamControlSP *toggleDeviceBootMode; Brightness *brightness; + OptionControlSP *interactivityTimeout; const QString alwaysOffroadStyle = R"( PushButtonSP { diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index c9dc70cdff..f60ff9a707 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -164,8 +164,9 @@ void Device::setAwake(bool on) { } void Device::resetInteractiveTimeout(int timeout) { + int customTimeout = QString::fromStdString(Params().get("InteractivityTimeout")).toInt(); if (timeout == -1) { - timeout = (ignition_on ? 10 : 30); + timeout = customTimeout == 0 ? (ignition_on ? 10 : 30) : customTimeout; } interactive_timeout = timeout * UI_FREQ; } diff --git a/system/manager/manager.py b/system/manager/manager.py index 692c7717ff..b68207b691 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -56,6 +56,7 @@ def manager_init() -> None: ("DeviceBootMode", "0"), ("DynamicExperimentalControl", "0"), ("HyundaiLongitudinalTuning", "0"), + ("InteractivityTimeout", "0"), ("LagdToggle", "1"), ("LagdToggledelay", "0.2"), ("Mads", "1"), From e43c60c00479a48fca895da72407b91e3f61da73 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon, 14 Jul 2025 11:39:19 -0700 Subject: [PATCH 61/74] ci: Automate model build, publish, and update docs with JSON metadata (#1036) * gh workflows: Create build single and build all models Recompile action, and build single model action... I'm tired, and this could definitely most probably be wrong. on push : Revert me! Revert "on push : Revert me!" This reverts commit c6ab17adad981585f93c285e9d8a418e4c975879. checkout gitlab Update build-single-tinygrad-model.yaml Whoops. ubuntu-latest not self-hosted self-hosted is comma device gh api because artifacts Dynamic long Update build-single-tinygrad-model.yaml * FoF to FOF, because spellchecker is no fun * ignore * Create new dir and json by default since its full recompile red diff, who doesn't like it * Minimal patch to ssh agent from yesterday * Update .codespellignore --------- Co-authored-by: DevTekVE --- .codespellignore | 1 + .../workflows/build-all-tinygrad-models.yaml | 226 ++++++++++++++++++ .../build-single-tinygrad-model.yaml | 198 +++++++++++++++ 3 files changed, 425 insertions(+) create mode 100644 .github/workflows/build-all-tinygrad-models.yaml create mode 100644 .github/workflows/build-single-tinygrad-model.yaml diff --git a/.codespellignore b/.codespellignore index c7beb6f04f..9328517d4a 100644 --- a/.codespellignore +++ b/.codespellignore @@ -2,3 +2,4 @@ Wen REGIST PullRequest cancelled +FOF diff --git a/.github/workflows/build-all-tinygrad-models.yaml b/.github/workflows/build-all-tinygrad-models.yaml new file mode 100644 index 0000000000..f1c81455ca --- /dev/null +++ b/.github/workflows/build-all-tinygrad-models.yaml @@ -0,0 +1,226 @@ +name: Build All Tinygrad Models and Push to GitLab + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch to run workflow from' + required: false + default: 'master-new' + type: string + +jobs: + setup: + runs-on: ubuntu-latest + outputs: + json_file: ${{ steps.get-json.outputs.json_file }} + steps: + - name: Checkout docs repo + uses: actions/checkout@v4 + with: + repository: sunnypilot/sunnypilot-docs + ref: gh-pages + path: docs + ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }} + + - name: Get next JSON version to use + id: get-json + run: | + cd docs/docs + latest=$(ls driving_models_v*.json | sed -E 's/.*_v([0-9]+)\.json/\1/' | sort -n | tail -1) + next=$((latest+1)) + json_file="driving_models_v${next}.json" + cp "driving_models_v${latest}.json" "$json_file" + echo "json_file=$json_file" >> $GITHUB_OUTPUT + + - name: Upload context for next jobs + uses: actions/upload-artifact@v4 + with: + name: context + path: docs + + build-all: + runs-on: ubuntu-latest + needs: setup + env: + JSON_FILE: docs/docs/${{ needs.setup.outputs.json_file }} + steps: + - name: Set up SSH + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }} + + - name: Add GitLab.com SSH key to known_hosts + run: | + mkdir -p ~/.ssh + ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts + + - name: Clone GitLab docs repo + env: + 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 + cd gitlab_docs + git checkout main + cd .. + + - name: Set next recompiled dir + id: set-recompiled + run: | + cd gitlab_docs/models + latest_dir=$(ls -d recompiled* 2>/dev/null | sed -E 's/recompiled([0-9]+)/\1/' | sort -n | tail -1) + if [[ -z "$latest_dir" ]]; then + next_dir=1 + else + next_dir=$((latest_dir+1)) + fi + recompiled_dir="recompiled${next_dir}" + mkdir -p "$recompiled_dir" + echo "RECOMPILED_DIR=$recompiled_dir" >> $GITHUB_ENV + + - name: Download context + uses: actions/download-artifact@v4 + with: + name: context + path: . + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y jq gh + + - name: Build all tinygrad models + id: trigger-builds + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + > triggered_run_ids.txt + BRANCH="${{ github.event.inputs.branch }}" + jq -c '.bundles[] | select(.runner=="tinygrad")' "$JSON_FILE" | while read -r bundle; do + ref=$(echo "$bundle" | jq -r '.ref') + display_name=$(echo "$bundle" | jq -r '.display_name' | sed 's/ ([^)]*)//g') + is_20hz=$(echo "$bundle" | jq -r '.is_20hz') + echo "Triggering build for: $display_name ($ref) [20Hz: $is_20hz]" + START_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + gh workflow run sunnypilot-build-model.yaml \ + --repo sunnypilot/sunnypilot \ + --ref "$BRANCH" \ + -f upstream_branch="$ref" \ + -f custom_name="$display_name" \ + -f is_20hz="$is_20hz" + for i in {1..24}; do + RUN_ID=$(gh run list --repo sunnypilot/sunnypilot --workflow=sunnypilot-build-model.yaml --branch="$BRANCH" --created ">$START_TIME" --limit=1 --json databaseId --jq '.[0].databaseId') + if [ -n "$RUN_ID" ]; then + break + fi + sleep 5 + done + if [ -z "$RUN_ID" ]; then + echo "ould not find the triggered workflow run for $display_name ($ref)" + exit 1 + fi + echo "$RUN_ID" >> triggered_run_ids.txt + done + + - name: Wait for all model builds to finish + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + SUCCESS_RUNS=() + FAILED_RUNS=() + declare -A RUN_ID_TO_NAME + + while read -r RUN_ID; do + ARTIFACT_NAME=$(gh api repos/sunnypilot/sunnypilot/actions/runs/$RUN_ID/artifacts --jq '.artifacts[] | select(.name | startswith("model-")) | .name' || echo "unknown") + RUN_ID_TO_NAME["$RUN_ID"]="$ARTIFACT_NAME" + done < triggered_run_ids.txt + + while read -r RUN_ID; do + echo "Watching run ID: $RUN_ID" + gh run watch "$RUN_ID" --repo sunnypilot/sunnypilot + CONCLUSION=$(gh run view "$RUN_ID" --repo sunnypilot/sunnypilot --json conclusion --jq '.conclusion') + ARTIFACT_NAME="${RUN_ID_TO_NAME[$RUN_ID]}" + echo "Run $RUN_ID ($ARTIFACT_NAME) concluded with: $CONCLUSION" + if [[ "$CONCLUSION" == "success" ]]; then + SUCCESS_RUNS+=("$RUN_ID") + else + FAILED_RUNS+=("$RUN_ID") + fi + done < triggered_run_ids.txt + + if [[ ${#SUCCESS_RUNS[@]} -eq 0 ]]; then + echo "All model builds failed. Aborting." + exit 1 + fi + + if [[ ${#FAILED_RUNS[@]} -gt 0 ]]; then + echo "WARNING: The following model builds failed:" + for RUN_ID in "${FAILED_RUNS[@]}"; do + echo "- $RUN_ID (${RUN_ID_TO_NAME[$RUN_ID]})" + done + echo "You may want to rerun these models manually." + fi + + - name: Download and extract all model artifacts + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + ARTIFACT_DIR="gitlab_docs/models/$RECOMPILED_DIR" + SUCCESS_RUNS=() + while read -r RUN_ID; do + CONCLUSION=$(gh run view "$RUN_ID" --repo sunnypilot/sunnypilot --json conclusion --jq '.conclusion') + if [[ "$CONCLUSION" == "success" ]]; then + SUCCESS_RUNS+=("$RUN_ID") + fi + done < triggered_run_ids.txt + + for RUN_ID in "${SUCCESS_RUNS[@]}"; do + ARTIFACT_NAME=$(gh api repos/sunnypilot/sunnypilot/actions/runs/$RUN_ID/artifacts --jq '.artifacts[] | select(.name | startswith("model-")) | .name') + echo "Downloading artifact: $ARTIFACT_NAME from run: $RUN_ID" + mkdir -p "$ARTIFACT_DIR/$ARTIFACT_NAME" + echo "Created directory: $ARTIFACT_DIR/$ARTIFACT_NAME" + gh run download "$RUN_ID" --repo sunnypilot/sunnypilot -n "$ARTIFACT_NAME" --dir "$ARTIFACT_DIR/$ARTIFACT_NAME" + echo "Downloaded artifact zip(s) to: $ARTIFACT_DIR/$ARTIFACT_NAME" + ZIP_PATH=$(find "$ARTIFACT_DIR/$ARTIFACT_NAME" -type f -name '*.zip' | head -n1) + if [ -n "$ZIP_PATH" ]; then + echo "Unzipping $ZIP_PATH to $ARTIFACT_DIR/$ARTIFACT_NAME" + unzip -o "$ZIP_PATH" -d "$ARTIFACT_DIR/$ARTIFACT_NAME" + rm -f "$ZIP_PATH" + echo "Unzipped and removed $ZIP_PATH" + else + echo "No zip file found in $ARTIFACT_DIR/$ARTIFACT_NAME (This is NOT an error)." + fi + echo "Done processing $ARTIFACT_NAME" + done + + - name: Push recompiled dir to GitLab + env: + GITLAB_SSH_PRIVATE_KEY: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }} + run: | + cd gitlab_docs + git checkout main + mkdir -p models/"$(basename $RECOMPILED_DIR)" + git add models/"$(basename $RECOMPILED_DIR)" + git config --global user.name "GitHub Action" + git config --global user.email "action@github.com" + git commit -m "Add $(basename $RECOMPILED_DIR) from build-all-tinygrad-models" + git push origin main + + - name: Run json_parser.py to update JSON + run: | + python3 docs/json_parser.py \ + --json-path "$JSON_FILE" \ + --recompiled-dir "gitlab_docs/models/$RECOMPILED_DIR" + + - name: Push updated JSON to GitHub docs repo + run: | + cd docs + git config --global user.name "GitHub Action" + git config --global user.email "action@github.com" + git checkout gh-pages + git add docs/"$(basename $JSON_FILE)" + git commit -m "Update $(basename $JSON_FILE) after recompiling models" || echo "No changes to commit" + git push origin gh-pages diff --git a/.github/workflows/build-single-tinygrad-model.yaml b/.github/workflows/build-single-tinygrad-model.yaml new file mode 100644 index 0000000000..e161a56b7d --- /dev/null +++ b/.github/workflows/build-single-tinygrad-model.yaml @@ -0,0 +1,198 @@ +name: Build Single Tinygrad Model and Push + +on: + workflow_dispatch: + inputs: + build_model_ref: + description: 'Branch to use for build-model workflow' + required: false + default: 'master-new' + type: string + upstream_branch: + description: 'Upstream commit to build from' + required: true + type: string + custom_name: + description: 'Custom name for the model (no date, only name)' + required: false + type: string + recompiled_dir: + description: 'Existing recompiled directory number (e.g. 3 for recompiled3)' + required: true + type: number + json_version: + description: 'driving_models version number to update (e.g. 5 for driving_models_v5.json)' + required: true + type: number + model_folder: + description: 'Model folder' + required: true + type: choice + options: + - Simple Plan Models + - TR Models + - DTR Models + - Custom Merge Models + - FOF series models + - Other + custom_model_folder: + description: 'Custom model folder name (if "Other" selected)' + required: false + type: string + generation: + description: 'Model generation' + required: false + type: number + version: + description: 'Minimum selector version' + required: false + type: number + +jobs: + build-single: + runs-on: ubuntu-latest + env: + RECOMPILED_DIR: recompiled${{ github.event.inputs.recompiled_dir }} + JSON_FILE: docs/docs/driving_models_v${{ github.event.inputs.json_version }}.json + steps: + - name: Set up SSH + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }} + + - name: Add GitLab.com SSH key to known_hosts + run: | + mkdir -p ~/.ssh + ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts + + - name: Clone GitLab docs repo + env: + 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 + cd gitlab_docs + echo "checkout models/${RECOMPILED_DIR}" + git sparse-checkout set --no-cone models/${RECOMPILED_DIR} + git checkout main + cd .. + + - name: Checkout docs repo + uses: actions/checkout@v4 + with: + repository: sunnypilot/sunnypilot-docs + ref: gh-pages + path: docs + ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }} + + - name: Validate recompiled dir and JSON version + run: | + if [ ! -d "gitlab_docs/models/$RECOMPILED_DIR" ]; then + echo "Recompiled dir $RECOMPILED_DIR does not exist in GitLab repo" + exit 1 + fi + if [ ! -f "$JSON_FILE" ]; then + echo "JSON file $JSON_FILE does not exist!" + exit 1 + fi + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y jq gh + + - name: Build model + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + START_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + gh workflow run sunnypilot-build-model.yaml \ + --repo sunnypilot/sunnypilot \ + --ref "${{ github.event.inputs.build_model_ref }}" \ + -f upstream_branch="${{ github.event.inputs.upstream_branch }}" \ + -f custom_name="${{ github.event.inputs.custom_name }}" + + for i in {1..24}; do + RUN_ID=$(gh run list --repo sunnypilot/sunnypilot --workflow=sunnypilot-build-model.yaml --branch="${{ github.event.inputs.build_model_ref }}" --created ">$START_TIME" --limit=1 --json databaseId --jq '.[0].databaseId') + if [ -n "$RUN_ID" ]; then + break + fi + sleep 5 + done + + if [ -z "$RUN_ID" ]; then + echo "Could not find the triggered workflow run." + exit 1 + fi + + echo "Watching run ID: $RUN_ID" + gh run watch "$RUN_ID" --repo sunnypilot/sunnypilot + CONCLUSION=$(gh run view "$RUN_ID" --repo sunnypilot/sunnypilot --json conclusion --jq '.conclusion') + echo "Run concluded with: $CONCLUSION" + if [[ "$CONCLUSION" != "success" ]]; then + echo "Workflow run failed with conclusion: $CONCLUSION" + exit 1 + fi + + - name: Download and extract model artifact + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + ARTIFACT_DIR="gitlab_docs/models/$RECOMPILED_DIR" + RUN_ID=$(gh run list --repo sunnypilot/sunnypilot --workflow=sunnypilot-build-model.yaml --branch="${{ github.event.inputs.build_model_ref }}" --limit=1 --json databaseId --jq '.[0].databaseId') + ARTIFACT_NAME=$(gh api repos/sunnypilot/sunnypilot/actions/runs/$RUN_ID/artifacts --jq '.artifacts[] | select(.name | startswith("model-")) | .name') + echo "Downloading artifact: $ARTIFACT_NAME from run: $RUN_ID" + mkdir -p "$ARTIFACT_DIR/$ARTIFACT_NAME" + echo "Created directory: $ARTIFACT_DIR/$ARTIFACT_NAME" + gh run download "$RUN_ID" --repo sunnypilot/sunnypilot -n "$ARTIFACT_NAME" --dir "$ARTIFACT_DIR/$ARTIFACT_NAME" + echo "Downloaded artifact zip(s) to: $ARTIFACT_DIR/$ARTIFACT_NAME" + ZIP_PATH=$(find "$ARTIFACT_DIR/$ARTIFACT_NAME" -type f -name '*.zip' | head -n1) + if [ -n "$ZIP_PATH" ]; then + echo "Unzipping $ZIP_PATH to $ARTIFACT_DIR/$ARTIFACT_NAME" + unzip -o "$ZIP_PATH" -d "$ARTIFACT_DIR/$ARTIFACT_NAME" + rm -f "$ZIP_PATH" + echo "Unzipped and removed $ZIP_PATH" + else + echo "No zip file found in $ARTIFACT_DIR/$ARTIFACT_NAME" + fi + echo "Done processing $ARTIFACT_NAME" + + - name: Push recompiled dir to GitLab + env: + GITLAB_SSH_PRIVATE_KEY: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }} + run: | + cd gitlab_docs + git checkout main + for d in models/"$RECOMPILED_DIR"/*/; do + git sparse-checkout add "$d" + done + git add models/"$RECOMPILED_DIR" + git config --global user.name "GitHub Action" + git config --global user.email "action@github.com" + git commit -m "Update $RECOMPILED_DIR with new/updated model from build-single-tinygrad-model" || echo "No changes to commit" + git push origin main + + - name: Run json_parser.py to update JSON + run: | + FOLDER="${{ github.event.inputs.model_folder }}" + if [ "$FOLDER" = "Other" ]; then + FOLDER="${{ github.event.inputs.custom_model_folder }}" + fi + ARGS="" + [ -n "$FOLDER" ] && ARGS="$ARGS --model-folder \"$FOLDER\"" + [ -n "${{ github.event.inputs.generation }}" ] && ARGS="$ARGS --generation \"${{ github.event.inputs.generation }}\"" + [ -n "${{ github.event.inputs.version }}" ] && ARGS="$ARGS --version \"${{ github.event.inputs.version }}\"" + eval python3 docs/json_parser.py \ + --json-path "$JSON_FILE" \ + --recompiled-dir "gitlab_docs/models/$RECOMPILED_DIR" \ + $ARGS + + - name: Push updated JSON to GitHub docs repo + run: | + cd docs + git config --global user.name "GitHub Action" + git config --global user.email "action@github.com" + git checkout gh-pages + git add docs/"$(basename $JSON_FILE)" + git commit -m "Update $(basename $JSON_FILE) after recompiling model" || echo "No changes to commit" + git push origin gh-pages From 0b15b8810429d3251d3423125ec2b32a96e4ebdb Mon Sep 17 00:00:00 2001 From: Warren Togami Date: Sat, 19 Jul 2025 02:30:39 -0500 Subject: [PATCH 62/74] NNLC: SIENNA_4TH_GEN gen 1, KIA_NERO_PHEV_2022 (#1013) * `SIENNA_4TH_GEN` tuning Gen 1 (June 14th, 2025) 2021-2023 Toyota Sienna * `KIA_NIRO_PHEV_2022` tuning (July 15th, 2025) 2021-2022 Kia Niro PHEV * Fix: rename files to remove spaces in filenames for build compatibility --- sunnypilot/neural_network_data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sunnypilot/neural_network_data b/sunnypilot/neural_network_data index b59ab483c8..03cac2d30e 160000 --- a/sunnypilot/neural_network_data +++ b/sunnypilot/neural_network_data @@ -1 +1 @@ -Subproject commit b59ab483c86cd70d04de91ad878024cd66fe86b6 +Subproject commit 03cac2d30e111e0689c0429cb8c1fe6cb5a905af From 517919c7b7994fe5839aa075532dd5c47c8ea305 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 19 Jul 2025 00:41:01 -0700 Subject: [PATCH 63/74] ui: display recorded timestamp on error view (#827) * Error log clear on manager start * Error log clear on manager start * 12 hour reset. may change soon * Add last modified date * unused * rm space * Squashed commit of the following: commit bcf5b06c822fd1bce1de5f119e42cb763d8e282f Author: discountchubbs Date: Thu Jul 17 14:14:29 2025 -0700 Init Developer panel SP commit cf8bbb70ab9d132d7b15043a73496a17b841641b Author: discountchubbs Date: Thu Jul 17 06:22:18 2025 -0700 Chronological in manager too commit ff9b873468c8ef63eca6de524c924bb80a287b4d Author: discountchubbs Date: Thu Jul 17 06:19:59 2025 -0700 Move down, since name change commit 38f6cb8c5735815faaca3226c7c8da0e051452c2 Author: discountchubbs Date: Thu Jul 17 06:17:36 2025 -0700 FastBoot -> QuickBoot commit 38562db8dd6b4cdeb06db74f1a71de2c285ed3ab Author: discountchubbs Date: Wed Jul 16 11:20:17 2025 -0700 Unused commit c7a751b02d7146e83dfa0ee5037fa4cf8ee6def5 Author: discountchubbs Date: Wed Jul 16 10:24:47 2025 -0700 Fast boot mode * * Depends on #1054 * Update developer_panel.h --------- Co-authored-by: Jason Wen Co-authored-by: DevTekVE --- selfdrive/ui/qt/offroad/developer_panel.cc | 13 ------ selfdrive/ui/qt/offroad/developer_panel.h | 2 - selfdrive/ui/sunnypilot/SConscript | 1 + .../qt/offroad/settings/developer_panel.cc | 40 +++++++++++++++++++ .../qt/offroad/settings/developer_panel.h | 28 +++++++++++++ .../qt/offroad/settings/settings.cc | 4 +- 6 files changed, 71 insertions(+), 17 deletions(-) create mode 100644 selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc create mode 100644 selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h diff --git a/selfdrive/ui/qt/offroad/developer_panel.cc b/selfdrive/ui/qt/offroad/developer_panel.cc index 3e9647d2cf..fbfb16294b 100644 --- a/selfdrive/ui/qt/offroad/developer_panel.cc +++ b/selfdrive/ui/qt/offroad/developer_panel.cc @@ -45,17 +45,6 @@ DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) { }); addItem(experimentalLongitudinalToggle); - enableGithubRunner = new ParamControl("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), ""); - addItem(enableGithubRunner); - - // error log button - errorLogBtn = new ButtonControl(tr("Error Log"), tr("VIEW"), tr("View the error log for sunnypilot crashes.")); - connect(errorLogBtn, &ButtonControl::clicked, [=]() { - std::string txt = util::read_file("/data/community/crashes/error.log"); - ConfirmationDialog::rich(QString::fromStdString(txt), this); - }); - addItem(errorLogBtn); - // Joystick and longitudinal maneuvers should be hidden on release branches is_release = params.getBool("IsReleaseBranch"); @@ -104,8 +93,6 @@ void DeveloperPanel::updateToggles(bool _offroad) { experimentalLongitudinalToggle->refresh(); // Handle specific controls visibility for release branches - enableGithubRunner->setVisible(!is_release); - errorLogBtn->setVisible(!is_release); joystickToggle->setVisible(!is_release); offroad = _offroad; diff --git a/selfdrive/ui/qt/offroad/developer_panel.h b/selfdrive/ui/qt/offroad/developer_panel.h index c15b574f5a..51b1b0b6f0 100644 --- a/selfdrive/ui/qt/offroad/developer_panel.h +++ b/selfdrive/ui/qt/offroad/developer_panel.h @@ -16,10 +16,8 @@ private: Params params; ParamControl* adbToggle; ParamControl* joystickToggle; - ButtonControl* errorLogBtn; ParamControl* longManeuverToggle; ParamControl* experimentalLongitudinalToggle; - ParamControl* enableGithubRunner; bool is_release; bool offroad = false; diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 41cc09141b..810338aae8 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -21,6 +21,7 @@ qt_src = [ "sunnypilot/qt/home.cc", "sunnypilot/qt/offroad/exit_offroad_button.cc", "sunnypilot/qt/offroad/offroad_home.cc", + "sunnypilot/qt/offroad/settings/developer_panel.cc", "sunnypilot/qt/offroad/settings/device_panel.cc", "sunnypilot/qt/offroad/settings/lateral_panel.cc", "sunnypilot/qt/offroad/settings/longitudinal_panel.cc", diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc new file mode 100644 index 0000000000..a11a6cdbeb --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc @@ -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. + */ +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h" + +DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(parent) { + // Github Runner Toggle + enableGithubRunner = new ParamControlSP("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), ""); + addItem(enableGithubRunner); + + // Error log button + errorLogBtn = new ButtonControlSP(tr("Error Log"), tr("VIEW"), tr("View the error log for sunnypilot crashes.")); + connect(errorLogBtn, &ButtonControlSP::clicked, [=]() { + QFileInfo file("/data/community/crashes/error.log"); + QString text; + if (file.exists()) { + text = "" + file.lastModified().toString("dd-MMM-yyyy hh:mm:ss ").toUpper() + "

"; + } + text += QString::fromStdString(util::read_file("/data/community/crashes/error.log")); + ConfirmationDialog::rich(text, this); + }); + addItem(errorLogBtn); + + QObject::connect(uiState(), &UIState::offroadTransition, this, &DeveloperPanelSP::updateToggles); +} + +void DeveloperPanelSP::updateToggles(bool offroad) { + bool is_release = params.getBool("IsReleaseBranch"); + + enableGithubRunner->setVisible(!is_release); + errorLogBtn->setVisible(!is_release); +} + +void DeveloperPanelSP::showEvent(QShowEvent *event) { + DeveloperPanel::showEvent(event); + updateToggles(!uiState()->scene.started); +} diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h new file mode 100644 index 0000000000..ba707b64de --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h @@ -0,0 +1,28 @@ +/** + * 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 + +#include "selfdrive/ui/qt/offroad/developer_panel.h" + +class DeveloperPanelSP : public DeveloperPanel { + Q_OBJECT +public: + explicit DeveloperPanelSP(SettingsWindow *parent); + +private: + ParamControlSP *enableGithubRunner; + ButtonControlSP *errorLogBtn; + Params params; + +private slots: + void updateToggles(bool offroad); + +protected: + void showEvent(QShowEvent *event) override; +}; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc index 05ea5ff899..5415e75ea9 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc @@ -8,10 +8,10 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h" #include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h" -#include "selfdrive/ui/qt/offroad/developer_panel.h" #include "selfdrive/ui/qt/offroad/firehose.h" #include "selfdrive/ui/sunnypilot/qt/network/networking.h" +#include "selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h" @@ -91,7 +91,7 @@ SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) { PanelInfo(" " + tr("Trips"), new TripsPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_trips.png"), PanelInfo(" " + tr("Vehicle"), new VehiclePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_vehicle.png"), PanelInfo(" " + tr("Firehose"), new FirehosePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_firehose.svg"), - PanelInfo(" " + tr("Developer"), new DeveloperPanel(this), "../assets/icons/shell.png"), + PanelInfo(" " + tr("Developer"), new DeveloperPanelSP(this), "../assets/icons/shell.png"), }; nav_btns = new QButtonGroup(this); From 47833ed73a3a2263b3c60a39137e201637387c96 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 19 Jul 2025 06:01:39 -0700 Subject: [PATCH 64/74] ui: Disable Updates Toggle (#1040) * Disable Updates * Add default to manager.py * Add enabled state and alternate descriptions --------- Co-authored-by: Nayan Co-authored-by: DevTekVE --- .../qt/offroad/settings/software_panel.cc | 36 +++++++++++++++++++ .../qt/offroad/settings/software_panel.h | 6 ++++ system/manager/manager.py | 1 + 3 files changed, 43 insertions(+) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.cc index 9430d02ba8..7bef7ed203 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.cc @@ -18,6 +18,18 @@ SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) { searchBranches(d.text()); } }); + + // Disable Updates toggle + disableUpdatesToggle = new ParamControl("DisableUpdates", + tr("Disable Updates"), + tr("When enabled, software updates will be disabled. This requires a reboot to take effect."), + "../assets/icons/icon_warning.png", + this); + disableUpdatesToggle->showDescription(); + addItem(disableUpdatesToggle); + connect(disableUpdatesToggle, &ParamControl::toggleFlipped, this, &SoftwarePanelSP::handleDisableUpdatesToggled); + connect(uiState(), &UIState::offroadTransition, this, &SoftwarePanelSP::updateDisableUpdatesToggle); + updateDisableUpdatesToggle(!uiState()->scene.started); } /** @@ -49,3 +61,27 @@ void SoftwarePanelSP::searchBranches(const QString &query) { checkForUpdates(); } } + +void SoftwarePanelSP::handleDisableUpdatesToggled(bool state) { + if (ConfirmationDialog::confirm(tr("%1 updates requires a reboot.
Reboot now?") + .arg(state ? "Disabling" : "Enabling"), tr("Reboot"), this)) { + params.putBool("DoReboot", true); + } else { + params.putBool("DisableUpdates", !state); + disableUpdatesToggle->refresh(); + } +} + +void SoftwarePanelSP::updateDisableUpdatesToggle(bool offroad) { + bool enabled = offroad; + disableUpdatesToggle->setEnabled(enabled); + disableUpdatesToggle->setDescription(enabled + ? tr("When enabled, software updates will be disabled.
This requires a reboot to take effect.") + : tr("Please enable always offroad mode or turn off vehicle to adjust these toggles")); +} + +void SoftwarePanelSP::showEvent(QShowEvent *event) { + SoftwarePanel::showEvent(event); + updateDisableUpdatesToggle(!uiState()->scene.started); + disableUpdatesToggle->showDescription(); +} diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h index 56d8b9be5b..efaa836a21 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h @@ -19,4 +19,10 @@ public: private: void searchBranches(const QString &query); + ParamControl *disableUpdatesToggle = nullptr; + void handleDisableUpdatesToggled(bool state); +private slots: + void updateDisableUpdatesToggle(bool offroad); +protected: + void showEvent(QShowEvent *event) override; }; diff --git a/system/manager/manager.py b/system/manager/manager.py index b68207b691..922640de8b 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -54,6 +54,7 @@ def manager_init() -> None: ("CustomAccLongPressIncrement", "5"), ("CustomAccShortPressIncrement", "1"), ("DeviceBootMode", "0"), + ("DisableUpdates", "0"), ("DynamicExperimentalControl", "0"), ("HyundaiLongitudinalTuning", "0"), ("InteractivityTimeout", "0"), From 3faf709387afa0e2e632ea1047035c55c8f015fd Mon Sep 17 00:00:00 2001 From: Kumar <36933347+rav4kumar@users.noreply.github.com> Date: Sat, 19 Jul 2025 06:17:19 -0700 Subject: [PATCH 65/74] ui: Visuals Panel Lead Chevron Info (#1033) * lead info * ui:chevron params * Update system/manager/manager.py. --------- Co-authored-by: DevTekVE --- common/params_keys.h | 1 + selfdrive/ui/qt/onroad/model.cc | 168 ++++++++++++++++++ selfdrive/ui/qt/onroad/model.h | 11 ++ .../qt/offroad/settings/visuals_panel.cc | 20 ++- .../qt/offroad/settings/visuals_panel.h | 1 + system/manager/manager.py | 3 +- 6 files changed, 201 insertions(+), 3 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index 102b31bea9..e8c155ab41 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -132,6 +132,7 @@ inline static std::unordered_map keys = { {"CarParamsSPCache", CLEAR_ON_MANAGER_START}, {"CarParamsSPPersistent", PERSISTENT}, {"CarPlatformBundle", PERSISTENT}, + {"ChevronInfo", PERSISTENT | BACKUP}, {"CustomAccIncrementsEnabled", PERSISTENT | BACKUP}, {"CustomAccLongPressIncrement", PERSISTENT | BACKUP}, {"CustomAccShortPressIncrement", PERSISTENT | BACKUP}, diff --git a/selfdrive/ui/qt/onroad/model.cc b/selfdrive/ui/qt/onroad/model.cc index dc801e591f..a1e3756cb6 100644 --- a/selfdrive/ui/qt/onroad/model.cc +++ b/selfdrive/ui/qt/onroad/model.cc @@ -34,6 +34,7 @@ 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(); } @@ -173,6 +174,173 @@ QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float (1 - t) * start.alphaF() + t * end.alphaF()); } + +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 85eb236e76..e40f832c37 100644 --- a/selfdrive/ui/qt/onroad/model.h +++ b/selfdrive/ui/qt/onroad/model.h @@ -34,6 +34,12 @@ 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); @@ -58,4 +64,9 @@ protected: QPointF lead_vertices[2] = {}; 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 0d42bd74f4..818da9b75e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc @@ -12,7 +12,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) { connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) { paramsRefresh(); }); - + main_layout = new QStackedLayout(this); ListWidgetSP *list = new ListWidgetSP(this, false); @@ -30,6 +30,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) { }, }; + // Add regular toggles first for (auto &[param, title, desc, icon, needs_restart] : toggle_defs) { auto toggle = new ParamControlSP(param, title, desc, icon, this); @@ -53,9 +54,20 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) { param_watcher->addParam(param); } + // 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)."), + "", + chevron_info_settings_texts, + 200); + chevron_info_settings->showDescription(); + list->addItem(chevron_info_settings); + param_watcher->addParam("ChevronInfo"); + sunnypilotScroller = new ScrollViewSP(list, this); vlayout->addWidget(sunnypilotScroller); - + main_layout->addWidget(sunnypilotScreen); } @@ -67,4 +79,8 @@ void VisualsPanel::paramsRefresh() { for (auto toggle : toggles) { toggle.second->refresh(); } + + if (chevron_info_settings) { + chevron_info_settings->refresh(); + } } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h index 42e0688957..f342662c22 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h @@ -27,4 +27,5 @@ protected: Params params; std::map toggles; ParamWatcher * param_watcher; + ButtonParamControlSP *chevron_info_settings; }; diff --git a/system/manager/manager.py b/system/manager/manager.py index 922640de8b..b67d592821 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -50,6 +50,8 @@ def manager_init() -> None: ("BlindSpot", "0"), ("BlinkerMinLateralControlSpeed", "20"), # MPH or km/h ("BlinkerPauseLateralControl", "0"), + ("Brightness", "0"), + ("ChevronInfo", "4"), ("CustomAccIncrementsEnabled", "0"), ("CustomAccLongPressIncrement", "5"), ("CustomAccShortPressIncrement", "1"), @@ -66,7 +68,6 @@ def manager_init() -> None: ("MadsUnifiedEngagementMode", "1"), ("MapdVersion", f"{VERSION}"), ("MaxTimeOffroad", "1800"), - ("Brightness", "0"), ("ModelManager_LastSyncTime", "0"), ("ModelManager_ModelsCache", ""), ("NeuralNetworkLateralControl", "0"), From 7c1f3f646cd5efdd3077afddfac90c195e1543c2 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sat, 19 Jul 2025 09:52:54 -0400 Subject: [PATCH 66/74] UI: Model Folders (#1031) * model folders.. yay * scroll expanded item in view --------- Co-authored-by: DevTekVE --- selfdrive/ui/qt/widgets/input.cc | 139 ++++++++++++++++++ selfdrive/ui/qt/widgets/input.h | 14 ++ .../qt/offroad/settings/models_panel.cc | 73 +++++++-- 3 files changed, 212 insertions(+), 14 deletions(-) diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc index 0cbf14931b..4f330dca8d 100644 --- a/selfdrive/ui/qt/widgets/input.cc +++ b/selfdrive/ui/qt/widgets/input.cc @@ -2,6 +2,7 @@ #include #include +#include #include "system/hardware/hw.h" #include "selfdrive/ui/qt/util.h" @@ -334,3 +335,141 @@ QString MultiOptionDialog::getSelection(const QString &prompt_text, const QStrin } return ""; } + +TreeOptionDialog::TreeOptionDialog(const QString &prompt_text, const QList> &items, + 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; } + QTreeWidget { + background-color: transparent; + border: none; + } + QTreeWidget::item { + height: 135; + padding: 0px 50px; + margin: 5px; + text-align: left; + font-size: 55px; + font-weight: 300; + border-radius: 10px; + background-color: #4F4F4F; + color: white; + } + QTreeWidget::item:selected { + background-color: #465BEA; + } + QTreeWidget::branch { + background-color: transparent; + } + )"); + + 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); + + treeWidget = new QTreeWidget(this); + treeWidget->setHeaderHidden(true); + treeWidget->setIndentation(50); + treeWidget->setExpandsOnDoubleClick(false); // Disable double-click expansion + treeWidget->setAnimated(true); + treeWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + treeWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + treeWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + treeWidget->setDragEnabled(false); + treeWidget->setMouseTracking(true); + + // Connect single-click to expand/collapse + QObject::connect(treeWidget, &QTreeWidget::itemClicked, [=](QTreeWidgetItem *item, int) { + if (item->childCount() > 0) { + item->setExpanded(!item->isExpanded()); + treeWidget->scrollToItem(item->child(0), QAbstractItemView::EnsureVisible); + } + }); + + QScroller::grabGesture(treeWidget->viewport(), QScroller::LeftMouseButtonGesture); + + // Populate tree + QListIterator> iter(items); + while (iter.hasNext()) { + QPair currItem = iter.next(); + if (currItem.first.isEmpty()) { + for (const QString &item : currItem.second) { + QTreeWidgetItem *topLevel = new QTreeWidgetItem(); + topLevel->setText(0, item); + topLevel->setFlags(topLevel->flags() | Qt::ItemIsSelectable); + treeWidget->addTopLevelItem(topLevel); + if (item == current) { + topLevel->setSelected(true); + } + } + } else { + QTreeWidgetItem *folderItem = new QTreeWidgetItem(treeWidget); + folderItem->setIcon(0, QIcon(QPixmap("../assets/icons/menu.png"))); + folderItem->setText(0, " " + currItem.first); + folderItem->setFlags(folderItem->flags() | Qt::ItemIsAutoTristate); + folderItem->setFlags(folderItem->flags() & ~Qt::ItemIsSelectable); + + for (const QString &item : currItem.second) + { + QTreeWidgetItem *childItem = new QTreeWidgetItem(folderItem); + childItem->setText(0, item); + childItem->setFlags(childItem->flags() | Qt::ItemIsSelectable); + + if (item == current) { + childItem->setSelected(true); + folderItem->setExpanded(true); + } + } + } + } + + confirm_btn = new QPushButton(tr("Select")); + confirm_btn->setObjectName("confirm_btn"); + confirm_btn->setEnabled(false); + + QObject::connect(treeWidget, &QTreeWidget::itemSelectionChanged, [=]() { + QList selectedItems = treeWidget->selectedItems(); + if (!selectedItems.isEmpty()) { + selection = selectedItems.first()->text(0); + confirm_btn->setEnabled(selection != current); + } + }); + + ScrollView *scroll_view = new ScrollView(treeWidget, 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 TreeOptionDialog::getSelection(const QString &prompt_text, const QList> &items, + const QString ¤t, QWidget *parent) { + TreeOptionDialog d(prompt_text, items, 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 index 089e54e4a0..76f87bf32f 100644 --- a/selfdrive/ui/qt/widgets/input.h +++ b/selfdrive/ui/qt/widgets/input.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "selfdrive/ui/qt/widgets/keyboard.h" @@ -69,3 +70,16 @@ public: static QString getSelection(const QString &prompt_text, const QStringList &l, const QString ¤t, QWidget *parent); QString selection; }; + +class TreeOptionDialog : public DialogBase { + Q_OBJECT + +public: + explicit TreeOptionDialog(const QString &prompt_text, const QList> &items, const QString ¤t, QWidget *parent = nullptr); + static QString getSelection(const QString &prompt_text, const QList> &items, const QString ¤t, QWidget *parent = nullptr); + QString selection; + +private: + QTreeWidget *treeWidget; + QPushButton *confirm_btn; +}; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc index 1a2e86622a..90bc80f00a 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc @@ -244,28 +244,73 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() { currentModelLblBtn->setEnabled(false); currentModelLblBtn->setValue(tr("Fetching models...")); - // Create mapping of bundle indices to display names - QMap index_to_bundle; + struct ModelEntry { + QString folder; + QString displayName; + int index; + }; + QList sortedModels; + QSet modelFolders; const auto bundles = model_manager.getAvailableBundles(); - for (const auto &bundle: bundles) { - index_to_bundle.insert(bundle.getIndex(), QString::fromStdString(bundle.getDisplayName())); + + for (const auto &bundle : bundles) { + auto overrides = bundle.getOverrides(); + QString gen; + for (const auto &override : overrides) { + if (override.getKey() == "folder") { + gen = QString::fromStdString(override.getValue().cStr()); + } + } + + modelFolders.insert(gen); + sortedModels.append(ModelEntry{ + gen, + QString::fromStdString(bundle.getDisplayName()), + static_cast(bundle.getIndex()) + }); } - // Sort bundles by index in descending order - QStringList bundleNames; - // Add "Default" as the first option - bundleNames.append(DEFAULT_MODEL); + std::sort(sortedModels.begin(), sortedModels.end(), + [](const ModelEntry &a, const ModelEntry &b) { + return a.index > b.index; + }); - auto indices = index_to_bundle.keys(); - std::sort(indices.begin(), indices.end(), std::greater()); - for (const auto &index: indices) { - bundleNames.append(index_to_bundle[index]); + // Create a list of folder-maxIndex pairs for sorting + QList> folderMaxIndices; + for (const auto &folder : modelFolders) { + int maxIndex = -1; + for (const auto &model : sortedModels) { + if (model.folder == folder) { + maxIndex = std::max(maxIndex, model.index); + } + } + folderMaxIndices.append(qMakePair(folder, maxIndex)); } + // Sort folders by their highest model index + std::sort(folderMaxIndices.begin(), folderMaxIndices.end(), + [](const QPair &a, const QPair &b) { + return a.second > b.second; + }); + + // Create the final items list using sorted folders + QList> items; + for (const auto &folderPair : folderMaxIndices) { + QStringList folderModels; + for (const auto &model : sortedModels) { + if (model.folder == folderPair.first) { + folderModels.append(model.displayName); + } + } + items.append(qMakePair(folderPair.first, folderModels)); + } + + items.insert(0, qMakePair(QString(""), QStringList{DEFAULT_MODEL})); + currentModelLblBtn->setValue(GetActiveModelInternalName()); - const QString selectedBundleName = MultiOptionDialog::getSelection( - tr("Select a Model"), bundleNames, GetActiveModelName(), this); + const QString selectedBundleName = TreeOptionDialog::getSelection( + tr("Select a Model"), items, GetActiveModelName(), this); if (selectedBundleName.isEmpty() || !canContinueOnMeteredDialog()) { return; From eae44df6881f7cdd35f9e2e436a24164a37b656d Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 19 Jul 2025 06:57:01 -0700 Subject: [PATCH 67/74] ui: Models panel Refine Software Delay Adjustments (#1048) Allow lower values to match some makes liveDelay Co-authored-by: Nayan --- selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc index 90bc80f00a..4cbd4f11e4 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc @@ -98,7 +98,7 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) { delay_control = new OptionControlSP("LagdToggledelay", tr("Adjust Software Delay"), tr("Adjust the software delay when Live Learning Steer Delay is toggled off." "\nThe default software delay value is 0.2"), - "", {10, 30}, 1, false, nullptr, true); + "", {5, 30}, 1, false, nullptr, true); connect(delay_control, &OptionControlSP::updateLabels, [=]() { float value = QString::fromStdString(params.get("LagdToggledelay")).toFloat(); From 1b570ef4189f11583fdd0175da82c778fd01b107 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 19 Jul 2025 07:22:02 -0700 Subject: [PATCH 68/74] sunnypilot modeld: Refactor Modeld to Allow Dynamic Plan and Lead (#1030) * Introduce zero inputs for Lead, and plan to conform with new SP model introduced Monday, July 7, 2025 * Clean this up * We can revert this after dev-c3-new testing and ready to merge. * This needs to be apart of the conditional else fail * Add full conditional * Update longitudinal_planner.py * Mypy from myphone! * red diff * Make generation a property for clarity * Even clearer! * Affix to generation, while allowing older models to use this IF param is set. * seems a bit repetitive yea? * dynamic * Make most outputs dynamic * Rm toggle from refactor * refactor(modeld): simplify MHP output parsing logic - Introduced `_parse_mhp_output` helper to remove redundancy and streamline `parse_dynamic_outputs`. - Ensures improved code maintainability and clarity. * refactor(longitudinal_planner): streamline generation handling logic - Simplified `generation` assignment with inline conditional for better readability. - Adjusted `mlsim` logic to default to model simulation when `generation` is unset. * for ease of syncs from now on * fix --------- Co-authored-by: DevTekVE --- selfdrive/modeld/modeld.py | 2 +- sunnypilot/modeld/fill_model_msg.py | 2 +- sunnypilot/modeld/modeld.py | 2 +- sunnypilot/modeld_v2/fill_model_msg.py | 6 +- sunnypilot/modeld_v2/modeld.py | 12 ++- .../modeld_v2/parse_model_outputs_split.py | 78 +++++++++++++++---- sunnypilot/models/helpers.py | 2 +- .../controls/lib/longitudinal_planner.py | 6 +- 8 files changed, 79 insertions(+), 31 deletions(-) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 7cf70d8234..31bfd707bb 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -90,7 +90,7 @@ class ModelState: prev_desire: np.ndarray # for tracking the rising edge of the pulse def __init__(self, context: CLContext): - self.LAT_SMOOTH_SECONDS = 0.0 + self.LAT_SMOOTH_SECONDS = LAT_SMOOTH_SECONDS with open(VISION_METADATA_PATH, 'rb') as f: vision_metadata = pickle.load(f) self.vision_input_shapes = vision_metadata['input_shapes'] diff --git a/sunnypilot/modeld/fill_model_msg.py b/sunnypilot/modeld/fill_model_msg.py index 608f24424d..dadffc8433 100644 --- a/sunnypilot/modeld/fill_model_msg.py +++ b/sunnypilot/modeld/fill_model_msg.py @@ -103,7 +103,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D fill_xyzt(orientation_rate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T) # temporal pose - temporal_pose = modelV2.temporalPose + temporal_pose = modelV2.temporalPoseDEPRECATED temporal_pose.trans = net_output_data['plan'][0,0,Plan.VELOCITY].tolist() temporal_pose.transStd = net_output_data['plan_stds'][0,0,Plan.VELOCITY].tolist() temporal_pose.rot = net_output_data['plan'][0,0,Plan.ORIENTATION_RATE].tolist() diff --git a/sunnypilot/modeld/modeld.py b/sunnypilot/modeld/modeld.py index 86968ea26c..10725efb71 100755 --- a/sunnypilot/modeld/modeld.py +++ b/sunnypilot/modeld/modeld.py @@ -62,7 +62,7 @@ class ModelState: self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) bundle = get_active_bundle() overrides = {override.key: override.value for override in bundle.overrides} - self.LAT_SMOOTH_SECONDS = float(overrides.get('lat', ".2")) + self.LAT_SMOOTH_SECONDS = float(overrides.get('lat', ".0")) self.LONG_SMOOTH_SECONDS = float(overrides.get('long', ".0")) model_paths = get_model_path() diff --git a/sunnypilot/modeld_v2/fill_model_msg.py b/sunnypilot/modeld_v2/fill_model_msg.py index 4d04d6e5ec..c7de698f61 100644 --- a/sunnypilot/modeld_v2/fill_model_msg.py +++ b/sunnypilot/modeld_v2/fill_model_msg.py @@ -10,8 +10,8 @@ SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') ConfidenceClass = log.ModelDataV2.ConfidenceClass -def get_curvature_from_output(output, vego, lat_action_t, current_generation=None): - if current_generation != 11: +def get_curvature_from_output(output, 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]) @@ -100,7 +100,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D fill_xyzt(modelV2.orientationRate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T) # temporal pose - temporal_pose = modelV2.temporalPose + temporal_pose = modelV2.temporalPoseDEPRECATED if 'sim_pose' in net_output_data: temporal_pose.trans = net_output_data['sim_pose'][0,:ModelConstants.POSE_WIDTH//2].tolist() temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:ModelConstants.POSE_WIDTH//2].tolist() diff --git a/sunnypilot/modeld_v2/modeld.py b/sunnypilot/modeld_v2/modeld.py index 58e79eceb5..93a8a3f05e 100755 --- a/sunnypilot/modeld_v2/modeld.py +++ b/sunnypilot/modeld_v2/modeld.py @@ -54,10 +54,10 @@ class ModelState: raise model_bundle = get_active_bundle() - self.generation = model_bundle.generation + self.generation = model_bundle.generation if model_bundle is not None else None overrides = {override.key: override.value for override in model_bundle.overrides} - self.LAT_SMOOTH_SECONDS = float(overrides.get('lat', ".2")) + self.LAT_SMOOTH_SECONDS = float(overrides.get('lat', ".0")) self.LONG_SMOOTH_SECONDS = float(overrides.get('long', ".0")) self.MIN_LAT_CONTROL_SPEED = 0.3 @@ -86,6 +86,10 @@ class ModelState: self.desire_reshape_dims = (self.numpy_inputs['desire'].shape[0], self.numpy_inputs['desire'].shape[1], -1, self.numpy_inputs['desire'].shape[2]) + @property + def mlsim(self) -> bool: + return bool(self.generation is not None and self.generation >= 11) + def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None: # Model decides when action is completed, so desire input is just a pulse triggered on rising edge @@ -151,7 +155,7 @@ class ModelState: self.full_prev_desired_curv[0,:-1] = self.full_prev_desired_curv[0,1:] self.full_prev_desired_curv[0,-1,:] = outputs['desired_curvature'][0, :] self.numpy_inputs[input_name_prev][:] = self.full_prev_desired_curv[0, self.temporal_idxs] - if self.generation == 11: + if self.mlsim: self.numpy_inputs[input_name_prev][:] = 0*self.full_prev_desired_curv[0, self.temporal_idxs] else: length = outputs['desired_curvature'][0].size @@ -165,7 +169,7 @@ class ModelState: 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.generation) + desired_curvature = get_curvature_from_output(model_output, v_ego, lat_action_t, self.mlsim) if v_ego > self.MIN_LAT_CONTROL_SPEED: desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, self.LAT_SMOOTH_SECONDS) else: diff --git a/sunnypilot/modeld_v2/parse_model_outputs_split.py b/sunnypilot/modeld_v2/parse_model_outputs_split.py index 7fab66e038..f99d5692c7 100644 --- a/sunnypilot/modeld_v2/parse_model_outputs_split.py +++ b/sunnypilot/modeld_v2/parse_model_outputs_split.py @@ -1,5 +1,6 @@ import numpy as np from openpilot.sunnypilot.models.split_model_constants import SplitModelConstants +from openpilot.sunnypilot.models.helpers import get_active_bundle def safe_exp(x, out=None): @@ -24,6 +25,8 @@ def softmax(x, axis=-1): class Parser: def __init__(self, ignore_missing=False): self.ignore_missing = ignore_missing + model_bundle = get_active_bundle() + self.generation = model_bundle.generation if model_bundle is not None else None def check_missing(self, outs, name): if name not in outs and not self.ignore_missing: @@ -88,37 +91,78 @@ class Parser: outs[name] = pred_mu_final.reshape(final_shape) outs[name + '_stds'] = pred_std_final.reshape(final_shape) + def _parse_mhp_output(self, name, output, shape_threshold, in_n_mhp, out_n_mhp, out_shape) -> None: + if name not in output: + return + + shape = output[name].shape[1] + shape_is_expected_size = None + + if name == 'lead': + shape_is_expected_size = shape == 2 * shape_threshold + elif name == 'plan': + shape_is_expected_size = shape <= 2 * shape_threshold + + use_default_format = self.generation >= 12 and shape_is_expected_size + in_n = 0 if use_default_format else in_n_mhp + out_n = 0 if use_default_format else out_n_mhp + + self.parse_mdn(name, output, in_n, out_n, out_shape) + + def parse_dynamic_outputs(self, outs: dict[str, np.ndarray]) -> None: + self._parse_mhp_output( + name='lead', + output=outs, + shape_threshold=SplitModelConstants.LEAD_MHP_SELECTION * SplitModelConstants.LEAD_TRAJ_LEN * SplitModelConstants.LEAD_WIDTH, + in_n_mhp=SplitModelConstants.LEAD_MHP_N, + out_n_mhp=SplitModelConstants.LEAD_MHP_SELECTION, + out_shape=(SplitModelConstants.LEAD_TRAJ_LEN, SplitModelConstants.LEAD_WIDTH), + ) + + self._parse_mhp_output( + name='plan', + output=outs, + shape_threshold=SplitModelConstants.PLAN_WIDTH * SplitModelConstants.IDX_N, + in_n_mhp=SplitModelConstants.PLAN_MHP_N, + out_n_mhp=SplitModelConstants.PLAN_MHP_SELECTION, + out_shape=(SplitModelConstants.IDX_N, SplitModelConstants.PLAN_WIDTH), + ) + def split_outputs(self, outs: dict[str, np.ndarray]) -> None: + if 'desired_curvature' in outs: + self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.DESIRED_CURV_WIDTH,)) + if 'desire_pred' in outs: + self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(SplitModelConstants.DESIRE_PRED_LEN,SplitModelConstants.DESIRE_PRED_WIDTH)) + if 'desire_state' in outs: + self.parse_categorical_crossentropy('desire_state', outs, out_shape=(SplitModelConstants.DESIRE_PRED_WIDTH,)) if 'lane_lines' in outs: self.parse_mdn('lane_lines', outs, in_N=0, out_N=0, - out_shape=(SplitModelConstants.NUM_LANE_LINES,SplitModelConstants.IDX_N,SplitModelConstants.LANE_LINES_WIDTH)) + out_shape=(SplitModelConstants.NUM_LANE_LINES,SplitModelConstants.IDX_N,SplitModelConstants.LANE_LINES_WIDTH)) + if 'lane_lines_prob' in outs: + self.parse_binary_crossentropy('lane_lines_prob', outs) + if 'lead_prob' in outs: + self.parse_binary_crossentropy('lead_prob', outs) + if 'lat_planner_solution' in outs: + self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.IDX_N,SplitModelConstants.LAT_PLANNER_SOLUTION_WIDTH)) + if 'meta' in outs: + self.parse_binary_crossentropy('meta', outs) + if 'road_edges' in outs: self.parse_mdn('road_edges', outs, in_N=0, out_N=0, - out_shape=(SplitModelConstants.NUM_ROAD_EDGES,SplitModelConstants.IDX_N,SplitModelConstants.LANE_LINES_WIDTH)) - self.parse_mdn('lead', outs, in_N=SplitModelConstants.LEAD_MHP_N, out_N=SplitModelConstants.LEAD_MHP_SELECTION, - out_shape=(SplitModelConstants.LEAD_TRAJ_LEN,SplitModelConstants.LEAD_WIDTH)) - if 'sim_pose' in outs: - self.parse_mdn('sim_pose', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.POSE_WIDTH,)) - for k in ['lead_prob', 'lane_lines_prob']: - self.parse_binary_crossentropy(k, outs) + out_shape=(SplitModelConstants.NUM_ROAD_EDGES,SplitModelConstants.IDX_N,SplitModelConstants.LANE_LINES_WIDTH)) + if 'sim_pose' in outs: + self.parse_mdn('sim_pose', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.POSE_WIDTH,)) def parse_vision_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.POSE_WIDTH,)) self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.WIDE_FROM_DEVICE_WIDTH,)) self.parse_mdn('road_transform', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.POSE_WIDTH,)) + self.parse_dynamic_outputs(outs) self.split_outputs(outs) - self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(SplitModelConstants.DESIRE_PRED_LEN,SplitModelConstants.DESIRE_PRED_WIDTH)) - self.parse_binary_crossentropy('meta', outs) return outs def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: - self.parse_mdn('plan', outs, in_N=SplitModelConstants.PLAN_MHP_N, out_N=SplitModelConstants.PLAN_MHP_SELECTION, - out_shape=(SplitModelConstants.IDX_N,SplitModelConstants.PLAN_WIDTH)) + self.parse_dynamic_outputs(outs) self.split_outputs(outs) - if 'lat_planner_solution' in outs: - self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.IDX_N,SplitModelConstants.LAT_PLANNER_SOLUTION_WIDTH)) - if 'desired_curvature' in outs: - self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.DESIRED_CURV_WIDTH,)) - self.parse_categorical_crossentropy('desire_state', outs, out_shape=(SplitModelConstants.DESIRE_PRED_WIDTH,)) return outs def parse_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: diff --git a/sunnypilot/models/helpers.py b/sunnypilot/models/helpers.py index a614447ceb..61a7e1a006 100644 --- a/sunnypilot/models/helpers.py +++ b/sunnypilot/models/helpers.py @@ -19,7 +19,7 @@ from openpilot.system.hardware import PC from openpilot.system.hardware.hw import Paths from pathlib import Path -CURRENT_SELECTOR_VERSION = 6 +CURRENT_SELECTOR_VERSION = 7 REQUIRED_MIN_SELECTOR_VERSION = 5 USE_ONNX = os.getenv('USE_ONNX', PC) diff --git a/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py b/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py index 18bfbe925c..6952a97f1f 100644 --- a/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py +++ b/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py @@ -16,12 +16,12 @@ DecState = custom.LongitudinalPlanSP.DynamicExperimentalControl.DynamicExperimen class LongitudinalPlannerSP: def __init__(self, CP: structs.CarParams, mpc): self.dec = DynamicExperimentalController(CP, mpc) - model_bundle = get_active_bundle() - self.generation = model_bundle.generation if model_bundle is not None else None + self.generation = int(model_bundle.generation) if (model_bundle := get_active_bundle()) else None @property def mlsim(self) -> bool: - return self.generation == 11 + # If we don't have a generation set, we assume it's default model. Which as of today are mlsim. + return bool(self.generation is None or self.generation >= 11) def get_mpc_mode(self) -> str | None: if not self.dec.active(): From e955590f111d94de69af69f00e30722af0f8f1e0 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sat, 19 Jul 2025 10:39:34 -0400 Subject: [PATCH 69/74] chore: remove compile_db from build targets (#1059) remove --compile_db flag --- .idea/tools/External Tools.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.idea/tools/External Tools.xml b/.idea/tools/External Tools.xml index fa2375e2ee..d5d03136ea 100644 --- a/.idea/tools/External Tools.xml +++ b/.idea/tools/External Tools.xml @@ -2,7 +2,7 @@ @@ -16,7 +16,7 @@ From 0d0f764a79b9d049539de867a0c4f3b29e669494 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 19 Jul 2025 07:55:13 -0700 Subject: [PATCH 70/74] ui: Quickboot Mode Toggle (#1045) * Fast boot mode * Unused * FastBoot -> QuickBoot * Move down, since name change * Chronological in manager too * last one b4 merge --------- Co-authored-by: Nayan Co-authored-by: DevTekVE --- common/params_keys.h | 4 ++- .../qt/offroad/settings/developer_panel.cc | 29 ++++++++++++++++++- .../qt/offroad/settings/developer_panel.h | 3 +- system/manager/manager.py | 2 ++ system/version.py | 6 +++- 5 files changed, 40 insertions(+), 4 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index e8c155ab41..8121e610f3 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -128,6 +128,7 @@ inline static std::unordered_map keys = { {"AutoLaneChangeTimer", PERSISTENT}, {"BlinkerMinLateralControlSpeed", PERSISTENT | BACKUP}, {"BlinkerPauseLateralControl", PERSISTENT | BACKUP}, + {"Brightness", PERSISTENT | BACKUP}, {"CarParamsSP", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"CarParamsSPCache", CLEAR_ON_MANAGER_START}, {"CarParamsSPPersistent", PERSISTENT}, @@ -139,10 +140,11 @@ inline static std::unordered_map keys = { {"DeviceBootMode", PERSISTENT | BACKUP}, {"EnableGithubRunner", PERSISTENT | BACKUP}, {"InteractivityTimeout", PERSISTENT | BACKUP}, + {"IsDevelopmentBranch", CLEAR_ON_MANAGER_START}, {"MaxTimeOffroad", PERSISTENT | BACKUP}, - {"Brightness", PERSISTENT | BACKUP}, {"ModelRunnerTypeCache", CLEAR_ON_ONROAD_TRANSITION}, {"OffroadMode", CLEAR_ON_MANAGER_START}, + {"QuickBootToggle", PERSISTENT | BACKUP}, {"QuietMode", PERSISTENT | BACKUP}, // MADS params diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc index a11a6cdbeb..4c4e18a5fc 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc @@ -11,6 +11,17 @@ DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(pare enableGithubRunner = new ParamControlSP("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), ""); addItem(enableGithubRunner); + // Quickboot Mode Toggle + prebuiltToggle = new ParamControlSP("QuickBootToggle", tr("Enable Quickboot Mode"), tr(""), ""); + addItem(prebuiltToggle); + + QObject::connect(prebuiltToggle, &ParamControl::toggleFlipped, [=](bool state) { + QString prebuiltPath = "/data/openpilot/prebuilt"; + state ? QFile(prebuiltPath).open(QIODevice::WriteOnly) : QFile::remove(prebuiltPath); + prebuiltToggle->refresh(); + }); + prebuiltToggle->setVisible(false); + // Error log button errorLogBtn = new ButtonControlSP(tr("Error Log"), tr("VIEW"), tr("View the error log for sunnypilot crashes.")); connect(errorLogBtn, &ButtonControlSP::clicked, [=]() { @@ -29,12 +40,28 @@ DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(pare void DeveloperPanelSP::updateToggles(bool offroad) { bool is_release = params.getBool("IsReleaseBranch"); + bool is_tested = params.getBool("IsTestedBranch"); + bool is_development = params.getBool("IsDevelopmentBranch"); + bool disable_updates = params.getBool("DisableUpdates"); + + prebuiltToggle->setVisible(!is_release && !is_tested && !is_development); + prebuiltToggle->setEnabled(disable_updates); + + params.putBool("QuickBootToggle", QFile::exists("/data/openpilot/prebuilt")); + prebuiltToggle->refresh(); + + prebuiltToggle->setDescription(disable_updates + ? tr("When toggled on, this creates a prebuilt file to allow accelerated boot times. When toggled off, " + "it immediately removes the prebuilt file so compilation of locally edited cpp files can be made. " + "

To edit C++ files locally on device, you MUST first turn off this toggle so the changes can recompile.") + : tr("Quickboot mode requires updates to be disabled.
Enable 'Disable Updates' in the Software panel first.")); enableGithubRunner->setVisible(!is_release); - errorLogBtn->setVisible(!is_release); + errorLogBtn->setVisible(!is_release && !is_tested); } void DeveloperPanelSP::showEvent(QShowEvent *event) { DeveloperPanel::showEvent(event); updateToggles(!uiState()->scene.started); + prebuiltToggle->showDescription(); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h index ba707b64de..1e375aa8de 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h @@ -5,7 +5,7 @@ * See the LICENSE.md file in the root directory for more details. */ #pragma once - +#include #include #include "selfdrive/ui/qt/offroad/developer_panel.h" @@ -18,6 +18,7 @@ public: private: ParamControlSP *enableGithubRunner; ButtonControlSP *errorLogBtn; + ParamControlSP *prebuiltToggle; Params params; private slots: diff --git a/system/manager/manager.py b/system/manager/manager.py index b67d592821..83c5fe2f55 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -71,6 +71,7 @@ def manager_init() -> None: ("ModelManager_LastSyncTime", "0"), ("ModelManager_ModelsCache", ""), ("NeuralNetworkLateralControl", "0"), + ("QuickBootToggle", "0"), ("QuietMode", "0"), ] @@ -103,6 +104,7 @@ def manager_init() -> None: params.put("GitCommitDate", build_metadata.openpilot.git_commit_date) params.put("GitBranch", build_metadata.channel) params.put("GitRemote", build_metadata.openpilot.git_origin) + 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("HardwareSerial", serial) diff --git a/system/version.py b/system/version.py index 11c6ced2b7..6e4d0fb42d 100755 --- a/system/version.py +++ b/system/version.py @@ -117,9 +117,13 @@ class BuildMetadata: def master_channel(self) -> bool: return self.channel in MASTER_SP_BRANCHES + @property + def development_channel(self) -> bool: + return self.channel.startswith("dev-") or self.channel.endswith("-prebuilt") + @property def channel_type(self) -> str: - if self.channel.startswith("dev-"): + if self.development_channel: return "development" elif self.channel.startswith("staging-"): return "staging" From 75556831059ef970c0cda7ee81ac53d546775993 Mon Sep 17 00:00:00 2001 From: Nayan Date: Sat, 19 Jul 2025 13:11:49 -0400 Subject: [PATCH 71/74] UI: Advanced Controls Toggle (#1053) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add advancedControl bool to SP Controls * add Advanced Controls toggle in SP Dev Panel * merge - ui: Init Developer Panel SP#1054 * because @discountchubbs did not want to commit directly to the pr 🤷‍♂️ * enable onroad too * hide by default only on staging & release --------- Co-authored-by: discountchubbs Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Co-authored-by: DevTekVE --- common/params_keys.h | 1 + .../qt/offroad/settings/developer_panel.cc | 15 +++++++- .../qt/offroad/settings/developer_panel.h | 1 + .../ui/sunnypilot/qt/widgets/controls.cc | 31 +++++++++++----- selfdrive/ui/sunnypilot/qt/widgets/controls.h | 36 ++++++++++++++----- system/manager/manager.py | 1 + 6 files changed, 67 insertions(+), 18 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index 8121e610f3..76b5033fd1 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -146,6 +146,7 @@ inline static std::unordered_map keys = { {"OffroadMode", CLEAR_ON_MANAGER_START}, {"QuickBootToggle", PERSISTENT | BACKUP}, {"QuietMode", PERSISTENT | BACKUP}, + {"ShowAdvancedControls", PERSISTENT | BACKUP}, // MADS params {"Mads", PERSISTENT | BACKUP}, diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc index 4c4e18a5fc..f781a015c8 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc @@ -7,6 +7,17 @@ #include "selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h" DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(parent) { + + // Advanced Controls Toggle + showAdvancedControls = new ParamControlSP("ShowAdvancedControls", tr("Show Advanced Controls"), tr("Toggle visibility of advanced sunnypilot controls.\nThis only toggles the visibility of the controls; it does not toggle the actual control enabled/disabled state."), ""); + addItem(showAdvancedControls); + + QObject::connect(showAdvancedControls, &ParamControlSP::toggleFlipped, this, [=](bool) { + AbstractControlSP::UpdateAllAdvancedControls(); + updateToggles(!uiState()->scene.started); + }); + showAdvancedControls->showDescription(); + // Github Runner Toggle enableGithubRunner = new ParamControlSP("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), ""); addItem(enableGithubRunner); @@ -57,11 +68,13 @@ void DeveloperPanelSP::updateToggles(bool offroad) { : tr("Quickboot mode requires updates to be disabled.
Enable 'Disable Updates' in the Software panel first.")); enableGithubRunner->setVisible(!is_release); - errorLogBtn->setVisible(!is_release && !is_tested); + errorLogBtn->setVisible(!is_release); + showAdvancedControls->setEnabled(true); } void DeveloperPanelSP::showEvent(QShowEvent *event) { DeveloperPanel::showEvent(event); updateToggles(!uiState()->scene.started); + AbstractControlSP::UpdateAllAdvancedControls(); prebuiltToggle->showDescription(); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h index 1e375aa8de..d63fe18d04 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h @@ -20,6 +20,7 @@ private: ButtonControlSP *errorLogBtn; ParamControlSP *prebuiltToggle; Params params; + ParamControlSP *showAdvancedControls; private slots: void updateToggles(bool offroad); diff --git a/selfdrive/ui/sunnypilot/qt/widgets/controls.cc b/selfdrive/ui/sunnypilot/qt/widgets/controls.cc index d873913b57..28374f3a9f 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/controls.cc +++ b/selfdrive/ui/sunnypilot/qt/widgets/controls.cc @@ -30,9 +30,24 @@ QFrame *vertical_space(int height, QWidget *parent) { } // AbstractControlSP +std::vector AbstractControlSP::advanced_controls_; +AbstractControlSP::~AbstractControlSP() { UnregisterAdvancedControl(this); } -AbstractControlSP::AbstractControlSP(const QString &title, const QString &desc, const QString &icon, QWidget *parent) - : AbstractControl(title, desc, icon, parent) { +void AbstractControlSP::RegisterAdvancedControl(AbstractControlSP *ctrl) { advanced_controls_.push_back(ctrl); } + +void AbstractControlSP::UnregisterAdvancedControl(AbstractControlSP *ctrl) { + advanced_controls_.erase(std::remove(advanced_controls_.begin(), advanced_controls_.end(), ctrl), advanced_controls_.end()); +} + +void AbstractControlSP::UpdateAllAdvancedControls() { + bool visibility = Params().getBool("ShowAdvancedControls"); + advanced_controls_.erase(std::remove(advanced_controls_.begin(), advanced_controls_.end(), nullptr), advanced_controls_.end()); + for (auto *ctrl : advanced_controls_) ctrl->setVisible(visibility); +} + +AbstractControlSP::AbstractControlSP(const QString &title, const QString &desc, const QString &icon, QWidget *parent, bool advancedControl) + : AbstractControl(title, desc, icon, parent), isAdvancedControl(advancedControl) { + if (isAdvancedControl) RegisterAdvancedControl(this); main_layout = new QVBoxLayout(this); main_layout->setMargin(0); @@ -82,8 +97,8 @@ void AbstractControlSP::hideEvent(QHideEvent *e) { } } -AbstractControlSP_SELECTOR::AbstractControlSP_SELECTOR(const QString &title, const QString &desc, const QString &icon, QWidget *parent) - : AbstractControlSP(title, desc, icon, parent) { +AbstractControlSP_SELECTOR::AbstractControlSP_SELECTOR(const QString &title, const QString &desc, const QString &icon, QWidget *parent, bool advancedControl) + : AbstractControlSP(title, desc, icon, parent, advancedControl) { if (title_label != nullptr) { delete title_label; @@ -169,8 +184,8 @@ void AbstractControlSP_SELECTOR::hideEvent(QHideEvent *e) { // controls -ButtonControlSP::ButtonControlSP(const QString &title, const QString &text, const QString &desc, QWidget *parent) - : AbstractControlSP(title, desc, "", parent) { +ButtonControlSP::ButtonControlSP(const QString &title, const QString &text, const QString &desc, QWidget *parent, bool advancedControl) + : AbstractControlSP(title, desc, "", parent, advancedControl) { btn.setText(text); btn.setStyleSheet(R"( @@ -225,8 +240,8 @@ void ElidedLabelSP::paintEvent(QPaintEvent *event) { // ParamControlSP -ParamControlSP::ParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent) - : ToggleControlSP(title, desc, icon, false, parent) { +ParamControlSP::ParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent, bool advancedControl) + : ToggleControlSP(title, desc, icon, false, parent, advancedControl){ key = param.toStdString(); QObject::connect(this, &ParamControlSP::toggleFlipped, this, &ParamControlSP::toggleClicked); diff --git a/selfdrive/ui/sunnypilot/qt/widgets/controls.h b/selfdrive/ui/sunnypilot/qt/widgets/controls.h index 29b6e6e6ea..a840febfe7 100644 --- a/selfdrive/ui/sunnypilot/qt/widgets/controls.h +++ b/selfdrive/ui/sunnypilot/qt/widgets/controls.h @@ -57,6 +57,7 @@ class AbstractControlSP : public AbstractControl { Q_OBJECT public: + ~AbstractControlSP(); void setDescription(const QString &desc) override { if (description) description->setText(desc); } @@ -81,13 +82,30 @@ public slots: description->setVisible(true); } + void setVisible(bool visible) override { + bool _visible = visible; + if (isAdvancedControl && !params.getBool("ShowAdvancedControls")) { + _visible = false; + } + AbstractControl::setVisible(_visible); + } + + static void RegisterAdvancedControl(AbstractControlSP *ctrl); + static void UnregisterAdvancedControl(AbstractControlSP *ctrl); + static void UpdateAllAdvancedControls(); + protected: - AbstractControlSP(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr); + AbstractControlSP(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr, bool advancedControl = false); void hideEvent(QHideEvent *e) override; QVBoxLayout *main_layout; ElidedLabelSP *value; QLabel *description = nullptr; + bool isAdvancedControl; + +private: + Params params; + static std::vector advanced_controls_; }; // AbstractControlSP_SELECTOR @@ -97,7 +115,7 @@ class AbstractControlSP_SELECTOR : public AbstractControlSP { protected: QSpacerItem *spacingItem = new QSpacerItem(44, 44, QSizePolicy::Minimum, QSizePolicy::Fixed); - AbstractControlSP_SELECTOR(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr); + AbstractControlSP_SELECTOR(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr, bool advancedControl = false); void hideEvent(QHideEvent *e) override; }; @@ -123,7 +141,7 @@ class ButtonControlSP : public AbstractControlSP { Q_OBJECT public: - ButtonControlSP(const QString &title, const QString &text, const QString &desc = "", QWidget *parent = nullptr); + ButtonControlSP(const QString &title, const QString &text, const QString &desc = "", QWidget *parent = nullptr, bool advancedControl = false); inline void setText(const QString &text) { btn.setText(text); } inline QString text() const { return btn.text(); } inline void click() { btn.click(); } @@ -142,7 +160,7 @@ class ToggleControlSP : public AbstractControlSP { Q_OBJECT public: - ToggleControlSP(const QString &title, const QString &desc = "", const QString &icon = "", const bool state = false, QWidget *parent = nullptr) : AbstractControlSP(title, desc, icon, parent) { + ToggleControlSP(const QString &title, const QString &desc = "", const QString &icon = "", const bool state = false, QWidget *parent = nullptr, bool advancedControl = false) : AbstractControlSP(title, desc, icon, parent, advancedControl) { // space between toggle and title icon_label = new QLabel(this); hlayout->addWidget(icon_label); @@ -173,7 +191,7 @@ class ParamControlSP : public ToggleControlSP { Q_OBJECT public: - ParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr); + ParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr, bool advancedControl = false); void setConfirmation(bool _confirm, bool _store_confirm) { confirm = _confirm; store_confirm = _store_confirm; @@ -219,7 +237,7 @@ class MultiButtonControlSP : public AbstractControlSP_SELECTOR { public: MultiButtonControlSP(const QString &title, const QString &desc, const QString &icon, - const std::vector &button_texts, const int minimum_button_width = 225, const bool inline_layout = false) : AbstractControlSP_SELECTOR(title, desc, icon), button_texts(button_texts), is_inline_layout(inline_layout) { + const std::vector &button_texts, const int minimum_button_width = 225, const bool inline_layout = false, bool advancedControl = false) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr, advancedControl), button_texts(button_texts), is_inline_layout(inline_layout) { const QString style = R"( QPushButton { border-radius: 20px; @@ -371,8 +389,8 @@ class ButtonParamControlSP : public MultiButtonControlSP { Q_OBJECT public: ButtonParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, - const std::vector &button_texts, const int minimum_button_width = 225, const bool inline_layout = false) : MultiButtonControlSP(title, desc, icon, - button_texts, minimum_button_width, inline_layout) { + const std::vector &button_texts, const int minimum_button_width = 225, 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()); @@ -499,7 +517,7 @@ private: public: OptionControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, const MinMaxValue &range, const int per_value_change = 1, const bool inline_layout = false, - const QMap *valMap = nullptr, bool scale_float = false) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr), _title(title), valueMap(valMap), is_inline_layout(inline_layout), use_float_scaling(scale_float) { + const QMap *valMap = nullptr, bool scale_float = false, bool advancedControl = false) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr, advancedControl), _title(title), valueMap(valMap), is_inline_layout(inline_layout), use_float_scaling(scale_float) { const QString style = R"( QPushButton { border-radius: 20px; diff --git a/system/manager/manager.py b/system/manager/manager.py index 83c5fe2f55..c59f0118aa 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -73,6 +73,7 @@ def manager_init() -> None: ("NeuralNetworkLateralControl", "0"), ("QuickBootToggle", "0"), ("QuietMode", "0"), + ("ShowAdvancedControls", "0" if build_metadata.tested_channel else "1"), ] # device boot mode From 08f075ab23c3fcc29e860bcd4940d862dfc9c98c Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Sat, 19 Jul 2025 10:35:34 -0700 Subject: [PATCH 72/74] ui: Link Advanced Controls (#1060) --- .../ui/sunnypilot/qt/offroad/settings/developer_panel.cc | 4 ++-- selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc | 2 +- selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.cc | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc index f781a015c8..5c9f0ff99e 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc @@ -19,11 +19,11 @@ DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(pare showAdvancedControls->showDescription(); // Github Runner Toggle - enableGithubRunner = new ParamControlSP("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), ""); + enableGithubRunner = new ParamControlSP("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), "", this, true); addItem(enableGithubRunner); // Quickboot Mode Toggle - prebuiltToggle = new ParamControlSP("QuickBootToggle", tr("Enable Quickboot Mode"), tr(""), ""); + prebuiltToggle = new ParamControlSP("QuickBootToggle", tr("Enable Quickboot Mode"), tr(""), "", this, true); addItem(prebuiltToggle); QObject::connect(prebuiltToggle, &ParamControl::toggleFlipped, [=](bool state) { diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc index 4cbd4f11e4..e565269eb7 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc @@ -98,7 +98,7 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) { delay_control = new OptionControlSP("LagdToggledelay", tr("Adjust Software Delay"), tr("Adjust the software delay when Live Learning Steer Delay is toggled off." "\nThe default software delay value is 0.2"), - "", {5, 30}, 1, false, nullptr, true); + "", {5, 30}, 1, false, nullptr, true, true); connect(delay_control, &OptionControlSP::updateLabels, [=]() { float value = QString::fromStdString(params.get("LagdToggledelay")).toFloat(); diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.cc index 7bef7ed203..a1961cb1ea 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.cc @@ -24,7 +24,7 @@ SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) { tr("Disable Updates"), tr("When enabled, software updates will be disabled. This requires a reboot to take effect."), "../assets/icons/icon_warning.png", - this); + this, true); disableUpdatesToggle->showDescription(); addItem(disableUpdatesToggle); connect(disableUpdatesToggle, &ParamControl::toggleFlipped, this, &SoftwarePanelSP::handleDisableUpdatesToggled); From 8e3e5b13aacdbca1b347122350a6b9151dc80563 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sat, 19 Jul 2025 20:05:32 +0200 Subject: [PATCH 73/74] sunnylink: Adding more keys to be backed up (#1061) * Enhance parameter backup capabilities - Added `BACKUP` attribute to various persistent parameters to ensure their inclusion in backup processes. * Add more keys --------- Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> --- common/params_keys.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index 76b5033fd1..7aa4bc6b72 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -5,8 +5,8 @@ inline static std::unordered_map keys = { {"AccessToken", CLEAR_ON_MANAGER_START | DONT_LOG}, - {"AdbEnabled", PERSISTENT}, - {"AlwaysOnDM", PERSISTENT}, + {"AdbEnabled", PERSISTENT | BACKUP}, + {"AlwaysOnDM", PERSISTENT | BACKUP}, {"ApiCache_Device", PERSISTENT}, {"ApiCache_FirehoseStats", PERSISTENT}, {"AssistNowToken", PERSISTENT}, @@ -78,7 +78,7 @@ inline static std::unordered_map keys = { {"LocationFilterInitialState", PERSISTENT}, {"LongitudinalManeuverMode", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"LongitudinalPersonality", PERSISTENT | BACKUP}, - {"NetworkMetered", PERSISTENT}, + {"NetworkMetered", PERSISTENT | BACKUP}, {"ObdMultiplexingChanged", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"ObdMultiplexingEnabled", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"Offroad_BadNvme", CLEAR_ON_MANAGER_START}, @@ -124,15 +124,15 @@ inline static std::unordered_map keys = { // --- sunnypilot params --- // {"ApiCache_DriveStats", PERSISTENT}, - {"AutoLaneChangeBsmDelay", PERSISTENT}, - {"AutoLaneChangeTimer", PERSISTENT}, + {"AutoLaneChangeBsmDelay", PERSISTENT | BACKUP}, + {"AutoLaneChangeTimer", PERSISTENT | BACKUP}, {"BlinkerMinLateralControlSpeed", PERSISTENT | BACKUP}, {"BlinkerPauseLateralControl", PERSISTENT | BACKUP}, {"Brightness", PERSISTENT | BACKUP}, {"CarParamsSP", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"CarParamsSPCache", CLEAR_ON_MANAGER_START}, {"CarParamsSPPersistent", PERSISTENT}, - {"CarPlatformBundle", PERSISTENT}, + {"CarPlatformBundle", PERSISTENT | BACKUP}, {"ChevronInfo", PERSISTENT | BACKUP}, {"CustomAccIncrementsEnabled", PERSISTENT | BACKUP}, {"CustomAccLongPressIncrement", PERSISTENT | BACKUP}, @@ -177,9 +177,9 @@ inline static std::unordered_map keys = { {"BackupManager_RestoreVersion", PERSISTENT}, // sunnypilot car specific params - {"HyundaiLongitudinalTuning", PERSISTENT}, + {"HyundaiLongitudinalTuning", PERSISTENT | BACKUP}, - {"DynamicExperimentalControl", PERSISTENT}, + {"DynamicExperimentalControl", PERSISTENT | BACKUP}, {"BlindSpot", PERSISTENT | BACKUP}, // model panel params From c46ecd18fad1767f15d748e218abafed3481febd Mon Sep 17 00:00:00 2001 From: Nayan Date: Sat, 19 Jul 2025 15:40:16 -0400 Subject: [PATCH 74/74] UI: Clear Model Cache (#1058) * clear model cache * add cache size * move to model manager * fix handling for default model --------- Co-authored-by: DevTekVE Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> --- common/params_keys.h | 1 + .../qt/offroad/settings/models_panel.cc | 38 +++++++++++++++++++ .../qt/offroad/settings/models_panel.h | 3 ++ sunnypilot/models/manager.py | 29 ++++++++++++++ system/hardware/hw.h | 4 ++ 5 files changed, 75 insertions(+) diff --git a/common/params_keys.h b/common/params_keys.h index 7aa4bc6b72..dfcf410a51 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -156,6 +156,7 @@ inline static std::unordered_map keys = { // Model Manager params {"ModelManager_ActiveBundle", PERSISTENT}, + {"ModelManager_ClearCache", CLEAR_ON_MANAGER_START}, {"ModelManager_DownloadIndex", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"ModelManager_LastSyncTime", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"ModelManager_ModelsCache", PERSISTENT | BACKUP}, diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc index e565269eb7..c4cb4b4767 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include "common/model.h" #include "selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h" @@ -66,6 +68,11 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) { connect(uiStateSP(), &UIStateSP::uiUpdate, this, &ModelsPanel::updateLabels); list->addItem(currentModelLblBtn); + clearModelCacheBtn = new ButtonControlSP(tr("Clear Model Cache"), tr("CLEAR"), "", this); + connect(clearModelCacheBtn, &ButtonControlSP::clicked, this, &ModelsPanel::clearModelCache); + + list->addItem(clearModelCacheBtn); + // Create progress bars for downloads supercomboProgressBar = createProgressBar(this); QString supercomboType = tr("Driving Model"); @@ -367,6 +374,8 @@ void ModelsPanel::updateLabels() { delay_control->setLabel(QString::number(value, 'f', 2) + "s"); delay_control->showDescription(); } + + clearModelCacheBtn->setValue(QString::number(calculateCacheSize(), 'f', 2) + " MB"); } /** @@ -387,3 +396,32 @@ void ModelsPanel::showResetParamsDialog() { params.remove("LiveTorqueParameters"); } } + +void ModelsPanel::clearModelCache() { + QString confirmMsg = tr("This will delete ALL downloaded models from the cache" + "
except the currently active model." + "

Are you sure you want to continue?"); + QString content("

" + tr("Driving Model Selector") + "


" + "

" + confirmMsg + "

"); + if (showConfirmationDialog( + content, + tr("Clear Cache"))) { + params.putBool("ModelManager_ClearCache", true); + } +} + +double ModelsPanel::calculateCacheSize() { + QFuture future_ModelCacheSize = QtConcurrent::run([=]() { + + QDir model_dir(QString::fromStdString(Path::model_root())); + QFileInfoList model_files = model_dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot); + qint64 totalSize = 0; + for (const QFileInfo &model_file : model_files) { + if (model_file.isFile()) { + totalSize += model_file.size(); + } + } + return totalSize; + }); + return static_cast(future_ModelCacheSize) / (1024.0 * 1024.0); +} diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h index 61f15e6d3c..06a2380f39 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h @@ -41,6 +41,8 @@ private: cereal::ModelManagerSP::Reader model_manager; cereal::ModelManagerSP::DownloadStatus download_status{}; cereal::ModelManagerSP::DownloadStatus prev_download_status{}; + void clearModelCache(); + double calculateCacheSize(); bool canContinueOnMeteredDialog() { if (!is_metered) return true; @@ -75,5 +77,6 @@ private: QProgressBar *policyProgressBar; QFrame *policyFrame; Params params; + ButtonControlSP *clearModelCacheBtn; }; diff --git a/sunnypilot/models/manager.py b/sunnypilot/models/manager.py index 630d6a8d77..8913135203 100644 --- a/sunnypilot/models/manager.py +++ b/sunnypilot/models/manager.py @@ -178,6 +178,10 @@ class ModelManagerSP: finally: self.params.put("ModelManager_DownloadIndex", "") + if self.params.get("ModelManager_ClearCache", block=False, encoding="utf-8"): + self.clear_model_cache() + self.params.remove("ModelManager_ClearCache") + self._report_status() rk.keep_time() @@ -185,6 +189,31 @@ class ModelManagerSP: cloudlog.exception(f"Error in main thread: {str(e)}") rk.keep_time() + def clear_model_cache(self) -> None: + """ + Clears the model cache directory of all files except those in the active model bundle. + """ + + # Get list of files used by active model bundle + active_files = [] + if self.active_bundle is not None: # When the default model is active + for model in self.active_bundle.models: + if hasattr(model, 'artifact') and model.artifact.fileName: + active_files.append(model.artifact.fileName) + if hasattr(model, 'metadata') and model.metadata.fileName: + active_files.append(model.metadata.fileName) + + # Remove all files except active ones + model_dir = Paths.model_root() + try: + for filename in os.listdir(model_dir): + if filename not in active_files: + file_path = os.path.join(model_dir, filename) + if os.path.isfile(file_path): + os.remove(file_path) + cloudlog.info("Model cache cleared, keeping active model files") + except Exception as e: + cloudlog.exception(f"Error clearing model cache: {str(e)}") def main(): ModelManagerSP().main_thread() diff --git a/system/hardware/hw.h b/system/hardware/hw.h index d2083a5985..bc9d17dd81 100644 --- a/system/hardware/hw.h +++ b/system/hardware/hw.h @@ -55,4 +55,8 @@ namespace Path { return "/dev/shm"; #endif } + + inline std::string model_root() { + return Hardware::PC() ? Path::comma_home() + "/media/0/models" : "/data/media/0/models"; + } } // namespace Path