Spectrum: stat tiles (peak, floor, our channel, busy), plot and waterfall drawn at screen size (the lv_chart and zoomed waterfall were the page's render cost), dark-to-yellow waterfall, peak hold, our-channel marker, Band/Channel and Live/1 min/10 min chips. RF Monitor 1.4 (tiles, filled chart, typed feed, packet detail card; the old type table did not match MeshCore payload types) and Airtime 1.6 (big figure, tiles, duty line, rx/tx split). Lua SDK: ui.panel, bold labels and a size-24 figure font, colors.warn/border, app.on_audio (fires with the screen off), read-only wada.sd.list and sd: audio on the SD_MMC boards (P4, Tanmatsu, Wio L2), caps panel/audio_events/epaper, a "music" icon and an "audio" Store requirement. button:set() no longer writes label fields into the button object (heap corruption, every earlier firmware). The SDK page's input table described events the firmware never sent. Music 1.0 (Store app): plays MP3/WAV from the card's Music folder, shuffle, repeat, keeps going through the list with the screen off. Verified on the T-Display P4 end to end. Store: the update check also fetches the app catalog and badges the Store tile with the number of installed apps that have a newer version. Baked Lua apps are raw-deflated and inflated by the S3 ROM (14.6 KB back on the V4); the V4 language table keeps only texts of apps it can run. Fixes: the SD-card notice no longer fires when the card mounts fine (#667); the Control Center clock keeps time while open (#666); M9 keyboard brightness (#664); the battery widget's number no longer sits on a ring too small for it (Discord); M9 shortcuts doc follows #669's composer order. New texts in all 14 languages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
3.3 KiB
Python
96 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Documentation screenshot capture for the DOC_CAPTURE firmware build.
|
|
|
|
Build the firmware with -DDOC_CAPTURE, flash it to the T-Deck, then run:
|
|
|
|
python3 scripts/doc/capture.py /dev/cu.usbmodemXXXX [outdir]
|
|
|
|
The device waits for a 'G' byte, then walks every screen and streams each
|
|
framebuffer over USB as a 16-bit BMP wrapped in <<<WMSHOT ...>>> / <<<WMEND>>>
|
|
markers. This script sends 'G', reads each frame, and writes a PNG per screen
|
|
(via macOS `sips`; falls back to leaving the .bmp if sips is unavailable).
|
|
"""
|
|
import sys, os, re, time, subprocess
|
|
|
|
try:
|
|
import serial
|
|
except ImportError:
|
|
sys.exit("pyserial not found. Try: /Library/Developer/CommandLineTools/usr/bin/python3 -m pip install pyserial")
|
|
|
|
if len(sys.argv) < 2:
|
|
sys.exit("usage: capture.py <serial-port> [outdir]")
|
|
port = sys.argv[1]
|
|
outdir = sys.argv[2] if len(sys.argv) > 2 else "doc-shots"
|
|
os.makedirs(outdir, exist_ok=True)
|
|
|
|
ser = serial.Serial(port, 115200, timeout=2)
|
|
# The T-Display P4 sits behind a CH343 bridge whose RTS drives EN: pyserial opens
|
|
# with RTS asserted, which holds the chip in reset. Pulse it once and let it run.
|
|
# The P4 also boots far slower (C6 bring-up), so give it longer to start the tour.
|
|
P4 = bool(os.environ.get("WADA_P4"))
|
|
if P4:
|
|
ser.setDTR(False); ser.setRTS(True); time.sleep(0.15); ser.setRTS(False)
|
|
print(f"listening on {port} -> {outdir}/")
|
|
|
|
hdr_re = re.compile(rb"<<<WMSHOT name=(\S+) w=(\d+) h=(\d+) bytes=(\d+)>>>")
|
|
|
|
def read_exact(n):
|
|
data = bytearray()
|
|
while len(data) < n:
|
|
chunk = ser.read(n - len(data))
|
|
if not chunk:
|
|
break
|
|
data += chunk
|
|
return bytes(data)
|
|
|
|
count = 0
|
|
started = False
|
|
last_go = 0.0
|
|
deadline = time.time() + (180 if P4 else 90) # give the device time to start
|
|
while time.time() < deadline:
|
|
if not started and time.time() - last_go > 0.4:
|
|
ser.write(b"G") # nudge the device to begin the tour
|
|
ser.flush()
|
|
last_go = time.time()
|
|
line = ser.readline()
|
|
if not line:
|
|
continue
|
|
if b"WMTOUR START" in line:
|
|
started = True
|
|
print("tour started")
|
|
deadline = time.time() + 60 # from here on: give up only after 60 s without a frame
|
|
continue
|
|
if b"WMTOUR END" in line:
|
|
print("tour ended")
|
|
break
|
|
if line.startswith(b"DBG:"):
|
|
print(line.decode(errors="replace").rstrip()) # debug lines from a capture build
|
|
continue
|
|
m = hdr_re.search(line)
|
|
if not m:
|
|
continue
|
|
name = m.group(1).decode()
|
|
nbytes = int(m.group(4))
|
|
data = read_exact(nbytes)
|
|
if len(data) != nbytes:
|
|
print(f" ! {name}: short read {len(data)}/{nbytes}")
|
|
continue
|
|
bmp = os.path.join(outdir, name + ".bmp")
|
|
png = os.path.join(outdir, name + ".png")
|
|
deadline = time.time() + 60 # the tour is still going
|
|
with open(bmp, "wb") as f:
|
|
f.write(data)
|
|
try:
|
|
subprocess.run(["sips", "-s", "format", "png", bmp, "--out", png],
|
|
check=True, capture_output=True)
|
|
os.remove(bmp)
|
|
count += 1
|
|
print(f" [{count:2d}] {name}.png ({nbytes} bytes)")
|
|
except Exception as e:
|
|
count += 1
|
|
print(f" [{count:2d}] {name}.bmp ({nbytes} bytes; sips convert failed: {e})")
|
|
|
|
ser.close()
|
|
print(f"done: {count} screenshots in {outdir}/")
|