diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index c93d9f08..d4376010 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -99,8 +99,29 @@ void CrossPointSettings::validateFrontButtonMapping(CrossPointSettings& settings } } +#include + +void CrossPointSettings::loadStartupFromNvs() { + Preferences nvs; + nvs.begin("Crosspoint", true); // read-only + btnShortPower = nvs.getUChar("bSPwr", BTN_DEFAULT); + btnDoublePower = nvs.getUChar("bDPwr", BTN_DEFAULT); + useClock = nvs.getUChar("useClk", 0); + nvs.end(); +} + +void CrossPointSettings::saveStartupToNvs() const { + Preferences nvs; + nvs.begin("Crosspoint", false); // read-write + nvs.putUChar("bSPwr", btnShortPower); + nvs.putUChar("bDPwr", btnDoublePower); + nvs.putUChar("useClk", useClock); + nvs.end(); +} + bool CrossPointSettings::saveToFile() const { Storage.mkdir("/.crosspoint"); + saveStartupToNvs(); return JsonSettingsIO::saveSettings(*this, SETTINGS_FILE_JSON); } @@ -113,6 +134,7 @@ bool CrossPointSettings::loadFromFile() { bool result = JsonSettingsIO::loadSettings(*this, json.c_str(), &resave); if (result) { enforceFixedShortActions(*this); + saveStartupToNvs(); // Ensure NVS is in sync on boot if (resave) { if (saveToFile()) { LOG_DBG("CPS", "Resaved settings to update format"); diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 1797a31c..b9c44513 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -339,6 +339,8 @@ class CrossPointSettings { bool saveToFile() const; bool loadFromFile(); + void loadStartupFromNvs(); + void saveStartupToNvs() const; static void validateFrontButtonMapping(CrossPointSettings& settings); diff --git a/src/main.cpp b/src/main.cpp index dbc530ec..57b03fef 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -195,6 +195,15 @@ void setup() { powerManager.begin(); gpio_deep_sleep_hold_dis(); // Release deep sleep GPIO hold state from previous sleep cycle + const auto wakeupReason = gpio.getWakeupReason(); + + if (wakeupReason == HalGPIO::WakeupReason::AfterUSBPower) { + // If USB power caused a cold boot, go back to sleep immediately without initializing subsystems + LOG_DBG("MAIN", "Wakeup reason: After USB Power => Deep sleep"); + powerManager.startDeepSleep(gpio); + return; + } + #ifdef ENABLE_SERIAL_LOG if (gpio.isUsbConnected()) { Serial.begin(115200); @@ -206,6 +215,25 @@ void setup() { #endif LOG_INF("MAIN", "Hardware detect: %s", gpio.deviceIsX3() ? "X3" : "X4"); + LOG_DBG("MAIN", "Wakeup reason: %d, millis=%lu, rawPowerPin=%d", static_cast(wakeupReason), millis(), + digitalRead(InputManager::POWER_BUTTON_PIN) == LOW); + + // Load just the settings we need *before* initializing the SD card to speed up and reduce power on unverified wakes + SETTINGS.loadStartupFromNvs(); + + if (wakeupReason == HalGPIO::WakeupReason::PowerButton) { + LOG_DBG("MAIN", "Verifying power button press duration (required=%u ms)", + CrossPointSettings::getPowerButtonDuration()); + + // We only want to skip the hold verification (allowing a short press to wake) if the short + // press or double press actually have an action assigned, or if the clock screensaver is active. + // Otherwise, short presses from sleep should be ignored entirely and return to sleep. + bool allowShortPress = (SETTINGS.useClock != 0) || (SETTINGS.btnShortPower != CrossPointSettings::BTN_DEFAULT) || + (SETTINGS.btnDoublePower != CrossPointSettings::BTN_DEFAULT); + + gpio.verifyPowerButtonWakeup(CrossPointSettings::getPowerButtonDuration(), allowShortPress); + LOG_DBG("MAIN", "Power button verification passed, millis=%lu", millis()); + } // SD Card Initialization // We need 6 open files concurrently when parsing a new chapter @@ -216,8 +244,9 @@ void setup() { return; } - HalSystem::checkPanic(); SETTINGS.loadFromFile(); + + HalSystem::checkPanic(); HalSystem::clearPanic(); // TODO: move this to an activity when we have one to display the panic info HalClock::applyTimezone(SETTINGS.timeZone); I18N.loadSettings(); @@ -227,31 +256,6 @@ void setup() { UITheme::getInstance().reload(); ButtonNavigator::setMappedInputManager(mappedInputManager); - const auto wakeupReason = gpio.getWakeupReason(); - LOG_DBG("MAIN", "Wakeup reason: %d, millis=%lu, rawPowerPin=%d", static_cast(wakeupReason), millis(), - digitalRead(InputManager::POWER_BUTTON_PIN) == LOW); - - switch (wakeupReason) { - case HalGPIO::WakeupReason::PowerButton: { - constexpr uint16_t defaultPowerButtonDurationMs = 400; - LOG_DBG("MAIN", "Verifying power button press duration (required=%u ms, default only)", - defaultPowerButtonDurationMs); - gpio.verifyPowerButtonWakeup(defaultPowerButtonDurationMs, false); - LOG_DBG("MAIN", "Power button verification passed, millis=%lu", millis()); - break; - } - case HalGPIO::WakeupReason::AfterUSBPower: - // If USB power caused a cold boot, go back to sleep - LOG_DBG("MAIN", "Wakeup reason: After USB Power"); - powerManager.startDeepSleep(gpio); - break; - case HalGPIO::WakeupReason::AfterFlash: - // After flashing, just proceed to boot - case HalGPIO::WakeupReason::Other: - default: - break; - } - // First serial output only here to avoid timing inconsistencies for power button press duration verification LOG_DBG("MAIN", "Starting CrossPoint version " CROSSPOINT_VERSION); @@ -277,6 +281,13 @@ void setup() { APP_STATE.saveToFile(); activityManager.goToReader(path); } + + // Ensure we're not still holding the power button before leaving setup + // waitForStablePowerRelease protects against switch bounce that might register as a false double-press. + gpio.waitForStablePowerRelease(); + // Flush any pin state transitions that occurred during boot before entering the main loop + mappedInputManager.update(); + buttonEventManager.drain(); } void loop() {