Compare commits
4
Commits
36033daf3f
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4050b3b5d7 | ||
|
|
70fe2299ba | ||
|
|
75c61cace2 | ||
|
|
5305fab309 |
+7
-1
@@ -161,7 +161,7 @@ python3 tests/ui_contract_check.py http://192.168.3.21:8790
|
|||||||
senza telefono)
|
senza telefono)
|
||||||
- **48/48** verifiche offline del bridge (`bridge/test_bridge_commands.py`),
|
- **48/48** verifiche offline del bridge (`bridge/test_bridge_commands.py`),
|
||||||
comprese le conversioni Wh→kWh e degli importi delle statistiche
|
comprese le conversioni Wh→kWh e degli importi delle statistiche
|
||||||
- **190/190** stringhe tradotte nel catalogo italiano: il test carica il `.qm`
|
- **191/191** stringhe tradotte nel catalogo italiano: il test carica il `.qm`
|
||||||
compilato come fa `main.cpp` e verifica che in inglese l'etichetta di stato sia
|
compilato come fa `main.cpp` e verifica che in inglese l'etichetta di stato sia
|
||||||
la sorgente e in italiano quella tradotta, e che togliendo il catalogo si torni
|
la sorgente e in italiano quella tradotta, e che togliendo il catalogo si torni
|
||||||
all'inglese
|
all'inglese
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -149,13 +149,18 @@ Verified so far:
|
|||||||
including that a zone command always carries every zone, that absurd values
|
including that a zone command always carries every zone, that absurd values
|
||||||
are refused (40 °C, zone 3, text), and the Wh→kWh / money conversions of the
|
are refused (40 °C, zone 3, text), and the Wh→kWh / money conversions of the
|
||||||
statistics
|
statistics
|
||||||
- **190/190** strings translated in the Italian catalogue, checked by loading
|
- **191/191** strings translated in the Italian catalogue, checked by loading
|
||||||
the compiled `.qm` in the test the same way `main.cpp` does: source English
|
the compiled `.qm` in the test the same way `main.cpp` does: source English
|
||||||
in, Italian out, and back to English when the catalogue is removed
|
in, Italian out, and back to English when the catalogue is removed
|
||||||
- numbers, amounts and month names formatted through `QLocale`, so the decimal
|
- numbers, amounts and month names formatted through `QLocale`, so the decimal
|
||||||
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)
|
||||||
|
|||||||
+19
-1
@@ -275,8 +275,26 @@ si rilegge da solo (2 s): l'aggiornamento automatico, prima, non esisteva.
|
|||||||
|
|
||||||
**Stato breve ed esteso in C++** (`AiraJson::stateShort/stateLong/statusLevel`):
|
**Stato breve ed esteso in C++** (`AiraJson::stateShort/stateLong/statusLevel`):
|
||||||
logica pura, verificabile senza SDK e non duplicata fra Home e Impianto. Test
|
logica pura, verificabile senza SDK e non duplicata fra Home e Impianto. Test
|
||||||
del nucleo: **79/79** (erano 61). Stringhe: **190/190** tradotte. Anteprime
|
del nucleo: **79/79** (erano 61). Stringhe: **191/191** tradotte. Anteprime
|
||||||
rigenerate (nuova `regolazioni.png`) in italiano e inglese.
|
rigenerate (nuova `regolazioni.png`) in italiano e inglese.
|
||||||
|
|
||||||
|
**Copertina** (`qml/cover/CoverPage.qml`): pallino di stato come in Home, la
|
||||||
|
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
|
- 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.
|
||||||
|
|||||||
+11
-3
@@ -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
|
||||||
@@ -26,12 +34,12 @@ P9, P10 (diagnostica in Impianto), P11.
|
|||||||
Rimandato: P8 (azioni sulla copertina), in attesa della prova sul telefono.
|
Rimandato: P8 (azioni sulla copertina), in attesa della prova sul telefono.
|
||||||
|
|
||||||
Verifiche fatte qui: **79 verifiche del nucleo** (erano 61), `qmllint` pulito su
|
Verifiche fatte qui: **79 verifiche del nucleo** (erano 61), `qmllint` pulito su
|
||||||
tutti i file QML, 190/190 stringhe tradotte, anteprime rigenerate in inglese e
|
tutti i file QML, 191/191 stringhe tradotte, anteprime rigenerate in inglese e
|
||||||
italiano, contratto dei dati fra UI e bridge rispettato
|
italiano, contratto dei dati fra UI e bridge rispettato
|
||||||
(`tests/ui_contract_check.py`).
|
(`tests/ui_contract_check.py`).
|
||||||
Da provare sul telefono: che `TextSwitch` si istanzi davvero, l'altezza reale
|
Da provare sul telefono: che `TextSwitch` si istanzi davvero, l'altezza reale
|
||||||
della Home (riga di log con `contentHeight` contro l'altezza dello schermo) e
|
della Home (la riga `harbour-aira home: contenuto … schermo …` è già nel codice,
|
||||||
l'uso con una mano sola.
|
basta leggerla nei log) e l'uso con una mano sola.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 31 KiB |
+40
-8
@@ -11,14 +11,40 @@ CoverBackground {
|
|||||||
return api.formatValue(v)
|
return api.formatValue(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
// Colore del pallino: gli stessi criteri della Home (vedi MainPage.qml).
|
||||||
id: titleLabel
|
// Un dato vecchio e' ambra come un modo attivo: un dato vecchio non e' un
|
||||||
|
// dato buono, e la copertina e' il posto dove si guarda di sfuggita.
|
||||||
|
function statusColor() {
|
||||||
|
if (!api.hasData || api.stale)
|
||||||
|
return "orange"
|
||||||
|
var livello = api.statusLevel()
|
||||||
|
if (livello === 2)
|
||||||
|
return Theme.errorColor
|
||||||
|
if (livello === 1)
|
||||||
|
return Theme.highlightColor
|
||||||
|
return "#4caf50"
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: intestazione
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.topMargin: Theme.paddingLarge
|
anchors.topMargin: Theme.paddingLarge
|
||||||
text: "Aira"
|
spacing: Theme.paddingSmall
|
||||||
color: Theme.primaryColor
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
Rectangle {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: Theme.paddingSmall
|
||||||
|
height: width
|
||||||
|
radius: width / 2
|
||||||
|
color: cover.statusColor()
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Aira"
|
||||||
|
color: Theme.primaryColor
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
@@ -29,8 +55,9 @@ CoverBackground {
|
|||||||
Label {
|
Label {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
// Spenta quando i numeri sono vecchi: non deve sembrare fresca.
|
||||||
|
color: api.stale ? Theme.secondaryColor : Theme.primaryColor
|
||||||
text: cover.t("zone1_temperature") + " °C"
|
text: cover.t("zone1_temperature") + " °C"
|
||||||
color: Theme.primaryColor
|
|
||||||
font.pixelSize: Theme.fontSizeHuge
|
font.pixelSize: Theme.fontSizeHuge
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,8 +84,13 @@ CoverBackground {
|
|||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.bottomMargin: Theme.paddingMedium
|
anchors.bottomMargin: Theme.paddingMedium
|
||||||
text: qsTr("outdoor %1 °C").arg(cover.t("outdoor_temperature"))
|
// Qui c'e' posto per dire che i numeri non sono freschi.
|
||||||
color: Theme.secondaryColor
|
text: api.stale
|
||||||
|
? qsTr("Not updated · last %1").arg(api.lastUpdate)
|
||||||
|
: qsTr("outside %1 °C").arg(cover.t("outdoor_temperature"))
|
||||||
|
color: api.stale ? "orange" : Theme.secondaryColor
|
||||||
font.pixelSize: Theme.fontSizeExtraSmall
|
font.pixelSize: Theme.fontSizeExtraSmall
|
||||||
|
truncationMode: TruncationMode.Fade
|
||||||
|
width: parent.width - 2 * Theme.paddingMedium
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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.
|
||||||
@@ -294,4 +298,15 @@ Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NoticeBar { }
|
NoticeBar { }
|
||||||
|
|
||||||
|
// 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
|
||||||
|
onTriggered: console.log("harbour-aira home: contenuto", flick.contentHeight,
|
||||||
|
"schermo", height)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -78,7 +84,7 @@ make %{?_smp_mflags}
|
|||||||
# mancante NON fa fallire la compilazione: l'app resterebbe in inglese senza dire
|
# mancante NON fa fallire la compilazione: l'app resterebbe in inglese senza dire
|
||||||
# niente. Qui lo si rigenera comunque; il file finisce nella stessa cartella dove
|
# niente. Qui lo si rigenera comunque; il file finisce nella stessa cartella dove
|
||||||
# lo cerca src/translator.cpp. Il .ts si cerca con find perche' sfdk usa i
|
# lo cerca src/translator.cpp. Il .ts si cerca con find perche' sfdk usa i
|
||||||
# sorgenti live (shadow build) e la cartella puo' chiamarsi harbour-aira-0.5.0
|
# sorgenti live (shadow build) e la cartella puo' chiamarsi harbour-aira-0.6.0
|
||||||
# (tarball), harbour-aira (clone) o harbour-aira-main (archivio di Gitea).
|
# (tarball), harbour-aira (clone) o harbour-aira-main (archivio di Gitea).
|
||||||
TS=$(find %{_builddir}/.. -maxdepth 4 -name 'harbour-aira-it.ts' 2>/dev/null | head -1)
|
TS=$(find %{_builddir}/.. -maxdepth 4 -name 'harbour-aira-it.ts' 2>/dev/null | head -1)
|
||||||
if [ -z "$TS" ]; then
|
if [ -z "$TS" ]; then
|
||||||
|
|||||||
@@ -557,6 +557,8 @@ def cover_screen() -> Image.Image:
|
|||||||
card = [96, 430, W - 96, 430 + 470]
|
card = [96, 430, W - 96, 430 + 470]
|
||||||
d.rounded_rectangle(card, radius=34, fill=(14, 18, 24), outline=ACCENT, width=2)
|
d.rounded_rectangle(card, radius=34, fill=(14, 18, 24), outline=ACCENT, width=2)
|
||||||
d.text((card[0] + 34, card[1] + 30), T("app"), font=font(40, bold=True), fill=PRIMARY)
|
d.text((card[0] + 34, card[1] + 30), T("app"), font=font(40, bold=True), fill=PRIMARY)
|
||||||
|
# pallino di stato, come nella copertina vera (verde = tutto regolare)
|
||||||
|
d.ellipse([card[2] - 56, card[1] + 48, card[2] - 32, card[1] + 72], fill=OK)
|
||||||
d.line([(card[0] + 34, card[1] + 100), (card[2] - 34, card[1] + 100)],
|
d.line([(card[0] + 34, card[1] + 100), (card[2] - 34, card[1] + 100)],
|
||||||
fill=BTN_EDGE, width=2)
|
fill=BTN_EDGE, width=2)
|
||||||
y = card[1] + 132
|
y = card[1] + 132
|
||||||
|
|||||||
+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))
|
||||||
@@ -281,7 +281,7 @@
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../qml/pages/CommandsPage.qml" line="75"/>
|
<location filename="../qml/pages/CommandsPage.qml" line="75"/>
|
||||||
<source>Lowers the house and stops the hot water</source>
|
<source>Lowers the house and stops the hot water</source>
|
||||||
<translation>Abbassa la casa e spegne l'acqua calda</translation>
|
<translation>Abbassa la casa e spegne l'acqua calda</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/pages/CommandsPage.qml" line="88"/>
|
<location filename="../qml/pages/CommandsPage.qml" line="88"/>
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../qml/pages/CommandsPage.qml" line="121"/>
|
<location filename="../qml/pages/CommandsPage.qml" line="121"/>
|
||||||
<source>The heat pump keeps the water in temperature</source>
|
<source>The heat pump keeps the water in temperature</source>
|
||||||
<translation>Mantiene l'acqua in temperatura</translation>
|
<translation>Mantiene l'acqua in temperatura</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/pages/CommandsPage.qml" line="135"/>
|
<location filename="../qml/pages/CommandsPage.qml" line="135"/>
|
||||||
@@ -337,19 +337,24 @@
|
|||||||
<context>
|
<context>
|
||||||
<name>CoverPage</name>
|
<name>CoverPage</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/cover/CoverPage.qml" line="40"/>
|
<location filename="../qml/cover/CoverPage.qml" line="67"/>
|
||||||
<source>house · target %1 °C</source>
|
<source>house · target %1 °C</source>
|
||||||
<translation>casa · obiettivo %1 °C</translation>
|
<translation>casa · obiettivo %1 °C</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/cover/CoverPage.qml" line="49"/>
|
<location filename="../qml/cover/CoverPage.qml" line="76"/>
|
||||||
<source>DHW %1 °C · target %2 °C</source>
|
<source>DHW %1 °C · target %2 °C</source>
|
||||||
<translation>ACS %1 °C · obiettivo %2 °C</translation>
|
<translation>ACS %1 °C · obiettivo %2 °C</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../qml/cover/CoverPage.qml" line="60"/>
|
<location filename="../qml/cover/CoverPage.qml" line="89"/>
|
||||||
<source>outdoor %1 °C</source>
|
<source>Not updated · last %1</source>
|
||||||
<translation>est. %1 °C</translation>
|
<translation>Non aggiornato · ultimo %1</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/cover/CoverPage.qml" line="90"/>
|
||||||
|
<source>outside %1 °C</source>
|
||||||
|
<translation>esterna %1 °C</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
|
|||||||
Reference in New Issue
Block a user