diff --git a/.github/workflows/update-pr-branch.yaml b/.github/workflows/update-pr-branch.yaml new file mode 100644 index 00000000000000..8facfe305a6f4b --- /dev/null +++ b/.github/workflows/update-pr-branch.yaml @@ -0,0 +1,59 @@ +name: Update MAKE-PRS-HERE + +on: + push: + branches: + - FrogPilot-Staging + +env: + SOURCE_BRANCH: FrogPilot-Staging + TARGET_BRANCH: MAKE-PRS-HERE + +jobs: + squash-and-cherry-pick: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + ref: ${{ env.SOURCE_BRANCH }} + fetch-depth: 0 + + - name: Set Git user name and email + run: | + git config --global user.name "${{ github.actor }}" + git config --global user.email "${{ github.actor }}@users.noreply.github.com" + + - name: Get the second to last commit hash and create a temporary branch + run: | + commit_hash=$(git rev-parse HEAD~1) + git checkout -b temp-branch $commit_hash + + - name: Squash all commits into one with today's date in Phoenix time zone + run: | + day=$(TZ='America/Phoenix' date '+%-d') + suffix="th" + case $day in + 1|21|31) suffix="st" ;; + 2|22) suffix="nd" ;; + 3|23) suffix="rd" ;; + esac + commit_message="$(TZ='America/Phoenix' date '+%B ')$day$suffix, $(TZ='America/Phoenix' date '+%Y') Update" + git reset --soft $(git rev-list --max-parents=0 HEAD) + git commit -m "$commit_message" + + - name: Cherry-pick the squashed commit to target branch and push + run: | + git fetch origin + git checkout ${{ env.TARGET_BRANCH }} + git cherry-pick temp-branch -X theirs || { + if git status | grep -q "nothing to commit, working tree clean"; then + echo "Empty commit detected, skipping cherry-pick." + git cherry-pick --skip + else + echo "Continuing with cherry-pick." + git cherry-pick --continue + fi + } + git push origin ${{ env.TARGET_BRANCH }} diff --git a/.github/workflows/update-previous-branch.yaml b/.github/workflows/update-previous-branch.yaml new file mode 100644 index 00000000000000..301b3dacbb5fb9 --- /dev/null +++ b/.github/workflows/update-previous-branch.yaml @@ -0,0 +1,21 @@ +name: Update FrogPilot-Previous Branch + +on: + schedule: + - cron: '0 7 1 * *' + +jobs: + update-branch: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Reset "FrogPilot-Previous" branch to match "FrogPilot" + run: | + git fetch origin + git checkout FrogPilot-Previous || git checkout -b FrogPilot-Previous + git reset --hard origin/FrogPilot + git push origin FrogPilot-Previous --force diff --git a/.github/workflows/update-release-branch.yaml b/.github/workflows/update-release-branch.yaml new file mode 100644 index 00000000000000..8c2c35fb3aada2 --- /dev/null +++ b/.github/workflows/update-release-branch.yaml @@ -0,0 +1,21 @@ +name: Update FrogPilot Branch + +on: + schedule: + - cron: '0 19 1 * *' + +jobs: + update-branch: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Reset "FrogPilot" branch to match "FrogPilot-Staging" + run: | + git fetch origin + git checkout FrogPilot || git checkout -b FrogPilot + git reset --hard origin/FrogPilot-Staging + git push origin FrogPilot --force diff --git a/README.md b/README.md index 77ceea89f70dc6..63c96555f30ac1 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ FrogPilot is a fully open-sourced fork of openpilot, featuring clear and concise ------ FrogPilot was last updated on: -**August 2nd, 2024** +**September 1st, 2024** Features ------ diff --git a/cereal/custom.capnp b/cereal/custom.capnp index e3873266ee4cbd..8375709c59d73a 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -1,6 +1,8 @@ using Cxx = import "./include/c++.capnp"; $Cxx.namespace("cereal"); +using Car = import "car.capnp"; + @0xb526ba661d550a59; # custom.capnp: a home for empty structs reserved for custom forks @@ -9,9 +11,11 @@ $Cxx.namespace("cereal"); # you can rename the struct, but don't change the identifier struct FrogPilotCarControl @0x81c2f05a394cf4af { - alwaysOnLateral @0 :Bool; - resumePressed @1 :Bool; - speedLimitChanged @2 :Bool; + alwaysOnLateralActive @0 :Bool; + fcwEventTriggered @1 :Bool; + noEntryEventTriggered @2 :Bool; + resumePressed @3 :Bool; + steerSaturatedEventTriggered @4 :Bool; } struct FrogPilotCarState @0xaedffd8f31e7b55d { @@ -46,31 +50,32 @@ struct FrogPilotPlan @0x80ae746ee2596b11 { accelerationJerk @0 :Float32; accelerationJerkStock @1 :Float32; adjustedCruise @2 :Float32; - conditionalExperimentalActive @3 :Bool; + alwaysOnLateralActive @3 :Bool; dangerJerk @4 :Float32; desiredFollowDistance @5 :Float32; - forcingStop @6 :Bool; - greenLight @7 :Bool; - laneWidthLeft @8 :Float32; - laneWidthRight @9 :Float32; - leadDeparting @10 :Bool; - maxAcceleration @11 :Float32; - minAcceleration @12 :Float32; - redLight @13 :Bool; - safeObstacleDistance @14 :Int16; - safeObstacleDistanceStock @15 :Int16; - slcOverridden @16 :Bool; - slcOverriddenSpeed @17 :Float32; - slcSpeedLimit @18 :Float32; - slcSpeedLimitOffset @19 :Float32; - speedJerk @20 :Float32; - speedJerkStock @21 :Float32; - stoppedEquivalenceFactor @22 :Int16; - takingCurveQuickly @23 :Bool; - tFollow @24 :Float32; - unconfirmedSlcSpeedLimit @25 :Float32; - vCruise @26 :Float32; - vtscControllingCurve @27 :Bool; + experimentalMode @6 :Bool; + forcingStop @7 :Bool; + frogpilotEvents @8: List(Car.CarEvent); + lateralCheck @9 :Bool; + laneWidthLeft @10 :Float32; + laneWidthRight @11 :Float32; + maxAcceleration @12 :Float32; + minAcceleration @13 :Float32; + redLight @14 :Bool; + safeObstacleDistance @15 :Int32; + safeObstacleDistanceStock @16 :Int32; + slcOverridden @17 :Bool; + slcOverriddenSpeed @18 :Float32; + slcSpeedLimit @19 :Float32; + slcSpeedLimitOffset @20 :Float32; + speedJerk @21 :Float32; + speedJerkStock @22 :Float32; + speedLimitChanged @23 :Bool; + stoppedEquivalenceFactor @24 :Int32; + tFollow @25 :Float32; + unconfirmedSlcSpeedLimit @26 :Float32; + vCruise @27 :Float32; + vtscControllingCurve @28 :Bool; } struct CustomReserved5 @0xa5cd762cd951a455 { diff --git a/common/conversions.py b/common/conversions.py index 62c8debdb56e0e..8554646ed0bb72 100644 --- a/common/conversions.py +++ b/common/conversions.py @@ -12,6 +12,8 @@ class Conversions: KNOTS_TO_MS = 1. / MS_TO_KNOTS # Distance + KM_TO_MILES = KPH_TO_MPH + MILES_TO_KM = MPH_TO_KPH METER_TO_FOOT = 3.28084 FOOT_TO_METER = 1. / METER_TO_FOOT CM_TO_INCH = 1. / 2.54 diff --git a/common/params.cc b/common/params.cc index b0f62bf7422e64..ee3a6f86e8efe0 100644 --- a/common/params.cc +++ b/common/params.cc @@ -213,7 +213,6 @@ std::unordered_map keys = { {"AccelerationProfile", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"AdjacentPath", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"AdjacentPathMetrics", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, - {"AggressiveAcceleration", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"AggressiveFollow", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"AggressiveJerkAcceleration", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"AggressiveJerkDanger", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, @@ -223,7 +222,7 @@ std::unordered_map keys = { {"AlwaysOnLateral", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"AlwaysOnLateralLKAS", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"AlwaysOnLateralMain", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, - {"AlwaysOnLateralSet", CLEAR_ON_ONROAD_TRANSITION}, + {"AlwaysOnLateralSet", CLEAR_ON_OFFROAD_TRANSITION}, {"AMapKey1", PERSISTENT}, {"AMapKey2", PERSISTENT}, {"ApiCache_DriveStats", PERSISTENT}, @@ -235,16 +234,19 @@ std::unordered_map keys = { {"BlacklistedModels", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"BlindSpotMetrics", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"BlindSpotPath", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, + {"BonusContent", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"BorderMetrics", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"CameraFPS", PERSISTENT}, {"CameraView", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"CancelModelDownload", PERSISTENT}, + {"CancelThemeDownload", PERSISTENT}, {"CarMake", PERSISTENT}, {"CarModel", PERSISTENT}, {"CarModelName", PERSISTENT}, {"CECurves", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"CECurvesLead", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"CELead", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, + {"CEModelStopTime", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"CENavigation", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"CENavigationIntersections", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"CENavigationLead", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, @@ -258,28 +260,30 @@ std::unordered_map keys = { {"CESpeed", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"CESpeedLead", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"CEStatus", PERSISTENT}, - {"CEStopLights", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, - {"CEStopLightsLessSensitive", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"CEStoppedLead", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, + {"ClairvoyantDriverCalibrationParams", PERSISTENT}, + {"ClairvoyantDriverDrives", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, + {"ClairvoyantDriverLiveTorqueParameters", PERSISTENT}, + {"ClairvoyantDriverScore", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"ClusterOffset", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VEHICLES}, + {"ColorToDownload", PERSISTENT}, {"Compass", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"ConditionalExperimental", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"CrosstrekTorque", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VEHICLES}, - {"CurrentHolidayTheme", PERSISTENT}, + {"CurrentHolidayTheme", CLEAR_ON_MANAGER_START}, {"CurrentModel", PERSISTENT | CLEAR_ON_OFFROAD_TRANSITION}, {"CurrentModelName", PERSISTENT | CLEAR_ON_OFFROAD_TRANSITION}, - {"CurrentRandomEvent", PERSISTENT}, {"CurveSensitivity", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"CustomAlerts", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"CustomColors", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"CustomCruise", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"CustomCruiseLong", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, + {"CustomDistanceIcons", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"CustomIcons", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"CustomPaths", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"CustomPersonalities", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"CustomSignals", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"CustomSounds", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, - {"CustomTheme", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"CustomUI", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"CydiaTune", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VEHICLES}, {"DecelerationProfile", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, @@ -290,8 +294,15 @@ std::unordered_map keys = { {"DisableOnroadUploads", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"DisableOpenpilotLongitudinal", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VEHICLES}, {"DisableVTSCSmoothing", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, + {"DistanceIconToDownload", PERSISTENT}, {"DisengageVolume", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"DoToggleReset", PERSISTENT}, + {"DownloadableColors", PERSISTENT}, + {"DownloadableDistanceIcons", PERSISTENT}, + {"DownloadableIcons", PERSISTENT}, + {"DownloadableSignals", PERSISTENT}, + {"DownloadableSounds", PERSISTENT}, + {"DownloadableWheels", PERSISTENT}, {"DownloadAllModels", PERSISTENT}, {"DragonPilotTune", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VEHICLES}, {"DriveRated", CLEAR_ON_ONROAD_TRANSITION}, @@ -304,6 +315,7 @@ std::unordered_map keys = { {"DynamicPathWidth", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"DynamicPedalsOnUI", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"EngageVolume", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, + {"ExperimentalGMTune", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VEHICLES}, {"ExperimentalModeActivation", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"ExperimentalModels", PERSISTENT}, {"ExperimentalModeViaDistance", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, @@ -340,9 +352,11 @@ std::unordered_map keys = { {"HideSpeedUI", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"HideUIElements", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"HolidayThemes", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, + {"HumanAcceleration", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, + {"HumanFollowing", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, + {"IconToDownload", PERSISTENT}, {"IncreaseThermalLimits", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"JerkInfo", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, - {"KaofuiIcons", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"LaneChangeCustomizations", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"LaneChangeTime", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"LaneDetectionWidth", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, @@ -374,6 +388,7 @@ std::unordered_map keys = { {"MapStyle", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"MapTargetLatA", PERSISTENT}, {"MapTargetVelocities", PERSISTENT}, + {"MinimumBackupSize", PERSISTENT}, {"MinimumLaneChangeSpeed", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"Model", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"ModelDownloadProgress", PERSISTENT}, @@ -393,7 +408,7 @@ std::unordered_map keys = { {"NewLongAPIGM", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VEHICLES}, {"NNFF", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"NNFFLite", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, - {"NNFFModelName", PERSISTENT}, + {"NNFFModelName", CLEAR_ON_OFFROAD_TRANSITION}, {"NoLogging", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"NorthDakotaCalibrationParams", PERSISTENT}, {"NorthDakotaDrives", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, @@ -421,13 +436,13 @@ std::unordered_map keys = { {"OSMDownloadBounds", PERSISTENT}, {"OSMDownloadLocations", PERSISTENT}, {"OSMDownloadProgress", CLEAR_ON_MANAGER_START}, - {"ParamConversionVersion", PERSISTENT}, {"PathEdgeWidth", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"PathWidth", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"PauseAOLOnBrake", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"PauseLateralOnSignal", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"PauseLateralSpeed", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"PedalsOnUI", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, + {"PersonalizeOpenpilot", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"PreferredSchedule", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_OTHER}, {"PreviousSpeedLimit", PERSISTENT}, {"PromptDistractedVolume", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, @@ -483,6 +498,7 @@ std::unordered_map keys = { {"Sidebar", PERSISTENT | FROGPILOT_OTHER}, {"SidebarMetrics", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"SignalMetrics", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, + {"SignalToDownload", PERSISTENT}, {"SLCConfirmation", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"SLCConfirmationHigher", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"SLCConfirmationLower", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, @@ -495,8 +511,8 @@ std::unordered_map keys = { {"SLCPriority1", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"SLCPriority2", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"SLCPriority3", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, - {"SmoothBraking", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"SNGHack", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VEHICLES}, + {"SoundToDownload", PERSISTENT}, {"SpeedLimitChangedAlert", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"SpeedLimitController", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"StandardFollow", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, @@ -505,6 +521,8 @@ std::unordered_map keys = { {"StandardJerkSpeed", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"StandardPersonalityProfile", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"StandbyMode", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, + {"StartupMessageBottom", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, + {"StartupMessageTop", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"StaticPedalsOnUI", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"SteerRatio", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"SteerRatioStock", PERSISTENT}, @@ -513,6 +531,7 @@ std::unordered_map keys = { {"StoppingDistance", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"TacoTune", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"TetheringEnabled", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_OTHER}, + {"ThemeDownloadProgress", PERSISTENT}, {"ToyotaDoors", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VEHICLES}, {"TrafficFollow", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"TrafficJerkAcceleration", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, @@ -526,7 +545,10 @@ std::unordered_map keys = { {"UnlimitedLength", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"UnlockDoors", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VEHICLES}, {"Updated", PERSISTENT}, + {"UpdateTheme", PERSISTENT}, + {"UpdateWheelImage", PERSISTENT}, {"UseSI", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, + {"UseStockColors", CLEAR_ON_MANAGER_START}, {"UseVienna", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"VisionTurnControl", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_CONTROLS}, {"VoltSNG", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VEHICLES}, @@ -538,6 +560,7 @@ std::unordered_map keys = { {"WD40Score", PERSISTENT | FROGPILOT_CONTROLS}, {"WheelIcon", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, {"WheelSpeed", PERSISTENT | FROGPILOT_STORAGE | FROGPILOT_VISUALS}, + {"WheelToDownload", PERSISTENT}, }; } // namespace diff --git a/opendbc/gm_global_a_powertrain_generated.dbc b/opendbc/gm_global_a_powertrain_generated.dbc index 3528e98d16749b..8de42d5989b8c5 100644 --- a/opendbc/gm_global_a_powertrain_generated.dbc +++ b/opendbc/gm_global_a_powertrain_generated.dbc @@ -221,6 +221,7 @@ BO_ 715 ASCMGasRegenCmd: 8 K124_ASCM SG_ GasRegenFullStopActive : 13|1@0+ (1,0) [0|0] "" NEO SG_ GasRegenCmdActive : 0|1@0+ (1,0) [0|0] "" NEO SG_ RollingCounter : 7|2@0+ (1,0) [0|0] "" NEO + SG_ GasRegenAlwaysOne3 : 23|1@0+ (1,0) [0|1] "" NEO SG_ GasRegenCmd : 8|14@0+ (1,0) [0|0] "" NEO BO_ 717 ASCM_2CD: 5 K124_ASCM diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index fa170f99b3edb6..4686aed3b16f95 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -94,6 +94,7 @@ def __init__(self, CI=None): # FrogPilot variables self.frogpilot_toggles = FrogPilotVariables.toggles + FrogPilotVariables.update_frogpilot_params() self.update_toggles = False diff --git a/selfdrive/car/ford/carcontroller.py b/selfdrive/car/ford/carcontroller.py index c89ab953d4df69..6773aa67207dc3 100644 --- a/selfdrive/car/ford/carcontroller.py +++ b/selfdrive/car/ford/carcontroller.py @@ -7,6 +7,8 @@ from openpilot.selfdrive.car.interfaces import CarControllerBase from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import get_max_allowed_accel + LongCtrlState = car.CarControl.Actuators.LongControlState VisualAlert = car.CarControl.HUDControl.VisualAlert @@ -95,7 +97,7 @@ def update(self, CC, CS, now_nanos, frogpilot_toggles): if self.CP.openpilotLongitudinalControl and (self.frame % CarControllerParams.ACC_CONTROL_STEP) == 0: # Both gas and accel are in m/s^2, accel is used solely for braking if frogpilot_toggles.sport_plus: - accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX_PLUS) + accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, get_max_allowed_accel(CS.out.vEgoRaw)) else: accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX) gas = accel diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index c1bfa579a04520..b1868bfa9bb97d 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -33,7 +33,6 @@ class CarControllerParams: CURVATURE_ERROR = 0.002 # ~6 degrees at 10 m/s, ~10 degrees at 35 m/s ACCEL_MAX = 2.0 # m/s^2 max acceleration - ACCEL_MAX_PLUS = 4.0 # m/s^2 max acceleration ACCEL_MIN = -3.5 # m/s^2 max deceleration MIN_GAS = -0.5 INACTIVE_GAS = -5.0 diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index aeaca3f82deca9..e2cfe7d6284f8c 100644 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -12,6 +12,8 @@ from openpilot.selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD, LatControlInputs, NanoFFModel from openpilot.selfdrive.controls.lib.drive_helpers import get_friction +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import get_max_allowed_accel + ButtonType = car.CarState.ButtonEvent.Type FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type EventName = car.CarEvent.EventName @@ -40,7 +42,7 @@ class CarInterface(CarInterfaceBase): @staticmethod def get_pid_accel_limits(CP, current_speed, cruise_speed, frogpilot_toggles): if frogpilot_toggles.sport_plus: - return CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX_PLUS + return CarControllerParams.ACCEL_MIN, get_max_allowed_accel(current_speed) else: return CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX @@ -113,6 +115,11 @@ def _get_params(ret, candidate, fingerprint, car_fw, disable_openpilot_long, exp else: ret.transmissionType = TransmissionType.automatic + if params.get_bool("ExperimentalGMTune"): + ret.stoppingDecelRate = 0.3 + ret.vEgoStopping = 0.15 + ret.vEgoStarting = 0.15 + if use_new_api: ret.longitudinalTuning.kiBP = [5., 35.] else: diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 60ea5f870bba7c..8e7a15b4831918 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -7,6 +7,8 @@ from openpilot.selfdrive.car.docs_definitions import CarHarness, CarDocs, CarParts from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import get_max_allowed_accel + Ecu = car.CarParams.Ecu @@ -33,7 +35,6 @@ class CarControllerParams: # Our controller should still keep the 2 second average above # -3.5 m/s^2 as per planner limits ACCEL_MAX = 2. # m/s^2 - ACCEL_MAX_PLUS = 4. # m/s^2 ACCEL_MIN = -4. # m/s^2 def __init__(self, CP): @@ -48,14 +49,14 @@ def __init__(self, CP): self.INACTIVE_REGEN = 5650 # Camera ACC vehicles have no regen while enabled. # Camera transitions to MAX_ACC_REGEN from ZERO_GAS and uses friction brakes instantly - max_regen_acceleration = 0. + self.max_regen_acceleration = 0. elif CP.carFingerprint in SDGM_CAR: self.MAX_GAS = 7496 self.MAX_GAS_PLUS = 7496 self.MAX_ACC_REGEN = 5610 self.INACTIVE_REGEN = 5650 - max_regen_acceleration = 0. + self.max_regen_acceleration = 0. else: self.MAX_GAS = 7168 # Safety limit, not ACC max. Stock ACC >8192 from standstill. @@ -64,14 +65,13 @@ def __init__(self, CP): self.INACTIVE_REGEN = 5500 # ICE has much less engine braking force compared to regen in EVs, # lower threshold removes some braking deadzone - max_regen_acceleration = -1. if CP.carFingerprint in EV_CAR else -0.1 + self.max_regen_acceleration = -1. if CP.carFingerprint in EV_CAR else -0.1 - self.GAS_LOOKUP_BP = [max_regen_acceleration, 0., self.ACCEL_MAX] - self.GAS_LOOKUP_BP_PLUS = [max_regen_acceleration, 0., self.ACCEL_MAX_PLUS] + self.GAS_LOOKUP_BP = [self.max_regen_acceleration, 0., self.ACCEL_MAX] self.GAS_LOOKUP_V = [self.MAX_ACC_REGEN, self.ZERO_GAS, self.MAX_GAS] self.GAS_LOOKUP_V_PLUS = [self.MAX_ACC_REGEN, self.ZERO_GAS, self.MAX_GAS_PLUS] - self.BRAKE_LOOKUP_BP = [self.ACCEL_MIN, max_regen_acceleration] + self.BRAKE_LOOKUP_BP = [self.ACCEL_MIN, self.max_regen_acceleration] self.BRAKE_LOOKUP_V = [self.MAX_BRAKE, 0.] # determined by letting Volt regen to a stop in L gear from 89mph, @@ -83,9 +83,10 @@ def __init__(self, CP): def update_ev_gas_brake_threshold(self, v_ego): gas_brake_threshold = interp(v_ego, self.EV_GAS_BRAKE_THRESHOLD_BP, self.EV_GAS_BRAKE_THRESHOLD_V) self.EV_GAS_LOOKUP_BP = [gas_brake_threshold, max(0., gas_brake_threshold), self.ACCEL_MAX] - self.EV_GAS_LOOKUP_BP_PLUS = [gas_brake_threshold, max(0., gas_brake_threshold), self.ACCEL_MAX_PLUS] + self.EV_GAS_LOOKUP_BP_PLUS = [gas_brake_threshold, max(0., gas_brake_threshold), get_max_allowed_accel(v_ego)] self.EV_BRAKE_LOOKUP_BP = [self.ACCEL_MIN, gas_brake_threshold] + self.GAS_LOOKUP_BP_PLUS = [self.max_regen_acceleration, 0., get_max_allowed_accel(v_ego)] @dataclass class GMCarDocs(CarDocs): diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py index d57567ca3ea180..2fec5878a87ab1 100644 --- a/selfdrive/car/honda/carcontroller.py +++ b/selfdrive/car/honda/carcontroller.py @@ -10,6 +10,8 @@ from openpilot.selfdrive.car.interfaces import CarControllerBase from openpilot.selfdrive.controls.lib.drive_helpers import rate_limit +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import get_max_allowed_accel + VisualAlert = car.CarControl.HUDControl.VisualAlert LongCtrlState = car.CarControl.Actuators.LongControlState @@ -217,7 +219,7 @@ def update(self, CC, CS, now_nanos, frogpilot_toggles): if self.CP.carFingerprint in HONDA_BOSCH: if frogpilot_toggles.sport_plus: - self.accel = clip(accel, self.params.BOSCH_ACCEL_MIN, self.params.BOSCH_ACCEL_MAX_PLUS) + self.accel = clip(accel, self.params.BOSCH_ACCEL_MIN, get_max_allowed_accel(CS.out.vEgo)) else: self.accel = clip(accel, self.params.BOSCH_ACCEL_MIN, self.params.BOSCH_ACCEL_MAX) self.gas = interp(accel, self.params.BOSCH_GAS_LOOKUP_BP, self.params.BOSCH_GAS_LOOKUP_V) diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 69ad577fd6062c..89433fe2f63230 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -10,6 +10,8 @@ from openpilot.selfdrive.car.interfaces import CarInterfaceBase from openpilot.selfdrive.car.disable_ecu import disable_ecu +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import get_max_allowed_accel + ButtonType = car.CarState.ButtonEvent.Type FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type @@ -25,12 +27,12 @@ class CarInterface(CarInterfaceBase): def get_pid_accel_limits(CP, current_speed, cruise_speed, frogpilot_toggles): if CP.carFingerprint in HONDA_BOSCH: if frogpilot_toggles.sport_plus: - return CarControllerParams.BOSCH_ACCEL_MIN, CarControllerParams.BOSCH_ACCEL_MAX_PLUS + return CarControllerParams.BOSCH_ACCEL_MIN, get_max_allowed_accel(current_speed) else: return CarControllerParams.BOSCH_ACCEL_MIN, CarControllerParams.BOSCH_ACCEL_MAX elif CP.enableGasInterceptor: if frogpilot_toggles.sport_plus: - return CarControllerParams.NIDEC_ACCEL_MIN, CarControllerParams.NIDEC_ACCEL_MAX_PLUS + return CarControllerParams.NIDEC_ACCEL_MIN, get_max_allowed_accel(current_speed) else: return CarControllerParams.NIDEC_ACCEL_MIN, CarControllerParams.NIDEC_ACCEL_MAX else: diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index 3410e495af8f08..7c2e3d8b3a03ed 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -20,7 +20,6 @@ class CarControllerParams: # -3.5 m/s^2 as per planner limits NIDEC_ACCEL_MIN = -4.0 # m/s^2 NIDEC_ACCEL_MAX = 1.6 # m/s^2, lower than 2.0 m/s^2 for tuning reasons - NIDEC_ACCEL_MAX_PLUS = 4.0 # m/s^2 NIDEC_ACCEL_LOOKUP_BP = [-1., 0., .6] NIDEC_ACCEL_LOOKUP_V = [-4.8, 0., 2.0] @@ -33,7 +32,6 @@ class CarControllerParams: BOSCH_ACCEL_MIN = -3.5 # m/s^2 BOSCH_ACCEL_MAX = 2.0 # m/s^2 - BOSCH_ACCEL_MAX_PLUS = 4.0 # m/s^2 BOSCH_GAS_LOOKUP_BP = [-0.2, 2.0] # 2m/s^2 BOSCH_GAS_LOOKUP_V = [0, 1600] diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 9ffbaa848fb8c7..93a9e4859738c2 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -9,6 +9,8 @@ from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CANFD_CAR, CAR from openpilot.selfdrive.car.interfaces import CarControllerBase +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import get_max_allowed_accel + VisualAlert = car.CarControl.HUDControl.VisualAlert LongCtrlState = car.CarControl.Actuators.LongControlState @@ -80,7 +82,7 @@ def update(self, CC, CS, now_nanos, frogpilot_toggles): # accel + longitudinal if frogpilot_toggles.sport_plus: - accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX_PLUS) + accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, get_max_allowed_accel(CS.out.vEgo)) else: accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX) stopping = actuators.longControlState == LongCtrlState.stopping diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 8587141b1d6822..3d9a8fc9d959cc 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -15,7 +15,6 @@ class CarControllerParams: ACCEL_MIN = -3.5 # m/s ACCEL_MAX = 2.0 # m/s - ACCEL_MAX_PLUS = 4.0 # m/s def __init__(self, CP): self.STEER_DELTA_UP = 3 diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index db0449b2f12b09..3b4b9f74f65a17 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -22,6 +22,8 @@ from openpilot.selfdrive.controls.lib.events import Events from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import get_max_allowed_accel + ButtonType = car.CarState.ButtonEvent.Type FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type GearShifter = car.CarState.GearShifter @@ -29,7 +31,6 @@ MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS ACCEL_MAX = 2.0 -ACCEL_MAX_PLUS = 4.0 ACCEL_MIN = -3.5 FRICTION_THRESHOLD = 0.3 @@ -266,7 +267,7 @@ def apply(self, c: car.CarControl, now_nanos: int, frogpilot_toggles) -> tuple[c @staticmethod def get_pid_accel_limits(CP, current_speed, cruise_speed, frogpilot_toggles): if frogpilot_toggles.sport_plus: - return ACCEL_MIN, ACCEL_MAX_PLUS + return ACCEL_MIN, get_max_allowed_accel(current_speed) else: return ACCEL_MIN, ACCEL_MAX diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index 9eeabca6a79231..cc2cbf1f587eaa 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -6,9 +6,11 @@ from openpilot.selfdrive.car.toyota import toyotacan from openpilot.selfdrive.car.toyota.values import CAR, STATIC_DSU_MSGS, NO_STOP_TIMER_CAR, TSS2_CAR, \ MIN_ACC_SPEED, PEDAL_TRANSITION, CarControllerParams, ToyotaFlags, \ - UNSUPPORTED_DSU_CAR, STOP_AND_GO_CAR, TSS2_CAR + UNSUPPORTED_DSU_CAR, STOP_AND_GO_CAR from opendbc.can.packer import CANPacker +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import get_max_allowed_accel + LongCtrlState = car.CarControl.Actuators.LongControlState SteerControlType = car.CarParams.SteerControlType VisualAlert = car.CarControl.HUDControl.VisualAlert @@ -38,15 +40,6 @@ COMPENSATORY_CALCULATION_THRESHOLD_V = [-0.3, -0.25, 0.] # m/s^2 COMPENSATORY_CALCULATION_THRESHOLD_BP = [0., 11., 23.] # m/s -def compute_gb_toyota(accel, speed): - creep_brake = 0.0 - creep_speed = 2.3 - creep_brake_value = 0.15 - if speed < creep_speed: - creep_brake = (creep_speed - speed) / creep_speed * creep_brake_value - gb = accel - creep_brake - return gb - class CarController(CarControllerBase): def __init__(self, dbc_name, CP, VM): self.CP = CP @@ -162,28 +155,27 @@ def update(self, CC, CS, now_nanos, frogpilot_toggles): self.prohibit_neg_calculation = False # limit minimum to only positive until first positive is reached after engagement, don't calculate when long isn't active - if CC.longActive and not self.prohibit_neg_calculation and (self.cydia_tune or self.frogs_go_moo_tune and self.CP.carFingerprint not in TSS2_CAR): + if CC.longActive and not self.prohibit_neg_calculation and self.cydia_tune: accel_offset = CS.pcm_neutral_force / self.CP.mass + elif CC.longActive and self.frogs_go_moo_tune: + accel_offset = min(CS.pcm_neutral_force / self.CP.mass, 0.0) + + if CS.out.cruiseState.standstill or actuators.longControlState == LongCtrlState.stopping: + self.pcm_accel_comp = 0.0 + else: + self.pcm_accel_comp = clip(actuators.accel - CS.pcm_accel_net, self.pcm_accel_comp - 0.01, self.pcm_accel_comp + 0.01) + + accel_offset += self.pcm_accel_comp else: accel_offset = 0. - - if CC.longActive and self.frogs_go_moo_tune: - wind_brake = interp(CS.out.vEgo, [0.0, 2.3, 35.0], [0.001, 0.002, 0.15]) - gas_accel = compute_gb_toyota(actuators.accel, CS.out.vEgo) + wind_brake - self.pcm_accel_comp = clip(gas_accel - CS.pcm_accel_net, self.pcm_accel_comp - 0.03, self.pcm_accel_comp + 0.03) + self.pcm_accel_comp = 0.0 # only calculate pcm_accel_cmd when long is active to prevent disengagement from accelerator depression if CC.longActive: if frogpilot_toggles.sport_plus: - if self.frogs_go_moo_tune: - pcm_accel_cmd = clip(gas_accel + self.pcm_accel_comp, self.params.ACCEL_MIN, self.params.ACCEL_MAX_PLUS) - else: - pcm_accel_cmd = clip(actuators.accel + accel_offset, self.params.ACCEL_MIN, self.params.ACCEL_MAX_PLUS) + pcm_accel_cmd = clip(actuators.accel + accel_offset, self.params.ACCEL_MIN, get_max_allowed_accel(CS.out.vEgo)) else: - if self.frogs_go_moo_tune: - pcm_accel_cmd = clip(gas_accel + self.pcm_accel_comp, self.params.ACCEL_MIN, self.params.ACCEL_MAX) - else: - pcm_accel_cmd = clip(actuators.accel + accel_offset, self.params.ACCEL_MIN, self.params.ACCEL_MAX) + pcm_accel_cmd = clip(actuators.accel + accel_offset, self.params.ACCEL_MIN, self.params.ACCEL_MAX) else: pcm_accel_cmd = 0. @@ -204,7 +196,7 @@ def update(self, CC, CS, now_nanos, frogpilot_toggles): if (self.frame % 3 == 0 and self.CP.openpilotLongitudinalControl) or pcm_cancel_cmd: lead = hud_control.leadVisible or CS.out.vEgo < 12. # at low speed we always assume the lead is present so ACC can be engaged # when stopping, send -2.5 raw acceleration immediately to prevent vehicle from creeping, else send actuators.accel - accel_raw = -2.5 if stopping and self.cydia_tune else actuators.accel + accel_raw = -2.5 if stopping and (self.cydia_tune or self.frogs_go_moo_tune) else actuators.accel # Press distance button until we are at the correct bar length. Only change while enabled to avoid skipping startup popup if self.frame % 6 == 0 and self.CP.openpilotLongitudinalControl: diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index 4faa40019b0b07..98dea943f78cca 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -223,8 +223,7 @@ def update(self, cp, cp_cam, frogpilot_toggles): if self.CP.carFingerprint != CAR.TOYOTA_PRIUS_V: self.lkas_previously_enabled = self.lkas_enabled - message_keys = ["LDA_ON_MESSAGE", "SET_ME_X02"] - self.lkas_enabled = any(self.lkas_hud.get(key) == 1 for key in message_keys) + self.lkas_enabled = self.lkas_hud.get("LDA_ON_MESSAGE") == 1 self.pcm_accel_net = cp.vl["PCM_CRUISE"]["ACCEL_NET"] self.pcm_neutral_force = cp.vl["PCM_CRUISE"]["NEUTRAL_FORCE"] diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 847c17aeb74734..c00a7e9e401c8d 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -7,6 +7,8 @@ from openpilot.selfdrive.car.disable_ecu import disable_ecu from openpilot.selfdrive.car.interfaces import CarInterfaceBase +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import get_max_allowed_accel + ButtonType = car.CarState.ButtonEvent.Type FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type EventName = car.CarEvent.EventName @@ -17,7 +19,7 @@ class CarInterface(CarInterfaceBase): @staticmethod def get_pid_accel_limits(CP, current_speed, cruise_speed, frogpilot_toggles): if frogpilot_toggles.sport_plus: - return CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX_PLUS + return CarControllerParams.ACCEL_MIN, get_max_allowed_accel(current_speed) else: return CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX @@ -136,21 +138,21 @@ def _get_params(ret, candidate, fingerprint, car_fw, disable_openpilot_long, exp ret.minEnableSpeed = -1. if (candidate in STOP_AND_GO_CAR or ret.enableGasInterceptor) else MIN_ACC_SPEED tune = ret.longitudinalTuning - if params.get_bool("CydiaTune") or params.get_bool("FrogsGoMooTune"): - ret.stopAccel = -2.5 # on stock Toyota this is -2.5 - ret.stoppingDecelRate = 0.3 # reach stopping target smoothly + if params.get_bool("CydiaTune"): + ret.stopAccel = -2.5 # on stock Toyota this is -2.5 + ret.stoppingDecelRate = 0.3 # reach stopping target smoothly if candidate in TSS2_CAR or ret.enableGasInterceptor: - tune.kpV = [0.0] tune.kiV = [0.5] - if params.get_bool("FrogsGoMooTune"): - ret.vEgoStopping = 0.15 - ret.vEgoStarting = 0.15 - else: - ret.vEgoStopping = 0.25 - ret.vEgoStarting = 0.25 + ret.vEgoStopping = 0.25 + ret.vEgoStarting = 0.25 else: - tune.kpV = [0.0] - tune.kiV = [1.2] # appears to produce minimal oscillation on TSS-P + tune.kiV = [1.2] # appears to produce minimal oscillation on TSS-P + elif params.get_bool("FrogsGoMooTune"): + ret.stopAccel = -2.5 # on stock Toyota this is -2.5 + ret.stoppingDecelRate = 0.1 # reach stopping target smoothly + ret.vEgoStarting = 0.15 + ret.vEgoStopping = 0.15 + tune.kiV = [1.0] elif candidate in TSS2_CAR or ret.enableGasInterceptor: tune.kpV = [0.0] tune.kiV = [0.5] diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 06feac6c8af04c..b441e6f727ce10 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -17,7 +17,6 @@ class CarControllerParams: ACCEL_MAX = 1.5 # m/s2, lower than allowed 2.0 m/s2 for tuning reasons - ACCEL_MAX_PLUS = 4.0 # m/s2 ACCEL_MIN = -3.5 # m/s2 STEER_STEP = 1 diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py index fe659b1ba492f3..9568b51dcffc21 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -8,6 +8,8 @@ from openpilot.selfdrive.car.volkswagen import mqbcan, pqcan from openpilot.selfdrive.car.volkswagen.values import CANBUS, CarControllerParams, VolkswagenFlags +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import get_max_allowed_accel + VisualAlert = car.CarControl.HUDControl.VisualAlert LongCtrlState = car.CarControl.Actuators.LongControlState @@ -81,7 +83,7 @@ def update(self, CC, CS, now_nanos, frogpilot_toggles): if self.frame % self.CCP.ACC_CONTROL_STEP == 0 and self.CP.openpilotLongitudinalControl: acc_control = self.CCS.acc_control_value(CS.out.cruiseState.available, CS.out.accFaulted, CC.longActive) if frogpilot_toggles.sport_plus: - accel = clip(actuators.accel, self.CCP.ACCEL_MIN, self.CCP.ACCEL_MAX_PLUS) if CC.longActive else 0 + accel = clip(actuators.accel, self.CCP.ACCEL_MIN, get_max_allowed_accel(CS.out.vEgo)) if CC.longActive else 0 else: accel = clip(actuators.accel, self.CCP.ACCEL_MIN, self.CCP.ACCEL_MAX) if CC.longActive else 0 stopping = actuators.longControlState == LongCtrlState.stopping diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index d0be20a49db794..8b58769b3fd87a 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -37,7 +37,6 @@ class CarControllerParams: DEFAULT_MIN_STEER_SPEED = 0.4 # m/s, newer EPS racks fault below this speed, don't show a low speed alert ACCEL_MAX = 2.0 # 2.0 m/s max acceleration - ACCEL_MAX_PLUS = 4.0 # 4.0 m/s max acceleration ACCEL_MIN = -3.5 # 3.5 m/s max deceleration def __init__(self, CP): diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 70df9f6c57443c..e7ff0fcf05c42b 100644 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -1,13 +1,11 @@ #!/usr/bin/env python3 import os import math -import random import time import threading from typing import SupportsFloat import cereal.messaging as messaging -import openpilot.system.sentry as sentry from cereal import car, custom, log from msgq.visionipc import VisionIpcClient, VisionStreamType @@ -22,7 +20,7 @@ from openpilot.selfdrive.car.car_helpers import get_car_interface, get_startup_event from openpilot.selfdrive.controls.lib.alertmanager import AlertManager, set_offroad_alert -from openpilot.selfdrive.controls.lib.drive_helpers import IMPERIAL_INCREMENT, VCruiseHelper, clip_curvature +from openpilot.selfdrive.controls.lib.drive_helpers import VCruiseHelper, clip_curvature from openpilot.selfdrive.controls.lib.events import Events, ET from openpilot.selfdrive.controls.lib.latcontrol import LatControl, MIN_LATERAL_CONTROL_SPEED from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID @@ -33,8 +31,7 @@ from openpilot.system.hardware import HARDWARE -from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import CRUISING_SPEED, FrogPilotVariables -from openpilot.selfdrive.frogpilot.controls.lib.speed_limit_controller import SpeedLimitController +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import FrogPilotVariables SOFT_DISABLE_TIME = 3 # seconds LDW_MIN_SPEED = 31 * CV.MPH_TO_MS @@ -55,7 +52,6 @@ EventName = car.CarEvent.EventName ButtonType = car.CarState.ButtonEvent.Type FrogPilotButtonType = custom.FrogPilotCarState.ButtonEvent.Type -GearShifter = car.CarState.GearShifter SafetyModel = car.CarParams.SafetyModel IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput) @@ -68,7 +64,6 @@ class Controls: def __init__(self, CI=None): self.params = Params() - self.params_memory = Params("/dev/shm/params") if CI is None: cloudlog.info("controlsd is waiting for CarParams") @@ -186,40 +181,22 @@ def __init__(self, CI=None): # FrogPilot variables self.frogpilot_toggles = FrogPilotVariables.toggles + FrogPilotVariables.update_frogpilot_params() - self.params_tracking = Params("/persist/tracking") - - self.total_drives = self.params_tracking.get_int("FrogPilotDrives") - self.total_kilometers = self.params_tracking.get_float("FrogPilotKilometers") - self.total_minutes = self.params_tracking.get_float("FrogPilotMinutes") + self.params_memory = Params("/dev/shm/params") self.always_on_lateral_active = False - self.drive_added = False - self.fcw_random_event_triggered = False - self.holiday_theme_alerted = False - self.no_entry_alert_played = False + self.fcw_event_triggered = False self.no_entry_alert_triggered = False self.onroad_distance_pressed = False - self.openpilot_crashed_triggered = False - self.previous_traffic_mode = False - self.random_event_triggered = False self.resume_pressed = False self.resume_previously_pressed = False - self.speed_check = False - self.speed_limit_changed = False - self.stopped_for_light = False + self.steer_saturated_event_triggered = False self.update_toggles = False self.use_old_long = self.CP.carName == "hyundai" and not self.params.get_bool("NewLongAPI") self.use_old_long |= self.CP.carName == "gm" and not self.params.get_bool("NewLongAPIGM") self.display_timer = 0 - self.drive_distance = 0 - self.drive_time = 0 - self.max_acceleration = 0 - self.previous_speed_limit = 0 - self.previous_v_cruise = 0 - self.random_event_timer = 0 - self.speed_limit_timer = 0 def set_initial_state(self): if REPLAY: @@ -421,11 +398,7 @@ def update_events(self, CS): planner_fcw = self.sm['longitudinalPlan'].fcw and self.enabled if (planner_fcw or model_fcw) and not (self.CP.notCar and self.joystick_mode): self.events.add(EventName.fcw) - self.fcw_random_event_triggered = not self.random_event_triggered - elif self.fcw_random_event_triggered and self.frogpilot_toggles.random_events: - self.events.add(EventName.yourFrogTriedToKillMe) - self.fcw_random_event_triggered = False - self.random_event_triggered = True + self.fcw_event_triggered = True for m in messaging.drain_sock(self.log_sock, wait_for_one=False): try: @@ -450,8 +423,8 @@ def update_events(self, CS): if self.sm['modelV2'].frameDropPerc > 20: self.events.add(EventName.modeldLagging) - # Update FrogPilot events - self.update_frogpilot_events(CS, self.sm['frogpilotCarState'], self.sm['frogpilotPlan']) + # Add FrogPilot events + self.events.add_from_msg(self.sm['frogpilotPlan'].frogpilotEvents) def data_sample(self): """Receive data from sockets""" @@ -502,7 +475,7 @@ def data_sample(self): 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.speed_limit_changed, self.frogpilot_toggles) + self.v_cruise_helper.update_v_cruise(CS, self.enabled, self.is_metric, self.sm['frogpilotPlan'].speedLimitChanged, self.frogpilot_toggles) # decrement the soft disable timer at every step, as it's reset on # entrance in SOFT_DISABLING state @@ -582,8 +555,8 @@ def state_transition(self, CS): # Check if openpilot is engaged and actuators are enabled self.enabled = self.state in ENABLED_STATES - self.active = self.state in ACTIVE_STATES - if self.active or self.always_on_lateral_active: + self.active = self.state in ACTIVE_STATES or self.always_on_lateral_active + if self.active: self.current_alert_types.append(ET.WARNING) def state_control(self, CS): @@ -610,8 +583,8 @@ def state_control(self, CS): # Check which actuators can be enabled standstill = CS.vEgo <= max(self.CP.minSteerSpeed, MIN_LATERAL_CONTROL_SPEED) or CS.standstill - CC.latActive = (self.active or self.always_on_lateral_active) and self.speed_check and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \ - (not standstill or self.joystick_mode) + CC.latActive = self.active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \ + (not standstill or self.joystick_mode) and self.sm['frogpilotPlan'].lateralCheck CC.longActive = self.enabled and not self.events.contains(ET.OVERRIDE_LONGITUDINAL) and self.CP.openpilotLongitudinalControl actuators = CC.actuators @@ -688,18 +661,11 @@ def state_control(self, CS): turning = abs(lac_log.desiredLateralAccel) > 1.0 good_speed = CS.vEgo > 5 max_torque = abs(self.sm['carOutput'].actuatorsOutput.steer) > 0.99 - if undershooting and turning and good_speed and max_torque and not self.random_event_triggered: - event_choices = [1, 2] - if self.sm.frame % (10000 // len(event_choices)) == 0 and self.frogpilot_toggles.random_events: - event_choice = random.choice(event_choices) - if event_choice == 1: - lac_log.active and self.events.add(EventName.firefoxSteerSaturated) - self.params_memory.put_int("CurrentRandomEvent", 1) - elif event_choice == 2: - lac_log.active and self.events.add(EventName.goatSteerSaturated) - self.random_event_triggered = True - else: - lac_log.active and self.events.add(EventName.goatSteerSaturated if self.frogpilot_toggles.goat_scream else EventName.steerSaturated) + if undershooting and turning and good_speed and max_torque: + lac_log.active and self.events.add(EventName.goatSteerSaturated if self.frogpilot_toggles.goat_scream else EventName.steerSaturated) + self.steer_saturated_event_triggered = True + else: + self.steer_saturated_event_triggered = False elif lac_log.saturated: # TODO probably should not use dpath_points but curvature dpath_points = model_v2.position.y @@ -739,10 +705,37 @@ def state_control(self, CS): self.display_timer -= 1 - FPCC = self.update_frogpilot_variables(CS, self.sm['frogpilotCarState'], self.sm['frogpilotPlan']) + FPCC = self.update_frogpilot_variables(CS) return CC, lac_log, FPCC + def update_frogpilot_variables(self, CS): + self.always_on_lateral_active = self.sm['frogpilotPlan'].alwaysOnLateralActive and self.sm.all_checks(['frogpilotPlan']) + if self.frogpilot_toggles.conditional_experimental_mode: + self.experimental_mode = self.sm['frogpilotPlan'].experimentalMode + + if any(be.pressed and be.type == FrogPilotButtonType.lkas for be in CS.buttonEvents): + if self.frogpilot_toggles.experimental_mode_via_lkas and self.enabled: + if self.frogpilot_toggles.conditional_experimental_mode: + conditional_status = self.params_memory.get_int("CEStatus") + override_value = 0 if conditional_status in {1, 2, 3, 4, 5, 6} else 3 if conditional_status >= 7 else 4 + self.params_memory.put_int("CEStatus", override_value) + else: + self.experimental_mode = not self.experimental_mode + self.params.put_bool_nonblocking("ExperimentalMode", self.experimental_mode) + + if self.sm.frame % 10 == 0 or self.resume_pressed: + self.resume_previously_pressed = self.resume_pressed + + FPCC = custom.FrogPilotCarControl.new_message() + FPCC.alwaysOnLateralActive = self.always_on_lateral_active + FPCC.fcwEventTriggered = self.fcw_event_triggered + FPCC.noEntryEventTriggered = self.no_entry_alert_triggered + FPCC.resumePressed = self.resume_previously_pressed + FPCC.steerSaturatedEventTriggered = self.steer_saturated_event_triggered + + return FPCC + def publish_logs(self, CS, start_time, CC, lac_log, FPCC): """Send actuators and hud commands to the car, send controlsstate and MPC logging""" @@ -922,8 +915,7 @@ def read_personality_param(self): def params_thread(self, evt): while not evt.is_set(): self.is_metric = self.params.get_bool("IsMetric") - if self.CP.openpilotLongitudinalControl and not self.frogpilot_toggles.conditional_experimental_mode: - self.experimental_mode = self.params.get_bool("ExperimentalMode") or self.frogpilot_toggles.speed_limit_controller and SpeedLimitController.experimental_mode + self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl self.personality = self.read_personality_param() if self.CP.notCar: self.joystick_mode = self.params.get_bool("JoystickDebugMode") @@ -932,7 +924,7 @@ def params_thread(self, evt): # Update FrogPilot parameters if FrogPilotVariables.toggles_updated: self.update_toggles = True - elif self.update_toggles or self.sm.frame * DT_CTRL < 1: # Force updates at first to check the current state of "Always On Lateral" and holiday theme + elif self.update_toggles: FrogPilotVariables.update_frogpilot_params() self.update_toggles = False @@ -948,197 +940,6 @@ def controlsd_thread(self): e.set() t.join() - def update_frogpilot_events(self, CS, frogpilotCarState, frogpilotPlan): - if frogpilotPlan.forcingStop: - self.events.add(EventName.forcingStop) - - if self.frogpilot_toggles.green_light_alert and not self.sm['longitudinalPlan'].hasLead and CS.standstill: - if frogpilotPlan.greenLight and self.stopped_for_light: - self.events.add(EventName.greenLight) - self.stopped_for_light = frogpilotPlan.redLight - else: - self.stopped_for_light = False - - if not self.holiday_theme_alerted and self.frogpilot_toggles.current_holiday_theme != 0 and self.sm.frame * DT_CTRL >= 10: - self.events.add(EventName.holidayActive) - self.holiday_theme_alerted = True - - if frogpilotPlan.leadDeparting: - self.events.add(EventName.leadDeparting) - - if not self.openpilot_crashed_triggered and os.path.isfile(os.path.join(sentry.CRASHES_DIR, 'error.txt')): - if self.frogpilot_toggles.random_events: - self.events.add(EventName.openpilotCrashedRandomEvent) - else: - self.events.add(EventName.openpilotCrashed) - self.openpilot_crashed_triggered = True - - if self.frogpilot_toggles.random_events and not self.random_event_triggered: - acceleration = CS.aEgo - - if not CS.gasPressed: - self.max_acceleration = max(acceleration, self.max_acceleration) - else: - self.max_acceleration = 0 - - if 3.5 > self.max_acceleration >= 3.0 and acceleration < 1.5: - self.events.add(EventName.accel30) - self.params_memory.put_int("CurrentRandomEvent", 2) - self.random_event_triggered = True - self.max_acceleration = 0 - - elif 4.0 > self.max_acceleration >= 3.5 and acceleration < 1.5: - self.events.add(EventName.accel35) - self.params_memory.put_int("CurrentRandomEvent", 3) - self.random_event_triggered = True - self.max_acceleration = 0 - - elif self.max_acceleration >= 4.0 and acceleration < 1.5: - self.events.add(EventName.accel40) - self.params_memory.put_int("CurrentRandomEvent", 4) - self.random_event_triggered = True - self.max_acceleration = 0 - - if frogpilotPlan.takingCurveQuickly: - self.events.add(EventName.dejaVuCurve) - self.params_memory.put_int("CurrentRandomEvent", 5) - self.random_event_triggered = True - - if self.no_entry_alert_triggered and not self.no_entry_alert_played: - self.events.add(EventName.hal9000) - self.no_entry_alert_played = True - self.random_event_triggered = True - - conversion = 1 if self.is_metric else IMPERIAL_INCREMENT - v_cruise = max(self.v_cruise_helper.v_cruise_kph, self.v_cruise_helper.v_cruise_cluster_kph) * conversion - - if 70 > v_cruise >= 69 and v_cruise != self.previous_v_cruise: - self.events.add(EventName.vCruise69) - self.random_event_triggered = True - self.previous_v_cruise = v_cruise - - if self.frogpilot_toggles.speed_limit_alert and self.speed_limit_changed: - self.events.add(EventName.speedLimitChanged) - - if self.sm.frame * DT_CTRL == 5.5 and self.CP.lateralTuning.which() == "torque" and self.CI.use_nnff: - self.events.add(EventName.torqueNNLoad) - - if frogpilotCarState.trafficModeActive != self.previous_traffic_mode: - if self.previous_traffic_mode: - self.events.add(EventName.trafficModeInactive) - else: - self.events.add(EventName.trafficModeActive) - self.previous_traffic_mode = frogpilotCarState.trafficModeActive - - if self.sm['modelV2'].meta.turnDirection == Desire.turnLeft: - self.events.add(EventName.turningLeft) - elif self.sm['modelV2'].meta.turnDirection == Desire.turnRight: - self.events.add(EventName.turningRight) - - def update_frogpilot_variables(self, CS, frogpilotCarState, frogpilotPlan): - driving_gear = CS.gearShifter not in (GearShifter.neutral, GearShifter.park, GearShifter.reverse, GearShifter.unknown) - - self.always_on_lateral_active |= self.frogpilot_toggles.always_on_lateral_main or CS.cruiseState.enabled - self.always_on_lateral_active &= self.frogpilot_toggles.always_on_lateral and CS.cruiseState.available - self.always_on_lateral_active &= driving_gear - self.always_on_lateral_active &= self.speed_check - self.always_on_lateral_active &= not (self.frogpilot_toggles.always_on_lateral_lkas and frogpilotCarState.alwaysOnLateralDisabled) - self.always_on_lateral_active &= not (CS.brakePressed and CS.vEgo < self.frogpilot_toggles.always_on_lateral_pause_speed) or CS.standstill - - self.drive_distance += CS.vEgo * DT_CTRL - self.drive_time += DT_CTRL - - if self.drive_time > 60 and CS.standstill: - self.total_kilometers += self.drive_distance / 1000 - self.params_tracking.put_float_nonblocking("FrogPilotKilometers", self.total_kilometers) - self.drive_distance = 0 - - self.total_minutes += self.drive_time / 60 - self.params_tracking.put_float_nonblocking("FrogPilotMinutes", self.total_minutes) - self.drive_time = 0 - - if not self.drive_added and self.sm.frame * DT_CTRL > 60 * 15: - self.total_drives += 1 - self.params_tracking.put_int_nonblocking("FrogPilotDrives", self.total_drives) - self.drive_added = True - - if self.frogpilot_toggles.conditional_experimental_mode: - self.experimental_mode = frogpilotPlan.conditionalExperimentalActive - - if any(be.pressed and be.type == FrogPilotButtonType.lkas for be in CS.buttonEvents): - if self.frogpilot_toggles.experimental_mode_via_lkas and self.enabled: - if self.frogpilot_toggles.conditional_experimental_mode: - conditional_status = self.params_memory.get_int("CEStatus") - override_value = 0 if conditional_status in {1, 2, 3, 4, 5, 6} else 3 if conditional_status >= 7 else 4 - self.params_memory.put_int("CEStatus", override_value) - else: - self.experimental_mode = not self.experimental_mode - self.params.put_bool_nonblocking("ExperimentalMode", self.experimental_mode) - - if self.random_event_triggered: - self.random_event_timer += DT_CTRL - if self.random_event_timer >= 4: - self.random_event_triggered = False - self.random_event_timer = 0 - self.params_memory.remove("CurrentRandomEvent") - - if self.sm.frame % 10 == 0 or self.resume_pressed: - self.resume_previously_pressed = self.resume_pressed - - self.speed_check = CS.vEgo >= self.frogpilot_toggles.pause_lateral_below_speed - self.speed_check |= self.frogpilot_toggles.pause_lateral_below_signal and not (CS.leftBlinker or CS.rightBlinker) - self.speed_check |= CS.standstill - - if self.frogpilot_toggles.speed_limit_confirmation: - current_speed_limit = frogpilotPlan.slcSpeedLimit - desired_speed_limit = frogpilotPlan.unconfirmedSlcSpeedLimit - - limit_changed = desired_speed_limit != self.previous_speed_limit and abs(current_speed_limit - desired_speed_limit) > 1 - - speed_limit_decreased = limit_changed and self.previous_speed_limit > desired_speed_limit - speed_limit_increased = limit_changed and self.previous_speed_limit < desired_speed_limit - - self.previous_speed_limit = desired_speed_limit - - if self.CP.pcmCruise and self.speed_limit_changed: - if self.resume_pressed: - self.params_memory.put_bool("SLCConfirmed", True) - self.params_memory.put_bool("SLCConfirmedPressed", True) - elif any(be.type == ButtonType.decelCruise for be in CS.buttonEvents): - self.params_memory.put_bool("SLCConfirmed", False) - self.params_memory.put_bool("SLCConfirmedPressed", True) - - if speed_limit_decreased: - if self.frogpilot_toggles.speed_limit_confirmation_lower: - self.speed_limit_changed = True - else: - self.params_memory.put_bool("SLCConfirmed", True) - elif speed_limit_increased: - if self.frogpilot_toggles.speed_limit_confirmation_higher: - self.speed_limit_changed = True - else: - self.params_memory.put_bool("SLCConfirmed", True) - - if self.params_memory.get_bool("SLCConfirmedPressed") or not abs(current_speed_limit - desired_speed_limit) > 1: - self.speed_limit_changed = False - self.params_memory.put_bool("SLCConfirmedPressed", False) - - if self.speed_limit_changed: - self.speed_limit_timer += DT_CTRL - if self.speed_limit_timer >= 10: - self.speed_limit_changed = False - self.speed_limit_timer = 0 - else: - self.speed_limit_timer = 0 - else: - self.speed_limit_changed = False - - FPCC = custom.FrogPilotCarControl.new_message() - FPCC.alwaysOnLateral = self.always_on_lateral_active - FPCC.resumePressed = self.resume_pressed or self.resume_previously_pressed - FPCC.speedLimitChanged = self.speed_limit_changed - - return FPCC def main(): config_realtime_process(4, Priority.CTRL_HIGH) diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 9c3ac1e6c32e88..ce764fb09301e4 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -46,9 +46,6 @@ def __init__(self, CP): self.button_timers = {ButtonType.decelCruise: 0, ButtonType.accelCruise: 0} self.button_change_states = {btn: {"standstill": False, "enabled": False} for btn in self.button_timers} - # FrogPilot variables - self.params_memory = Params("/dev/shm/params") - @property def v_cruise_initialized(self): return self.v_cruise_kph != V_CRUISE_UNSET @@ -96,16 +93,9 @@ def _update_v_cruise_non_pcm(self, CS, enabled, is_metric, speed_limit_changed, if button_type is None: return - # Confirm or deny the new speed limit value + # Don't adjust speed when pressing to confirm/deny speed limits if speed_limit_changed: - if button_type == ButtonType.accelCruise: - self.params_memory.put_bool("SLCConfirmed", True) - self.params_memory.put_bool("SLCConfirmedPressed", True) - return - elif button_type == ButtonType.decelCruise: - self.params_memory.put_bool("SLCConfirmed", False) - self.params_memory.put_bool("SLCConfirmedPressed", True) - return + return # Don't adjust speed when pressing resume to exit standstill cruise_standstill = self.button_change_states[button_type]["standstill"] or CS.cruiseState.standstill diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index 6780ba3d87c63d..d6f5bd495e0223 100755 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -13,9 +13,6 @@ from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER -params = Params() -params_memory = Params("/dev/shm/params") - AlertSize = log.ControlsState.AlertSize AlertStatus = log.ControlsState.AlertStatus VisualAlert = car.CarControl.HUDControl.VisualAlert @@ -229,11 +226,13 @@ def func(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: b return func def startup_master_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: + params = Params() + branch = get_short_branch() # Ensure get_short_branch is cached to avoid lags on startup if "REPLAY" in os.environ: branch = "replay" - return StartupAlert("Hippity hoppity this is my property", "so I do what I want 🐸", alert_status=AlertStatus.frogpilot) + return StartupAlert(params.get("StartupMessageTop", encoding='utf-8'), params.get("StartupMessageBottom", encoding='utf-8'), alert_status=AlertStatus.frogpilot) def below_engage_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: return NoEntryAlert(f"Drive above {get_display_speed(CP.minEnableSpeed, metric)} to engage") @@ -257,7 +256,7 @@ def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messag def torque_nn_load_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: - model_name = params.get("NNFFModelName", encoding='utf-8') + model_name = Params().get("NNFFModelName", encoding='utf-8') if model_name == "": return Alert( "NNFF Torque Controller not available", @@ -348,21 +347,21 @@ def joystick_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, # FrogPilot Alerts def holiday_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: holiday_messages = { - 1: ("Happy April Fool's Day! 🤡", "aprilFoolsAlert"), - 2: ("Merry Christmas! 🎄", "christmasAlert"), - 3: ("¡Feliz Cinco de Mayo! 🌮", "cincoDeMayoAlert"), - 4: ("Happy Easter! 🐰", "easterAlert"), - 5: ("Happy Fourth of July! 🎆", "fourthOfJulyAlert"), - 6: ("Happy Halloween! 🎃", "halloweenAlert"), - 7: ("Happy New Year! 🎉", "newYearsDayAlert"), - 8: ("Happy St. Patrick's Day! 🍀", "stPatricksDayAlert"), - 9: ("Happy Thanksgiving! 🦃", "thanksgivingAlert"), - 10: ("Happy Valentine's Day! ❤️", "valentinesDayAlert"), - 11: ("Happy World Frog Day! 🐸", "worldFrogDayAlert"), + "new_years": ("Happy New Year! 🎉", "newYearsDayAlert"), + "valentines": ("Happy Valentine's Day! ❤️", "valentinesDayAlert"), + "st_patricks": ("Happy St. Patrick's Day! 🍀", "stPatricksDayAlert"), + "world_frog_day": ("Happy World Frog Day! 🐸", "worldFrogDayAlert"), + "april_fools": ("Happy April Fool's Day! 🤡", "aprilFoolsAlert"), + "easter_week": ("Happy Easter! 🐰", "easterAlert"), + "cinco_de_mayo": ("¡Feliz Cinco de Mayo! 🌮", "cincoDeMayoAlert"), + "fourth_of_july": ("Happy Fourth of July! 🎆", "fourthOfJulyAlert"), + "halloween_week": ("Happy Halloween! 🎃", "halloweenAlert"), + "thanksgiving_week": ("Happy Thanksgiving! 🦃", "thanksgivingAlert"), + "christmas_week": ("Merry Christmas! 🎄", "christmasAlert") } - theme_id = params_memory.get_int("CurrentHolidayTheme") - message, alert_type = holiday_messages.get(theme_id, ("", "")) + holiday_name = Params().get("CurrentHolidayTheme", encoding='utf-8') + message, alert_type = holiday_messages.get(holiday_name, ("", "")) return Alert( message, diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index 0dc7753bda5f07..160dbc3937116f 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -115,6 +115,9 @@ def update(self, active, CS, a_target, should_stop, accel_limits): if output_accel > self.CP.stopAccel: output_accel = min(output_accel, 0.0) output_accel -= self.CP.stoppingDecelRate * DT_CTRL + elif output_accel < self.CP.stopAccel: + output_accel = min(output_accel, 0.0) + output_accel += self.CP.stoppingDecelRate * DT_CTRL self.reset() elif self.long_control_state == LongCtrlState.starting: diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 6c72daabf84fca..f4c8763b3bae88 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -299,7 +299,7 @@ def set_cost_weights(self, cost_weights, constraint_cost_weights): for i in range(N): self.solver.cost_set(i, 'Zl', Zl) - def set_weights(self, acceleration_jerk=1.0, danger_jerk = 1.0, speed_jerk=1.0, prev_accel_constraint=True, personality=log.LongitudinalPersonality.standard): + def set_weights(self, acceleration_jerk=1.0, danger_jerk=1.0, speed_jerk=1.0, prev_accel_constraint=True, personality=log.LongitudinalPersonality.standard): if self.mode == 'acc': a_change_cost = acceleration_jerk if prev_accel_constraint else 0 cost_weights = [X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST, a_change_cost, speed_jerk] diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index b41379e0ab34d3..1bbaebb51efbc4 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -174,10 +174,8 @@ def parse_model(model_msg, model_error, v_ego, taco_tune): return x, v, a, j - def update(self, sm, frogpilot_toggles): - self.secret_good_openpilot = frogpilot_toggles.secretgoodopenpilot_model - - self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc' + def update(self, clairvoyant_model, e2e_longitudinal_model, sm, frogpilot_toggles): + self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode and not clairvoyant_model else 'acc' v_ego = sm['carState'].vEgo v_cruise_kph = min(sm['controlsState'].vCruise, V_CRUISE_MAX) @@ -206,7 +204,7 @@ def update(self, sm, frogpilot_toggles): # Prevent divergence, smooth in current v_ego self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego)) # Compute model v_ego error - self.v_model_error = 0. if self.secret_good_openpilot else get_speed_error(sm['modelV2'], v_ego) + self.v_model_error = 0.0 if e2e_longitudinal_model else get_speed_error(sm['modelV2'], v_ego) if force_slow_decel: v_cruise = 0.0 @@ -250,7 +248,7 @@ def update(self, sm, frogpilot_toggles): self.a_desired = float(interp(self.dt, CONTROL_N_T_IDX, self.a_desired_trajectory)) self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.a_desired + a_prev) / 2.0 - def publish(self, sm, pm): + def publish(self, e2e_longitudinal_model, sm, pm): plan_send = messaging.new_message('longitudinalPlan') plan_send.valid = sm.all_checks(service_list=['carState', 'controlsState']) @@ -272,15 +270,21 @@ def publish(self, sm, pm): longitudinalPlan.longitudinalPlanSource = self.mpc.source longitudinalPlan.fcw = self.fcw - a_target, should_stop = get_accel_from_plan(self.CP, longitudinalPlan.speeds, longitudinalPlan.accels) - if self.secret_good_openpilot and sm['controlsState'].experimentalMode: + a_target_mpc, should_stop_mpc = get_accel_from_plan(self.CP, longitudinalPlan.speeds, longitudinalPlan.accels) + + if e2e_longitudinal_model and sm['controlsState'].experimentalMode: model_speeds = np.interp(CONTROL_N_T_IDX, ModelConstants.T_IDXS, sm['modelV2'].velocity.x) model_accels = np.interp(CONTROL_N_T_IDX, ModelConstants.T_IDXS, sm['modelV2'].acceleration.x) a_target_model, should_stop_model = get_accel_from_plan(self.CP, model_speeds, model_accels) - a_target = min(a_target, a_target_model) - should_stop |= should_stop_model + a_target = min(a_target_mpc, a_target_model) + should_stop = should_stop_mpc or should_stop_model + else: + a_target = a_target_mpc + should_stop = should_stop_mpc + longitudinalPlan.aTarget = float(a_target) longitudinalPlan.shouldStop = bool(should_stop) + longitudinalPlan.allowBrake = True longitudinalPlan.allowThrottle = True diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 88d09cc55e7e0c..208da6c8186d85 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -35,14 +35,18 @@ def plannerd_thread(): # FrogPilot variables frogpilot_toggles = FrogPilotVariables.toggles + FrogPilotVariables.update_frogpilot_params() + + clairvoyant_model = frogpilot_toggles.clairvoyant_model + e2e_longitudinal_model = clairvoyant_model or frogpilot_toggles.secretgoodopenpilot_model update_toggles = False while True: sm.update() if sm.updated['modelV2']: - longitudinal_planner.update(sm, frogpilot_toggles) - longitudinal_planner.publish(sm, pm) + longitudinal_planner.update(clairvoyant_model, e2e_longitudinal_model, sm, frogpilot_toggles) + longitudinal_planner.publish(e2e_longitudinal_model, sm, pm) publish_ui_plan(sm, pm, longitudinal_planner) # Update FrogPilot parameters diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py index a82a1aea059e84..691d34fda1e77a 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -214,8 +214,9 @@ def __init__(self, radar_ts: float, delay: int = 0): # FrogPilot variables self.frogpilot_toggles = FrogPilotVariables.toggles + FrogPilotVariables.update_frogpilot_params() - self.secret_good_openpilot = self.frogpilot_toggles.secretgoodopenpilot_model + self.e2e_longitudinal_model = self.frogpilot_toggles.clairvoyant_model or self.frogpilot_toggles.secretgoodopenpilot_model self.update_toggles = False def update(self, sm: messaging.SubMaster, rr): @@ -261,7 +262,7 @@ def update(self, sm: messaging.SubMaster, rr): self.radar_state.radarErrors = list(radar_errors) self.radar_state.carStateMonoTime = sm.logMonoTime['carState'] - if len(sm['modelV2'].temporalPose.trans) and not self.secret_good_openpilot: + if len(sm['modelV2'].temporalPose.trans) and not self.e2e_longitudinal_model: model_v_ego = sm['modelV2'].temporalPose.trans[0] else: model_v_ego = self.v_ego @@ -340,20 +341,20 @@ def main(): # *** setup messaging can_sock = messaging.sub_sock('can') - pub_sock = messaging.pub_sock('liveTracks') RI = RadarInterface(CP) - # TODO timing is different between cars, need a single time step for all cars - # TODO just take the fastest one for now, and keep resending same messages for slower radars rk = Ratekeeper(1.0 / CP.radarTimeStep, print_delay_threshold=None) RD = RadarD(CP.radarTimeStep, RI.delay) - if not FrogPilotVariables.toggles.radarless_model: + # FrogPilot variables + frogpilot_toggles = FrogPilotVariables.toggles + FrogPilotVariables.update_frogpilot_params() + + if not frogpilot_toggles.radarless_model: sm = messaging.SubMaster(['modelV2', 'carState'], frequency=int(1./DT_CTRL)) pm = messaging.PubMaster(['radarState', 'liveTracks']) - - while True: + while 1: can_strings = messaging.drain_sock_raw(can_sock, wait_for_one=True) rr = RI.update(can_strings) sm.update(0) @@ -364,7 +365,8 @@ def main(): RD.publish(pm, -rk.remaining*1000.0) rk.monitor_time() else: - while True: + pub_sock = messaging.pub_sock('liveTracks') + while 1: can_strings = messaging.drain_sock_raw(can_sock, wait_for_one=True) rr = RI.update(can_strings) if rr is None: diff --git a/selfdrive/frogpilot/assets/active_theme/colors/colors.json b/selfdrive/frogpilot/assets/active_theme/colors/colors.json new file mode 100644 index 00000000000000..80e70f351c2180 --- /dev/null +++ b/selfdrive/frogpilot/assets/active_theme/colors/colors.json @@ -0,0 +1,50 @@ +{ + "LaneLines": { + "red": 23, + "green": 134, + "blue": 68, + "alpha": 242 + }, + "LeadMarker": { + "red": 23, + "green": 134, + "blue": 68, + "alpha": 242 + }, + "Path": { + "red": 23, + "green": 134, + "blue": 68, + "alpha": 242 + }, + "PathEdge": { + "red": 18, + "green": 107, + "blue": 54, + "alpha": 242 + }, + "RoadEdges": { + "red": 23, + "green": 134, + "blue": 68, + "alpha": 242 + }, + "Sidebar1": { + "red": 23, + "green": 134, + "blue": 68, + "alpha": 242 + }, + "Sidebar2": { + "red": 23, + "green": 134, + "blue": 68, + "alpha": 242 + }, + "Sidebar3": { + "red": 23, + "green": 134, + "blue": 68, + "alpha": 242 + } +} diff --git a/selfdrive/frogpilot/assets/active_theme/distance_icons/aggressive.gif b/selfdrive/frogpilot/assets/active_theme/distance_icons/aggressive.gif new file mode 100644 index 00000000000000..05cd8d01c4e7ec Binary files /dev/null and b/selfdrive/frogpilot/assets/active_theme/distance_icons/aggressive.gif differ diff --git a/selfdrive/frogpilot/assets/active_theme/distance_icons/relaxed.gif b/selfdrive/frogpilot/assets/active_theme/distance_icons/relaxed.gif new file mode 100644 index 00000000000000..7882afbbbe2352 Binary files /dev/null and b/selfdrive/frogpilot/assets/active_theme/distance_icons/relaxed.gif differ diff --git a/selfdrive/frogpilot/assets/active_theme/distance_icons/standard.gif b/selfdrive/frogpilot/assets/active_theme/distance_icons/standard.gif new file mode 100644 index 00000000000000..c97c85cd94d85b Binary files /dev/null and b/selfdrive/frogpilot/assets/active_theme/distance_icons/standard.gif differ diff --git a/selfdrive/frogpilot/assets/active_theme/distance_icons/traffic.gif b/selfdrive/frogpilot/assets/active_theme/distance_icons/traffic.gif new file mode 100644 index 00000000000000..3e3f898c349a92 Binary files /dev/null and b/selfdrive/frogpilot/assets/active_theme/distance_icons/traffic.gif differ diff --git a/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/button_flag.png b/selfdrive/frogpilot/assets/active_theme/icons/button_flag.png similarity index 100% rename from selfdrive/frogpilot/assets/custom_themes/frog_theme/images/button_flag.png rename to selfdrive/frogpilot/assets/active_theme/icons/button_flag.png diff --git a/selfdrive/frogpilot/assets/active_theme/icons/button_home.gif b/selfdrive/frogpilot/assets/active_theme/icons/button_home.gif new file mode 100644 index 00000000000000..133d8d34ed1c00 Binary files /dev/null and b/selfdrive/frogpilot/assets/active_theme/icons/button_home.gif differ diff --git a/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/button_settings.png b/selfdrive/frogpilot/assets/active_theme/icons/button_settings.png similarity index 100% rename from selfdrive/frogpilot/assets/custom_themes/frog_theme/images/button_settings.png rename to selfdrive/frogpilot/assets/active_theme/icons/button_settings.png diff --git a/selfdrive/frogpilot/assets/active_theme/signals/traditional_100 b/selfdrive/frogpilot/assets/active_theme/signals/traditional_100 new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_1.png b/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_1.png new file mode 100644 index 00000000000000..8c2f7a54ed26a3 Binary files /dev/null and b/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_1.png differ diff --git a/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_2.png b/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_2.png new file mode 100644 index 00000000000000..3c7d2d9eb8a0ad Binary files /dev/null and b/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_2.png differ diff --git a/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_3.png b/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_3.png new file mode 100644 index 00000000000000..b495eeae190cbc Binary files /dev/null and b/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_3.png differ diff --git a/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_4.png b/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_4.png new file mode 100644 index 00000000000000..024a0b03ea6c08 Binary files /dev/null and b/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_4.png differ diff --git a/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_5.png b/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_5.png new file mode 100644 index 00000000000000..91eb05f12f4579 Binary files /dev/null and b/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_5.png differ diff --git a/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_6.png b/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_6.png new file mode 100644 index 00000000000000..3c7d2d9eb8a0ad Binary files /dev/null and b/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_6.png differ diff --git a/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_blindspot.png b/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_blindspot.png new file mode 100644 index 00000000000000..b721266fb2f1b1 Binary files /dev/null and b/selfdrive/frogpilot/assets/active_theme/signals/turn_signal_blindspot.png differ diff --git a/selfdrive/frogpilot/assets/custom_themes/frog_theme/sounds/disengage.wav b/selfdrive/frogpilot/assets/active_theme/sounds/disengage.wav similarity index 100% rename from selfdrive/frogpilot/assets/custom_themes/frog_theme/sounds/disengage.wav rename to selfdrive/frogpilot/assets/active_theme/sounds/disengage.wav diff --git a/selfdrive/frogpilot/assets/custom_themes/frog_theme/sounds/engage.wav b/selfdrive/frogpilot/assets/active_theme/sounds/engage.wav similarity index 100% rename from selfdrive/frogpilot/assets/custom_themes/frog_theme/sounds/engage.wav rename to selfdrive/frogpilot/assets/active_theme/sounds/engage.wav diff --git a/selfdrive/frogpilot/assets/wheel_images/frog.png b/selfdrive/frogpilot/assets/active_theme/steering_wheel/wheel.png similarity index 100% rename from selfdrive/frogpilot/assets/wheel_images/frog.png rename to selfdrive/frogpilot/assets/active_theme/steering_wheel/wheel.png diff --git a/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/button_home.png b/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/button_home.png deleted file mode 100644 index 2658aa9b7a859d..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/button_home.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/frog_theme/sounds/goat.wav b/selfdrive/frogpilot/assets/custom_themes/frog_theme/sounds/goat.wav deleted file mode 100644 index e6f3c2c13b5c3b..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/frog_theme/sounds/goat.wav and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/button_flag.png b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/button_flag.png deleted file mode 100644 index ef6fafe7bb63b2..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/button_flag.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/button_home.png b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/button_home.png deleted file mode 100644 index ef6fafe7bb63b2..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/button_home.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/button_settings.png b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/button_settings.png deleted file mode 100644 index b58a7268b87efc..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/button_settings.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_1.png b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_1.png deleted file mode 100644 index f5116df86547b5..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_1.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_1_red.png b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_1_red.png deleted file mode 100644 index 4c9481352e5144..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_1_red.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_2.png b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_2.png deleted file mode 100644 index 8ceed2389a013b..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_2.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_3.png b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_3.png deleted file mode 100644 index 48dbe09b776be3..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_3.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_4.png b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_4.png deleted file mode 100644 index 3e4627989019fe..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/images/turn_signal_4.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/sounds/disengage.wav b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/sounds/disengage.wav deleted file mode 100644 index 4cb158641b95c6..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/sounds/disengage.wav and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/sounds/engage.wav b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/sounds/engage.wav deleted file mode 100644 index 4e5b747cb83e51..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/sounds/engage.wav and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/sounds/refuse.wav b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/sounds/refuse.wav deleted file mode 100644 index ef69b98e9af31e..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/sounds/refuse.wav and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/sounds/warning_soft.wav b/selfdrive/frogpilot/assets/custom_themes/stalin_theme/sounds/warning_soft.wav deleted file mode 100644 index ee9b71114d9a6c..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/stalin_theme/sounds/warning_soft.wav and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/button_flag.png b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/button_flag.png deleted file mode 100644 index 2cc81a70018332..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/button_flag.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/button_home.png b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/button_home.png deleted file mode 100644 index 2cc81a70018332..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/button_home.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/button_settings.png b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/button_settings.png deleted file mode 100644 index 549e2bedb87062..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/button_settings.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_1.png b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_1.png deleted file mode 100644 index b635c577898f38..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_1.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_1_red.png b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_1_red.png deleted file mode 100644 index a2f508f7aba888..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_1_red.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_2.png b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_2.png deleted file mode 100644 index 5158c0f307f45a..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_2.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_3.png b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_3.png deleted file mode 100644 index c0e2a87713ca6d..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_3.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_4.png b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_4.png deleted file mode 100644 index 5932e83b0a2522..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/images/turn_signal_4.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/sounds/disengage.wav b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/sounds/disengage.wav deleted file mode 100644 index 81dcc4bd93409a..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/sounds/disengage.wav and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/sounds/engage.wav b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/sounds/engage.wav deleted file mode 100644 index 108f7cf19287b7..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/sounds/engage.wav and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/sounds/prompt_distracted.wav b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/sounds/prompt_distracted.wav deleted file mode 100644 index c8b61322cce6e3..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/sounds/prompt_distracted.wav and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/sounds/warning_immediate.wav b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/sounds/warning_immediate.wav deleted file mode 100644 index f038f13161f438..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/sounds/warning_immediate.wav and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/sounds/warning_soft.wav b/selfdrive/frogpilot/assets/custom_themes/tesla_theme/sounds/warning_soft.wav deleted file mode 100644 index d02180baaf1445..00000000000000 Binary files a/selfdrive/frogpilot/assets/custom_themes/tesla_theme/sounds/warning_soft.wav and /dev/null differ diff --git a/selfdrive/frogpilot/assets/holiday_themes/april_fools/images/button_flag.png b/selfdrive/frogpilot/assets/holiday_themes/april_fools/icons/button_flag.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/april_fools/images/button_flag.png rename to selfdrive/frogpilot/assets/holiday_themes/april_fools/icons/button_flag.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/april_fools/images/button_home.png b/selfdrive/frogpilot/assets/holiday_themes/april_fools/icons/button_home.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/april_fools/images/button_home.png rename to selfdrive/frogpilot/assets/holiday_themes/april_fools/icons/button_home.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/april_fools/images/button_settings.png b/selfdrive/frogpilot/assets/holiday_themes/april_fools/icons/button_settings.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/april_fools/images/button_settings.png rename to selfdrive/frogpilot/assets/holiday_themes/april_fools/icons/button_settings.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/christmas/images/button_flag.png b/selfdrive/frogpilot/assets/holiday_themes/christmas/icons/button_flag.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/christmas/images/button_flag.png rename to selfdrive/frogpilot/assets/holiday_themes/christmas/icons/button_flag.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/christmas/images/button_home.png b/selfdrive/frogpilot/assets/holiday_themes/christmas/icons/button_home.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/christmas/images/button_home.png rename to selfdrive/frogpilot/assets/holiday_themes/christmas/icons/button_home.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/christmas/images/button_settings.png b/selfdrive/frogpilot/assets/holiday_themes/christmas/icons/button_settings.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/christmas/images/button_settings.png rename to selfdrive/frogpilot/assets/holiday_themes/christmas/icons/button_settings.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/cinco_de_mayo/images/button_flag.png b/selfdrive/frogpilot/assets/holiday_themes/cinco_de_mayo/icons/button_flag.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/cinco_de_mayo/images/button_flag.png rename to selfdrive/frogpilot/assets/holiday_themes/cinco_de_mayo/icons/button_flag.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/cinco_de_mayo/images/button_home.png b/selfdrive/frogpilot/assets/holiday_themes/cinco_de_mayo/icons/button_home.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/cinco_de_mayo/images/button_home.png rename to selfdrive/frogpilot/assets/holiday_themes/cinco_de_mayo/icons/button_home.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/cinco_de_mayo/images/button_settings.png b/selfdrive/frogpilot/assets/holiday_themes/cinco_de_mayo/icons/button_settings.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/cinco_de_mayo/images/button_settings.png rename to selfdrive/frogpilot/assets/holiday_themes/cinco_de_mayo/icons/button_settings.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/easter/images/button_flag.png b/selfdrive/frogpilot/assets/holiday_themes/easter/icons/button_flag.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/easter/images/button_flag.png rename to selfdrive/frogpilot/assets/holiday_themes/easter/icons/button_flag.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/easter/images/button_home.png b/selfdrive/frogpilot/assets/holiday_themes/easter/icons/button_home.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/easter/images/button_home.png rename to selfdrive/frogpilot/assets/holiday_themes/easter/icons/button_home.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/easter/images/button_settings.png b/selfdrive/frogpilot/assets/holiday_themes/easter/icons/button_settings.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/easter/images/button_settings.png rename to selfdrive/frogpilot/assets/holiday_themes/easter/icons/button_settings.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/easter/images/turn_signal_1.png b/selfdrive/frogpilot/assets/holiday_themes/easter/signals/turn_signal_1.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/easter/images/turn_signal_1.png rename to selfdrive/frogpilot/assets/holiday_themes/easter/signals/turn_signal_1.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/easter/images/turn_signal_1_red.png b/selfdrive/frogpilot/assets/holiday_themes/easter/signals/turn_signal_1_red.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/easter/images/turn_signal_1_red.png rename to selfdrive/frogpilot/assets/holiday_themes/easter/signals/turn_signal_1_red.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/easter/images/turn_signal_2.png b/selfdrive/frogpilot/assets/holiday_themes/easter/signals/turn_signal_2.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/easter/images/turn_signal_2.png rename to selfdrive/frogpilot/assets/holiday_themes/easter/signals/turn_signal_2.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/easter/images/turn_signal_3.png b/selfdrive/frogpilot/assets/holiday_themes/easter/signals/turn_signal_3.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/easter/images/turn_signal_3.png rename to selfdrive/frogpilot/assets/holiday_themes/easter/signals/turn_signal_3.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/easter/images/turn_signal_4.png b/selfdrive/frogpilot/assets/holiday_themes/easter/signals/turn_signal_4.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/easter/images/turn_signal_4.png rename to selfdrive/frogpilot/assets/holiday_themes/easter/signals/turn_signal_4.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/images/button_flag.png b/selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/icons/button_flag.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/images/button_flag.png rename to selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/icons/button_flag.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/images/button_home.png b/selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/icons/button_home.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/images/button_home.png rename to selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/icons/button_home.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/images/button_settings.png b/selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/icons/button_settings.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/images/button_settings.png rename to selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/icons/button_settings.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/images/turn_signal_1.png b/selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/signals/turn_signal_1.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/images/turn_signal_1.png rename to selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/signals/turn_signal_1.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/images/turn_signal_1_red.png b/selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/signals/turn_signal_1_red.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/images/turn_signal_1_red.png rename to selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/signals/turn_signal_1_red.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/images/turn_signal_2.png b/selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/signals/turn_signal_2.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/images/turn_signal_2.png rename to selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/signals/turn_signal_2.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/images/turn_signal_3.png b/selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/signals/turn_signal_3.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/images/turn_signal_3.png rename to selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/signals/turn_signal_3.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/images/turn_signal_4.png b/selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/signals/turn_signal_4.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/images/turn_signal_4.png rename to selfdrive/frogpilot/assets/holiday_themes/fourth_of_july/signals/turn_signal_4.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/halloween/images/button_flag.png b/selfdrive/frogpilot/assets/holiday_themes/halloween/icons/button_flag.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/halloween/images/button_flag.png rename to selfdrive/frogpilot/assets/holiday_themes/halloween/icons/button_flag.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/halloween/images/button_home.png b/selfdrive/frogpilot/assets/holiday_themes/halloween/icons/button_home.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/halloween/images/button_home.png rename to selfdrive/frogpilot/assets/holiday_themes/halloween/icons/button_home.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/halloween/images/button_settings.png b/selfdrive/frogpilot/assets/holiday_themes/halloween/icons/button_settings.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/halloween/images/button_settings.png rename to selfdrive/frogpilot/assets/holiday_themes/halloween/icons/button_settings.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/new_years_day/images/button_flag.png b/selfdrive/frogpilot/assets/holiday_themes/new_years_day/icons/button_flag.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/new_years_day/images/button_flag.png rename to selfdrive/frogpilot/assets/holiday_themes/new_years_day/icons/button_flag.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/new_years_day/images/button_home.png b/selfdrive/frogpilot/assets/holiday_themes/new_years_day/icons/button_home.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/new_years_day/images/button_home.png rename to selfdrive/frogpilot/assets/holiday_themes/new_years_day/icons/button_home.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/new_years_day/images/button_settings.png b/selfdrive/frogpilot/assets/holiday_themes/new_years_day/icons/button_settings.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/new_years_day/images/button_settings.png rename to selfdrive/frogpilot/assets/holiday_themes/new_years_day/icons/button_settings.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/st_patricks_day/images/button_flag.png b/selfdrive/frogpilot/assets/holiday_themes/st_patricks_day/icons/button_flag.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/st_patricks_day/images/button_flag.png rename to selfdrive/frogpilot/assets/holiday_themes/st_patricks_day/icons/button_flag.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/st_patricks_day/images/button_home.png b/selfdrive/frogpilot/assets/holiday_themes/st_patricks_day/icons/button_home.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/st_patricks_day/images/button_home.png rename to selfdrive/frogpilot/assets/holiday_themes/st_patricks_day/icons/button_home.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/st_patricks_day/images/button_settings.png b/selfdrive/frogpilot/assets/holiday_themes/st_patricks_day/icons/button_settings.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/st_patricks_day/images/button_settings.png rename to selfdrive/frogpilot/assets/holiday_themes/st_patricks_day/icons/button_settings.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/thanksgiving/images/button_flag.png b/selfdrive/frogpilot/assets/holiday_themes/thanksgiving/icons/button_flag.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/thanksgiving/images/button_flag.png rename to selfdrive/frogpilot/assets/holiday_themes/thanksgiving/icons/button_flag.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/thanksgiving/images/button_home.png b/selfdrive/frogpilot/assets/holiday_themes/thanksgiving/icons/button_home.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/thanksgiving/images/button_home.png rename to selfdrive/frogpilot/assets/holiday_themes/thanksgiving/icons/button_home.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/thanksgiving/images/button_settings.png b/selfdrive/frogpilot/assets/holiday_themes/thanksgiving/icons/button_settings.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/thanksgiving/images/button_settings.png rename to selfdrive/frogpilot/assets/holiday_themes/thanksgiving/icons/button_settings.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/valentines_day/images/button_flag.png b/selfdrive/frogpilot/assets/holiday_themes/valentines_day/icons/button_flag.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/valentines_day/images/button_flag.png rename to selfdrive/frogpilot/assets/holiday_themes/valentines_day/icons/button_flag.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/valentines_day/images/button_home.png b/selfdrive/frogpilot/assets/holiday_themes/valentines_day/icons/button_home.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/valentines_day/images/button_home.png rename to selfdrive/frogpilot/assets/holiday_themes/valentines_day/icons/button_home.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/valentines_day/images/button_settings.png b/selfdrive/frogpilot/assets/holiday_themes/valentines_day/icons/button_settings.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/valentines_day/images/button_settings.png rename to selfdrive/frogpilot/assets/holiday_themes/valentines_day/icons/button_settings.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/aggressive.gif b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/aggressive.gif new file mode 100644 index 00000000000000..05cd8d01c4e7ec Binary files /dev/null and b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/aggressive.gif differ diff --git a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/button_flag.png b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/button_flag.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/button_flag.png rename to selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/button_flag.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/button_home.gif b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/button_home.gif new file mode 100644 index 00000000000000..6b20a726e4ce2f Binary files /dev/null and b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/button_home.gif differ diff --git a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/button_settings.png b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/button_settings.png similarity index 100% rename from selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/button_settings.png rename to selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/button_settings.png diff --git a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/relaxed.gif b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/relaxed.gif new file mode 100644 index 00000000000000..7882afbbbe2352 Binary files /dev/null and b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/relaxed.gif differ diff --git a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/standard.gif b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/standard.gif new file mode 100644 index 00000000000000..c97c85cd94d85b Binary files /dev/null and b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/standard.gif differ diff --git a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/steering_wheel/frog.png b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/steering_wheel/frog.png new file mode 100644 index 00000000000000..712255984600c0 Binary files /dev/null and b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/steering_wheel/frog.png differ diff --git a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/traffic.gif b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/traffic.gif new file mode 100644 index 00000000000000..3e3f898c349a92 Binary files /dev/null and b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/icons/traffic.gif differ diff --git a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/button_home.png b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/button_home.png deleted file mode 100644 index 2658aa9b7a859d..00000000000000 Binary files a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/button_home.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/turn_signal_1.png b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/turn_signal_1.png deleted file mode 100644 index 43e0b446f8ea9d..00000000000000 Binary files a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/turn_signal_1.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/turn_signal_1_red.png b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/turn_signal_1_red.png deleted file mode 100644 index 7c102456e6efb6..00000000000000 Binary files a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/turn_signal_1_red.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/turn_signal_2.png b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/turn_signal_2.png deleted file mode 100644 index e8e147976b7330..00000000000000 Binary files a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/turn_signal_2.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/turn_signal_3.png b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/turn_signal_3.png deleted file mode 100644 index b59b003f95eb2f..00000000000000 Binary files a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/turn_signal_3.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/turn_signal_4.png b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/turn_signal_4.png deleted file mode 100644 index c3c1d204e68375..00000000000000 Binary files a/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/images/turn_signal_4.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_1.png b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/signals/turn_signal_1.png similarity index 100% rename from selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_1.png rename to selfdrive/frogpilot/assets/holiday_themes/world_frog_day/signals/turn_signal_1.png diff --git a/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_1_red.png b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/signals/turn_signal_1_red.png similarity index 100% rename from selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_1_red.png rename to selfdrive/frogpilot/assets/holiday_themes/world_frog_day/signals/turn_signal_1_red.png diff --git a/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_2.png b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/signals/turn_signal_2.png similarity index 100% rename from selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_2.png rename to selfdrive/frogpilot/assets/holiday_themes/world_frog_day/signals/turn_signal_2.png diff --git a/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_3.png b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/signals/turn_signal_3.png similarity index 100% rename from selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_3.png rename to selfdrive/frogpilot/assets/holiday_themes/world_frog_day/signals/turn_signal_3.png diff --git a/selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_4.png b/selfdrive/frogpilot/assets/holiday_themes/world_frog_day/signals/turn_signal_4.png similarity index 100% rename from selfdrive/frogpilot/assets/custom_themes/frog_theme/images/turn_signal_4.png rename to selfdrive/frogpilot/assets/holiday_themes/world_frog_day/signals/turn_signal_4.png diff --git a/selfdrive/frogpilot/assets/other_images/aggressive.png b/selfdrive/frogpilot/assets/other_images/aggressive.png deleted file mode 100644 index 42d005538c462e..00000000000000 Binary files a/selfdrive/frogpilot/assets/other_images/aggressive.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/other_images/aggressive_kaofui.png b/selfdrive/frogpilot/assets/other_images/aggressive_kaofui.png deleted file mode 100644 index 52da0a46a85b29..00000000000000 Binary files a/selfdrive/frogpilot/assets/other_images/aggressive_kaofui.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/other_images/original_bg.jpg b/selfdrive/frogpilot/assets/other_images/original_bg.jpg new file mode 100644 index 00000000000000..13c36a7e240359 Binary files /dev/null and b/selfdrive/frogpilot/assets/other_images/original_bg.jpg differ diff --git a/selfdrive/frogpilot/assets/other_images/relaxed.png b/selfdrive/frogpilot/assets/other_images/relaxed.png deleted file mode 100644 index fb77ec3ca8f908..00000000000000 Binary files a/selfdrive/frogpilot/assets/other_images/relaxed.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/other_images/relaxed_kaofui.png b/selfdrive/frogpilot/assets/other_images/relaxed_kaofui.png deleted file mode 100644 index 897ad3d2aae516..00000000000000 Binary files a/selfdrive/frogpilot/assets/other_images/relaxed_kaofui.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/other_images/standard.png b/selfdrive/frogpilot/assets/other_images/standard.png deleted file mode 100644 index 57c5a67e77f79a..00000000000000 Binary files a/selfdrive/frogpilot/assets/other_images/standard.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/other_images/standard_kaofui.png b/selfdrive/frogpilot/assets/other_images/standard_kaofui.png deleted file mode 100644 index 481e257725971d..00000000000000 Binary files a/selfdrive/frogpilot/assets/other_images/standard_kaofui.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/other_images/traffic.png b/selfdrive/frogpilot/assets/other_images/traffic.png deleted file mode 100644 index b59f7bc65f14f3..00000000000000 Binary files a/selfdrive/frogpilot/assets/other_images/traffic.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/other_images/traffic_kaofui.png b/selfdrive/frogpilot/assets/other_images/traffic_kaofui.png deleted file mode 100644 index 9930ea8bd5cc7b..00000000000000 Binary files a/selfdrive/frogpilot/assets/other_images/traffic_kaofui.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/random_events/icons/button_home.gif b/selfdrive/frogpilot/assets/random_events/icons/button_home.gif new file mode 100644 index 00000000000000..0ccd94030fda8f Binary files /dev/null and b/selfdrive/frogpilot/assets/random_events/icons/button_home.gif differ diff --git a/selfdrive/frogpilot/assets/random_events/images/firefox.gif b/selfdrive/frogpilot/assets/random_events/icons/firefox.gif similarity index 100% rename from selfdrive/frogpilot/assets/random_events/images/firefox.gif rename to selfdrive/frogpilot/assets/random_events/icons/firefox.gif diff --git a/selfdrive/frogpilot/assets/random_events/icons/goat.gif b/selfdrive/frogpilot/assets/random_events/icons/goat.gif new file mode 100644 index 00000000000000..37dc6b9065e553 Binary files /dev/null and b/selfdrive/frogpilot/assets/random_events/icons/goat.gif differ diff --git a/selfdrive/frogpilot/assets/random_events/images/great_scott.gif b/selfdrive/frogpilot/assets/random_events/icons/great_scott.gif similarity index 100% rename from selfdrive/frogpilot/assets/random_events/images/great_scott.gif rename to selfdrive/frogpilot/assets/random_events/icons/great_scott.gif diff --git a/selfdrive/frogpilot/assets/random_events/images/tree_fiddy.gif b/selfdrive/frogpilot/assets/random_events/icons/tree_fiddy.gif similarity index 100% rename from selfdrive/frogpilot/assets/random_events/images/tree_fiddy.gif rename to selfdrive/frogpilot/assets/random_events/icons/tree_fiddy.gif diff --git a/selfdrive/frogpilot/assets/random_events/images/weeb_wheel.gif b/selfdrive/frogpilot/assets/random_events/icons/weeb_wheel.gif similarity index 100% rename from selfdrive/frogpilot/assets/random_events/images/weeb_wheel.gif rename to selfdrive/frogpilot/assets/random_events/icons/weeb_wheel.gif diff --git a/selfdrive/frogpilot/assets/stock_theme/distance_icons/aggressive.png b/selfdrive/frogpilot/assets/stock_theme/distance_icons/aggressive.png new file mode 100644 index 00000000000000..f29c4516c66dcc Binary files /dev/null and b/selfdrive/frogpilot/assets/stock_theme/distance_icons/aggressive.png differ diff --git a/selfdrive/frogpilot/assets/stock_theme/distance_icons/relaxed.png b/selfdrive/frogpilot/assets/stock_theme/distance_icons/relaxed.png new file mode 100644 index 00000000000000..006a84cdc886ff Binary files /dev/null and b/selfdrive/frogpilot/assets/stock_theme/distance_icons/relaxed.png differ diff --git a/selfdrive/frogpilot/assets/stock_theme/distance_icons/standard.png b/selfdrive/frogpilot/assets/stock_theme/distance_icons/standard.png new file mode 100644 index 00000000000000..43b56551e36c19 Binary files /dev/null and b/selfdrive/frogpilot/assets/stock_theme/distance_icons/standard.png differ diff --git a/selfdrive/frogpilot/assets/stock_theme/distance_icons/traffic.png b/selfdrive/frogpilot/assets/stock_theme/distance_icons/traffic.png new file mode 100644 index 00000000000000..9c9310b0cd1936 Binary files /dev/null and b/selfdrive/frogpilot/assets/stock_theme/distance_icons/traffic.png differ diff --git a/selfdrive/frogpilot/assets/stock_theme/icons/button_flag.png b/selfdrive/frogpilot/assets/stock_theme/icons/button_flag.png new file mode 100644 index 00000000000000..cac4db6d4cdb51 Binary files /dev/null and b/selfdrive/frogpilot/assets/stock_theme/icons/button_flag.png differ diff --git a/selfdrive/frogpilot/assets/stock_theme/icons/button_home.png b/selfdrive/frogpilot/assets/stock_theme/icons/button_home.png new file mode 100644 index 00000000000000..9f52faf9e2da48 Binary files /dev/null and b/selfdrive/frogpilot/assets/stock_theme/icons/button_home.png differ diff --git a/selfdrive/frogpilot/assets/stock_theme/icons/button_settings.png b/selfdrive/frogpilot/assets/stock_theme/icons/button_settings.png new file mode 100644 index 00000000000000..e04262b887cea5 Binary files /dev/null and b/selfdrive/frogpilot/assets/stock_theme/icons/button_settings.png differ diff --git a/selfdrive/frogpilot/assets/stock_theme/steering_wheel/stock.png b/selfdrive/frogpilot/assets/stock_theme/steering_wheel/stock.png new file mode 100644 index 00000000000000..3f09a35a79bf45 Binary files /dev/null and b/selfdrive/frogpilot/assets/stock_theme/steering_wheel/stock.png differ diff --git a/selfdrive/frogpilot/assets/toggle_icons/frog.png b/selfdrive/frogpilot/assets/toggle_icons/frog.png new file mode 100644 index 00000000000000..3561616fb26f84 Binary files /dev/null and b/selfdrive/frogpilot/assets/toggle_icons/frog.png differ diff --git a/selfdrive/frogpilot/assets/wheel_images/hyundai.png b/selfdrive/frogpilot/assets/wheel_images/hyundai.png deleted file mode 100644 index 4a0371ccfe69c4..00000000000000 Binary files a/selfdrive/frogpilot/assets/wheel_images/hyundai.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/wheel_images/lexus.png b/selfdrive/frogpilot/assets/wheel_images/lexus.png deleted file mode 100644 index aa9779fc777513..00000000000000 Binary files a/selfdrive/frogpilot/assets/wheel_images/lexus.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/wheel_images/rocket.png b/selfdrive/frogpilot/assets/wheel_images/rocket.png deleted file mode 100644 index b624d00b93bd69..00000000000000 Binary files a/selfdrive/frogpilot/assets/wheel_images/rocket.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/wheel_images/stalin.png b/selfdrive/frogpilot/assets/wheel_images/stalin.png deleted file mode 100644 index 4c2e1e0468db58..00000000000000 Binary files a/selfdrive/frogpilot/assets/wheel_images/stalin.png and /dev/null differ diff --git a/selfdrive/frogpilot/assets/wheel_images/toyota.png b/selfdrive/frogpilot/assets/wheel_images/toyota.png deleted file mode 100644 index bc392ed1354179..00000000000000 Binary files a/selfdrive/frogpilot/assets/wheel_images/toyota.png and /dev/null differ diff --git a/selfdrive/frogpilot/controls/frogpilot_planner.py b/selfdrive/frogpilot/controls/frogpilot_planner.py index b9fce6126fcf3b..69dcd6519c2d8c 100644 --- a/selfdrive/frogpilot/controls/frogpilot_planner.py +++ b/selfdrive/frogpilot/controls/frogpilot_planner.py @@ -3,76 +3,47 @@ from cereal import car from openpilot.common.conversions import Conversions as CV -from openpilot.common.numpy_fast import clip, interp from openpilot.common.params import Params -from openpilot.common.realtime import DT_MDL -from openpilot.selfdrive.car.interfaces import ACCEL_MIN, ACCEL_MAX from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_UNSET -from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import A_CHANGE_COST, COMFORT_BRAKE, DANGER_ZONE_COST, J_EGO_COST, STOP_DISTANCE, \ - get_jerk_factor, get_safe_obstacle_distance, get_stopped_equivalence_factor, get_T_FOLLOW -from openpilot.selfdrive.controls.lib.longitudinal_planner import A_CRUISE_MIN, Lead, get_max_accel +from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import A_CHANGE_COST, DANGER_ZONE_COST, J_EGO_COST, STOP_DISTANCE +from openpilot.selfdrive.controls.lib.longitudinal_planner import Lead -from openpilot.selfdrive.frogpilot.controls.lib.conditional_experimental_mode import MODEL_LENGTH, PLANNER_TIME, ConditionalExperimentalMode -from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import MovingAverageCalculator, calculate_lane_width, calculate_road_curvature -from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import CITY_SPEED_LIMIT, CRUISING_SPEED, PROBABILITY -from openpilot.selfdrive.frogpilot.controls.lib.map_turn_speed_controller import MapTurnSpeedController -from openpilot.selfdrive.frogpilot.controls.lib.speed_limit_controller import SpeedLimitController +from openpilot.selfdrive.frogpilot.controls.lib.conditional_experimental_mode import ConditionalExperimentalMode +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_acceleration import FrogPilotAcceleration +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_events import FrogPilotEvents +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_following import FrogPilotFollowing +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import WeightedMovingAverageCalculator, calculate_lane_width, calculate_road_curvature, update_frogpilot_toggles +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import CITY_SPEED_LIMIT, CRUISING_SPEED, MODEL_LENGTH, PLANNER_TIME, THRESHOLD +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_vcruise import FrogPilotVCruise GearShifter = car.CarState.GearShifter -A_CRUISE_MIN_ECO = A_CRUISE_MIN / 5 -A_CRUISE_MIN_SPORT = A_CRUISE_MIN / 2 - # MPH = [ 0., 11, 22, 34, 45, 56, 89] -A_CRUISE_MAX_BP_CUSTOM = [ 0., 5., 10., 15., 20., 25., 40.] -A_CRUISE_MAX_VALS_ECO = [1.4, 1.2, 1.0, 0.8, 0.6, 0.4, 0.2] -A_CRUISE_MAX_VALS_SPORT = [3.0, 2.5, 2.0, 1.0, 0.9, 0.8, 0.6] -A_CRUISE_MAX_VALS_SPORT_PLUS = [4.0, 3.5, 3.0, 1.0, 0.9, 0.8, 0.6] - -TARGET_LAT_A = 1.9 - -TRAFFIC_MODE_BP = [0., CITY_SPEED_LIMIT] - -def get_max_accel_eco(v_ego): - return interp(v_ego, A_CRUISE_MAX_BP_CUSTOM, A_CRUISE_MAX_VALS_ECO) - -def get_max_accel_sport(v_ego): - return interp(v_ego, A_CRUISE_MAX_BP_CUSTOM, A_CRUISE_MAX_VALS_SPORT) - -def get_max_accel_sport_plus(v_ego): - return interp(v_ego, A_CRUISE_MAX_BP_CUSTOM, A_CRUISE_MAX_VALS_SPORT_PLUS) - class FrogPilotPlanner: def __init__(self): self.params_memory = Params("/dev/shm/params") self.cem = ConditionalExperimentalMode(self) + self.frogpilot_acceleration = FrogPilotAcceleration(self) + self.frogpilot_events = FrogPilotEvents(self) + self.frogpilot_following = FrogPilotFollowing(self) + self.frogpilot_vcruise = FrogPilotVCruise(self) self.lead_one = Lead() - self.mtsc = MapTurnSpeedController() - self.forcing_stop = False + self.always_on_lateral_active = False + self.lateral_check = False self.lead_departing = False self.model_stopped = False - self.override_force_stop = False - self.override_slc = False self.slower_lead = False self.taking_curve_quickly = False self.tracking_lead = False - self.acceleration_jerk = 0 - self.danger_jerk = 0 self.model_length = 0 - self.mtsc_target = 0 - self.overridden_speed = 0 - self.road_curvature = 0 - self.slc_target = 0 - self.speed_jerk = 0 - self.tracked_model_length = 0 + self.road_curvature = 1 self.tracking_lead_distance = 0 self.v_cruise = 0 - self.vtsc_target = 0 - self.tracking_lead_mac = MovingAverageCalculator() + self.tracking_lead_mac = WeightedMovingAverageCalculator(window_size=4) def update(self, carState, controlsState, frogpilotCarControl, frogpilotCarState, frogpilotNavigation, modelData, radarState, frogpilot_toggles): if frogpilot_toggles.radarless_model: @@ -95,9 +66,25 @@ def update(self, carState, controlsState, frogpilotCarControl, frogpilotCarState lead_distance = self.lead_one.dRel - distance_offset stopping_distance = STOP_DISTANCE + distance_offset - run_cem = frogpilot_toggles.conditional_experimental_mode or frogpilot_toggles.force_stops or frogpilot_toggles.show_stopping_point - if run_cem and (controlsState.enabled or frogpilotCarControl.alwaysOnLateral) and driving_gear: + if frogpilot_toggles.openpilot_longitudinal: + self.frogpilot_acceleration.update(controlsState, frogpilotCarState, v_cruise, v_ego, frogpilot_toggles) + + self.always_on_lateral_active |= frogpilot_toggles.always_on_lateral_main or carState.cruiseState.enabled + self.always_on_lateral_active &= frogpilot_toggles.always_on_lateral and carState.cruiseState.available + self.always_on_lateral_active &= driving_gear + self.always_on_lateral_active &= self.lateral_check + self.always_on_lateral_active &= not (frogpilot_toggles.always_on_lateral_lkas and frogpilotCarState.alwaysOnLateralDisabled) + self.always_on_lateral_active &= not (carState.brakePressed and v_ego < frogpilot_toggles.always_on_lateral_pause_speed) or carState.standstill + + run_cem = frogpilot_toggles.conditional_experimental_mode or frogpilot_toggles.force_stops or frogpilot_toggles.green_light_alert or frogpilot_toggles.show_stopping_point + if run_cem and (controlsState.enabled or self.always_on_lateral_active) and driving_gear: self.cem.update(carState, frogpilotNavigation, modelData, v_ego, v_lead, frogpilot_toggles) + else: + self.cem.stop_light_detected = False + + self.frogpilot_events.update(carState, controlsState, frogpilotCarControl, frogpilotCarState, modelData, frogpilot_toggles) + if frogpilot_toggles.openpilot_longitudinal: + self.frogpilot_following.update(controlsState, frogpilotCarState, lead_distance, stopping_distance, v_ego, v_lead, frogpilot_toggles) check_lane_width = frogpilot_toggles.adjacent_lanes or frogpilot_toggles.blind_spot_path or frogpilot_toggles.lane_detection if check_lane_width and v_ego >= frogpilot_toggles.minimum_lane_change_speed: @@ -117,252 +104,82 @@ def update(self, carState, controlsState, frogpilotCarControl, frogpilotCarState self.lead_departing = False self.tracking_lead_distance = 0 + self.lateral_check = v_ego >= frogpilot_toggles.pause_lateral_below_speed + self.lateral_check |= frogpilot_toggles.pause_lateral_below_signal and not (carState.leftBlinker or carState.rightBlinker) + self.lateral_check |= carState.standstill + self.model_length = modelData.position.x[MODEL_LENGTH - 1] self.model_stopped = self.model_length < CRUISING_SPEED * PLANNER_TIME - self.model_stopped |= self.forcing_stop - self.override_force_stop |= carState.gasPressed - self.override_force_stop |= frogpilot_toggles.force_stops and carState.standstill and self.tracking_lead - self.override_force_stop |= frogpilotCarControl.resumePressed - self.road_curvature = calculate_road_curvature(modelData, v_ego) if not carState.standstill and driving_gear else 1 + self.model_stopped |= self.frogpilot_vcruise.forcing_stop + + self.road_curvature = calculate_road_curvature(modelData, v_ego) if not carState.standstill else 1 if frogpilot_toggles.random_events and v_ego > CRUISING_SPEED and driving_gear: self.taking_curve_quickly = v_ego > (1 / self.road_curvature)**0.5 * 2 > CRUISING_SPEED * 2 and abs(carState.steeringAngleDeg) > 30 - - self.set_acceleration(controlsState, frogpilotCarState, v_cruise, v_ego, frogpilot_toggles) - self.set_follow_values(controlsState, frogpilotCarState, lead_distance, stopping_distance, v_ego, v_lead, frogpilot_toggles) - self.set_lead_status(lead_distance, stopping_distance, v_ego) - self.update_v_cruise(carState, controlsState, frogpilotCarState, frogpilotNavigation, modelData, v_cruise, v_ego, frogpilot_toggles) - - def set_acceleration(self, controlsState, frogpilotCarState, v_cruise, v_ego, frogpilot_toggles): - eco_gear = frogpilotCarState.ecoGear - sport_gear = frogpilotCarState.sportGear - - if self.tracking_lead and frogpilot_toggles.aggressive_acceleration: - self.max_accel = clip(self.lead_one.aLeadK, get_max_accel_sport_plus(v_ego), 2.0 if v_ego >= 20 else 4.0) - elif frogpilot_toggles.map_acceleration and (eco_gear or sport_gear): - if eco_gear: - self.max_accel = get_max_accel_eco(v_ego) - else: - if frogpilot_toggles.acceleration_profile == 3: - self.max_accel = get_max_accel_sport_plus(v_ego) - else: - self.max_accel = get_max_accel_sport(v_ego) - else: - if frogpilot_toggles.acceleration_profile == 1: - self.max_accel = get_max_accel_eco(v_ego) - elif frogpilot_toggles.acceleration_profile == 2: - self.max_accel = get_max_accel_sport(v_ego) - elif frogpilot_toggles.acceleration_profile == 3: - self.max_accel = get_max_accel_sport_plus(v_ego) - elif controlsState.experimentalMode: - self.max_accel = ACCEL_MAX - else: - self.max_accel = get_max_accel(v_ego) - - if not self.tracking_lead: - self.max_accel = min(self.max_accel, self.max_accel * (self.v_cruise / CITY_SPEED_LIMIT)) - - if controlsState.experimentalMode: - self.min_accel = ACCEL_MIN - elif min(self.mtsc_target, self.vtsc_target) < v_cruise: - self.min_accel = A_CRUISE_MIN - elif frogpilot_toggles.map_deceleration and (eco_gear or sport_gear): - if eco_gear: - self.min_accel = A_CRUISE_MIN_ECO - else: - self.min_accel = A_CRUISE_MIN_SPORT - else: - if frogpilot_toggles.deceleration_profile == 1: - self.min_accel = A_CRUISE_MIN_ECO - elif frogpilot_toggles.deceleration_profile == 2: - self.min_accel = A_CRUISE_MIN_SPORT - else: - self.min_accel = A_CRUISE_MIN - - def set_follow_values(self, controlsState, frogpilotCarState, lead_distance, stopping_distance, v_ego, v_lead, frogpilot_toggles): - if frogpilotCarState.trafficModeActive: - self.base_acceleration_jerk = interp(v_ego, TRAFFIC_MODE_BP, frogpilot_toggles.traffic_mode_jerk_acceleration) - self.base_danger_jerk = interp(v_ego, TRAFFIC_MODE_BP, frogpilot_toggles.traffic_mode_jerk_danger) - self.base_speed_jerk = interp(v_ego, TRAFFIC_MODE_BP, frogpilot_toggles.traffic_mode_jerk_speed) - self.t_follow = interp(v_ego, TRAFFIC_MODE_BP, frogpilot_toggles.traffic_mode_t_follow) else: - self.base_acceleration_jerk, self.base_danger_jerk, self.base_speed_jerk = get_jerk_factor( - frogpilot_toggles.aggressive_jerk_acceleration, frogpilot_toggles.aggressive_jerk_danger, frogpilot_toggles.aggressive_jerk_speed, - frogpilot_toggles.standard_jerk_acceleration, frogpilot_toggles.standard_jerk_danger, frogpilot_toggles.standard_jerk_speed, - frogpilot_toggles.relaxed_jerk_acceleration, frogpilot_toggles.relaxed_jerk_danger, frogpilot_toggles.relaxed_jerk_speed, - frogpilot_toggles.custom_personalities, controlsState.personality - ) - - self.t_follow = get_T_FOLLOW( - frogpilot_toggles.aggressive_follow, - frogpilot_toggles.standard_follow, - frogpilot_toggles.relaxed_follow, - frogpilot_toggles.custom_personalities, controlsState.personality - ) - - if self.tracking_lead: - self.safe_obstacle_distance = int(get_safe_obstacle_distance(v_ego, self.t_follow)) - self.safe_obstacle_distance_stock = self.safe_obstacle_distance - self.stopped_equivalence_factor = int(get_stopped_equivalence_factor(v_lead)) - self.update_follow_values(lead_distance, stopping_distance, v_ego, v_lead, frogpilot_toggles) + self.taking_curve_quickly = False + + self.tracking_lead = self.set_lead_status(lead_distance, stopping_distance, v_ego) + if frogpilot_toggles.openpilot_longitudinal: + self.v_cruise = self.frogpilot_vcruise.update(carState, controlsState, frogpilotCarControl, frogpilotCarState, frogpilotNavigation, modelData, v_cruise, v_ego, frogpilot_toggles) else: - self.safe_obstacle_distance = 0 - self.safe_obstacle_distance_stock = 0 - self.stopped_equivalence_factor = 0 - self.acceleration_jerk = self.base_acceleration_jerk - self.danger_jerk = self.base_danger_jerk - self.speed_jerk = self.base_speed_jerk + self.frogpilot_vcruise.mtsc_target = v_cruise + self.frogpilot_vcruise.vtsc_target = v_cruise + + if self.frogpilot_events.frame == 1: # Force update to check the current state of "Always On Lateral" and holiday theme + update_frogpilot_toggles() def set_lead_status(self, lead_distance, stopping_distance, v_ego): following_lead = self.lead_one.status and 1 < lead_distance < self.model_length + stopping_distance following_lead &= v_ego > CRUISING_SPEED or self.tracking_lead self.tracking_lead_mac.add_data(following_lead) - self.tracking_lead = self.tracking_lead_mac.get_moving_average() >= PROBABILITY - - def update_follow_values(self, lead_distance, stopping_distance, v_ego, v_lead, frogpilot_toggles): - # Offset by FrogAi for FrogPilot for a more natural approach to a faster lead - if frogpilot_toggles.aggressive_acceleration and v_lead > v_ego: - distance_factor = max(lead_distance - (v_ego * self.t_follow), 1) - standstill_offset = max(stopping_distance - v_ego, 0) * max(v_lead - v_ego, 0) - acceleration_offset = clip((v_lead - v_ego) + standstill_offset - COMFORT_BRAKE, 1, distance_factor) - self.acceleration_jerk = self.base_acceleration_jerk / acceleration_offset - self.danger_jerk = self.base_danger_jerk / acceleration_offset - self.speed_jerk = self.base_speed_jerk / acceleration_offset - self.t_follow /= acceleration_offset - - # Offset by FrogAi for FrogPilot for a more natural approach to a slower lead - if (frogpilot_toggles.conditional_slower_lead or frogpilot_toggles.smoother_braking) and v_lead < v_ego: - distance_factor = max(lead_distance - (v_lead * self.t_follow), 1) - far_lead_offset = max(lead_distance - (v_ego * self.t_follow) - stopping_distance + (v_lead - CITY_SPEED_LIMIT), 0) - braking_offset = clip((v_ego - v_lead) + far_lead_offset - COMFORT_BRAKE, 1, distance_factor) - if frogpilot_toggles.smoother_braking: - self.acceleration_jerk = self.base_acceleration_jerk * min(braking_offset, COMFORT_BRAKE / 2) - self.speed_jerk = self.base_speed_jerk * min(braking_offset, COMFORT_BRAKE * 2) - self.t_follow /= braking_offset - self.slower_lead = braking_offset - far_lead_offset > 1 - - def update_v_cruise(self, carState, controlsState, frogpilotCarState, frogpilotNavigation, modelData, v_cruise, v_ego, frogpilot_toggles): - v_cruise_cluster = max(controlsState.vCruiseCluster, v_cruise) * CV.KPH_TO_MS - v_cruise_diff = v_cruise_cluster - v_cruise - - v_ego_cluster = max(carState.vEgoCluster, v_ego) - v_ego_diff = v_ego_cluster - v_ego - - # Pfeiferj's Map Turn Speed Controller - if frogpilot_toggles.map_turn_speed_controller and v_ego > CRUISING_SPEED and controlsState.enabled: - mtsc_active = self.mtsc_target < v_cruise - self.mtsc_target = clip(self.mtsc.target_speed(v_ego, carState.aEgo), CRUISING_SPEED, v_cruise) - - if frogpilot_toggles.mtsc_curvature_check and self.road_curvature < 1.0 and not mtsc_active: - self.mtsc_target = v_cruise - if self.mtsc_target == CRUISING_SPEED: - self.mtsc_target = v_cruise - else: - self.mtsc_target = v_cruise if v_cruise != V_CRUISE_UNSET else 0 - - # Pfeiferj's Speed Limit Controller - if frogpilot_toggles.speed_limit_controller: - SpeedLimitController.update(frogpilotCarState.dashboardSpeedLimit, controlsState.enabled, frogpilotNavigation.navigationSpeedLimit, v_cruise, v_ego, frogpilot_toggles) - unconfirmed_slc_target = SpeedLimitController.desired_speed_limit - - if frogpilot_toggles.speed_limit_confirmation and self.slc_target != 0: - if self.params_memory.get_bool("SLCConfirmed"): - self.slc_target = unconfirmed_slc_target - self.params_memory.put_bool("SLCConfirmed", False) - else: - self.slc_target = unconfirmed_slc_target - - self.override_slc = self.overridden_speed > self.slc_target - self.override_slc |= carState.gasPressed and v_ego > self.slc_target - self.override_slc &= controlsState.enabled - - if self.override_slc: - if frogpilot_toggles.speed_limit_controller_override_manual: - if carState.gasPressed: - self.overridden_speed = v_ego + v_ego_diff - self.overridden_speed = clip(self.overridden_speed, self.slc_target, v_cruise + v_cruise_diff) - elif frogpilot_toggles.speed_limit_controller_override_set_speed: - self.overridden_speed = v_cruise + v_cruise_diff - else: - self.overridden_speed = 0 - else: - self.slc_target = 0 - - # Pfeiferj's Vision Turn Controller - if frogpilot_toggles.vision_turn_controller and v_ego > CRUISING_SPEED and controlsState.enabled: - adjusted_road_curvature = self.road_curvature * frogpilot_toggles.curve_sensitivity - adjusted_target_lat_a = TARGET_LAT_A * frogpilot_toggles.turn_aggressiveness - - self.vtsc_target = (adjusted_target_lat_a / adjusted_road_curvature)**0.5 - self.vtsc_target = clip(self.vtsc_target, CRUISING_SPEED, v_cruise) - else: - self.vtsc_target = v_cruise if v_cruise != V_CRUISE_UNSET else 0 - - if frogpilot_toggles.force_standstill and carState.standstill and not self.override_force_stop and controlsState.enabled: - self.forcing_stop = True - self.v_cruise = -1 - - elif frogpilot_toggles.force_stops and self.cem.stop_light_detected and not self.override_force_stop and controlsState.enabled: - if self.tracked_model_length == 0: - self.tracked_model_length = self.model_length - - self.forcing_stop = True - self.tracked_model_length -= v_ego * DT_MDL - self.v_cruise = min((self.tracked_model_length / PLANNER_TIME) - 1, v_cruise) - - else: - if not self.cem.stop_light_detected: - self.override_force_stop = False - - self.forcing_stop = False - self.tracked_model_length = 0 - - targets = [self.mtsc_target, max(self.overridden_speed, self.slc_target) - v_ego_diff, self.vtsc_target] - self.v_cruise = float(min([target if target > CRUISING_SPEED else v_cruise for target in targets])) + return self.tracking_lead_mac.get_weighted_average() >= THRESHOLD def publish(self, sm, pm, frogpilot_toggles): frogpilot_plan_send = messaging.new_message('frogpilotPlan') frogpilot_plan_send.valid = sm.all_checks(service_list=['carState', 'controlsState']) frogpilotPlan = frogpilot_plan_send.frogpilotPlan - frogpilotPlan.accelerationJerk = float(A_CHANGE_COST * self.acceleration_jerk) - frogpilotPlan.accelerationJerkStock = float(A_CHANGE_COST * self.base_acceleration_jerk) - frogpilotPlan.dangerJerk = float(DANGER_ZONE_COST * self.danger_jerk) - frogpilotPlan.speedJerk = float(J_EGO_COST * self.speed_jerk) - frogpilotPlan.speedJerkStock = float(J_EGO_COST * self.base_speed_jerk) - frogpilotPlan.tFollow = float(self.t_follow) + frogpilotPlan.accelerationJerk = float(A_CHANGE_COST * self.frogpilot_following.acceleration_jerk) + frogpilotPlan.accelerationJerkStock = float(A_CHANGE_COST * self.frogpilot_following.base_acceleration_jerk) + frogpilotPlan.dangerJerk = float(DANGER_ZONE_COST * self.frogpilot_following.danger_jerk) + frogpilotPlan.speedJerk = float(J_EGO_COST * self.frogpilot_following.speed_jerk) + frogpilotPlan.speedJerkStock = float(J_EGO_COST * self.frogpilot_following.base_speed_jerk) + frogpilotPlan.tFollow = float(self.frogpilot_following.t_follow) + + frogpilotPlan.adjustedCruise = float(min(self.frogpilot_vcruise.mtsc_target, self.frogpilot_vcruise.vtsc_target) * (CV.MS_TO_KPH if frogpilot_toggles.is_metric else CV.MS_TO_MPH)) + frogpilotPlan.vtscControllingCurve = bool(self.frogpilot_vcruise.mtsc_target > self.frogpilot_vcruise.vtsc_target) - frogpilotPlan.adjustedCruise = float(min(self.mtsc_target, self.vtsc_target) * (CV.MS_TO_KPH if frogpilot_toggles.is_metric else CV.MS_TO_MPH)) - frogpilotPlan.vtscControllingCurve = bool(self.mtsc_target > self.vtsc_target) + frogpilotPlan.alwaysOnLateralActive = self.always_on_lateral_active - frogpilotPlan.conditionalExperimentalActive = self.cem.experimental_mode + frogpilotPlan.desiredFollowDistance = self.frogpilot_following.safe_obstacle_distance - self.frogpilot_following.stopped_equivalence_factor + frogpilotPlan.safeObstacleDistance = self.frogpilot_following.safe_obstacle_distance + frogpilotPlan.safeObstacleDistanceStock = self.frogpilot_following.safe_obstacle_distance_stock + frogpilotPlan.stoppedEquivalenceFactor = self.frogpilot_following.stopped_equivalence_factor - frogpilotPlan.desiredFollowDistance = self.safe_obstacle_distance - self.stopped_equivalence_factor - frogpilotPlan.safeObstacleDistance = self.safe_obstacle_distance - frogpilotPlan.safeObstacleDistanceStock = self.safe_obstacle_distance_stock - frogpilotPlan.stoppedEquivalenceFactor = self.stopped_equivalence_factor + frogpilotPlan.experimentalMode = self.cem.experimental_mode or self.frogpilot_vcruise.slc.experimental_mode - frogpilotPlan.forcingStop = self.forcing_stop + frogpilotPlan.forcingStop = self.frogpilot_vcruise.forcing_stop - frogpilotPlan.greenLight = not self.model_stopped - frogpilotPlan.redLight = self.cem.stop_light_detected + frogpilotPlan.frogpilotEvents = self.frogpilot_events.events.to_msg() frogpilotPlan.laneWidthLeft = self.lane_width_left frogpilotPlan.laneWidthRight = self.lane_width_right - frogpilotPlan.leadDeparting = self.lead_departing + frogpilotPlan.lateralCheck = self.lateral_check - frogpilotPlan.maxAcceleration = float(self.max_accel) - frogpilotPlan.minAcceleration = float(self.min_accel) + frogpilotPlan.maxAcceleration = float(self.frogpilot_acceleration.max_accel) + frogpilotPlan.minAcceleration = float(self.frogpilot_acceleration.min_accel) - frogpilotPlan.slcOverridden = bool(self.override_slc) - frogpilotPlan.slcOverriddenSpeed = float(self.overridden_speed) - frogpilotPlan.slcSpeedLimit = self.slc_target - frogpilotPlan.slcSpeedLimitOffset = SpeedLimitController.offset - frogpilotPlan.unconfirmedSlcSpeedLimit = SpeedLimitController.desired_speed_limit + frogpilotPlan.redLight = bool(self.cem.stop_light_detected) - frogpilotPlan.takingCurveQuickly = self.taking_curve_quickly + frogpilotPlan.slcOverridden = bool(self.frogpilot_vcruise.override_slc) + frogpilotPlan.slcOverriddenSpeed = float(self.frogpilot_vcruise.overridden_speed) + frogpilotPlan.slcSpeedLimit = self.frogpilot_vcruise.slc_target + frogpilotPlan.slcSpeedLimitOffset = self.frogpilot_vcruise.slc.offset + frogpilotPlan.speedLimitChanged = self.frogpilot_vcruise.speed_limit_changed + frogpilotPlan.unconfirmedSlcSpeedLimit = self.frogpilot_vcruise.slc.desired_speed_limit frogpilotPlan.vCruise = self.v_cruise diff --git a/selfdrive/frogpilot/controls/lib/conditional_experimental_mode.py b/selfdrive/frogpilot/controls/lib/conditional_experimental_mode.py index e5e304670ec82d..a15dad77f96dab 100644 --- a/selfdrive/frogpilot/controls/lib/conditional_experimental_mode.py +++ b/selfdrive/frogpilot/controls/lib/conditional_experimental_mode.py @@ -1,22 +1,15 @@ -from openpilot.common.params import Params -from openpilot.selfdrive.modeld.constants import ModelConstants - -from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import MovingAverageCalculator -from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import CITY_SPEED_LIMIT, PROBABILITY -from openpilot.selfdrive.frogpilot.controls.lib.speed_limit_controller import SpeedLimitController - -MODEL_LENGTH = ModelConstants.IDX_N -PLANNER_TIME = ModelConstants.T_IDXS[MODEL_LENGTH - 1] +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import WeightedMovingAverageCalculator +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import CITY_SPEED_LIMIT, THRESHOLD class ConditionalExperimentalMode: def __init__(self, FrogPilotPlanner): - self.params_memory = Params("/dev/shm/params") - self.frogpilot_planner = FrogPilotPlanner - self.curvature_mac = MovingAverageCalculator() - self.slow_lead_mac = MovingAverageCalculator() - self.stop_light_mac = MovingAverageCalculator() + self.params_memory = self.frogpilot_planner.params_memory + + self.curvature_wmac = WeightedMovingAverageCalculator(window_size=5) + self.slow_lead_wmac = WeightedMovingAverageCalculator(window_size=3) + self.stop_light_wmac = WeightedMovingAverageCalculator(window_size=5) self.curve_detected = False self.experimental_mode = False @@ -34,6 +27,7 @@ def update(self, carState, frogpilotNavigation, modelData, v_ego, v_lead, frogpi self.params_memory.put_int("CEStatus", self.status_value if self.experimental_mode else 0) else: self.experimental_mode = self.status_value in {2, 4, 6} or carState.standstill and self.experimental_mode + self.stop_light_detected = False def check_conditions(self, carState, frogpilotNavigation, modelData, tracking_lead, v_ego, v_lead, frogpilot_toggles): below_speed = frogpilot_toggles.conditional_limit > v_ego >= 1 and not tracking_lead @@ -59,42 +53,47 @@ def check_conditions(self, carState, frogpilotNavigation, modelData, tracking_le self.status_value = 13 if v_lead < 1 else 14 return True - if frogpilot_toggles.conditional_stop_lights and self.stop_light_detected: - self.status_value = 15 if not self.frogpilot_planner.forcing_stop else 16 + if frogpilot_toggles.conditional_model_stop_time != 0 and self.stop_light_detected: + self.status_value = 15 if not self.frogpilot_planner.frogpilot_vcruise.forcing_stop else 16 return True - if SpeedLimitController.experimental_mode: + if self.frogpilot_planner.frogpilot_vcruise.slc.experimental_mode: self.status_value = 17 return True - return False - def update_conditions(self, tracking_lead, v_ego, v_lead, frogpilot_toggles): - self.curve_detection(v_ego, frogpilot_toggles) + self.curve_detection(tracking_lead, v_ego, frogpilot_toggles) self.slow_lead(tracking_lead, v_lead, frogpilot_toggles) self.stop_sign_and_light(tracking_lead, v_ego, frogpilot_toggles) - def curve_detection(self, v_ego, frogpilot_toggles): - curve_detected = (1 / self.frogpilot_planner.road_curvature)**0.5 < v_ego - curve_active = (0.9 / self.frogpilot_planner.road_curvature)**0.5 < v_ego and self.curve_detected + def curve_detection(self, tracking_lead, v_ego, frogpilot_toggles): + if frogpilot_toggles.conditional_curves_lead or not tracking_lead: + curve_detected = (1 / self.frogpilot_planner.road_curvature)**0.5 < v_ego + curve_active = (0.9 / self.frogpilot_planner.road_curvature)**0.5 < v_ego and self.curve_detected - self.curvature_mac.add_data(curve_detected or curve_active) - self.curve_detected = self.curvature_mac.get_moving_average() >= PROBABILITY + self.curvature_wmac.add_data(curve_detected or curve_active) + self.curve_detected = self.curvature_wmac.get_weighted_average() >= THRESHOLD + else: + self.curvature_wmac.reset_data() + self.curve_detected = False def slow_lead(self, tracking_lead, v_lead, frogpilot_toggles): if tracking_lead: - slower_lead = self.frogpilot_planner.slower_lead and frogpilot_toggles.conditional_slower_lead + slower_lead = self.frogpilot_planner.frogpilot_following.slower_lead and frogpilot_toggles.conditional_slower_lead stopped_lead = frogpilot_toggles.conditional_stopped_lead and v_lead < 1 - self.slow_lead_mac.add_data(slower_lead or stopped_lead) - self.slow_lead_detected = self.slow_lead_mac.get_moving_average() >= PROBABILITY + self.slow_lead_wmac.add_data(slower_lead or stopped_lead) + self.slow_lead_detected = self.slow_lead_wmac.get_weighted_average() >= THRESHOLD else: - self.slow_lead_mac.reset_data() + self.slow_lead_wmac.reset_data() self.slow_lead_detected = False def stop_sign_and_light(self, tracking_lead, v_ego, frogpilot_toggles): - model_projection = PLANNER_TIME - (5 if frogpilot_toggles.less_sensitive_lights else 3) - model_stopping = self.frogpilot_planner.model_length < v_ego * model_projection + if not (self.curve_detected or tracking_lead): + model_stopping = self.frogpilot_planner.model_length < v_ego * frogpilot_toggles.conditional_model_stop_time - self.stop_light_mac.add_data((self.frogpilot_planner.model_stopped or model_stopping) and not (self.curve_detected or tracking_lead)) - self.stop_light_detected = self.stop_light_mac.get_moving_average() >= PROBABILITY + self.stop_light_wmac.add_data(self.frogpilot_planner.model_stopped or model_stopping) + self.stop_light_detected = self.stop_light_wmac.get_weighted_average() >= THRESHOLD + else: + self.stop_light_wmac.reset_data() + self.stop_light_detected = False diff --git a/selfdrive/frogpilot/controls/lib/download_functions.py b/selfdrive/frogpilot/controls/lib/download_functions.py new file mode 100644 index 00000000000000..7aa23ef44b816f --- /dev/null +++ b/selfdrive/frogpilot/controls/lib/download_functions.py @@ -0,0 +1,100 @@ +import os +import requests + +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import delete_file, is_url_pingable + +GITHUB_URL = "https://raw.githubusercontent.com/FrogAi/FrogPilot-Resources/" +GITLAB_URL = "https://gitlab.com/FrogAi/FrogPilot-Resources/-/raw/" + +def download_file(cancel_param, destination, progress_param, url, download_param, params_memory): + try: + os.makedirs(os.path.dirname(destination), exist_ok=True) + total_size = get_remote_file_size(url) + if total_size == 0: + return + + downloaded_size = 0 + with requests.get(url, stream=True, timeout=5) as response, open(destination, 'wb') as file: + response.raise_for_status() + for chunk in response.iter_content(chunk_size=8192): + if params_memory.get_bool(cancel_param): + handle_error(destination, "Download cancelled.", "Download cancelled.", download_param, progress_param, params_memory) + return + + if chunk: + file.write(chunk) + downloaded_size += len(chunk) + progress = (downloaded_size / total_size) * 100 + + if progress != 100: + params_memory.put(progress_param, f"{progress:.0f}%") + else: + params_memory.put(progress_param, "Verifying authenticity...") + except Exception as e: + handle_request_error(e, destination, download_param, progress_param, params_memory) + +def handle_error(destination, error_message, error, download_param, progress_param, params_memory): + print(f"Error occurred: {error}") + if destination: + delete_file(destination) + if download_param: + params_memory.remove(download_param) + if progress_param: + params_memory.put(progress_param, error_message) + +def handle_request_error(error, destination, download_param, progress_param, params_memory): + if isinstance(error, requests.HTTPError): + if error.response.status_code == 404: + return + error_message = f"Server error ({error.response.status_code})" if error.response else "Server error." + elif isinstance(error, requests.ConnectionError): + error_message = "Connection dropped." + elif isinstance(error, requests.Timeout): + error_message = "Download timed out." + elif isinstance(error, requests.RequestException): + error_message = "Network request error. Check connection." + else: + error_message = "Unexpected error." + + handle_error(destination, f"Failed: {error_message}", error, download_param, progress_param, params_memory) + +def get_remote_file_size(url): + try: + response = requests.head(url, headers={'Accept-Encoding': 'identity'}, timeout=5) + response.raise_for_status() + return int(response.headers.get('Content-Length', 0)) + except requests.HTTPError as e: + if e.response.status_code == 404: + return 0 + else: + handle_request_error(e, None, None, None, None) + except Exception as e: + handle_request_error(e, None, None, None, None) + return 0 + +def get_repository_url(): + if is_url_pingable("https://github.com"): + return GITHUB_URL + if is_url_pingable("https://gitlab.com"): + return GITLAB_URL + return None + +def link_valid(url): + try: + response = requests.head(url, allow_redirects=True, timeout=5) + response.raise_for_status() + return True + except Exception as e: + handle_request_error(e, None, None, None, None) + return False + +def verify_download(file_path, url): + if not os.path.exists(file_path): + print(f"File not found: {file_path}") + return False + + remote_file_size = get_remote_file_size(url) + if remote_file_size is None: + return False + + return remote_file_size == os.path.getsize(file_path) diff --git a/selfdrive/frogpilot/controls/lib/frogpilot_acceleration.py b/selfdrive/frogpilot/controls/lib/frogpilot_acceleration.py new file mode 100644 index 00000000000000..ae77c0514b0924 --- /dev/null +++ b/selfdrive/frogpilot/controls/lib/frogpilot_acceleration.py @@ -0,0 +1,91 @@ +from openpilot.common.numpy_fast import clip, interp + +from openpilot.selfdrive.car.interfaces import ACCEL_MIN, ACCEL_MAX +from openpilot.selfdrive.controls.lib.longitudinal_planner import A_CRUISE_MIN, get_max_accel + +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import CITY_SPEED_LIMIT, CRUISING_SPEED, get_max_allowed_accel + +A_CRUISE_MIN_ECO = A_CRUISE_MIN / 4 +A_CRUISE_MIN_SPORT = A_CRUISE_MIN / 2 + + # MPH = [ 0., 11, 22, 34, 45, 56, 89] +A_CRUISE_MAX_BP_CUSTOM = [ 0., 5., 10., 15., 20., 25., 40.] +A_CRUISE_MAX_VALS_ECO = [1.4, 1.3, 1.2, 1.1, 1.0, 0.8, 0.6] +A_CRUISE_MAX_VALS_SPORT = [3.0, 2.5, 2.0, 1.5, 1.0, 0.8, 0.6] +A_CRUISE_MAX_VALS_SPORT_PLUS = [4.0, 3.5, 3.0, 2.0, 1.0, 0.8, 0.6] + +def get_max_accel_eco(v_ego): + return interp(v_ego, A_CRUISE_MAX_BP_CUSTOM, A_CRUISE_MAX_VALS_ECO) + +def get_max_accel_sport(v_ego): + return interp(v_ego, A_CRUISE_MAX_BP_CUSTOM, A_CRUISE_MAX_VALS_SPORT) + +def get_max_accel_sport_plus(v_ego): + return interp(v_ego, A_CRUISE_MAX_BP_CUSTOM, A_CRUISE_MAX_VALS_SPORT_PLUS) + +def get_max_accel_ramp_off(max_accel, v_cruise, v_ego): + return interp(v_ego, [0., v_cruise * 0.75, v_cruise * 0.9, v_cruise], [max_accel, max_accel, max_accel / 2, max_accel / 4]) + +class FrogPilotAcceleration: + def __init__(self, FrogPilotPlanner): + self.frogpilot_planner = FrogPilotPlanner + + self.acceleration_jerk = 0 + self.base_acceleration_jerk = 0 + self.base_speed_jerk = 0 + self.danger_jerk = 0 + self.max_accel = 0 + self.min_accel = 0 + self.safe_obstacle_distance = 0 + self.safe_obstacle_distance_stock = 0 + self.speed_jerk = 0 + self.stopped_equivalence_factor = 0 + self.t_follow = 0 + + def update(self, controlsState, frogpilotCarState, v_cruise, v_ego, frogpilot_toggles): + eco_gear = frogpilotCarState.ecoGear + sport_gear = frogpilotCarState.sportGear + + if frogpilotCarState.trafficModeActive: + self.max_accel = get_max_accel(v_ego) + elif frogpilot_toggles.map_acceleration and (eco_gear or sport_gear): + if eco_gear: + self.max_accel = get_max_accel_eco(v_ego) + else: + if frogpilot_toggles.acceleration_profile == 3: + self.max_accel = get_max_accel_sport_plus(v_ego) + else: + self.max_accel = get_max_accel_sport(v_ego) + else: + if frogpilot_toggles.acceleration_profile == 1: + self.max_accel = get_max_accel_eco(v_ego) + elif frogpilot_toggles.acceleration_profile == 2: + self.max_accel = get_max_accel_sport(v_ego) + elif frogpilot_toggles.acceleration_profile == 3: + self.max_accel = get_max_accel_sport_plus(v_ego) + elif controlsState.experimentalMode: + self.max_accel = ACCEL_MAX + else: + self.max_accel = get_max_accel(v_ego) + + if frogpilot_toggles.human_acceleration: + if self.frogpilot_planner.tracking_lead and self.frogpilot_planner.lead_one.dRel < CITY_SPEED_LIMIT * 2 and not frogpilotCarState.trafficModeActive: + self.max_accel = clip(self.frogpilot_planner.lead_one.aLeadK, get_max_accel_sport_plus(v_ego), get_max_allowed_accel(v_ego)) + self.max_accel = get_max_accel_ramp_off(self.max_accel, self.frogpilot_planner.v_cruise, v_ego) + + if controlsState.experimentalMode: + self.min_accel = ACCEL_MIN + elif min(self.frogpilot_planner.frogpilot_vcruise.mtsc_target, self.frogpilot_planner.frogpilot_vcruise.vtsc_target) < v_cruise: + self.min_accel = A_CRUISE_MIN + elif frogpilot_toggles.map_deceleration and (eco_gear or sport_gear): + if eco_gear: + self.min_accel = A_CRUISE_MIN_ECO + else: + self.min_accel = A_CRUISE_MIN_SPORT + else: + if frogpilot_toggles.deceleration_profile == 1: + self.min_accel = A_CRUISE_MIN_ECO + elif frogpilot_toggles.deceleration_profile == 2: + self.min_accel = A_CRUISE_MIN_SPORT + else: + self.min_accel = A_CRUISE_MIN diff --git a/selfdrive/frogpilot/controls/lib/frogpilot_events.py b/selfdrive/frogpilot/controls/lib/frogpilot_events.py new file mode 100644 index 00000000000000..25b23638cec278 --- /dev/null +++ b/selfdrive/frogpilot/controls/lib/frogpilot_events.py @@ -0,0 +1,169 @@ +import os +import random + +import openpilot.system.sentry as sentry + +from openpilot.common.conversions import Conversions as CV +from openpilot.common.params import Params +from openpilot.common.realtime import DT_MDL +from openpilot.selfdrive.controls.controlsd import Desire +from openpilot.selfdrive.controls.lib.events import EventName, Events + +from openpilot.selfdrive.frogpilot.controls.lib.theme_manager import update_wheel_image + +class FrogPilotEvents: + def __init__(self, FrogPilotPlanner): + self.params = Params() + self.params_memory = Params("/dev/shm/params") + + self.events = Events() + self.frogpilot_planner = FrogPilotPlanner + + self.accel30_played = False + self.accel35_played = False + self.accel40_played = False + self.dejaVu_played = False + self.fcw_played = False + self.firefox_played = False + self.goat_played = False + self.holiday_theme_played = False + self.no_entry_alert_played = False + self.openpilot_crashed_played = False + self.previous_traffic_mode = False + self.random_event_played = False + self.stopped_for_light = False + self.vCruise69_played = False + + self.frame = 0 + self.max_acceleration = 0 + self.random_event_timer = 0 + + def update(self, carState, controlsState, frogpilotCarControl, frogpilotCarState, modelData, frogpilot_toggles): + v_cruise = max(controlsState.vCruise, controlsState.vCruiseCluster) + + self.events.clear() + + if self.random_event_played: + self.random_event_timer += DT_MDL + if self.random_event_timer >= 4: + update_wheel_image(frogpilot_toggles.wheel_image, None, False) + self.params_memory.put_bool("UpdateWheelImage", True) + self.random_event_played = False + self.random_event_timer = 0 + + if self.frogpilot_planner.frogpilot_vcruise.forcing_stop: + self.events.add(EventName.forcingStop) + + if frogpilot_toggles.green_light_alert and not self.frogpilot_planner.tracking_lead and carState.standstill: + if not self.frogpilot_planner.model_stopped and self.stopped_for_light: + self.events.add(EventName.greenLight) + self.stopped_for_light = self.frogpilot_planner.cem.stop_light_detected + else: + self.stopped_for_light = False + + if not self.holiday_theme_played and frogpilot_toggles.current_holiday_theme != None and self.frame >= 10: + self.events.add(EventName.holidayActive) + self.holiday_theme_played = True + + if self.frogpilot_planner.lead_departing: + self.events.add(EventName.leadDeparting) + + if not self.openpilot_crashed_played and os.path.isfile(os.path.join(sentry.CRASHES_DIR, 'error.txt')): + if frogpilot_toggles.random_events: + self.events.add(EventName.openpilotCrashedRandomEvent) + else: + self.events.add(EventName.openpilotCrashed) + self.openpilot_crashed_played = True + + if not self.random_event_played and frogpilot_toggles.random_events: + acceleration = carState.aEgo + + if not carState.gasPressed: + self.max_acceleration = max(acceleration, self.max_acceleration) + else: + self.max_acceleration = 0 + + if not self.accel30_played and 3.5 > self.max_acceleration >= 3.0 and acceleration < 1.5: + self.events.add(EventName.accel30) + update_wheel_image("weeb_wheel") + self.params_memory.put_bool("UpdateWheelImage", True) + self.accel30_played = True + self.random_event_played = True + self.max_acceleration = 0 + + elif not self.accel35_played and 4.0 > self.max_acceleration >= 3.5 and acceleration < 1.5: + self.events.add(EventName.accel35) + update_wheel_image("tree_fiddy") + self.params_memory.put_bool("UpdateWheelImage", True) + self.accel35_played = True + self.random_event_played = True + self.max_acceleration = 0 + + elif not self.accel40_played and self.max_acceleration >= 4.0 and acceleration < 1.5: + self.events.add(EventName.accel40) + update_wheel_image("great_scott") + self.params_memory.put_bool("UpdateWheelImage", True) + self.accel40_played = True + self.random_event_played = True + self.max_acceleration = 0 + + if not self.dejaVu_played and self.frogpilot_planner.taking_curve_quickly: + self.events.add(EventName.dejaVuCurve) + self.dejaVu_played = True + self.random_event_played = True + + if not self.no_entry_alert_played and frogpilotCarControl.noEntryEventTriggered: + self.events.add(EventName.hal9000) + self.no_entry_alert_played = True + self.random_event_played = True + + if frogpilotCarControl.steerSaturatedEventTriggered: + event_choices = [] + if not self.firefox_played: + event_choices.append("firefoxSteerSaturated") + if not self.goat_played: + event_choices.append("goatSteerSaturated") + + if event_choices and self.frame % (100 // len(event_choices)) == 0: + event_choice = random.choice(event_choices) + if event_choice == "firefoxSteerSaturated": + self.events.add(EventName.firefoxSteerSaturated) + update_wheel_image("firefox") + self.params_memory.put_bool("UpdateWheelImage", True) + self.firefox_played = True + elif event_choice == "goatSteerSaturated": + self.events.add(EventName.goatSteerSaturated) + update_wheel_image("goat") + self.params_memory.put_bool("UpdateWheelImage", True) + self.goat_played = True + self.random_event_played = True + + if not self.vCruise69_played and 70 > v_cruise * (1 if frogpilot_toggles.is_metric else CV.KPH_TO_MPH) >= 69: + self.events.add(EventName.vCruise69) + self.vCruise69_played = True + self.random_event_played = True + + if not self.fcw_played and frogpilotCarControl.fcwEventTriggered: + self.events.add(EventName.yourFrogTriedToKillMe) + self.fcw_played = True + self.random_event_played = True + + if frogpilot_toggles.speed_limit_alert and self.frogpilot_planner.frogpilot_vcruise.speed_limit_changed: + self.events.add(EventName.speedLimitChanged) + + if self.frame == 4 and self.params.get("NNFFModelName", encoding='utf-8') is not None: + self.events.add(EventName.torqueNNLoad) + + if frogpilotCarState.trafficModeActive != self.previous_traffic_mode: + if self.previous_traffic_mode: + self.events.add(EventName.trafficModeInactive) + else: + self.events.add(EventName.trafficModeActive) + self.previous_traffic_mode = frogpilotCarState.trafficModeActive + + if modelData.meta.turnDirection == Desire.turnLeft: + self.events.add(EventName.turningLeft) + elif modelData.meta.turnDirection == Desire.turnRight: + self.events.add(EventName.turningRight) + + self.frame = round(self.frame + DT_MDL, 2) diff --git a/selfdrive/frogpilot/controls/lib/frogpilot_following.py b/selfdrive/frogpilot/controls/lib/frogpilot_following.py new file mode 100644 index 00000000000000..bc51e20d4709a5 --- /dev/null +++ b/selfdrive/frogpilot/controls/lib/frogpilot_following.py @@ -0,0 +1,87 @@ +from openpilot.common.numpy_fast import clip, interp + +from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import COMFORT_BRAKE, get_jerk_factor, get_safe_obstacle_distance, get_stopped_equivalence_factor, get_T_FOLLOW + +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import CITY_SPEED_LIMIT, CRUISING_SPEED + +TRAFFIC_MODE_BP = [0., CITY_SPEED_LIMIT] + +class FrogPilotFollowing: + def __init__(self, FrogPilotPlanner): + self.frogpilot_planner = FrogPilotPlanner + + self.slower_lead = False + + self.acceleration_jerk = 0 + self.base_acceleration_jerk = 0 + self.base_speed_jerk = 0 + self.danger_jerk = 0 + self.safe_obstacle_distance = 0 + self.safe_obstacle_distance_stock = 0 + self.speed_jerk = 0 + self.stopped_equivalence_factor = 0 + self.t_follow = 0 + + def update(self, controlsState, frogpilotCarState, lead_distance, stopping_distance, v_ego, v_lead, frogpilot_toggles): + if frogpilotCarState.trafficModeActive: + self.base_acceleration_jerk = interp(v_ego, TRAFFIC_MODE_BP, frogpilot_toggles.traffic_mode_jerk_acceleration) + self.base_danger_jerk = interp(v_ego, TRAFFIC_MODE_BP, frogpilot_toggles.traffic_mode_jerk_danger) + self.base_speed_jerk = interp(v_ego, TRAFFIC_MODE_BP, frogpilot_toggles.traffic_mode_jerk_speed) + self.t_follow = interp(v_ego, TRAFFIC_MODE_BP, frogpilot_toggles.traffic_mode_t_follow) + else: + self.base_acceleration_jerk, self.base_danger_jerk, self.base_speed_jerk = get_jerk_factor( + frogpilot_toggles.aggressive_jerk_acceleration, frogpilot_toggles.aggressive_jerk_danger, frogpilot_toggles.aggressive_jerk_speed, + frogpilot_toggles.standard_jerk_acceleration, frogpilot_toggles.standard_jerk_danger, frogpilot_toggles.standard_jerk_speed, + frogpilot_toggles.relaxed_jerk_acceleration, frogpilot_toggles.relaxed_jerk_danger, frogpilot_toggles.relaxed_jerk_speed, + frogpilot_toggles.custom_personalities, controlsState.personality + ) + self.t_follow = get_T_FOLLOW( + frogpilot_toggles.aggressive_follow, + frogpilot_toggles.standard_follow, + frogpilot_toggles.relaxed_follow, + frogpilot_toggles.custom_personalities, controlsState.personality + ) + + if self.frogpilot_planner.tracking_lead: + self.safe_obstacle_distance = int(get_safe_obstacle_distance(v_ego, self.t_follow)) + self.safe_obstacle_distance_stock = self.safe_obstacle_distance + self.stopped_equivalence_factor = int(get_stopped_equivalence_factor(v_lead)) + self.update_follow_values(lead_distance, stopping_distance, v_ego, v_lead, frogpilot_toggles) + else: + self.safe_obstacle_distance = 0 + self.safe_obstacle_distance_stock = 0 + self.stopped_equivalence_factor = 0 + self.acceleration_jerk = self.base_acceleration_jerk + self.danger_jerk = self.base_danger_jerk + self.speed_jerk = self.base_speed_jerk + + def update_follow_values(self, lead_distance, stopping_distance, v_ego, v_lead, frogpilot_toggles): + # Offset by FrogAi for FrogPilot for a more natural approach to a faster lead + if frogpilot_toggles.human_following and v_lead > v_ego: + distance_factor = max(lead_distance - (v_ego * self.t_follow), 1) + standstill_offset = max(stopping_distance - v_ego, 0) * max(v_lead - v_ego, 1) + acceleration_offset = clip((v_lead - v_ego) + standstill_offset - COMFORT_BRAKE, 1, distance_factor) + self.acceleration_jerk = self.base_acceleration_jerk / acceleration_offset + self.speed_jerk = self.base_speed_jerk / acceleration_offset + self.t_follow /= acceleration_offset + + # Offset by FrogAi for FrogPilot for a more natural approach to a slower lead + if (frogpilot_toggles.conditional_slower_lead or frogpilot_toggles.human_following) and v_lead < v_ego: + distance_factor = max(lead_distance - (v_lead * self.t_follow), 1) + far_lead_offset = max(lead_distance - (v_ego * self.t_follow) - stopping_distance + (v_lead - CITY_SPEED_LIMIT), 0) + braking_offset = clip((v_ego - v_lead) + far_lead_offset - COMFORT_BRAKE, 1, distance_factor) + if frogpilot_toggles.human_following: + self.acceleration_jerk = self.base_acceleration_jerk * braking_offset + self.speed_jerk = self.base_speed_jerk * braking_offset + self.t_follow /= braking_offset + self.slower_lead = braking_offset - far_lead_offset > 1 + + if frogpilot_toggles.human_following and v_ego < CRUISING_SPEED: + if v_ego > v_lead: + self.acceleration_jerk *= CRUISING_SPEED + self.danger_jerk = self.base_danger_jerk * CRUISING_SPEED + self.speed_jerk *= CRUISING_SPEED + else: + self.acceleration_jerk /= CRUISING_SPEED + self.danger_jerk = self.base_danger_jerk / CRUISING_SPEED + self.speed_jerk /= CRUISING_SPEED diff --git a/selfdrive/frogpilot/controls/lib/frogpilot_functions.py b/selfdrive/frogpilot/controls/lib/frogpilot_functions.py index 911416253c9154..bba42100b6a608 100644 --- a/selfdrive/frogpilot/controls/lib/frogpilot_functions.py +++ b/selfdrive/frogpilot/controls/lib/frogpilot_functions.py @@ -1,4 +1,5 @@ import datetime +import errno import filecmp import glob import http.client @@ -8,6 +9,7 @@ import socket import subprocess import sys +import tarfile import threading import time import urllib.error @@ -19,23 +21,10 @@ from openpilot.common.time import system_time_valid from openpilot.system.hardware import HARDWARE +ACTIVE_THEME_PATH = os.path.join(BASEDIR, "selfdrive", "frogpilot", "assets", "active_theme") MODELS_PATH = "/data/models" - -def delete_file(file): - try: - os.remove(file) - print(f"Deleted file: {file}") - except FileNotFoundError: - print(f"File not found: {file}") - except Exception as e: - print(f"An error occurred: {e}") - -def is_url_pingable(url, timeout=5): - try: - urllib.request.urlopen(url, timeout=timeout) - return True - except (http.client.IncompleteRead, http.client.RemoteDisconnected, socket.gaierror, socket.timeout, urllib.error.HTTPError, urllib.error.URLError): - return False +RANDOM_EVENTS_PATH = os.path.join(BASEDIR, "selfdrive", "frogpilot", "assets", "random_events") +THEME_SAVE_PATH = "/data/themes" def update_frogpilot_toggles(): def update_params(): @@ -45,58 +34,82 @@ def update_params(): params_memory.put_bool("FrogPilotTogglesUpdated", False) threading.Thread(target=update_params).start() -def run_cmd(cmd, success_msg, fail_msg): +def cleanup_backups(directory, limit, minimum_backup_size=0, compressed=False): + backups = sorted(glob.glob(os.path.join(directory, "*_auto*")), key=os.path.getmtime, reverse=True) + + if compressed: + for backup in backups: + if os.path.getsize(backup) < minimum_backup_size: + run_cmd(["sudo", "rm", "-rf", backup], f"Deleted incomplete backup: {os.path.basename(backup)}", f"Failed to delete incomplete backup: {os.path.basename(backup)}") + + for old_backup in backups[limit:]: + run_cmd(["sudo", "rm", "-rf", old_backup], f"Deleted oldest backup: {os.path.basename(old_backup)}", f"Failed to delete backup: {os.path.basename(old_backup)}") + +def backup_directory(backup, destination, success_message, fail_message, minimum_backup_size=0, params=None, compressed=False): + compressed_backup = f"{destination}.tar.gz" + in_progress_compressed_backup = f"{compressed_backup}_in_progress" + in_progress_destination = f"{destination}_in_progress" + try: - subprocess.check_call(cmd) - print(success_msg) - except subprocess.CalledProcessError as e: - print(f"{fail_msg}: {e}") - except Exception as e: - print(f"Unexpected error occurred: {e}") + if not compressed: + os.makedirs(in_progress_destination, exist_ok=False) + run_cmd(["sudo", "rsync", "-avq", os.path.join(backup, "."), in_progress_destination], success_message, fail_message) -def calculate_lane_width(lane, current_lane, road_edge): - current_x, current_y = np.array(current_lane.x), np.array(current_lane.y) + if os.path.exists(destination): + shutil.rmtree(destination) - lane_y_interp = interp(current_x, np.array(lane.x), np.array(lane.y)) - road_edge_y_interp = interp(current_x, np.array(road_edge.x), np.array(road_edge.y)) + os.rename(in_progress_destination, destination) + print(f"Backup successfully created at {destination}.") + else: + if os.path.exists(compressed_backup) or os.path.exists(in_progress_compressed_backup): + print("Backup already exists. Aborting.") + return - distance_to_lane = np.mean(abs(current_y - lane_y_interp)) - distance_to_road_edge = np.mean(abs(current_y - road_edge_y_interp)) + os.makedirs(in_progress_destination, exist_ok=True) + run_cmd(["sudo", "rsync", "-avq", os.path.join(backup, "."), in_progress_destination], success_message, fail_message) - return float(min(distance_to_lane, distance_to_road_edge)) + with tarfile.open(in_progress_compressed_backup, "w:gz") as tar: + tar.add(in_progress_destination, arcname=os.path.basename(destination)) -# Credit goes to Pfeiferj! -def calculate_road_curvature(modelData, v_ego): - orientation_rate = np.abs(modelData.orientationRate.z) - velocity = modelData.velocity.x - max_pred_lat_acc = np.amax(orientation_rate * velocity) - return abs(float(max(max_pred_lat_acc / v_ego**2, sys.float_info.min))) + shutil.rmtree(in_progress_destination) + os.rename(in_progress_compressed_backup, compressed_backup) + print(f"Backup successfully compressed to {compressed_backup}.") -def backup_directory(backup, destination, success_msg, fail_msg): - os.makedirs(destination, exist_ok=True) - try: - run_cmd(['sudo', 'cp', '-a', os.path.join(backup, '.'), destination], success_msg, fail_msg) + compressed_backup_size = os.path.getsize(compressed_backup) + if compressed_backup_size < minimum_backup_size or minimum_backup_size == 0: + params.put_int_nonblocking("MinimumBackupSize", compressed_backup_size) + + except FileExistsError: + print(f"Destination '{destination}' already exists. Backup aborted.") + except subprocess.CalledProcessError: + print(fail_message) + cleanup_backup(in_progress_destination, in_progress_compressed_backup) except OSError as e: - if e.errno == 28: + if e.errno == errno.ENOSPC: print("Not enough space to perform the backup.") else: print(f"Failed to backup due to unexpected error: {e}") + cleanup_backup(in_progress_destination, in_progress_compressed_backup) + finally: + cleanup_backup(in_progress_destination, in_progress_compressed_backup) -def cleanup_backups(directory, limit): - backups = sorted(glob.glob(os.path.join(directory, "*_auto")), key=os.path.getmtime, reverse=True) - for old_backup in backups[limit:]: - subprocess.run(['sudo', 'rm', '-rf', old_backup], check=True) - print(f"Deleted oldest backup: {os.path.basename(old_backup)}") +def cleanup_backup(in_progress_destination, in_progress_compressed_backup): + if os.path.exists(in_progress_destination): + shutil.rmtree(in_progress_destination) + if os.path.exists(in_progress_compressed_backup): + os.remove(in_progress_compressed_backup) + +def backup_frogpilot(build_metadata, params): + minimum_backup_size = params.get_int("MinimumBackupSize") -def backup_frogpilot(build_metadata): backup_path = "/data/backups" - cleanup_backups(backup_path, 4) + cleanup_backups(backup_path, 4, minimum_backup_size, True) branch = build_metadata.channel commit = build_metadata.openpilot.git_commit_date[12:-16] - backup_dir = f"{backup_path}/{branch}_{commit}_auto" - backup_directory(BASEDIR, backup_dir, f"Successfully backed up FrogPilot to {backup_dir}.", f"Failed to backup FrogPilot to {backup_dir}.") + backup_dir = os.path.join(backup_path, f"{branch}_{commit}_auto") + backup_directory(BASEDIR, backup_dir, f"Successfully backed up FrogPilot to {backup_dir}.", f"Failed to backup FrogPilot to {backup_dir}.", minimum_backup_size, params, True) def backup_toggles(params, params_storage): for key in params.all_keys(): @@ -108,99 +121,186 @@ def backup_toggles(params, params_storage): backup_path = "/data/toggle_backups" cleanup_backups(backup_path, 9) - backup_dir = f"{backup_path}/{datetime.datetime.now().strftime('%Y-%m-%d_%I-%M%p').lower()}_auto" + backup_dir = os.path.join(backup_path, datetime.datetime.now().strftime('%Y-%m-%d_%I-%M%p').lower() + "_auto") backup_directory("/data/params/d", backup_dir, f"Successfully backed up toggles to {backup_dir}.", f"Failed to backup toggles to {backup_dir}.") +def calculate_lane_width(lane, current_lane, road_edge): + current_x = np.array(current_lane.x) + current_y = np.array(current_lane.y) + + lane_y_interp = interp(current_x, np.array(lane.x), np.array(lane.y)) + road_edge_y_interp = interp(current_x, np.array(road_edge.x), np.array(road_edge.y)) + + distance_to_lane = np.mean(np.abs(current_y - lane_y_interp)) + distance_to_road_edge = np.mean(np.abs(current_y - road_edge_y_interp)) + + return float(min(distance_to_lane, distance_to_road_edge)) + +# Credit goes to Pfeiferj! +def calculate_road_curvature(modelData, v_ego): + orientation_rate = np.abs(modelData.orientationRate.z) + velocity = modelData.velocity.x + max_pred_lat_acc = np.amax(orientation_rate * velocity) + return abs(float(max(max_pred_lat_acc / v_ego**2, sys.float_info.min))) + def convert_params(params, params_storage): - def convert_param(key, action_func): + print("Starting to convert params") + + required_type = str + + def remove_param(key): try: - if params_storage.check_key(key) and params_storage.get_bool(key): - action_func() - except UnknownKeyName: - pass + value = params_storage.get(key) + value = value.decode('utf-8') if isinstance(value, bytes) else value - version = 8 + if isinstance(value, str) and value.replace('.', '', 1).isdigit(): + value = float(value) if '.' in value else int(value) - try: - if params_storage.check_key("ParamConversionVersion") and params_storage.get_int("ParamConversionVersion") == version: - print("Params already converted, moving on.") - return - except UnknownKeyName: - pass + if (required_type == int and not isinstance(value, int)) or (required_type == str and isinstance(value, int)): + params.remove(key) + params_storage.remove(key) + elif key == "CustomIcons" and value == "frog_(animated)": + params.remove(key) + params_storage.remove(key) - print("Converting params...") - convert_param("ModelSelector", lambda: params.put_nonblocking("ModelManagement", "True")) - convert_param("DragonPilotTune", lambda: params.put_nonblocking("FrogsGoMooTune", "True")) + except (UnknownKeyName, ValueError): + pass - print("Params successfully converted!") - params_storage.put_int_nonblocking("ParamConversionVersion", version) + for key in ["CustomColors", "CustomDistanceIcons", "CustomIcons", "CustomSignals", "CustomSounds", "WheelIcon"]: + remove_param(key) -def frogpilot_boot_functions(build_metadata, params, params_storage): - convert_params(params, params_storage) + print("Param conversion completed") + +def delete_file(file): + try: + os.remove(file) + print(f"Deleted file: {file}") + except FileNotFoundError: + print(f"File not found: {file}") + except Exception as e: + print(f"An error occurred: {e}") +def frogpilot_boot_functions(build_metadata, params, params_storage): while not system_time_valid(): print("Waiting for system time to become valid...") time.sleep(1) try: - backup_frogpilot(build_metadata) + backup_frogpilot(build_metadata, params) backup_toggles(params, params_storage) except subprocess.CalledProcessError as e: print(f"Backup failed: {e}") +def is_url_pingable(url, timeout=5): + try: + urllib.request.urlopen(url, timeout=timeout) + return True + except (http.client.IncompleteRead, http.client.RemoteDisconnected, socket.gaierror, socket.timeout, urllib.error.HTTPError, urllib.error.URLError): + return False + +def run_cmd(cmd, success_message, fail_message): + try: + subprocess.check_call(cmd) + print(success_message) + except subprocess.CalledProcessError as e: + print(f"{fail_message}: {e}") + except Exception as e: + print(f"Unexpected error occurred: {e}") + def setup_frogpilot(build_metadata): - remount_persist = ['sudo', 'mount', '-o', 'remount,rw', '/persist'] + remount_persist = ["sudo", "mount", "-o", "remount,rw", "/persist"] run_cmd(remount_persist, "Successfully remounted /persist as read-write.", "Failed to remount /persist.") os.makedirs("/persist/params", exist_ok=True) os.makedirs(MODELS_PATH, exist_ok=True) + os.makedirs(THEME_SAVE_PATH, exist_ok=True) + + def copy_if_exists(source, destination): + if os.path.exists(source): + shutil.copytree(source, destination, dirs_exist_ok=True) + print(f"Successfully copied {source} to {destination}.") + else: + print(f"Source directory {source} does not exist. Skipping copy.") + + frog_color_source = os.path.join(ACTIVE_THEME_PATH, "colors") + frog_color_destination = os.path.join(THEME_SAVE_PATH, "theme_packs/frog/colors") + copy_if_exists(frog_color_source, frog_color_destination) + + frog_distance_icon_source = os.path.join(ACTIVE_THEME_PATH, "distance_icons") + frog_distance_icon_destination = os.path.join(THEME_SAVE_PATH, "distance_icons/frog-animated") + copy_if_exists(frog_distance_icon_source, frog_distance_icon_destination) + + frog_icon_source = os.path.join(ACTIVE_THEME_PATH, "icons") + frog_icon_destination = os.path.join(THEME_SAVE_PATH, "theme_packs/frog-animated/icons") + copy_if_exists(frog_icon_source, frog_icon_destination) - remount_root = ['sudo', 'mount', '-o', 'remount,rw', '/'] + frog_signal_source = os.path.join(ACTIVE_THEME_PATH, "signals") + frog_signal_destination = os.path.join(THEME_SAVE_PATH, "theme_packs/frog/signals") + copy_if_exists(frog_signal_source, frog_signal_destination) + + frog_sound_source = os.path.join(ACTIVE_THEME_PATH, "sounds") + frog_sound_destination = os.path.join(THEME_SAVE_PATH, "theme_packs/frog/sounds") + copy_if_exists(frog_sound_source, frog_sound_destination) + + steering_wheel_source = os.path.join(ACTIVE_THEME_PATH, "steering_wheel") + steering_wheel_destination = os.path.join(THEME_SAVE_PATH, "steering_wheels") + + if not os.path.exists(steering_wheel_destination): + os.makedirs(steering_wheel_destination) + for item in os.listdir(steering_wheel_source): + source_item = os.path.join(steering_wheel_source, item) + destination_item = os.path.join(steering_wheel_destination, "frog.png") + shutil.copy2(source_item, destination_item) + print(f"Successfully copied {source_item} to {destination_item}.") + + remount_root = ["sudo", "mount", "-o", "remount,rw", "/"] run_cmd(remount_root, "File system remounted as read-write.", "Failed to remount file system.") - frogpilot_boot_logo = f'{BASEDIR}/selfdrive/frogpilot/assets/other_images/frogpilot_boot_logo.png' - frogpilot_boot_logo_jpg = f'{BASEDIR}/selfdrive/frogpilot/assets/other_images/frogpilot_boot_logo.jpg' + frogpilot_boot_logo = os.path.join(BASEDIR, "selfdrive", "frogpilot", "assets", "other_images", "frogpilot_boot_logo.png") + frogpilot_boot_logo_jpg = os.path.join(BASEDIR, "selfdrive", "frogpilot", "assets", "other_images", "frogpilot_boot_logo.jpg") - boot_logo_location = '/usr/comma/bg.jpg' - boot_logo_save_location = f'{BASEDIR}/selfdrive/frogpilot/assets/other_images/original_bg.jpg' + boot_logo_location = "/usr/comma/bg.jpg" + boot_logo_save_location = os.path.join(BASEDIR, "selfdrive", "frogpilot", "assets", "other_images", "original_bg.jpg") if not os.path.exists(boot_logo_save_location): shutil.copy(boot_logo_location, boot_logo_save_location) print("Successfully saved original_bg.jpg.") if filecmp.cmp(boot_logo_save_location, frogpilot_boot_logo_jpg, shallow=False): - os.remove(boot_logo_save_location) + delete_file(boot_logo_save_location) if not filecmp.cmp(frogpilot_boot_logo, boot_logo_location, shallow=False): - run_cmd(['sudo', 'cp', frogpilot_boot_logo, boot_logo_location], "Successfully replaced bg.jpg with frogpilot_boot_logo.png.", "Failed to replace boot logo.") + run_cmd(["sudo", "cp", frogpilot_boot_logo, boot_logo_location], "Successfully replaced bg.jpg with frogpilot_boot_logo.png.", "Failed to replace boot logo.") if build_metadata.channel == "FrogPilot-Development": subprocess.run(["sudo", "python3", "/persist/frogsgomoo.py"], check=True) def uninstall_frogpilot(): - boot_logo_location = '/usr/comma/bg.jpg' - boot_logo_restore_location = f'{BASEDIR}/selfdrive/frogpilot/assets/other_images/original_bg.jpg' + boot_logo_location = "/usr/comma/bg.jpg" + boot_logo_restore_location = os.path.join(BASEDIR, "selfdrive", "frogpilot", "assets", "other_images", "original_bg.jpg") - copy_cmd = ['sudo', 'cp', boot_logo_restore_location, boot_logo_location] + copy_cmd = ["sudo", "cp", boot_logo_restore_location, boot_logo_location] run_cmd(copy_cmd, "Successfully restored the original boot logo.", "Failed to restore the original boot logo.") HARDWARE.uninstall() -class MovingAverageCalculator: - def __init__(self): - self.reset_data() +class WeightedMovingAverageCalculator: + def __init__(self, window_size): + self.window_size = window_size + self.data = [] + self.weights = np.linspace(1, 2, window_size) def add_data(self, value): - if len(self.data) == 5: - self.total -= self.data.pop(0) + if len(self.data) == self.window_size: + self.data.pop(0) self.data.append(value) - self.total += value - def get_moving_average(self): + def get_weighted_average(self): if len(self.data) == 0: return None - return self.total / len(self.data) + weighted_sum = np.dot(self.data, self.weights[-len(self.data):]) + weight_total = np.sum(self.weights[-len(self.data):]) + return weighted_sum / weight_total def reset_data(self): self.data = [] - self.total = 0 diff --git a/selfdrive/frogpilot/controls/lib/frogpilot_tracking.py b/selfdrive/frogpilot/controls/lib/frogpilot_tracking.py new file mode 100644 index 00000000000000..d23ce9665241c5 --- /dev/null +++ b/selfdrive/frogpilot/controls/lib/frogpilot_tracking.py @@ -0,0 +1,34 @@ +from openpilot.common.params import Params +from openpilot.common.realtime import DT_MDL + +class FrogPilotTracking: + def __init__(self): + self.params_tracking = Params("/persist/tracking") + + self.total_drives = self.params_tracking.get_int("FrogPilotDrives") + self.total_kilometers = self.params_tracking.get_float("FrogPilotKilometers") + self.total_minutes = self.params_tracking.get_float("FrogPilotMinutes") + + self.drive_added = False + + self.drive_distance = 0 + self.drive_time = 0 + self.starting_total_minutes = self.total_minutes + + def update(self, carState): + self.drive_distance += carState.vEgo * DT_MDL + self.drive_time += DT_MDL + + if self.drive_time > 60 and carState.standstill: + self.total_kilometers += self.drive_distance / 1000 + self.params_tracking.put_float_nonblocking("FrogPilotKilometers", self.total_kilometers) + self.drive_distance = 0 + + self.total_minutes += self.drive_time / 60 + self.params_tracking.put_float_nonblocking("FrogPilotMinutes", self.total_minutes) + self.drive_time = 0 + + if not self.drive_added and (self.total_minutes - self.starting_total_minutes > 15): + self.total_drives += 1 + self.params_tracking.put_int_nonblocking("FrogPilotDrives", self.total_drives) + self.drive_added = True diff --git a/selfdrive/frogpilot/controls/lib/frogpilot_variables.py b/selfdrive/frogpilot/controls/lib/frogpilot_variables.py index 4c99b8756b3f8b..d567d83b1f5617 100644 --- a/selfdrive/frogpilot/controls/lib/frogpilot_variables.py +++ b/selfdrive/frogpilot/controls/lib/frogpilot_variables.py @@ -5,16 +5,23 @@ from cereal import car from openpilot.common.conversions import Conversions as CV -from openpilot.common.params import Params +from openpilot.common.numpy_fast import interp +from openpilot.common.params import Params, UnknownKeyName from openpilot.selfdrive.controls.lib.desire_helper import LANE_CHANGE_SPEED_MIN -from openpilot.system.version import get_build_metadata +from openpilot.selfdrive.modeld.constants import ModelConstants +from openpilot.system.hardware.power_monitoring import VBATT_PAUSE_CHARGING from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import MODELS_PATH from openpilot.selfdrive.frogpilot.controls.lib.model_manager import DEFAULT_MODEL, DEFAULT_MODEL_NAME, process_model_name -CITY_SPEED_LIMIT = 25 # 55mph is typically the minimum speed for highways -CRUISING_SPEED = 5 # Roughly the speed cars go when not touching the gas while in drive -PROBABILITY = 0.6 # 60% chance of condition being true +CITY_SPEED_LIMIT = 25 # 55mph is typically the minimum speed for highways +CRUISING_SPEED = 5 # Roughly the speed cars go when not touching the gas while in drive +MODEL_LENGTH = ModelConstants.IDX_N # Minimum length of the model +PLANNER_TIME = ModelConstants.T_IDXS[MODEL_LENGTH - 1] # Length of time the model projects out for +THRESHOLD = 0.6 # 60% chance of condition being true + +def get_max_allowed_accel(v_ego): + return interp(v_ego, [0., 5., 20.], [4.0, 4.0, 2.0]) # ISO 15622:2018 class FrogPilotVariables: def __init__(self): @@ -24,7 +31,6 @@ def __init__(self): self.params_memory = Params("/dev/shm/params") self.has_prime = self.params.get_int("PrimeType") > 0 - self.release = get_build_metadata().release_channel self.update_frogpilot_params(False) @@ -36,7 +42,7 @@ def toggles(self): def toggles_updated(self): return self.params_memory.get_bool("FrogPilotTogglesUpdated") - def update_frogpilot_params(self, started=True): + def update_frogpilot_params(self, started=True, frogpilot_process=False): toggle = self.frogpilot_toggles openpilot_installed = self.params.get_bool("HasAcceptedTerms") @@ -46,16 +52,14 @@ def update_frogpilot_params(self, started=True): if msg_bytes: with car.CarParams.from_bytes(msg_bytes) as CP: - always_on_lateral_set = self.params.get_bool("AlwaysOnLateralSet") car_make = CP.carName car_model = CP.carFingerprint - openpilot_longitudinal = CP.openpilotLongitudinalControl + toggle.openpilot_longitudinal = CP.openpilotLongitudinalControl pcm_cruise = CP.pcmCruise else: - always_on_lateral_set = False car_make = "mock" car_model = "mock" - openpilot_longitudinal = False + toggle.openpilot_longitudinal = False pcm_cruise = False toggle.is_metric = self.params.get_bool("IsMetric") @@ -69,18 +73,27 @@ def update_frogpilot_params(self, started=True): toggle.promptDistracted_volume = self.params.get_int("PromptDistractedVolume") if toggle.alert_volume_control else 100 toggle.refuse_volume = self.params.get_int("RefuseVolume") if toggle.alert_volume_control else 100 toggle.warningSoft_volume = self.params.get_int("WarningSoftVolume") if toggle.alert_volume_control else 100 - toggle.warningImmediate_volume = self.params.get_int("WarningImmediateVolume") if toggle.alert_volume_control else 100 + toggle.warningImmediate_volume = max(self.params.get_int("WarningImmediateVolume"), 25) if toggle.alert_volume_control else 100 - toggle.always_on_lateral = always_on_lateral_set and self.params.get_bool("AlwaysOnLateral") + toggle.always_on_lateral = self.params.get_bool("AlwaysOnLateral") and self.params.get_bool("AlwaysOnLateralSet") toggle.always_on_lateral_lkas = toggle.always_on_lateral and self.params.get_bool("AlwaysOnLateralLKAS") toggle.always_on_lateral_main = toggle.always_on_lateral and self.params.get_bool("AlwaysOnLateralMain") toggle.always_on_lateral_pause_speed = self.params.get_int("PauseAOLOnBrake") if toggle.always_on_lateral else 0 toggle.automatic_updates = self.params.get_bool("AutomaticUpdates") + bonus_content = self.params.get_bool("BonusContent") + toggle.goat_scream = bonus_content and self.params.get_bool("GoatScream") + holiday_themes = bonus_content and self.params.get_bool("HolidayThemes") + toggle.current_holiday_theme = self.params.get("CurrentHolidayTheme", encoding='utf-8') if holiday_themes else None + personalize_openpilot = bonus_content and self.params.get_bool("PersonalizeOpenpilot") + toggle.sound_pack = self.params.get("CustomSignals", encoding='utf-8') if personalize_openpilot else "stock" + toggle.wheel_image = self.params.get("WheelIcon", encoding='utf-8') if personalize_openpilot else "stock" + toggle.random_events = bonus_content and self.params.get_bool("RandomEvents") + toggle.cluster_offset = self.params.get_float("ClusterOffset") if car_make == "toyota" else 1 - toggle.conditional_experimental_mode = openpilot_longitudinal and self.params.get_bool("ConditionalExperimental") + toggle.conditional_experimental_mode = toggle.openpilot_longitudinal and self.params.get_bool("ConditionalExperimental") toggle.conditional_curves = toggle.conditional_experimental_mode and self.params.get_bool("CECurves") toggle.conditional_curves_lead = toggle.conditional_curves and self.params.get_bool("CECurvesLead") toggle.conditional_lead = toggle.conditional_experimental_mode and self.params.get_bool("CELead") @@ -88,26 +101,20 @@ def update_frogpilot_params(self, started=True): toggle.conditional_stopped_lead = toggle.conditional_lead and self.params.get_bool("CEStoppedLead") toggle.conditional_limit = self.params.get_int("CESpeed") * speed_conversion if toggle.conditional_experimental_mode else 0 toggle.conditional_limit_lead = self.params.get_int("CESpeedLead") * speed_conversion if toggle.conditional_experimental_mode else 0 + toggle.conditional_model_stop_time = self.params.get_int("CEModelStopTime") if toggle.conditional_experimental_mode else 0 toggle.conditional_navigation = toggle.conditional_experimental_mode and self.params.get_bool("CENavigation") toggle.conditional_navigation_intersections = toggle.conditional_navigation and self.params.get_bool("CENavigationIntersections") toggle.conditional_navigation_lead = toggle.conditional_navigation and self.params.get_bool("CENavigationLead") toggle.conditional_navigation_turns = toggle.conditional_navigation and self.params.get_bool("CENavigationTurns") toggle.conditional_signal = toggle.conditional_experimental_mode and self.params.get_bool("CESignal") - toggle.conditional_stop_lights = toggle.conditional_experimental_mode and self.params.get_bool("CEStopLights") - toggle.less_sensitive_lights = toggle.conditional_stop_lights and self.params.get_bool("CEStopLightsLessSensitive") + if toggle.conditional_experimental_mode: + self.params.put_bool("ExperimentalMode", True) custom_alerts = self.params.get_bool("CustomAlerts") toggle.green_light_alert = custom_alerts and self.params.get_bool("GreenLightAlert") toggle.lead_departing_alert = custom_alerts and self.params.get_bool("LeadDepartingAlert") toggle.loud_blindspot_alert = custom_alerts and self.params.get_bool("LoudBlindspotAlert") - custom_themes = self.params.get_bool("CustomTheme") - holiday_themes = custom_themes and self.params.get_bool("HolidayThemes") - toggle.current_holiday_theme = self.params_memory.get_int("CurrentHolidayTheme") if holiday_themes else 0 - toggle.custom_sounds = self.params.get_int("CustomSounds") if custom_themes else 0 - toggle.goat_scream = toggle.current_holiday_theme == 0 and toggle.custom_sounds == 1 and self.params.get_bool("GoatScream") - toggle.random_events = custom_themes and self.params.get_bool("RandomEvents") - custom_ui = self.params.get_bool("CustomUI") custom_paths = custom_ui and self.params.get_bool("CustomPaths") toggle.adjacent_lanes = custom_paths and self.params.get_bool("AdjacentPath") @@ -118,10 +125,10 @@ def update_frogpilot_params(self, started=True): device_shutdown_setting = self.params.get_int("DeviceShutdown") if toggle.device_management else 33 toggle.device_shutdown_time = (device_shutdown_setting - 3) * 3600 if device_shutdown_setting >= 4 else device_shutdown_setting * (60 * 15) toggle.increase_thermal_limits = toggle.device_management and self.params.get_bool("IncreaseThermalLimits") - toggle.low_voltage_shutdown = self.params.get_float("LowVoltageShutdown") if toggle.device_management else 11.8 + toggle.low_voltage_shutdown = self.params.get_float("LowVoltageShutdown") if toggle.device_management else VBATT_PAUSE_CHARGING toggle.offline_mode = toggle.device_management and self.params.get_bool("OfflineMode") - driving_personalities = openpilot_longitudinal and self.params.get_bool("DrivingPersonalities") + driving_personalities = toggle.openpilot_longitudinal and self.params.get_bool("DrivingPersonalities") toggle.custom_personalities = driving_personalities and self.params.get_bool("CustomPersonalities") aggressive_profile = toggle.custom_personalities and self.params.get_bool("AggressivePersonalityProfile") toggle.aggressive_jerk_acceleration = self.params.get_int("AggressiveJerkAcceleration") / 100. if aggressive_profile else 0.5 @@ -143,8 +150,10 @@ def update_frogpilot_params(self, started=True): toggle.traffic_mode_jerk_danger = [self.params.get_int("TrafficJerkDanger") / 100., toggle.aggressive_jerk_danger] if traffic_profile else [1.0, 1.0] toggle.traffic_mode_jerk_speed = [self.params.get_int("TrafficJerkSpeed") / 100., toggle.aggressive_jerk_speed] if traffic_profile else [0.5, 0.5] toggle.traffic_mode_t_follow = [self.params.get_float("TrafficFollow"), toggle.aggressive_follow] if traffic_profile else [0.5, 1.0] + onroad_distance_button = toggle.custom_personalities and self.params.get_bool("OnroadDistanceButton") + toggle.distance_icons = self.params.get("CustomDistanceIcons", encoding='utf-8') if onroad_distance_button else "stock" - toggle.experimental_mode_via_press = openpilot_longitudinal and self.params.get_bool("ExperimentalModeActivation") + toggle.experimental_mode_via_press = toggle.openpilot_longitudinal and self.params.get_bool("ExperimentalModeActivation") toggle.experimental_mode_via_distance = toggle.experimental_mode_via_press and self.params.get_bool("ExperimentalModeViaDistance") toggle.experimental_mode_via_lkas = not toggle.always_on_lateral_lkas and toggle.experimental_mode_via_press and self.params.get_bool("ExperimentalModeViaLKAS") @@ -164,29 +173,29 @@ def update_frogpilot_params(self, started=True): toggle.taco_tune = lateral_tune and self.params.get_bool("TacoTune") toggle.turn_desires = lateral_tune and self.params.get_bool("TurnDesires") - toggle.long_pitch = openpilot_longitudinal and car_make == "gm" and self.params.get_bool("LongPitch") + toggle.long_pitch = toggle.openpilot_longitudinal and car_make == "gm" and self.params.get_bool("LongPitch") toggle.volt_sng = car_model == "CHEVROLET_VOLT" and self.params.get_bool("VoltSNG") - longitudinal_tune = openpilot_longitudinal and self.params.get_bool("LongitudinalTune") + longitudinal_tune = toggle.openpilot_longitudinal and self.params.get_bool("LongitudinalTune") toggle.acceleration_profile = self.params.get_int("AccelerationProfile") if longitudinal_tune else 0 - toggle.aggressive_acceleration = longitudinal_tune and self.params.get_bool("AggressiveAcceleration") toggle.deceleration_profile = self.params.get_int("DecelerationProfile") if longitudinal_tune else 0 + toggle.human_acceleration = longitudinal_tune and self.params.get_bool("HumanAcceleration") + toggle.human_following = longitudinal_tune and self.params.get_bool("HumanFollowing") toggle.increased_stopping_distance = self.params.get_int("StoppingDistance") * distance_conversion if longitudinal_tune else 0 toggle.lead_detection_threshold = self.params.get_int("LeadDetectionThreshold") / 100. if longitudinal_tune else 0.5 - toggle.smoother_braking = longitudinal_tune and self.params.get_bool("SmoothBraking") toggle.sport_plus = longitudinal_tune and toggle.acceleration_profile == 3 toggle.traffic_mode = longitudinal_tune and self.params.get_bool("TrafficMode") - toggle.map_turn_speed_controller = openpilot_longitudinal and self.params.get_bool("MTSCEnabled") + toggle.map_turn_speed_controller = toggle.openpilot_longitudinal and self.params.get_bool("MTSCEnabled") toggle.mtsc_curvature_check = toggle.map_turn_speed_controller and self.params.get_bool("MTSCCurvatureCheck") self.params_memory.put_float("MapTargetLatA", 2 * (self.params.get_int("MTSCAggressiveness") / 100.)) toggle.model_manager = self.params.get_bool("ModelManagement", block=openpilot_installed) - available_models = self.params.get("AvailableModels", block=toggle.model_manager, encoding='utf-8') - available_model_names = self.params.get("AvailableModelsNames", block=toggle.model_manager, encoding='utf-8') + available_models = self.params.get("AvailableModels", block=toggle.model_manager, encoding='utf-8') or '' + available_model_names = self.params.get("AvailableModelsNames", block=toggle.model_manager, encoding='utf-8') or '' current_model = self.params_memory.get("CurrentModel", encoding='utf-8') current_model_name = self.params_memory.get("CurrentModelName", encoding='utf-8') - if toggle.model_manager and available_models and current_model is None: + if toggle.model_manager and available_models and (current_model is None or not started): toggle.model_randomizer = self.params.get_bool("ModelRandomizer") if toggle.model_randomizer: blacklisted_models = (self.params.get("BlacklistedModels", encoding='utf-8') or '').split(',') @@ -196,29 +205,26 @@ def update_frogpilot_params(self, started=True): toggle.model = self.params.get("Model", block=True, encoding='utf-8') else: toggle.model = current_model - if not os.path.exists(os.path.join(MODELS_PATH, f"{toggle.model}.thneed")): - toggle.model = DEFAULT_MODEL - current_model_name = DEFAULT_MODEL_NAME - toggle.part_model_param = "" - elif available_model_names is None: - current_model_name = DEFAULT_MODEL_NAME - toggle.part_model_param = "" - else: + if toggle.model in available_models.split(',') and os.path.exists(os.path.join(MODELS_PATH, f"{toggle.model}.thneed")): current_model_name = available_model_names.split(',')[available_models.split(',').index(toggle.model)] toggle.part_model_param = process_model_name(current_model_name) - navigation_models = self.params.get("NavigationModels", encoding='utf-8') - if navigation_models is not None: - toggle.navigationless_model = toggle.model not in navigation_models.split(',') - else: - toggle.navigationless_model = False - radarless_model = self.params.get("RadarlessModels", encoding='utf-8') - if radarless_model is not None: - toggle.radarless_model = toggle.model in radarless_model.split(',') + try: + self.params.check_key(toggle.part_model_param + "CalibrationParams") + except UnknownKeyName: + toggle.part_model_param = "" else: - toggle.radarless_model = False + toggle.model = DEFAULT_MODEL + current_model_name = DEFAULT_MODEL_NAME + toggle.part_model_param = "" + navigation_models = self.params.get("NavigationModels", encoding='utf-8') or '' + toggle.navigationless_model = navigation_models and toggle.model not in navigation_models.split(',') + radarless_models = self.params.get("RadarlessModels", encoding='utf-8') or '' + toggle.radarless_model = radarless_models and toggle.model in radarless_models.split(',') + toggle.clairvoyant_model = toggle.model == "clairvoyant-driver" toggle.secretgoodopenpilot_model = toggle.model == "secret-good-openpilot" - self.params_memory.put("CurrentModel", toggle.model) - self.params_memory.put("CurrentModelName", current_model_name) + if frogpilot_process: + self.params_memory.put("CurrentModel", toggle.model) + self.params_memory.put("CurrentModelName", current_model_name) quality_of_life_controls = self.params.get_bool("QOLControls") toggle.custom_cruise_increase = self.params.get_int("CustomCruise") if quality_of_life_controls and not pcm_cruise else 1 @@ -233,9 +239,9 @@ def update_frogpilot_params(self, started=True): toggle.reverse_cruise_increase = quality_of_life_controls and pcm_cruise and self.params.get_bool("ReverseCruise") toggle.set_speed_offset = self.params.get_int("SetSpeedOffset") * (1. if toggle.is_metric else CV.MPH_TO_KPH) if quality_of_life_controls and not pcm_cruise else 0 - toggle.sng_hack = openpilot_longitudinal and car_make == "toyota" and self.params.get_bool("SNGHack") + toggle.sng_hack = toggle.openpilot_longitudinal and car_make == "toyota" and self.params.get_bool("SNGHack") - toggle.speed_limit_controller = openpilot_longitudinal and self.params.get_bool("SpeedLimitController") + toggle.speed_limit_controller = toggle.openpilot_longitudinal and self.params.get_bool("SpeedLimitController") toggle.force_mph_dashboard = toggle.speed_limit_controller and self.params.get_bool("ForceMPHDashboard") toggle.map_speed_lookahead_higher = self.params.get_int("SLCLookaheadHigher") if toggle.speed_limit_controller else 0 toggle.map_speed_lookahead_lower = self.params.get_int("SLCLookaheadLower") if toggle.speed_limit_controller else 0 @@ -264,7 +270,7 @@ def update_frogpilot_params(self, started=True): toggle.lock_doors = toyota_doors and self.params.get_bool("LockDoors") toggle.unlock_doors = toyota_doors and self.params.get_bool("UnlockDoors") - toggle.vision_turn_controller = openpilot_longitudinal and self.params.get_bool("VisionTurnControl") + toggle.vision_turn_controller = toggle.openpilot_longitudinal and self.params.get_bool("VisionTurnControl") toggle.curve_sensitivity = self.params.get_int("CurveSensitivity") / 100. if toggle.vision_turn_controller else 1 toggle.turn_aggressiveness = self.params.get_int("TurnAggressiveness") / 100. if toggle.vision_turn_controller else 1 diff --git a/selfdrive/frogpilot/controls/lib/frogpilot_vcruise.py b/selfdrive/frogpilot/controls/lib/frogpilot_vcruise.py new file mode 100644 index 00000000000000..8b84f8247bdf5d --- /dev/null +++ b/selfdrive/frogpilot/controls/lib/frogpilot_vcruise.py @@ -0,0 +1,148 @@ +from openpilot.common.conversions import Conversions as CV +from openpilot.common.numpy_fast import clip +from openpilot.common.realtime import DT_MDL + +from openpilot.selfdrive.controls.controlsd import ButtonType +from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_UNSET + +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import CRUISING_SPEED, PLANNER_TIME +from openpilot.selfdrive.frogpilot.controls.lib.map_turn_speed_controller import MapTurnSpeedController +from openpilot.selfdrive.frogpilot.controls.lib.speed_limit_controller import SpeedLimitController + +TARGET_LAT_A = 1.9 + +class FrogPilotVCruise: + def __init__(self, FrogPilotPlanner): + self.frogpilot_planner = FrogPilotPlanner + + self.params_memory = self.frogpilot_planner.params_memory + + self.mtsc = MapTurnSpeedController() + self.slc = SpeedLimitController() + + self.forcing_stop = False + self.override_force_stop = False + self.override_slc = False + self.speed_limit_changed = False + + self.model_length = 0 + self.mtsc_target = 0 + self.overridden_speed = 0 + self.previous_speed_limit = 0 + self.slc_target = 0 + self.speed_limit_timer = 0 + self.tracked_model_length = 0 + self.vtsc_target = 0 + + def update(self, carState, controlsState, frogpilotCarControl, frogpilotCarState, frogpilotNavigation, modelData, v_cruise, v_ego, frogpilot_toggles): + self.override_force_stop |= carState.gasPressed + self.override_force_stop |= frogpilot_toggles.force_stops and carState.standstill and self.frogpilot_planner.tracking_lead + self.override_force_stop |= frogpilotCarControl.resumePressed + + v_cruise_cluster = max(controlsState.vCruiseCluster, v_cruise) * CV.KPH_TO_MS + v_cruise_diff = v_cruise_cluster - v_cruise + + v_ego_cluster = max(carState.vEgoCluster, v_ego) + v_ego_diff = v_ego_cluster - v_ego + + # Pfeiferj's Map Turn Speed Controller + if frogpilot_toggles.map_turn_speed_controller and v_ego > CRUISING_SPEED and controlsState.enabled: + mtsc_active = self.mtsc_target < v_cruise + self.mtsc_target = clip(self.mtsc.target_speed(v_ego, carState.aEgo), CRUISING_SPEED, v_cruise) + + if frogpilot_toggles.mtsc_curvature_check and self.frogpilot_planner.road_curvature < 1.0 and not mtsc_active: + self.mtsc_target = v_cruise + if self.mtsc_target == CRUISING_SPEED: + self.mtsc_target = v_cruise + else: + self.mtsc_target = v_cruise if v_cruise != V_CRUISE_UNSET else 0 + + # Pfeiferj's Speed Limit Controller + if frogpilot_toggles.speed_limit_controller: + self.slc.update(frogpilotCarState.dashboardSpeedLimit, controlsState.enabled, frogpilotNavigation.navigationSpeedLimit, v_cruise, v_ego, frogpilot_toggles) + unconfirmed_slc_target = self.slc.desired_speed_limit + + if frogpilot_toggles.speed_limit_confirmation and self.slc_target != 0: + self.speed_limit_changed = unconfirmed_slc_target != self.previous_speed_limit and abs(self.slc_target - unconfirmed_slc_target) > 1 + + speed_limit_decreased = self.speed_limit_changed and self.slc_target > unconfirmed_slc_target + speed_limit_increased = self.speed_limit_changed and self.slc_target < unconfirmed_slc_target + + accepted_via_ui = self.params_memory.get_bool("SLCConfirmedPressed") and self.params_memory.get_bool("SLCConfirmed") + denied_via_ui = self.params_memory.get_bool("SLCConfirmedPressed") and not self.params_memory.get_bool("SLCConfirmed") + + speed_limit_accepted = frogpilotCarControl.resumePressed or accepted_via_ui + speed_limit_denied = any(be.type == ButtonType.decelCruise for be in carState.buttonEvents) or denied_via_ui or self.speed_limit_timer >= 10 + + if speed_limit_accepted or speed_limit_denied: + self.previous_speed_limit = unconfirmed_slc_target + self.params_memory.put_bool("SLCConfirmed", False) + self.params_memory.put_bool("SLCConfirmedPressed", False) + + if speed_limit_decreased: + speed_limit_confirmed = not frogpilot_toggles.speed_limit_confirmation_lower or speed_limit_accepted + elif speed_limit_increased: + speed_limit_confirmed = not frogpilot_toggles.speed_limit_confirmation_higher or speed_limit_accepted + else: + speed_limit_confirmed = False + + if self.speed_limit_changed: + self.speed_limit_timer += DT_MDL + else: + self.speed_limit_timer = 0 + + if speed_limit_confirmed: + self.slc_target = unconfirmed_slc_target + self.speed_limit_changed = False + else: + self.slc_target = unconfirmed_slc_target + + self.override_slc = self.overridden_speed > self.slc_target + self.override_slc |= carState.gasPressed and v_ego > self.slc_target + self.override_slc &= controlsState.enabled + + if self.override_slc: + if frogpilot_toggles.speed_limit_controller_override_manual: + if carState.gasPressed: + self.overridden_speed = v_ego + v_ego_diff + self.overridden_speed = clip(self.overridden_speed, self.slc_target, v_cruise + v_cruise_diff) + elif frogpilot_toggles.speed_limit_controller_override_set_speed: + self.overridden_speed = v_cruise + v_cruise_diff + else: + self.overridden_speed = 0 + else: + self.slc_target = 0 + + # Pfeiferj's Vision Turn Controller + if frogpilot_toggles.vision_turn_controller and v_ego > CRUISING_SPEED and controlsState.enabled: + adjusted_road_curvature = self.frogpilot_planner.road_curvature * frogpilot_toggles.curve_sensitivity + adjusted_target_lat_a = TARGET_LAT_A * frogpilot_toggles.turn_aggressiveness + + self.vtsc_target = (adjusted_target_lat_a / adjusted_road_curvature)**0.5 + self.vtsc_target = clip(self.vtsc_target, CRUISING_SPEED, v_cruise) + else: + self.vtsc_target = v_cruise if v_cruise != V_CRUISE_UNSET else 0 + + if frogpilot_toggles.force_standstill and carState.standstill and not self.override_force_stop and controlsState.enabled: + self.forcing_stop = True + v_cruise = -1 + + elif frogpilot_toggles.force_stops and self.frogpilot_planner.cem.stop_light_detected and not self.override_force_stop and controlsState.enabled: + if self.tracked_model_length == 0: + self.tracked_model_length = self.model_length + + self.forcing_stop = True + self.tracked_model_length -= v_ego * DT_MDL + v_cruise = min((self.tracked_model_length / PLANNER_TIME) - 1, v_cruise) + + else: + if not self.frogpilot_planner.cem.stop_light_detected: + self.override_force_stop = False + + self.forcing_stop = False + self.tracked_model_length = 0 + + targets = [self.mtsc_target, max(self.overridden_speed, self.slc_target) - v_ego_diff, self.vtsc_target] + v_cruise = float(min([target if target > CRUISING_SPEED else v_cruise for target in targets])) + + return v_cruise diff --git a/selfdrive/frogpilot/controls/lib/map_turn_speed_controller.py b/selfdrive/frogpilot/controls/lib/map_turn_speed_controller.py index ea291d7633d4ac..19fe7399583b7e 100644 --- a/selfdrive/frogpilot/controls/lib/map_turn_speed_controller.py +++ b/selfdrive/frogpilot/controls/lib/map_turn_speed_controller.py @@ -10,7 +10,6 @@ R = 6373000.0 # approximate radius of earth in meters TO_RADIANS = math.pi / 180 -TO_DEGREES = 180 / math.pi TARGET_JERK = -0.6 # m/s^3 should match up with the long planner TARGET_ACCEL = -1.2 # m/s^2 should match up with the long planner TARGET_OFFSET = 1.0 # seconds - This controls how soon before the curve you reach the target velocity. It also helps diff --git a/selfdrive/frogpilot/controls/lib/model_manager.py b/selfdrive/frogpilot/controls/lib/model_manager.py index bda0cbe946accd..f3d7f68285f6cc 100644 --- a/selfdrive/frogpilot/controls/lib/model_manager.py +++ b/selfdrive/frogpilot/controls/lib/model_manager.py @@ -3,296 +3,232 @@ import re import requests import shutil -import subprocess import time import urllib.request from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params, UnknownKeyName -from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import MODELS_PATH, delete_file, is_url_pingable +from openpilot.selfdrive.frogpilot.controls.lib.download_functions import GITHUB_URL, GITLAB_URL, download_file, get_repository_url, handle_error, handle_request_error, verify_download +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import MODELS_PATH, delete_file -VERSION = "v4" - -GITHUB_REPOSITORY_URL = "https://raw.githubusercontent.com/FrogAi/FrogPilot-Resources/" -GITLAB_REPOSITORY_URL = "https://gitlab.com/FrogAi/FrogPilot-Resources/-/raw/" +VERSION = "v5" DEFAULT_MODEL = "north-dakota-v2" DEFAULT_MODEL_NAME = "North Dakota V2 (Default)" -def get_repository_url(): - if is_url_pingable("https://github.com"): - return GITHUB_REPOSITORY_URL - if is_url_pingable("https://gitlab.com"): - return GITLAB_REPOSITORY_URL - return None - -def get_remote_file_size(url): - try: - response = requests.head(url, timeout=5) - response.raise_for_status() - return int(response.headers.get('Content-Length', 0)) - except requests.RequestException as e: - print(f"Error fetching file size: {e}") - return None - def process_model_name(model_name): - model_cleaned = re.sub(r'[🗺️👀📡]', '', model_name).strip() - score_param = re.sub(r'[^a-zA-Z0-9()-]', '', model_cleaned).replace(' ', '').strip().replace('(Default)', '').replace('-', '') - cleaned_name = ''.join(score_param.split()) - print(f'Processed Model Name: {cleaned_name}') - return cleaned_name - -def handle_error(destination, error_message, error, params_memory): - print(f"Error occurred: {error}") - params_memory.put("ModelDownloadProgress", error_message) - params_memory.remove("DownloadAllModels") - params_memory.remove("ModelToDownload") - delete_file(destination) - -def verify_download(file_path, model_url): - if not os.path.exists(file_path): - return False - - remote_file_size = get_remote_file_size(model_url) - if remote_file_size is None: - return False - - return remote_file_size == os.path.getsize(file_path) - -def download_file(destination, url, params_memory): - try: - with requests.get(url, stream=True, timeout=5) as r: - r.raise_for_status() - total_size = get_remote_file_size(url) - downloaded_size = 0 - - with open(destination, 'wb') as f: - for chunk in r.iter_content(chunk_size=8192): - if params_memory.get_bool("CancelModelDownload"): - handle_error(destination, "Download cancelled...", "Download cancelled...", params_memory) - return - if chunk: - f.write(chunk) - downloaded_size += len(chunk) - progress = (downloaded_size / total_size) * 100 - if progress != 100: - params_memory.put("ModelDownloadProgress", f"{progress:.0f}%") - else: - params_memory.put("ModelDownloadProgress", "Verifying authenticity...") - - except requests.HTTPError as http_error: - handle_error(destination, f"Failed: Server error ({http_error.response.status_code})", http_error, params_memory) - except requests.ConnectionError as connection_error: - handle_error(destination, "Failed: Connection dropped...", connection_error, params_memory) - except requests.Timeout as timeout_error: - handle_error(destination, "Failed: Download timed out...", timeout_error, params_memory) - except requests.RequestException as request_error: - handle_error(destination, "Failed: Network request error. Check connection.", request_error, params_memory) - except Exception as e: - handle_error(destination, "Failed: Unexpected error.", e, params_memory) - -def handle_existing_model(model, params_memory): - print(f"Model {model} already exists, skipping download...") - params_memory.put("ModelDownloadProgress", "Model already exists...") - params_memory.remove("ModelToDownload") - -def handle_verification_failure(model, model_path, model_url, params_memory): - if params_memory.get_bool("CancelModelDownload"): - handle_error(model_path, "Download cancelled...", "Download cancelled...", params_memory) - return - - handle_error(model_path, "Issue connecting to Github, trying Gitlab", f"Model {model} verification failed. Redownloading from Gitlab...", params_memory) - second_model_url = f"{GITLAB_REPOSITORY_URL}Models/{model}.thneed" - download_file(model_path, second_model_url, params_memory) - - if verify_download(model_path, second_model_url): - print(f"Model {model} redownloaded and verified successfully from Gitlab.") - else: - print(f"Model {model} redownload verification failed from Gitlab.") - -def download_model(model_to_download, params_memory): - model_path = os.path.join(MODELS_PATH, f"{model_to_download}.thneed") - if os.path.exists(model_path): - handle_existing_model(model_to_download, params_memory) - return - - repo_url = get_repository_url() - if repo_url is None: - handle_error(model_path, "Github and Gitlab are offline...", "Github and Gitlab are offline...", params_memory) - return - - model_url = f"{repo_url}Models/{model_to_download}.thneed" - download_file(model_path, model_url, params_memory) - - if verify_download(model_path, model_url): - print(f"Model {model_to_download} downloaded and verified successfully!") - params_memory.put("ModelDownloadProgress", "Downloaded!") - params_memory.remove("ModelToDownload") - else: - handle_verification_failure(model_to_download, model_path, model_url, params_memory) - -def fetch_models(url): - try: - with urllib.request.urlopen(url) as response: - return json.loads(response.read().decode('utf-8'))['models'] - except Exception as e: - print(f"Failed to update models list. Error: {e}") - return None - -def are_all_models_downloaded(available_models, available_model_names, repo_url, params, params_memory): - automatically_update_models = params.get_bool("AutomaticallyUpdateModels") - all_models_downloaded = True - - for model in available_models: - model_path = os.path.join(MODELS_PATH, f"{model}.thneed") - model_url = f"{repo_url}Models/{model}.thneed" + cleaned_name = re.sub(r'[🗺️👀📡]', '', model_name) + cleaned_name = re.sub(r'[^a-zA-Z0-9()-]', '', cleaned_name) + return cleaned_name.replace(' ', '').replace('(Default)', '').replace('-', '') - if os.path.exists(model_path): - if automatically_update_models: - remote_file_size = get_remote_file_size(model_url) - try: - local_file_size = os.path.getsize(model_path) - except FileNotFoundError: - print(f"File not found: {model_path}. It may have been moved or deleted.") - local_file_size = 0 - - if remote_file_size is not None and remote_file_size != local_file_size: - print(f"Model {model} is outdated. Local size: {local_file_size}, Remote size: {remote_file_size}. Re-downloading...") - delete_file(model_path) - part_model_param = process_model_name(available_model_names[available_models.index(model)]) - params.remove(part_model_param + "CalibrationParams") - params.remove(part_model_param + "LiveTorqueParameters") - while params_memory.get("ModelToDownload", encoding='utf-8') is not None: - time.sleep(1) - params_memory.put("ModelToDownload", model) - all_models_downloaded = False - else: - if automatically_update_models: - while params_memory.get("ModelToDownload", encoding='utf-8') is not None: - time.sleep(1) - print(f"Model {model} is missing. Re-downloading...") - params_memory.put("ModelToDownload", model) - part_model_param = process_model_name(available_model_names[available_models.index(model)]) - params.remove(part_model_param + "CalibrationParams") - params.remove(part_model_param + "LiveTorqueParameters") - all_models_downloaded = False - - return all_models_downloaded - -def update_model_params(model_info, repo_url, params, params_memory): - available_models = [] - available_model_names = [] - experimental_models = [] - navigation_models = [] - radarless_models = [] - - for model in model_info: - available_models.append(model['id']) - available_model_names.append(model['name']) - if model.get("experimental", False): - experimental_models.append(model['id']) - if "🗺️" in model['name']: - navigation_models.append(model['id']) - if "📡" not in model['name']: - radarless_models.append(model['id']) - - params.put_nonblocking("AvailableModels", ','.join(available_models)) - params.put_nonblocking("AvailableModelsNames", ','.join(available_model_names)) - params.put_nonblocking("ExperimentalModels", ','.join(experimental_models)) - params.put_nonblocking("NavigationModels", ','.join(navigation_models)) - params.put_nonblocking("RadarlessModels", ','.join(radarless_models)) - print("Models list updated successfully.") - - if available_models is not None: - params.put_bool_nonblocking("ModelsDownloaded", are_all_models_downloaded(available_models, available_model_names, repo_url, params, params_memory)) - -def validate_models(params): - current_model = params.get("Model", encoding='utf-8') - current_model_name = params.get("ModelName", encoding='utf-8') - if "(Default)" in current_model_name and current_model_name != DEFAULT_MODEL_NAME: - params.put_nonblocking("ModelName", current_model_name.replace(" (Default)", "")) - - available_models = params.get("AvailableModels", encoding='utf-8') - if available_models is None: - return - - for model_file in os.listdir(MODELS_PATH): - if model_file.endswith('.thneed') and model_file[:-7] not in available_models.split(','): - if model_file == current_model: - params.put_nonblocking("Model", DEFAULT_MODEL) - params.put_nonblocking("ModelName", DEFAULT_MODEL_NAME) - delete_file(os.path.join(MODELS_PATH, model_file)) - print(f"Deleted model file: {model_file}") - -def copy_default_model(): - default_model_path = os.path.join(MODELS_PATH, f"{DEFAULT_MODEL}.thneed") - if not os.path.exists(default_model_path): - source_path = os.path.join(BASEDIR, "selfdrive/modeld/models/supercombo.thneed") - if os.path.exists(source_path): - shutil.copyfile(source_path, default_model_path) - print(f"Copied default model from {source_path} to {default_model_path}") - else: - print(f"Source default model not found at {source_path}. Exiting...") +class ModelManager: + def __init__(self): + self.params = Params() + self.params_memory = Params("/dev/shm/params") -def update_models(params, params_memory, boot_run=True): - try: - if boot_run: - copy_default_model() - validate_models(params) + self.cancel_download_param = "CancelModelDownload" + self.download_param = "ModelToDownload" + self.download_progress_param = "ModelDownloadProgress" + + def handle_verification_failure(self, model, model_path): + if self.params_memory.get_bool(self.cancel_download_param): + return - repo_url = get_repository_url() - if repo_url is None: + print(f"Verification failed for model {model}. Retrying from GitLab...") + model_url = f"{GITLAB_URL}Models/{model}.thneed" + download_file(self.cancel_download_param, model_path, self.download_progress_param, model_url, self.download_param, self.params_memory) + + if verify_download(model_path, model_url): + print(f"Model {model} redownloaded and verified successfully from GitLab.") + else: + handle_error(model_path, "GitLab verification failed", "Verification failed", self.download_param, self.download_progress_param, self.params_memory) + + def download_model(self, model_to_download): + model_path = os.path.join(MODELS_PATH, f"{model_to_download}.thneed") + if os.path.exists(model_path): + handle_error(model_path, "Model already exists...", "Model already exists...", self.download_param, self.download_progress_param, self.params_memory) return - model_info = fetch_models(f"{repo_url}Versions/model_names_{VERSION}.json") - if model_info is None: + self.repo_url = get_repository_url() + if not self.repo_url: + handle_error(model_path, "GitHub and GitLab are offline...", "Repository unavailable", self.download_param, self.download_progress_param, self.params_memory) return - update_model_params(model_info, repo_url, params, params_memory) - except subprocess.CalledProcessError as e: - print(f"Failed to update models. Error: {e}") + model_url = f"{self.repo_url}Models/{model_to_download}.thneed" + print(f"Downloading model: {model_to_download}") + download_file(self.cancel_download_param, model_path, self.download_progress_param, model_url, self.download_param, self.params_memory) -def download_all_models(params, params_memory): - copy_default_model() + if verify_download(model_path, model_url): + print(f"Model {model_to_download} downloaded and verified successfully!") + self.params_memory.put(self.download_progress_param, "Downloaded!") + self.params_memory.remove(self.download_param) + else: + self.handle_verification_failure(model_to_download, model_path) + + def fetch_models(self, url): + try: + with urllib.request.urlopen(url, timeout=10) as response: + return json.loads(response.read().decode('utf-8'))['models'] + except Exception as error: + handle_request_error(error, None, None, None, None) + return [] + + def update_model_params(self, model_info): + available_models, available_model_names, experimental_models, navigation_models, radarless_models = [], [], [], [], [] + + for model in model_info: + available_models.append(model['id']) + available_model_names.append(model['name']) + + if model.get("experimental", False): + experimental_models.append(model['id']) + if "🗺️" in model['name']: + navigation_models.append(model['id']) + if "📡" not in model['name']: + radarless_models.append(model['id']) + + self.params.put_nonblocking("AvailableModels", ','.join(available_models)) + self.params.put_nonblocking("AvailableModelsNames", ','.join(available_model_names)) + self.params.put_nonblocking("ExperimentalModels", ','.join(experimental_models)) + self.params.put_nonblocking("NavigationModels", ','.join(navigation_models)) + self.params.put_nonblocking("RadarlessModels", ','.join(radarless_models)) + print("Models list updated successfully.") + + if available_models: + models_downloaded = self.are_all_models_downloaded(available_models, available_model_names) + self.params.put_bool_nonblocking("ModelsDownloaded", models_downloaded) + + def are_all_models_downloaded(self, available_models, available_model_names): + automatically_update_models = self.params.get_bool("AutomaticallyUpdateModels") + all_models_downloaded = True + + for model in available_models: + model_path = os.path.join(MODELS_PATH, f"{model}.thneed") + model_url = f"{self.repo_url}Models/{model}.thneed" + + if os.path.exists(model_path): + if automatically_update_models: + if not verify_download(model_path, model_url): + print(f"Model {model} is outdated. Re-downloading...") + delete_file(model_path) + self.remove_model_params(available_model_names, available_models, model) + self.queue_model_download(model) + all_models_downloaded = False + else: + if automatically_update_models: + print(f"Model {model} isn't downloaded. Downloading...") + self.remove_model_params(available_model_names, available_models, model) + self.queue_model_download(model) + all_models_downloaded = False + + return all_models_downloaded + + def remove_model_params(self, available_model_names, available_models, model): + part_model_param = process_model_name(available_model_names[available_models.index(model)]) + try: + self.params.check_key(part_model_param + "CalibrationParams") + except UnknownKeyName: + return + self.params.remove(part_model_param + "CalibrationParams") + self.params.remove(part_model_param + "LiveTorqueParameters") - repo_url = get_repository_url() - if repo_url is None: - handle_error(None, "Github and Gitlab are offline...", "Github and Gitlab are offline...", params_memory) - return + def queue_model_download(self, model, model_name=None): + while self.params_memory.get(self.download_param, encoding='utf-8'): + time.sleep(1) - model_info = fetch_models(f"{repo_url}Versions/model_names_{VERSION}.json") - if model_info is None: - handle_error(None, "Unable to update model list...", "Unable to update model list...", params_memory) - return + self.params_memory.put(self.download_param, model) + if model_name: + self.params_memory.put(self.download_progress_param, f"Downloading {model_name}...") - update_model_params(model_info, repo_url, params, params_memory) + def validate_models(self): + current_model = self.params.get("Model", encoding='utf-8') + current_model_name = self.params.get("ModelName", encoding='utf-8') - available_models = params.get("AvailableModels", encoding='utf-8').split(',') - available_model_names = params.get("AvailableModelsNames", encoding='utf-8').split(',') + if "(Default)" in current_model_name and current_model_name != DEFAULT_MODEL_NAME: + self.params.put_nonblocking("ModelName", current_model_name.replace(" (Default)", "")) - for model in available_models: - if params_memory.get_bool("CancelModelDownload"): - handle_error(None, "Download cancelled...", "Download cancelled...", params_memory) + available_models = self.params.get("AvailableModels", encoding='utf-8') + if not available_models: return - model_path = os.path.join(MODELS_PATH, f"{model}.thneed") - if not os.path.exists(model_path): - model_index = available_models.index(model) - model_name = available_model_names[model_index] - cleaned_model_name = re.sub(r'[🗺️👀📡]', '', model_name).strip() - print(f"Downloading model: {cleaned_model_name}") - params_memory.put("ModelToDownload", model) - params_memory.put("ModelDownloadProgress", f"Downloading {cleaned_model_name}...") - while params_memory.get("ModelToDownload", encoding='utf-8') is not None: + + for model_file in os.listdir(MODELS_PATH): + if model_file.replace(".thneed", "") not in available_models.split(','): + if model_file == current_model: + self.params.put_nonblocking("Model", DEFAULT_MODEL) + self.params.put_nonblocking("ModelName", DEFAULT_MODEL_NAME) + delete_file(os.path.join(MODELS_PATH, model_file)) + print(f"Deleted model file: {model_file}") + + def copy_default_model(self): + default_model_path = os.path.join(MODELS_PATH, f"{DEFAULT_MODEL}.thneed") + + if not os.path.exists(default_model_path): + source_path = os.path.join(BASEDIR, "selfdrive", "modeld", "models", "supercombo.thneed") + + if os.path.exists(source_path): + shutil.copyfile(source_path, default_model_path) + print(f"Copied default model from {source_path} to {default_model_path}") + else: + print(f"Source default model not found at {source_path}. Exiting...") + + def update_models(self, boot_run=True): + self.repo_url = get_repository_url() + if boot_run: + self.copy_default_model() + boot_checks = 0 + while self.repo_url is None and boot_checks < 60: + boot_checks += 1 + if boot_checks > 60: + break time.sleep(1) + self.validate_models() + elif self.repo_url is None: + print("GitHub and GitLab are offline...") + return + + model_info = self.fetch_models(f"{self.repo_url}Versions/model_names_{VERSION}.json") + if model_info: + self.update_model_params(model_info) + + def download_all_models(self): + self.repo_url = get_repository_url() + if not self.repo_url: + handle_error(None, "GitHub and GitLab are offline...", "Repository unavailable", self.download_param, self.download_progress_param, self.params_memory) + return + + model_info = self.fetch_models(f"{self.repo_url}Versions/model_names_{VERSION}.json") + if not model_info: + handle_error(None, "Unable to update model list...", "Model list unavailable", self.download_param, self.download_progress_param, self.params_memory) + return - all_downloaded = False - while not all_downloaded: - if params_memory.get_bool("CancelModelDownload"): - handle_error(None, "Download cancelled...", "Download cancelled...", params_memory) + available_models = self.params.get("AvailableModels", encoding='utf-8') + if not available_models: + handle_error(None, "There's no model to download...", "There's no model to download...", self.download_param, self.download_progress_param, self.params_memory) return - all_downloaded = all([os.path.exists(os.path.join(MODELS_PATH, f"{model}.thneed")) for model in available_models]) - time.sleep(1) - params_memory.put("ModelDownloadProgress", "All models downloaded!") - params_memory.remove("DownloadAllModels") - params.put_bool_nonblocking("ModelsDownloaded", True) + available_models = available_models.split(',') + available_model_names = self.params.get("AvailableModelsNames", encoding='utf-8').split(',') + + for model in available_models: + if self.params_memory.get_bool(self.cancel_download_param): + return + + if not os.path.exists(os.path.join(MODELS_PATH, f"{model}.thneed")): + model_index = available_models.index(model) + model_name = available_model_names[model_index] + + cleaned_model_name = re.sub(r'[🗺️👀📡]', '', model_name).strip() + print(f"Downloading model: {cleaned_model_name}") + + self.queue_model_download(model, cleaned_model_name) + + while self.params_memory.get(self.download_param, encoding='utf-8'): + time.sleep(1) + + while not all(os.path.exists(os.path.join(MODELS_PATH, f"{model}.thneed")) for model in available_models): + time.sleep(1) + + self.params_memory.put(self.download_progress_param, "All models downloaded!") + self.params_memory.remove("DownloadAllModels") + self.params.put_bool_nonblocking("ModelsDownloaded", True) diff --git a/selfdrive/frogpilot/controls/lib/speed_limit_controller.py b/selfdrive/frogpilot/controls/lib/speed_limit_controller.py index bba906f87958e9..50f7a810c0aef8 100644 --- a/selfdrive/frogpilot/controls/lib/speed_limit_controller.py +++ b/selfdrive/frogpilot/controls/lib/speed_limit_controller.py @@ -20,6 +20,7 @@ def distance_to_point(ax, ay, bx, by): class SpeedLimitController: def __init__(self): self.frogpilot_toggles = FrogPilotVariables.toggles + FrogPilotVariables.update_frogpilot_params() self.params = Params() self.params_memory = Params("/dev/shm/params") @@ -125,5 +126,3 @@ def speed_limit(self): return self.max_speed_limit return 0 - -SpeedLimitController = SpeedLimitController() diff --git a/selfdrive/frogpilot/controls/lib/theme_manager.py b/selfdrive/frogpilot/controls/lib/theme_manager.py index ec4d3ab135b923..7a1e31045dc95b 100644 --- a/selfdrive/frogpilot/controls/lib/theme_manager.py +++ b/selfdrive/frogpilot/controls/lib/theme_manager.py @@ -1,14 +1,110 @@ import datetime +import glob +import os +import re +import requests +import shutil +import time +import zipfile +from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params -from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import update_frogpilot_toggles +from openpilot.selfdrive.frogpilot.controls.lib.download_functions import GITHUB_URL, GITLAB_URL, download_file, get_repository_url, handle_error, handle_request_error, link_valid, verify_download +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import ACTIVE_THEME_PATH, THEME_SAVE_PATH, delete_file, update_frogpilot_toggles + +def copy_theme_asset(asset_type, theme, holiday_theme, params): + save_location = os.path.join(ACTIVE_THEME_PATH, asset_type) + + if holiday_theme: + source_location = os.path.join(BASEDIR, "selfdrive", "frogpilot", "assets", "holiday_themes", holiday_theme, asset_type) + else: + source_location = os.path.join(THEME_SAVE_PATH, "theme_packs", theme, asset_type) + + if not os.path.exists(source_location): + if asset_type == "colors": + params.put_bool("UseStockColors", True) + print("Using the stock color scheme instead") + return + elif asset_type == "icons": + source_location = os.path.join(BASEDIR, "selfdrive", "frogpilot", "assets", "stock_theme", "icons") + print("Using the stock icon pack instead") + else: + if os.path.exists(save_location): + shutil.rmtree(save_location) + print(f"Using the stock {asset_type[:-1]} instead") + return + elif asset_type == "colors": + params.put_bool("UseStockColors", False) + + if os.path.exists(save_location): + shutil.rmtree(save_location) + + shutil.copytree(source_location, save_location) + print(f"Copied {source_location} to {save_location}") + +def update_distance_icons(pack, holiday_theme): + if holiday_theme: + distance_icons_location = os.path.join(BASEDIR, "selfdrive", "frogpilot", "assets", "holiday_themes", holiday_theme, "distance_icons") + else: + distance_icons_location = os.path.join(THEME_SAVE_PATH, "distance_icons", pack) + + if not os.path.exists(distance_icons_location): + distance_icons_location = os.path.join(BASEDIR, "selfdrive", "frogpilot", "assets", "stock_theme", "distance_icons") + + distance_icon_save_location = os.path.join(ACTIVE_THEME_PATH, "distance_icons") + + if os.path.exists(distance_icon_save_location): + shutil.rmtree(distance_icon_save_location) + + os.makedirs(distance_icon_save_location, exist_ok=True) + + for item in os.listdir(distance_icons_location): + source = os.path.join(distance_icons_location, item) + destination = os.path.join(distance_icon_save_location, item) + if os.path.isdir(source): + shutil.copytree(source, destination) + else: + shutil.copy2(source, destination) + + print(f"Copied contents of {distance_icons_location} to {distance_icon_save_location}") + +def update_wheel_image(image, holiday_theme=None, random_event=True): + if image == "stock": + wheel_location = os.path.join(BASEDIR, "selfdrive", "frogpilot", "assets", "stock_theme", "steering_wheel") + elif random_event: + wheel_location = os.path.join(BASEDIR, "selfdrive", "frogpilot", "assets", "random_events", "icons") + elif holiday_theme: + wheel_location = os.path.join(BASEDIR, "selfdrive", "frogpilot", "assets", "holiday_themes", holiday_theme, "icons") + else: + wheel_location = os.path.join(THEME_SAVE_PATH, "steering_wheels") + + if not os.path.exists(wheel_location): + return + + wheel_save_location = os.path.join(ACTIVE_THEME_PATH, "steering_wheel") + for filename in os.listdir(wheel_save_location): + if filename.startswith("wheel"): + delete_file(os.path.join(wheel_save_location, filename)) + + image_name = image.replace(" ", "_").lower() + for filename in os.listdir(wheel_location): + if os.path.splitext(filename)[0].lower() == image_name: + source_file = os.path.join(wheel_location, filename) + destination_file = os.path.join(wheel_save_location, f"wheel{os.path.splitext(filename)[1]}") + shutil.copy2(source_file, destination_file) + print(f"Copied {source_file} to {destination_file}") + break class ThemeManager: def __init__(self): + self.params = Params() self.params_memory = Params("/dev/shm/params") - self.previous_theme_id = 0 + self.cancel_download_param = "CancelThemeDownload" + self.download_progress_param = "ThemeDownloadProgress" + + self.previous_assets = {} @staticmethod def calculate_easter(year): @@ -45,28 +141,276 @@ def update_holiday(self): year = current_date.year holidays = { - "april_fools": (datetime.datetime(year, 4, 1), 1), - "christmas_week": (datetime.datetime(year, 12, 25), 2), - "cinco_de_mayo": (datetime.datetime(year, 5, 5), 3), - "easter_week": (self.calculate_easter(year), 4), - "fourth_of_july": (datetime.datetime(year, 7, 4), 5), - "halloween_week": (datetime.datetime(year, 10, 31), 6), - "new_years": (datetime.datetime(year, 1, 1), 7), - "st_patricks": (datetime.datetime(year, 3, 17), 8), - "thanksgiving_week": (self.calculate_thanksgiving(year), 9), - "valentines": (datetime.datetime(year, 2, 14), 10), - "world_frog_day": (datetime.datetime(year, 3, 20), 11) + "new_years": datetime.datetime(year, 1, 1), + "valentines": datetime.datetime(year, 2, 14), + "st_patricks": datetime.datetime(year, 3, 17), + "world_frog_day": datetime.datetime(year, 3, 20), + "april_fools": datetime.datetime(year, 4, 1), + "easter_week": self.calculate_easter(year), + "cinco_de_mayo": datetime.datetime(year, 5, 5), + "fourth_of_july": datetime.datetime(year, 7, 4), + "halloween_week": datetime.datetime(year, 10, 31), + "thanksgiving_week": self.calculate_thanksgiving(year), + "christmas_week": datetime.datetime(year, 12, 25) + } + + for holiday, date in holidays.items(): + if (holiday.endswith("_week") and self.is_within_week_of(date, current_date)) or (current_date.date() == date.date()): + if holiday != self.previous_assets.get("holiday_theme"): + self.params.put("CurrentHolidayTheme", holiday) + self.update_active_theme() + self.previous_assets["holiday_theme"] = holiday + return + + if "holiday_theme" in self.previous_assets: + self.params.remove("CurrentHolidayTheme") + self.update_active_theme() + self.previous_assets.pop("holiday_theme", None) + + def update_active_theme(self): + if not os.path.exists(THEME_SAVE_PATH): + return + + bonus_content = self.params.get_bool("BonusContent") + holiday_themes = bonus_content and self.params.get_bool("HolidayThemes") + current_holiday_theme = self.previous_assets.get("holiday_theme") if holiday_themes else None + personalize_openpilot = bonus_content and self.params.get_bool("PersonalizeOpenpilot") + + default_value = "stock" + asset_mappings = { + "color_scheme": ("colors", self.params.get("CustomColors", encoding='utf-8') if personalize_openpilot else default_value), + "distance_icons": ("distance_icons", self.params.get("CustomDistanceIcons", encoding='utf-8') if personalize_openpilot else default_value), + "icon_pack": ("icons", self.params.get("CustomIcons", encoding='utf-8') if personalize_openpilot else default_value), + "sound_pack": ("sounds", self.params.get("CustomSounds", encoding='utf-8') if personalize_openpilot else default_value), + "turn_signal_pack": ("signals", self.params.get("CustomSignals", encoding='utf-8') if personalize_openpilot else default_value), + "wheel_image": ("wheel_image", self.params.get("WheelIcon", encoding='utf-8') if personalize_openpilot else default_value) } - for holiday, (date, theme_id) in holidays.items(): - if holiday.endswith("_week") and self.is_within_week_of(date, current_date) or current_date.date() == date.date(): - if theme_id != self.previous_theme_id: - self.params_memory.put_int("CurrentHolidayTheme", theme_id) - update_frogpilot_toggles() - self.previous_theme_id = theme_id + for toggle, (asset_type, current_value) in asset_mappings.items(): + if current_value != self.previous_assets.get(toggle): + if asset_type == "distance_icons": + update_distance_icons(current_value, current_holiday_theme) + elif asset_type == "wheel_image": + update_wheel_image(current_value, current_holiday_theme, random_event=False) + else: + copy_theme_asset(asset_type, current_value, current_holiday_theme, self.params) + self.previous_assets[toggle] = current_value + self.params_memory.remove("UpdateTheme") + + update_frogpilot_toggles() + + def extract_zip(self, zip_file, extract_path): + print(f"Extracting {zip_file} to {extract_path}") + with zipfile.ZipFile(zip_file, 'r') as zip_ref: + zip_ref.extractall(extract_path) + os.remove(zip_file) + print(f"Extraction completed and zip file deleted.") + + def handle_existing_theme(self, theme_name, theme_param): + print(f"Theme {theme_name} already exists, skipping download...") + self.params_memory.put(self.download_progress_param, "Theme already exists...") + self.params_memory.remove(theme_param) + + def handle_verification_failure(self, extentions, theme_component, theme_name, theme_param, download_path): + if theme_component == "distance_icons": + download_link = f"{GITLAB_URL}Distance-Icons/{theme_name}" + elif theme_component == "steering_wheels": + download_link = f"{GITLAB_URL}Steering-Wheels/{theme_name}" + else: + download_link = f"{GITLAB_URL}Themes/{theme_name}/{theme_component}" + + for ext in extentions: + theme_path = download_path + ext + theme_url = download_link + ext + print(f"Downloading theme from GitLab: {theme_name}") + download_file(self.cancel_download_param, theme_path, self.download_progress_param, theme_url, theme_param, self.params_memory) + + if verify_download(theme_path, theme_url): + print(f"Theme {theme_name} downloaded and verified successfully from GitLab!") + if ext == ".zip": + self.params_memory.put(self.download_progress_param, "Unpacking theme...") + self.extract_zip(theme_path, os.path.join(THEME_SAVE_PATH, theme_name)) + self.params_memory.put(self.download_progress_param, "Downloaded!") + self.params_memory.remove(theme_param) + return True + + handle_error(download_path, "GitLab verification failed", "Verification failed", theme_param, self.download_progress_param, self.params_memory) + return False + + def download_theme(self, theme_component, theme_name, theme_param): + repo_url = get_repository_url() + if not repo_url: + handle_error(None, "GitHub and GitLab are offline...", "Repository unavailable", theme_param, self.download_progress_param, self.params_memory) + return + + if theme_component == "distance_icons": + download_link = f"{repo_url}Distance-Icons/{theme_name}" + download_path = os.path.join(THEME_SAVE_PATH, theme_component, theme_name) + extentions = [".zip"] + elif theme_component == "steering_wheels": + download_link = f"{repo_url}Steering-Wheels/{theme_name}" + download_path = os.path.join(THEME_SAVE_PATH, theme_component, theme_name) + extentions = [".gif", ".png"] + else: + download_link = f"{repo_url}Themes/{theme_name}/{theme_component}" + download_path = os.path.join(THEME_SAVE_PATH, "theme_packs", theme_name, theme_component) + extentions = [".zip"] + + for ext in extentions: + theme_path = download_path + ext + if os.path.exists(theme_path): + handle_error(theme_path, "Theme already exists...", "Theme already exists...", theme_param, self.download_progress_param, self.params_memory) + return + + theme_url = download_link + ext + print(f"Downloading theme from GitHub: {theme_name}") + download_file(self.cancel_download_param, theme_path, self.download_progress_param, theme_url, theme_param, self.params_memory) + + if verify_download(theme_path, theme_url): + print(f"Theme {theme_name} downloaded and verified successfully from GitHub!") + if ext == ".zip": + self.params_memory.put(self.download_progress_param, "Unpacking theme...") + self.extract_zip(theme_path, download_path) + self.params_memory.put(self.download_progress_param, "Downloaded!") + self.params_memory.remove(theme_param) return - if self.previous_theme_id != 0: - self.params_memory.put_int("CurrentHolidayTheme", "0") - update_frogpilot_toggles() - self.previous_theme_id = 0 + self.handle_verification_failure(extentions, theme_component, theme_name, theme_param, download_path) + + @staticmethod + def fetch_files(url): + try: + response = requests.get(url, timeout=10) + response.raise_for_status() + return [name for name in re.findall(r'href="[^"]*\/blob\/[^"]*\/([^"]*)"', response.text) if name.lower() != "license"] + except Exception as error: + handle_request_error(error, None, None, None, None) + return [] + + @staticmethod + def fetch_folders(url): + try: + response = requests.get(url, timeout=10) + response.raise_for_status() + return re.findall(r'href="[^"]*\/tree\/[^"]*\/([^"]*)"', response.text) + except Exception as error: + handle_request_error(error, None, None, None, None) + return [] + + def update_theme_params(self, downloadable_colors, downloadable_distance_icons, downloadable_icons, downloadable_signals, downloadable_sounds, downloadable_wheels): + def filter_existing_assets(assets, subfolder): + existing_themes = { + theme.replace('_', ' ').title() + for theme in os.listdir(os.path.join(THEME_SAVE_PATH, "theme_packs")) + if os.path.isdir(os.path.join(THEME_SAVE_PATH, "theme_packs", theme, subfolder)) + } + return sorted(set(assets) - existing_themes) + + self.params.put("DownloadableColors", ','.join(filter_existing_assets(downloadable_colors, "colors"))) + print("Colors list updated successfully.") + + distance_icons_directory = os.path.join(THEME_SAVE_PATH, "distance_icons") + self.params.put("DownloadableDistanceIcons", ','.join(sorted(set(downloadable_distance_icons) - { + distance_icons.replace('_', ' ').split('.')[0].title() + for distance_icons in os.listdir(distance_icons_directory) + })) + ) + + self.params.put("DownloadableIcons", ','.join(filter_existing_assets(downloadable_icons, "icons"))) + print("Icons list updated successfully.") + + self.params.put("DownloadableSignals", ','.join(filter_existing_assets(downloadable_signals, "signals"))) + print("Signals list updated successfully.") + + self.params.put("DownloadableSounds", ','.join(filter_existing_assets(downloadable_sounds, "sounds"))) + print("Sounds list updated successfully.") + + wheel_directory = os.path.join(THEME_SAVE_PATH, "steering_wheels") + self.params.put("DownloadableWheels", ','.join(sorted(set(downloadable_wheels) - { + wheel.replace('_', ' ').split('.')[0].title() + for wheel in os.listdir(wheel_directory) if wheel != "img_chffr_wheel.png" + })) + ) + + def validate_themes(self): + asset_mappings = { + "CustomColors": "colors", + "CustomDistanceIcons": "distance_icons", + "CustomIcons": "icons", + "CustomSounds": "sounds", + "CustomSignals": "signals", + "WheelIcon": "steering_wheels" + } + + for theme_param, theme_component in asset_mappings.items(): + theme_name = self.params.get(theme_param, encoding='utf-8') + if not theme_name or theme_name == "stock": + continue + + if theme_component == "distance_icons": + theme_path = os.path.join(THEME_SAVE_PATH, theme_component, theme_name) + elif theme_component == "steering_wheels": + pattern = os.path.join(THEME_SAVE_PATH, theme_component, theme_name + ".*") + matching_files = glob.glob(pattern) + + if matching_files: + theme_path = matching_files[0] + else: + theme_path = None + else: + theme_path = os.path.join(THEME_SAVE_PATH, "theme_packs", theme_name, theme_component) + + if theme_path is None or not os.path.exists(theme_path): + print(f"{theme_name} for {theme_component} not found. Downloading...") + self.download_theme(theme_component, theme_name, theme_param) + self.previous_assets = {} + self.update_active_theme() + + def update_themes(self, boot_run=True): + if not os.path.exists(THEME_SAVE_PATH): + return + + repo_url = get_repository_url() + if boot_run: + boot_checks = 0 + while repo_url is None and boot_checks < 60: + boot_checks += 1 + if boot_checks > 60: + break + time.sleep(1) + self.validate_themes() + elif repo_url is None: + print("GitHub and GitLab are offline...") + return + + if repo_url == GITHUB_URL: + base_url = "https://github.com/FrogAi/FrogPilot-Resources/blob/Themes/" + distance_icons_files = self.fetch_files("https://github.com/FrogAi/FrogPilot-Resources/blob/Distance-Icons") + wheel_files = self.fetch_files("https://github.com/FrogAi/FrogPilot-Resources/blob/Steering-Wheels") + else: + base_url = "https://gitlab.com/FrogAi/FrogPilot-Resources/-/blob/Themes/" + distance_icons_files = self.fetch_files("https://github.com/FrogAi/FrogPilot-Resources/blob/Distance-Icons") + wheel_files = self.fetch_files("https://gitlab.com/FrogAi/FrogPilot-Resources/-/blob/Steering-Wheels") + + theme_folders = self.fetch_folders(base_url) + downloadable_colors = [] + downloadable_icons = [] + downloadable_signals = [] + downloadable_sounds = [] + + for theme in theme_folders: + theme_name = theme.replace('_', ' ').split('.')[0].title() + + if link_valid(f"{base_url}{theme}/colors.zip"): + downloadable_colors.append(theme_name) + if link_valid(f"{base_url}{theme}/icons.zip"): + downloadable_icons.append(theme_name) + if link_valid(f"{base_url}{theme}/signals.zip"): + downloadable_signals.append(theme_name) + if link_valid(f"{base_url}{theme}/sounds.zip"): + downloadable_sounds.append(theme_name) + + downloadable_distance_icons = [distance_icons.replace('_', ' ').split('.')[0].title() for distance_icons in distance_icons_files] + downloadable_wheels = [wheel.replace('_', ' ').split('.')[0].title() for wheel in wheel_files] + + self.update_theme_params(downloadable_colors, downloadable_distance_icons, downloadable_icons, downloadable_signals, downloadable_sounds, downloadable_wheels) diff --git a/selfdrive/frogpilot/frogpilot_process.py b/selfdrive/frogpilot/frogpilot_process.py index f1f07afa538ecb..0e3c4ce388d9c0 100644 --- a/selfdrive/frogpilot/frogpilot_process.py +++ b/selfdrive/frogpilot/frogpilot_process.py @@ -10,26 +10,30 @@ from openpilot.selfdrive.frogpilot.controls.frogpilot_planner import FrogPilotPlanner from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import backup_toggles, is_url_pingable +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_tracking import FrogPilotTracking from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import FrogPilotVariables -from openpilot.selfdrive.frogpilot.controls.lib.model_manager import DEFAULT_MODEL, DEFAULT_MODEL_NAME, download_all_models, download_model, update_models +from openpilot.selfdrive.frogpilot.controls.lib.model_manager import DEFAULT_MODEL, DEFAULT_MODEL_NAME, ModelManager from openpilot.selfdrive.frogpilot.controls.lib.theme_manager import ThemeManager -OFFLINE = log.DeviceState.NetworkType.none +WIFI = log.DeviceState.NetworkType.wifi locks = { "backup_toggles": threading.Lock(), "download_all_models": threading.Lock(), "download_model": threading.Lock(), + "download_theme": threading.Lock(), "time_checks": threading.Lock(), - "update_frogpilot_params": threading.Lock(), - "update_models": threading.Lock() + "toggle_updates": threading.Lock(), + "update_active_theme": threading.Lock(), + "update_models": threading.Lock(), + "update_themes": threading.Lock() } running_threads = {} -def run_thread_with_lock(name, lock, target, args): - if name not in running_threads or not running_threads[name].is_alive(): - with lock: +def run_thread_with_lock(name, target, args=()): + if not running_threads.get(name, threading.Thread()).is_alive(): + with locks[name]: thread = threading.Thread(target=target, args=args) thread.start() running_threads[name] = thread @@ -46,8 +50,30 @@ def automatic_update_check(started, params): elif update_state_idle: os.system("pkill -SIGUSR1 -f system.updated.updated") -def time_checks(automatic_updates, deviceState, now, started, params, params_memory): - if deviceState.networkType == OFFLINE: +def download_assets(model_manager, theme_manager, params, params_memory): + model_to_download = params_memory.get("ModelToDownload", encoding='utf-8') + if model_to_download: + run_thread_with_lock("download_model", model_manager.download_model, (model_to_download,)) + + if params_memory.get_bool("DownloadAllModels"): + run_thread_with_lock("download_all_models", model_manager.download_all_models) + + assets = [ + ("ColorToDownload", "colors"), + ("DistanceIconToDownload", "distance_icons"), + ("IconToDownload", "icons"), + ("SignalToDownload", "signals"), + ("SoundToDownload", "sounds"), + ("WheelToDownload", "steering_wheels") + ] + + for param, asset_type in assets: + asset_to_download = params_memory.get(param, encoding='utf-8') + if asset_to_download: + run_thread_with_lock("download_theme", theme_manager.download_theme, (asset_type, asset_to_download, param)) + +def time_checks(automatic_updates, deviceState, model_manager, now, started, theme_manager, params, params_memory): + if deviceState.networkType != WIFI: return if not is_url_pingable("https://github.com"): @@ -60,11 +86,24 @@ def time_checks(automatic_updates, deviceState, now, started, params, params_mem update_maps(now, params, params_memory) with locks["update_models"]: - update_models(params, params_memory, False) + model_manager.update_models(boot_run=False) + + with locks["update_themes"]: + theme_manager.update_themes(boot_run=False) + +def toggle_updates(frogpilot_toggles, started, time_validated, params, params_storage): + FrogPilotVariables.update_frogpilot_params(started, True) + + if not frogpilot_toggles.model_manager: + params.put_nonblocking("Model", DEFAULT_MODEL) + params.put_nonblocking("ModelName", DEFAULT_MODEL_NAME) + + if time_validated and not started: + run_thread_with_lock("backup_toggles", backup_toggles, (params, params_storage)) def update_maps(now, params, params_memory): maps_selected = params.get("MapsSelected", encoding='utf8') - if maps_selected is None: + if not maps_selected: return day = now.day @@ -90,17 +129,22 @@ def frogpilot_thread(): config_realtime_process(5, Priority.CTRL_LOW) frogpilot_toggles = FrogPilotVariables.toggles + FrogPilotVariables.update_frogpilot_params() params = Params() params_memory = Params("/dev/shm/params") params_storage = Params("/persist/params") frogpilot_planner = FrogPilotPlanner() + frogpilot_tracking = FrogPilotTracking() + model_manager = ModelManager() theme_manager = ThemeManager() + theme_manager.update_active_theme() + run_time_checks = False started_previously = False - time_validated = system_time_valid() + time_validated = False update_toggles = False pm = messaging.PubMaster(['frogpilotPlan']) @@ -117,46 +161,41 @@ def frogpilot_thread(): if not started and started_previously: frogpilot_planner = FrogPilotPlanner() + frogpilot_tracking = FrogPilotTracking() if started and sm.updated['modelV2']: frogpilot_planner.update(sm['carState'], sm['controlsState'], sm['frogpilotCarControl'], sm['frogpilotCarState'], sm['frogpilotNavigation'], sm['modelV2'], sm['radarState'], frogpilot_toggles) frogpilot_planner.publish(sm, pm, frogpilot_toggles) - model_to_download = params_memory.get("ModelToDownload", encoding='utf-8') - if model_to_download: - run_thread_with_lock("download_model", locks["download_model"], download_model, (model_to_download, params_memory)) + frogpilot_tracking.update(sm['carState']) - if params_memory.get_bool("DownloadAllModels"): - run_thread_with_lock("download_all_models", locks["download_all_models"], download_all_models, (params, params_memory)) + if params_memory.get_bool("UpdateTheme"): + run_thread_with_lock("update_active_theme", theme_manager.update_active_theme) if FrogPilotVariables.toggles_updated: update_toggles = True elif update_toggles: - run_thread_with_lock("update_frogpilot_params", locks["update_frogpilot_params"], FrogPilotVariables.update_frogpilot_params, (started,)) - - if not frogpilot_toggles.model_manager: - params.put_nonblocking("Model", DEFAULT_MODEL) - params.put_nonblocking("ModelName", DEFAULT_MODEL_NAME) - - if time_validated and not started: - run_thread_with_lock("backup_toggles", locks["backup_toggles"], backup_toggles, (params, params_storage)) + run_thread_with_lock("toggle_updates", toggle_updates, (frogpilot_toggles, started, time_validated, params, params_storage)) update_toggles = False started_previously = started + download_assets(model_manager, theme_manager, params, params_memory) + if now.second == 0: run_time_checks = True elif run_time_checks or not time_validated: - run_thread_with_lock("time_checks", locks["time_checks"], time_checks, (frogpilot_toggles.automatic_updates, deviceState, now, started, params, params_memory)) + run_thread_with_lock("time_checks", time_checks, (frogpilot_toggles.automatic_updates, deviceState, model_manager, now, started, theme_manager, params, params_memory)) run_time_checks = False if not time_validated: time_validated = system_time_valid() if not time_validated: continue - run_thread_with_lock("update_models", locks["update_models"], update_models, (params, params_memory)) + run_thread_with_lock("update_models", model_manager.update_models) + run_thread_with_lock("update_themes", theme_manager.update_themes) theme_manager.update_holiday() diff --git a/selfdrive/frogpilot/navigation/mapd.py b/selfdrive/frogpilot/navigation/mapd.py index 1d2935e2cd21d7..2b763e149c5d23 100644 --- a/selfdrive/frogpilot/navigation/mapd.py +++ b/selfdrive/frogpilot/navigation/mapd.py @@ -1,9 +1,13 @@ # PFEIFER - MAPD - Modified by FrogAi for FrogPilot to automatically update +import json import os import stat import subprocess +import time import urllib.request -import json +import http.client +import socket +import openpilot.system.sentry as sentry from openpilot.common.realtime import Ratekeeper @@ -18,47 +22,56 @@ VERSION_PATH = '/data/media/0/osm/mapd_version' def get_latest_version(): - urls = [GITHUB_VERSION_URL, GITLAB_VERSION_URL] - for url in urls: + for url in [GITHUB_VERSION_URL, GITLAB_VERSION_URL]: try: - with urllib.request.urlopen(url) as response: - data = json.loads(response.read().decode('utf-8')) - return data['version'] - except Exception as e: + with urllib.request.urlopen(url, timeout=5) as response: + return json.loads(response.read().decode('utf-8'))['version'] + except (http.client.IncompleteRead, http.client.RemoteDisconnected, socket.gaierror, socket.timeout, urllib.error.HTTPError, urllib.error.URLError) as e: + sentry.capture_exception(e) print(f"Failed to get latest version from {url}. Error: {e}") - print("Failed to get latest version from both sources.") + print("Failed to get the latest version from both sources.") return None def download(current_version): urls = [ f"https://github.com/pfeiferj/openpilot-mapd/releases/download/{current_version}/mapd", - f"https://gitlab.com/FrogAi/openpilot-mapd/-/releases/download/{current_version}/mapd" + f"https://gitlab.com/FrogAi/FrogPilot-Resources/-/raw/Mapd/{current_version}" ] - mapd_dir = os.path.dirname(MAPD_PATH) - if not os.path.exists(mapd_dir): - os.makedirs(mapd_dir) + os.makedirs(os.path.dirname(MAPD_PATH), exist_ok=True) for url in urls: try: - with urllib.request.urlopen(url) as f: + with urllib.request.urlopen(url, timeout=5) as f: with open(MAPD_PATH, 'wb') as output: output.write(f.read()) os.fsync(output) - current_permissions = stat.S_IMODE(os.lstat(MAPD_PATH).st_mode) - os.chmod(MAPD_PATH, current_permissions | stat.S_IEXEC) - with open(VERSION_PATH, 'w') as output: - output.write(current_version) - os.fsync(output) + os.chmod(MAPD_PATH, os.stat(MAPD_PATH).st_mode | stat.S_IEXEC) + + with open(VERSION_PATH, 'w') as version_file: + version_file.write(current_version) + os.fsync(version_file) + print(f"Successfully downloaded mapd from {url}") - return - except Exception as e: + return True + except (http.client.IncompleteRead, http.client.RemoteDisconnected, socket.gaierror, socket.timeout, urllib.error.HTTPError, urllib.error.URLError) as e: + sentry.capture_exception(e) print(f"Failed to download from {url}. Error: {e}") print(f"Failed to download mapd for version {current_version} from both sources.") + return False + +def ensure_mapd_is_running(): + while True: + try: + subprocess.run([MAPD_PATH], check=True) + except Exception as e: + sentry.capture_exception(e) + print(f"Error running mapd process: {e}") + time.sleep(1) def mapd_thread(sm=None, pm=None): - rk = Ratekeeper(0.05, print_delay_threshold=None) + rk = Ratekeeper(0.05) while True: try: @@ -66,26 +79,28 @@ def mapd_thread(sm=None, pm=None): current_version = get_latest_version() if current_version: if not os.path.exists(MAPD_PATH): - download(current_version) - continue + if download(current_version): + continue if not os.path.exists(VERSION_PATH): - download(current_version) - continue - with open(VERSION_PATH) as f: - content = f.read() - if content != current_version: - download(current_version) + if download(current_version): continue - - process = subprocess.Popen(MAPD_PATH) - process.wait() + if open(VERSION_PATH).read() != current_version: + if download(current_version): + continue + ensure_mapd_is_running() except Exception as e: - print(e) + sentry.capture_exception(e) + print(f"Exception in mapd_thread: {e}") + time.sleep(1) rk.keep_time() def main(sm=None, pm=None): - mapd_thread(sm, pm) + try: + mapd_thread(sm, pm) + except Exception as e: + sentry.capture_exception(e) + print(f"Unhandled exception in main: {e}") if __name__ == "__main__": main() diff --git a/selfdrive/frogpilot/navigation/ui/navigation_settings.cc b/selfdrive/frogpilot/navigation/ui/navigation_settings.cc index 182bedc32d09da..c80929751ab0a6 100644 --- a/selfdrive/frogpilot/navigation/ui/navigation_settings.cc +++ b/selfdrive/frogpilot/navigation/ui/navigation_settings.cc @@ -507,6 +507,8 @@ void Primeless::createMapboxKeyControl(ButtonControl *&control, const QString &l } if (key.length() >= 80) { params.putNonBlocking(paramKey, key.toStdString()); + } else { + FrogPilotConfirmationDialog::toggleAlert(tr("Inputted key is too short to be valid!"), tr("Okay"), this); } } else { if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure you want to remove your %1?").arg(label), this)) { diff --git a/selfdrive/frogpilot/ui/qt/offroad/control_settings.cc b/selfdrive/frogpilot/ui/qt/offroad/control_settings.cc index d4eb1964971dc5..27d5ddb98eec4e 100644 --- a/selfdrive/frogpilot/ui/qt/offroad/control_settings.cc +++ b/selfdrive/frogpilot/ui/qt/offroad/control_settings.cc @@ -39,8 +39,8 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil {"CESpeed", tr("Below"), tr("Switch to 'Experimental Mode' below this speed when not following a lead vehicle."), ""}, {"CECurves", tr("Curve Detected Ahead"), tr("Switch to 'Experimental Mode' when a curve is detected."), ""}, {"CELead", tr("Lead Detected Ahead"), tr("Switch to 'Experimental Mode' when a slower or stopped lead vehicle is detected ahead."), ""}, + {"CEModelStopTime", tr("Model Wants To Stop In The Next"), tr("Switch to 'Experimental Mode' when the model wants to stop like when it detects a stop light or stop sign."), ""}, {"CENavigation", tr("Navigation Based"), tr("Switch to 'Experimental Mode' based on navigation data. (i.e. Intersections, stop signs, upcoming turns, etc.)"), ""}, - {"CEStopLights", tr("Stop Lights and Stop Signs"), tr("Switch to 'Experimental Mode' when a stop light or stop sign is detected."), ""}, {"CESignal", tr("Turn Signal When Below Highway Speeds"), tr("Switch to 'Experimental Mode' when using turn signals below highway speeds to help assist with turns."), ""}, {"HideCEMStatusBar", tr("Hide the Status Bar"), tr("Don't use the status bar for 'Conditional Experimental Mode'."), ""}, @@ -54,32 +54,34 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil {"DrivingPersonalities", tr("Driving Personalities"), tr("Manage the driving behaviors of comma's 'Personality Profiles'."), "../frogpilot/assets/toggle_icons/icon_personality.png"}, {"CustomPersonalities", tr("Customize Personalities"), tr("Customize the driving personality profiles to your driving style."), ""}, - {"PersonalityInfo", tr("What Do All These Do"), tr("Learn what all the values in 'Custom Personality Profiles' do on openpilot's driving behaviors."), ""}, - {"TrafficPersonalityProfile", tr("Traffic Personality"), tr("Customize the 'Traffic' personality profile."), "../frogpilot/assets/other_images/traffic.png"}, - {"TrafficFollow", tr("Following Distance"), tr("Set the minimum following distance when using 'Traffic Mode'. Your following distance will dynamically adjust between this distance and the following distance from the 'Aggressive' profile when driving between 0 and %1.\n\nFor example:\n\nTraffic Mode: 0.5s\nAggressive: 1.0s\n\n0%2 = 0.5s\n%3 = 0.75s\n%1 = 1.0s"), ""}, + {"PersonalityInfo", tr("What Do All These Do?"), tr("Learn what all the values in 'Custom Personality Profiles' do on openpilot's driving behaviors."), ""}, + {"TrafficPersonalityProfile", tr("Traffic Personality"), tr("Customize the 'Traffic' personality profile."), "../frogpilot/assets/stock_theme/distance_icons/traffic.png"}, + {"TrafficFollow", tr("Following Distance"), tr("Set the minimum following distance when using 'Traffic Mode'. Your following distance will dynamically adjust between this distance and the following distance from the 'Aggressive' profile."), ""}, {"TrafficJerkAcceleration", tr("Acceleration Jerk"), tr("Customize the acceleration jerk when using 'Traffic Mode'."), ""}, {"TrafficJerkDanger", tr("Danger Zone Jerk"), tr("Customize the danger zone jerk when using the 'Traffic' personality."), ""}, {"TrafficJerkSpeed", tr("Speed Control Jerk"), tr("Customize the speed control jerk when using 'Traffic Mode'."), ""}, {"ResetTrafficPersonality", tr("Reset Settings"), tr("Reset the values for the 'Traffic Mode' personality back to stock."), ""}, - {"AggressivePersonalityProfile", tr("Aggressive Personality"), tr("Customize the 'Aggressive' personality profile."), "../frogpilot/assets/other_images/aggressive.png"}, + {"AggressivePersonalityProfile", tr("Aggressive Personality"), tr("Customize the 'Aggressive' personality profile."), "../frogpilot/assets/stock_theme/distance_icons/aggressive.png"}, {"AggressiveFollow", tr("Following Distance"), tr("Set the 'Aggressive' personality following distance. Represents seconds to follow behind the lead vehicle.\n\nStock: 1.25 seconds."), ""}, {"AggressiveJerkAcceleration", tr("Acceleration Jerk"), tr("Customize the acceleration jerk when using the 'Aggressive' personality."), ""}, {"AggressiveJerkDanger", tr("Danger Zone Jerk"), tr("Customize the danger zone jerk when using the 'Aggressive' personality."), ""}, {"AggressiveJerkSpeed", tr("Speed Control Jerk"), tr("Customize the speed control jerk when using the 'Aggressive' personality."), ""}, {"ResetAggressivePersonality", tr("Reset Settings"), tr("Reset the values for the 'Aggressive' personality back to stock."), ""}, - {"StandardPersonalityProfile", tr("Standard Personality"), tr("Customize the 'Standard' personality profile."), "../frogpilot/assets/other_images/standard.png"}, + {"StandardPersonalityProfile", tr("Standard Personality"), tr("Customize the 'Standard' personality profile."), "../frogpilot/assets/stock_theme/distance_icons/standard.png"}, {"StandardFollow", tr("Following Distance"), tr("Set the 'Standard' personality following distance. Represents seconds to follow behind the lead vehicle.\n\nStock: 1.45 seconds."), ""}, {"StandardJerkAcceleration", tr("Acceleration Jerk"), tr("Customize the acceleration jerk when using the 'Standard' personality."), ""}, {"StandardJerkDanger", tr("Danger Zone Jerk"), tr("Customize the danger zone jerk when using the 'Standard' personality."), ""}, {"StandardJerkSpeed", tr("Speed Control Jerk"), tr("Customize the speed control jerk when using the 'Standard' personality."), ""}, {"ResetStandardPersonality", tr("Reset Settings"), tr("Reset the values for the 'Standard' personality back to stock."), ""}, - {"RelaxedPersonalityProfile", tr("Relaxed Personality"), tr("Customize the 'Relaxed' personality profile."), "../frogpilot/assets/other_images/relaxed.png"}, + {"RelaxedPersonalityProfile", tr("Relaxed Personality"), tr("Customize the 'Relaxed' personality profile."), "../frogpilot/assets/stock_theme/distance_icons/relaxed.png"}, {"RelaxedFollow", tr("Following Distance"), tr("Set the 'Relaxed' personality following distance. Represents seconds to follow behind the lead vehicle.\n\nStock: 1.75 seconds."), ""}, {"RelaxedJerkAcceleration", tr("Acceleration Jerk"), tr("Customize the acceleration jerk when using the 'Relaxed' personality."), ""}, {"RelaxedJerkDanger", tr("Danger Zone Jerk"), tr("Customize the danger zone jerk when using the 'Relaxed' personality."), ""}, {"RelaxedJerkSpeed", tr("Speed Control Jerk"), tr("Customize the speed control jerk when using the 'Relaxed' personality."), ""}, {"ResetRelaxedPersonality", tr("Reset Settings"), tr("Reset the values for the 'Relaxed' personality back to stock."), ""}, {"OnroadDistanceButton", tr("Onroad Distance Button"), tr("Simulate a distance button via the onroad UI to control personalities, 'Experimental Mode', and 'Traffic Mode'."), ""}, + {"OnroadDistanceButtonButtons", "Icon Pack", "", ""}, + {"DownloadStatusLabel", tr("Download Status"), "", ""}, {"ExperimentalModeActivation", tr("Experimental Mode Activation"), tr("Toggle Experimental Mode with either buttons on the steering wheel or the screen. \n\nOverrides 'Conditional Experimental Mode'."), "../assets/img_experimental_white.svg"}, {"ExperimentalModeViaLKAS", tr("Click LKAS Button"), tr("Enable/disable 'Experimental Mode' by clicking the 'LKAS' button on your steering wheel."), ""}, @@ -104,10 +106,10 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil {"LongitudinalTune", tr("Longitudinal Tuning"), tr("Modify openpilot's acceleration and braking behavior."), "../frogpilot/assets/toggle_icons/icon_longitudinal_tune.png"}, {"AccelerationProfile", tr("Acceleration Profile"), tr("Change the acceleration rate to be either sporty or eco-friendly."), ""}, {"DecelerationProfile", tr("Deceleration Profile"), tr("Change the deceleration rate to be either sporty or eco-friendly."), ""}, - {"AggressiveAcceleration", tr("Increase Acceleration Behind Lead"), tr("Increase aggressiveness when following a faster lead."), ""}, + {"HumanAcceleration", tr("Human-Like Acceleration"), tr("Tweaks the acceleration behavior to be more 'human-like'."), ""}, + {"HumanFollowing", tr("Human-Like Following Distance"), tr("Tweaks the following distance dynamically to be more 'human-like' when coming up behind slower/stopped leads or following faster leads."), ""}, {"StoppingDistance", tr("Increase Stop Distance Behind Lead"), tr("Increase the stopping distance for a more comfortable stop from lead vehicles."), ""}, {"LeadDetectionThreshold", tr("Lead Detection Threshold"), tr("Increase or decrease the lead detection threshold to either detect leads sooner, or increase model confidence."), ""}, - {"SmoothBraking", tr("Smoother Braking"), tr("Smoothen out the braking behavior when approaching slower vehicles."), ""}, {"TrafficMode", tr("Traffic Mode"), tr("Enable the ability to activate 'Traffic Mode' by holding down the 'distance' button for 2.5 seconds. When 'Traffic Mode' is active the onroad UI will turn red and openpilot will drive catered towards stop and go traffic."), ""}, {"MTSCEnabled", tr("Map Turn Speed Control"), tr("Slow down for anticipated curves detected by the downloaded maps."), "../frogpilot/assets/toggle_icons/icon_speed_map.png"}, @@ -116,7 +118,7 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil {"MTSCAggressiveness", tr("Turn Speed Aggressiveness"), tr("Set turn speed aggressiveness. Higher values result in faster turns, lower values yield gentler turns. \n\nA change of +- 1% results in the speed being raised or lowered by about 1 mph."), ""}, {"ModelManagement", tr("Model Management"), tr("Manage openpilot's driving models."), "../assets/offroad/icon_calibration.png"}, - {"AutomaticallyUpdateModels", tr("Automatically Update Models"), tr("Automatically download models as they're updated or added to the model list."), ""}, + {"AutomaticallyUpdateModels", tr("Automatically Update and Download Models"), tr("Automatically download models as they're updated or added to the model list."), ""}, {"ModelRandomizer", tr("Model Randomizer"), tr("Have a random model be selected each drive that can be reviewed at the end of each drive to find your preferred model."), ""}, {"ManageBlacklistedModels", tr("Manage Model Blacklist"), "Manage the models on your blacklist.", ""}, {"ResetScores", tr("Reset Model Scores"), tr("Reset the scores you have rated the openpilot models."), ""}, @@ -168,10 +170,11 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil if (param == "AlwaysOnLateral") { FrogPilotParamManageControl *aolToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(aolToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openParentToggle(); + for (auto &[key, toggle] : toggles) { toggle->setVisible(aolKeys.find(key.c_str()) != aolKeys.end()); } - openParentToggle(); }); controlToggle = aolToggle; } else if (param == "PauseAOLOnBrake") { @@ -180,10 +183,11 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil } else if (param == "ConditionalExperimental") { FrogPilotParamManageControl *conditionalExperimentalToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(conditionalExperimentalToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openParentToggle(); + for (auto &[key, toggle] : toggles) { toggle->setVisible(conditionalExperimentalKeys.find(key.c_str()) != conditionalExperimentalKeys.end()); } - openParentToggle(); }); controlToggle = conditionalExperimentalToggle; } else if (param == "CESpeed") { @@ -203,23 +207,22 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil std::vector navigationToggles{"CENavigationIntersections", "CENavigationTurns", "CENavigationLead"}; std::vector navigationToggleNames{tr("Intersections"), tr("Turns"), tr("With Lead")}; controlToggle = new FrogPilotParamToggleControl(param, title, desc, icon, navigationToggles, navigationToggleNames); - } else if (param == "CEStopLights") { - std::vector stopLightsToggles{"CEStopLightsLessSensitive"}; - std::vector stopLightsToggleNames{tr("Decrease Sensitivity")}; - controlToggle = new FrogPilotParamToggleControl(param, title, desc, icon, stopLightsToggles, stopLightsToggleNames); + } else if (param == "CEModelStopTime") { + controlToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 10, std::map(), this, false, tr(" seconds")); } else if (param == "DeviceManagement") { FrogPilotParamManageControl *deviceManagementToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(deviceManagementToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openParentToggle(); + for (auto &[key, toggle] : toggles) { toggle->setVisible(deviceManagementKeys.find(key.c_str()) != deviceManagementKeys.end()); } - openParentToggle(); }); controlToggle = deviceManagementToggle; } else if (param == "DeviceShutdown") { std::map shutdownLabels; - for (int i = 0; i <= 33; ++i) { + for (int i = 0; i <= 33; i++) { shutdownLabels[i] = i == 0 ? tr("5 mins") : i <= 3 ? QString::number(i * 15) + tr(" mins") : QString::number(i - 3) + (i == 4 ? tr(" hour") : tr(" hours")); } controlToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 33, shutdownLabels, this, false); @@ -233,20 +236,28 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil } else if (param == "DrivingPersonalities") { FrogPilotParamManageControl *drivingPersonalitiesToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(drivingPersonalitiesToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openParentToggle(); + + drivingPersonalitiesOpen = true; + for (auto &[key, toggle] : toggles) { toggle->setVisible(drivingPersonalityKeys.find(key.c_str()) != drivingPersonalityKeys.end()); } - openParentToggle(); + + downloadStatusLabel->setVisible(onroadDistanceButton); + manageDistanceIconsBtn->setVisible(onroadDistanceButton); }); controlToggle = drivingPersonalitiesToggle; } else if (param == "CustomPersonalities") { FrogPilotParamManageControl *customPersonalitiesToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(customPersonalitiesToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openSubParentToggle(); + customPersonalitiesOpen = true; + for (auto &[key, toggle] : toggles) { toggle->setVisible(customdrivingPersonalityKeys.find(key.c_str()) != customdrivingPersonalityKeys.end()); } - openSubParentToggle(); }); controlToggle = customPersonalitiesToggle; } else if (param == "PersonalityInfo") { @@ -263,37 +274,41 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil } else if (param == "TrafficPersonalityProfile") { FrogPilotParamManageControl *trafficPersonalityToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(trafficPersonalityToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openSubSubParentToggle(); + for (auto &[key, toggle] : toggles) { toggle->setVisible(trafficPersonalityKeys.find(key.c_str()) != trafficPersonalityKeys.end()); } - openSubSubParentToggle(); }); controlToggle = trafficPersonalityToggle; } else if (param == "AggressivePersonalityProfile") { FrogPilotParamManageControl *aggressivePersonalityToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(aggressivePersonalityToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openSubSubParentToggle(); + for (auto &[key, toggle] : toggles) { toggle->setVisible(aggressivePersonalityKeys.find(key.c_str()) != aggressivePersonalityKeys.end()); } - openSubSubParentToggle(); }); controlToggle = aggressivePersonalityToggle; } else if (param == "StandardPersonalityProfile") { FrogPilotParamManageControl *standardPersonalityToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(standardPersonalityToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openSubSubParentToggle(); + for (auto &[key, toggle] : toggles) { toggle->setVisible(standardPersonalityKeys.find(key.c_str()) != standardPersonalityKeys.end()); } - openSubSubParentToggle(); }); controlToggle = standardPersonalityToggle; } else if (param == "RelaxedPersonalityProfile") { FrogPilotParamManageControl *relaxedPersonalityToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(relaxedPersonalityToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openSubSubParentToggle(); + for (auto &[key, toggle] : toggles) { toggle->setVisible(relaxedPersonalityKeys.find(key.c_str()) != relaxedPersonalityKeys.end()); } - openSubSubParentToggle(); }); controlToggle = relaxedPersonalityToggle; } else if (trafficPersonalityKeys.find(param) != trafficPersonalityKeys.end() || @@ -309,14 +324,145 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil } else { controlToggle = new FrogPilotParamValueControl(param, title, desc, icon, 1, 500, std::map(), this, false, "%"); } - } else if (param == "OnroadDistanceButton") { - std::vector onroadDistanceToggles{"KaofuiIcons"}; - std::vector onroadDistanceToggleNames{tr("Kaofui's Icons")}; - controlToggle = new FrogPilotParamToggleControl(param, title, desc, icon, onroadDistanceToggles, onroadDistanceToggleNames); + + } else if (param == "OnroadDistanceButtonButtons") { + std::vector CustomDistanceIconsOptions{tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")}; + manageDistanceIconsBtn = new FrogPilotButtonsControl(title, desc, icon, CustomDistanceIconsOptions); + + std::function formatIconName = [](QString name) -> QString { + QChar separator = name.contains('_') ? '_' : '-'; + QStringList parts = name.replace(separator, ' ').split(' '); + + for (int i = 0; i < parts.size(); ++i) { + parts[i][0] = parts[i][0].toUpper(); + } + + if (separator == '-' && parts.size() > 1) { + return parts.first() + " (" + parts.last() + ")"; + } + + return parts.join(' '); + }; + + std::function formatIconNameForStorage = [](QString name) -> QString { + name = name.toLower(); + name = name.replace(" (", "-"); + name = name.replace(' ', '_'); + name.remove('(').remove(')'); + return name; + }; + + QObject::connect(manageDistanceIconsBtn, &FrogPilotButtonsControl::buttonClicked, [=](int id) { + QDir themesDir{"/data/themes/distance_icons"}; + QFileInfoList dirList = themesDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + + QString currentDistanceIcon = QString::fromStdString(params.get("CustomDistanceIcons")).replace('_', ' ').replace('-', " (").toLower(); + currentDistanceIcon[0] = currentDistanceIcon[0].toUpper(); + for (int i = 1; i < currentDistanceIcon.length(); i++) { + if (currentDistanceIcon[i - 1] == ' ' || currentDistanceIcon[i - 1] == '(') { + currentDistanceIcon[i] = currentDistanceIcon[i].toUpper(); + } + } + if (currentDistanceIcon.contains(" (")) { + currentDistanceIcon.append(')'); + } + + QStringList availableIcons; + for (const QFileInfo &dirInfo : dirList) { + QString iconPackDir = dirInfo.absoluteFilePath(); + + availableIcons << formatIconName(dirInfo.fileName()); + } + availableIcons.append("Stock"); + std::sort(availableIcons.begin(), availableIcons.end()); + + if (id == 0) { + QStringList iconPackList = availableIcons; + iconPackList.removeAll("Stock"); + iconPackList.removeAll(currentDistanceIcon); + + QString iconPackToDelete = MultiOptionDialog::getSelection(tr("Select an icon pack to delete"), iconPackList, "", this); + if (!iconPackToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Are you sure you want to delete the '%1' icon pack?").arg(iconPackToDelete), tr("Delete"), this)) { + themeDeleting = true; + iconsDownloaded = false; + + QString selectedIconPack = formatIconNameForStorage(iconPackToDelete); + for (const QFileInfo &dirInfo : dirList) { + if (dirInfo.fileName() == selectedIconPack) { + QDir iconPackDir(dirInfo.absoluteFilePath() + "/distance_icons"); + if (iconPackDir.exists()) { + iconPackDir.removeRecursively(); + } + } + } + + QStringList downloadableIcons = QString::fromStdString(params.get("DownloadableDistanceIcons")).split(","); + downloadableIcons << iconPackToDelete; + downloadableIcons.removeDuplicates(); + downloadableIcons.removeAll(""); + std::sort(downloadableIcons.begin(), downloadableIcons.end()); + + params.put("DownloadableDistanceIcons", downloadableIcons.join(",").toStdString()); + themeDeleting = false; + } + } else if (id == 1) { + if (manageDistanceIconsBtn->getButton(id)->text() == tr("CANCEL")) { + paramsMemory.putBool("CancelThemeDownload", true); + cancellingDownload = true; + + QTimer::singleShot(2000, [=]() { + paramsMemory.putBool("CancelThemeDownload", false); + cancellingDownload = false; + iconsDownloading = false; + themeDownloading = false; + }); + } else { + QStringList downloadableIcons = QString::fromStdString(params.get("DownloadableDistanceIcons")).split(","); + QString iconPackToDownload = MultiOptionDialog::getSelection(tr("Select an icon pack to download"), downloadableIcons, "", this); + + if (!iconPackToDownload.isEmpty()) { + QString convertedIconPack = formatIconNameForStorage(iconPackToDownload); + paramsMemory.put("DistanceIconToDownload", convertedIconPack.toStdString()); + downloadStatusLabel->setText("Downloading..."); + paramsMemory.put("ThemeDownloadProgress", "Downloading..."); + iconsDownloading = true; + themeDownloading = true; + + downloadableIcons.removeAll(iconPackToDownload); + params.put("DownloadableDistanceIcons", downloadableIcons.join(",").toStdString()); + } + } + } else if (id == 2) { + QString iconPackToSelect = MultiOptionDialog::getSelection(tr("Select an icon pack"), availableIcons, currentDistanceIcon, this); + if (!iconPackToSelect.isEmpty()) { + params.put("CustomDistanceIcons", formatIconNameForStorage(iconPackToSelect).toStdString()); + manageDistanceIconsBtn->setValue(iconPackToSelect); + paramsMemory.putBool("UpdateTheme", true); + } + } + }); + + QString currentDistanceIcon = QString::fromStdString(params.get("CustomDistanceIcons")).replace('_', ' ').replace('-', " (").toLower(); + currentDistanceIcon[0] = currentDistanceIcon[0].toUpper(); + for (int i = 1; i < currentDistanceIcon.length(); i++) { + if (currentDistanceIcon[i - 1] == ' ' || currentDistanceIcon[i - 1] == '(') { + currentDistanceIcon[i] = currentDistanceIcon[i].toUpper(); + } + } + if (currentDistanceIcon.contains(" (")) { + currentDistanceIcon.append(')'); + } + manageDistanceIconsBtn->setValue(currentDistanceIcon); + controlToggle = reinterpret_cast(manageDistanceIconsBtn); + } else if (param == "DownloadStatusLabel") { + downloadStatusLabel = new LabelControl(title, "Idle"); + controlToggle = reinterpret_cast(downloadStatusLabel); } else if (param == "ExperimentalModeActivation") { FrogPilotParamManageControl *experimentalModeActivationToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(experimentalModeActivationToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openParentToggle(); + for (auto &[key, toggle] : toggles) { std::set modifiedExperimentalModeActivationKeys = experimentalModeActivationKeys; @@ -326,13 +472,14 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil toggle->setVisible(modifiedExperimentalModeActivationKeys.find(key.c_str()) != modifiedExperimentalModeActivationKeys.end()); } - openParentToggle(); }); controlToggle = experimentalModeActivationToggle; } else if (param == "LateralTune") { FrogPilotParamManageControl *lateralTuneToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(lateralTuneToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openParentToggle(); + for (auto &[key, toggle] : toggles) { std::set modifiedLateralTuneKeys = lateralTuneKeys; @@ -348,7 +495,6 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil toggle->setVisible(modifiedLateralTuneKeys.find(key.c_str()) != modifiedLateralTuneKeys.end()); } - openParentToggle(); }); controlToggle = lateralTuneToggle; } else if (param == "SteerRatio") { @@ -359,16 +505,20 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil } else if (param == "LongitudinalTune") { FrogPilotParamManageControl *longitudinalTuneToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(longitudinalTuneToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openParentToggle(); + + bool radarlessModel = QString::fromStdString(params.get("RadarlessModels")).split(",").contains(QString::fromStdString(params.get("Model"))); + for (auto &[key, toggle] : toggles) { std::set modifiedLongitudinalTuneKeys = longitudinalTuneKeys; - if (!isRelease && params.get("Model") == "radical-turtle") { + if (radarlessModel) { modifiedLongitudinalTuneKeys.erase("LeadDetectionThreshold"); } toggle->setVisible(modifiedLongitudinalTuneKeys.find(key.c_str()) != modifiedLongitudinalTuneKeys.end()); } - openParentToggle(); + }); controlToggle = longitudinalTuneToggle; } else if (param == "AccelerationProfile") { @@ -387,10 +537,12 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil } else if (param == "MTSCEnabled") { FrogPilotParamManageControl *mtscToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(mtscToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openParentToggle(); + for (auto &[key, toggle] : toggles) { toggle->setVisible(mtscKeys.find(key.c_str()) != mtscKeys.end()); } - openParentToggle(); + }); controlToggle = mtscToggle; } else if (param == "MTSCAggressiveness") { @@ -399,152 +551,152 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil } else if (param == "ModelManagement") { FrogPilotParamManageControl *modelManagementToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(modelManagementToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openParentToggle(); + availableModelNames = QString::fromStdString(params.get("AvailableModelsNames")).split(","); availableModels = QString::fromStdString(params.get("AvailableModels")).split(","); experimentalModels = QString::fromStdString(params.get("ExperimentalModels")).split(","); modelManagementOpen = true; + for (auto &[key, toggle] : toggles) { toggle->setVisible(modelManagementKeys.find(key.c_str()) != modelManagementKeys.end()); } - std::string currentModel = params.get("Model") + ".thneed"; + QString currentModel = QString::fromStdString(params.get("Model")) + ".thneed"; QStringList modelFiles = modelDir.entryList({"*.thneed"}, QDir::Files); - modelFiles.removeAll(QString::fromStdString(currentModel)); + modelFiles.removeAll(currentModel); haveModelsDownloaded = modelFiles.size() > 1; modelsDownloaded = params.getBool("ModelsDownloaded"); - openParentToggle(); }); controlToggle = modelManagementToggle; } else if (param == "ModelRandomizer") { FrogPilotParamManageControl *modelRandomizerToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(modelRandomizerToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openSubParentToggle(); + for (auto &[key, toggle] : toggles) { toggle->setVisible(modelRandomizerKeys.find(key.c_str()) != modelRandomizerKeys.end()); } - openSubParentToggle(); }); controlToggle = modelRandomizerToggle; } else if (param == "ManageBlacklistedModels") { - std::vector blacklistOptions{tr("ADD"), tr("REMOVE")}; - FrogPilotButtonsControl *manageModelsBlacklistBtn = new FrogPilotButtonsControl(title, desc, "", blacklistOptions); - QObject::connect(manageModelsBlacklistBtn, &FrogPilotButtonsControl::buttonClicked, [=](int id) { + FrogPilotButtonsControl *blacklistBtn = new FrogPilotButtonsControl(title, desc, icon, {tr("ADD"), tr("REMOVE")}); + QObject::connect(blacklistBtn, &FrogPilotButtonsControl::buttonClicked, [=](int id) { QStringList blacklistedModels = QString::fromStdString(params.get("BlacklistedModels")).split(",", QString::SkipEmptyParts); QMap labelToModelMap; QStringList selectableModels, deletableModels; - for (int i = 0; i < availableModels.size(); ++i) { - QString modelFileName = availableModels[i]; - QString readableName = availableModelNames[i]; - if (!blacklistedModels.contains(modelFileName)) { - selectableModels.append(readableName); + for (int i = 0; i < availableModels.size(); i++) { + QString model = availableModels[i]; + if (blacklistedModels.contains(model)) { + deletableModels.append(availableModelNames[i]); } else { - deletableModels.append(readableName); + selectableModels.append(availableModelNames[i]); } - labelToModelMap[readableName] = modelFileName; + labelToModelMap[availableModelNames[i]] = model; } if (id == 0) { if (selectableModels.size() == 1) { - QString onlyModel = selectableModels.first(); - FrogPilotConfirmationDialog::toggleAlert( - tr("There's no more models to blacklist! The only available model is \"%1\"!").arg(onlyModel), - tr("OK"), this); + FrogPilotConfirmationDialog::toggleAlert(tr("There's no more models to blacklist! The only available model is \"%1\"!").arg(selectableModels.first()), tr("OK"), this); } else { QString selectedModel = MultiOptionDialog::getSelection(tr("Select a model to add to the blacklist"), selectableModels, "", this); - if (!selectedModel.isEmpty() && ConfirmationDialog::confirm(tr("Are you sure you want to add this model to the blacklist?"), tr("Add"), this)) { - QString modelToAdd = labelToModelMap[selectedModel]; - if (!blacklistedModels.contains(modelToAdd)) { - blacklistedModels.append(modelToAdd); - params.putNonBlocking("BlacklistedModels", blacklistedModels.join(",").toStdString()); + if (!selectedModel.isEmpty()) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to add the '%1' model to the blacklist?").arg(selectedModel), tr("Add"), this)) { + QString modelToAdd = labelToModelMap[selectedModel]; + if (!blacklistedModels.contains(modelToAdd)) { + blacklistedModels.append(modelToAdd); + params.putNonBlocking("BlacklistedModels", blacklistedModels.join(",").toStdString()); + } } } } } else if (id == 1) { QString selectedModel = MultiOptionDialog::getSelection(tr("Select a model to remove from the blacklist"), deletableModels, "", this); - if (!selectedModel.isEmpty() && ConfirmationDialog::confirm(tr("Are you sure you want to remove this model from the blacklist?"), tr("Remove"), this)) { - QString modelToRemove = labelToModelMap[selectedModel]; - if (blacklistedModels.contains(modelToRemove)) { - blacklistedModels.removeAll(modelToRemove); - params.putNonBlocking("BlacklistedModels", blacklistedModels.join(",").toStdString()); - paramsStorage.put("BlacklistedModels", blacklistedModels.join(",").toStdString()); + if (!selectedModel.isEmpty()) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to remove the '%1' model from the blacklist?").arg(selectedModel), tr("Remove"), this)) { + QString modelToRemove = labelToModelMap[selectedModel]; + if (blacklistedModels.contains(modelToRemove)) { + blacklistedModels.removeAll(modelToRemove); + params.putNonBlocking("BlacklistedModels", blacklistedModels.join(",").toStdString()); + paramsStorage.put("BlacklistedModels", blacklistedModels.join(",").toStdString()); + } } } } }); - controlToggle = reinterpret_cast(manageModelsBlacklistBtn); + controlToggle = reinterpret_cast(blacklistBtn); } else if (param == "ResetScores") { - ButtonControl *resetScoresBtn = new ButtonControl(title, tr("RESET"), desc); - QObject::connect(resetScoresBtn, &ButtonControl::clicked, [=]() { - if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure you want to completely reset all of your model scores?"), this)) { - for (QString model : availableModelNames) { - QString cleanedModelName = processModelName(model); - params.remove(QString("%1Drives").arg(cleanedModelName).toStdString()); - paramsStorage.remove(QString("%1Drives").arg(cleanedModelName).toStdString()); - params.remove(QString("%1Score").arg(cleanedModelName).toStdString()); - paramsStorage.remove(QString("%1Score").arg(cleanedModelName).toStdString()); + ButtonControl *resetCalibrationsBtn = new ButtonControl(title, tr("RESET"), desc); + QObject::connect(resetCalibrationsBtn, &ButtonControl::clicked, [=]() { + if (FrogPilotConfirmationDialog::yesorno(tr("Reset all model scores?"), this)) { + for (const QString &model : availableModelNames) { + QString cleanedModel = processModelName(model); + params.remove(QString("%1Drives").arg(cleanedModel).toStdString()); + paramsStorage.remove(QString("%1Drives").arg(cleanedModel).toStdString()); + params.remove(QString("%1Score").arg(cleanedModel).toStdString()); + paramsStorage.remove(QString("%1Score").arg(cleanedModel).toStdString()); } - updateModelLabels(); } }); - controlToggle = reinterpret_cast(resetScoresBtn); + controlToggle = reinterpret_cast(resetCalibrationsBtn); } else if (param == "ReviewScores") { ButtonControl *reviewScoresBtn = new ButtonControl(title, tr("VIEW"), desc); QObject::connect(reviewScoresBtn, &ButtonControl::clicked, [=]() { + openSubSubParentToggle(); + for (LabelControl *label : labelControls) { label->setVisible(true); } + for (auto &[key, toggle] : toggles) { toggle->setVisible(false); } - openSubSubParentToggle(); }); controlToggle = reinterpret_cast(reviewScoresBtn); } else if (param == "DeleteModel") { deleteModelBtn = new ButtonControl(title, tr("DELETE"), desc); QObject::connect(deleteModelBtn, &ButtonControl::clicked, [=]() { - std::string currentModel = params.get("Model") + ".thneed"; + QStringList deletableModels, existingModels = modelDir.entryList({"*.thneed"}, QDir::Files); QMap labelToFileMap; + QString currentModel = QString::fromStdString(params.get("Model")) + ".thneed"; - QStringList existingModelFiles = modelDir.entryList({"*.thneed"}, QDir::Files); - QStringList deletableModelLabels; - - for (int i = 0; i < availableModels.size(); ++i) { - QString modelFileName = availableModels[i] + ".thneed"; - if (existingModelFiles.contains(modelFileName) && modelFileName != QString::fromStdString(currentModel) && !availableModelNames[i].contains(" (Default)")) { - deletableModelLabels.append(availableModelNames[i]); - labelToFileMap[availableModelNames[i]] = modelFileName; + for (int i = 0; i < availableModels.size(); i++) { + QString modelFile = availableModels[i] + ".thneed"; + if (existingModels.contains(modelFile) && modelFile != currentModel && !availableModelNames[i].contains("(Default)")) { + deletableModels.append(availableModelNames[i]); + labelToFileMap[availableModelNames[i]] = modelFile; } } - QString selectedModel = MultiOptionDialog::getSelection(tr("Select a model to delete"), deletableModelLabels, "", this); - if (!selectedModel.isEmpty() && ConfirmationDialog::confirm(tr("Are you sure you want to delete this model?"), tr("Delete"), this)) { - std::thread([=]() { - modelDeleting = true; - modelsDownloaded = false; - update(); - - params.putBoolNonBlocking("ModelsDownloaded", false); - - deleteModelBtn->setValue(tr("Deleting...")); + QString selectedModel = MultiOptionDialog::getSelection(tr("Select a model to delete"), deletableModels, "", this); + if (!selectedModel.isEmpty()) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to delete the '%1' model?").arg(selectedModel), tr("Delete"), this)) { + std::thread([=]() { + modelDeleting = true; + modelsDownloaded = false; + update(); - QFile::remove(modelDir.absoluteFilePath(labelToFileMap[selectedModel])); + params.putBoolNonBlocking("ModelsDownloaded", false); + deleteModelBtn->setValue(tr("Deleting...")); - deleteModelBtn->setValue(tr("Deleted!")); + QFile::remove(modelDir.absoluteFilePath(labelToFileMap[selectedModel])); + deleteModelBtn->setValue(tr("Deleted!")); - std::this_thread::sleep_for(std::chrono::seconds(2)); - deleteModelBtn->setValue(""); - modelDeleting = false; + util::sleep_for(1000); + deleteModelBtn->setValue(""); + modelDeleting = false; - std::string currentModel = params.get("Model") + ".thneed"; - QStringList modelFiles = modelDir.entryList({"*.thneed"}, QDir::Files); - modelFiles.removeAll(QString::fromStdString(currentModel)); + QStringList modelFiles = modelDir.entryList({"*.thneed"}, QDir::Files); + modelFiles.removeAll(currentModel); - haveModelsDownloaded = modelFiles.size() > 1; - update(); - }).detach(); + haveModelsDownloaded = modelFiles.size() > 1; + update(); + }).detach(); + } } }); controlToggle = reinterpret_cast(deleteModelBtn); @@ -557,18 +709,18 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil cancellingDownload = true; } else { QMap labelToModelMap; - QStringList existingModelFiles = modelDir.entryList({"*.thneed"}, QDir::Files); - QStringList downloadableModelLabels; + QStringList existingModels = modelDir.entryList({"*.thneed"}, QDir::Files); + QStringList downloadableModels; - for (int i = 0; i < availableModels.size(); ++i) { - QString modelFileName = availableModels[i] + ".thneed"; - if (!existingModelFiles.contains(modelFileName) && !availableModelNames[i].contains("(Default)")) { - downloadableModelLabels.append(availableModelNames[i]); + for (int i = 0; i < availableModels.size(); i++) { + QString modelFile = availableModels[i] + ".thneed"; + if (!existingModels.contains(modelFile) && !availableModelNames[i].contains("(Default)")) { + downloadableModels.append(availableModelNames[i]); labelToModelMap.insert(availableModelNames[i], availableModels[i]); } } - QString modelToDownload = MultiOptionDialog::getSelection(tr("Select a driving model to download"), downloadableModelLabels, "", this); + QString modelToDownload = MultiOptionDialog::getSelection(tr("Select a driving model to download"), downloadableModels, "", this); if (!modelToDownload.isEmpty()) { modelDownloading = true; paramsMemory.put("ModelToDownload", labelToModelMap.value(modelToDownload).toStdString()); @@ -581,21 +733,22 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil QObject::connect(progressTimer, &QTimer::timeout, this, [=]() { QString progress = QString::fromStdString(paramsMemory.get("ModelDownloadProgress")); - bool downloadFailed = progress.contains(QRegularExpression("cancelled|exists|Failed|offline", QRegularExpression::CaseInsensitiveOption)); + bool downloadComplete = progress.contains(QRegularExpression("downloaded", QRegularExpression::CaseInsensitiveOption)); + bool downloadFailed = progress.contains(QRegularExpression("cancelled|exists|failed|offline", QRegularExpression::CaseInsensitiveOption)); - if (progress != "0%") { + if (!progress.isEmpty() && progress != "0%") { downloadModelBtn->setValue(progress); } - if (progress == "Downloaded!" || downloadFailed) { - bool lastModelDownloaded = !downloadFailed; + if (downloadComplete || downloadFailed) { + bool lastModelDownloaded = downloadComplete; - if (!downloadFailed) { + if (downloadComplete) { haveModelsDownloaded = true; update(); } - if (lastModelDownloaded) { + if (downloadComplete) { for (const QString &model : availableModels) { if (!QFile::exists(modelDir.filePath(model + ".thneed"))) { lastModelDownloaded = false; @@ -606,6 +759,7 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil downloadModelBtn->setValue(progress); + paramsMemory.remove("CancelModelDownload"); paramsMemory.remove("ModelDownloadProgress"); progressTimer->stop(); @@ -614,8 +768,8 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil QTimer::singleShot(2000, this, [=]() { cancellingDownload = false; modelDownloading = false; + downloadModelBtn->setValue(""); - paramsMemory.remove("CancelModelDownload"); if (lastModelDownloaded) { modelsDownloaded = true; @@ -646,18 +800,16 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil } else if (param == "SelectModel") { selectModelBtn = new ButtonControl(title, tr("SELECT"), desc); QObject::connect(selectModelBtn, &ButtonControl::clicked, [=]() { - QSet modelFilesBaseNames = QSet::fromList( - modelDir.entryList({"*.thneed"}, QDir::Files).replaceInStrings(QRegExp("\\.thneed$"), "") - ); + QSet modelFilesBaseNames = QSet::fromList(modelDir.entryList({"*.thneed"}, QDir::Files).replaceInStrings(QRegExp("\\.thneed$"), "")); + QStringList selectableModels; - QStringList selectableModelLabels; - for (int i = 0; i < availableModels.size(); ++i) { + for (int i = 0; i < availableModels.size(); i++) { if (modelFilesBaseNames.contains(availableModels[i]) || availableModelNames[i].contains("(Default)")) { - selectableModelLabels.append(availableModelNames[i]); + selectableModels.append(availableModelNames[i]); } } - QString modelToSelect = MultiOptionDialog::getSelection(tr("Select a model - 🗺️ = Navigation | 📡 = Radar | 👀 = VOACC"), selectableModelLabels, "", this); + QString modelToSelect = MultiOptionDialog::getSelection(tr("Select a model - 🗺️ = Navigation | 📡 = Radar | 👀 = VOACC"), selectableModels, "", this); if (!modelToSelect.isEmpty()) { selectModelBtn->setValue(modelToSelect); int modelIndex = availableModelNames.indexOf(modelToSelect); @@ -666,16 +818,14 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil params.putNonBlocking("ModelName", modelToSelect.toStdString()); if (experimentalModels.contains(availableModels.at(modelIndex))) { - FrogPilotConfirmationDialog::toggleAlert( - tr("WARNING: This is a very experimental model and may drive dangerously!"), - tr("I understand the risks."), this); + FrogPilotConfirmationDialog::toggleAlert(tr("WARNING: This is a very experimental model and may drive dangerously!"), tr("I understand the risks."), this); } QString model = availableModelNames.at(modelIndex); QString part_model_param = processModelName(model); if (!params.checkKey(part_model_param.toStdString() + "CalibrationParams") || !params.checkKey(part_model_param.toStdString() + "LiveTorqueParameters")) { - if (FrogPilotConfirmationDialog::yesorno(tr("Do you want to start with a fresh calibration for the newly selected model?"), this)) { + if (FrogPilotConfirmationDialog::yesorno(tr("Start with a fresh calibration for the newly selected model?"), this)) { params.remove("CalibrationParams"); params.remove("LiveTorqueParameters"); } @@ -691,34 +841,33 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil selectModelBtn->setValue(QString::fromStdString(params.get("ModelName"))); controlToggle = reinterpret_cast(selectModelBtn); } else if (param == "ResetCalibrations") { - std::vector resetOptions{tr("RESET ALL"), tr("RESET ONE")}; - FrogPilotButtonsControl *resetCalibrationsBtn = new FrogPilotButtonsControl(title, desc, "", resetOptions); + FrogPilotButtonsControl *resetCalibrationsBtn = new FrogPilotButtonsControl(title, desc, icon, {tr("RESET ALL"), tr("RESET ONE")}); QObject::connect(resetCalibrationsBtn, &FrogPilotButtonsControl::showDescriptionEvent, this, &FrogPilotControlsPanel::updateCalibrationDescription); QObject::connect(resetCalibrationsBtn, &FrogPilotButtonsControl::buttonClicked, [=](int id) { if (id == 0) { if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure you want to completely reset all of your model calibrations?"), this)) { - for (QString model : availableModelNames) { - QString cleanedModelName = processModelName(model); - params.remove(QString("%1CalibrationParams").arg(cleanedModelName).toStdString()); - paramsStorage.remove(QString("%1CalibrationParams").arg(cleanedModelName).toStdString()); - params.remove(QString("%1LiveTorqueParameters").arg(cleanedModelName).toStdString()); - paramsStorage.remove(QString("%1LiveTorqueParameters").arg(cleanedModelName).toStdString()); + for (const QString &model : availableModelNames) { + QString cleanedModel = processModelName(model); + params.remove(QString("%1CalibrationParams").arg(cleanedModel).toStdString()); + paramsStorage.remove(QString("%1CalibrationParams").arg(cleanedModel).toStdString()); + params.remove(QString("%1LiveTorqueParameters").arg(cleanedModel).toStdString()); + paramsStorage.remove(QString("%1LiveTorqueParameters").arg(cleanedModel).toStdString()); } } } else if (id == 1) { QStringList selectableModelLabels; - for (int i = 0; i < availableModels.size(); ++i) { + for (int i = 0; i < availableModels.size(); i++) { selectableModelLabels.append(availableModelNames[i]); } QString modelToReset = MultiOptionDialog::getSelection(tr("Select a model to reset"), selectableModelLabels, "", this); if (!modelToReset.isEmpty()) { if (FrogPilotConfirmationDialog::yesorno(tr("Are you sure you want to completely reset this model's calibrations?"), this)) { - QString cleanedModelName = processModelName(modelToReset); - params.remove(QString("%1CalibrationParams").arg(cleanedModelName).toStdString()); - paramsStorage.remove(QString("%1CalibrationParams").arg(cleanedModelName).toStdString()); - params.remove(QString("%1LiveTorqueParameters").arg(cleanedModelName).toStdString()); - paramsStorage.remove(QString("%1LiveTorqueParameters").arg(cleanedModelName).toStdString()); + QString cleanedModel = processModelName(modelToReset); + params.remove(QString("%1CalibrationParams").arg(cleanedModel).toStdString()); + paramsStorage.remove(QString("%1CalibrationParams").arg(cleanedModel).toStdString()); + params.remove(QString("%1LiveTorqueParameters").arg(cleanedModel).toStdString()); + paramsStorage.remove(QString("%1LiveTorqueParameters").arg(cleanedModel).toStdString()); } } } @@ -728,6 +877,8 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil } else if (param == "QOLControls") { FrogPilotParamManageControl *qolToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(qolToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openParentToggle(); + for (auto &[key, toggle] : toggles) { std::set modifiedQolKeys = qolKeys; @@ -745,7 +896,7 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil toggle->setVisible(modifiedQolKeys.find(key.c_str()) != modifiedQolKeys.end()); } - openParentToggle(); + }); controlToggle = qolToggle; } else if (param == "CustomCruise") { @@ -776,15 +927,17 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil } else if (param == "LaneChangeCustomizations") { FrogPilotParamManageControl *laneChangeToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(laneChangeToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openParentToggle(); + for (auto &[key, toggle] : toggles) { toggle->setVisible(laneChangeKeys.find(key.c_str()) != laneChangeKeys.end()); } - openParentToggle(); + }); controlToggle = laneChangeToggle; } else if (param == "LaneChangeTime") { std::map laneChangeTimeLabels; - for (int i = 0; i <= 10; ++i) { + for (int i = 0; i <= 10; i++) { laneChangeTimeLabels[i] = i == 0 ? "Instant" : QString::number(i / 2.0) + " seconds"; } controlToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 10, laneChangeTimeLabels, this, false); @@ -796,25 +949,30 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil } else if (param == "SpeedLimitController") { FrogPilotParamManageControl *speedLimitControllerToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(speedLimitControllerToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openParentToggle(); + slcOpen = true; for (auto &[key, toggle] : toggles) { toggle->setVisible(speedLimitControllerKeys.find(key.c_str()) != speedLimitControllerKeys.end()); } - openParentToggle(); + }); controlToggle = speedLimitControllerToggle; } else if (param == "SLCControls") { FrogPilotParamManageControl *manageSLCControlsToggle = new FrogPilotParamManageControl(param, title, desc, icon, this, true); QObject::connect(manageSLCControlsToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openSubParentToggle(); + for (auto &[key, toggle] : toggles) { toggle->setVisible(speedLimitControllerControlsKeys.find(key.c_str()) != speedLimitControllerControlsKeys.end()); } - openSubParentToggle(); }); controlToggle = manageSLCControlsToggle; } else if (param == "SLCQOL") { FrogPilotParamManageControl *manageSLCQOLToggle = new FrogPilotParamManageControl(param, title, desc, icon, this, true); QObject::connect(manageSLCQOLToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openSubParentToggle(); + for (auto &[key, toggle] : toggles) { std::set modifiedSpeedLimitControllerQOLKeys = speedLimitControllerQOLKeys; @@ -828,7 +986,7 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil toggle->setVisible(modifiedSpeedLimitControllerQOLKeys.find(key.c_str()) != modifiedSpeedLimitControllerQOLKeys.end()); } - openSubParentToggle(); + }); controlToggle = manageSLCQOLToggle; } else if (param == "SLCConfirmation") { @@ -840,10 +998,11 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil } else if (param == "SLCVisuals") { FrogPilotParamManageControl *manageSLCVisualsToggle = new FrogPilotParamManageControl(param, title, desc, icon, this, true); QObject::connect(manageSLCVisualsToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openSubParentToggle(); + for (auto &[key, toggle] : toggles) { toggle->setVisible(speedLimitControllerVisualsKeys.find(key.c_str()) != speedLimitControllerVisualsKeys.end()); } - openSubParentToggle(); }); controlToggle = manageSLCVisualsToggle; } else if (param == "Offset1" || param == "Offset2" || param == "Offset3" || param == "Offset4") { @@ -869,7 +1028,7 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil QObject::connect(slcPriorityButton, &ButtonControl::clicked, [=]() { QStringList selectedPriorities; - for (int i = 1; i <= 3; ++i) { + for (int i = 1; i <= 3; i++) { QStringList currentPriorities = (i == 1) ? primaryPriorities : secondaryTertiaryPriorities; QStringList prioritiesToDisplay = currentPriorities; for (const auto &selectedPriority : qAsConst(selectedPriorities)) { @@ -902,7 +1061,7 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil }); QStringList initialPriorities; - for (int i = 1; i <= 3; ++i) { + for (int i = 1; i <= 3; i++) { QString priorityKey = QString("SLCPriority%1").arg(i); QString priority = QString::fromStdString(params.get(priorityKey.toStdString())); @@ -916,10 +1075,12 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil } else if (param == "VisionTurnControl") { FrogPilotParamManageControl *visionTurnControlToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(visionTurnControlToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openParentToggle(); + for (auto &[key, toggle] : toggles) { toggle->setVisible(visionTurnControlKeys.find(key.c_str()) != visionTurnControlKeys.end()); } - openParentToggle(); + }); controlToggle = visionTurnControlToggle; } else if (param == "CurveSensitivity" || param == "TurnAggressiveness") { @@ -1003,6 +1164,13 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil } }); + QObject::connect(static_cast(toggles["OnroadDistanceButton"]), &ToggleControl::toggleFlipped, [this](bool state) { + downloadStatusLabel->setVisible(state); + manageDistanceIconsBtn->setVisible(state); + onroadDistanceButton = state; + update(); + }); + FrogPilotParamValueControl *trafficFollowToggle = static_cast(toggles["TrafficFollow"]); FrogPilotParamValueControl *trafficAccelerationToggle = static_cast(toggles["TrafficJerkAcceleration"]); FrogPilotParamValueControl *trafficDangerToggle = static_cast(toggles["TrafficJerkDanger"]); @@ -1105,13 +1273,42 @@ FrogPilotControlsPanel::FrogPilotControlsPanel(SettingsWindow *parent) : FrogPil void FrogPilotControlsPanel::showEvent(QShowEvent *event) { disableOpenpilotLongitudinal = params.getBool("DisableOpenpilotLongitudinal"); + iconsDownloaded = params.get("DownloadableDistanceIcons").empty(); modelRandomizer = params.getBool("ModelRandomizer"); + onroadDistanceButton = params.getBool("OnroadDistanceButton"); } void FrogPilotControlsPanel::updateState(const UIState &s) { if (!isVisible()) return; - if (modelManagementOpen) { + if (drivingPersonalitiesOpen && onroadDistanceButton) { + if (themeDownloading) { + QString progress = QString::fromStdString(paramsMemory.get("ThemeDownloadProgress")); + bool downloadFailed = progress.contains(QRegularExpression("cancelled|exists|Failed|offline", QRegularExpression::CaseInsensitiveOption)); + + if (progress != "Downloading...") { + downloadStatusLabel->setText(progress); + } + + if (progress == "Downloaded!" || downloadFailed) { + QTimer::singleShot(2000, [=]() { + if (!themeDownloading) { + downloadStatusLabel->setText("Idle"); + } + }); + paramsMemory.remove("ThemeDownloadProgress"); + iconsDownloading = false; + themeDownloading = false; + + iconsDownloaded = params.get("DownloadableDistanceIcons").empty(); + } + } + + manageDistanceIconsBtn->setText(1, iconsDownloading ? tr("CANCEL") : tr("DOWNLOAD")); + manageDistanceIconsBtn->setButtonEnabled(0, !themeDeleting && !themeDownloading); + manageDistanceIconsBtn->setButtonEnabled(1, s.scene.online && (!themeDownloading || iconsDownloading) && !cancellingDownload && !themeDeleting && !iconsDownloaded); + manageDistanceIconsBtn->setButtonEnabled(2, !themeDeleting && !themeDownloading); + } else if (modelManagementOpen) { downloadAllModelsBtn->setText(modelDownloading && allModelsDownloading ? tr("CANCEL") : tr("DOWNLOAD")); downloadModelBtn->setText(modelDownloading && !allModelsDownloading ? tr("CANCEL") : tr("DOWNLOAD")); @@ -1281,41 +1478,43 @@ void FrogPilotControlsPanel::startDownloadAllModels() { downloadAllModelsBtn->setValue(tr("Downloading models...")); - QTimer *checkDownloadTimer = new QTimer(this); - checkDownloadTimer->setInterval(100); + QTimer *progressTimer = new QTimer(this); + progressTimer->setInterval(100); - QObject::connect(checkDownloadTimer, &QTimer::timeout, this, [=]() { + QObject::connect(progressTimer, &QTimer::timeout, this, [=]() { QString progress = QString::fromStdString(paramsMemory.get("ModelDownloadProgress")); - bool downloadFailed = progress.contains(QRegularExpression("cancelled|exists|Failed|offline", QRegularExpression::CaseInsensitiveOption)); + bool downloadComplete = progress.contains(QRegularExpression("All models downloaded!", QRegularExpression::CaseInsensitiveOption)); + bool downloadFailed = progress.contains(QRegularExpression("cancelled|exists|failed|offline", QRegularExpression::CaseInsensitiveOption)); if (!progress.isEmpty() && progress != "0%") { downloadAllModelsBtn->setValue(progress); } - if (progress == "All models downloaded!" || downloadFailed) { - if (!downloadFailed) { + if (downloadComplete || downloadFailed) { + if (downloadComplete) { haveModelsDownloaded = true; update(); } + downloadAllModelsBtn->setValue(progress); + + paramsMemory.remove("CancelModelDownload"); + paramsMemory.remove("ModelDownloadProgress"); + + progressTimer->stop(); + progressTimer->deleteLater(); + QTimer::singleShot(2000, this, [=]() { - allModelsDownloading = false; cancellingDownload = false; modelDownloading = false; - downloadAllModelsBtn->setValue(""); - modelsDownloaded = params.getBool("ModelsDownloaded"); - paramsMemory.remove("CancelModelDownload"); - update(); - }); - paramsMemory.remove("ModelDownloadProgress"); + paramsMemory.remove("DownloadAllModels"); - checkDownloadTimer->stop(); - checkDownloadTimer->deleteLater(); + downloadAllModelsBtn->setValue(""); + }); } }); - - checkDownloadTimer->start(); + progressTimer->start(); } QString FrogPilotControlsPanel::processModelName(const QString &modelName) { @@ -1384,6 +1583,7 @@ void FrogPilotControlsPanel::updateModelLabels() { void FrogPilotControlsPanel::hideToggles() { customPersonalitiesOpen = false; + drivingPersonalitiesOpen = false; modelManagementOpen = false; slcOpen = false; @@ -1435,6 +1635,9 @@ void FrogPilotControlsPanel::hideSubToggles() { bool isVisible = drivingPersonalityKeys.find(key.c_str()) != drivingPersonalityKeys.end(); toggle->setVisible(isVisible); } + + downloadStatusLabel->setVisible(onroadDistanceButton); + manageDistanceIconsBtn->setVisible(onroadDistanceButton); } else if (slcOpen) { for (auto &[key, toggle] : toggles) { bool isVisible = speedLimitControllerKeys.find(key.c_str()) != speedLimitControllerKeys.end(); diff --git a/selfdrive/frogpilot/ui/qt/offroad/control_settings.h b/selfdrive/frogpilot/ui/qt/offroad/control_settings.h index 5b1b6f36f98b84..65dadcc4ee06dd 100644 --- a/selfdrive/frogpilot/ui/qt/offroad/control_settings.h +++ b/selfdrive/frogpilot/ui/qt/offroad/control_settings.h @@ -35,18 +35,22 @@ class FrogPilotControlsPanel : public FrogPilotListWidget { ButtonControl *downloadModelBtn; ButtonControl *selectModelBtn; + FrogPilotButtonsControl *manageDistanceIconsBtn; + FrogPilotParamValueToggleControl *steerRatioToggle; + LabelControl *downloadStatusLabel; + std::set aggressivePersonalityKeys = {"PersonalityInfo", "AggressiveFollow", "AggressiveJerkAcceleration", "AggressiveJerkDanger", "AggressiveJerkSpeed", "ResetAggressivePersonality"}; std::set aolKeys = {"AlwaysOnLateralLKAS", "AlwaysOnLateralMain", "HideAOLStatusBar", "PauseAOLOnBrake"}; - std::set conditionalExperimentalKeys = {"CESpeed", "CESpeedLead", "CECurves", "CELead", "CENavigation", "CESignal", "CEStopLights", "HideCEMStatusBar"}; + std::set conditionalExperimentalKeys = {"CESpeed", "CESpeedLead", "CECurves", "CELead", "CEModelStopTime", "CENavigation", "CESignal", "HideCEMStatusBar"}; std::set customdrivingPersonalityKeys = {"AggressivePersonalityProfile", "RelaxedPersonalityProfile", "StandardPersonalityProfile", "TrafficPersonalityProfile"}; std::set deviceManagementKeys = {"DeviceShutdown", "IncreaseThermalLimits", "LowVoltageShutdown", "NoLogging", "NoUploads", "OfflineMode"}; - std::set drivingPersonalityKeys = {"CustomPersonalities", "OnroadDistanceButton"}; + std::set drivingPersonalityKeys = {"CustomPersonalities", "OnroadDistanceButton", "OnroadDistanceButtonButtons", "DownloadStatusLabel"}; std::set experimentalModeActivationKeys = {"ExperimentalModeViaDistance", "ExperimentalModeViaLKAS", "ExperimentalModeViaTap"}; std::set laneChangeKeys = {"LaneChangeTime", "LaneDetectionWidth", "MinimumLaneChangeSpeed", "NudgelessLaneChange", "OneLaneChange"}; std::set lateralTuneKeys = {"ForceAutoTune", "NNFF", "NNFFLite", "SteerRatio", "TacoTune", "TurnDesires"}; - std::set longitudinalTuneKeys = {"AccelerationProfile", "AggressiveAcceleration", "DecelerationProfile", "LeadDetectionThreshold", "SmoothBraking", "StoppingDistance", "TrafficMode"}; + std::set longitudinalTuneKeys = {"AccelerationProfile", "DecelerationProfile", "HumanAcceleration", "HumanFollowing", "LeadDetectionThreshold", "StoppingDistance", "TrafficMode"}; std::set modelManagementKeys = {"AutomaticallyUpdateModels", "ModelRandomizer", "DeleteModel", "DownloadModel", "DownloadAllModels", "SelectModel", "ResetCalibrations"}; std::set modelRandomizerKeys = {"ManageBlacklistedModels", "ResetScores", "ReviewScores"}; std::set mtscKeys = {"DisableMTSCSmoothing", "MTSCAggressiveness", "MTSCCurvatureCheck"}; @@ -74,6 +78,7 @@ class FrogPilotControlsPanel : public FrogPilotListWidget { bool cancellingDownload; bool customPersonalitiesOpen; bool disableOpenpilotLongitudinal; + bool drivingPersonalitiesOpen; bool hasAutoTune; bool hasCommaNNFFSupport; bool hasNNFFLog; @@ -81,6 +86,8 @@ class FrogPilotControlsPanel : public FrogPilotListWidget { bool hasPCMCruise; bool hasDashSpeedLimits; bool haveModelsDownloaded; + bool iconsDownloading; + bool iconsDownloaded; bool isGM; bool isHKGCanFd; bool isMetric = params.getBool("IsMetric"); @@ -91,8 +98,11 @@ class FrogPilotControlsPanel : public FrogPilotListWidget { bool modelManagementOpen; bool modelRandomizer; bool modelsDownloaded; + bool onroadDistanceButton; bool slcOpen; bool started; + bool themeDeleting; + bool themeDownloading; float steerRatioStock; diff --git a/selfdrive/frogpilot/ui/qt/offroad/vehicle_settings.cc b/selfdrive/frogpilot/ui/qt/offroad/vehicle_settings.cc index 6a5c84ba4fc875..0345611715eb42 100644 --- a/selfdrive/frogpilot/ui/qt/offroad/vehicle_settings.cc +++ b/selfdrive/frogpilot/ui/qt/offroad/vehicle_settings.cc @@ -138,6 +138,7 @@ FrogPilotVehiclesPanel::FrogPilotVehiclesPanel(SettingsWindow *parent) : FrogPil addItem(disableOpenpilotLong); std::vector> vehicleToggles { + {"ExperimentalGMTune", tr("Experimental GM Tune"), tr("FrogsGoMoo's experimental GM tune that is based on nothing but guesswork. Use at your own risk."), ""}, {"LongPitch", tr("Long Pitch Compensation"), tr("Smoothen out the gas and pedal controls."), ""}, {"VoltSNG", tr("2017 Volt SNG"), tr("Enable the 'Stop and Go' hack for 2017 Chevy Volts."), ""}, {"NewLongAPIGM", tr("Use comma's New Longitudinal API"), tr("Use comma's new longitudinal controls that have shown great improvement with acceleration and braking, but has a few issues on some GM vehicles."), ""}, @@ -149,7 +150,7 @@ FrogPilotVehiclesPanel::FrogPilotVehiclesPanel(SettingsWindow *parent) : FrogPil {"ToyotaDoors", tr("Automatically Lock/Unlock Doors"), tr("Automatically lock the doors when in drive and unlock when in park."), ""}, {"ClusterOffset", tr("Cluster Offset"), tr("Set the cluster offset openpilot uses to try and match the speed displayed on the dash."), ""}, {"SNGHack", tr("Stop and Go Hack"), tr("Enable the 'Stop and Go' hack for vehicles without stock stop and go functionality."), ""}, - {"ToyotaTune", tr("Toyota Tune"), tr("Use a custom Toyota longitudinal tune.\n\nCydia = More focused on TSS-P vehicles but works for all Toyotas\n\nDragonPilot = Focused on TSS2 vehicles\n\nFrogPilot = Takes the best of both worlds with some personal tweaks focused around FrogsGoMoo's 2019 Lexus ES 350"), ""}, + {"ToyotaTune", tr("Toyota Tune"), tr("Use a custom Toyota longitudinal tune.\n\nCydia = More focused on TSS-P vehicles but works for all Toyotas\n\nFrogPilot = Takes the Cydia tune with some personal tweaks focused around FrogsGoMoo's 2019 Lexus ES 350"), ""}, }; for (const auto &[param, title, desc, icon] : vehicleToggles) { @@ -211,7 +212,7 @@ FrogPilotVehiclesPanel::FrogPilotVehiclesPanel(SettingsWindow *parent) : FrogPil QObject::connect(uiState(), &UIState::offroadTransition, [this]() { std::thread([this]() { while (carMake.isEmpty()) { - std::this_thread::sleep_for(std::chrono::seconds(1)); + util::sleep_for(1000); carMake = QString::fromStdString(params.get("CarMake")); carModel = QString::fromStdString(params.get(params.get("CarModelName").empty() ? "CarModel" : "CarModelName")); } diff --git a/selfdrive/frogpilot/ui/qt/offroad/vehicle_settings.h b/selfdrive/frogpilot/ui/qt/offroad/vehicle_settings.h index abaa3c6e52d134..0f8374980862e0 100644 --- a/selfdrive/frogpilot/ui/qt/offroad/vehicle_settings.h +++ b/selfdrive/frogpilot/ui/qt/offroad/vehicle_settings.h @@ -31,7 +31,7 @@ class FrogPilotVehiclesPanel : public FrogPilotListWidget { QMap carModels; - std::set gmKeys = {"LongPitch", "NewLongAPIGM", "VoltSNG"}; + std::set gmKeys = {"ExperimentalGMTune", "LongPitch", "NewLongAPIGM", "VoltSNG"}; std::set hyundaiKeys = {"NewLongAPI"}; std::set subaruKeys = {"CrosstrekTorque"}; std::set toyotaKeys = {"ClusterOffset", "SNGHack", "ToyotaDoors", "ToyotaTune"}; diff --git a/selfdrive/frogpilot/ui/qt/offroad/visual_settings.cc b/selfdrive/frogpilot/ui/qt/offroad/visual_settings.cc index 47a47130f4e4ea..dc75b000093edd 100644 --- a/selfdrive/frogpilot/ui/qt/offroad/visual_settings.cc +++ b/selfdrive/frogpilot/ui/qt/offroad/visual_settings.cc @@ -14,6 +14,19 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot {"WarningSoftVolume", tr("Warning Soft Volume"), tr("Related alerts:\n\nBRAKE!, Risk of Collision\nTAKE CONTROL IMMEDIATELY"), ""}, {"WarningImmediateVolume", tr("Warning Immediate Volume"), tr("Related alerts:\n\nDISENGAGE IMMEDIATELY, Driver Distracted\nDISENGAGE IMMEDIATELY, Driver Unresponsive"), ""}, + {"BonusContent", tr("Bonus Content"), tr("Bonus FrogPilot features to make openpilot a bit more fun!"), "../frogpilot/assets/toggle_icons/frog.png"}, + {"GoatScream", tr("Goat Scream"), tr("Enable the famed 'Goat Scream' that has brought both joy and anger to FrogPilot users all around the world!"), ""}, + {"HolidayThemes", tr("Holiday Themes"), tr("The openpilot theme changes according to the current/upcoming holiday. Minor holidays last a day, while major holidays (Easter, Christmas, Halloween, etc.) last a week."), ""}, + {"PersonalizeOpenpilot", tr("Personalize openpilot"), tr("Customize openpilot to your personal tastes!"), ""}, + {"CustomColors", tr("Color Theme"), tr("Switch out the standard openpilot color scheme with themed colors.\n\nWant to submit your own color scheme? Post it in the 'feature-request' channel in the FrogPilot Discord!"), ""}, + {"CustomIcons", tr("Icon Pack"), tr("Switch out the standard openpilot icons with a set of themed icons.\n\nWant to submit your own icon pack? Post it in the 'feature-request' channel in the FrogPilot Discord!"), ""}, + {"CustomSounds", tr("Sound Pack"), tr("Switch out the standard openpilot sounds with a set of themed sounds.\n\nWant to submit your own sound pack? Post it in the 'feature-request' channel in the FrogPilot Discord!"), ""}, + {"CustomSignals", tr("Turn Signals"), tr("Add themed animation for your turn signals.\n\nWant to submit your own turn signal animation? Post it in the 'feature-request' channel in the FrogPilot Discord!"), ""}, + {"WheelIcon", tr("Steering Wheel"), tr("Replace the default steering wheel icon with a custom icon."), ""}, + {"DownloadStatusLabel", tr("Download Status"), "", ""}, + {"StartupAlert", tr("Startup Alert"), tr("Customize the 'Startup' alert that is shown when you go onroad."), ""}, + {"RandomEvents", tr("Random Events"), tr("Enjoy a bit of unpredictability with random events that can occur during certain driving conditions. This is purely cosmetic and has no impact on driving controls!"), ""}, + {"CustomAlerts", tr("Custom Alerts"), tr("Enable custom alerts for openpilot events."), "../frogpilot/assets/toggle_icons/icon_green_light.png"}, {"GreenLightAlert", tr("Green Light Alert"), tr("Get an alert when a traffic light changes from red to green."), ""}, {"LeadDepartingAlert", tr("Lead Departing Alert"), tr("Get an alert when the lead vehicle starts departing when at a standstill."), ""}, @@ -24,17 +37,9 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot {"CustomPaths", tr("Paths"), tr("Show your projected acceleration on the driving path, detected adjacent lanes, or when a vehicle is detected in your blindspot."), ""}, {"PedalsOnUI", tr("Pedals Being Pressed"), tr("Display the brake and gas pedals on the onroad UI below the steering wheel icon."), ""}, {"RoadNameUI", tr("Road Name"), tr("Display the current road's name at the bottom of the screen. Sourced from OpenStreetMap."), ""}, - {"WheelIcon", tr("Steering Wheel Icon"), tr("Replace the default steering wheel icon with a custom icon."), ""}, + {"RotatingWheel", tr("Rotating Steering Wheel"), tr("Rotate the steering wheel in the onroad UI alongside your physical steering wheel."), ""}, {"ShowStoppingPoint", tr("Stopping Points"), tr("Display the point where openpilot wants to stop for red lights/stop signs."), ""}, - {"CustomTheme", tr("Custom Themes"), tr("Enable the ability to use custom themes."), "../frogpilot/assets/wheel_images/frog.png"}, - {"CustomColors", tr("Color Theme"), tr("Switch out the standard openpilot color scheme with themed colors.\n\nWant to submit your own color scheme? Post it in the 'feature-request' channel in the FrogPilot Discord!"), ""}, - {"CustomIcons", tr("Icon Pack"), tr("Switch out the standard openpilot icons with a set of themed icons.\n\nWant to submit your own icon pack? Post it in the 'feature-request' channel in the FrogPilot Discord!"), ""}, - {"CustomSounds", tr("Sound Pack"), tr("Switch out the standard openpilot sounds with a set of themed sounds.\n\nWant to submit your own sound pack? Post it in the 'feature-request' channel in the FrogPilot Discord!"), ""}, - {"CustomSignals", tr("Turn Signals"), tr("Add themed animation for your turn signals.\n\nWant to submit your own turn signal animation? Post it in the 'feature-request' channel in the FrogPilot Discord!"), ""}, - {"HolidayThemes", tr("Holiday Themes"), tr("The openpilot theme changes according to the current/upcoming holiday. Minor holidays last a day, while major holidays (Easter, Christmas, Halloween, etc.) last a week."), ""}, - {"RandomEvents", tr("Random Events"), tr("Enjoy a bit of unpredictability with random events that can occur during certain driving conditions. This is purely cosmetic and has no impact on driving controls!"), ""}, - {"DeveloperUI", tr("Developer UI"), tr("Get various detailed information of what openpilot is doing behind the scenes."), "../frogpilot/assets/toggle_icons/icon_device.png"}, {"BorderMetrics", tr("Border Metrics"), tr("Display metrics in onroad UI border."), ""}, {"FPSCounter", tr("FPS Counter"), tr("Display the 'Frames Per Second' (FPS) of your onroad UI for monitoring system performance."), ""}, @@ -79,6 +84,7 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot FrogPilotParamManageControl *alertVolumeControlToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(alertVolumeControlToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { openParentToggle(); + for (auto &[key, toggle] : toggles) { toggle->setVisible(alertVolumeControlKeys.find(key.c_str()) != alertVolumeControlKeys.end()); } @@ -91,10 +97,724 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot visualToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 100, std::map(), this, false, "%"); } + } else if (param == "BonusContent") { + FrogPilotParamManageControl *BonusContentToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); + QObject::connect(BonusContentToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openParentToggle(); + + for (auto &[key, toggle] : toggles) { + toggle->setVisible(bonusContentKeys.find(key.c_str()) != bonusContentKeys.end()); + } + }); + visualToggle = BonusContentToggle; + } else if (param == "PersonalizeOpenpilot") { + FrogPilotParamManageControl *personalizeOpenpilotToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); + QObject::connect(personalizeOpenpilotToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { + openSubParentToggle(); + + personalizeOpenpilotOpen = true; + for (auto &[key, toggle] : toggles) { + toggle->setVisible(personalizeOpenpilotKeys.find(key.c_str()) != personalizeOpenpilotKeys.end()); + } + }); + visualToggle = personalizeOpenpilotToggle; + } else if (param == "CustomColors") { + std::vector customColorsOptions{tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")}; + manageCustomColorsBtn = new FrogPilotButtonsControl(title, desc, icon, customColorsOptions); + + std::function formatColorName = [](QString name) -> QString { + QChar separator = name.contains('_') ? '_' : '-'; + QStringList parts = name.replace(separator, ' ').split(' '); + + for (int i = 0; i < parts.size(); ++i) { + parts[i][0] = parts[i][0].toUpper(); + } + + if (separator == '-' && parts.size() > 1) { + return parts.first() + " (" + parts.last() + ")"; + } + + return parts.join(' '); + }; + + std::function formatColorNameForStorage = [](QString name) -> QString { + name = name.toLower(); + name = name.replace(" (", "-"); + name = name.replace(' ', '_'); + name.remove('(').remove(')'); + return name; + }; + + QObject::connect(manageCustomColorsBtn, &FrogPilotButtonsControl::buttonClicked, [=](int id) { + QDir themesDir{"/data/themes/theme_packs"}; + QFileInfoList dirList = themesDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + + QString currentColor = QString::fromStdString(params.get("CustomColors")).replace('_', ' ').replace('-', " (").toLower(); + currentColor[0] = currentColor[0].toUpper(); + for (int i = 1; i < currentColor.length(); i++) { + if (currentColor[i - 1] == ' ' || currentColor[i - 1] == '(') { + currentColor[i] = currentColor[i].toUpper(); + } + } + if (currentColor.contains(" (")) { + currentColor.append(')'); + } + + QStringList availableColors; + for (const QFileInfo &dirInfo : dirList) { + QString colorSchemeDir = dirInfo.absoluteFilePath(); + + if (QDir(colorSchemeDir + "/colors").exists()) { + availableColors << formatColorName(dirInfo.fileName()); + } + } + availableColors.append("Stock"); + std::sort(availableColors.begin(), availableColors.end()); + + if (id == 0) { + QStringList colorSchemesList = availableColors; + colorSchemesList.removeAll("Stock"); + colorSchemesList.removeAll(currentColor); + + QString colorSchemeToDelete = MultiOptionDialog::getSelection(tr("Select a color scheme to delete"), colorSchemesList, "", this); + if (!colorSchemeToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Are you sure you want to delete the '%1' color scheme?").arg(colorSchemeToDelete), tr("Delete"), this)) { + themeDeleting = true; + colorsDownloaded = false; + + QString selectedColor = formatColorNameForStorage(colorSchemeToDelete); + for (const QFileInfo &dirInfo : dirList) { + if (dirInfo.fileName() == selectedColor) { + QDir colorDir(dirInfo.absoluteFilePath() + "/colors"); + if (colorDir.exists()) { + colorDir.removeRecursively(); + } + } + } + + QStringList downloadableColors = QString::fromStdString(params.get("DownloadableColors")).split(","); + downloadableColors << colorSchemeToDelete; + downloadableColors.removeDuplicates(); + downloadableColors.removeAll(""); + std::sort(downloadableColors.begin(), downloadableColors.end()); + + params.put("DownloadableColors", downloadableColors.join(",").toStdString()); + themeDeleting = false; + } + } else if (id == 1) { + if (manageCustomColorsBtn->getButton(id)->text() == tr("CANCEL")) { + paramsMemory.putBool("CancelThemeDownload", true); + cancellingDownload = true; + + QTimer::singleShot(2000, [=]() { + paramsMemory.putBool("CancelThemeDownload", false); + cancellingDownload = false; + colorDownloading = false; + themeDownloading = false; + }); + } else { + QStringList downloadableColors = QString::fromStdString(params.get("DownloadableColors")).split(","); + QString colorSchemeToDownload = MultiOptionDialog::getSelection(tr("Select a color scheme to download"), downloadableColors, "", this); + + if (!colorSchemeToDownload.isEmpty()) { + QString convertedColorScheme = formatColorNameForStorage(colorSchemeToDownload); + paramsMemory.put("ColorToDownload", convertedColorScheme.toStdString()); + downloadStatusLabel->setText("Downloading..."); + paramsMemory.put("ThemeDownloadProgress", "Downloading..."); + colorDownloading = true; + themeDownloading = true; + + downloadableColors.removeAll(colorSchemeToDownload); + params.put("DownloadableColors", downloadableColors.join(",").toStdString()); + } + } + } else if (id == 2) { + QString colorSchemeToSelect = MultiOptionDialog::getSelection(tr("Select a color scheme"), availableColors, currentColor, this); + if (!colorSchemeToSelect.isEmpty()) { + params.put("CustomColors", formatColorNameForStorage(colorSchemeToSelect).toStdString()); + manageCustomColorsBtn->setValue(colorSchemeToSelect); + paramsMemory.putBool("UpdateTheme", true); + } + } + }); + + QString currentColor = QString::fromStdString(params.get("CustomColors")).replace('_', ' ').replace('-', " (").toLower(); + currentColor[0] = currentColor[0].toUpper(); + for (int i = 1; i < currentColor.length(); i++) { + if (currentColor[i - 1] == ' ' || currentColor[i - 1] == '(') { + currentColor[i] = currentColor[i].toUpper(); + } + } + if (currentColor.contains(" (")) { + currentColor.append(')'); + } + manageCustomColorsBtn->setValue(currentColor); + visualToggle = reinterpret_cast(manageCustomColorsBtn); + } else if (param == "CustomIcons") { + std::vector customIconsOptions{tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")}; + manageCustomIconsBtn = new FrogPilotButtonsControl(title, desc, icon, customIconsOptions); + + std::function formatIconName = [](QString name) -> QString { + QChar separator = name.contains('_') ? '_' : '-'; + QStringList parts = name.replace(separator, ' ').split(' '); + + for (int i = 0; i < parts.size(); ++i) { + parts[i][0] = parts[i][0].toUpper(); + } + + if (separator == '-' && parts.size() > 1) { + return parts.first() + " (" + parts.last() + ")"; + } + + return parts.join(' '); + }; + + std::function formatIconNameForStorage = [](QString name) -> QString { + name = name.toLower(); + name = name.replace(" (", "-"); + name = name.replace(' ', '_'); + name.remove('(').remove(')'); + return name; + }; + + QObject::connect(manageCustomIconsBtn, &FrogPilotButtonsControl::buttonClicked, [=](int id) { + QDir themesDir{"/data/themes/theme_packs"}; + QFileInfoList dirList = themesDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + + QString currentIcon = QString::fromStdString(params.get("CustomIcons")).replace('_', ' ').replace('-', " (").toLower(); + currentIcon[0] = currentIcon[0].toUpper(); + for (int i = 1; i < currentIcon.length(); i++) { + if (currentIcon[i - 1] == ' ' || currentIcon[i - 1] == '(') { + currentIcon[i] = currentIcon[i].toUpper(); + } + } + if (currentIcon.contains(" (")) { + currentIcon.append(')'); + } + + QStringList availableIcons; + for (const QFileInfo &dirInfo : dirList) { + QString iconDir = dirInfo.absoluteFilePath(); + if (QDir(iconDir + "/icons").exists()) { + availableIcons << formatIconName(dirInfo.fileName()); + } + } + availableIcons.append("Stock"); + std::sort(availableIcons.begin(), availableIcons.end()); + + if (id == 0) { + QStringList customIconsList = availableIcons; + customIconsList.removeAll("Stock"); + customIconsList.removeAll(currentIcon); + + QString iconPackToDelete = MultiOptionDialog::getSelection(tr("Select an icon pack to delete"), customIconsList, "", this); + if (!iconPackToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Are you sure you want to delete the '%1' icon pack?").arg(iconPackToDelete), tr("Delete"), this)) { + themeDeleting = true; + iconsDownloaded = false; + + QString selectedIcon = formatIconNameForStorage(iconPackToDelete); + for (const QFileInfo &dirInfo : dirList) { + if (dirInfo.fileName() == selectedIcon) { + QDir iconDir(dirInfo.absoluteFilePath() + "/icons"); + if (iconDir.exists()) { + iconDir.removeRecursively(); + } + } + } + + QStringList downloadableIcons = QString::fromStdString(params.get("DownloadableIcons")).split(","); + downloadableIcons << iconPackToDelete; + downloadableIcons.removeDuplicates(); + downloadableIcons.removeAll(""); + std::sort(downloadableIcons.begin(), downloadableIcons.end()); + + params.put("DownloadableIcons", downloadableIcons.join(",").toStdString()); + themeDeleting = false; + } + } else if (id == 1) { + if (manageCustomIconsBtn->getButton(id)->text() == tr("CANCEL")) { + paramsMemory.putBool("CancelThemeDownload", true); + cancellingDownload = true; + + QTimer::singleShot(2000, [=]() { + paramsMemory.putBool("CancelThemeDownload", false); + cancellingDownload = false; + iconDownloading = false; + themeDownloading = false; + }); + } else { + QStringList downloadableIcons = QString::fromStdString(params.get("DownloadableIcons")).split(","); + QString iconPackToDownload = MultiOptionDialog::getSelection(tr("Select an icon pack to download"), downloadableIcons, "", this); + + if (!iconPackToDownload.isEmpty()) { + QString convertedIconPack = formatIconNameForStorage(iconPackToDownload); + paramsMemory.put("IconToDownload", convertedIconPack.toStdString()); + downloadStatusLabel->setText("Downloading..."); + paramsMemory.put("ThemeDownloadProgress", "Downloading..."); + iconDownloading = true; + themeDownloading = true; + + downloadableIcons.removeAll(iconPackToDownload); + params.put("DownloadableIcons", downloadableIcons.join(",").toStdString()); + } + } + } else if (id == 2) { + QString iconPackToSelect = MultiOptionDialog::getSelection(tr("Select an icon pack"), availableIcons, currentIcon, this); + if (!iconPackToSelect.isEmpty()) { + params.put("CustomIcons", formatIconNameForStorage(iconPackToSelect).toStdString()); + manageCustomIconsBtn->setValue(iconPackToSelect); + paramsMemory.putBool("UpdateTheme", true); + } + } + }); + + QString currentIcon = QString::fromStdString(params.get("CustomIcons")).replace('_', ' ').replace('-', " (").toLower(); + currentIcon[0] = currentIcon[0].toUpper(); + for (int i = 1; i < currentIcon.length(); i++) { + if (currentIcon[i - 1] == ' ' || currentIcon[i - 1] == '(') { + currentIcon[i] = currentIcon[i].toUpper(); + } + } + if (currentIcon.contains(" (")) { + currentIcon.append(')'); + } + manageCustomIconsBtn->setValue(currentIcon); + visualToggle = reinterpret_cast(manageCustomIconsBtn); + } else if (param == "CustomSignals") { + std::vector customSignalsOptions{tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")}; + manageCustomSignalsBtn = new FrogPilotButtonsControl(title, desc, icon, customSignalsOptions); + + std::function formatSignalName = [](QString name) -> QString { + QChar separator = name.contains('_') ? '_' : '-'; + QStringList parts = name.replace(separator, ' ').split(' '); + + for (int i = 0; i < parts.size(); ++i) { + parts[i][0] = parts[i][0].toUpper(); + } + + if (separator == '-' && parts.size() > 1) { + return parts.first() + " (" + parts.last() + ")"; + } + + return parts.join(' '); + }; + + std::function formatSignalNameForStorage = [](QString name) -> QString { + name = name.toLower(); + name = name.replace(" (", "-"); + name = name.replace(' ', '_'); + name.remove('(').remove(')'); + return name; + }; + + QObject::connect(manageCustomSignalsBtn, &FrogPilotButtonsControl::buttonClicked, [=](int id) { + QDir themesDir{"/data/themes/theme_packs"}; + QFileInfoList dirList = themesDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + + QString currentSignal = QString::fromStdString(params.get("CustomSignals")).replace('_', ' ').replace('-', " (").toLower(); + currentSignal[0] = currentSignal[0].toUpper(); + for (int i = 1; i < currentSignal.length(); i++) { + if (currentSignal[i - 1] == ' ' || currentSignal[i - 1] == '(') { + currentSignal[i] = currentSignal[i].toUpper(); + } + } + if (currentSignal.contains(" (")) { + currentSignal.append(')'); + } + + QStringList availableSignals; + for (const QFileInfo &dirInfo : dirList) { + QString signalDir = dirInfo.absoluteFilePath(); + if (QDir(signalDir + "/signals").exists()) { + availableSignals << formatSignalName(dirInfo.fileName()); + } + } + availableSignals.append("Stock"); + std::sort(availableSignals.begin(), availableSignals.end()); + + if (id == 0) { + QStringList customSignalsList = availableSignals; + customSignalsList.removeAll("Stock"); + customSignalsList.removeAll(currentSignal); + + QString signalPackToDelete = MultiOptionDialog::getSelection(tr("Select a signal pack to delete"), customSignalsList, "", this); + if (!signalPackToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Are you sure you want to delete the '%1' signal pack?").arg(signalPackToDelete), tr("Delete"), this)) { + themeDeleting = true; + signalsDownloaded = false; + + QString selectedSignal = formatSignalNameForStorage(signalPackToDelete); + for (const QFileInfo &dirInfo : dirList) { + if (dirInfo.fileName() == selectedSignal) { + QDir signalDir(dirInfo.absoluteFilePath() + "/signals"); + if (signalDir.exists()) { + signalDir.removeRecursively(); + } + } + } + + QStringList downloadableSignals = QString::fromStdString(params.get("DownloadableSignals")).split(","); + downloadableSignals << signalPackToDelete; + downloadableSignals.removeDuplicates(); + downloadableSignals.removeAll(""); + std::sort(downloadableSignals.begin(), downloadableSignals.end()); + + params.put("DownloadableSignals", downloadableSignals.join(",").toStdString()); + themeDeleting = false; + } + } else if (id == 1) { + if (manageCustomSignalsBtn->getButton(id)->text() == tr("CANCEL")) { + paramsMemory.putBool("CancelThemeDownload", true); + cancellingDownload = true; + + QTimer::singleShot(2000, [=]() { + paramsMemory.putBool("CancelThemeDownload", false); + cancellingDownload = false; + signalDownloading = false; + themeDownloading = false; + }); + } else { + QStringList downloadableSignals = QString::fromStdString(params.get("DownloadableSignals")).split(","); + QString signalPackToDownload = MultiOptionDialog::getSelection(tr("Select a signal pack to download"), downloadableSignals, "", this); + + if (!signalPackToDownload.isEmpty()) { + QString convertedSignalPack = formatSignalNameForStorage(signalPackToDownload); + paramsMemory.put("SignalToDownload", convertedSignalPack.toStdString()); + downloadStatusLabel->setText("Downloading..."); + paramsMemory.put("ThemeDownloadProgress", "Downloading..."); + signalDownloading = true; + themeDownloading = true; + + downloadableSignals.removeAll(signalPackToDownload); + params.put("DownloadableSignals", downloadableSignals.join(",").toStdString()); + } + } + } else if (id == 2) { + QString signalPackToSelect = MultiOptionDialog::getSelection(tr("Select a signal pack"), availableSignals, currentSignal, this); + if (!signalPackToSelect.isEmpty()) { + params.put("CustomSignals", formatSignalNameForStorage(signalPackToSelect).toStdString()); + manageCustomSignalsBtn->setValue(signalPackToSelect); + paramsMemory.putBool("UpdateTheme", true); + } + } + }); + + QString currentSignal = QString::fromStdString(params.get("CustomSignals")).replace('_', ' ').replace('-', " (").toLower(); + currentSignal[0] = currentSignal[0].toUpper(); + for (int i = 1; i < currentSignal.length(); i++) { + if (currentSignal[i - 1] == ' ' || currentSignal[i - 1] == '(') { + currentSignal[i] = currentSignal[i].toUpper(); + } + } + if (currentSignal.contains(" (")) { + currentSignal.append(')'); + } + manageCustomSignalsBtn->setValue(currentSignal); + visualToggle = reinterpret_cast(manageCustomSignalsBtn); + } else if (param == "CustomSounds") { + std::vector customSoundsOptions{tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")}; + manageCustomSoundsBtn = new FrogPilotButtonsControl(title, desc, icon, customSoundsOptions); + + std::function formatSoundName = [](QString name) -> QString { + QChar separator = name.contains('_') ? '_' : '-'; + QStringList parts = name.replace(separator, ' ').split(' '); + + for (int i = 0; i < parts.size(); ++i) { + parts[i][0] = parts[i][0].toUpper(); + } + + if (separator == '-' && parts.size() > 1) { + return parts.first() + " (" + parts.last() + ")"; + } + + return parts.join(' '); + }; + + std::function formatSoundNameForStorage = [](QString name) -> QString { + name = name.toLower(); + name = name.replace(" (", "-"); + name = name.replace(' ', '_'); + name.remove('(').remove(')'); + return name; + }; + + QObject::connect(manageCustomSoundsBtn, &FrogPilotButtonsControl::buttonClicked, [=](int id) { + QDir themesDir{"/data/themes/theme_packs"}; + QFileInfoList dirList = themesDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + + QString currentSound = QString::fromStdString(params.get("CustomSounds")).replace('_', ' ').replace('-', " (").toLower(); + currentSound[0] = currentSound[0].toUpper(); + for (int i = 1; i < currentSound.length(); i++) { + if (currentSound[i - 1] == ' ' || currentSound[i - 1] == '(') { + currentSound[i] = currentSound[i].toUpper(); + } + } + if (currentSound.contains(" (")) { + currentSound.append(')'); + } + + QStringList availableSounds; + for (const QFileInfo &dirInfo : dirList) { + QString soundDir = dirInfo.absoluteFilePath(); + if (QDir(soundDir + "/sounds").exists()) { + availableSounds << formatSoundName(dirInfo.fileName()); + } + } + availableSounds.append("Stock"); + std::sort(availableSounds.begin(), availableSounds.end()); + + if (id == 0) { + QStringList customSoundsList = availableSounds; + customSoundsList.removeAll("Stock"); + customSoundsList.removeAll(currentSound); + + QString soundSchemeToDelete = MultiOptionDialog::getSelection(tr("Select a sound pack to delete"), customSoundsList, "", this); + if (!soundSchemeToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Are you sure you want to delete the '%1' sound scheme?").arg(soundSchemeToDelete), tr("Delete"), this)) { + themeDeleting = true; + soundsDownloaded = false; + + QString selectedSound = formatSoundNameForStorage(soundSchemeToDelete); + for (const QFileInfo &dirInfo : dirList) { + if (dirInfo.fileName() == selectedSound) { + QDir soundDir(dirInfo.absoluteFilePath() + "/sounds"); + if (soundDir.exists()) { + soundDir.removeRecursively(); + } + } + } + + QStringList downloadableSounds = QString::fromStdString(params.get("DownloadableSounds")).split(","); + downloadableSounds << soundSchemeToDelete; + downloadableSounds.removeDuplicates(); + downloadableSounds.removeAll(""); + std::sort(downloadableSounds.begin(), downloadableSounds.end()); + + params.put("DownloadableSounds", downloadableSounds.join(",").toStdString()); + themeDeleting = false; + } + } else if (id == 1) { + if (manageCustomSoundsBtn->getButton(id)->text() == tr("CANCEL")) { + paramsMemory.putBool("CancelThemeDownload", true); + cancellingDownload = true; + + QTimer::singleShot(2000, [=]() { + paramsMemory.putBool("CancelThemeDownload", false); + cancellingDownload = false; + soundDownloading = false; + themeDownloading = false; + }); + } else { + QStringList downloadableSounds = QString::fromStdString(params.get("DownloadableSounds")).split(","); + QString soundSchemeToDownload = MultiOptionDialog::getSelection(tr("Select a sound pack to download"), downloadableSounds, "", this); + + if (!soundSchemeToDownload.isEmpty()) { + QString convertedSoundScheme = formatSoundNameForStorage(soundSchemeToDownload); + paramsMemory.put("SoundToDownload", convertedSoundScheme.toStdString()); + downloadStatusLabel->setText("Downloading..."); + paramsMemory.put("ThemeDownloadProgress", "Downloading..."); + soundDownloading = true; + themeDownloading = true; + + downloadableSounds.removeAll(soundSchemeToDownload); + params.put("DownloadableSounds", downloadableSounds.join(",").toStdString()); + } + } + } else if (id == 2) { + QString soundSchemeToSelect = MultiOptionDialog::getSelection(tr("Select a sound scheme"), availableSounds, currentSound, this); + if (!soundSchemeToSelect.isEmpty()) { + params.put("CustomSounds", formatSoundNameForStorage(soundSchemeToSelect).toStdString()); + manageCustomSoundsBtn->setValue(soundSchemeToSelect); + paramsMemory.putBool("UpdateTheme", true); + } + } + }); + + QString currentSound = QString::fromStdString(params.get("CustomSounds")).replace('_', ' ').replace('-', " (").toLower(); + currentSound[0] = currentSound[0].toUpper(); + for (int i = 1; i < currentSound.length(); i++) { + if (currentSound[i - 1] == ' ' || currentSound[i - 1] == '(') { + currentSound[i] = currentSound[i].toUpper(); + } + } + if (currentSound.contains(" (")) { + currentSound.append(')'); + } + manageCustomSoundsBtn->setValue(currentSound); + visualToggle = reinterpret_cast(manageCustomSoundsBtn); + } else if (param == "WheelIcon") { + std::vector wheelIconOptions{tr("DELETE"), tr("DOWNLOAD"), tr("SELECT")}; + manageWheelIconsBtn = new FrogPilotButtonsControl(title, desc, icon, wheelIconOptions); + + std::function formatWheelName = [](QString name) -> QString { + QChar separator = name.contains('_') ? '_' : '-'; + QStringList parts = name.replace(separator, ' ').split(' '); + + for (int i = 0; i < parts.size(); ++i) { + parts[i][0] = parts[i][0].toUpper(); + } + + if (separator == '-' && parts.size() > 1) { + return parts.first() + " (" + parts.last() + ")"; + } + + return parts.join(' '); + }; + + std::function formatWheelNameForStorage = [](QString name) -> QString { + name = name.toLower(); + name = name.replace(" (", "-"); + name = name.replace(' ', '_'); + name.remove('(').remove(')'); + return name; + }; + + QObject::connect(manageWheelIconsBtn, &FrogPilotButtonsControl::buttonClicked, [=](int id) { + QDir wheelDir{"/data/themes/steering_wheels"}; + QFileInfoList fileList = wheelDir.entryInfoList(QDir::Files); + + QString currentWheel = QString::fromStdString(params.get("WheelIcon")).replace('_', ' ').replace('-', " (").toLower(); + currentWheel[0] = currentWheel[0].toUpper(); + for (int i = 1; i < currentWheel.length(); i++) { + if (currentWheel[i - 1] == ' ' || currentWheel[i - 1] == '(') { + currentWheel[i] = currentWheel[i].toUpper(); + } + } + if (currentWheel.contains(" (")) { + currentWheel.append(')'); + } + + QStringList availableWheels; + for (const QFileInfo &fileInfo : fileList) { + QString baseName = fileInfo.completeBaseName(); + QString formattedName = formatWheelName(baseName); + if (formattedName != "Img Chffr Wheel") { + availableWheels << formattedName; + } + } + availableWheels.append("Stock"); + availableWheels.append("None"); + std::sort(availableWheels.begin(), availableWheels.end()); + + if (id == 0) { + QStringList steeringWheelList = availableWheels; + steeringWheelList.removeAll("None"); + steeringWheelList.removeAll("Stock"); + steeringWheelList.removeAll(currentWheel); + + QString imageToDelete = MultiOptionDialog::getSelection(tr("Select a steering wheel to delete"), steeringWheelList, "", this); + if (!imageToDelete.isEmpty() && ConfirmationDialog::confirm(tr("Are you sure you want to delete the '%1' steering wheel image?").arg(imageToDelete), tr("Delete"), this)) { + themeDeleting = true; + wheelsDownloaded = false; + + QString selectedImage = formatWheelNameForStorage(imageToDelete); + for (const QFileInfo &fileInfo : fileList) { + if (fileInfo.completeBaseName() == selectedImage) { + QFile::remove(fileInfo.filePath()); + } + } + + QStringList downloadableWheels = QString::fromStdString(params.get("DownloadableWheels")).split(","); + downloadableWheels << imageToDelete; + downloadableWheels.removeDuplicates(); + downloadableWheels.removeAll(""); + std::sort(downloadableWheels.begin(), downloadableWheels.end()); + + params.put("DownloadableWheels", downloadableWheels.join(",").toStdString()); + themeDeleting = false; + } + } else if (id == 1) { + if (manageWheelIconsBtn->getButton(id)->text() == tr("CANCEL")) { + paramsMemory.putBool("CancelThemeDownload", true); + cancellingDownload = true; + + QTimer::singleShot(2000, [=]() { + paramsMemory.putBool("CancelThemeDownload", false); + cancellingDownload = false; + themeDownloading = false; + wheelDownloading = false; + }); + } else { + QStringList downloadableWheels = QString::fromStdString(params.get("DownloadableWheels")).split(","); + QString wheelToDownload = MultiOptionDialog::getSelection(tr("Select a steering wheel to download"), downloadableWheels, "", this); + + if (!wheelToDownload.isEmpty()) { + QString convertedImage = formatWheelNameForStorage(wheelToDownload); + paramsMemory.put("WheelToDownload", convertedImage.toStdString()); + downloadStatusLabel->setText("Downloading..."); + paramsMemory.put("ThemeDownloadProgress", "Downloading..."); + themeDownloading = true; + wheelDownloading = true; + + downloadableWheels.removeAll(wheelToDownload); + params.put("DownloadableWheels", downloadableWheels.join(",").toStdString()); + } + } + } else if (id == 2) { + QString imageToSelect = MultiOptionDialog::getSelection(tr("Select a steering wheel"), availableWheels, currentWheel, this); + if (!imageToSelect.isEmpty()) { + params.put("WheelIcon", formatWheelNameForStorage(imageToSelect).toStdString()); + manageWheelIconsBtn->setValue(imageToSelect); + paramsMemory.putBool("UpdateTheme", true); + } + } + }); + + QString currentWheel = QString::fromStdString(params.get("WheelIcon")).replace('_', ' ').replace('-', " (").toLower(); + currentWheel[0] = currentWheel[0].toUpper(); + for (int i = 1; i < currentWheel.length(); i++) { + if (currentWheel[i - 1] == ' ' || currentWheel[i - 1] == '(') { + currentWheel[i] = currentWheel[i].toUpper(); + } + } + if (currentWheel.contains(" (")) { + currentWheel.append(')'); + } + manageWheelIconsBtn->setValue(currentWheel); + visualToggle = reinterpret_cast(manageWheelIconsBtn); + } else if (param == "DownloadStatusLabel") { + downloadStatusLabel = new LabelControl(title, "Idle"); + visualToggle = reinterpret_cast(downloadStatusLabel); + } else if (param == "StartupAlert") { + std::vector startupAlertOptions{tr("STOCK"), tr("FROGPILOT"), tr("CUSTOM"), tr("CLEAR")}; + FrogPilotButtonsControl *startupAlertButton = new FrogPilotButtonsControl(title, desc, icon, startupAlertOptions); + QObject::connect(startupAlertButton, &FrogPilotButtonsControl::buttonClicked, [=](int id) { + int maxLengthTop = 35; + int maxLengthBottom = 45; + + QString stockTop = "Be ready to take over at any time"; + QString stockBottom = "Always keep hands on wheel and eyes on road"; + + QString frogpilotTop = "Hippity hoppity this is my property"; + QString frogpilotBottom = "so I do what I want 🐸"; + + QString currentTop = QString::fromStdString(params.get("StartupMessageTop")); + QString currentBottom = QString::fromStdString(params.get("StartupMessageBottom")); + + if (id == 0) { + params.put("StartupMessageTop", stockTop.toStdString()); + params.put("StartupMessageBottom", stockBottom.toStdString()); + } else if (id == 1) { + params.put("StartupMessageTop", frogpilotTop.toStdString()); + params.put("StartupMessageBottom", frogpilotBottom.toStdString()); + } else if (id == 2) { + QString newTop = InputDialog::getText(tr("Enter your text for the top half"), this, tr("Characters: 0/%1").arg(maxLengthTop), false, -1, currentTop, maxLengthTop).trimmed(); + if (newTop.length() > 0) { + params.putNonBlocking("StartupMessageTop", newTop.toStdString()); + QString newBottom = InputDialog::getText(tr("Enter your text for the bottom half"), this, tr("Characters: 0/%1").arg(maxLengthBottom), false, -1, currentBottom, maxLengthBottom).trimmed(); + if (newBottom.length() > 0) { + params.putNonBlocking("StartupMessageBottom", newBottom.toStdString()); + } + } + } else if (id == 3) { + params.remove("StartupMessageTop"); + params.remove("StartupMessageBottom"); + } + }); + visualToggle = reinterpret_cast(startupAlertButton); + } else if (param == "CustomAlerts") { FrogPilotParamManageControl *customAlertsToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(customAlertsToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { openParentToggle(); + for (auto &[key, toggle] : toggles) { std::set modifiedCustomAlertsKeys = customAlertsKeys; @@ -107,36 +827,11 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot }); visualToggle = customAlertsToggle; - } else if (param == "CustomTheme") { - FrogPilotParamManageControl *customThemeToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); - QObject::connect(customThemeToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { - openParentToggle(); - for (auto &[key, toggle] : toggles) { - toggle->setVisible(customThemeKeys.find(key.c_str()) != customThemeKeys.end()); - } - }); - visualToggle = customThemeToggle; - } else if (param == "CustomColors" || param == "CustomIcons" || param == "CustomSignals" || param == "CustomSounds") { - std::vector themeOptions{tr("Stock"), tr("Frog"), tr("Tesla"), tr("Stalin")}; - FrogPilotButtonParamControl *themeSelection = new FrogPilotButtonParamControl(param, title, desc, icon, themeOptions); - visualToggle = themeSelection; - - if (param == "CustomSounds") { - QObject::connect(themeSelection, &FrogPilotButtonParamControl::buttonClicked, [this](int id) { - if (id == 1) { - if (FrogPilotConfirmationDialog::yesorno(tr("Do you want to enable the bonus 'Goat' sound effect?"), this)) { - params.putBoolNonBlocking("GoatScream", true); - } else { - params.putBoolNonBlocking("GoatScream", false); - } - } - }); - } - } else if (param == "CustomUI") { FrogPilotParamManageControl *customUIToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(customUIToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { openParentToggle(); + for (auto &[key, toggle] : toggles) { toggle->setVisible(customOnroadUIKeys.find(key.c_str()) != customOnroadUIKeys.end()); } @@ -160,11 +855,6 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot pedalsToggle->updateButtonStates(); }); visualToggle = pedalsToggle; - } else if (param == "WheelIcon") { - std::vector wheelToggles{"RotatingWheel"}; - std::vector wheelToggleNames{"Live Rotation"}; - std::map steeringWheelLabels = {{-1, tr("None")}, {0, tr("Stock")}, {1, tr("Lexus")}, {2, tr("Toyota")}, {3, tr("Frog")}, {4, tr("Rocket")}, {5, tr("Hyundai")}, {6, tr("Stalin")}}; - visualToggle = new FrogPilotParamValueToggleControl(param, title, desc, icon, -1, 6, steeringWheelLabels, this, true, "", 1, 1, wheelToggles, wheelToggleNames); } else if (param == "ShowStoppingPoint") { std::vector stoppingPointToggles{"ShowStoppingPointMetrics"}; std::vector stoppingPointToggleNames{tr("Show Distance")}; @@ -174,6 +864,7 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot FrogPilotParamManageControl *developerUIToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(developerUIToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { openParentToggle(); + for (auto &[key, toggle] : toggles) { std::set modifiedDeveloperUIKeys = developerUIKeys ; @@ -225,6 +916,7 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot FrogPilotParamManageControl *modelUIToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(modelUIToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { openParentToggle(); + for (auto &[key, toggle] : toggles) { std::set modifiedModelUIKeysKeys = modelUIKeys; @@ -247,6 +939,7 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot FrogPilotParamManageControl *qolToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(qolToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { openParentToggle(); + for (auto &[key, toggle] : toggles) { toggle->setVisible(qolKeys.find(key.c_str()) != qolKeys.end()); } @@ -301,6 +994,7 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot FrogPilotParamManageControl *screenToggle = new FrogPilotParamManageControl(param, title, desc, icon, this); QObject::connect(screenToggle, &FrogPilotParamManageControl::manageButtonClicked, this, [this]() { openParentToggle(); + for (auto &[key, toggle] : toggles) { toggle->setVisible(screenKeys.find(key.c_str()) != screenKeys.end()); } @@ -355,6 +1049,7 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot } QObject::connect(parent, &SettingsWindow::closeParentToggle, this, &FrogPilotVisualsPanel::hideToggles); + QObject::connect(parent, &SettingsWindow::closeSubParentToggle, this, &FrogPilotVisualsPanel::hideSubToggles); QObject::connect(parent, &SettingsWindow::updateMetric, this, &FrogPilotVisualsPanel::updateMetric); QObject::connect(uiState(), &UIState::offroadTransition, this, &FrogPilotVisualsPanel::updateCarToggles); QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotVisualsPanel::updateState); @@ -364,11 +1059,74 @@ FrogPilotVisualsPanel::FrogPilotVisualsPanel(SettingsWindow *parent) : FrogPilot void FrogPilotVisualsPanel::showEvent(QShowEvent *event) { disableOpenpilotLongitudinal = params.getBool("DisableOpenpilotLongitudinal"); + + colorsDownloaded = params.get("DownloadableColors").empty(); + iconsDownloaded = params.get("DownloadableIcons").empty(); + signalsDownloaded = params.get("DownloadableSignals").empty(); + soundsDownloaded = params.get("DownloadableSounds").empty(); + wheelsDownloaded = params.get("DownloadableWheels").empty(); } void FrogPilotVisualsPanel::updateState(const UIState &s) { if (!isVisible()) return; + if (personalizeOpenpilotOpen) { + if (themeDownloading) { + QString progress = QString::fromStdString(paramsMemory.get("ThemeDownloadProgress")); + bool downloadFailed = progress.contains(QRegularExpression("cancelled|exists|Failed|offline", QRegularExpression::CaseInsensitiveOption)); + + if (progress != "Downloading...") { + downloadStatusLabel->setText(progress); + } + + if (progress == "Downloaded!" || downloadFailed) { + QTimer::singleShot(2000, [=]() { + if (!themeDownloading) { + downloadStatusLabel->setText("Idle"); + } + }); + paramsMemory.remove("ThemeDownloadProgress"); + colorDownloading = false; + iconDownloading = false; + signalDownloading = false; + soundDownloading = false; + themeDownloading = false; + wheelDownloading = false; + + colorsDownloaded = params.get("DownloadableColors").empty(); + iconsDownloaded = params.get("DownloadableIcons").empty(); + signalsDownloaded = params.get("DownloadableSignals").empty(); + soundsDownloaded = params.get("DownloadableSounds").empty(); + wheelsDownloaded = params.get("DownloadableWheels").empty(); + } + } + + manageCustomColorsBtn->setText(1, colorDownloading ? tr("CANCEL") : tr("DOWNLOAD")); + manageCustomColorsBtn->setButtonEnabled(0, !themeDeleting && !themeDownloading); + manageCustomColorsBtn->setButtonEnabled(1, s.scene.online && (!themeDownloading || colorDownloading) && !cancellingDownload && !themeDeleting && !colorsDownloaded); + manageCustomColorsBtn->setButtonEnabled(2, !themeDeleting && !themeDownloading); + + manageCustomIconsBtn->setText(1, iconDownloading ? tr("CANCEL") : tr("DOWNLOAD")); + manageCustomIconsBtn->setButtonEnabled(0, !themeDeleting && !themeDownloading); + manageCustomIconsBtn->setButtonEnabled(1, s.scene.online && (!themeDownloading || iconDownloading) && !cancellingDownload && !themeDeleting && !iconsDownloaded); + manageCustomIconsBtn->setButtonEnabled(2, !themeDeleting && !themeDownloading); + + manageCustomSignalsBtn->setText(1, signalDownloading ? tr("CANCEL") : tr("DOWNLOAD")); + manageCustomSignalsBtn->setButtonEnabled(0, !themeDeleting && !themeDownloading); + manageCustomSignalsBtn->setButtonEnabled(1, s.scene.online && (!themeDownloading || signalDownloading) && !cancellingDownload && !themeDeleting && !signalsDownloaded); + manageCustomSignalsBtn->setButtonEnabled(2, !themeDeleting && !themeDownloading); + + manageCustomSoundsBtn->setText(1, soundDownloading ? tr("CANCEL") : tr("DOWNLOAD")); + manageCustomSoundsBtn->setButtonEnabled(0, !themeDeleting && !themeDownloading); + manageCustomSoundsBtn->setButtonEnabled(1, s.scene.online && (!themeDownloading || soundDownloading) && !cancellingDownload && !themeDeleting && !soundsDownloaded); + manageCustomSoundsBtn->setButtonEnabled(2, !themeDeleting && !themeDownloading); + + manageWheelIconsBtn->setText(1, wheelDownloading ? tr("CANCEL") : tr("DOWNLOAD")); + manageWheelIconsBtn->setButtonEnabled(0, !themeDeleting && !themeDownloading); + manageWheelIconsBtn->setButtonEnabled(1, s.scene.online && (!themeDownloading || wheelDownloading) && !cancellingDownload && !themeDeleting && !wheelsDownloaded); + manageWheelIconsBtn->setButtonEnabled(2, !themeDeleting && !themeDownloading); + } + started = s.scene.started; } @@ -433,11 +1191,14 @@ void FrogPilotVisualsPanel::updateMetric() { } void FrogPilotVisualsPanel::hideToggles() { + personalizeOpenpilotOpen = false; + for (auto &[key, toggle] : toggles) { bool subToggles = alertVolumeControlKeys.find(key.c_str()) != alertVolumeControlKeys.end() || + bonusContentKeys.find(key.c_str()) != bonusContentKeys.end() || customAlertsKeys.find(key.c_str()) != customAlertsKeys.end() || customOnroadUIKeys.find(key.c_str()) != customOnroadUIKeys.end() || - customThemeKeys.find(key.c_str()) != customThemeKeys.end() || + personalizeOpenpilotKeys.find(key.c_str()) != personalizeOpenpilotKeys.end() || developerUIKeys.find(key.c_str()) != developerUIKeys.end() || modelUIKeys.find(key.c_str()) != modelUIKeys.end() || qolKeys.find(key.c_str()) != qolKeys.end() || @@ -447,3 +1208,14 @@ void FrogPilotVisualsPanel::hideToggles() { update(); } + +void FrogPilotVisualsPanel::hideSubToggles() { + if (personalizeOpenpilotOpen) { + for (auto &[key, toggle] : toggles) { + bool isVisible = bonusContentKeys.find(key.c_str()) != bonusContentKeys.end(); + toggle->setVisible(isVisible); + } + } + + update(); +} diff --git a/selfdrive/frogpilot/ui/qt/offroad/visual_settings.h b/selfdrive/frogpilot/ui/qt/offroad/visual_settings.h index 4726ab69a4f044..e6473917d952cf 100644 --- a/selfdrive/frogpilot/ui/qt/offroad/visual_settings.h +++ b/selfdrive/frogpilot/ui/qt/offroad/visual_settings.h @@ -13,32 +13,58 @@ class FrogPilotVisualsPanel : public FrogPilotListWidget { signals: void openParentToggle(); + void openSubParentToggle(); private: + void hideSubToggles(); void hideToggles(); void showEvent(QShowEvent *event) override; void updateCarToggles(); void updateMetric(); void updateState(const UIState &s); + FrogPilotButtonsControl *manageCustomColorsBtn; + FrogPilotButtonsControl *manageCustomIconsBtn; + FrogPilotButtonsControl *manageCustomSignalsBtn; + FrogPilotButtonsControl *manageCustomSoundsBtn; + FrogPilotButtonsControl *manageWheelIconsBtn; + + LabelControl *downloadStatusLabel; + std::set alertVolumeControlKeys = {"DisengageVolume", "EngageVolume", "PromptDistractedVolume", "PromptVolume", "RefuseVolume", "WarningImmediateVolume", "WarningSoftVolume"}; + std::set bonusContentKeys = {"GoatScream", "HolidayThemes", "PersonalizeOpenpilot", "RandomEvents"}; std::set customAlertsKeys = {"GreenLightAlert", "LeadDepartingAlert", "LoudBlindspotAlert"}; - std::set customOnroadUIKeys = {"Compass", "CustomPaths", "PedalsOnUI", "RoadNameUI", "ShowStoppingPoint", "WheelIcon"}; - std::set customThemeKeys = {"CustomColors", "CustomIcons", "CustomSignals", "CustomSounds", "HolidayThemes", "RandomEvents"}; + std::set customOnroadUIKeys = {"Compass", "CustomPaths", "PedalsOnUI", "RoadNameUI", "RotatingWheel", "ShowStoppingPoint"}; std::set developerUIKeys = {"BorderMetrics", "FPSCounter", "LateralMetrics", "LongitudinalMetrics", "NumericalTemp", "SidebarMetrics", "UseSI"}; std::set modelUIKeys = {"DynamicPathWidth", "HideLeadMarker", "LaneLinesWidth", "PathEdgeWidth", "PathWidth", "RoadEdgesWidth", "UnlimitedLength"}; + std::set personalizeOpenpilotKeys = {"CustomColors", "CustomIcons", "CustomSignals", "CustomSounds", "DownloadStatusLabel", "StartupAlert", "WheelIcon"}; std::set qolKeys = {"BigMap", "CameraView", "DriverCamera", "FullMap", "HideSpeed", "MapStyle", "StoppedTimer", "WheelSpeed"}; std::set screenKeys = {"HideUIElements", "ScreenBrightness", "ScreenBrightnessOnroad", "ScreenRecorder", "ScreenTimeout", "ScreenTimeoutOnroad", "StandbyMode"}; std::map toggles; Params params; + Params paramsMemory{"/dev/shm/params"}; + bool cancellingDownload; + bool colorDownloading; + bool colorsDownloaded; bool disableOpenpilotLongitudinal; bool hasAutoTune; bool hasBSM; bool hasOpenpilotLongitudinal; + bool iconDownloading; + bool iconsDownloaded; bool isMetric = params.getBool("IsMetric"); bool isRelease; + bool personalizeOpenpilotOpen; + bool signalDownloading; + bool signalsDownloaded; + bool soundDownloading; + bool soundsDownloaded; bool started; + bool themeDeleting; + bool themeDownloading; + bool wheelDownloading; + bool wheelsDownloaded; }; diff --git a/selfdrive/frogpilot/ui/qt/widgets/frogpilot_controls.cc b/selfdrive/frogpilot/ui/qt/widgets/frogpilot_controls.cc index 63cf81ba1970e1..5cc6a7b0d3cc96 100644 --- a/selfdrive/frogpilot/ui/qt/widgets/frogpilot_controls.cc +++ b/selfdrive/frogpilot/ui/qt/widgets/frogpilot_controls.cc @@ -8,13 +8,32 @@ void updateFrogPilotToggles() { int currentCall = ++callCounter; std::thread([currentCall]() { paramsMemory.putBool("FrogPilotTogglesUpdated", true); - std::this_thread::sleep_for(std::chrono::seconds(1)); + util::sleep_for(1000); if (currentCall == callCounter) { paramsMemory.putBool("FrogPilotTogglesUpdated", false); } }).detach(); } +QColor loadThemeColors(const QString &colorKey) { + QFile file("../frogpilot/assets/active_theme/colors/colors.json"); + if (!file.open(QIODevice::ReadOnly)) return QColor(); + + QJsonParseError parseError; + QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &parseError); + file.close(); + + if (parseError.error != QJsonParseError::NoError || !doc.isObject()) return QColor(); + + QJsonObject colorObj = doc.object().value(colorKey).toObject(); + int red = colorObj["red"].toInt(); + int green = colorObj["green"].toInt(); + int blue = colorObj["blue"].toInt(); + int alpha = colorObj["alpha"].toInt(); + + return QColor(red, green, blue, alpha); +} + bool FrogPilotConfirmationDialog::toggle(const QString &prompt_text, const QString &confirm_text, QWidget *parent) { ConfirmationDialog d = ConfirmationDialog(prompt_text, confirm_text, tr("Reboot Later"), false, parent); return d.exec(); diff --git a/selfdrive/frogpilot/ui/qt/widgets/frogpilot_controls.h b/selfdrive/frogpilot/ui/qt/widgets/frogpilot_controls.h index cf5e5470277ded..87fb8db130e1fc 100644 --- a/selfdrive/frogpilot/ui/qt/widgets/frogpilot_controls.h +++ b/selfdrive/frogpilot/ui/qt/widgets/frogpilot_controls.h @@ -2,12 +2,17 @@ #include +#include +#include +#include #include #include "selfdrive/ui/qt/widgets/controls.h" void updateFrogPilotToggles(); +QColor loadThemeColors(const QString &colorKey); + class FrogPilotConfirmationDialog : public ConfirmationDialog { Q_OBJECT @@ -241,6 +246,22 @@ class FrogPilotButtonsControl : public ParamControl { } } + void setButtonEnabled(int id, bool enable) { + if (QPushButton *button = qobject_cast(button_group->button(id))) { + button->setEnabled(enable); + } + } + + void setText(int id, const QString &text) { + if (QPushButton *button = qobject_cast(button_group->button(id))) { + button->setText(text); + } + } + + QPushButton *getButton(int id) const { + return qobject_cast(button_group->button(id)); + } + signals: void buttonClicked(int id); diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index d46fea28dd5522..a40f63737ec11c 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -62,7 +62,8 @@ def moving_avg_with_linear_decay(prev_mean: np.ndarray, new_val: np.ndarray, idx class Calibrator: def __init__(self, param_put: bool = False): # FrogPilot variables - self.frogpilot_toggles = FrogPilotVariables.toggles + frogpilot_toggles = FrogPilotVariables.toggles + FrogPilotVariables.update_frogpilot_params() self.update_toggles = False @@ -72,10 +73,8 @@ def __init__(self, param_put: bool = False): # Read saved calibration self.params = Params() - if self.params.check_key(self.frogpilot_toggles.part_model_param + "CalibrationParams"): - calibration_params = self.params.get(self.frogpilot_toggles.part_model_param + "CalibrationParams") - else: - calibration_params = self.params.get("CalibrationParams") + self.calibration_key = frogpilot_toggles.part_model_param + "CalibrationParams" + calibration_params = self.params.get(self.calibration_key) rpy_init = RPY_INIT wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT height = HEIGHT_INIT @@ -174,7 +173,7 @@ def update_status(self) -> None: write_this_cycle = (self.idx == 0) and (self.block_idx % (INPUTS_WANTED//5) == 5) if self.param_put and write_this_cycle: - self.params.put_nonblocking(self.frogpilot_toggles.part_model_param + "CalibrationParams", self.get_msg(True).to_bytes()) + self.params.put_nonblocking(self.calibration_key, self.get_msg(True).to_bytes()) # Update FrogPilot parameters if FrogPilotVariables.toggles_updated: diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 2c09d0b370a6f7..7617716dc21309 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -54,7 +54,8 @@ def add_point(self, x, y): class TorqueEstimator(ParameterEstimator): def __init__(self, CP, decimated=False): # FrogPilot variables - self.frogpilot_toggles = FrogPilotVariables.toggles + frogpilot_toggles = FrogPilotVariables.toggles + FrogPilotVariables.update_frogpilot_params() self.update_toggles = False @@ -100,10 +101,8 @@ def __init__(self, CP, decimated=False): # try to restore cached params params = Params() params_cache = params.get("CarParamsPrevRoute") - if params.check_key(self.frogpilot_toggles.part_model_param + "LiveTorqueParameters"): - torque_cache = params.get(self.frogpilot_toggles.part_model_param + "LiveTorqueParameters") - else: - torque_cache = params.get("LiveTorqueParameters") + self.torque_key = frogpilot_toggles.part_model_param + "LiveTorqueParameters" + torque_cache = params.get(self.torque_key) if params_cache is not None and torque_cache is not None: try: with log.Event.from_bytes(torque_cache) as log_evt: @@ -123,7 +122,7 @@ def __init__(self, CP, decimated=False): cloudlog.info("restored torque params from cache") except Exception: cloudlog.exception("failed to restore cached torque params") - params.remove(self.frogpilot_toggles.part_model_param + "LiveTorqueParameters") + params.remove(self.torque_key) self.filtered_params = {} for param in initial_params: @@ -245,6 +244,10 @@ def main(demo=False): # FrogPilot variables frogpilot_toggles = FrogPilotVariables.toggles + FrogPilotVariables.update_frogpilot_params() + + torque_key = frogpilot_toggles.part_model_param + "LiveTorqueParameters" + torque_cache = params.get(torque_key) while True: sm.update() @@ -261,7 +264,7 @@ def main(demo=False): # Cache points every 60 seconds while onroad if sm.frame % 240 == 0: msg = estimator.get_msg(valid=sm.all_checks(), with_points=True) - params.put_nonblocking(frogpilot_toggles.part_model_param + "LiveTorqueParameters", msg.to_bytes()) + params.put_nonblocking(torque_key, msg.to_bytes()) if __name__ == "__main__": import argparse diff --git a/selfdrive/modeld/fill_model_msg.py b/selfdrive/modeld/fill_model_msg.py index 7ccc2250fba30d..ba1dd7cb779013 100644 --- a/selfdrive/modeld/fill_model_msg.py +++ b/selfdrive/modeld/fill_model_msg.py @@ -8,6 +8,7 @@ ConfidenceClass = log.ModelDataV2.ConfidenceClass + class PublishState: def __init__(self): self.disengage_buffer = np.zeros(ModelConstants.CONFIDENCE_BUFFER_LEN*ModelConstants.DISENGAGE_WIDTH, dtype=np.float32) @@ -43,8 +44,8 @@ def fill_xyvat(builder, t, x, y, v, a, x_std=None, y_std=None, v_std=None, a_std def fill_model_msg(msg: capnp._DynamicStructBuilder, net_output_data: dict[str, np.ndarray], publish_state: PublishState, vipc_frame_id: int, vipc_frame_id_extra: int, frame_id: int, frame_drop: float, - timestamp_eof: int, timestamp_llk: int, model_execution_time: float, - nav_enabled: bool, valid: bool, secret_good_openpilot: bool) -> None: + timestamp_eof: int, timestamp_llk: int, model_execution_time: float, valid: bool, nav_enabled: bool, + clairvoyant_model: bool, secret_good_openpilot: bool) -> None: frame_age = frame_id - vipc_frame_id if frame_id > vipc_frame_id else 0 msg.valid = valid @@ -161,17 +162,18 @@ def fill_model_msg(msg: capnp._DynamicStructBuilder, net_output_data: dict[str, meta.hardBrakePredicted = hard_brake_predicted.item() # temporal pose - temporal_pose = modelV2.temporalPose - if secret_good_openpilot: - temporal_pose.trans = np.zeros((3,), dtype=np.float32).reshape(-1).tolist() - temporal_pose.transStd = np.zeros((3,), dtype=np.float32).reshape(-1).tolist() - temporal_pose.rot = np.zeros((3,), dtype=np.float32).reshape(-1).tolist() - temporal_pose.rotStd = np.zeros((3,), dtype=np.float32).reshape(-1).tolist() - else: - temporal_pose.trans = net_output_data['sim_pose'][0,:3].tolist() - temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:3].tolist() - temporal_pose.rot = net_output_data['sim_pose'][0,3:].tolist() - temporal_pose.rotStd = net_output_data['sim_pose_stds'][0,3:].tolist() + if not clairvoyant_model: + temporal_pose = modelV2.temporalPose + if secret_good_openpilot: + temporal_pose.trans = np.zeros((3,), dtype=np.float32).reshape(-1).tolist() + temporal_pose.transStd = np.zeros((3,), dtype=np.float32).reshape(-1).tolist() + temporal_pose.rot = np.zeros((3,), dtype=np.float32).reshape(-1).tolist() + temporal_pose.rotStd = np.zeros((3,), dtype=np.float32).reshape(-1).tolist() + else: + temporal_pose.trans = net_output_data['sim_pose'][0,:3].tolist() + temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:3].tolist() + temporal_pose.rot = net_output_data['sim_pose'][0,3:].tolist() + temporal_pose.rotStd = net_output_data['sim_pose_stds'][0,3:].tolist() # confidence if vipc_frame_id % (2*ModelConstants.MODEL_FREQ) == 0: diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 992d582388ca62..fd45e761d7cac9 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -35,6 +35,7 @@ MODEL_NAME = frogpilot_toggles.model +CLAIRVOYANT_MODEL = frogpilot_toggles.clairvoyant_model DISABLE_NAV = frogpilot_toggles.navigationless_model DISABLE_RADAR = frogpilot_toggles.radarless_model SECRET_GOOD_OPENPILOT = frogpilot_toggles.secretgoodopenpilot_model @@ -43,7 +44,12 @@ ModelRunner.THNEED: Path(__file__).parent / ('models/supercombo.thneed' if MODEL_NAME == DEFAULT_MODEL else f'{MODELS_PATH}/{MODEL_NAME}.thneed'), ModelRunner.ONNX: Path(__file__).parent / 'models/supercombo.onnx'} -METADATA_PATH = Path(__file__).parent / ('models/supercombo_metadata.pkl' if not SECRET_GOOD_OPENPILOT else 'models/secret-good-openpilot_metadata.pkl') +metadata_file = ( + 'secret-good-openpilot_metadata.pkl' if SECRET_GOOD_OPENPILOT else + 'clairvoyant-driver_metadata.pkl' if CLAIRVOYANT_MODEL else + 'supercombo_metadata.pkl' +) +METADATA_PATH = Path(__file__).parent / f'models/{metadata_file}' MODEL_WIDTH = 512 MODEL_HEIGHT = 256 @@ -76,7 +82,7 @@ def __init__(self, context: CLContext): 'desire': np.zeros(ModelConstants.DESIRE_LEN * (ModelConstants.HISTORY_BUFFER_LEN_SECRET+1 if SECRET_GOOD_OPENPILOT else ModelConstants.HISTORY_BUFFER_LEN+1), dtype=np.float32), 'traffic_convention': np.zeros(ModelConstants.TRAFFIC_CONVENTION_LEN, dtype=np.float32), 'lateral_control_params': np.zeros(ModelConstants.LATERAL_CONTROL_PARAMS_LEN, dtype=np.float32), - 'prev_desired_curv': np.zeros(ModelConstants.PREV_DESIRED_CURV_LEN * (ModelConstants.HISTORY_BUFFER_LEN_SECRET+1 if SECRET_GOOD_OPENPILOT else ModelConstants.HISTORY_BUFFER_LEN+1), dtype=np.float32), + 'prev_desired_curv': np.zeros(ModelConstants.PREV_DESIRED_CURV_LEN * (ModelConstants.HISTORY_BUFFER_LEN+1), dtype=np.float32), **({'nav_features': np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32), 'nav_instructions': np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32)} if not DISABLE_NAV else {}), 'features_buffer': np.zeros((ModelConstants.HISTORY_BUFFER_LEN_SECRET if SECRET_GOOD_OPENPILOT else ModelConstants.HISTORY_BUFFER_LEN) * ModelConstants.FEATURE_LEN, dtype=np.float32), @@ -126,9 +132,11 @@ def run(self, buf: VisionBuf, wbuf: VisionBuf, transform: np.ndarray, transform_ self.inputs['traffic_convention'][:] = inputs['traffic_convention'] self.inputs['lateral_control_params'][:] = inputs['lateral_control_params'] + if not DISABLE_NAV: self.inputs['nav_features'][:] = inputs['nav_features'] self.inputs['nav_instructions'][:] = inputs['nav_instructions'] + if DISABLE_RADAR: self.inputs['radar_tracks'][:] = inputs['radar_tracks'] @@ -156,7 +164,7 @@ def run(self, buf: VisionBuf, wbuf: VisionBuf, transform: np.ndarray, transform_ return None self.model.execute() - outputs = self.parser.parse_outputs(self.slice_outputs(self.output), SECRET_GOOD_OPENPILOT) + outputs = self.parser.parse_outputs(self.slice_outputs(self.output), CLAIRVOYANT_MODEL, SECRET_GOOD_OPENPILOT) if SECRET_GOOD_OPENPILOT: self.full_features_20Hz[:-1] = self.full_features_20Hz[1:] @@ -361,7 +369,8 @@ def main(demo=False): modelv2_send = messaging.new_message('modelV2') posenet_send = messaging.new_message('cameraOdometry') fill_model_msg(modelv2_send, model_output, publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id, frame_drop_ratio, - meta_main.timestamp_eof, timestamp_llk, model_execution_time, nav_enabled, live_calib_seen, SECRET_GOOD_OPENPILOT) + meta_main.timestamp_eof, timestamp_llk, model_execution_time, live_calib_seen, nav_enabled, + CLAIRVOYANT_MODEL, SECRET_GOOD_OPENPILOT) desire_state = modelv2_send.modelV2.meta.desireState l_lane_change_prob = desire_state[log.Desire.laneChangeLeft] diff --git a/selfdrive/modeld/models/clairvoyant-driver_metadata.pkl b/selfdrive/modeld/models/clairvoyant-driver_metadata.pkl new file mode 100644 index 00000000000000..c7fd569848aab2 Binary files /dev/null and b/selfdrive/modeld/models/clairvoyant-driver_metadata.pkl differ diff --git a/selfdrive/modeld/parse_model_outputs.py b/selfdrive/modeld/parse_model_outputs.py index d01e456d352953..20c902450b03a6 100644 --- a/selfdrive/modeld/parse_model_outputs.py +++ b/selfdrive/modeld/parse_model_outputs.py @@ -81,14 +81,14 @@ def parse_mdn(self, name, outs, in_N=0, out_N=1, out_shape=None): outs[name] = pred_mu_final.reshape(final_shape) outs[name + '_stds'] = pred_std_final.reshape(final_shape) - def parse_outputs(self, outs: dict[str, np.ndarray], secret_good_openpilot) -> dict[str, np.ndarray]: + def parse_outputs(self, outs: dict[str, np.ndarray], clairvoyant_model, secret_good_openpilot) -> dict[str, np.ndarray]: self.parse_mdn('plan', outs, in_N=ModelConstants.PLAN_MHP_N, out_N=ModelConstants.PLAN_MHP_SELECTION, out_shape=(ModelConstants.IDX_N,ModelConstants.PLAN_WIDTH)) self.parse_mdn('lane_lines', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_LANE_LINES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) self.parse_mdn('road_edges', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_ROAD_EDGES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) self.parse_mdn('road_transform', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) - if not secret_good_openpilot: + if not (clairvoyant_model or secret_good_openpilot): self.parse_mdn('sim_pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,)) self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION, @@ -99,7 +99,6 @@ def parse_outputs(self, outs: dict[str, np.ndarray], secret_good_openpilot) -> d self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,)) for k in ['lead_prob', 'lane_lines_prob', 'meta']: self.parse_binary_crossentropy(k, outs) - if not secret_good_openpilot: - self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,)) + self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,)) self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH)) return outs diff --git a/selfdrive/navd/map_renderer.cc b/selfdrive/navd/map_renderer.cc index 1e57ad3e7c9f88..d52ee162bd0661 100644 --- a/selfdrive/navd/map_renderer.cc +++ b/selfdrive/navd/map_renderer.cc @@ -41,8 +41,6 @@ MapRenderer::MapRenderer(const QMapLibre::Settings &settings, bool online) : m_s QSurfaceFormat fmt; fmt.setRenderableType(QSurfaceFormat::OpenGLES); - m_settings.setMapMode(QMapLibre::Settings::MapMode::Static); - ctx = std::make_unique(); ctx->setFormat(fmt); ctx->create(); @@ -89,18 +87,6 @@ MapRenderer::MapRenderer(const QMapLibre::Settings &settings, bool online) : m_s LOGE("Map loading failed with %d: '%s'\n", err_code, reason.toStdString().c_str()); }); - QObject::connect(m_map.data(), &QMapLibre::Map::staticRenderFinished, [=](const QString &error) { - rendering = false; - - if (!error.isEmpty()) { - LOGE("Static map rendering failed with error: '%s'\n", error.toStdString().c_str()); - } else if (vipc_server != nullptr) { - double end_render_t = millis_since_boot(); - publish((end_render_t - start_render_t) / 1000.0, true); - last_llk_rendered = (*sm)["liveLocationKalman"].getLogMonoTime(); - } - }); - if (online) { vipc_server.reset(new VisionIpcServer("navd")); vipc_server->create_buffers(VisionStreamType::VISION_STREAM_MAP, NUM_VIPC_BUFFERS, false, WIDTH, HEIGHT); @@ -128,16 +114,22 @@ void MapRenderer::msgUpdate() { float bearing = RAD2DEG(orientation.getValue()[2]); updatePosition(get_point_along_line(pos.getValue()[0], pos.getValue()[1], bearing, MAP_OFFSET), bearing); - if (!rendering) { + // TODO: use the static rendering mode instead + // retry render a few times + for (int i = 0; i < 5 && !rendered(); i++) { + QApplication::processEvents(QEventLoop::AllEvents, 100); update(); + if (rendered()) { + LOGW("rendered after %d retries", i+1); + break; + } } + // fallback to sending a blank frame if (!rendered()) { publish(0, false); } } - - } if (sm->updated("navRoute")) { @@ -165,9 +157,7 @@ void MapRenderer::updatePosition(QMapLibre::Coordinate position, float bearing) m_map->setCoordinate(position); m_map->setBearing(bearing); m_map->setZoom(zoom); - if (!rendering) { - update(); - } + update(); } bool MapRenderer::loaded() { @@ -175,10 +165,16 @@ bool MapRenderer::loaded() { } void MapRenderer::update() { - rendering = true; + double start_t = millis_since_boot(); gl_functions->glClear(GL_COLOR_BUFFER_BIT); - start_render_t = millis_since_boot(); - m_map->startStaticRender(); + m_map->render(); + gl_functions->glFlush(); + double end_t = millis_since_boot(); + + if ((vipc_server != nullptr) && loaded()) { + publish((end_t - start_t) / 1000.0, true); + last_llk_rendered = (*sm)["liveLocationKalman"].getLogMonoTime(); + } } void MapRenderer::sendThumbnail(const uint64_t ts, const kj::Array &buf) { diff --git a/selfdrive/navd/map_renderer.h b/selfdrive/navd/map_renderer.h index 956c1d54bc6a43..ef29bb988d9654 100644 --- a/selfdrive/navd/map_renderer.h +++ b/selfdrive/navd/map_renderer.h @@ -43,10 +43,8 @@ class MapRenderer : public QObject { void initLayers(); - double start_render_t; uint32_t frame_id = 0; uint64_t last_llk_rendered = 0; - bool rendering = false; bool rendered() { return last_llk_rendered == (*sm)["liveLocationKalman"].getLogMonoTime(); } diff --git a/selfdrive/navd/navd.py b/selfdrive/navd/navd.py index 0ef74d9f44c6e3..6e9888e4c65de8 100755 --- a/selfdrive/navd/navd.py +++ b/selfdrive/navd/navd.py @@ -58,14 +58,15 @@ def __init__(self, sm, pm): self.mapbox_token = os.environ["MAPBOX_TOKEN"] self.mapbox_host = "https://api.mapbox.com" elif not FrogPilotVariables.has_prime: - self.mapbox_token = self.params.get("MapboxPublicKey", encoding='utf8') + self.mapbox_token = self.params.get("MapboxSecretKey", encoding='utf8') self.mapbox_host = "https://api.mapbox.com" else: self.api = Api(self.params.get("DongleId", encoding='utf8')) - self.mapbox_host = "https://maps.comma.ai" + self.mapbox_host = os.getenv('MAPS_HOST', 'https://maps.comma.ai') # FrogPilot variables self.frogpilot_toggles = FrogPilotVariables.toggles + FrogPilotVariables.update_frogpilot_params() self.stop_coord = [] self.stop_signal = [] diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index 5b89fecc376270..4ae9b7b42d54da 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -174,7 +174,7 @@ void MapWindow::updateState(const UIState &s) { if (sm.updated("modelV2")) { // set path color on change, and show map on rising edge of navigate on openpilot bool nav_enabled = sm["modelV2"].getModelV2().getNavEnabled() && - (sm["controlsState"].getControlsState().getEnabled() || sm["frogpilotCarControl"].getFrogpilotCarControl().getAlwaysOnLateral()); + (sm["controlsState"].getControlsState().getEnabled() || uiState()->scene.always_on_lateral_active); if (nav_enabled != uiState()->scene.navigate_on_openpilot) { if (loaded_once) { m_map->setPaintProperty("navLayer", "line-color", getNavPathColor(nav_enabled)); diff --git a/selfdrive/ui/qt/maps/map_helpers.h b/selfdrive/ui/qt/maps/map_helpers.h index d1ac768e967ccf..2e2145eb5c2c45 100644 --- a/selfdrive/ui/qt/maps/map_helpers.h +++ b/selfdrive/ui/qt/maps/map_helpers.h @@ -14,7 +14,9 @@ #include "common/transformations/orientation.hpp" #include "cereal/messaging/messaging.h" -const QString MAPBOX_TOKEN = QString::fromStdString(Params().get("MapboxPublicKey")); +const QString MAPBOX_TOKEN = !util::getenv("MAPBOX_TOKEN").empty() ? util::getenv("MAPBOX_TOKEN").c_str() : + !Params().get("MapboxSecretKey").empty() ? QString::fromStdString(Params().get("MapboxSecretKey")) : + QString(); const QString MAPS_HOST = util::getenv("MAPS_HOST", MAPBOX_TOKEN.isEmpty() ? "https://maps.comma.ai" : "https://api.mapbox.com").c_str(); const QString MAPS_CACHE_PATH = "/data/mbgl-cache-navd.db"; diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 3ba8f75c900c61..69430a753735c3 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -306,7 +307,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { deleteDrivingDataBtn->setValue(tr("Deleted!")); - std::this_thread::sleep_for(std::chrono::seconds(2)); + util::sleep_for(2000); deleteDrivingDataBtn->setValue(""); deleteDrivingDataBtn->setEnabled(true); }).detach(); @@ -336,7 +337,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { screenRecordingsBtn->setValue(tr("Failed...")); } - std::this_thread::sleep_for(std::chrono::seconds(2)); + util::sleep_for(2000); screenRecordingsBtn->setValue(""); screenRecordingsBtn->setEnabled(true); }).detach(); @@ -360,7 +361,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { screenRecordingsBtn->setValue(tr("Failed...")); } - std::this_thread::sleep_for(std::chrono::seconds(2)); + util::sleep_for(2000); screenRecordingsBtn->setValue(""); screenRecordingsBtn->setEnabled(true); }).detach(); @@ -375,81 +376,153 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { FrogPilotButtonsControl *frogpilotBackupBtn = new FrogPilotButtonsControl(tr("FrogPilot Backups"), tr("Backup, delete, or restore your FrogPilot backups."), "", frogpilotBackupOptions); connect(frogpilotBackupBtn, &FrogPilotButtonsControl::buttonClicked, [=](int id) { QDir backupDir("/data/backups"); + QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, QDir::Name); + backupNames = backupNames.filter(QRegularExpression("^(?!.*_in_progress$).*$")); if (id == 0) { QString nameSelection = InputDialog::getText(tr("Name your backup"), this, "", false, 1); if (!nameSelection.isEmpty()) { + bool compressed = FrogPilotConfirmationDialog::yesorno(tr("Do you want to compress this backup? The end file size will be 2.25x smaller, but can take 10+ minutes."), this); std::thread([=]() { frogpilotBackupBtn->setEnabled(false); frogpilotBackupBtn->setValue(tr("Backing up...")); std::string fullBackupPath = backupDir.absolutePath().toStdString() + "/" + nameSelection.toStdString(); - std::string command = "mkdir -p " + fullBackupPath + " && rsync -av /data/openpilot/ " + fullBackupPath + "/"; + std::string inProgressBackupPath = fullBackupPath + "_in_progress"; + std::string command = "mkdir -p " + inProgressBackupPath + " && rsync -av /data/openpilot/ " + inProgressBackupPath + "/"; int result = std::system(command.c_str()); - frogpilotBackupBtn->setValue(result == 0 ? tr("Success!") : tr("Failed...")); - std::this_thread::sleep_for(std::chrono::seconds(2)); + if (result == 0) { + if (compressed) { + frogpilotBackupBtn->setValue(tr("Compressing backup...")); + std::string tarFilePathInProgress = fullBackupPath + "_in_progress.tar.gz"; + command = "tar -czf " + tarFilePathInProgress + " -C " + inProgressBackupPath + " . && rm -rf " + inProgressBackupPath; + result = std::system(command.c_str()); + + if (result == 0) { + std::string tarFilePath = fullBackupPath + ".tar.gz"; + command = "mv " + tarFilePathInProgress + " " + tarFilePath; + result = std::system(command.c_str()); + + if (result == 0) { + frogpilotBackupBtn->setValue(tr("Success!")); + } else { + frogpilotBackupBtn->setValue(tr("Failed...")); + std::system(("rm -f " + tarFilePathInProgress).c_str()); + } + } else { + frogpilotBackupBtn->setValue(tr("Failed...")); + std::system(("rm -f " + tarFilePathInProgress).c_str()); + std::system(("rm -rf " + inProgressBackupPath).c_str()); + } + } else { + command = "mv " + inProgressBackupPath + " " + fullBackupPath; + result = std::system(command.c_str()); + + if (result == 0) { + frogpilotBackupBtn->setValue(tr("Success!")); + } else { + frogpilotBackupBtn->setValue(tr("Failed...")); + std::system(("rm -rf " + inProgressBackupPath).c_str()); + } + } + } else { + frogpilotBackupBtn->setValue(tr("Failed...")); + std::system(("rm -rf " + inProgressBackupPath).c_str()); + } + + util::sleep_for(2000); frogpilotBackupBtn->setValue(""); frogpilotBackupBtn->setEnabled(true); }).detach(); } } else if (id == 1) { - QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - QString selection = MultiOptionDialog::getSelection(tr("Select a backup to delete"), backupNames, "", this); if (!selection.isEmpty()) { - if (!ConfirmationDialog::confirm(tr("Are you sure you want to delete this backup?"), tr("Delete"), this)) return; - std::thread([=]() { - frogpilotBackupBtn->setEnabled(false); - frogpilotBackupBtn->setValue(tr("Deleting...")); - - QDir dirToDelete(backupDir.absoluteFilePath(selection)); + if (ConfirmationDialog::confirm(tr("Are you sure you want to delete this backup?"), tr("Delete"), this)) { + std::thread([=]() { + frogpilotBackupBtn->setEnabled(false); + frogpilotBackupBtn->setValue(tr("Deleting...")); - frogpilotBackupBtn->setValue(dirToDelete.removeRecursively() ? tr("Deleted!") : tr("Failed...")); + QDir dirToDelete(backupDir.absoluteFilePath(selection)); + if (selection.endsWith(".tar.gz")) { + frogpilotBackupBtn->setValue(QFile::remove(dirToDelete.absolutePath()) ? tr("Deleted!") : tr("Failed...")); + } else { + frogpilotBackupBtn->setValue(dirToDelete.removeRecursively() ? tr("Deleted!") : tr("Failed...")); + } - std::this_thread::sleep_for(std::chrono::seconds(2)); - frogpilotBackupBtn->setValue(""); - frogpilotBackupBtn->setEnabled(true); - }).detach(); + util::sleep_for(2000); + frogpilotBackupBtn->setValue(""); + frogpilotBackupBtn->setEnabled(true); + }).detach(); + } } } else if (id == 2) { - QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - QString selection = MultiOptionDialog::getSelection(tr("Select a restore point"), backupNames, "", this); if (!selection.isEmpty()) { - if (!ConfirmationDialog::confirm(tr("Are you sure you want to restore this version of FrogPilot?"), tr("Restore"), this)) return; - std::thread([=]() { - frogpilotBackupBtn->setEnabled(false); - frogpilotBackupBtn->setValue(tr("Restoring...")); - - std::string sourcePath = backupDir.absolutePath().toStdString() + "/" + selection.toStdString(); - std::string targetPath = "/data/safe_staging/finalized"; - std::string consistentFilePath = targetPath + "/.overlay_consistent"; - std::string command = "rsync -av --delete -l --exclude='.overlay_consistent' " + sourcePath + "/ " + targetPath + "/"; - int result = std::system(command.c_str()); + if (ConfirmationDialog::confirm(tr("Are you sure you want to restore this version of FrogPilot?"), tr("Restore"), this)) { + std::thread([=]() { + frogpilotBackupBtn->setEnabled(false); + frogpilotBackupBtn->setValue(tr("Restoring...")); + + std::string sourcePath = backupDir.absolutePath().toStdString() + "/" + selection.toStdString(); + std::string targetPath = "/data/safe_staging/finalized"; + std::string consistentFilePath = targetPath + "/.overlay_consistent"; + std::string extractDirectory = "/data/restore_temp"; + + if (selection.endsWith(".tar.gz")) { + frogpilotBackupBtn->setValue(tr("Extracting...")); + + if (std::system(("mkdir -p " + extractDirectory).c_str()) != 0) { + frogpilotBackupBtn->setValue(tr("Failed...")); + util::sleep_for(2000); + frogpilotBackupBtn->setValue(""); + frogpilotBackupBtn->setEnabled(true); + return; + } + + if (std::system(("tar --strip-components=1 -xzf " + sourcePath + " -C " + extractDirectory).c_str()) != 0) { + frogpilotBackupBtn->setValue(tr("Failed...")); + util::sleep_for(2000); + frogpilotBackupBtn->setValue(""); + frogpilotBackupBtn->setEnabled(true); + return; + } + + sourcePath = extractDirectory; + frogpilotBackupBtn->setValue(tr("Restoring...")); + } - if (result == 0) { - std::ofstream consistentFile(consistentFilePath); - if (consistentFile) { - frogpilotBackupBtn->setValue(tr("Restored!")); - std::this_thread::sleep_for(std::chrono::seconds(2)); - frogpilotBackupBtn->setValue(tr("Rebooting...")); - std::this_thread::sleep_for(std::chrono::seconds(2)); - consistentFile.close(); - Hardware::reboot(); + if (std::system(("rsync -av --delete -l --exclude='.overlay_consistent' " + sourcePath + "/ " + targetPath + "/").c_str()) == 0) { + std::ofstream consistentFile(consistentFilePath); + if (consistentFile) { + frogpilotBackupBtn->setValue(tr("Restored!")); + params.putBool("AutomaticUpdates", false); + util::sleep_for(2000); + + frogpilotBackupBtn->setValue(tr("Rebooting...")); + consistentFile.close(); + std::filesystem::remove_all(extractDirectory); + util::sleep_for(2000); + + Hardware::reboot(); + } else { + frogpilotBackupBtn->setValue(tr("Failed...")); + util::sleep_for(2000); + frogpilotBackupBtn->setValue(""); + frogpilotBackupBtn->setEnabled(true); + } } else { frogpilotBackupBtn->setValue(tr("Failed...")); + util::sleep_for(2000); + frogpilotBackupBtn->setValue(""); + frogpilotBackupBtn->setEnabled(true); } - } else { - frogpilotBackupBtn->setValue(tr("Failed...")); - } - std::this_thread::sleep_for(std::chrono::seconds(2)); - frogpilotBackupBtn->setValue(""); - frogpilotBackupBtn->setEnabled(true); - }).detach(); + }).detach(); + } } } }); @@ -460,6 +533,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { FrogPilotButtonsControl *toggleBackupBtn = new FrogPilotButtonsControl(tr("Toggle Backups"), tr("Backup, delete, or restore your toggle backups."), "", toggleBackupOptions); connect(toggleBackupBtn, &FrogPilotButtonsControl::buttonClicked, [=](int id) { QDir backupDir("/data/toggle_backups"); + QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); if (id == 0) { QString nameSelection = InputDialog::getText(tr("Name your backup"), this, "", false, 1); @@ -474,58 +548,69 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { int result = std::system(command.c_str()); toggleBackupBtn->setValue(result == 0 ? tr("Success!") : tr("Failed...")); - std::this_thread::sleep_for(std::chrono::seconds(2)); + util::sleep_for(2000); toggleBackupBtn->setValue(""); toggleBackupBtn->setEnabled(true); }).detach(); } } else if (id == 1) { - QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - QString selection = MultiOptionDialog::getSelection(tr("Select a backup to delete"), backupNames, "", this); if (!selection.isEmpty()) { - if (!ConfirmationDialog::confirm(tr("Are you sure you want to delete this backup?"), tr("Delete"), this)) return; - std::thread([=]() { - toggleBackupBtn->setEnabled(false); - toggleBackupBtn->setValue(tr("Deleting...")); + if (ConfirmationDialog::confirm(tr("Are you sure you want to delete this backup?"), tr("Delete"), this)) { + std::thread([=]() { + toggleBackupBtn->setEnabled(false); + toggleBackupBtn->setValue(tr("Deleting...")); - QDir dirToDelete(backupDir.absoluteFilePath(selection)); + QDir dirToDelete(backupDir.absoluteFilePath(selection)); - toggleBackupBtn->setValue(dirToDelete.removeRecursively() ? tr("Deleted!") : tr("Failed...")); + toggleBackupBtn->setValue(dirToDelete.removeRecursively() ? tr("Deleted!") : tr("Failed...")); - std::this_thread::sleep_for(std::chrono::seconds(2)); - toggleBackupBtn->setValue(""); - toggleBackupBtn->setEnabled(true); - }).detach(); + util::sleep_for(2000); + toggleBackupBtn->setValue(""); + toggleBackupBtn->setEnabled(true); + }).detach(); + } } } else if (id == 2) { - QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - QString selection = MultiOptionDialog::getSelection(tr("Select a restore point"), backupNames, "", this); if (!selection.isEmpty()) { - if (!ConfirmationDialog::confirm(tr("Are you sure you want to restore this toggle backup?"), tr("Restore"), this)) return; - std::thread([=]() { - toggleBackupBtn->setEnabled(false); - toggleBackupBtn->setValue(tr("Restoring...")); + if (ConfirmationDialog::confirm(tr("Are you sure you want to restore this toggle backup?"), tr("Restore"), this)) { + std::thread([=]() { + toggleBackupBtn->setEnabled(false); - std::string sourcePath = backupDir.absolutePath().toStdString() + "/" + selection.toStdString() + "/"; - std::string targetPath = "/data/params/d/"; - std::string command = "rsync -av --delete -l " + sourcePath + " " + targetPath; - int result = std::system(command.c_str()); + std::string targetPath = "/data/params/d/"; + std::string tempBackupPath = "/data/params/d_backup/"; - if (result == 0) { - toggleBackupBtn->setValue(tr("Success!")); - updateFrogPilotToggles(); - } else { - toggleBackupBtn->setValue(tr("Failed...")); - } + std::string backupCommand = "rsync -av --delete -l " + targetPath + " " + tempBackupPath; + int backupResult = std::system(backupCommand.c_str()); - std::this_thread::sleep_for(std::chrono::seconds(2)); - toggleBackupBtn->setValue(""); - toggleBackupBtn->setEnabled(true); - }).detach(); + if (backupResult == 0) { + toggleBackupBtn->setValue(tr("Restoring...")); + + std::string sourcePath = backupDir.absolutePath().toStdString() + "/" + selection.toStdString() + "/"; + std::string restoreCommand = "rsync -av --delete -l " + sourcePath + " " + targetPath; + + int restoreResult = std::system(restoreCommand.c_str()); + + if (restoreResult == 0) { + toggleBackupBtn->setValue(tr("Success!")); + updateFrogPilotToggles(); + std::system(("rm -rf " + tempBackupPath).c_str()); + } else { + toggleBackupBtn->setValue(tr("Failed...")); + std::system(("rsync -av --delete -l " + tempBackupPath + " " + targetPath).c_str()); + } + } else { + toggleBackupBtn->setValue(tr("Failed...")); + } + + util::sleep_for(2000); + toggleBackupBtn->setValue(""); + toggleBackupBtn->setEnabled(true); + }).detach(); + } } } }); @@ -558,9 +643,9 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { } flashPandaBtn->setValue(tr("Flashed!")); - std::this_thread::sleep_for(std::chrono::seconds(2)); + util::sleep_for(2000); flashPandaBtn->setValue(tr("Rebooting...")); - std::this_thread::sleep_for(std::chrono::seconds(2)); + util::sleep_for(2000); Hardware::reboot(); }).detach(); } @@ -579,9 +664,9 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { params.putBool("DoToggleReset", true); resetTogglesBtn->setValue(tr("Reset!")); - std::this_thread::sleep_for(std::chrono::seconds(2)); + util::sleep_for(2000); resetTogglesBtn->setValue(tr("Rebooting...")); - std::this_thread::sleep_for(std::chrono::seconds(2)); + util::sleep_for(2000); Hardware::reboot(); }).detach(); } @@ -767,6 +852,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { FrogPilotVisualsPanel *frogpilotVisuals = new FrogPilotVisualsPanel(this); QObject::connect(frogpilotVisuals, &FrogPilotVisualsPanel::openParentToggle, this, [this]() {parentToggleOpen=true;}); + QObject::connect(frogpilotVisuals, &FrogPilotVisualsPanel::openSubParentToggle, this, [this]() {subParentToggleOpen=true;}); QList> panels = { {tr("Device"), device}, diff --git a/selfdrive/ui/qt/onroad/alerts.cc b/selfdrive/ui/qt/onroad/alerts.cc index 1914f594377733..355c71754b53dc 100644 --- a/selfdrive/ui/qt/onroad/alerts.cc +++ b/selfdrive/ui/qt/onroad/alerts.cc @@ -6,7 +6,7 @@ #include "selfdrive/ui/qt/util.h" void OnroadAlerts::updateState(const UIState &s) { - Alert a = getAlert(*(s.sm), s.scene.started_frame); + Alert a = getAlert(*(s.sm), s.scene.started_frame, s.scene.force_onroad); if (!alert.equal(a)) { alert = a; update(); @@ -15,10 +15,10 @@ void OnroadAlerts::updateState(const UIState &s) { // FrogPilot variables const UIScene &scene = s.scene; - hideAlerts = scene.hide_alerts; - roadNameUI = scene.road_name_ui; - showAOLStatusBar = scene.show_aol_status_bar; - showCEMStatusBar = scene.show_cem_status_bar; + hide_alerts = scene.hide_alerts; + road_name_ui = scene.road_name_ui; + show_aol_status_bar = scene.show_aol_status_bar; + show_cem_status_bar = scene.show_cem_status_bar; } void OnroadAlerts::clear() { @@ -26,7 +26,7 @@ void OnroadAlerts::clear() { update(); } -OnroadAlerts::Alert OnroadAlerts::getAlert(const SubMaster &sm, uint64_t started_frame) { +OnroadAlerts::Alert OnroadAlerts::getAlert(const SubMaster &sm, uint64_t started_frame, bool force_onroad) { const cereal::ControlsState::Reader &cs = sm["controlsState"].getControlsState(); const uint64_t controls_frame = sm.rcv_frame("controlsState"); @@ -36,7 +36,7 @@ OnroadAlerts::Alert OnroadAlerts::getAlert(const SubMaster &sm, uint64_t started cs.getAlertType().cStr(), cs.getAlertSize(), cs.getAlertStatus()}; } - if (!sm.updated("controlsState") && (sm.frame - started_frame) > 5 * UI_FREQ) { + if (!sm.updated("controlsState") && (sm.frame - started_frame) > 5 * UI_FREQ && !force_onroad) { const int CONTROLS_TIMEOUT = 5; const int controls_missing = (nanos_since_boot() - sm.rcv_time("controlsState")) / 1e9; @@ -64,10 +64,12 @@ OnroadAlerts::Alert OnroadAlerts::getAlert(const SubMaster &sm, uint64_t started void OnroadAlerts::paintEvent(QPaintEvent *event) { if (alert.size == cereal::ControlsState::AlertSize::NONE) { + alert_height = 0; return; } - if (hideAlerts && alert.status == cereal::ControlsState::AlertStatus::NORMAL) { + if (hide_alerts && alert.status == cereal::ControlsState::AlertStatus::NORMAL) { + alert_height = 0; return; } @@ -80,7 +82,8 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) { int margin = 40; int radius = 30; - int offset = roadNameUI || showAOLStatusBar || showCEMStatusBar ? 25 : 0; + int offset = road_name_ui || show_aol_status_bar || show_cem_status_bar ? 25 : 0; + alert_height = h - margin + offset; if (alert.size == cereal::ControlsState::AlertSize::FULL) { margin = 0; radius = 0; diff --git a/selfdrive/ui/qt/onroad/alerts.h b/selfdrive/ui/qt/onroad/alerts.h index 65cbb86e0847b9..0ee444355b4a1b 100644 --- a/selfdrive/ui/qt/onroad/alerts.h +++ b/selfdrive/ui/qt/onroad/alerts.h @@ -12,6 +12,9 @@ class OnroadAlerts : public QWidget { void updateState(const UIState &s); void clear(); + // FrogPilot variables + int alert_height; + protected: struct Alert { QString text1; @@ -35,14 +38,14 @@ class OnroadAlerts : public QWidget { }; void paintEvent(QPaintEvent*) override; - OnroadAlerts::Alert getAlert(const SubMaster &sm, uint64_t started_frame); + OnroadAlerts::Alert getAlert(const SubMaster &sm, uint64_t started_frame, bool force_onroad); QColor bg; Alert alert = {}; // FrogPilot variables - bool hideAlerts; - bool roadNameUI; - bool showAOLStatusBar; - bool showCEMStatusBar; + bool hide_alerts; + bool road_name_ui; + bool show_aol_status_bar; + bool show_cem_status_bar; }; diff --git a/selfdrive/ui/qt/onroad/annotated_camera.cc b/selfdrive/ui/qt/onroad/annotated_camera.cc index e610e6891d0323..daa3f202b51e26 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.cc +++ b/selfdrive/ui/qt/onroad/annotated_camera.cc @@ -1,4 +1,3 @@ - #include "selfdrive/ui/qt/onroad/annotated_camera.h" #include @@ -46,7 +45,7 @@ AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* par initializeFrogPilotWidgets(); } -void AnnotatedCameraWidget::updateState(const UIState &s) { +void AnnotatedCameraWidget::updateState(int alert_height, const UIState &s) { const int SET_SPEED_NA = 255; const SubMaster &sm = *(s.sm); @@ -81,7 +80,7 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { has_eu_speed_limit = (nav_alive && speed_limit_sign == cereal::NavInstruction::SpeedLimitSign::VIENNA) && !(speedLimitController && !useViennaSLCSign) || (speedLimitController && useViennaSLCSign); is_metric = s.scene.is_metric; speedUnit = s.scene.is_metric ? tr("km/h") : tr("mph"); - hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE || customSignals != 0 && (turnSignalLeft || turnSignalRight) || bigMapOpen); + hideBottomIcons = (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE || turnSignalAnimation && (turnSignalLeft || turnSignalRight) && signalStyle == "traditional" || bigMapOpen); status = s.status; // update engageability/experimental mode button @@ -99,6 +98,9 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { map_settings_btn->setVisible(!hideBottomIcons && compass && !hideMapIcon); main_layout->setAlignment(map_settings_btn, (rightHandDM && !compass || !rightHandDM && compass ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignBottom); } + + // Update FrogPilot widgets + updateFrogPilotWidgets(alert_height, s.scene); } void AnnotatedCameraWidget::drawHud(QPainter &p) { @@ -313,7 +315,7 @@ void AnnotatedCameraWidget::updateFrameMat() { .translate(-intrinsic_matrix.v[2], -intrinsic_matrix.v[5]); } -void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s, const float v_ego) { +void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { painter.save(); const UIScene &scene = s->scene; @@ -321,24 +323,20 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s, c // lanelines for (int i = 0; i < std::size(scene.lane_line_vertices); ++i) { - if (currentHolidayTheme != 0) { - painter.setBrush(std::get<2>(holidayThemeConfiguration[currentHolidayTheme]).begin()->second); - } else if (customColors != 0) { - painter.setBrush(std::get<2>(themeConfiguration[customColors]).begin()->second); - } else { + if (useStockColors) { painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp(scene.lane_line_probs[i], 0.0, 0.7))); + } else { + painter.setBrush(scene.lane_lines_color); } painter.drawPolygon(scene.lane_line_vertices[i]); } // road edges for (int i = 0; i < std::size(scene.road_edge_vertices); ++i) { - if (currentHolidayTheme != 0) { - painter.setBrush(std::get<2>(holidayThemeConfiguration[currentHolidayTheme]).begin()->second); - } else if (customColors != 0) { - painter.setBrush(std::get<2>(themeConfiguration[customColors]).begin()->second); - } else { + if (useStockColors) { painter.setBrush(QColor::fromRgbF(1.0, 0, 0, std::clamp(1.0 - scene.road_edge_stds[i], 0.0, 1.0))); + } else { + painter.setBrush(scene.road_edges_color); } painter.drawPolygon(scene.road_edge_vertices[i]); } @@ -360,23 +358,23 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s, c for (int i = 0; i < max_len; ++i) { // Some points are out of frame - int track_idx = (scene.track_vertices.length() / 2) - i; // flip idx to start from top + int track_idx = max_len - i - 1; // flip idx to start from bottom right if (scene.track_vertices[track_idx].y() < 0 || scene.track_vertices[track_idx].y() > height()) continue; // Flip so 0 is bottom of frame float lin_grad_point = (height() - scene.track_vertices[track_idx].y()) / height(); // If acceleration is between -0.25 and 0.25, resort to the theme color - if (std::abs(acceleration[i]) < 0.25 && (currentHolidayTheme != 0)) { - const std::map &colorMap = std::get<2>(holidayThemeConfiguration[currentHolidayTheme]); - for (const std::pair &entry : colorMap) { - bg.setColorAt(entry.first, entry.second.color()); - } - } else if (std::abs(acceleration[i]) < 0.25 && (customColors != 0)) { - const std::map &colorMap = std::get<2>(themeConfiguration[customColors]); - for (const std::pair &entry : colorMap) { - bg.setColorAt(entry.first, entry.second.color()); - } + if (std::abs(acceleration[i]) < 0.25 && !useStockColors) { + QColor color = scene.path_color; + + bg.setColorAt(0.0, color); + + color.setAlphaF(0.5); + bg.setColorAt(0.5, color); + + color.setAlphaF(0.1); + bg.setColorAt(1.0, color); } else { // speed up: 120, slow down: 0 float path_hue = fmax(fmin(60 + acceleration[i] * 35, 120), 0); @@ -392,16 +390,6 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s, c i += (i + 2) < max_len ? 1 : 0; } } - } else if (currentHolidayTheme != 0) { - const std::map &colorMap = std::get<2>(holidayThemeConfiguration[currentHolidayTheme]); - for (const std::pair &entry : colorMap) { - bg.setColorAt(entry.first, entry.second.color()); - } - } else if (customColors != 0) { - const std::map &colorMap = std::get<2>(themeConfiguration[customColors]); - for (const std::pair &entry : colorMap) { - bg.setColorAt(entry.first, entry.second.color()); - } } else { bg.setColorAt(0.0, QColor::fromHslF(148 / 360., 0.94, 0.51, 0.4)); @@ -412,14 +400,14 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s, c painter.setBrush(bg); painter.drawPolygon(scene.track_vertices); - if (scene.show_stopping_point && scene.red_light && v_ego > 1 && !(conditionalStatus == 1 || conditionalStatus == 3 || conditionalStatus == 5)) { + if (scene.show_stopping_point && scene.red_light && speed > 1 && !(conditionalStatus == 1 || conditionalStatus == 3 || conditionalStatus == 5)) { QPointF last_point = scene.track_vertices.last(); QPointF adjusted_point = last_point - QPointF(stopSignImg.width() / 2, stopSignImg.height()); painter.drawPixmap(adjusted_point, stopSignImg); if (scene.show_stopping_point_metrics) { - QString text = QString::number(scene.model_length * distanceConversion) + leadDistanceUnit; + QString text = QString::number(modelLength * distanceConversion) + leadDistanceUnit; QFont font = InterFont(35, QFont::DemiBold); QFontMetrics fm(font); int text_width = fm.horizontalAdvance(text); @@ -506,16 +494,8 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s, c setGradientColors(bg_colors[STATUS_TRAFFIC_MODE_ACTIVE]); } else if (scene.navigate_on_openpilot) { setGradientColors(bg_colors[STATUS_NAVIGATION_ACTIVE]); - } else if (currentHolidayTheme != 0) { - const std::map &colorMap = std::get<2>(holidayThemeConfiguration[currentHolidayTheme]); - for (const std::pair &entry : colorMap) { - pe.setColorAt(entry.first, entry.second.color().darker(120)); - } - } else if (customColors != 0) { - const std::map &colorMap = std::get<2>(themeConfiguration[customColors]); - for (const std::pair &entry : colorMap) { - pe.setColorAt(entry.first, entry.second.color().darker(120)); - } + } else if (!useStockColors) { + setGradientColors(scene.path_edges_color); } else { pe.setColorAt(0.0, QColor::fromHslF(148 / 360., 0.94, 0.51, 1.0)); pe.setColorAt(0.5, QColor::fromHslF(112 / 360., 1.00, 0.68, 0.5)); @@ -545,7 +525,7 @@ void AnnotatedCameraWidget::drawDriverState(QPainter &painter, const UIState *s) } else if (onroadDistanceButton) { x += 250; } - offset += showAlwaysOnLateralStatusBar || showConditionalExperimentalStatusBar || roadNameUI ? 25 : 0; + offset += statusBarHeight / 2; int y = height() - offset; float opacity = dmActive ? 0.65 : 0.2; drawIcon(painter, QPoint(x, y), dm_img, blackColor(70), opacity); @@ -579,11 +559,11 @@ void AnnotatedCameraWidget::drawDriverState(QPainter &painter, const UIState *s) painter.restore(); } -void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd, const float v_ego) { +void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd, const float v_ego, const QColor lead_marker_color) { painter.save(); - const float speedBuff = currentHolidayTheme != 0 || customColors != 0 ? 25. : 10.; // Make the center of the chevron appear sooner if a theme is active - const float leadBuff = currentHolidayTheme != 0 || customColors != 0 ? 100. : 40.; // Make the center of the chevron appear sooner if a theme is active + const float speedBuff = useStockColors ? 10. : 25.; // Make the center of the chevron appear sooner if a theme is active + const float leadBuff = useStockColors ? 40. : 100.; // Make the center of the chevron appear sooner if a theme is active const float d_rel = lead_data.getX()[0]; const float v_rel = lead_data.getV()[0] - v_ego; @@ -609,12 +589,10 @@ void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::ModelDataV // chevron QPointF chevron[] = {{x + (sz * 1.25), y + sz}, {x, y}, {x - (sz * 1.25), y + sz}}; - if (currentHolidayTheme != 0) { - painter.setBrush(std::get<2>(holidayThemeConfiguration[currentHolidayTheme]).begin()->second); - } else if (customColors != 0) { - painter.setBrush(std::get<2>(themeConfiguration[customColors]).begin()->second); - } else { + if (useStockColors) { painter.setBrush(redColor(fillAlpha)); + } else { + painter.setBrush(lead_marker_color); } painter.drawPolygon(chevron, std::size(chevron)); @@ -706,7 +684,7 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { if (s->scene.world_objects_visible) { update_model(s, model, sm["uiPlan"].getUiPlan()); - drawLaneLines(painter, s, v_ego); + drawLaneLines(painter, s); if (s->scene.longitudinal_control && sm.rcv_frame("modelV2") > s->scene.started_frame && !s->scene.hide_lead_marker) { update_leads(s, model); @@ -715,7 +693,7 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { const auto &lead = model.getLeadsV3()[i]; auto lead_drel = lead.getX()[0]; if (s->scene.has_lead && (prev_drel < 0 || std::abs(lead_drel - prev_drel) > 3.0)) { - drawLead(painter, lead, s->scene.lead_vertices[i], (speed / (is_metric ? MS_TO_KPH : MS_TO_MPH))); + drawLead(painter, lead, s->scene.lead_vertices[i], (speed / (is_metric ? MS_TO_KPH : MS_TO_MPH)), s->scene.lead_marker_color); } prev_drel = lead_drel; } @@ -746,7 +724,7 @@ void AnnotatedCameraWidget::paintEvent(QPaintEvent *event) { pm->send("uiDebug", msg); // Paint FrogPilot widgets - paintFrogPilotWidgets(painter, s->scene); + paintFrogPilotWidgets(painter); } void AnnotatedCameraWidget::showEvent(QShowEvent *event) { @@ -754,6 +732,11 @@ void AnnotatedCameraWidget::showEvent(QShowEvent *event) { ui_update_params(uiState()); prev_draw_t = millis_since_boot(); + + // Update FrogPilot images + distance_btn->updateIcon(); + experimental_btn->updateIcon(); + updateSignals(); } // FrogPilot widgets @@ -761,75 +744,26 @@ void AnnotatedCameraWidget::initializeFrogPilotWidgets() { bottom_layout = new QHBoxLayout(); distance_btn = new DistanceButton(this); - bottom_layout->addWidget(distance_btn, 0, Qt::AlignBottom | Qt::AlignLeft); + bottom_layout->addWidget(distance_btn); QSpacerItem *spacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum); bottom_layout->addItem(spacer); compass_img = new Compass(this); - bottom_layout->addWidget(compass_img, 0, Qt::AlignBottom | Qt::AlignRight); + bottom_layout->addWidget(compass_img); map_settings_btn_bottom = new MapSettingsButton(this); - bottom_layout->addWidget(map_settings_btn_bottom, 0, Qt::AlignBottom | Qt::AlignRight); + bottom_layout->addWidget(map_settings_btn_bottom); main_layout->addLayout(bottom_layout); stopSignImg = loadPixmap("../frogpilot/assets/other_images/stop_sign.png", QSize(img_size, img_size)); - themeConfiguration = { - {1, {"frog_theme", QColor(23, 134, 68, 242), {{0.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.9))}, - {0.5, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.5))}, - {1.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.1))}}}}, - {2, {"tesla_theme", QColor(0, 72, 255, 255), {{0.0, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.9))}, - {0.5, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.5))}, - {1.0, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.1))}}}}, - {3, {"stalin_theme", QColor(255, 0, 0, 255), {{0.0, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.9))}, - {0.5, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.5))}, - {1.0, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.1))}}}}, - }; - - holidayThemeConfiguration = { - {1, {"april_fools", QColor(255, 165, 0, 255), {{0.0, QBrush(QColor::fromHslF(39 / 360., 1.0, 0.5, 0.9))}, - {0.5, QBrush(QColor::fromHslF(39 / 360., 1.0, 0.5, 0.5))}, - {1.0, QBrush(QColor::fromHslF(39 / 360., 1.0, 0.5, 0.1))}}}}, - {2, {"christmas", QColor(0, 72, 255, 255), {{0.0, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.9))}, - {0.5, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.5))}, - {1.0, QBrush(QColor::fromHslF(223 / 360., 1.0, 0.5, 0.1))}}}}, - {3, {"cinco_de_mayo", QColor(0, 104, 71, 255), {{0.0, QBrush(QColor::fromHslF(161 / 360., 1.0, 0.2, 0.9))}, - {0.5, QBrush(QColor::fromHslF(161 / 360., 1.0, 0.2, 0.5))}, - {1.0, QBrush(QColor::fromHslF(161 / 360., 1.0, 0.2, 0.1))}}}}, - {4, {"easter", QColor(200, 150, 200, 255), {{0.0, QBrush(QColor::fromHslF(300 / 360., 0.31, 0.69, 0.9))}, - {0.5, QBrush(QColor::fromHslF(300 / 360., 0.31, 0.69, 0.5))}, - {1.0, QBrush(QColor::fromHslF(300 / 360., 0.31, 0.69, 0.1))}}}}, - {5, {"fourth_of_july", QColor(10, 49, 97, 255), {{0.0, QBrush(QColor::fromHslF(213 / 360., 0.81, 0.21, 0.9))}, - {0.5, QBrush(QColor::fromHslF(213 / 360., 0.81, 0.21, 0.5))}, - {1.0, QBrush(QColor::fromHslF(213 / 360., 0.81, 0.21, 0.1))}}}}, - {6, {"halloween", QColor(255, 0, 0, 255), {{0.0, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.9))}, - {0.5, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.5))}, - {1.0, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.1))}}}}, - {7, {"new_years_day", QColor(23, 134, 68, 242), {{0.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.9))}, - {0.5, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.5))}, - {1.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.1))}}}}, - {8, {"st_patricks_day", QColor(0, 128, 0, 255), {{0.0, QBrush(QColor::fromHslF(120 / 360., 1.0, 0.25, 0.9))}, - {0.5, QBrush(QColor::fromHslF(120 / 360., 1.0, 0.25, 0.5))}, - {1.0, QBrush(QColor::fromHslF(120 / 360., 1.0, 0.25, 0.1))}}}}, - {9, {"thanksgiving", QColor(255, 0, 0, 255), {{0.0, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.9))}, - {0.5, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.5))}, - {1.0, QBrush(QColor::fromHslF(0 / 360., 1.0, 0.5, 0.1))}}}}, - {10, {"valentines_day", QColor(23, 134, 68, 242), {{0.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.9))}, - {0.5, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.5))}, - {1.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.1))}}}}, - {11, {"world_frog_day", QColor(23, 134, 68, 242), {{0.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.9))}, - {0.5, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.5))}, - {1.0, QBrush(QColor::fromHslF(144 / 360., 0.71, 0.31, 0.1))}}}}, - }; - animationTimer = new QTimer(this); - connect(animationTimer, &QTimer::timeout, this, [this] { + QObject::connect(animationTimer, &QTimer::timeout, this, [this] { animationFrameIndex = (animationFrameIndex + 1) % totalFrames; }); - // Initialize the timer for the screen recorder QTimer *recordTimer = new QTimer(this); QObject::connect(recordTimer, &QTimer::timeout, this, [this] { recorder->updateScreen(); @@ -837,7 +771,7 @@ void AnnotatedCameraWidget::initializeFrogPilotWidgets() { recordTimer->start(75); } -void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &painter, const UIScene &scene) { +void AnnotatedCameraWidget::updateFrogPilotWidgets(int alert_height, const UIScene &scene) { if (is_metric || useSI) { accelerationUnit = tr("m/s²"); leadDistanceUnit = tr(mapOpen ? "m" : "meters"); @@ -856,13 +790,10 @@ void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &painter, const UISce speedConversion = MS_TO_MPH; } - alertSize = scene.alert_size; + alertHeight = alert_height; alwaysOnLateralActive = scene.always_on_lateral_active; showAlwaysOnLateralStatusBar = scene.show_aol_status_bar; - if ((showAlwaysOnLateralStatusBar || showConditionalExperimentalStatusBar || roadNameUI) && !bigMapOpen) { - drawStatusBar(painter); - } blindSpotLeft = scene.blind_spot_left; blindSpotRight = scene.blind_spot_right; @@ -888,10 +819,6 @@ void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &painter, const UISce currentAcceleration = scene.acceleration; - currentRandomEvent = scene.current_random_event; - - customColors = scene.custom_colors; - desiredFollow = scene.desired_follow; stoppedEquivalence = scene.stopped_equivalence; @@ -908,9 +835,6 @@ void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &painter, const UISce leadInfo = scene.lead_info; obstacleDistance = scene.obstacle_distance; obstacleDistanceStock = scene.obstacle_distance_stock; - if (leadInfo && !bigMapOpen) { - drawLeadInfo(painter); - } mapOpen = scene.map_open; bigMapOpen = mapOpen && scene.big_map; @@ -920,6 +844,8 @@ void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &painter, const UISce bottom_layout->setAlignment(map_settings_btn_bottom, (rightHandDM ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignBottom); } + modelLength = scene.model_length; + onroadDistanceButton = scene.onroad_distance_button; bool enableDistanceButton = onroadDistanceButton && !hideBottomIcons; distance_btn->setVisible(enableDistanceButton); @@ -947,9 +873,6 @@ void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &painter, const UISce speedLimitChanged = speedLimitController && scene.speed_limit_changed; unconfirmedSpeedLimit = speedLimitController ? scene.unconfirmed_speed_limit : 0; useViennaSLCSign = scene.use_vienna_slc_sign; - if (speedLimitChanged) { - drawSLCConfirmation(painter); - } bool stoppedTimer = scene.stopped_timer && scene.standstill && scene.started_timer / UI_FREQ >= 10; if (stoppedTimer) { @@ -966,54 +889,96 @@ void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &painter, const UISce turnSignalLeft = scene.turn_signal_left; turnSignalRight = scene.turn_signal_right; - if (customSignals != 0 && (turnSignalLeft || turnSignalRight) && !bigMapOpen) { - if (!animationTimer->isActive()) { - animationTimer->start(totalFrames * 11); // 440 milliseconds per loop; syncs up perfectly with my 2019 Lexus ES 350 turn signal clicks - } - drawTurnSignals(painter); - } else if (animationTimer->isActive()) { - animationTimer->stop(); - } useSI = scene.use_si; - if (currentHolidayTheme != scene.current_holiday_theme || customSignals != scene.custom_signals) { - currentHolidayTheme = scene.current_holiday_theme; - customSignals = scene.custom_signals; + useStockColors = scene.use_stock_colors; +} + +void AnnotatedCameraWidget::updateSignals() { + blindspotImages.clear(); + regularImages.clear(); + + const QString signalFolderPath = "../frogpilot/assets/active_theme/signals/"; + QDir directory(signalFolderPath); - QString themePath; + QFileInfoList fileList = directory.entryInfoList({"turn_signal_*.png"}, QDir::Files); - if (currentHolidayTheme != 0) { - themePath = QString("../frogpilot/assets/holiday_themes/%1/images").arg( - holidayThemeConfiguration.find(currentHolidayTheme) != holidayThemeConfiguration.end() ? - std::get<0>(holidayThemeConfiguration[currentHolidayTheme]) : ""); + const QTransform flipTransform = QTransform().scale(-1, 1); + std::vector flippedImages; + + for (const QFileInfo &fileInfo : fileList) { + QPixmap pixmap(fileInfo.absoluteFilePath()); + + if (fileInfo.fileName().contains("blindspot")) { + blindspotImages.push_back(pixmap); + blindspotImages.push_back(pixmap.transformed(flipTransform)); } else { - themePath = QString("../frogpilot/assets/custom_themes/%1/images").arg( - themeConfiguration.find(customSignals) != themeConfiguration.end() ? - std::get<0>(themeConfiguration[customSignals]) : ""); + regularImages.push_back(pixmap); + flippedImages.push_back(pixmap.transformed(flipTransform)); } + } - const QStringList imagePaths = { - themePath + "/turn_signal_1.png", - themePath + "/turn_signal_2.png", - themePath + "/turn_signal_3.png", - themePath + "/turn_signal_4.png" - }; + regularImages.insert(regularImages.end(), flippedImages.begin(), flippedImages.end()); - signalImgVector.clear(); - signalImgVector.reserve(2 * imagePaths.size() + 2); + QFileInfoList nonPngFileList = directory.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name); - for (const QString &imagePath : imagePaths) { - QPixmap pixmap(imagePath); - signalImgVector.push_back(pixmap); - signalImgVector.push_back(pixmap.transformed(QTransform().scale(-1, 1))); + for (QFileInfoList::iterator it = nonPngFileList.begin(); it != nonPngFileList.end(); ) { + if ((*it).suffix() == "png") { + it = nonPngFileList.erase(it); + } else { + it++; } + } + + if (!nonPngFileList.isEmpty()) { + const QFileInfo &fileInfo = nonPngFileList.first(); + const QStringList parts = fileInfo.fileName().split('_'); + + if (parts.size() == 2) { + signalStyle = parts[0]; + signalAnimationLength = parts[1].toInt(); + } + } + + if (!regularImages.empty()) { + const QPixmap &firstImage = regularImages.front(); + signalWidth = firstImage.width(); + signalHeight = firstImage.height(); - const QPixmap blindSpotPixmap(themePath + "/turn_signal_1_red.png"); - signalImgVector.push_back(blindSpotPixmap); - signalImgVector.push_back(blindSpotPixmap.transformed(QTransform().scale(-1, 1))); + totalFrames = regularImages.size() / 2; + turnSignalAnimation = true; + } else { + signalWidth = 0; + signalHeight = 0; - totalFrames = 8; + totalFrames = 0; + turnSignalAnimation = false; + } +} + +void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &painter) { + if ((showAlwaysOnLateralStatusBar || showConditionalExperimentalStatusBar || roadNameUI) && !bigMapOpen) { + drawStatusBar(painter); + } else { + statusBarHeight = 0; + } + + if (leadInfo && !bigMapOpen) { + drawLeadInfo(painter); + } + + if (speedLimitChanged) { + drawSLCConfirmation(painter); + } + + if (turnSignalAnimation && (turnSignalLeft || turnSignalRight) && !bigMapOpen && ((!mapOpen && standstillDuration == 0) || signalStyle != "static")) { + if (!animationTimer->isActive()) { + animationTimer->start(signalAnimationLength); + } + drawTurnSignals(painter); + } else if (animationTimer->isActive()) { + animationTimer->stop(); } } @@ -1129,11 +1094,8 @@ void AnnotatedCameraWidget::drawLeadInfo(QPainter &p) { isFiveSecondsPassed = false; }; - if ((acceleration > maxAcceleration && (status == STATUS_ENGAGED || status == STATUS_TRAFFIC_MODE_ACTIVE)) || - (currentRandomEvent == 2 && maxAcceleration < 3.0) || - (currentRandomEvent == 3 && maxAcceleration < 3.5) || - (currentRandomEvent == 4 && maxAcceleration < 4.0)) { - maxAcceleration = std::max({acceleration, currentRandomEvent == 2 ? 3.0 : maxAcceleration, currentRandomEvent == 3 ? 3.5 : maxAcceleration, currentRandomEvent == 4 ? 4.0 : maxAcceleration}); + if (acceleration > maxAcceleration && (status == STATUS_ENGAGED || status == STATUS_TRAFFIC_MODE_ACTIVE)) { + maxAcceleration = acceleration; resetTimer(); } else { isFiveSecondsPassed = timer.hasExpired(maxAccelDuration); @@ -1289,11 +1251,15 @@ void AnnotatedCameraWidget::drawStatusBar(QPainter &p) { QString newStatus; - QRect statusBarRect(rect().left() - 1, rect().bottom() - 50, rect().width() + 2, 100); + int offset = 50; + QRect statusBarRect(rect().left() - 1, rect().bottom() - offset, rect().width() + 2, 100); + statusBarHeight = statusBarRect.height() - offset; p.setBrush(QColor(0, 0, 0, 150)); p.setOpacity(1.0); p.drawRoundedRect(statusBarRect, 30, 30); + int modelStopTime = std::nearbyint(modelLength / (speed / (is_metric ? MS_TO_KPH : MS_TO_MPH))); + std::map conditionalStatusMap = { {0, tr("Conditional Experimental Mode ready")}, {1, tr("Conditional Experimental overridden")}, @@ -1302,16 +1268,16 @@ void AnnotatedCameraWidget::drawStatusBar(QPainter &p) { {4, tr("Experimental Mode manually activated")}, {5, tr("Conditional Experimental overridden")}, {6, tr("Experimental Mode manually activated")}, - {7, tr("Experimental Mode activated for") + (mapOpen ? tr(" low speed") : tr(" speed being less than ") + QString::number(conditionalSpeedLead) + (is_metric ? tr("kph") : tr("mph")))}, - {8, tr("Experimental Mode activated for") + (mapOpen ? tr(" low speed") : tr(" speed being less than ") + QString::number(conditionalSpeed) + (is_metric ? tr("kph") : tr("mph")))}, - {9, tr("Experimental Mode activated for turn") + (mapOpen ? "" : tr(" / lane change"))}, + {7, tr("Experimental Mode activated for %1").arg(mapOpen ? tr("low speed") : tr("speed being less than %1 %2").arg(conditionalSpeedLead).arg(is_metric ? tr("kph") : tr("mph")))}, + {8, tr("Experimental Mode activated for %1").arg(mapOpen ? tr("low speed") : tr("speed being less than %1 %2").arg(conditionalSpeed).arg(is_metric ? tr("kph") : tr("mph")))}, + {9, tr("Experimental Mode activated for turn") + (mapOpen ? " signal" : tr(" / lane change"))}, {10, tr("Experimental Mode activated for intersection")}, {11, tr("Experimental Mode activated for upcoming turn")}, {12, tr("Experimental Mode activated for curve")}, {13, tr("Experimental Mode activated for stopped lead")}, {14, tr("Experimental Mode activated for slower lead")}, - {15, tr("Experimental Mode activated for stop light") + (mapOpen ? tr("") : tr(" or stop sign"))}, - {16, tr("Experimental Mode forced on for stop light") + (mapOpen ? tr("") : tr(" or stop sign"))}, + {15, tr("Experimental Mode activated %1").arg(mapOpen || modelStopTime < 1 || speed < 1 ? tr("to stop") : QString("for the model wanting to stop in %1 seconds").arg(modelStopTime))}, + {16, tr("Experimental Mode forced on %1").arg(mapOpen || modelStopTime < 1 || speed < 1 ? tr("to stop") : QString("for the model wanting to stop in %1 seconds").arg(modelStopTime))}, {17, tr("Experimental Mode activated due to no speed limit")}, }; @@ -1361,13 +1327,13 @@ void AnnotatedCameraWidget::drawStatusBar(QPainter &p) { p.setRenderHint(QPainter::TextAntialiasing); QRect textRect = p.fontMetrics().boundingRect(statusBarRect, Qt::AlignCenter | Qt::TextWordWrap, newStatus); - textRect.moveBottom(statusBarRect.bottom() - 50); + textRect.moveBottom(statusBarRect.bottom() - offset); p.drawText(textRect, Qt::AlignCenter | Qt::TextWordWrap, newStatus); if (!roadName.isEmpty()) { p.setOpacity(roadNameOpacity); textRect = p.fontMetrics().boundingRect(statusBarRect, Qt::AlignCenter | Qt::TextWordWrap, roadName); - textRect.moveBottom(statusBarRect.bottom() - 50); + textRect.moveBottom(statusBarRect.bottom() - offset); p.drawText(textRect, Qt::AlignCenter | Qt::TextWordWrap, roadName); } @@ -1375,25 +1341,29 @@ void AnnotatedCameraWidget::drawStatusBar(QPainter &p) { } void AnnotatedCameraWidget::drawTurnSignals(QPainter &p) { - constexpr int signalHeight = 480; - constexpr int signalWidth = 360; - p.setRenderHint(QPainter::Antialiasing); - int baseYPosition = (height() - signalHeight) / 2 + (showAlwaysOnLateralStatusBar || showConditionalExperimentalStatusBar || roadNameUI ? 225 : 300) - alertSize; - int leftSignalXPosition = 75 + width() - signalWidth - 300 * (blindSpotLeft ? 0 : animationFrameIndex); - int rightSignalXPosition = -75 + 300 * (blindSpotRight ? 0 : animationFrameIndex); + bool blindspotActive = turnSignalLeft ? blindSpotLeft : blindSpotRight; - if (animationFrameIndex < signalImgVector.size()) { - auto drawSignal = [&](bool signalActivated, int xPosition, bool flip, bool blindspot) { - if (signalActivated) { - int index = (blindspot ? totalFrames : 2 * animationFrameIndex % totalFrames) + (flip ? 1 : 0); - QPixmap &signal = signalImgVector[index]; - p.drawPixmap(xPosition, baseYPosition, signalWidth, signalHeight, signal); - } - }; + if (signalStyle == "static") { + int signalXPosition = turnSignalLeft ? (rect().center().x() * 0.75) - signalWidth : rect().center().x() * 1.25; + int signalYPosition = signalHeight / 2; + + if (blindspotActive && !blindspotImages.empty()) { + p.drawPixmap(signalXPosition, signalYPosition, signalWidth, signalHeight, blindspotImages[turnSignalLeft ? 0 : 1]); + } else { + p.drawPixmap(signalXPosition, signalYPosition, signalWidth, signalHeight, regularImages[animationFrameIndex + (turnSignalLeft ? 0 : totalFrames)]); + } + } else if (signalStyle == "traditional") { + int signalXPosition = turnSignalLeft ? width() - ((animationFrameIndex + 1) * signalWidth) : animationFrameIndex * signalWidth; + int signalYPosition = height() - signalHeight; + + signalYPosition -= fmax(alertHeight, statusBarHeight); - drawSignal(turnSignalLeft, leftSignalXPosition, false, blindSpotLeft); - drawSignal(turnSignalRight, rightSignalXPosition, true, blindSpotRight); + if (blindspotActive && !blindspotImages.empty()) { + p.drawPixmap(turnSignalLeft ? width() - signalWidth : 0, signalYPosition, signalWidth, signalHeight, blindspotImages[turnSignalLeft ? 0 : 1]); + } else { + p.drawPixmap(signalXPosition, signalYPosition, signalWidth, signalHeight, regularImages[animationFrameIndex + (turnSignalLeft ? 0 : totalFrames)]); + } } } diff --git a/selfdrive/ui/qt/onroad/annotated_camera.h b/selfdrive/ui/qt/onroad/annotated_camera.h index 2ab4a9717e40b9..1f86892dd03071 100644 --- a/selfdrive/ui/qt/onroad/annotated_camera.h +++ b/selfdrive/ui/qt/onroad/annotated_camera.h @@ -59,7 +59,7 @@ class AnnotatedCameraWidget : public CameraWidget { public: explicit AnnotatedCameraWidget(VisionStreamType type, QWidget* parent = 0); - void updateState(const UIState &s); + void updateState(int alert_height, const UIState &s); MapSettingsButton *map_settings_btn; MapSettingsButton *map_settings_btn_bottom; @@ -91,7 +91,9 @@ class AnnotatedCameraWidget : public CameraWidget { // FrogPilot widgets void initializeFrogPilotWidgets(); - void paintFrogPilotWidgets(QPainter &painter, const UIScene &scene); + void paintFrogPilotWidgets(QPainter &painter); + void updateFrogPilotWidgets(int alert_height, const UIScene &scene); + void updateSignals(); void drawLeadInfo(QPainter &p); void drawSLCConfirmation(QPainter &p); @@ -129,8 +131,10 @@ class AnnotatedCameraWidget : public CameraWidget { bool speedLimitChanged; bool speedLimitController; bool trafficModeActive; + bool turnSignalAnimation; bool turnSignalLeft; bool turnSignalRight; + bool useStockColors; bool useSI; bool useViennaSLCSign; bool vtscControllingCurve; @@ -147,38 +151,38 @@ class AnnotatedCameraWidget : public CameraWidget { float speedConversion; float unconfirmedSpeedLimit; - int alertSize; + int alertHeight; + int animationFrameIndex; int cameraView; int conditionalSpeed; int conditionalSpeedLead; int conditionalStatus; - int currentHolidayTheme; - int currentRandomEvent; - int customColors; - int customSignals; int desiredFollow; + int modelLength; int obstacleDistance; int obstacleDistanceStock; + int signalAnimationLength; + int signalHeight; + int signalWidth; int standstillDuration; + int statusBarHeight; int stoppedEquivalence; int totalFrames; + QElapsedTimer standstillTimer; + QPixmap stopSignImg; QString accelerationUnit; QString leadDistanceUnit; QString leadSpeedUnit; + QString signalStyle; - size_t animationFrameIndex; - - std::unordered_map>> themeConfiguration; - std::unordered_map>> holidayThemeConfiguration; - - std::vector signalImgVector; - - QElapsedTimer standstillTimer; QTimer *animationTimer; + std::vector regularImages; + std::vector blindspotImages; + inline QColor blueColor(int alpha = 255) { return QColor(0, 150, 255, alpha); } inline QColor greenColor(int alpha = 242) { return QColor(23, 134, 68, alpha); } @@ -187,8 +191,8 @@ class AnnotatedCameraWidget : public CameraWidget { void initializeGL() override; void showEvent(QShowEvent *event) override; void updateFrameMat() override; - void drawLaneLines(QPainter &painter, const UIState *s, const float v_ego); - void drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd, const float v_ego); + void drawLaneLines(QPainter &painter, const UIState *s); + void drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd, const float v_ego, const QColor lead_marker_color); void drawHud(QPainter &p); void drawDriverState(QPainter &painter, const UIState *s); void paintEvent(QPaintEvent *event) override; diff --git a/selfdrive/ui/qt/onroad/buttons.cc b/selfdrive/ui/qt/onroad/buttons.cc index c318a004d44cd8..b0f4c77e906017 100644 --- a/selfdrive/ui/qt/onroad/buttons.cc +++ b/selfdrive/ui/qt/onroad/buttons.cc @@ -19,18 +19,6 @@ void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrus p.restore(); } -void drawIconGif(QPainter &p, const QPoint ¢er, const QMovie &img, const QBrush &bg, float opacity) { - p.setRenderHint(QPainter::Antialiasing); - p.setOpacity(1.0); // bg dictates opacity of ellipse - p.setPen(Qt::NoPen); - p.setBrush(bg); - p.drawEllipse(center.x() - btn_size / 2, center.y() - btn_size / 2, btn_size, btn_size); - p.setOpacity(opacity); - QPixmap currentFrame = img.currentPixmap(); - p.drawPixmap(center - QPoint(currentFrame.width() / 2, currentFrame.height() / 2), currentFrame); - p.setOpacity(1.0); -} - // ExperimentalButton ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(false), engageable(false), QPushButton(parent) { setFixedSize(btn_size, btn_size + 10); @@ -39,40 +27,39 @@ ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(fals experimental_img = loadPixmap("../assets/img_experimental.svg", {img_size, img_size}); QObject::connect(this, &QPushButton::clicked, this, &ExperimentalButton::changeMode); - wheelImages = { - {0, loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size})}, - {1, loadPixmap("../frogpilot/assets/wheel_images/lexus.png", {img_size, img_size})}, - {2, loadPixmap("../frogpilot/assets/wheel_images/toyota.png", {img_size, img_size})}, - {3, loadPixmap("../frogpilot/assets/wheel_images/frog.png", {img_size, img_size})}, - {4, loadPixmap("../frogpilot/assets/wheel_images/rocket.png", {img_size, img_size})}, - {5, loadPixmap("../frogpilot/assets/wheel_images/hyundai.png", {img_size, img_size})}, - {6, loadPixmap("../frogpilot/assets/wheel_images/stalin.png", {img_size, img_size})}, - }; + // FrogPilot variables + wheel_gif_path = "../frogpilot/assets/active_theme/steering_wheel/wheel.gif"; + wheel_png_path = "../frogpilot/assets/active_theme/steering_wheel/wheel.png"; - wheelImagesGif = { - {0, new QMovie("../frogpilot/assets/random_events/images/firefox.gif", QByteArray(), this)}, - {1, new QMovie("../frogpilot/assets/random_events/images/weeb_wheel.gif", QByteArray(), this)}, - {2, new QMovie("../frogpilot/assets/random_events/images/tree_fiddy.gif", QByteArray(), this)}, - {3, new QMovie("../frogpilot/assets/random_events/images/great_scott.gif", QByteArray(), this)}, - }; + gif_label = new QLabel(this); + gif_label->setScaledContents(true); +} + +ExperimentalButton::~ExperimentalButton() { + if (gif != nullptr) { + gif->stop(); + delete gif; + gif = nullptr; + gif_label->hide(); + } } void ExperimentalButton::changeMode() { const auto cp = (*uiState()->sm)["carParams"].getCarParams(); bool can_change = hasLongitudinalControl(cp) && params.getBool("ExperimentalModeConfirmed"); if (can_change) { - if (conditionalExperimental) { - int override_value = (conditionalStatus >= 1 && conditionalStatus <= 6) ? 0 : conditionalStatus >= 7 ? 5 : 6; - paramsMemory.putIntNonBlocking("CEStatus", override_value); + if (conditional_experimental) { + int override_value = (conditional_status >= 1 && conditional_status <= 6) ? 0 : conditional_status >= 7 ? 5 : 6; + params_memory.putInt("CEStatus", override_value); } else { params.putBool("ExperimentalMode", !experimental_mode); } } } -void ExperimentalButton::updateState(const UIState &s, bool leadInfo) { +void ExperimentalButton::updateState(const UIState &s, bool lead_info) { const auto cs = (*s.sm)["controlsState"].getControlsState(); - bool eng = cs.getEngageable() || cs.getEnabled() || s.scene.always_on_lateral_active; + bool eng = cs.getEngageable() || cs.getEnabled() || always_on_lateral_active; if ((cs.getExperimentalMode() != experimental_mode) || (eng != engageable)) { engageable = eng; experimental_mode = cs.getExperimentalMode(); @@ -82,68 +69,100 @@ void ExperimentalButton::updateState(const UIState &s, bool leadInfo) { // FrogPilot variables const UIScene &scene = s.scene; - alwaysOnLateralActive = scene.always_on_lateral_active; - bigMap = scene.big_map; - conditionalExperimental = scene.conditional_experimental; - conditionalStatus = scene.conditional_status; - mapOpen = scene.map_open; - navigateOnOpenpilot = scene.navigate_on_openpilot; - randomEvent = scene.current_random_event; - rotatingWheel = scene.rotating_wheel; - trafficModeActive = scene.traffic_mode_active; - wheelIcon = scene.wheel_icon; - wheelIconGif = 0; - y_offset = leadInfo ? 10 : 0; - - if (randomEvent == 0 && gifLabel) { - delete gifLabel; - gifLabel = nullptr; - } else if (randomEvent == 1 || randomEvent == 2 || randomEvent == 3 || randomEvent == 4) { - gifLabel = new QLabel(this); - - QMovie *movie = wheelImagesGif[randomEvent - 1]; - if (movie) { - movie->setScaledSize(QSize(img_size, img_size)); - gifLabel->setMovie(movie); - gifLabel->move((width() - gifLabel->width()) / 2, (height() - gifLabel->height()) / 2 + y_offset); - gifLabel->movie()->start(); - } - - wheelIconGif = randomEvent - 1; - update(); - } else if (rotatingWheel && steeringAngleDeg != scene.steering_angle_deg) { - steeringAngleDeg = scene.steering_angle_deg; + always_on_lateral_active = scene.always_on_lateral_active; + big_map = scene.big_map; + conditional_experimental = scene.conditional_experimental; + conditional_status = scene.conditional_status; + map_open = scene.map_open; + navigate_on_openpilot = scene.navigate_on_openpilot; + rotating_wheel = scene.rotating_wheel; + traffic_mode_active = scene.traffic_mode_active; + use_stock_wheel = scene.use_stock_wheel; + y_offset = lead_info ? 10 : 0; + + if (rotating_wheel && steering_angle_deg != scene.steering_angle_deg) { + steering_angle_deg = scene.steering_angle_deg; update(); - } else if (!rotatingWheel) { - steeringAngleDeg = 0; + } else if (!rotating_wheel) { + steering_angle_deg = 0; + } + + if (params_memory.getBool("UpdateWheelImage")) { + updateIcon(); + params_memory.remove("UpdateWheelImage"); + } +} + +void ExperimentalButton::updateBackgroundColor() { + static const QMap status_color_map { + {"default", QColor(0, 0, 0, 166)}, + {"always_on_lateral_active", bg_colors[STATUS_ALWAYS_ON_LATERAL_ACTIVE]}, + {"conditional_overridden", bg_colors[STATUS_CONDITIONAL_OVERRIDDEN]}, + {"experimental_mode_active", bg_colors[STATUS_EXPERIMENTAL_MODE_ACTIVE]}, + {"navigation_active", bg_colors[STATUS_NAVIGATION_ACTIVE]}, + {"traffic_mode_active", bg_colors[STATUS_TRAFFIC_MODE_ACTIVE]} + }; + + if (isDown() || !engageable || use_stock_wheel) { + background_color = status_color_map["default"]; + return; + } + + if (always_on_lateral_active) { + background_color = status_color_map["always_on_lateral_active"]; + } else if (conditional_status == 1 || conditional_status == 3 || conditional_status == 5) { + background_color = status_color_map["conditional_overridden"]; + } else if (experimental_mode) { + background_color = status_color_map["experimental_mode_active"]; + } else if (navigate_on_openpilot) { + background_color = status_color_map["navigation_active"]; + } else if (traffic_mode_active) { + background_color = status_color_map["traffic_mode_active"]; + } else { + background_color = status_color_map["default"]; + } +} + +void ExperimentalButton::updateIcon() { + if (gif != nullptr) { + gif->stop(); + delete gif; + gif = nullptr; + gif_label->hide(); + } + + if (QFile::exists(wheel_gif_path)) { + gif = new QMovie(wheel_gif_path); + + gif_label->setMovie(gif); + gif_label->resize(img_size, img_size); + gif_label->move((btn_size - img_size) / 2, (btn_size - img_size) / 2 + y_offset); + gif_label->show(); + + gif->start(); + + use_gif = true; + image_empty = false; + } else if (QFile::exists(wheel_png_path)) { + img = loadPixmap(wheel_png_path, QSize(img_size, img_size)); + image_empty = false; + use_gif = false; + } else { + image_empty = true; + use_gif = false; } + + update(); } void ExperimentalButton::paintEvent(QPaintEvent *event) { - if (wheelIcon < 0) { + if ((big_map && map_open) || image_empty || use_gif) { return; } QPainter p(this); - engage_img = wheelImages[wheelIcon]; - QPixmap img = wheelIcon != 0 ? engage_img : (experimental_mode ? experimental_img : engage_img); - QMovie *gif = wheelImagesGif[wheelIconGif]; - - QColor background_color = wheelIcon != 0 && !isDown() && engageable ? - (alwaysOnLateralActive ? bg_colors[STATUS_ALWAYS_ON_LATERAL_ACTIVE] : - (conditionalStatus == 1 || conditionalStatus == 3 || conditionalStatus == 5 ? bg_colors[STATUS_CONDITIONAL_OVERRIDDEN] : - (experimental_mode ? bg_colors[STATUS_EXPERIMENTAL_MODE_ACTIVE] : - (trafficModeActive ? bg_colors[STATUS_TRAFFIC_MODE_ACTIVE] : - (navigateOnOpenpilot ? bg_colors[STATUS_NAVIGATION_ACTIVE] : QColor(0, 0, 0, 166)))))) : - QColor(0, 0, 0, 166); - - if (!(bigMap && mapOpen)) { - if (wheelIconGif != 0) { - drawIconGif(p, QPoint(btn_size / 2, btn_size / 2 + y_offset), *gif, background_color, 1.0); - } else { - drawIcon(p, QPoint(btn_size / 2, btn_size / 2 + y_offset), img, background_color, (isDown() || !engageable) ? 0.6 : 1.0, steeringAngleDeg); - } - } + updateBackgroundColor(); + drawIcon(p, QPoint(btn_size / 2, btn_size / 2 + y_offset), img, background_color, (isDown() || !engageable) ? 0.6 : 1.0, steering_angle_deg); } // MapSettingsButton @@ -167,51 +186,92 @@ void MapSettingsButton::paintEvent(QPaintEvent *event) { DistanceButton::DistanceButton(QWidget *parent) : QPushButton(parent) { setFixedSize(btn_size * 1.5, btn_size * 1.5); - connect(this, &QPushButton::pressed, this, &DistanceButton::buttonPressed); - connect(this, &QPushButton::released, this, &DistanceButton::buttonReleased); -} + gif_label = new QLabel(this); + gif_label->setScaledContents(true); -void DistanceButton::buttonPressed() { - paramsMemory.putBool("OnroadDistanceButtonPressed", true); + connect(this, &QPushButton::pressed, [this] {params_memory.putBool("OnroadDistanceButtonPressed", true);}); + connect(this, &QPushButton::released, [this] {params_memory.putBool("OnroadDistanceButtonPressed", false);}); } -void DistanceButton::buttonReleased() { - paramsMemory.putBool("OnroadDistanceButtonPressed", false); +DistanceButton::~DistanceButton() { + qDeleteAll(profile_data_gif); + profile_data_gif.clear(); + profile_data_png.clear(); } void DistanceButton::updateState(const UIScene &scene) { - bool stateChanged = (trafficModeActive != scene.traffic_mode_active) || - (personality != static_cast(scene.personality) + 1 && !trafficModeActive); + bool state_changed = (traffic_mode_active != scene.traffic_mode_active) || + (personality != static_cast(scene.personality) + 1 && !traffic_mode_active); - if (stateChanged) { - personality = static_cast(scene.personality) + 1; - trafficModeActive = scene.traffic_mode_active; + if (!state_changed) { + return; + } - int profile = trafficModeActive ? 0 : personality; - std::tie(profileImage, profileText) = (scene.use_kaofui_icons ? profileDataKaofui : profileData)[profile]; + personality = static_cast(scene.personality) + 1; + traffic_mode_active = scene.traffic_mode_active; - transitionTimer.restart(); - update(); - } else if (transitionTimer.isValid()) { - update(); + int profile_index = traffic_mode_active ? 0 : personality; + + if (QMovie *gif = profile_data_gif.value(profile_index)) { + gif_label->setMovie(gif); + gif_label->resize(btn_size, btn_size); + gif_label->move(UI_BORDER_SIZE, btn_size / 2.5); + gif_label->show(); + + gif->start(); + + use_gif = true; } else { - return; + gif_label->hide(); + + profile_image = profile_data_png.value(profile_index); + + use_gif = false; } + + update(); +} + +void DistanceButton::updateIcon() { + qDeleteAll(profile_data_gif); + profile_data_gif.clear(); + profile_data_png.clear(); + + static const QVector file_names = { + "../frogpilot/assets/active_theme/distance_icons/traffic", + "../frogpilot/assets/active_theme/distance_icons/aggressive", + "../frogpilot/assets/active_theme/distance_icons/standard", + "../frogpilot/assets/active_theme/distance_icons/relaxed" + }; + + for (int i = 0; i < file_names.size(); ++i) { + const QString &file_name = file_names[i]; + QString gif_file = file_name + ".gif"; + QString png_file = file_name + ".png"; + QString fallback_file = QString("../frogpilot/assets/other_images/%1.png").arg(QFileInfo(file_name).baseName().toLower()); + + if (QFile::exists(gif_file)) { + QMovie *movie = new QMovie(gif_file); + profile_data_gif.push_back(movie); + profile_data_png.push_back(QPixmap()); + } else { + int pixmap_size = btn_size * 1.25; + QPixmap pixmap = loadPixmap(QFile::exists(png_file) ? png_file : fallback_file, QSize(pixmap_size, pixmap_size)); + profile_data_gif.push_back(nullptr); + profile_data_png.push_back(pixmap); + } + } + + personality = 0; } void DistanceButton::paintEvent(QPaintEvent *event) { + if (use_gif) { + return; + } + QPainter p(this); p.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); - int elapsed = transitionTimer.elapsed(); - qreal textOpacity = qBound(0.0, 1.0 - ((elapsed - 3000.0) / 1000.0), 1.0); - qreal imageOpacity = 1.0 - textOpacity; - - p.setOpacity(textOpacity); - p.setFont(InterFont(40, QFont::Bold)); - p.setPen(Qt::white); - QRect textRect(-25, 0, width(), height() + btn_size / 2); - p.drawText(textRect, Qt::AlignCenter, profileText); - - drawIcon(p, QPoint((btn_size / 2) * 1.25, btn_size), profileImage, Qt::transparent, imageOpacity); + drawIcon(p, QPoint((btn_size / 2) + (UI_BORDER_SIZE * 0.5), btn_size - (UI_BORDER_SIZE * 1.5)), profile_image, Qt::transparent, 1.0); } diff --git a/selfdrive/ui/qt/onroad/buttons.h b/selfdrive/ui/qt/onroad/buttons.h index 6ccfc1a338c7a8..c0547f18d3481c 100644 --- a/selfdrive/ui/qt/onroad/buttons.h +++ b/selfdrive/ui/qt/onroad/buttons.h @@ -14,7 +14,11 @@ class ExperimentalButton : public QPushButton { public: explicit ExperimentalButton(QWidget *parent = 0); - void updateState(const UIState &s, bool leadInfo); + ~ExperimentalButton(); + void updateState(const UIState &s, bool lead_info); + + // FrogPilot widgets + void updateIcon(); private: void paintEvent(QPaintEvent *event) override; @@ -26,28 +30,37 @@ class ExperimentalButton : public QPushButton { bool experimental_mode; bool engageable; + // FrogPilot widgets + void updateBackgroundColor(); + // FrogPilot variables - bool alwaysOnLateralActive; - bool bigMap; - bool conditionalExperimental; - bool mapOpen; - bool navigateOnOpenpilot; - bool rotatingWheel; - bool trafficModeActive; - - int conditionalStatus; - int randomEvent; - int steeringAngleDeg; - int wheelIcon; - int wheelIconGif; + Params params_memory{"/dev/shm/params"}; + + bool always_on_lateral_active; + bool big_map; + bool conditional_experimental; + bool image_empty; + bool map_open; + bool navigate_on_openpilot; + bool rotating_wheel; + bool traffic_mode_active; + bool use_gif; + bool use_stock_wheel; + + int conditional_status; + int steering_angle_deg; int y_offset; - QLabel *gifLabel; + QColor background_color; + + QLabel *gif_label; + + QMovie *gif; - QMap wheelImages; - QMap wheelImagesGif; + QPixmap img; - Params paramsMemory{"/dev/shm/params"}; + QString wheel_gif_path; + QString wheel_png_path; }; @@ -71,35 +84,23 @@ class DistanceButton : public QPushButton { public: explicit DistanceButton(QWidget *parent = 0); + ~DistanceButton(); + void updateIcon(); void updateState(const UIScene &scene); private: - void buttonPressed(); - void buttonReleased(); void paintEvent(QPaintEvent *event) override; - bool trafficModeActive; + Params params_memory{"/dev/shm/params"}; - int personality; - - QElapsedTimer transitionTimer; + bool traffic_mode_active; + bool use_gif; - QPixmap profileImage; - QString profileText; - - Params paramsMemory{"/dev/shm/params"}; + int personality; - const QVector> profileData = { - {QPixmap("../frogpilot/assets/other_images/traffic.png"), "Traffic"}, - {QPixmap("../frogpilot/assets/other_images/aggressive.png"), "Aggressive"}, - {QPixmap("../frogpilot/assets/other_images/standard.png"), "Standard"}, - {QPixmap("../frogpilot/assets/other_images/relaxed.png"), "Relaxed"} - }; + QLabel *gif_label; + QPixmap profile_image; - const QVector> profileDataKaofui = { - {QPixmap("../frogpilot/assets/other_images/traffic_kaofui.png"), "Traffic"}, - {QPixmap("../frogpilot/assets/other_images/aggressive_kaofui.png"), "Aggressive"}, - {QPixmap("../frogpilot/assets/other_images/standard_kaofui.png"), "Standard"}, - {QPixmap("../frogpilot/assets/other_images/relaxed_kaofui.png"), "Relaxed"} - }; + QVector profile_data_png; + QVector profile_data_gif; }; diff --git a/selfdrive/ui/qt/onroad/onroad_home.cc b/selfdrive/ui/qt/onroad/onroad_home.cc index f1fcbf4ecca5f3..c7fb1d347370f3 100644 --- a/selfdrive/ui/qt/onroad/onroad_home.cc +++ b/selfdrive/ui/qt/onroad/onroad_home.cc @@ -70,7 +70,7 @@ void OnroadWindow::updateState(const UIState &s) { } alerts->updateState(s); - nvg->updateState(s); + nvg->updateState(alerts->alert_height, s); bool shouldUpdate = false; diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index 9fde190b0c34e2..d5206b624f652e 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -25,10 +25,6 @@ void Sidebar::drawMetric(QPainter &p, const QPair &label, QCol } Sidebar::Sidebar(QWidget *parent) : QFrame(parent), onroad(false), flag_pressed(false), settings_pressed(false) { - home_img = loadPixmap("../assets/images/button_home.png", home_btn.size()); - flag_img = loadPixmap("../assets/images/button_flag.png", home_btn.size()); - settings_img = loadPixmap("../assets/images/button_settings.png", settings_btn.size(), Qt::IgnoreAspectRatio); - connect(this, &Sidebar::valueChanged, [=] { update(); }); setAttribute(Qt::WA_OpaquePaintEvent); @@ -40,55 +36,57 @@ Sidebar::Sidebar(QWidget *parent) : QFrame(parent), onroad(false), flag_pressed( pm = std::make_unique>({"userFlag"}); // FrogPilot variables - UIState *s = uiState(); - UIScene &scene = s->scene; + home_label = new QLabel(this); + settings_label = new QLabel(this); + + flagPngPath = "../frogpilot/assets/active_theme/icons/button_flag.png"; + homeGifPath = "../frogpilot/assets/active_theme/icons/button_home.gif"; + homePngPath = "../frogpilot/assets/active_theme/icons/button_home.png"; + settingsGifPath = "../frogpilot/assets/active_theme/icons/button_settings.gif"; + settingsPngPath = "../frogpilot/assets/active_theme/icons/button_settings.png"; + + randomEventGifPath = "../frogpilot/assets/random_events/icons/button_home.gif"; +} + +void Sidebar::showEvent(QShowEvent *event) { + updateIcon(home_label, home_gif, homeGifPath, home_btn, homePngPath, isHomeGif); + updateIcon(settings_label, settings_gif, settingsGifPath, settings_btn, settingsPngPath, isSettingsGif); +} - holidayThemeConfiguration = { - {0, {"stock", {QColor(255, 255, 255)}}}, - {1, {"april_fools", {QColor(255, 165, 0)}}}, - {2, {"christmas", {QColor(0, 72, 255)}}}, - {3, {"cinco_de_mayo", {QColor(0, 104, 71)}}}, - {4, {"easter", {QColor(200, 150, 200)}}}, - {5, {"fourth_of_july", {QColor(10, 49, 97)}}}, - {6, {"halloween", {QColor(255, 0, 0)}}}, - {7, {"new_years_day", {QColor(23, 134, 68)}}}, - {8, {"st_patricks_day", {QColor(0, 128, 0)}}}, - {9, {"thanksgiving", {QColor(255, 0, 0)}}}, - {10, {"valentines_day", {QColor(23, 134, 68)}}}, - {11, {"world_frog_day", {QColor(23, 134, 68)}}}, - }; - - for (auto &[key, themeData] : holidayThemeConfiguration) { - QString &themeName = themeData.first; - QString base = themeName == "stock" ? "../assets/images" : QString("../frogpilot/assets/holiday_themes/%1/images").arg(themeName); - std::vector paths = {base + "/button_home.png", base + "/button_flag.png", base + "/button_settings.png"}; - - holiday_home_imgs[key] = loadPixmap(paths[0], home_btn.size()); - holiday_flag_imgs[key] = loadPixmap(paths[1], home_btn.size()); - holiday_settings_imgs[key] = loadPixmap(paths[2], settings_btn.size(), Qt::IgnoreAspectRatio); +void Sidebar::updateIcon(QLabel *&label, QMovie *&gif, const QString &gifPath, const QRect &btnRect, const QString &pngPath, bool &isGif) { + QString selectedGifPath = gifPath; + if (qrand() % 100 == 0 && btnRect == home_btn && isRandomEvents) { + selectedGifPath = randomEventGifPath; } - themeConfiguration = { - {0, {"stock", {QColor(255, 255, 255)}}}, - {1, {"frog_theme", {QColor(23, 134, 68)}}}, - {2, {"tesla_theme", {QColor(0, 72, 255)}}}, - {3, {"stalin_theme", {QColor(255, 0, 0)}}} - }; - - for (auto &[key, themeData] : themeConfiguration) { - QString &themeName = themeData.first; - QString base = themeName == "stock" ? "../assets/images" : QString("../frogpilot/assets/custom_themes/%1/images").arg(themeName); - std::vector paths = {base + "/button_home.png", base + "/button_flag.png", base + "/button_settings.png"}; - - home_imgs[key] = loadPixmap(paths[0], home_btn.size()); - flag_imgs[key] = loadPixmap(paths[1], home_btn.size()); - settings_imgs[key] = loadPixmap(paths[2], settings_btn.size(), Qt::IgnoreAspectRatio); + if (gif != nullptr) { + gif->stop(); + delete gif; + gif = nullptr; + label->hide(); } - home_img = home_imgs[scene.custom_icons]; - flag_img = flag_imgs[scene.custom_icons]; - settings_img = settings_imgs[scene.custom_icons]; - currentColors = themeConfiguration[scene.custom_colors].second; + if (QFile::exists(selectedGifPath)) { + gif = new QMovie(selectedGifPath); + gif->setScaledSize(btnRect.size()); + + label->setGeometry(btnRect); + label->setMovie(gif); + label->show(); + + gif->start(); + + isGif = true; + } else { + if (btnRect == home_btn) { + home_img = loadPixmap(homePngPath, btnRect.size()); + flag_img = loadPixmap(flagPngPath, btnRect.size()); + } else { + settings_img = loadPixmap(settingsPngPath, btnRect.size(), Qt::IgnoreAspectRatio); + } + + isGif = false; + } } void Sidebar::mousePressEvent(QMouseEvent *event) { @@ -105,7 +103,7 @@ void Sidebar::mousePressEvent(QMouseEvent *event) { static int showMemory = 0; static int showTemp = 0; - if (cpuRect.contains(pos) && sidebarMetrics) { + if (cpuRect.contains(pos) && isSidebarMetrics) { showChip = (showChip + 1) % 3; isCPU = (showChip == 1); @@ -119,7 +117,7 @@ void Sidebar::mousePressEvent(QMouseEvent *event) { update(); return; - } else if (memoryRect.contains(pos) && sidebarMetrics) { + } else if (memoryRect.contains(pos) && isSidebarMetrics) { showMemory = (showMemory + 1) % 4; isMemoryUsage = (showMemory == 1); @@ -136,10 +134,12 @@ void Sidebar::mousePressEvent(QMouseEvent *event) { update(); return; - } else if (tempRect.contains(pos) && sidebarMetrics) { + } else if (tempRect.contains(pos) && isSidebarMetrics) { showTemp = (showTemp + 1) % 3; - scene.fahrenheit = showTemp == 2; + isFahrenheit = showTemp == 2; + + scene.fahrenheit = isFahrenheit; scene.numerical_temp = showTemp != 0; params.putBoolNonBlocking("Fahrenheit", showTemp == 2); @@ -178,7 +178,23 @@ void Sidebar::offroadTransition(bool offroad) { } void Sidebar::updateState(const UIState &s) { - if (!isVisible()) return; + if (!isVisible()) { + if (home_gif != nullptr) { + home_gif->stop(); + delete home_gif; + home_gif = nullptr; + home_label->hide(); + } + + if (settings_gif != nullptr) { + settings_gif->stop(); + delete settings_gif; + settings_gif = nullptr; + settings_label->hide(); + } + + return; + } auto &sm = *(s.sm); @@ -187,34 +203,57 @@ void Sidebar::updateState(const UIState &s) { int strength = (int)deviceState.getNetworkStrength(); setProperty("netStrength", strength > 0 ? strength + 1 : 0); - // FrogPilot properties - const UIScene &scene = s.scene; - - if (scene.current_holiday_theme != 0) { - home_img = holiday_home_imgs[scene.current_holiday_theme]; - flag_img = holiday_flag_imgs[scene.current_holiday_theme]; - settings_img = holiday_settings_imgs[scene.current_holiday_theme]; - currentColors = holidayThemeConfiguration[scene.current_holiday_theme].second; + ItemStatus connectStatus; + auto last_ping = deviceState.getLastAthenaPingTime(); + if (last_ping == 0) { + connectStatus = ItemStatus{{tr("CONNECT"), tr("OFFLINE")}, warning_color}; } else { - home_img = home_imgs[scene.custom_icons]; - flag_img = flag_imgs[scene.custom_icons]; - settings_img = settings_imgs[scene.custom_icons]; - currentColors = themeConfiguration[scene.custom_colors].second; + connectStatus = nanos_since_boot() - last_ping < 80e9 + ? ItemStatus{{tr("CONNECT"), tr("ONLINE")}, sidebar_color1} + : ItemStatus{{tr("CONNECT"), tr("ERROR")}, danger_color}; + } + setProperty("connectStatus", QVariant::fromValue(connectStatus)); + + ItemStatus tempStatus = {{tr("TEMP"), isNumericalTemp ? max_temp : tr("HIGH")}, danger_color}; + auto ts = deviceState.getThermalStatus(); + if (ts == cereal::DeviceState::ThermalStatus::GREEN) { + tempStatus = {{tr("TEMP"), isNumericalTemp ? max_temp : tr("GOOD")}, sidebar_color1}; + } else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) { + tempStatus = {{tr("TEMP"), isNumericalTemp ? max_temp : tr("OK")}, warning_color}; } + setProperty("tempStatus", QVariant::fromValue(tempStatus)); + + ItemStatus pandaStatus = {{tr("VEHICLE"), tr("ONLINE")}, sidebar_color2}; + if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) { + pandaStatus = {{tr("NO"), tr("PANDA")}, danger_color}; + } else if (s.scene.started && !sm["liveLocationKalman"].getLiveLocationKalman().getGpsOK()) { + pandaStatus = {{tr("GPS"), tr("SEARCH")}, warning_color}; + } + setProperty("pandaStatus", QVariant::fromValue(pandaStatus)); - auto frogpilotDeviceState = sm["frogpilotDeviceState"].getFrogpilotDeviceState(); + // FrogPilot variables + const UIScene &scene = s.scene; isCPU = scene.is_CPU; + isFahrenheit = scene.fahrenheit; isGPU = scene.is_GPU; isIP = scene.is_IP; isMemoryUsage = scene.is_memory; isNumericalTemp = scene.numerical_temp; + isRandomEvents = scene.random_events; isStorageLeft = scene.is_storage_left; isStorageUsed = scene.is_storage_used; - sidebarMetrics = scene.sidebar_metrics; + isSidebarMetrics = scene.sidebar_metrics; + + bool useStockColors = scene.use_stock_colors; + sidebar_color1 = useStockColors ? good_color : scene.sidebar_color1; + sidebar_color2 = useStockColors ? good_color : scene.sidebar_color2; + sidebar_color3 = useStockColors ? good_color : scene.sidebar_color3; + + const cereal::FrogPilotDeviceState::Reader &frogpilotDeviceState = sm["frogpilotDeviceState"].getFrogpilotDeviceState(); int maxTempC = deviceState.getMaxTempC(); - QString max_temp = scene.fahrenheit ? QString::number(maxTempC * 9 / 5 + 32) + "°F" : QString::number(maxTempC) + "°C"; + max_temp = isFahrenheit ? QString::number(maxTempC * 9 / 5 + 32) + "°F" : QString::number(maxTempC) + "°C"; if (isCPU || isGPU) { auto cpu_loads = deviceState.getCpuUsagePercent(); @@ -227,7 +266,7 @@ void Sidebar::updateState(const UIState &s) { QString metric = isGPU ? gpu : cpu; int usage = isGPU ? gpu_usage : cpu_usage; - ItemStatus cpuStatus = {{isGPU ? tr("GPU") : tr("CPU"), metric}, currentColors[0]}; + ItemStatus cpuStatus = {{isGPU ? tr("GPU") : tr("CPU"), metric}, sidebar_color2}; if (usage >= 85) { cpuStatus = {{isGPU ? tr("GPU") : tr("CPU"), metric}, danger_color}; } else if (usage >= 70) { @@ -245,7 +284,7 @@ void Sidebar::updateState(const UIState &s) { QString storage = QString::number(isStorageLeft ? storage_left : storage_used) + tr(" GB"); if (isMemoryUsage) { - ItemStatus memoryStatus = {{tr("MEMORY"), memory}, currentColors[0]}; + ItemStatus memoryStatus = {{tr("MEMORY"), memory}, sidebar_color3}; if (memory_usage >= 85) { memoryStatus = {{tr("MEMORY"), memory}, danger_color}; } else if (memory_usage >= 70) { @@ -253,7 +292,7 @@ void Sidebar::updateState(const UIState &s) { } setProperty("memoryStatus", QVariant::fromValue(memoryStatus)); } else { - ItemStatus storageStatus = {{isStorageLeft ? tr("LEFT") : tr("USED"), storage}, currentColors[0]}; + ItemStatus storageStatus = {{isStorageLeft ? tr("LEFT") : tr("USED"), storage}, sidebar_color3}; if (25 > storage_left && storage_left >= 10) { storageStatus = {{isStorageLeft ? tr("LEFT") : tr("USED"), storage}, warning_color}; } else if (10 > storage_left) { @@ -262,34 +301,6 @@ void Sidebar::updateState(const UIState &s) { setProperty("storageStatus", QVariant::fromValue(storageStatus)); } } - - ItemStatus connectStatus; - auto last_ping = deviceState.getLastAthenaPingTime(); - if (last_ping == 0) { - connectStatus = ItemStatus{{tr("CONNECT"), tr("OFFLINE")}, warning_color}; - } else { - connectStatus = nanos_since_boot() - last_ping < 80e9 - ? ItemStatus{{tr("CONNECT"), tr("ONLINE")}, currentColors[0]} - : ItemStatus{{tr("CONNECT"), tr("ERROR")}, danger_color}; - } - setProperty("connectStatus", QVariant::fromValue(connectStatus)); - - ItemStatus tempStatus = {{tr("TEMP"), isNumericalTemp ? max_temp : tr("HIGH")}, danger_color}; - auto ts = deviceState.getThermalStatus(); - if (ts == cereal::DeviceState::ThermalStatus::GREEN) { - tempStatus = {{tr("TEMP"), isNumericalTemp ? max_temp : tr("GOOD")}, currentColors[0]}; - } else if (ts == cereal::DeviceState::ThermalStatus::YELLOW) { - tempStatus = {{tr("TEMP"), isNumericalTemp ? max_temp : tr("OK")}, warning_color}; - } - setProperty("tempStatus", QVariant::fromValue(tempStatus)); - - ItemStatus pandaStatus = {{tr("VEHICLE"), tr("ONLINE")}, currentColors[0]}; - if (s.scene.pandaType == cereal::PandaState::PandaType::UNKNOWN) { - pandaStatus = {{tr("NO"), tr("PANDA")}, danger_color}; - } else if (s.scene.started && !sm["liveLocationKalman"].getLiveLocationKalman().getGpsOK()) { - pandaStatus = {{tr("GPS"), tr("SEARCH")}, warning_color}; - } - setProperty("pandaStatus", QVariant::fromValue(pandaStatus)); } void Sidebar::paintEvent(QPaintEvent *event) { @@ -300,10 +311,14 @@ void Sidebar::paintEvent(QPaintEvent *event) { p.fillRect(rect(), QColor(57, 57, 57)); // buttons - p.setOpacity(settings_pressed ? 0.65 : 1.0); - p.drawPixmap(settings_btn.x(), settings_btn.y(), settings_img); - p.setOpacity(onroad && flag_pressed ? 0.65 : 1.0); - p.drawPixmap(home_btn.x(), home_btn.y(), onroad ? flag_img : home_img); + if (!isSettingsGif) { + p.setOpacity(settings_pressed ? 0.65 : 1.0); + p.drawPixmap(settings_btn.x(), settings_btn.y(), settings_img); + } + if (!isHomeGif) { + p.setOpacity(onroad && flag_pressed ? 0.65 : 1.0); + p.drawPixmap(home_btn.x(), home_btn.y(), onroad ? flag_img : home_img); + } p.setOpacity(1.0); // network diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index e2263f799e44da..6fa7bac1dca313 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -3,7 +3,9 @@ #include #include +#include #include +#include #include "selfdrive/ui/ui.h" @@ -65,29 +67,44 @@ public slots: private: std::unique_ptr pm; + // FrogPilot widgets + void showEvent(QShowEvent *event); + void updateIcon(QLabel *&label, QMovie *&gif, const QString &gifPath, const QRect &btnRect, const QString &pngPath, bool &isGif); + void updateIcons(); + // FrogPilot variables Params params; ItemStatus cpu_status, memory_status, storage_status; bool isCPU; + bool isFahrenheit; bool isGPU; + bool isHomeGif; bool isIP; bool isMemoryUsage; bool isNumericalTemp; + bool isRandomEvents; + bool isSettingsGif; + bool isSidebarMetrics; bool isStorageLeft; bool isStorageUsed; - bool sidebarMetrics; - std::unordered_map>> themeConfiguration; - std::unordered_map flag_imgs; - std::unordered_map home_imgs; - std::unordered_map settings_imgs; + QColor sidebar_color1; + QColor sidebar_color2; + QColor sidebar_color3; + + QLabel *home_label; + QLabel *settings_label; - std::unordered_map>> holidayThemeConfiguration; - std::unordered_map holiday_flag_imgs; - std::unordered_map holiday_home_imgs; - std::unordered_map holiday_settings_imgs; + QMovie *home_gif; + QMovie *settings_gif; - std::vector currentColors; + QString flagPngPath; + QString homeGifPath; + QString homePngPath; + QString max_temp; + QString randomEventGifPath; + QString settingsGifPath; + QString settingsPngPath; }; diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc index 4ff0de29f9fe2c..a69442e20f65b0 100644 --- a/selfdrive/ui/qt/widgets/input.cc +++ b/selfdrive/ui/qt/widgets/input.cc @@ -141,19 +141,25 @@ InputDialog::InputDialog(const QString &title, QWidget *parent, const QString &s QObject::connect(k, &Keyboard::emitEnter, this, &InputDialog::handleEnter); QObject::connect(k, &Keyboard::emitBackspace, this, [=]() { line->backspace(); + updateMaxLengthSublabel(line->text()); }); QObject::connect(k, &Keyboard::emitKey, this, [=](const QString &key) { - line->insert(key.left(1)); + if (line->text().length() < maxLength || maxLength == DEFAULT_MAX_LENGTH) { + line->insert(key.left(1)); + updateMaxLengthSublabel(line->text()); + } }); main_layout->addWidget(k, 2, Qt::AlignBottom); } QString InputDialog::getText(const QString &prompt, QWidget *parent, const QString &subtitle, - bool secret, int minLength, const QString &defaultText) { + bool secret, int minLength, const QString &defaultText, int maxLength) { InputDialog d = InputDialog(prompt, parent, subtitle, secret); d.line->setText(defaultText); d.setMinLength(minLength); + d.setMaxLength(maxLength); + d.updateMaxLengthSublabel(defaultText); const int ret = d.exec(); return ret ? d.text() : QString(); } @@ -186,6 +192,17 @@ void InputDialog::setMinLength(int length) { minLength = length; } +// FrogPilot functions +void InputDialog::setMaxLength(int length) { + maxLength = length; +} + +void InputDialog::updateMaxLengthSublabel(const QString &text) { + if (maxLength != DEFAULT_MAX_LENGTH) { + sublabel->setText(tr("Characters: %1/%2").arg(text.length()).arg(maxLength)); + } +} + // ConfirmationDialog ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString &confirm_text, const QString &cancel_text, diff --git a/selfdrive/ui/qt/widgets/input.h b/selfdrive/ui/qt/widgets/input.h index 089e54e4a0018d..d321137efde30d 100644 --- a/selfdrive/ui/qt/widgets/input.h +++ b/selfdrive/ui/qt/widgets/input.h @@ -9,6 +9,7 @@ #include "selfdrive/ui/qt/widgets/keyboard.h" +const int DEFAULT_MAX_LENGTH = 512; class DialogBase : public QDialog { Q_OBJECT @@ -27,12 +28,15 @@ class InputDialog : public DialogBase { public: explicit InputDialog(const QString &title, QWidget *parent, const QString &subtitle = "", bool secret = false); static QString getText(const QString &title, QWidget *parent, const QString &subtitle = "", - bool secret = false, int minLength = -1, const QString &defaultText = ""); + bool secret = false, int minLength = -1, const QString &defaultText = "", int maxLength = DEFAULT_MAX_LENGTH); QString text(); void setMessage(const QString &message, bool clearInputField = true); void setMinLength(int length); void show(); + // FrogPilot widgets + void setMaxLength(int length); + private: int minLength; QLineEdit *line; @@ -42,6 +46,12 @@ class InputDialog : public DialogBase { QVBoxLayout *main_layout; QPushButton *eye_btn; + // FrogPilot widgets + void updateMaxLengthSublabel(const QString &text); + + // FrogPilot variables + int maxLength; + private slots: void handleEnter(); diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index f9924d0e696645..d7463b81e245b1 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -1,5 +1,6 @@ import math import numpy as np +import os import time import wave @@ -13,6 +14,7 @@ from openpilot.system import micd +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import ACTIVE_THEME_PATH, RANDOM_EVENTS_PATH from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import FrogPilotVariables SAMPLE_RATE = 48000 @@ -79,8 +81,9 @@ def __init__(self): # FrogPilot variables self.frogpilot_toggles = FrogPilotVariables.toggles - self.previous_sound_directory = None - self.random_events_directory = BASEDIR + "/selfdrive/frogpilot/assets/random_events/sounds/" + self.previous_sound_pack = None + + self.random_events_directory = os.path.join(RANDOM_EVENTS_PATH, "sounds/") self.random_events_map = { AudibleAlert.angry: MAX_VOLUME, @@ -109,8 +112,6 @@ def load_sounds(self): if sound in self.random_events_map: wavefile = wave.open(self.random_events_directory + filename, 'r') else: - if sound == AudibleAlert.goat and not self.frogpilot_toggles.goat_scream: - continue try: wavefile = wave.open(self.sound_directory + filename, 'r') except FileNotFoundError: @@ -213,11 +214,12 @@ def soundd_thread(self): if FrogPilotVariables.toggles_updated: self.update_toggles = True elif self.update_toggles: - FrogPilotVariables.update_frogpilot_params() self.update_frogpilot_sounds() self.update_toggles = False def update_frogpilot_sounds(self): + FrogPilotVariables.update_frogpilot_params() + self.volume_map = { AudibleAlert.engage: self.frogpilot_toggles.engage_volume, AudibleAlert.disengage: self.frogpilot_toggles.disengage_volume, @@ -233,38 +235,14 @@ def update_frogpilot_sounds(self): AudibleAlert.goat: self.frogpilot_toggles.prompt_volume, } - holiday_theme_configuration = { - 1: "april_fools", - 2: "christmas", - 3: "cinco_de_mayo", - 4: "easter", - 5: "fourth_of_july", - 6: "halloween", - 7: "new_years_day", - 8: "st_patricks_day", - 9: "thanksgiving", - 10: "valentines_day", - 11: "world_frog_day", - } - - theme_configuration = { - 0: "stock_theme", - 1: "frog_theme", - 2: "tesla_theme", - 3: "stalin_theme" - } - - if self.frogpilot_toggles.current_holiday_theme != 0: - theme_name = holiday_theme_configuration.get(self.frogpilot_toggles.current_holiday_theme) - self.sound_directory = BASEDIR + ("/selfdrive/frogpilot/assets/holiday_themes/" + theme_name + "/sounds/") + if self.frogpilot_toggles.sound_pack != "stock": + self.sound_directory = os.path.join(ACTIVE_THEME_PATH, "sounds/") else: - theme_name = theme_configuration.get(self.frogpilot_toggles.custom_sounds) - self.sound_directory = BASEDIR + ("/selfdrive/frogpilot/assets/custom_themes/" + theme_name + "/sounds/" if theme_name != "stock_theme" else "/selfdrive/assets/sounds/") + self.sound_directory = os.path.join(BASEDIR, "selfdrive", "assets", "sounds/") - if self.sound_directory != self.previous_sound_directory: + if self.frogpilot_toggles.sound_pack != self.previous_sound_pack: self.load_sounds() - - self.previous_sound_directory = self.sound_directory + self.previous_sound_pack = self.frogpilot_toggles.sound_pack def main(): s = Soundd() diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index ab275bc81f2445..6b0a9befbdbf72 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -245,18 +245,16 @@ static void update_state(UIState *s) { } if (sm.updated("controlsState")) { auto controlsState = sm["controlsState"].getControlsState(); - scene.alert_size = controlsState.getAlertSize() == cereal::ControlsState::AlertSize::MID ? 350 : controlsState.getAlertSize() == cereal::ControlsState::AlertSize::SMALL ? 200 : 0; scene.enabled = controlsState.getEnabled(); scene.experimental_mode = scene.enabled && controlsState.getExperimentalMode(); } if (sm.updated("deviceState")) { auto deviceState = sm["deviceState"].getDeviceState(); - scene.online = deviceState.getNetworkType() != cereal::DeviceState::NetworkType::NONE; + scene.online = deviceState.getNetworkType() == cereal::DeviceState::NetworkType::WIFI; } if (sm.updated("frogpilotCarControl")) { auto frogpilotCarControl = sm["frogpilotCarControl"].getFrogpilotCarControl(); - scene.always_on_lateral_active = !scene.enabled && frogpilotCarControl.getAlwaysOnLateral(); - scene.speed_limit_changed = scene.speed_limit_controller && frogpilotCarControl.getSpeedLimitChanged(); + scene.always_on_lateral_active = !scene.enabled && frogpilotCarControl.getAlwaysOnLateralActive(); } if (sm.updated("frogpilotCarState")) { auto frogpilotCarState = sm["frogpilotCarState"].getFrogpilotCarState(); @@ -277,6 +275,7 @@ static void update_state(UIState *s) { scene.speed_jerk = frogpilotPlan.getSpeedJerk(); scene.speed_jerk_difference = frogpilotPlan.getSpeedJerkStock() - scene.speed_jerk; scene.speed_limit = frogpilotPlan.getSlcSpeedLimit(); + scene.speed_limit_changed = scene.speed_limit_controller && frogpilotPlan.getSpeedLimitChanged(); scene.speed_limit_offset = frogpilotPlan.getSlcSpeedLimitOffset(); scene.speed_limit_overridden = frogpilotPlan.getSlcOverridden(); scene.speed_limit_overridden_speed = frogpilotPlan.getSlcOverriddenSpeed(); @@ -335,6 +334,21 @@ void ui_update_frogpilot_params(UIState *s, Params ¶ms) { bool always_on_lateral = params.getBool("AlwaysOnLateral"); scene.show_aol_status_bar = always_on_lateral && !params.getBool("HideAOLStatusBar"); + bool bonus_content = params.getBool("BonusContent"); + bool personalize_openpilot = bonus_content && params.getBool("PersonalizeOpenpilot"); + scene.lane_lines_color = loadThemeColors("LaneLines"); + scene.lead_marker_color = loadThemeColors("LeadMarker"); + scene.path_color = loadThemeColors("Path"); + scene.path_edges_color = loadThemeColors("PathEdge"); + scene.road_edges_color = loadThemeColors("RoadEdges"); + scene.sidebar_color1 = loadThemeColors("Sidebar1"); + scene.sidebar_color2 = loadThemeColors("Sidebar2"); + scene.sidebar_color3 = loadThemeColors("Sidebar3"); + QString colorScheme = QString::fromStdString(params.get("CustomColors")); + scene.use_stock_colors = !personalize_openpilot || colorScheme == "stock" || params.getBool("UseStockColors"); + scene.use_stock_wheel = !personalize_openpilot || QString::fromStdString(params.get("WheelIcon")) == "stock"; + scene.random_events = bonus_content && params.getBool("RandomEvents"); + scene.conditional_experimental = scene.longitudinal_control && params.getBool("ConditionalExperimental"); scene.conditional_speed = scene.conditional_experimental ? params.getInt("CESpeed") : 0; scene.conditional_speed_lead = scene.conditional_experimental ? params.getInt("CESpeedLead") : 0; @@ -354,14 +368,6 @@ void ui_update_frogpilot_params(UIState *s, Params ¶ms) { scene.rotating_wheel = custom_onroad_ui && params.getBool("RotatingWheel"); scene.show_stopping_point = custom_onroad_ui && params.getBool("ShowStoppingPoint"); scene.show_stopping_point_metrics = scene.show_stopping_point && params.getBool("ShowStoppingPointMetrics"); - scene.wheel_icon = custom_onroad_ui ? params.getInt("WheelIcon") : 0; - - bool custom_theme = params.getBool("CustomTheme"); - scene.custom_colors = custom_theme ? params.getInt("CustomColors") : 0; - scene.custom_icons = custom_theme ? params.getInt("CustomIcons") : 0; - scene.custom_signals = custom_theme ? params.getInt("CustomSignals") : 0; - scene.holiday_themes = custom_theme && params.getBool("HolidayThemes"); - scene.random_events = custom_theme && params.getBool("RandomEvents"); bool developer_ui = params.getBool("DeveloperUI"); bool border_metrics = developer_ui && params.getBool("BorderMetrics"); @@ -390,7 +396,6 @@ void ui_update_frogpilot_params(UIState *s, Params ¶ms) { bool driving_personalities = scene.longitudinal_control && params.getBool("DrivingPersonalities"); scene.onroad_distance_button = driving_personalities && params.getBool("OnroadDistanceButton"); - scene.use_kaofui_icons = scene.onroad_distance_button && params.getBool("KaofuiIcons"); scene.experimental_mode_via_screen = scene.longitudinal_control && params.getBool("ExperimentalModeActivation") && params.getBool("ExperimentalModeViaTap"); @@ -469,7 +474,7 @@ void UIState::updateStatus() { scene.wake_up_screen = controls_state.getAlertStatus() != cereal::ControlsState::AlertStatus::NORMAL || status != previous_status; } - scene.started |= paramsMemory.getBool("ForceOnroad"); + scene.started |= scene.force_onroad; scene.started &= !paramsMemory.getBool("ForceOffroad"); // Handle onroad/offroad transition @@ -494,8 +499,8 @@ UIState::UIState(QObject *parent) : QObject(parent) { "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "uiPlan", "clocks", - "carControl", "liveTorqueParameters", - "frogpilotCarControl", "frogpilotCarState", "frogpilotDeviceState", "frogpilotPlan", + "carControl", "liveTorqueParameters", "frogpilotCarControl", "frogpilotCarState", "frogpilotDeviceState", + "frogpilotPlan", }); Params params; @@ -543,9 +548,8 @@ void UIState::update() { // FrogPilot variables that need to be constantly updated scene.conditional_status = scene.conditional_experimental && scene.enabled ? paramsMemory.getInt("CEStatus") : 0; - scene.current_holiday_theme = scene.holiday_themes ? paramsMemory.getInt("CurrentHolidayTheme") : 0; - scene.current_random_event = scene.random_events ? paramsMemory.getInt("CurrentRandomEvent") : 0; scene.driver_camera_timer = scene.driver_camera && scene.reverse ? scene.driver_camera_timer + 1 : 0; + scene.force_onroad = paramsMemory.getBool("ForceOnroad"); scene.started_timer = scene.started || started_prev ? scene.started_timer + 1 : 0; } diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index f22420b904a570..646ce950f7f6a6 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -142,6 +142,7 @@ typedef struct UIScene { bool experimental_mode; bool experimental_mode_via_screen; bool fahrenheit; + bool force_onroad; bool full_map; bool has_auto_tune; bool has_lead; @@ -151,7 +152,6 @@ typedef struct UIScene { bool hide_max_speed; bool hide_speed; bool hide_speed_ui; - bool holiday_themes; bool is_CPU; bool is_GPU; bool is_IP; @@ -203,8 +203,9 @@ typedef struct UIScene { bool turn_signal_left; bool turn_signal_right; bool unlimited_road_ui_length; - bool use_kaofui_icons; bool use_si; + bool use_stock_colors; + bool use_stock_wheel; bool use_vienna_slc_sign; bool vtsc_controlling_curve; bool wake_up_screen; @@ -234,17 +235,11 @@ typedef struct UIScene { float steer; float unconfirmed_speed_limit; - int alert_size; int bearing_deg; int camera_view; int conditional_speed; int conditional_speed_lead; int conditional_status; - int current_holiday_theme; - int current_random_event; - int custom_colors; - int custom_icons; - int custom_signals; int desired_follow; int driver_camera_timer; int map_style; @@ -259,7 +254,15 @@ typedef struct UIScene { int steering_angle_deg; int stopped_equivalence; int tethering_config; - int wheel_icon; + + QColor lane_lines_color; + QColor lead_marker_color; + QColor path_color; + QColor path_edges_color; + QColor road_edges_color; + QColor sidebar_color1; + QColor sidebar_color2; + QColor sidebar_color3; QPolygonF track_adjacent_vertices[6]; QPolygonF track_edge_vertices; @@ -303,6 +306,7 @@ class UIState : public QObject { // FrogPilot signals void driveRated(); void reviewModel(); + private slots: void update(); diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index f6e5abd3d6af37..222bdef2afdf04 100755 --- a/system/hardware/hardwared.py +++ b/system/hardware/hardwared.py @@ -208,6 +208,7 @@ def hardware_thread(end_event, hw_queue) -> None: # FrogPilot variables frogpilot_toggles = FrogPilotVariables.toggles + FrogPilotVariables.update_frogpilot_params() params_memory = Params("/dev/shm/params") diff --git a/system/manager/manager.py b/system/manager/manager.py index 34f9c513b517ae..cfddaa2f44da98 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -19,7 +19,7 @@ from openpilot.common.swaglog import cloudlog, add_file_handler from openpilot.system.version import get_build_metadata, terms_version, training_version -from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import frogpilot_boot_functions, setup_frogpilot, uninstall_frogpilot +from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import convert_params, frogpilot_boot_functions, setup_frogpilot, uninstall_frogpilot from openpilot.selfdrive.frogpilot.controls.lib.model_manager import DEFAULT_MODEL, DEFAULT_MODEL_NAME @@ -38,6 +38,7 @@ def manager_init() -> None: if build_metadata.release_channel: params.clear_all(ParamKeyType.DEVELOPMENT_ONLY) + convert_params(params, params_storage) threading.Thread(target=frogpilot_boot_functions, args=(build_metadata, params, params_storage,)).start() default_params: list[tuple[str, str | bytes]] = [ @@ -68,7 +69,6 @@ def manager_init() -> None: ("AccelerationProfile", "2"), ("AdjacentPath", "0"), ("AdjacentPathMetrics", "0"), - ("AggressiveAcceleration", "1"), ("AggressiveFollow", "1.25"), ("AggressiveJerkAcceleration", "50"), ("AggressiveJerkDanger", "100"), @@ -88,6 +88,7 @@ def manager_init() -> None: ("BlacklistedModels", ""), ("BlindSpotMetrics", "0"), ("BlindSpotPath", "1"), + ("BonusContent", "1"), ("BorderMetrics", "1"), ("CameraView", "2"), ("CarMake", ""), @@ -104,28 +105,31 @@ def manager_init() -> None: ("CertifiedHerbalistDrives", "0"), ("CertifiedHerbalistLiveTorqueParameters", ""), ("CertifiedHerbalistScore", "0"), + ("CEModelStopTime", "8"), ("CESignal", "1"), ("CESlowerLead", "1"), ("CESpeed", "0"), ("CESpeedLead", "0"), - ("CEStopLights", "1"), - ("CEStopLightsLessSensitive", "0"), ("CEStoppedLead", "1"), + ("ClairvoyantDriverCalibrationParams", ""), + ("ClairvoyantDriverDrives", "0"), + ("ClairvoyantDriverLiveTorqueParameters", ""), + ("ClairvoyantDriverScore", "0"), ("ClusterOffset", "1.015"), ("Compass", "0"), ("ConditionalExperimental", "1"), ("CrosstrekTorque", "1"), ("CurveSensitivity", "100"), ("CustomAlerts", "1"), - ("CustomColors", "1"), + ("CustomColors", "frog"), ("CustomCruise", "1"), ("CustomCruiseLong", "5"), - ("CustomIcons", "1"), + ("CustomDistanceIcons", "stock"), + ("CustomIcons", "frog-animated"), ("CustomPaths", "1"), ("CustomPersonalities", "0"), - ("CustomSignals", "1"), - ("CustomSounds", "1"), - ("CustomTheme", "1"), + ("CustomSignals", "frog"), + ("CustomSounds", "frog"), ("CustomUI", "1"), ("CydiaTune", "0"), ("DecelerationProfile", "1"), @@ -146,6 +150,7 @@ def manager_init() -> None: ("DynamicPathWidth", "0"), ("DynamicPedalsOnUI", "1"), ("EngageVolume", "100"), + ("ExperimentalGMTune", "0"), ("ExperimentalModeActivation", "1"), ("ExperimentalModels", ""), ("ExperimentalModeViaDistance", "1"), @@ -162,7 +167,7 @@ def manager_init() -> None: ("FullMap", "0"), ("GasRegenCmd", "1"), ("GMapKey", ""), - ("GoatScream", "1"), + ("GoatScream", "0"), ("GreenLightAlert", "0"), ("HideAlerts", "0"), ("HideAOLStatusBar", "0"), @@ -174,9 +179,10 @@ def manager_init() -> None: ("HideSpeedUI", "0"), ("HideUIElements", "0"), ("HolidayThemes", "1"), + ("HumanAcceleration", "1"), + ("HumanFollowing", "1"), ("IncreaseThermalLimits", "0"), ("JerkInfo", "1"), - ("KaofuiIcons", "0"), ("LaneChangeCustomizations", "1"), ("LaneChangeTime", "0"), ("LaneDetectionWidth", "60"), @@ -246,6 +252,7 @@ def manager_init() -> None: ("PauseLateralOnSignal", "0"), ("PauseLateralSpeed", "0"), ("PedalsOnUI", "1"), + ("PersonalizeOpenpilot", "1"), ("PreferredSchedule", "0"), ("PromptDistractedVolume", "100"), ("PromptVolume", "100"), @@ -309,10 +316,11 @@ def manager_init() -> None: ("SLCPriority1", "Dashboard"), ("SLCPriority2", "Offline Maps"), ("SLCPriority3", "Navigation"), - ("SmoothBraking", "1"), ("SNGHack", "1"), ("SpeedLimitChangedAlert", "1"), ("SpeedLimitController", "1"), + ("StartupMessageBottom", "so I do what I want 🐸"), + ("StartupMessageTop", "Hippity hoppity this is my property"), ("StandardFollow", "1.45"), ("StandardJerkAcceleration", "100"), ("StandardJerkDanger", "100"), @@ -348,7 +356,7 @@ def manager_init() -> None: ("WD40Drives", "0"), ("WD40LiveTorqueParameters", ""), ("WD40Score", "0"), - ("WheelIcon", "3"), + ("WheelIcon", "frog"), ("WheelSpeed", "0") ] if not PC: diff --git a/system/manager/process.py b/system/manager/process.py index 36f299ae628b22..81d8a7ffc8f099 100644 --- a/system/manager/process.py +++ b/system/manager/process.py @@ -76,6 +76,8 @@ class ManagerProcess(ABC): watchdog_seen = False shutting_down = False + started_time = 0 + @abstractmethod def prepare(self) -> None: pass @@ -88,7 +90,7 @@ def restart(self) -> None: self.stop(sig=signal.SIGKILL) self.start() - def check_watchdog(self, started: bool) -> None: + def check_watchdog(self, started: bool, params: Params) -> None: if self.watchdog_max_dt is None or self.proc is None: return @@ -101,9 +103,12 @@ def check_watchdog(self, started: bool) -> None: pass dt = time.monotonic() - self.last_watchdog_time / 1e9 + self.started_time = (self.started_time + 1) if started else 0 if dt > self.watchdog_max_dt: if self.watchdog_seen and ENABLE_WATCHDOG: + if self.started_time > 100: + sentry.capture_tmux(params) cloudlog.error(f"Watchdog timeout for {self.name} (exitcode {self.proc.exitcode}) restarting ({started=})") self.restart() else: @@ -285,7 +290,7 @@ def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params=None else: p.stop(block=False) - p.check_watchdog(started) + p.check_watchdog(started, params) for p in running: p.start() diff --git a/system/sentry.py b/system/sentry.py index 91f7a5692fe69b..f4fd577f399707 100644 --- a/system/sentry.py +++ b/system/sentry.py @@ -1,6 +1,7 @@ """Install exception handler for process crash.""" import os import sentry_sdk +import subprocess import time import traceback @@ -20,20 +21,38 @@ class SentryProject(Enum): # python project - SELFDRIVE = "https://5ad1714d27324c74a30f9c538bff3b8d@o4505034923769856.ingest.sentry.io/4505034930651136" + SELFDRIVE = "https://b42f6e8bea596ec3d7dc1d9a80280027@o4507524429185024.ingest.us.sentry.io/4507524452057088" # native project - SELFDRIVE_NATIVE = "https://5ad1714d27324c74a30f9c538bff3b8d@o4505034923769856.ingest.sentry.io/4505034930651136" + SELFDRIVE_NATIVE = "https://b42f6e8bea596ec3d7dc1d9a80280027@o4507524429185024.ingest.us.sentry.io/4507524452057088" def bind_user() -> None: sentry_sdk.set_user({"id": HARDWARE.get_serial()}) -def report_tombstone(fn: str, message: str, contents: str) -> None: - FrogPilot = "frogai" in get_build_metadata().openpilot.git_origin.lower() - if not FrogPilot or PC: - return +def capture_tmux(params) -> None: + updated = params.get("Updated", encoding='utf-8') + + try: + result = subprocess.run(['tmux', 'capture-pane', '-p', '-S', '-250'], stdout=subprocess.PIPE) + lines = result.stdout.decode('utf-8').splitlines() + + if lines: + while True: + if sentry_pinged(): + with sentry_sdk.configure_scope() as scope: + bind_user() + scope.set_extra("tmux_log", "\n".join(lines)) + sentry_sdk.capture_message(f"User's UI crashed ({updated})", level='error') + sentry_sdk.flush() + break + time.sleep(60) + + except Exception: + cloudlog.exception("Failed to capture tmux log") + +def report_tombstone(fn: str, message: str, contents: str) -> None: no_internet = 0 while True: if is_url_pingable("https://sentry.io"): @@ -128,10 +147,6 @@ def capture_exception(*args, **kwargs) -> None: save_exception(exc_text) cloudlog.error("crash", exc_info=kwargs.get('exc_info', 1)) - FrogPilot = "frogai" in get_build_metadata().openpilot.git_origin.lower() - if not FrogPilot or PC: - return - try: bind_user() sentry_sdk.capture_exception(*args, **kwargs) @@ -166,7 +181,8 @@ def set_tag(key: str, value: str) -> None: def init(project: SentryProject) -> bool: build_metadata = get_build_metadata() - if PC: + FrogPilot = "FrogAi" in build_metadata.openpilot.git_origin + if not FrogPilot or PC: return False params = Params() @@ -193,7 +209,7 @@ def init(project: SentryProject) -> bool: release=get_version(), integrations=integrations, traces_sample_rate=1.0, - max_value_length=8192, + max_value_length=98304, environment=env) build_metadata = get_build_metadata()