Add internet radio streaming with runtime API, modernize web UI, remove auto-tune/beep at boot

Streaming (main feature this session):
- New WebRadioConfig/WebRadioJson core types, ISecureStore-backed persistence
- New webradio::WebRadioService (thread-safe live config) + GET/POST /api/streaming
- web_radio_stream task now runtime-toggleable (no reboot), no hardcoded URL
- Content-Type diagnostic: warns clearly when a URL is a webpage, not an audio stream

Boot cleanup:
- Removed boot-time auto FM/DAB tune, auto-beep, and the (now-concluded) Si4684
  crystal IBIAS/CTUN empirical sweep from main.cpp — tuning/beep are on-demand
  via the existing REST API only

Web UI:
- Modernized styling (cards, gradients, toggle switches, light/dark theme)
- New Stream tab wired to /api/streaming

Fixes found via real idf.py build (not just clangd):
- Restored wrongly-removed si4684/Si4684Tuner.hpp include in main.cpp
- Fixed MP3Decode() argument types in web_radio_stream.cpp (unsigned char**/int*)

Quality-gate fixes:
- Host-test stub headers (esp_log.h, freertos/*) so TunerService.cpp's
  scanForStation logging/pacing compiles for station_service_test /
  integration_service_test instead of running stale binaries
- Added WifiScanner and WebRadioService manual sections; filled in missing
  Doxygen docs on BluetoothService, i2s_sdata_probe, test_firmware, Bt1035At
- Ignore clangd's .cache/ index directory

Also includes prior uncommitted work carried in the tree: Wi-Fi/Bluetooth
device scan REST API and UI (WifiScanner, BT scan), SigmaStudio TCP bridge,
and the current ADAU1701 SigmaStudio DSP program export.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 21:15:22 +02:00
co-authored by Claude Sonnet 5
parent 8a8523515d
commit 6f7b6dd12c
131 changed files with 20309 additions and 1702 deletions
+298
View File
@@ -0,0 +1,298 @@
# Audio bring-up — handoff per Claude Code
> **RISOLTO (2026-08-07):** audio confermato funzionante end-to-end su Bose
> (Si4684/ESP32 → ADAU1701 → BT1035 → A2DP). Causa finale del silenzio:
> la cella DSP "Beep - variable gain" (toolbox ADI Sound Generation) usata
> come tono di test richiede sia `ENABLE` **sia** `KICK` (il parametro
> trigger) per avviare davvero la generazione — scrivere solo `ENABLE`
> lasciava il generatore silenzioso nonostante tutto il resto della catena
> (clock I2S, `AT+I2SCFG=35` a 24-bit, `AT+A2DPAUDIO`, encoder SBC attivo)
> fosse già corretto. Vedi `Adau1701Driver::setBeepEnabled()`.
>
> Il tono di test 440 Hz ora è generato dall'oscillatore interno del DSP
> ADAU1701 (programma SigmaStudio), non più dall'ESP32. Il codice
> `esp32_i2s_tone` e `CONFIG_ESP32_I2S_TONE_TEST` descritti in questo
> documento sono stati rimossi dal firmware; il resto del documento resta
> come riferimento storico del bring-up.
**Data:** 2026-08-05
**Contesto:** bring-up hardware DigiRadio su scheda reale (ESP32-S3 + Si4684 + ADAU1701 + FSC-BT1035 → Bose Solo II).
**Problema utente:** *non si sente* nulla sul Bose nonostante connessione Bluetooth.
**Stato:** fix A2DP streaming applicati e verificati parzialmente; test tono ESP32 ancora da confermare a orecchio; FM debole senza antenna.
---
## Setup laboratorio
| Parametro | Valore |
|-----------|--------|
| Serial | `/dev/cu.usbmodem1101` |
| WiFi STA | `MikiLab` |
| IP / mDNS | ~`192.168.1.56`, `digiradio-CC4DB4.local` |
| Speaker BT | Bose Solo II, MAC `BC87FAE69D6E` |
| Area FM test | Massa (MS) — RTL **100.9 MHz**, RAI1 **95.5**, Club FM **97.3** |
| Working dir build | `Software/` |
---
## Catena audio (SigmaStudio / hardware)
```
Si4684 I2S (slave) → ADAU1701 MP0 (fader Si4674) ─┐
ESP32 I2S (slave) → ADAU1701 MP1 (fader ESP32) ─┤→ St Mixer1 → EQ → master → MP6 out
└→ BT1035 I2S slave (AUXCFG=3) → A2DP → Bose
```
- **ADAU1701** = I2S **master** @ 48 kHz
- **ESP32 / BT1035 / Si4684** = I2S **slave**
- GPIO I2S ESP32: `BCLK=6`, `LRCLK/WS=7`, `DOUT=16` → ADAU MP1
---
## Modifiche effettuate (non committate)
Tutte le modifiche sono su branch `main`, **working tree dirty** (~48 file toccati in totale, molti pre-esistenti + sessione audio).
### 1. Bluetooth — A2DP streaming (causa principale del silenzio)
**Problema:** il BT1035 restava su `+A2DPSTAT=3` (Connected) senza passare a `4` (Streaming). Senza streaming il modulo non invia audio I2S→A2DP al Bose.
**Fix Feasycom §5.3.6:** dopo la connessione serve `AT+A2DPAUDIO=1`.
| File | Modifica |
|------|----------|
| `components/core/include/core/Bt1035At.hpp` | Doc + `buildBt1035A2dpAudioLine()` |
| `components/core/src/Bt1035At.cpp` | `AT+A2DPAUDIO=1/0` |
| `components/core/test/bt1035_at_test.cpp` | Test linee A2DPAUDIO |
| `components/drivers/bt1035/include/bt1035/Bt1035Driver.hpp` | `startA2dpAudio()`, `waitForA2dpStreaming()` |
| `components/drivers/bt1035/src/Bt1035Driver.cpp` | Poll A2DPSTAT; invia A2DPAUDIO=1 se bloccato su Connected; **non uscire subito** se query fallisce (retry fino a timeout) |
| `components/services/bluetooth/src/BluetoothService.cpp` | `ensureA2dpStreaming()` dopo connect/reconnect; boot reconnect attende STAT=4 |
**Verifica log (OK):**
```
I (Bt1035) A2DP audio start (AT+A2DPAUDIO=1)
I (Bt1035) stream wait: A2DPSTAT=4
I (BluetoothSvc) A2DP streaming OK
```
### 2. ADAU1701 — routing mixer boot
| File | Modifica |
|------|----------|
| `components/core/src/MixerState.cpp` | `radioFirst()` / `esp32First()` — selezione sorgente ST0/ST1 |
| `components/services/audio/src/AudioService.cpp` | `applyRadioFirstMix()` / `applyEsp32FirstMix()` |
| `main/hardware_bootstrap.cpp` | Dopo `loadAndApply()`: mix in base a Kconfig |
Con `CONFIG_ESP32_I2S_TONE_TEST=y`:
```
I (hw_boot) ADAU1701 ESP32 I2S input routed (esp32-first mix)
```
Senza tone test (FM):
```
I (hw_boot) ADAU1701 Si4684 input routed (radio-first mix)
```
### 3. Si4684 FM — I2S out + tune/scan
| File | Modifica |
|------|----------|
| `components/drivers/si4684/src/Si4684Driver.cpp` | `AUDIO_OUTPUT_CONFIG` bit I2SOUTEN @ boot; unmute; volume max; offset RSQ AN649; tune con fallback STC; band FM 87500107900 kHz |
| `components/drivers/si4684/src/Si4684Tuner.cpp` | Volume default 63; `readStatus()` non sovrascrive freq con READFREQ stale |
| `components/services/tuner/src/TunerService.cpp` | Scan FM step +100 kHz; filtro SNR; `fmChipReadFrequency` in status |
| `components/core/include/core/TunerStatus.hpp` | Campo `fmChipReadFrequency` |
**Nota:** con filo antenna corto, `rssi=0` su 100.9 MHz è normale — non indica per forza bug software.
### 4. Test tono ESP32 → ADAU → BT (isolamento path)
| File | Modifica |
|------|----------|
| `main/Kconfig.projbuild` | `ESP32_I2S_TONE_TEST` (default y in Kconfig, disabilitare in produzione) |
| `main/esp32_i2s_tone.cpp` | Sinusoide 440 Hz, 48 kHz, sample 24-bit MSB in slot 32-bit, amp 0.5 |
| `main/esp32_i2s_tone.hpp` | API `start()` |
| `main/main.cpp` | Task `esp32_tone`: delay 15 s (lascia finire boot reconnect), poi `waitForA2DPStreaming`, poi I2S |
| `main/CMakeLists.txt` | Compila `esp32_i2s_tone.cpp` se Kconfig attivo |
| `sdkconfig.defaults` | `CONFIG_ESP32_I2S_TONE_TEST=y` |
| `sdkconfig` | `CONFIG_ESP32_I2S_TONE_TEST=y` (abilitato manualmente — prima era `# is not set` e il tone test **non veniva compilato**) |
### 5. FM probe boot (alternativa al tone test)
| File | Modifica |
|------|----------|
| `main/main.cpp` | `fm_probe` task stack **16384** (era 4096 → stack overflow); tune 100.9 MHz, volume 63 |
Attivo solo se `CONFIG_ESP32_I2S_TONE_TEST` è **disabilitato**.
---
## Build e flash
```bash
cd Software
# Tone test ON (bring-up attuale)
idf.py build
idf.py -p /dev/cu.usbmodem1101 flash monitor
# FM probe al posto del tone test
idf.py -DCONFIG_ESP32_I2S_TONE_TEST=n fullclean reconfigure build flash
```
**Attenzione:** `-DCONFIG_ESP32_I2S_TONE_TEST=y` da riga di comando **non basta** se `sdkconfig` esistente ha `# CONFIG_ESP32_I2S_TONE_TEST is not set`. Verificare:
```bash
rg CONFIG_ESP32_I2S_TONE_TEST build/config/sdkconfig.h
# deve mostrare: #define CONFIG_ESP32_I2S_TONE_TEST 1
```
Ultimo build riuscito: `digiradio.bin` ~`0x237fe0` bytes.
---
## Log osservati (sessione 2026-08-05 sera)
### A2DP — OK dopo fix
```
I (BluetoothSvc) reconnect saved speaker BC87FAE69D6E (BOSE SOLO 2)
I (Bt1035) A2DP audio start (AT+A2DPAUDIO=1)
I (Bt1035) stream wait: A2DPSTAT=4
I (BluetoothSvc) A2DP streaming OK
```
A volte compare brevemente `A2DPSTAT=5` prima del `4`.
### Tone test — problemi I2S (in corso)
**Primo flash (tone test non compilato):** partiva `fm_probe`, mix radio-first, nessun log `esp32_i2s`.
**Secondo flash (tone test compilato):**
1. Race: tone task partiva insieme al reconnect → `waitForA2dpStreaming` usciva subito (query fallita) → tono partiva **prima** dello streaming.
2. I2S init falliva:
```
E (i2s_std) i2s_std_calculate_clock(68): sample rate is too large
E (esp32_i2s) i2s_channel_init_std_mode failed
```
Causa: `I2S_CLK_SRC_EXTERNAL` senza `ext_clk_freq_hz` coerente con `bclk_div`.
**Fix applicati (post [Fix no audio path](8719ea5e-ee61-48fe-ba57-7298b5638cf9)):**
- Rimosso `I2S_CLK_SRC_EXTERNAL`; slave usa BCLK/LRCLK da ADAU con `clk_src` default.
- `waitForA2dpStreaming`: continua a pollare se query fallisce (no early exit).
- Tone task: **delay 15 s** prima di attendere A2DP (evita race con boot reconnect).
- **Re-apply `applyEsp32FirstMix()`** + `startA2dpAudio()` subito prima del tono (NVS/integration sovrascriveva il mix al boot).
- I2S **24-bit** Philips + `mclk_multiple=384`; ampiezza tono 0.85.
- `sdkconfig`: `CONFIG_ESP32_I2S_TONE_TEST=y` obbligatorio (altrimenti tone test non compilato).
**Log verificati su serial (2026-08-05 ~20:15, I2S OK ma Bose silenzioso):**
```
I (Bt1035) stream wait: A2DPSTAT=4
I (BluetoothSvc) A2DP streaming OK
I (digiradio) A2DP streaming — starting I2S tone
I (digiradio) ADAU esp32-first mix re-applied before tone
I (esp32_i2s) I2S slave TX started (BCLK=6 WS=7 DOUT=16)
I (esp32_i2s) tone task: 440 Hz sine, 48000 Hz, ADAU I2S slave TX
I (esp32_i2s) i2s streaming 2048 bytes/frame to ADAU MP1
```
### FM (senza tone test)
```
I (digiradio) FM audio probe OK: 100900 kHz rssi=0 dBuV snr=0 dB locked=0
```
Segnale assente/ debole — serve antenna.
---
## Dove siamo arrivati
| Componente | Stato |
|------------|-------|
| Boot ADAU + profilo NVS | OK |
| BT1035 I2S slave (`AUXCFG=3`, `I2SCFG=67`) | OK |
| Reconnect Bose saved speaker | OK |
| **A2DP Streaming (STAT=4)** | **OK** — fix `AT+A2DPAUDIO=1` ([Fix no audio path](8719ea5e-ee61-48fe-ba57-7298b5638cf9)) |
| Mix ADAU esp32-first / radio-first | OK; **re-apply prima del tono** necessario (NVS) |
| **Test tono 440 Hz — firmware** | **OK** (I2S init + write verificati in log) |
| **Test tono 440 Hz — Bose** | **Silenzio** — probabile hardware ADAU MP6 → BT1035 o sorgente BT Bose |
| FM audio su Bose | Non testabile finché tono non si sente o RSSI > 0 |
| Utente | *«no nulla»* — silenzio persistente nonostante log software OK |
---
## Prossimi passi consigliati (priorità)
1. **Se log mostrano `A2DPSTAT=4` + `i2s streaming … bytes/frame` ma Bose muto** → debug **hardware**:
- Oscilloscopio: BCLK/LRCLK GPIO 6/7; dati MP6 ADAU → BT1035; dati GPIO 16 → ADAU MP1.
- Bose: sorgente BT = DigiRadio, telefono scollegato, volume alto.
- Verificare saldature/cavi I2S tra ADAU e BT1035 (path dopo DSP).
2. **Se I2S init fallisce ancora**
- Provare `I2S_DATA_BIT_WIDTH_24BIT` + slot 32-bit (allineamento ADAU MP1).
- Provare `bclk_div` 4 o 12 (vedi test ESP-IDF `i2s_multi_dev` slave).
- Verificare con oscilloscopio BCLK/LRCLK su GPIO 6/7 quando ADAU è bootato (ADAU deve essere master attivo prima di `i2s_channel_enable`).
3. **Se tono OK ma FM no**
- Tornare a build senza tone test (`CONFIG_ESP32_I2S_TONE_TEST=n`).
- Antenna FM; verificare `I2SOUTEN` Si4684 e `applyRadioFirstMix`.
- Cercare `rssi>15`, `locked=1`.
4. **Se A2DPSTAT=4 ma silenzio totale**
- Hardware: MP6 ADAU → pin I2S BT1035; alimentazione Bose.
- Verificare master volume / mute ADAU (safeload profilo — non dovrebbe essere a −∞).
5. **Commit** — non richiesto dall'utente finora; quando pronto, separare almeno:
- BT A2DPAUDIO / streaming
- Si4684 I2SOUTEN + scan/tune
- Tone test Kconfig (dev-only)
---
## File chiave (quick reference)
```
main/main.cpp — fm_probe vs esp32_tone task
main/hardware_bootstrap.cpp — mix ADAU al boot
main/esp32_i2s_tone.cpp — generatore tono I2S slave
main/Kconfig.projbuild — ESP32_I2S_TONE_TEST
sdkconfig / sdkconfig.defaults — Kconfig effettivo
components/drivers/bt1035/ — A2DPAUDIO, waitForA2dpStreaming
components/services/bluetooth/ — ensureA2dpStreaming al reconnect
components/services/audio/ — applyRadioFirstMix / applyEsp32FirstMix
components/core/MixerState.cpp — preset mixer ST0/ST1
components/drivers/si4684/ — I2SOUTEN, tune, RSQ
components/services/tuner/ — FM scan step tune
```
---
## Note per Claude Code
- Leggere `AGENTS.md` e `.cursor/rules/` prima di modifiche.
- **Non committare** salvo richiesta esplicita utente.
- Il tone test è **solo bring-up** — disabilitare per produzione (`sdkconfig.defaults.production` o `CONFIG_ESP32_I2S_TONE_TEST=n`).
- Due task competono sulla UART BT1035 (reconnect + tone wait): il delay 15 s mitiga ma non elimina del tutto la contesa; alternativa migliore: avviare il tone **dal callback** post-`boot reconnect OK` in `BluetoothService`.
- Transcript conversazione Cursor: thread audio/FM, ID `d10654e5-d27a-4999-812e-f371dc48567a`.
---
## Comandi utili debug
```bash
# Monitor filtrato
idf.py -p /dev/cu.usbmodem1101 monitor | rg -i 'A2DP|esp32_i2s|hw_boot|tone|streaming|error'
# Verifica config tone test nel binario
rg CONFIG_ESP32_I2S_TONE_TEST build/config/sdkconfig.h
# Reset + cattura serial (python)
python3 -c "
import serial, time
s=serial.Serial('/dev/cu.usbmodem1101',115200,timeout=0.2)
s.dtr=False; s.rts=True; time.sleep(0.1); s.rts=False
t=time.time()
while time.time()-t<90:
l=s.readline()
if l: print(l.decode(errors='replace'), end='')
"
```