mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2025-12-16 22:27:42 +03:00
Use InputManager from community-sdk
This commit is contained in:
parent
f0d92da8f2
commit
6414f85257
@ -1 +1 @@
|
||||
Subproject commit 90a19bb8a73d99d9ec57c8459af51838116557ac
|
||||
Subproject commit 8224d278c58e76abf781c2e015f28a09419f27b2
|
||||
@ -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
|
||||
|
||||
@ -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};
|
||||
}
|
||||
28
src/Input.h
28
src/Input.h
@ -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);
|
||||
74
src/main.cpp
74
src/main.cpp
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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() {}
|
||||
};
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user