diff --git a/open-x4-sdk b/open-x4-sdk index bd4e6707..82e6f846 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit bd4e6707503ab9c97d13ee0d8f8c69e9ff03cd12 +Subproject commit 82e6f846bc427c130a3ca9ecf0b4d27214699d4e diff --git a/src/activities/settings/FormatSDCardActivity.cpp b/src/activities/settings/FormatSDCardActivity.cpp new file mode 100644 index 00000000..b26ccdcc --- /dev/null +++ b/src/activities/settings/FormatSDCardActivity.cpp @@ -0,0 +1,134 @@ +#include "FormatSDCardActivity.h" + +#include +#include +#include + +#include "MappedInputManager.h" +#include "fontIds.h" + +void FormatSDCardActivity::taskTrampoline(void* param) { + auto* self = static_cast(param); + self->displayTaskLoop(); +} + +void FormatSDCardActivity::onEnter() { + ActivityWithSubactivity::onEnter(); + renderingMutex = xSemaphoreCreateMutex(); + updateRequired = true; + + xTaskCreate(&FormatSDCardActivity::taskTrampoline, "FormatSDCardTask", + 4096, // Stack size + this, // Parameters + 1, // Priority + &displayTaskHandle // Task handle + ); +} + +void FormatSDCardActivity::onExit() { + ActivityWithSubactivity::onExit(); + + xSemaphoreTake(renderingMutex, portMAX_DELAY); + if (displayTaskHandle) { + vTaskDelete(displayTaskHandle); + displayTaskHandle = nullptr; + } + vSemaphoreDelete(renderingMutex); + renderingMutex = nullptr; +} + +void FormatSDCardActivity::displayTaskLoop() { + while (true) { + if (updateRequired) { + updateRequired = false; + xSemaphoreTake(renderingMutex, portMAX_DELAY); + render(); + xSemaphoreGive(renderingMutex); + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +void FormatSDCardActivity::render() { + if (subActivity) return; + + renderer.clearScreen(); + + renderer.drawCenteredText(UI_12_FONT_ID, 15, "Format SD Card", true, EpdFontFamily::BOLD); + + if (state == WAITING_CONFIRMATION) { + renderer.drawCenteredText(UI_10_FONT_ID, 150, "WARNING!", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 200, "This will ERASE ALL DATA"); + renderer.drawCenteredText(UI_10_FONT_ID, 230, "on the SD card including:"); + renderer.drawCenteredText(UI_10_FONT_ID, 270, "- All books and documents"); + renderer.drawCenteredText(UI_10_FONT_ID, 300, "- Reading progress"); + renderer.drawCenteredText(UI_10_FONT_ID, 330, "- Cached data"); + renderer.drawCenteredText(UI_10_FONT_ID, 380, "This action CANNOT be undone."); + + const auto labels = mappedInput.mapLabels("Cancel", "FORMAT", "", ""); + renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + } else if (state == FORMATTING) { + renderer.drawCenteredText(UI_10_FONT_ID, 300, "Formatting...", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 350, "Please wait, do not power off"); + } else if (state == SUCCESS) { + renderer.drawCenteredText(UI_10_FONT_ID, 280, "Format Complete!", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 330, "SD card has been formatted."); + renderer.drawCenteredText(UI_10_FONT_ID, 380, "Device will restart..."); + } else if (state == FAILED) { + renderer.drawCenteredText(UI_10_FONT_ID, 300, "Format Failed", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_10_FONT_ID, 350, "Please try again or check SD card."); + + const auto labels = mappedInput.mapLabels("« Back", "", "", ""); + renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + } + + renderer.displayBuffer(); +} + +void FormatSDCardActivity::loop() { + if (subActivity) { + subActivity->loop(); + return; + } + + if (state == WAITING_CONFIRMATION) { + if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + state = FORMATTING; + render(); // Render synchronously to show "Formatting..." before we start + xSemaphoreGive(renderingMutex); + performFormat(); + } + if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { + goBack(); + } + return; + } + + if (state == FAILED) { + if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { + goBack(); + } + return; + } + + if (state == SUCCESS) { + // Auto-restart after brief delay + vTaskDelay(2000 / portTICK_PERIOD_MS); + esp_restart(); + } +} + +void FormatSDCardActivity::performFormat() { + Serial.printf("[%lu] [FORMAT] Starting SD card format...\n", millis()); + + // Call the format method on SDCardManager + bool success = SdMan.format(&Serial); + + xSemaphoreTake(renderingMutex, portMAX_DELAY); + state = success ? SUCCESS : FAILED; + xSemaphoreGive(renderingMutex); + updateRequired = true; + + Serial.printf("[%lu] [FORMAT] Format %s\n", millis(), success ? "succeeded" : "failed"); +} diff --git a/src/activities/settings/FormatSDCardActivity.h b/src/activities/settings/FormatSDCardActivity.h new file mode 100644 index 00000000..a9ad7185 --- /dev/null +++ b/src/activities/settings/FormatSDCardActivity.h @@ -0,0 +1,32 @@ +#pragma once +#include +#include +#include + +#include + +#include "activities/ActivityWithSubactivity.h" + +class FormatSDCardActivity final : public ActivityWithSubactivity { + enum State { WAITING_CONFIRMATION, FORMATTING, SUCCESS, FAILED }; + + TaskHandle_t displayTaskHandle = nullptr; + SemaphoreHandle_t renderingMutex = nullptr; + bool updateRequired = false; + const std::function goBack; + State state = WAITING_CONFIRMATION; + + static void taskTrampoline(void* param); + [[noreturn]] void displayTaskLoop(); + void render(); + void performFormat(); + + public: + explicit FormatSDCardActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + const std::function& goBack) + : ActivityWithSubactivity("FormatSDCard", renderer, mappedInput), goBack(goBack) {} + void onEnter() override; + void onExit() override; + void loop() override; + bool preventAutoSleep() override { return state == FORMATTING; } +}; diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index efa0b9e1..0e0d4377 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -7,13 +7,14 @@ #include "CalibreSettingsActivity.h" #include "CrossPointSettings.h" +#include "FormatSDCardActivity.h" #include "MappedInputManager.h" #include "OtaUpdateActivity.h" #include "fontIds.h" // Define the static settings list namespace { -constexpr int settingsCount = 20; +constexpr int settingsCount = 21; const SettingInfo settingsList[settingsCount] = { // Should match with SLEEP_SCREEN_MODE SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}), @@ -42,7 +43,8 @@ const SettingInfo settingsList[settingsCount] = { SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency, {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}), SettingInfo::Action("Calibre Settings"), - SettingInfo::Action("Check for updates")}; + SettingInfo::Action("Check for updates"), + SettingInfo::Action("Format SD Card")}; } // namespace void SettingsActivity::taskTrampoline(void* param) { @@ -155,6 +157,14 @@ void SettingsActivity::toggleCurrentSetting() { updateRequired = true; })); xSemaphoreGive(renderingMutex); + } else if (strcmp(setting.name, "Format SD Card") == 0) { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + exitActivity(); + enterNewActivity(new FormatSDCardActivity(renderer, mappedInput, [this] { + exitActivity(); + updateRequired = true; + })); + xSemaphoreGive(renderingMutex); } } else { // Only toggle if it's a toggle type and has a value pointer diff --git a/src/main.cpp b/src/main.cpp index 8a7c3b91..5229d3c1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -150,6 +151,11 @@ void enterNewActivity(Activity* activity) { // Verify long press on wake-up from deep sleep void verifyWakeupLongPress() { + // Skip verification for software resets (for example when calling esp_restart after sd card format) + if (esp_reset_reason() == ESP_RST_SW) { + return; + } + // Give the user up to 1000ms to start holding the power button, and must hold for SETTINGS.getPowerButtonDuration() const auto start = millis(); bool abort = false;