diff --git a/common/libcommon.a b/common/libcommon.a index 3b5d13e0..adac862e 100644 Binary files a/common/libcommon.a and b/common/libcommon.a differ diff --git a/common/params_keys.h b/common/params_keys.h index 31629ec7..f60a84a9 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -250,6 +250,7 @@ inline static std::unordered_map keys = { {"Fahrenheit", {PERSISTENT, BOOL, "0", "0", 3}}, {"FlashPanda", {CLEAR_ON_MANAGER_START, BOOL, "0", "0"}}, {"GMPedalLongitudinal", {PERSISTENT, BOOL, "1", "1", 2}}, + {"LongPitch", {PERSISTENT, BOOL, "1", "0", 2}}, {"RemoteStartBootsComma", {PERSISTENT, BOOL, "0", "0"}}, {"RemapCancelToDistance", {PERSISTENT, BOOL, "0", "0"}}, {"ForceAutoTune", {PERSISTENT, BOOL, "0", "0", 3}}, diff --git a/common/params_pyx.so b/common/params_pyx.so index 1cb9b4a8..ddfd9e62 100755 Binary files a/common/params_pyx.so and b/common/params_pyx.so differ diff --git a/frogpilot/common/frogpilot_variables.py b/frogpilot/common/frogpilot_variables.py index f0217989..fa383a62 100644 --- a/frogpilot/common/frogpilot_variables.py +++ b/frogpilot/common/frogpilot_variables.py @@ -878,6 +878,10 @@ class FrogPilotVariables: "GMPedalLongitudinal", condition=toggle.car_make == "gm" and toggle.has_pedal, ) + toggle.long_pitch = self.get_value( + "LongPitch", + condition=toggle.openpilot_longitudinal and toggle.car_make == "gm", + ) toggle.remote_start_boots_comma = self.get_value("RemoteStartBootsComma", condition=toggle.car_make == "gm") toggle.remap_cancel_to_distance = self.get_value( "RemapCancelToDistance", diff --git a/frogpilot/controls/frogpilot_planner.py b/frogpilot/controls/frogpilot_planner.py index 3fd5a5e4..94fc7fe2 100644 --- a/frogpilot/controls/frogpilot_planner.py +++ b/frogpilot/controls/frogpilot_planner.py @@ -8,7 +8,7 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.gps import get_gps_location_service from openpilot.common.params import Params from openpilot.common.realtime import DT_MDL -from openpilot.selfdrive.car.cruise import V_CRUISE_MAX +from openpilot.selfdrive.car.cruise import V_CRUISE_MAX, V_CRUISE_UNSET from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import A_CHANGE_COST, DANGER_ZONE_COST, J_EGO_COST, STOP_DISTANCE from openpilot.frogpilot.common.frogpilot_utilities import calculate_lane_width, calculate_road_curvature @@ -64,7 +64,10 @@ class FrogPilotPlanner: controls_enabled = sm["selfdriveState"].enabled planner_active = controls_enabled or long_control_active - v_cruise = min(sm["carState"].vCruise, V_CRUISE_MAX) * CV.KPH_TO_MS + v_cruise_kph = sm["carState"].vCruise + if 0 < v_cruise_kph < V_CRUISE_UNSET and frogpilot_toggles.set_speed_offset > 0: + v_cruise_kph += frogpilot_toggles.set_speed_offset + v_cruise = min(v_cruise_kph, V_CRUISE_MAX) * CV.KPH_TO_MS v_ego = max(sm["carState"].vEgo, 0) if planner_active: diff --git a/frogpilot/system/the_pond/assets/components/tools/device_settings_layout.json b/frogpilot/system/the_pond/assets/components/tools/device_settings_layout.json index e282e17b..5dae5329 100644 --- a/frogpilot/system/the_pond/assets/components/tools/device_settings_layout.json +++ b/frogpilot/system/the_pond/assets/components/tools/device_settings_layout.json @@ -1688,6 +1688,13 @@ "data_type": "bool", "ui_type": "toggle" }, + { + "key": "LongPitch", + "label": "Smooth Pedal Response on Hills", + "description": "Smoothen acceleration and braking when driving downhill/uphill.", + "data_type": "bool", + "ui_type": "toggle" + }, { "key": "RemoteStartBootsComma", "label": "Remote Start Boots comma", diff --git a/frogpilot/ui/qt/offroad/vehicle_settings.cc b/frogpilot/ui/qt/offroad/vehicle_settings.cc index ceff5a91..78c25594 100644 --- a/frogpilot/ui/qt/offroad/vehicle_settings.cc +++ b/frogpilot/ui/qt/offroad/vehicle_settings.cc @@ -175,6 +175,7 @@ FrogPilotVehiclesPanel::FrogPilotVehiclesPanel(FrogPilotSettingsWindow *parent, std::vector> vehicleToggles { {"GMToggles", tr("General Motors Settings"), tr("FrogPilot features for General Motors vehicles."), ""}, {"GMPedalLongitudinal", tr("Use Pedal For Longitudinal"), tr("Use the pedal interceptor for full longitudinal control on supported GM vehicles."), ""}, + {"LongPitch", tr("Smooth Pedal Response on Hills"), tr("Smoothen acceleration and braking when driving downhill/uphill."), ""}, {"RemoteStartBootsComma", tr("Remote Start Boots comma"), tr("Use the remote-start GM panda firmware at boot.

Required for GM remote-start startup signal behavior."), ""}, {"RemapCancelToDistance", tr("Remap Cancel To Distance"), tr("On pedal-interceptor Bolts, remap the steering-wheel CANCEL button to distance/personality input."), ""}, {"VoltSNG", tr("Stop-and-Go Hack"), tr("Force stop-and-go on the 2017 Chevy Volt."), ""}, diff --git a/frogpilot/ui/qt/offroad/vehicle_settings.h b/frogpilot/ui/qt/offroad/vehicle_settings.h index 4f52fb12..2f584ef9 100644 --- a/frogpilot/ui/qt/offroad/vehicle_settings.h +++ b/frogpilot/ui/qt/offroad/vehicle_settings.h @@ -23,9 +23,9 @@ private: std::map toggles; - QSet gmKeys = {"GMPedalLongitudinal", "RemoteStartBootsComma", "RemapCancelToDistance", "VoltSNG"}; + QSet gmKeys = {"GMPedalLongitudinal", "LongPitch", "RemoteStartBootsComma", "RemapCancelToDistance", "VoltSNG"}; QSet hkgKeys = {"TacoTuneHacks"}; - QSet longitudinalKeys = {"FrogsGoMoosTweak", "RemapCancelToDistance", "SNGHack", "VoltSNG"}; + QSet longitudinalKeys = {"FrogsGoMoosTweak", "LongPitch", "RemapCancelToDistance", "SNGHack", "VoltSNG"}; QSet subaruKeys = {"SubaruSNG"}; QSet toyotaKeys = {"ClusterOffset", "FrogsGoMoosTweak", "LockDoorsTimer", "SNGHack", "ToyotaDoors"}; QSet vehicleInfoKeys = {"BlindSpotSupport", "HardwareDetected", "OpenpilotLongitudinal", "PedalSupport", "RadarSupport", "SDSUSupport", "SNGSupport"}; diff --git a/opendbc_repo/opendbc/car/gm/carcontroller.py b/opendbc_repo/opendbc/car/gm/carcontroller.py index 54d71829..89c0dabf 100644 --- a/opendbc_repo/opendbc/car/gm/carcontroller.py +++ b/opendbc_repo/opendbc/car/gm/carcontroller.py @@ -370,8 +370,10 @@ class CarController(CarControllerBase): self.apply_gas = self.params.INACTIVE_REGEN self.apply_brake = int(min(-100 * stop_accel, self.params.MAX_BRAKE)) else: + long_pitch_enabled = bool(getattr(frogpilot_toggles, "long_pitch", True)) + if self.is_volt: - if len(CC.orientationNED) == 3 and CS.out.vEgo > self.CP.vEgoStopping: + if long_pitch_enabled and len(CC.orientationNED) == 3 and CS.out.vEgo > self.CP.vEgoStopping: volt_pitch_accel = math.sin(CC.orientationNED[1]) * ACCELERATION_DUE_TO_GRAVITY else: volt_pitch_accel = 0.0 @@ -395,7 +397,7 @@ class CarController(CarControllerBase): if self.apply_brake > 0: self.apply_gas = self.params.INACTIVE_REGEN else: - if len(CC.orientationNED) == 3 and CS.out.vEgo > self.CP.vEgoStopping: + if long_pitch_enabled and len(CC.orientationNED) == 3 and CS.out.vEgo > self.CP.vEgoStopping: accel_due_to_pitch = math.sin(CC.orientationNED[1]) * ACCELERATION_DUE_TO_GRAVITY else: accel_due_to_pitch = 0.0 @@ -424,7 +426,12 @@ class CarController(CarControllerBase): if self.CP.carFingerprint in CC_ONLY_CAR: # gas interceptor only used for full long control on cars without ACC - interceptor_gas_cmd, press_regen_paddle = self.calc_pedal_command(actuators.accel, CC.longActive, CS.out.vEgo) + pedal_accel_cmd = actuators.accel + if (long_pitch_enabled and + len(CC.orientationNED) == 3 and CS.out.vEgo > self.CP.vEgoStopping): + pedal_accel_cmd += math.sin(CC.orientationNED[1]) * ACCELERATION_DUE_TO_GRAVITY + + interceptor_gas_cmd, press_regen_paddle = self.calc_pedal_command(pedal_accel_cmd, CC.longActive, CS.out.vEgo) if self.CP.enableGasInterceptorDEPRECATED and self.apply_gas > self.params.INACTIVE_REGEN and CS.out.cruiseState.standstill: interceptor_gas_cmd = self.params.SNG_INTERCEPTOR_GAS diff --git a/panda/board/obj/body_h7.bin.signed b/panda/board/obj/body_h7.bin.signed index 5e298952..9fc57018 100644 Binary files a/panda/board/obj/body_h7.bin.signed and b/panda/board/obj/body_h7.bin.signed differ diff --git a/panda/board/obj/body_h7/bootstub.elf b/panda/board/obj/body_h7/bootstub.elf index 4f8d6c18..70bfe2e5 100755 Binary files a/panda/board/obj/body_h7/bootstub.elf and b/panda/board/obj/body_h7/bootstub.elf differ diff --git a/panda/board/obj/body_h7/main.bin b/panda/board/obj/body_h7/main.bin index 26b07d2c..7059f4fc 100755 Binary files a/panda/board/obj/body_h7/main.bin and b/panda/board/obj/body_h7/main.bin differ diff --git a/panda/board/obj/body_h7/main.elf b/panda/board/obj/body_h7/main.elf index 2a750ff4..5ce712e4 100755 Binary files a/panda/board/obj/body_h7/main.elf and b/panda/board/obj/body_h7/main.elf differ diff --git a/panda/board/obj/bootstub.body_h7.bin b/panda/board/obj/bootstub.body_h7.bin index 9728146a..3525569a 100755 Binary files a/panda/board/obj/bootstub.body_h7.bin and b/panda/board/obj/bootstub.body_h7.bin differ diff --git a/panda/board/obj/bootstub.panda.bin b/panda/board/obj/bootstub.panda.bin index e99c1b21..e7929e1b 100755 Binary files a/panda/board/obj/bootstub.panda.bin and b/panda/board/obj/bootstub.panda.bin differ diff --git a/panda/board/obj/bootstub.panda_h7.bin b/panda/board/obj/bootstub.panda_h7.bin index a3a38a96..180c6273 100755 Binary files a/panda/board/obj/bootstub.panda_h7.bin and b/panda/board/obj/bootstub.panda_h7.bin differ diff --git a/panda/board/obj/bootstub.panda_h7_remote.bin b/panda/board/obj/bootstub.panda_h7_remote.bin index a3a38a96..180c6273 100755 Binary files a/panda/board/obj/bootstub.panda_h7_remote.bin and b/panda/board/obj/bootstub.panda_h7_remote.bin differ diff --git a/panda/board/obj/bootstub.panda_jungle_h7.bin b/panda/board/obj/bootstub.panda_jungle_h7.bin index 69c2ebe4..f785c32a 100755 Binary files a/panda/board/obj/bootstub.panda_jungle_h7.bin and b/panda/board/obj/bootstub.panda_jungle_h7.bin differ diff --git a/panda/board/obj/bootstub.panda_remote.bin b/panda/board/obj/bootstub.panda_remote.bin index e99c1b21..e7929e1b 100755 Binary files a/panda/board/obj/bootstub.panda_remote.bin and b/panda/board/obj/bootstub.panda_remote.bin differ diff --git a/panda/board/obj/gitversion.h b/panda/board/obj/gitversion.h index 7b3ec8b8..074a85a8 100644 --- a/panda/board/obj/gitversion.h +++ b/panda/board/obj/gitversion.h @@ -1,2 +1,2 @@ extern const uint8_t gitversion[19]; -const uint8_t gitversion[19] = "DEV-e9100341-DEBUG"; +const uint8_t gitversion[19] = "DEV-6a639c86-DEBUG"; diff --git a/panda/board/obj/panda.bin.signed b/panda/board/obj/panda.bin.signed index 7ee3ea9b..12190579 100644 Binary files a/panda/board/obj/panda.bin.signed and b/panda/board/obj/panda.bin.signed differ diff --git a/panda/board/obj/panda/bootstub.elf b/panda/board/obj/panda/bootstub.elf index eda01ee4..652b6b96 100755 Binary files a/panda/board/obj/panda/bootstub.elf and b/panda/board/obj/panda/bootstub.elf differ diff --git a/panda/board/obj/panda/main.bin b/panda/board/obj/panda/main.bin index 626e2974..74d5a7bb 100755 Binary files a/panda/board/obj/panda/main.bin and b/panda/board/obj/panda/main.bin differ diff --git a/panda/board/obj/panda/main.elf b/panda/board/obj/panda/main.elf index f4dcd0cf..4758df03 100755 Binary files a/panda/board/obj/panda/main.elf and b/panda/board/obj/panda/main.elf differ diff --git a/panda/board/obj/panda_h7.bin.signed b/panda/board/obj/panda_h7.bin.signed index 0d21ac59..659eaa9c 100644 Binary files a/panda/board/obj/panda_h7.bin.signed and b/panda/board/obj/panda_h7.bin.signed differ diff --git a/panda/board/obj/panda_h7/bootstub.elf b/panda/board/obj/panda_h7/bootstub.elf index 0f281ad6..a59f31b1 100755 Binary files a/panda/board/obj/panda_h7/bootstub.elf and b/panda/board/obj/panda_h7/bootstub.elf differ diff --git a/panda/board/obj/panda_h7/main.bin b/panda/board/obj/panda_h7/main.bin index 955c295d..723750da 100755 Binary files a/panda/board/obj/panda_h7/main.bin and b/panda/board/obj/panda_h7/main.bin differ diff --git a/panda/board/obj/panda_h7/main.elf b/panda/board/obj/panda_h7/main.elf index a6538294..08730e27 100755 Binary files a/panda/board/obj/panda_h7/main.elf and b/panda/board/obj/panda_h7/main.elf differ diff --git a/panda/board/obj/panda_h7_remote.bin.signed b/panda/board/obj/panda_h7_remote.bin.signed index 83e6d968..936615ab 100644 Binary files a/panda/board/obj/panda_h7_remote.bin.signed and b/panda/board/obj/panda_h7_remote.bin.signed differ diff --git a/panda/board/obj/panda_h7_remote/bootstub.elf b/panda/board/obj/panda_h7_remote/bootstub.elf index d966ebb8..50782234 100755 Binary files a/panda/board/obj/panda_h7_remote/bootstub.elf and b/panda/board/obj/panda_h7_remote/bootstub.elf differ diff --git a/panda/board/obj/panda_h7_remote/main.bin b/panda/board/obj/panda_h7_remote/main.bin index 9512d19d..3830dc1d 100755 Binary files a/panda/board/obj/panda_h7_remote/main.bin and b/panda/board/obj/panda_h7_remote/main.bin differ diff --git a/panda/board/obj/panda_h7_remote/main.elf b/panda/board/obj/panda_h7_remote/main.elf index 4672806d..82e1391b 100755 Binary files a/panda/board/obj/panda_h7_remote/main.elf and b/panda/board/obj/panda_h7_remote/main.elf differ diff --git a/panda/board/obj/panda_jungle_h7.bin.signed b/panda/board/obj/panda_jungle_h7.bin.signed index 62f0ee6d..6286fded 100644 Binary files a/panda/board/obj/panda_jungle_h7.bin.signed and b/panda/board/obj/panda_jungle_h7.bin.signed differ diff --git a/panda/board/obj/panda_jungle_h7/bootstub.elf b/panda/board/obj/panda_jungle_h7/bootstub.elf index 60d80feb..e185e9d8 100755 Binary files a/panda/board/obj/panda_jungle_h7/bootstub.elf and b/panda/board/obj/panda_jungle_h7/bootstub.elf differ diff --git a/panda/board/obj/panda_jungle_h7/main.bin b/panda/board/obj/panda_jungle_h7/main.bin index f2c94c5d..6b438ea5 100755 Binary files a/panda/board/obj/panda_jungle_h7/main.bin and b/panda/board/obj/panda_jungle_h7/main.bin differ diff --git a/panda/board/obj/panda_jungle_h7/main.elf b/panda/board/obj/panda_jungle_h7/main.elf index c3aea28c..2460d22c 100755 Binary files a/panda/board/obj/panda_jungle_h7/main.elf and b/panda/board/obj/panda_jungle_h7/main.elf differ diff --git a/panda/board/obj/panda_remote.bin.signed b/panda/board/obj/panda_remote.bin.signed index 59bfe43b..c8c33bf9 100644 Binary files a/panda/board/obj/panda_remote.bin.signed and b/panda/board/obj/panda_remote.bin.signed differ diff --git a/panda/board/obj/panda_remote/bootstub.elf b/panda/board/obj/panda_remote/bootstub.elf index de0323ef..25dbad77 100755 Binary files a/panda/board/obj/panda_remote/bootstub.elf and b/panda/board/obj/panda_remote/bootstub.elf differ diff --git a/panda/board/obj/panda_remote/main.bin b/panda/board/obj/panda_remote/main.bin index ab9120c4..44535f30 100755 Binary files a/panda/board/obj/panda_remote/main.bin and b/panda/board/obj/panda_remote/main.bin differ diff --git a/panda/board/obj/panda_remote/main.elf b/panda/board/obj/panda_remote/main.elf index 5b1dc81d..93371214 100755 Binary files a/panda/board/obj/panda_remote/main.elf and b/panda/board/obj/panda_remote/main.elf differ diff --git a/panda/board/obj/version b/panda/board/obj/version index b307aeec..76455bbe 100644 --- a/panda/board/obj/version +++ b/panda/board/obj/version @@ -1 +1 @@ -DEV-e9100341-DEBUG \ No newline at end of file +DEV-6a639c86-DEBUG \ No newline at end of file diff --git a/selfdrive/car/cruise.py b/selfdrive/car/cruise.py index 27eb73bf..a7e828e6 100644 --- a/selfdrive/car/cruise.py +++ b/selfdrive/car/cruise.py @@ -112,12 +112,6 @@ class VCruiseHelper: else: self.v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type] - # FrogPilot variables - if long_press and frogpilot_toggles.set_speed_offset > 0: - self.v_cruise_kph += frogpilot_toggles.set_speed_offset - if button_type == ButtonType.decelCruise: - self.v_cruise_kph -= v_cruise_delta - # If set is pressed while overriding, clip cruise speed to minimum of vEgo if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise): self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH) diff --git a/selfdrive/pandad/pandad b/selfdrive/pandad/pandad index e72526a8..acfc5148 100755 Binary files a/selfdrive/pandad/pandad and b/selfdrive/pandad/pandad differ diff --git a/selfdrive/ui/layouts/settings/starpilot/vehicle.py b/selfdrive/ui/layouts/settings/starpilot/vehicle.py index 1a4d5987..af1122bc 100644 --- a/selfdrive/ui/layouts/settings/starpilot/vehicle.py +++ b/selfdrive/ui/layouts/settings/starpilot/vehicle.py @@ -251,6 +251,14 @@ class StarPilotGMVehicleLayout(StarPilotPanel): "color": "#FFC40D", "key": "GMPedalLongitudinal", }, + { + "title": tr_noop("Smooth Pedal on Hills"), + "type": "toggle", + "get_state": lambda: self._params.get_bool("LongPitch"), + "set_state": lambda s: self._params.put_bool("LongPitch", s), + "color": "#FFC40D", + "key": "LongPitch", + }, { "title": tr_noop("Remote Start Panda"), "type": "toggle", diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 565fef5a..f7cfeba4 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -238,8 +238,8 @@ class NetworkInfoPage(NavWidget): self._connect_btn.set_label("connecting...") self._connect_btn.set_enabled(False) elif self._network.is_connected: - self._connect_btn.set_label("connected") - self._connect_btn.set_enabled(False) + self._connect_btn.set_label("disconnect") + self._connect_btn.set_enabled(True) elif self._network.security_type == SecurityType.UNSUPPORTED: self._connect_btn.set_label("connect") self._connect_btn.set_enabled(False) @@ -409,6 +409,10 @@ class WifiUIMici(BigMultiOptionDialog): cloudlog.warning(f"Trying to connect to unknown network: {ssid}") return + if network.is_connected: + self._wifi_manager.disconnect_network(network.ssid) + return + if network.is_saved: self._connecting = network.ssid self._wifi_manager.activate_connection(network.ssid) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 1575a2f8..83ef6656 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -3,6 +3,7 @@ import numpy as np import pyray as rl from cereal import messaging, car, log from msgq.visionipc import VisionStreamType +from openpilot.common.constants import CV from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.mici.onroad import SIDE_PANEL_WIDTH from openpilot.selfdrive.ui.mici.onroad.alert_renderer import AlertRenderer @@ -178,6 +179,93 @@ class ExperimentalModeBanner(Widget): self._label.render(banner_rect) +class MinSteerSpeedBanner(Widget): + """One-shot-per-drive banner shown for the full first below-min-steer interval.""" + + def __init__(self): + super().__init__() + self._shown_this_drive = False + self._showing_interval = False + self._has_been_above_min = False + self._was_under_min = False + self._last_started_frame = -1 + self._label = UnifiedLabel( + "", + 34, + FontWeight.BOLD, + text_color=rl.WHITE, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, + ) + + def _reset(self): + self._shown_this_drive = False + self._showing_interval = False + self._has_been_above_min = False + self._was_under_min = False + + @staticmethod + def _get_message(min_steer_speed: float) -> str: + speed_units = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH + speed = int(round(min_steer_speed * speed_units)) + unit = "km/h" if ui_state.is_metric else "mph" + return f"Steer Unavailable Under {speed} {unit}" + + def _update_state(self): + if not ui_state.started: + self._last_started_frame = -1 + self._reset() + return + + if ui_state.started_frame != self._last_started_frame: + self._last_started_frame = ui_state.started_frame + self._reset() + + sm = ui_state.sm + if sm.recv_frame["carParams"] < ui_state.started_frame or sm.recv_frame["carState"] < ui_state.started_frame: + return + + min_steer_speed = float(sm["carParams"].minSteerSpeed) + if min_steer_speed <= 0: + self._showing_interval = False + self._was_under_min = False + return + + under_min = float(sm["carState"].vEgo) < min_steer_speed + if not under_min: + self._has_been_above_min = True + + crossed_below = under_min and not self._was_under_min + if (not self._shown_this_drive) and crossed_below and self._has_been_above_min: + self._showing_interval = True + self._shown_this_drive = True + + if self._showing_interval and not under_min: + self._showing_interval = False + + self._was_under_min = under_min + if self._showing_interval: + self._label.set_text(self._get_message(min_steer_speed)) + + def _render(self, rect): + self._update_state() + if not self._showing_interval: + return + + banner_width = min(rect.width - 120, 760) + banner_height = 72 + banner_rect = rl.Rectangle( + rect.x + (rect.width - banner_width) / 2, + rect.y + 22, + banner_width, + banner_height, + ) + + rl.draw_rectangle_rounded(banner_rect, 0.3, 12, rl.Color(0, 0, 0, 185)) + rl.draw_rectangle_rounded_lines_ex(banner_rect, 0.3, 12, 4, rl.Color(218, 111, 37, 255)) + self._label.render(banner_rect) + + class AugmentedRoadView(CameraView): def __init__(self, bookmark_callback=None, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD): super().__init__("camerad", stream_type) @@ -204,6 +292,7 @@ class AugmentedRoadView(CameraView): self._driver_state_renderer = DriverStateRenderer() self._confidence_ball = ConfidenceBall() self._experimental_mode_banner = ExperimentalModeBanner() + self._min_steer_speed_banner = MinSteerSpeedBanner() self._offroad_label = UnifiedLabel("start the car to\nuse openpilot", 54, FontWeight.DISPLAY, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, @@ -307,6 +396,8 @@ class AugmentedRoadView(CameraView): self._hud_renderer.render(self._content_rect) if (not in_reverse) and alert_to_render is None: self._experimental_mode_banner.render(self._content_rect) + if not in_reverse: + self._min_steer_speed_banner.render(self._content_rect) # End clipping region rl.end_scissor_mode() diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index 1f202141..f5ae56ef 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -3,6 +3,7 @@ import numpy as np import pyray as rl from cereal import log, messaging from msgq.visionipc import VisionStreamType +from openpilot.common.constants import CV from openpilot.selfdrive.ui import UI_BORDER_SIZE from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus from openpilot.selfdrive.ui.onroad.alert_renderer import AlertRenderer @@ -31,6 +32,95 @@ ROAD_CAM_MIN_SPEED = 15.0 # m/s (34 mph) INF_POINT = np.array([1000.0, 0.0, 0.0]) +class MinSteerSpeedBanner: + """One-shot-per-drive banner that stays visible for the first below-min-steer interval.""" + + def __init__(self): + self._shown_this_drive = False + self._showing_interval = False + self._has_been_above_min = False + self._was_under_min = False + self._last_started_frame = -1 + + def _reset(self): + self._shown_this_drive = False + self._showing_interval = False + self._has_been_above_min = False + self._was_under_min = False + + def _get_message(self, min_steer_speed: float) -> str: + speed_units = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH + speed = int(round(min_steer_speed * speed_units)) + unit = "km/h" if ui_state.is_metric else "mph" + return f"Steer Unavailable Under {speed} {unit}" + + def _update_state(self): + if not ui_state.started: + self._last_started_frame = -1 + self._reset() + return + + if ui_state.started_frame != self._last_started_frame: + self._last_started_frame = ui_state.started_frame + self._reset() + + sm = ui_state.sm + if sm.recv_frame["carParams"] < ui_state.started_frame or sm.recv_frame["carState"] < ui_state.started_frame: + return + + min_steer_speed = float(sm["carParams"].minSteerSpeed) + if min_steer_speed <= 0: + self._showing_interval = False + self._was_under_min = False + return + + under_min = float(sm["carState"].vEgo) < min_steer_speed + if not under_min: + self._has_been_above_min = True + + crossed_below = under_min and not self._was_under_min + if (not self._shown_this_drive) and crossed_below and self._has_been_above_min: + self._showing_interval = True + self._shown_this_drive = True + + if self._showing_interval and not under_min: + self._showing_interval = False + + self._was_under_min = under_min + + def render(self, rect: rl.Rectangle): + self._update_state() + + if not self._showing_interval: + return + + min_steer_speed = float(ui_state.sm["carParams"].minSteerSpeed) + if min_steer_speed <= 0: + return + + banner_width = min(rect.width - 120, 760) + banner_height = 84 + banner_rect = rl.Rectangle( + rect.x + (rect.width - banner_width) / 2, + rect.y + 24, + banner_width, + banner_height, + ) + + rl.draw_rectangle_rounded(banner_rect, 0.3, 12, rl.Color(0, 0, 0, 185)) + rl.draw_rectangle_rounded_lines_ex(banner_rect, 0.3, 12, 4, rl.Color(218, 111, 37, 255)) + + text = self._get_message(min_steer_speed) + font = gui_app.font() + font_size = 44 + text_size = rl.measure_text_ex(font, text, font_size, 0) + text_pos = rl.Vector2( + banner_rect.x + (banner_rect.width - text_size.x) / 2, + banner_rect.y + (banner_rect.height - text_size.y) / 2, + ) + rl.draw_text_ex(font, text, text_pos, font_size, 0, rl.WHITE) + + class AugmentedRoadView(CameraView): def __init__(self, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD): super().__init__("camerad", stream_type) @@ -48,6 +138,7 @@ class AugmentedRoadView(CameraView): self._hud_renderer = HudRenderer() self.alert_renderer = AlertRenderer() self.driver_state_renderer = DriverStateRenderer() + self._min_steer_speed_banner = MinSteerSpeedBanner() # debug self._pm = messaging.PubMaster(['uiDebug']) @@ -88,6 +179,7 @@ class AugmentedRoadView(CameraView): self._hud_renderer.render(self._content_rect) self.alert_renderer.render(self._content_rect) self.driver_state_renderer.render(self._content_rect) + self._min_steer_speed_banner.render(self._content_rect) # Custom UI extension point - add custom overlays here # Use self._content_rect for positioning within camera bounds diff --git a/selfdrive/ui/ui b/selfdrive/ui/ui index caa21aea..7cbb5242 100755 Binary files a/selfdrive/ui/ui and b/selfdrive/ui/ui differ diff --git a/system/athena/athenad.py b/system/athena/athenad.py index e4f62751..a74ede13 100755 --- a/system/athena/athenad.py +++ b/system/athena/athenad.py @@ -134,6 +134,7 @@ log_recv_queue: Queue[str] = queue.Queue() cancelled_uploads: set[str] = set() cur_upload_items: dict[int, UploadItem | None] = {} +params_store = Params() def strip_zst_extension(fn: str) -> str: @@ -146,6 +147,10 @@ class AbortTransferException(Exception): pass +def always_allow_uploads() -> bool: + return params_store.get_bool("AlwaysAllowUploads") + + class UploadQueueCache: @staticmethod @@ -249,7 +254,7 @@ def cb(sm, item, tid, end_event: threading.Event, sz: int, cur: int) -> None: if not item.allow_cellular: if (time.monotonic() - sm.recv_time['deviceState']) > DEVICE_STATE_UPDATE_INTERVAL: sm.update(0) - if sm['deviceState'].networkMetered: + if sm['deviceState'].networkMetered and not always_allow_uploads(): raise AbortTransferException if end_event.is_set(): @@ -282,7 +287,7 @@ def upload_handler(end_event: threading.Event) -> None: sm.update(0) metered = sm['deviceState'].networkMetered network_type = sm['deviceState'].networkType.raw - if metered and (not item.allow_cellular): + if metered and (not item.allow_cellular) and not always_allow_uploads(): retry_upload(tid, end_event, False) continue diff --git a/system/camerad/camerad b/system/camerad/camerad index 2446c0e6..29777620 100755 Binary files a/system/camerad/camerad and b/system/camerad/camerad differ diff --git a/system/loggerd/bootlog b/system/loggerd/bootlog index e5ee78ed..be5da2c8 100755 Binary files a/system/loggerd/bootlog and b/system/loggerd/bootlog differ diff --git a/system/loggerd/encoderd b/system/loggerd/encoderd index 4dea6c13..be0a7528 100755 Binary files a/system/loggerd/encoderd and b/system/loggerd/encoderd differ diff --git a/system/loggerd/loggerd b/system/loggerd/loggerd index bd2da731..a63c4372 100755 Binary files a/system/loggerd/loggerd and b/system/loggerd/loggerd differ diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index 1588dddd..827dc812 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -251,6 +251,7 @@ def main(exit_event: threading.Event | None = None) -> None: backoff = 0.1 while not exit_event.is_set(): sm.update(0) + always_allow_uploads = params.get_bool("AlwaysAllowUploads") offroad = params.get_bool("IsOffroad") network_type = sm['deviceState'].networkType if not force_wifi else NetworkType.wifi if network_type == NetworkType.none: @@ -258,7 +259,7 @@ def main(exit_event: threading.Event | None = None) -> None: time.sleep(60 if offroad else 5) continue - success = uploader.step(sm['deviceState'].networkType.raw, sm['deviceState'].networkMetered) + success = uploader.step(sm['deviceState'].networkType.raw, sm['deviceState'].networkMetered and not always_allow_uploads) if success is None: backoff = 60 if offroad else 5 elif success: diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 6d1acb06..0ca84629 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -722,11 +722,86 @@ class WifiManager: return def is_tethering_active(self) -> bool: + if self._fake_networking: + for network in self._networks: + if network.is_connected: + return bool(network.ssid == self._tethering_ssid) + return False + + if self._nmcli_networking: + try: + active = subprocess.run( + ["nmcli", "-t", "-f", "NAME,TYPE,802-11-wireless.ssid", "connection", "show", "--active"], + check=False, capture_output=True, text=True, + ) + for line in active.stdout.splitlines(): + parts = self._parse_nmcli_line(line) + if len(parts) < 3 or parts[1] != "802-11-wireless": + continue + if _canonicalize_ssid(parts[2]) == _canonicalize_ssid(self._tethering_ssid): + return True + except Exception as e: + cloudlog.warning(f"nmcli tethering state lookup failed: {e}") + return False + + if not self._dbus_available: + return False + + def decode_ssid(value: Any) -> str: + if isinstance(value, bytes): + return value.decode("utf-8", "replace") + if isinstance(value, str): + return value + if isinstance(value, list): + try: + return bytes(value).decode("utf-8", "replace") + except Exception: + return str(value) + return str(value) + + try: + for conn_path in self._get_active_connections(): + conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) + conn_type = self._router_main.send_and_get_reply(Properties(conn_addr).get('Type')).body[0][1] + if conn_type != '802-11-wireless': + continue + + settings_conn_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('Connection')).body[0][1] + if settings_conn_path != "/": + settings = self._get_connection_settings(settings_conn_path) + ssid_value = settings.get('802-11-wireless', {}).get('ssid') + if isinstance(ssid_value, tuple) and len(ssid_value) > 1: + ssid = decode_ssid(ssid_value[1]) + if _canonicalize_ssid(ssid) == _canonicalize_ssid(self._tethering_ssid): + return True + + specific_obj_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('SpecificObject')).body[0][1] + if specific_obj_path != "/": + ap_addr = DBusAddress(specific_obj_path, bus_name=NM, interface=NM_ACCESS_POINT_IFACE) + ap_ssid = decode_ssid(self._router_main.send_and_get_reply(Properties(ap_addr).get('Ssid')).body[0][1]) + if _canonicalize_ssid(ap_ssid) == _canonicalize_ssid(self._tethering_ssid): + return True + except Exception as e: + cloudlog.warning(f"DBus tethering state lookup failed: {e}") + for network in self._networks: if network.is_connected: return bool(network.ssid == self._tethering_ssid) return False + def disconnect_network(self, ssid: str, block: bool = False): + if not (self._dbus_available or self._fake_networking or self._nmcli_networking): + cloudlog.warning("disconnect_network called with no available networking backend") + return + + def worker(): + self._deactivate_connection(ssid) + + if block: + worker() + else: + threading.Thread(target=worker, daemon=True).start() + def set_tethering_password(self, password: str): if self._fake_networking: self._tethering_password = password @@ -863,6 +938,10 @@ class WifiManager: self._enqueue_callbacks(self._networks_updated, self._networks) return + # Keep UI state responsive while the DBus update completes. + self._current_network_metered = metered + self._enqueue_callbacks(self._networks_updated, self._networks) + def worker(): for active_conn in self._get_active_connections(): conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE)