# harbour-aira — app Sailfish per la pompa di calore Aira Piano scritto il 9 ottobre 2026, prima di scrivere la UI (convenzione di lavoro). ## 1. Stato verificato sul campo | Cosa | Esito | |---|---| | Accesso cloud Aira | **funziona** — `pyairahome` 2.3.0, pool Cognito `eu-north-1_cnqyjWtbz` (indice 0), token salvati in `~/.hermes/profiles/carlo/aira_tokens.json` (600) | | Lettura impianto | **funziona** — esterna, interna, ACS attuale/obiettivo, modalità, curve zone, firmware, errori | | Bridge HTTP sul server | **scritto e provato** — `~/workspace/Carlo/Aira/aira_bridge.py`, v0.1.0 | | Bluetooth sul server | **assente** — nessun adattatore HCI (esclude la via locale BLE su questa macchina) | | SDK Sailfish (`sfdk`) | **assente qui** — build e deploy li fa Carlo dal suo PC | | Jolla Phone 2026 | non raggiungibile dalla rete del server: deploy solo dal PC | ## 2. Perché serve un bridge e non una chiamata diretta Il cloud Aira espone **gRPC su HTTP/2**. Qt 5.6 — il toolkit su cui gira Sailfish — **non ha HTTP/2**, quindi un'app nativa non può parlarci direttamente. Vie alternative valutate e scartate: - **PyOtherSide + pyairahome dentro l'app**: servirebbe impacchettare `grpcio` e `bleak` compilati per aarch64/SFOS, con l'RPM che diventa fragile a ogni aggiornamento. - **Cognito + gRPC riscritti in C++**: HTTP/2 e protobuf da zero. Molto lavoro, molto rischio. Scelta: **la logica resta sul server** (dove Python e i token già funzionano), l'app è un client sottile. Vantaggio collaterale: credenziali e token non stanno mai sul telefono. ``` Jolla (QML/C++ Qt 5.6) --HTTP/1.1 + JSON--> bridge sul server --gRPC--> cloud Aira (pyairahome + token) ``` ## 3. Protocollo del bridge (v1 — già implementato) Autenticazione: `Authorization: Bearer ` su tutto tranne `/health`. Token generato al primo avvio in `~/.hermes/profiles/carlo/aira_bridge.json` (600). | Metodo | Rotta | Risposta | |---|---|---| | GET | `/health` | `{ok, version}` — nessuna auth, per la diagnostica | | GET | `/summary` | valori leggibili: temperature, ACS, modalità, flag, errori | | GET | `/state` | stato completo grezzo (42 campi) | | GET | `/commands` | elenco comandi ammessi + parametri | | POST | `/command` | `{"id": "...", "value": ...}` | Comandi ammessi in v1: `ping`, `set_hot_water_target` (45–60 °C), `hot_water_boost_on/off`, `hot_water_heating_on/off`, `away_mode_on/off`, `night_mode_1h`, `force_heating_on/off`, `heating_on/off`, `legionella_cycle`. **Whitelist voluta**: la libreria espone ~90 comandi, inclusi `FactoryReset`, `InstallFirmware`, `RebootDevice`, `RotateCertificate`. Quelli restano fuori di proposito: un client mobile non deve poter riformattare l'impianto per un tap sbagliato. ## 4. Architettura dell'app - `harbour-aira.pro` / `rpm/` / `icons/` dai template consolidati della skill. - **C++ minimo**: `Settings` (QSettings su `AppConfigLocation`, obbligatorio per la sandbox SailJail) e `ApiClient` (QNetworkAccessManager + watchdog di timeout 15 s, JSON parsing). Esposti come context properties: `appSettings`, `api`. - **QML**: `MainPage` (dashboard), `SettingsPage` (indirizzo bridge + token), `CoverPage`. - Nessun dato personale nel binario: indirizzo e token si configurano nell'app (le repo sono pubbliche su Gitea). ## 5. Fasi - **F0 — ponte (fatto)**: bridge scritto, provato su `/health`, `/summary`, `/commands`, errori 400/401 verificati. Nessun comando inviato all'impianto. - **F1 — v0.1 in sola lettura (fatto)**: dashboard con esterna/interna/ACS, modalità, stato ACS, pull-to-refresh, copertina con le temperature. - **F2 — comandi (fatto in v0.1)**: obiettivo ACS (slider 45–60), boost on/off, con conferma `RemorseItem`. Gli altri comandi della whitelist restano raggiungibili dal bridge ma non hanno ancora un pulsante nell'app. - **F3 — servizio e rete**: unità systemd per il bridge (avvio automatico), accesso dal telefono. *Decisione dell'utente: per ora solo rete locale* — quindi l'indirizzo del bridge è l'IP del server in LAN, e la copertura fuori casa si affronta più avanti (VPN WireGuard già attiva, oppure sottodominio HTTPS). ## 5-bis. Consegna del 9 ottobre 2026 (sera) Stato: progetto completo in `~/workspace/Carlo/AppSailfish/harbour-aira` (commit iniziale, 27 file) e tarball `AppSailfish/pacchetti/harbour-aira-0.1.0.tar.gz`. Verificato qui, senza SDK: - nucleo puro (`src/airajson.cpp`): **26/26** verifiche superate con l'harness Qt 5.15; - compilazione del nucleo **senza warning** (`-Wall -Wextra`); - `qmllint` sui 6 file QML: nessun errore oltre ai moduli Silica assenti (atteso senza SDK). Non verificabile qui (serve il suo PC + il telefono): build RPM, istallazione, UI reale. ## 5-ter. Aggiornamento del 10 ottobre 2026 — v0.2.0 (riscaldamento) - **Bridge 0.2.0**: comandi `set_heating_setpoint` e `set_cooling_setpoint` (5–30 °C, zona opzionale 1 o 2) e campi `zone1_*` / `zone2_*` nel riassunto. Il messaggio `ZoneTemperatures` va inviato **completo**: la zona non toccata viaggia col suo valore attuale (letto dall'ultimo stato, tenuto in cache nella sessione), altrimenti a zero verrebbe spenta. Verifica: `test_bridge_commands.py`, **19/19**, nessun comando inviato all'impianto. - **App 0.2.0**: sezione "Riscaldamento" nella dashboard (temperatura in casa, obiettivo, stato zona, slider 5–30 °C a passi di 0,5 con conferma) e copertina con la temperatura di casa. Aggiunte le etichette per gli stati `PUMP_MODE_STATE_*`. Nucleo: **29/29**. - **Bridge 0.3.0 + App 0.3.0**: pagina **"Altri comandi"** nell'app (dalla tendina in alto o dal pulsante in fondo alla home) con assenza, night mode un'ora, riscaldamento forzato on/off, funzione riscaldamento on/off, ciclo antilegionella, riscaldamento ACS on/off e verifica impianto; i comandi che durano (forzato, legionella, disabilitazioni) chiedono conferma. In cima alla pagina lo **stato attuale**, così non si preme alla cieca. **Bug corretto nello stesso giro**: `force_heating` nello stato è un oggetto `{enabled, remaining_time}`, e il bridge lo leggeva con `bool()` — sempre vero. Ora si legge il campo `enabled`; il test lo copre (27/27). Nucleo app: **31/31**. - **App 0.2.1** (richiesta dell'utente): la temperatura della casa diventa il dato principale **in cima** alla home (valore grande, barra, obiettivo, slider e pulsante), l'acqua calda sanitaria scende in una propria sezione sotto, lo stato impianto va in fondo. Stessa gerarchia nella copertina: casa in grande, ACS in una riga secondaria. - **F3 fatto**: `harbour-aira-bridge.service` (systemd utente, `enable --now`, parte al boot grazie a `Linger=yes`, `Restart=on-failure` verificato con SIGKILL → riparte in ~10 s) e regola `52-aira-bridge-lan.conf` in `/etc/nftables.d/`. Il firewall YunoHost ha `policy drop` e apriva solo 22/25/53/80/443/587/993/5349/5350: la 8790 va aperta a mano, solo per LAN e VPN. *Lezione*: un test da `127.0.0.1` non attraversa quelle regole e non rivela il problema — va provato dalla LAN. ## 6. Rischi noti - **Dipendenza dal server**: se il bridge è spento, l'app non vede nulla. Mitigazione: unità systemd con riavvio automatico (F3). - **Scadenza credenziali**: i token Cognito si rinnovano da soli per settimane; quando il refresh token scade serve rilanciare `setup_creds.py` sul server. L'app deve mostrare un errore chiaro, non un retry infinito. - **Assenza di test sulla UI**: senza SDK né device raggiungibile da qui, la UI Silica si verifica solo sul telefono di Carlo (ciclo consolidato: io consegno il tarball, lui compila e incolla i log). - **Token del bridge**: è l'unica credenziale sul telefono; va trattato come una password e revocabile rigenerando `aira_bridge.json`. ## 7. Decisioni aperte 1. **Come raggiungere il bridge dal telefono**: VPN WireGuard (già attiva, zero configurazione nuova) oppure sottodominio HTTPS pubblico con reverse proxy. 2. **Ambito della v0.1**: sola lettura (consigliata) oppure lettura + comandi ACS subito. 3. **Nome visibile dell'app**: default proposto "Aira" (pacchetto `harbour-aira`).