Skip to content

Display-only: no-controller auto-sleep + battery indicator (LilyGo T-RGB)#773

Open
dmq1219 wants to merge 7 commits into
jniebuhr:masterfrom
dmq1219:feature/display-auto-sleep-battery
Open

Display-only: no-controller auto-sleep + battery indicator (LilyGo T-RGB)#773
dmq1219 wants to merge 7 commits into
jniebuhr:masterfrom
dmq1219:feature/display-auto-sleep-battery

Conversation

@dmq1219

@dmq1219 dmq1219 commented Jun 15, 2026

Copy link
Copy Markdown

Summary

Two display-only features for the LilyGo T-RGB screen (env:display):

  1. No-controller auto-sleep — if the display has no BLE connection to the Controller (PCB) for 120 s, it deep-sleeps and wakes on touch (chip reboots and re-scans).
  2. Battery indicator — shows the single-cell 3.7 V Li-ion state (approximate % + raw voltage) on every screen via a top-layer overlay.

Branched from master.

Scope / guardrails

  • display-only — all changes under src/display/**, sim/**, docs/**.
  • ✅ no controller / coffee-control (heater/pump/valve/pressure/temperature/profile) / BLE-command changes. The display only reads BLE connection state and never sends control commands before sleeping.
  • ✅ no bootloader / partition / eFuse / Secure Boot / Flash Encryption changes — re-flashing official firmware fully reverts.

9 files changed (+190), plus 2 pure-logic headers and 1 doc.

How it works

  • AutoSleepManager (pure logic): countdown from boot/disconnect, cancels on reconnect; touch resets the UI idle timer but does not block the no-controller countdown; suppressed during OTA/update/Wi-Fi-setup/autotune; sleeps after ¼ timeout when battery is critical. Toggle + timeout in Settings (default on / 2 min), also via the web-config API.
  • BatteryMonitor (pure logic): Driver::getBatteryMilliVolts() → on T-RGB panel.getBattVoltage() (GPIO4, averages ~20 reads, handles the 1/2 divider). Sampled every 30 s; coarse Li-ion LUT → percent.
  • Sim: SdlDriver mocks the battery (GM_SIM_BATTERY_MV) and logs a sleep event instead of exiting.

Caveats

  • Battery percentage is approximate (raw voltage shown alongside on purpose).
  • USB caveat: USB-C readings reflect charging voltage, not true remaining charge — meaningful only on battery. Documented in docs/DISPLAY_BATTERY_AUTOSLEEP.md.

Testing

pio run -e display ✅ · pio run -e display-sim ✅. pio check -e display: no real defects added (only pre-existing cppcheck line-0 macro bailouts).

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a persistent top-layer battery indicator overlay (approx. percentage, voltage-based status, and charging indication).
    • Added display-only auto-sleep when no controller is detected, including touch wake and a configurable timeout.
    • Battery-aware behavior: if the battery is critical, auto-sleep triggers earlier to conserve power.
  • Updates

    • Enhanced auto-sleep to avoid sleeping during OTA/update, network setup, and autotune activities.
    • Updated Web UI to configure “sleep when controller not found” and its timeout.
  • Documentation

    • Added a dedicated guide for display-only auto-sleep + battery indicator behavior, simulation notes, and hardware checklist.

…only)

Display firmware only (LilyGo T-RGB, env:display). No controller, coffee-control
(heater/pump/valve/pressure/temperature), profile, BLE-command, bootloader,
partition or flash-security changes.

Auto-sleep: deep-sleep the screen (wake on touch) after 120s with no BLE link to
the controller. Cancels on reconnect; suppressed during OTA/update/Wi-Fi-setup;
sleeps sooner when the battery is critical. Toggle + timeout in Settings (default
on / 2 min). Implemented as a pure-logic AutoSleepManager.

Battery: read the single-cell Li-ion voltage via Driver::getBatteryMilliVolts()
(LilyGo panel.getBattVoltage(), GPIO4, handles the 1/2 divider). Sampled every
30s; BatteryMonitor maps it to an APPROXIMATE percent (piecewise LUT) shown with
the raw voltage on a top-layer overlay. USB-plugged readings reflect charging
voltage, not true charge (documented).

Simulator: SdlDriver mocks the battery (GM_SIM_BATTERY_MV) and logs a sleep event
instead of exiting. Verified: pio run -e display, pio run -e display-sim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cla-bot

cla-bot Bot commented Jun 15, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have yours on file. In order for us to review and merge your code, please contact @jniebuhr (mdwasp) on Discord to get yourself added.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds two display-firmware features: (1) deep sleep when no BLE controller is detected for a configurable timeout, with touch wake and suppression during OTA/AP/autotune, and (2) a battery voltage/percentage overlay. Introduces AutoSleepManager, BatteryMonitor, new Driver virtual methods, hardware and simulator implementations, Settings persistence, and WebUI API endpoints.

Changes

Auto-Sleep and Battery Indicator

Layer / File(s) Summary
Constants and Driver interface
src/display/core/constants.h, src/display/drivers/Driver.h
Adds auto-sleep and battery telemetry macros, then extends the Driver base class with default-noop virtual sleep(), hasBattery(), and getBatteryMilliVolts() methods.
AutoSleepManager pure logic
src/display/core/AutoSleepManager.h
Introduces header-only AutoSleepManager class encapsulating BLE disconnect timeout policy with enable/suppression/short-timeout modes, connection state tracking, and evaluate() method returning SleepReason based on timeout expiry.
BatteryMonitor pure logic
src/display/core/BatteryMonitor.h
Introduces header-only BatteryMonitor class storing sampled voltage, computing percentage via lookup-table linear interpolation, and exposing validity and low/critical threshold queries.
Settings persistence
src/display/core/Settings.h, src/display/core/Settings.cpp
Adds getters, setters, private fields, constructor loading of as_noctl/as_noctl_t preference keys, and doSave() persistence for autoSleepNoController and noControllerSleepTimeout.
Hardware and simulator driver implementations
src/display/drivers/LilyGoDriver.h, sim/driver/SdlDriver.h
LilyGoDriver overrides sleep() with touch-wakeup deep sleep and battery voltage from panel; SdlDriver mocks sleep() via stdout and reads battery millivolts from GM_SIM_BATTERY_MV env var (default 3900 mV).
DefaultUI integration
src/display/ui/default/DefaultUI.h, src/display/ui/default/DefaultUI.cpp
Wires BLE connect/disconnect events to AutoSleepManager, sets up the LVGL battery label at boot, runs per-loop battery sampling, and evaluates auto-sleep policy once per second with suppression during OTA/AP/autotune.
WebUI API, frontend, and documentation
src/display/plugins/WebUIPlugin.cpp, web/src/pages/Settings/index.jsx, docs/DISPLAY_BATTERY_AUTOSLEEP.md
Exposes autoSleepNoController and noControllerSleepTimeout (with seconds↔milliseconds conversion) via GET/POST settings endpoints; Settings UI adds toggle and timeout input; adds comprehensive feature specification doc covering behavior, simulator, build, test, and recovery.

Sequence Diagram(s)

sequenceDiagram
  participant BLE as BLE Event
  participant DefaultUI as DefaultUI
  participant AutoSleep as AutoSleepManager
  participant Battery as BatteryMonitor
  participant Driver as Driver (LilyGo/Sdl)
  participant Settings as Settings

  BLE->>DefaultUI: onControllerDisconnected
  DefaultUI->>AutoSleep: onControllerDisconnected(millis())

  loop Every loop tick
    DefaultUI->>Driver: getBatteryMilliVolts()
    Driver-->>DefaultUI: voltageMv
    DefaultUI->>Battery: update(voltageMv, now)
    Battery-->>DefaultUI: percent, isCritical, isLow
    DefaultUI->>AutoSleep: setForceShortTimeout(critical && !connected)
    DefaultUI->>Settings: isAutoSleepNoController(), getNoControllerSleepTimeout()
    DefaultUI->>AutoSleep: setEnabled(), setTimeoutMs(), setSuppressed()
    DefaultUI->>AutoSleep: evaluate(now)
    AutoSleep-->>DefaultUI: SleepReason
    alt SleepReason == NoController
      DefaultUI->>Driver: setBrightness(0)
      DefaultUI->>Driver: sleep()
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 Hop hop, the screen goes dim,
No controller found — time to snooze on a whim!
A battery gauge glows low then red,
Touch the glass and wake from bed.
Deep sleep claimed, millivolts tracked with care,
The bunny rests — power saved with flair! 🔋

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main changes: display-only no-controller auto-sleep and battery indicator features for LilyGo T-RGB hardware.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…eenshots

TOP_RIGHT placed the indicator in the round T-RGB's dead corner; move it to
top-centre under the status-icon row so it's visible. Add simulator screenshots
(normal + critical battery) and reference them in the docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cla-bot

cla-bot Bot commented Jun 15, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have yours on file. In order for us to review and merge your code, please contact @jniebuhr (mdwasp) on Discord to get yourself added.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sim/driver/SdlDriver.h`:
- Around line 26-29: The getBatteryMilliVolts() method directly casts the result
of atoi() to uint16_t without validating the input, which causes negative values
to wrap around (e.g., -1 becomes 65535). Modify the method to validate and clamp
the integer value returned by atoi() to ensure it falls within a valid range for
uint16_t before performing the cast, treating invalid or out-of-range values as
the default 3900 millivolts instead.

In `@src/display/core/AutoSleepManager.h`:
- Line 40: The code uses 0 as a sentinel value to indicate "not counting" for
the controllerDisconnectedSince_ variable, but coerces it to 1 on lines 40 and
54 to avoid sentinel issues. However, line 69 performs unsigned subtraction (now
- controllerDisconnectedSince_), which causes an underflow when now equals 0,
resulting in a very large value that immediately satisfies the timeout check and
triggers unintended deep sleep at boot/disconnect. Fix this by separating the
"counting active" state from the timestamp value itself—use a distinct boolean
flag to track whether the controller disconnection timer is actively counting,
rather than relying on sentinel values in the timestamp field. Update lines 40,
54, and 69 to check this flag instead of relying on the 0-to-1 coercion and
unsigned math behavior.

In `@src/display/core/Settings.cpp`:
- Line 99: The noControllerSleepTimeout values read via preferences.getInt() are
not being validated or clamped to acceptable ranges before flowing into
AutoSleepManager::setTimeoutMs(uint32_t). Negative, zero, or corrupt values can
cause wrap-around or trigger unintended immediate sleep behavior. Clamp the
noControllerSleepTimeout values at both the point where they are read from
preferences and where they are set from incoming values to ensure they fall
within a valid timeout range before being passed to the AutoSleepManager method.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dc28be7d-b903-47f5-9c75-9fb2f4e0c47f

📥 Commits

Reviewing files that changed from the base of the PR and between 591abe7 and f8954a8.

📒 Files selected for processing (12)
  • docs/DISPLAY_BATTERY_AUTOSLEEP.md
  • sim/driver/SdlDriver.h
  • src/display/core/AutoSleepManager.h
  • src/display/core/BatteryMonitor.h
  • src/display/core/Settings.cpp
  • src/display/core/Settings.h
  • src/display/core/constants.h
  • src/display/drivers/Driver.h
  • src/display/drivers/LilyGoDriver.h
  • src/display/plugins/WebUIPlugin.cpp
  • src/display/ui/default/DefaultUI.cpp
  • src/display/ui/default/DefaultUI.h

Comment thread sim/driver/SdlDriver.h
Comment on lines +26 to +29
uint16_t getBatteryMilliVolts() override {
const char *mv = getenv("GM_SIM_BATTERY_MV");
return static_cast<uint16_t>(mv ? atoi(mv) : 3900);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate and clamp GM_SIM_BATTERY_MV before casting.

atoi() + direct uint16_t cast can wrap invalid inputs (e.g., -1 becomes 65535), which distorts simulated battery percentage and auto-sleep behavior.

Suggested fix
 uint16_t getBatteryMilliVolts() override {
-    const char *mv = getenv("GM_SIM_BATTERY_MV");
-    return static_cast<uint16_t>(mv ? atoi(mv) : 3900);
+    const char *mv = std::getenv("GM_SIM_BATTERY_MV");
+    if (mv == nullptr) {
+        return 3900;
+    }
+    char *end = nullptr;
+    const long parsed = std::strtol(mv, &end, 10);
+    if (end == mv || *end != '\0' || parsed < 0 || parsed > 5000) {
+        return 3900;
+    }
+    return static_cast<uint16_t>(parsed);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
uint16_t getBatteryMilliVolts() override {
const char *mv = getenv("GM_SIM_BATTERY_MV");
return static_cast<uint16_t>(mv ? atoi(mv) : 3900);
}
uint16_t getBatteryMilliVolts() override {
const char *mv = std::getenv("GM_SIM_BATTERY_MV");
if (mv == nullptr) {
return 3900;
}
char *end = nullptr;
const long parsed = std::strtol(mv, &end, 10);
if (end == mv || *end != '\0' || parsed < 0 || parsed > 5000) {
return 3900;
}
return static_cast<uint16_t>(parsed);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sim/driver/SdlDriver.h` around lines 26 - 29, The getBatteryMilliVolts()
method directly casts the result of atoi() to uint16_t without validating the
input, which causes negative values to wrap around (e.g., -1 becomes 65535).
Modify the method to validate and clamp the integer value returned by atoi() to
ensure it falls within a valid range for uint16_t before performing the cast,
treating invalid or out-of-range values as the default 3900 millivolts instead.

// Begin with no connection yet (start the countdown at boot).
void begin(uint32_t now) {
controllerConnected_ = false;
controllerDisconnectedSince_ = now ? now : 1; // 0 means "not counting"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix immediate-sleep underflow when timestamp is 0.

Line 40 and Line 54 coerce now == 0 to 1, but Line 69 computes now - controllerDisconnectedSince_ as unsigned math. At now == 0, that underflows and can instantly satisfy timeout, causing an unintended deep sleep right at boot/disconnect edge.

💡 Suggested fix (separate “counting active” state from timestamp value)
 class AutoSleepManager {
   public:
@@
     void begin(uint32_t now) {
         controllerConnected_ = false;
-        controllerDisconnectedSince_ = now ? now : 1; // 0 means "not counting"
+        disconnectCountdownActive_ = true;
+        controllerDisconnectedSince_ = now;
         lastUserInteractionAt_ = now;
     }
@@
     void onControllerConnected(uint32_t now) {
         controllerConnected_ = true;
         lastControllerConnectedAt_ = now;
-        controllerDisconnectedSince_ = 0; // cancel the countdown
+        disconnectCountdownActive_ = false; // cancel the countdown
+        controllerDisconnectedSince_ = 0;
     }
@@
     void onControllerDisconnected(uint32_t now) {
@@
-        if (controllerConnected_ || controllerDisconnectedSince_ == 0) {
-            controllerDisconnectedSince_ = now ? now : 1;
+        if (controllerConnected_ || !disconnectCountdownActive_) {
+            controllerDisconnectedSince_ = now;
+            disconnectCountdownActive_ = true;
         }
         controllerConnected_ = false;
     }
@@
     SleepReason evaluate(uint32_t now) const {
-        if (!enabled_ || suppressed_ || controllerConnected_ || controllerDisconnectedSince_ == 0) {
+        if (!enabled_ || suppressed_ || controllerConnected_ || !disconnectCountdownActive_) {
             return SleepReason::None;
         }
         const uint32_t timeout = forceShortTimeout_ ? (timeoutMs_ / 4) : timeoutMs_;
         if (now - controllerDisconnectedSince_ >= timeout) {
             return SleepReason::NoController;
         }
         return SleepReason::None;
     }
@@
   private:
@@
+    bool disconnectCountdownActive_ = false;
     uint32_t controllerDisconnectedSince_ = 0;

Also applies to: 54-54, 69-69

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/display/core/AutoSleepManager.h` at line 40, The code uses 0 as a
sentinel value to indicate "not counting" for the controllerDisconnectedSince_
variable, but coerces it to 1 on lines 40 and 54 to avoid sentinel issues.
However, line 69 performs unsigned subtraction (now -
controllerDisconnectedSince_), which causes an underflow when now equals 0,
resulting in a very large value that immediately satisfies the timeout check and
triggers unintended deep sleep at boot/disconnect. Fix this by separating the
"counting active" state from the timestamp value itself—use a distinct boolean
flag to track whether the controller disconnection timer is actively counting,
rather than relying on sentinel values in the timestamp field. Update lines 40,
54, and 69 to check this flag instead of relying on the 0-to-1 coercion and
unsigned math behavior.

standbyBrightnessTimeout = preferences.getInt("standby_bt", 60000);
// [display-auto-sleep] display-only
autoSleepNoController = preferences.getBool("as_noctl", DEFAULT_AUTO_SLEEP_NO_CONTROLLER);
noControllerSleepTimeout = preferences.getInt("as_noctl_t", DEFAULT_NO_CONTROLLER_SLEEP_TIMEOUT_MS);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clamp persisted and incoming noControllerSleepTimeout values.

Line [99] and Line [381] accept raw int timeout values; those flow into AutoSleepManager::setTimeoutMs(uint32_t) (see src/display/ui/default/DefaultUI.cpp Line [362]). Negative/zero/corrupt values can wrap or trigger unintended immediate sleep behavior.

🔧 Proposed fix
-    noControllerSleepTimeout = preferences.getInt("as_noctl_t", DEFAULT_NO_CONTROLLER_SLEEP_TIMEOUT_MS);
+    noControllerSleepTimeout =
+        std::clamp(preferences.getInt("as_noctl_t", DEFAULT_NO_CONTROLLER_SLEEP_TIMEOUT_MS),
+                   NO_CONTROLLER_SLEEP_CHECK_INTERVAL_MS, 24 * 60 * 60 * 1000);

 void Settings::setNoControllerSleepTimeout(int timeout_ms) {
-    noControllerSleepTimeout = timeout_ms;
+    noControllerSleepTimeout =
+        std::clamp(timeout_ms, NO_CONTROLLER_SLEEP_CHECK_INTERVAL_MS, 24 * 60 * 60 * 1000);
     save();
 }

Also applies to: 380-383

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/display/core/Settings.cpp` at line 99, The noControllerSleepTimeout
values read via preferences.getInt() are not being validated or clamped to
acceptable ranges before flowing into AutoSleepManager::setTimeoutMs(uint32_t).
Negative, zero, or corrupt values can cause wrap-around or trigger unintended
immediate sleep behavior. Clamp the noControllerSleepTimeout values at both the
point where they are read from preferences and where they are set from incoming
values to ensure they fall within a valid timeout range before being passed to
the AutoSleepManager method.

Adds the Display Settings → Auto Sleep entry (enable toggle + timeout) in
web/src/pages/Settings/index.jsx so it's user-configurable, not just API-settable.
Fix the backend to use the hasArg presence convention for the checkbox (matching
clock24hFormat/autowakeupEnabled). Document the web entry and a hardware test
record template.

Verified: pio run -e display, web `vite build` + eslint (0 errors).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cla-bot

cla-bot Bot commented Jun 15, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have yours on file. In order for us to review and merge your code, please contact @jniebuhr (mdwasp) on Discord to get yourself added.

@dmq1219

dmq1219 commented Jun 15, 2026

Copy link
Copy Markdown
Author

Follow-up commits:

  • Added Display Settings → Auto Sleep (enable toggle + timeout) to the web config UI (web/src/pages/Settings/index.jsx); backend now uses the hasArg presence convention for the checkbox, matching clock24hFormat/autowakeupEnabled.
  • Centred the battery overlay inside the round panel (top-centre); added simulator screenshots.
  • Verified: pio run -e display ✅, pio run -e display-sim ✅, pio check -e display PASSED (when run from a path without spaces — a space in the project path breaks cppcheck's LV_CONF_PATH), web vite build + eslint (0 errors).
  • Still display-only; hardware confirmation pending (test-record template in the doc).

…ched' test item

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cla-bot

cla-bot Bot commented Jun 15, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have yours on file. In order for us to review and merge your code, please contact @jniebuhr (mdwasp) on Discord to get yourself added.

…ent only

The T-RGB has no charge-status pin, so when USB is plugged the ADC reads the
charging voltage (~4.2V) and the overlay used to show a misleading "100%". Now
voltage >= BATTERY_CHARGING_MV (4200) is treated as on-USB/charging and the overlay
shows "⚡ USB" (green) instead of a percentage. On battery it shows the approximate
percentage only (voltage text removed, per request). Screenshots + docs updated.

Verified: pio run -e display, pio run -e display-sim (normal / charging / low).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cla-bot

cla-bot Bot commented Jun 15, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have yours on file. In order for us to review and merge your code, please contact @jniebuhr (mdwasp) on Discord to get yourself added.

…h wake

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cla-bot

cla-bot Bot commented Jun 15, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have yours on file. In order for us to review and merge your code, please contact @jniebuhr (mdwasp) on Discord to get yourself added.

Charging detection has no percentage of its own; show the (charging-voltage based)
percentage next to the bolt so the level is visible while plugged in, instead of
just '⚡ USB'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cla-bot

cla-bot Bot commented Jun 15, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have yours on file. In order for us to review and merge your code, please contact @jniebuhr (mdwasp) on Discord to get yourself added.

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant