feat(firmware): M1 recording core - mic capture to WAV on SD
Some checks failed
ci / server (pull_request) Failing after 34s
ci / firmware (pull_request) Failing after 36s

Implements the first working feature: the device records audio to the microSD
card, toggled by the button, with LED status and per-recording JSON metadata.

What changed:
- firmware/src/audio.{h,cpp}: mic capture via the core-bundled ESP_I2S. Dev board
  uses I2S standard mode (INMP441/ICS-43434); XIAO uses PDM mode (onboard mic).
  Presents 16-bit PCM mono to callers regardless of board.
- firmware/src/storage.{h,cpp}: microSD on a dedicated HSPI bus + a streaming
  WavWriter that writes a 44-byte PCM header and patches RIFF/data sizes on close;
  plus sidecar JSON metadata writer.
- firmware/src/recorder.{h,cpp}: idle/recording state machine; creates
  /recordings/<id>.wav, pumps mic chunks in on update(), finalises + writes
  <id>.json (Recording schema from api/openapi.yaml) on stop.
- firmware/src/ux.{h,cpp}: debounced button (short press toggles) + status LED
  patterns (idle/recording/error), active-low aware.
- firmware/src/main.cpp: wires ux + recorder; loop toggles on button and drains
  the mic while recording.
- firmware/include/audio_config.h: 16 kHz mono 16-bit, chunk size, rec dir.
- firmware/include/pins.h: added XIAO PDM mic + onboard SD pins, LED active-low flag.
- state/: TODO, ARCHITECTURE, NOTES updated for M1 and the deferred follow-ups.

Why:
- Recording is the foundation every later milestone (transfer, upload, transcription)
  builds on. Kept dependency-free (only core-bundled ESP_I2S + SD) for simple CI builds.

Notes:
- Not compiled locally (no PlatformIO on the dev host) or hardware-verified; Forgejo
  Actions CI builds both board profiles. Follow-ups tracked in state/TODO.md:
  INMP441 16-bit level calibration, and real NTP timestamps (ids are uptime-based
  until M2 brings WiFi/NTP).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Laurence 2026-07-03 11:35:34 +01:00
parent 031074c9a9
commit 054b2f6981
14 changed files with 508 additions and 43 deletions

View file

@ -33,11 +33,11 @@ Three sync paths, exactly as specified:
- Responsibility: capture audio, store it, manage power/controls, expose control + data
over BLE and WiFi, and upload autonomously when powered.
- Location: `firmware/` (PlatformIO, Arduino-ESP32, target ESP32-S3).
- Modules (planned):
- `audio` - I2S mic driver, DMA capture, PSRAM ring buffer, WAV encoder.
- `storage`- microSD (FAT) recording files + sidecar JSON metadata.
- `recorder` - session state machine (idle/recording), file naming, metadata.
- `ux` - button (start/stop, long-press pair), LED/haptic status.
- Modules (M1 landed: audio, storage, recorder, ux; rest planned per milestone):
- `audio` - I2S/PDM mic capture presenting 16-bit PCM mono (ESP_I2S). [M1]
- `storage`- microSD (FAT) streaming WAV writer + sidecar JSON metadata. [M1]
- `recorder` - session state machine (idle/recording), file naming, metadata. [M1]
- `ux` - button (short-press start/stop) + status LED (haptic later). [M1]
- `power` - battery read (ADC), charge/VBUS detect -> mode switch.
- `config` - NVS-stored settings (WiFi creds, upload target + keys, codec).
- `net_wifi` - WiFi manager (join, reconnect), mDNS.

View file

@ -42,6 +42,16 @@ Per component (see each component's README for detail):
- Do not put object-store credentials or WiFi passwords on the microSD in clear; they live
in ESP32 NVS.
## Firmware specifics (M1)
- I2S/PDM via the core-bundled `ESP_I2S` (`I2SClass`) - no extra lib_deps. Dev board uses
`I2S_MODE_STD` (INMP441/ICS-43434); XIAO uses `I2S_MODE_PDM_RX` (onboard mic).
- SD uses a dedicated HSPI bus (`SPIClass(HSPI)`) so it does not clash with other SPI use.
- WAV writer streams a 44-byte header then patches RIFF/data sizes on close by seeking.
- Recording ids are uptime-based until NTP lands (M2); metadata `started_at` is null until
then. See the follow-ups in TODO.md.
- Not yet compiled locally (no PlatformIO on the dev host) - CI builds both profiles.
## Dead ends
- (none yet)

View file

@ -5,13 +5,13 @@
## In progress
- [ ] M0 Scaffold - repo, licences, state docs, BOM, OpenAPI, component skeletons, CI
(branch: bootstrap on `main`; this is the setup commit)
- [ ] M1 Firmware recording core - code complete on `feature/fw-recorder-core`, in PR.
I2S/PDM mic capture -> WAV on microSD, button start/stop, LED status, sidecar JSON
metadata. Not yet compiled locally (no PlatformIO here) or hardware-verified; relies
on Forgejo Actions CI to build both board profiles.
## Pending (roughly in build order)
- [ ] M1 Firmware recording core: I2S mic capture -> PSRAM ring buffer -> WAV on microSD,
button start/stop, LED status, sidecar JSON metadata. (branch `feature/fw-recorder-core`)
- [ ] M2 On-device WiFi + REST API: WiFi manager, mDNS, list/get/download(range)/delete
recordings, device status. Implements `api/openapi.yaml` device paths.
(branch `feature/fw-rest-api`)
@ -34,12 +34,22 @@
## Done
- [x] M0 Scaffold - repo, licences, state docs, BOM, OpenAPI, skeletons, CI
(merged to `main`, 2026-07-03)
- [x] Create Forgejo repo `laurence/openscribe` (public, main) - 2026-07-03
- [x] Decide hardware, sync model, AI stack, app platform, storage, licensing (see
DECISIONS.md) - 2026-07-03
## Follow-ups (deferred, do when convenient)
- [ ] Audio calibration: INMP441 emits 24-bit samples in a 32-bit slot. M1 reads in
16-bit mode (usable for speech, may be quiet). If levels are low on the dev build,
capture 32-bit and right-shift into int16 in `audio.cpp`.
- [ ] Real timestamps: M1 ids/`started_at` are uptime-based (no RTC/NTP yet). M2 adds NTP
over WiFi; switch ids and `started_at` to real UTC then.
## Blocked / waiting
- [ ] Physical build + on-device verification - waiting on parts from `hardware/BOM.md`.
Until parts arrive, firmware is developed against the ESP32-S3 target and validated
by build + (where possible) native/host unit tests, not on real hardware.
Until parts arrive, firmware is validated by CI build (Forgejo Actions) of both
board profiles, not on real hardware.