diff --git a/Translations/translation_EN.json b/Translations/translation_EN.json index f543e2dc60..ea6c30217e 100644 --- a/Translations/translation_EN.json +++ b/Translations/translation_EN.json @@ -68,6 +68,9 @@ }, "TooHotToStartProfileWarning": { "message": "Too hot to\nstart profile" + }, + "WarningFreefallDetected": { + "message": "Freefall\nDetected" } }, "characters": { @@ -348,6 +351,10 @@ "SolderingTipType": { "displayText": "Soldering\nTip Type", "description": "Select the tip type fitted" + }, + "FreefallDetection": { + "displayText": "Freefall\ndetect", + "description": "Power off if freefall or rapid drop is detected (requires accelerometer)" } } } diff --git a/Translations/translations_definitions.json b/Translations/translations_definitions.json index 787d14cd12..f3703c6e7e 100644 --- a/Translations/translations_definitions.json +++ b/Translations/translations_definitions.json @@ -18,7 +18,9 @@ }, { "id": "NoPowerDeliveryMessage", - "include": ["POW_PD"], + "include": [ + "POW_PD" + ], "description": "The IC required for USB-PD could not be communicated with. This is an error warning that USB-PD WILL NOT FUNCTION. Generally indicative of either a hardware or software issues." }, { @@ -56,44 +58,58 @@ { "id": "UVLOWarningString", "maxLen": 8, - "include": ["POW_DC"], + "include": [ + "POW_DC" + ], "description": "Warning text shown when the unit turns off due to undervoltage in simple mode." }, { "id": "UndervoltageString", "maxLen": 15, - "include": ["POW_DC"], + "include": [ + "POW_DC" + ], "description": "Warning text shown when the unit turns off due to undervoltage in advanced mode." }, { "id": "InputVoltageString", "maxLen": 11, "note": "Preferably end with a space", - "include": ["POW_DC"], + "include": [ + "POW_DC" + ], "description": "Prefix text for 'Input Voltage' shown before showing the input voltage reading." }, { "id": "ProfilePreheatString", "maxLen": 9, - "include": ["PROFILE_SUPPORT"], + "include": [ + "PROFILE_SUPPORT" + ], "description": "Shown in profile mode while preheating" }, { "id": "ProfileCooldownString", "maxLen": 9, - "include": ["PROFILE_SUPPORT"], + "include": [ + "PROFILE_SUPPORT" + ], "description": "Shown in profile mode while cooling down" }, { "id": "SleepingAdvancedString", "maxLen": 15, - "exclude": ["NO_SLEEP_MODE"], + "exclude": [ + "NO_SLEEP_MODE" + ], "description": "The text shown to indicate the unit is in sleep mode when the advanced view is turned on." }, { "id": "SleepingTipAdvancedString", "maxLen": 6, - "exclude": ["NO_SLEEP_MODE"], + "exclude": [ + "NO_SLEEP_MODE" + ], "description": "The prefix text shown before tip temperature when the unit is sleeping with advanced view on." }, { @@ -104,8 +120,15 @@ { "id": "TooHotToStartProfileWarning", "default": "Too hot to\nstart profile", - "include": ["PROFILE_SUPPORT"], + "include": [ + "PROFILE_SUPPORT" + ], "description": "Shown when profile mode is started while the device is too hot." + }, + { + "id": "WarningFreefallDetected", + "default": "Freefall\nDetected", + "description": "Warning shown when freefall is detected and the heater is cut off." } ], "characters": [ @@ -184,7 +207,11 @@ "id": "PowerMenu", "maxLen": 5, "maxLen2": 11, - "include": ["POW_DC", "POW_PD", "POW_QC"], + "include": [ + "POW_DC", + "POW_PD", + "POW_QC" + ], "description": "Menu for settings related to power. Main settings to do with the input voltage." }, { @@ -255,35 +282,45 @@ "id": "DCInCutoff", "maxLen": 5, "maxLen2": 11, - "include": ["POW_DC"], + "include": [ + "POW_DC" + ], "description": "When the device is powered by a battery, this adjusts the low voltage threshold for when the unit should turn off the heater to protect the battery." }, { "id": "MinVolCell", "maxLen": 4, "maxLen2": 9, - "include": ["POW_DC"], + "include": [ + "POW_DC" + ], "description": "When powered by a battery, this adjusts the minimum voltage per cell before shutdown. (This is multiplied by the cell count.)" }, { "id": "QCMaxVoltage", "maxLen": 8, "maxLen2": 15, - "include": ["POW_QC"], + "include": [ + "POW_QC" + ], "description": "This adjusts the maximum voltage the QC negotiation will adjust to. Does NOT affect USB-PD. Should be set safely based on the current rating of your power supply." }, { "id": "PDNegTimeout", "maxLen": 8, "maxLen2": 15, - "include": ["POW_PD"], + "include": [ + "POW_PD" + ], "description": "How long until firmware stops trying to negotiate for USB-PD and tries QC instead. Longer times may help dodgy / old PD adapters, faster times move onto PD quickly. Units of 100ms. Recommended to keep small values." }, { "id": "USBPDMode", "maxLen": 7, "maxLen2": 15, - "include": ["POW_PD"], + "include": [ + "POW_PD" + ], "description": "Adjusts how the USB-PD Logic selects the voltage. No Dynamic disables EPR & PPS protocols, Safe mode does not use padding resistance (will select a slightly lower voltage)." }, { @@ -320,98 +357,126 @@ "id": "ProfilePhases", "maxLen": 6, "maxLen2": 13, - "include": ["PROFILE_SUPPORT"], + "include": [ + "PROFILE_SUPPORT" + ], "description": "set the number of phases for profile mode." }, { "id": "ProfilePreheatTemp", "maxLen": 4, "maxLen2": 9, - "include": ["PROFILE_SUPPORT"], + "include": [ + "PROFILE_SUPPORT" + ], "description": "Preheat to this temperature at the start of profile mode." }, { "id": "ProfilePreheatSpeed", "maxLen": 5, "maxLen2": 11, - "include": ["PROFILE_SUPPORT"], + "include": [ + "PROFILE_SUPPORT" + ], "description": "How fast the temperature is allowed to rise during the preheat phase at the start of profile mode." }, { "id": "ProfilePhase1Temp", "maxLen": 4, "maxLen2": 9, - "include": ["PROFILE_SUPPORT"], + "include": [ + "PROFILE_SUPPORT" + ], "description": "Target temperature for the end of phase 1 of profile mode." }, { "id": "ProfilePhase1Duration", "maxLen": 4, "maxLen2": 9, - "include": ["PROFILE_SUPPORT"], + "include": [ + "PROFILE_SUPPORT" + ], "description": "Duration of phase 1 of profile mode. The phase might actually take longer if it takes longer to reach the target temperature." }, { "id": "ProfilePhase2Temp", "maxLen": 4, "maxLen2": 9, - "include": ["PROFILE_SUPPORT"], + "include": [ + "PROFILE_SUPPORT" + ], "description": "Target temperature for the end of phase 2 of profile mode." }, { "id": "ProfilePhase2Duration", "maxLen": 4, "maxLen2": 9, - "include": ["PROFILE_SUPPORT"], + "include": [ + "PROFILE_SUPPORT" + ], "description": "Duration of phase 2 of profile mode. The phase might actually take longer if it takes longer to reach the target temperature." }, { "id": "ProfilePhase3Temp", "maxLen": 4, "maxLen2": 9, - "include": ["PROFILE_SUPPORT"], + "include": [ + "PROFILE_SUPPORT" + ], "description": "Target temperature for the end of phase 3 of profile mode." }, { "id": "ProfilePhase3Duration", "maxLen": 4, "maxLen2": 9, - "include": ["PROFILE_SUPPORT"], + "include": [ + "PROFILE_SUPPORT" + ], "description": "Duration of phase 3 of profile mode. The phase might actually take longer if it takes longer to reach the target temperature." }, { "id": "ProfilePhase4Temp", "maxLen": 4, "maxLen2": 9, - "include": ["PROFILE_SUPPORT"], + "include": [ + "PROFILE_SUPPORT" + ], "description": "Target temperature for the end of phase 5 of profile mode." }, { "id": "ProfilePhase4Duration", "maxLen": 4, "maxLen2": 9, - "include": ["PROFILE_SUPPORT"], + "include": [ + "PROFILE_SUPPORT" + ], "description": "Duration of phase 5 of profile mode. The phase might actually take longer if it takes longer to reach the target temperature." }, { "id": "ProfilePhase5Temp", "maxLen": 4, "maxLen2": 9, - "include": ["PROFILE_SUPPORT"], + "include": [ + "PROFILE_SUPPORT" + ], "description": "Target temperature for the end of phase 5 of profile mode." }, { "id": "ProfilePhase5Duration", "maxLen": 4, "maxLen2": 9, - "include": ["PROFILE_SUPPORT"], + "include": [ + "PROFILE_SUPPORT" + ], "description": "Duration of phase 5 of profile mode. The phase might actually take longer if it takes longer to reach the target temperature." }, { "id": "ProfileCooldownSpeed", "maxLen": 5, "maxLen2": 11, - "include": ["PROFILE_SUPPORT"], + "include": [ + "PROFILE_SUPPORT" + ], "description": "How fast the temperature is allowed to drop during the cooldown phase at the end of profile mode." }, { @@ -424,14 +489,18 @@ "id": "SleepTemperature", "maxLen": 4, "maxLen2": 9, - "exclude": ["NO_SLEEP_MODE"], + "exclude": [ + "NO_SLEEP_MODE" + ], "description": "Temperature the device will drop down to while asleep. Typically around halfway between off and soldering temperature." }, { "id": "SleepTimeout", "maxLen": 4, "maxLen2": 9, - "exclude": ["NO_SLEEP_MODE"], + "exclude": [ + "NO_SLEEP_MODE" + ], "description": "How long of a period without movement / button-pressing is required before the device drops down to the sleep temperature." }, { @@ -444,14 +513,18 @@ "id": "HallEffSensitivity", "maxLen": 6, "maxLen2": 13, - "include": ["HALL_SENSOR"], + "include": [ + "HALL_SENSOR" + ], "description": "If the unit has a hall effect sensor (Pinecil), this adjusts how sensitive it is at detecting a magnet to put the device into sleep mode." }, { "id": "HallEffSleepTimeout", "maxLen": 10, "maxLen2": 10, - "include": ["HALL_SENSOR"], + "include": [ + "HALL_SENSOR" + ], "description": "If the unit has a hall effect sensor (Pinecil), this adjusts how long the device takes before it drops down to the sleep temperature when hall sensor is over threshold." }, { @@ -464,7 +537,9 @@ "id": "DisplayRotation", "maxLen": 6, "maxLen2": 13, - "exclude": ["NO_DISPLAY_ROTATE"], + "exclude": [ + "NO_DISPLAY_ROTATE" + ], "description": "If the display should rotate automatically or if it should be fixed for left- or right-handed mode." }, { @@ -537,7 +612,9 @@ "id": "BluetoothLE", "maxLen": 7, "maxLen2": 15, - "include": ["BLE_ENABLED"], + "include": [ + "BLE_ENABLED" + ], "description": "BLE mode: Off, full read/write access (+), or read-only mode (R) which blocks all writes over BLE." }, { @@ -593,6 +670,12 @@ "maxLen": 7, "maxLen2": 15, "description": "For manually selecting the type of tip fitted" + }, + { + "id": "FreefallDetection", + "maxLen": 7, + "maxLen2": 15, + "description": "Enable power off on freefall or rapid drop detection" } ] } diff --git a/source/Core/Inc/Settings.h b/source/Core/Inc/Settings.h index 10467fd2bd..a0e111b9fd 100644 --- a/source/Core/Inc/Settings.h +++ b/source/Core/Inc/Settings.h @@ -78,8 +78,9 @@ enum SettingsOptions { HallEffectSleepTime = 53, // Seconds (/5) timeout to sleep when hall effect over threshold SolderingTipType = 54, // Selecting the type of soldering tip fitted ReverseButtonSettings = 55, // Change the A and B button assigment in Settings menu + FreefallDetection = 56, // Power off if freefall or rapid drop is detected // - SettingsOptionsLength = 56, // End marker + SettingsOptionsLength = 57, // End marker }; // For every setting we need to store the min/max/increment values diff --git a/source/Core/Inc/Translation.h b/source/Core/Inc/Translation.h index 93799325cf..a92e163bfb 100644 --- a/source/Core/Inc/Translation.h +++ b/source/Core/Inc/Translation.h @@ -108,6 +108,7 @@ enum class SettingsItemIndex : uint8_t { SettingsReset, LanguageSwitch, SolderingTipType, + FreefallDetection, NUM_ITEMS, }; @@ -136,6 +137,7 @@ struct TranslationIndexTable { uint16_t SleepingTipAdvancedString; uint16_t DeviceFailedValidationWarning; uint16_t TooHotToStartProfileWarning; + uint16_t WarningFreefallDetected; uint16_t SettingRightChar; uint16_t SettingLeftChar; diff --git a/source/Core/Inc/main.hpp b/source/Core/Inc/main.hpp index 0ffac19dca..5b9903d114 100644 --- a/source/Core/Inc/main.hpp +++ b/source/Core/Inc/main.hpp @@ -22,6 +22,7 @@ extern TaskHandle_t pidTaskNotification; extern int32_t powerSupplyWattageLimit; extern uint8_t accelInit; extern TickType_t lastMovementTime; +extern volatile bool freefallDetected; #ifdef __cplusplus } // Accelerometer type diff --git a/source/Core/Src/settingsGUI.cpp b/source/Core/Src/settingsGUI.cpp index be283f298c..b22842135b 100644 --- a/source/Core/Src/settingsGUI.cpp +++ b/source/Core/Src/settingsGUI.cpp @@ -107,6 +107,7 @@ static void displayPowerPulseWait(void); static bool showPowerPulseOptions(void); static void displayPowerPulseDuration(void); static void displayBrightnessLevel(void); +static void displayFreefallDetection(void); static void displayInvertColor(void); static void displayLogoTime(void); @@ -444,6 +445,8 @@ const menuitem advancedMenu[] = { {SETTINGS_DESC(SettingsItemIndex::PowerPulseWait), nullptr, displayPowerPulseWait, showPowerPulseOptions, SettingsOptions::KeepAwakePulseWait, SettingsItemIndex::PowerPulseWait, 7}, /* Power Pulse Duration adjustment */ {SETTINGS_DESC(SettingsItemIndex::PowerPulseDuration), nullptr, displayPowerPulseDuration, showPowerPulseOptions, SettingsOptions::KeepAwakePulseDuration, SettingsItemIndex::PowerPulseDuration, 7}, + /* Freefall Detection */ + {SETTINGS_DESC(SettingsItemIndex::FreefallDetection), nullptr, displayFreefallDetection, nullptr, SettingsOptions::FreefallDetection, SettingsItemIndex::FreefallDetection, 7}, /* Resets settings */ {SETTINGS_DESC(SettingsItemIndex::SettingsReset), setResetSettings, noOpDisplay, nullptr, SettingsOptions::SettingsOptionsLength, SettingsItemIndex::SettingsReset, 7}, /* vvvv end of menu marker. DO NOT REMOVE vvvv */ @@ -912,6 +915,8 @@ static void displayAdvancedIDLEScreens(void) { OLED::drawCheckbox(getSettingValu static void displayAdvancedSolderingScreens(void) { OLED::drawCheckbox(getSettingValue(SettingsOptions::DetailedSoldering)); } +static void displayFreefallDetection(void) { OLED::drawCheckbox(getSettingValue(SettingsOptions::FreefallDetection)); } + #ifdef BLE_ENABLED static void displayBluetoothLE(void) { switch (getSettingValue(SettingsOptions::BluetoothLE)) { diff --git a/source/Core/Threads/GUIThread.cpp b/source/Core/Threads/GUIThread.cpp index d3d225e530..f206f44cd4 100644 --- a/source/Core/Threads/GUIThread.cpp +++ b/source/Core/Threads/GUIThread.cpp @@ -138,6 +138,9 @@ OperatingMode guiHandleDraw(void) { /*TODO*/ newMode = OperatingMode::HomeScreen; break; + case OperatingMode::FreefallWarning: + newMode = gui_freefallWarning(buttons, &context); + break; }; return newMode; } diff --git a/source/Core/Threads/MOVThread.cpp b/source/Core/Threads/MOVThread.cpp index 9739cd4670..01e8903609 100644 --- a/source/Core/Threads/MOVThread.cpp +++ b/source/Core/Threads/MOVThread.cpp @@ -26,8 +26,12 @@ #include "task.h" #define MOVFilter 8 -uint8_t accelInit = 0; -TickType_t lastMovementTime = 0; +uint8_t accelInit = 0; +TickType_t lastMovementTime = 0; +volatile bool freefallDetected = false; +static int32_t slowGravRefX = 0; +static int32_t slowGravRefY = 0; +static int32_t slowGravRefZ = 0; // Order matters for probe order, some Acceleromters do NOT like bad reads; and we have a bunch of overlap of addresses void detectAccelerometerVersion() { #ifdef ACCEL_MMA @@ -162,7 +166,8 @@ void startMOVTask(void const *argument __unused) { uint8_t currentPointer = 0; int16_t tx = 0, ty = 0, tz = 0; int32_t avgx, avgy, avgz; - Orientation rotation = ORIENTATION_FLAT; + Orientation rotation = ORIENTATION_FLAT; + uint8_t freefallCounter = 0; #ifdef ACCEL_EXITS_ON_MOVEMENT uint16_t tripCounter = 0; #endif @@ -184,7 +189,10 @@ void startMOVTask(void const *argument __unused) { datay[i] = (int32_t)ty; dataz[i] = (int32_t)tz; } - accelInit = 1; + accelInit = 1; + slowGravRefX = tx; + slowGravRefY = ty; + slowGravRefZ = tz; } currentPointer = (currentPointer + 1) % MOVFilter; avgx = avgy = avgz = 0; @@ -198,6 +206,41 @@ void startMOVTask(void const *argument __unused) { avgy /= MOVFilter; avgz /= MOVFilter; + // Update the slow gravity reference (IIR, ~3.2 second time constant) + // This is resistant to short falls and provides a stable 1g baseline for detection + slowGravRefX = (slowGravRefX * 31 + avgx) / 32; + slowGravRefY = (slowGravRefY * 31 + avgy) / 32; + slowGravRefZ = (slowGravRefZ * 31 + avgz) / 32; + + // Freefall and sustained downward motion detection (only when enabled in settings) + if (getSettingValue(SettingsOptions::FreefallDetection)) { + // Divide by 4 before squaring to prevent int32 overflow (max 32767/4=8191, 8191^2*3 ~201M) + int32_t gx = slowGravRefX / 4; + int32_t gy = slowGravRefY / 4; + int32_t gz = slowGravRefZ / 4; + int32_t refMagSq = gx * gx + gy * gy + gz * gz; + int32_t cx = (int32_t)tx / 4; + int32_t cy = (int32_t)ty / 4; + int32_t cz = (int32_t)tz / 4; + int32_t curMagSq = cx * cx + cy * cy + cz * cz; + + if (refMagSq > 0) { + if (curMagSq < refMagSq / 100) { + // Magnitude < 10% of gravity: genuine freefall + // Require 5 consecutive samples (~500ms) to reject brief bumps and normal motion + freefallCounter++; + if (freefallCounter >= 5) { + freefallDetected = true; + } + } else { + freefallCounter = 0; + } + } + } else { + freefallCounter = 0; + freefallDetected = false; + } + // Sum the deltas int32_t error = (abs(avgx - tx) + abs(avgy - ty) + abs(avgz - tz)); // So now we have averages, we want to look if these are different by more diff --git a/source/Core/Threads/UI/logic/FreefallWarning.cpp b/source/Core/Threads/UI/logic/FreefallWarning.cpp new file mode 100644 index 0000000000..f2b09df189 --- /dev/null +++ b/source/Core/Threads/UI/logic/FreefallWarning.cpp @@ -0,0 +1,15 @@ + +#include "OperatingModes.h" + +OperatingMode gui_freefallWarning(const ButtonState buttons, guiContext *cxt) { + currentTempTargetDegC = 0; // Keep heater off + + OLED::clearScreen(); + OLED::printWholeScreen(translatedString(Tr->WarningFreefallDetected)); + + // Dismiss on any button press or after 2.5 seconds + if (buttons != BUTTON_NONE || (xTaskGetTickCount() - cxt->viewEnterTime) > (TICKS_SECOND * 2 + TICKS_SECOND / 2)) { + return OperatingMode::HomeScreen; + } + return OperatingMode::FreefallWarning; +} diff --git a/source/Core/Threads/UI/logic/OperatingModes.h b/source/Core/Threads/UI/logic/OperatingModes.h index 3b38bb92ae..82883fcaea 100644 --- a/source/Core/Threads/UI/logic/OperatingModes.h +++ b/source/Core/Threads/UI/logic/OperatingModes.h @@ -39,6 +39,7 @@ enum class OperatingMode { TemperatureAdjust=7, // Set point temperature adjustment UsbPDDebug=8, // USB PD debugging information ThermalRunaway=9, // Thermal Runaway warning state. + FreefallWarning=15, // Freefall detected warning screen. }; enum class TransitionAnimation { @@ -79,6 +80,7 @@ OperatingMode performCJCC(const ButtonState buttons, guiContext *cxt); OperatingMode showDebugMenu(const ButtonState buttons, guiContext *cxt); // Debugging values OperatingMode showPDDebug(const ButtonState buttons, guiContext *cxt); // Debugging menu that shows PD adaptor info OperatingMode showWarnings(const ButtonState buttons, guiContext *cxt); // Shows user warnings if required +OperatingMode gui_freefallWarning(const ButtonState buttons, guiContext *cxt); // Freefall detected warning screen // Common helpers int8_t getPowerSourceNumber(void); // Returns number ID of power source diff --git a/source/Core/Threads/UI/logic/Sleep.cpp b/source/Core/Threads/UI/logic/Sleep.cpp index dad421ffde..a9d74ce387 100644 --- a/source/Core/Threads/UI/logic/Sleep.cpp +++ b/source/Core/Threads/UI/logic/Sleep.cpp @@ -38,6 +38,11 @@ OperatingMode gui_SolderingSleepingMode(const ButtonState buttons, guiContext *c return cxt->previousMode; } + if (freefallDetected) { + freefallDetected = false; + currentTempTargetDegC = 0; + return OperatingMode::FreefallWarning; + } if (shouldShutdown()) { // shutdown currentTempTargetDegC = 0; diff --git a/source/Core/Threads/UI/logic/Soldering.cpp b/source/Core/Threads/UI/logic/Soldering.cpp index e0d078f3a2..5f29615caa 100644 --- a/source/Core/Threads/UI/logic/Soldering.cpp +++ b/source/Core/Threads/UI/logic/Soldering.cpp @@ -153,6 +153,13 @@ OperatingMode gui_solderingMode(const ButtonState buttons, guiContext *cxt) { cxt->transitionMode = detailedView ? TransitionAnimation::None : TransitionAnimation::Right; return OperatingMode::HomeScreen; } + // Emergency freefall shutdown: always active regardless of sleep mode + if (freefallDetected) { + freefallDetected = false; + currentTempTargetDegC = 0; + setBuzzer(false); + return OperatingMode::FreefallWarning; + } #ifdef NO_SLEEP_MODE if (shouldShutdown()) { diff --git a/source/Core/Threads/UI/logic/SolderingProfile.cpp b/source/Core/Threads/UI/logic/SolderingProfile.cpp index 606175cf82..8d24ab9d74 100644 --- a/source/Core/Threads/UI/logic/SolderingProfile.cpp +++ b/source/Core/Threads/UI/logic/SolderingProfile.cpp @@ -159,6 +159,12 @@ OperatingMode gui_solderingProfileMode(const ButtonState buttons, guiContext *cx setBuzzer(false); return OperatingMode::HomeScreen; } + if (freefallDetected) { + freefallDetected = false; + currentTempTargetDegC = 0; + setBuzzer(false); + return OperatingMode::FreefallWarning; + } if (heaterThermalRunawayCounter > 8) { currentTempTargetDegC = 0; // heater control off heaterThermalRunawayCounter = 0; diff --git a/source/Core/Threads/UI/logic/utils/shouldDeviceShutdown.cpp b/source/Core/Threads/UI/logic/utils/shouldDeviceShutdown.cpp index 541a2669bb..bdb1158b4f 100644 --- a/source/Core/Threads/UI/logic/utils/shouldDeviceShutdown.cpp +++ b/source/Core/Threads/UI/logic/utils/shouldDeviceShutdown.cpp @@ -1,10 +1,13 @@ #include "OperatingModeUtilities.h" +#include "main.hpp" extern TickType_t lastMovementTime; extern TickType_t lastHallEffectSleepStart; #include "Buttons.hpp" bool shouldShutdown(void) { + // Note: freefall is handled directly in each soldering mode (returns FreefallWarning). + // shouldShutdown() is called from Sleep mode only; propagate it from there too. if (getSettingValue(SettingsOptions::ShutdownTime)) { // only allow shutdown exit if time > 0 if (lastMovementTime) { if (((TickType_t)(xTaskGetTickCount() - lastMovementTime)) > (TickType_t)(getSettingValue(SettingsOptions::ShutdownTime) * TICKS_MIN)) { diff --git a/source/Settings/settings.yaml b/source/Settings/settings.yaml index 507805aea2..81b6d72613 100644 --- a/source/Settings/settings.yaml +++ b/source/Settings/settings.yaml @@ -279,3 +279,8 @@ settings: max: 1 min: 0 name: ReverseButtonSettings + - default: 0 + increment: 1 + max: 1 + min: 0 + name: FreefallDetection