Files
DigiRadio/Software/docs/app-cursor-brief-2026-08-25.md
T
micheleandClaude Sonnet 5 b46dcea698 Fix web radio streaming: HTTPS URLs were rejected outright
POST /api/streaming's JSON parser only accepted "http://" URLs,
rejecting any "https://" URL with invalid_json before ever attempting
a connection. Nearly every real internet radio stream today is HTTPS-
only, so this made the feature fail for essentially any station a
user would actually try.

Two changes were needed together: the parser now accepts both http://
and https://, and web_radio_stream.cpp's esp_http_client now attaches
ESP-IDF's built-in CA certificate bundle (crt_bundle_attach) so the
TLS handshake actually verifies -- CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
was already enabled in sdkconfig but never wired up here. Requires
adding mbedtls to main's PRIV_REQUIRES (esp_crt_bundle.h lives there).

Confirmed live: an https:// stream URL now gets accepted by the API
and produces a real HTTP response (404, from a guessed-wrong path) --
before this fix it never reached the network at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 00:27:02 +02:00

11 KiB

Brief per Cursor — allineamento app iOS al firmware DigiRadioFinale

Questo documento è vincolante: ogni punto marcato DEVE è un requisito, non un suggerimento. Se qualcosa non è chiaro o sembra in conflitto con codice esistente, chiedi prima di improvvisare una soluzione diversa — non inventare comportamenti non specificati qui.

Dispositivo di test reale: http://192.168.1.62 (mDNS digiradio-CC4DB4.local). Verifica ogni funzionalità contro il dispositivo vero prima di considerarla finita, non solo con dati mock.


0. Problemi riportati oggi dall'utente (da risolvere tutti)

  1. L'interfaccia non è "in stile Apple" — troppo grezza/disorganizzata.
  2. I nuovi controlli DSP (selettore sorgente, Bass Boost, Stereo Spread) non sono stati implementati.
  3. Non è possibile selezionare la sorgente di ingresso (radio/Bluetooth).
  4. Il controllo del volume è sbagliato/inconsistente.
  5. Le stazioni: uno scan completo produce una lista lunga sia FM che DAB, ma aprendo il tab FM o il tab DAB separatamente dice "nessuna stazione" — vanno unificate in un'unica lista.

Le sezioni seguenti danno il contratto dati esatto e i requisiti UI per risolvere ciascuno di questi punti. Non è accettabile implementare solo una parte e considerare il resto "per dopo" senza dirlo esplicitamente.


1. Selettore sorgente (sostituisce il vecchio mixer) — OBBLIGATORIO

Il firmware non ha più un mixer che combina sorgenti. DEVE esserci un controllo a scelta singola (es. Picker segmented, non due slider separati) con tre opzioni: Radio, Bluetooth, e opzionalmente Test tone (quest'ultimo solo in una sezione diagnostica, non nella UI principale).

Contratto API — PUT /api/audio/profile, corpo completo (tutti i campi sono obbligatori nella richiesta, il firmware non li fa opzionali):

{
  "active_source": "radio",
  "master": {"left_db": 0, "right_db": 0},
  "eq": [
    {"gain_db": 0, "center_hz": 20, "q": 1.414},
    {"gain_db": 0, "center_hz": 100, "q": 1},
    {"gain_db": 0, "center_hz": 400, "q": 1},
    {"gain_db": 0, "center_hz": 1000, "q": 1},
    {"gain_db": 0, "center_hz": 3000, "q": 1},
    {"gain_db": 0, "center_hz": 8000, "q": 1}
  ],
  "enhancements": {"stereo_level": 0, "bass_level": 0}
}

active_source accetta esattamente le stringhe "radio", "bluetooth", "beep" (minuscolo, invariato). Nessun altro valore.

GET /api/audio/profile restituisce lo stesso oggetto — usalo per inizializzare il Picker all'avvio schermata, non assumere sempre "radio".

Errore comune da evitare: NON costruire il body facendo merge parziale di un vecchio oggetto "mixer" — quel campo non esiste più, se lo mandi il parser lo ignora e basta (non causa errore, ma non fa nulla).


2. Volume — contratto esatto

Il volume master è nello stesso oggetto profilo, campo "master":

"master": {"left_db": -6, "right_db": -6}
  • Range valido: -96.0 .. +12.0 (dB). Valori fuori range vengono rifiutati dal firmware (risposta di errore, non clamping silenzioso) — clampa lato client PRIMA di inviare.
  • Non fermare lo slider a 0 dB — il firmware supporta fino a +12 dB di guadagno reale sul master. Se lo slider attuale si ferma a 0 e "sembra basso", è un limite arbitrario della UI, non del firmware: estendi il range fino a +12 dB.
  • Normalmente L e R vanno impostati uguali con un unico slider "Volume" (non serve un controllo stereo separato per il volume master, a meno che non venga esplicitamente richiesto un bilanciamento L/R).
  • Ogni PUT /api/audio/profile sostituisce l'intero profilo — se lo slider del volume invia solo {"master": {...}} senza gli altri campi (active_source, eq, enhancements), il firmware risponde con errore di campo mancante. DEVI sempre inviare l'oggetto completo, leggendo prima lo stato corrente con GET /api/audio/profile (o tenendolo in uno store locale sempre sincronizzato) e poi cambiando solo il campo che l'utente ha toccato.

Questo "invia sempre tutto l'oggetto" è quasi certamente la causa del "volume sbagliato": se il codice attuale manda solo il volume da solo, il firmware lo rifiuta o (se altri campi arrivano con default sbagliati) resetta sorgente/EQ/enhancement senza che l'utente l'abbia chiesto.


3. Bass Boost e Stereo Spread — nuovi controlli, OBBLIGATORI

Due slider 0-100, indipendenti da sorgente/volume/EQ:

POST /api/audio/bass-enhance     {"level": 0-100}
POST /api/audio/stereo-enhance   {"level": 0-100}
  • Questi sono endpoint a sé stanti, non fanno parte del body di /api/audio/profile per la scrittura (ma il loro stato corrente torna dentro GET /api/audio/profile"enhancements": {"bass_level":.., "stereo_level":..}).
  • Bass Boost è un algoritmo dinamico: su un tono fisso di test non si sente quasi nulla, è normale — non è un bug se durante un test con la radio spenta sembra "non fare niente".
  • Non esiste più il vecchio comportamento per cui alzare questi livelli "bloccava" delle bande dell'equalizzatore manuale — l'EQ è ora sempre indipendente. Il campo "locked" per banda nell'array eq[] (in GET /api/audio/profile) è ora sempre false tranne la banda 0 (che è sempre true, invariato da prima — è un passa-alto fisso, non toccarlo).

4. Lista stazioni unificata — OBBLIGATORIO

Il backend ha un solo elenco stazioni, non due:

GET /api/stations

restituisce un array dove ogni stazione ha un campo "band" che vale "fm" oppure "dab", più i campi specifici di banda (fm_frequency_khz per FM; dab_freq_index, dab_service_id, dab_component_id per DAB).

Il bug riportato oggi (lo scan produce una lista lunga, ma il tab FM o il tab DAB dicono "nessuna stazione") indica che l'app sta usando due sorgenti dati diverse — una per la lista generale/scan e una diversa (vuota o rotta) per i tab FM/DAB filtrati. DEVI unificare: un solo StationListStore (o equivalente) alimentato da GET /api/stations, con i tab FM e DAB che sono semplicemente filtri client-side (station.band == .fm / .dab) sulla STESSA lista, non due chiamate/store separati.

UI richiesta: un'unica lista in stile Apple (List con sezioni, o una vista tipo Impostazioni), non due implementazioni diverse per FM e DAB — stesso componente di riga, stesso stile, filtrato per banda.


4bis. RDS (nome stazione FM) — ora funziona davvero, da oggi

Fino a oggi il firmware non decodificava mai l'RDS (bug a tre livelli, risolto). Ora GET /api/tuner/status e lo scan FM completo possono restituire:

"fm": {
  "frequency_khz": 92100,
  "rssi_dbuv": 57,
  "snr_db": 40,
  "station_name": "M DUE O",
  "radiotext": "...testo libero..."
}

station_name e radiotext sono opzionali — compaiono solo dopo qualche secondo di ricezione stabile (l'RDS impiega tempo ad accumularsi), quindi possono mancare subito dopo una sintonizzazione. Mostrali quando presenti (es. nella card "Now Playing" e nella riga della lista stazioni FM), altrimenti mostra la sola frequenza come già fai.

Se durante l'ascolto normale (non solo durante lo scan) il nome stazione o il radiotext cambiano o compaiono per la prima volta, aggiorna la UI di conseguenza — l'utente ha chiesto esplicitamente un piccolo banner/notifica quando arriva un nuovo nome/messaggio RDS durante la riproduzione.


7. VU-meter — nuovo, disponibile da oggi

GET /api/audio/levels

Risposta:

{
  "radio_in_left_db": -1.5,
  "radio_in_right_db": -1.5,
  "bluetooth_in_left_db": -0.9,
  "bluetooth_in_right_db": -1.0,
  "output_left_db": -1.9,
  "output_right_db": -2.0
}

Valori reali in dBFS, letti dal vivo dal DSP ad ogni chiamata — il firmware non fa polling in background né cache. Se vuoi un meter che si aggiorna in tempo reale, il polling lo fai tu lato app (es. ogni 200-500ms mentre la schermata è visibile) — fermalo quando la schermata non è a video, per non generare traffico I2C continuo inutile sul dispositivo. Questa è la sezione "grafica" giusta per barre VU vere (radio in / BT in / uscita), non un'animazione finta.


8. Scan FM completo — NON è bloccato, è solo lento (86s misurati)

POST /api/tuner/scan/full

Fa una scansione dell'intera banda FM (fino a 60 canali candidati, con pausa+lettura RDS per ciascuno) e risponde una sola volta alla fine, misurato: ~86 secondi per uno scan completo. Non è un bug, è il tempo reale che serve per farlo bene (RDS incluso).

Se l'app usa un timeout HTTP standard (30-60s), questa richiesta scade prima che il firmware finisca — la request fallisce lato client, ma il firmware nel frattempo continua e completa comunque (il risultato però va perso perché il client non lo aspetta più). Sembra "bloccato", ma non lo è.

Azione richiesta: per questa chiamata specifica, imposta un timeout di almeno 120 secondi sulla request HTTP, e mostra un indicatore "scansione in corso..." per tutta la durata (non un caricamento breve). POST /api/tuner/scan (senza /full, per una singola stazione con filtro nome) è invece rapido, timeout normale va bene.


8bis. Streaming web radio — bug corretto, ora accetta HTTPS

POST /api/streaming {"enabled":true,"url":"..."} prima rifiutava categoricamente qualsiasi URL https:// (errore invalid_json), accettando solo http:// — dato che quasi tutte le radio via internet reali sono HTTPS-only, questo probabilmente era il motivo per cui "qualsiasi cosa si faccia" dava errore. Corretto oggi: ora accetta sia http:// che https://, e il firmware verifica il certificato TLS con la CA bundle integrata di ESP-IDF. Nessun cambio di contratto per l'app — stessa forma JSON di prima, semplicemente ora funziona anche con URL HTTPS.


9. Checklist di autoverifica prima di considerare il lavoro finito

  • Cambiare sorgente da Radio a Bluetooth nell'app cambia davvero l'audio sul dispositivo reale (non solo lo stato locale dell'app).
  • Cambiare il volume aggiorna il volume reale e non resetta accidentalmente sorgente/EQ/enhancement.
  • Bass Boost e Stereo Spread hanno uno slider visibile, funzionante, e il valore torna corretto dopo un refresh/riavvio app (persistente lato firmware).
  • Il tab FM mostra le stazioni FM dello scan; il tab DAB mostra quelle DAB; entrambe vengono dalla stessa fonte dati.
  • Nessuna schermata mostra contemporaneamente controlli tecnici (indirizzi DSP, tono di test) mescolati a quelli quotidiani.
  • Lo slider del volume arriva fino a +12 dB, non si ferma a 0 dB.
  • I VU-meter (se implementati) mostrano numeri che cambiano nel tempo con l'audio reale, e il polling si ferma quando la schermata non è visibile.