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:
@@ -94,6 +94,14 @@ jobs:
|
|||||||
cmake --build build/debug --target ds5-bridge
|
cmake --build build/debug --target ds5-bridge
|
||||||
cp build/debug/ds5-bridge.uf2 artifacts/ds5-bridge-debug.uf2
|
cp build/debug/ds5-bridge.uf2 artifacts/ds5-bridge-debug.uf2
|
||||||
|
|
||||||
|
- name: Build no-batt-led firmware (compile check only)
|
||||||
|
run: |
|
||||||
|
cmake -S . -B build/no-batt-led -G Ninja \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DPICO_SDK_PATH="$PICO_SDK_PATH" \
|
||||||
|
-DENABLE_BATT_LED=OFF
|
||||||
|
cmake --build build/no-batt-led --target ds5-bridge
|
||||||
|
|
||||||
- name: Upload standard UF2 artifact
|
- name: Upload standard UF2 artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -40,6 +40,12 @@ if(ENABLE_VERBOSE)
|
|||||||
else()
|
else()
|
||||||
set(ENABLE_VERBOSE_VALUE 0)
|
set(ENABLE_VERBOSE_VALUE 0)
|
||||||
endif ()
|
endif ()
|
||||||
|
option(ENABLE_BATT_LED "Blink Pico LED when DualSense battery is low (<=10%)" ON)
|
||||||
|
if (ENABLE_BATT_LED)
|
||||||
|
set(ENABLE_BATT_LED_VALUE 1)
|
||||||
|
else ()
|
||||||
|
set(ENABLE_BATT_LED_VALUE 0)
|
||||||
|
endif ()
|
||||||
|
|
||||||
# Pull in Raspberry Pi Pico SDK (must be before project)
|
# Pull in Raspberry Pi Pico SDK (must be before project)
|
||||||
include(pico_sdk_import.cmake)
|
include(pico_sdk_import.cmake)
|
||||||
@@ -61,6 +67,10 @@ add_executable(ds5-bridge
|
|||||||
src/cmd.cpp
|
src/cmd.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (ENABLE_BATT_LED)
|
||||||
|
target_sources(ds5-bridge PRIVATE src/battery_led.cpp)
|
||||||
|
endif ()
|
||||||
|
|
||||||
# Cockos WDLResampler
|
# Cockos WDLResampler
|
||||||
add_library(wdl_resampler STATIC
|
add_library(wdl_resampler STATIC
|
||||||
lib/WDL/WDL/resample.cpp
|
lib/WDL/WDL/resample.cpp
|
||||||
@@ -88,6 +98,7 @@ target_compile_definitions(ds5-bridge PRIVATE
|
|||||||
PICO_STDIO_USB_ENABLE_RESET_VIA_BAUD_RATE=0
|
PICO_STDIO_USB_ENABLE_RESET_VIA_BAUD_RATE=0
|
||||||
PICO_STDIO_USB_ENABLE_RESET_VIA_VENDOR_INTERFACE=0
|
PICO_STDIO_USB_ENABLE_RESET_VIA_VENDOR_INTERFACE=0
|
||||||
ENABLE_VERBOSE=${ENABLE_VERBOSE_VALUE}
|
ENABLE_VERBOSE=${ENABLE_VERBOSE_VALUE}
|
||||||
|
ENABLE_BATT_LED=${ENABLE_BATT_LED_VALUE}
|
||||||
)
|
)
|
||||||
|
|
||||||
pico_set_program_name(ds5-bridge "ds5-bridge")
|
pico_set_program_name(ds5-bridge "ds5-bridge")
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ The Pico device will only be visible to the system after the controller is conne
|
|||||||
|
|
||||||
Some behaviors depend on reconnection cycles to take effect
|
Some behaviors depend on reconnection cycles to take effect
|
||||||
|
|
||||||
|
### Low-battery LED indicator
|
||||||
|
|
||||||
|
When the connected DualSense reports its battery at or below 10% (and it is not charging), the Pico onboard LED switches from solid-on to a 1 Hz blink so you can see the warning at a glance. The LED returns to solid-on as soon as the controller is plugged in or its reported level rises again. The indicator respects the existing `disable_pico_led` setting (Speaker mute toggle).
|
||||||
|
|
||||||
|
To opt out at build time, configure with `-DENABLE_BATT_LED=OFF`. Default is ON.
|
||||||
|
|
||||||
## Known Issues
|
## Known Issues
|
||||||
|
|
||||||
- ⚠️ Audio may experience slight stuttering
|
- ⚠️ Audio may experience slight stuttering
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// Low-battery LED indicator for the Pico onboard LED.
|
||||||
|
// Reads PowerPercent / PowerState from interrupt_in_data[52]
|
||||||
|
// (DualSense BT 0x31 report, see USBGetStateData in utils.h).
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
void battery_led_init(void);
|
||||||
|
|
||||||
|
// Call once per main-loop iteration. Drives the LED blink while the
|
||||||
|
// battery is low and the controller is connected; otherwise no-op.
|
||||||
|
void battery_led_tick(void);
|
||||||
|
|
||||||
|
// Call from the BT input-report callback whenever a fresh 0x31 report
|
||||||
|
// has been copied into interrupt_in_data. Used to detect disconnection
|
||||||
|
// via stale-report timeout.
|
||||||
|
void battery_led_note_report(void);
|
||||||
@@ -17,6 +17,9 @@
|
|||||||
#endif
|
#endif
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "cmd.h"
|
#include "cmd.h"
|
||||||
|
#if ENABLE_BATT_LED
|
||||||
|
#include "battery_led.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
// Pico SDK speciifically for waiting on conditions
|
// Pico SDK speciifically for waiting on conditions
|
||||||
#include "pico/critical_section.h"
|
#include "pico/critical_section.h"
|
||||||
@@ -85,6 +88,9 @@ void on_bt_data(CHANNEL_TYPE channel, uint8_t *data, uint16_t len) {
|
|||||||
|
|
||||||
if (get_config().polling_rate_mode != 2) {
|
if (get_config().polling_rate_mode != 2) {
|
||||||
memcpy(interrupt_in_data, data + 3, 63);
|
memcpy(interrupt_in_data, data + 3, 63);
|
||||||
|
#if ENABLE_BATT_LED
|
||||||
|
battery_led_note_report();
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +104,9 @@ void on_bt_data(CHANNEL_TYPE channel, uint8_t *data, uint16_t len) {
|
|||||||
memcpy(interrupt_in_data, data + 3, 63);
|
memcpy(interrupt_in_data, data + 3, 63);
|
||||||
report_dirty = true;
|
report_dirty = true;
|
||||||
critical_section_exit(&report_cs);
|
critical_section_exit(&report_cs);
|
||||||
|
#if ENABLE_BATT_LED
|
||||||
|
battery_led_note_report();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +213,10 @@ int main() {
|
|||||||
}
|
}
|
||||||
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_init();
|
||||||
|
#endif
|
||||||
|
|
||||||
#if !ENABLE_SERIAL
|
#if !ENABLE_SERIAL
|
||||||
if (watchdog_caused_reboot()) {
|
if (watchdog_caused_reboot()) {
|
||||||
printf("Rebooted by Watchdog!\n");
|
printf("Rebooted by Watchdog!\n");
|
||||||
@@ -243,5 +256,8 @@ int main() {
|
|||||||
tud_task();
|
tud_task();
|
||||||
audio_loop();
|
audio_loop();
|
||||||
interrupt_loop();
|
interrupt_loop();
|
||||||
|
#if ENABLE_BATT_LED
|
||||||
|
battery_led_tick();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user