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__/
/compile_commands.json
/.cache
notes.md
/notes.md
+23 -2
View File
@@ -1,5 +1,6 @@
#include <HalGPIO.h>
#include <SPI.h>
#include <Wire.h>
#include <esp_sleep.h>
void HalGPIO::begin() {
@@ -14,6 +15,16 @@ void HalGPIO::begin() {
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);
_useI2C = true;
_i2cAddr = 0x55;
_socRegister = 0x2C;
}
}
void HalGPIO::update() { inputMgr.update(); }
@@ -43,8 +54,18 @@ void HalGPIO::startDeepSleep() {
}
int HalGPIO::getBatteryPercentage() const {
if (_deviceType == DeviceType::X3) {
return 0;
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;
}
static const BatteryMonitor bat(BAT_GPIO0);
return bat.readPercentage();
+5
View File
@@ -30,6 +30,9 @@ class HalGPIO {
DeviceType _deviceType = DeviceType::X4;
int _detectAdcValue = 0;
int _batteryPin = BAT_GPIO0;
bool _useI2C = false;
uint8_t _i2cAddr = 0;
uint8_t _socRegister = 0;
public:
HalGPIO() = default;
@@ -73,3 +76,5 @@ class HalGPIO {
static constexpr uint8_t BTN_DOWN = 5;
static constexpr uint8_t BTN_POWER = 6;
};
extern HalGPIO gpio;
+6 -4
View File
@@ -41,10 +41,12 @@ extra_scripts =
; Libraries
lib_deps =
BatteryMonitor=symlink://open-x4-sdk/libs/hardware/BatteryMonitor
InputManager=symlink://open-x4-sdk/libs/hardware/InputManager
EInkDisplay=symlink://open-x4-sdk/libs/display/EInkDisplay
SDCardManager=symlink://open-x4-sdk/libs/hardware/SDCardManager
; Use standalone community-sdk checkout (sibling folder) instead of submodule.
; This makes local SDK edits in ../community-sdk immediately used by crosspoint-reader.
BatteryMonitor=symlink://../community-sdk/libs/hardware/BatteryMonitor
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
ricmoo/QRCode @ 0.0.1
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 <vector>
#include "Battery.h"
#include "CrossPointSettings.h"
#include "CrossPointState.h"
#include "MappedInputManager.h"
+2 -7
View File
@@ -3,6 +3,7 @@
#include <Epub/Page.h>
#include <FsHelpers.h>
#include <GfxRenderer.h>
#include <HalGPIO.h>
#include <HalStorage.h>
#include <Logging.h>
@@ -34,12 +35,6 @@ int clampPercent(int 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.
// This centralizes orientation mapping so we don't duplicate switch logic elsewhere.
void applyReaderOrientation(GfxRenderer& renderer, const uint8_t orientation) {
@@ -688,7 +683,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
pagesUntilFullRefresh--;
}
const bool useGrayscaleAA = SETTINGS.textAntiAliasing && !isX3DisplayGeometry(renderer);
const bool useGrayscaleAA = SETTINGS.textAntiAliasing && gpio.getDeviceType() != HalGPIO::DeviceType::X3;
if (useGrayscaleAA) {
// Save BW buffer only when we actually run grayscale passes.
renderer.storeBwBuffer();
+3 -3
View File
@@ -8,7 +8,7 @@
#include <cstdint>
#include <string>
#include "Battery.h"
#include <HalGPIO.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,
@@ -233,7 +233,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());
}
+3 -3
View File
@@ -6,7 +6,7 @@
#include <cstdint>
#include <string>
#include "Battery.h"
#include <HalGPIO.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());
}
+3 -19
View File
@@ -6,12 +6,9 @@
#include <HalStorage.h>
#include <Logging.h>
#include <SPI.h>
#include <Wire.h>
#include <builtinFonts/all.h>
#include <cstring>
#include "Battery.h"
#include "CrossPointSettings.h"
#include "CrossPointState.h"
#include "KOReaderCredentialStore.h"
@@ -130,12 +127,6 @@ EpdFontFamily ui12FontFamily(&ui12RegularFont, &ui12BoldFont);
unsigned long t1 = 0;
unsigned long t2 = 0;
inline void requestResyncIfX3(uint8_t settlePasses = 0) {
if (gpio.getDeviceType() == HalGPIO::DeviceType::X3) {
display.requestResync(settlePasses);
}
}
void exitActivity() {
if (currentActivity) {
currentActivity->onExit();
@@ -243,7 +234,7 @@ void enterDeepSleep() {
APP_STATE.lastSleepFromReader = currentActivity && currentActivity->isReaderActivity();
APP_STATE.saveToFile();
exitActivity();
requestResyncIfX3(0);
display.requestResync();
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
display.deepSleep();
@@ -294,9 +285,7 @@ void onGoToBrowser() {
void onGoHome() {
const bool returningFromReader = currentActivity && currentActivity->isReaderActivity();
if (returningFromReader && (gpio.getDeviceType() == HalGPIO::DeviceType::X3)) {
// Force Home's first frame to run a full resync on X3.
// Avoid doing a blocking scrub refresh before activity transition.
if (returningFromReader) {
display.requestResync(1);
}
exitActivity();
@@ -307,11 +296,6 @@ void onGoHome() {
void setupDisplayAndFonts() {
if (gpio.getDeviceType() == HalGPIO::DeviceType::X3) {
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();
renderer.begin();
@@ -401,7 +385,7 @@ void setup() {
setupDisplayAndFonts();
if (wakeupReason == HalGPIO::WakeupReason::PowerButton || wakeupReason == HalGPIO::WakeupReason::AfterFlash ||
wakeupReason == HalGPIO::WakeupReason::Other) {
requestResyncIfX3(0);
display.requestResync();
}
exitActivity();