Use InputManager from community-sdk

This commit is contained in:
Dave Allie 2025-12-06 12:35:41 +11:00
parent f0d92da8f2
commit 6414f85257
No known key found for this signature in database
GPG Key ID: F2FDDB3AD8D0276F
13 changed files with 134 additions and 163 deletions

@ -1 +1 @@
Subproject commit 90a19bb8a73d99d9ec57c8459af51838116557ac
Subproject commit 8224d278c58e76abf781c2e015f28a09419f27b2

View File

@ -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

View File

@ -1,43 +0,0 @@
#include "Input.h"
#include <esp32-hal-adc.h>
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};
}

View File

@ -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);

View File

@ -2,12 +2,12 @@
#include <EpdRenderer.h>
#include <Epub.h>
#include <GxEPD2_BW.h>
#include <InputManager.h>
#include <SD.h>
#include <SPI.h>
#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<GxEPD2_426_GDEQ0426T82, GxEPD2_426_GDEQ0426T82::HEIGHT> 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();
}
}

View File

@ -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;
};

View File

@ -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();
}
}

View File

@ -24,9 +24,10 @@ class EpubReaderScreen final : public Screen {
void renderStatusBar() const;
public:
explicit EpubReaderScreen(EpdRenderer* renderer, Epub* epub, const std::function<void()>& onGoHome)
: Screen(renderer), epub(epub), onGoHome(onGoHome) {}
explicit EpubReaderScreen(EpdRenderer* renderer, InputManager& inputManager, Epub* epub,
const std::function<void()>& onGoHome)
: Screen(renderer, inputManager), epub(epub), onGoHome(onGoHome) {}
void onEnter() override;
void onExit() override;
void handleInput(Input input) override;
void handleInput() override;
};

View File

@ -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;
}
}

View File

@ -24,9 +24,10 @@ class FileSelectionScreen final : public Screen {
void loadFiles();
public:
explicit FileSelectionScreen(EpdRenderer* renderer, const std::function<void(const std::string&)>& onSelect)
: Screen(renderer), onSelect(onSelect) {}
explicit FileSelectionScreen(EpdRenderer* renderer, InputManager& inputManager,
const std::function<void(const std::string&)>& onSelect)
: Screen(renderer, inputManager), onSelect(onSelect) {}
void onEnter() override;
void onExit() override;
void handleInput(Input input) override;
void handleInput() override;
};

View File

@ -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;
};

View File

@ -1,16 +1,17 @@
#pragma once
#include "Input.h"
#include <InputManager.h>
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() {}
};

View File

@ -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;
};