From 7f1bf9b88948dfeab4143b7d8b74393a5faa69b6 Mon Sep 17 00:00:00 2001 From: Suyash Date: Fri, 18 Sep 2026 03:02:22 +0000 Subject: [PATCH] fix(ui): refresh the battery level while connected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `requestBattery()` only runs in the post-connect read, so the percentage in the header is a snapshot from the moment you pressed Connect. Leave the app open for a few hours and it still shows that first value while the real headset drains — and on a charging headset it never climbs. Piggyback a battery read on the existing ambient poll worker, once every ~30s (`BATTERY_POLL_FRAMES`), so no extra thread or future is needed. The read is guarded: a device that doesn't answer the inquiry keeps its stale value instead of dropping the poll. Verified against a simulated v2 device whose reported level changes over time: the header follows it instead of freezing at the connect-time value. Co-Authored-By: Claude Opus 5 --- Client/CrossPlatformGUI.cpp | 21 ++++++++++++++++++++- Client/CrossPlatformGUI.h | 4 ++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Client/CrossPlatformGUI.cpp b/Client/CrossPlatformGUI.cpp index 993d9a85..b5b08e4d 100644 --- a/Client/CrossPlatformGUI.cpp +++ b/Client/CrossPlatformGUI.cpp @@ -241,7 +241,26 @@ void CrossPlatformGUI::_pumpConnectionState() if (!this->_pollFuture.valid() && ++this->_pollCounter >= DYNAMIC_POLL_FRAMES) { this->_pollCounter = 0; - this->_pollFuture.setFromAsync([this]() { this->_headphones.requestAmbientState(); }); + + // Battery is otherwise read once, in the post-connect phase, and never again: the header keeps + // showing the level from the moment you connected for as long as the app stays open. Refresh it + // on the same worker, on its own slower schedule. + bool withBattery = false; + this->_batteryPollCounter += DYNAMIC_POLL_FRAMES; + if (this->_isV2() && this->_batteryPollCounter >= BATTERY_POLL_FRAMES) + { + this->_batteryPollCounter = 0; + withBattery = true; + } + + this->_pollFuture.setFromAsync([this, withBattery]() { + this->_headphones.requestAmbientState(); + // Best effort: a device that doesn't answer the battery inquiry must not kill the poll. + if (withBattery) + { + try { this->_headphones.requestBattery(); } catch (const std::exception&) {} + } + }); } } diff --git a/Client/CrossPlatformGUI.h b/Client/CrossPlatformGUI.h index f396939f..f4a3d5b3 100644 --- a/Client/CrossPlatformGUI.h +++ b/Client/CrossPlatformGUI.h @@ -26,6 +26,9 @@ const auto WINDOW_COLOR = ImVec4(0.09f, 0.09f, 0.11f, 1.00f); // Physical-button changes (ASM) are polled roughly every two seconds so the app stays in sync. constexpr auto DYNAMIC_POLL_FRAMES = FPS * 2; +// Battery moves slowly and each read costs a round trip, so it rides along with the ambient poll on a +// much slower schedule instead of getting a request of its own. +constexpr auto BATTERY_POLL_FRAMES = FPS * 30; class CrossPlatformGUI { @@ -81,6 +84,7 @@ class CrossPlatformGUI bool _synced = false; bool _probed = false; int _pollCounter = 0; + int _batteryPollCounter = 0; // UI state, synced from the device once after connect; user edits are pushed back to the headphones. bool _uiAsmOn = false;