Retesting DAB service-list retrieval later the same night (after the service-list body-parsing fix and antenna calibration work) found it far less reliable than the earlier confirmation: locked with excellent FIC quality (97-100) but GET /api/tuner/services kept returning service_list_empty for 30-65+ seconds. readDabEventStatus()'s SVRLISTINT bit is gated by AN649 Property 0xB300 DAB_EVENT_INTERRUPT_SOURCE, bit0=SRVLIST_INTEN, default 0x0000 (disabled) at power-on — never written anywhere in this driver. configureAfterBoot()'s DAB branch already writes a similarly-named DIGITAL_SERVICE_INT_SOURCE (property 0x8100), but AN649's own text for 0x8100 is internally inconsistent between its prose and its bit table (VHFCAPS/VHFSW, a front-end switch field) — almost certainly a pdftotext -raw extraction artifact merging two adjacent property tables, the same failure mode already logged this session for AN649/adau1701.pdf extraction. Only 0xB300's own section reads internally consistent with its own prose, so it — not 0x8100 — is the one that actually gates SVRLISTINT. Verified live: the service list came back complete and correct (22 real station labels) on the next test after reflashing. Not proven conclusively faster than before given DAB acquisition timing is inherently variable and this was only tested once post-fix, but the property write is unambiguously correct per its own AN649 section regardless. Also logged, NOT fixed: intermittent multi-second HTTP unresponsiveness (connection timeouts even on /api/health) observed independent of this change, both before and after — heartbeat kept logging on schedule throughout so the system didn't crash, only the HTTP server (or something it was blocking on, most likely a Si4684 SPI/CTS wait) stalled and recovered on its own. Root cause not yet found; details and next-session candidates in 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.