mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-06-11 22:14:46 +08:00
Compare commits
15 Commits
tune
...
minimal-tu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea4e165020 | ||
|
|
80663f0406 | ||
|
|
6a741b0539 | ||
|
|
66cc67cfcb | ||
|
|
f9695484ef | ||
|
|
1f8f980729 | ||
|
|
082cf39d73 | ||
|
|
5ccabb9d54 | ||
|
|
0bb2f8c9d4 | ||
|
|
3a74f8c568 | ||
|
|
5eed9490c6 | ||
|
|
57b461a186 | ||
|
|
80e23509ba | ||
|
|
c07ddcefb0 | ||
|
|
e8f65bc652 |
@@ -241,10 +241,3 @@ jobs:
|
||||
gh run watch "$RUN_ID"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Trigger prebuilt workflow
|
||||
if: success() && steps.push-changes.outputs.has_changes == 'true'
|
||||
run: |
|
||||
gh workflow run sunnypilot-build-prebuilt.yaml --ref "${{ inputs.target_branch || env.DEFAULT_TARGET_BRANCH }}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
101
CHANGELOG.md
101
CHANGELOG.md
@@ -1,5 +1,104 @@
|
||||
sunnypilot Version 2025.003.000 (20xx-xx-xx)
|
||||
sunnypilot Version 2026.001.000 (2026-03-xx)
|
||||
========================
|
||||
* What's Changed (sunnypilot/sunnypilot)
|
||||
* Complete rewrite of the user interface from Qt C++ to Raylib Python
|
||||
* comma four support
|
||||
* ui: sunnypilot toggle style by @nayan8teen
|
||||
* ui: fix scroll panel mouse wheel behavior by @nayan8teen
|
||||
* ui: sunnypilot panels by @nayan8teen
|
||||
* sunnylink: centralize key pair handling in sunnylink registration by @devtekve
|
||||
* ui: reimplement sunnypilot branding with Raylib by @sunnyhaibin
|
||||
* ui: Platform Selector by @Discountchubbs
|
||||
* ui: vehicle brand settings by @Discountchubbs
|
||||
* ui: sunnylink client-side implementation by @nayan8teen
|
||||
* ui: `NetworkUISP` by @Discountchubbs
|
||||
* ui: add sunnypilot font by @nayan8teen
|
||||
* ui: sunnypilot sponsor tier color mapping by @sunnyhaibin
|
||||
* ui: sunnylink panel by @nayan8teen
|
||||
* ui: Models panel by @Discountchubbs
|
||||
* ui: software panel by @Discountchubbs
|
||||
* modeld_v2: support planplus outputs by @Discountchubbs
|
||||
* ui: OSM panel by @Discountchubbs
|
||||
* ui: Developer panel extension by @Discountchubbs
|
||||
* sunnylink: Vehicle Selector support by @sunnyhaibin
|
||||
* [TIZI/TICI] ui: Developer Metrics by @rav4kumar
|
||||
* [comma 4] ui: sunnylink panel by @nayan8teen
|
||||
* ui: lateral-only and longitudinal-only UI statuses support by @royjr
|
||||
* sunnylink: elliptic curve keys support and improve key path handling by @nayan8teen
|
||||
* sunnylink: block remote modification of SSH key parameters by @zikeji
|
||||
* [TIZI/TICI] ui: rainbow path by @rav4kumar
|
||||
* [TIZI/TICI] ui: chevron metrics by @rav4kumar
|
||||
* ui: include MADS enabled state to `engaged` check by @sunnyhaibin
|
||||
* Toyota: Enforce Factory Longitudinal Control by @sunnyhaibin
|
||||
* ui: fix malformed dongle ID display on the PC if dongleID is not set by @dzid26
|
||||
* SL: Re enable and validate ingestion of swaglogs by @devtekve
|
||||
* modeld_v2: planplus model tuning by @Discountchubbs
|
||||
* ui: fix Always Offroad button visibility by @nayan8teen
|
||||
* Reimplement sunnypilot Terms of Service & sunnylink Consent Screens by @sunnyhaibin
|
||||
* [TIZI/TICI] ui: update dmoji position and Developer UI adjustments by @rav4kumar
|
||||
* modeld: configurable camera offset by @Discountchubbs
|
||||
* [TIZI/TICI] ui: sunnylink status on sidebar by @Copilot
|
||||
* ui: Global Brightness Override by @nayan8teen
|
||||
* ui: Customizable Interactive Timeout by @sunnyhaibin
|
||||
* sunnylink: add units to param metadata by @nayan8teen
|
||||
* ui: Customizable Onroad Brightness by @sunnyhaibin
|
||||
* [TIZI/TICI] ui: Steering panel by @nayan8teen
|
||||
* [TIZI/TICI] ui: Rocket Fuel by @rav4kumar
|
||||
* [TIZI/TICI] ui: MICI style turn signals by @rav4kumar
|
||||
* [TIZI/TICI] ui: MICI style blindspot indicators by @sunnyhaibin
|
||||
* [MICI] ui: display blindspot indicators when available by @rav4kumar
|
||||
* [TIZI/TICI] ui: Road Name by @rav4kumar
|
||||
* [TIZI/TICI] ui: Blue "Exit Always Offroad" button by @dzid26
|
||||
* [TIZI/TICI] ui: Speed Limit by @rav4kumar
|
||||
* Reapply "latcontrol_torque: lower kp and lower friction threshold (commaai/openpilot#36619)" by @sunnyhaibin
|
||||
* [TIZI/TICI] ui: steering arc by @royjr
|
||||
* [TIZI/TICI] ui: Smart Cruise Control elements by @sunnyhaibin
|
||||
* [TIZI/TICI] ui: Green Light and Lead Departure elements by @sunnyhaibin
|
||||
* [TIZI/TICI] ui: standstill timer by @sunnyhaibin
|
||||
* [MICI] ui: driving models selector by @Discountchubbs
|
||||
* [TIZI/TICI] ui: Hide vEgo and True vEgo by @sunnyhaibin
|
||||
* [TIZI/TICI] ui: Visuals panel by @nayan8teen
|
||||
* Device: Retain QuickBoot state after op switch by @nayan8teen
|
||||
* [TIZI/TICI] ui: Trips panel by @sunnyhaibin
|
||||
* [TIZI/TICI] ui: dynamic ICBM status by @sunnyhaibin
|
||||
* [TIZI/TICI] ui: Cruise panel by @sunnyhaibin
|
||||
* ui: better wake mode support by @nayan8teen
|
||||
* Pause Lateral Control with Blinker: Post-Blinker Delay by @CHaucke89
|
||||
* SCC-V: Use p97 for predicted lateral accel by @yasu-oh
|
||||
* Controls: Support for Torque Lateral Control v0 Tune by @sunnyhaibin
|
||||
* What's Changed (sunnypilot/opendbc)
|
||||
* Honda: DBC for Accord 9th Generation by @mvl-boston
|
||||
* FCA: update tire stiffness values for `RAM_HD` by @dparring
|
||||
* Honda: Nidec hybrid baseline brake support by @mvl-boston
|
||||
* Subaru Global Gen2: bump steering limits and update tuning by @sunnyhaibin
|
||||
* Toyota: Enforce Stock Longitudinal Control by @rav4kumar
|
||||
* Nissan: use MADS enabled status for LKAS HUD logic by @downquark7
|
||||
* Reapply "Lateral: lower friction threshold (#2915)" (#378) by @sunnyhaibin
|
||||
* HKG: add KIA_FORTE_2019_NON_SCC fingerprint by @royjr
|
||||
* Nissan: Parse cruise control buttons by @downquark7
|
||||
* Rivian: Add stalk down ACC behavior to match stock Rivian by @lukasloetkolben
|
||||
* Tesla: remove `TESLA_MODEL_X` from `dashcamOnly` by @ssysm
|
||||
* Hyundai Longitudinal: refactor tuning by @Discountchubbs
|
||||
* Tesla: add fingerprint for Model 3 Performance HW4 by @sunnyhaibin
|
||||
* Toyota: do not disable radar when smartDSU or CAN Filter detected by @sunnyhaibin
|
||||
* Honda: add missing `GasInterceptor` messages to Taiwan Odyssey DBC by @mvl-boston
|
||||
* GM: remove `CHEVROLET_EQUINOX_NON_ACC_3RD_GEN` from `dashcamOnly` by @sunnyhaibin
|
||||
* GM: remove `CHEVROLET_BOLT_NON_ACC_2ND_GEN` from `dashcamOnly` by @sunnyhaibin
|
||||
* New Contributors (sunnypilot/sunnypilot)
|
||||
* @TheSecurityDev made their first contribution in "ui: fix sidebar scroll in UI screenshots"
|
||||
* @zikeji made their first contribution in "sunnylink: block remote modification of SSH key parameters"
|
||||
* @Candy0707 made their first contribution in "[TIZI/TICI] ui: Fix misaligned turn signals and blindspot indicators with sidebar"
|
||||
* @CHaucke89 made their first contribution in "Pause Lateral Control with Blinker: Post-Blinker Delay"
|
||||
* @yasu-oh made their first contribution in "SCC-V: Use p97 for predicted lateral accel"
|
||||
* New Contributors (sunnypilot/opendbc)
|
||||
* @AmyJeanes made their first contribution in "Tesla: Fix stock LKAS being blocked when MADS is enabled"
|
||||
* @mvl-boston made their first contribution in "Honda: Update Clarity brake to renamed DBC message name"
|
||||
* @dzid26 made their first contribution in "Tesla: Parse speed limit from CAN"
|
||||
* @firestar5683 made their first contribution in "GM: Non-ACC platforms with steering only support"
|
||||
* @downquark7 made their first contribution in "Nissan: use MADS enabled status for LKAS HUD logic"
|
||||
* @royjr made their first contribution in "HKG: add KIA_FORTE_2019_NON_SCC fingerprint"
|
||||
* @ssysm made their first contribution in "Tesla: remove `TESLA_MODEL_X` from `dashcamOnly`"
|
||||
* Full Changelog: https://github.com/sunnypilot/sunnypilot/compare/v2025.002.000...v2026.001.000
|
||||
|
||||
sunnypilot Version 2025.002.000 (2025-11-06)
|
||||
========================
|
||||
|
||||
@@ -268,6 +268,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"EnforceTorqueControl", {PERSISTENT | BACKUP, BOOL}},
|
||||
{"LiveTorqueParamsToggle", {PERSISTENT | BACKUP , BOOL}},
|
||||
{"LiveTorqueParamsRelaxedToggle", {PERSISTENT | BACKUP , BOOL}},
|
||||
{"TorqueControlTune", {PERSISTENT | BACKUP, FLOAT}},
|
||||
{"TorqueParamsOverrideEnabled", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"TorqueParamsOverrideFriction", {PERSISTENT | BACKUP, FLOAT, "0.1"}},
|
||||
{"TorqueParamsOverrideLatAccelFactor", {PERSISTENT | BACKUP, FLOAT, "2.5"}},
|
||||
|
||||
Submodule opendbc_repo updated: 383a720260...a54fae3101
@@ -64,6 +64,8 @@ class Controls(ControlsExt):
|
||||
elif self.CP.lateralTuning.which() == 'torque':
|
||||
self.LaC = LatControlTorque(self.CP, self.CP_SP, self.CI, DT_CTRL)
|
||||
|
||||
self.LaC = ControlsExt.initialize_lateral_control(self, self.LaC, self.CI, DT_CTRL)
|
||||
|
||||
def update(self):
|
||||
self.sm.update(15)
|
||||
if self.sm.updated["liveCalibration"]:
|
||||
|
||||
@@ -99,7 +99,7 @@ class ModelsLayout(Widget):
|
||||
"Keeping this on provides the stock openpilot experience.")
|
||||
if lagd_toggle:
|
||||
desc += f"<br>{tr('Live Steer Delay:')} {ui_state.sm['liveDelay'].lateralDelay:.3f} s"
|
||||
elif ui_state.CP:
|
||||
elif ui_state.CP is not None:
|
||||
sw = float(ui_state.params.get("LagdToggleDelay", "0.2"))
|
||||
cp = ui_state.CP.steerActuatorDelay
|
||||
desc += f"<br>{tr('Actuator Delay:')} {cp:.2f} s + {tr('Software Delay:')} {sw:.2f} s = {tr('Total Delay:')} {cp + sw:.2f} s"
|
||||
|
||||
@@ -75,7 +75,7 @@ class LaneChangeSettingsLayout(Widget):
|
||||
self._scroller.show_event()
|
||||
|
||||
def _update_toggles(self):
|
||||
enable_bsm = ui_state.CP and ui_state.CP.enableBsm
|
||||
enable_bsm = ui_state.CP is not None and ui_state.CP.enableBsm
|
||||
if not enable_bsm and ui_state.params.get_bool("AutoLaneChangeBsmDelay"):
|
||||
ui_state.params.remove("AutoLaneChangeBsmDelay")
|
||||
self._bsm_delay.action_item.set_enabled(enable_bsm and ui_state.params.get("AutoLaneChangeTimer", return_default=True) > AutoLaneChangeMode.NUDGE)
|
||||
|
||||
@@ -91,12 +91,12 @@ class MadsSettingsLayout(Widget):
|
||||
if bundle:
|
||||
brand = bundle.get("brand", "")
|
||||
if not brand:
|
||||
brand = ui_state.CP.brand if ui_state.CP else ""
|
||||
brand = ui_state.CP.brand if ui_state.CP is not None else ""
|
||||
|
||||
if brand == "rivian":
|
||||
return True
|
||||
elif brand == "tesla":
|
||||
return not (ui_state.CP_SP and ui_state.CP_SP.flags & TeslaFlagsSP.HAS_VEHICLE_BUS)
|
||||
return not (ui_state.CP_SP is not None and ui_state.CP_SP.flags & TeslaFlagsSP.HAS_VEHICLE_BUS)
|
||||
return False
|
||||
|
||||
def _update_steering_mode_description(self, button_index: int):
|
||||
|
||||
@@ -4,15 +4,24 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from collections.abc import Callable
|
||||
import pyray as rl
|
||||
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp, option_item_sp
|
||||
from openpilot.system.ui.sunnypilot.lib.utils import NoElideButtonAction
|
||||
from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP, toggle_item_sp, option_item_sp
|
||||
from openpilot.system.ui.sunnypilot.widgets.tree_dialog import TreeOptionDialog, TreeFolder, TreeNode
|
||||
from openpilot.system.ui.widgets import Widget, DialogResult
|
||||
from openpilot.system.ui.widgets.network import NavButton
|
||||
from openpilot.system.ui.widgets.scroller_tici import Scroller
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
|
||||
TORQUE_VERSIONS_PATH = os.path.join(BASEDIR, "sunnypilot", "selfdrive", "controls", "lib", "latcontrol_torque_versions.json")
|
||||
|
||||
|
||||
class TorqueSettingsLayout(Widget):
|
||||
@@ -20,10 +29,23 @@ class TorqueSettingsLayout(Widget):
|
||||
super().__init__()
|
||||
self._back_button = NavButton(tr("Back"))
|
||||
self._back_button.set_click_callback(back_btn_callback)
|
||||
self._torque_version_dialog: TreeOptionDialog | None = None
|
||||
self.cached_torque_versions = {}
|
||||
self._load_versions()
|
||||
items = self._initialize_items()
|
||||
self._scroller = Scroller(items, line_separator=True, spacing=0)
|
||||
|
||||
def _load_versions(self):
|
||||
with open(TORQUE_VERSIONS_PATH) as f:
|
||||
self.cached_torque_versions = json.load(f)
|
||||
|
||||
def _initialize_items(self):
|
||||
self._torque_control_versions = ListItemSP(
|
||||
title=tr("Torque Control Tune Version"),
|
||||
description="Select the version of Torque Control Tune to use.",
|
||||
action_item=NoElideButtonAction(tr("SELECT")),
|
||||
callback=self._show_torque_version_dialog,
|
||||
)
|
||||
self._self_tune_toggle = toggle_item_sp(
|
||||
param="LiveTorqueParamsToggle",
|
||||
title=lambda: tr("Self-Tune"),
|
||||
@@ -73,6 +95,7 @@ class TorqueSettingsLayout(Widget):
|
||||
)
|
||||
|
||||
items = [
|
||||
self._torque_control_versions,
|
||||
self._self_tune_toggle,
|
||||
self._relaxed_tune_toggle,
|
||||
self._custom_tune_toggle,
|
||||
@@ -103,6 +126,7 @@ class TorqueSettingsLayout(Widget):
|
||||
title_text = tr("Real-Time & Offline") if ui_state.params.get("TorqueParamsOverrideEnabled") else tr("Offline Only")
|
||||
self._torque_lat_accel_factor.set_title(lambda: tr("Lateral Acceleration Factor") + " (" + title_text + ")")
|
||||
self._torque_friction.set_title(lambda: tr("Friction") + " (" + title_text + ")")
|
||||
self._torque_control_versions.action_item.set_value(self._get_current_torque_version_label())
|
||||
|
||||
def _render(self, rect):
|
||||
self._back_button.set_position(self._rect.x, self._rect.y + 20)
|
||||
@@ -113,3 +137,54 @@ class TorqueSettingsLayout(Widget):
|
||||
|
||||
def show_event(self):
|
||||
self._scroller.show_event()
|
||||
|
||||
def _get_current_torque_version_label(self):
|
||||
current_val_bytes = ui_state.params.get("TorqueControlTune")
|
||||
if current_val_bytes is None:
|
||||
return tr("Default")
|
||||
|
||||
try:
|
||||
current_val = float(current_val_bytes)
|
||||
for label, info in self.cached_torque_versions.items():
|
||||
if math.isclose(float(info["version"]), current_val, rel_tol=1e-5):
|
||||
return label
|
||||
except (ValueError, KeyError):
|
||||
pass
|
||||
|
||||
return tr("Default")
|
||||
|
||||
def _show_torque_version_dialog(self):
|
||||
options_map = {}
|
||||
for label, info in self.cached_torque_versions.items():
|
||||
try:
|
||||
options_map[label] = float(info["version"])
|
||||
except (ValueError, KeyError):
|
||||
pass
|
||||
|
||||
# Sort options by label in descending order
|
||||
sorted_labels = sorted(options_map.keys(), key=lambda k: options_map[k], reverse=True)
|
||||
|
||||
nodes = [TreeNode(tr("Default"))]
|
||||
for label in sorted_labels:
|
||||
nodes.append(TreeNode(label))
|
||||
|
||||
folders = [TreeFolder("", nodes)]
|
||||
|
||||
current_label = self._get_current_torque_version_label()
|
||||
|
||||
def handle_selection(result: int):
|
||||
if result == DialogResult.CONFIRM and self._torque_version_dialog:
|
||||
selected_ref = self._torque_version_dialog.selection_ref
|
||||
if selected_ref == tr("Default"):
|
||||
ui_state.params.remove("TorqueControlTune")
|
||||
elif selected_ref in options_map:
|
||||
ui_state.params.put("TorqueControlTune", options_map[selected_ref])
|
||||
self._torque_version_dialog = None
|
||||
|
||||
self._torque_version_dialog = TreeOptionDialog(
|
||||
tr("Select Torque Control Tune Version"),
|
||||
folders,
|
||||
current_ref=current_label,
|
||||
option_font_weight=FontWeight.UNIFONT,
|
||||
)
|
||||
gui_app.set_modal_overlay(self._torque_version_dialog, callback=handle_selection)
|
||||
|
||||
@@ -35,7 +35,7 @@ class VehicleLayout(Widget):
|
||||
def get_brand():
|
||||
if bundle := ui_state.params.get("CarPlatformBundle"):
|
||||
return bundle.get("brand", "")
|
||||
elif ui_state.CP and ui_state.CP.carFingerprint != "MOCK":
|
||||
elif ui_state.CP is not None and ui_state.CP.carFingerprint != "MOCK":
|
||||
return ui_state.CP.brand
|
||||
return ""
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class HyundaiSettings(BrandSettings):
|
||||
if bundle:
|
||||
platform = bundle.get("platform")
|
||||
self.alpha_long_available = CAR[platform] not in (UNSUPPORTED_LONGITUDINAL_CAR | CANFD_UNSUPPORTED_LONGITUDINAL_CAR)
|
||||
elif ui_state.CP:
|
||||
elif ui_state.CP is not None:
|
||||
self.alpha_long_available = ui_state.CP.alphaLongitudinalAvailable
|
||||
|
||||
tuning_param = int(ui_state.params.get("HyundaiLongitudinalTuning") or "0")
|
||||
|
||||
@@ -39,7 +39,7 @@ class SubaruSettings(BrandSettings):
|
||||
platform = bundle.get("platform")
|
||||
config = CAR[platform].config
|
||||
self.has_stop_and_go = not (config.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID))
|
||||
elif ui_state.CP:
|
||||
elif ui_state.CP is not None:
|
||||
self.has_stop_and_go = not (ui_state.CP.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID))
|
||||
|
||||
disabled_msg = self.stop_and_go_disabled_msg()
|
||||
|
||||
@@ -131,7 +131,7 @@ class PlatformSelector(Button):
|
||||
self._platform = bundle.get("name", "")
|
||||
self.set_text(self._platform)
|
||||
self.color = style.BLUE
|
||||
elif ui_state.CP and ui_state.CP.carFingerprint != "MOCK":
|
||||
elif ui_state.CP is not None and ui_state.CP.carFingerprint != "MOCK":
|
||||
self._platform = ui_state.CP.carFingerprint
|
||||
self.set_text(self._platform)
|
||||
self.color = style.GREEN
|
||||
|
||||
@@ -22,6 +22,8 @@ from openpilot.system.ui.widgets import Widget
|
||||
METER_TO_FOOT = 3.28084
|
||||
METER_TO_MILE = 0.000621371
|
||||
AHEAD_THRESHOLD = 5
|
||||
SET_SPEED_NA = 255
|
||||
KM_TO_MILE = 0.621371
|
||||
|
||||
AssistState = custom.LongitudinalPlanSP.SpeedLimit.AssistState
|
||||
SpeedLimitSource = custom.LongitudinalPlanSP.SpeedLimit.Source
|
||||
@@ -58,8 +60,11 @@ class SpeedLimitRenderer(Widget):
|
||||
self.speed_limit_ahead_frame = 0
|
||||
|
||||
self.assist_frame = 0
|
||||
self.speed = 0.0
|
||||
self.set_speed = 0.0
|
||||
self.is_cruise_set: bool = False
|
||||
self.is_cruise_available: bool = True
|
||||
self.set_speed: float = SET_SPEED_NA
|
||||
self.speed: float = 0.0
|
||||
self.v_ego_cluster_seen: bool = False
|
||||
|
||||
self.font_bold = gui_app.font(FontWeight.BOLD)
|
||||
self.font_demi = gui_app.font(FontWeight.SEMI_BOLD)
|
||||
@@ -77,6 +82,8 @@ class SpeedLimitRenderer(Widget):
|
||||
def update(self):
|
||||
sm = ui_state.sm
|
||||
if sm.recv_frame["carState"] < ui_state.started_frame:
|
||||
self.set_speed = SET_SPEED_NA
|
||||
self.speed = 0.0
|
||||
return
|
||||
|
||||
if sm.updated["longitudinalPlanSP"]:
|
||||
@@ -106,9 +113,21 @@ class SpeedLimitRenderer(Widget):
|
||||
|
||||
self.speed_limit_ahead_dist_prev = self.speed_limit_ahead_dist
|
||||
|
||||
cs = sm["carState"]
|
||||
self.set_speed = cs.cruiseState.speed * self.speed_conv
|
||||
v_ego = cs.vEgoCluster if cs.vEgoCluster != 0.0 else cs.vEgo
|
||||
controls_state = sm['controlsState']
|
||||
car_state = sm["carState"]
|
||||
|
||||
v_cruise_cluster = car_state.vCruiseCluster
|
||||
self.set_speed = (
|
||||
controls_state.vCruiseDEPRECATED if v_cruise_cluster == 0.0 else v_cruise_cluster
|
||||
)
|
||||
self.is_cruise_set = 0 < self.set_speed < SET_SPEED_NA
|
||||
self.is_cruise_available = self.set_speed != -1
|
||||
|
||||
if self.is_cruise_set and not ui_state.is_metric:
|
||||
self.set_speed *= KM_TO_MILE
|
||||
|
||||
self.v_ego_cluster_seen = self.v_ego_cluster_seen or car_state.vEgoCluster != 0.0
|
||||
v_ego = car_state.vEgoCluster if self.v_ego_cluster_seen else car_state.vEgo
|
||||
self.speed = max(0.0, v_ego * self.speed_conv)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define SUNNYPILOT_VERSION "2025.003.000"
|
||||
#define SUNNYPILOT_VERSION "2026.001.000"
|
||||
|
||||
@@ -16,6 +16,7 @@ from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD
|
||||
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
||||
from openpilot.sunnypilot.modeld.modeld_base import ModelStateBase
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.blinker_pause_lateral import BlinkerPauseLateral
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.latcontrol_torque_v0 import LatControlTorque as LatControlTorqueV0
|
||||
|
||||
|
||||
class ControlsExt(ModelStateBase):
|
||||
@@ -33,6 +34,17 @@ class ControlsExt(ModelStateBase):
|
||||
self.sm_services_ext = ['radarState', 'selfdriveStateSP']
|
||||
self.pm_services_ext = ['carControlSP']
|
||||
|
||||
def initialize_lateral_control(self, lac, CI, dt):
|
||||
enforce_torque_control = self.params.get_bool("EnforceTorqueControl")
|
||||
torque_versions = self.params.get("TorqueControlTune")
|
||||
if not enforce_torque_control:
|
||||
return lac
|
||||
|
||||
if torque_versions == 0.0: # v0
|
||||
return LatControlTorqueV0(self.CP, self.CP_SP, CI, dt)
|
||||
else:
|
||||
return lac
|
||||
|
||||
def get_params_sp(self, sm: messaging.SubMaster) -> None:
|
||||
if time.monotonic() - self._param_update_time > PARAMS_UPDATE_PERIOD:
|
||||
self.blinker_pause_lateral.get_params()
|
||||
|
||||
128
sunnypilot/selfdrive/controls/lib/latcontrol_torque_v0.py
Normal file
128
sunnypilot/selfdrive/controls/lib/latcontrol_torque_v0.py
Normal file
@@ -0,0 +1,128 @@
|
||||
import math
|
||||
import numpy as np
|
||||
from collections import deque
|
||||
|
||||
from cereal import log
|
||||
from opendbc.car.lateral import get_friction
|
||||
from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY
|
||||
from openpilot.common.filter_simple import FirstOrderFilter
|
||||
from openpilot.selfdrive.controls.lib.latcontrol import LatControl
|
||||
from openpilot.common.pid import PIDController
|
||||
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.latcontrol_torque_ext import LatControlTorqueExt
|
||||
|
||||
# At higher speeds (25+mph) we can assume:
|
||||
# Lateral acceleration achieved by a specific car correlates to
|
||||
# torque applied to the steering rack. It does not correlate to
|
||||
# wheel slip, or to speed.
|
||||
|
||||
# This controller applies torque to achieve desired lateral
|
||||
# accelerations. To compensate for the low speed effects the
|
||||
# proportional gain is increased at low speeds by the PID controller.
|
||||
# Additionally, there is friction in the steering wheel that needs
|
||||
# to be overcome to move it at all, this is compensated for too.
|
||||
|
||||
KP = 1.0
|
||||
KI = 0.3
|
||||
KD = 0.0
|
||||
INTERP_SPEEDS = [1, 1.5, 2.0, 3.0, 5, 7.5, 10, 15, 30]
|
||||
KP_INTERP = [250, 120, 65, 30, 11.5, 5.5, 3.5, 2.0, KP]
|
||||
|
||||
LP_FILTER_CUTOFF_HZ = 1.2
|
||||
LAT_ACCEL_REQUEST_BUFFER_SECONDS = 1.0
|
||||
FRICTION_THRESHOLD = 0.3
|
||||
VERSION = 0
|
||||
|
||||
|
||||
class LatControlTorque(LatControl):
|
||||
def __init__(self, CP, CP_SP, CI, dt):
|
||||
super().__init__(CP, CP_SP, CI, dt)
|
||||
self.torque_params = CP.lateralTuning.torque.as_builder()
|
||||
self.torque_from_lateral_accel = CI.torque_from_lateral_accel()
|
||||
self.lateral_accel_from_torque = CI.lateral_accel_from_torque()
|
||||
self.pid = PIDController([INTERP_SPEEDS, KP_INTERP], KI, KD, rate=1/self.dt)
|
||||
self.update_limits()
|
||||
self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg
|
||||
self.lat_accel_request_buffer_len = int(LAT_ACCEL_REQUEST_BUFFER_SECONDS / self.dt)
|
||||
self.lat_accel_request_buffer = deque([0.] * self.lat_accel_request_buffer_len , maxlen=self.lat_accel_request_buffer_len)
|
||||
self.previous_measurement = 0.0
|
||||
self.measurement_rate_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt)
|
||||
|
||||
self.extension = LatControlTorqueExt(self, CP, CP_SP, CI)
|
||||
|
||||
def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction):
|
||||
self.torque_params.latAccelFactor = latAccelFactor
|
||||
self.torque_params.latAccelOffset = latAccelOffset
|
||||
self.torque_params.friction = friction
|
||||
self.update_limits()
|
||||
|
||||
def update_limits(self):
|
||||
self.pid.set_limits(self.lateral_accel_from_torque(self.steer_max, self.torque_params),
|
||||
self.lateral_accel_from_torque(-self.steer_max, self.torque_params))
|
||||
|
||||
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, calibrated_pose, curvature_limited, lat_delay):
|
||||
# Override torque params from extension
|
||||
if self.extension.update_override_torque_params(self.torque_params):
|
||||
self.update_limits()
|
||||
|
||||
pid_log = log.ControlsState.LateralTorqueState.new_message()
|
||||
pid_log.version = VERSION
|
||||
if not active:
|
||||
output_torque = 0.0
|
||||
pid_log.active = False
|
||||
else:
|
||||
measured_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
|
||||
roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY
|
||||
curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0))
|
||||
lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2
|
||||
|
||||
delay_frames = int(np.clip(lat_delay / self.dt, 1, self.lat_accel_request_buffer_len))
|
||||
expected_lateral_accel = self.lat_accel_request_buffer[-delay_frames]
|
||||
# TODO factor out lateral jerk from error to later replace it with delay independent alternative
|
||||
future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2
|
||||
self.lat_accel_request_buffer.append(future_desired_lateral_accel)
|
||||
gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation
|
||||
desired_lateral_jerk = (future_desired_lateral_accel - expected_lateral_accel) / lat_delay
|
||||
|
||||
measurement = measured_curvature * CS.vEgo ** 2
|
||||
measurement_rate = self.measurement_rate_filter.update((measurement - self.previous_measurement) / self.dt)
|
||||
self.previous_measurement = measurement
|
||||
|
||||
setpoint = lat_delay * desired_lateral_jerk + expected_lateral_accel
|
||||
error = setpoint - measurement
|
||||
|
||||
# do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly
|
||||
pid_log.error = float(error)
|
||||
ff = gravity_adjusted_future_lateral_accel
|
||||
# latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll
|
||||
ff -= self.torque_params.latAccelOffset
|
||||
# TODO jerk is weighted by lat_delay for legacy reasons, but should be made independent of it
|
||||
ff += get_friction(error, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params)
|
||||
|
||||
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
|
||||
output_lataccel = self.pid.update(pid_log.error,
|
||||
-measurement_rate,
|
||||
feedforward=ff,
|
||||
speed=CS.vEgo,
|
||||
freeze_integrator=freeze_integrator)
|
||||
output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params)
|
||||
|
||||
# Lateral acceleration torque controller extension updates
|
||||
# Overrides pid_log.error and output_torque
|
||||
pid_log, output_torque = self.extension.update(CS, VM, self.pid, params, ff, pid_log, setpoint, measurement, calibrated_pose, roll_compensation,
|
||||
future_desired_lateral_accel, measurement, lateral_accel_deadzone, gravity_adjusted_future_lateral_accel,
|
||||
desired_curvature, measured_curvature, steer_limited_by_safety, output_torque)
|
||||
|
||||
pid_log.active = True
|
||||
pid_log.p = float(self.pid.p)
|
||||
pid_log.i = float(self.pid.i)
|
||||
pid_log.d = float(self.pid.d)
|
||||
pid_log.f = float(self.pid.f)
|
||||
pid_log.output = float(-output_torque) # TODO: log lat accel?
|
||||
pid_log.actualLateralAccel = float(measurement)
|
||||
pid_log.desiredLateralAccel = float(setpoint)
|
||||
pid_log.desiredLateralJerk = float(desired_lateral_jerk)
|
||||
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited))
|
||||
|
||||
# TODO left is positive in this convention
|
||||
return -output_torque, 0.0, pid_log
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"v1.0": {
|
||||
"version": "1.0"
|
||||
},
|
||||
"v0.0": {
|
||||
"version": "0.0"
|
||||
}
|
||||
}
|
||||
@@ -1247,6 +1247,24 @@
|
||||
"title": "[TIZI/TICI only] Steering Arc",
|
||||
"description": "Display steering arc on the driving screen when lateral control is enabled."
|
||||
},
|
||||
"TorqueControlTune": {
|
||||
"title": "Torque Control Tune Version",
|
||||
"description": "Select the version of Torque Control Tune to use.",
|
||||
"options": [
|
||||
{
|
||||
"value": "",
|
||||
"label": "Default"
|
||||
},
|
||||
{
|
||||
"value": 1.0,
|
||||
"label": "v1.0"
|
||||
},
|
||||
{
|
||||
"value": 0.0,
|
||||
"label": "v0.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"TorqueParamsOverrideEnabled": {
|
||||
"title": "Manual Real-Time Tuning",
|
||||
"description": ""
|
||||
|
||||
@@ -200,3 +200,85 @@ def test_known_params_metadata():
|
||||
assert acc_long["min"] == 1
|
||||
assert acc_long["max"] == 10
|
||||
assert acc_long["step"] == 1
|
||||
|
||||
|
||||
def test_torque_control_tune_versions_in_sync():
|
||||
"""
|
||||
Test that TorqueControlTune options in params_metadata.json match versions in latcontrol_torque_versions.json.
|
||||
|
||||
Why:
|
||||
The TorqueControlTune dropdown in the UI should always reflect the available torque tune versions.
|
||||
If versions are added/removed from latcontrol_torque_versions.json, the metadata must be updated accordingly.
|
||||
|
||||
Expected:
|
||||
- TorqueControlTune should have a 'Default' option with empty string value
|
||||
- All versions from latcontrol_torque_versions.json should be present in the options
|
||||
- The version values and labels should match between both files
|
||||
"""
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
|
||||
versions_json_path = os.path.join(BASEDIR, "sunnypilot", "selfdrive", "controls", "lib", "latcontrol_torque_versions.json")
|
||||
sync_script_path = "python3 sunnypilot/sunnylink/tools/sync_torque_versions.py"
|
||||
|
||||
# Load both files
|
||||
with open(METADATA_PATH) as f:
|
||||
metadata = json.load(f)
|
||||
|
||||
with open(versions_json_path) as f:
|
||||
versions = json.load(f)
|
||||
|
||||
# Get TorqueControlTune metadata
|
||||
torque_tune = metadata.get("TorqueControlTune")
|
||||
if torque_tune is None:
|
||||
pytest.fail(f"TorqueControlTune not found in params_metadata.json. Please run '{sync_script_path}' to sync.")
|
||||
|
||||
if "options" not in torque_tune:
|
||||
pytest.fail(f"TorqueControlTune must have options. Please run '{sync_script_path}' to sync.")
|
||||
|
||||
options = torque_tune["options"]
|
||||
if not isinstance(options, list):
|
||||
pytest.fail(f"TorqueControlTune options must be a list. Please run '{sync_script_path}' to sync.")
|
||||
|
||||
if len(options) == 0:
|
||||
pytest.fail(f"TorqueControlTune must have at least one option. Please run '{sync_script_path}' to sync.")
|
||||
|
||||
# Check that Default option exists
|
||||
default_option = next((opt for opt in options if opt.get("value") == ""), None)
|
||||
if default_option is None:
|
||||
pytest.fail(f"TorqueControlTune must have a 'Default' option with empty string value. Please run '{sync_script_path}' to sync.")
|
||||
|
||||
if default_option.get("label") != "Default":
|
||||
pytest.fail(f"Default option must have label 'Default'. Please run '{sync_script_path}' to sync.")
|
||||
|
||||
# Build expected options from versions.json
|
||||
expected_version_keys = set(versions.keys())
|
||||
actual_version_keys = set()
|
||||
|
||||
for option in options:
|
||||
if option.get("value") == "":
|
||||
continue # Skip the default option
|
||||
|
||||
label = option.get("label")
|
||||
value = option.get("value")
|
||||
|
||||
# Check that this option corresponds to a version
|
||||
if label not in versions:
|
||||
pytest.fail(f"Option label '{label}' not found in latcontrol_torque_versions.json. Please run '{sync_script_path}' to sync.")
|
||||
|
||||
# Check that the value matches the version number
|
||||
expected_value = float(versions[label]["version"])
|
||||
if value != expected_value:
|
||||
pytest.fail(f"Option '{label}' has value {value}, expected {expected_value}. Please run '{sync_script_path}' to sync.")
|
||||
|
||||
actual_version_keys.add(label)
|
||||
|
||||
# Check that all versions are represented
|
||||
missing_versions = expected_version_keys - actual_version_keys
|
||||
if missing_versions:
|
||||
pytest.fail(f"The following versions are missing from TorqueControlTune options: {missing_versions}. " +
|
||||
f"Please run '{sync_script_path}' to sync.")
|
||||
|
||||
extra_versions = actual_version_keys - expected_version_keys
|
||||
if extra_versions:
|
||||
pytest.fail("The following versions in TorqueControlTune options are not in latcontrol_torque_versions.json: " +
|
||||
f"{extra_versions}. Please run '{sync_script_path}' to sync.")
|
||||
|
||||
@@ -8,9 +8,11 @@ See the LICENSE.md file in the root directory for more details.
|
||||
import json
|
||||
import os
|
||||
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.common.params import Params
|
||||
|
||||
METADATA_PATH = os.path.join(os.path.dirname(__file__), "../params_metadata.json")
|
||||
TORQUE_VERSIONS_JSON = os.path.join(BASEDIR, "sunnypilot", "selfdrive", "controls", "lib", "latcontrol_torque_versions.json")
|
||||
|
||||
|
||||
def main():
|
||||
@@ -51,6 +53,33 @@ def main():
|
||||
|
||||
print(f"Updated {METADATA_PATH}")
|
||||
|
||||
# update torque versions param
|
||||
update_torque_versions_param()
|
||||
|
||||
def update_torque_versions_param():
|
||||
with open(TORQUE_VERSIONS_JSON) as f:
|
||||
current_versions = json.load(f)
|
||||
|
||||
try:
|
||||
with open(METADATA_PATH) as f:
|
||||
params_metadata = json.load(f)
|
||||
|
||||
options = [{"value": "", "label": "Default"}]
|
||||
for version_key, version_data in current_versions.items():
|
||||
version_value = float(version_data["version"])
|
||||
options.append({"value": version_value, "label": str(version_key)})
|
||||
|
||||
if "TorqueControlTune" in params_metadata:
|
||||
params_metadata["TorqueControlTune"]["options"] = options
|
||||
|
||||
with open(METADATA_PATH, 'w') as f:
|
||||
json.dump(params_metadata, f, indent=2)
|
||||
f.write('\n')
|
||||
|
||||
print(f"Updated TorqueControlTune options in params_metadata.json with {len(options)} options: \n{options}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to update TorqueControlTune versions in params_metadata.json: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -44,7 +44,8 @@ class OptionControlSP(ItemAction):
|
||||
self.current_value = int(key)
|
||||
break
|
||||
else:
|
||||
self.current_value = int(self.params.get(self.param_key, return_default=True))
|
||||
value = self.params.get(self.param_key, return_default=True)
|
||||
self.current_value = int(float(value) * 100.0) if self.use_float_scaling else int(value)
|
||||
|
||||
# Initialize font and button styles
|
||||
self._font = gui_app.font(FontWeight.MEDIUM)
|
||||
|
||||
21
uv.lock
generated
21
uv.lock
generated
@@ -356,13 +356,12 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "dearpygui"
|
||||
version = "2.1.1"
|
||||
version = "2.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/79/41/2146e8d03d28b5a66d5282beb26ffd9ab68a729a29d31e2fe91809271bf5/dearpygui-2.1.1-cp312-cp312-macosx_10_6_x86_64.whl", hash = "sha256:238aea7b4be7376f564dae8edd563b280ec1483a03786022969938507691e017", size = 2101529, upload-time = "2025-11-14T14:47:39.646Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/c5/fcc37ef834fe225241aa4f18d77aaa2903134f283077978d65a901c624c6/dearpygui-2.1.1-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:c27ca6ecd4913555b717f3bb341c0b6a27d6c9fdc9932f0b3c31ae2ef893ae35", size = 1895555, upload-time = "2025-11-14T14:47:48.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/66/19f454ba02d5f03a847cc1dfee4a849cd2307d97add5ba26fecdca318adb/dearpygui-2.1.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:8c071e9c165d89217bdcdaf769c6069252fcaee50bf369489add524107932273", size = 2641509, upload-time = "2025-11-14T14:47:54.581Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/58/d01538556103d544a5a5b4cbcb00646ff92d8a97f0a6283a56bede4307c8/dearpygui-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f2291313d2035f8a4108e13f60d8c1a0e7c19af7554a7739a3fd15b3d5af8f7", size = 1808971, upload-time = "2025-11-14T14:47:28.15Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/c8/b4afdac89c7bf458513366af3143f7383d7b09721637989c95788d93e24c/dearpygui-2.2-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:34ceae1ca1b65444e49012d6851312e44f08713da1b8cc0150cf41f1c207af9c", size = 1931443, upload-time = "2026-02-17T14:21:54.394Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/93/a2d083b2e0edb095be815662cc41e40cf9ea7b65d6323e47bb30df7eb284/dearpygui-2.2-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:e1fae9ae59fec0e41773df64c80311a6ba67696219dde5506a2a4c013e8bcdfa", size = 2592645, upload-time = "2026-02-17T14:22:02.869Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/ba/eae13acaad479f522db853e8b1ccd695a7bc8da2b9685c1d70a3b318df89/dearpygui-2.2-cp312-cp312-win_amd64.whl", hash = "sha256:7d399543b5a26ab6426ef3bbd776e55520b491b3e169647bde5e6b2de3701b35", size = 1830531, upload-time = "2026-02-17T14:21:43.386Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -415,11 +414,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.24.2"
|
||||
version = "3.24.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/02/a8/dae62680be63cbb3ff87cfa2f51cf766269514ea5488479d42fec5aa6f3a/filelock-3.24.2.tar.gz", hash = "sha256:c22803117490f156e59fafce621f0550a7a853e2bbf4f87f112b11d469b6c81b", size = 37601, upload-time = "2026-02-16T02:50:45.614Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/73/92/a8e2479937ff39185d20dd6a851c1a63e55849e447a55e798cc2e1f49c65/filelock-3.24.3.tar.gz", hash = "sha256:011a5644dc937c22699943ebbfc46e969cdde3e171470a6e40b9533e5a72affa", size = 37935, upload-time = "2026-02-19T00:48:20.543Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/04/a94ebfb4eaaa08db56725a40de2887e95de4e8641b9e902c311bfa00aa39/filelock-3.24.2-py3-none-any.whl", hash = "sha256:667d7dc0b7d1e1064dd5f8f8e80bdac157a6482e8d2e02cd16fd3b6b33bd6556", size = 24152, upload-time = "2026-02-16T02:50:44Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/0f/5d0c71a1aefeb08efff26272149e07ab922b64f46c63363756224bd6872e/filelock-3.24.3-py3-none-any.whl", hash = "sha256:426e9a4660391f7f8a810d71b0555bce9008b0a1cc342ab1f6947d37639e002d", size = 24331, upload-time = "2026-02-19T00:48:18.465Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4089,15 +4088,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "2.52.0"
|
||||
version = "2.53.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/59/eb/1b497650eb564701f9a7b8a95c51b2abe9347ed2c0b290ba78f027ebe4ea/sentry_sdk-2.52.0.tar.gz", hash = "sha256:fa0bec872cfec0302970b2996825723d67390cdd5f0229fb9efed93bd5384899", size = 410273, upload-time = "2026-02-04T15:03:54.706Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d3/06/66c8b705179bc54087845f28fd1b72f83751b6e9a195628e2e9af9926505/sentry_sdk-2.53.0.tar.gz", hash = "sha256:6520ef2c4acd823f28efc55e43eb6ce2e6d9f954a95a3aa96b6fd14871e92b77", size = 412369, upload-time = "2026-02-16T11:11:14.743Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/63/2c6daf59d86b1c30600bff679d039f57fd1932af82c43c0bde1cbc55e8d4/sentry_sdk-2.52.0-py2.py3-none-any.whl", hash = "sha256:931c8f86169fc6f2752cb5c4e6480f0d516112e78750c312e081ababecbaf2ed", size = 435547, upload-time = "2026-02-04T15:03:51.567Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/d4/2fdf854bc3b9c7f55219678f812600a20a138af2dd847d99004994eada8f/sentry_sdk-2.53.0-py2.py3-none-any.whl", hash = "sha256:46e1ed8d84355ae54406c924f6b290c3d61f4048625989a723fd622aab838899", size = 437908, upload-time = "2026-02-16T11:11:13.227Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user