mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-08 08:37:38 +03:00
Compare commits
15 Commits
d1dc7fca38
...
d293185ab2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d293185ab2 | ||
|
|
7185e5d287 | ||
|
|
12940cc546 | ||
|
|
7089bf84c5 | ||
|
|
c2f08a7422 | ||
|
|
80b34bb395 | ||
|
|
bd3dc2ecd2 | ||
|
|
306b38e356 | ||
|
|
1cb59eba90 | ||
|
|
6a7b42cb03 | ||
|
|
f30b47ec69 | ||
|
|
8aecf5b73a | ||
|
|
1f8f4d8735 | ||
|
|
3b5aadfc8a | ||
|
|
f4ced6ac7b |
@ -203,7 +203,7 @@ bool Xtc::generateCoverBmp() const {
|
|||||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&colorsImportant), 4);
|
coverBmp.write(reinterpret_cast<const uint8_t*>(&colorsImportant), 4);
|
||||||
|
|
||||||
// Color palette (2 colors for 1-bit)
|
// Color palette (2 colors for 1-bit)
|
||||||
// XTC uses inverted polarity: 0 = black, 1 = white
|
// XTC 1-bit polarity: 0 = black, 1 = white (standard BMP palette order)
|
||||||
// Color 0: Black (text/foreground in XTC)
|
// Color 0: Black (text/foreground in XTC)
|
||||||
uint8_t black[4] = {0x00, 0x00, 0x00, 0x00};
|
uint8_t black[4] = {0x00, 0x00, 0x00, 0x00};
|
||||||
coverBmp.write(black, 4);
|
coverBmp.write(black, 4);
|
||||||
@ -506,8 +506,8 @@ bool Xtc::generateThumbBmp() const {
|
|||||||
// Bounds check for buffer access
|
// Bounds check for buffer access
|
||||||
if (byteIdx < bitmapSize) {
|
if (byteIdx < bitmapSize) {
|
||||||
const uint8_t pixelBit = (pageBuffer[byteIdx] >> bitIdx) & 1;
|
const uint8_t pixelBit = (pageBuffer[byteIdx] >> bitIdx) & 1;
|
||||||
// XTC polarity: 1=black, 0=white
|
// XTC 1-bit polarity: 0=black, 1=white (same as BMP palette)
|
||||||
grayValue = pixelBit ? 0 : 255;
|
grayValue = pixelBit ? 255 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
Subproject commit bd4e6707503ab9c97d13ee0d8f8c69e9ff03cd12
|
Subproject commit 82e6f846bc427c130a3ca9ecf0b4d27214699d4e
|
||||||
134
src/activities/settings/FormatSDCardActivity.cpp
Normal file
134
src/activities/settings/FormatSDCardActivity.cpp
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
#include "FormatSDCardActivity.h"
|
||||||
|
|
||||||
|
#include <GfxRenderer.h>
|
||||||
|
#include <SDCardManager.h>
|
||||||
|
#include <esp_system.h>
|
||||||
|
|
||||||
|
#include "MappedInputManager.h"
|
||||||
|
#include "fontIds.h"
|
||||||
|
|
||||||
|
void FormatSDCardActivity::taskTrampoline(void* param) {
|
||||||
|
auto* self = static_cast<FormatSDCardActivity*>(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");
|
||||||
|
}
|
||||||
32
src/activities/settings/FormatSDCardActivity.h
Normal file
32
src/activities/settings/FormatSDCardActivity.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#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<void()> 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<void()>& goBack)
|
||||||
|
: ActivityWithSubactivity("FormatSDCard", renderer, mappedInput), goBack(goBack) {}
|
||||||
|
void onEnter() override;
|
||||||
|
void onExit() override;
|
||||||
|
void loop() override;
|
||||||
|
bool preventAutoSleep() override { return state == FORMATTING; }
|
||||||
|
};
|
||||||
@ -7,13 +7,14 @@
|
|||||||
|
|
||||||
#include "CalibreSettingsActivity.h"
|
#include "CalibreSettingsActivity.h"
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
|
#include "FormatSDCardActivity.h"
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "OtaUpdateActivity.h"
|
#include "OtaUpdateActivity.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
|
|
||||||
// Define the static settings list
|
// Define the static settings list
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int settingsCount = 20;
|
constexpr int settingsCount = 21;
|
||||||
const SettingInfo settingsList[settingsCount] = {
|
const SettingInfo settingsList[settingsCount] = {
|
||||||
// Should match with SLEEP_SCREEN_MODE
|
// Should match with SLEEP_SCREEN_MODE
|
||||||
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}),
|
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,
|
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
|
||||||
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}),
|
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}),
|
||||||
SettingInfo::Action("Calibre Settings"),
|
SettingInfo::Action("Calibre Settings"),
|
||||||
SettingInfo::Action("Check for updates")};
|
SettingInfo::Action("Check for updates"),
|
||||||
|
SettingInfo::Action("Format SD Card")};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void SettingsActivity::taskTrampoline(void* param) {
|
void SettingsActivity::taskTrampoline(void* param) {
|
||||||
@ -155,6 +157,14 @@ void SettingsActivity::toggleCurrentSetting() {
|
|||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}));
|
}));
|
||||||
xSemaphoreGive(renderingMutex);
|
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 {
|
} else {
|
||||||
// Only toggle if it's a toggle type and has a value pointer
|
// Only toggle if it's a toggle type and has a value pointer
|
||||||
|
|||||||
@ -73,7 +73,7 @@ int KeyboardEntryActivity::getRowLength(const int row) const {
|
|||||||
case 3:
|
case 3:
|
||||||
return 10; // zxcvbnm,./
|
return 10; // zxcvbnm,./
|
||||||
case 4:
|
case 4:
|
||||||
return 10; // caps (2 wide), space (5 wide), backspace (2 wide), OK
|
return 10; // shift (2 wide), space (5 wide), backspace (2 wide), OK
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -145,6 +145,11 @@ void KeyboardEntryActivity::loop() {
|
|||||||
// Clamp column to valid range for new row
|
// Clamp column to valid range for new row
|
||||||
const int maxCol = getRowLength(selectedRow) - 1;
|
const int maxCol = getRowLength(selectedRow) - 1;
|
||||||
if (selectedCol > maxCol) selectedCol = maxCol;
|
if (selectedCol > maxCol) selectedCol = maxCol;
|
||||||
|
} else {
|
||||||
|
// Wrap to bottom row
|
||||||
|
selectedRow = NUM_ROWS - 1;
|
||||||
|
const int maxCol = getRowLength(selectedRow) - 1;
|
||||||
|
if (selectedCol > maxCol) selectedCol = maxCol;
|
||||||
}
|
}
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
@ -154,16 +159,24 @@ void KeyboardEntryActivity::loop() {
|
|||||||
selectedRow++;
|
selectedRow++;
|
||||||
const int maxCol = getRowLength(selectedRow) - 1;
|
const int maxCol = getRowLength(selectedRow) - 1;
|
||||||
if (selectedCol > maxCol) selectedCol = maxCol;
|
if (selectedCol > maxCol) selectedCol = maxCol;
|
||||||
|
} else {
|
||||||
|
// Wrap to top row
|
||||||
|
selectedRow = 0;
|
||||||
|
const int maxCol = getRowLength(selectedRow) - 1;
|
||||||
|
if (selectedCol > maxCol) selectedCol = maxCol;
|
||||||
}
|
}
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||||
|
const int maxCol = getRowLength(selectedRow) - 1;
|
||||||
|
|
||||||
// Special bottom row case
|
// Special bottom row case
|
||||||
if (selectedRow == SPECIAL_ROW) {
|
if (selectedRow == SPECIAL_ROW) {
|
||||||
// Bottom row has special key widths
|
// Bottom row has special key widths
|
||||||
if (selectedCol >= SHIFT_COL && selectedCol < SPACE_COL) {
|
if (selectedCol >= SHIFT_COL && selectedCol < SPACE_COL) {
|
||||||
// In shift key, do nothing
|
// In shift key, wrap to end of row
|
||||||
|
selectedCol = maxCol;
|
||||||
} else if (selectedCol >= SPACE_COL && selectedCol < BACKSPACE_COL) {
|
} else if (selectedCol >= SPACE_COL && selectedCol < BACKSPACE_COL) {
|
||||||
// In space bar, move to shift
|
// In space bar, move to shift
|
||||||
selectedCol = SHIFT_COL;
|
selectedCol = SHIFT_COL;
|
||||||
@ -180,10 +193,9 @@ void KeyboardEntryActivity::loop() {
|
|||||||
|
|
||||||
if (selectedCol > 0) {
|
if (selectedCol > 0) {
|
||||||
selectedCol--;
|
selectedCol--;
|
||||||
} else if (selectedRow > 0) {
|
} else {
|
||||||
// Wrap to previous row
|
// Wrap to end of current row
|
||||||
selectedRow--;
|
selectedCol = maxCol;
|
||||||
selectedCol = getRowLength(selectedRow) - 1;
|
|
||||||
}
|
}
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
@ -204,7 +216,8 @@ void KeyboardEntryActivity::loop() {
|
|||||||
// In backspace, move to done
|
// In backspace, move to done
|
||||||
selectedCol = DONE_COL;
|
selectedCol = DONE_COL;
|
||||||
} else if (selectedCol >= DONE_COL) {
|
} else if (selectedCol >= DONE_COL) {
|
||||||
// At done button, do nothing
|
// At done button, wrap to beginning of row
|
||||||
|
selectedCol = SHIFT_COL;
|
||||||
}
|
}
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
return;
|
return;
|
||||||
@ -212,9 +225,8 @@ void KeyboardEntryActivity::loop() {
|
|||||||
|
|
||||||
if (selectedCol < maxCol) {
|
if (selectedCol < maxCol) {
|
||||||
selectedCol++;
|
selectedCol++;
|
||||||
} else if (selectedRow < NUM_ROWS - 1) {
|
} else {
|
||||||
// Wrap to next row
|
// Wrap to beginning of current row
|
||||||
selectedRow++;
|
|
||||||
selectedCol = 0;
|
selectedCol = 0;
|
||||||
}
|
}
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
@ -288,14 +300,14 @@ void KeyboardEntryActivity::render() const {
|
|||||||
|
|
||||||
// Handle bottom row (row 4) specially with proper multi-column keys
|
// Handle bottom row (row 4) specially with proper multi-column keys
|
||||||
if (row == 4) {
|
if (row == 4) {
|
||||||
// Bottom row layout: CAPS (2 cols) | SPACE (5 cols) | <- (2 cols) | OK (2 cols)
|
// Bottom row layout: SHIFT (2 cols) | SPACE (5 cols) | <- (2 cols) | OK (2 cols)
|
||||||
// Total: 11 visual columns, but we use logical positions for selection
|
// Total: 11 visual columns, but we use logical positions for selection
|
||||||
|
|
||||||
int currentX = startX;
|
int currentX = startX;
|
||||||
|
|
||||||
// CAPS key (logical col 0, spans 2 key widths)
|
// SHIFT key (logical col 0, spans 2 key widths)
|
||||||
const bool capsSelected = (selectedRow == 4 && selectedCol >= SHIFT_COL && selectedCol < SPACE_COL);
|
const bool shiftSelected = (selectedRow == 4 && selectedCol >= SHIFT_COL && selectedCol < SPACE_COL);
|
||||||
renderItemWithSelector(currentX + 2, rowY, shiftActive ? "CAPS" : "caps", capsSelected);
|
renderItemWithSelector(currentX + 2, rowY, shiftActive ? "SHIFT" : "shift", shiftSelected);
|
||||||
currentX += 2 * (keyWidth + keySpacing);
|
currentX += 2 * (keyWidth + keySpacing);
|
||||||
|
|
||||||
// Space bar (logical cols 2-6, spans 5 key widths)
|
// Space bar (logical cols 2-6, spans 5 key widths)
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
#include <builtinFonts/all.h>
|
#include <builtinFonts/all.h>
|
||||||
|
#include <esp_system.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
@ -150,6 +151,11 @@ void enterNewActivity(Activity* activity) {
|
|||||||
|
|
||||||
// Verify long press on wake-up from deep sleep
|
// Verify long press on wake-up from deep sleep
|
||||||
void verifyWakeupLongPress() {
|
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()
|
// Give the user up to 1000ms to start holding the power button, and must hold for SETTINGS.getPowerButtonDuration()
|
||||||
const auto start = millis();
|
const auto start = millis();
|
||||||
bool abort = false;
|
bool abort = false;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user