Sulla copertina compare l'icona del boost (CoverAction). Una sola azione: la documentazione Silica ammette piu' di una azione ma dice che lo stile delle app di sistema ne mostra una. CoverBackground eredita da Cover, quindi l'elenco dichiarato li' si attacca; l'icona e' iconSource (non icon.source del Button: sbagliarlo non da' errore, l'icona semplicemente non compare). La riga in fondo si ancora a Cover.coverActionArea, cosi' l'icona non la copre. Nuovo controllo statico tests/cover_action_check.py: elenco figlio diretto del Cover, una sola azione, icona che esiste, id del comando presente nella tabella del bridge. Il componente e' nominato nella guida Silica dell'SDK di Carlo.
388 lines
23 KiB
Markdown
388 lines
23 KiB
Markdown
# 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`).
|
||
|
||
## 8. Riordino come l'app ufficiale — v0.5.0 (richiesta dell'utente, 10/10/2026)
|
||
|
||
L'utente ha allegato le schermate dell'app ufficiale (Home, dettaglio impianto,
|
||
Accessori, Impostazioni, Aira Intelligence, Dati) e ha chiesto di **riordinare le
|
||
informazioni allo stesso modo**. Piano scritto prima di toccare la UI, come da
|
||
convenzione.
|
||
|
||
### Cosa si può replicare (verificato con sonda di sola lettura)
|
||
|
||
Sonda `aira_stats_probe.py` (nessun comando inviato all'impianto): il cloud Aira
|
||
espone anche un **servizio statistiche** (`HeatPumpStatisticsService`) che la
|
||
libreria usava solo in parte. I numeri **coincidono con quelli dell'app ufficiale**:
|
||
|
||
| Dato dell'app ufficiale | Fonte | Valore reale letto | Coincide |
|
||
|---|---|---|---|
|
||
| Calore prodotto (questo mese) 70.8 kWh | `GetHeatPumpInsights` → `delivered_heat_wh` | 70 844 Wh | sì |
|
||
| Elettricità usata 29.3 kWh | `GetHeatPumpInsights` → `energy_consumption_wh` | 29 300 Wh | sì |
|
||
| Efficienza acqua calda 4.4 | `GetCop` mensile → `dhw_cop_value` | 4.42 (settembre) | sì |
|
||
| Efficienza riscaldamento: *Nessun dato* | `GetCop` → `room_heating_cop_value` | vuoto (solo ACS) | sì |
|
||
| Risparmio Smart Tariff Control €2.7 | `GetOptimisationSavings` → `rolling_month_optimisation` | € 2,71 | sì |
|
||
| Risparmi (ultimi 30 giorni) | `GetSavings` (180 gg) | € 95,46 + 153,9 kg CO₂ | sì |
|
||
| Dispositivo ID `1f4b26` | `heat_pump_id` (UUID) | `1f4b26c4-…` | sì |
|
||
| Versione software 3.30.1 | `versions.connectivity_manager` | 3.30.1 | sì |
|
||
| Versione piattaforma 6.10.0 | `versions.linux_build_id` | 6.10.0 | sì |
|
||
| Termostato a parete Aira · Zone 1 | `thermostats[]` | articolo 201884, zona 1, 22,8 °C, 73% UR | sì |
|
||
| Modalità Vacanza | `away_mode_enabled` | inattivo | sì |
|
||
|
||
**Resta fuori**: Wi-Fi, messaggi luminosi/LED, nucleo familiare, tariffe energia,
|
||
Aira Intelligence (comfort/risparmio) e le notifiche push. Sono funzioni che
|
||
dipendono da impostazioni dell'app ufficiale o da dati che il cloud non espone in
|
||
lettura (le tariffe servono per i *suoi* calcoli, non li leggiamo).
|
||
|
||
### Struttura nuova dell'app
|
||
|
||
Come l'app ufficiale: una **Home** con il dato esterno in alto e la scheda
|
||
dell'impianto, poi **Dati** e **Impianto** (al posto di Accessori + Riepilogo
|
||
impostazioni). La navigazione resta con il **menu pulley** di Silica e non con la
|
||
barra a tre tab in basso: la tab bar non è un pattern Sailfish e su Qt 5.6/Silica 5.1
|
||
andrebbe disegnata a mano.
|
||
|
||
1. **Home**: `esterno 16,8 °C` in alto a sinistra e stato assenza a destra; messaggio
|
||
di stato grande (derivato: errori, assenza, acqua calda in corso, riscaldamento in
|
||
corso, tutto regolare); **scheda impianto** — "Aira Pompa di calore · Connesso
|
||
tramite Cloud" con due righe (Temperatura interna con l'obiettivo, Acqua calda) e
|
||
la freccia che apre la pagina Impianto; sotto, i comandi (obiettivo casa, obiettivo
|
||
ACS e boost) che l'app ufficiale non ha e che restano il valore aggiunto nostro.
|
||
2. **Dati**: "Il tuo riscaldamento (questo mese)" con calore prodotto, elettricità
|
||
usata, efficienza (COP riscaldamento / acqua calda); "I tuoi risparmi" con il
|
||
risparmio totale del periodo, la CO₂ evitata e il risparmio Smart Tariff Control.
|
||
3. **Impianto**: stato (connessione, modalità, assenza, notte, riscaldamento forzato,
|
||
resistenza interna, temperature, zone, ultima lettura), accessori (termostato: zona,
|
||
temperatura, umidità, segnale, batteria, articolo), versioni (ID dispositivo,
|
||
piattaforma, connettività, controllo climatico, unità esterna, EEPROM) ed errori.
|
||
Le luci di firma (`signature_lights`, `led_pattern`) restano per ora solo in
|
||
`/summary`: la pagina non le mostra.
|
||
|
||
### Lavoro
|
||
|
||
- **Bridge 0.4.0**: nuova rotta `/stats` (COP, risparmi, ottimizzazione, insight) e
|
||
campi del dispositivo in `/summary` (`device_*`, `version_*`, `thermostat_*`,
|
||
`connected_via`, `timezone`); cache di 10 minuti lato bridge, perché sono quattro
|
||
chiamate al cloud, più lente di `/state`.
|
||
- **App 0.5.0**: `MainPage` riordinata, nuove `DataPage.qml` e `PlantPage.qml`
|
||
(quest'ultima si è chiamata `InfoPage` nella prima stesura del piano), nuovo
|
||
componente `ValueRow.qml`, 86 stringhe nuove tradotte (170 in totale), anteprime
|
||
rigenerate (dati, impianto) in italiano e inglese, test del nucleo estesi al
|
||
parsing delle statistiche e alla formattazione di importi e mesi (61/61), versione,
|
||
README e `docs/PROTOCOL.md` aggiornati.
|
||
- Nessun comando nuovo all'impianto: tutto questo è **sola lettura**.
|
||
|
||
---
|
||
|
||
## 9. Studio UX/UI e riordino delle schermate — v0.6.0
|
||
|
||
Prima di scrivere codice è stato fatto lo studio in `docs/UX-STUDIO.md`: dieci
|
||
problemi rilevati nelle schermate della 0.5.0, la proposta, i wireframe
|
||
(`docs/ux/`, generati da `scripts/render_wireframes.py`) e il piano di verifica.
|
||
Le quattro scelte di design sono state prese dall'autore: la frase di stato
|
||
lunga va in Impianto, "Altri comandi" diventa "Comandi impianto", le azioni
|
||
rapide sono pulsanti visibili, le azioni sulla copertina restano per dopo la
|
||
prova di `CoverAction` sul kit.
|
||
|
||
**Home** (`MainPage.qml`, riscritta): una schermata da guardare, non un modulo.
|
||
Stato in una riga con il pallino colorato (verde regolare, ambra modo attivo,
|
||
numeri vecchi o avviso, rosso solo allarme che ferma la macchina), le due
|
||
letture che contano — casa e acqua calda,
|
||
con obiettivo e barra di carica — affiancate e **toccabili**, e tre azioni
|
||
rapide: boost, assenza, comandi. Escono dalla Home i due slider, i sette
|
||
pulsanti e i tre pulsanti di navigazione ridondanti col menù a tendina. Sotto,
|
||
in una riga toccabile che porta a Impianto: esterna, termostato, zone.
|
||
|
||
**Regolazioni** (`SetpointsPage.qml`, nuova): un blocco per grandezza, con il
|
||
valore vero dell'impianto sempre visibile, slider (la proposta), fila di valori
|
||
rapidi per le dita fredde e pulsante di conferma che si attiva solo se c'è
|
||
davvero qualcosa da cambiare. Il boost ha qui i suoi due pulsanti.
|
||
|
||
**Comandi impianto** (`CommandsPage.qml`): da 12 pulsanti in quattro sezioni
|
||
(otto in coppie acceso/spento) a **4 interruttori `TextSwitch` + 3 pulsanti**.
|
||
Gli interruttori non anticipano l'impianto: `automaticCheck: false`, `checked`
|
||
sull'ultimo dato vero, e se il comando non riesce restano dov'erano. La
|
||
diagnostica (verifica impianto) si sposta in Impianto, insieme alle letture che
|
||
non avevano posto: pompa, modi consentiti, modalità manuale, sbrinamento, luci
|
||
di firma.
|
||
|
||
**Feedback e dati vecchi**: i messaggi dei comandi passano in `ApiClient`
|
||
(`api.notice`) e si mostrano nel nuovo `NoticeBar`, **ancorato al bordo
|
||
inferiore della pagina** invece che dentro il contenuto che scorre: prima,
|
||
premendo un pulsante in fondo alla pagina, il messaggio nasceva fuori schermo.
|
||
In più `stale` e `lastUpdate`: la Home scrive "aggiornato alle HH:MM" e spegne i
|
||
numeri quando l'ultima lettura non è recente. Dopo un comando riuscito lo stato
|
||
si rilegge da solo (2 s): l'aggiornamento automatico, prima, non esisteva.
|
||
|
||
**Stato breve ed esteso in C++** (`AiraJson::stateShort/stateLong/statusLevel`):
|
||
logica pura, verificabile senza SDK e non duplicata fra Home e Impianto. Test
|
||
del nucleo: **79/79** (erano 61; **85/85** dalla 0.6.2, con le verifiche sulla
|
||
severità degli allarmi — vedi §10). Stringhe: **191/191** tradotte. Anteprime
|
||
rigenerate (nuova `regolazioni.png`) in italiano e inglese.
|
||
|
||
**Copertina** (`qml/cover/CoverPage.qml`): pallino di stato come in Home, la
|
||
temperatura grande si spegne quando i numeri sono vecchi e l'etichetta finale è
|
||
diventata "esterna 18,0 °C" (era "est.", sigla che non diceva niente), con la
|
||
riga dei numeri vecchi al posto della temperatura esterna quando serve.
|
||
|
||
**Prima build sul telefono (0.6.0): la Home non si apriva.** Il log diceva
|
||
`harbour-aira.qml:9:30: Type MainPage unavailable`, cioè l'app si avviava senza
|
||
la pagina iniziale, e non nominava nessun file dei miei. Causa: due assegnazioni
|
||
di `Component.onCompleted` nello stesso oggetto (`MainPage.qml`), una vecchia e
|
||
una aggiunta con la riga di misura dell'altezza. In Qt 5.6 non è un warning
|
||
innocuo: la pagina non si crea più. Il codice è stato unito in una sola
|
||
assegnazione e, perché non ricapiti, `tests/qml_check_bindings.py` cerca le
|
||
assegnazioni duplicate in tutti i QML (verificato: intercetta il caso
|
||
`70fe229`, il codice attuale è pulito). Da questa prima esecuzione sappiamo
|
||
anche che il resto dell'avvio funziona: configurazione letta
|
||
(`baseUrl=[192.168.3.21:8790]`, token presente), catalogo italiano caricato
|
||
(`it_IT`), versione corretta in log.
|
||
|
||
- Nessun comando nuovo all'impianto: anche questa versione resta **sola
|
||
lettura** finché non si prova l'invio dal telefono.
|
||
|
||
## 10. Dalla prima build sul telefono alla 0.6.4
|
||
|
||
Il §9 racconta il riordino delle schermate fino alla 0.6.0. Da lì sono venute
|
||
quattro versioni piccole, tutte nate da quello che si è visto usare l'app.
|
||
|
||
**0.6.1 — la Home non si apriva.** La prima build sul telefono ha rivelato il
|
||
difetto descritto al §9: due assegnazioni di `Component.onCompleted` nello stesso
|
||
oggetto, che in Qt 5.6 impediscono la creazione della pagina. Non basta
|
||
correggerlo: `tests/qml_check_bindings.py` ora cerca le assegnazioni duplicate in
|
||
tutti i QML, e gira prima della build.
|
||
|
||
**0.6.2 — il pallino non deve gridare.** Il 10/10 il pallino era rosso per un
|
||
avviso innocuo (compressore limitato dalla temperatura esterna): qualunque voce
|
||
nell'elenco errori accendeva il rosso. Ora il rosso è riservato a un allarme che
|
||
ferma la macchina o di severità CRITICAL/ERROR, l'avviso è ambra, e l'esito
|
||
ignoto resta ambra. Il bridge espone `error_severity` e `alarm_stopping` in
|
||
`/summary`, così la decisione si prende sui dati e non sul testo del messaggio.
|
||
|
||
**0.6.3 — l'icona.** Scelta fra quattro proposte disegnate con Pillow
|
||
(`scripts/icon_proposals.py`, tutte conservate in `docs/icona/proposte/`): casa
|
||
con l'onda calda dentro. È ridisegnata a 344 px e rigenerata alle quattro misure
|
||
(86/108/128/172). Disegno originale, nessun logo di terzi: il permesso per nome e
|
||
logo Aira è ancora in sospeso.
|
||
|
||
**0.6.4 — la copertina.** Due righe con l'etichetta davanti, "Casa" e "Acqua",
|
||
ognuna con temperatura attuale / impostata. Le frasi lunghe di prima ("ACS
|
||
53,4 °C · obiettivo 55 °C") sulla cover, che è stretta, venivano tagliate a metà:
|
||
il numero vero spariva proprio dove lo si guarda di sfuggita. Per stare nella
|
||
colonna stretta dell'etichetta il valore attuale è più grande e l'obiettivo più
|
||
piccolo e attaccato ("51,5/55,0 °C").
|
||
|
||
**0.6.5 — un nome solo per l'acqua.** Sulla copertina l'acqua si chiama "Acqua
|
||
calda" come in Home (era "Acqua"): era l'unico punto in cui la stessa cosa aveva
|
||
due nomi.
|
||
|
||
**0.6.6 — le regolazioni dell'oggetto toccato.** Toccando "Casa" si apriva la
|
||
pagina con casa e acqua insieme: chi tocca una lettura si aspetta le regolazioni
|
||
di quella, non di tutto l'impianto. Ora la Home dice quale oggetto è stato
|
||
toccato e la pagina mostra solo quello — l'acqua porta con sé anche il boost, che
|
||
è suo. Due lati della stessa cosa, tenuti insieme da un controllo statico
|
||
(`tests/qml_focus_check.py`), perché questa parte della UI non è eseguibile senza
|
||
il kit Silica.
|
||
|
||
**0.6.7 — le icone dei tasti.** I tre tasti delle azioni rapide in Home hanno
|
||
un'icona: fiamma per il boost, casa per l'assenza, tre manopole per i comandi.
|
||
Non vengono dal tema Sailfish: nell'elenco ufficiale delle icone non c'è nessuna
|
||
fiamma e nessuna goccia, e "boost" e "assenza" non hanno un simbolo di sistema.
|
||
Sono disegnate con Pillow (`scripts/make_glyphs.py`) in PNG bianche su
|
||
trasparente, in `qml/icons/`: il PNG si vede di sicuro dentro il sandbox, l'SVG
|
||
dipende dal supporto SVG.
|
||
|
||
**0.6.8 — le icone dei tasti, misura vera.** Le icone della 0.6.7 erano grosse:
|
||
in un tasto largo un terzo di schermo il testo veniva tagliato. Dalla
|
||
documentazione Silica risulta che `icon` è un `Image`: senza dimensione prende
|
||
quella del file, e il pulsante la riporta all'icona di tema (misurata sullo
|
||
screenshot: 64 px, `Theme.iconSizeMedium`), lasciando 41 px di testo. Ora il
|
||
tasto dichiara `Theme.iconSizeExtraSmall` e i glifi riempiono il riquadro. Con
|
||
la geometria misurata sul telefono (161 px di tasto, rientri 16, distacco 24)
|
||
le etichette **Assenza**, **Torna**, **Altro** e le inglesi **Away**, **Home**,
|
||
**More** ci stanno con margine; "Comandi" (8 lettere) no, quindi il tasto dice
|
||
"Altro" e la pagina resta "Comandi impianto". Prova ripetibile:
|
||
`tests/label_fit_check.py` (le etichette si leggono dai QML e dal catalogo: se
|
||
una si allunga il controllo lo segnala) e `docs/icona/tasti-alla-misura-vera.png`
|
||
per l'occhio. Il pulsante Silica non espone il carattere, quindi non si può
|
||
rimpicciolire il testo: l'unica leva è l'icona.
|
||
|
||
**0.6.9 — l'azione sulla copertina (P8).** Sulla copertina c'è l'icona del
|
||
boost: si avvia il boost dell'acqua calda senza aprire l'app. Una sola azione,
|
||
perché la documentazione Silica dice che due si possono avere ma che lo stile
|
||
delle app di sistema ne mostra una. Il componente esiste: `CoverActionList` e
|
||
`CoverAction` sono nominati nella guida Silica che sta nell'SDK, e
|
||
`CoverBackground` eredita da `Cover`, quindi l'elenco dichiarato lì si attacca
|
||
(non serve avvolgere la copertina in un `Cover`). L'icona si dichiara con
|
||
`iconSource`, non con `icon.source` del pulsante `Button`: sbagliarlo non dà
|
||
errore, semplicemente l'icona non compare. La riga in fondo (esterna o «non
|
||
aggiornato») si ancora a `Cover.coverActionArea`, così l'icona dell'azione non
|
||
la copre; se l'area non c'è la riga resta dov'era. Nuovo controllo statico
|
||
`tests/cover_action_check.py`: elenco figlio diretto, una sola azione, icona che
|
||
esiste, e l'id del comando confrontato con la tabella del bridge — un id
|
||
sbagliato sarebbe un 400 scoperto solo sul telefono.
|
||
|
||
**Stato attuale (0.6.9).** 85/85 verifiche del nucleo, 48/48 del bridge,
|
||
191/191 stringhe tradotte, `qmllint` pulito, contratto UI↔bridge rispettato.
|
||
Nessun comando è stato inviato all'impianto: tutto quanto sopra è a sola lettura.
|