v2 Phase 5: Now Playing — Windows media session on the watch
The daemon reads the Windows "now playing" media session (WinRT SMTC) and
adds np/nt/na to the BLE payload; the watch shows a play/pause glyph, a
scrolling title, artist and status. Best-effort and token-independent, so
it works even when the rate-limit data is unavailable.
Cyrillic titles: Styrene B has no Cyrillic glyphs, so the title/artist use
new composite fonts (font_styrene_cyr_{28,20}) — Latin from Styrene B,
Cyrillic + typographic punctuation from Montserrat, merged by lv_font_conv.
Latin titles stay on-brand; only Cyrillic falls back to Montserrat.
- daemon: read_now_playing() via winrt-Windows.Media.Control (lazy import,
never raises; PLAYING->1, PAUSED->2, else 0; title/artist truncated,
empty fields omitted)
- firmware: UsageData np_state/np_title/np_artist + parser; the real Now
Playing screen replaces the shared stub (SCREEN_NOWPLAYING)
- fonts: assets/Montserrat-Medium.ttf, font_styrene_cyr_{28,20}.c
- tools: patch_lvgl9_font.py (automates the 4 LVGL 9 font patches),
screenshot_win.py (Windows serial screenshot via pyserial + Pillow)
- README: document Cyrillic composite font generation
Verified on hardware (waveshare_amoled_206): idle state, a live Latin
title (scrolling), and Cyrillic (Prohozhdenie / Dunduk) all render.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Capture a screenshot from the watch over USB serial (Windows).
|
||||
|
||||
The Unix `screenshot.sh` shells out to ffmpeg; this is a self-contained Windows
|
||||
equivalent that needs only pyserial + Pillow (both in the daemon venv). The
|
||||
firmware's `screenshot` serial command dumps the LVGL framebuffer as raw RGB565LE
|
||||
between `SCREENSHOT_START w h size` and `SCREENSHOT_END`.
|
||||
|
||||
Usage:
|
||||
python tools/screenshot_win.py [COM_PORT] [out.png] (defaults: COM7 shot.png)
|
||||
"""
|
||||
import array
|
||||
import sys
|
||||
import time
|
||||
|
||||
import serial
|
||||
from PIL import Image
|
||||
|
||||
port_name = sys.argv[1] if len(sys.argv) > 1 else "COM7"
|
||||
out = sys.argv[2] if len(sys.argv) > 2 else "shot.png"
|
||||
|
||||
port = serial.Serial(port_name, 115200, timeout=3)
|
||||
time.sleep(0.3)
|
||||
port.reset_input_buffer()
|
||||
port.write(b"screenshot\n")
|
||||
port.flush()
|
||||
|
||||
w = h = raw_size = 0
|
||||
deadline = time.time() + 15
|
||||
while time.time() < deadline:
|
||||
line = port.readline().decode("utf-8", errors="replace").strip()
|
||||
if line.startswith("SCREENSHOT_START"):
|
||||
_, sw, sh, ss = line.split()
|
||||
w, h, raw_size = int(sw), int(sh), int(ss)
|
||||
break
|
||||
if line in ("SCREENSHOT_ERR", "SCREENSHOT_UNSUPPORTED"):
|
||||
sys.exit(f"device error: {line}")
|
||||
if not raw_size:
|
||||
sys.exit("no SCREENSHOT_START (is the device booted and on COM port?)")
|
||||
|
||||
data = b""
|
||||
while len(data) < raw_size:
|
||||
chunk = port.read(min(8192, raw_size - len(data)))
|
||||
if not chunk:
|
||||
sys.exit(f"timeout: got {len(data)} of {raw_size} bytes")
|
||||
data += chunk
|
||||
port.close()
|
||||
|
||||
# RGB565 little-endian -> RGB888. array 'H' is native (LE on x86) == RGB565LE.
|
||||
px = array.array("H")
|
||||
px.frombytes(data)
|
||||
rgb = bytearray(len(px) * 3)
|
||||
for i, v in enumerate(px):
|
||||
r = (v >> 11) & 0x1F
|
||||
g = (v >> 5) & 0x3F
|
||||
b = v & 0x1F
|
||||
rgb[i * 3] = (r << 3) | (r >> 2)
|
||||
rgb[i * 3 + 1] = (g << 2) | (g >> 4)
|
||||
rgb[i * 3 + 2] = (b << 3) | (b >> 2)
|
||||
Image.frombytes("RGB", (w, h), bytes(rgb)).save(out)
|
||||
print(f"saved {out} {w}x{h} ({len(data)} bytes)")
|
||||
Reference in New Issue
Block a user