Replace USB serial/HID with Bluetooth Low Energy

Move all communication from USB to BLE. The device now advertises as
"Claude Controller" and acts as both a BLE HID keyboard (for touch
gestures) and a GATT data server (for usage updates from the daemon).

- Add NimBLE-Arduino BLE module with custom GATT service + HID keyboard
- Add third screen (Bluetooth) with connection status, MAC, reset button
- Rewrite daemon in bash using bluetoothctl/busctl for BLE GATT writes
- Add screenshot capture via LVGL snapshot over serial
- Remove TinyUSB mode — normal pio upload works again
- Update README with BLE architecture, screenshots, gesture docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hermann Björgvin Haraldsson
2026-04-04 19:30:19 +00:00
co-authored by Claude Opus 4.6
parent 930055170f
commit 10b8052bcc
22 changed files with 917 additions and 167 deletions
Executable
+66
View File
@@ -0,0 +1,66 @@
#!/bin/bash
# Take a screenshot from the SC01 Plus display via LVGL snapshot.
# Usage: ./screenshot.sh [output.png] [port]
OUTPUT="${1:-screenshot.png}"
PORT="${2:-/dev/ttyACM0}"
TMPRAW=$(mktemp /tmp/screenshot_XXXXXX.raw)
trap "rm -f '$TMPRAW'" EXIT
echo "Taking screenshot from $PORT..."
python3 - "$PORT" "$TMPRAW" << 'PYEOF'
import serial, sys
port_path, raw_path = sys.argv[1], sys.argv[2]
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)
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
ffmpeg -y -f rawvideo -pixel_format rgb565le -video_size 480x320 \
-i "$TMPRAW" -update 1 -frames:v 1 "$OUTPUT" 2>/dev/null || true
if [ -f "$OUTPUT" ]; then
echo "Saved: $OUTPUT"
else
echo "Error: conversion failed"
exit 1
fi