v2: real-time Now Playing cadence + robust long-title BLE writes
Decouple the two data sources that share the BLE link: - Anthropic usage / rate-limit: still polled every 60s. - Windows media session: read every 3s and pushed the moment the track or play/pause state changes, so a song change reaches the watch in seconds instead of at the next 60s poll. The last usage payload is cached and merged into each now-playing write, so the firmware always gets one complete JSON object and the usage screens never blank between polls. Fix: long media titles (a 44-char Cyrillic title -> ~256 B once json escapes each char to \uXXXX) overflowed the ATT MTU, so every write-without-response failed with E_INVALIDARG and tripped the zombie-link break in a reconnect loop. Switch the RX write to response=True (WinRT does a reliable long write; the RX char already advertises WRITE and NimBLE reassembles into its 512 B buffer) and serialize with ensure_ascii=False so Cyrillic goes as 2-byte UTF-8 instead of 6-byte escapes. Verified on hardware: stable link, writes succeed across the 60s heartbeat. Firmware: only re-set the Now Playing labels when the track / state actually changed, so the faster cadence does not restart the circular title-scroll animation on every (often identical) payload. requirements-windows.txt: add winrt-Windows.Media[.Control]. Phase 5 imports them but they were never declared, so now-playing silently degraded to "nothing playing" on a fresh machine. tests: fix two stale poll_api assertions that expected an "ok" key poll_api has not emitted since that flag moved to the caller (connect_and_run). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+31
-15
@@ -1,6 +1,7 @@
|
||||
#include "ui.h"
|
||||
#include "splash.h"
|
||||
#include <lvgl.h>
|
||||
#include <string.h>
|
||||
#include "logo.h"
|
||||
#include "icons.h"
|
||||
#include "hal/board_caps.h"
|
||||
@@ -860,22 +861,37 @@ void ui_update(const UsageData* data) {
|
||||
|
||||
// Now Playing — local media session relayed by the daemon. Like Session, it's
|
||||
// independent of the rate-limit data, so refresh it before the ok==false bail.
|
||||
// Only touch the labels when the track / playback state actually changed: the
|
||||
// daemon pushes a payload every few seconds (fast "music card" cadence), and
|
||||
// re-running lv_label_set_text() on an unchanged title restarts the circular
|
||||
// scroll animation each time, so a long title would never get to scroll.
|
||||
if (nowplaying_container) {
|
||||
if (data->np_state == 0) {
|
||||
lv_label_set_text(np_icon_lbl, LV_SYMBOL_AUDIO);
|
||||
lv_obj_set_style_text_color(np_icon_lbl, COL_DIM, 0);
|
||||
lv_label_set_text(np_title_lbl, "Nothing playing");
|
||||
lv_obj_set_style_text_color(np_title_lbl, COL_DIM, 0);
|
||||
lv_label_set_text(np_artist_lbl, "");
|
||||
lv_label_set_text(np_status_lbl, "");
|
||||
} else {
|
||||
bool playing = (data->np_state == 1);
|
||||
lv_label_set_text(np_icon_lbl, playing ? LV_SYMBOL_PLAY : LV_SYMBOL_PAUSE);
|
||||
lv_obj_set_style_text_color(np_icon_lbl, COL_ACCENT, 0);
|
||||
lv_label_set_text(np_title_lbl, data->np_title[0] ? data->np_title : "(no title)");
|
||||
lv_obj_set_style_text_color(np_title_lbl, COL_TEXT, 0);
|
||||
lv_label_set_text(np_artist_lbl, data->np_artist);
|
||||
lv_label_set_text(np_status_lbl, playing ? "Playing" : "Paused");
|
||||
static int np_state_shown = -1;
|
||||
static char np_title_shown[64] = {0};
|
||||
static char np_artist_shown[64] = {0};
|
||||
if (data->np_state != np_state_shown
|
||||
|| strcmp(data->np_title, np_title_shown) != 0
|
||||
|| strcmp(data->np_artist, np_artist_shown) != 0) {
|
||||
np_state_shown = data->np_state;
|
||||
strlcpy(np_title_shown, data->np_title, sizeof(np_title_shown));
|
||||
strlcpy(np_artist_shown, data->np_artist, sizeof(np_artist_shown));
|
||||
|
||||
if (data->np_state == 0) {
|
||||
lv_label_set_text(np_icon_lbl, LV_SYMBOL_AUDIO);
|
||||
lv_obj_set_style_text_color(np_icon_lbl, COL_DIM, 0);
|
||||
lv_label_set_text(np_title_lbl, "Nothing playing");
|
||||
lv_obj_set_style_text_color(np_title_lbl, COL_DIM, 0);
|
||||
lv_label_set_text(np_artist_lbl, "");
|
||||
lv_label_set_text(np_status_lbl, "");
|
||||
} else {
|
||||
bool playing = (data->np_state == 1);
|
||||
lv_label_set_text(np_icon_lbl, playing ? LV_SYMBOL_PLAY : LV_SYMBOL_PAUSE);
|
||||
lv_obj_set_style_text_color(np_icon_lbl, COL_ACCENT, 0);
|
||||
lv_label_set_text(np_title_lbl, data->np_title[0] ? data->np_title : "(no title)");
|
||||
lv_obj_set_style_text_color(np_title_lbl, COL_TEXT, 0);
|
||||
lv_label_set_text(np_artist_lbl, data->np_artist);
|
||||
lv_label_set_text(np_status_lbl, playing ? "Playing" : "Paused");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user