diff --git a/Software/components/core/src/DeviceIdentity.cpp b/Software/components/core/src/DeviceIdentity.cpp index f629c82..94ff514 100644 --- a/Software/components/core/src/DeviceIdentity.cpp +++ b/Software/components/core/src/DeviceIdentity.cpp @@ -18,9 +18,9 @@ namespace core { namespace { constexpr std::string_view kSerialUnknown = "unknown"; -constexpr std::string_view kSoftApFallback = "DigiRadio-setup"; -constexpr std::string_view kBluetoothFallback = "DigiRadio"; -constexpr std::string_view kHostnameFallback = "digiradio"; +constexpr std::string_view kSoftApFallback = "igiRadio-setup"; +constexpr std::string_view kBluetoothFallback = "igiRadio"; +constexpr std::string_view kHostnameFallback = "igiradio"; [[nodiscard]] std::string prefixed(std::string_view prefix, std::string_view suffix) @@ -44,9 +44,9 @@ DeviceIdentity DeviceIdentity::fromEui48(Eui48 eui) const std::string suffix = eui.shortSuffix(); return DeviceIdentity(eui, eui.serialNumber(), - prefixed("DigiRadio-", suffix), - prefixed("DigiRadio-", suffix), - prefixed("digiradio-", suffix)); + prefixed("igiRadio-", suffix), + prefixed("igiRadio-", suffix), + prefixed("igiradio-", suffix)); } bool DeviceIdentity::isKnown() const noexcept diff --git a/Software/components/core/test/device_identity_test.cpp b/Software/components/core/test/device_identity_test.cpp index 1dd165c..61574dc 100644 --- a/Software/components/core/test/device_identity_test.cpp +++ b/Software/components/core/test/device_identity_test.cpp @@ -52,13 +52,13 @@ namespace { if (!expectEqual(identity.serialNumber(), "0004A3123456")) { return EXIT_FAILURE; } - if (!expectEqual(identity.softApSsid(), "DigiRadio-123456")) { + if (!expectEqual(identity.softApSsid(), "igiRadio-123456")) { return EXIT_FAILURE; } - if (!expectEqual(identity.bluetoothName(), "DigiRadio-123456")) { + if (!expectEqual(identity.bluetoothName(), "igiRadio-123456")) { return EXIT_FAILURE; } - if (!expectEqual(identity.hostname(), "digiradio-123456")) { + if (!expectEqual(identity.hostname(), "igiradio-123456")) { return EXIT_FAILURE; } return EXIT_SUCCESS; @@ -74,13 +74,13 @@ namespace { if (!expectEqual(identity.serialNumber(), "unknown")) { return EXIT_FAILURE; } - if (!expectEqual(identity.softApSsid(), "DigiRadio-setup")) { + if (!expectEqual(identity.softApSsid(), "igiRadio-setup")) { return EXIT_FAILURE; } - if (!expectEqual(identity.bluetoothName(), "DigiRadio")) { + if (!expectEqual(identity.bluetoothName(), "igiRadio")) { return EXIT_FAILURE; } - if (!expectEqual(identity.hostname(), "digiradio")) { + if (!expectEqual(identity.hostname(), "igiradio")) { return EXIT_FAILURE; } return EXIT_SUCCESS; diff --git a/Software/components/net/include/net/SoftApConfig.hpp b/Software/components/net/include/net/SoftApConfig.hpp index 7774ecf..528292b 100644 --- a/Software/components/net/include/net/SoftApConfig.hpp +++ b/Software/components/net/include/net/SoftApConfig.hpp @@ -41,7 +41,7 @@ public: * @brief setupDefault — factory for the Slice 1 setup SoftAP. * * @dname setupDefault - * @return SoftApConfig with SSID DigiRadio-setup. + * @return SoftApConfig with SSID igiRadio-setup. * @pubstate none * * @author Michele Bigi diff --git a/Software/components/net/src/SoftApConfig.cpp b/Software/components/net/src/SoftApConfig.cpp index 4cd18bc..a6ad303 100644 --- a/Software/components/net/src/SoftApConfig.cpp +++ b/Software/components/net/src/SoftApConfig.cpp @@ -21,7 +21,7 @@ namespace net { namespace { -constexpr std::string_view kSetupSsid = "DigiRadio-setup"; +constexpr std::string_view kSetupSsid = "igiRadio-setup"; constexpr std::uint8_t kSetupChannel = 1; constexpr std::uint8_t kSetupMaxConnections = 4; } // namespace diff --git a/Software/docs/app-cursor-brief-2026-08-25.md b/Software/docs/app-cursor-brief-2026-08-25.md index 3cf5dc8..ec6a1e5 100644 --- a/Software/docs/app-cursor-brief-2026-08-25.md +++ b/Software/docs/app-cursor-brief-2026-08-25.md @@ -147,6 +147,35 @@ 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: + +```json +"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. + +--- + ## 5. Stile UI — Apple, minimalista ma con una sezione grafica curata - Componenti nativi SwiftUI: `Picker` segmented per la sorgente, `Slider` con diff --git a/Software/docs/cursor-note-volume-vumeter-2026-08-25.md b/Software/docs/cursor-note-volume-vumeter-2026-08-25.md new file mode 100644 index 0000000..834300e --- /dev/null +++ b/Software/docs/cursor-note-volume-vumeter-2026-08-25.md @@ -0,0 +1,105 @@ +# Nota per Cursor — Volume master e VU-meter + +Due punti precisi da correggere/implementare nell'app. Segui esattamente i +contratti sotto, non improvvisare formati diversi. + +Dispositivo di test: `http://192.168.1.62`. + +--- + +## 1. Volume master + +**Endpoint**: fa parte del profilo audio completo, non ha una rotta a sé. + +``` +GET /api/audio/profile -> legge lo stato attuale (incluso "master") +PUT /api/audio/profile -> scrive il profilo COMPLETO +``` + +Campo: + +```json +"master": {"left_db": 0, "right_db": 0} +``` + +Regole obbligatorie: + +1. **Range reale: da -96.0 a +12.0 dB.** Lo slider volume in UI non deve + fermarsi a 0 dB — quello è solo "unity gain", non il massimo. Il massimo + vero è **+12 dB**. Se oggi lo slider arriva solo a 0, è un limite messo + nell'app, va tolto. +2. **`PUT /api/audio/profile` sostituisce l'intero oggetto**, non solo il + volume. Ogni volta che l'utente muove lo slider del volume, il body della + PUT deve contenere ANCHE `active_source`, `eq` (tutte e 6 le bande) ed + `enhancements`, con i valori correnti — non solo `{"master": {...}}`. + Il modo corretto: + - tieni sempre in memoria (o rileggi con GET) lo stato completo del + profilo; + - quando l'utente cambia il volume, aggiorna SOLO il campo `master` in + quello stato locale; + - invia l'intero oggetto aggiornato con PUT. +3. Normalmente `left_db` e `right_db` vanno impostati **uguali** con un unico + slider "Volume" (non serve un secondo controllo per il bilanciamento L/R, + a meno che non venga chiesto esplicitamente). +4. Valori fuori range (-96/+12) vengono rifiutati dal firmware con errore — + clampa lato client prima di inviare. + +Esempio completo di richiesta corretta (cambio solo il volume a -6 dB, +tutto il resto invariato): + +```json +PUT /api/audio/profile +{ + "active_source": "radio", + "master": {"left_db": -6, "right_db": -6}, + "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} +} +``` + +--- + +## 2. VU-meter + +**Endpoint nuovo, disponibile da oggi**: + +``` +GET /api/audio/levels +``` + +Risposta (tutti i valori in dBFS, tipicamente negativi, 0 = fondo scala): + +```json +{ + "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 +} +``` + +Regole obbligatorie: + +1. **Il firmware non fa polling né cache** — ogni chiamata GET rilegge live + dal DSP in quel preciso istante. Se vuoi un meter che si aggiorna nel + tempo, il polling periodico lo devi fare tu lato app. +2. **Frequenza consigliata: ogni 200-500 ms**, non più veloce — ogni + chiamata impegna il bus I2C del dispositivo per 6 letture sequenziali + (una per meter, il chip ha solo 2 registri hardware di cattura). +3. **Ferma il polling quando la schermata con i meter non è visibile** + (es. `onDisappear` / quando l'utente cambia tab) — non lasciarlo attivo + in background, non serve e spreca risorse sul dispositivo. +4. `radio_in_*` sono il livello Si4684 (post-compressore), `bluetooth_in_*` + il livello ESP32, `output_*` il livello dopo Bass Boost (prima del + limiter finale) — utile per capire dove mostrare quale barra. +5. Se la risposta HTTP non è 200 (es. 500), mostra i meter come "non + disponibili" invece di un valore congelato/stantio.