M2: On-device WiFi + REST API #2

Merged
laurence merged 1 commit from feature/fw-rest-api into main 2026-07-03 12:09:54 +01:00
Owner

M2: On-device WiFi + REST API

Feature

The device now joins WiFi and serves the open REST API on the LAN, so the app (and any
client) can list recordings, download audio (with resumable Range requests), delete
recordings, read device status, control recording, and read/update config. If no WiFi
credentials are stored, the device opens a provisioning SoftAP so the same API is reachable
to set them.

What was achieved

The WiFi-to-app sync path and the "completely open API" on the device side. Implements the
device paths of api/openapi.yaml. Persistent config (device id, WiFi creds, API token,
upload settings) now lives in NVS. Advertised over mDNS as openscribe.local.

How to verify

  • CI (Forgejo Actions) builds both board profiles: pio run -e esp32s3_dev and
    -e xiao_esp32s3 (now also fetching ArduinoJson).
  • On hardware / in an emulator (see docs/testing.md): set WiFi via
    PUT /api/v1/device/config, then:
    • GET http://openscribe.local/api/v1/device -> status JSON.
    • GET .../api/v1/recordings -> list; GET .../recordings/{id}/audio with a
      Range: bytes=0-1023 header -> 206 + Content-Range.
    • POST .../record/start then .../record/stop -> recording toggles.

Not yet compiled locally or hardware-verified - relies on CI to build.

Tools used

C++ (Arduino-ESP32 3.x), PlatformIO. New dependency: bblanchon/ArduinoJson@^7. WiFi,
WebServer, ESPmDNS, Preferences, SD are all core-bundled.

How it works

  • config (src/config.{h,cpp}) - NVS (Preferences) store: stable device id (from MAC),
    WiFi SSID/PSK, API bearer token, upload settings. Secrets stay off the SD card.
  • net_wifi (src/net_wifi.{h,cpp}) - station join with stored creds; on no-creds/failure
    opens a SoftAP OpenScribe-<macsuffix> for provisioning; starts mDNS openscribe.local;
    reconnect logic in loop().
  • api_http (src/api_http.{h,cpp}) - synchronous WebServer on :80 with routes for
    /device, /device/config (GET/PUT), /recordings, /recordings/{},
    /recordings/{}/audio (HTTP Range -> 206), delete, and /record/start|stop. Bearer-token
    auth on mutating calls (open when no token is set). JSON via ArduinoJson.
  • storage gained list/read/delete/count helpers and path builders used by the API.
  • main.cpp wires config + WiFi + API into setup/loop alongside the M1 recorder.

State updated

  • state/TODO.md (M2 in PR; new security + NTP follow-ups)
  • state/ARCHITECTURE.md (config/net_wifi/api_http marked landed)
  • state/NOTES.md (M2 firmware specifics + security follow-ups)

Follow ups

  • NTP now that WiFi exists: real UTC ids/started_at.
  • Harden auth (require a token; consider read auth) and make the SoftAP passphrase user-set
    during provisioning (M4/BLE).
  • M3 next: charge/VBUS detection + independent upload to S3/WebDAV when powered.
# M2: On-device WiFi + REST API ## Feature The device now joins WiFi and serves the open REST API on the LAN, so the app (and any client) can list recordings, download audio (with resumable Range requests), delete recordings, read device status, control recording, and read/update config. If no WiFi credentials are stored, the device opens a provisioning SoftAP so the same API is reachable to set them. ## What was achieved The WiFi-to-app sync path and the "completely open API" on the device side. Implements the device paths of `api/openapi.yaml`. Persistent config (device id, WiFi creds, API token, upload settings) now lives in NVS. Advertised over mDNS as `openscribe.local`. ## How to verify - CI (Forgejo Actions) builds both board profiles: `pio run -e esp32s3_dev` and `-e xiao_esp32s3` (now also fetching ArduinoJson). - On hardware / in an emulator (see `docs/testing.md`): set WiFi via `PUT /api/v1/device/config`, then: - `GET http://openscribe.local/api/v1/device` -> status JSON. - `GET .../api/v1/recordings` -> list; `GET .../recordings/{id}/audio` with a `Range: bytes=0-1023` header -> `206` + `Content-Range`. - `POST .../record/start` then `.../record/stop` -> recording toggles. Not yet compiled locally or hardware-verified - relies on CI to build. ## Tools used C++ (Arduino-ESP32 3.x), PlatformIO. New dependency: `bblanchon/ArduinoJson@^7`. WiFi, `WebServer`, `ESPmDNS`, `Preferences`, `SD` are all core-bundled. ## How it works - `config` (`src/config.{h,cpp}`) - NVS (`Preferences`) store: stable device id (from MAC), WiFi SSID/PSK, API bearer token, upload settings. Secrets stay off the SD card. - `net_wifi` (`src/net_wifi.{h,cpp}`) - station join with stored creds; on no-creds/failure opens a SoftAP `OpenScribe-<macsuffix>` for provisioning; starts mDNS `openscribe.local`; reconnect logic in `loop()`. - `api_http` (`src/api_http.{h,cpp}`) - synchronous `WebServer` on :80 with routes for `/device`, `/device/config` (GET/PUT), `/recordings`, `/recordings/{}`, `/recordings/{}/audio` (HTTP Range -> 206), delete, and `/record/start|stop`. Bearer-token auth on mutating calls (open when no token is set). JSON via ArduinoJson. - `storage` gained list/read/delete/count helpers and path builders used by the API. - `main.cpp` wires config + WiFi + API into setup/loop alongside the M1 recorder. ## State updated - [x] `state/TODO.md` (M2 in PR; new security + NTP follow-ups) - [x] `state/ARCHITECTURE.md` (config/net_wifi/api_http marked landed) - [x] `state/NOTES.md` (M2 firmware specifics + security follow-ups) ## Follow ups - NTP now that WiFi exists: real UTC ids/`started_at`. - Harden auth (require a token; consider read auth) and make the SoftAP passphrase user-set during provisioning (M4/BLE). - M3 next: charge/VBUS detection + independent upload to S3/WebDAV when powered.
laurence added 1 commit 2026-07-03 12:09:53 +01:00
feat(firmware): M2 WiFi + on-device REST API
Some checks failed
ci / server (pull_request) Failing after 27s
ci / firmware (pull_request) Failing after 36s
87a00265a3
Adds the WiFi-to-app sync path and the on-device open REST API implementing the
device paths of api/openapi.yaml, plus persistent config in NVS.

What changed:
- firmware/src/config.{h,cpp}: NVS (Preferences) store - stable device id from MAC,
  WiFi SSID/PSK, API bearer token, upload settings. Secrets stay off the SD card.
- firmware/src/net_wifi.{h,cpp}: station join with stored creds; SoftAP provisioning
  fallback when no creds / join fails; mDNS openscribe.local; reconnect in loop().
- firmware/src/api_http.{h,cpp}: synchronous WebServer on :80. Routes: GET /device,
  GET/PUT /device/config, GET /recordings, GET /recordings/{id}, GET
  /recordings/{id}/audio (HTTP Range -> 206 + Content-Range), DELETE /recordings/{id},
  POST /record/start|stop. Bearer-token auth on mutating calls (open when no token).
- firmware/src/storage.{h,cpp}: list/read/delete/count helpers + path builders.
- firmware/src/main.cpp: wire config + WiFi + API into setup/loop; bump fw to 0.2.0.
- firmware/platformio.ini: add bblanchon/ArduinoJson@^7 (only new dep).
- state/: ARCHITECTURE, NOTES, TODO updated; security + NTP follow-ups recorded.

Why:
- This is the WiFi transfer channel and the device half of the "completely open API":
  clients can list, download (resumable), delete and control without any cloud.

Notes:
- Not compiled locally (no PlatformIO on the dev host) or hardware-verified; CI builds
  both board profiles. Security follow-ups logged: API is open when no token is set,
  and the SoftAP uses a fixed default passphrase (make user-set at M4/BLE).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
laurence merged commit 72633f02c3 into main 2026-07-03 12:09:54 +01:00
laurence deleted branch feature/fw-rest-api 2026-07-03 12:09:54 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: laurence/openscribe#2
No description provided.