From f71e0b629f3a16e08162ab8913729a7339d9e702 Mon Sep 17 00:00:00 2001 From: FrogAi <91348155+FrogAi@users.noreply.github.com> Date: Fri, 12 Jan 2024 22:39:30 -0700 Subject: [PATCH] Quality of life toggles Co-Authored-By: Tim Wilson <7284371+twilsonco@users.noreply.github.com> Update visual_settings.cc --- common/params.cc | 5 ++ selfdrive/car/car_helpers.py | 10 +-- selfdrive/controls/controlsd.py | 11 +-- selfdrive/controls/lib/drive_helpers.py | 12 +++- .../assets/toggle_icons/quality_of_life.png | Bin 0 -> 42955 bytes selfdrive/frogpilot/ui/control_settings.cc | 67 +++++++++++++----- selfdrive/frogpilot/ui/control_settings.h | 1 + selfdrive/frogpilot/ui/frogpilot_functions.cc | 4 ++ selfdrive/frogpilot/ui/visual_settings.cc | 21 +++++- selfdrive/frogpilot/ui/visual_settings.h | 1 + selfdrive/ui/qt/onroad.cc | 34 +++++---- selfdrive/ui/qt/onroad.h | 2 +- selfdrive/ui/ui.cc | 22 +++--- selfdrive/ui/ui.h | 2 + system/loggerd/loggerd.h | 2 +- 15 files changed, 138 insertions(+), 56 deletions(-) create mode 100644 selfdrive/frogpilot/assets/toggle_icons/quality_of_life.png diff --git a/common/params.cc b/common/params.cc index 3b93663..8efbe29 100644 --- a/common/params.cc +++ b/common/params.cc @@ -259,11 +259,13 @@ std::unordered_map keys = { {"Fahrenheit", PERSISTENT}, {"FireTheBabysitter", PERSISTENT}, {"FrogPilotTogglesUpdated", PERSISTENT}, + {"FullMap", PERSISTENT}, {"GasRegenCmd", PERSISTENT}, {"GMapKey", PERSISTENT}, {"GoatScream", PERSISTENT}, {"GreenLightAlert", PERSISTENT}, {"HideSpeed", PERSISTENT}, + {"HigherBitrate", PERSISTENT}, {"LaneChangeTime", PERSISTENT}, {"LaneDetection", PERSISTENT}, {"LaneLinesWidth", PERSISTENT}, @@ -313,6 +315,8 @@ std::unordered_map keys = { {"PersonalityChangedViaWheel", PERSISTENT}, {"PreferredSchedule", PERSISTENT}, {"PreviousSpeedLimit", PERSISTENT}, + {"QOLControls", PERSISTENT}, + {"QOLVisuals", PERSISTENT}, {"RelaxedFollow", PERSISTENT}, {"RelaxedJerk", PERSISTENT}, {"ReverseCruise", PERSISTENT}, @@ -323,6 +327,7 @@ std::unordered_map keys = { {"SchedulePending", PERSISTENT}, {"ScreenBrightness", PERSISTENT}, {"SearchInput", PERSISTENT}, + {"SetSpeedOffset", PERSISTENT}, {"ShowCPU", PERSISTENT}, {"ShowFPS", PERSISTENT}, {"ShowGPU", PERSISTENT}, diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 1981810..5196397 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -216,16 +216,16 @@ def crash_log(candidate): "AggressiveJerk", "StandardFollow", "StandardJerk", "RelaxedFollow", "RelaxedJerk", "DeviceShutdown", "ExperimentalModeViaPress", "FireTheBabysitter", "NoLogging", "MuteDM", "MuteDoor", "MuteSeatbelt", "MuteOverheated", "LateralTune", "AverageCurvature", "NNFF", "LongitudinalTune", "AccelerationProfile", "StoppingDistance", "AggressiveAcceleration", "SmoothBraking", "Model", "MTSCEnabled", - "NudgelessLaneChange", "LaneChangeTime", "LaneDetection", "OneLaneChange", "PauseLateralOnSignal", "SpeedLimitController", "SLCFallback", - "SLCOverride", "SLCPriority", "Offset1", "Offset2", "Offset3", "Offset4", "TurnDesires", "VisionTurnControl", "CurveSensitivity", "TurnAggressiveness", - "DisableOnroadUploads", "OfflineMode", "ReverseCruise" + "NudgelessLaneChange", "LaneChangeTime", "LaneDetection", "OneLaneChange", "QOLControls", "HigherBitrate", "PauseLateralOnSignal", "ReverseCruise", + "SetSpeedOffset", "SpeedLimitController", "SLCFallback","SLCOverride", "SLCPriority", "Offset1", "Offset2", "Offset3", "Offset4", "TurnDesires", + "VisionTurnControl", "CurveSensitivity", "TurnAggressiveness", "DisableOnroadUploads", "OfflineMode" ], [ "EVTable", "GasRegenCmd", "LongPitch", "LowerVolt", "LockDoors", "SNGHack", "TSS2Tune" ], [ "CustomTheme", "CustomColors", "CustomIcons", "CustomSignals", "CustomSounds", "GoatScream", "CameraView", "Compass", "CustomUI", "LaneLinesWidth", "RoadEdgesWidth", "PathWidth", "PathEdgeWidth", "AccelerationPath", "AdjacentPath", "BlindSpotPath", "ShowFPS", "LeadInfo", "RoadNameUI", "UnlimitedLength", - "DriverCamera", "GreenLightAlert", "ModelUI", "RandomEvents", "RotatingWheel", "ScreenBrightness", "Sidebar", "SilentMode", "WheelIcon", "HideSpeed", - "NumericalTemp", "Fahrenheit", "ShowCPU", "ShowGPU", "ShowMemoryUsage", "ShowSLCOffset", "ShowStorageLeft", "ShowStorageUsed", "UseSI" + "DriverCamera", "GreenLightAlert", "ModelUI", "QOLVisuals", "FullMap", "HideSpeed", "RandomEvents", "RotatingWheel", "ScreenBrightness", "Sidebar", "SilentMode", + "WheelIcon", "NumericalTemp", "Fahrenheit", "ShowCPU", "ShowGPU", "ShowMemoryUsage", "ShowSLCOffset", "ShowStorageLeft", "ShowStorageUsed", "UseSI" ] control_params, vehicle_params, visual_params = map(lambda keys: get_frogpilot_params(params, keys), [control_keys, vehicle_keys, visual_keys]) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 6d2b715..aa077a1 100644 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -18,7 +18,6 @@ from openpilot.system.version import get_short_branch from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp from openpilot.selfdrive.car.car_helpers import get_car, get_startup_event, get_one_can from openpilot.selfdrive.controls.lib.lateral_planner import CAMERA_OFFSET -from openpilot.selfdrive.controls.lib.desire_helper import LANE_CHANGE_SPEED_MIN from openpilot.selfdrive.controls.lib.drive_helpers import VCruiseHelper, get_lag_adjusted_curvature from openpilot.selfdrive.controls.lib.latcontrol import LatControl, MIN_LATERAL_CONTROL_SPEED from openpilot.selfdrive.controls.lib.longcontrol import LongControl @@ -527,7 +526,7 @@ class Controls: def state_transition(self, CS): """Compute conditional state transitions and execute actions on state transitions""" - self.v_cruise_helper.update_v_cruise(CS, self.enabled, self.is_metric, self.reverse_cruise_increase) + self.v_cruise_helper.update_v_cruise(CS, self.enabled, self.is_metric, self.reverse_cruise_increase, self.set_speed_offset) # decrement the soft disable timer at every step, as it's reset on # entrance in SOFT_DISABLING state @@ -641,7 +640,7 @@ class Controls: gear = car.CarState.GearShifter driving_gear = CS.gearShifter not in (gear.neutral, gear.park, gear.reverse, gear.unknown) - signal_check = not ((CS.leftBlinker or CS.rightBlinker) and self.pause_lateral_on_signal and CS.vEgo < LANE_CHANGE_SPEED_MIN) + signal_check = not ((CS.leftBlinker or CS.rightBlinker) and CS.vEgo < self.pause_lateral_on_signal and not CS.standstill) # Always on lateral if self.always_on_lateral: @@ -1000,8 +999,10 @@ class Controls: longitudinal_tune = self.params.get_bool("LongitudinalTune") self.sport_plus = self.params.get_int("AccelerationProfile") == 3 and longitudinal_tune - self.pause_lateral_on_signal = self.params.get_bool("PauseLateralOnSignal") - self.reverse_cruise_increase = self.params.get_bool("ReverseCruise") + quality_of_life = self.params.get_bool("QOLControls") + self.pause_lateral_on_signal = self.params.get_int("PauseLateralOnSignal") * (CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS) if quality_of_life else 0 + self.reverse_cruise_increase = self.params.get_bool("ReverseCruise") and quality_of_life + self.set_speed_offset = self.params.get_int("SetSpeedOffset") * (1 if self.is_metric else CV.MPH_TO_KPH) if quality_of_life else 0 def main(): controls = Controls() diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 0e2862e..d6186be 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -52,13 +52,13 @@ class VCruiseHelper: def v_cruise_initialized(self): return self.v_cruise_kph != V_CRUISE_UNSET - def update_v_cruise(self, CS, enabled, is_metric, reverse_cruise_increase): + def update_v_cruise(self, CS, enabled, is_metric, reverse_cruise_increase, set_speed_offset): self.v_cruise_kph_last = self.v_cruise_kph if CS.cruiseState.available: if not self.CP.pcmCruise: # if stock cruise is completely disabled, then we can use our own set speed logic - self._update_v_cruise_non_pcm(CS, enabled, is_metric, reverse_cruise_increase) + self._update_v_cruise_non_pcm(CS, enabled, is_metric, reverse_cruise_increase, set_speed_offset) self.v_cruise_cluster_kph = self.v_cruise_kph self.update_button_timers(CS, enabled) else: @@ -68,7 +68,7 @@ class VCruiseHelper: self.v_cruise_kph = V_CRUISE_UNSET self.v_cruise_cluster_kph = V_CRUISE_UNSET - def _update_v_cruise_non_pcm(self, CS, enabled, is_metric, reverse_cruise_increase): + def _update_v_cruise_non_pcm(self, CS, enabled, is_metric, reverse_cruise_increase, set_speed_offset): # handle button presses. TODO: this should be in state_control, but a decelCruise press # would have the effect of both enabling and changing speed is checked after the state transition if not enabled: @@ -110,6 +110,12 @@ class VCruiseHelper: else: self.v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type] + # Apply offset + v_cruise_offset = (set_speed_offset * CRUISE_INTERVAL_SIGN[button_type]) if long_press else 0 + if v_cruise_offset < 0: + v_cruise_offset = set_speed_offset - v_cruise_delta + self.v_cruise_kph += v_cruise_offset + # 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/frogpilot/assets/toggle_icons/quality_of_life.png b/selfdrive/frogpilot/assets/toggle_icons/quality_of_life.png new file mode 100644 index 0000000000000000000000000000000000000000..1719a605f293614de0a1b59246c283bf43e5e2a1 GIT binary patch literal 42955 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 LR^Qax=Kntc+i=!{ literal 0 HcmV?d00001 diff --git a/selfdrive/frogpilot/ui/control_settings.cc b/selfdrive/frogpilot/ui/control_settings.cc index 70e43d6..5d9ce55 100644 --- a/selfdrive/frogpilot/ui/control_settings.cc +++ b/selfdrive/frogpilot/ui/control_settings.cc @@ -42,7 +42,12 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil {"LaneChangeTime", "Lane Change Timer", "Specify a delay before executing a nudgeless lane change.", ""}, {"LaneDetection", "Lane Detection", "Block nudgeless lane changes when a lane isn't detected.", ""}, {"OneLaneChange", "One Lane Change Per Signal", "Limit to one nudgeless lane change per turn signal activation.", ""}, - {"PauseLateralOnSignal", "Pause Lateral On Turn Signal", "Temporarily disable lateral control during turn signal use.", ""}, + + {"QOLControls", "Quality of Life", "Miscellaneous quality of life changes to improve your overall openpilot experience.", "../frogpilot/assets/toggle_icons/quality_of_life.png"}, + {"HigherBitrate", "Higher Bitrate Recording", "Increases the quality of the footage uploaded to comma connect.", ""}, + {"PauseLateralOnSignal", "Pause Lateral On Turn Signal Below", "Temporarily disable lateral control during turn signal use below the set speed.", ""}, + {"ReverseCruise", "Reverse Cruise Increase", "Reverses the 'long press' functionality when increasing the max set speed. Useful to increase the max speed quickly.", ""}, + {"SetSpeedOffset", "Set Speed Offset", "Set an offset for your desired set speed.", ""}, {"SpeedLimitController", "Speed Limit Controller", "Automatically adjust vehicle speed to match speed limits using 'Open Street Map's, 'Navigate On openpilot', or your car's dashboard (TSS2 Toyotas only).", "../assets/offroad/icon_speed_limit.png"}, {"Offset1", "Speed Limit Offset (0-34 mph)", "Speed limit offset for speed limits between 0-34 mph.", ""}, @@ -94,16 +99,16 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil toggle = conditionalExperimentalToggle; } else if (param == "CECurves") { FrogPilotParamValueControl *CESpeedImperial = new FrogPilotParamValueControl("CESpeed", "Below", "Switch to 'Experimental Mode' below this speed in absence of a lead vehicle.", "", 0, 99, - std::map(), this, false, " mph"); + std::map(), this, false, " mph"); FrogPilotParamValueControl *CESpeedLeadImperial = new FrogPilotParamValueControl("CESpeedLead", " w/Lead", "Switch to 'Experimental Mode' below this speed when following a lead vehicle.", "", 0, 99, - std::map(), this, false, " mph"); + std::map(), this, false, " mph"); conditionalSpeedsImperial = new FrogPilotDualParamControl(CESpeedImperial, CESpeedLeadImperial, this); addItem(conditionalSpeedsImperial); FrogPilotParamValueControl *CESpeedMetric = new FrogPilotParamValueControl("CESpeed", "Below", "Switch to 'Experimental Mode' below this speed in absence of a lead vehicle.", "", 0, 150, - std::map(), this, false, " kph"); - FrogPilotParamValueControl *CESpeedLeadMetric = new FrogPilotParamValueControl("CESpeedLead", " w/Lead", "Switch to 'Experimental Mode' below this speed when following a lead vehicle.", "", - 0, 150, std::map(), this, false, " kph"); + std::map(), this, false, " kph"); + FrogPilotParamValueControl *CESpeedLeadMetric = new FrogPilotParamValueControl("CESpeedLead", " w/Lead", "Switch to 'Experimental Mode' below this speed when following a lead vehicle.", "", 0, 150, + std::map(), this, false, " kph"); conditionalSpeedsMetric = new FrogPilotDualParamControl(CESpeedMetric, CESpeedLeadMetric, this); addItem(conditionalSpeedsMetric); @@ -245,6 +250,20 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil } toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 10, laneChangeTimeLabels, this, false); + } else if (param == "QOLControls") { + FrogPilotParamManageControl *qolToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); + QObject::connect(qolToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + parentToggleClicked(); + for (auto &[key, toggle] : toggles) { + toggle->setVisible(qolKeys.find(key.c_str()) != qolKeys.end()); + } + }); + toggle = qolToggle; + } else if (param == "PauseLateralOnSignal") { + toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, std::map(), this, false, " mph"); + } else if (param == "SetSpeedOffset") { + toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, std::map(), this, false, " mph"); + } else if (param == "SpeedLimitController") { FrogPilotParamManageControl *speedLimitControllerToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(speedLimitControllerToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { @@ -366,7 +385,7 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil }); } - std::set rebootKeys = {"AlwaysOnLateral", "FireTheBabysitter", "MuteDM", "NNFF"}; + std::set rebootKeys = {"AlwaysOnLateral", "FireTheBabysitter", "HigherBitrate", "MuteDM", "NNFF"}; for (const std::string &key : rebootKeys) { QObject::connect(toggles[key], &ToggleControl::toggleFlipped, [this]() { if (FrogPilotConfirmationDialog::toggle("Reboot required to take effect.", "Reboot Now", this)) { @@ -377,9 +396,10 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil conditionalExperimentalKeys = {"CECurves", "CECurvesLead", "CESlowerLead", "CENavigation", "CEStopLights", "CESignal"}; fireTheBabysitterKeys = {"NoLogging", "MuteDM", "MuteDoor", "MuteOverheated", "MuteSeatbelt"}; - laneChangeKeys = {"LaneChangeTime", "LaneDetection", "OneLaneChange", "PauseLateralOnSignal"}; + laneChangeKeys = {"LaneChangeTime", "LaneDetection", "OneLaneChange"}; lateralTuneKeys = {"AverageCurvature", "NNFF", "SteerRatio"}; longitudinalTuneKeys = {"AccelerationProfile", "AggressiveAcceleration", "SmoothBraking", "StoppingDistance"}; + qolKeys = {"HigherBitrate", "PauseLateralOnSignal", "ReverseCruise", "SetSpeedOffset"}; speedLimitControllerKeys = {"Offset1", "Offset2", "Offset3", "Offset4", "SLCFallback", "SLCOverride", "SLCPriority"}; visionTurnControlKeys = {"CurveSensitivity", "TurnAggressiveness"}; @@ -426,6 +446,8 @@ void FrogPilotControlsPanel::updateMetric() { params.putInt("Offset2", std::nearbyint(params.getInt("Offset2") * speedConversion)); params.putInt("Offset3", std::nearbyint(params.getInt("Offset3") * speedConversion)); params.putInt("Offset4", std::nearbyint(params.getInt("Offset4") * speedConversion)); + params.putInt("PauseLateralOnSignal", std::nearbyint(params.getInt("PauseLateralOnSignal") * speedConversion)); + params.putInt("SetSpeedOffset", std::nearbyint(params.getInt("SetSpeedOffset") * speedConversion)); params.putInt("StoppingDistance", std::nearbyint(params.getInt("StoppingDistance") * distanceConversion)); } @@ -433,6 +455,8 @@ void FrogPilotControlsPanel::updateMetric() { FrogPilotParamValueControl *offset2Toggle = static_cast(toggles["Offset2"]); FrogPilotParamValueControl *offset3Toggle = static_cast(toggles["Offset3"]); FrogPilotParamValueControl *offset4Toggle = static_cast(toggles["Offset4"]); + FrogPilotParamValueControl *pauseLateralToggle = static_cast(toggles["PauseLateralOnSignal"]); + FrogPilotParamValueControl *setSpeedOffsetToggle = static_cast(toggles["SetSpeedOffset"]); FrogPilotParamValueControl *stoppingDistanceToggle = static_cast(toggles["StoppingDistance"]); if (isMetric) { @@ -446,10 +470,12 @@ void FrogPilotControlsPanel::updateMetric() { offset3Toggle->setDescription("Set speed limit offset for limits between 55-64 kph."); offset4Toggle->setDescription("Set speed limit offset for limits between 65-99 kph."); - offset1Toggle->updateControl(0, 99, " kph"); - offset2Toggle->updateControl(0, 99, " kph"); - offset3Toggle->updateControl(0, 99, " kph"); - offset4Toggle->updateControl(0, 99, " kph"); + offset1Toggle->updateControl(0, 150, " kph"); + offset2Toggle->updateControl(0, 150, " kph"); + offset3Toggle->updateControl(0, 150, " kph"); + offset4Toggle->updateControl(0, 150, " kph"); + pauseLateralToggle->updateControl(0, 150, " kph"); + setSpeedOffsetToggle->updateControl(0, 150, " kph"); stoppingDistanceToggle->updateControl(0, 5, " meters"); } else { offset1Toggle->setTitle("Speed Limit Offset (0-34 mph)"); @@ -466,6 +492,8 @@ void FrogPilotControlsPanel::updateMetric() { offset2Toggle->updateControl(0, 99, " mph"); offset3Toggle->updateControl(0, 99, " mph"); offset4Toggle->updateControl(0, 99, " mph"); + pauseLateralToggle->updateControl(0, 99, " mph"); + setSpeedOffsetToggle->updateControl(0, 99, " mph"); stoppingDistanceToggle->updateControl(0, 10, " feet"); } @@ -473,6 +501,8 @@ void FrogPilotControlsPanel::updateMetric() { offset2Toggle->refresh(); offset3Toggle->refresh(); offset4Toggle->refresh(); + pauseLateralToggle->refresh(); + setSpeedOffsetToggle->refresh(); stoppingDistanceToggle->refresh(); previousIsMetric = isMetric; @@ -501,12 +531,13 @@ void FrogPilotControlsPanel::hideSubToggles() { for (auto &[key, toggle] : toggles) { bool subToggles = conditionalExperimentalKeys.find(key.c_str()) != conditionalExperimentalKeys.end() || - fireTheBabysitterKeys.find(key.c_str()) != fireTheBabysitterKeys.end() || - laneChangeKeys.find(key.c_str()) != laneChangeKeys.end() || - lateralTuneKeys.find(key.c_str()) != lateralTuneKeys.end() || - longitudinalTuneKeys.find(key.c_str()) != longitudinalTuneKeys.end() || - speedLimitControllerKeys.find(key.c_str()) != speedLimitControllerKeys.end() || - visionTurnControlKeys.find(key.c_str()) != visionTurnControlKeys.end(); + fireTheBabysitterKeys.find(key.c_str()) != fireTheBabysitterKeys.end() || + laneChangeKeys.find(key.c_str()) != laneChangeKeys.end() || + lateralTuneKeys.find(key.c_str()) != lateralTuneKeys.end() || + longitudinalTuneKeys.find(key.c_str()) != longitudinalTuneKeys.end() || + qolKeys.find(key.c_str()) != qolKeys.end() || + speedLimitControllerKeys.find(key.c_str()) != speedLimitControllerKeys.end() || + visionTurnControlKeys.find(key.c_str()) != visionTurnControlKeys.end(); toggle->setVisible(!subToggles); } diff --git a/selfdrive/frogpilot/ui/control_settings.h b/selfdrive/frogpilot/ui/control_settings.h index d49f3df..1520c11 100644 --- a/selfdrive/frogpilot/ui/control_settings.h +++ b/selfdrive/frogpilot/ui/control_settings.h @@ -38,6 +38,7 @@ private: std::set laneChangeKeys; std::set lateralTuneKeys; std::set longitudinalTuneKeys; + std::set qolKeys; std::set speedLimitControllerKeys; std::set visionTurnControlKeys; diff --git a/selfdrive/frogpilot/ui/frogpilot_functions.cc b/selfdrive/frogpilot/ui/frogpilot_functions.cc index 3585bd0..496989a 100644 --- a/selfdrive/frogpilot/ui/frogpilot_functions.cc +++ b/selfdrive/frogpilot/ui/frogpilot_functions.cc @@ -83,10 +83,12 @@ void setDefaultParams() { {"ExperimentalModeViaPress", "1"}, {"Fahrenheit", "0"}, {"FireTheBabysitter", FrogsGoMoo ? "1" : "0"}, + {"FullMap", "0"}, {"GasRegenCmd", "0"}, {"GoatScream", "1"}, {"GreenLightAlert", "0"}, {"HideSpeed", "0"}, + {"HigherBitrate", "0"}, {"LaneChangeTime", "0"}, {"LaneDetection", "1"}, {"LaneLinesWidth", "4"}, @@ -116,6 +118,8 @@ void setDefaultParams() { {"PathWidth", "61"}, {"PauseLateralOnSignal", "0"}, {"PreferredSchedule", "0"}, + {"QOLControls", "1"}, + {"QOLVisuals", "1"}, {"RandomEvents", FrogsGoMoo ? "1" : "0"}, {"RelaxedFollow", "30"}, {"RelaxedJerk", "50"}, diff --git a/selfdrive/frogpilot/ui/visual_settings.cc b/selfdrive/frogpilot/ui/visual_settings.cc index c3915e0..117ad8f 100644 --- a/selfdrive/frogpilot/ui/visual_settings.cc +++ b/selfdrive/frogpilot/ui/visual_settings.cc @@ -30,6 +30,11 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot {"RoadEdgesWidth", "Road Edges", "Adjust the visual thickness of road edges on your display.\n\nDefault is 1/2 of the MUTCD average lane line width of 4 inches.", ""}, {"UnlimitedLength", "'Unlimited' Road UI Length", "Extend the display of the path, lane lines, and road edges as far as the system can detect, providing a more expansive view of the road ahead.", ""}, + {"QOLVisuals", "Quality of Life", "Miscellaneous quality of life changes to improve your overall openpilot experience.", "../frogpilot/assets/toggle_icons/quality_of_life.png"}, + {"DriveStats", "Drive Stats In Home Screen", "Display your device's drive stats in the home screen.", ""}, + {"HideSpeed", "Hide Speed", "Hide the speed indicator in the onroad UI.", ""}, + {"ShowSLCOffset", "Show Speed Limit Offset", "Show the speed limit offset seperated from the speed limit in the onroad UI when using 'Speed Limit Controller'.", ""}, + {"ScreenBrightness", "Screen Brightness", "Customize your screen brightness.", "../frogpilot/assets/toggle_icons/icon_light.png"}, {"SilentMode", "Silent Mode", "Mute openpilot sounds for a quieter driving experience.", "../frogpilot/assets/toggle_icons/icon_mute.png"}, {"WheelIcon", "Steering Wheel Icon", "Replace the default steering wheel icon with a custom design, adding a unique touch to your interface.", "../assets/offroad/icon_openpilot.png"}, @@ -99,6 +104,16 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot } else if (param == "PathWidth") { toggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 100, std::map(), this, false, " feet", 10); + } else if (param == "QOLVisuals") { + FrogPilotParamManageControl *qolToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); + QObject::connect(qolToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + parentToggleClicked(); + for (auto &[key, toggle] : toggles) { + toggle->setVisible(qolKeys.find(key.c_str()) != qolKeys.end()); + } + }); + toggle = qolToggle; + } else if (param == "ScreenBrightness") { std::map brightnessLabels; for (int i = 0; i <= 101; ++i) { @@ -139,6 +154,7 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot customOnroadUIKeys = {"AdjacentPath", "BlindSpotPath", "ShowFPS", "LeadInfo", "RoadNameUI"}; customThemeKeys = {"CustomColors", "CustomIcons", "CustomSignals", "CustomSounds"}; modelUIKeys = {"AccelerationPath", "LaneLinesWidth", "PathEdgeWidth", "PathWidth", "RoadEdgesWidth", "UnlimitedLength"}; + qolKeys = {"DriveStats", "HideSpeed", "ShowSLCOffset"}; QObject::connect(device(), &Device::interactiveTimeout, this, &FrogPilotVisualsPanel::hideSubToggles); QObject::connect(parent, &SettingsWindow::closeParentToggle, this, &FrogPilotVisualsPanel::hideSubToggles); @@ -201,8 +217,9 @@ void FrogPilotVisualsPanel::parentToggleClicked() { void FrogPilotVisualsPanel::hideSubToggles() { for (auto &[key, toggle] : toggles) { bool subToggles = modelUIKeys.find(key.c_str()) != modelUIKeys.end() || - customOnroadUIKeys.find(key.c_str()) != customOnroadUIKeys.end() || - customThemeKeys.find(key.c_str()) != customThemeKeys.end(); + customOnroadUIKeys.find(key.c_str()) != customOnroadUIKeys.end() || + customThemeKeys.find(key.c_str()) != customThemeKeys.end() || + qolKeys.find(key.c_str()) != qolKeys.end(); toggle->setVisible(!subToggles); } diff --git a/selfdrive/frogpilot/ui/visual_settings.h b/selfdrive/frogpilot/ui/visual_settings.h index 55602cb..f30cda7 100644 --- a/selfdrive/frogpilot/ui/visual_settings.h +++ b/selfdrive/frogpilot/ui/visual_settings.h @@ -25,6 +25,7 @@ private: std::set customOnroadUIKeys; std::set customThemeKeys; std::set modelUIKeys; + std::set qolKeys; std::map toggles; diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 94dd816..fea0732 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -138,14 +138,23 @@ void OnroadWindow::mousePressEvent(QMouseEvent* e) { if (isMaxSpeedClicked) { reverseCruise = !params.getBool("ReverseCruise"); params.putBoolNonBlocking("ReverseCruise", reverseCruise); + if (!params.getBool("QOLControls")) { + params.putBoolNonBlocking("QOLControls", true); + } paramsMemory.putBoolNonBlocking("FrogPilotTogglesUpdated", true); // Check if the click was within the speed text area } else if (isSpeedClicked) { - speedHidden = !params.getBool("HideSpeed"); - params.putBoolNonBlocking("HideSpeed", speedHidden); + hideSpeed = !params.getBool("HideSpeed"); + params.putBoolNonBlocking("HideSpeed", hideSpeed); + if (!params.getBool("QOLVisuals")) { + params.putBoolNonBlocking("QOLVisuals", true); + } } else { showSLCOffset = !params.getBool("ShowSLCOffset"); params.putBoolNonBlocking("ShowSLCOffset", showSLCOffset); + if (!params.getBool("QOLVisuals")) { + params.putBoolNonBlocking("QOLVisuals", true); + } } widgetClicked = true; // If the click wasn't for anything specific, change the value of "ExperimentalMode" @@ -193,7 +202,11 @@ void OnroadWindow::offroadTransition(bool offroad) { QObject::connect(nvg->map_settings_btn_bottom, &MapSettingsButton::clicked, m, &MapPanel::toggleMapSettings); nvg->map_settings_btn->setEnabled(true); - m->setFixedWidth(topWidget(this)->width() / 2 - UI_BORDER_SIZE); + if (scene.full_map) { + m->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + } else { + m->setFixedWidth(topWidget(this)->width() / 2 - UI_BORDER_SIZE); + } split->insertWidget(0, m); // hidden by default, made visible when navRoute is published @@ -643,7 +656,7 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { } // current speed - if (!speedHidden) { + if (!hideSpeed) { p.setFont(InterFont(176, QFont::Bold)); drawText(p, rect().center().x(), 210, speedStr); p.setFont(InterFont(66)); @@ -1125,16 +1138,13 @@ void AnnotatedCameraWidget::initializeFrogPilotWidgets() { main_layout->addLayout(bottom_layout); - if (params.getBool("HideSpeed")) { - speedHidden = true; + if (params.getBool("QOLControls")) { + reverseCruise = params.getBool("ReverseCruise"); } - if (params.getBool("ReverseCruise")) { - reverseCruise = true; - } - - if (params.getBool("ShowSLCOffset")) { - showSLCOffset = true; + if (params.getBool("QOLVisuals")) { + hideSpeed = params.getBool("HideSpeed"); + showSLCOffset = params.getBool("ShowSLCOffset"); } // Custom themes configuration diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index fd33a65..5265eaa 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -17,9 +17,9 @@ const int btn_size = 192; const int img_size = (btn_size / 4) * 3; // FrogPilot global variables +static bool hideSpeed; static bool reverseCruise; static bool showSLCOffset; -static bool speedHidden; static double fps; // ***** onroad widgets ***** diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 47a8467..48b8081 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -303,29 +303,33 @@ void ui_update_params(UIState *s) { scene.conditional_speed_lead = params.getInt("CESpeedLead"); scene.custom_onroad_ui = params.getBool("CustomUI"); - scene.adjacent_path = scene.custom_onroad_ui && params.getBool("AdjacentPath"); - scene.blind_spot_path = scene.custom_onroad_ui && params.getBool("BlindSpotPath"); - scene.lead_info = scene.custom_onroad_ui && params.getBool("LeadInfo"); - scene.road_name_ui = scene.custom_onroad_ui && params.getBool("RoadNameUI"); - scene.show_fps = scene.custom_onroad_ui && params.getBool("ShowFPS"); - scene.use_si = scene.custom_onroad_ui && params.getBool("UseSI"); + scene.adjacent_path = params.getBool("AdjacentPath") && scene.custom_onroad_ui; + scene.blind_spot_path = params.getBool("BlindSpotPath") && scene.custom_onroad_ui; + scene.lead_info = params.getBool("LeadInfo") && scene.custom_onroad_ui; + scene.road_name_ui = params.getBool("RoadNameUI") && scene.custom_onroad_ui; + scene.show_fps = params.getBool("ShowFPS") && scene.custom_onroad_ui; + scene.use_si = params.getBool("UseSI") && scene.custom_onroad_ui; scene.custom_theme = params.getBool("CustomTheme"); scene.custom_colors = scene.custom_theme ? params.getInt("CustomColors") : 0; scene.custom_signals = scene.custom_theme ? params.getInt("CustomSignals") : 0; scene.model_ui = params.getBool("ModelUI"); - scene.acceleration_path = scene.model_ui && params.getBool("AccelerationPath"); + scene.acceleration_path = params.getBool("AccelerationPath") && scene.model_ui; scene.lane_line_width = params.getInt("LaneLinesWidth") * (scene.is_metric ? 1 : INCH_TO_CM) / 200; scene.path_edge_width = params.getInt("PathEdgeWidth"); scene.path_width = params.getInt("PathWidth") / 10.0 * (scene.is_metric ? 1 : FOOT_TO_METER) / 2; scene.road_edge_width = params.getInt("RoadEdgesWidth") * (scene.is_metric ? 1 : INCH_TO_CM) / 200; - scene.unlimited_road_ui_length = scene.model_ui && params.getBool("UnlimitedLength"); + scene.unlimited_road_ui_length = params.getBool("UnlimitedLength") && scene.model_ui; scene.driver_camera = params.getBool("DriverCamera"); scene.experimental_mode_via_press = params.getBool("ExperimentalModeViaPress"); - scene.mute_dm = params.getBool("FireTheBabysitter") && params.getBool("MuteDM"); + scene.mute_dm = params.getBool("MuteDM") && params.getBool("FireTheBabysitter"); scene.personalities_via_screen = (params.getInt("AdjustablePersonalities") == 2 || params.getInt("AdjustablePersonalities") == 3); + + scene.quality_of_life_visuals = params.getBool("QOLVisuals"); + scene.full_map = params.getBool("QOLVisuals") && scene.quality_of_life_visuals; + scene.rotating_wheel = params.getBool("RotatingWheel"); scene.screen_brightness = params.getInt("ScreenBrightness"); scene.speed_limit_controller = params.getBool("SpeedLimitController"); diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index bd615a8..9ed1d3a 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -185,11 +185,13 @@ typedef struct UIScene { bool enabled; bool experimental_mode; bool experimental_mode_via_press; + bool full_map; bool lead_info; bool map_open; bool model_ui; bool mute_dm; bool personalities_via_screen; + bool quality_of_life_visuals; bool road_name_ui; bool rotating_wheel; bool show_driver_camera; diff --git a/system/loggerd/loggerd.h b/system/loggerd/loggerd.h index cfc06c2..236a88a 100644 --- a/system/loggerd/loggerd.h +++ b/system/loggerd/loggerd.h @@ -14,7 +14,7 @@ #include "system/loggerd/logger.h" constexpr int MAIN_FPS = 20; -const int MAIN_BITRATE = 1e7; +const int MAIN_BITRATE = Params().getBool("HigherBitrate") ? 20000000 : 1e7; const int LIVESTREAM_BITRATE = 1e6; const int QCAM_BITRATE = 256000;