Files
harbour-aira/docs/PIANO.md
T
Carlo fddd21e16a 0.4.0: interfaccia in italiano e inglese, scelta nelle impostazioni
L'inglese è la lingua sorgente delle stringhe; l'italiano sta nel catalogo
translations/harbour-aira-it.ts (99 stringhe), che lupdate aggiorna e lrelease
compila in build. Di serie la lingua del sistema, con ripiego sull'inglese.

- nuova classe Translator: risolve la lingua prima di createView e allinea
  QLocale::setDefault, così anche i numeri seguono la lingua (23,0 °C in
  italiano, 23.0 °C in inglese) — le temperature passano da formatValue, non
  più dal toFixed() di JavaScript che metteva sempre il punto
- Qt 5.6 non ha QQmlEngine::retranslate(): al cambio di lingua la radice QML
  viene ricaricata (setSource ricrea il componente anche a url invariata)
- le etichette di stato in airajson.cpp usano QCoreApplication::translate per
  esteso: lupdate non riconosce una funzione wrapper e le avrebbe perse
- selettore della lingua in Impostazioni (Sistema / English / Italiano)
- test: 41/41, carica il .qm vero come fa main.cpp e verifica entrambe le
  lingue, il ripiego e i formati numerici
- anteprime bilingui (docs/anteprima/ inglese, it/ italiano), README e PIANO
  aggiornati, spec RPM con i cataloghi in %files
2026-10-10 08:19:20 +02:00

164 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`, v**0.3.0** (servizio systemd utente attivo) |
| 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 <token>` 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.
- **App 0.4.0 — localizzazione ita/eng** (richiesta dell'utente): l'interfaccia si
può mettere in italiano o inglese **dalle impostazioni**; di serie segue la lingua
del sistema, con **ripiego sull'inglese**. L'inglese è la lingua sorgente delle
stringhe (restano nel codice), l'italiano sta in `translations/harbour-aira-it.ts`
(**99 stringhe**, aggiornate con `lupdate` e compilate da `lrelease` in build).
Nuova classe `Translator` (`src/translator.{h,cpp}`): risolve la lingua **prima**
di `createView`, così le `qsTr()` del QML nascono già tradotte, e allinea
`QLocale::setDefault` alla lingua scelta — così i numeri seguono la lingua
(`23,0 °C` in italiano, `23.0 °C` in inglese): le temperature si formattano in
`ApiClient::formatValue`, non più con il `toFixed()` di JavaScript, che avrebbe
sempre messo il punto. *Vincolo di piattaforma*: **Qt 5.6 non ha
`QQmlEngine::retranslate()`** (arriva con Qt 5.10), quindi installare il
traduttore non ritraduce ciò che è già a schermo: al cambio lingua l'app
**ricarica la radice QML**. *Trappola di `lupdate`*: riconosce solo le funzioni di
traduzione note (`tr()`, `QCoreApplication::translate()`), **non una funzione
wrapper scritta da noi** — le etichette di `airajson.cpp` sono quindi chiamate
`QCoreApplication::translate("AiraJson", …)` per esteso, altrimenti sparirebbero
dal catalogo. Nucleo app: **39/39** (le attese sono cambiate in inglese e il test
carica il `.qm` vero come fa `main.cpp`, verificando entrambe le lingue, il
ripiego e i formati numerici). Anteprime rigenerate in **due lingue**
(`docs/anteprima/` inglese, `docs/anteprima/it/` italiano), impostazioni comprese
la nuova riga della lingua.
## 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`).