Rename device identity from DigiRadio to igiRadio
SoftAP SSID, Bluetooth name, and mDNS hostname now use the "igiRadio" prefix (igiRadio-<serial>, igiradio-<serial>.local), matching the iOS app's name instead of the firmware project's repo name. Confirmed live: igiradio-CC4DB4.local resolves, the old digiradio-CC4DB4.local no longer does. Also adds the app-side briefs/notes accumulated today (active_source, volume contract, VU-meter polling, RDS availability) as a single up-to-date file to hand to Cursor. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,9 +18,9 @@ namespace core {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr std::string_view kSerialUnknown = "unknown";
|
constexpr std::string_view kSerialUnknown = "unknown";
|
||||||
constexpr std::string_view kSoftApFallback = "DigiRadio-setup";
|
constexpr std::string_view kSoftApFallback = "igiRadio-setup";
|
||||||
constexpr std::string_view kBluetoothFallback = "DigiRadio";
|
constexpr std::string_view kBluetoothFallback = "igiRadio";
|
||||||
constexpr std::string_view kHostnameFallback = "digiradio";
|
constexpr std::string_view kHostnameFallback = "igiradio";
|
||||||
|
|
||||||
[[nodiscard]] std::string prefixed(std::string_view prefix,
|
[[nodiscard]] std::string prefixed(std::string_view prefix,
|
||||||
std::string_view suffix)
|
std::string_view suffix)
|
||||||
@@ -44,9 +44,9 @@ DeviceIdentity DeviceIdentity::fromEui48(Eui48 eui)
|
|||||||
const std::string suffix = eui.shortSuffix();
|
const std::string suffix = eui.shortSuffix();
|
||||||
return DeviceIdentity(eui,
|
return DeviceIdentity(eui,
|
||||||
eui.serialNumber(),
|
eui.serialNumber(),
|
||||||
prefixed("DigiRadio-", suffix),
|
prefixed("igiRadio-", suffix),
|
||||||
prefixed("DigiRadio-", suffix),
|
prefixed("igiRadio-", suffix),
|
||||||
prefixed("digiradio-", suffix));
|
prefixed("igiradio-", suffix));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DeviceIdentity::isKnown() const noexcept
|
bool DeviceIdentity::isKnown() const noexcept
|
||||||
|
|||||||
@@ -52,13 +52,13 @@ namespace {
|
|||||||
if (!expectEqual(identity.serialNumber(), "0004A3123456")) {
|
if (!expectEqual(identity.serialNumber(), "0004A3123456")) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
if (!expectEqual(identity.softApSsid(), "DigiRadio-123456")) {
|
if (!expectEqual(identity.softApSsid(), "igiRadio-123456")) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
if (!expectEqual(identity.bluetoothName(), "DigiRadio-123456")) {
|
if (!expectEqual(identity.bluetoothName(), "igiRadio-123456")) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
if (!expectEqual(identity.hostname(), "digiradio-123456")) {
|
if (!expectEqual(identity.hostname(), "igiradio-123456")) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
@@ -74,13 +74,13 @@ namespace {
|
|||||||
if (!expectEqual(identity.serialNumber(), "unknown")) {
|
if (!expectEqual(identity.serialNumber(), "unknown")) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
if (!expectEqual(identity.softApSsid(), "DigiRadio-setup")) {
|
if (!expectEqual(identity.softApSsid(), "igiRadio-setup")) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
if (!expectEqual(identity.bluetoothName(), "DigiRadio")) {
|
if (!expectEqual(identity.bluetoothName(), "igiRadio")) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
if (!expectEqual(identity.hostname(), "digiradio")) {
|
if (!expectEqual(identity.hostname(), "igiradio")) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public:
|
|||||||
* @brief setupDefault — factory for the Slice 1 setup SoftAP.
|
* @brief setupDefault — factory for the Slice 1 setup SoftAP.
|
||||||
*
|
*
|
||||||
* @dname setupDefault
|
* @dname setupDefault
|
||||||
* @return SoftApConfig with SSID DigiRadio-setup.
|
* @return SoftApConfig with SSID igiRadio-setup.
|
||||||
* @pubstate none
|
* @pubstate none
|
||||||
*
|
*
|
||||||
* @author Michele Bigi
|
* @author Michele Bigi
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
namespace net {
|
namespace net {
|
||||||
|
|
||||||
namespace {
|
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 kSetupChannel = 1;
|
||||||
constexpr std::uint8_t kSetupMaxConnections = 4;
|
constexpr std::uint8_t kSetupMaxConnections = 4;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@@ -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
|
## 5. Stile UI — Apple, minimalista ma con una sezione grafica curata
|
||||||
|
|
||||||
- Componenti nativi SwiftUI: `Picker` segmented per la sorgente, `Slider` con
|
- Componenti nativi SwiftUI: `Picker` segmented per la sorgente, `Slider` con
|
||||||
|
|||||||
@@ -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.
|
||||||
Reference in New Issue
Block a user