diff --git a/src/Battery.cpp b/src/Battery.cpp new file mode 100644 index 00000000..2f16709e --- /dev/null +++ b/src/Battery.cpp @@ -0,0 +1,40 @@ +#include "Battery.h" + +void BatteryPercentageRingBuffer::init(uint16_t v) { + for (size_t i = 0; i < MAX_SAMPLES; i++) { + buf[i] = v; + } + sum = v * MAX_SAMPLES; + prev_val = v; + head = 0; +} + +void BatteryPercentageRingBuffer::update(uint16_t v) { + // Previous percentage is set > 100 only if buffer was constructed but not initialized yet + if (prev_val > 100) { + init(v); + } + + // Recalculate rolling sum + sum -= buf[head]; + buf[head] = v; + sum += v; + + // Shift head + head++; + if (head >= MAX_SAMPLES) head = 0; +} + +uint16_t BatteryPercentageRingBuffer::evaluate() { + // We 'round' (only works for pos numbers but oh well good enough for battery percentages) to an int + float avg = (sum / MAX_SAMPLES) + 0.5; + uint16_t new_val = (int)avg; + + // Battery percentage should not increase when not charging so we just cap it to be lower than the last value + if (new_val < prev_val) { + prev_val = new_val; + return new_val; + } else { + return prev_val; + } +} diff --git a/src/Battery.h b/src/Battery.h index dcfcbf79..0baf56ae 100644 --- a/src/Battery.h +++ b/src/Battery.h @@ -1,6 +1,21 @@ #pragma once #include +#include + #define BAT_GPIO0 0 // Battery voltage static BatteryMonitor battery(BAT_GPIO0); + +struct BatteryPercentageRingBuffer { + static constexpr uint16_t MAX_SAMPLES = 10; + + uint16_t buf[MAX_SAMPLES]; + uint16_t head = 0; + uint16_t sum = 0; + uint16_t prev_val = 161; + + void init(uint16_t value); + void update(uint16_t value); + uint16_t evaluate(); +}; diff --git a/src/ScreenComponents.cpp b/src/ScreenComponents.cpp index 72f7faf0..f00c07df 100644 --- a/src/ScreenComponents.cpp +++ b/src/ScreenComponents.cpp @@ -1,5 +1,6 @@ #include "ScreenComponents.h" +#include #include #include @@ -8,10 +9,24 @@ #include "Battery.h" #include "fontIds.h" +BatteryPercentageRingBuffer ScreenComponents::batteryBuffer; + void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left, const int top, const bool showPercentage) { + const bool charging = (digitalRead(20) == HIGH); + // Left aligned battery icon and percentage - const uint16_t percentage = battery.readPercentage(); + uint16_t percentage = battery.readPercentage(); + + if (charging) { + // If charging reinitialize buffer with current percentage and display this value + batteryBuffer.init(percentage); + } else { + // Else update buffer with new percentage and return smoothed validated percentage to display + batteryBuffer.update(percentage); + percentage = batteryBuffer.evaluate(); + } + const auto percentageText = showPercentage ? std::to_string(percentage) + "%" : ""; renderer.drawText(SMALL_FONT_ID, left + 20, top, percentageText.c_str()); @@ -34,12 +49,38 @@ void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left, renderer.drawLine(x + batteryWidth - 0, y + 4, x + batteryWidth - 0, y + batteryHeight - 5); // The +1 is to round up, so that we always fill at least one pixel - int filledWidth = percentage * (batteryWidth - 5) / 100 + 1; - if (filledWidth > batteryWidth - 5) { - filledWidth = batteryWidth - 5; // Ensure we don't overflow + constexpr int maxFillWidth = batteryWidth - 5; + int filledWidth = percentage * maxFillWidth / 100 + 1; + if (filledWidth > maxFillWidth) { + filledWidth = maxFillWidth; + } + + // When charging, ensure minimum fill so lightning bolt is fully visible + constexpr int minFillForBolt = 8; // Bolt extends 6px wide, needs padding + if (charging && filledWidth < minFillForBolt) { + filledWidth = minFillForBolt; } renderer.fillRect(x + 2, y + 2, filledWidth, batteryHeight - 4); + + // Draw lightning bolt when charging (white/inverted on black fill for visibility) + if (charging) { + // Lightning bolt: 6px wide, 8px tall, centered in battery + const int boltX = x + 4; + const int boltY = y + 2; + + // Draw bolt in white (state=false) for visibility on black fill + // Upper diagonal pointing right + renderer.drawLine(boltX + 4, boltY + 0, boltX + 5, boltY + 0, false); + renderer.drawLine(boltX + 3, boltY + 1, boltX + 4, boltY + 1, false); + renderer.drawLine(boltX + 2, boltY + 2, boltX + 5, boltY + 2, false); // Wide middle + renderer.drawLine(boltX + 3, boltY + 3, boltX + 4, boltY + 3, false); + // Lower diagonal pointing left + renderer.drawLine(boltX + 2, boltY + 4, boltX + 3, boltY + 4, false); + renderer.drawLine(boltX + 1, boltY + 5, boltX + 4, boltY + 5, false); // Wide middle + renderer.drawLine(boltX + 2, boltY + 6, boltX + 3, boltY + 6, false); + renderer.drawLine(boltX + 1, boltY + 7, boltX + 2, boltY + 7, false); + } } ScreenComponents::PopupLayout ScreenComponents::drawPopup(const GfxRenderer& renderer, const char* message) { diff --git a/src/ScreenComponents.h b/src/ScreenComponents.h index 78ed5920..a0c16251 100644 --- a/src/ScreenComponents.h +++ b/src/ScreenComponents.h @@ -4,6 +4,8 @@ #include #include +#include "Battery.h" + class GfxRenderer; struct TabInfo { @@ -15,6 +17,8 @@ class ScreenComponents { public: static const int BOOK_PROGRESS_BAR_HEIGHT = 4; + static BatteryPercentageRingBuffer batteryBuffer; + struct PopupLayout { int x; int y;