0.6.1: la Home non si apriva (doppio Component.onCompleted); controllo automatico delle assegnazioni duplicate
This commit is contained in:
@@ -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)
|
posizione del simbolo di valuta seguono la lingua (verificato per entrambe)
|
||||||
- `qmllint` pulito su tutti i QML (valida solo la sintassi: senza il modulo
|
- `qmllint` pulito su tutti i QML (valida solo la sintassi: senza il modulo
|
||||||
Silica una proprietà inesistente passa in silenzio)
|
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
|
- provata dal vivo in **sola lettura** su un impianto Aira Home, statistiche
|
||||||
comprese: coincidono con quelle mostrate dall'app ufficiale per lo stesso
|
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,
|
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
|
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)
|
||||||
|
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/aira_bridge.py servizio HTTP/1.1 + JSON verso il cloud (gRPC)
|
||||||
bridge/setup_creds.py salva le credenziali Aira (file 600)
|
bridge/setup_creds.py salva le credenziali Aira (file 600)
|
||||||
bridge/aira_probe.py sonda di sola lettura: login + stato impianto
|
bridge/aira_probe.py sonda di sola lettura: login + stato impianto
|
||||||
|
|||||||
@@ -156,6 +156,11 @@ Verified so far:
|
|||||||
separator and the currency position follow the language (verified for both)
|
separator and the currency position follow the language (verified for both)
|
||||||
- `qmllint` clean on every QML file. Note it validates syntax only: without the
|
- `qmllint` clean on every QML file. Note it validates syntax only: without the
|
||||||
Silica module an invalid Silica property passes silently
|
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 —
|
- read-only queries against one real Aira Home unit, through the bridge —
|
||||||
including the cloud statistics, which match what the official app shows for
|
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
|
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
|
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)
|
||||||
|
tests/qml_check_bindings.py duplicate property assignments in the QML
|
||||||
bridge/ local HTTP bridge -> see bridge/README.md
|
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)
|
||||||
|
|||||||
@@ -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
|
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.
|
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
|
- Nessun comando nuovo all'impianto: anche questa versione resta **sola
|
||||||
lettura** finché non si prova l'invio dal telefono.
|
lettura** finché non si prova l'invio dal telefono.
|
||||||
|
|||||||
@@ -10,6 +10,14 @@ Immagini di questo studio (schemi, non anteprime): `docs/ux/`.
|
|||||||
|
|
||||||
## 0. Cosa è stato deciso e cosa è stato fatto (0.6.0)
|
## 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:
|
Le quattro scelte, applicate:
|
||||||
|
|
||||||
1. La frase di stato lunga sta nella pagina **Impianto**; in Home resta la riga
|
1. La frase di stato lunga sta nella pagina **Impianto**; in Home resta la riga
|
||||||
|
|||||||
@@ -78,9 +78,13 @@ Page {
|
|||||||
pageStack.push(Qt.resolvedUrl("DataPage.qml"))
|
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: {
|
Component.onCompleted: {
|
||||||
if (api.configured)
|
if (api.configured)
|
||||||
api.refresh()
|
api.refresh()
|
||||||
|
misura.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fuori dal pulsante: dentro il pulsante i figli finiscono nel contentItem.
|
// Fuori dal pulsante: dentro il pulsante i figli finiscono nel contentItem.
|
||||||
@@ -295,12 +299,10 @@ Page {
|
|||||||
|
|
||||||
NoticeBar { }
|
NoticeBar { }
|
||||||
|
|
||||||
Component.onCompleted: misura.start()
|
// 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
|
||||||
// La disposizione non e' definitiva in Component.onCompleted: si misura un
|
// stare in una schermata sola, se "contenuto" supera "schermo" c'e' ancora
|
||||||
// secondo dopo. Questa e' la riga da leggere alla prima build sul telefono:
|
// qualcosa da togliere (o da accettare, ma sapendolo).
|
||||||
// la Home deve stare in una schermata sola, se "contenuto" supera "schermo"
|
|
||||||
// c'e' ancora qualcosa da togliere (o da accettare, ma sapendolo).
|
|
||||||
Timer {
|
Timer {
|
||||||
id: misura
|
id: misura
|
||||||
interval: 1000
|
interval: 1000
|
||||||
|
|||||||
@@ -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.0
|
Version: 0.6.1
|
||||||
Release: 1
|
Release: 1
|
||||||
Group: Qt/Qt
|
Group: Qt/Qt
|
||||||
License: MIT
|
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.
|
no personal data is stored in the binary.
|
||||||
|
|
||||||
%changelog
|
%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
|
* 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
|
- 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
|
letture (casa e acqua calda) grandi e toccabili, tre azioni rapide; le due
|
||||||
|
|||||||
+2
-2
@@ -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.0"));
|
app->setApplicationVersion(QStringLiteral("0.6.1"));
|
||||||
|
|
||||||
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.0 build" << __DATE__ << __TIME__;
|
qDebug() << "harbour-aira v0.6.1 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);
|
||||||
|
|||||||
@@ -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))
|
||||||
Reference in New Issue
Block a user