0.6.7: icone sui tre tasti delle azioni rapide (fiamma, casa, manopole), disegnate in casa

This commit is contained in:
2026-10-11 10:08:43 +02:00
parent afb2878a8c
commit f1fdc9dfdb
14 changed files with 130 additions and 10 deletions
+4 -2
View File
@@ -20,8 +20,9 @@ e ripiega sull'inglese; la si può scegliere anche nelle impostazioni dell'app.
- **Home**, una schermata da guardare: pallino colorato con lo stato in una riga - **Home**, una schermata da guardare: pallino colorato con lo stato in una riga
("Tutto regolare", "Sta scaldando l'acqua"), l'ora dell'ultimo aggiornamento, le ("Tutto regolare", "Sta scaldando l'acqua"), l'ora dell'ultimo aggiornamento, le
due letture che contano (casa e acqua calda, ognuna con il suo obiettivo) due letture che contano (casa e acqua calda, ognuna con il suo obiettivo)
affiancate e **toccabili**, e tre azioni rapide: boost, assenza, comandi affiancate e **toccabili**, e tre azioni rapide — boost, assenza, comandi
impianto. Sta in una schermata, senza scorrere: i comandi hanno pagine loro impianto — ognuna con la sua icona. Sta in una schermata, senza scorrere: i
comandi hanno pagine loro
- **Regolazioni**: obiettivo casa e obiettivo acqua calda, ognuno con il valore - **Regolazioni**: obiettivo casa e obiettivo acqua calda, ognuno con il valore
che l'impianto sta tenendo adesso, lo slider (la proposta: il valore vero non si 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, muove), una fila di valori rapidi per le dita fredde e il pulsante di conferma,
@@ -228,6 +229,7 @@ qml/components/ValueRow.qml titolo e sottotitolo a sinistra, valore grande a d
qml/components/ReadingCell.qml lettura toccabile: titolo, valore grande, barra qml/components/ReadingCell.qml lettura toccabile: titolo, valore grande, barra
qml/components/NoticeBar.qml messaggi dei comandi, ancorati in fondo 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
qml/icons/ i tre glifi delle azioni rapide (disegnati, non dal tema)
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)
tests/qml_check_bindings.py assegnazioni duplicate di proprieta' nei QML tests/qml_check_bindings.py assegnazioni duplicate di proprieta' nei QML
+4 -3
View File
@@ -14,9 +14,9 @@ status, domestic hot water, and the house target temperature per zone.
- **Home**, one screen to look at: a coloured dot with a one-line status - **Home**, one screen to look at: a coloured dot with a one-line status
("Everything is fine", "Heating the hot water"), the time of the last update, ("Everything is fine", "Heating the hot water"), the time of the last update,
the two readings that matter (house and domestic hot water, each with its the two readings that matter (house and domestic hot water, each with its
target) side by side and **tappable**, and three quick actions: boost, away target) side by side and **tappable**, and three quick actions — boost, away
mode and the plant commands. It fits without scrolling — the controls live in mode and the plant commands, each with its own icon. It fits without scrolling
their own pages — the controls live in their own pages
- **Setpoints** page: house target and hot water target, each with the value the - **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 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 move), a row of quick values for cold fingers and a confirmation button that
@@ -243,6 +243,7 @@ qml/components/ValueRow.qml title + subtitle left, big value right
qml/components/ReadingCell.qml tappable reading: title, big value, charge bar qml/components/ReadingCell.qml tappable reading: title, big value, charge bar
qml/components/NoticeBar.qml command messages, anchored to the bottom edge 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
qml/icons/ the three quick-action glyphs (drawn, not from the theme)
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)
tests/qml_check_bindings.py duplicate property assignments in the QML tests/qml_check_bindings.py duplicate property assignments in the QML
+9 -1
View File
@@ -344,6 +344,14 @@ toccato e la pagina mostra solo quello — l'acqua porta con sé anche il boost,
(`tests/qml_focus_check.py`), perché questa parte della UI non è eseguibile senza (`tests/qml_focus_check.py`), perché questa parte della UI non è eseguibile senza
il kit Silica. il kit Silica.
**Stato attuale (0.6.6).** 85/85 verifiche del nucleo, 48/48 del bridge, **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.
**Stato attuale (0.6.7).** 85/85 verifiche del nucleo, 48/48 del bridge,
191/191 stringhe tradotte, `qmllint` pulito, contratto UI↔bridge rispettato. 191/191 stringhe tradotte, `qmllint` pulito, contratto UI↔bridge rispettato.
Nessun comando è stato inviato all'impianto: tutto quanto sopra è a sola lettura. Nessun comando è stato inviato all'impianto: tutto quanto sopra è a sola lettura.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

+3
View File
@@ -248,6 +248,7 @@ Page {
id: boostBtn id: boostBtn
width: (parent.width - 2 * parent.spacing) / 3 width: (parent.width - 2 * parent.spacing) / 3
text: qsTr("Boost") text: qsTr("Boost")
icon.source: "../icons/quick-boost.png"
onClicked: remorse.execute(boostBtn, qsTr("Starting DHW boost"), onClicked: remorse.execute(boostBtn, qsTr("Starting DHW boost"),
function() { api.sendCommand("hot_water_boost_on") }) function() { api.sendCommand("hot_water_boost_on") })
} }
@@ -257,6 +258,7 @@ Page {
width: (parent.width - 2 * parent.spacing) / 3 width: (parent.width - 2 * parent.spacing) / 3
// Il pulsante dice cosa succede premendolo, non lo stato. // Il pulsante dice cosa succede premendolo, non lo stato.
text: page.raw("away_mode") ? qsTr("Back home") : qsTr("Away mode") text: page.raw("away_mode") ? qsTr("Back home") : qsTr("Away mode")
icon.source: "../icons/quick-away.png"
onClicked: { onClicked: {
if (page.raw("away_mode")) { if (page.raw("away_mode")) {
remorse.execute(assenzaBtn, qsTr("Away mode off"), remorse.execute(assenzaBtn, qsTr("Away mode off"),
@@ -271,6 +273,7 @@ Page {
Button { Button {
width: (parent.width - 2 * parent.spacing) / 3 width: (parent.width - 2 * parent.spacing) / 3
text: qsTr("Commands") text: qsTr("Commands")
icon.source: "../icons/quick-commands.png"
onClicked: page.openCommands() onClicked: page.openCommands()
} }
} }
+6 -1
View File
@@ -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.6.6 Version: 0.6.7
Release: 1 Release: 1
Group: Qt/Qt Group: Qt/Qt
License: MIT License: MIT
@@ -37,6 +37,11 @@ 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
* Sun Oct 11 2026 Carlo <carlo@carlobaratto.it> - 0.6.7-1
- Home: i tre tasti delle azioni rapide hanno un'icona (fiamma = boost, casa =
assenza, tre manopole = comandi). Il tema Sailfish non ha icone per questi
concetti (nell'elenco ufficiale non c'e' nessuna fiamma): sono disegnate con
Pillow in scripts/make_glyphs.py, PNG bianche su trasparente in qml/icons/
* Sun Oct 11 2026 Carlo <carlo@carlobaratto.it> - 0.6.6-1 * Sun Oct 11 2026 Carlo <carlo@carlobaratto.it> - 0.6.6-1
- Home: toccare "Casa" o "Acqua calda" apre le regolazioni di quell'oggetto e - Home: toccare "Casa" o "Acqua calda" apre le regolazioni di quell'oggetto e
non piu' casa e acqua insieme; l'acqua porta con se' il boost, che e' suo. non piu' casa e acqua insieme; l'acqua porta con se' il boost, che e' suo.
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""Disegna le icone dei tre tasti delle opzioni rapide in Home.
Stanno in qml/icons/ perche' il pulsante Silica vuole un'immagine, non un disegno
QML. PNG e non SVG di proposito: il PNG si vede di sicuro, l'SVG dipende dal
supporto SVG dentro il sandbox dell'app.
Bianche su trasparente: sul tema Sailfish i tasti sono scuri e le icone di
sistema sono cosi'. Tre concetti che il tema Sailfish non ha (nell'elenco
ufficiale non c'e' nessuna fiamma, nessuna goccia): boost = fiamma, assenza =
casa (la stessa casa dell'icona dell'app), comandi = tre righe con le manopole.
Uso: python3 scripts/make_glyphs.py
"""
import math
import pathlib
from PIL import Image, ImageDraw
BASE = 688 # si disegna grande e si riduce: bordi puliti a ogni taglia
OUT = 172 # come l'icona piu' grande dell'app, abbondante per il tasto
DEST = pathlib.Path(__file__).resolve().parent.parent / "qml" / "icons"
BIANCO = (255, 255, 255, 255)
VUOTO = (0, 0, 0, 0)
def nuova():
return Image.new("RGBA", (BASE, BASE), VUOTO)
def fiamma(d, cx, cy, w, h):
"""Boost: fiamma — stretta, punta piegata a sinistra, fondo tondo."""
r = 0.26 * w
alto = h - 0.55 * r
fondo = cy + h / 2
destra, sinistra = [], []
passi = 160
for i in range(passi + 1):
t = i / passi
mezza = (w / 2) * math.sin(math.pi * (t ** 0.7))
piega = (w * 0.18) * (t ** 1.7) # la punta pende a sinistra
y = fondo - alto * t
destra.append((cx + mezza - piega, y))
sinistra.append((cx - mezza - piega, y))
d.polygon(destra + list(reversed(sinistra)), fill=BIANCO)
# fondo arrotondato: copre la punta stretta in basso
d.ellipse([cx - r, fondo - 0.55 * r, cx + r, fondo + 0.55 * r], fill=BIANCO)
def casa(d, cx, cy, w, h):
"""Assenza: casa con la porta (senza la porta si legge come una freccia)."""
cima = cy - h / 2
base = cy + h / 2
spalle = cima + h * 0.36 # dove il tetto incontra il muro
d.polygon([(cx - w * 0.52, spalle), (cx, cima), (cx + w * 0.52, spalle)], fill=BIANCO)
d.rectangle([cx - w * 0.33, spalle - 1, cx + w * 0.33, base], fill=BIANCO)
# la porta: e' quello che la distingue da una freccia
d.rectangle([cx - w * 0.20 / 2, base - h * 0.30, cx + w * 0.20 / 2, base], fill=VUOTO)
def comandi(d, cx, cy, w, h):
"""Comandi: tre righe con la manopola in posizioni diverse."""
manopole = (-0.18, 0.20, -0.06)
for i, posizione in enumerate(manopole):
y = cy - h / 2 + h * (0.16 + 0.34 * i)
spessore = h * 0.115
d.rounded_rectangle([cx - w / 2, y - spessore / 2, cx + w / 2, y + spessore / 2],
radius=spessore / 2, fill=BIANCO)
kx = cx + w * posizione
r = h * 0.13
d.ellipse([kx - r * 1.5, y - r * 1.5, kx + r * 1.5, y + r * 1.5], fill=VUOTO)
d.ellipse([kx - r, y - r, kx + r, y + r], fill=BIANCO)
def main():
DEST.mkdir(parents=True, exist_ok=True)
disegni = (
("quick-boost.png", fiamma, 0.58),
("quick-away.png", casa, 0.74),
("quick-commands.png", comandi, 0.82),
)
lato = BASE * 0.72 # margine generoso: il tasto ha il suo
for nome, disegno, strettezza in disegni:
img = nuova()
d = ImageDraw.Draw(img)
disegno(d, BASE / 2, BASE / 2, lato * strettezza, lato)
img = img.resize((OUT, OUT), Image.LANCZOS)
img.save(DEST / nome)
print("scritto", (DEST / nome).relative_to(DEST.parent.parent))
if __name__ == "__main__":
main()
+9 -1
View File
@@ -503,12 +503,20 @@ def main_screen() -> Image.Image:
# ---- azioni rapide ----------------------------------------------------- # # ---- azioni rapide ----------------------------------------------------- #
y = section_header(d, y, T("quick_actions")) y = section_header(d, y, T("quick_actions"))
bw = (W - 2 * M - 2 * gap) // 3 bw = (W - 2 * M - 2 * gap) // 3
# icona a sinistra e testo di fianco, come fa il pulsante Silica
glifi = ("quick-boost.png", "quick-away.png", "quick-commands.png")
for i, testo in enumerate((T("boost"), T("away_btn"), T("commands"))): for i, testo in enumerate((T("boost"), T("away_btn"), T("commands"))):
x = M + i * (bw + gap) x = M + i * (bw + gap)
d.rounded_rectangle([x, y, x + bw, y + 74], radius=18, fill=BTN_BG, d.rounded_rectangle([x, y, x + bw, y + 74], radius=18, fill=BTN_BG,
outline=BTN_EDGE, width=2) outline=BTN_EDGE, width=2)
icona = Image.open(OUT.parent.parent / "qml" / "icons" / glifi[i]).convert("RGBA")
lato = 42
icona = icona.resize((lato, lato), Image.LANCZOS)
tw = d.textlength(testo, font=font(24)) tw = d.textlength(testo, font=font(24))
d.text((x + (bw - tw) / 2, y + 23), testo, font=font(24), fill=PRIMARY) blocco = lato + 12 + tw
x0 = x + (bw - blocco) / 2
img.paste(icona, (int(x0), int(y + (74 - lato) / 2)), icona)
d.text((x0 + lato + 12, y + 23), testo, font=font(24), fill=PRIMARY)
y += 104 y += 104
# ---- letture di contorno ----------------------------------------------- # # ---- letture di contorno ----------------------------------------------- #
+2 -2
View File
@@ -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.6.6")); app->setApplicationVersion(QStringLiteral("0.6.7"));
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.6.6 build" << __DATE__ << __TIME__; qDebug() << "harbour-aira v0.6.7 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);