Safeload mixer/EQ/master on the ADAU1701, persist AudioProfile in NVS, expose /api/audio routes and web UI controls, with host tests and manual sync. Co-authored-by: Cursor <cursoragent@cursor.com>
6.3 KiB
instructions.md — DigiRadio firmware, agent kickoff
Read this together with AGENTS.md and everything under
.cursor/rules/. Those define how to write code; this file defines
what we are building and what to do first.
What DigiRadio is
An open-source Hi-Fi DAB+/FM digital radio board. Firmware runs on an ESP32-S3 and coordinates three companion chips:
- Si4684 — DAB+/FM tuner (delivers the audio stream).
- ADAU1701 — SigmaDSP: equaliser + input mixer between the Si4684 and the ESP32 audio path. Program is written to DSP RAM at every boot (no self-boot EEPROM).
- FSC-BT1035 (QCC3056) — Bluetooth 5.2 out with aptX Adaptive, controlled by AT commands over UART.
Plus: an elegant, essential web UI for network configuration; encrypted storage for Wi-Fi and user credentials and the station list.
Repository: https://github.com/manvalan/DigiRadio
Confirmed technical decisions (do not re-litigate)
| Area | Decision |
|---|---|
| Framework | ESP-IDF v5.5.x (native, not Arduino) |
| Language | C++23, pinned -std=gnu++23 |
| Errors | std::expected<T, Error> (native); exceptions OFF |
| DSP boot | ESP32 writes ADAU1701 RAM at every boot (no EEPROM) |
| Architecture | Functional core (pure, host-tested) + imperative shell |
| Docs | Doxygen, build must exit 0 (enforced) |
| HW licence | CERN-OHL-S v2 · FW licence: Apache-2.0 |
Working agreement
- Confirm understanding before writing code. On kickoff, summarise the plan and list any blockers or unclear hardware invariants first.
- Blockers first, always. State risks before solutions.
- One vertical slice at a time.
mainalways builds and runs. - Every file gets the Apache header; every class/method its doc block;
doxygen Doxyfilestays green. Small commits, 50/72 messages. - Never invent a register/opcode/boot step — cite the datasheet or stop.
Roadmap (slices, in order)
- Walking skeleton — done (Slice 1).
- Secure store + Wi-Fi provisioning — done (Slice 2).
- Companion-chip boot — done (Slice 3): Si4684 DAB + ADAU1701 RAM load.
- Station/frequency list model + persistence + UI.
- Si4684 tuning: RSQ, station list, DAB properties.
- ADAU1701 runtime — done (Slice 5): safeload EQ + input mixer + HTTP.
- FSC-BT1035 driver: AT init (incl.
AT+AUXCFG=1), audio out. - Integration: TunerService + AudioService end to end.
Slice 1 — Walking skeleton (complete)
Goal: exercise the whole toolchain end to end with zero chip hardware, so every later slice drops into a working frame.
Build:
- Top-level ESP-IDF project targeting
esp32s3. sdkconfig.defaultssets C++23, exceptions off, and documents flash/NVS encryption options (not hard-enabled until production).- The
components/corecomponent compiles both under ESP-IDF and standalone on the host.
Behaviour:
app_mainstarts a FreeRTOS task that logs a heartbeat on a timer.- Bring up SoftAP with a known SSID (e.g.
DigiRadio-setup). - Start an HTTP server serving one minimal gzipped page from flash.
- Expose
GET /api/healthreturning a typed DTO serialised by the pure core, e.g.{"status":"ok","fw":"0.3.0"}.
Documentation (required):
- Doxygen doc blocks on every class/method;
doxygen Doxyfilegreen. - Manual: class sections in
docs/manual/ch-classes.tex; HTTP API indocs/manual/ch-api.tex. python3 tools/check-manual-sync.pygreen.
Slice 2 — Secure store + Wi-Fi STA (complete)
Goal: persist Wi-Fi credentials and join the configured network after provisioning; fall back to SoftAP when no credentials or join fails.
Build on Slice 1:
core::ISecureStoreinterface +secure_store::NvsSecureStore(NVS).core::Secret,WifiSsid,WifiCredentials,parseWifiProvisionJson.net::StaClient,NetBootstrap::start(store)state machine.POST /api/wifi+ provisioning form in the web UI; reboot after save.
Acceptance criteria:
- Provisioning via SoftAP saves credentials and reboots; next boot joins STA.
- Host tests for health JSON and Wi-Fi provision parse/serialise.
- Doxygen green; manual sync green;
ch-api.texdocuments endpoints. - No ESP-IDF headers in
components/core.
Out of scope: station list, user credentials, NVS encryption enablement (production), chip drivers.
Slice 3 — Companion-chip boot (complete)
Goal: load Si4684 DAB firmware and ADAU1701 SigmaStudio program from
Firmware/ on every boot, before network bring-up.
Build:
Firmware/Si4684-Firmware/—rom_patch_016.bin,dab_firmware.bin,fm_firmware.bin(FM viatools/fetch_si4684_firmware.py --si46xx-dir).Firmware/ADAU1701-Firmware/— SigmaStudio export (DigiRadio_IC_1.h, …).core::IFirmwareBlobReader+EmbeddedBlobReaderfor chunked HOST_LOAD.si4684::Si4684Driver,adau1701::Adau1701Driver,HardwareBootstrap.
Behaviour:
app_maincallsHardwareBootstrap::boot()first (Si4684, then ADAU1701).- On failure, firmware logs and halts before Wi-Fi.
Acceptance criteria:
- AN649 boot sequence with streaming blobs (no full image on heap).
- ADAU1701 reset + I2C +
default_download_IC_1()replay. - Host test for
EmbeddedBlobReader; manual sync green. - Device flash verified (requires ESP-IDF toolchain on build host).
Slice 5 — ADAU1701 runtime + audio API (complete)
Goal: safeload mixer/EQ/master on the ADAU1701 at runtime; persist user profiles in NVS; expose REST and web UI controls.
Build on Slice 3–4:
- Pure core:
GainDb,EqProfile,AudioProfile,IDsp, biquad design,parseAudioProfileJson/serializeAudioProfileJson. - Driver:
sigma_safeload_*, extendedAdau1701Driver,Adau1701Dsp. - Service:
audio::AudioService,secure_store::NvsAudioProfileStore. - HTTP:
GET/PUT /api/audio/profile,POST /api/audio/reset; Audio section in the web UI. Firmware 0.5.0.
Acceptance criteria:
- Safeload volume/mixer/EQ without direct param RAM writes during audio.
- Profile load/apply after ADAU boot; NVS round-trip via JSON.
- Host tests for biquad fixpoint and audio profile JSON.
- Doxygen green; manual sync green;
ch-api.texdocuments audio routes. - Device flash verified on hardware.