Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
88ff0aa
Add an advertise-only Bluetooth test image (make ble)
VinSpee Nov 6, 2026
1f08da9
no-mistakes(review): Guard BLE scheduler delays; drop build.sh text test
VinSpee Nov 6, 2026
0626291
Fix the BLE test image's boot crash: leave the stack-limit guard off,…
VinSpee Nov 6, 2026
1f041dd
BLE test image: show the failing step and return code, and scroll the…
VinSpee Nov 6, 2026
38e5a81
no-mistakes(document): Correct BLE-TEST status example and marquee width
VinSpee Nov 6, 2026
cdf669b
BLE test image: create the CRC lock the controller start-up waits on,…
VinSpee Nov 6, 2026
bd5b54b
no-mistakes(document): Refresh BLE-TEST status after second flash
VinSpee Nov 6, 2026
cc05542
BLE test image: fix the null low-power handle, add a stall watch, kee…
VinSpee Nov 6, 2026
50606f0
no-mistakes(document): Update BLE-TEST status for third flash
VinSpee Nov 6, 2026
73c2531
feat: add a command-line flasher (op.mjs flash) and the openpocket-fl…
VinSpee Nov 6, 2026
f8b3a6a
feat: host the simulated pedal over HTTP (op.mjs sim) with a virtual …
VinSpee Nov 6, 2026
93174ff
BLE test image: proof counters for the radio, a public address, and a…
VinSpee Nov 6, 2026
861aea5
no-mistakes(document): Point README and pxota.js header at the CLI fl…
VinSpee Nov 6, 2026
7baa8b3
no-mistakes(document): Point README to the hosted simulator
VinSpee Nov 6, 2026
4d6b372
no-mistakes(document): Refresh BLE-TEST status, address and ADVERTISI…
VinSpee Nov 6, 2026
d66406e
Merge fm/openpocket-upstream-cli-flasher
VinSpee Nov 6, 2026
8b4ae72
Merge fm/openpocket-upstream-pedal-simulator
VinSpee Nov 6, 2026
74d08a9
Merge fm/openpocket-upstream-ble-advertise
VinSpee Nov 6, 2026
5fab7d1
feat: add a rescue mode to the command-line flasher (flash --rescue)
VinSpee Nov 6, 2026
4b4b186
Resume leftover update loader on fresh flash --rescue
VinSpee Nov 6, 2026
fb3e3fa
Refuse foreign update loaders in rescue dry run
VinSpee Nov 6, 2026
95cfc52
Document --package requirement for rescue loader resume
VinSpee Nov 6, 2026
19ae77b
feat(sim): hold buttons with down and up, timed as the firmware
VinSpee Nov 6, 2026
ea07ca7
feat(sim): the pedal's front panel as the simulator's page
VinSpee Nov 6, 2026
b458901
Key held buttons by e.code; assert served page types
VinSpee Nov 6, 2026
1cbecde
feat(sim): five dry guitar loops from Guitar-TECHS, CC BY 4.0
VinSpee Nov 6, 2026
b22cf0f
feat(sim): the engine as WebAssembly
VinSpee Nov 6, 2026
77e55c8
feat(sim): hear the pedal, from the skin
VinSpee Nov 6, 2026
69b9d14
Retry audio build after a failed start
VinSpee Nov 6, 2026
7741f46
Test all 52 presets and loops: WebAssembly matches native
VinSpee Nov 6, 2026
d1953b9
Correct simulator audio parity docs
VinSpee Nov 6, 2026
f1031c3
BLE test image: a read-only log channel over USB MIDI (DIAG) and node…
VinSpee Nov 6, 2026
ac4ee42
Add the Mac Bluetooth scan to op.mjs log and the verify-pedal skill: …
VinSpee Nov 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions .claude/skills/openpocket-flash/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
name: openpocket-flash
description: Build, dry-run and flash an OpenPocket package to a connected Donner Pocket X or M-VAVE BlackBox from the command line (node tools/op.mjs flash, over USB MIDI, no browser). Use to check that a built package fits the connected pedal, or, only when the captain explicitly instructs it, to write it. Covers the safety rules and the SD-card recovery.
---

# Flash a pedal from the command line

`node tools/op.mjs flash` (or `make flash ARGS="..."`) runs the same update protocol as the browser installer
(`docs/index.html`): the code is `docs/pxota.js` and `docs/pxpkg.js`, shared with the page; `tools/flash.mjs` only adds the
command line. Run everything from the repository root. The command runs on the host, not in the container: the pedal is a USB
device.

## Safety rules (read first)

1. **Never flash without the captain's explicit instruction** for this pedal and this package. A task that says "build" or
"test" is not that instruction. Without it, stop after the dry run and report.
2. **Always dry-run first**, and read its output: pedal, model and package must be the ones intended.
3. Never put `--confirm-flash` in a script, alias or default. It is typed for one flash, on the captain's say-so.
4. Keep the pedal plugged in and powered until the command prints `done`. Close the browser installer and any other MIDI
program first; they hold the port.
5. Only one pedal on the bus. The command refuses when none or several answer.
6. A failure prints the recovery pointer. Do not retry in a loop: report the output to the captain.

## 1. Set up once

```
node tools/op.mjs doctor # Node 18+ (Docker only for building)
npm ci --prefix tools # the one dependency, @julusian/midi (prebuilt RtMidi), into tools/node_modules
```

On Linux the MIDI library needs ALSA (`libasound2`); the pedal's USB MIDI port must be visible to your user.

## 2. Build the package

```
node tools/op.mjs package # docs/firmware/PocketX_9NN.fwsc, BlackBox_9NN.fwsc, manifest.json, build/SHA256SUMS
```

Details and failures: the `openpocket-toolchain` skill. To flash a package built elsewhere (for example a test image from
another branch), keep its `.fwsc` next to the `manifest.json` that was built with it and pass both.

## 3. Dry run (writes nothing)

```
node tools/op.mjs flash --dry-run
node tools/op.mjs flash --dry-run --package build-x/PocketX_925.fwsc --manifest build-x/manifest.json
```

It checks the package against the manifest (size, SHA-256, identity, UFW and CRC structure), finds the pedal by asking each
MIDI input for its identity (a read-only query), and refuses a package for the other model. The pedal's flash is not touched.
Success ends with `dry run ok: would update PocketX_923 -> PocketX_924`.

Refusals (exit 2, nothing sent to the pedal): no `--confirm-flash` and no `--dry-run`; package not listed in the manifest;
SHA-256, size or identity differs from the manifest; wrong model; no pedal; several pedals; pedal already in update mode
(power-cycle it).

## 4. Flash (only on the captain's explicit instruction)

```
node tools/op.mjs flash --confirm-flash [--package FILE] [--manifest FILE]
```

Same checks as the dry run, then: the pedal stages its update loader (step 1), the loader writes the app area (step 2), the
pedal restarts, and the command reads the version it reports. `done: the pedal reports PocketX_924.` is success (exit 0); a
version that differs from the package is a failure. It takes about a minute. Report the full output to the captain.

## 4b. A pedal that restarts in a loop (only on the captain's explicit instruction)

```
node tools/op.mjs flash --rescue --confirm-flash [--package FILE] [--manifest FILE] [--timeout SECONDS]
make flash-rescue ARGS="--confirm-flash"
```

The installer page's rescue mode, on the command line. Start it first, then plug the pedal in or let it restart. It polls every
MIDI port every 100 ms; the moment the pedal answers its identity query it sends the upgrade command (which stops the sound
processing, so the restarts stop) and installs the package of the pedal's model. `--package` pins one package: a pedal of
the other model is refused before anything is sent. Without `--package` the manifest's package for the pedal's model is used.
It waits `--timeout` seconds (default 180); an install already running is never cut short. A pedal that vanishes before the
update finished has had nothing written: it is waited for again. `--rescue --dry-run` waits for the pedal and checks the model,
writing nothing. Reached states: the running firmware's MIDI port, and an OpenPocket update loader left half-done (finished only with `--package`: a loader does not report its model; a foreign loader is refused). Not reached
(same as the page): a firmware that crashes before it answers, and the chip's mask-ROM loader `WL80UBOOT` (USB 4C4A:8057, no
MIDI); both need the SD-card recovery. Timeout is exit 1 with the recovery text. One pedal on the bus: the first to answer is used.

## 5. Recovery

A failure (exit 1) prints `FAILED: ...` and the recovery text. In order:

1. Unplug USB, power-cycle the pedal, run `--dry-run` again to see what it reports. A pedal in update mode (`ota-...`) is
finished by the browser installer's resume button, or by this command after a power-cycle.
2. If it stays dead, the SD-card recovery in the README (`flash/`: fm1buddy on an ESP32-S3; the image comes from your factory
backup with `make appimage DUMP=fm1-1.bin` or `makeimage.bat`). Only the captain does this.

Never publish or commit a pedal backup (`fm1-1.bin`, `fm1-300.bin`): they hold the official firmware.

## Tests

`node tests/test_flash.mjs` (part of `node tools/op.mjs test`) drives the real command against the simulated pedal that runs
the pedal's own `ota.c` and loader code: update, dry run writes nothing, wrong model, damaged package, missing confirmation,
no pedal, two pedals, failure with the recovery text, and `--rescue` against a pedal that restarts in a loop (answers on the
Nth restart, never answers, vanishes mid-transfer, wrong model, missing confirmation). Real hardware is not covered by any test.
74 changes: 74 additions & 0 deletions .claude/skills/openpocket-simulator/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
name: openpocket-simulator
description: Start and drive the hosted OpenPocket simulated pedal (node tools/op.mjs sim, make sim): the firmware's real screen, editor protocol and preset store running natively behind a local HTTP API, with a code-drawn front panel of the pedal as its page. Use to press or hold footswitches and buttons, turn the knobs, read the screen as a PNG, read the pedal state, send SysEx, dump or reset the flash, or to use the installer and editor pages against a simulated pedal in a browser. Use for verifying UI, editor-protocol or preset-store changes without a pedal, and for trying a preset or a screen. Use to hear a preset through bundled guitar loops, a file or a computer input. Not for USB or Bluetooth (not simulated).
---

# OpenPocket simulator

The pedal's own `ui.h`, `px_editor.h` and `px_store.h` run natively (`tests/edsim.c`) behind HTTP. Full reference:
`docs/SIMULATOR.md` (every endpoint, the state fields, what is not simulated). Run from the repository root. Needs Node 18+
and a C compiler (`node tools/op.mjs doctor` says whether `cc` is there); no Docker.

## Start

```
node tools/op.mjs sim --port 0 --reset & # prints: OpenPocket simulator (pedal) listening on http://127.0.0.1:PORT/
```

`--port 0` picks a free port: read the URL from the first line of output. Without `--port` it is 8766. The flash lives in
`build/sim/flash.bin` (`--flash FILE`); without `--reset` a session continues where it stopped. The first start compiles
the simulator (a few seconds). Stop it with Ctrl-C or by killing the process **you** started.

## Drive (with `U=http://127.0.0.1:PORT`)

| Do | Request |
| --- | --- |
| A footswitch or button | `curl -X POST $U/api/press/NAME`: `fs_up` `fs_dn` `fx` `home` `push` `mod` `amp` `cab` `dly` `rev`; `push_long` (SAVE), `dpush` (MOVE), `home_long` (tuner) |
| Hold a button | `curl -X POST $U/api/down/NAME`, later `$U/api/up/NAME`: the same ten names, timed as the firmware does (HOME 0.7 s, PUSH 0.8 s, double click 350 ms) |
| Turn the knob | `curl -X POST $U/api/knob/3` (negative: other way; `?fast` for steps of 5) |
| Volume, battery, tuner pitch | `POST $U/api/volume/55`, `/api/battery/3640[/USB_MV]`, `/api/tuner/110` |
| Bypass, load in the status text | `POST $U/api/bypass/1`, `/api/load/17` |
| Read the state | `curl -s $U/api/state` (slot, name, mode, focused block, the whole sound, `frame`, `held`, `knob`) |
| Read the screen | `curl -s "$U/api/screen.png?scale=2" -o screen.png`, then look at it; `screen.rgb565` is the raw pixels |
| SysEx | `curl -s -X POST $U/api/sysex -d 'f0 7d 4f 50 01 f7'` returns `{"reply":"hex"}` (`docs/EDITOR-PROTOCOL.md`) |
| Dump / reset the flash | `curl -s $U/api/flash -o flash.bin`; `curl -X POST $U/api/reset` |

Every request is applied before it is answered. The pedal's pages: `chain` (PLAY) -> `block` -> `edit`; `push_long` opens
`save` (knob picks the slot, `push` saves); `home_long` opens the tuner, any control leaves it. A worked path to change a
setting: `press/amp`, `knob/2`, `press/push`, `knob/8`, then `state` shows `preset.p[5][0]` up by 8 and `dirty: 1`.
Prove a screen claim by reading `screen.png`, not only the state.

## Browser

`$U/` is the pedal's front panel (`tools/sim/pedal.html`) with the live screen in its LCD: press and hold the buttons and
footswitches, drag or wheel the knobs, click PARA for PUSH, or use the keys listed on the page. Scripts on the page have
`window.pedal` (`down`, `up`, `tap`, `turn`, `master`, `state`); a control is `[data-control=NAME]`, `#master` or `#para`.
`$U/plain` is the plain page with the screen, every control as a button, the battery, the tuner pitch and the state.
`$U/site/index.html` (installer) and `$U/site/editor.html`
(editor) are `docs/` with a virtual Web MIDI injected, so Connect finds the simulated pedal in any browser; drive them with
`chrome-devtools-axi` or Playwright. For the installer use the update flow instead of the pedal:
`node tools/op.mjs sim --update FLASH.bin` (an image such as `flash_old.bin` from `python3 tests/fixtures.py DIR`).

## Sound

The skin's Sound section runs the real engine (`engine/px_dsp.c`, built to `tools/sim/px_engine.wasm`) in an AudioWorklet:
pick a bundled loop (`assets/loops/`), a dropped file or a device, press Play (the first click starts the audio; a device
starts muted), and the preset on the screen is what you hear. `window.pedal.audio()` reports it (`running`, `heard`,
`muted`, ...), and `#in-meter`, `#out-meter` carry `data-db`. Without a browser, `node tools/sim/render.mjs OUTDIR [preset]`
renders the loops to WAV. After any change to `engine/`, run `node tools/op.mjs wasm` (Docker) and commit the new binary:
`tests/test_audio.mjs` fails otherwise. `docs/SIMULATOR.md` ("The sound") has the reasons and limits.

## Limits (say so when verifying)

No LEDs or bypass footswitch (the firmware has none: the skin's backlights are
its own reading of the state), no USB or Bluetooth. `press/NAME` has no timing, `down` and `up` do. The panel's TUNER line
is the stock firmware's: in OpenPocket both footswitches are two presses, and the tuner is a long press of HOME.
`press()` and the `button_*` functions in `tests/edsim.c` mirror the control dispatch of `firmware/openpocket.c`: if that
loop changes, change them too. The proof is the firmware's real code on a PC, not the pedal.

## Tests

`node tests/test_sim.mjs` (also in `node tools/op.mjs test`) covers the API, the flash file and the shim.
`node tests/test_pedal_input.mjs` covers the skin's input mapping, and `node tests/test_pedal_page.mjs OUTDIR` drives the skin
in Chromium and saves screenshots (needs Playwright). `node tests/test_audio.mjs` covers the engine and the loops, and
`node tests/test_audio_page.mjs OUTDIR` the Sound section in Chromium with a fake audio input (needs Playwright).
7 changes: 5 additions & 2 deletions .claude/skills/openpocket-toolchain/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ Run commands from the repository root.
| Build the firmware, the update loader and the installer packages | `node tools/op.mjs build` |
| Build, then write `build/SHA256SUMS` | `node tools/op.mjs package` |
| Host tests (engine, preset store, screen, simulated pedal, installer, editor) | `node tools/op.mjs test` |
| The simulated pedal on a local port (host `cc`, no Docker; skill `openpocket-simulator`) | `node tools/op.mjs sim` |
| Remove every build output (needs no Docker) | `node tools/op.mjs clean` |
| A shell in the container | `node tools/op.mjs shell` |
| Dry-run or flash a built package to a connected pedal (host, USB MIDI) | `node tools/op.mjs flash`: see the `openpocket-flash` skill |

Outputs: `firmware/openpocket.bin`, `docs/firmware/PocketX_9NN.fwsc`, `BlackBox_9NN.fwsc`, `manifest.json`. All of them
are gitignored. Two clean builds give identical bytes; a changed checksum means the source or a pin changed.
Expand All @@ -50,8 +52,9 @@ Follow `references/updating-pins.md`. Never bypass the checksum check.

## Safety rules of this repository

- **Never flash a device from an agent.** No `flash`, no WebUSB or MIDI write to a real pedal. Only the captain installs,
from the browser installer, by hand. Hardware checks are in `docs/MIDI-TEST.md` when present.
- **Never flash a device unless the captain explicitly instructs it.** `node tools/op.mjs flash --dry-run` is allowed; `--confirm-flash`
and any other write to a real pedal only on the captain's say-so, following the `openpocket-flash` skill. Builds and tests never
touch a pedal. Hardware checks are in `docs/MIDI-TEST.md` when present.
- **Never publish an unreviewed package as the installer default.** A test package is a separate, labelled file and is
not listed in the default `docs/firmware/manifest.json`. `PUBLISH-*.bat` is the maintainer's tool: do not run it.
- **Keep the official-firmware analysis out of the repository.** Facts about the vendor's firmware (offsets, tables,
Expand Down
73 changes: 73 additions & 0 deletions .claude/skills/verify-pedal/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
name: verify-pedal
description: Verify an OpenPocket build on the real, USB-connected pedal without a person reading its screen: flash the Bluetooth test image, wait for boot, read boot state, last-boot report and radio counters over USB MIDI (node tools/op.mjs log), run the Mac Bluetooth scan, and report pass or fail with the reason in words. Use after a firmware/ble or boot change when a pedal is attached. For checks without a pedal, use the verify skill.
---

# Verify on the real pedal

`node tools/op.mjs log` asks the pedal for the text its screen shows (the `DIAG` request, read only) and judges it. Only the
Bluetooth test image answers `DIAG`; the release firmware does not. The pedal is one shared device: one driver at a time.

## Launch

The pedal must run the build under test. Follow the `openpocket-flash` skill: its rules on authority apply, and
`--confirm-flash` never goes in a script. One flash per attempt, no sudo.

```
npm ci --prefix tools # once
node tools/op.mjs ble # build/ble/*.fwsc, manifest.json
node tools/op.mjs flash --dry-run --manifest build/ble/manifest.json
node tools/op.mjs flash --confirm-flash --manifest build/ble/manifest.json # ends with "done: the pedal reports ..."
```

Ready means the log says stage `OK`, about 7 s after start. `--wait` below waits for that. A pedal that restarts again
and again is a crash loop: stop, do not flash again, and report (see `features/boot-failure.md`).

## Doctor

`node tools/op.mjs log --count 1` is read only and says how far the pedal answers. Exit 2 means the MIDI library is missing.
Check first that no other agent is flashing: `ps ax | grep -E 'op.mjs (flash|log)'`.

## Drive

```
node tools/op.mjs log --wait 40 --scan 30 # the whole check: two readings, the scan, a third reading, the verdict
node tools/op.mjs log --wait 40 # state only, no scan
node tools/op.mjs log --follow --interval 2 # watch it; add --json for one object per reading
```

| Verdict | Exit | Meaning |
| --- | --- | --- |
| `PASS` | 0 | the Mac saw the advertisement |
| `RUNNING` | 0 | radio events rise, no scan was run: not proof of packets on the air |
| `FAIL` | 1 | the reason says which step, or that nothing was seen |
| `PENDING`, `UNKNOWN` | 3 | not decided: still starting, scanner unusable, or less could be read (below) |

Report the verdict line as it stands. Field meanings: `docs/BLE-TEST.md`, and printed by the command.

## What it cannot read

| Pedal state | What the log gives | Fallback |
| --- | --- | --- |
| red `CRASH` screen, or the 12 s `LAST BOOT FAILED` report | no port, or a silent port, while it shows | read again after it: the next boot gives the same report as `last boot`; a screen photo meanwhile |
| main loop stuck, firmware failed before USB service | port there, nothing answers | screen photo; power cycle or the UBOOT rescue |
| boot loader, update mode | identity only, or no port | finish the update; `openpocket-flash` skill |
| release firmware, or a test image without `DIAG` | version and DSP load, says the rest is missing | screen photo |
| stock firmware | identity only | nothing to read |

The log never shows sound, footswitches or the drawing on the screen.

## Evidence

Save the output with `tee` to a folder outside the repository, with the package SHA-256 from the flash log. Proof standards:
the flash log, the `log --wait 40 --scan 30` output and the verdict, from the real pedal. The scan listens only; it never
connects or pairs. `RUNNING` is not `PASS`. `r` stays 0 with a Mac scan, so it says nothing here. A boot-failure claim needs
the `last boot` line from the real pedal, not the simulator.

## Cleanup

Nothing stays running; stop `--follow` with Ctrl-C. Restore the release firmware only when the task says so:
`node tools/op.mjs flash --confirm-flash` after `node tools/op.mjs package`. `build/ble-scan` is rebuilt when missing.
Evidence stays in the folder you chose.

Features: `features/README.md`. Keep them true with `/maintain-verification-skill`.
8 changes: 8 additions & 0 deletions .claude/skills/verify-pedal/features/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Pedal feature map

| Feature | File | Proof |
| --- | --- | --- |
| Log channel | `log-channel.md` | parsed fields from the real pedal, rising counters |
| Flash and boot | `flash-and-boot.md` | `done: the pedal reports ...`, then stage `OK` |
| Bluetooth advertising | `ble-advertising.md` | verdict `PASS` |
| Boot failure report | `boot-failure.md` | `last boot FAILED` with the stage and the reset or crash |
25 changes: 25 additions & 0 deletions .claude/skills/verify-pedal/features/ble-advertising.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Bluetooth advertising

The test image sends `OpenPocket-BLE-TEST` with the BLE-MIDI service UUID, at 100 ms, not connectable.

## Sub-features

- Controller start-up: `STARTING n`, `FAILED s c`, `ADV ...`.
- Radio counters: `i` about 10 a second is the advertising rate; `t`, `s`, and `V` (stored radio records) are reported as notes.
- The Mac scan: `tools/ble-scan.swift`, built into `build/ble-scan` on first use.

## How to get to it (user POV)

Pedal running the test image, within a few metres of the Mac, Bluetooth on.

## Driving it with tools/op.mjs

`node tools/op.mjs log --wait 40 --scan 30`: 30 s filtered on `03B80E5A-EDE8-4B33-A751-6CE34EC4C700`, then 30 s unfiltered,
matching the name or the UUID. Proof: `verdict PASS`, with the name and RSSI.

## Gotchas

- `others` above 0 is what shows the scanner works when the target is not seen; a scanner with no authorization gives `UNKNOWN`.
- The first run may ask macOS for Bluetooth permission for the terminal.
- The name is in the scan response, the UUID in the advertising data: a sighting may carry only one.
- `ADV` only means the controller accepted the command.
Loading
Loading