This commit is contained in:
Joshua King
2026-02-10 21:31:36 -05:00
parent b2752b8f72
commit 08a2ee0852
6 changed files with 64 additions and 20 deletions

View File

@@ -10,6 +10,9 @@ lib_deps =
adafruit/Adafruit SSD1306 adafruit/Adafruit SSD1306
bblanchon/ArduinoJson bblanchon/ArduinoJson
; board_build.filesystem = spiffs
; board_build.partitions = default.csv
build_flags = build_flags =
-D PB_VERSION=\"1.0.0\" -D PB_VERSION=\"1.0.0\"
-D PB_HOSTNAME=\"faceplant\" -D PB_HOSTNAME=\"faceplant\"

View File

@@ -3,11 +3,12 @@
void BatterySensor::begin() { void BatterySensor::begin() {
pinMode(PIN_BATTERY_ADC, INPUT); pinMode(PIN_BATTERY_ADC, INPUT);
pinMode(PIN_LBO, INPUT); pinMode(PIN_LBO, INPUT);
pinMode(PIN_CHG, INPUT_PULLUP); // CHG is LOW when charging
analogReadResolution(12); // 12-bit ADC (0-4095) analogReadResolution(12); // 12-bit ADC (0-4095)
Serial.println("[Battery] Initialized"); Serial.println("[Battery] Initialized");
Serial.println("[Battery] ADC on GPIO 2, LBO on GPIO 5"); Serial.println("[Battery] ADC on GPIO 2, LBO on GPIO 5, CHG on GPIO 4");
// Initial reading // Initial reading
_voltage = readBatteryVoltage(); _voltage = readBatteryVoltage();
@@ -29,30 +30,47 @@ void BatterySensor::loop() {
float prevVoltage = _voltage; float prevVoltage = _voltage;
int prevPercent = _percent; int prevPercent = _percent;
bool wasLow = _isLow; bool wasLow = _isLow;
bool wasCharging = _isCharging;
// Read battery voltage via ADC // Read battery voltage via ADC
_voltage = readBatteryVoltage(); _voltage = readBatteryVoltage();
_percent = voltageToPercent(_voltage); _percent = voltageToPercent(_voltage);
// Check CHG pin (LOW when charging)
bool chgLow = !digitalRead(PIN_CHG);
_isCharging = chgLow;
// Check LBO pin as backup confirmation // Check LBO pin as backup confirmation
bool lboHigh = digitalRead(PIN_LBO); bool lboHigh = digitalRead(PIN_LBO);
bool lboIndicatesLow = !lboHigh; bool lboIndicatesLow = !lboHigh;
// Battery is low if either voltage is low OR LBO pin indicates low // Battery is low if either voltage is low OR LBO pin indicates low (and not charging)
_isLow = (_voltage < VOLTAGE_LOW) || lboIndicatesLow; _isLow = ((_voltage < VOLTAGE_LOW) || lboIndicatesLow) && !_isCharging;
// Log significant changes // Log significant changes
if (abs(_percent - prevPercent) >= 5 || _isLow != wasLow) { if (abs(_percent - prevPercent) >= 5 || _isLow != wasLow || _isCharging != wasCharging) {
Serial.print("[Battery] Voltage: "); Serial.print("[Battery] Voltage: ");
Serial.print(_voltage, 2); Serial.print(_voltage, 2);
Serial.print("V ("); Serial.print("V (");
Serial.print(_percent); Serial.print(_percent);
Serial.print("%) LBO: "); Serial.print("%) ");
if (_isCharging) {
Serial.print("CHARGING ");
}
Serial.print("LBO: ");
Serial.println(lboHigh ? "OK" : "LOW"); Serial.println(lboHigh ? "OK" : "LOW");
if (_isLow && !wasLow) { if (_isLow && !wasLow && !_isCharging) {
Serial.println("[Battery] ⚠️ LOW BATTERY WARNING - Please recharge!"); Serial.println("[Battery] ⚠️ LOW BATTERY WARNING - Please recharge!");
} }
if (_isCharging && !wasCharging) {
Serial.println("[Battery] 🔌 Charging started");
} else if (!_isCharging && wasCharging) {
Serial.println("[Battery] ✓ Charging complete or disconnected");
}
} }
} }

View File

@@ -10,6 +10,7 @@ public:
int percent() const { return _percent; } int percent() const { return _percent; }
float voltage() const { return _voltage; } float voltage() const { return _voltage; }
bool isLow() const { return _isLow; } bool isLow() const { return _isLow; }
bool isCharging() const { return _isCharging; }
bool shouldBlink() const; bool shouldBlink() const;
// For display // For display
@@ -18,6 +19,7 @@ public:
private: private:
static constexpr int PIN_BATTERY_ADC = 2; // Voltage divider input static constexpr int PIN_BATTERY_ADC = 2; // Voltage divider input
static constexpr int PIN_LBO = 5; // PowerBoost LBO pin static constexpr int PIN_LBO = 5; // PowerBoost LBO pin
static constexpr int PIN_CHG = 4; // PowerBoost CHG pin (LOW when charging)
static constexpr unsigned long CHECK_INTERVAL_MS = 2000; static constexpr unsigned long CHECK_INTERVAL_MS = 2000;
static constexpr int SAMPLES = 10; // Number of ADC samples to average static constexpr int SAMPLES = 10; // Number of ADC samples to average
@@ -33,6 +35,7 @@ private:
int _percent = 100; int _percent = 100;
float _voltage = 3.7; float _voltage = 3.7;
bool _isLow = false; bool _isLow = false;
bool _isCharging = false;
unsigned long _lastCheckMs = 0; unsigned long _lastCheckMs = 0;
float readBatteryVoltage(); float readBatteryVoltage();

View File

@@ -60,11 +60,11 @@ _oled.display();
delay(2100); delay(2100);
} }
void Display::drawBatteryIcon(int x, int y, int percent, bool blink) { void Display::drawBatteryIcon(int x, int y, int percent, bool blink, bool charging) {
if (!_ok) return; if (!_ok) return;
// Skip if blinking and blink state is off // Skip if blinking and blink state is off (unless charging)
if (blink && ((millis() / 500) % 2 == 1)) return; if (!charging && blink && ((millis() / 500) % 2 == 1)) return;
// Battery body: 14x7 rectangle // Battery body: 14x7 rectangle
_oled.drawRect(x, y, 14, 7, 1); _oled.drawRect(x, y, 14, 7, 1);
@@ -74,6 +74,25 @@ void Display::drawBatteryIcon(int x, int y, int percent, bool blink) {
// Fill level: 12 pixels max width inside battery // Fill level: 12 pixels max width inside battery
int fillWidth = (percent * 12) / 100; int fillWidth = (percent * 12) / 100;
if (charging) {
// Draw animated lightning bolt when charging
bool phase = (millis() / 300) % 2 == 0;
int color = phase ? 1 : 0; // Blink between white and inverted
// Lightning bolt shape (simple zigzag)
_oled.drawLine(x + 7, y + 1, x + 5, y + 3, color); // Top diagonal
_oled.drawLine(x + 5, y + 3, x + 9, y + 3, color); // Middle horizontal
_oled.drawLine(x + 9, y + 3, x + 7, y + 5, color); // Bottom diagonal
// Always draw at least some fill when charging
if (fillWidth == 0 && phase) {
_oled.fillRect(x + 1, y + 1, 2, 5, 1); // Small pulse even at 0%
} else if (fillWidth > 0) {
_oled.fillRect(x + 1, y + 1, fillWidth, 5, 1);
}
} else {
// Normal fill level
if (fillWidth > 0) { if (fillWidth > 0) {
_oled.fillRect(x + 1, y + 1, fillWidth, 5, 1); _oled.fillRect(x + 1, y + 1, fillWidth, 5, 1);
} }
@@ -83,4 +102,5 @@ void Display::drawBatteryIcon(int x, int y, int percent, bool blink) {
// Draw thicker outline for emphasis // Draw thicker outline for emphasis
_oled.drawRect(x - 1, y - 1, 16, 9, 1); _oled.drawRect(x - 1, y - 1, 16, 9, 1);
} }
}
} }

View File

@@ -11,7 +11,7 @@ public:
void showStatus(const String& line1, const String& line2); void showStatus(const String& line1, const String& line2);
void bootAnimation(); void bootAnimation();
void drawBatteryIcon(int x, int y, int percent, bool blink); void drawBatteryIcon(int x, int y, int percent, bool blink, bool charging);
private: private:
static constexpr int PIN_SDA = 6; static constexpr int PIN_SDA = 6;

View File

@@ -157,7 +157,7 @@ void FaceRenderer::renderDead(unsigned long now, const BatterySensor& battery) {
} }
// Draw battery icon in top-right corner // Draw battery icon in top-right corner
_display->drawBatteryIcon(110, 2, battery.percent(), battery.shouldBlink()); _display->drawBatteryIcon(110, 2, battery.percent(), battery.shouldBlink(), battery.isCharging());
d.display(); d.display();
} }
@@ -199,7 +199,7 @@ void FaceRenderer::renderBatteryLow(unsigned long now, const BatterySensor& batt
drawMouthNervous(); drawMouthNervous();
// Draw battery icon in top-right corner (blinking) // Draw battery icon in top-right corner (blinking)
_display->drawBatteryIcon(110, 2, battery.percent(), true); _display->drawBatteryIcon(110, 2, battery.percent(), true, battery.isCharging());
d.display(); d.display();
} }
@@ -230,7 +230,7 @@ void FaceRenderer::renderNormal(unsigned long now, const BatterySensor& battery)
} }
// Draw battery icon in top-right corner // Draw battery icon in top-right corner
_display->drawBatteryIcon(110, 2, battery.percent(), battery.shouldBlink()); _display->drawBatteryIcon(110, 2, battery.percent(), battery.shouldBlink(), battery.isCharging());
d.display(); d.display();
} }