0.6.0 blocco 3: copertina, versione, documenti e anteprime nelle due lingue
@@ -15,24 +15,29 @@ e ripiega sull'inglese; la si può scegliere anche nelle impostazioni dell'app.
|
|||||||
|
|
||||||
## Cosa mostra
|
## Cosa mostra
|
||||||
|
|
||||||
- **Home**, ordinata come l'app ufficiale: temperatura esterna e *in casa /
|
- **Home**, una schermata da guardare: pallino colorato con lo stato in una riga
|
||||||
assente* in alto, una frase di stato in chiaro ("Relax: la pompa di calore
|
("Tutto regolare", "Sta scaldando l'acqua"), l'ora dell'ultimo aggiornamento, le
|
||||||
funziona regolarmente"), poi la **scheda dell'impianto** con le due letture che
|
due letture che contano (casa e acqua calda, ognuna con il suo obiettivo)
|
||||||
contano (temperatura stanza e acqua calda, ognuna con il suo obiettivo).
|
affiancate e **toccabili**, e tre azioni rapide: boost, assenza, comandi
|
||||||
Toccando la scheda si apre la pagina dell'impianto. Sotto, i comandi:
|
impianto. Sta in una schermata, senza scorrere: i comandi hanno pagine loro
|
||||||
obiettivo della casa con slider 5–30 °C (passi da 0,5 °C) e conferma prima
|
- **Regolazioni**: obiettivo casa e obiettivo acqua calda, ognuno con il valore
|
||||||
dell'invio, obiettivo ACS (45–60 °C) e i pulsanti del boost
|
che l'impianto sta tenendo adesso, lo slider (la proposta: il valore vero non si
|
||||||
|
muove), una fila di valori rapidi per le dita fredde e il pulsante di conferma,
|
||||||
|
che si accende solo se c'è davvero qualcosa da cambiare. Il boost sta qui
|
||||||
- **Dati**: calore prodotto, elettricità usata, efficienza (COP) di acqua calda e
|
- **Dati**: calore prodotto, elettricità usata, efficienza (COP) di acqua calda e
|
||||||
riscaldamento, risparmio in bolletta, CO₂ evitata e risparmio Smart Tariff
|
riscaldamento, risparmio in bolletta, CO₂ evitata e risparmio Smart Tariff
|
||||||
Control, mese per mese. Sono i numeri che Aira calcola nel cloud, gli stessi
|
Control, mese per mese. Sono i numeri che Aira calcola nel cloud, gli stessi
|
||||||
della scheda *Dati* dell'app ufficiale
|
della scheda *Dati* dell'app ufficiale
|
||||||
- **Impianto**: stato (connessione, modalità, assenza/notte/riscaldamento
|
- **Impianto**: stato (connessione, modalità, assenza/notte/riscaldamento
|
||||||
forzato, temperature esterna e interna, zone, ultima lettura), accessori (il
|
forzato, modalità manuale, sbrinamento, luci di firma, stato pompa, modi
|
||||||
|
consentiti, temperature esterna e interna, zone, ultima lettura), accessori (il
|
||||||
termostato a parete con temperatura, umidità, segnale, batteria), versioni del
|
termostato a parete con temperatura, umidità, segnale, batteria), versioni del
|
||||||
software e numero di errori
|
software, numero di errori e verifica impianto. Qui sta anche la frase di stato
|
||||||
- **Altri comandi**: assenza, night mode per un'ora, riscaldamento forzato,
|
estesa ("Relax: la pompa di calore funziona regolarmente")
|
||||||
funzione riscaldamento on/off, ciclo legionella, ACS on/off e verifica
|
- **Comandi impianto**: quattro interruttori (assenza, riscaldamento forzato,
|
||||||
impianto, con lo stato attuale in cima
|
riscaldamento, acqua calda attiva) che mostrano lo stato e lo cambiano in un
|
||||||
|
tocco, più i pulsanti per quello che uno stato non ce l'ha: ciclo legionella e
|
||||||
|
night mode per un'ora. Gli interruttori seguono l'impianto, non lo anticipano
|
||||||
- **Copertina** con la temperatura della casa
|
- **Copertina** con la temperatura della casa
|
||||||
- Indirizzo e token del bridge stanno nelle impostazioni dell'app (QSettings),
|
- Indirizzo e token del bridge stanno nelle impostazioni dell'app (QSettings),
|
||||||
mai nel binario
|
mai nel binario
|
||||||
@@ -43,13 +48,13 @@ Anteprime del layout (valori veri dell'impianto, **non** screenshot — vedi
|
|||||||
*Stato*). Queste sono in italiano; quelle in inglese stanno in
|
*Stato*). Queste sono in italiano; quelle in inglese stanno in
|
||||||
`docs/anteprima/`:
|
`docs/anteprima/`:
|
||||||
|
|
||||||
| Home | Dati | Impianto |
|
| Home | Regolazioni | Dati |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|  |  |  |
|
|  |  |  |
|
||||||
|
|
||||||
| Altri comandi | Impostazioni | Copertina |
|
| Impianto | Comandi impianto | Impostazioni | Copertina |
|
||||||
|---|---|---|
|
|---|---|---|---|
|
||||||
|  |  |  |
|
|  |  |  |  |
|
||||||
|
|
||||||
## La lingua dell'interfaccia
|
## La lingua dell'interfaccia
|
||||||
|
|
||||||
@@ -151,10 +156,12 @@ python3 tests/ui_contract_check.py http://192.168.3.21:8790
|
|||||||
|
|
||||||
## Stato
|
## Stato
|
||||||
|
|
||||||
- **61/61** verifiche del nucleo, compilazione senza warning
|
- **79/79** verifiche del nucleo, compilazione senza warning (comprese le frasi
|
||||||
|
di stato, breve ed estesa: quello che la Home dice dell'impianto è verificato
|
||||||
|
senza telefono)
|
||||||
- **48/48** verifiche offline del bridge (`bridge/test_bridge_commands.py`),
|
- **48/48** verifiche offline del bridge (`bridge/test_bridge_commands.py`),
|
||||||
comprese le conversioni Wh→kWh e degli importi delle statistiche
|
comprese le conversioni Wh→kWh e degli importi delle statistiche
|
||||||
- **170/170** stringhe tradotte nel catalogo italiano: il test carica il `.qm`
|
- **190/190** stringhe tradotte nel catalogo italiano: il test carica il `.qm`
|
||||||
compilato come fa `main.cpp` e verifica che in inglese l'etichetta di stato sia
|
compilato come fa `main.cpp` e verifica che in inglese l'etichetta di stato sia
|
||||||
la sorgente e in italiano quella tradotta, e che togliendo il catalogo si torni
|
la sorgente e in italiano quella tradotta, e che togliendo il catalogo si torni
|
||||||
all'inglese
|
all'inglese
|
||||||
@@ -188,13 +195,16 @@ src/translator.{h,cpp} lingua dell'interfaccia: locale di sistema, ripiego
|
|||||||
sull'inglese, ricarica della UI al cambio
|
sull'inglese, ricarica della UI al cambio
|
||||||
src/main.cpp context properties: appSettings, api, translator
|
src/main.cpp context properties: appSettings, api, translator
|
||||||
qml/harbour-aira.qml ApplicationWindow + copertina
|
qml/harbour-aira.qml ApplicationWindow + copertina
|
||||||
qml/pages/MainPage.qml home: stato, scheda impianto, riscaldamento e acqua
|
qml/pages/MainPage.qml home: stato in una riga, due letture, azioni rapide
|
||||||
|
qml/pages/SetpointsPage.qml obiettivi casa e acqua, valori rapidi, boost
|
||||||
qml/pages/DataPage.qml energia, efficienza e risparmi
|
qml/pages/DataPage.qml energia, efficienza e risparmi
|
||||||
qml/pages/PlantPage.qml stato, accessori, versioni, errori
|
qml/pages/PlantPage.qml stato, accessori, versioni, errori, diagnostica
|
||||||
qml/pages/CommandsPage.qml gli altri comandi, con lo stato attuale in cima
|
qml/pages/CommandsPage.qml i comandi impianto, stato negli interruttori
|
||||||
qml/pages/SettingsPage.qml
|
qml/pages/SettingsPage.qml
|
||||||
qml/components/InfoRow.qml etichetta a sinistra, valore a destra
|
qml/components/InfoRow.qml etichetta a sinistra, valore a destra
|
||||||
qml/components/ValueRow.qml titolo e sottotitolo a sinistra, valore grande a destra
|
qml/components/ValueRow.qml titolo e sottotitolo a sinistra, valore grande a destra
|
||||||
|
qml/components/ReadingCell.qml lettura toccabile: titolo, valore grande, barra
|
||||||
|
qml/components/NoticeBar.qml messaggi dei comandi, ancorati in fondo
|
||||||
qml/components/ActionButton.qml pulsante di comando, con conferma opzionale
|
qml/components/ActionButton.qml pulsante di comando, con conferma opzionale
|
||||||
tests/core_test.cpp verifiche del nucleo (headless)
|
tests/core_test.cpp verifiche del nucleo (headless)
|
||||||
tests/ui_contract_check.py chiavi dei QML contro quelle del bridge (bridge attivo)
|
tests/ui_contract_check.py chiavi dei QML contro quelle del bridge (bridge attivo)
|
||||||
@@ -207,7 +217,7 @@ bridge/harbour-aira-bridge.service unit systemd utente
|
|||||||
bridge/README.md istruzioni di installazione del bridge (inglese)
|
bridge/README.md istruzioni di installazione del bridge (inglese)
|
||||||
docs/PIANO.md piano di progetto e scelte
|
docs/PIANO.md piano di progetto e scelte
|
||||||
docs/PROTOCOL.md API del bridge
|
docs/PROTOCOL.md API del bridge
|
||||||
docs/UX-STUDIO.md studio UX e proposta per la 0.6.0
|
docs/UX-STUDIO.md studio UX e cosa è cambiato nella 0.6.0
|
||||||
docs/ux/ schemi dello studio UX
|
docs/ux/ schemi dello studio UX
|
||||||
translations/ harbour-aira-it.ts (l'inglese è la lingua sorgente)
|
translations/ harbour-aira-it.ts (l'inglese è la lingua sorgente)
|
||||||
docs/anteprima/ anteprime di layout in inglese (in italiano: it/)
|
docs/anteprima/ anteprime di layout in inglese (in italiano: it/)
|
||||||
|
|||||||
@@ -9,24 +9,30 @@ status, domestic hot water, and the house target temperature per zone.
|
|||||||
|
|
||||||
## What it does
|
## What it does
|
||||||
|
|
||||||
- **Home**, organised like the official Aira app: outdoor temperature and
|
- **Home**, one screen to look at: a coloured dot with a one-line status
|
||||||
*at home / away* at the top, a plain-language status line ("Relax: the heat
|
("Everything is fine", "Heating the hot water"), the time of the last update,
|
||||||
pump is running smoothly"), then the **plant card** with the two readings that
|
the two readings that matter (house and domestic hot water, each with its
|
||||||
matter (room temperature and domestic hot water, each with its target).
|
target) side by side and **tappable**, and three quick actions: boost, away
|
||||||
Tapping the card opens the plant page. Below that the controls: house target
|
mode and the plant commands. It fits without scrolling — the controls live in
|
||||||
with a 5–30 °C slider (0.5 °C steps) and a confirmation before the command
|
their own pages
|
||||||
leaves, domestic hot water target (45–60 °C) and the boost buttons
|
- **Setpoints** page: house target and hot water target, each with the value the
|
||||||
|
plant is keeping right now, a slider (the proposal, the plant value does not
|
||||||
|
move), a row of quick values for cold fingers and a confirmation button that
|
||||||
|
only lights up when there is something to change
|
||||||
- **Data** page: heat produced, electricity used, efficiency (COP) for hot water
|
- **Data** page: heat produced, electricity used, efficiency (COP) for hot water
|
||||||
and heating, savings on the bill, CO₂ avoided and the Smart Tariff Control
|
and heating, savings on the bill, CO₂ avoided and the Smart Tariff Control
|
||||||
saving, month by month. These are the numbers Aira computes in the cloud,
|
saving, month by month. These are the numbers Aira computes in the cloud,
|
||||||
exactly as in the official app's *Data* tab
|
exactly as in the official app's *Data* tab
|
||||||
- **Plant** page: status (connection, mode, away/night/forced heating, outdoor
|
- **Plant** page: status (connection, mode, away/night/forced heating, manual
|
||||||
and indoor temperature, zones, last reading), accessories (the wall thermostat
|
mode, defrost, signature lights, pump state, allowed modes, outdoor and indoor
|
||||||
with temperature, humidity, signal, battery), software versions and the error
|
temperature, zones, last reading), accessories (the wall thermostat with
|
||||||
count
|
temperature, humidity, signal, battery), software versions, the error count and
|
||||||
- **Other commands** page: away mode, night mode for one hour, force heating,
|
the plant check. The extended status sentence ("Relax: the heat pump is running
|
||||||
heating function on/off, legionella cycle, DHW heating on/off and a plant
|
smoothly") lives here
|
||||||
check — with the current state at the top, so nothing is pressed blindly
|
- **Plant commands** page: four switches (away mode, forced heating, heating, hot
|
||||||
|
water active) that show the state of the plant and change it in one tap, plus
|
||||||
|
the buttons for what has no state: legionella cycle and night mode for one
|
||||||
|
hour. The switches follow the plant — they never anticipate it
|
||||||
- **Cover page** showing the house temperature
|
- **Cover page** showing the house temperature
|
||||||
- Address and bridge token live in the app settings (QSettings), never in the
|
- Address and bridge token live in the app settings (QSettings), never in the
|
||||||
binary
|
binary
|
||||||
@@ -39,13 +45,13 @@ status, domestic hot water, and the house target temperature per zone.
|
|||||||
Layout previews (rendered with the real values the plant returned, **not**
|
Layout previews (rendered with the real values the plant returned, **not**
|
||||||
screenshots — see *Status*):
|
screenshots — see *Status*):
|
||||||
|
|
||||||
| Home | Data | Plant |
|
| Home | Setpoints | Data |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|  |  |  |
|
|  |  |  |
|
||||||
|
|
||||||
| Other commands | Settings | Cover |
|
| Plant | Plant commands | Settings | Cover |
|
||||||
|---|---|---|
|
|---|---|---|---|
|
||||||
|  |  |  |
|
|  |  |  |  |
|
||||||
|
|
||||||
## Why there is a bridge
|
## Why there is a bridge
|
||||||
|
|
||||||
@@ -136,12 +142,14 @@ python3 tests/ui_contract_check.py http://192.168.3.21:8790
|
|||||||
|
|
||||||
Verified so far:
|
Verified so far:
|
||||||
|
|
||||||
- **61/61** core checks (`tests/core_test.cpp`), compiled with no warnings
|
- **79/79** core checks (`tests/core_test.cpp`), compiled with no warnings —
|
||||||
|
including the one-line and extended status sentences, so what the home says
|
||||||
|
about the plant is verified without a phone
|
||||||
- **48/48** bridge checks, offline (`bridge/test_bridge_commands.py`) —
|
- **48/48** bridge checks, offline (`bridge/test_bridge_commands.py`) —
|
||||||
including that a zone command always carries every zone, that absurd values
|
including that a zone command always carries every zone, that absurd values
|
||||||
are refused (40 °C, zone 3, text), and the Wh→kWh / money conversions of the
|
are refused (40 °C, zone 3, text), and the Wh→kWh / money conversions of the
|
||||||
statistics
|
statistics
|
||||||
- **170/170** strings translated in the Italian catalogue, checked by loading
|
- **190/190** strings translated in the Italian catalogue, checked by loading
|
||||||
the compiled `.qm` in the test the same way `main.cpp` does: source English
|
the compiled `.qm` in the test the same way `main.cpp` does: source English
|
||||||
in, Italian out, and back to English when the catalogue is removed
|
in, Italian out, and back to English when the catalogue is removed
|
||||||
- numbers, amounts and month names formatted through `QLocale`, so the decimal
|
- numbers, amounts and month names formatted through `QLocale`, so the decimal
|
||||||
@@ -202,13 +210,16 @@ src/settings.{h,cpp} QSettings in AppConfigLocation (SailJail sandbox)
|
|||||||
src/translator.{h,cpp} UI language: system locale, English fallback, reload
|
src/translator.{h,cpp} UI language: system locale, English fallback, reload
|
||||||
src/main.cpp context properties: appSettings, api, translator
|
src/main.cpp context properties: appSettings, api, translator
|
||||||
qml/harbour-aira.qml ApplicationWindow + cover
|
qml/harbour-aira.qml ApplicationWindow + cover
|
||||||
qml/pages/MainPage.qml home: status, plant card, heating and water
|
qml/pages/MainPage.qml home: one-line status, two readings, quick actions
|
||||||
|
qml/pages/SetpointsPage.qml house and water targets, quick values, boost
|
||||||
qml/pages/DataPage.qml energy, efficiency and savings
|
qml/pages/DataPage.qml energy, efficiency and savings
|
||||||
qml/pages/PlantPage.qml status, accessories, versions, errors
|
qml/pages/PlantPage.qml status, accessories, versions, errors, diagnostics
|
||||||
qml/pages/CommandsPage.qml the other commands, current state on top
|
qml/pages/CommandsPage.qml the plant commands, state shown by switches
|
||||||
qml/pages/SettingsPage.qml
|
qml/pages/SettingsPage.qml
|
||||||
qml/components/InfoRow.qml label on the left, value on the right
|
qml/components/InfoRow.qml label on the left, value on the right
|
||||||
qml/components/ValueRow.qml title + subtitle left, big value right
|
qml/components/ValueRow.qml title + subtitle left, big value right
|
||||||
|
qml/components/ReadingCell.qml tappable reading: title, big value, charge bar
|
||||||
|
qml/components/NoticeBar.qml command messages, anchored to the bottom edge
|
||||||
qml/components/ActionButton.qml command button, optional confirmation
|
qml/components/ActionButton.qml command button, optional confirmation
|
||||||
tests/core_test.cpp core checks (headless)
|
tests/core_test.cpp core checks (headless)
|
||||||
tests/ui_contract_check.py QML keys vs bridge keys (bridge must be running)
|
tests/ui_contract_check.py QML keys vs bridge keys (bridge must be running)
|
||||||
@@ -216,7 +227,7 @@ bridge/ local HTTP bridge -> see bridge/README.md
|
|||||||
translations/ harbour-aira-it.ts (English is the source language)
|
translations/ harbour-aira-it.ts (English is the source language)
|
||||||
docs/PIANO.md project plan and decisions (Italian)
|
docs/PIANO.md project plan and decisions (Italian)
|
||||||
docs/PROTOCOL.md bridge API
|
docs/PROTOCOL.md bridge API
|
||||||
docs/UX-STUDIO.md UX review + proposal for 0.6.0 (Italian)
|
docs/UX-STUDIO.md UX review and what changed in 0.6.0 (Italian)
|
||||||
docs/ux/ UX study wireframes
|
docs/ux/ UX study wireframes
|
||||||
docs/anteprima/ layout previews (English; docs/anteprima/it/ Italian)
|
docs/anteprima/ layout previews (English; docs/anteprima/it/ Italian)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -231,3 +231,52 @@ andrebbe disegnata a mano.
|
|||||||
parsing delle statistiche e alla formattazione di importi e mesi (61/61), versione,
|
parsing delle statistiche e alla formattazione di importi e mesi (61/61), versione,
|
||||||
README e `docs/PROTOCOL.md` aggiornati.
|
README e `docs/PROTOCOL.md` aggiornati.
|
||||||
- Nessun comando nuovo all'impianto: tutto questo è **sola lettura**.
|
- 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 o
|
||||||
|
numeri vecchi, rosso errore), 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). Stringhe: **190/190** tradotte. Anteprime
|
||||||
|
rigenerate (nuova `regolazioni.png`) in italiano e inglese.
|
||||||
|
|
||||||
|
- Nessun comando nuovo all'impianto: anche questa versione resta **sola
|
||||||
|
lettura** finché non si prova l'invio dal telefono.
|
||||||
|
|||||||
@@ -8,6 +8,33 @@ Immagini di questo studio (schemi, non anteprime): `docs/ux/`.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 0. Cosa è stato deciso e cosa è stato fatto (0.6.0)
|
||||||
|
|
||||||
|
Le quattro scelte, applicate:
|
||||||
|
|
||||||
|
1. La frase di stato lunga sta nella pagina **Impianto**; in Home resta la riga
|
||||||
|
compatta con il pallino colorato.
|
||||||
|
2. "Altri comandi" è diventata **"Comandi impianto"**.
|
||||||
|
3. Le azioni rapide sono **pulsanti visibili** in Home.
|
||||||
|
4. Azioni sulla copertina: **rimandate** a dopo la prova di `CoverAction` sul kit.
|
||||||
|
|
||||||
|
Problemi risolti: P1 (Home di consultazione), P2 (feedback ancorato), P3
|
||||||
|
(interruttori), P4 (un solo percorso per pagina), P5 (un blocco per
|
||||||
|
grandezza, valori rapidi), P6 (numeri vecchi spenti + ora), P7 ("esterna"),
|
||||||
|
P9, P10 (diagnostica in Impianto), P11.
|
||||||
|
|
||||||
|
Rimandato: P8 (azioni sulla copertina), in attesa della prova sul telefono.
|
||||||
|
|
||||||
|
Verifiche fatte qui: **79 verifiche del nucleo** (erano 61), `qmllint` pulito su
|
||||||
|
tutti i file QML, 190/190 stringhe tradotte, anteprime rigenerate in inglese e
|
||||||
|
italiano, contratto dei dati fra UI e bridge rispettato
|
||||||
|
(`tests/ui_contract_check.py`).
|
||||||
|
Da provare sul telefono: che `TextSwitch` si istanzi davvero, l'altezza reale
|
||||||
|
della Home (riga di log con `contentHeight` contro l'altezza dello schermo) e
|
||||||
|
l'uso con una mano sola.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 1. Metodo
|
## 1. Metodo
|
||||||
|
|
||||||
Tre riferimenti, in quest'ordine:
|
Tre riferimenti, in quest'ordine:
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 73 KiB |
@@ -1,6 +1,6 @@
|
|||||||
Name: harbour-aira
|
Name: harbour-aira
|
||||||
Summary: Control your Aira heat pump through a local bridge
|
Summary: Control your Aira heat pump through a local bridge
|
||||||
Version: 0.5.0
|
Version: 0.6.0
|
||||||
Release: 1
|
Release: 1
|
||||||
Group: Qt/Qt
|
Group: Qt/Qt
|
||||||
License: MIT
|
License: MIT
|
||||||
@@ -37,6 +37,18 @@ Address and token of the bridge live in the app settings (AppConfigLocation);
|
|||||||
no personal data is stored in the binary.
|
no personal data is stored in the binary.
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Sat Oct 10 2026 Carlo <carlo@carlobaratto.it> - 0.6.0-1
|
||||||
|
- Home di sola consultazione: stato in una riga con pallino colorato, le due
|
||||||
|
letture (casa e acqua calda) grandi e toccabili, tre azioni rapide; le due
|
||||||
|
regolazioni e i pulsanti di navigazione escono dalla Home
|
||||||
|
- pagina Regolazioni: obiettivo casa e obiettivo acqua calda, ognuno con il
|
||||||
|
valore attuale sempre visibile, slider, valori rapidi e conferma; boost ACS
|
||||||
|
- Comandi impianto: quattro interruttori al posto di otto pulsanti acceso/
|
||||||
|
spento, piu' tre pulsanti per le azioni senza stato; diagnostica spostata
|
||||||
|
nella pagina Impianto
|
||||||
|
- messaggi dei comandi ancorati in fondo alla pagina, visibili sempre
|
||||||
|
- numeri vecchi mostrati spenti, con l'ora dell'ultimo aggiornamento
|
||||||
|
- lo stato si rilegge da solo due secondi dopo un comando
|
||||||
* Sat Oct 10 2026 Carlo <carlo@carlobaratto.it> - 0.5.0-1
|
* Sat Oct 10 2026 Carlo <carlo@carlobaratto.it> - 0.5.0-1
|
||||||
- schermata iniziale riordinata come l'app ufficiale: temperatura esterna in
|
- schermata iniziale riordinata come l'app ufficiale: temperatura esterna in
|
||||||
alto, messaggio di stato, scheda dell'impianto con casa e acqua calda
|
alto, messaggio di stato, scheda dell'impianto con casa e acqua calda
|
||||||
|
|||||||
@@ -327,6 +327,92 @@ TEXTS = {
|
|||||||
|
|
||||||
LANG = "en"
|
LANG = "en"
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Testi aggiunti nella 0.6.0 (Home di consultazione, pagina Regolazioni,
|
||||||
|
# Comandi con interruttori). Le due lingue in un colpo solo: le anteprime
|
||||||
|
# restano bilingui come l'app.
|
||||||
|
TEXTS["en"].update({
|
||||||
|
"outside": "Outside {v}",
|
||||||
|
"house_cell": "House",
|
||||||
|
"hot_water_cell": "Hot water",
|
||||||
|
"cell_target": "target {v}",
|
||||||
|
"status_fine": "Everything is fine",
|
||||||
|
"status_water": "Heating the hot water",
|
||||||
|
"updated_at": "Updated at 21:18",
|
||||||
|
"pull_hint": "Pull down: Aggiorna · Dati · Impianto · Impostazioni",
|
||||||
|
"tap_adjust": "Tap a value to adjust it",
|
||||||
|
"quick_actions": "Quick actions",
|
||||||
|
"boost": "Boost",
|
||||||
|
"away_btn": "Away mode",
|
||||||
|
"back_home": "Back home",
|
||||||
|
"commands": "Commands",
|
||||||
|
"contorno": "Outside {a} · Thermostat {b} · Zones 1",
|
||||||
|
"setpoints": "Setpoints",
|
||||||
|
"current_target": "Current target",
|
||||||
|
"keep_now": "What the plant is keeping now",
|
||||||
|
"water_now": "Water now {v}",
|
||||||
|
"hot_water_boost": "Hot water boost",
|
||||||
|
"set_house_v": "Set house to {v} °C",
|
||||||
|
"set_water_v": "Set target {v} °C",
|
||||||
|
"plant_commands": "Plant commands",
|
||||||
|
"modes": "Modes",
|
||||||
|
"sw_away_desc": "Lowers the house and stops the hot water",
|
||||||
|
"sw_forced_desc": "Heats at full power until you stop it",
|
||||||
|
"sw_heating_desc": "The heating can turn on",
|
||||||
|
"sw_water_desc": "The heat pump keeps the water in temperature",
|
||||||
|
"legionella": "Legionella cycle",
|
||||||
|
"switches_note": "The switches show the current state of the plant.",
|
||||||
|
"commands_note": "Commands go through the bridge and may take a few seconds.",
|
||||||
|
"diagnostics": "Diagnostics",
|
||||||
|
"plant_check": "Plant check",
|
||||||
|
"manual_mode": "Manual mode",
|
||||||
|
"defrost": "Defrost",
|
||||||
|
"signature_lights": "Signature lights",
|
||||||
|
"save_and_test": "Save and test",
|
||||||
|
"cover_outdoor": "outside",
|
||||||
|
})
|
||||||
|
|
||||||
|
TEXTS["it"].update({
|
||||||
|
"house_cell": "Casa",
|
||||||
|
"hot_water_cell": "Acqua calda",
|
||||||
|
"cell_target": "obiettivo {v}",
|
||||||
|
"status_fine": "Tutto regolare",
|
||||||
|
"status_water": "Sta scaldando l'acqua",
|
||||||
|
"updated_at": "Aggiornato alle 21:18",
|
||||||
|
"pull_hint": "Trascina in giù: Aggiorna · Dati · Impianto · Impostazioni",
|
||||||
|
"tap_adjust": "Tocca un valore per regolarlo",
|
||||||
|
"quick_actions": "Azioni rapide",
|
||||||
|
"boost": "Boost",
|
||||||
|
"away_btn": "Assenza",
|
||||||
|
"back_home": "Torna a casa",
|
||||||
|
"commands": "Comandi",
|
||||||
|
"contorno": "Esterno {a} · Termostato {b} · Zone 1",
|
||||||
|
"setpoints": "Regolazioni",
|
||||||
|
"current_target": "Obiettivo attuale",
|
||||||
|
"keep_now": "Quello che l'impianto mantiene adesso",
|
||||||
|
"water_now": "Acqua ora {v}",
|
||||||
|
"hot_water_boost": "Boost acqua calda",
|
||||||
|
"set_house_v": "Imposta casa a {v} °C",
|
||||||
|
"set_water_v": "Imposta obiettivo {v} °C",
|
||||||
|
"plant_commands": "Comandi impianto",
|
||||||
|
"modes": "Modalità",
|
||||||
|
"sw_away_desc": "Abbassa la casa e spegne l'acqua calda",
|
||||||
|
"sw_forced_desc": "Scalda a pieno regime finché non lo fermi",
|
||||||
|
"sw_heating_desc": "Il riscaldamento può accendersi",
|
||||||
|
"sw_water_desc": "Mantiene l'acqua in temperatura",
|
||||||
|
"legionella": "Ciclo legionella",
|
||||||
|
"switches_note": "Gli interruttori mostrano lo stato attuale dell'impianto.",
|
||||||
|
"commands_note": "I comandi passano dal bridge e possono richiedere qualche secondo.",
|
||||||
|
"diagnostics": "Diagnostica",
|
||||||
|
"plant_check": "Verifica impianto",
|
||||||
|
"manual_mode": "Modalità manuale",
|
||||||
|
"defrost": "Sbrinamento",
|
||||||
|
"signature_lights": "Luci di firma",
|
||||||
|
"save_and_test": "Salva e prova",
|
||||||
|
"cover_outdoor": "esterna",
|
||||||
|
"night_one_hour": "Modalità notte per un'ora",
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def T(key: str) -> str:
|
def T(key: str) -> str:
|
||||||
return TEXTS[LANG][key]
|
return TEXTS[LANG][key]
|
||||||
@@ -363,65 +449,67 @@ def value_row(d: ImageDraw.ImageDraw, y: int, title: str, subtitle: str,
|
|||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
|
def reading_cell(d: ImageDraw.ImageDraw, x: int, y: int, w: int, h: int,
|
||||||
|
title: str, value: str, subtitle: str,
|
||||||
|
frac: float = None) -> None:
|
||||||
|
"""Riquadro di una lettura grande (componente ReadingCell dell'app): titolo,
|
||||||
|
valore, riga sotto e barra di avanzamento facoltativa."""
|
||||||
|
d.rounded_rectangle([x, y, x + w, y + h], radius=18, fill=(26, 32, 41),
|
||||||
|
outline=BTN_EDGE, width=2)
|
||||||
|
d.text((x + 26, y + 22), title, font=font(24), fill=SECONDARY)
|
||||||
|
d.text((x + 26, y + 56), value, font=font(56, bold=True), fill=PRIMARY)
|
||||||
|
d.text((x + 26, y + 132), subtitle, font=font(20), fill=SECONDARY)
|
||||||
|
if frac is not None:
|
||||||
|
bx0, bx1 = x + 26, x + w - 26
|
||||||
|
by = y + h - 30
|
||||||
|
d.rounded_rectangle([bx0, by, bx1, by + 10], radius=5, fill=(46, 54, 66))
|
||||||
|
d.rounded_rectangle([bx0, by, bx0 + (bx1 - bx0) * max(0.0, min(1.0, frac)), by + 10],
|
||||||
|
radius=5, fill=ACCENT)
|
||||||
|
|
||||||
|
|
||||||
def main_screen() -> Image.Image:
|
def main_screen() -> Image.Image:
|
||||||
"""Schermata iniziale, ordinata come quella dell'app ufficiale: temperatura
|
"""Home della 0.6.0: una schermata da guardare, non un modulo. Stato in una
|
||||||
esterna in alto, messaggio di stato, scheda dell'impianto, poi i comandi."""
|
riga, le due letture che contano, tre azioni rapide. Niente scorrimento."""
|
||||||
img = background()
|
img = background()
|
||||||
d = ImageDraw.Draw(img)
|
d = ImageDraw.Draw(img)
|
||||||
status_bar(d)
|
status_bar(d)
|
||||||
page_header(d, T("app"))
|
page_header(d, T("app"))
|
||||||
|
d.text((M, 128), T("updated_at"), font=font(22), fill=SECONDARY)
|
||||||
|
|
||||||
y = 150
|
y = 176
|
||||||
# ---- barra in alto: fuori casa / a casa ------------------------------ #
|
# ---- stato in una riga ------------------------------------------------ #
|
||||||
d.text((M, y), T("outside").format(v=dec(18.0) + " °C"), font=font(24), fill=SECONDARY)
|
d.ellipse([M, y + 7, M + 18, y + 25], fill=OK)
|
||||||
casa = T("at_home")
|
d.text((M + 34, y), T("status_water"), font=font(26), fill=PRIMARY)
|
||||||
d.text((W - M - d.textlength(casa, font=font(22)), y + 2), casa,
|
y += 54
|
||||||
|
|
||||||
|
# ---- le due letture che contano --------------------------------------- #
|
||||||
|
gap = 20
|
||||||
|
cw = (W - 2 * M - gap) // 2
|
||||||
|
ch = 200
|
||||||
|
reading_cell(d, M, y, cw, ch, T("house_cell"), dec(23.0) + " °C",
|
||||||
|
T("cell_target").format(v=dec(19.0) + " °C"))
|
||||||
|
reading_cell(d, M + cw + gap, y, cw, ch, T("hot_water_cell"), dec(50.6) + " °C",
|
||||||
|
T("cell_target").format(v=dec(55.0) + " °C"),
|
||||||
|
(50.6 - 10) / (55.0 - 10))
|
||||||
|
y += ch + 18
|
||||||
|
|
||||||
|
d.text((M, y), T("tap_adjust"), font=font(22), fill=SECONDARY)
|
||||||
|
y += 54
|
||||||
|
|
||||||
|
# ---- azioni rapide ----------------------------------------------------- #
|
||||||
|
y = section_header(d, y, T("quick_actions"))
|
||||||
|
bw = (W - 2 * M - 2 * gap) // 3
|
||||||
|
for i, testo in enumerate((T("boost"), T("away_btn"), T("commands"))):
|
||||||
|
x = M + i * (bw + gap)
|
||||||
|
d.rounded_rectangle([x, y, x + bw, y + 74], radius=18, fill=BTN_BG,
|
||||||
|
outline=BTN_EDGE, width=2)
|
||||||
|
tw = d.textlength(testo, font=font(24))
|
||||||
|
d.text((x + (bw - tw) / 2, y + 23), testo, font=font(24), fill=PRIMARY)
|
||||||
|
y += 104
|
||||||
|
|
||||||
|
# ---- letture di contorno ----------------------------------------------- #
|
||||||
|
d.text((M, y), T("contorno").format(a=dec(17.8) + " °C", b=dec(22.8) + " °C"),
|
||||||
font=font(22), fill=SECONDARY)
|
font=font(22), fill=SECONDARY)
|
||||||
y += 58
|
|
||||||
|
|
||||||
# ---- messaggio di stato ---------------------------------------------- #
|
|
||||||
fnt = font(30)
|
|
||||||
righe = wrap(d, T("status_relax"), fnt, W - 2 * M - 20)
|
|
||||||
for i, riga in enumerate(righe):
|
|
||||||
d.text(((W - d.textlength(riga, font=fnt)) / 2, y + i * 40), riga,
|
|
||||||
font=fnt, fill=PRIMARY)
|
|
||||||
y += 40 * len(righe) + 6
|
|
||||||
det = T("status_detail")
|
|
||||||
d.text(((W - d.textlength(det, font=font(22))) / 2, y), det, font=font(22), fill=SECONDARY)
|
|
||||||
y += 46
|
|
||||||
|
|
||||||
# ---- scheda dell'impianto -------------------------------------------- #
|
|
||||||
card(d, y, 282)
|
|
||||||
d.text((M + 28, y + 22), T("device_name"), font=font(26), fill=PRIMARY)
|
|
||||||
d.text((M + 28, y + 58), T("device_link"), font=font(22), fill=SECONDARY)
|
|
||||||
d.text((W - M - 46, y + 24), "›", font=font(42), fill=ACCENT)
|
|
||||||
value_row(d, y + 100, T("room_temperature"),
|
|
||||||
T("target_value").format(v=dec(19.0) + " °C"), dec(22.8) + " °C")
|
|
||||||
value_row(d, y + 186, T("dhw_row"),
|
|
||||||
T("target_value").format(v=dec(55.0) + " °C"), dec(51.0) + " °C")
|
|
||||||
y += 282 + 26
|
|
||||||
|
|
||||||
# ---- riscaldamento della casa ---------------------------------------- #
|
|
||||||
y = section_header(d, y, T("heating_house"))
|
|
||||||
line = T("setpoint_line")
|
|
||||||
d.text(((W - d.textlength(line, font=font(22))) / 2, y - 8), line,
|
|
||||||
font=font(22), fill=SECONDARY)
|
|
||||||
y = slider(d, y + 28, (19.0 - 5) / (30 - 5), dec(19.0) + " °C", T("new_house_target"))
|
|
||||||
y = button(d, y, T("set_house").format(v=dec(19.0)), primary=True)
|
|
||||||
|
|
||||||
# ---- acqua calda sanitaria ------------------------------------------- #
|
|
||||||
y = section_header(d, y + 6, T("dhw_section"))
|
|
||||||
progress(d, y, (51.0 - 20) / (65 - 20))
|
|
||||||
y += 22
|
|
||||||
y = slider(d, y + 4, (55.0 - 45) / (60 - 45), "55 °C", T("target_label"))
|
|
||||||
y = button(d, y, T("set_acs"), primary=True)
|
|
||||||
y = button(d, y, T("start_boost"))
|
|
||||||
y = button(d, y, T("stop_boost"))
|
|
||||||
|
|
||||||
# ---- pagine collegate (scorrono sotto) ------------------------------- #
|
|
||||||
y = section_header(d, y + 6, T("details"))
|
|
||||||
y = button(d, y, T("plant_accessories"))
|
|
||||||
y = button(d, y, T("consumption"))
|
|
||||||
return img
|
return img
|
||||||
|
|
||||||
|
|
||||||
@@ -449,10 +537,9 @@ def settings_screen() -> Image.Image:
|
|||||||
|
|
||||||
d.multiline_text((M, y), T("settings_note"), font=font(22), fill=SECONDARY, spacing=10)
|
d.multiline_text((M, y), T("settings_note"), font=font(22), fill=SECONDARY, spacing=10)
|
||||||
y += 104
|
y += 104
|
||||||
y = button(d, y, T("test_connection"), primary=True)
|
# Un pulsante solo: salvare e provare erano due gesti per la stessa cosa.
|
||||||
|
y = button(d, y, T("save_and_test"), primary=True)
|
||||||
d.text((M, y), T("connection_ok"), font=font(24), fill=OK)
|
d.text((M, y), T("connection_ok"), font=font(24), fill=OK)
|
||||||
y += 58
|
|
||||||
y = button(d, y, T("save"))
|
|
||||||
return img
|
return img
|
||||||
|
|
||||||
|
|
||||||
@@ -488,32 +575,87 @@ def cover_screen() -> Image.Image:
|
|||||||
return img
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
def switch_row(d: ImageDraw.ImageDraw, y: int, text: str, description: str,
|
||||||
|
checked: bool) -> int:
|
||||||
|
"""Riga con interruttore (componente TextSwitch dell'app): etichetta e
|
||||||
|
spiegazione a sinistra, interruttore a destra con lo stato dell'impianto."""
|
||||||
|
d.text((M, y), text, font=font(28), fill=PRIMARY)
|
||||||
|
d.text((M, y + 38), description, font=font(22), fill=SECONDARY)
|
||||||
|
pw, ph = 96, 44
|
||||||
|
x0 = W - M - pw
|
||||||
|
y0 = y + 8
|
||||||
|
d.rounded_rectangle([x0, y0, x0 + pw, y0 + ph], radius=ph / 2,
|
||||||
|
fill=ACCENT if checked else (48, 56, 68))
|
||||||
|
cx = x0 + pw - ph / 2 if checked else x0 + ph / 2
|
||||||
|
d.ellipse([cx - ph / 2 + 5, y0 + 5, cx + ph / 2 - 5, y0 + ph - 5],
|
||||||
|
fill=(238, 242, 246))
|
||||||
|
return y + 98
|
||||||
|
|
||||||
|
|
||||||
def commands_screen() -> Image.Image:
|
def commands_screen() -> Image.Image:
|
||||||
|
"""Comandi impianto della 0.6.0: gli interruttori dicono lo stato e si
|
||||||
|
cambiano in un tocco; i pulsanti restano per le azioni senza stato."""
|
||||||
img = background()
|
img = background()
|
||||||
d = ImageDraw.Draw(img)
|
d = ImageDraw.Draw(img)
|
||||||
status_bar(d)
|
status_bar(d)
|
||||||
page_header(d, T("other_commands"))
|
page_header(d, T("plant_commands"))
|
||||||
|
|
||||||
y = section_header(d, 156, T("current_state"))
|
y = section_header(d, 150, T("modes"))
|
||||||
for lab, val in ((T("away"), T("off")), (T("night_mode"), T("off")),
|
y = switch_row(d, y, T("away"), T("sw_away_desc"), False)
|
||||||
(T("forced_heating"), T("off")), (T("hot_water_active"), T("on")),
|
y = switch_row(d, y, T("forced_heating"), T("sw_forced_desc"), False)
|
||||||
(T("pump"), T("stopped")), (T("allowed_modes"), T("heating"))):
|
y = switch_row(d, y, T("heating"), T("sw_heating_desc"), True)
|
||||||
y = label_pair(d, y, lab, val)
|
|
||||||
|
|
||||||
y = section_header(d, y + 24, T("away"))
|
y = section_header(d, y + 10, T("dhw_section"))
|
||||||
y = button(d, y, T("turn_away_on"))
|
y = switch_row(d, y, T("hot_water_active"), T("sw_water_desc"), True)
|
||||||
y = button(d, y, T("turn_away_off"))
|
y = button(d, y, T("legionella"))
|
||||||
|
|
||||||
y = section_header(d, y + 24, T("noise"))
|
y = section_header(d, y + 10, T("noise"))
|
||||||
y = button(d, y, T("night_one_hour"))
|
y = button(d, y, T("night_one_hour"))
|
||||||
|
|
||||||
y = section_header(d, y + 24, T("heating"))
|
d.text((M, y + 12), T("switches_note"), font=font(22), fill=SECONDARY)
|
||||||
y = button(d, y, T("force_heating"), primary=True)
|
|
||||||
hint = T("confirm_hint")
|
|
||||||
d.text(((W - d.textlength(hint, font=font(22))) / 2, y - 4), hint, font=font(22), fill=SECONDARY)
|
|
||||||
return img
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
def setpoints_screen() -> Image.Image:
|
||||||
|
"""Regolazioni: un blocco per grandezza, con il valore attuale sempre
|
||||||
|
visibile, le preimpostazioni e la conferma."""
|
||||||
|
img = background()
|
||||||
|
d = ImageDraw.Draw(img)
|
||||||
|
status_bar(d)
|
||||||
|
page_header(d, T("setpoints"))
|
||||||
|
|
||||||
|
y = section_header(d, 150, T("heating_house"))
|
||||||
|
y = value_row(d, y, T("current_target"), T("keep_now"), dec(19.0) + " °C")
|
||||||
|
y = slider(d, y, (19.0 - 5) / (30 - 5), dec(19.0) + " °C", T("new_house_target"))
|
||||||
|
y = preset_row(d, y, ["16°", "17°", "18°", "19°", "20°", "21°"])
|
||||||
|
y = button(d, y, T("set_house_v").format(v=dec(19.0)), primary=True)
|
||||||
|
|
||||||
|
y = section_header(d, y + 6, T("dhw_section"))
|
||||||
|
y = value_row(d, y, T("dhw_target"), T("water_now").format(v=dec(50.6) + " °C"),
|
||||||
|
dec(55.0) + " °C")
|
||||||
|
y = slider(d, y, (55.0 - 45) / (60 - 45), dec(55.0) + " °C", T("target_label"))
|
||||||
|
y = preset_row(d, y, ["45°", "47°", "50°", "55°", "60°"])
|
||||||
|
y = button(d, y, T("set_water_v").format(v=dec(55.0)), primary=True)
|
||||||
|
|
||||||
|
y = section_header(d, y + 6, T("hot_water_boost"))
|
||||||
|
y = button(d, y, T("start_boost"))
|
||||||
|
y = button(d, y, T("stop_boost"))
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
def preset_row(d: ImageDraw.ImageDraw, y: int, valori: list) -> int:
|
||||||
|
"""Fila di valori rapidi: un tocco invece del cursore, per le dita fredde."""
|
||||||
|
gap = 12
|
||||||
|
bw = (W - 2 * M - (len(valori) - 1) * gap) // len(valori)
|
||||||
|
for i, testo in enumerate(valori):
|
||||||
|
x = M + i * (bw + gap)
|
||||||
|
d.rounded_rectangle([x, y, x + bw, y + 62], radius=16, fill=BTN_BG,
|
||||||
|
outline=BTN_EDGE, width=2)
|
||||||
|
tw = d.textlength(testo, font=font(24))
|
||||||
|
d.text((x + (bw - tw) / 2, y + 17), testo, font=font(24), fill=PRIMARY)
|
||||||
|
return y + 86
|
||||||
|
|
||||||
|
|
||||||
def data_screen() -> Image.Image:
|
def data_screen() -> Image.Image:
|
||||||
"""Pagina "Dati": gli stessi numeri della scheda Dati dell'app ufficiale."""
|
"""Pagina "Dati": gli stessi numeri della scheda Dati dell'app ufficiale."""
|
||||||
img = background()
|
img = background()
|
||||||
@@ -566,11 +708,18 @@ def plant_screen() -> Image.Image:
|
|||||||
status_bar(d)
|
status_bar(d)
|
||||||
page_header(d, T("plant"))
|
page_header(d, T("plant"))
|
||||||
|
|
||||||
y = 150
|
# La frase estesa dell'app ufficiale: sta qui, non in Home.
|
||||||
|
righe = wrap(d, T("status_relax"), font(24), W - 2 * M)
|
||||||
|
for i, riga in enumerate(righe):
|
||||||
|
d.text((M, 132 + i * 32), riga, font=font(24), fill=PRIMARY)
|
||||||
|
|
||||||
|
y = 150 + 32 * len(righe)
|
||||||
y = section_header(d, y, T("status"))
|
y = section_header(d, y, T("status"))
|
||||||
for lab, val in ((T("connection"), T("cloud")), (T("mode"), T("automatic")),
|
for lab, val in ((T("connection"), T("cloud")), (T("mode"), T("automatic")),
|
||||||
(T("away"), T("off")), (T("night_mode"), T("off")),
|
(T("away"), T("off")), (T("night_mode"), T("off")),
|
||||||
(T("forced_heating"), T("off"))):
|
(T("forced_heating"), T("off")), (T("manual_mode"), T("off")),
|
||||||
|
(T("defrost"), T("off")), (T("signature_lights"), T("on")),
|
||||||
|
(T("pump"), T("stopped")), (T("allowed_modes"), T("heating"))):
|
||||||
y = label_pair(d, y, lab, val)
|
y = label_pair(d, y, lab, val)
|
||||||
|
|
||||||
y = section_header(d, y + 20, T("accessories"))
|
y = section_header(d, y + 20, T("accessories"))
|
||||||
@@ -587,6 +736,9 @@ def plant_screen() -> Image.Image:
|
|||||||
|
|
||||||
y = section_header(d, y + 20, T("errors"))
|
y = section_header(d, y + 20, T("errors"))
|
||||||
d.text((M, y), T("no_errors"), font=font(24), fill=OK)
|
d.text((M, y), T("no_errors"), font=font(24), fill=OK)
|
||||||
|
|
||||||
|
y = section_header(d, y + 40, T("diagnostics"))
|
||||||
|
y = button(d, y, T("plant_check"))
|
||||||
return img
|
return img
|
||||||
|
|
||||||
|
|
||||||
@@ -602,7 +754,8 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
out = pathlib.Path("docs/anteprima") if lang == "en" else pathlib.Path("docs/anteprima") / lang
|
out = pathlib.Path("docs/anteprima") if lang == "en" else pathlib.Path("docs/anteprima") / lang
|
||||||
out.mkdir(parents=True, exist_ok=True)
|
out.mkdir(parents=True, exist_ok=True)
|
||||||
for name, fn in (("dashboard", main_screen), ("dati", data_screen),
|
for name, fn in (("dashboard", main_screen), ("regolazioni", setpoints_screen),
|
||||||
|
("dati", data_screen),
|
||||||
("impianto", plant_screen), ("comandi", commands_screen),
|
("impianto", plant_screen), ("comandi", commands_screen),
|
||||||
("impostazioni", settings_screen), ("copertina", cover_screen)):
|
("impostazioni", settings_screen), ("copertina", cover_screen)):
|
||||||
path = out / f"{name}.png"
|
path = out / f"{name}.png"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
|
|||||||
|
|
||||||
app->setOrganizationName(QStringLiteral("harbour"));
|
app->setOrganizationName(QStringLiteral("harbour"));
|
||||||
app->setApplicationName(QStringLiteral("aira"));
|
app->setApplicationName(QStringLiteral("aira"));
|
||||||
app->setApplicationVersion(QStringLiteral("0.5.0"));
|
app->setApplicationVersion(QStringLiteral("0.6.0"));
|
||||||
|
|
||||||
Settings settings;
|
Settings settings;
|
||||||
ApiClient api(&settings);
|
ApiClient api(&settings);
|
||||||
@@ -36,7 +36,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
|
|||||||
|
|
||||||
// Prima riga del log: dice subito se il device sta girando la build nuova
|
// Prima riga del log: dice subito se il device sta girando la build nuova
|
||||||
// o una stale (trappola nota del build engine).
|
// o una stale (trappola nota del build engine).
|
||||||
qDebug() << "harbour-aira v0.5.0 build" << __DATE__ << __TIME__;
|
qDebug() << "harbour-aira v0.6.0 build" << __DATE__ << __TIME__;
|
||||||
|
|
||||||
view->rootContext()->setContextProperty(QStringLiteral("appSettings"), &settings);
|
view->rootContext()->setContextProperty(QStringLiteral("appSettings"), &settings);
|
||||||
view->rootContext()->setContextProperty(QStringLiteral("api"), &api);
|
view->rootContext()->setContextProperty(QStringLiteral("api"), &api);
|
||||||
|
|||||||