0.6.2: pallino rosso solo per un allarme che ferma la macchina (avviso = ambra)
Il pallino era rosso per qualunque voce nell'elenco errori: un semplice avviso (compressore limitato dalla temperatura esterna, caso reale del 10/10) sembrava un guasto. Ora il ponte espone in /summary anche error_severity e alarm_stopping, e il livello 3 (rosso) e' riservato agli allarmi che fermano la macchina o di severita' CRITICAL/ERROR; avviso = ambra. Copertina allineata. 6 verifiche nuove nell'harness del nucleo (avviso/stop/critical/error/ precedenza sul modo attivo/gravita' ignota = ambra).
This commit is contained in:
+25
-1
@@ -53,7 +53,7 @@ CREDS = BASE / "aira.json"
|
||||
TOKENS = BASE / "aira_tokens.json"
|
||||
POOL_CFG = BASE / "aira_config.json"
|
||||
|
||||
VERSION = "0.4.0"
|
||||
VERSION = "0.6.2"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
@@ -195,6 +195,23 @@ def summarize(st: dict) -> dict:
|
||||
|
||||
errs = st.get("errors") or []
|
||||
|
||||
# ---- gravita' degli errori -------------------------------------------
|
||||
# La severita' arriva come stringa ("SEVERITY_WARNING") e le voci possono
|
||||
# essere piu' di una: si normalizza al nome nudo e si tiene la peggiore.
|
||||
# "allarme che ferma" NON si deduce dalla severita': sta nei metadati
|
||||
# (compreso quello del compressore).
|
||||
_GRAVITA = {"WARNING": 1, "ERROR": 2, "CRITICAL": 3}
|
||||
_meta = st.get("error_metadata") or {}
|
||||
alarm_stopping = bool(_meta.get("hp_has_stopping_alarms")) or bool(
|
||||
_meta.get("compressor_has_stopping_alarm"))
|
||||
severita = ""
|
||||
for _voce in errs if isinstance(errs, list) else []:
|
||||
_nome = str((_voce or {}).get("severity") or "")
|
||||
if _nome.startswith("SEVERITY_"):
|
||||
_nome = _nome[len("SEVERITY_"):]
|
||||
if _GRAVITA.get(_nome, 0) > _GRAVITA.get(severita, 0):
|
||||
severita = _nome
|
||||
|
||||
# ---- dati del dispositivo (per la pagina "Impianto": accessori + riepilogo,
|
||||
# come la scheda "Accessori" e il "Riepilogo impostazioni" dell'app ufficiale)
|
||||
hp_id = g("heat_pump_id", "value") or ""
|
||||
@@ -249,6 +266,13 @@ def summarize(st: dict) -> dict:
|
||||
"zone2_mode": g("current_pump_mode_state", "zone_2"),
|
||||
"zone2_cooling_setpoint": g("zone_setpoints_cooling", "zone_2"),
|
||||
"errors_count": len(errs) if isinstance(errs, list) else 0,
|
||||
# Gravita' degli errori, per il pallino di stato dell'app: un semplice
|
||||
# avviso (WARNING) non deve diventare rosso come un allarme che ferma la
|
||||
# macchina. "error_metadata" dice se c'e' un allarme che ferma; le voci
|
||||
# di "errors" portano la severita' di ognuna (normalizzata senza il
|
||||
# prefisso SEVERITY_, cosi' la UI non dipende dal protobuf).
|
||||
"alarm_stopping": alarm_stopping,
|
||||
"error_severity": severita,
|
||||
"pump_updated": g("aws_iot_received_time") or st.get("time"),
|
||||
# Identificativo e versioni: l'app ufficiale mostra le prime 6 cifre
|
||||
# dell'UUID come "Dispositivo ID".
|
||||
|
||||
+3
-2
@@ -245,8 +245,9 @@ rapide sono pulsanti visibili, le azioni sulla copertina restano per dopo la
|
||||
prova di `CoverAction` sul kit.
|
||||
|
||||
**Home** (`MainPage.qml`, riscritta): una schermata da guardare, non un modulo.
|
||||
Stato in una riga con il pallino colorato (verde regolare, ambra modo attivo o
|
||||
numeri vecchi, rosso errore), le due letture che contano — casa e acqua calda,
|
||||
Stato in una riga con il pallino colorato (verde regolare, ambra modo attivo,
|
||||
numeri vecchi o avviso, rosso solo allarme che ferma la macchina), le due
|
||||
letture che contano — casa e acqua calda,
|
||||
con obiettivo e barra di carica — affiancate e **toccabili**, e tre azioni
|
||||
rapide: boost, assenza, comandi. Escono dalla Home i due slider, i sette
|
||||
pulsanti e i tre pulsanti di navigazione ridondanti col menù a tendina. Sotto,
|
||||
|
||||
+5
-2
@@ -169,8 +169,11 @@ I richiami numerati dell'immagine:
|
||||
Solo navigazione fra pagine, una voce per pagina: i pulsanti in fondo alla
|
||||
home non ci sono più (risolve P4).
|
||||
2. **Stato in una riga**: pallino colorato + "Tutto regolare · Automatico" +
|
||||
ora dell'ultimo aggiornamento. Verde regolare, ambra attenzione, rosso
|
||||
errore o non raggiungibile (risolve P6). La frase estesa dell'app ufficiale
|
||||
ora dell'ultimo aggiornamento. Verde regolare, ambra attenzione (modo
|
||||
attivo, numeri vecchi o avviso della pompa), rosso solo un allarme che
|
||||
ferma la macchina o non raggiungibile (risolve P6 — la distinzione fra
|
||||
avviso e allarme è arrivata il 10/10: prima qualunque voce nell'elenco
|
||||
errori accendeva il rosso). La frase estesa dell'app ufficiale
|
||||
("Relax: la pompa funziona regolarmente") è bella ma occupa 2 righe in cima:
|
||||
sta meglio nella pagina Impianto (decisione 1).
|
||||
3. **Due letture grandi, affiancate**: Casa 22,8 °C (obiettivo 19,0) e Acqua
|
||||
|
||||
@@ -14,12 +14,16 @@ CoverBackground {
|
||||
// Colore del pallino: gli stessi criteri della Home (vedi MainPage.qml).
|
||||
// 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.
|
||||
// Rosso solo per un allarme che ferma la macchina (livello 3): un avviso
|
||||
// (livello 2) e' ambra.
|
||||
function statusColor() {
|
||||
if (!api.hasData || api.stale)
|
||||
return "orange"
|
||||
var livello = api.statusLevel()
|
||||
if (livello === 2)
|
||||
if (livello === 3)
|
||||
return Theme.errorColor
|
||||
if (livello === 2)
|
||||
return "#ffb300"
|
||||
if (livello === 1)
|
||||
return Theme.highlightColor
|
||||
return "#4caf50"
|
||||
|
||||
@@ -29,14 +29,19 @@ Page {
|
||||
}
|
||||
|
||||
// Colore del pallino di stato. Il livello (0 regolare, 1 modo attivo,
|
||||
// 2 errore) lo calcola il C++; qui si sceglie il colore, e l'ambra quando
|
||||
// i numeri sono vecchi: un dato vecchio non e' un dato buono.
|
||||
// 2 avviso, 3 allarme serio) lo calcola il C++; qui si sceglie il colore,
|
||||
// e l'ambra quando i numeri sono vecchi: un dato vecchio non e' un dato
|
||||
// buono. Rosso solo quando e' la macchina a essere ferma o guasta: un
|
||||
// avviso (per esempio il compressore limitato dalla temperatura esterna)
|
||||
// e' ambra, come nell'app ufficiale.
|
||||
function statusColor() {
|
||||
if (!api.hasData || api.stale)
|
||||
return "orange"
|
||||
var livello = api.statusLevel()
|
||||
if (livello === 2)
|
||||
if (livello === 3)
|
||||
return Theme.errorColor
|
||||
if (livello === 2)
|
||||
return "#ffb300"
|
||||
if (livello === 1)
|
||||
return Theme.highlightColor
|
||||
return "#4caf50"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name: harbour-aira
|
||||
Summary: Control your Aira heat pump through a local bridge
|
||||
Version: 0.6.1
|
||||
Version: 0.6.2
|
||||
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
|
||||
* Sat Oct 10 2026 Carlo <carlo@carlobaratto.it> - 0.6.2-1
|
||||
- pallino di stato: rosso solo per un allarme che ferma la macchina (o di
|
||||
severita' CRITICAL/ERROR); un avviso ora e' ambra. Prima qualunque voce
|
||||
nell'elenco errori accendeva il rosso, e un semplice avviso (compressore
|
||||
limitato dalla temperatura esterna, caso reale del 10/10) sembrava un guasto.
|
||||
Il bridge espone error_severity e alarm_stopping in /summary; l'esito ignoto
|
||||
resta ambra, il rosso si accende solo su un guasto che sappiamo essere serio
|
||||
* 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
|
||||
|
||||
+14
-1
@@ -211,6 +211,19 @@ int errorCount(const QVariantMap &summary)
|
||||
return summary.value(QStringLiteral("errors_count")).toInt();
|
||||
}
|
||||
|
||||
// Allarme "serio": quello che ferma la macchina (flag del bridge, dai metadati
|
||||
// della pompa) oppure una voce di severita' CRITICAL/ERROR. Un semplice avviso
|
||||
// (WARNING: per esempio il compressore limitato dalla temperatura esterna) NON
|
||||
// e' serio, e non deve accendere il pallino rosso. Senza i campi (bridge piu'
|
||||
// vecchio) si resta prudenti sul colore: ambra, non rosso.
|
||||
bool alarmSerious(const QVariantMap &summary)
|
||||
{
|
||||
if (flagValue(summary, QStringLiteral("alarm_stopping")))
|
||||
return true;
|
||||
const QString severita = summary.value(QStringLiteral("error_severity")).toString();
|
||||
return severita == QStringLiteral("CRITICAL") || severita == QStringLiteral("ERROR");
|
||||
}
|
||||
|
||||
bool pumpRunning(const QVariantMap &summary)
|
||||
{
|
||||
return summary.value(QStringLiteral("pump_active_state")).toString()
|
||||
@@ -268,7 +281,7 @@ int statusLevel(const QVariantMap &summary)
|
||||
if (summary.isEmpty())
|
||||
return 1;
|
||||
if (errorCount(summary) > 0)
|
||||
return 2;
|
||||
return alarmSerious(summary) ? 3 : 2;
|
||||
if (flagValue(summary, QStringLiteral("away_mode"))
|
||||
|| flagValue(summary, QStringLiteral("night_mode"))
|
||||
|| flagValue(summary, QStringLiteral("force_heating")))
|
||||
|
||||
+4
-2
@@ -38,8 +38,10 @@ QString stateShort(const QVariantMap &summary);
|
||||
// "Relax: la pompa di calore funziona regolarmente").
|
||||
QString stateLong(const QVariantMap &summary);
|
||||
|
||||
// Livello dello stato, per il colore della riga: 0 regolare, 1 modo attivo
|
||||
// (assenza, notte, riscaldamento forzato), 2 errore sull'impianto.
|
||||
// Livello dello stato, per il colore del pallino in Home:
|
||||
// 0 regolare, 1 modo attivo (assenza, notte, riscaldamento forzato),
|
||||
// 2 avviso (errori di severita' WARNING: la macchina lavora),
|
||||
// 3 allarme serio (allarme che ferma la macchina, o severita' CRITICAL/ERROR).
|
||||
int statusLevel(const QVariantMap &summary);
|
||||
|
||||
// Temperatura leggibile: "51,0" oppure "—" se il valore manca.
|
||||
|
||||
@@ -245,6 +245,42 @@ int main(int argc, char *argv[])
|
||||
"stato breve: l'errore vince sul modo attivo");
|
||||
check(AiraJson::statusLevel(misto) == 2, "livello: errore prima di tutto");
|
||||
|
||||
// Un avviso NON e' un allarme: pallino ambra (livello 2), non rosso.
|
||||
// Caso reale 10/10/2026: compressore limitato dalla temperatura esterna
|
||||
// (SEVERITY_WARNING, nessun allarme che ferma la macchina) -> l'app
|
||||
// accendeva il rosso per un semplice avviso.
|
||||
QVariantMap avviso = calmo;
|
||||
avviso.insert("errors_count", 1);
|
||||
avviso.insert("alarm_stopping", false);
|
||||
avviso.insert("error_severity", "WARNING");
|
||||
check(AiraJson::statusLevel(avviso) == 2,
|
||||
"livello: avviso (WARNING) = ambra, non rosso");
|
||||
|
||||
// Allarme che ferma la macchina: rosso anche con severita' di avviso.
|
||||
QVariantMap fermo = avviso;
|
||||
fermo.insert("alarm_stopping", true);
|
||||
check(AiraJson::statusLevel(fermo) == 3, "livello: allarme che ferma = rosso");
|
||||
|
||||
// Severita' alta: rosso anche senza il flag di stop.
|
||||
QVariantMap critico = avviso;
|
||||
critico.insert("error_severity", "CRITICAL");
|
||||
check(AiraJson::statusLevel(critico) == 3, "livello: CRITICAL = rosso");
|
||||
QVariantMap guasto = avviso;
|
||||
guasto.insert("error_severity", "ERROR");
|
||||
check(AiraJson::statusLevel(guasto) == 3, "livello: ERROR = rosso");
|
||||
|
||||
// Il rosso vince sul modo attivo.
|
||||
QVariantMap fermoAssente = assente;
|
||||
fermoAssente.insert("errors_count", 1);
|
||||
fermoAssente.insert("alarm_stopping", true);
|
||||
check(AiraJson::statusLevel(fermoAssente) == 3,
|
||||
"livello: allarme che ferma prima del modo attivo");
|
||||
|
||||
// Senza i campi di gravita' (bridge piu' vecchio) si resta ambra:
|
||||
// il rosso si accende solo su un guasto che sappiamo essere serio.
|
||||
check(AiraJson::statusLevel(uno) == 2,
|
||||
"livello: gravita' ignota = ambra");
|
||||
|
||||
check(AiraJson::stateShort(QVariantMap()) == "No data",
|
||||
"stato breve: nessun dato");
|
||||
check(AiraJson::stateLong(QVariantMap()).isEmpty(),
|
||||
|
||||
Reference in New Issue
Block a user