Files
clawdmeter/screenshot.sh
tobby168andClaude Opus 4.7 f252a871c4 Make BLE pairing work on macOS, allow daemon + HID to coexist
macOS Bluetooth quirks made the original BLE setup unusable from a Mac
host. Five focused changes to fix discovery, pairing, the keyboard
identification wizard, and concurrent connections, plus a couple of
cross-platform daemon/script niceties.

Firmware (ble.cpp):
  * Advertise the standard HID Service UUID (0x1812) in the primary
    packet. Without it, macOS Sequoia's Bluetooth Settings GUI
    recognizes the device internally but silently hides it from the
    "Nearby Devices" list. Service UUIDs >16-bit overflow the 31-byte
    advertising packet, so the custom data-service UUID moved to the
    scan response.
  * Switch PnP ID from Apple's USB vendor (0x05AC + Magic Keyboard
    PID 0x820A) to Espressif's BT SIG vendor (0x02E5). macOS validates
    Apple-claimed HIDs against known device IDs and refuses to surface
    a Connect button for spoofers.
  * Add the LED output report (Num/Caps/Scroll Lock) to the HID
    descriptor — macOS treats a keyboard descriptor without LEDs as
    "incomplete" and triggers the Keyboard Setup Assistant repeatedly.
  * Set HID country code to 33 (US ANSI) instead of 0 (Not Supported)
    so macOS can identify the layout without asking the user.
  * Bump CONFIG_BT_NIMBLE_MAX_CONNECTIONS to 2 and restart advertising
    after each accept. macOS holds one connection for the HID keyboard
    link; the daemon now gets its own slot for the data service in
    parallel, instead of either side starving the other.

screenshot.sh: auto-pick /dev/cu.usbmodem101 on macOS vs /dev/ttyACM0
on Linux, fall back to PlatformIO's bundled Python if pyserial isn't
on the system Python (PEP 668 blocks `pip install` on Homebrew Python),
and pass the actual framebuffer dimensions to ffmpeg instead of
hardcoding 480x480.

daemon/claude_usage_daemon.py: log API HTTP status + response body on
4xx/5xx so silent token-expiry failures (the daemon was reporting
{"s":0,"w":0} payloads instead of surfacing a 401) are visible in the
daemon log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 16:56:05 -07:00

88 lines
2.3 KiB
Bash
Executable File

#!/bin/bash
# Take a screenshot from the Waveshare AMOLED display via LVGL snapshot.
# Usage: ./screenshot.sh [output.png] [port]
# Default port: /dev/cu.usbmodem101 on macOS, /dev/ttyACM0 on Linux.
OUTPUT="${1:-screenshot.png}"
if [ -z "$2" ]; then
case "$(uname -s)" in
Darwin) PORT="/dev/cu.usbmodem101" ;;
*) PORT="/dev/ttyACM0" ;;
esac
else
PORT="$2"
fi
# Use pio's bundled python if pyserial isn't on the system python.
PY="python3"
if ! python3 -c "import serial" 2>/dev/null; then
if [ -x "$HOME/.platformio/penv/bin/python" ]; then
PY="$HOME/.platformio/penv/bin/python"
fi
fi
TMPRAW=$(mktemp /tmp/screenshot_XXXXXX.raw)
TMPDIMS=$(mktemp /tmp/screenshot_XXXXXX.dims)
trap "rm -f '$TMPRAW' '$TMPDIMS'" EXIT
echo "Taking screenshot from $PORT..."
"$PY" - "$PORT" "$TMPRAW" "$TMPDIMS" << 'PYEOF'
import serial, sys
port_path, raw_path, dims_path = sys.argv[1], sys.argv[2], sys.argv[3]
port = serial.Serial(port_path, 115200, timeout=10)
port.reset_input_buffer()
port.write(b"screenshot\n")
port.flush()
while True:
line = port.readline().decode("utf-8", errors="replace").strip()
if line.startswith("SCREENSHOT_START"):
parts = line.split()
w, h, raw_size = int(parts[1]), int(parts[2]), int(parts[3])
break
if line == "SCREENSHOT_ERR":
print("Device reported screenshot error", file=sys.stderr)
sys.exit(1)
data = b""
while len(data) < raw_size:
chunk = port.read(min(4096, raw_size - len(data)))
if not chunk:
print(f"Timeout: got {len(data)} of {raw_size} bytes", file=sys.stderr)
sys.exit(1)
data += chunk
with open(raw_path, "wb") as f:
f.write(data)
with open(dims_path, "w") as f:
f.write(f"{w}x{h}\n")
for _ in range(10):
line = port.readline().decode("utf-8", errors="replace").strip()
if line == "SCREENSHOT_END":
break
port.close()
print(f"Captured {w}x{h} ({len(data)} bytes)")
PYEOF
if [ $? -ne 0 ]; then
echo "Screenshot capture failed"
exit 1
fi
DIMS=$(cat "$TMPDIMS")
ffmpeg -y -f rawvideo -pixel_format rgb565le -video_size "$DIMS" \
-i "$TMPRAW" -update 1 -frames:v 1 "$OUTPUT" 2>/dev/null || true
if [ -f "$OUTPUT" ]; then
echo "Saved: $OUTPUT ($DIMS)"
else
echo "Error: conversion failed"
exit 1
fi