From f4ced6ac7bdedebd7a7fa56c50b1e7edea3f65d5 Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Wed, 14 Jan 2026 01:36:37 +0100 Subject: [PATCH 01/11] feat: format sd card screen --- .../settings/FormatSDCardActivity.cpp | 137 ++++++++++++++++++ .../settings/FormatSDCardActivity.h | 37 +++++ src/activities/settings/SettingsActivity.cpp | 12 +- 3 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 src/activities/settings/FormatSDCardActivity.cpp create mode 100644 src/activities/settings/FormatSDCardActivity.h diff --git a/src/activities/settings/FormatSDCardActivity.cpp b/src/activities/settings/FormatSDCardActivity.cpp new file mode 100644 index 00000000..e4698065 --- /dev/null +++ b/src/activities/settings/FormatSDCardActivity.cpp @@ -0,0 +1,137 @@ +#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(); + + const auto pageWidth = renderer.getScreenWidth(); + + 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; + xSemaphoreGive(renderingMutex); + updateRequired = true; + vTaskDelay(10 / portTICK_PERIOD_MS); + 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..3f87511b --- /dev/null +++ b/src/activities/settings/FormatSDCardActivity.h @@ -0,0 +1,37 @@ +#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 f22850a9..9f959139 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -7,6 +7,7 @@ #include "CalibreSettingsActivity.h" #include "CrossPointSettings.h" +#include "FormatSDCardActivity.h" #include "MappedInputManager.h" #include "OtaUpdateActivity.h" #include "fontIds.h" @@ -41,7 +42,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) { @@ -154,6 +156,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 From 3b5aadfc8aae025ca402935d05243f7b7a91e776 Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Thu, 15 Jan 2026 00:06:55 +0100 Subject: [PATCH 02/11] Update SettingsActivity.cpp --- src/activities/settings/SettingsActivity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 9f959139..18536dab 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -14,7 +14,7 @@ // Define the static settings list namespace { -constexpr int settingsCount = 19; +constexpr int settingsCount = 20; const SettingInfo settingsList[settingsCount] = { // Should match with SLEEP_SCREEN_MODE SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}), From 8aecf5b73a8bf7733ea407a53af6a009f3466e0c Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Thu, 15 Jan 2026 00:09:00 +0100 Subject: [PATCH 03/11] Update SettingsActivity.cpp --- src/activities/settings/SettingsActivity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 5499aaff..0e0d4377 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -14,7 +14,7 @@ // 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"}), From f30b47ec6905e7708bdce7e0c3f3acbad2f838f0 Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Thu, 15 Jan 2026 00:25:48 +0100 Subject: [PATCH 04/11] Update main.cpp --- src/main.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 8a7c3b91..fdb5d317 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -150,6 +151,13 @@ void enterNewActivity(Activity* activity) { // Verify long press on wake-up from deep sleep void verifyWakeupLongPress() { + // Only verify button press if waking from deep sleep + // Skip for software resets (esp_restart) and power-on + esp_reset_reason_t reason = esp_reset_reason(); + if (reason != ESP_RST_DEEPSLEEP) { + 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; From 6a7b42cb037c956a37a72f866d6e01c6f9aa0a3c Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Thu, 15 Jan 2026 00:26:57 +0100 Subject: [PATCH 05/11] Update main.cpp --- src/main.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index fdb5d317..c4bd785f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -151,10 +151,9 @@ void enterNewActivity(Activity* activity) { // Verify long press on wake-up from deep sleep void verifyWakeupLongPress() { - // Only verify button press if waking from deep sleep - // Skip for software resets (esp_restart) and power-on - esp_reset_reason_t reason = esp_reset_reason(); - if (reason != ESP_RST_DEEPSLEEP) { + // Skip verification for software resets (esp_restart after format, OTA, etc.) + // All other cases (deep sleep wake, power on, etc.) still require long press + if (esp_reset_reason() == ESP_RST_SW) { return; } From 1cb59eba90aec0e19e8c616b984b047263f87d5c Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Thu, 15 Jan 2026 00:47:23 +0100 Subject: [PATCH 06/11] Update FormatSDCardActivity.cpp --- src/activities/settings/FormatSDCardActivity.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/activities/settings/FormatSDCardActivity.cpp b/src/activities/settings/FormatSDCardActivity.cpp index e4698065..5941e73b 100644 --- a/src/activities/settings/FormatSDCardActivity.cpp +++ b/src/activities/settings/FormatSDCardActivity.cpp @@ -97,9 +97,8 @@ void FormatSDCardActivity::loop() { if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { xSemaphoreTake(renderingMutex, portMAX_DELAY); state = FORMATTING; + render(); // Render synchronously to show "Formatting..." before we start xSemaphoreGive(renderingMutex); - updateRequired = true; - vTaskDelay(10 / portTICK_PERIOD_MS); performFormat(); } if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { From 306b38e3563f718de2d80efef334a528b770fc36 Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Thu, 15 Jan 2026 00:54:20 +0100 Subject: [PATCH 07/11] Update main.cpp --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index c4bd785f..7cb2a979 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -151,7 +151,7 @@ void enterNewActivity(Activity* activity) { // Verify long press on wake-up from deep sleep void verifyWakeupLongPress() { - // Skip verification for software resets (esp_restart after format, OTA, etc.) + // Skip verification for software resets (for example when calling esp_restart after sd card format) // All other cases (deep sleep wake, power on, etc.) still require long press if (esp_reset_reason() == ESP_RST_SW) { return; From bd3dc2ecd2cec0c95ace4c9ea38ca4938792f5e1 Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Thu, 15 Jan 2026 00:54:33 +0100 Subject: [PATCH 08/11] Update main.cpp --- src/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 7cb2a979..5229d3c1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -152,7 +152,6 @@ 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) - // All other cases (deep sleep wake, power on, etc.) still require long press if (esp_reset_reason() == ESP_RST_SW) { return; } From 80b34bb3958b08314f73fb7ae9c6dd68d30cafa4 Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Thu, 15 Jan 2026 01:00:15 +0100 Subject: [PATCH 09/11] Update open-x4-sdk --- open-x4-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From c2f08a7422a2581f6088161080d5908314e3cd97 Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Thu, 15 Jan 2026 01:08:58 +0100 Subject: [PATCH 10/11] Update FormatSDCardActivity.cpp --- src/activities/settings/FormatSDCardActivity.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/activities/settings/FormatSDCardActivity.cpp b/src/activities/settings/FormatSDCardActivity.cpp index 5941e73b..b26ccdcc 100644 --- a/src/activities/settings/FormatSDCardActivity.cpp +++ b/src/activities/settings/FormatSDCardActivity.cpp @@ -54,8 +54,6 @@ void FormatSDCardActivity::render() { renderer.clearScreen(); - const auto pageWidth = renderer.getScreenWidth(); - renderer.drawCenteredText(UI_12_FONT_ID, 15, "Format SD Card", true, EpdFontFamily::BOLD); if (state == WAITING_CONFIRMATION) { From 7089bf84c5df81e5ccfb84e3c0cd10b29115fdbf Mon Sep 17 00:00:00 2001 From: Stanislav Khromov Date: Thu, 15 Jan 2026 01:13:52 +0100 Subject: [PATCH 11/11] format --- src/activities/settings/FormatSDCardActivity.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/activities/settings/FormatSDCardActivity.h b/src/activities/settings/FormatSDCardActivity.h index 3f87511b..a9ad7185 100644 --- a/src/activities/settings/FormatSDCardActivity.h +++ b/src/activities/settings/FormatSDCardActivity.h @@ -8,12 +8,7 @@ #include "activities/ActivityWithSubactivity.h" class FormatSDCardActivity final : public ActivityWithSubactivity { - enum State { - WAITING_CONFIRMATION, - FORMATTING, - SUCCESS, - FAILED - }; + enum State { WAITING_CONFIRMATION, FORMATTING, SUCCESS, FAILED }; TaskHandle_t displayTaskHandle = nullptr; SemaphoreHandle_t renderingMutex = nullptr;