diff --git a/.github/workflows/update-pr-branch.yaml b/.github/workflows/update-pr-branch.yaml new file mode 100644 index 000000000..8facfe305 --- /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 000000000..301b3dacb --- /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 000000000..8c2c35fb3 --- /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 77ceea89f..63c96555f 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 e3873266e..8375709c5 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 62c8debdb..8554646ed 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 b0f62bf74..ee3a6f86e 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 3528e98d1..8de42d598 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 fa170f99b..4686aed3b 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -94,6 +94,7 @@ class Car: # 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 c89ab953d..6773aa672 100644 --- a/selfdrive/car/ford/carcontroller.py +++ b/selfdrive/car/ford/carcontroller.py @@ -7,6 +7,8 @@ from openpilot.selfdrive.car.ford.values import CarControllerParams, FordFlags 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 @@ class CarController(CarControllerBase): 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 c1bfa579a..b1868bfa9 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 aeaca3f82..e2cfe7d62 100644 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -12,6 +12,8 @@ from openpilot.selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerP 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 @@ class CarInterface(CarInterfaceBase): 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 60ea5f870..8e7a15b48 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -7,6 +7,8 @@ from openpilot.selfdrive.car import dbc_dict, PlatformConfig, DbcDict, Platforms 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 @@ class CarControllerParams: 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 @@ class CarControllerParams: 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 @@ class CarControllerParams: 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 d57567ca3..2fec5878a 100644 --- a/selfdrive/car/honda/carcontroller.py +++ b/selfdrive/car/honda/carcontroller.py @@ -10,6 +10,8 @@ from openpilot.selfdrive.car.honda.values import CruiseButtons, VISUAL_HUD, HOND 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 @@ class CarController(CarControllerBase): 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 69ad577fd..89433fe2f 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -10,6 +10,8 @@ from openpilot.selfdrive.car import create_button_events, get_safety_config 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 3410e495a..7c2e3d8b3 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 9ffbaa848..93a9e4859 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -9,6 +9,8 @@ from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus 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 @@ class CarController(CarControllerBase): # 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 8587141b1..3d9a8fc9d 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -15,7 +15,6 @@ Ecu = car.CarParams.Ecu 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 db0449b2f..3b4b9f74f 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -22,6 +22,8 @@ from openpilot.selfdrive.controls.lib.drive_helpers import CRUISE_LONG_PRESS, V_ 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 @@ EventName = car.CarEvent.EventName 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 @@ class CarInterfaceBase(ABC): @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 9eeabca6a..cc2cbf1f5 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -6,9 +6,11 @@ from openpilot.selfdrive.car.interfaces import CarControllerBase 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 @@ PARK = car.CarState.GearShifter.park 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 @@ class CarController(CarControllerBase): 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 @@ class CarController(CarControllerBase): 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 4faa40019..98dea943f 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -223,8 +223,7 @@ class CarState(CarStateBase): 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 847c17aeb..c00a7e9e4 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -7,6 +7,8 @@ from openpilot.selfdrive.car import create_button_events, get_safety_config 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 @@ class CarInterface(CarInterfaceBase): 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 06feac6c8..b441e6f72 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -17,7 +17,6 @@ PEDAL_TRANSITION = 10. * CV.MPH_TO_MS 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 fe659b1ba..9568b51dc 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -8,6 +8,8 @@ from openpilot.selfdrive.car.interfaces import CarControllerBase 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 @@ class CarController(CarControllerBase): 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 d0be20a49..8b58769b3 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 70df9f6c5..e7ff0fcf0 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.common.swaglog import cloudlog 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.selfdrive.controls.lib.vehicle_model import VehicleModel 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 @@ LaneChangeDirection = log.LaneChangeDirection 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 @@ ENABLED_STATES = (State.preEnabled, *ACTIVE_STATES) 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 @@ class Controls: # 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 @@ class Controls: 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 @@ class Controls: 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 @@ class Controls: def state_transition(self, CS): """Compute conditional state transitions and execute actions on state transitions""" - self.v_cruise_helper.update_v_cruise(CS, self.enabled, self.is_metric, self.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 @@ class Controls: # 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 @@ class Controls: # 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 @@ class Controls: 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 @@ class Controls: 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 @@ class Controls: 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 @@ class Controls: # 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 @@ class Controls: 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 9c3ac1e6c..ce764fb09 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -46,9 +46,6 @@ class VCruiseHelper: 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 @@ class VCruiseHelper: 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 6780ba3d8..d6f5bd495 100755 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -13,9 +13,6 @@ from openpilot.common.params import Params 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 user_soft_disable_alert(alert_text_2: str) -> AlertCallbackType: 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 0dc7753bd..160dbc393 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -115,6 +115,9 @@ class LongControl: 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 6c72daabf..f4c8763b3 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 @@ class LongitudinalMpc: 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 b41379e0a..1bbaebb51 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -174,10 +174,8 @@ class LongitudinalPlanner: 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 @@ class LongitudinalPlanner: # 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 @@ class LongitudinalPlanner: 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 @@ class LongitudinalPlanner: 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 88d09cc55..208da6c81 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 a82a1aea0..691d34fda 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -214,8 +214,9 @@ class RadarD: # 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 @@ class RadarD: 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 000000000..80e70f351 --- /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 000000000..05cd8d01c 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 000000000..7882afbbb 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 000000000..c97c85cd9 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 000000000..3e3f898c3 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 000000000..133d8d34e 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 000000000..e69de29bb 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 000000000..8c2f7a54e 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 000000000..3c7d2d9eb 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 000000000..b495eeae1 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 000000000..024a0b03e 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 000000000..91eb05f12 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 000000000..3c7d2d9eb 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 000000000..b721266fb 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 2658aa9b7..000000000 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 e6f3c2c13..000000000 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 ef6fafe7b..000000000 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 ef6fafe7b..000000000 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 b58a7268b..000000000 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 f5116df86..000000000 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 4c9481352..000000000 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 8ceed2389..000000000 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 48dbe09b7..000000000 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 3e4627989..000000000 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 4cb158641..000000000 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 4e5b747cb..000000000 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 ef69b98e9..000000000 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 ee9b71114..000000000 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 2cc81a700..000000000 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 2cc81a700..000000000 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 549e2bedb..000000000 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 b635c5778..000000000 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 a2f508f7a..000000000 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 5158c0f30..000000000 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 c0e2a8771..000000000 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 5932e83b0..000000000 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 81dcc4bd9..000000000 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 108f7cf19..000000000 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 c8b61322c..000000000 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 f038f1316..000000000 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 d02180baa..000000000 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 000000000..05cd8d01c 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 000000000..6b20a726e 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 000000000..7882afbbb 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 000000000..c97c85cd9 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 000000000..712255984 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 000000000..3e3f898c3 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 2658aa9b7..000000000 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 43e0b446f..000000000 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 7c102456e..000000000 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 e8e147976..000000000 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 b59b003f9..000000000 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 c3c1d204e..000000000 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 42d005538..000000000 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 52da0a46a..000000000 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 000000000..13c36a7e2 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 fb77ec3ca..000000000 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 897ad3d2a..000000000 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 57c5a67e7..000000000 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 481e25772..000000000 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 b59f7bc65..000000000 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 9930ea8bd..000000000 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 000000000..0ccd94030 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 000000000..37dc6b906 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 000000000..f29c4516c 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 000000000..006a84cdc 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 000000000..43b56551e 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 000000000..9c9310b0c 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 000000000..cac4db6d4 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 000000000..9f52faf9e 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 000000000..e04262b88 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 000000000..3f09a35a7 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 000000000..3561616fb 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 4a0371ccf..000000000 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 aa9779fc7..000000000 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 b624d00b9..000000000 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 4c2e1e046..000000000 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 bc392ed13..000000000 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 b9fce6126..69dcd6519 100644 --- a/selfdrive/frogpilot/controls/frogpilot_planner.py +++ b/selfdrive/frogpilot/controls/frogpilot_planner.py @@ -3,76 +3,47 @@ import cereal.messaging as messaging 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 @@ class FrogPilotPlanner: 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 @@ class FrogPilotPlanner: 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) + self.taking_curve_quickly = False - 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 + 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: - 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 + self.frogpilot_vcruise.mtsc_target = v_cruise + self.frogpilot_vcruise.vtsc_target = v_cruise - 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) - 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 + 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.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.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.conditionalExperimentalActive = self.cem.experimental_mode + frogpilotPlan.alwaysOnLateralActive = self.always_on_lateral_active - 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.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.forcingStop = self.forcing_stop + frogpilotPlan.experimentalMode = self.cem.experimental_mode or self.frogpilot_vcruise.slc.experimental_mode - frogpilotPlan.greenLight = not self.model_stopped - frogpilotPlan.redLight = self.cem.stop_light_detected + frogpilotPlan.forcingStop = self.frogpilot_vcruise.forcing_stop + + 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 e5e304670..a15dad77f 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 @@ class ConditionalExperimentalMode: 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 @@ class ConditionalExperimentalMode: 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 000000000..7aa23ef44 --- /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 000000000..ae77c0514 --- /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 000000000..25b23638c --- /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 000000000..bc51e20d4 --- /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 911416253..bba42100b 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 shutil import socket import subprocess import sys +import tarfile import threading import time import urllib.error @@ -19,23 +21,10 @@ from openpilot.common.params_pyx import Params, ParamKeyType, UnknownKeyName 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_frogpilot_toggles(): 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: + value = params_storage.get(key) + value = value.decode('utf-8') if isinstance(value, bytes) else value + + if isinstance(value, str) and value.replace('.', '', 1).isdigit(): + value = float(value) if '.' in value else int(value) + + 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) + + except (UnknownKeyName, ValueError): pass - version = 8 + for key in ["CustomColors", "CustomDistanceIcons", "CustomIcons", "CustomSignals", "CustomSounds", "WheelIcon"]: + remove_param(key) + print("Param conversion completed") + +def delete_file(file): try: - if params_storage.check_key("ParamConversionVersion") and params_storage.get_int("ParamConversionVersion") == version: - print("Params already converted, moving on.") - return - except UnknownKeyName: - pass - - print("Converting params...") - convert_param("ModelSelector", lambda: params.put_nonblocking("ModelManagement", "True")) - convert_param("DragonPilotTune", lambda: params.put_nonblocking("FrogsGoMooTune", "True")) - - print("Params successfully converted!") - params_storage.put_int_nonblocking("ParamConversionVersion", version) + 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): - convert_params(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) - remount_root = ['sudo', 'mount', '-o', 'remount,rw', '/'] + 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) + + 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 000000000..d23ce9665 --- /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 4c99b8756..d567d83b1 100644 --- a/selfdrive/frogpilot/controls/lib/frogpilot_variables.py +++ b/selfdrive/frogpilot/controls/lib/frogpilot_variables.py @@ -5,16 +5,23 @@ from types import SimpleNamespace 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 @@ class FrogPilotVariables: 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 @@ class FrogPilotVariables: 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 @@ class FrogPilotVariables: 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 @@ class FrogPilotVariables: 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 @@ class FrogPilotVariables: 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 @@ class FrogPilotVariables: 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 @@ class FrogPilotVariables: 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 @@ class FrogPilotVariables: 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 @@ class FrogPilotVariables: 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")): + 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) + try: + self.params.check_key(toggle.part_model_param + "CalibrationParams") + except UnknownKeyName: + toggle.part_model_param = "" + else: 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: - 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(',') - else: - toggle.radarless_model = False + 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 @@ class FrogPilotVariables: 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 @@ class FrogPilotVariables: 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 000000000..8b84f8247 --- /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 ea291d763..19fe73995 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 @@ params_memory = Params("/dev/shm/params") 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 bda0cbe94..f3d7f6828 100644 --- a/selfdrive/frogpilot/controls/lib/model_manager.py +++ b/selfdrive/frogpilot/controls/lib/model_manager.py @@ -3,296 +3,232 @@ import os 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 + 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('-', '') -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) +class ModelManager: + def __init__(self): + self.params = Params() + self.params_memory = Params("/dev/shm/params") -def verify_download(file_path, model_url): - if not os.path.exists(file_path): - return False + self.cancel_download_param = "CancelModelDownload" + self.download_param = "ModelToDownload" + self.download_progress_param = "ModelDownloadProgress" - remote_file_size = get_remote_file_size(model_url) - if remote_file_size is None: - return False + def handle_verification_failure(self, model, model_path): + if self.params_memory.get_bool(self.cancel_download_param): + return - return remote_file_size == os.path.getsize(file_path) + 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) -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" + 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): - 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 + handle_error(model_path, "Model already exists...", "Model already exists...", self.download_param, self.download_progress_param, self.params_memory) + return - 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 + 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 + + 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) + + 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: - 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 + self.handle_verification_failure(model_to_download, model_path) - return all_models_downloaded + 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(model_info, repo_url, params, params_memory): - available_models = [] - available_model_names = [] - experimental_models = [] - navigation_models = [] - radarless_models = [] + 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']) + for model in model_info: + available_models.append(model['id']) + available_model_names.append(model['name']) - 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 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']) - 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)) + 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.") -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)", "")) + if available_models: + models_downloaded = self.are_all_models_downloaded(available_models, available_model_names) + self.params.put_bool_nonblocking("ModelsDownloaded", models_downloaded) - available_models = params.get("AvailableModels", encoding='utf-8') - if available_models is None: - return + 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_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}") + for model in available_models: + model_path = os.path.join(MODELS_PATH, f"{model}.thneed") + model_url = f"{self.repo_url}Models/{model}.thneed" -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...") + 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 -def update_models(params, params_memory, boot_run=True): - try: + 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") + + def queue_model_download(self, model, model_name=None): + while self.params_memory.get(self.download_param, encoding='utf-8'): + time.sleep(1) + + self.params_memory.put(self.download_param, model) + if model_name: + self.params_memory.put(self.download_progress_param, f"Downloading {model_name}...") + + def validate_models(self): + current_model = self.params.get("Model", encoding='utf-8') + current_model_name = self.params.get("ModelName", encoding='utf-8') + + if "(Default)" in current_model_name and current_model_name != DEFAULT_MODEL_NAME: + self.params.put_nonblocking("ModelName", current_model_name.replace(" (Default)", "")) + + available_models = self.params.get("AvailableModels", encoding='utf-8') + if not available_models: + return + + 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: - copy_default_model() - validate_models(params) - - repo_url = get_repository_url() - if repo_url is None: - return - - model_info = fetch_models(f"{repo_url}Versions/model_names_{VERSION}.json") - if model_info is None: - return - - update_model_params(model_info, repo_url, params, params_memory) - except subprocess.CalledProcessError as e: - print(f"Failed to update models. Error: {e}") - -def download_all_models(params, params_memory): - copy_default_model() - - 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 - - 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 - - update_model_params(model_info, repo_url, params, params_memory) - - available_models = params.get("AvailableModels", encoding='utf-8').split(',') - available_model_names = params.get("AvailableModelsNames", encoding='utf-8').split(',') - - for model in available_models: - if params_memory.get_bool("CancelModelDownload"): - handle_error(None, "Download cancelled...", "Download cancelled...", params_memory) - 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: + 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) - - all_downloaded = False - while not all_downloaded: - if params_memory.get_bool("CancelModelDownload"): - handle_error(None, "Download cancelled...", "Download cancelled...", params_memory) + self.validate_models() + elif self.repo_url is None: + print("GitHub and GitLab are offline...") 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) + 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 + + 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 + + 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 bba906f87..50f7a810c 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 @@ class SpeedLimitController: 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 ec4d3ab13..7a1e31045 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 @@ class ThemeManager: 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, 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 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 self.previous_theme_id != 0: - self.params_memory.put_int("CurrentHolidayTheme", "0") - update_frogpilot_toggles() - self.previous_theme_id = 0 + 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 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 + + 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 f1f07afa5..0e3c4ce38 100644 --- a/selfdrive/frogpilot/frogpilot_process.py +++ b/selfdrive/frogpilot/frogpilot_process.py @@ -10,26 +10,30 @@ from openpilot.system.hardware import HARDWARE 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 1d2935e2c..2b763e149 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 @@ MAPD_PATH = '/data/media/0/osm/mapd' 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 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 not os.path.exists(VERSION_PATH): + if download(current_version): + continue + 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 182bedc32..c80929751 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 d4eb19649..27d5ddb98 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(); + 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(); - params.putBoolNonBlocking("ModelsDownloaded", false); + params.putBoolNonBlocking("ModelsDownloaded", false); + deleteModelBtn->setValue(tr("Deleting...")); - deleteModelBtn->setValue(tr("Deleting...")); + QFile::remove(modelDir.absoluteFilePath(labelToFileMap[selectedModel])); + deleteModelBtn->setValue(tr("Deleted!")); - QFile::remove(modelDir.absoluteFilePath(labelToFileMap[selectedModel])); + util::sleep_for(1000); + deleteModelBtn->setValue(""); + modelDeleting = false; - deleteModelBtn->setValue(tr("Deleted!")); + QStringList modelFiles = modelDir.entryList({"*.thneed"}, QDir::Files); + modelFiles.removeAll(currentModel); - std::this_thread::sleep_for(std::chrono::seconds(2)); - deleteModelBtn->setValue(""); - modelDeleting = false; - - std::string currentModel = params.get("Model") + ".thneed"; - QStringList modelFiles = modelDir.entryList({"*.thneed"}, QDir::Files); - modelFiles.removeAll(QString::fromStdString(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(); } - QTimer::singleShot(2000, this, [=]() { - allModelsDownloading = false; - cancellingDownload = false; - modelDownloading = false; - downloadAllModelsBtn->setValue(""); - modelsDownloaded = params.getBool("ModelsDownloaded"); - paramsMemory.remove("CancelModelDownload"); - update(); - }); + downloadAllModelsBtn->setValue(progress); + paramsMemory.remove("CancelModelDownload"); paramsMemory.remove("ModelDownloadProgress"); - checkDownloadTimer->stop(); - checkDownloadTimer->deleteLater(); + progressTimer->stop(); + progressTimer->deleteLater(); + + QTimer::singleShot(2000, this, [=]() { + cancellingDownload = false; + modelDownloading = false; + + paramsMemory.remove("DownloadAllModels"); + + 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 5b1b6f36f..65dadcc4e 100644 --- a/selfdrive/frogpilot/ui/qt/offroad/control_settings.h +++ b/selfdrive/frogpilot/ui/qt/offroad/control_settings.h @@ -35,18 +35,22 @@ private: 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 @@ private: bool cancellingDownload; bool customPersonalitiesOpen; bool disableOpenpilotLongitudinal; + bool drivingPersonalitiesOpen; bool hasAutoTune; bool hasCommaNNFFSupport; bool hasNNFFLog; @@ -81,6 +86,8 @@ private: bool hasPCMCruise; bool hasDashSpeedLimits; bool haveModelsDownloaded; + bool iconsDownloading; + bool iconsDownloaded; bool isGM; bool isHKGCanFd; bool isMetric = params.getBool("IsMetric"); @@ -91,8 +98,11 @@ private: 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 6a5c84ba4..034561171 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 abaa3c6e5..0f8374980 100644 --- a/selfdrive/frogpilot/ui/qt/offroad/vehicle_settings.h +++ b/selfdrive/frogpilot/ui/qt/offroad/vehicle_settings.h @@ -31,7 +31,7 @@ private: 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 47a47130f..dc75b0000 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 4726ab69a..e6473917d 100644 --- a/selfdrive/frogpilot/ui/qt/offroad/visual_settings.h +++ b/selfdrive/frogpilot/ui/qt/offroad/visual_settings.h @@ -13,32 +13,58 @@ public: 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 63cf81ba1..5cc6a7b0d 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 cf5e54702..87fb8db13 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 @@ public: } } + 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 d46fea28d..a40f63737 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 @@ class Calibrator: # 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 @@ class Calibrator: 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 2c09d0b37..7617716dc 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -54,7 +54,8 @@ class TorqueBuckets(PointBuckets): 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 @@ class TorqueEstimator(ParameterEstimator): # 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 @@ class TorqueEstimator(ParameterEstimator): 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 7ccc2250f..ba1dd7cb7 100644 --- a/selfdrive/modeld/fill_model_msg.py +++ b/selfdrive/modeld/fill_model_msg.py @@ -8,6 +8,7 @@ SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') 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 992d58238..fd45e761d 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -35,6 +35,7 @@ SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') 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 @@ MODEL_PATHS = { 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 @@ class ModelState: '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 @@ class ModelState: 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 @@ class ModelState: 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 000000000..c7fd56984 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 d01e456d3..20c902450 100644 --- a/selfdrive/modeld/parse_model_outputs.py +++ b/selfdrive/modeld/parse_model_outputs.py @@ -81,14 +81,14 @@ class Parser: 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 @@ class Parser: 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 1e57ad3e7..d52ee162b 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 956c1d54b..ef29bb988 100644 --- a/selfdrive/navd/map_renderer.h +++ b/selfdrive/navd/map_renderer.h @@ -43,10 +43,8 @@ private: 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 0ef74d9f4..6e9888e4c 100755 --- a/selfdrive/navd/navd.py +++ b/selfdrive/navd/navd.py @@ -58,14 +58,15 @@ class RouteEngine: 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 5b89fecc3..4ae9b7b42 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 d1ac768e9..2e2145eb5 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 3ba8f75c9..69430a753 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...")); + 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...")); - QDir dirToDelete(backupDir.absoluteFilePath(selection)); + 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...")); + } - 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...")); + 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 command = "rsync -av --delete -l --exclude='.overlay_consistent' " + sourcePath + "/ " + targetPath + "/"; - int result = std::system(command.c_str()); + 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 (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 (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 (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 1914f5943..355c71754 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 65cbb86e0..0ee444355 100644 --- a/selfdrive/ui/qt/onroad/alerts.h +++ b/selfdrive/ui/qt/onroad/alerts.h @@ -12,6 +12,9 @@ public: void updateState(const UIState &s); void clear(); + // FrogPilot variables + int alert_height; + protected: struct Alert { QString text1; @@ -35,14 +38,14 @@ protected: }; 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 e610e6891..daa3f202b 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,55 +889,97 @@ void AnnotatedCameraWidget::paintFrogPilotWidgets(QPainter &painter, const UISce turnSignalLeft = scene.turn_signal_left; turnSignalRight = scene.turn_signal_right; - if (customSignals != 0 && (turnSignalLeft || turnSignalRight) && !bigMapOpen) { + + useSI = scene.use_si; + + useStockColors = scene.use_stock_colors; +} + +void AnnotatedCameraWidget::updateSignals() { + blindspotImages.clear(); + regularImages.clear(); + + const QString signalFolderPath = "../frogpilot/assets/active_theme/signals/"; + QDir directory(signalFolderPath); + + QFileInfoList fileList = directory.entryInfoList({"turn_signal_*.png"}, QDir::Files); + + 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 { + regularImages.push_back(pixmap); + flippedImages.push_back(pixmap.transformed(flipTransform)); + } + } + + regularImages.insert(regularImages.end(), flippedImages.begin(), flippedImages.end()); + + QFileInfoList nonPngFileList = directory.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name); + + 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(); + + totalFrames = regularImages.size() / 2; + turnSignalAnimation = true; + } else { + signalWidth = 0; + signalHeight = 0; + + 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(totalFrames * 11); // 440 milliseconds per loop; syncs up perfectly with my 2019 Lexus ES 350 turn signal clicks + animationTimer->start(signalAnimationLength); } 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; - - QString themePath; - - if (currentHolidayTheme != 0) { - themePath = QString("../frogpilot/assets/holiday_themes/%1/images").arg( - holidayThemeConfiguration.find(currentHolidayTheme) != holidayThemeConfiguration.end() ? - std::get<0>(holidayThemeConfiguration[currentHolidayTheme]) : ""); - } else { - themePath = QString("../frogpilot/assets/custom_themes/%1/images").arg( - themeConfiguration.find(customSignals) != themeConfiguration.end() ? - std::get<0>(themeConfiguration[customSignals]) : ""); - } - - const QStringList imagePaths = { - themePath + "/turn_signal_1.png", - themePath + "/turn_signal_2.png", - themePath + "/turn_signal_3.png", - themePath + "/turn_signal_4.png" - }; - - signalImgVector.clear(); - signalImgVector.reserve(2 * imagePaths.size() + 2); - - for (const QString &imagePath : imagePaths) { - QPixmap pixmap(imagePath); - signalImgVector.push_back(pixmap); - signalImgVector.push_back(pixmap.transformed(QTransform().scale(-1, 1))); - } - - const QPixmap blindSpotPixmap(themePath + "/turn_signal_1_red.png"); - signalImgVector.push_back(blindSpotPixmap); - signalImgVector.push_back(blindSpotPixmap.transformed(QTransform().scale(-1, 1))); - - totalFrames = 8; - } } Compass::Compass(QWidget *parent) : QWidget(parent) { @@ -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; - drawSignal(turnSignalLeft, leftSignalXPosition, false, blindSpotLeft); - drawSignal(turnSignalRight, rightSignalXPosition, true, blindSpotRight); + 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); + + 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 2ab4a9717..1f86892dd 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 @@ private: // 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 @@ private: bool speedLimitChanged; bool speedLimitController; bool trafficModeActive; + bool turnSignalAnimation; bool turnSignalLeft; bool turnSignalRight; + bool useStockColors; bool useSI; bool useViennaSLCSign; bool vtscControllingCurve; @@ -147,38 +151,38 @@ private: 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 @@ protected: 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 c318a004d..b0f4c77e9 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; + 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 (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; + if (rotating_wheel && steering_angle_deg != scene.steering_angle_deg) { + steering_angle_deg = scene.steering_angle_deg; update(); - } else if (rotatingWheel && steeringAngleDeg != scene.steering_angle_deg) { - steeringAngleDeg = 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); + + connect(this, &QPushButton::pressed, [this] {params_memory.putBool("OnroadDistanceButtonPressed", true);}); + connect(this, &QPushButton::released, [this] {params_memory.putBool("OnroadDistanceButtonPressed", false);}); } -void DistanceButton::buttonPressed() { - paramsMemory.putBool("OnroadDistanceButtonPressed", true); -} - -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; - - int profile = trafficModeActive ? 0 : personality; - std::tie(profileImage, profileText) = (scene.use_kaofui_icons ? profileDataKaofui : profileData)[profile]; - - transitionTimer.restart(); - update(); - } else if (transitionTimer.isValid()) { - update(); - } else { + if (!state_changed) { return; } + + personality = static_cast(scene.personality) + 1; + traffic_mode_active = scene.traffic_mode_active; + + 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 { + 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 6ccfc1a33..c0547f18d 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 @@ private: bool experimental_mode; bool engageable; - // FrogPilot variables - bool alwaysOnLateralActive; - bool bigMap; - bool conditionalExperimental; - bool mapOpen; - bool navigateOnOpenpilot; - bool rotatingWheel; - bool trafficModeActive; + // FrogPilot widgets + void updateBackgroundColor(); - int conditionalStatus; - int randomEvent; - int steeringAngleDeg; - int wheelIcon; - int wheelIconGif; + // FrogPilot variables + 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; - QMap wheelImages; - QMap wheelImagesGif; + QLabel *gif_label; - Params paramsMemory{"/dev/shm/params"}; + QMovie *gif; + + QPixmap img; + + 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"}; + + bool traffic_mode_active; + bool use_gif; int personality; - QElapsedTimer transitionTimer; + QLabel *gif_label; + QPixmap profile_image; - QPixmap profileImage; - QString profileText; - - Params paramsMemory{"/dev/shm/params"}; - - 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"} - }; - - 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 f1fcbf4ec..c7fb1d347 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 9fde190b0..d5206b624 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); - 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)}}}, - }; + 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"; - 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"}; + randomEventGifPath = "../frogpilot/assets/random_events/icons/button_home.gif"; +} - 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::showEvent(QShowEvent *event) { + updateIcon(home_label, home_gif, homeGifPath, home_btn, homePngPath, isHomeGif); + updateIcon(settings_label, settings_gif, settingsGifPath, settings_btn, settingsPngPath, isSettingsGif); +} + +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 + 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")}, 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)); + + // FrogPilot variables 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; - } 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; - } - - auto frogpilotDeviceState = sm["frogpilotDeviceState"].getFrogpilotDeviceState(); - 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 e2263f799..6fa7bac1d 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 @@ protected: 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; - std::unordered_map>> holidayThemeConfiguration; - std::unordered_map holiday_flag_imgs; - std::unordered_map holiday_home_imgs; - std::unordered_map holiday_settings_imgs; + QLabel *home_label; + QLabel *settings_label; - std::vector currentColors; + QMovie *home_gif; + QMovie *settings_gif; + + 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 4ff0de29f..a69442e20 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 089e54e4a..d321137ef 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 @@ private: 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 f9924d0e6..d7463b81e 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.common.swaglog import cloudlog 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 @@ class Soundd: # 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 @@ class Soundd: 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 @@ class Soundd: 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 @@ class Soundd: 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 ab275bc81..6b0a9befb 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 f22420b90..646ce950f 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 @@ signals: // FrogPilot signals void driveRated(); void reviewModel(); + private slots: void update(); diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index f6e5abd3d..222bdef2a 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 34f9c513b..cfddaa2f4 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -19,7 +19,7 @@ from openpilot.system.athena.registration import register, UNREGISTERED_DONGLE_I 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 36f299ae6..81d8a7ffc 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 @@ class ManagerProcess(ABC): 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 @@ class ManagerProcess(ABC): 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 91f7a5692..f4fd577f3 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 @@ CRASHES_DIR = "/data/crashes/" 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()