From 6414f85257ed28188b9f67a895946944503aaf72 Mon Sep 17 00:00:00 2001 From: Dave Allie Date: Sat, 6 Dec 2025 12:35:41 +1100 Subject: [PATCH] Use InputManager from community-sdk --- open-x4-sdk | 2 +- platformio.ini | 1 + src/Input.cpp | 43 ------------- src/Input.h | 28 --------- src/main.cpp | 74 +++++++++++++--------- src/screens/BootLogoScreen.h | 2 +- src/screens/EpubReaderScreen.cpp | 90 ++++++++++++++++----------- src/screens/EpubReaderScreen.h | 7 ++- src/screens/FileSelectionScreen.cpp | 23 ++++--- src/screens/FileSelectionScreen.h | 7 ++- src/screens/FullScreenMessageScreen.h | 11 +++- src/screens/Screen.h | 7 ++- src/screens/SleepScreen.h | 2 +- 13 files changed, 134 insertions(+), 163 deletions(-) delete mode 100644 src/Input.cpp delete mode 100644 src/Input.h diff --git a/open-x4-sdk b/open-x4-sdk index 90a19bb..8224d27 160000 --- a/open-x4-sdk +++ b/open-x4-sdk @@ -1 +1 @@ -Subproject commit 90a19bb8a73d99d9ec57c8459af51838116557ac +Subproject commit 8224d278c58e76abf781c2e015f28a09419f27b2 diff --git a/platformio.ini b/platformio.ini index 688328a..1039634 100644 --- a/platformio.ini +++ b/platformio.ini @@ -34,3 +34,4 @@ lib_deps = zinggjm/GxEPD2@^1.6.5 https://github.com/leethomason/tinyxml2.git#11.0.0 BatteryMonitor=symlink://open-x4-sdk/libs/hardware/BatteryMonitor + InputManager=symlink://open-x4-sdk/libs/hardware/InputManager diff --git a/src/Input.cpp b/src/Input.cpp deleted file mode 100644 index 235416d..0000000 --- a/src/Input.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "Input.h" - -#include - -void setupInputPinModes() { - pinMode(BTN_GPIO1, INPUT); - pinMode(BTN_GPIO2, INPUT); - pinMode(BTN_GPIO3, INPUT_PULLUP); // Power button -} - -// Get currently pressed button by reading ADC values (and digital for power -// button) -Button getPressedButton() { - // Check BTN_GPIO3 (Power button) - digital read - if (digitalRead(BTN_GPIO3) == LOW) return POWER; - - // Check BTN_GPIO1 (4 buttons on resistor ladder) - const int btn1 = analogRead(BTN_GPIO1); - if (btn1 < BTN_RIGHT_VAL + BTN_THRESHOLD) return RIGHT; - if (btn1 < BTN_LEFT_VAL + BTN_THRESHOLD) return LEFT; - if (btn1 < BTN_CONFIRM_VAL + BTN_THRESHOLD) return CONFIRM; - if (btn1 < BTN_BACK_VAL + BTN_THRESHOLD) return BACK; - - // Check BTN_GPIO2 (2 buttons on resistor ladder) - const int btn2 = analogRead(BTN_GPIO2); - if (btn2 < BTN_VOLUME_DOWN_VAL + BTN_THRESHOLD) return VOLUME_DOWN; - if (btn2 < BTN_VOLUME_UP_VAL + BTN_THRESHOLD) return VOLUME_UP; - - return NONE; -} - -Input getInput(const long maxHoldMs) { - const Button button = getPressedButton(); - if (button == NONE) return {NONE, 0}; - - const auto start = millis(); - unsigned long held = 0; - while (getPressedButton() == button && (maxHoldMs < 0 || held < maxHoldMs)) { - delay(50); - held = millis() - start; - } - return {button, held}; -} diff --git a/src/Input.h b/src/Input.h deleted file mode 100644 index da24385..0000000 --- a/src/Input.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -// 4 buttons on ADC resistor ladder: Back, Confirm, Left, Right -#define BTN_GPIO1 1 -// 2 buttons on ADC resistor ladder: Volume Up, Volume Down -#define BTN_GPIO2 2 -// Power button (digital) -#define BTN_GPIO3 3 - -// Button ADC thresholds -#define BTN_THRESHOLD 100 // Threshold tolerance -#define BTN_RIGHT_VAL 3 -#define BTN_LEFT_VAL 1500 -#define BTN_CONFIRM_VAL 2700 -#define BTN_BACK_VAL 3550 -#define BTN_VOLUME_DOWN_VAL 3 -#define BTN_VOLUME_UP_VAL 2305 - -enum Button { NONE = 0, RIGHT, LEFT, CONFIRM, BACK, VOLUME_UP, VOLUME_DOWN, POWER }; - -struct Input { - Button button; - unsigned long pressTime; -}; - -void setupInputPinModes(); -Button getPressedButton(); -Input getInput(long maxHoldMs = -1); diff --git a/src/main.cpp b/src/main.cpp index 4dd0ea9..647bb6f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,12 +2,12 @@ #include #include #include +#include #include #include #include "Battery.h" #include "CrossPointState.h" -#include "Input.h" #include "screens/BootLogoScreen.h" #include "screens/EpubReaderScreen.h" #include "screens/FileSelectionScreen.h" @@ -30,6 +30,7 @@ GxEPD2_BW display(GxEPD2_426_GDEQ0426T82(EPD_CS, EPD_DC, EPD_RST, EPD_BUSY)); +InputManager inputManager; auto renderer = new EpdRenderer(&display); Screen* currentScreen; CrossPointState* appState; @@ -72,36 +73,55 @@ void enterNewScreen(Screen* screen) { void verifyWakeupLongPress() { // Give the user up to 1000ms to start holding the power button, and must hold for POWER_BUTTON_WAKEUP_MS const auto start = millis(); - auto input = getInput(POWER_BUTTON_WAKEUP_MS); - while (input.button != POWER && millis() - start < 1000) { + bool abort = false; + + Serial.println("Verifying power button press"); + inputManager.update(); + while (!inputManager.isPressed(InputManager::BTN_POWER) && millis() - start < 1000) { delay(50); - input = getInput(POWER_BUTTON_WAKEUP_MS); + inputManager.update(); + Serial.println("Waiting..."); } - if (input.button != POWER || input.pressTime < POWER_BUTTON_WAKEUP_MS) { + Serial.printf("Made it? %s\n", inputManager.isPressed(InputManager::BTN_POWER) ? "yes" : "no"); + if (inputManager.isPressed(InputManager::BTN_POWER)) { + do { + delay(50); + inputManager.update(); + } while (inputManager.isPressed(InputManager::BTN_POWER) && inputManager.getHeldTime() < POWER_BUTTON_WAKEUP_MS); + abort = inputManager.getHeldTime() < POWER_BUTTON_WAKEUP_MS; + } else { + abort = true; + } + + Serial.printf("held for %lu\n", inputManager.getHeldTime()); + + if (abort) { // Button released too early. Returning to sleep. // IMPORTANT: Re-arm the wakeup trigger before sleeping again - esp_deep_sleep_enable_gpio_wakeup(1ULL << BTN_GPIO3, ESP_GPIO_WAKEUP_GPIO_LOW); + esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW); esp_deep_sleep_start(); } } -void waitForNoButton() { - while (getInput().button != NONE) { +void waitForPowerRelease() { + inputManager.update(); + while (inputManager.isPressed(InputManager::BTN_POWER)) { delay(50); + inputManager.update(); } } // Enter deep sleep mode void enterDeepSleep() { exitScreen(); - enterNewScreen(new SleepScreen(renderer)); + enterNewScreen(new SleepScreen(renderer, inputManager)); Serial.println("Power button released after a long press. Entering deep sleep."); delay(1000); // Allow Serial buffer to empty and display to update // Enable Wakeup on LOW (button press) - esp_deep_sleep_enable_gpio_wakeup(1ULL << BTN_GPIO3, ESP_GPIO_WAKEUP_GPIO_LOW); + esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW); display.hibernate(); @@ -112,17 +132,17 @@ void enterDeepSleep() { void onGoHome(); void onSelectEpubFile(const std::string& path) { exitScreen(); - enterNewScreen(new FullScreenMessageScreen(renderer, "Loading...")); + enterNewScreen(new FullScreenMessageScreen(renderer, inputManager, "Loading...")); Epub* epub = loadEpub(path); if (epub) { appState->openEpubPath = path; appState->saveToFile(); exitScreen(); - enterNewScreen(new EpubReaderScreen(renderer, epub, onGoHome)); + enterNewScreen(new EpubReaderScreen(renderer, inputManager, epub, onGoHome)); } else { exitScreen(); - enterNewScreen(new FullScreenMessageScreen(renderer, "Failed to load epub", REGULAR, false, false)); + enterNewScreen(new FullScreenMessageScreen(renderer, inputManager, "Failed to load epub", REGULAR, false, false)); delay(2000); onGoHome(); } @@ -130,11 +150,11 @@ void onSelectEpubFile(const std::string& path) { void onGoHome() { exitScreen(); - enterNewScreen(new FileSelectionScreen(renderer, onSelectEpubFile)); + enterNewScreen(new FileSelectionScreen(renderer, inputManager, onSelectEpubFile)); } void setup() { - setupInputPinModes(); + inputManager.begin(); verifyWakeupLongPress(); // Begin serial only if USB connected @@ -157,7 +177,7 @@ void setup() { Serial.println("Display initialized"); exitScreen(); - enterNewScreen(new BootLogoScreen(renderer)); + enterNewScreen(new BootLogoScreen(renderer, inputManager)); // SD Card Initialization SD.begin(SD_SPI_CS, SPI, SPI_FQ); @@ -167,37 +187,31 @@ void setup() { Epub* epub = loadEpub(appState->openEpubPath); if (epub) { exitScreen(); - enterNewScreen(new EpubReaderScreen(renderer, epub, onGoHome)); + enterNewScreen(new EpubReaderScreen(renderer, inputManager, epub, onGoHome)); // Ensure we're not still holding the power button before leaving setup - waitForNoButton(); + waitForPowerRelease(); return; } } exitScreen(); - enterNewScreen(new FileSelectionScreen(renderer, onSelectEpubFile)); + enterNewScreen(new FileSelectionScreen(renderer, inputManager, onSelectEpubFile)); // Ensure we're not still holding the power button before leaving setup - waitForNoButton(); + waitForPowerRelease(); } void loop() { - delay(50); + delay(10); - const Input input = getInput(); - - if (input.button == NONE) { - return; - } - - if (input.button == POWER && input.pressTime > POWER_BUTTON_SLEEP_MS) { + inputManager.update(); + if (inputManager.wasReleased(InputManager::BTN_POWER) && inputManager.getHeldTime() > POWER_BUTTON_WAKEUP_MS) { enterDeepSleep(); // This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start - delay(1000); return; } if (currentScreen) { - currentScreen->handleInput(input); + currentScreen->handleInput(); } } diff --git a/src/screens/BootLogoScreen.h b/src/screens/BootLogoScreen.h index 96f8122..153b1ab 100644 --- a/src/screens/BootLogoScreen.h +++ b/src/screens/BootLogoScreen.h @@ -3,6 +3,6 @@ class BootLogoScreen final : public Screen { public: - explicit BootLogoScreen(EpdRenderer* renderer) : Screen(renderer) {} + explicit BootLogoScreen(EpdRenderer* renderer, InputManager& inputManager) : Screen(renderer, inputManager) {} void onEnter() override; }; diff --git a/src/screens/EpubReaderScreen.cpp b/src/screens/EpubReaderScreen.cpp index df630bb..5ec94ba 100644 --- a/src/screens/EpubReaderScreen.cpp +++ b/src/screens/EpubReaderScreen.cpp @@ -59,55 +59,69 @@ void EpubReaderScreen::onExit() { epub = nullptr; } -void EpubReaderScreen::handleInput(const Input input) { - if (input.button == VOLUME_UP || input.button == VOLUME_DOWN || input.button == LEFT || input.button == RIGHT) { - const bool skipChapter = input.pressTime > SKIP_CHAPTER_MS; +void EpubReaderScreen::handleInput() { + if (inputManager.wasPressed(InputManager::BTN_BACK)) { + onGoHome(); + return; + } - // No current section, attempt to rerender the book - if (!section) { - updateRequired = true; - return; - } + const bool prevReleased = + inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT); + const bool nextReleased = + inputManager.wasReleased(InputManager::BTN_DOWN) || inputManager.wasReleased(InputManager::BTN_RIGHT); - if ((input.button == VOLUME_UP || input.button == LEFT) && skipChapter) { - nextPageNumber = 0; + if (!prevReleased && !nextReleased) { + return; + } + + Serial.printf("Prev released: %d, Next released: %d\n", prevReleased, nextReleased); + + const bool skipChapter = inputManager.getHeldTime() > SKIP_CHAPTER_MS; + + if (skipChapter) { + // We don't want to delete the section mid-render, so grab the semaphore + xSemaphoreTake(renderingMutex, portMAX_DELAY); + nextPageNumber = 0; + currentSpineIndex = nextReleased ? currentSpineIndex + 1 : currentSpineIndex - 1; + delete section; + section = nullptr; + xSemaphoreGive(renderingMutex); + updateRequired = true; + return; + } + + // No current section, attempt to rerender the book + if (!section) { + updateRequired = true; + return; + } + + if (prevReleased) { + if (section->currentPage > 0) { + section->currentPage--; + } else { + // We don't want to delete the section mid-render, so grab the semaphore + xSemaphoreTake(renderingMutex, portMAX_DELAY); + nextPageNumber = UINT16_MAX; currentSpineIndex--; delete section; section = nullptr; - } else if ((input.button == VOLUME_DOWN || input.button == RIGHT) && skipChapter) { + xSemaphoreGive(renderingMutex); + } + updateRequired = true; + } else { + if (section->currentPage < section->pageCount - 1) { + section->currentPage++; + } else { + // We don't want to delete the section mid-render, so grab the semaphore + xSemaphoreTake(renderingMutex, portMAX_DELAY); nextPageNumber = 0; currentSpineIndex++; delete section; section = nullptr; - } else if (input.button == VOLUME_UP || input.button == LEFT) { - if (section->currentPage > 0) { - section->currentPage--; - } else { - // We don't want to delete the section mid-render, so grab the semaphore - xSemaphoreTake(renderingMutex, portMAX_DELAY); - nextPageNumber = UINT16_MAX; - currentSpineIndex--; - delete section; - section = nullptr; - xSemaphoreGive(renderingMutex); - } - } else if (input.button == VOLUME_DOWN || input.button == RIGHT) { - if (section->currentPage < section->pageCount - 1) { - section->currentPage++; - } else { - // We don't want to delete the section mid-render, so grab the semaphore - xSemaphoreTake(renderingMutex, portMAX_DELAY); - nextPageNumber = 0; - currentSpineIndex++; - delete section; - section = nullptr; - xSemaphoreGive(renderingMutex); - } + xSemaphoreGive(renderingMutex); } - updateRequired = true; - } else if (input.button == BACK) { - onGoHome(); } } diff --git a/src/screens/EpubReaderScreen.h b/src/screens/EpubReaderScreen.h index ca10479..1fe44ed 100644 --- a/src/screens/EpubReaderScreen.h +++ b/src/screens/EpubReaderScreen.h @@ -24,9 +24,10 @@ class EpubReaderScreen final : public Screen { void renderStatusBar() const; public: - explicit EpubReaderScreen(EpdRenderer* renderer, Epub* epub, const std::function& onGoHome) - : Screen(renderer), epub(epub), onGoHome(onGoHome) {} + explicit EpubReaderScreen(EpdRenderer* renderer, InputManager& inputManager, Epub* epub, + const std::function& onGoHome) + : Screen(renderer, inputManager), epub(epub), onGoHome(onGoHome) {} void onEnter() override; void onExit() override; - void handleInput(Input input) override; + void handleInput() override; }; diff --git a/src/screens/FileSelectionScreen.cpp b/src/screens/FileSelectionScreen.cpp index 15cb363..be800e2 100644 --- a/src/screens/FileSelectionScreen.cpp +++ b/src/screens/FileSelectionScreen.cpp @@ -59,14 +59,13 @@ void FileSelectionScreen::onExit() { files.clear(); } -void FileSelectionScreen::handleInput(const Input input) { - if (input.button == VOLUME_DOWN || input.button == RIGHT) { - selectorIndex = (selectorIndex + 1) % files.size(); - updateRequired = true; - } else if (input.button == VOLUME_UP || input.button == LEFT) { - selectorIndex = (selectorIndex + files.size() - 1) % files.size(); - updateRequired = true; - } else if (input.button == CONFIRM) { +void FileSelectionScreen::handleInput() { + const bool prevPressed = + inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT); + const bool nextPressed = + inputManager.wasPressed(InputManager::BTN_DOWN) || inputManager.wasPressed(InputManager::BTN_RIGHT); + + if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { if (files.empty()) { return; } @@ -79,11 +78,17 @@ void FileSelectionScreen::handleInput(const Input input) { } else { onSelect(basepath + files[selectorIndex]); } - } else if (input.button == BACK && basepath != "/") { + } else if (inputManager.wasPressed(InputManager::BTN_BACK) && basepath != "/") { basepath = basepath.substr(0, basepath.rfind('/')); if (basepath.empty()) basepath = "/"; loadFiles(); updateRequired = true; + } else if (prevPressed) { + selectorIndex = (selectorIndex + files.size() - 1) % files.size(); + updateRequired = true; + } else if (nextPressed) { + selectorIndex = (selectorIndex + 1) % files.size(); + updateRequired = true; } } diff --git a/src/screens/FileSelectionScreen.h b/src/screens/FileSelectionScreen.h index b11b895..25d8402 100644 --- a/src/screens/FileSelectionScreen.h +++ b/src/screens/FileSelectionScreen.h @@ -24,9 +24,10 @@ class FileSelectionScreen final : public Screen { void loadFiles(); public: - explicit FileSelectionScreen(EpdRenderer* renderer, const std::function& onSelect) - : Screen(renderer), onSelect(onSelect) {} + explicit FileSelectionScreen(EpdRenderer* renderer, InputManager& inputManager, + const std::function& onSelect) + : Screen(renderer, inputManager), onSelect(onSelect) {} void onEnter() override; void onExit() override; - void handleInput(Input input) override; + void handleInput() override; }; diff --git a/src/screens/FullScreenMessageScreen.h b/src/screens/FullScreenMessageScreen.h index 588f500..68c971b 100644 --- a/src/screens/FullScreenMessageScreen.h +++ b/src/screens/FullScreenMessageScreen.h @@ -12,8 +12,13 @@ class FullScreenMessageScreen final : public Screen { bool partialUpdate; public: - explicit FullScreenMessageScreen(EpdRenderer* renderer, std::string text, const EpdFontStyle style = REGULAR, - const bool invert = false, const bool partialUpdate = true) - : Screen(renderer), text(std::move(text)), style(style), invert(invert), partialUpdate(partialUpdate) {} + explicit FullScreenMessageScreen(EpdRenderer* renderer, InputManager& inputManager, std::string text, + const EpdFontStyle style = REGULAR, const bool invert = false, + const bool partialUpdate = true) + : Screen(renderer, inputManager), + text(std::move(text)), + style(style), + invert(invert), + partialUpdate(partialUpdate) {} void onEnter() override; }; diff --git a/src/screens/Screen.h b/src/screens/Screen.h index 6668802..373525e 100644 --- a/src/screens/Screen.h +++ b/src/screens/Screen.h @@ -1,16 +1,17 @@ #pragma once -#include "Input.h" +#include class EpdRenderer; class Screen { protected: EpdRenderer* renderer; + InputManager& inputManager; public: - explicit Screen(EpdRenderer* renderer) : renderer(renderer) {} + explicit Screen(EpdRenderer* renderer, InputManager& inputManager) : renderer(renderer), inputManager(inputManager) {} virtual ~Screen() = default; virtual void onEnter() {} virtual void onExit() {} - virtual void handleInput(Input input) {} + virtual void handleInput() {} }; diff --git a/src/screens/SleepScreen.h b/src/screens/SleepScreen.h index 08dcfd9..02bd15f 100644 --- a/src/screens/SleepScreen.h +++ b/src/screens/SleepScreen.h @@ -3,6 +3,6 @@ class SleepScreen final : public Screen { public: - explicit SleepScreen(EpdRenderer* renderer) : Screen(renderer) {} + explicit SleepScreen(EpdRenderer* renderer, InputManager& inputManager) : Screen(renderer, inputManager) {} void onEnter() override; };