scaffold: OpenScribe open-source self-hosted AI voice recorder
Some checks failed
ci / firmware (push) Failing after 2s
ci / server (push) Failing after 45s

Bootstrap of the project (M0). Sets up the monorepo, design docs, hardware BOM,
the open API contract, component skeletons, licensing and CI, following the
Default Workflow SOP.

What changed:
- CLAUDE.md + docs/: copied the Default Workflow so sessions load the SOP.
- state/: PROJECT, ARCHITECTURE, DECISIONS, TODO, NOTES filled in for OpenScribe.
  ARCHITECTURE captures the four-part design (firmware, server, app, case) and the
  three sync paths; DECISIONS records the hardware, AI-stack, storage, app and
  licensing choices; TODO lays out milestones M1-M9.
- hardware/BOM.md: two build options (compact XIAO ESP32-S3 Sense; dev ESP32-S3 +
  I2S mic + SD), wiring/pinout, indicative cost.
- api/openapi.yaml: the completely open API (device + server surfaces), including
  recording list/download/delete and exports (wav/ogg/txt/srt/vtt/md/json).
- firmware/: PlatformIO ESP32-S3 project, two board profiles, pin map, boot scaffold
  with module seams for M1-M4.
- server/: FastAPI skeleton mirroring the OpenAPI, config for self-hosted MinIO,
  faster-whisper and Ollama; stub routes browsable at /docs.
- app/, case/: Flutter app plan; parametric OpenSCAD enclosure.
- Licensing: GPL-3.0 (code), CERN-OHL-S-2.0 (hardware), CC-BY-SA-4.0 (case/docs),
  REUSE-style LICENSES/ with SPDX headers; LICENSING.md explains the split.
- CI: Forgejo Actions workflow builds firmware (both profiles) and lints/imports server.

Why:
- Everything self-hosted and openly licensed per the user's requirements: an open
  API, three sync paths (BLE control, WiFi transfer, independent WiFi upload on
  charge to generic cloud storage), and a full self-hosted transcription+summary stack.

Notes:
- No custom PCB in v1; off-the-shelf modules. Physical verification waits on parts.
- Component code is stubs at M0; features land milestone by milestone, each as its
  own branch/PR per the workflow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Laurence 2026-07-03 10:21:37 +01:00
commit 031074c9a9
36 changed files with 2922 additions and 0 deletions

39
firmware/README.md Normal file
View file

@ -0,0 +1,39 @@
# OpenScribe firmware
ESP32-S3 recorder firmware. Built with PlatformIO + Arduino-ESP32.
> Status: M0 scaffold. Boots and reports the board profile + PSRAM. Audio, storage, WiFi,
> REST API, uploader and BLE land in milestones M1-M4 (see `../state/TODO.md`).
## Boards
Two profiles, matching `../hardware/BOM.md`:
- `esp32s3_dev` (default) - ESP32-S3 devkit with PSRAM, external I2S mic + SD module.
- `xiao_esp32s3` - Seeed XIAO ESP32-S3 (Sense): compact, onboard PDM mic + microSD.
## Build and flash
```bash
pio run # build default env (esp32s3_dev)
pio run -e xiao_esp32s3 # build the compact profile
pio run -t upload # flash over USB
pio device monitor # serial console at 115200
```
Install PlatformIO (`pip install platformio` or the VS Code extension). On Windows install
the USB-serial driver (CP210x / CH34x) so the board enumerates a COM port.
## Layout
```
platformio.ini Build profiles (esp32s3_dev, xiao_esp32s3)
include/pins.h Pin map (override per board; defaults = BOM Build B)
src/main.cpp Entry point + module seams
```
## Design
The device records audio to microSD and syncs three ways: BLE (control/provisioning),
WiFi REST API (transfer to the app), and independent WiFi upload to object storage when on
charge. See `../state/ARCHITECTURE.md` and the API in `../api/openapi.yaml`.

33
firmware/include/pins.h Normal file
View file

@ -0,0 +1,33 @@
// SPDX-License-Identifier: GPL-3.0-only
// OpenScribe pin map. Defaults match hardware/BOM.md (Build B, ESP32-S3 devkit).
// Override per board via the OPENSCRIBE_BOARD_* flags set in platformio.ini.
#pragma once
#if defined(OPENSCRIBE_BOARD_XIAO)
// Seeed XIAO ESP32-S3 Sense: onboard PDM mic + microSD use the Sense board's fixed pins.
// The audio driver selects the PDM path for this board; these are placeholders for the
// user-facing controls only.
#define PIN_BUTTON 2 // external tactile button to GND
#define PIN_LED 21 // XIAO onboard user LED
#define PIN_VBUS_SENSE -1 // use USB-serial/VBUS API on XIAO
#define PIN_CHARGE_STAT -1
#else // OPENSCRIBE_BOARD_DEV (default)
// I2S MEMS microphone (INMP441 / ICS-43434)
#define PIN_I2S_BCLK 4
#define PIN_I2S_WS 5
#define PIN_I2S_DIN 6
// microSD (SPI mode)
#define PIN_SD_SCK 12
#define PIN_SD_MOSI 11
#define PIN_SD_MISO 13
#define PIN_SD_CS 10
// Controls / status
#define PIN_BUTTON 7 // active-low, internal pull-up
#define PIN_LED 48 // WS2812 data (or plain LED via 330R)
#define PIN_CHARGE_STAT 14 // TP4056 STAT (open-drain)
#define PIN_VBUS_SENSE 15 // USB 5V present -> on-charge / powered mode
#define PIN_MOTOR 16 // optional haptic, via transistor
#endif

35
firmware/platformio.ini Normal file
View file

@ -0,0 +1,35 @@
; SPDX-License-Identifier: GPL-3.0-only
; OpenScribe firmware - PlatformIO project.
; Two build profiles: a dev board (Build B) and the compact XIAO ESP32-S3 (Build A).
; See ../hardware/BOM.md for the matching hardware and pinout.
[platformio]
default_envs = esp32s3_dev
src_dir = src
include_dir = include
[env]
platform = espressif32
framework = arduino
monitor_speed = 115200
build_flags =
-DCORE_DEBUG_LEVEL=3
; PSRAM is required for audio ring buffers
-DBOARD_HAS_PSRAM
lib_deps =
; Pinned in later milestones as modules land (I2S mic, SD, BLE, HTTP server).
; --- Build B: ESP32-S3 dev board with PSRAM (N16R8), external I2S mic + SD ---
[env:esp32s3_dev]
board = esp32-s3-devkitc-1
board_build.arduino.memory_type = qio_opi
build_flags =
${env.build_flags}
-DOPENSCRIBE_BOARD_DEV=1
; --- Build A: Seeed XIAO ESP32-S3 (Sense) - compact wearable, onboard PDM mic + SD ---
[env:xiao_esp32s3]
board = seeed_xiao_esp32s3
build_flags =
${env.build_flags}
-DOPENSCRIBE_BOARD_XIAO=1

51
firmware/src/main.cpp Normal file
View file

@ -0,0 +1,51 @@
// SPDX-License-Identifier: GPL-3.0-only
//
// OpenScribe firmware entry point.
//
// This is the M0 scaffold: it boots, reports the board profile and PSRAM, and defines the
// module seams the later milestones fill in (see state/TODO.md and state/ARCHITECTURE.md).
// No audio/WiFi/BLE yet - those land in M1-M4 so each is a reviewable feature branch.
//
// Module seams (added incrementally):
// audio - I2S/PDM mic capture -> PSRAM ring buffer -> WAV encoder (M1)
// storage - microSD FAT files + sidecar JSON metadata (M1)
// recorder - record session state machine, file naming (M1)
// ux - button + LED/haptic (M1)
// power - battery ADC, charge/VBUS detect -> mode switch (M3)
// config - NVS settings (WiFi, upload target, codec, token) (M2/M3)
// net_wifi - WiFi join/reconnect + mDNS (M2)
// api_http - on-device REST server (api/openapi.yaml device paths) (M2)
// uploader - S3/WebDAV upload when powered (M3)
// ble - GATT control + WiFi provisioning (M4)
// ota - firmware update over HTTP (M9)
#include <Arduino.h>
static const char *FIRMWARE_VERSION = "0.1.0-scaffold";
void setup() {
Serial.begin(115200);
delay(300);
Serial.println();
Serial.printf("OpenScribe firmware %s booting\n", FIRMWARE_VERSION);
#if defined(OPENSCRIBE_BOARD_XIAO)
Serial.println("Board profile: XIAO ESP32-S3 (Build A, PDM mic + onboard SD)");
#else
Serial.println("Board profile: ESP32-S3 dev (Build B, I2S mic + SD module)");
#endif
if (psramFound()) {
Serial.printf("PSRAM: %u bytes free\n", (unsigned)ESP.getFreePsram());
} else {
Serial.println("WARNING: no PSRAM detected - audio buffering needs a PSRAM board");
}
// M1 onwards wires the modules here:
// config::begin(); storage::begin(); audio::begin(); ux::begin(); ...
}
void loop() {
// Placeholder heartbeat until the recorder state machine lands (M1).
delay(1000);
}