fix(power): stop a battery-less board deep-sleeping itself forever (#11821)

* fix(power): stop a battery-less board deep-sleeping itself forever

The low-battery counter only reset inside its `hasBattery && !hasUSB` guard, so a board with no
battery - whose floating divider drifts in and out of the battery-present window - ratcheted the
count up across the gaps until it tripped `sds_secs`, which defaults to a ~24.8-day deep sleep. The
button could not rescue it either, because `doDeepSleep()` force-holds `BUTTON_PIN` and a held pad
ignores `ext1_wakeup_prepare()`'s re-route to RTC; `rtc_gpio_isolate()`'s pin list has the same
effect on boards whose button is GPIO 2 or 34. Separately the cutoff now scales by `NUM_CELLS`,
without which no multi-cell pack can ever read low enough to shut down at all.

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

* fix(power): satisfy trunk check

Apply the `ascii-dash` autoformat that `trunk fmt` wants on the comments this PR's file already
carries, and rename the no-battery test so its `test_` prefix plus exactly 35 characters stops
matching trufflehog's Lob API-key shape.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thomas GöttgensandClaude Opus 5 authored and GitHub committed 2026-09-11 16:43:28 +00:00
1 parent c3df4914b4
commit 8a9e10d120
5 files changed
+197 -21

No files matched your search

+27 -13
View File
@@ -784,7 +784,7 @@ class ADS1115BatteryLevel : public AnalogBatteryLevel
sum += _ads.computeVolts(raw);
}
// Piggyback a toggle-engine watchdog on this same throttle interval.
// Only re-arm when VBUS is absent — calling this while attached
// Only re-arm when VBUS is absent - calling this while attached
// would restart CC toggling and could glitch an active sink attach.
if (_aw35615.isReady() && !_aw35615.isVbusPresent()) {
_aw35615.rearmToggle();
@@ -812,7 +812,7 @@ class ADS1115BatteryLevel : public AnalogBatteryLevel
bool vbus = _aw35615.isVbusPresent();
if (!vbus) {
// VBUS just went away (or has been away) — make sure the CC
// VBUS just went away (or has been away) - make sure the CC
// toggle engine is re-armed so the next attach gets detected.
_aw35615.rearmToggle();
}
@@ -830,7 +830,7 @@ class ADS1115BatteryLevel : public AnalogBatteryLevel
if (_aw35615.isReady()) {
concurrency::LockGuard guard(spiLock);
// Charging == VBUS present AND we're attached as a sink.
// (isSinkAttached() is a latched result — safe to trust here since
// (isSinkAttached() is a latched result - safe to trust here since
// isVbusIn() above keeps re-arming toggle on every detach.)
return _aw35615.isVbusPresent() && _aw35615.isSinkAttached();
}
@@ -1097,6 +1097,20 @@ void Power::shutdown()
#endif
}
// Consecutive readings only: a battery-less board's floating divider drifts in and out of the
// "battery present" window, and a count that survived the gaps would deep-sleep a USB-powered node.
bool updateLowVoltageCounter(uint8_t &counter, bool hasBattery, bool hasUsb, uint16_t battMv, uint16_t cutoffMv)
{
if (!hasBattery || hasUsb || battMv >= cutoffMv) {
counter = 0;
return false;
}
if (counter < UINT8_MAX)
counter++;
return counter > LOW_VOLTAGE_READINGS_BEFORE_SHUTDOWN;
}
/// Reads power status to powerStatus singleton.
//
// TODO(girts): move this and other axp stuff to power.h/power.cpp.
@@ -1229,16 +1243,16 @@ void Power::readPowerStatus()
// is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough.
//
if (batteryLevel && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) {
if (batteryLevel->getBattVoltage() < OCV[NUM_OCV_POINTS - 1]) {
low_voltage_counter++;
LOG_DEBUG("Low voltage counter: %d/10", low_voltage_counter);
if (low_voltage_counter > 10) {
LOG_INFO("Low voltage detected, trigger deep sleep");
powerFSM.trigger(EVENT_LOW_BATTERY);
}
} else {
low_voltage_counter = 0;
if (batteryLevel) {
// getBattVoltage() reports pack voltage; the OCV table is per cell.
const bool shutdownNow =
updateLowVoltageCounter(low_voltage_counter, powerStatus2.getHasBattery(), powerStatus2.getHasUSB(),
batteryLevel->getBattVoltage(), OCV[NUM_OCV_POINTS - 1] * NUM_CELLS);
if (low_voltage_counter)
LOG_DEBUG("Low voltage counter: %d/%d", low_voltage_counter, LOW_VOLTAGE_READINGS_BEFORE_SHUTDOWN);
if (shutdownNow) {
LOG_INFO("Low voltage detected, trigger deep sleep");
powerFSM.trigger(EVENT_LOW_BATTERY);
}
}
}