From 05c9d9cc565fa3a2bd249b772cd0a5d028eb3025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Martin?= Date: Fri, 2 Oct 2026 13:38:25 +0200 Subject: [PATCH] Add serial dev commands and a serial log helper The firmware accepts burst, key , sound on|off and short|normal over serial, so UI behaviour can be reproduced on the device without the keyboard. scripts/serial_log.sh records (and drives) it in a named container that flash.sh removes before uploading. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT --- README.md | 13 +++++++++++++ scripts/flash.sh | 1 + scripts/serial_log.py | 30 ++++++++++++++++++++++++++++++ scripts/serial_log.sh | 8 ++++++++ src/main.cpp | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+) create mode 100644 scripts/serial_log.py create mode 100755 scripts/serial_log.sh diff --git a/README.md b/README.md index c2f1356..ff012fb 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,16 @@ This runs the host-side unit tests (`test/`, `native` environment), then builds ```sh sudo usermod -aG dialout "$USER" ``` + +## Development aids + +`scripts/serial_log.sh [seconds] [command…]` records the serial output, and can send commands to the firmware first. For example, `scripts/serial_log.sh 30 short sleep:12 burst` sets short screen timeouts, waits 12 s, then sends a burst of Toasts. + +| Command | Effect | +|---|---| +| `burst` | Publishes 5 Notifications at once | +| `key up\|down\|left\|right\|select\|back\|home` | Injects a logical key press | +| `sound on` / `sound off` | Toggles the Sound setting (beep + LED) | +| `short` / `normal` | Screen timeouts 5 s / 10 s, or 30 s / 60 s | + +`scripts/flash.sh` stops a running serial log first, since it would hold the port. diff --git a/scripts/flash.sh b/scripts/flash.sh index 7a962ef..01739ac 100755 --- a/scripts/flash.sh +++ b/scripts/flash.sh @@ -5,6 +5,7 @@ set -euo pipefail PORT="${1:-$(readlink -f /dev/serial/by-id/*Espressif* 2>/dev/null | head -1)}" [ -n "$PORT" ] || { echo "No Cardputer found on USB (is it plugged in / attached to the VM?)" >&2; exit 1; } source "$(dirname "$0")/_docker.sh" +docker rm -f roro9stack-serial >/dev/null 2>&1 || true # a serial log would hold the port DOCKER_EXTRA=(--device "$PORT" --group-add "$(stat -c %g "$PORT")" -it) run_in_container bash -c "git config --global --add safe.directory /work && pio run -e cardputer-adv -t upload --upload-port $PORT && pio device monitor -p $PORT -b 115200" diff --git a/scripts/serial_log.py b/scripts/serial_log.py new file mode 100644 index 0000000..3ee9797 --- /dev/null +++ b/scripts/serial_log.py @@ -0,0 +1,30 @@ +# Reads the Cardputer's serial port, reconnecting when it re-enumerates. Run via serial_log.sh. +import glob, os, sys, time +import serial + +end = time.time() + float(sys.argv[1]) +commands = sys.argv[2:] # sent once after connecting, e.g. "burst" +while time.time() < end: + ports = glob.glob('/dev/serial/by-id/*Espressif*') + if not ports: + time.sleep(1) + continue + try: + s = serial.Serial(os.path.realpath(ports[0]), 115200, timeout=0.2) + print('[connected]', flush=True) + for c in commands: # "sleep:N" waits N seconds between commands + if c.startswith('sleep:'): + time.sleep(float(c[6:])) + continue + time.sleep(1) + s.write((c + '\n').encode()) + commands = [] + buf = b'' + while time.time() < end: + buf += s.read(512) + while b'\n' in buf: + line, buf = buf.split(b'\n', 1) + print(line.decode(errors='replace').rstrip(), flush=True) + except Exception as e: + print('[disconnected]', e, flush=True) + time.sleep(1) diff --git a/scripts/serial_log.sh b/scripts/serial_log.sh new file mode 100755 index 0000000..5a0fb6a --- /dev/null +++ b/scripts/serial_log.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Dev helper: record the Cardputer's serial output for N seconds (follows port changes). +# Usage: scripts/serial_log.sh [seconds] [command...] — stop early with: docker rm -f roro9stack-serial +set -euo pipefail +source "$(dirname "$0")/_docker.sh" +docker rm -f roro9stack-serial >/dev/null 2>&1 || true +DOCKER_EXTRA=(--name roro9stack-serial --group-add "$(getent group dialout | cut -d: -f3)" --privileged -v /dev:/dev) +run_in_container /pio/penv/bin/python scripts/serial_log.py "${1:-60}" "${@:2}" diff --git a/src/main.cpp b/src/main.cpp index 5efb0fb..73bf9c3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -99,11 +99,45 @@ void setup() { Serial.printf("%s %s ready, free heap %u\n", kProductName, versionString(), ESP.getFreeHeap()); } +// Dev aid: serial commands to drive the UI without the keyboard. +static void serialCommands() { + static String line; + while (Serial.available()) { + char c = Serial.read(); + if (c != '\n') { + line += c; + continue; + } + line.trim(); + if (line == "burst") + for (int i = 1; i <= 5; i++) + bus.publish(Event::withText(EventType::Notification, ("Burst " + String(i)).c_str(), 0)); + if (line.startsWith("key ")) { // key up|down|left|right|select|back|home + String k = line.substring(4); + Key key = k == "up" ? Key::Up : k == "down" ? Key::Down : k == "left" ? Key::Left + : k == "right" ? Key::Right : k == "back" ? Key::Back : k == "home" ? Key::Home : Key::Select; + if (!power->onKey(millis())) apps->handleKey(KeyEvent::of(key)); + } + if (line == "sound off") settings.setBool(Setting::Sound, false); + if (line == "sound on") settings.setBool(Setting::Sound, true); + if (line == "short") { + settings.setInt(Setting::DimTimeoutS, 5); + settings.setInt(Setting::OffTimeoutS, 10); + } + if (line == "normal") { + settings.setInt(Setting::OffTimeoutS, 60); + settings.setInt(Setting::DimTimeoutS, 30); + } + line = ""; + } +} + void loop() { static StatusInfo lastStatus; static uint32_t lastLog = 0; uint32_t now = millis(); + serialCommands(); M5Cardputer.update(); if (M5Cardputer.Keyboard.isChange()) { bool swallow = M5Cardputer.Keyboard.isPressed() && power->onKey(now); // only woke the screen