feat: low-battery LED indicator (ENABLE_BATT_LED, default ON)

Blink the Pico onboard LED at 1 Hz when the connected DualSense
reports PowerPercent <= 1 (i.e. <= 10%) and PowerState == Discharging.
Source data is byte 52 of the BT 0x31 input report, already copied
into interrupt_in_data; no new BT parsing required.

The new module owns the LED only while it is actively blinking; it
detects controller disconnection via stale-report timeout and steps
out, so bt.cpp's existing connect/disconnect LED handling stays in
charge in all other states. Honors disable_pico_led.

Gated by -DENABLE_BATT_LED=ON (default). With the option off, the
source file is not compiled and behavior is identical to upstream.
CI gains a compile-only check for the OFF flavor.

(cherry picked from commit 2f8ea73c9fb695e24e7cc3329db7cb925e82e1c9)
This commit is contained in:
Thierry Perraut
2026-05-13 18:09:31 +08:00
committed by awalol
parent d3311a35d9
commit 63c62081eb
6 changed files with 136 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
//
// Low-battery LED indicator. See battery_led.h.
//
#include "battery_led.h"
#include <cstdint>
#include "config.h"
#include "pico/cyw43_arch.h"
#include "pico/time.h"
extern uint8_t interrupt_in_data[63];
namespace {
constexpr uint64_t REPORT_STALE_US = 2'000'000; // assume disconnected if no report for 2 s
constexpr uint64_t BLINK_PERIOD_US = 500'000; // 1 Hz, 50% duty
constexpr uint8_t THRESHOLD_LEVEL = 1; // PowerPercent <= 1 (i.e. <= 10%)
constexpr uint8_t POWER_STATE_DISCHARGING = 0x0;
uint64_t last_report_us = 0;
uint64_t last_toggle_us = 0;
bool blinking = false;
bool led_state = false;
} // namespace
void battery_led_init(void) {
last_report_us = 0;
last_toggle_us = 0;
blinking = false;
led_state = false;
}
void battery_led_note_report(void) {
last_report_us = time_us_64();
}
void battery_led_tick(void) {
if (get_config().disable_pico_led) {
blinking = false;
return;
}
const uint64_t now = time_us_64();
if (last_report_us == 0 || (now - last_report_us) >= REPORT_STALE_US) {
// No fresh data — bt.cpp owns the LED while disconnected.
blinking = false;
return;
}
const uint8_t b = interrupt_in_data[52];
const uint8_t pct = b & 0x0F;
const uint8_t st = (b >> 4) & 0x0F;
const bool low = (st == POWER_STATE_DISCHARGING) && (pct <= THRESHOLD_LEVEL);
if (low) {
if (!blinking) {
blinking = true;
led_state = true;
last_toggle_us = now;
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, true);
return;
}
if ((now - last_toggle_us) >= BLINK_PERIOD_US) {
led_state = !led_state;
last_toggle_us = now;
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, led_state);
}
} else if (blinking) {
blinking = false;
// We were blinking and are still receiving fresh reports => still connected.
// Restore the LED to the bt.cpp "connected = solid on" state.
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, true);
}
}