From 7859de77b91f09accf1f0cab2ae6b88dde746dfb Mon Sep 17 00:00:00 2001 From: Comma Device Date: Mon, 17 Aug 2026 20:04:02 +0800 Subject: [PATCH] Apply upstream sync merge 9fb45b31 (sync-20260816) into c3xl-dev Applied merge commit 9fb45b31 (-m 1 vs 94a32493e) bringing upstream 053d9c4468 changes: chestnut/hardware updates, Offroad_ChestnutBranch param (+ libparams_c.so rebuild), bigModelFailed alert, hardwared/loggerd/pigeon updates, UI alert renderer tweaks. Skipped opendbc_repo / teleoprtc_repo submodule bumps (vendored trees kept), and skipped LFS-tracked big_driving_supercombo.onnx / software.png. --- docs/CARS.md | 25 ++-- openpilot/common/hardware/base.h | 2 +- openpilot/common/hardware/comma/hardware.h | 16 +- openpilot/common/libparams_c.so | Bin 2988808 -> 2988824 bytes openpilot/common/params_keys.h | 2 +- .../selfdrive/controls/lib/longcontrol.py | 3 +- .../lib/longitudinal_mpc_lib/SConscript | 1 + openpilot/selfdrive/modeld/SConscript | 4 +- .../selfdrive/pandad/tests/test_pandad_spi.py | 1 - .../selfdrive/selfdrived/alerts_offroad.json | 4 +- openpilot/selfdrive/selfdrived/events.py | 3 +- openpilot/selfdrive/selfdrived/selfdrived.py | 5 +- .../test/process_replay/test_processes.py | 5 +- openpilot/selfdrive/test/test_onroad.py | 34 +++-- .../ui/mici/layouts/settings/software.py | 2 +- .../ui/mici/onroad/alert_renderer.py | 2 +- openpilot/selfdrive/ui/mici/widgets/button.py | 4 +- .../selfdrive/ui/onroad/alert_renderer.py | 2 +- openpilot/selfdrive/ui/tests/test_soundd.py | 8 +- openpilot/system/hardware/chestnut/flash.py | 9 +- openpilot/system/hardware/hardwared.py | 3 + openpilot/system/loggerd/logger.cc | 6 +- openpilot/system/loggerd/logger.h | 2 +- openpilot/system/ubloxd/pigeond.py | 38 +++-- openpilot/system/updated/updated.py | 3 - tools/release/build_release.sh | 50 ++++--- tools/release/build_stripped.sh | 16 +- tools/release/release_files.py | 18 ++- uv.lock | 139 ++++++++++-------- 29 files changed, 237 insertions(+), 170 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 22c35b9a1..0866436c0 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 342 Supported Cars +# 345 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -34,6 +34,7 @@ A supported vehicle is one that just works when you install a comma device. All |Chrysler|Pacifica Hybrid 2019-25|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None||| |CUPRA[12](#footnotes)|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|CUPRA[12](#footnotes)|Born 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot[16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -82,7 +83,7 @@ A supported vehicle is one that just works when you install a comma device. All |Honda|City (Brazil only) 2023-25|All|openpilot available[1,5](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic 2019-21|All|openpilot available[1,5](#footnotes)|0 mph|2 mph[4](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Honda|Civic 2022-24|All|openpilot available[1,5](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic 2022-26|All|openpilot available[1,5](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[1,5](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic Hatchback 2019-21|All|openpilot available[1,5](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic Hatchback 2022-24|All|openpilot available[1,5](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -158,7 +159,7 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Tucson Plug-in Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Carnival 2022-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Carnival (China only) 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -242,20 +243,20 @@ A supported vehicle is one that just works when you install a comma device. All |Rivian|R1T 2025|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |SEAT[12](#footnotes)|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |SEAT[12](#footnotes)|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Subaru|Ascent 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Ascent 2019-21|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Forester 2017-18|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Forester 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Impreza 2017-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Impreza 2020-22|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Forester 2019-21|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Impreza 2017-19|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Impreza 2020-22|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Legacy 2015-18|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Legacy 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Outback 2015-17|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Outback 2018-19|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Outback 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|XV 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|XV 2020-21|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|XV 2018-19|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|XV 2020-21|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Škoda|Fabia 2022-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[17](#footnotes)||| |Škoda|Kamiq 2021-23[13,15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[17](#footnotes)||| |Škoda[12](#footnotes)|Karoq 2019-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| @@ -334,6 +335,8 @@ A supported vehicle is one that just works when you install a comma device. All |Volkswagen[12](#footnotes)|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Volkswagen[12](#footnotes)|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Volkswagen[12](#footnotes)|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[12](#footnotes)|ID.4 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot[16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[12](#footnotes)|ID.4 2024-25|Adaptive Cruise Control (ACC) & Lane Assist|openpilot[16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Volkswagen[12](#footnotes)|Jetta 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Volkswagen[12](#footnotes)|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Volkswagen|Passat 2015-22[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| diff --git a/openpilot/common/hardware/base.h b/openpilot/common/hardware/base.h index f4546adfa..53db48ff5 100644 --- a/openpilot/common/hardware/base.h +++ b/openpilot/common/hardware/base.h @@ -15,7 +15,7 @@ public: static std::string get_serial() { return "cccccc"; } - static std::map get_init_logs() { + static std::map get_init_logs(bool route_log = false) { return {}; } diff --git a/openpilot/common/hardware/comma/hardware.h b/openpilot/common/hardware/comma/hardware.h index 8189a32a9..48fa5d7bf 100644 --- a/openpilot/common/hardware/comma/hardware.h +++ b/openpilot/common/hardware/comma/hardware.h @@ -60,7 +60,7 @@ public: std::ofstream("/sys/class/leds/led:switch_2/brightness") << value << "\n"; } - static std::map get_init_logs() { + static std::map get_init_logs(bool route_log = false) { std::map ret = { {"/BUILD", util::read_file("/BUILD")}, {"lsblk", util::check_output("lsblk -o NAME,SIZE,STATE,VENDOR,MODEL,REV,SERIAL")}, @@ -74,12 +74,14 @@ public: temp.erase(temp.find_last_not_of(std::string("\0\r\n", 3))+1); ret["boot temp"] = temp; - // TODO: log something from system and boot - for (std::string part : {"xbl", "abl", "aop", "devcfg", "xbl_config"}) { - for (std::string slot : {"a", "b"}) { - std::string partition = part + "_" + slot; - std::string hash = util::check_output("sha256sum /dev/disk/by-partlabel/" + partition); - ret[partition] = hash.substr(0, hash.find_first_of(" ")); + // TODO: these are too slow to do on route log inits. need to do it async? + if (!route_log) { + for (std::string part : {"xbl", "abl", "aop", "devcfg", "xbl_config"}) { + for (std::string slot : {"a", "b"}) { + std::string partition = part + "_" + slot; + std::string hash = util::check_output("sha256sum /dev/disk/by-partlabel/" + partition); + ret[partition] = hash.substr(0, hash.find_first_of(" ")); + } } } diff --git a/openpilot/common/libparams_c.so b/openpilot/common/libparams_c.so index 256aeb034dad06fcb19460e75d01e11ca4ab86c0..ac55ae211abb9a5ea676da59ed08b7eec94493bf 100755 GIT binary patch delta 68071 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 delta 68219 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 keys = { {"ObdMultiplexingChanged", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}}, {"ObdMultiplexingEnabled", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}}, {"Offroad_CarUnrecognized", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}}, + {"Offroad_ChestnutBranch", {CLEAR_ON_MANAGER_START, JSON}}, {"Offroad_ConnectivityNeeded", {CLEAR_ON_MANAGER_START, JSON}}, {"Offroad_ConnectivityNeededPrompt", {CLEAR_ON_MANAGER_START, JSON}}, {"Offroad_ExcessiveActuation", {PERSISTENT, JSON}}, - {"Offroad_NeosUpdate", {CLEAR_ON_MANAGER_START, JSON}}, {"Offroad_NoFirmware", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}}, {"Offroad_Recalibration", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, JSON}}, {"Offroad_TemperatureTooHigh", {CLEAR_ON_MANAGER_START, JSON}}, diff --git a/openpilot/selfdrive/controls/lib/longcontrol.py b/openpilot/selfdrive/controls/lib/longcontrol.py index e3b612e79..3a20601c4 100644 --- a/openpilot/selfdrive/controls/lib/longcontrol.py +++ b/openpilot/selfdrive/controls/lib/longcontrol.py @@ -44,8 +44,7 @@ class LongControl: self.CP = CP self.CP_SP = CP_SP self.long_control_state = LongCtrlState.off - self.pid = PIDController((CP.longitudinalTuning.kpBP, CP.longitudinalTuning.kpV), - (CP.longitudinalTuning.kiBP, CP.longitudinalTuning.kiV), + self.pid = PIDController(0.0, (CP.longitudinalTuning.kiBP, CP.longitudinalTuning.kiV), rate=1 / DT_CTRL) self.last_output_accel = 0.0 diff --git a/openpilot/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript b/openpilot/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript index 636ef0fb2..fa249765b 100644 --- a/openpilot/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript +++ b/openpilot/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript @@ -35,6 +35,7 @@ build_files = [f'{gen}/acados_solver_long.c'] + casadi_model + casadi_cost_y + c # extra generated files used to trigger a rebuild generated_files = [ + 'acados_ocp_long.json', f'{gen}/Makefile', f'{gen}/main_long.c', diff --git a/openpilot/selfdrive/modeld/SConscript b/openpilot/selfdrive/modeld/SConscript index 30b008078..30a31aae2 100644 --- a/openpilot/selfdrive/modeld/SConscript +++ b/openpilot/selfdrive/modeld/SConscript @@ -79,7 +79,9 @@ for usbgpu in [False, True] if USBGPU else [False]: file_prefix, cmd_flags = ('big_', usbgpu_tg_flags) if usbgpu else ('big_' if os.getenv('BIG_INTO_SMALL') else '', tg_flags) driving_onnx_deps = get_existing_chunks(File(f"models/{file_prefix}driving_supercombo.onnx").abspath) camera_res_args = ' '.join(f'{cw}x{ch}' for cw, ch in CAMERA_CONFIGS) - cmd = (f'{cmd_flags} {mac_brew_string} python3 {modeld_dir}/compile_modeld.py ' + # CPU 7 is isolated with isolcpus on AGNOS, so explicitly pin the compiler to it. + taskset = 'taskset -c 7 ' if arch == 'comma_arm64' else '' + cmd = (f'{cmd_flags} {mac_brew_string} {taskset}python3 {modeld_dir}/compile_modeld.py ' f'--model-size {model_w}x{model_h} ' f'--camera-resolutions {camera_res_args} ' f'--onnx {File(f"models/{file_prefix}driving_supercombo.onnx").abspath} ' diff --git a/openpilot/selfdrive/pandad/tests/test_pandad_spi.py b/openpilot/selfdrive/pandad/tests/test_pandad_spi.py index ba2e31ce8..8ca39445a 100755 --- a/openpilot/selfdrive/pandad/tests/test_pandad_spi.py +++ b/openpilot/selfdrive/pandad/tests/test_pandad_spi.py @@ -102,7 +102,6 @@ class TestBoarddSpi(OpenpilotTestCase): edt = 1e3 / SERVICE_LIST[service].frequency assert edt*0.9 < np.mean(dts) < edt*1.1 assert np.max(dts) < edt*8 - assert np.min(dts) < edt assert len(dts) >= ((et-0.5)*SERVICE_LIST[service].frequency*0.8) with subtests.test(msg="CAN traffic"): diff --git a/openpilot/selfdrive/selfdrived/alerts_offroad.json b/openpilot/selfdrive/selfdrived/alerts_offroad.json index 8a77bd29a..226be0668 100644 --- a/openpilot/selfdrive/selfdrived/alerts_offroad.json +++ b/openpilot/selfdrive/selfdrived/alerts_offroad.json @@ -17,8 +17,8 @@ "severity": 1, "_comment": "Set extra field to the failed reason." }, - "Offroad_NeosUpdate": { - "text": "An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install.", + "Offroad_ChestnutBranch": { + "text": "Chestnut detected! Switch to the release-chestnut branch to use chestnut-class models.", "severity": 0 }, "Offroad_UnregisteredHardware": { diff --git a/openpilot/selfdrive/selfdrived/events.py b/openpilot/selfdrive/selfdrived/events.py index b53ee45fd..bddd1661e 100755 --- a/openpilot/selfdrive/selfdrived/events.py +++ b/openpilot/selfdrive/selfdrived/events.py @@ -236,7 +236,8 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { }, EventName.bigModelFailed: { - ET.PERMANENT: NormalPermanentAlert(tr_noop("Big Model Failed "), "Restart the car to retry,\nnow driving on small model", duration=20.), + ET.SOFT_DISABLE: soft_disable_alert("Big Model Failed"), + ET.PERMANENT: NormalPermanentAlert(tr_noop("Big Model Failed "), "Restart the car to retry,\nsmall model is still available", duration=20.), }, EventName.lateralManeuver: { diff --git a/openpilot/selfdrive/selfdrived/selfdrived.py b/openpilot/selfdrive/selfdrived/selfdrived.py index 26077eaf8..1813c6703 100755 --- a/openpilot/selfdrive/selfdrived/selfdrived.py +++ b/openpilot/selfdrive/selfdrived/selfdrived.py @@ -338,7 +338,7 @@ class SelfdriveD(CruiseHelper): device_motion = Pose.from_device_motion(self.sm['deviceMotion']) self.calibrated_pose = self.pose_calibrator.build_calibrated_pose(device_motion) - if self.calibrated_pose is not None: + if self.calibrated_pose is not None and not self.CP.notCar: excessive_actuation = self.excessive_actuation_check.update(self.sm, CS, self.calibrated_pose) if not self.excessive_actuation and excessive_actuation is not None: set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text=str(excessive_actuation)) @@ -398,6 +398,9 @@ class SelfdriveD(CruiseHelper): # All events here should at least have NO_ENTRY and SOFT_DISABLE. num_events = len(self.events) + if self.big_model_active and big_failed: + self.events.add(EventName.bigModelFailed) + not_running = {p.name for p in self.sm['managerState'].processes if not p.running and p.shouldBeRunning} if self.sm.recv_frame['managerState'] and len(not_running): if not_running != self.not_running_prev: diff --git a/openpilot/selfdrive/test/process_replay/test_processes.py b/openpilot/selfdrive/test/process_replay/test_processes.py index 1627ab065..5b72f7b71 100755 --- a/openpilot/selfdrive/test/process_replay/test_processes.py +++ b/openpilot/selfdrive/test/process_replay/test_processes.py @@ -7,7 +7,7 @@ import traceback from collections import defaultdict from tqdm import tqdm from typing import Any -from opendbc.car.car_helpers import interface_names +from opendbc.car.car_helpers import interface_names, interfaces from openpilot.common.git import get_commit from openpilot.tools.lib.openpilotci import get_url from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_diff @@ -64,7 +64,8 @@ segments = [ ] # dashcamOnly makes don't need to be tested until a full port is done -excluded_interfaces = ["mock", "body", "psa"] +excluded_interfaces = {brand for brand, platforms in interface_names.items() + if all(interfaces[platform].get_non_essential_params(platform).dashcamOnly for platform in platforms)} | {"body"} BASE_URL = "https://raw.githubusercontent.com/sunnypilot/ci-artifacts/refs/heads/process-replay/" REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit") diff --git a/openpilot/selfdrive/test/test_onroad.py b/openpilot/selfdrive/test/test_onroad.py index 6768dc1d9..f524046ab 100755 --- a/openpilot/selfdrive/test/test_onroad.py +++ b/openpilot/selfdrive/test/test_onroad.py @@ -67,7 +67,7 @@ PROCS = { "openpilot.selfdrive.pandad.pandad": 0, "openpilot.system.loggerd.uploader": 15.0, "openpilot.system.loggerd.deleter": 1.0, - "./pandad": 19.0, + "./pandad": 40.0, "openpilot.system.qcomgpsd.qcomgpsd": 1.0, "openpilot.common.hardware.comma.modem": 10.0, } @@ -107,6 +107,10 @@ def cputime_total(ct): class TestOnroad(OpenpilotTestCase): COMMA_HARDWARE_TEST = True + def setUp(self): + # Hardware setup is handled once for the full onroad test in setup_class. + unittest.TestCase.setUp(self) + @classmethod def setup_class(cls): if "DEBUG" in os.environ: @@ -332,26 +336,34 @@ class TestOnroad(OpenpilotTestCase): assert np.all(eof_sof_diff < 50*1e6) first_fid = {min(self.ts[c]['frameId']) for c in cams} - assert len(first_fid) == 1, "Cameras don't start on same frame ID" - if cam.endswith('CameraState'): + if cams[0].endswith('CameraState'): # camerad guarantees that all cams start on frame ID 0 # (note loggerd also needs to start up fast enough to catch it) - assert next(iter(first_fid)) < 100, "Cameras start on frame ID too high" + assert min(first_fid) < 100, "Cameras start on frame ID too high" + else: + # encoderd synchronizes all camera encoders to the same starting frame + assert len(first_fid) == 1, "Camera encoders don't start on same frame ID" # we don't do a full segment rotation, so these might not match exactly last_fid = {max(self.ts[c]['frameId']) for c in cams} assert max(last_fid) - min(last_fid) < 10 - start, end = min(first_fid), min(last_fid) - for i in range(end-start): + timestamps = { + cam: dict(zip(self.ts[cam]['frameId'], self.ts[cam]['timestampSof'], strict=True)) + for cam in cams + } + common_frame_ids = set.intersection(*(set(ts) for ts in timestamps.values())) + assert common_frame_ids, "Cameras have no overlapping frame IDs" + + for frame_id in sorted(common_frame_ids): # road and wide cameras (first two) should be synced within 2ms - ts = {c: round(self.ts[c]['timestampSof'][i]/1e6, 1) for c in cams[:2]} - diff = (max(ts.values()) - min(ts.values())) - assert diff < 2, f"Cameras not synced properly: frame_id={start+i}, {diff=:.1f}ms, {ts=}" + ts = {cam: timestamps[cam][frame_id] / 1e6 for cam in cams[:2]} + diff = max(ts.values()) - min(ts.values()) + assert diff < 2, f"Cameras not synced properly: {frame_id=}, {diff=:.1f}ms, {ts=}" # cabin camera should be staggered ~25ms from road camera - offset_ms = abs(self.ts[cams[2]]['timestampSof'][i] - self.ts[cams[0]]['timestampSof'][i]) / 1e6 - assert 20 < offset_ms < 30, f"cabin camera stagger out of range at frame {start+i}: {offset_ms:.1f}ms" + offset_ms = abs(timestamps[cams[2]][frame_id] - timestamps[cams[0]][frame_id]) / 1e6 + assert 20 < offset_ms < 30, f"cabin camera stagger out of range at frame {frame_id}: {offset_ms:.1f}ms" def test_camera_encoder_matches(self, subtests): # sanity check that the frame metadata is consistent with the encoded frames diff --git a/openpilot/selfdrive/ui/mici/layouts/settings/software.py b/openpilot/selfdrive/ui/mici/layouts/settings/software.py index d71982ca2..35fc94142 100644 --- a/openpilot/selfdrive/ui/mici/layouts/settings/software.py +++ b/openpilot/selfdrive/ui/mici/layouts/settings/software.py @@ -47,7 +47,7 @@ class SoftwareInfoLayoutMici(Widget): self._branch_label = UnifiedLabel("branch", 48, max_width=max_width, font_weight=FontWeight.DISPLAY, wrap_text=False) self._branch_text_label = UnifiedLabel("", 32, max_width=max_width, text_color=subheader_color, - font_weight=FontWeight.ROMAN, wrap_text=False) + font_weight=FontWeight.ROMAN, wrap_text=False, scroll=True) def _update_state(self): desc = _split_description(ui_state.params.get("UpdaterCurrentDescription") or "") diff --git a/openpilot/selfdrive/ui/mici/onroad/alert_renderer.py b/openpilot/selfdrive/ui/mici/onroad/alert_renderer.py index 6d4f39220..c08778eaf 100644 --- a/openpilot/selfdrive/ui/mici/onroad/alert_renderer.py +++ b/openpilot/selfdrive/ui/mici/onroad/alert_renderer.py @@ -132,7 +132,7 @@ class AlertRenderer(Widget, SpeedLimitAlertRenderer): # 1. Never received selfdriveState since going onroad waiting_for_startup = recv_frame < ui_state.started_frame - if waiting_for_startup and time_since_onroad > 5: + if waiting_for_startup and time_since_onroad > 10: return ALERT_STARTUP_PENDING # 2. Lost communication with selfdriveState after receiving it diff --git a/openpilot/selfdrive/ui/mici/widgets/button.py b/openpilot/selfdrive/ui/mici/widgets/button.py index 0ecda6a0c..cad40d7d0 100644 --- a/openpilot/selfdrive/ui/mici/widgets/button.py +++ b/openpilot/selfdrive/ui/mici/widgets/button.py @@ -150,8 +150,8 @@ class BigButton(Widget): super().set_touch_valid_callback(lambda: touch_callback() and self._grow_animation_until is None) def _width_hint(self) -> int: - # Single line if scrolling, so hide behind icon if exists - icon_size = self._txt_icon.width if self._txt_icon and self._scroll and self.value else 0 + # A value moves the title to the top, where it shares space with the icon. + icon_size = self._txt_icon.width if self._txt_icon and self.value else 0 return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2 - icon_size) def _get_label_font_size(self): diff --git a/openpilot/selfdrive/ui/onroad/alert_renderer.py b/openpilot/selfdrive/ui/onroad/alert_renderer.py index a3a5424be..6bdedaf01 100644 --- a/openpilot/selfdrive/ui/onroad/alert_renderer.py +++ b/openpilot/selfdrive/ui/onroad/alert_renderer.py @@ -92,7 +92,7 @@ class AlertRenderer(Widget): # 1. Never received selfdriveState since going onroad waiting_for_startup = recv_frame < ui_state.started_frame - if waiting_for_startup and time_since_onroad > 5: + if waiting_for_startup and time_since_onroad > 10: return ALERT_STARTUP_PENDING # 2. Lost communication with selfdriveState after receiving it diff --git a/openpilot/selfdrive/ui/tests/test_soundd.py b/openpilot/selfdrive/ui/tests/test_soundd.py index e2cd90f98..3416a22c9 100644 --- a/openpilot/selfdrive/ui/tests/test_soundd.py +++ b/openpilot/selfdrive/ui/tests/test_soundd.py @@ -20,14 +20,14 @@ class TestSoundd(OpenpilotTestCase): sm.update(100) assert sm.updated['selfdriveState'] - received_at = sm.recv_time['selfdriveState'] - clock = mocker.patch("openpilot.selfdrive.ui.soundd.time.monotonic", return_value=received_at + SELFDRIVE_STATE_TIMEOUT) + sm.recv_time['selfdriveState'] = 0 + clock = mocker.patch("openpilot.selfdrive.ui.soundd.time.monotonic", return_value=SELFDRIVE_STATE_TIMEOUT) assert not check_selfdrive_timeout_alert(sm) - clock.return_value = received_at + SELFDRIVE_STATE_TIMEOUT + 0.1 + clock.return_value = SELFDRIVE_STATE_TIMEOUT + 0.1 assert check_selfdrive_timeout_alert(sm) - clock.return_value = received_at + SELFDRIVE_STATE_TIMEOUT + 10 + clock.return_value = SELFDRIVE_STATE_TIMEOUT + 10 assert not check_selfdrive_timeout_alert(sm) def test_check_selfdrive_timeout_alert_mads_lateral_only(self): diff --git a/openpilot/system/hardware/chestnut/flash.py b/openpilot/system/hardware/chestnut/flash.py index 32aa5a443..f0d828031 100755 --- a/openpilot/system/hardware/chestnut/flash.py +++ b/openpilot/system/hardware/chestnut/flash.py @@ -33,6 +33,7 @@ USBDEVFS_SETCONFIGURATION = 0x80045505 USBDEVFS_CLAIMINTERFACE = 0x8004550F USBDEVFS_RESET = 0x5514 USBDEVFS_CLEAR_HALT = 0x80045515 +MAX_REGISTER_READ_SIZE = 255 _deadline = float("inf") @@ -146,6 +147,7 @@ def claim_interface(path, setup=False): class Flash: def __init__(self): self.fd = -1 + self.max_register_read_size = MAX_REGISTER_READ_SIZE def close(self): if self.fd >= 0: @@ -160,6 +162,9 @@ class Flash: if in_rom_bootloader(vid_pid, product): raise RomFallback("chestnut fell back to the ROM bootloader") if path is not None: + speed = int(open(path + "/speed").read()) + # USB2 firmware truncates larger reads to one full packet without a terminating ZLP. + self.max_register_read_size = 64 if speed < 5000 else MAX_REGISTER_READ_SIZE self.fd = claim_interface(path) return time.sleep(0.1) @@ -231,8 +236,8 @@ class Flash: while len(out) < length: n = min(4096, length - len(out)) self.transaction(0x03, addr + len(out), max(4096, n)) - for off in range(0, n, 255): - out += self.reg_read(0x7000 + off, min(255, n - off)) + for off in range(0, n, self.max_register_read_size): + out += self.reg_read(0x7000 + off, min(self.max_register_read_size, n - off)) return bytes(out) def erase_sector(self, addr): diff --git a/openpilot/system/hardware/hardwared.py b/openpilot/system/hardware/hardwared.py index b47c31af7..2b1ce8ae4 100755 --- a/openpilot/system/hardware/hardwared.py +++ b/openpilot/system/hardware/hardwared.py @@ -238,6 +238,8 @@ def hardware_thread(end_event, hw_queue) -> None: fan_controller = FanController(int(1./DT_HW)) chestnut = Chestnut() + big_model_available = os.path.isfile(os.path.join(BASEDIR, "openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx")) or \ + os.path.isfile(os.path.join(BASEDIR, "openpilot/selfdrive/modeld/models/big_driving_tinygrad.pkl.chunkmanifest")) while not end_event.is_set(): sm.update(PANDA_STATES_TIMEOUT) @@ -299,6 +301,7 @@ def hardware_thread(end_event, hw_queue) -> None: set_usb_state(msg.deviceState, last_hw_state.usb_state) chestnut.update(started_ts is None, last_hw_state.usb_state) + set_offroad_alert_if_changed("Offroad_ChestnutBranch", msg.deviceState.chestnutPresent and not big_model_available) # this subset is only used for offroad temp_sources = [ diff --git a/openpilot/system/loggerd/logger.cc b/openpilot/system/loggerd/logger.cc index e8aeb96d0..f8f94c4b7 100644 --- a/openpilot/system/loggerd/logger.cc +++ b/openpilot/system/loggerd/logger.cc @@ -14,7 +14,7 @@ #include "sunnypilot/common/version.h" // ***** log metadata ***** -kj::Array logger_build_init_data() { +kj::Array logger_build_init_data(bool route_log) { uint64_t wall_time = nanos_since_epoch(); MessageBuilder msg; @@ -72,7 +72,7 @@ kj::Array logger_build_init_data() { "df -h", // usage for all filesystems }; - auto hw_logs = Hardware::get_init_logs(); + auto hw_logs = Hardware::get_init_logs(route_log); auto commands = init.initCommands().initEntries(log_commands.size() + hw_logs.size()); for (int i = 0; i < log_commands.size(); i++) { @@ -166,7 +166,7 @@ static void log_sentinel(LoggerState *log, SentinelType type, int exit_signal = LoggerState::LoggerState(const std::string &log_root) { route_name = logger_get_identifier("RouteCount"); route_path = log_root + "/" + route_name; - init_data = logger_build_init_data(); + init_data = logger_build_init_data(true); } LoggerState::~LoggerState() { diff --git a/openpilot/system/loggerd/logger.h b/openpilot/system/loggerd/logger.h index 419becfe5..17c29d1a0 100644 --- a/openpilot/system/loggerd/logger.h +++ b/openpilot/system/loggerd/logger.h @@ -32,6 +32,6 @@ protected: std::unique_ptr rlog, qlog; }; -kj::Array logger_build_init_data(); +kj::Array logger_build_init_data(bool route_log = false); std::string logger_get_identifier(std::string key); std::string zstd_decompress(const std::string &in); diff --git a/openpilot/system/ubloxd/pigeond.py b/openpilot/system/ubloxd/pigeond.py index 4ab198b53..aa171a9a2 100755 --- a/openpilot/system/ubloxd/pigeond.py +++ b/openpilot/system/ubloxd/pigeond.py @@ -3,11 +3,12 @@ import sys import time import signal import struct +import threading import requests import urllib.parse from datetime import datetime, UTC -from openpilot.cereal import messaging +from openpilot.cereal import log, messaging from openpilot.common.api import Api from openpilot.common.time_helpers import system_time_valid from openpilot.common.params import Params @@ -46,7 +47,6 @@ def get_assistnow_messages() -> list[bytes]: params = Params() if token := params.get('AssistNowToken'): cloudlog.warning("Downloading AssistNow data directly from u-blox") - # TODO: implement adding the last known location r = requests.get("https://online-live2.services.u-blox.com/GetOnlineData.ashx", params=urllib.parse.urlencode({ 'token': token, 'gnss': 'gps,glo', @@ -240,14 +240,6 @@ def init_pigeon(pigeon: TTYPigeon) -> bool: )) pigeon.send_with_ack(msg, ack=UBLOX_ASSIST_ACK) - # A configured u-blox token takes precedence over comma's AGPS proxy. - try: - for msg in get_assistnow_messages(): - pigeon.send_with_ack(msg, ack=UBLOX_ASSIST_ACK) - cloudlog.warning("AssistNow messages sent") - except Exception: - cloudlog.warning("failed to get AssistNow messages") - cloudlog.warning("Pigeon GPS on!") break except TimeoutError: @@ -287,12 +279,38 @@ def run_receiving(duration: int = 0): start_time = time.monotonic() last_almanac_save = time.monotonic() + assist_attempted = False + assist_messages = None + + def download_assistnow() -> None: + nonlocal assist_messages + sm = messaging.SubMaster(['deviceState']) + while assist_messages is None: + sm.update(1000) + if system_time_valid() and sm['deviceState'].networkType != log.DeviceState.NetworkType.none: + try: + assist_messages = get_assistnow_messages() + except Exception: + cloudlog.warning("failed to get AssistNow messages") + time.sleep(10.) + threading.Thread(target=download_assistnow, daemon=True).start() + while (duration == 0) or (time.monotonic() - start_time < duration): + if assist_messages is not None and not assist_attempted: + assist_attempted = True + try: + for msg in assist_messages: + pigeon.send_with_ack(msg, ack=UBLOX_ASSIST_ACK) + cloudlog.warning("AssistNow messages sent") + except Exception: + cloudlog.warning("failed to send AssistNow messages") + dat = pigeon.receive() if len(dat) > 0: if dat[0] == 0x00: cloudlog.warning("received invalid data from ublox, re-initing!") init(pigeon) + assist_attempted = False continue # send out to socket diff --git a/openpilot/system/updated/updated.py b/openpilot/system/updated/updated.py index dd2f71f30..62fa10ec9 100755 --- a/openpilot/system/updated/updated.py +++ b/openpilot/system/updated/updated.py @@ -207,13 +207,10 @@ def handle_agnos_update() -> None: set_consistent_flag(False) cloudlog.info(f"Beginning background installation for AGNOS {updated_version}") - set_offroad_alert("Offroad_NeosUpdate", True) manifest_path = os.path.join(OVERLAY_MERGED, "openpilot/system/hardware/comma/agnos.json") target_slot_number = get_target_slot_number() flash_agnos_update(manifest_path, target_slot_number, cloudlog) - set_offroad_alert("Offroad_NeosUpdate", False) - class Updater: diff --git a/tools/release/build_release.sh b/tools/release/build_release.sh index 3711cd582..21d0bba44 100755 --- a/tools/release/build_release.sh +++ b/tools/release/build_release.sh @@ -2,15 +2,14 @@ set -e set -x -# git diff --name-status origin/release3-staging | grep "^A" | less - DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" - cd $DIR BUILD_DIR=/data/openpilot SOURCE_DIR="$(git rev-parse --show-toplevel)" +export PYTHONPATH="$BUILD_DIR:$BUILD_DIR/msgq_repo:$BUILD_DIR/opendbc_repo:$BUILD_DIR/rednose_repo:$BUILD_DIR/teleoprtc_repo:$BUILD_DIR/tinygrad_repo" + if [ -z "$RELEASE_BRANCH" ]; then echo "RELEASE_BRANCH is not set" exit 1 @@ -23,33 +22,36 @@ BUILD_BRANCH=release-mici-staging source $DIR/identity.sh echo "[-] Setting up repo T=$SECONDS" -rm -rf $BUILD_DIR -mkdir -p $BUILD_DIR +if ! git -C "$SOURCE_DIR" worktree remove --force "$BUILD_DIR" 2>/dev/null; then + rm -rf $BUILD_DIR +fi +git -C "$SOURCE_DIR" worktree prune +git -C "$SOURCE_DIR" worktree add --detach --no-checkout "$BUILD_DIR" cd $BUILD_DIR -git init -git remote add origin git@github.com:commaai/openpilot.git -git checkout --orphan $BUILD_BRANCH +git update-ref -d "refs/heads/$BUILD_BRANCH" +git symbolic-ref HEAD "refs/heads/$BUILD_BRANCH" +git read-tree --empty # do the files copy echo "[-] copying files T=$SECONDS" cd $SOURCE_DIR -cp -pR --parents $(./tools/release/release_files.py) $BUILD_DIR/ +./tools/release/release_files.py | xargs -0 cp -pR --parents -t "$BUILD_DIR" -- # in the directory cd $BUILD_DIR -rm -f panda/board/obj/panda.bin.signed -rm -f panda/board/obj/panda_h7.bin.signed +# use the full CPU available for speeding up the build. +# openpilot resets the CPU frequencies when test_onroad.py runs below. +for policy in /sys/devices/system/cpu/cpufreq/policy*; do + [ -d "$policy" ] || continue + hardware_max="$(cat "$policy/cpuinfo_max_freq")" + echo "$hardware_max" | sudo tee "$policy/scaling_max_freq" >/dev/null +done -VERSION=$(cat openpilot/sunnypilot/common/version.h | awk -F[\"-] '{print $2}') -echo "[-] committing version $VERSION T=$SECONDS" -git add -f . -git commit -a -m "openpilot v$VERSION release" - -# Build and test before launch_chffrplus.sh creates the on-device package -# symlinks. SConstruct uses the same package roots for build subprocesses. -export PYTHONPATH="$BUILD_DIR:$BUILD_DIR/msgq_repo:$BUILD_DIR/opendbc_repo:$BUILD_DIR/rednose_repo:$BUILD_DIR/teleoprtc_repo:$BUILD_DIR/tinygrad_repo" scons +if [ -n "$INCLUDE_BIG_MODEL" ]; then + test -f openpilot/selfdrive/modeld/models/big_driving_tinygrad.pkl.chunkmanifest +fi if [ -z "$PANDA_DEBUG_BUILD" ]; then # release panda fw @@ -72,7 +74,6 @@ find . -name '*.a' -delete find . -name '*.o' -delete find . -name '*.os' -delete find . -name '*.pyc' -delete -find . -name 'moc_*' -delete find . -name '__pycache__' -delete rm -rf .sconsign.dblite Jenkinsfile tools/release/ rm -f openpilot/selfdrive/modeld/models/*.onnx* @@ -88,9 +89,11 @@ git checkout openpilot/third_party/ # Mark as prebuilt release touch prebuilt +VERSION=$(cat openpilot/sunnypilot/common/version.h | awk -F[\"-] '{print $2}') # Add built files to git -git add -f . -git commit --amend -m "openpilot v$VERSION" +# writing larger objects is faster than compressing them on-device +git -c core.compression=0 add -f . +git -c core.compression=0 -c gc.auto=0 commit -m "openpilot v$VERSION" # Run tests cd $BUILD_DIR @@ -102,6 +105,7 @@ REFS=() for branch in ${RELEASE_BRANCH//,/ }; do REFS+=("$BUILD_BRANCH:$branch") done -git push -f origin "${REFS[@]}" +# uploading the larger pack is faster than spending CPU to optimize it +git -c pack.window=0 -c pack.depth=0 -c pack.compression=0 push -f origin "${REFS[@]}" echo "[-] done T=$SECONDS" diff --git a/tools/release/build_stripped.sh b/tools/release/build_stripped.sh index b5092b15a..6015fdc5d 100755 --- a/tools/release/build_stripped.sh +++ b/tools/release/build_stripped.sh @@ -30,20 +30,14 @@ git submodule deinit -f --all git rm -rf --cached . find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -rf '{}' \; -# cleanup before the copy -cd $SOURCE_DIR -git clean -xdff -git submodule foreach --recursive git clean -xdff - # do the files copy echo "[-] copying files T=$SECONDS" cd $SOURCE_DIR -./tools/release/release_files.py | xargs -d '\n' cp -pR --parents -t "$TARGET_DIR" +./tools/release/release_files.py | xargs -0 cp -pR --parents -t "$TARGET_DIR" -- # in the directory cd $TARGET_DIR rm -rf .git/modules/ -rm -f panda/board/obj/panda.bin.signed find openpilot/selfdrive/modeld/models -name '*.onnx' -size +95M -exec ./openpilot/common/file_chunker.py {} \; @@ -57,9 +51,10 @@ echo -n "$GIT_HASH" > git_src_commit echo -n "$GIT_COMMIT_DATE" > git_src_commit_date echo "[-] committing version $VERSION T=$SECONDS" -git add -f . +# writing larger objects is faster than compressing them on-device +git -c core.compression=0 add -f . git status -git commit -a -m "sunnypilot v$VERSION release +git -c core.compression=0 commit -a -m "sunnypilot v$VERSION release date: $DATETIME master commit: $GIT_HASH @@ -83,7 +78,8 @@ fi if [ ! -z "$BRANCH" ]; then echo "[-] Pushing to $BRANCH T=$SECONDS" - git push -f origin tmp:$BRANCH + # uploading the larger pack is faster than spending CPU to optimize it + git -c pack.window=0 -c pack.depth=0 -c pack.compression=0 push -f origin tmp:$BRANCH fi echo "[-] done T=$SECONDS, ready at $TARGET_DIR" diff --git a/tools/release/release_files.py b/tools/release/release_files.py index 54e92f5ff..641dd7ff8 100755 --- a/tools/release/release_files.py +++ b/tools/release/release_files.py @@ -1,20 +1,19 @@ #!/usr/bin/env python3 import os import re -from pathlib import Path +import subprocess +import sys HERE = os.path.abspath(os.path.dirname(__file__)) ROOT = os.path.abspath(os.path.join(HERE, "../..")) blacklist = [ ".git/", + ".venv/", ".github/workflows/", "matlab.*.md", - # skip big model for now - "openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx", - # no LFS or submodules in release ".lfsconfig", ".gitattributes", @@ -30,14 +29,17 @@ whitelist: list[str] = [ if __name__ == "__main__": - for f in Path(ROOT).rglob("**/*"): - if not (f.is_file() or f.is_symlink()): + tracked_files = subprocess.check_output(["git", "ls-files", "-z", "--recurse-submodules"], cwd=ROOT).split(b"\0") + for tracked_file in tracked_files: + if not tracked_file: continue - rf = str(f.relative_to(ROOT)) + rf = os.fsdecode(tracked_file) + if not os.getenv("INCLUDE_BIG_MODEL") and rf.startswith("openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx"): + continue blacklisted = any(re.search(p, rf) for p in blacklist) whitelisted = any(re.search(p, rf) for p in whitelist) if blacklisted and not whitelisted: continue - print(rf) + sys.stdout.buffer.write(tracked_file + b"\0") diff --git a/uv.lock b/uv.lock index a86cd55d3..8f3d4ca96 100644 --- a/uv.lock +++ b/uv.lock @@ -52,24 +52,43 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.9" +version = "3.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, - { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, - { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, - { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, - { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, - { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, - { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, - { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, - { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, - { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, - { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, - { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, - { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, - { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, + { url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456, upload-time = "2026-08-15T08:17:10.072Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530, upload-time = "2026-08-15T08:17:11.563Z" }, + { url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200, upload-time = "2026-08-15T08:17:12.919Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a0/47b18adeed31c8f16ba9700f32c1b18594cfa09f47eb672a488c273c22bf/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893", size = 262222, upload-time = "2026-08-15T08:17:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/38/fe/341861ac118dae06f3ec0eb487488af52128f2ef2faf0b11003944d22259/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0", size = 258951, upload-time = "2026-08-15T08:17:16.158Z" }, + { url = "https://files.pythonhosted.org/packages/6f/89/bb5108dc6c3651dca963f2b0a3ba19bbcb370c94e1b6d3e0e844a58e6dca/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08", size = 248801, upload-time = "2026-08-15T08:17:17.683Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ba/ef83ae3aca816393decfa3530976f38a79812d707b80b580ac33b83f9877/charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada", size = 244070, upload-time = "2026-08-15T08:17:19.191Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0b/c5292a2462d69b7378ea89793bbb5b2b6fcf6f7dd6d1667f9619094ad553/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9", size = 240110, upload-time = "2026-08-15T08:17:20.547Z" }, + { url = "https://files.pythonhosted.org/packages/46/22/111e5be3b740d5c2a5bfcedb3d237b6591e5c2e82ae9d6ffcb121fe0909c/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e", size = 232836, upload-time = "2026-08-15T08:17:21.895Z" }, + { url = "https://files.pythonhosted.org/packages/f9/d2/d2aad6fe0dbb44b194bf3becb60f5a0ac48446ade999a47fe7bb41eb09a7/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6", size = 262712, upload-time = "2026-08-15T08:17:23.727Z" }, + { url = "https://files.pythonhosted.org/packages/35/5a/337e4663a5eae6de99db940ee8066d4145caafb61327db62deda15313cce/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf", size = 242977, upload-time = "2026-08-15T08:17:25.157Z" }, + { url = "https://files.pythonhosted.org/packages/ca/85/f82f8a92e31c7519410e2e1afdc630f28ec47490ce2c09a11c1a43cbb459/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71", size = 260207, upload-time = "2026-08-15T08:17:26.602Z" }, + { url = "https://files.pythonhosted.org/packages/b7/52/643d11ffd60e9ac2fd1fb87e167a19285b9eefeff4a40e63c87cbfbeab36/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573", size = 250562, upload-time = "2026-08-15T08:17:27.971Z" }, + { url = "https://files.pythonhosted.org/packages/62/16/46556278c2168d12df9da7fede5dc6fc70e60301b26a82bbeec238c9cfe3/charset_normalizer-3.5.1-cp312-cp312-win32.whl", hash = "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2", size = 178507, upload-time = "2026-08-15T08:17:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/4c6c298171e6b3e745633180ff59350fc0ca0db1ffd28df1e369e0579f71/charset_normalizer-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2", size = 200551, upload-time = "2026-08-15T08:17:30.668Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d7/eb95a042f0dd22e304b0b6472b154f3546a1a039a9ee89ccb2a7f61591fc/charset_normalizer-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a", size = 180700, upload-time = "2026-08-15T08:17:32.028Z" }, + { url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467, upload-time = "2026-08-15T08:19:50.959Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057, upload-time = "2026-08-15T08:19:52.654Z" }, + { url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930, upload-time = "2026-08-15T08:19:54.163Z" }, + { url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822, upload-time = "2026-08-15T08:19:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037, upload-time = "2026-08-15T08:19:57.312Z" }, + { url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097, upload-time = "2026-08-15T08:19:59.056Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166, upload-time = "2026-08-15T08:20:00.612Z" }, + { url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821, upload-time = "2026-08-15T08:20:02.321Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529, upload-time = "2026-08-15T08:20:04.008Z" }, + { url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348, upload-time = "2026-08-15T08:20:05.877Z" }, + { url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234, upload-time = "2026-08-15T08:20:07.487Z" }, + { url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917, upload-time = "2026-08-15T08:20:09.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846, upload-time = "2026-08-15T08:20:10.727Z" }, + { url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216, upload-time = "2026-08-15T08:20:12.334Z" }, + { url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764, upload-time = "2026-08-15T08:20:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318, upload-time = "2026-08-15T08:20:15.551Z" }, + { url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" }, ] [[package]] @@ -1092,49 +1111,49 @@ wheels = [ [[package]] name = "ruff" -version = "0.16.2" +version = "0.16.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/e1/4508a569211b35599016e84ba65c1a992b7a4004b4b6c4bea02a851cba1b/ruff-0.16.2.tar.gz", hash = "sha256:c3d7828d12e8927a6fc65fe38e2c2541b9e762d360a1786d752cb1b8883b3c9c", size = 4885811, upload-time = "2026-08-07T13:31:01.432Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/b3/3213589383f8f1b3938781bd1278713f6d18621a14992b3e81fefb8a5ef9/ruff-0.16.3.tar.gz", hash = "sha256:e76d33a347661a84b5be6d043d0347fdc745dfdcf825a8f4fed64b5e26eebdf2", size = 4891904, upload-time = "2026-08-13T15:17:13.381Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/57/db19951540f98859c956b50bdb4d31089b4d91e9f15e2968e7d5193806d5/ruff-0.16.2-py3-none-linux_armv6l.whl", hash = "sha256:3c8de4cf2181f01d57946d87d777aa52916976fc09942aed89938fab5e013318", size = 10847925, upload-time = "2026-08-07T13:30:14.468Z" }, - { url = "https://files.pythonhosted.org/packages/13/5a/995fe85a8470d3e391ac0f7fa8054bb454eaf33ee138196d6172ed1079c0/ruff-0.16.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a48cc05c6fbc811ca81b5d7ba95375affea6582d1b8024e455e41afbbf55344", size = 11072662, upload-time = "2026-08-07T13:30:18.143Z" }, - { url = "https://files.pythonhosted.org/packages/32/53/370d767c61c71a971a4ace36703a7ecd8c393956349a7325d7fab2b56827/ruff-0.16.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a2c0d14fcbb26c91f0f867a6dc9bd71bbc30b1b6151829c884f23faeab2e5700", size = 10566771, upload-time = "2026-08-07T13:30:20.899Z" }, - { url = "https://files.pythonhosted.org/packages/85/d6/9d96948caf5a632be62d62202d5ec914d6856f204fd79eb036e5915e79ea/ruff-0.16.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:335c621622c4650330be50842561c6586ac6971bb8ab5407fe34dcc9efb16bbe", size = 10975825, upload-time = "2026-08-07T13:30:23.517Z" }, - { url = "https://files.pythonhosted.org/packages/3b/92/ea87129b3414acb0b5770563779c51804d37ac67675c7ba35447ddb14773/ruff-0.16.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20e66910f2c37cc753f9ef6580c914a621b80c4fa3549d3e3521e29d0f5bfc3f", size = 10649437, upload-time = "2026-08-07T13:30:26.097Z" }, - { url = "https://files.pythonhosted.org/packages/ac/43/f8f291dcd4af5bb7872b74fdfa41a7cd7c856ca1d4069670971cf1b9f5cb/ruff-0.16.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7e36fbfba65510548156902bcf1350a979a958ce0347ce0f90d73894036b39f", size = 11446761, upload-time = "2026-08-07T13:30:28.752Z" }, - { url = "https://files.pythonhosted.org/packages/71/4a/ef991fb2fcf516ab71f0808adcdd8da5e18c8cde447f4ceaf5f47a5132a5/ruff-0.16.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f0eab35f80df8f134aae5d1630e751901321d317cc8e50dc39e36fa3ed34cd12", size = 12336364, upload-time = "2026-08-07T13:30:31.468Z" }, - { url = "https://files.pythonhosted.org/packages/f3/24/f615e74f307e6ca0e56a482872477b856c70d530aa356abfb6dfe5ca8a80/ruff-0.16.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ea8c0594feb894e89c8c61ab9c103d38b0ea72dfde6c594107147ca31b1140", size = 11630720, upload-time = "2026-08-07T13:30:34.426Z" }, - { url = "https://files.pythonhosted.org/packages/c5/d3/8ef50149e8412a77f7ab409efdef0e2b23803707a3863da4fc64cb23d459/ruff-0.16.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab3d62dde0b19facdd632008cc4827fc28ada7736c6bd35ab6f1050f0bfed53f", size = 11466130, upload-time = "2026-08-07T13:30:36.958Z" }, - { url = "https://files.pythonhosted.org/packages/dd/a7/a19334985c4dea8c381981fa252cd854c7ee52dc4b1686dc16f4a911c702/ruff-0.16.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e43e1f5b8388da9eca1b9e88328d47a5cec794633ccf6f7484ac2dd15eee92c0", size = 11523634, upload-time = "2026-08-07T13:30:39.822Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6c/96d192b0e742412ceda08c0a50f9669b253dde9fd6a60ea1a10c9fa79a63/ruff-0.16.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c24788a980581e1d7ea3a0cbe4344c4fbeb0a6a9b1f4713aa46bb104f8294690", size = 10949807, upload-time = "2026-08-07T13:30:42.745Z" }, - { url = "https://files.pythonhosted.org/packages/fa/51/e26599ceca11e79ee255c7df515995561edf87e9ca1893284e44d98f5a86/ruff-0.16.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:81806b08329130005dd4a8a8394a0c9da8c6f4cafb16ba438d2a2ee6a18bedf1", size = 10646891, upload-time = "2026-08-07T13:30:45.522Z" }, - { url = "https://files.pythonhosted.org/packages/68/01/800c4b1f97bc8d7c6029e06b1f20473a3cf1e13c4933d8f3342add83fc55/ruff-0.16.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4ce4e02bad779bef557f541a1b31f20d6abeae1cc05ed1b1ac019d4ffd1044c8", size = 11162063, upload-time = "2026-08-07T13:30:48.131Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d0/1477ea50fc5a0d4b0b71d1d63d50770bdd794d90b43e37a7618e63ec9894/ruff-0.16.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e0422abdf70070255fc4073ce9dfc814cc03db577013761ddd09bc1e4a9a4fbd", size = 11556038, upload-time = "2026-08-07T13:30:50.686Z" }, - { url = "https://files.pythonhosted.org/packages/b8/76/a7776f32048d991e16d4fa8ff91790b877342d3596cc3ed04acdbf1aaedc/ruff-0.16.2-py3-none-win32.whl", hash = "sha256:bf3a63d78fb39f4bf5ac8ae52051c5520505301abe19ba4e204c453b3f09bb0b", size = 10872850, upload-time = "2026-08-07T13:30:53.471Z" }, - { url = "https://files.pythonhosted.org/packages/00/0d/929c800d920e61397d82a01b60bffc68da3052c17d31de59efaad2e4ed75/ruff-0.16.2-py3-none-win_amd64.whl", hash = "sha256:bcabe2f6d0fc7819f1431793005af4e4de7371927d037345bf941252b195b9fa", size = 12023338, upload-time = "2026-08-07T13:30:56.193Z" }, - { url = "https://files.pythonhosted.org/packages/5b/6c/93e26c22c5f78ff87363e07da49c84955affbeb1098bd1936bf3b3f293bf/ruff-0.16.2-py3-none-win_arm64.whl", hash = "sha256:d614e95cedf38a2053fd351c55b103ba30d017d61688fdbfd40ee0412852a99f", size = 11374065, upload-time = "2026-08-07T13:30:58.775Z" }, + { url = "https://files.pythonhosted.org/packages/bf/96/493770daebd68c0a67f1549fdf519f53be51fc435186c0585bcc272fd76c/ruff-0.16.3-py3-none-linux_armv6l.whl", hash = "sha256:0c5710e247a58a4521e66e124ba9a74655b414f61ba3a2e9e3811e11098f48f7", size = 10902799, upload-time = "2026-08-13T15:16:27.382Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e6/2becf3942fddc29a29b8df47691d456fb1085391a694f74d84513251418c/ruff-0.16.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fe155130631a2471fd2e14a7a664a4dfbd7194b8229c3d7b2a40b21178639081", size = 11135539, upload-time = "2026-08-13T15:16:30.87Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1e/4b8b72f0d006dbf19326aa99f9ca0ee2ff374187c4d301cf529a51aa06fe/ruff-0.16.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e2ed719e14aa64d895c2ee922594a90a43c861a93f0575a95ff8c47cdbd13eb9", size = 10475095, upload-time = "2026-08-13T15:16:33.259Z" }, + { url = "https://files.pythonhosted.org/packages/92/32/2201fa49ba1f6c101ee321e83f051ac7a4b8d07b0ef6b4d3f2772b302275/ruff-0.16.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e0b1da805eb043654645d74d5de1e5ce2edc686e40790d2b86f56d71cc06a84", size = 10668771, upload-time = "2026-08-13T15:16:35.65Z" }, + { url = "https://files.pythonhosted.org/packages/c3/66/4afc5c8363bd04d45effce1b7c8713ca037d7a6740b7451a2403a6e3a972/ruff-0.16.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a37bdea0bbe21780f590bf437d6412c8c4e1b6cd010f91a65c2c40c5e5f5f870", size = 10699568, upload-time = "2026-08-13T15:16:38.195Z" }, + { url = "https://files.pythonhosted.org/packages/53/fd/c67d246bf36bf1698551c56de39e95cd07f70e64433e0098e6267d77061b/ruff-0.16.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09571e6d1288ed9be475207a3ac04ada404f1cd898104be0f6ab8d7df438575b", size = 11499365, upload-time = "2026-08-13T15:16:40.623Z" }, + { url = "https://files.pythonhosted.org/packages/67/0b/00ecbceb99a263af7b12f6f05ac3c92bc47b905e91adc3f207a836e3bc01/ruff-0.16.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c18c5a101eb540010638cc1ff3c84944d3adb3df62b8d98ca8f22ba484d3413", size = 12311728, upload-time = "2026-08-13T15:16:43.564Z" }, + { url = "https://files.pythonhosted.org/packages/54/b2/b7b3bb54f4d3f7db504e476ad4ab8de530dceebe2c061384b2757ee419e8/ruff-0.16.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8457c44f15033c85ddbb77b15d451df9e24e4bd03b628396dd3610cedc3b8f82", size = 11699896, upload-time = "2026-08-13T15:16:46.209Z" }, + { url = "https://files.pythonhosted.org/packages/c7/30/4c468429ac195addc5ee1b717b6ab1b66632786737ca3b2ed3443fb0c26a/ruff-0.16.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:294b95c4ae0cda9388525c2047778aa758d6b8d4bb876fd4e9eaa3ebc92343eb", size = 11058736, upload-time = "2026-08-13T15:16:48.823Z" }, + { url = "https://files.pythonhosted.org/packages/43/67/7a113cdaddf24b64d7f75b1242a99d04c82fcef4f6921fdbb832beaffb5f/ruff-0.16.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:3d0c7c40c87c2a820509c31ba007968da6e1306468c067b2d82fbfdbcd0e8474", size = 11586911, upload-time = "2026-08-13T15:16:51.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c1/2e66f24c0f3ead25a5e660111778685e505e5da353c82802bf49f0cbe7b9/ruff-0.16.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9f738c0fdfa8eed0b2ce7fb27ee7258208a92a68d7949e62aa15164bc7b389da", size = 10954265, upload-time = "2026-08-13T15:16:54.763Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ba/4cee23bf52cba9a058d3726de623624daf50ef9638868edd86f4126157f6/ruff-0.16.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fb785f0be25abe69d320415cd4f833b59e17ba7613d9ba6a958023b6bceb0a50", size = 10709886, upload-time = "2026-08-13T15:16:57.339Z" }, + { url = "https://files.pythonhosted.org/packages/82/df/7da7194fa5d9dc0a285f7e6fa5a4722e7c63faac0b45b614ded9314363a1/ruff-0.16.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c5536e3acfbf9563085aa2be7b13c629c3077e902afc5b941ac44024dbb9f506", size = 11210392, upload-time = "2026-08-13T15:17:00.171Z" }, + { url = "https://files.pythonhosted.org/packages/35/85/7795f6e817af050e7517bf3e7aa9b061cce70ef33d280aad902c956c1ecf/ruff-0.16.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a2d85c02f9b8e165d85e6779184d38c4132de12603dab59c51c28e22584f9e4d", size = 11626910, upload-time = "2026-08-13T15:17:03.299Z" }, + { url = "https://files.pythonhosted.org/packages/78/9b/475b927cf27a5cbbda3c7bafb69ed6ff77e1d7923d5d85f17c2749d7ae32/ruff-0.16.3-py3-none-win32.whl", hash = "sha256:388cdf2166642bd9b13d52b5932d3170f34f8abed7e8d9a855f1d84b83645a0a", size = 10931415, upload-time = "2026-08-13T15:17:05.726Z" }, + { url = "https://files.pythonhosted.org/packages/b2/99/e2a2bfc4fbf0a1e8a916bc9ebe6fe6c58cc34c28e0ffc6ce281d572d1c2e/ruff-0.16.3-py3-none-win_amd64.whl", hash = "sha256:e80a7d69ca2a6d1c4d352ec91458cdca6e56c83cdbcabd93e4abe1e53591d948", size = 11445993, upload-time = "2026-08-13T15:17:08.353Z" }, + { url = "https://files.pythonhosted.org/packages/69/3e/4132e539aed78c148854d4997a2685b0ed4dc4e87110b59ce528564e184e/ruff-0.16.3-py3-none-win_arm64.whl", hash = "sha256:b8ca152da82c1acc1fa8d5874b15951935f0eef46f10e6954c83859011b6178a", size = 11399302, upload-time = "2026-08-13T15:17:10.908Z" }, ] [[package]] name = "scons" -version = "4.10.1" +version = "4.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/c9/2f430bb39e4eccba32ce8008df4a3206df651276422204e177a09e12b30b/scons-4.10.1.tar.gz", hash = "sha256:99c0e94a42a2c1182fa6859b0be697953db07ba936ecc9817ae0d218ced20b15", size = 3258403, upload-time = "2025-11-16T22:43:39.258Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/82/3c4e089ac8df2eaee8a7f14e489b2a76f94f4c1d8defa4e46c8ad15cae86/scons-4.11.0.tar.gz", hash = "sha256:5ba48f9e2eb6b9178cabdc9893792418e6970c84f43f4b027e4468e20616a89c", size = 3269126, upload-time = "2026-08-11T04:29:45.62Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/bf/931fb9fbb87234c32b8b1b1c15fba23472a10777c12043336675633809a7/scons-4.10.1-py3-none-any.whl", hash = "sha256:bd9d1c52f908d874eba92a8c0c0a8dcf2ed9f3b88ab956d0fce1da479c4e7126", size = 4136069, upload-time = "2025-11-16T22:43:35.933Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ac/a4445bbbd58a5fa6a5c8b3b0458ffbee04e4acaff87677058eab9c6af682/scons-4.11.0-py3-none-any.whl", hash = "sha256:2edc077aaeafc43377ba46ce1fa3e7b40edea59c62db9ef7e39e07dc88b754fa", size = 4123742, upload-time = "2026-08-11T04:29:42.881Z" }, ] [[package]] name = "sentry-sdk" -version = "2.67.1" +version = "2.68.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ad/8a/b2eec40df8a67bf073e244d29001d04ee365d163bc4f15efdfce35f53090/sentry_sdk-2.67.1.tar.gz", hash = "sha256:f263d8c9aa4137750640de8fb0ed5404df6bb564e20e4b59cb16a6eeba18d4ed", size = 990599, upload-time = "2026-08-10T13:05:55.892Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/94/23b7dd072acb9628907bd3f4fbf61794a7b12a9db8f33c1276f70ae5ac92/sentry_sdk-2.68.0.tar.gz", hash = "sha256:648c58e9887311a03470a41539e24bdbbf64a30ca4f5336f7e3dcc87276400b3", size = 1008854, upload-time = "2026-08-13T09:06:21.268Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/e2/70692eba662037cddf93391cbbf98297159f3038612e9b9a8129e16feb7a/sentry_sdk-2.67.1-py3-none-any.whl", hash = "sha256:a66bfbce1cd8a93c51c369d642ad85b46253ea7a6f7938141315b83e2823cda5", size = 515591, upload-time = "2026-08-10T13:05:54.213Z" }, + { url = "https://files.pythonhosted.org/packages/7d/9b/e2421d08956d0bc4691d995393d835e563886bff499d8fb10fdefae85a8d/sentry_sdk-2.68.0-py3-none-any.whl", hash = "sha256:538e56c2d03679d42f7c0cb5f1af73a7a510b00abc7e296c13ac49b107b713a4", size = 518670, upload-time = "2026-08-13T09:06:19.735Z" }, ] [[package]] @@ -1316,27 +1335,27 @@ wheels = [ [[package]] name = "ty" -version = "0.0.69" +version = "0.0.72" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/5b/7a618632dfe9373b7df572ecd7a08c8f799d772fbc317da82dd3aa363207/ty-0.0.69.tar.gz", hash = "sha256:b65106e9ff24fa76e25e1142fb09c85244e815c40450e3021d2bf652c231bb43", size = 6565094, upload-time = "2026-08-06T10:04:25.667Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/df/656e684bafb13c1d146e7d5b5f3e7978ca177232acc84998ff36427e9462/ty-0.0.72.tar.gz", hash = "sha256:ec2b8066b618df18cab4cb8e992f8da45d360332acb23fa34df7fa29cd1b9d3a", size = 6654939, upload-time = "2026-08-14T21:35:42.612Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/60/6534092f4d2c15e2491807edd609c2e50d527c1fed957acf40b9f110b64a/ty-0.0.69-py3-none-linux_armv6l.whl", hash = "sha256:98bfd383b273540829af673e7f98b9c1c4bcc8547d12a1a3806cd0bec7f0e087", size = 12364185, upload-time = "2026-08-06T10:03:47.137Z" }, - { url = "https://files.pythonhosted.org/packages/34/2b/5c29689bd4f74c2e3394d983d85e4011b629f2ce3730c9442553b8554bf8/ty-0.0.69-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:964621ddd05771660017c51b4e74078d861d9fc863c21ef2a500db1ab62c9ccf", size = 12042510, upload-time = "2026-08-06T10:03:49.481Z" }, - { url = "https://files.pythonhosted.org/packages/09/46/fa085bde4d23516d7ef14b24736fc5dd7dc498f60f52b3d077e59ffdea20/ty-0.0.69-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3ffea4048dd0da4c9c97393b4be0901098a9065b06fa81be2477cbde65d8a151", size = 11549397, upload-time = "2026-08-06T10:03:51.747Z" }, - { url = "https://files.pythonhosted.org/packages/25/cc/97b9efb2061dcab6fef1e94a4ad99df0bb45bd2cc15d4f5794c787ee0552/ty-0.0.69-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8684d4a70aadd1eab0f41bdba835e3288ef49db8402a8e6ca81bab52ed5d610", size = 12115567, upload-time = "2026-08-06T10:03:53.79Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c1/a5e0404965093835f3e62544e661784ec0aa8ef0b006ed50af50b19c107e/ty-0.0.69-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:afaaba240ab4122e2069a796836d10be81b4ddb053ae268b3dff962a0b4ca5c7", size = 12149770, upload-time = "2026-08-06T10:03:55.993Z" }, - { url = "https://files.pythonhosted.org/packages/e2/39/8cad6b205a4abe8a044ca0c84aea71e8ccda29b07a75a5f090e310605580/ty-0.0.69-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11ea63ef07d4e33aeb1a775cf5f2c736b3ed22fa6f8b1b608591612c36795044", size = 12941278, upload-time = "2026-08-06T10:03:58.324Z" }, - { url = "https://files.pythonhosted.org/packages/d6/8b/8766d96b732c2a060d70dc8ccafcc4d6a54109a2a95f1deb0705de88892b/ty-0.0.69-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb3730b1268e92a2907d7aea3afe8dd1b360ae65862f0557080cf479d481b424", size = 13426509, upload-time = "2026-08-06T10:04:00.621Z" }, - { url = "https://files.pythonhosted.org/packages/02/1f/e991b2cde953ea5b94d6a9a4c45c87937bd916bc09235f764407bf471c0a/ty-0.0.69-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a544ff57a752ef186ed40b5a2f44c17402af4cdefeb74a311ca02ebd57c4fca0", size = 13106582, upload-time = "2026-08-06T10:04:02.818Z" }, - { url = "https://files.pythonhosted.org/packages/ea/bb/73538f1b99e3558fd9db87b98698426f0f60fc8666da0b1efd0e70e275eb/ty-0.0.69-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87ed2cbca20caddfdf8e3e14d213ce91b67e75feed78900f4aaf3ef884954028", size = 12708931, upload-time = "2026-08-06T10:04:05.233Z" }, - { url = "https://files.pythonhosted.org/packages/87/cd/484a5208d74c4ad1155933906295ccdce9aa81a257d8df2ab9e41bd60133/ty-0.0.69-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2684efcbce5b6fe45045faf610b377b50781b6d2aa7e61ea23ecf5b3d2bce421", size = 12985322, upload-time = "2026-08-06T10:04:07.587Z" }, - { url = "https://files.pythonhosted.org/packages/6e/81/b75003f0d4da9ab3bc8fd4f4802f836cb9921ff7e70f460604f7b769a0b5/ty-0.0.69-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:da9aeb26fdac1d2214937542b59e0d4d1ba94ec7a3f45444f33c846de1eb1d63", size = 12063910, upload-time = "2026-08-06T10:04:09.835Z" }, - { url = "https://files.pythonhosted.org/packages/8a/76/088469f547ef63dceefc4a75826aedee5014f9371dc5171cde931896a82c/ty-0.0.69-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:00e7677cd14ede381f705f71104ea7b8ea0ce217a8634e19a89781953de0e9ad", size = 12166823, upload-time = "2026-08-06T10:04:12.114Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c9/ce88a0bec0d46d8ae180b99c6ec014866fecc4cba1727b5feec8877b2765/ty-0.0.69-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d91965eb799649833d0d6042db09cd03d15289125245337cc46a2606effb7bda", size = 12483136, upload-time = "2026-08-06T10:04:14.33Z" }, - { url = "https://files.pythonhosted.org/packages/63/9e/6fae0ff225a0012642cf72c077e20f8f448c0a80771bc3360e8178fe2f32/ty-0.0.69-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1f03359cd8e5c412aa0c181118fa9b9061a4dddaedbb61bac0a424fb0814d402", size = 12799025, upload-time = "2026-08-06T10:04:16.445Z" }, - { url = "https://files.pythonhosted.org/packages/e4/43/78a658d18b2a4ccf35b053392f2213bf12e3c63b2abea512d3b6751d1f4c/ty-0.0.69-py3-none-win32.whl", hash = "sha256:ec460e01586b1eb91894c4a8403bee3e045a47e7a4ada943cc27ce8e348e88cf", size = 11787774, upload-time = "2026-08-06T10:04:18.622Z" }, - { url = "https://files.pythonhosted.org/packages/3a/5e/88db1f674403f2b81316a853a44a81ed220621fa96f8f7ae586fb6ca7513/ty-0.0.69-py3-none-win_amd64.whl", hash = "sha256:18976ca26a4e28fc3249477f79a695d5502e670803f2e080d89ac905baef3c6e", size = 12864038, upload-time = "2026-08-06T10:04:20.748Z" }, - { url = "https://files.pythonhosted.org/packages/4d/7b/6fc6efd00c69103d70f2bdbe824343089cd70b17b3079170057d3e5a3ac0/ty-0.0.69-py3-none-win_arm64.whl", hash = "sha256:7d4ca3bb74d91cb9947ba3f3b4cb131ad6a2b3ecc76d34040c4ec6092d2e411d", size = 12196693, upload-time = "2026-08-06T10:04:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3b/f51461239a4e66565d4b362f97a3b55fe7fdba2e944068341f87c62f6743/ty-0.0.72-py3-none-linux_armv6l.whl", hash = "sha256:fda86db153ffd85ee52000cf175d6a3f1c0223772cf7c5b6f726200bf92c7b44", size = 12621989, upload-time = "2026-08-14T21:35:01.676Z" }, + { url = "https://files.pythonhosted.org/packages/ca/fb/79ddf683affc679ca856f3510b5640ec3a88a842ba5f654f5d4bc78f1786/ty-0.0.72-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ceb944c612529b9023acfdc9cf4c0dcbb722549f9d17d46baecd1141baf01d7f", size = 12233910, upload-time = "2026-08-14T21:35:04.334Z" }, + { url = "https://files.pythonhosted.org/packages/5d/45/10562a0d84802158db8fa4ec46de54aa9fdcecdeeaabbfe3639ae7042b66/ty-0.0.72-py3-none-macosx_11_0_arm64.whl", hash = "sha256:108d76218333d6c092e5f1cebf8e9b06f25738613a0236a28e2dd47c936ee52c", size = 12084108, upload-time = "2026-08-14T21:35:06.686Z" }, + { url = "https://files.pythonhosted.org/packages/a1/dc/1fe1aef8d697e3509face271a5331700c7aa1d1e44a4b622707bdfa41d4b/ty-0.0.72-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f3943f186f741a2499a31053872169250c9264a9a49684920e48d8fcf4ef4f5", size = 12132640, upload-time = "2026-08-14T21:35:09.305Z" }, + { url = "https://files.pythonhosted.org/packages/14/46/41ceb265e96969487311a2014bd0e53abb4fbc1395efb2ebe411fcb4db62/ty-0.0.72-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf283c07dc3cc52ca48a3ad8ab100fb5aec3aebbd03ef6a12d5f910b8e596fc5", size = 12402489, upload-time = "2026-08-14T21:35:11.555Z" }, + { url = "https://files.pythonhosted.org/packages/2b/45/30bf43cb4fd505c5c2dd30fda27dde5f05208686cd21217adec77c954204/ty-0.0.72-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95f3b6462c38f9f115d10cee21f47fedf715fcf2040daf36eef210359300bc7c", size = 13130835, upload-time = "2026-08-14T21:35:13.746Z" }, + { url = "https://files.pythonhosted.org/packages/31/2f/03bba754d2613f640df168335c41f83f41db150bb515839c60d80e3a7880/ty-0.0.72-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30caf658feb8ffb250d9e9e47107657a78f5f3425c227df1664d8df2ebe38880", size = 13590392, upload-time = "2026-08-14T21:35:16.839Z" }, + { url = "https://files.pythonhosted.org/packages/04/c7/03c67f00e63005ec41585653dc3096064570b1e6273742baae2798cd242f/ty-0.0.72-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27bdc012ddfbeec8948e4a6036c0dc39ac7cf2c8ec7c7d48dc7d2fd56d57b399", size = 13309629, upload-time = "2026-08-14T21:35:19.169Z" }, + { url = "https://files.pythonhosted.org/packages/c1/df/102d3b264eb7f2a58dd11952f229bb5150bb5668d176a6154976a6675981/ty-0.0.72-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:802c5970a77d7739e6f499921fbb6984fb7ad8a31d95e1ff42fd46f3642e4f3b", size = 12734028, upload-time = "2026-08-14T21:35:22.099Z" }, + { url = "https://files.pythonhosted.org/packages/61/85/d0737c8c54d0ba67366ddfb9f31d88edf0b02299e65923e6945ae60ebcb5/ty-0.0.72-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:47dce65114fdc615c68ca0edb393b433df0956447e4267df0e264137a789598d", size = 13174832, upload-time = "2026-08-14T21:35:24.71Z" }, + { url = "https://files.pythonhosted.org/packages/1e/31/497f5a96c36d9b586ab6afe0574986835c6fd5b835a89773d2bec4711b49/ty-0.0.72-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:325144fa07e2675d0faa337fcc864213c272a499eb0cfe5bde2fdc62282d27bc", size = 12215005, upload-time = "2026-08-14T21:35:26.892Z" }, + { url = "https://files.pythonhosted.org/packages/df/7d/46e65b17b4966c7cd0140f134380d33d8e84fe6efccd761533ce793dc502/ty-0.0.72-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a5c9f15d0f58e43707d8848274be1821a0ef408eccb8aa7dda28a4a9eddf7640", size = 12421298, upload-time = "2026-08-14T21:35:29.301Z" }, + { url = "https://files.pythonhosted.org/packages/08/2a/12ada4ec17700b3cb1d4fd3bc3e5b1852df9e6885288429318cade87b3c1/ty-0.0.72-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8ee508d64b381871529cc22c412b41071bf5e908b7aa5d66a38f3f6b2573a806", size = 12669242, upload-time = "2026-08-14T21:35:31.444Z" }, + { url = "https://files.pythonhosted.org/packages/1c/1a/4692536880790fb550ed6d44a6096778dc71bb112f2c6d615cebb01a57e5/ty-0.0.72-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3699e2ec7921d44da79d6b089f7bf239b2cc53c4e45a5a38430adc34ee9e9a55", size = 12988199, upload-time = "2026-08-14T21:35:33.749Z" }, + { url = "https://files.pythonhosted.org/packages/9a/0d/f5e5a50322e9c45865e7b7a428ba6cd6527387cf0f2472492ac3cf746243/ty-0.0.72-py3-none-win32.whl", hash = "sha256:f25f72a67bd36cd247707c4784e52fad0b6b4f42a1b7dd14804110fa95c486ed", size = 11939708, upload-time = "2026-08-14T21:35:36.006Z" }, + { url = "https://files.pythonhosted.org/packages/3f/4e/8af3534b2e4214e6184a5a59c34101e94a68d578f081f97b995866bab1bf/ty-0.0.72-py3-none-win_amd64.whl", hash = "sha256:cdeee869341717e1736cea2e2d7856738c6957c320f584ed2f68c8f90100d2f5", size = 12643876, upload-time = "2026-08-14T21:35:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ea/a2606e654c7276bd08586391a2525b0af3f3bf60228a8c57b2d248f273f9/ty-0.0.72-py3-none-win_arm64.whl", hash = "sha256:1bd3ac3ed4424a6d6990a85dc388556aea012bd752de21349a84b685951de0d8", size = 12394857, upload-time = "2026-08-14T21:35:40.277Z" }, ] [[package]]