0.6.7: icone sui tre tasti delle azioni rapide (fiamma, casa, manopole), disegnate in casa
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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 ----------------------------------------------- #
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||