From 0da29e8871b1f82242f30050a8007ba25d8156c2 Mon Sep 17 00:00:00 2001 From: Evan Reichard Date: Fri, 24 Jul 2026 17:30:09 -0400 Subject: [PATCH] feat(skill): isolate firmware emulator invocation --- .pi/skills/test-xteink-firmware/SKILL.md | 46 ++++---------- .../test-xteink-firmware/scripts/variable.sh | 60 +++++++++++++++++++ .../scripts/xteink-emu.sh | 18 ++++++ 3 files changed, 90 insertions(+), 34 deletions(-) create mode 100755 .pi/skills/test-xteink-firmware/scripts/variable.sh create mode 100755 .pi/skills/test-xteink-firmware/scripts/xteink-emu.sh diff --git a/.pi/skills/test-xteink-firmware/SKILL.md b/.pi/skills/test-xteink-firmware/SKILL.md index dff77f99e8..e36d5b6d48 100644 --- a/.pi/skills/test-xteink-firmware/SKILL.md +++ b/.pi/skills/test-xteink-firmware/SKILL.md @@ -9,17 +9,7 @@ description: "Build and interactively test Xteink ESP32-C3 firmware in the nativ Build firmware for an Xteink device, boot it in the native emulator, and validate behavior through serial logs, physical controls, screenshots, SD state, and network access. -## Prerequisites - -Point `XTEINK_QEMU_REPO` at a prepared QEMU checkout containing `dist/qemu-native`: - -```sh -export XTEINK_QEMU_REPO=${XTEINK_QEMU_REPO:-../qemu} -export XTEINK_EMU_STATE=/tmp/xteink-firmware-$UID -export XTEINK_EMU_SD=/tmp/xteink-firmware-$UID.img -``` - -The emulator exits with setup instructions when its native QEMU distribution is missing. +Resolve script paths relative to this skill directory. `scripts/xteink-emu.sh` stores the qemu-xteink checkout location, builds native QEMU when missing, and runs the emulator. If its location is unset, run the `variable.sh --set` command printed by the wrapper after asking the user for the checkout path. ## Workflow @@ -28,6 +18,8 @@ The emulator exits with setup instructions when its native QEMU distribution is ```sh pio run export XTEINK_FIRMWARE=.pio/build/default/firmware.bin + export XTEINK_EMU_STATE=/tmp/xteink-firmware-$UID + export XTEINK_EMU_SD=/tmp/xteink-firmware-$UID.img ``` 2. Create a persistent FAT32 SD image once, unless the test requires a fresh card: @@ -40,9 +32,8 @@ The emulator exits with setup instructions when its native QEMU distribution is 3. Start a clean emulator process while preserving SD contents: ```sh - uv run --script "$XTEINK_QEMU_REPO/scripts/xteink-emu.py" stop \ - --state-dir "$XTEINK_EMU_STATE" || true - uv run --script "$XTEINK_QEMU_REPO/scripts/xteink-emu.py" start \ + scripts/xteink-emu.sh stop --state-dir "$XTEINK_EMU_STATE" || true + scripts/xteink-emu.sh start \ --state-dir "$XTEINK_EMU_STATE" \ --firmware "$XTEINK_FIRMWARE" \ --sdcard "$XTEINK_EMU_SD" @@ -53,7 +44,7 @@ The emulator exits with setup instructions when its native QEMU distribution is 4. Observe serial behavior with either a persistent regex cursor or the raw log: ```sh - uv run --script "$XTEINK_QEMU_REPO/scripts/xteink-emu.py" wait \ + scripts/xteink-emu.sh wait \ --state-dir "$XTEINK_EMU_STATE" --timeout 30 --match 'pattern' tail -f "$XTEINK_EMU_STATE/serial.log" ``` @@ -61,12 +52,9 @@ The emulator exits with setup instructions when its native QEMU distribution is 5. Drive the physical controls: ```sh - uv run --script "$XTEINK_QEMU_REPO/scripts/xteink-emu.py" button \ - --state-dir "$XTEINK_EMU_STATE" bottom-2 - uv run --script "$XTEINK_QEMU_REPO/scripts/xteink-emu.py" button \ - --state-dir "$XTEINK_EMU_STATE" power --down - uv run --script "$XTEINK_QEMU_REPO/scripts/xteink-emu.py" button \ - --state-dir "$XTEINK_EMU_STATE" power --up + scripts/xteink-emu.sh button --state-dir "$XTEINK_EMU_STATE" bottom-2 + scripts/xteink-emu.sh button --state-dir "$XTEINK_EMU_STATE" power --down + scripts/xteink-emu.sh button --state-dir "$XTEINK_EMU_STATE" power --up ``` Available buttons are `left`, `right`, `bottom-1` through `bottom-4`, and `power`. Use the labels rendered by the current screen as the authoritative mapping. Allow the e-ink panel to refresh after state-changing input. @@ -74,20 +62,11 @@ The emulator exits with setup instructions when its native QEMU distribution is 6. Capture and inspect the screen after meaningful transitions: ```sh - uv run --script "$XTEINK_QEMU_REPO/scripts/xteink-emu.py" screenshot \ + scripts/xteink-emu.sh screenshot \ --state-dir "$XTEINK_EMU_STATE" --output /tmp/xteink-screen.png ``` -7. Enter text when the firmware's on-screen keyboard is focused: - - ```sh - uv run --script "$XTEINK_QEMU_REPO/scripts/xteink-type-keyboard.py" \ - "$XTEINK_EMU_STATE" normal 'Example' - uv run --script "$XTEINK_QEMU_REPO/scripts/xteink-type-keyboard.py" \ - "$XTEINK_EMU_STATE" url 'https://example.com' - ``` - -8. For network tests, connect the firmware to the single open Wi-Fi network named `qemu`, then validate the feature's observable result. +7. For network tests, connect the firmware to the single open Wi-Fi network named `qemu`, then validate the feature's observable result. ## Diagnose Failures @@ -100,6 +79,5 @@ The emulator exits with setup instructions when its native QEMU distribution is ## Cleanup ```sh -uv run --script "$XTEINK_QEMU_REPO/scripts/xteink-emu.py" stop \ - --state-dir "$XTEINK_EMU_STATE" +scripts/xteink-emu.sh stop --state-dir "$XTEINK_EMU_STATE" ``` diff --git a/.pi/skills/test-xteink-firmware/scripts/variable.sh b/.pi/skills/test-xteink-firmware/scripts/variable.sh new file mode 100755 index 0000000000..0a817577a7 --- /dev/null +++ b/.pi/skills/test-xteink-firmware/scripts/variable.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# Skill-local variable store. Values live in /.vars/. +# +# Usage: +# variable.sh --get NAME # prints value to stdout, exits 0 +# # or prints a self-explaining hint to +# # stderr and exits 2 if unset. +# variable.sh --set NAME VALUE # writes value, exits 0. +# +# Callers should treat a non-zero exit as fatal; the stderr message tells +# the caller (agent or user) exactly how to populate the missing value. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SKILL_DIR="$(dirname "$SCRIPT_DIR")" +STORE="$SKILL_DIR/.vars" +SELF="$0" + +usage() { + cat >&2 <&2 < +EOF + exit 2 + fi + cat "$file" + ;; + --set) + [[ $# -eq 3 ]] || usage + name="$2"; value="$3" + [[ "$name" =~ ^[A-Z][A-Z0-9_]*$ ]] || { + echo >&2 "$SELF: invalid name '$name' (must match [A-Z][A-Z0-9_]*)" + exit 2 + } + mkdir -p "$STORE" + # Self-ignore the store so values never get committed, even if the + # skill root lacks a .gitignore entry for .vars/. + [[ -f "$STORE/.gitignore" ]] || printf '*\n' > "$STORE/.gitignore" + printf '%s' "$value" > "$STORE/$name" + ;; + *) + usage + ;; +esac diff --git a/.pi/skills/test-xteink-firmware/scripts/xteink-emu.sh b/.pi/skills/test-xteink-firmware/scripts/xteink-emu.sh new file mode 100755 index 0000000000..04c57ca2e2 --- /dev/null +++ b/.pi/skills/test-xteink-firmware/scripts/xteink-emu.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +QEMU_REPO="$("$SCRIPT_DIR/variable.sh" --get XTEINK_QEMU_REPO)" +EMU="$QEMU_REPO/scripts/xteink-emu.py" +QEMU="$QEMU_REPO/dist/qemu-native/bin/qemu-system-riscv32" + +if [[ ! -f "$EMU" ]]; then + echo >&2 "$QEMU_REPO is not a qemu-xteink checkout (missing scripts/xteink-emu.py)" + exit 2 +fi + +if [[ ! -x "$QEMU" ]]; then + make -C "$QEMU_REPO" xteink +fi + +exec "$EMU" "$@"