0.6.1: la Home non si apriva (doppio Component.onCompleted); controllo automatico delle assegnazioni duplicate

This commit is contained in:
2026-10-10 12:22:15 +02:00
parent 70fe2299ba
commit 4050b3b5d7
8 changed files with 168 additions and 9 deletions
+6
View File
@@ -169,6 +169,11 @@ python3 tests/ui_contract_check.py http://192.168.3.21:8790
posizione del simbolo di valuta seguono la lingua (verificato per entrambe)
- `qmllint` pulito su tutti i QML (valida solo la sintassi: senza il modulo
Silica una proprietà inesistente passa in silenzio)
- **nessuna assegnazione duplicata** nei QML (`tests/qml_check_bindings.py`,
13 file): assegnare due volte la stessa proprietà nello stesso oggetto — due
`Component.onCompleted` in una pagina, tipicamente — fa fallire la creazione
dell'oggetto in Qt 5.6, e sul telefono si vede solo `Type MainPage unavailable`
senza il nome del file. Il controllo impiega millisecondi e dice la riga
- provata dal vivo in **sola lettura** su un impianto Aira Home, statistiche
comprese: coincidono con quelle mostrate dall'app ufficiale per lo stesso
periodo (calore prodotto 70,8 kWh, elettricità 29,3 kWh, COP acqua calda 4,42,
@@ -208,6 +213,7 @@ qml/components/NoticeBar.qml messaggi dei comandi, ancorati in fondo
qml/components/ActionButton.qml pulsante di comando, con conferma opzionale
tests/core_test.cpp verifiche del nucleo (headless)
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
bridge/aira_bridge.py servizio HTTP/1.1 + JSON verso il cloud (gRPC)
bridge/setup_creds.py salva le credenziali Aira (file 600)
bridge/aira_probe.py sonda di sola lettura: login + stato impianto
+6
View File
@@ -156,6 +156,11 @@ Verified so far:
separator and the currency position follow the language (verified for both)
- `qmllint` clean on every QML file. Note it validates syntax only: without the
Silica module an invalid Silica property passes silently
- **no duplicate property assignment** in the QML (`tests/qml_check_bindings.py`,
13 files): assigning the same property twice in one object — two
`Component.onCompleted` in a page, typically — makes Qt 5.6 fail to create the
object, and all you see on the device is `Type MainPage unavailable` with no
file named. The check runs in milliseconds and names the line
- read-only queries against one real Aira Home unit, through the bridge —
including the cloud statistics, which match what the official app shows for
the same period (heat produced 70.8 kWh, electricity 29.3 kWh, hot-water COP
@@ -223,6 +228,7 @@ qml/components/NoticeBar.qml command messages, anchored to the bottom edge
qml/components/ActionButton.qml command button, optional confirmation
tests/core_test.cpp core checks (headless)
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
bridge/ local HTTP bridge -> see bridge/README.md
translations/ harbour-aira-it.ts (English is the source language)
docs/PIANO.md project plan and decisions (Italian)
+13
View File
@@ -283,5 +283,18 @@ 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.
+8
View File
@@ -10,6 +10,14 @@ Immagini di questo studio (schemi, non anteprime): `docs/ux/`.
## 0. Cosa è stato deciso e cosa è stato fatto (0.6.0)
**Prima build sul telefono:** la Home non si apriva — `Type MainPage unavailable`.
Causa: due assegnazioni di `Component.onCompleted` nello stesso oggetto, che in
Qt 5.6 impediscono la creazione della pagina. Corretto nella 0.6.1 e coperto da
`tests/qml_check_bindings.py`. Tutto il resto dell'avvio funziona (configurazione
letta, catalogo italiano caricato). Il primo giro sul device ha quindi già dato
la sua risposta più importante: la riga `harbour-aira home: contenuto … schermo …`
arriva nei log e si può leggere.
Le quattro scelte, applicate:
1. La frase di stato lunga sta nella pagina **Impianto**; in Home resta la riga
+8 -6
View File
@@ -78,9 +78,13 @@ Page {
pageStack.push(Qt.resolvedUrl("DataPage.qml"))
}
// UNA sola assegnazione: in Qt 5.6 assegnare Component.onCompleted due volte
// e' un errore di compilazione del file, e la pagina non si crea piu'
// ("Type MainPage unavailable" e app senza Home).
Component.onCompleted: {
if (api.configured)
api.refresh()
misura.start()
}
// Fuori dal pulsante: dentro il pulsante i figli finiscono nel contentItem.
@@ -295,12 +299,10 @@ Page {
NoticeBar { }
Component.onCompleted: misura.start()
// La disposizione non e' definitiva in Component.onCompleted: si misura un
// secondo dopo. Questa e' la riga da leggere alla prima build sul telefono:
// la Home deve stare in una schermata sola, se "contenuto" supera "schermo"
// c'e' ancora qualcosa da togliere (o da accettare, ma sapendolo).
// La disposizione non e' definitiva subito: si misura un secondo dopo.
// Questa e' la riga da leggere alla prima build sul telefono: la Home deve
// stare in una schermata sola, se "contenuto" supera "schermo" c'e' ancora
// qualcosa da togliere (o da accettare, ma sapendolo).
Timer {
id: misura
interval: 1000
+7 -1
View File
@@ -1,6 +1,6 @@
Name: harbour-aira
Summary: Control your Aira heat pump through a local bridge
Version: 0.6.0
Version: 0.6.1
Release: 1
Group: Qt/Qt
License: MIT
@@ -37,6 +37,12 @@ Address and token of the bridge live in the app settings (AppConfigLocation);
no personal data is stored in the binary.
%changelog
* Sat Oct 10 2026 Carlo <carlo@carlobaratto.it> - 0.6.1-1
- correzione: la Home non si apriva ("Type MainPage unavailable", app senza
pagina iniziale). Causa: due assegnazioni di Component.onCompleted nello
stesso oggetto; in Qt 5.6 la pagina non si crea piu' e il messaggio non dice
quale sia il file. Ora un controllo automatico le intercetta in pochi
millisecondi (tests/qml_check_bindings.py), prima della build
* 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
+2 -2
View File
@@ -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.6.0"));
app->setApplicationVersion(QStringLiteral("0.6.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.6.0 build" << __DATE__ << __TIME__;
qDebug() << "harbour-aira v0.6.1 build" << __DATE__ << __TIME__;
view->rootContext()->setContextProperty(QStringLiteral("appSettings"), &settings);
view->rootContext()->setContextProperty(QStringLiteral("api"), &api);
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""Assegnazioni duplicate di proprieta' negli stessi oggetti QML.
Perche' esiste: in Qt 5.6 assegnare due volte la stessa proprieta' di un oggetto
QML (tipico: due `Component.onCompleted` nella stessa pagina) non e' un errore
innocuo: il file compila con un warning "Property value set multiple times" e
l'oggetto NON si crea piu'. Dall'esterno si vede solo
harbour-aira.qml:9:30: Type MainPage unavailable
cioe' l'app che si apre senza la pagina iniziale, senza dire il perche'.
Il kit Silica non vede niente di tutto questo (qmllint conosce il QML, non il
significato di questi binding), quindi il controllo sta qui e gira in pochi
millisecondi.
Uso:
python3 tests/qml_check_bindings.py [cartella_qml]
python3 tests/qml_check_bindings.py --dettagli # elenca anche l'ok
Esce 1 se trova duplicati, 0 se e' pulito.
"""
import pathlib
import re
import sys
# Riga che apre un oggetto QML: un componente (maiuscola) oppure un gruppo di
# proprieta' noto. Il corpo di un gestore (onClicked:) o di una funzione e' JS,
# e li' dentro i "duplicati" sono normali (ternari, oggetti letterali, rami).
APRE_OGGETTO = re.compile(r"^\s*([A-Z][A-Za-z0-9_]*)\s*\{")
APRE_GRUPPO = re.compile(r"^\s*(anchors|font|Layout\.[A-Za-z]+)\s*\{")
APRE_JS = re.compile(r"^\s*(function\s+\w+|if\b|else\b|for\b|while\b|switch\b|do\b|try\b|catch\b)\s*[\({]")
APRE_GESTORE = re.compile(r"^\s*on[A-Z][A-Za-z0-9_]*\s*:")
PROPRIETA = re.compile(r"^\s*((?:readonly\s+)?property\s+\S+\s+([A-Za-z_]\w*)|signal\s+(\w+)|function\s+(\w+)|([A-Za-z_][\w.]*))\s*:")
SENZA_COMMENTO = re.compile(r"//.*$")
def chiave(riga: str):
"""Nome della proprieta' assegnata dalla riga, o None."""
m = PROPRIETA.match(SENZA_COMMENTO.sub("", riga))
if not m:
return None
return m.group(2) or m.group(3) or m.group(4) or m.group(5)
def controlla(testo: str):
"""Ritorna la lista di (riga, chiave) duplicate nello stesso oggetto."""
pila = [("qml", {})] # (tipo ambiente, chiavi viste)
problemi = []
for numero, riga in enumerate(testo.splitlines(), 1):
pulita = SENZA_COMMENTO.sub("", riga)
if not pulita.strip():
continue
# Chiusura: si esce dall'ambiente corrente.
while pulita.strip().startswith("}") and len(pila) > 1:
pila.pop()
pulita = pulita.strip()[1:]
if not pulita.strip():
break
if not pulita.strip():
continue
ambiente, chiavi = pila[-1]
if ambiente == "qml":
k = chiave(pulita)
if k:
if k in chiavi:
problemi.append((numero, k))
else:
chiavi[k] = numero
# Apertura di un nuovo ambiente: il contenuto e' QML solo se la riga
# apre un oggetto o un gruppo; gestori e funzioni aprono JS.
apre_js = bool(APRE_JS.match(pulita)) or bool(APRE_GESTORE.match(pulita))
apre_qml = (bool(APRE_OGGETTO.match(pulita)) or bool(APRE_GRUPPO.match(pulita))) \
and not apre_js
# Una proprieta' il cui valore e' un oggetto letterale JS non e' QML.
if ":" in pulita and "{" in pulita and not apre_qml and not apre_js:
apre_js = True
if pulita.count("{") > pulita.count("}"):
pila.append(("qml" if apre_qml else "js", {}))
return problemi
def main(argv):
argomenti = [a for a in argv[1:] if not a.startswith("--")]
radice = pathlib.Path(argomenti[0] if argomenti else "qml")
dettagli = "--dettagli" in argv
file_qml = sorted(radice.rglob("*.qml"))
if not file_qml:
print("nessun file QML in", radice)
return 1
totale = 0
for percorso in file_qml:
problemi = controlla(percorso.read_text(encoding="utf-8"))
relative = percorso.relative_to(radice.parent if radice.name == "qml" else radice)
if problemi:
totale += len(problemi)
print("%s: %d assegnazioni duplicate" % (relative, len(problemi)))
for numero, k in problemi:
print(" riga %d: '%s' gia' assegnata in questo oggetto" % (numero, k))
elif dettagli:
print("%s: pulito" % relative)
print("---")
if totale:
print("ESITO: %d assegnazioni duplicate (l'oggetto non si crea: "
"vedi Qt 5.6, 'Property value set multiple times')" % totale)
return 1
print("ESITO: nessuna assegnazione duplicata in %d file" % len(file_qml))
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))