writeCommand() always prepends a fixed ARG1=0x00 byte before the payload; tuneFm()/tuneDab() built their argument arrays starting at what the author believed was ARG1, so every byte landed one slot right of its real AN649 position and an extra unused byte was appended. The chip never received the requested frequency. This is the root cause of the months-long total RF blackout (RSQ frozen at all-zero on every frequency/ANTCAP value) previously attributed to a QFN exposed-pad hardware defect — that hypothesis is now overturned, confirmed live: RSSI/SNR now read real, frequency-dependent values with zero STC timeouts after the fix. Also make BT1035 boot failure non-fatal in HardwareBootstrap::boot() so a companion-chip fault no longer halts the whole device (Si4684 tuning, web UI, Wi-Fi already isolate BT1035 readiness via CompanionChipStatus). 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.