readFmRds(), readDabDigRadStatus()'s acquired field, and readDabEventStatus() all read raw[4] expecting AN649's RESP4 field, but this driver's own established convention (getPartInfo(), and the already-correct ficQuality/cnrDb fields in readDabDigRadStatus() itself) is raw[5]=RESP4 (raw[0]=SPI lead-in, raw[1..4]=STATUS0-3). This is why DAB_GET_EVENT_STATUS's serviceListReady never set — it was reading STATUS3's ERRNR bit instead of RESP4's SVRLISTINT bit, so /api/tuner/services returned service_list_empty forever regardless of lock quality. Fixed all four sites; readFmRds()'s fifoUsed/blockA-D were consequently also off by one and fixed together with it. Confirmed live: first DAB ensemble locks in this project's history (3 found sweeping freq_index 0-35, fic_quality=100, best CNR 20 dB on index 23), and /api/tuner/services now returns real entries instead of service_list_empty. The service-list entry contents themselves are still garbled (a third, separate bug in fetchDabServiceList()'s body parsing, documented but not fixed this session — see docs/si4684-rf-investigation-report.md). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0178rASQ6ZETPMUamvpoR2KR
DigiRadio Firmware
Open-source Hi-Fi DAB+/FM receiver firmware for the ESP32-S3.
Status: fw 0.8.5 on main — BT1035 I2S slave boot init, dual OTA + DSP blob updates, EEPROM identity,
NVS + flash encryption (development mode), tabbed Web UI with System uploads,
15 host tests, 4 CI jobs. Agent tasks T1–T12 complete; device HIL pending
first PCB.
| Area | Shipped in 0.8.5 |
|---|---|
| Boot | Si4684 HOST_LOAD, ADAU1701 RAM program, BT1035 I2S slave init (AUXCFG=3, I2SCFG=67) |
| Tuner | FM/DAB tune, seek up/down, RSQ, RDS, DAB services + DLS |
| Audio | 6-band EQ, mixer, stereo/bass enhance, NVS profile |
| Updates | ESP32 dual-OTA, ADAU1701 dsp partition blob replay |
| Identity | 24AA025E48 EUI-48 → serial, SoftAP/BT name suffix |
| Presets | CRUD, reorder, integrated recall + last-preset at boot |
| Network | SoftAP/STA, tabbed gzipped SPA, typed JSON REST API |
| Security | initEncryptedStorage() — see docs/security-flash-nvs.md |
| Quality | Doxygen, manual sync, Si4684 blob policy check |
Architecture: functional core (components/core, host-tested, no ESP-IDF) +
imperative shell (drivers, services, net, secure_store). Rules:
AGENTS.md · backlog: docs/TODO.md
Prerequisites
Si4684 blobs (proprietary, local only — not in git):
python3 tools/fetch_si4684_firmware.py --dab-only
python3 tools/fetch_si4684_firmware.py --si46xx-dir /path/to/si46xx_firmware
python3 tools/check_si4684_blobs.py
See Firmware/Si4684-Firmware/README.md.
ESP-IDF v5.5.x, target esp32s3.
Quick start
Open this directory (Software/) as the Cursor project so AGENTS.md and
.cursor/rules/ load automatically.
idf.py set-target esp32s3
idf.py build
idf.py erase-flash flash # once when first enabling encryption (0.8.3+)
idf.py -p <port> monitor
Production flash-encryption release mode: overlay sdkconfig.defaults.production
(see security doc — irreversible on chip).
Host tests
Pure core, no hardware (needs C++23 compiler on macOS — Homebrew llvm or GCC 14):
cmake -S components/core/test -B build-host \
-DCMAKE_CXX_COMPILER="$(brew --prefix llvm)/bin/clang++"
cmake --build build-host
ctest --test-dir build-host --output-on-failure
Quality gates (CI on every push to main)
Run from Software/:
doxygen Doxyfile
python3 tools/check-manual-sync.py
python3 tools/check_si4684_blobs.py
tools/gzip-www.sh # after editing components/net/www/index.html
CI jobs: host-tests, doxygen, manual-sync, si4684-blobs
(.github/workflows/ci.yml).
Web UI
Gzipped single-page app at / — tabs: Now (RDS/DLS), Radio, Presets,
Audio (6-band EQ), BT, Wi‑Fi, System (firmware OTA + DSP upload).
Header shows EEPROM serial from /api/health. Source:
components/net/www/index.html · regenerate embed:
tools/gzip-www.sh.
HTTP API (fw 0.8.5)
| Method | Path | Purpose |
|---|---|---|
| GET | /api/health |
Status, firmware version, serial, companion-chip flags |
| POST | /api/wifi |
Provision STA credentials; reboot on success |
| GET | /api/tuner/status |
Tuner snapshot (DAB/FM, RDS/DLS metadata) |
| GET | /api/tuner/services |
DAB service list for current ensemble |
| POST | /api/tuner/tune |
Tune DAB ensemble or FM frequency |
| POST | /api/tuner/play |
Start DAB service playback |
| POST | /api/tuner/seek |
FM seek up or down ({"direction":"up|down"}) |
| GET/PUT | /api/audio/profile |
Read/apply ADAU1701 mixer + 6-band EQ |
| POST | /api/audio/reset |
Factory-flat audio profile |
| POST | /api/audio/stereo-enhance |
Stereo depth overlay (0–100) |
| POST | /api/audio/bass-enhance |
Bass enhance overlay (0–100) |
| POST | /api/dsp/program |
Upload ADAU1701 DRAD blob (raw bytes, reboot) |
| POST | /api/system/ota |
Upload ESP32 firmware image (raw bytes, reboot) |
| GET | /api/bluetooth/status |
BT1035 boot, pairing, A2DP, name, auto-reconnect |
| GET | /api/bluetooth/paired |
Paired-device list from module |
| POST | /api/bluetooth/pair |
Enter discoverable mode |
| POST | /api/bluetooth/pair/stop |
Leave discoverable mode |
| POST | /api/bluetooth/disconnect |
Release A2DP session |
| POST | /api/bluetooth/auto-reconnect |
Set auto-reconnect count (0–15) |
| GET | /api/stations |
List saved presets |
| POST | /api/stations |
Add preset |
| POST | /api/stations/remove |
Remove preset by index |
| POST | /api/stations/reorder |
Move preset (from/to indices) |
| POST | /api/stations/tune |
Recall preset (tuner + audio profile + NVS) |
Wire schemas: docs/manual/ch-api.tex · C++ API:
doxygen Doxyfile → docs/api/html/index.html.
Layout
| Path | Role |
|---|---|
Firmware/ |
Si4684 .bin (local) + ADAU1701 SigmaStudio export |
components/core/ |
Pure domain (host-tested) |
components/drivers/ |
Si4684, ADAU1701, BT1035 |
components/services/ |
Tuner, audio, Bluetooth, station, integration |
components/secure_store/ |
Encrypted NVS: credentials, profiles, presets |
components/net/ |
Wi-Fi, HTTP server, gzipped web UI |
sdkconfig.defaults |
C++23, NVS + flash encryption (dev mode) |
partitions.csv |
nvs, otadata, dual OTA (ota_0/ota_1), dsp blob, nvs_keys |
docs/manual/ |
LaTeX technical manual |
docs/security-flash-nvs.md |
Encryption + device HIL checklist |
docs/TODO.md |
Completed tasks + HIL backlog |
tools/ |
Blob fetch, CI policy checks, UI gzip |
Manual PDF: cd docs/manual && latexmk -lualatex manual.tex
Licence
Apache-2.0 — see LICENSE.