Fixes maintainer requests

This commit is contained in:
Justin Mitchell
2026-02-13 19:04:58 -05:00
parent 4f0a39ab03
commit a78897478f
11 changed files with 46 additions and 102 deletions
+1 -1
View File
@@ -9,4 +9,4 @@ build
**/__pycache__/ **/__pycache__/
/compile_commands.json /compile_commands.json
/.cache /.cache
notes.md /notes.md
+23 -2
View File
@@ -1,5 +1,6 @@
#include <HalGPIO.h> #include <HalGPIO.h>
#include <SPI.h> #include <SPI.h>
#include <Wire.h>
#include <esp_sleep.h> #include <esp_sleep.h>
void HalGPIO::begin() { void HalGPIO::begin() {
@@ -14,6 +15,16 @@ void HalGPIO::begin() {
pinMode(_batteryPin, INPUT); pinMode(_batteryPin, INPUT);
pinMode(UART0_RXD, 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);
_useI2C = true;
_i2cAddr = 0x55;
_socRegister = 0x2C;
}
} }
void HalGPIO::update() { inputMgr.update(); } void HalGPIO::update() { inputMgr.update(); }
@@ -43,8 +54,18 @@ void HalGPIO::startDeepSleep() {
} }
int HalGPIO::getBatteryPercentage() const { int HalGPIO::getBatteryPercentage() const {
if (_deviceType == DeviceType::X3) { if (_useI2C) {
return 0; // 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(_i2cAddr);
Wire.write(_socRegister);
if (Wire.endTransmission(false) != 0) return 0;
Wire.requestFrom(_i2cAddr, (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); static const BatteryMonitor bat(BAT_GPIO0);
return bat.readPercentage(); return bat.readPercentage();
+5
View File
@@ -30,6 +30,9 @@ class HalGPIO {
DeviceType _deviceType = DeviceType::X4; DeviceType _deviceType = DeviceType::X4;
int _detectAdcValue = 0; int _detectAdcValue = 0;
int _batteryPin = BAT_GPIO0; int _batteryPin = BAT_GPIO0;
bool _useI2C = false;
uint8_t _i2cAddr = 0;
uint8_t _socRegister = 0;
public: public:
HalGPIO() = default; HalGPIO() = default;
@@ -73,3 +76,5 @@ class HalGPIO {
static constexpr uint8_t BTN_DOWN = 5; static constexpr uint8_t BTN_DOWN = 5;
static constexpr uint8_t BTN_POWER = 6; static constexpr uint8_t BTN_POWER = 6;
}; };
extern HalGPIO gpio;
+6 -4
View File
@@ -41,10 +41,12 @@ extra_scripts =
; Libraries ; Libraries
lib_deps = lib_deps =
BatteryMonitor=symlink://open-x4-sdk/libs/hardware/BatteryMonitor ; Use standalone community-sdk checkout (sibling folder) instead of submodule.
InputManager=symlink://open-x4-sdk/libs/hardware/InputManager ; This makes local SDK edits in ../community-sdk immediately used by crosspoint-reader.
EInkDisplay=symlink://open-x4-sdk/libs/display/EInkDisplay BatteryMonitor=symlink://../community-sdk/libs/hardware/BatteryMonitor
SDCardManager=symlink://open-x4-sdk/libs/hardware/SDCardManager InputManager=symlink://../community-sdk/libs/hardware/InputManager
EInkDisplay=symlink://../community-sdk/libs/display/EInkDisplay
SDCardManager=symlink://../community-sdk/libs/hardware/SDCardManager
bblanchon/ArduinoJson @ 7.4.2 bblanchon/ArduinoJson @ 7.4.2
ricmoo/QRCode @ 0.0.1 ricmoo/QRCode @ 0.0.1
links2004/WebSockets @ 2.7.3 links2004/WebSockets @ 2.7.3
-33
View File
@@ -1,33 +0,0 @@
#include "Battery.h"
#include <Wire.h>
void BatteryProvider::setI2CFuelGauge(uint8_t i2cAddr, uint8_t socRegister) {
_useI2C = true;
_i2cAddr = i2cAddr;
_socRegister = socRegister;
}
uint16_t BatteryProvider::readPercentage() const {
if (_useI2C) {
// 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(_i2cAddr);
Wire.write(_socRegister);
if (Wire.endTransmission(false) != 0) return 0;
Wire.requestFrom(_i2cAddr, (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;
}
// ADC path: read raw voltage, apply divider, convert via LiPo polynomial
return _adcMonitor.readPercentage();
}
// Meyer's singleton — single shared instance across all translation units.
// Defaults to X4 ADC mode. For X3, main.cpp calls setI2CFuelGauge() to switch.
BatteryProvider& battery() {
static BatteryProvider instance;
return instance;
}
-29
View File
@@ -1,29 +0,0 @@
#pragma once
#include <BatteryMonitor.h>
#include <cstdint>
#define BAT_GPIO0 0 // Battery voltage (X4 ADC pin)
// Unified battery reader supporting two backends:
// - X4: ADC voltage divider on GPIO0 (default, no setup needed)
// - X3: BQ27220 fuel gauge via I2C at 0x55, SOC register 0x2C
// (call setI2CFuelGauge() after Wire.begin())
class BatteryProvider {
public:
// Read battery percentage (0-100). Delegates to ADC or I2C depending on mode.
uint16_t readPercentage() const;
// Switch to I2C fuel gauge mode. Wire.begin() must be called first.
// i2cAddr: fuel gauge I2C address (e.g. 0x55 for BQ27220)
// socRegister: register holding state-of-charge 0-100% (e.g. 0x2C)
void setI2CFuelGauge(uint8_t i2cAddr, uint8_t socRegister);
private:
BatteryMonitor _adcMonitor{BAT_GPIO0};
bool _useI2C = false;
uint8_t _i2cAddr = 0;
uint8_t _socRegister = 0;
};
// Shared singleton used by themes and activities.
BatteryProvider& battery();
-1
View File
@@ -10,7 +10,6 @@
#include <cstring> #include <cstring>
#include <vector> #include <vector>
#include "Battery.h"
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
#include "CrossPointState.h" #include "CrossPointState.h"
#include "MappedInputManager.h" #include "MappedInputManager.h"
+2 -7
View File
@@ -3,6 +3,7 @@
#include <Epub/Page.h> #include <Epub/Page.h>
#include <FsHelpers.h> #include <FsHelpers.h>
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <HalGPIO.h>
#include <HalStorage.h> #include <HalStorage.h>
#include <Logging.h> #include <Logging.h>
@@ -34,12 +35,6 @@ int clampPercent(int percent) {
return percent; return percent;
} }
bool isX3DisplayGeometry(const GfxRenderer& renderer) {
const int w = renderer.getScreenWidth();
const int h = renderer.getScreenHeight();
return (w == 792 && h == 528) || (w == 528 && h == 792);
}
// Apply the logical reader orientation to the renderer. // Apply the logical reader orientation to the renderer.
// This centralizes orientation mapping so we don't duplicate switch logic elsewhere. // This centralizes orientation mapping so we don't duplicate switch logic elsewhere.
void applyReaderOrientation(GfxRenderer& renderer, const uint8_t orientation) { void applyReaderOrientation(GfxRenderer& renderer, const uint8_t orientation) {
@@ -688,7 +683,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
pagesUntilFullRefresh--; pagesUntilFullRefresh--;
} }
const bool useGrayscaleAA = SETTINGS.textAntiAliasing && !isX3DisplayGeometry(renderer); const bool useGrayscaleAA = SETTINGS.textAntiAliasing && gpio.getDeviceType() != HalGPIO::DeviceType::X3;
if (useGrayscaleAA) { if (useGrayscaleAA) {
// Save BW buffer only when we actually run grayscale passes. // Save BW buffer only when we actually run grayscale passes.
renderer.storeBwBuffer(); renderer.storeBwBuffer();
+3 -3
View File
@@ -8,7 +8,7 @@
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include "Battery.h" #include <HalGPIO.h>
#include "RecentBooksStore.h" #include "RecentBooksStore.h"
#include "components/UITheme.h" #include "components/UITheme.h"
#include "fontIds.h" #include "fontIds.h"
@@ -23,7 +23,7 @@ constexpr int homeMarginTop = 30;
void BaseTheme::drawBattery(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const { void BaseTheme::drawBattery(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const {
// Left aligned battery icon and percentage // Left aligned battery icon and percentage
// TODO refactor this so the percentage doesnt change after we position it // 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) { if (showPercentage) {
const auto percentageText = std::to_string(percentage) + "%"; const auto percentageText = std::to_string(percentage) + "%";
renderer.drawText(SMALL_FONT_ID, rect.x + batteryPercentSpacing + BaseMetrics::values.batteryWidth, rect.y, renderer.drawText(SMALL_FONT_ID, rect.x + batteryPercentSpacing + BaseMetrics::values.batteryWidth, rect.y,
@@ -233,7 +233,7 @@ void BaseTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* t
SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS; SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS;
int batteryX = rect.x + rect.width - BaseMetrics::values.contentSidePadding - BaseMetrics::values.batteryWidth; int batteryX = rect.x + rect.width - BaseMetrics::values.contentSidePadding - BaseMetrics::values.batteryWidth;
if (showBatteryPercentage) { if (showBatteryPercentage) {
const uint16_t percentage = battery().readPercentage(); const uint16_t percentage = gpio.getBatteryPercentage();
const auto percentageText = std::to_string(percentage) + "%"; const auto percentageText = std::to_string(percentage) + "%";
batteryX -= renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str()); batteryX -= renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
} }
+3 -3
View File
@@ -6,7 +6,7 @@
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include "Battery.h" #include <HalGPIO.h>
#include "RecentBooksStore.h" #include "RecentBooksStore.h"
#include "components/UITheme.h" #include "components/UITheme.h"
#include "fontIds.h" #include "fontIds.h"
@@ -22,7 +22,7 @@ constexpr int topHintButtonY = 345;
void LyraTheme::drawBattery(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const { void LyraTheme::drawBattery(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const {
// Left aligned battery icon and percentage // Left aligned battery icon and percentage
const uint16_t percentage = battery().readPercentage(); const uint16_t percentage = gpio.getBatteryPercentage();
if (showPercentage) { if (showPercentage) {
const auto percentageText = std::to_string(percentage) + "%"; const auto percentageText = std::to_string(percentage) + "%";
renderer.drawText(SMALL_FONT_ID, rect.x + batteryPercentSpacing + LyraMetrics::values.batteryWidth, rect.y, 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; SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS;
int batteryX = rect.x + rect.width - LyraMetrics::values.contentSidePadding - LyraMetrics::values.batteryWidth; int batteryX = rect.x + rect.width - LyraMetrics::values.contentSidePadding - LyraMetrics::values.batteryWidth;
if (showBatteryPercentage) { if (showBatteryPercentage) {
const uint16_t percentage = battery().readPercentage(); const uint16_t percentage = gpio.getBatteryPercentage();
const auto percentageText = std::to_string(percentage) + "%"; const auto percentageText = std::to_string(percentage) + "%";
batteryX -= renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str()); batteryX -= renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
} }
+3 -19
View File
@@ -6,12 +6,9 @@
#include <HalStorage.h> #include <HalStorage.h>
#include <Logging.h> #include <Logging.h>
#include <SPI.h> #include <SPI.h>
#include <Wire.h>
#include <builtinFonts/all.h> #include <builtinFonts/all.h>
#include <cstring> #include <cstring>
#include "Battery.h"
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
#include "CrossPointState.h" #include "CrossPointState.h"
#include "KOReaderCredentialStore.h" #include "KOReaderCredentialStore.h"
@@ -130,12 +127,6 @@ EpdFontFamily ui12FontFamily(&ui12RegularFont, &ui12BoldFont);
unsigned long t1 = 0; unsigned long t1 = 0;
unsigned long t2 = 0; unsigned long t2 = 0;
inline void requestResyncIfX3(uint8_t settlePasses = 0) {
if (gpio.getDeviceType() == HalGPIO::DeviceType::X3) {
display.requestResync(settlePasses);
}
}
void exitActivity() { void exitActivity() {
if (currentActivity) { if (currentActivity) {
currentActivity->onExit(); currentActivity->onExit();
@@ -243,7 +234,7 @@ void enterDeepSleep() {
APP_STATE.lastSleepFromReader = currentActivity && currentActivity->isReaderActivity(); APP_STATE.lastSleepFromReader = currentActivity && currentActivity->isReaderActivity();
APP_STATE.saveToFile(); APP_STATE.saveToFile();
exitActivity(); exitActivity();
requestResyncIfX3(0); display.requestResync();
enterNewActivity(new SleepActivity(renderer, mappedInputManager)); enterNewActivity(new SleepActivity(renderer, mappedInputManager));
display.deepSleep(); display.deepSleep();
@@ -294,9 +285,7 @@ void onGoToBrowser() {
void onGoHome() { void onGoHome() {
const bool returningFromReader = currentActivity && currentActivity->isReaderActivity(); const bool returningFromReader = currentActivity && currentActivity->isReaderActivity();
if (returningFromReader && (gpio.getDeviceType() == HalGPIO::DeviceType::X3)) { if (returningFromReader) {
// Force Home's first frame to run a full resync on X3.
// Avoid doing a blocking scrub refresh before activity transition.
display.requestResync(1); display.requestResync(1);
} }
exitActivity(); exitActivity();
@@ -307,11 +296,6 @@ void onGoHome() {
void setupDisplayAndFonts() { void setupDisplayAndFonts() {
if (gpio.getDeviceType() == HalGPIO::DeviceType::X3) { if (gpio.getDeviceType() == HalGPIO::DeviceType::X3) {
display.setDisplayDimensions(792, 528); display.setDisplayDimensions(792, 528);
// X3 has a BQ27220 fuel gauge on I2C (addr 0x55) instead of an ADC voltage
// divider. SOC (0-100%) is read directly from register 0x2C.
// I2C bus: SDA=GPIO20, SCL=GPIO0, 400kHz (matches stock X3 firmware).
Wire.begin(20, 0, 400000);
battery().setI2CFuelGauge(0x55, 0x2C);
} }
display.begin(); display.begin();
renderer.begin(); renderer.begin();
@@ -401,7 +385,7 @@ void setup() {
setupDisplayAndFonts(); setupDisplayAndFonts();
if (wakeupReason == HalGPIO::WakeupReason::PowerButton || wakeupReason == HalGPIO::WakeupReason::AfterFlash || if (wakeupReason == HalGPIO::WakeupReason::PowerButton || wakeupReason == HalGPIO::WakeupReason::AfterFlash ||
wakeupReason == HalGPIO::WakeupReason::Other) { wakeupReason == HalGPIO::WakeupReason::Other) {
requestResyncIfX3(0); display.requestResync();
} }
exitActivity(); exitActivity();