Answers "how do we emulate/lint this?" with runnable tooling for every part, and factors the pure firmware logic out so it is unit-testable on the host. What changed: - firmware/src/wav.h, firmware/src/httprange.h: pure, header-only WAV header builder and HTTP Range parser (no Arduino deps), so the tricky byte-layout and range logic can be tested on a PC. storage.cpp and api_http.cpp refactored to use them. - firmware/test/test_pure/: Unity tests for the WAV header fields and Range parsing (start-end, open-ended, clamped, unsatisfiable, non-bytes). Run: pio test -e native. - firmware/platformio.ini: add [env:native] (host tests) and [env:esp32s3_wokwi] (emulator build with default WiFi = Wokwi-GUEST so the API comes up in the sim). - firmware/wokwi.toml + firmware/diagram.json: Wokwi emulator harness (ESP32-S3 + microSD + button + LED). Note: Wokwi has no I2S mic part, so audio isn't emulated; the harness targets boot + WiFi + REST API. - firmware/src/main.cpp + config.cpp: bring up WiFi + API even if audio/SD init fails (device stays reachable, reports the fault via GET /device); compile-time default WiFi honoured when NVS is empty (used by the Wokwi build). - .forgejo/workflows/ci.yml: add native tests + cppcheck to the firmware job; new openapi job (openapi-spec-validator) and emulator job (Wokwi build, plus a full run when WOKWI_CLI_TOKEN is set). - docs/testing.md: the full emulate/lint guide for firmware, server, API, app, case. - state/: NOTES points at the guide; TODO reflects this branch. Why: - The firmware can't be flashed yet (no parts) and doesn't build on this dev host, so we need host-runnable checks. Pure-logic unit tests + OpenAPI validation run anywhere; Wokwi emulates boot/WiFi/API; CI compiles the real firmware. Verified locally: the OpenAPI spec validates (12 paths, 10 schemas). Notes: - Native tests and cppcheck run in CI (no compiler on the dev host). The Wokwi full run is skipped unless a WOKWI_CLI_TOKEN secret is present; the build is still verified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.3 KiB
Emulating and linting OpenScribe
How to check each part of the project without (and with) the physical device. Everything
here also runs in CI (.forgejo/workflows/ci.yml).
TL;DR
| Part | Lint / static check | Compile / build | Emulate / run |
|---|---|---|---|
| Firmware | pio check (cppcheck) |
pio run -e esp32s3_dev |
Wokwi (pio run -e esp32s3_wokwi + wokwi-cli) |
| Firmware logic | - | pio test -e native |
native unit tests (host) |
| Server | ruff check app |
- | uvicorn app.main:app + hit /docs |
| API spec | openapi-spec-validator |
- | Swagger UI at /docs |
| App | flutter analyze |
flutter build |
flutter run / emulator |
| Case | - | openscad -o out.stl ... |
OpenSCAD preview (F5) |
Firmware (ESP32-S3)
You do not need the board to catch most problems.
1. Compile-check (the strongest lint for C++)
Install PlatformIO once (pip install platformio), then from firmware/:
pio run -e esp32s3_dev # dev board profile
pio run -e xiao_esp32s3 # compact profile
A clean build type-checks every file and every library call. This is what CI does.
2. Static analysis
pio check -e esp32s3_dev --fail-on-defect high --skip-packages
Runs cppcheck across the sources. Add clang-tidy locally with --tool clangtidy if
installed.
3. Native unit tests (no hardware, fast)
The hardware-independent logic is factored into header-only files (src/wav.h,
src/httprange.h) so it compiles and runs on your PC:
pio test -e native
This builds and runs test/test_pure/ against desktop GCC and checks the WAV header
byte layout and the HTTP Range parser. No board, no toolchain download for Xtensa.
4. Emulate the running firmware with Wokwi
Wokwi simulates the ESP32-S3 including virtual WiFi, so you can boot the firmware, watch the serial log, and reach the REST API without hardware.
Build the emulator profile (its WiFi defaults to the open Wokwi-GUEST network so the API
comes up):
pio run -e esp32s3_wokwi
Then either:
- VS Code: install the "Wokwi Simulator" extension, open
firmware/, press play. It readswokwi.toml+diagram.json. - CLI / CI: with a free
WOKWI_CLI_TOKEN,wokwi-cli --timeout 15000 --expect-text "API at http" firmware.
Wokwi limitations to know: there is no I2S MEMS microphone part, so audio capture is not
emulated (the mic reads nothing); microSD support is basic. The harness is for boot, WiFi,
and the REST API surface, which is exactly what the emulator profile targets. Because the
firmware brings the API up even when audio/SD init fails, GET /api/v1/device is reachable
in the sim and reports the fault.
Server (FastAPI)
From server/:
python -m venv .venv && . .venv/Scripts/activate # or .venv/bin/activate
pip install -r requirements.txt
ruff check app # lint
uvicorn app.main:app --reload # run; browse http://localhost:8000/docs
pytest # tests (added as endpoints gain logic in M5+)
GET /health and the stubbed recording routes are live now; the AI pipeline arrives in
M5/M6.
API spec (OpenAPI)
api/openapi.yaml is the contract both the device and server implement. Validate it:
pip install openapi-spec-validator pyyaml
python -c "from openapi_spec_validator import validate; import yaml; \
validate(yaml.safe_load(open('api/openapi.yaml'))); print('OpenAPI valid')"
Browse it interactively by running the server (/docs) or pasting the file into
https://editor.swagger.io.
App (Flutter, from M7)
Once the app project exists:
cd app
flutter pub get
flutter analyze # lint + type-check
flutter test # widget/unit tests
flutter run # on an Android/iOS emulator or device
Case (OpenSCAD)
openscad -o case_top.stl -D 'part="top"' case/openscribe_case.scad
openscad --check-parameters and a headless render catch syntax/geometry errors; the GUI
preview (F5) is the visual check.
What CI runs
.forgejo/workflows/ci.yml on the self-hosted runner: firmware build (both profiles) +
native tests + cppcheck; OpenAPI validation; a Wokwi build (and a full emulator run if
WOKWI_CLI_TOKEN is set); server lint + import check.