You've already forked crosspoint-reader
mirror of
https://github.com/crosspoint-reader/crosspoint-reader.git
synced 2026-04-29 10:26:52 -07:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c0a7088fcc | |||
| e79f86fa24 | |||
| fd933ba368 | |||
| c14dc4c109 | |||
| cd4303a8fb | |||
| 961a704aaa | |||
| 7061d054ef | |||
| 39bf8a5609 | |||
| a78897478f | |||
| 4f0a39ab03 | |||
| d97a436a48 | |||
| 6114b80e5d | |||
| d4eb089e49 | |||
| 9a1548189c | |||
| cbea838f91 |
@@ -9,3 +9,4 @@ build
|
||||
**/__pycache__/
|
||||
/compile_commands.json
|
||||
/.cache
|
||||
/notes.md
|
||||
|
||||
@@ -9,6 +9,11 @@ void GfxRenderer::begin() {
|
||||
LOG_ERR("GFX", "!! No framebuffer");
|
||||
assert(false);
|
||||
}
|
||||
panelWidth = display.getDisplayWidth();
|
||||
panelHeight = display.getDisplayHeight();
|
||||
panelWidthBytes = display.getDisplayWidthBytes();
|
||||
frameBufferSize = display.getBufferSize();
|
||||
bwBufferChunks.assign((frameBufferSize + BW_BUFFER_CHUNK_SIZE - 1) / BW_BUFFER_CHUNK_SIZE, nullptr);
|
||||
}
|
||||
|
||||
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); }
|
||||
@@ -16,25 +21,25 @@ void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.ins
|
||||
// Translate logical (x,y) coordinates to physical panel coordinates based on current orientation
|
||||
// This should always be inlined for better performance
|
||||
static inline void rotateCoordinates(const GfxRenderer::Orientation orientation, const int x, const int y, int* phyX,
|
||||
int* phyY) {
|
||||
int* phyY, const uint16_t panelWidth, const uint16_t panelHeight) {
|
||||
switch (orientation) {
|
||||
case GfxRenderer::Portrait: {
|
||||
// Logical portrait (480x800) → panel (800x480)
|
||||
// Rotation: 90 degrees clockwise
|
||||
*phyX = y;
|
||||
*phyY = HalDisplay::DISPLAY_HEIGHT - 1 - x;
|
||||
*phyY = panelHeight - 1 - x;
|
||||
break;
|
||||
}
|
||||
case GfxRenderer::LandscapeClockwise: {
|
||||
// Logical landscape (800x480) rotated 180 degrees (swap top/bottom and left/right)
|
||||
*phyX = HalDisplay::DISPLAY_WIDTH - 1 - x;
|
||||
*phyY = HalDisplay::DISPLAY_HEIGHT - 1 - y;
|
||||
*phyX = panelWidth - 1 - x;
|
||||
*phyY = panelHeight - 1 - y;
|
||||
break;
|
||||
}
|
||||
case GfxRenderer::PortraitInverted: {
|
||||
// Logical portrait (480x800) → panel (800x480)
|
||||
// Rotation: 90 degrees counter-clockwise
|
||||
*phyX = HalDisplay::DISPLAY_WIDTH - 1 - y;
|
||||
*phyX = panelWidth - 1 - y;
|
||||
*phyY = x;
|
||||
break;
|
||||
}
|
||||
@@ -54,16 +59,16 @@ void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
|
||||
int phyY = 0;
|
||||
|
||||
// Note: this call should be inlined for better performance
|
||||
rotateCoordinates(orientation, x, y, &phyX, &phyY);
|
||||
rotateCoordinates(orientation, x, y, &phyX, &phyY, panelWidth, panelHeight);
|
||||
|
||||
// Bounds checking against physical panel dimensions
|
||||
if (phyX < 0 || phyX >= HalDisplay::DISPLAY_WIDTH || phyY < 0 || phyY >= HalDisplay::DISPLAY_HEIGHT) {
|
||||
// Bounds checking against runtime panel dimensions
|
||||
if (phyX < 0 || phyX >= panelWidth || phyY < 0 || phyY >= panelHeight) {
|
||||
LOG_ERR("GFX", "!! Outside range (%d, %d) -> (%d, %d)", x, y, phyX, phyY);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate byte position and bit position
|
||||
const uint16_t byteIndex = phyY * HalDisplay::DISPLAY_WIDTH_BYTES + (phyX / 8);
|
||||
const uint32_t byteIndex = static_cast<uint32_t>(phyY) * panelWidthBytes + (phyX / 8);
|
||||
const uint8_t bitPosition = 7 - (phyX % 8); // MSB first
|
||||
|
||||
if (state) {
|
||||
@@ -384,7 +389,7 @@ void GfxRenderer::fillRoundedRect(const int x, const int y, const int width, con
|
||||
void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
|
||||
int rotatedX = 0;
|
||||
int rotatedY = 0;
|
||||
rotateCoordinates(orientation, x, y, &rotatedX, &rotatedY);
|
||||
rotateCoordinates(orientation, x, y, &rotatedX, &rotatedY, panelWidth, panelHeight);
|
||||
// Rotate origin corner
|
||||
switch (orientation) {
|
||||
case Portrait:
|
||||
@@ -649,7 +654,7 @@ void GfxRenderer::clearScreen(const uint8_t color) const {
|
||||
}
|
||||
|
||||
void GfxRenderer::invertScreen() const {
|
||||
for (int i = 0; i < HalDisplay::BUFFER_SIZE; i++) {
|
||||
for (uint32_t i = 0; i < frameBufferSize; i++) {
|
||||
frameBuffer[i] = ~frameBuffer[i];
|
||||
}
|
||||
}
|
||||
@@ -685,13 +690,13 @@ int GfxRenderer::getScreenWidth() const {
|
||||
case Portrait:
|
||||
case PortraitInverted:
|
||||
// 480px wide in portrait logical coordinates
|
||||
return HalDisplay::DISPLAY_HEIGHT;
|
||||
return panelHeight;
|
||||
case LandscapeClockwise:
|
||||
case LandscapeCounterClockwise:
|
||||
// 800px wide in landscape logical coordinates
|
||||
return HalDisplay::DISPLAY_WIDTH;
|
||||
return panelWidth;
|
||||
}
|
||||
return HalDisplay::DISPLAY_HEIGHT;
|
||||
return panelHeight;
|
||||
}
|
||||
|
||||
int GfxRenderer::getScreenHeight() const {
|
||||
@@ -699,13 +704,13 @@ int GfxRenderer::getScreenHeight() const {
|
||||
case Portrait:
|
||||
case PortraitInverted:
|
||||
// 800px tall in portrait logical coordinates
|
||||
return HalDisplay::DISPLAY_WIDTH;
|
||||
return panelWidth;
|
||||
case LandscapeClockwise:
|
||||
case LandscapeCounterClockwise:
|
||||
// 480px tall in landscape logical coordinates
|
||||
return HalDisplay::DISPLAY_HEIGHT;
|
||||
return panelHeight;
|
||||
}
|
||||
return HalDisplay::DISPLAY_WIDTH;
|
||||
return panelWidth;
|
||||
}
|
||||
|
||||
int GfxRenderer::getSpaceWidth(const int fontId) const {
|
||||
@@ -842,7 +847,7 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
|
||||
|
||||
uint8_t* GfxRenderer::getFrameBuffer() const { return frameBuffer; }
|
||||
|
||||
size_t GfxRenderer::getBufferSize() { return HalDisplay::BUFFER_SIZE; }
|
||||
size_t GfxRenderer::getBufferSize() { return EInkDisplay::MAX_BUFFER_SIZE; }
|
||||
|
||||
// unused
|
||||
// void GfxRenderer::grayscaleRevert() const { display.grayscaleRevert(); }
|
||||
@@ -870,7 +875,7 @@ void GfxRenderer::freeBwBufferChunks() {
|
||||
*/
|
||||
bool GfxRenderer::storeBwBuffer() {
|
||||
// Allocate and copy each chunk
|
||||
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
|
||||
for (size_t i = 0; i < bwBufferChunks.size(); i++) {
|
||||
// Check if any chunks are already allocated
|
||||
if (bwBufferChunks[i]) {
|
||||
LOG_ERR("GFX", "!! BW buffer chunk %zu already stored - this is likely a bug, freeing chunk", i);
|
||||
@@ -879,19 +884,20 @@ bool GfxRenderer::storeBwBuffer() {
|
||||
}
|
||||
|
||||
const size_t offset = i * BW_BUFFER_CHUNK_SIZE;
|
||||
bwBufferChunks[i] = static_cast<uint8_t*>(malloc(BW_BUFFER_CHUNK_SIZE));
|
||||
const size_t chunkSize = std::min(BW_BUFFER_CHUNK_SIZE, static_cast<size_t>(frameBufferSize - offset));
|
||||
bwBufferChunks[i] = static_cast<uint8_t*>(malloc(chunkSize));
|
||||
|
||||
if (!bwBufferChunks[i]) {
|
||||
LOG_ERR("GFX", "!! Failed to allocate BW buffer chunk %zu (%zu bytes)", i, BW_BUFFER_CHUNK_SIZE);
|
||||
LOG_ERR("GFX", "!! Failed to allocate BW buffer chunk %zu (%zu bytes)", i, chunkSize);
|
||||
// Free previously allocated chunks
|
||||
freeBwBufferChunks();
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(bwBufferChunks[i], frameBuffer + offset, BW_BUFFER_CHUNK_SIZE);
|
||||
memcpy(bwBufferChunks[i], frameBuffer + offset, chunkSize);
|
||||
}
|
||||
|
||||
LOG_DBG("GFX", "Stored BW buffer in %zu chunks (%zu bytes each)", BW_BUFFER_NUM_CHUNKS, BW_BUFFER_CHUNK_SIZE);
|
||||
LOG_DBG("GFX", "Stored BW buffer in %zu chunks (%zu bytes each)", bwBufferChunks.size(), BW_BUFFER_CHUNK_SIZE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -915,7 +921,7 @@ void GfxRenderer::restoreBwBuffer() {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
|
||||
for (size_t i = 0; i < bwBufferChunks.size(); i++) {
|
||||
// Check if chunk is missing
|
||||
if (!bwBufferChunks[i]) {
|
||||
LOG_ERR("GFX", "!! BW buffer chunks not stored - this is likely a bug");
|
||||
@@ -924,7 +930,8 @@ void GfxRenderer::restoreBwBuffer() {
|
||||
}
|
||||
|
||||
const size_t offset = i * BW_BUFFER_CHUNK_SIZE;
|
||||
memcpy(frameBuffer + offset, bwBufferChunks[i], BW_BUFFER_CHUNK_SIZE);
|
||||
const size_t chunkSize = std::min(BW_BUFFER_CHUNK_SIZE, static_cast<size_t>(frameBufferSize - offset));
|
||||
memcpy(frameBuffer + offset, bwBufferChunks[i], chunkSize);
|
||||
}
|
||||
|
||||
display.cleanupGrayscaleBuffers(frameBuffer);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <HalDisplay.h>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "Bitmap.h"
|
||||
|
||||
@@ -25,16 +26,17 @@ class GfxRenderer {
|
||||
|
||||
private:
|
||||
static constexpr size_t BW_BUFFER_CHUNK_SIZE = 8000; // 8KB chunks to allow for non-contiguous memory
|
||||
static constexpr size_t BW_BUFFER_NUM_CHUNKS = HalDisplay::BUFFER_SIZE / BW_BUFFER_CHUNK_SIZE;
|
||||
static_assert(BW_BUFFER_CHUNK_SIZE * BW_BUFFER_NUM_CHUNKS == HalDisplay::BUFFER_SIZE,
|
||||
"BW buffer chunking does not line up with display buffer size");
|
||||
|
||||
HalDisplay& display;
|
||||
RenderMode renderMode;
|
||||
Orientation orientation;
|
||||
bool fadingFix;
|
||||
uint8_t* frameBuffer = nullptr;
|
||||
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
|
||||
uint16_t panelWidth = HalDisplay::DISPLAY_WIDTH;
|
||||
uint16_t panelHeight = HalDisplay::DISPLAY_HEIGHT;
|
||||
uint16_t panelWidthBytes = HalDisplay::DISPLAY_WIDTH_BYTES;
|
||||
uint32_t frameBufferSize = HalDisplay::BUFFER_SIZE;
|
||||
std::vector<uint8_t*> bwBufferChunks;
|
||||
std::map<int, EpdFontFamily> fontMap;
|
||||
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState,
|
||||
EpdFontFamily::Style style) const;
|
||||
|
||||
+43
-2
@@ -7,7 +7,26 @@ HalDisplay::HalDisplay() : einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_R
|
||||
|
||||
HalDisplay::~HalDisplay() {}
|
||||
|
||||
void HalDisplay::begin() { einkDisplay.begin(); }
|
||||
void HalDisplay::begin() {
|
||||
// Set X3-specific display dimensions before initializing
|
||||
if (gpio.deviceIsX3()) {
|
||||
einkDisplay.setDisplayDimensions(792, 528);
|
||||
}
|
||||
|
||||
einkDisplay.begin();
|
||||
|
||||
// Request resync after specific wakeup events to ensure clean display state
|
||||
const auto wakeupReason = gpio.getWakeupReason();
|
||||
if (wakeupReason == HalGPIO::WakeupReason::PowerButton ||
|
||||
wakeupReason == HalGPIO::WakeupReason::AfterFlash ||
|
||||
wakeupReason == HalGPIO::WakeupReason::Other) {
|
||||
einkDisplay.requestResync();
|
||||
}
|
||||
}
|
||||
|
||||
void HalDisplay::setDisplayDimensions(uint16_t width, uint16_t height) {
|
||||
einkDisplay.setDisplayDimensions(width, height);
|
||||
}
|
||||
|
||||
void HalDisplay::clearScreen(uint8_t color) const { einkDisplay.clearScreen(color); }
|
||||
|
||||
@@ -29,10 +48,18 @@ EInkDisplay::RefreshMode convertRefreshMode(HalDisplay::RefreshMode mode) {
|
||||
}
|
||||
|
||||
void HalDisplay::displayBuffer(HalDisplay::RefreshMode mode, bool turnOffScreen) {
|
||||
if (gpio.deviceIsX3() && (lastBufferWasGray || mode == RefreshMode::HALF_REFRESH)) {
|
||||
einkDisplay.requestResync(1);
|
||||
}
|
||||
lastBufferWasGray = false;
|
||||
einkDisplay.displayBuffer(convertRefreshMode(mode), turnOffScreen);
|
||||
}
|
||||
|
||||
void HalDisplay::refreshDisplay(HalDisplay::RefreshMode mode, bool turnOffScreen) {
|
||||
if (gpio.deviceIsX3() && (lastBufferWasGray || mode == RefreshMode::HALF_REFRESH)) {
|
||||
einkDisplay.requestResync(1);
|
||||
}
|
||||
lastBufferWasGray = false;
|
||||
einkDisplay.refreshDisplay(convertRefreshMode(mode), turnOffScreen);
|
||||
}
|
||||
|
||||
@@ -50,4 +77,18 @@ void HalDisplay::copyGrayscaleMsbBuffers(const uint8_t* msbBuffer) { einkDisplay
|
||||
|
||||
void HalDisplay::cleanupGrayscaleBuffers(const uint8_t* bwBuffer) { einkDisplay.cleanupGrayscaleBuffers(bwBuffer); }
|
||||
|
||||
void HalDisplay::displayGrayBuffer(bool turnOffScreen) { einkDisplay.displayGrayBuffer(turnOffScreen); }
|
||||
void HalDisplay::displayGrayBuffer(bool turnOffScreen) {
|
||||
if (gpio.deviceIsX3() && !lastBufferWasGray) {
|
||||
einkDisplay.requestResync(1);
|
||||
}
|
||||
lastBufferWasGray = true;
|
||||
einkDisplay.displayGrayBuffer(turnOffScreen);
|
||||
}
|
||||
|
||||
uint16_t HalDisplay::getDisplayWidth() const { return einkDisplay.getDisplayWidth(); }
|
||||
|
||||
uint16_t HalDisplay::getDisplayHeight() const { return einkDisplay.getDisplayHeight(); }
|
||||
|
||||
uint16_t HalDisplay::getDisplayWidthBytes() const { return einkDisplay.getDisplayWidthBytes(); }
|
||||
|
||||
uint32_t HalDisplay::getBufferSize() const { return einkDisplay.getBufferSize(); }
|
||||
|
||||
@@ -20,6 +20,9 @@ class HalDisplay {
|
||||
// Initialize the display hardware and driver
|
||||
void begin();
|
||||
|
||||
// Pre-begin display config passthroughs (used by X3 setup path)
|
||||
void setDisplayDimensions(uint16_t width, uint16_t height);
|
||||
|
||||
// Display dimensions
|
||||
static constexpr uint16_t DISPLAY_WIDTH = EInkDisplay::DISPLAY_WIDTH;
|
||||
static constexpr uint16_t DISPLAY_HEIGHT = EInkDisplay::DISPLAY_HEIGHT;
|
||||
@@ -47,6 +50,13 @@ class HalDisplay {
|
||||
|
||||
void displayGrayBuffer(bool turnOffScreen = false);
|
||||
|
||||
// Runtime geometry passthrough
|
||||
uint16_t getDisplayWidth() const;
|
||||
uint16_t getDisplayHeight() const;
|
||||
uint16_t getDisplayWidthBytes() const;
|
||||
uint32_t getBufferSize() const;
|
||||
|
||||
private:
|
||||
EInkDisplay einkDisplay;
|
||||
bool lastBufferWasGray = false;
|
||||
};
|
||||
|
||||
+84
-4
@@ -1,12 +1,33 @@
|
||||
#include <HalGPIO.h>
|
||||
#include <SPI.h>
|
||||
#include <Wire.h>
|
||||
#include <esp_sleep.h>
|
||||
|
||||
// Global HalGPIO instance
|
||||
HalGPIO gpio;
|
||||
|
||||
void HalGPIO::begin() {
|
||||
inputMgr.begin();
|
||||
SPI.begin(EPD_SCLK, SPI_MISO, EPD_MOSI, EPD_CS);
|
||||
pinMode(BAT_GPIO0, INPUT);
|
||||
|
||||
// X3 boards bias GPIO4 (EPD DC) around ~700 ADC counts at boot in our setup.
|
||||
// X4 boards do not, and use GPIO0 for battery ADC.
|
||||
_detectAdcValue = analogRead(4);
|
||||
_deviceType = (_detectAdcValue > 500 && _detectAdcValue < 1200) ? DeviceType::X3 : DeviceType::X4;
|
||||
_batteryPin = (_deviceType == DeviceType::X3) ? 4 : BAT_GPIO0;
|
||||
|
||||
pinMode(_batteryPin, INPUT);
|
||||
pinMode(UART0_RXD, INPUT);
|
||||
|
||||
// I2C init must come AFTER pinMode(UART0_RXD) because GPIO20 is shared
|
||||
// between USB detection (digital read) and I2C SDA. Wire.begin()
|
||||
// reconfigures the pin for I2C, so it must run last.
|
||||
if (_deviceType == DeviceType::X3) {
|
||||
Wire.begin(20, 0, 400000);
|
||||
_batteryUseI2C = true;
|
||||
_batteryI2cAddr = 0x55;
|
||||
_batterySocRegister = 0x2C;
|
||||
}
|
||||
}
|
||||
|
||||
void HalGPIO::update() { inputMgr.update(); }
|
||||
@@ -35,9 +56,68 @@ void HalGPIO::startDeepSleep() {
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
|
||||
void HalGPIO::verifyPowerButtonWakeup(uint16_t requiredDurationMs, bool shortPressAllowed) {
|
||||
if (shortPressAllowed) {
|
||||
// Fast path - no duration check needed
|
||||
return;
|
||||
}
|
||||
|
||||
// Calibrate: subtract boot time already elapsed, assuming button held since boot
|
||||
const uint16_t calibration = millis();
|
||||
const uint16_t calibratedDuration = (calibration < requiredDurationMs) ? (requiredDurationMs - calibration) : 1;
|
||||
|
||||
if (deviceIsX3()) {
|
||||
// X3: Direct GPIO read (inputMgr not yet reliable at this point)
|
||||
const uint8_t powerPin = InputManager::POWER_BUTTON_PIN;
|
||||
if (digitalRead(powerPin) != LOW) {
|
||||
startDeepSleep();
|
||||
}
|
||||
const unsigned long holdStart = millis();
|
||||
while (millis() - holdStart < calibratedDuration) {
|
||||
if (digitalRead(powerPin) != LOW) {
|
||||
startDeepSleep();
|
||||
}
|
||||
delay(5);
|
||||
}
|
||||
} else {
|
||||
// X4: Use inputMgr with wait window for it to stabilize
|
||||
const auto start = millis();
|
||||
inputMgr.update();
|
||||
// inputMgr.isPressed() may take up to ~500ms to return correct state
|
||||
while (!inputMgr.isPressed(BTN_POWER) && millis() - start < 1000) {
|
||||
delay(10);
|
||||
inputMgr.update();
|
||||
}
|
||||
if (inputMgr.isPressed(BTN_POWER)) {
|
||||
do {
|
||||
delay(10);
|
||||
inputMgr.update();
|
||||
} while (inputMgr.isPressed(BTN_POWER) && inputMgr.getHeldTime() < calibratedDuration);
|
||||
if (inputMgr.getHeldTime() < calibratedDuration) {
|
||||
startDeepSleep();
|
||||
}
|
||||
} else {
|
||||
startDeepSleep();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int HalGPIO::getBatteryPercentage() const {
|
||||
static const BatteryMonitor battery = BatteryMonitor(BAT_GPIO0);
|
||||
return battery.readPercentage();
|
||||
if (_batteryUseI2C) {
|
||||
// Read SOC directly from I2C fuel gauge (16-bit LE register).
|
||||
// Returns 0 on I2C error so the UI shows 0% rather than crashing.
|
||||
Wire.beginTransmission(_batteryI2cAddr);
|
||||
Wire.write(_batterySocRegister);
|
||||
if (Wire.endTransmission(false) != 0) return 0;
|
||||
Wire.requestFrom(_batteryI2cAddr, (uint8_t)2);
|
||||
if (Wire.available() < 2) return 0;
|
||||
const uint8_t lo = Wire.read();
|
||||
const uint8_t hi = Wire.read();
|
||||
const uint16_t soc = (hi << 8) | lo;
|
||||
return soc > 100 ? 100 : soc;
|
||||
}
|
||||
static const BatteryMonitor bat(BAT_GPIO0);
|
||||
return bat.readPercentage();
|
||||
}
|
||||
|
||||
bool HalGPIO::isUsbConnected() const {
|
||||
@@ -61,4 +141,4 @@ HalGPIO::WakeupReason HalGPIO::getWakeupReason() const {
|
||||
return WakeupReason::AfterUSBPower;
|
||||
}
|
||||
return WakeupReason::Other;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,25 @@ class HalGPIO {
|
||||
InputManager inputMgr;
|
||||
#endif
|
||||
|
||||
public:
|
||||
enum class DeviceType : uint8_t { X4, X3 };
|
||||
|
||||
private:
|
||||
DeviceType _deviceType = DeviceType::X4;
|
||||
int _detectAdcValue = 0;
|
||||
int _batteryPin = BAT_GPIO0;
|
||||
// I2C fuel gauge configuration for X3 battery monitoring
|
||||
bool _batteryUseI2C = false; // Whether to use I2C fuel gauge (X3) vs ADC (X4)
|
||||
uint8_t _batteryI2cAddr = 0; // I2C address of fuel gauge chip
|
||||
uint8_t _batterySocRegister = 0; // Register address for state-of-charge
|
||||
|
||||
public:
|
||||
HalGPIO() = default;
|
||||
|
||||
// Inline device type helpers for cleaner downstream checks
|
||||
inline bool deviceIsX3() const { return _deviceType == DeviceType::X3; }
|
||||
inline bool deviceIsX4() const { return _deviceType == DeviceType::X4; }
|
||||
|
||||
// Start button GPIO and setup SPI for screen and SD card
|
||||
void begin();
|
||||
|
||||
@@ -41,12 +57,22 @@ class HalGPIO {
|
||||
// Setup wake up GPIO and enter deep sleep
|
||||
void startDeepSleep();
|
||||
|
||||
// Verify power button was held long enough after wakeup.
|
||||
// If verification fails, enters deep sleep and does not return.
|
||||
// Should only be called when wakeup reason is PowerButton.
|
||||
void verifyPowerButtonWakeup(uint16_t requiredDurationMs, bool shortPressAllowed);
|
||||
|
||||
// Get battery percentage (range 0-100)
|
||||
int getBatteryPercentage() const;
|
||||
|
||||
// Check if USB is connected
|
||||
bool isUsbConnected() const;
|
||||
|
||||
// Device detection helpers
|
||||
DeviceType getDeviceType() const { return _deviceType; }
|
||||
int getDetectAdcValue() const { return _detectAdcValue; }
|
||||
int getBatteryPin() const { return _batteryPin; }
|
||||
|
||||
enum class WakeupReason { PowerButton, AfterFlash, AfterUSBPower, Other };
|
||||
|
||||
WakeupReason getWakeupReason() const;
|
||||
@@ -60,3 +86,5 @@ class HalGPIO {
|
||||
static constexpr uint8_t BTN_DOWN = 5;
|
||||
static constexpr uint8_t BTN_POWER = 6;
|
||||
};
|
||||
|
||||
extern HalGPIO gpio;
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
#pragma once
|
||||
#include <BatteryMonitor.h>
|
||||
|
||||
#define BAT_GPIO0 0 // Battery voltage
|
||||
|
||||
static BatteryMonitor battery(BAT_GPIO0);
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "Battery.h"
|
||||
#include "CrossPointSettings.h"
|
||||
#include "CrossPointState.h"
|
||||
#include "MappedInputManager.h"
|
||||
|
||||
@@ -201,6 +201,11 @@ void CalibreConnectActivity::displayTaskLoop() {
|
||||
}
|
||||
|
||||
void CalibreConnectActivity::render() const {
|
||||
// Don't render when WifiSelectionActivity subactivity is active
|
||||
if (state == CalibreConnectState::WIFI_SELECTION) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == CalibreConnectState::SERVER_RUNNING) {
|
||||
renderer.clearScreen();
|
||||
renderServerRunning();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <Epub/Page.h>
|
||||
#include <FsHelpers.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalGPIO.h>
|
||||
#include <HalStorage.h>
|
||||
#include <Logging.h>
|
||||
|
||||
@@ -682,12 +683,13 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
|
||||
pagesUntilFullRefresh--;
|
||||
}
|
||||
|
||||
// Save bw buffer to reset buffer state after grayscale data sync
|
||||
renderer.storeBwBuffer();
|
||||
const bool useGrayscaleAA = SETTINGS.textAntiAliasing && !gpio.deviceIsX3();
|
||||
if (useGrayscaleAA) {
|
||||
// Save BW buffer only when we actually run grayscale passes.
|
||||
renderer.storeBwBuffer();
|
||||
|
||||
// grayscale rendering
|
||||
// TODO: Only do this if font supports it
|
||||
if (SETTINGS.textAntiAliasing) {
|
||||
// grayscale rendering
|
||||
// TODO: Only do this if font supports it
|
||||
renderer.clearScreen(0x00);
|
||||
renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB);
|
||||
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop);
|
||||
@@ -702,10 +704,10 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
|
||||
// display grayscale part
|
||||
renderer.displayGrayBuffer();
|
||||
renderer.setRenderMode(GfxRenderer::BW);
|
||||
}
|
||||
|
||||
// restore the bw data
|
||||
renderer.restoreBwBuffer();
|
||||
// restore the bw data
|
||||
renderer.restoreBwBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const int orientedMarginBottom,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ReaderActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalStorage.h>
|
||||
|
||||
#include "Epub.h"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "BaseTheme.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalGPIO.h>
|
||||
#include <HalStorage.h>
|
||||
#include <Logging.h>
|
||||
#include <Utf8.h>
|
||||
@@ -8,7 +9,6 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "Battery.h"
|
||||
#include "RecentBooksStore.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
@@ -23,7 +23,7 @@ constexpr int homeMarginTop = 30;
|
||||
void BaseTheme::drawBattery(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const {
|
||||
// Left aligned battery icon and percentage
|
||||
// TODO refactor this so the percentage doesnt change after we position it
|
||||
const uint16_t percentage = battery.readPercentage();
|
||||
const uint16_t percentage = gpio.getBatteryPercentage();
|
||||
if (showPercentage) {
|
||||
const auto percentageText = std::to_string(percentage) + "%";
|
||||
renderer.drawText(SMALL_FONT_ID, rect.x + batteryPercentSpacing + BaseMetrics::values.batteryWidth, rect.y,
|
||||
@@ -88,7 +88,12 @@ void BaseTheme::drawButtonHints(GfxRenderer& renderer, const char* btn1, const c
|
||||
constexpr int buttonHeight = BaseMetrics::values.buttonHintsHeight;
|
||||
constexpr int buttonY = BaseMetrics::values.buttonHintsHeight; // Distance from bottom
|
||||
constexpr int textYOffset = 7; // Distance from top of button to text baseline
|
||||
constexpr int buttonPositions[] = {25, 130, 245, 350};
|
||||
|
||||
// X3 has wider screen in portrait (528 vs 480), use more spacing
|
||||
// X3: buttons at 38, 154, 268, 384 (10px between pairs, 8px between groups)
|
||||
constexpr int x4ButtonPositions[] = {25, 130, 245, 350};
|
||||
constexpr int x3ButtonPositions[] = {38, 154, 268, 384};
|
||||
const int* buttonPositions = gpio.deviceIsX3() ? x3ButtonPositions : x4ButtonPositions;
|
||||
const char* labels[] = {btn1, btn2, btn3, btn4};
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
@@ -110,50 +115,78 @@ void BaseTheme::drawSideButtonHints(const GfxRenderer& renderer, const char* top
|
||||
const int screenWidth = renderer.getScreenWidth();
|
||||
constexpr int buttonWidth = BaseMetrics::values.sideButtonHintsWidth; // Width on screen (height when rotated)
|
||||
constexpr int buttonHeight = 80; // Height on screen (width when rotated)
|
||||
constexpr int buttonX = 4; // Distance from right edge
|
||||
// Position for the button group - buttons share a border so they're adjacent
|
||||
constexpr int topButtonY = 345; // Top button position
|
||||
constexpr int buttonMargin = 4; // Distance from edge
|
||||
|
||||
const char* labels[] = {topBtn, bottomBtn};
|
||||
if (gpio.deviceIsX3()) {
|
||||
// X3 layout: Up on left side, Down on right side, positioned higher
|
||||
constexpr int x3ButtonY = 155; // Higher position for X3
|
||||
|
||||
// Draw the shared border for both buttons as one unit
|
||||
const int x = screenWidth - buttonX - buttonWidth;
|
||||
// Draw Up button on left side (topBtn)
|
||||
if (topBtn != nullptr && topBtn[0] != '\0') {
|
||||
const int leftX = buttonMargin;
|
||||
renderer.drawRect(leftX, x3ButtonY, buttonWidth, buttonHeight);
|
||||
|
||||
// Draw top button outline (3 sides, bottom open)
|
||||
if (topBtn != nullptr && topBtn[0] != '\0') {
|
||||
renderer.drawLine(x, topButtonY, x + buttonWidth - 1, topButtonY); // Top
|
||||
renderer.drawLine(x, topButtonY, x, topButtonY + buttonHeight - 1); // Left
|
||||
renderer.drawLine(x + buttonWidth - 1, topButtonY, x + buttonWidth - 1, topButtonY + buttonHeight - 1); // Right
|
||||
}
|
||||
|
||||
// Draw shared middle border
|
||||
if ((topBtn != nullptr && topBtn[0] != '\0') || (bottomBtn != nullptr && bottomBtn[0] != '\0')) {
|
||||
renderer.drawLine(x, topButtonY + buttonHeight, x + buttonWidth - 1, topButtonY + buttonHeight); // Shared border
|
||||
}
|
||||
|
||||
// Draw bottom button outline (3 sides, top is shared)
|
||||
if (bottomBtn != nullptr && bottomBtn[0] != '\0') {
|
||||
renderer.drawLine(x, topButtonY + buttonHeight, x, topButtonY + 2 * buttonHeight - 1); // Left
|
||||
renderer.drawLine(x + buttonWidth - 1, topButtonY + buttonHeight, x + buttonWidth - 1,
|
||||
topButtonY + 2 * buttonHeight - 1); // Right
|
||||
renderer.drawLine(x, topButtonY + 2 * buttonHeight - 1, x + buttonWidth - 1,
|
||||
topButtonY + 2 * buttonHeight - 1); // Bottom
|
||||
}
|
||||
|
||||
// Draw text for each button
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (labels[i] != nullptr && labels[i][0] != '\0') {
|
||||
const int y = topButtonY + i * buttonHeight;
|
||||
|
||||
// Draw rotated text centered in the button
|
||||
const int textWidth = renderer.getTextWidth(SMALL_FONT_ID, labels[i]);
|
||||
const int textWidth = renderer.getTextWidth(SMALL_FONT_ID, topBtn);
|
||||
const int textHeight = renderer.getTextHeight(SMALL_FONT_ID);
|
||||
const int textX = leftX + (buttonWidth - textHeight) / 2;
|
||||
const int textY = x3ButtonY + (buttonHeight + textWidth) / 2;
|
||||
renderer.drawTextRotated90CW(SMALL_FONT_ID, textX, textY, topBtn);
|
||||
}
|
||||
|
||||
// Center the rotated text in the button
|
||||
const int textX = x + (buttonWidth - textHeight) / 2;
|
||||
const int textY = y + (buttonHeight + textWidth) / 2;
|
||||
// Draw Down button on right side (bottomBtn)
|
||||
if (bottomBtn != nullptr && bottomBtn[0] != '\0') {
|
||||
const int rightX = screenWidth - buttonMargin - buttonWidth;
|
||||
renderer.drawRect(rightX, x3ButtonY, buttonWidth, buttonHeight);
|
||||
|
||||
renderer.drawTextRotated90CW(SMALL_FONT_ID, textX, textY, labels[i]);
|
||||
const int textWidth = renderer.getTextWidth(SMALL_FONT_ID, bottomBtn);
|
||||
const int textHeight = renderer.getTextHeight(SMALL_FONT_ID);
|
||||
const int textX = rightX + (buttonWidth - textHeight) / 2;
|
||||
const int textY = x3ButtonY + (buttonHeight + textWidth) / 2;
|
||||
renderer.drawTextRotated90CW(SMALL_FONT_ID, textX, textY, bottomBtn);
|
||||
}
|
||||
} else {
|
||||
// X4 layout: Both buttons stacked on right side
|
||||
constexpr int topButtonY = 345; // Top button position
|
||||
const int x = screenWidth - buttonMargin - buttonWidth;
|
||||
|
||||
const char* labels[] = {topBtn, bottomBtn};
|
||||
|
||||
// Draw top button outline (3 sides, bottom open)
|
||||
if (topBtn != nullptr && topBtn[0] != '\0') {
|
||||
renderer.drawLine(x, topButtonY, x + buttonWidth - 1, topButtonY); // Top
|
||||
renderer.drawLine(x, topButtonY, x, topButtonY + buttonHeight - 1); // Left
|
||||
renderer.drawLine(x + buttonWidth - 1, topButtonY, x + buttonWidth - 1, topButtonY + buttonHeight - 1); // Right
|
||||
}
|
||||
|
||||
// Draw shared middle border
|
||||
if ((topBtn != nullptr && topBtn[0] != '\0') || (bottomBtn != nullptr && bottomBtn[0] != '\0')) {
|
||||
renderer.drawLine(x, topButtonY + buttonHeight, x + buttonWidth - 1, topButtonY + buttonHeight); // Shared border
|
||||
}
|
||||
|
||||
// Draw bottom button outline (3 sides, top is shared)
|
||||
if (bottomBtn != nullptr && bottomBtn[0] != '\0') {
|
||||
renderer.drawLine(x, topButtonY + buttonHeight, x, topButtonY + 2 * buttonHeight - 1); // Left
|
||||
renderer.drawLine(x + buttonWidth - 1, topButtonY + buttonHeight, x + buttonWidth - 1,
|
||||
topButtonY + 2 * buttonHeight - 1); // Right
|
||||
renderer.drawLine(x, topButtonY + 2 * buttonHeight - 1, x + buttonWidth - 1,
|
||||
topButtonY + 2 * buttonHeight - 1); // Bottom
|
||||
}
|
||||
|
||||
// Draw text for each button
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (labels[i] != nullptr && labels[i][0] != '\0') {
|
||||
const int y = topButtonY + i * buttonHeight;
|
||||
|
||||
// Draw rotated text centered in the button
|
||||
const int textWidth = renderer.getTextWidth(SMALL_FONT_ID, labels[i]);
|
||||
const int textHeight = renderer.getTextHeight(SMALL_FONT_ID);
|
||||
|
||||
// Center the rotated text in the button
|
||||
const int textX = x + (buttonWidth - textHeight) / 2;
|
||||
const int textY = y + (buttonHeight + textWidth) / 2;
|
||||
|
||||
renderer.drawTextRotated90CW(SMALL_FONT_ID, textX, textY, labels[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,7 +266,7 @@ void BaseTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* t
|
||||
SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS;
|
||||
int batteryX = rect.x + rect.width - BaseMetrics::values.contentSidePadding - BaseMetrics::values.batteryWidth;
|
||||
if (showBatteryPercentage) {
|
||||
const uint16_t percentage = battery.readPercentage();
|
||||
const uint16_t percentage = gpio.getBatteryPercentage();
|
||||
const auto percentageText = std::to_string(percentage) + "%";
|
||||
batteryX -= renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "LyraTheme.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalGPIO.h>
|
||||
#include <HalStorage.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "Battery.h"
|
||||
#include "RecentBooksStore.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
@@ -22,7 +22,7 @@ constexpr int topHintButtonY = 345;
|
||||
|
||||
void LyraTheme::drawBattery(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const {
|
||||
// Left aligned battery icon and percentage
|
||||
const uint16_t percentage = battery.readPercentage();
|
||||
const uint16_t percentage = gpio.getBatteryPercentage();
|
||||
if (showPercentage) {
|
||||
const auto percentageText = std::to_string(percentage) + "%";
|
||||
renderer.drawText(SMALL_FONT_ID, rect.x + batteryPercentSpacing + LyraMetrics::values.batteryWidth, rect.y,
|
||||
@@ -64,7 +64,7 @@ void LyraTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* t
|
||||
SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS;
|
||||
int batteryX = rect.x + rect.width - LyraMetrics::values.contentSidePadding - LyraMetrics::values.batteryWidth;
|
||||
if (showBatteryPercentage) {
|
||||
const uint16_t percentage = battery.readPercentage();
|
||||
const uint16_t percentage = gpio.getBatteryPercentage();
|
||||
const auto percentageText = std::to_string(percentage) + "%";
|
||||
batteryX -= renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
|
||||
}
|
||||
@@ -199,7 +199,13 @@ void LyraTheme::drawButtonHints(GfxRenderer& renderer, const char* btn1, const c
|
||||
constexpr int buttonHeight = LyraMetrics::values.buttonHintsHeight;
|
||||
constexpr int buttonY = LyraMetrics::values.buttonHintsHeight; // Distance from bottom
|
||||
constexpr int textYOffset = 7; // Distance from top of button to text baseline
|
||||
constexpr int buttonPositions[] = {58, 146, 254, 342};
|
||||
|
||||
// X3 has wider screen in portrait (528 vs 480), use more spacing
|
||||
// X4: buttons at 58, 146, 254, 342 (8px between pairs, 28px between groups)
|
||||
// X3: buttons at 65, 157, 291, 383 (12px between pairs, 54px between groups)
|
||||
constexpr int x4ButtonPositions[] = {58, 146, 254, 342};
|
||||
constexpr int x3ButtonPositions[] = {65, 157, 291, 383};
|
||||
const int* buttonPositions = gpio.deviceIsX3() ? x3ButtonPositions : x4ButtonPositions;
|
||||
const char* labels[] = {btn1, btn2, btn3, btn4};
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
@@ -225,34 +231,52 @@ void LyraTheme::drawSideButtonHints(const GfxRenderer& renderer, const char* top
|
||||
const int screenWidth = renderer.getScreenWidth();
|
||||
constexpr int buttonWidth = LyraMetrics::values.sideButtonHintsWidth; // Width on screen (height when rotated)
|
||||
constexpr int buttonHeight = 78; // Height on screen (width when rotated)
|
||||
// Position for the button group - buttons share a border so they're adjacent
|
||||
constexpr int buttonMargin = 0;
|
||||
|
||||
const char* labels[] = {topBtn, bottomBtn};
|
||||
if (gpio.deviceIsX3()) {
|
||||
// X3 layout: Up on left side, Down on right side, positioned higher
|
||||
constexpr int x3ButtonY = 155;
|
||||
|
||||
// Draw the shared border for both buttons as one unit
|
||||
const int x = screenWidth - buttonWidth;
|
||||
// Draw Up button on left side (topBtn) - rounded corners on right (facing inward)
|
||||
if (topBtn != nullptr && topBtn[0] != '\0') {
|
||||
renderer.drawRoundedRect(buttonMargin, x3ButtonY, buttonWidth, buttonHeight, 1, cornerRadius, false, true, false,
|
||||
true, true);
|
||||
const int textWidth = renderer.getTextWidth(SMALL_FONT_ID, topBtn);
|
||||
renderer.drawTextRotated90CW(SMALL_FONT_ID, buttonMargin, x3ButtonY + (buttonHeight + textWidth) / 2, topBtn);
|
||||
}
|
||||
|
||||
// Draw top button outline
|
||||
if (topBtn != nullptr && topBtn[0] != '\0') {
|
||||
renderer.drawRoundedRect(x, topHintButtonY, buttonWidth, buttonHeight, 1, cornerRadius, true, false, true, false,
|
||||
true);
|
||||
}
|
||||
// Draw Down button on right side (bottomBtn)
|
||||
if (bottomBtn != nullptr && bottomBtn[0] != '\0') {
|
||||
const int rightX = screenWidth - buttonWidth;
|
||||
renderer.drawRoundedRect(rightX, x3ButtonY, buttonWidth, buttonHeight, 1, cornerRadius, true, false, true, false,
|
||||
true);
|
||||
const int textWidth = renderer.getTextWidth(SMALL_FONT_ID, bottomBtn);
|
||||
renderer.drawTextRotated90CW(SMALL_FONT_ID, rightX, x3ButtonY + (buttonHeight + textWidth) / 2, bottomBtn);
|
||||
}
|
||||
} else {
|
||||
// X4 layout: Both buttons stacked on right side
|
||||
const char* labels[] = {topBtn, bottomBtn};
|
||||
const int x = screenWidth - buttonWidth;
|
||||
|
||||
// Draw bottom button outline
|
||||
if (bottomBtn != nullptr && bottomBtn[0] != '\0') {
|
||||
renderer.drawRoundedRect(x, topHintButtonY + buttonHeight + 5, buttonWidth, buttonHeight, 1, cornerRadius, true,
|
||||
false, true, false, true);
|
||||
}
|
||||
// Draw top button outline
|
||||
if (topBtn != nullptr && topBtn[0] != '\0') {
|
||||
renderer.drawRoundedRect(x, topHintButtonY, buttonWidth, buttonHeight, 1, cornerRadius, true, false, true, false,
|
||||
true);
|
||||
}
|
||||
|
||||
// Draw text for each button
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (labels[i] != nullptr && labels[i][0] != '\0') {
|
||||
const int y = topHintButtonY + (i * buttonHeight + 5);
|
||||
// Draw bottom button outline
|
||||
if (bottomBtn != nullptr && bottomBtn[0] != '\0') {
|
||||
renderer.drawRoundedRect(x, topHintButtonY + buttonHeight + 5, buttonWidth, buttonHeight, 1, cornerRadius, true,
|
||||
false, true, false, true);
|
||||
}
|
||||
|
||||
// Draw rotated text centered in the button
|
||||
const int textWidth = renderer.getTextWidth(SMALL_FONT_ID, labels[i]);
|
||||
|
||||
renderer.drawTextRotated90CW(SMALL_FONT_ID, x, y + (buttonHeight + textWidth) / 2, labels[i]);
|
||||
// Draw text for each button
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (labels[i] != nullptr && labels[i][0] != '\0') {
|
||||
const int y = topHintButtonY + (i * buttonHeight + 5);
|
||||
const int textWidth = renderer.getTextWidth(SMALL_FONT_ID, labels[i]);
|
||||
renderer.drawTextRotated90CW(SMALL_FONT_ID, x, y + (buttonHeight + textWidth) / 2, labels[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-56
@@ -10,7 +10,6 @@
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "Battery.h"
|
||||
#include "CrossPointSettings.h"
|
||||
#include "CrossPointState.h"
|
||||
#include "KOReaderCredentialStore.h"
|
||||
@@ -31,7 +30,6 @@
|
||||
#include "util/ButtonNavigator.h"
|
||||
|
||||
HalDisplay display;
|
||||
HalGPIO gpio;
|
||||
MappedInputManager mappedInputManager(gpio);
|
||||
GfxRenderer renderer(display);
|
||||
Activity* currentActivity;
|
||||
@@ -125,10 +123,6 @@ EpdFont ui12RegularFont(&ubuntu_12_regular);
|
||||
EpdFont ui12BoldFont(&ubuntu_12_bold);
|
||||
EpdFontFamily ui12FontFamily(&ui12RegularFont, &ui12BoldFont);
|
||||
|
||||
// measurement of power button press duration calibration value
|
||||
unsigned long t1 = 0;
|
||||
unsigned long t2 = 0;
|
||||
|
||||
void exitActivity() {
|
||||
if (currentActivity) {
|
||||
currentActivity->onExit();
|
||||
@@ -142,50 +136,6 @@ void enterNewActivity(Activity* activity) {
|
||||
currentActivity->onEnter();
|
||||
}
|
||||
|
||||
// Verify power button press duration on wake-up from deep sleep
|
||||
// Pre-condition: isWakeupByPowerButton() == true
|
||||
void verifyPowerButtonDuration() {
|
||||
if (SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::SLEEP) {
|
||||
// Fast path for short press
|
||||
// Needed because inputManager.isPressed() may take up to ~500ms to return the correct state
|
||||
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;
|
||||
// Subtract the current time, because inputManager only starts counting the HeldTime from the first update()
|
||||
// This way, we remove the time we already took to reach here from the duration,
|
||||
// assuming the button was held until now from millis()==0 (i.e. device start time).
|
||||
const uint16_t calibration = start;
|
||||
const uint16_t calibratedPressDuration =
|
||||
(calibration < SETTINGS.getPowerButtonDuration()) ? SETTINGS.getPowerButtonDuration() - calibration : 1;
|
||||
|
||||
gpio.update();
|
||||
// Needed because inputManager.isPressed() may take up to ~500ms to return the correct state
|
||||
while (!gpio.isPressed(HalGPIO::BTN_POWER) && millis() - start < 1000) {
|
||||
delay(10); // only wait 10ms each iteration to not delay too much in case of short configured duration.
|
||||
gpio.update();
|
||||
}
|
||||
|
||||
t2 = millis();
|
||||
if (gpio.isPressed(HalGPIO::BTN_POWER)) {
|
||||
do {
|
||||
delay(10);
|
||||
gpio.update();
|
||||
} while (gpio.isPressed(HalGPIO::BTN_POWER) && gpio.getHeldTime() < calibratedPressDuration);
|
||||
abort = gpio.getHeldTime() < calibratedPressDuration;
|
||||
} else {
|
||||
abort = true;
|
||||
}
|
||||
|
||||
if (abort) {
|
||||
// Button released too early. Returning to sleep.
|
||||
// IMPORTANT: Re-arm the wakeup trigger before sleeping again
|
||||
gpio.startDeepSleep();
|
||||
}
|
||||
}
|
||||
|
||||
void waitForPowerRelease() {
|
||||
gpio.update();
|
||||
while (gpio.isPressed(HalGPIO::BTN_POWER)) {
|
||||
@@ -202,7 +152,6 @@ void enterDeepSleep() {
|
||||
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
|
||||
|
||||
display.deepSleep();
|
||||
LOG_DBG("MAIN", "Power button press calibration value: %lu ms", t2 - t1);
|
||||
LOG_DBG("MAIN", "Entering deep sleep");
|
||||
|
||||
gpio.startDeepSleep();
|
||||
@@ -279,8 +228,6 @@ void setupDisplayAndFonts() {
|
||||
}
|
||||
|
||||
void setup() {
|
||||
t1 = millis();
|
||||
|
||||
gpio.begin();
|
||||
|
||||
// Only start serial if USB connected
|
||||
@@ -308,11 +255,12 @@ void setup() {
|
||||
UITheme::getInstance().reload();
|
||||
ButtonNavigator::setMappedInputManager(mappedInputManager);
|
||||
|
||||
switch (gpio.getWakeupReason()) {
|
||||
const auto wakeupReason = gpio.getWakeupReason();
|
||||
switch (wakeupReason) {
|
||||
case HalGPIO::WakeupReason::PowerButton:
|
||||
// For normal wakeups, verify power button press duration
|
||||
LOG_DBG("MAIN", "Verifying power button press duration");
|
||||
verifyPowerButtonDuration();
|
||||
gpio.verifyPowerButtonWakeup(SETTINGS.getPowerButtonDuration(),
|
||||
SETTINGS.shortPwrBtn == CrossPointSettings::SHORT_PWRBTN::SLEEP);
|
||||
break;
|
||||
case HalGPIO::WakeupReason::AfterUSBPower:
|
||||
// If USB power caused a cold boot, go back to sleep
|
||||
|
||||
Reference in New Issue
Block a user