diff --git a/bridge/aira_bridge.py b/bridge/aira_bridge.py index 6938033..48dd741 100644 --- a/bridge/aira_bridge.py +++ b/bridge/aira_bridge.py @@ -58,7 +58,7 @@ CREDS = BASE / "aira.json" TOKENS = BASE / "aira_tokens.json" POOL_CFG = BASE / "aira_config.json" -VERSION = "0.7.0" +VERSION = "0.7.1" # --------------------------------------------------------------------------- # @@ -693,6 +693,12 @@ def boost_restanti(st: dict) -> int: quando = datetime.fromisoformat(fine.replace("Z", "+00:00")) except ValueError: return 0 + # L'impianto manda la fine del boost ora con il fuso, ora senza (ora + # locale): senza, va letta nel fuso dell'IMPIANTO, altrimenti la + # sottrazione con "adesso" (che il fuso ce l'ha) solleva TypeError e + # /summary risponde 502 -- l'app resta senza dati ("Nessun dato"). + if quando.tzinfo is None: + quando = quando.replace(tzinfo=PLANT_TZ) return max(0, int((quando - datetime.now(timezone.utc)).total_seconds())) diff --git a/bridge/test_bridge_commands.py b/bridge/test_bridge_commands.py index 57e126b..d9bd460 100644 --- a/bridge/test_bridge_commands.py +++ b/bridge/test_bridge_commands.py @@ -231,6 +231,18 @@ check("boost in corso: restano ~1200 secondi", check("boost finito: zero secondi", b.boost_restanti({"hot_water_boost": {"end_time": "2026-01-01T00:00:00Z"}}) == 0) check("boost mai avviato: zero secondi", b.boost_restanti({}) == 0) + +# L'impianto puo' mandare la fine del boost SENZA fuso (ora locale). E' la forma +# che ha fatto rispondere 502 a /summary: la sottrazione con "adesso" (che il +# fuso ce l'ha) solleva TypeError, e l'app resta senza dati ("Nessun dato"). +# Il conto alla rovescia deve reggere entrambe le forme. +_fine_naive = (datetime.now(b.PLANT_TZ) + timedelta(seconds=1200)).strftime("%Y-%m-%dT%H:%M:%S") +check("boost con fine senza fuso (ora dell'impianto)", + abs(b.boost_restanti({"hot_water_boost": {"end_time": _fine_naive}}) - 1200) <= 3) +check("riassunto con fine senza fuso (la forma che dava 502)", + (b.summarize({"hot_water_boost": {"end_time": _fine_naive}}) or {}).get("boost_active") is True) +check("boost con fine fuori orario (fuso diverso)", + b.boost_restanti({"hot_water_boost": {"end_time": "2020-01-01T00:00:00"}}) == 0) riassunto = b.summarize({"hot_water_boost": {"end_time": fra}}) check("riassunto: dice che il boost e' in corso", riassunto.get("boost_active") is True) check("riassunto: porta i secondi del boost", diff --git a/docs/PIANO.md b/docs/PIANO.md index 7015127..e920025 100644 --- a/docs/PIANO.md +++ b/docs/PIANO.md @@ -410,7 +410,32 @@ respinto con 400 **prima** di partire. 4. mentre il boost è in corso si deve vedere un **conto alla rovescia da 30:00 a 00:00**. -## 12. Conseguenze dell'uso reale (0.7.0) +## 12. I tasti delle azioni rapide e la riga che li copriva — v0.7.1 + +Carlo: *"Quando premo un bottone la riga sotto si disallinea coprendo i +pulsanti"*. + +Non era la riga a spostarsi: era il **tasto** a essere piu' basso del suo +contenuto. In Silica `Button` dichiara `height: implicitHeight` e +`implicitHeight: Theme.itemSizeExtraSmall` (~56 px), un'altezza pensata per +un'icona *accanto* a un'etichetta corta. I tre tasti delle azioni rapide hanno +icona **e** etichetta (ingombro visibile ~110-160 px, quello che io avevo +misurato come "tasto 161 px"): il contenuto esce dalla scatola, e la riga +successiva della colonna -- le letture di contorno -- viene messa subito sotto +la scatola, cioe' **sopra** l'etichetta che sporge. Da li' l'impressione che si +"disallinei". + +**Come è stato fatto (0.7.1).** +- i tre tasti dichiarano `height: Theme.itemSizeMedium` (`Button.qml` del target + Sailfish letto dentro il motore di build, non indovinato); +- `tests/label_fit_check.py` adesso pretende l'altezza dichiarata su ogni tasto + che porta icona e testo: senza, il tema ne da' ~56 px contro ~110 di + contenuto; +- il registro dell'avvio (dopo un secondo) elenca **posizione e altezza di ogni + riga della Home**, cosi' se la disposizione non torna si legge dal registro + quale riga finisce dove invece di dedurlo da uno screenshot. + +## 13. Conseguenze dell'uso reale (0.7.0) Deciso con l'uso, non in anticipo: diff --git a/qml/pages/MainPage.qml b/qml/pages/MainPage.qml index 9ebc2e8..46babd9 100644 --- a/qml/pages/MainPage.qml +++ b/qml/pages/MainPage.qml @@ -261,6 +261,13 @@ Page { Button { id: boostBtn width: (parent.width - 2 * parent.spacing) / 3 + // In Silica il tasto piglia l'altezza dal tema + // (itemSizeExtraSmall ~56 px), ma icona E etichetta ne + // occupano ~110: senza questa riga l'etichetta esce dalla + // scatola e la riga successiva della colonna le finisce + // sopra (segnalato da Carlo: "la riga sotto copre i + // pulsanti"). Vedi tests/label_fit_check.py. + height: Theme.itemSizeMedium text: qsTr("Boost") // La casella dell'icona la decide il tema: senza, l'Image // prende la misura del file e si mangia il testo. @@ -274,6 +281,7 @@ Page { Button { id: assenzaBtn width: (parent.width - 2 * parent.spacing) / 3 + height: Theme.itemSizeMedium // Il pulsante dice cosa succede premendolo, non lo stato. // Etichette corte: nel tasto, largo un terzo di schermo, // icona e testo devono starci insieme. @@ -302,6 +310,7 @@ Page { Button { width: (parent.width - 2 * parent.spacing) / 3 + height: Theme.itemSizeMedium // "Comandi" (8 lettere) non ci sta accanto a un'icona in un // terzo di schermo: la pagina resta "Comandi impianto". text: qsTr("More") @@ -350,7 +359,20 @@ Page { Timer { id: misura interval: 1000 - onTriggered: console.log("harbour-aira home: contenuto", flick.contentHeight, - "schermo", height) + onTriggered: { + console.log("harbour-aira home: contenuto", flick.contentHeight, + "schermo", height) + // Ogni riga della Home con la sua posizione: se una riga finisce + // addosso a un'altra, qui si legge quale e di quanto. E' la riga da + // mandare al primo avvio se la disposizione non torna. + for (var i = 0; i < col.children.length; ++i) { + var r = col.children[i] + if (r.height === undefined) + continue + console.log("harbour-aira riga", i, "y", Math.round(r.y), + "h", Math.round(r.height), + r.text !== undefined ? JSON.stringify(r.text) : "") + } + } } } diff --git a/rpm/harbour-aira.spec b/rpm/harbour-aira.spec index fbad7d7..384767d 100644 --- a/rpm/harbour-aira.spec +++ b/rpm/harbour-aira.spec @@ -1,6 +1,6 @@ Name: harbour-aira Summary: Control your Aira heat pump through a local bridge -Version: 0.7.0 +Version: 0.7.1 Release: 1 Group: Qt/Qt License: MIT @@ -37,6 +37,13 @@ Address and token of the bridge live in the app settings (AppConfigLocation); no personal data is stored in the binary. %changelog +* Sun Oct 11 2026 Carlo - 0.7.1-1 +- i tre tasti delle azioni rapide dichiarano la loro altezza: in Silica il tasto + la piglia dal tema (Theme.itemSizeExtraSmall, ~56 px) e non dal contenuto, + quindi con icona E etichetta il testo (~110 px) usciva dalla scatola e la riga + di contorno finiva sopra le etichette (segnalato: "la riga sotto si disallinea + coprendo i pulsanti"). +- il registro dell'avvio elenca posizione e altezza di ogni riga della Home. * Sun Oct 11 2026 Carlo - 0.7.0-1 - assenza con le date: popup con inizio e fine (DatePickerDialog, componenti verificati nella guida Silica dell'SDK). Il bridge manda inizio e fine come diff --git a/src/main.cpp b/src/main.cpp index 36a9792..ea82147 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,7 +21,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[]) app->setOrganizationName(QStringLiteral("harbour")); app->setApplicationName(QStringLiteral("aira")); - app->setApplicationVersion(QStringLiteral("0.7.0")); + app->setApplicationVersion(QStringLiteral("0.7.1")); Settings 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 // o una stale (trappola nota del build engine). - qDebug() << "harbour-aira v0.7.0 build" << __DATE__ << __TIME__; + qDebug() << "harbour-aira v0.7.1 build" << __DATE__ << __TIME__; view->rootContext()->setContextProperty(QStringLiteral("appSettings"), &settings); view->rootContext()->setContextProperty(QStringLiteral("api"), &api); diff --git a/tests/label_fit_check.py b/tests/label_fit_check.py index 631ef39..3fceaf1 100644 --- a/tests/label_fit_check.py +++ b/tests/label_fit_check.py @@ -31,7 +31,7 @@ FONT = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" def testi_dei_tasti(qml: str): - """I testi dei tasti che hanno un'icona, nell'ordine in cui appaiono.""" + """I tasti con icona: (testo, altezza dichiarata o None), in ordine.""" fuori = [] for blocco in re.finditer(r"Button\s*\{(.*?)\n\s*\}", qml, re.S): corpo = blocco.group(1) @@ -40,7 +40,11 @@ def testi_dei_tasti(qml: str): riga = re.search(r"text:\s*(.+)", corpo) if not riga: continue - fuori.extend(re.findall(r'qsTr\("([^"]+)"\)', riga.group(1))) + testo = re.findall(r'qsTr\("([^"]+)"\)', riga.group(1)) + if not testo: + continue + alt = re.search(r"^\s*height:\s*(.+)$", corpo, re.M) + fuori.append((testo[0], alt.group(1).strip() if alt else None)) return fuori @@ -62,13 +66,24 @@ def main(): qml = (RADICE / "qml" / "pages" / "MainPage.qml").read_text(encoding="utf-8") ts = (RADICE / "translations" / "harbour-aira-it.ts").read_text(encoding="utf-8") it = traduzioni(ts) - sorgenti = testi_dei_tasti(qml) - if len(sorgenti) < 3: - print("ESITO: trovati %d testi con icona, ne servono 3" % len(sorgenti)) + origine = testi_dei_tasti(qml) + if len(origine) < 3: + print("ESITO: trovati %d testi con icona, ne servono 3" % len(origine)) return 1 + sorgenti = [t for t, _ in origine] - disponibile = TASTO - 2 * RIENTRO - ICONA - DISTACCO problema = 0 + + # 1. L'altezza. In Silica il tasto la piglia dal tema + # (Theme.itemSizeExtraSmall, ~56 px) e non dal contenuto: con icona E + # etichetta il contenuto ne occupa ~110, esce dalla scatola, e la riga + # successiva della colonna viene messa sopra la scatola -- cioe' sopra + # l'etichetta che sporge. Chi ha entrambi deve dichiarare l'altezza. + for testo, alt in origine: + if not alt: + print(" %-9s nessuna altezza dichiarata: il tema ne da' ~56 px, il contenuto ~110" % testo) + problema += 1 + disponibile = TASTO - 2 * RIENTRO - ICONA - DISTACCO f = ImageFont.truetype(FONT, CARATTERE) def largo(testo):