Add igiRadio iOS/iPadOS app for DigiRadio control.

Introduces a new SwiftUI app with HTTP REST device control, mock mode for UI development, BLE-based discovery, and documented architecture aligned with the firmware API.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-18 15:45:55 +02:00
co-authored by Cursor
parent 3244b9f084
commit 6087bb5297
35 changed files with 4067 additions and 0 deletions
@@ -0,0 +1,35 @@
# APP_ARCHITECTURE — igiRadio
## Stack
```
SwiftUI Views
↓ @Observable ViewModels
DigiRadioService (protocol)
RealDigiRadioService ──→ HTTPDigiRadioClient (URLSession)
MockDigiRadioService ──→ dati in-memory
BLEProvisioningService (solo setup WiFi)
↓ CoreBluetooth + protocomm (ESP Security1)
```
## Principi
1. Le View **non** chiamano URLSession o CoreBluetooth direttamente.
2. Un solo `DigiRadioState` osservabile aggiornato dal service.
3. Errori tipizzati (`DigiRadioError`).
4. Dependency injection via `AppEnvironment` in `igiRadioApp`.
## Connessione
| Fase | Meccanismo |
|------|------------|
| Prima configurazione | BLE provisioning **oppure** join SoftAP + POST /api/wifi |
| Uso normale | mDNS `digiradio-xxxxxx.local` o IP manuale |
| Base URL | `http://{host}/api/...` |
## Polling vs push
- Nessuna notifica BLE documentata per stato tuner.
- `TunerViewModel` può pollare `GET /api/tuner/status` a intervallo configurabile quando la schermata radio è attiva.
@@ -0,0 +1,45 @@
# BLE_PROTOCOL — igiRadio
## Riepilogo
**DigiRadio non espone un protocollo BLE GATT documentato per il controllo radio/audio.**
Il BLE dell'ESP32-S3 è usato **solo** per il provisioning WiFi (setup iniziale).
---
## BLE WiFi Provisioning (documentato)
| Parametro | Valore |
|-----------|--------|
| Stack | ESP-IDF `wifi_provisioning` + `scheme_ble` |
| Sicurezza | protocomm **Security1** |
| Proof of possession | **Serial number** dispositivo (EUI-48 da EEPROM) |
| Nome advertising | `DigiRadio-<suffix>` (es. `DigiRadio-CC4DB4`) |
| Riferimento firmware | `components/net/src/BleProvisioning.cpp` |
| Riferimento manuale | `ch-api.tex` §BLE provisioning |
### Flusso
1. Dispositivo in setup mode (no credenziali WiFi salvate)
2. Advertise BLE con nome = SoftAP SSID
3. App iOS invia credenziali WiFi via protocomm (Security1 + PoP)
4. Firmware valida, salva in NVS, reboot in STA
5. App passa a controllo via **HTTP** su LAN
### Implementazione igiRadio
- Usare **CoreBluetooth** solo per questa fase
- Compatibile con app generiche "ESP BLE Provisioning" (stesso protocollo Espressif)
- PoP richiesto dall'utente: serial da etichetta o da schermata setup SoftAP
### UNKNOWN
- UUID servizio/caratteristica custom DigiRadio per controllo: **non documentati**
- Notifiche BLE stato tuner: **non documentate**
---
## Controllo dispositivo (post-provisioning)
Vedi `APP_ARCHITECTURE.md` — trasporto **HTTP REST** porta 80.
@@ -0,0 +1,58 @@
# DEVELOPMENT — igiRadio
## Requisiti
- macOS con **Xcode 15+** (testato Xcode 26.6)
- iOS 17+ deployment target
- Dispositivo DigiRadio su stessa rete WiFi (STA) o in setup SoftAP
## Aprire il progetto
```bash
open /Users/michelebigi/Documents/Develop/DigiRadio/Software/APP/igiRadio/igiRadio.xcodeproj
```
Rigenerare il progetto (se necessario):
```bash
cd Software/APP/igiRadio
python3 Scripts/generate_xcodeproj.py
```
## Build e test da terminale
```bash
cd Software/APP/igiRadio
xcodebuild -scheme igiRadio -destination 'platform=iOS Simulator,name=iPhone 17' build test
```
## Scheme
- **igiRadio** — app principale
- **igiRadioTests** — unit test protocollo e state
## Mock vs reale
In **DEBUG**, `AppEnvironment` avvia in modalità mock (`MockDigiRadioService`).
Disattivare in **Connessione → Modalità demo (Mock)** e inserire l'host HTTP reale.
## Test su hardware
1. Provisioning WiFi (BLE discovery in-app, o app ESP BLE Provisioning; oppure SoftAP `http://192.168.4.1`)
2. In app: Impostazioni → Connessione → host `http://digiradio-<suffix>.local`
3. Verificare `GET /api/health`
## Documentazione firmware
`/Users/michelebigi/Documents/Develop/DigiRadio/Software/docs/manual/ch-api.tex`
## Architettura runtime
```
SwiftUI → ViewModel → DigiRadioService
├── RealDigiRadioService → HTTPDigiRadioClient (REST JSON)
└── MockDigiRadioService
BLEProvisioningService (CoreBluetooth, solo discovery setup)
```
Il controllo tuner/audio **non** usa GATT proprietario — vedi `BLE_PROTOCOL.md`.
@@ -0,0 +1,17 @@
# DEVICE_STATE — DigiRadioState
Modello centrale in `Models/DigiRadioState.swift`.
## Sezioni
| Sezione | Campi documentati |
|---------|-------------------|
| `connection` | host, isConnected, lastError |
| `health` | status, firmware, serialNumber, chips |
| `tuner` | band, locked, volume, fm?, dab? |
| `audio` | mixer, master, eq, enhancements |
| `bluetooth` | booted, pairing, a2dpState, deviceName, speaker |
| `stations` | [Station] |
| `streaming` | enabled, url |
Campi non documentati nell'API **non** sono presenti nel modello.
@@ -0,0 +1,151 @@
# PROJECT_ANALYSIS — DigiRadio (fonte: documentazione firmware)
**Data analisi:** 2026-08-18
**Fonte primaria:** `Software/docs/manual/` (non modificata)
**Firmware di riferimento:** 0.8.5+
---
## 1. Documenti analizzati
| File | Contenuto |
|------|-----------|
| `ch-intro.tex` | Introduzione prodotto |
| `ch-hardware.tex` | PCB, pinout, catena audio |
| `ch-firmware.tex` | Architettura firmware ESP32-S3 |
| `ch-si4684.tex` | Tuner FM/DAB, tune, seek, RDS, DLS |
| `ch-adau1701.tex` | DSP SigmaStudio, mixer, EQ, safeload |
| `ch-bt1035.tex` | Modulo BT classic A2DP (UART AT), I2S da ADAU |
| `ch-sigmastudio.tex` | Design SigmaStudio |
| `ch-api.tex` | **HTTP REST API** (protocollo app) |
| `ch-classes.tex` | Classi firmware |
| `ch-build.tex` | Build/flash |
| `ch-licensing.tex` | Licenze |
Documentazione aggiuntiva letta: `Software/CLAUDE.md`, `Software/docs/security-flash-nvs.md`, `BleProvisioning.cpp`.
---
## 2. Distinzione critica: BLE vs controllo
### Controllo dispositivo (tuner, audio, preset, BT config)
**Trasporto documentato:** HTTP JSON REST su **TCP porta 80** (WiFi).
- Setup mode: SoftAP `DigiRadio-<suffix>``http://192.168.4.1/`
- STA mode: stessa API su IP LAN o **mDNS** `digiradio-<suffix>.local`
- **NON esiste** un protocollo GATT proprietario documentato per tuner/audio/preset.
### BLE (ESP32-S3 onboard)
**Solo provisioning WiFi** in setup mode (`ch-api.tex` §BLE provisioning):
- Stack: ESP-IDF `wifi_provisioning` + `scheme_ble`
- Sicurezza: **Security1** (protocomm)
- **Proof of possession (PoP):** serial number dispositivo (stesso valore di `GET /api/health``serialNumber`)
- **Nome servizio BLE:** SSID SoftAP = `DigiRadio-<suffix>` (es. `DigiRadio-CC4DB4`)
- Su successo: credenziali salvate in NVS, reboot in STA
- **Non è un endpoint HTTP** — nessuna REST call
### Bluetooth Audio (streaming verso speaker esterno)
**Modulo separato FSC-BT1035** (classic BR/EDR A2DP), controllato dal firmware via **UART AT**, esposto all'app solo tramite **HTTP** `/api/bluetooth/*`.
L'iPhone **non** si collega in A2DP al DigiRadio per ascoltare: il DigiRadio invia audio al Bose/Speaker via BT1035.
---
## 3. Architettura hardware (sintesi)
```
Si4684 (SPI) ──I2S──► ADAU1701 (I2S master 48 kHz) ──I2S──► BT1035 ──A2DP──► Speaker
ESP32-S3 (WiFi/BLE, HTTP server, opz. I2S test)
```
Sorgenti audio nel DSP: **Si4684** (radio), **ESP32 I2S** (stream locale / phone push).
---
## 4. Endpoint HTTP documentati (controllo app)
| Metodo | Path | Funzione |
|--------|------|----------|
| GET | `/api/health` | Stato, fw, serial, chip boot |
| POST | `/api/wifi` | Provisioning STA (reboot) |
| POST | `/api/wifi/scan` | Scan reti vicine |
| GET | `/api/tuner/status` | Stato tuner FM/DAB |
| GET | `/api/tuner/services` | Lista servizi DAB |
| POST | `/api/tuner/tune` | Sintonia FM/DAB |
| POST | `/api/tuner/play` | Play servizio DAB |
| POST | `/api/tuner/seek` | Seek FM up/down |
| POST | `/api/tuner/scan` | Scan singola stazione |
| POST | `/api/tuner/scan/full` | Scan completo banda FM |
| GET/PUT | `/api/audio/profile` | Profilo mixer/EQ/master |
| POST | `/api/audio/reset` | Reset profilo factory |
| POST | `/api/audio/stereo-enhance` | Enhancement stereo 0100 |
| POST | `/api/audio/bass-enhance` | Enhancement bass 0100 |
| POST | `/api/audio/beep` | Beep diagnostico ADAU |
| GET | `/api/dsp/params` | Tabella parametri SigmaStudio |
| PUT | `/api/dsp/param` | Scrittura cella raw (live) |
| POST | `/api/dsp/program` | Upload blob DSP (reboot) |
| POST | `/api/system/ota` | OTA firmware ESP32 (reboot) |
| GET/POST | `/api/streaming` | Web radio MP3 URL |
| PUT | `/api/stream/phone` | Push PCM stereo 48 kHz da phone |
| GET | `/api/bluetooth/status` | Stato BT1035 |
| POST | `/api/bluetooth/pair` | Modalità discoverable |
| POST | `/api/bluetooth/pair/stop` | Esci pairing |
| POST | `/api/bluetooth/disconnect` | Disconnect A2DP |
| GET | `/api/bluetooth/paired` | Lista paired |
| POST | `/api/bluetooth/scan` | Scan speaker vicini |
| POST | `/api/bluetooth/connect` | Connect A2DP + opz. save |
| GET/POST/DELETE | `/api/bluetooth/speaker` | Speaker default |
| POST | `/api/bluetooth/reconnect` | Reconnect manuale |
| POST | `/api/bluetooth/auto-reconnect` | Retry count 015 |
| GET | `/api/stations` | Lista preset |
| POST | `/api/stations` | Aggiungi preset |
| POST | `/api/stations/remove` | Rimuovi preset |
| POST | `/api/stations/reorder` | Riordina preset |
| POST | `/api/stations/tune` | Richiama preset |
---
## 5. Funzionalità per schermata app
| Area | Supportato (documentato) | Note |
|------|--------------------------|------|
| Connessione WiFi | ✅ | BLE prov. + SoftAP + POST /api/wifi |
| FM tune/seek/scan | ✅ | RSSI, SNR, RDS PS/RT |
| DAB tune/play/services | ✅ | DLS, ensemble, service list |
| Preset | ✅ | CRUD + reorder + recall |
| Volume/mixer/EQ | ✅ | Via audio profile |
| DSP raw params | ✅ | Escape hatch tecnico |
| BT speaker pairing | ✅ | Via HTTP → BT1035 AT |
| OTA firmware | ✅ | POST /api/system/ota |
| OTA DSP | ✅ | POST /api/dsp/program |
| Phone audio stream | ✅ | PUT /api/stream/phone |
| Web radio stream | ✅ | POST /api/streaming |
| Artwork album | ❌ | **UNKNOWN** — non in API |
| Batteria dispositivo | ❌ | Alimentato da rete |
| AUX/USB come sorgente UI | ❌ | Non esposto in API |
| Notifiche push BLE stato | ❌ | Nessun GATT documentato |
---
## 6. Implicazioni per igiRadio
1. **DigiRadioService** → implementazione **HTTP REST** (URLSession), non GATT custom.
2. **BLE layer** → solo **WiFi provisioning** (ESP protocomm Security1 + PoP = serial).
3. **MockDigiRadioService** → stessa interfaccia, dati fittizi.
4. **Discovery** → mDNS `.local`, IP manuale, o join SoftAP in setup.
5. **Real-time** → polling selettivo su `/api/tuner/status` quando in radio; nessuna notifica BLE documentata.
---
## 7. UNKNOWN — specification required
| Voce | Motivo |
|------|--------|
| UUID GATT controllo tuner/audio | Non documentati — controllo è HTTP |
| Artwork/metadata streaming | Non in API tuner |
| Protocol version field | Solo `fw` in health |
| Hardware revision API | Solo `serialNumber` + chip flags |
@@ -0,0 +1,31 @@
# UI_ARCHITECTURE — igiRadio
## iPhone
`TabView` con tab: Home, Radio, Presets, Audio, Settings.
## iPad
`NavigationSplitView`:
- Sidebar: Home, FM, DAB, Presets, Bluetooth, Audio, Settings
- Detail: contenuto selezionato
## Schermate
| Schermata | ViewModel |
|-----------|-----------|
| Connection | `ConnectionViewModel` |
| Home | `HomeViewModel` |
| FM | `FMViewModel` |
| DAB | `DABViewModel` |
| Presets | `PresetsViewModel` |
| Audio | `AudioViewModel` |
| Bluetooth | `BluetoothViewModel` |
| Settings | `SettingsViewModel` |
| Device Info | `DeviceViewModel` |
| Diagnostics | `DiagnosticsViewModel` |
| Firmware | `FirmwareViewModel` |
## Design System
`Components/DesignSystem/` — colori semantici, tipografia, card, slider, status dot.