fix(batt-led): force LED off + disarm on disconnect

The low-battery blink would stay frozen mid-cycle (or briefly resume
during reconnect retries) after a controller died and lost connection,
because the stale-report check stopped toggling but never turned the
LED off, and the cached interrupt_in_data[52] still read low.

New `battery_led_on_disconnect()` clears blink state, forces the LED
off, and zeros last_report_us so the stale-check early-return blocks
any new blink until a fresh 0x31 report arrives on the next connection.
Called from bt.cpp's HCI_EVENT_DISCONNECTION_COMPLETE handler.

Stale-check in the tick also now forces LED off when it fires while a
blink was in progress (defense in depth for unclean disconnects).

Same bug exists in upstream awalol/DS5Dongle (their battery_led.cpp is
byte-identical to ours pre-fix) — Sura Academy reported it in their
Discord. Plan to send this back as a PR after we validate it locally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MarcelineVPQ
2026-05-17 11:19:55 -06:00
co-authored by Claude Opus 4.7
parent 9e6269d413
commit fb68ea5608
3 changed files with 34 additions and 2 deletions
+19 -1
View File
@@ -37,11 +37,29 @@ void battery_led_note_report(void) {
last_report_us = time_us_64(); last_report_us = time_us_64();
} }
void battery_led_on_disconnect(void) {
// Stop any in-progress blink and force the LED off immediately. Zero
// last_report_us so the tick's stale-check early-returns until a fresh
// 0x31 report arrives on the next connection — prevents the cached
// low-battery byte from re-arming a blink during reconnect retries.
blinking = false;
led_state = false;
last_report_us = 0;
last_toggle_us = 0;
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);
}
void battery_led_tick(void) { void battery_led_tick(void) {
const uint64_t now = time_us_64(); const uint64_t now = time_us_64();
if (last_report_us == 0 || (now - last_report_us) >= REPORT_STALE_US) { if (last_report_us == 0 || (now - last_report_us) >= REPORT_STALE_US) {
// No fresh data — bt.cpp owns the LED while disconnected. // No fresh data — bt.cpp owns the LED while disconnected. If we
// were mid-blink when the report went stale, force the LED off
// so it doesn't freeze in whichever half-cycle it was in.
if (blinking) {
blinking = false; blinking = false;
led_state = false;
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);
}
return; return;
} }
+8
View File
@@ -16,3 +16,11 @@ void battery_led_tick(void);
// has been copied into interrupt_in_data. Used to detect disconnection // has been copied into interrupt_in_data. Used to detect disconnection
// via stale-report timeout. // via stale-report timeout.
void battery_led_note_report(void); void battery_led_note_report(void);
// Call from the BT disconnect handler. Cancels any in-progress blink,
// forces the LED off, and arms the module so it ignores the cached
// (now-stale) battery byte until a fresh report arrives on the next
// connection. Without this, the LED can stay frozen in whichever state
// it was at the moment of disconnect, or briefly resume blinking during
// reconnect retries while interrupt_in_data[52] still reads low.
void battery_led_on_disconnect(void);
+6
View File
@@ -19,6 +19,9 @@
#include "state_mgr.h" #include "state_mgr.h"
#include "pico/util/queue.h" #include "pico/util/queue.h"
#include "slots.h" #include "slots.h"
#if ENABLE_BATT_LED
#include "battery_led.h"
#endif
#define MTU_CONTROL 672 #define MTU_CONTROL 672
#define MTU_INTERRUPT 672 #define MTU_INTERRUPT 672
@@ -454,6 +457,9 @@ static void hci_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *p
hid_interrupt_cid = 0; hid_interrupt_cid = 0;
feature_data.clear(); feature_data.clear();
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false); cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);
#if ENABLE_BATT_LED
battery_led_on_disconnect();
#endif
printf("[HCI] Disconnected reason=0x%02X, start inquiry\n", reason); printf("[HCI] Disconnected reason=0x%02X, start inquiry\n", reason);
gap_inquiry_start(30); gap_inquiry_start(30);
break; break;