0.6.0 blocco 1: Home di consultazione e pagina Regolazioni

- Home: stato in una riga col pallino, due letture grandi toccabili, tre
  azioni rapide; via i due slider, i sette pulsanti e la scheda separata.
- Nuova pagina Regolazioni: un blocco per grandezza, valore attuale sempre
  visibile, preimpostazioni per le dita fredde, conferma esplicita; il
  pulsante e' attivo solo se c'e' davvero qualcosa da cambiare.
- Messaggi dei comandi spostati in ApiClient (api.notice) con NoticeBar
  ancorata in fondo: prima nascevano fuori dallo schermo.
- Stato breve/esteso/livello in C++ (testabile senza SDK), 18 verifiche
  nuove: 79/79.
- stale + lastUpdate: la Home dice 'aggiornato alle HH:MM' e spegne i
  numeri vecchi invece di farli passare per freschi.
- 192/192 stringhe tradotte (54 nuove).
This commit is contained in:
2026-10-10 09:47:16 +02:00
parent 667ff63907
commit d5b63b57df
10 changed files with 1038 additions and 600 deletions
+78
View File
@@ -198,4 +198,82 @@ QString errorMessage(const QByteArray &body, int httpStatus)
return QCoreApplication::translate("AiraJson", "HTTP error %1").arg(httpStatus);
}
namespace {
// Flag booleano del riassunto, con falso come ripiego.
bool flagValue(const QVariantMap &summary, const QString &key)
{
return summary.value(key).toBool();
}
int errorCount(const QVariantMap &summary)
{
return summary.value(QStringLiteral("errors_count")).toInt();
}
bool pumpRunning(const QVariantMap &summary)
{
return summary.value(QStringLiteral("pump_active_state")).toString()
.contains(QStringLiteral("RUNNING"));
}
} // namespace
QString stateShort(const QVariantMap &summary)
{
if (summary.isEmpty())
return QCoreApplication::translate("AiraJson", "No data");
const int errs = errorCount(summary);
if (errs > 0)
return (errs == 1)
? QCoreApplication::translate("AiraJson", "Attention: one error")
: QCoreApplication::translate("AiraJson", "Attention: %1 errors").arg(errs);
if (flagValue(summary, QStringLiteral("away_mode")))
return QCoreApplication::translate("AiraJson", "Away mode");
if (flagValue(summary, QStringLiteral("night_mode")))
return QCoreApplication::translate("AiraJson", "Night mode");
if (flagValue(summary, QStringLiteral("force_heating")))
return QCoreApplication::translate("AiraJson", "Heating at full power");
if (flagValue(summary, QStringLiteral("hot_water_heating")))
return QCoreApplication::translate("AiraJson", "Heating the hot water");
if (pumpRunning(summary))
return QCoreApplication::translate("AiraJson", "Working");
return QCoreApplication::translate("AiraJson", "Everything is fine");
}
QString stateLong(const QVariantMap &summary)
{
if (summary.isEmpty())
return QString();
const int errs = errorCount(summary);
if (errs > 0)
return (errs == 1)
? QCoreApplication::translate("AiraJson", "Attention: one error on the plant")
: QCoreApplication::translate("AiraJson", "Attention: %1 errors on the plant").arg(errs);
if (flagValue(summary, QStringLiteral("away_mode")))
return QCoreApplication::translate("AiraJson", "Away mode: the house is saving energy.");
if (flagValue(summary, QStringLiteral("night_mode")))
return QCoreApplication::translate("AiraJson", "Night mode is on.");
if (flagValue(summary, QStringLiteral("force_heating")))
return QCoreApplication::translate("AiraJson", "Boost: the house is heating at full power.");
if (flagValue(summary, QStringLiteral("hot_water_heating")))
return QCoreApplication::translate("AiraJson", "The heat pump is heating the hot water.");
if (pumpRunning(summary))
return QCoreApplication::translate("AiraJson", "The heat pump is working.");
return QCoreApplication::translate("AiraJson", "Relax: the heat pump is running smoothly.");
}
int statusLevel(const QVariantMap &summary)
{
if (summary.isEmpty())
return 1;
if (errorCount(summary) > 0)
return 2;
if (flagValue(summary, QStringLiteral("away_mode"))
|| flagValue(summary, QStringLiteral("night_mode"))
|| flagValue(summary, QStringLiteral("force_heating")))
return 1;
return 0;
}
} // namespace AiraJson
+12
View File
@@ -30,6 +30,18 @@ QVariantMap parseStats(const QByteArray &body, bool *ok);
// Codice di stato Aira ("OPERATING_STATUS_AUTO") in etichetta italiana.
QString modeLabel(const QString &operatingStatus);
// Riga di stato per la Home: breve, per stare in una riga sola
// ("Tutto regolare", "In assenza", "Attenzione: un errore").
QString stateShort(const QVariantMap &summary);
// Frase estesa per la pagina Impianto (quella dell'app ufficiale:
// "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.
int statusLevel(const QVariantMap &summary);
// Temperatura leggibile: "51,0" oppure "—" se il valore manca.
QString formatValue(const QVariant &value);
+67
View File
@@ -3,8 +3,10 @@
#include "settings.h"
#include <QDebug>
#include <QLocale>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QTime>
#include <QTimer>
#include <QUrl>
@@ -12,7 +14,32 @@ ApiClient::ApiClient(Settings *settings, QObject *parent)
: QObject(parent)
, m_settings(settings)
, m_busy(false)
, m_stale(false)
, m_noticeIsError(false)
, m_noticeTimer(0)
{
// Il messaggio dell'ultimo comando (o dell'ultimo errore) vale per ogni
// pagina: lo stato sta qui, si spegne da solo e la UI lo mostra ancorato
// in fondo alla schermata (NoticeBar), fuori dal contenuto che scorre.
m_noticeTimer = new QTimer(this);
m_noticeTimer->setSingleShot(true);
m_noticeTimer->setInterval(7000);
connect(m_noticeTimer, &QTimer::timeout, this, [this]() {
if (m_notice.isEmpty())
return;
m_notice.clear();
emit noticeChanged();
});
connect(this, &ApiClient::errorOccurred, this, [this](const QString &message) {
setNotice(message, true);
});
connect(this, &ApiClient::commandFinished, this, [this](bool ok, const QString &message) {
setNotice(message, !ok);
});
connect(this, &ApiClient::statsRefreshed, this, [this](bool ok, const QString &message) {
setNotice(message, !ok);
});
if (m_settings) {
// La UI deve riflettere subito il passaggio da "non configurato" a
// "configurato" quando si salvano indirizzo o token.
@@ -29,6 +56,30 @@ bool ApiClient::configured() const
&& !m_settings->token().trimmed().isEmpty();
}
QString ApiClient::stateShort() const
{
return AiraJson::stateShort(m_summary);
}
QString ApiClient::stateLong() const
{
return AiraJson::stateLong(m_summary);
}
int ApiClient::statusLevel() const
{
return AiraJson::statusLevel(m_summary);
}
void ApiClient::setNotice(const QString &message, bool isError)
{
m_notice = message;
m_noticeIsError = isError;
emit noticeChanged();
if (!message.isEmpty())
m_noticeTimer->start();
}
void ApiClient::setBusy(bool value)
{
if (value == m_busy)
@@ -102,6 +153,10 @@ void ApiClient::refresh()
? tr("Server unreachable (timeout)")
: AiraJson::errorMessage(body, status);
qWarning() << "[api] summary non riuscito:" << status << msg;
if (!m_stale) {
m_stale = true;
emit staleChanged();
}
emit errorOccurred(msg);
return;
}
@@ -110,10 +165,22 @@ void ApiClient::refresh()
const QVariantMap summary = AiraJson::parseSummary(body, &ok);
if (!ok) {
qWarning() << "[api] riassunto non interpretabile";
if (!m_stale) {
m_stale = true;
emit staleChanged();
}
emit errorOccurred(tr("Unreadable response from the bridge"));
return;
}
m_summary = summary;
if (m_stale) {
m_stale = false;
emit staleChanged();
}
// Ora dell'ultimo aggiornamento riuscito: la Home la mostra, cosi' un
// numero vecchio non passa per un numero fresco.
m_lastUpdate = QLocale::system().toString(QTime::currentTime(), QLocale::ShortFormat);
emit lastUpdateChanged();
emit summaryChanged(summary);
});
}
+33
View File
@@ -7,6 +7,7 @@
#include <QVariantMap>
class QNetworkReply;
class QTimer;
class Settings;
// Client HTTP verso il bridge sul server (~/workspace/Carlo/Aira/aira_bridge.py).
@@ -24,6 +25,16 @@ class ApiClient : public QObject
// Statistiche della pagina "Dati" (calore, elettricita', COP, risparmi).
// Separata dal riassunto: e' una chiamata piu' lenta, con cache lato bridge.
Q_PROPERTY(QVariantMap stats READ stats NOTIFY statsChanged)
// Salute dell'aggiornamento: "stale" = l'ultimo tentativo non e' riuscito,
// quindi i numeri a schermo sono vecchi e vanno mostrati spenti.
Q_PROPERTY(bool stale READ stale NOTIFY staleChanged)
Q_PROPERTY(QString lastUpdate READ lastUpdate NOTIFY lastUpdateChanged)
Q_PROPERTY(bool hasData READ hasData NOTIFY summaryChanged)
// Messaggio dell'ultimo comando (o errore), mostrato in fondo alla pagina:
// sta qui e non nel QML perche' vale per OGNI pagina, e perche' cosi' il
// messaggio e' lo stesso ovunque si stia.
Q_PROPERTY(QString notice READ notice NOTIFY noticeChanged)
Q_PROPERTY(bool noticeIsError READ noticeIsError NOTIFY noticeChanged)
public:
explicit ApiClient(Settings *settings, QObject *parent = 0);
@@ -32,6 +43,18 @@ public:
bool configured() const;
QVariantMap summary() const { return m_summary; }
QVariantMap stats() const { return m_stats; }
bool stale() const { return m_stale; }
QString lastUpdate() const { return m_lastUpdate; }
bool hasData() const { return !m_summary.isEmpty(); }
QString notice() const { return m_notice; }
bool noticeIsError() const { return m_noticeIsError; }
// Stato leggibile in una riga (stateShort) e per esteso (stateLong), piu'
// il livello da cui la UI prende il colore. Stanno in C++ perche' sono
// logica pura: verificate in tests/core_test.cpp e non duplicate fra pagine.
Q_INVOKABLE QString stateShort() const;
Q_INVOKABLE QString stateLong() const;
Q_INVOKABLE int statusLevel() const;
// Scarica il riassunto e lo emette con summaryChanged().
Q_INVOKABLE void refresh();
@@ -56,6 +79,9 @@ public:
signals:
void busyChanged();
void configuredChanged();
void staleChanged();
void lastUpdateChanged();
void noticeChanged();
void summaryChanged(const QVariantMap &summary);
void statsChanged(const QVariantMap &stats);
void statsRefreshed(bool ok, const QString &message);
@@ -68,10 +94,17 @@ private:
const QByteArray &body, int timeoutSec);
void armTimeout(QNetworkReply *reply, int seconds);
void setBusy(bool value);
// Mostra un messaggio in fondo alla pagina per qualche secondo.
void setNotice(const QString &message, bool isError);
Settings *m_settings;
QNetworkAccessManager m_nam;
bool m_busy;
bool m_stale;
QString m_lastUpdate;
QString m_notice;
bool m_noticeIsError;
QTimer *m_noticeTimer;
QVariantMap m_summary;
QVariantMap m_stats;
};