- home: temperatura esterna e in casa/assente in alto, messaggio di stato, scheda impianto con temperatura stanza e acqua calda; comandi sotto - pagina Dati: calore prodotto, elettricita' usata, COP acqua calda e riscaldamento, risparmio, CO2 evitata, Smart Tariff Control (mese per mese) - pagina Impianto: stato, termostato a parete, versioni software, errori - bridge 0.4.0: rotta /stats (servizio statistiche del cloud) e blocco dispositivo in /summary (ID breve, versioni, termostato) - C++: parseStats, formatNumber, formatMoney, monthLabel (separatore e valuta secondo la lingua); 61/61 verifiche del nucleo - anteprime aggiornate (dati, impianto) in inglese e italiano
202 lines
6.9 KiB
C++
202 lines
6.9 KiB
C++
#include "airajson.h"
|
|
|
|
#include <QCoreApplication>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QJsonValue>
|
|
#include <QLocale>
|
|
#include <QStringList>
|
|
#include <QUrl>
|
|
|
|
namespace AiraJson {
|
|
|
|
QString normalizeBaseUrl(const QString &raw)
|
|
{
|
|
QString s = raw.trimmed();
|
|
if (s.isEmpty())
|
|
return QString();
|
|
|
|
if (!s.startsWith(QLatin1String("http://")) && !s.startsWith(QLatin1String("https://")))
|
|
s.prepend(QLatin1String("http://"));
|
|
|
|
while (s.endsWith(QLatin1Char('/')))
|
|
s.chop(1);
|
|
|
|
return s;
|
|
}
|
|
|
|
QVariantMap parseSummary(const QByteArray &body, bool *ok)
|
|
{
|
|
if (ok)
|
|
*ok = false;
|
|
|
|
QJsonParseError err;
|
|
const QJsonDocument doc = QJsonDocument::fromJson(body, &err);
|
|
if (err.error != QJsonParseError::NoError || !doc.isObject())
|
|
return QVariantMap();
|
|
|
|
const QJsonObject root = doc.object();
|
|
if (root.value(QStringLiteral("ok")).toBool() == false)
|
|
return QVariantMap();
|
|
|
|
const QJsonObject summary = root.value(QStringLiteral("summary")).toObject();
|
|
if (summary.isEmpty())
|
|
return QVariantMap();
|
|
|
|
if (ok)
|
|
*ok = true;
|
|
return summary.toVariantMap();
|
|
}
|
|
|
|
QVariantMap parseStats(const QByteArray &body, bool *ok)
|
|
{
|
|
if (ok)
|
|
*ok = false;
|
|
|
|
QJsonParseError err;
|
|
const QJsonDocument doc = QJsonDocument::fromJson(body, &err);
|
|
if (err.error != QJsonParseError::NoError || !doc.isObject())
|
|
return QVariantMap();
|
|
|
|
const QJsonObject root = doc.object();
|
|
if (root.value(QStringLiteral("ok")).toBool() == false)
|
|
return QVariantMap();
|
|
|
|
const QJsonObject stats = root.value(QStringLiteral("stats")).toObject();
|
|
if (stats.isEmpty())
|
|
return QVariantMap();
|
|
|
|
if (ok)
|
|
*ok = true;
|
|
return stats.toVariantMap();
|
|
}
|
|
|
|
QString modeLabel(const QString &operatingStatus)
|
|
{
|
|
const QString s = operatingStatus;
|
|
if (s == QLatin1String("OPERATING_STATUS_AUTO"))
|
|
return QCoreApplication::translate("AiraJson", "Automatic");
|
|
if (s == QLatin1String("OPERATING_STATUS_HEATING"))
|
|
return QCoreApplication::translate("AiraJson", "Heating");
|
|
if (s == QLatin1String("OPERATING_STATUS_COOLING"))
|
|
return QCoreApplication::translate("AiraJson", "Cooling");
|
|
if (s == QLatin1String("OPERATING_STATUS_HOT_WATER"))
|
|
return QCoreApplication::translate("AiraJson", "Hot water");
|
|
if (s == QLatin1String("OPERATING_STATUS_STANDBY"))
|
|
return QCoreApplication::translate("AiraJson", "Standby");
|
|
if (s == QLatin1String("OPERATING_STATUS_OFF"))
|
|
return QCoreApplication::translate("AiraJson", "Off");
|
|
// Stato della pompa per zona (state.zoneN_mode).
|
|
if (s == QLatin1String("PUMP_MODE_STATE_HEATING"))
|
|
return QCoreApplication::translate("AiraJson", "Heating");
|
|
if (s == QLatin1String("PUMP_MODE_STATE_COOLING"))
|
|
return QCoreApplication::translate("AiraJson", "Cooling");
|
|
if (s == QLatin1String("PUMP_MODE_STATE_HOT_WATER"))
|
|
return QCoreApplication::translate("AiraJson", "Hot water");
|
|
if (s == QLatin1String("PUMP_MODE_STATE_IDLE"))
|
|
return QCoreApplication::translate("AiraJson", "Idle");
|
|
if (s == QLatin1String("PUMP_MODE_STATE_STANDBY"))
|
|
return QCoreApplication::translate("AiraJson", "Standby");
|
|
if (s == QLatin1String("PUMP_MODE_STATE_OFF"))
|
|
return QCoreApplication::translate("AiraJson", "Off");
|
|
// Stato operativo della pompa (state.pump_active_state).
|
|
if (s == QLatin1String("PUMP_ACTIVE_STATE_IDLE"))
|
|
return QCoreApplication::translate("AiraJson", "Stopped");
|
|
if (s == QLatin1String("PUMP_ACTIVE_STATE_RUNNING"))
|
|
return QCoreApplication::translate("AiraJson", "Running");
|
|
if (s.isEmpty())
|
|
return QStringLiteral("—");
|
|
// Sconosciuto: togliamo il prefisso e mostriamo il resto, minuscolo.
|
|
QString rest = s;
|
|
rest.remove(QLatin1String("OPERATING_STATUS_"));
|
|
rest.remove(QLatin1String("PUMP_MODE_STATE_"));
|
|
rest.remove(QLatin1String("PUMP_ACTIVE_STATE_"));
|
|
rest.remove(QLatin1String("POWER_PREFERENCE_"));
|
|
return rest.toLower();
|
|
}
|
|
|
|
QString formatValue(const QVariant &value)
|
|
{
|
|
// Un decimale: e' il caso delle temperature ("51,0").
|
|
return formatNumber(value, 1);
|
|
}
|
|
|
|
QString formatNumber(const QVariant &value, int decimals)
|
|
{
|
|
if (!value.isValid() || value.isNull())
|
|
return QStringLiteral("—");
|
|
|
|
bool isNumber = false;
|
|
const double d = value.toDouble(&isNumber);
|
|
if (!isNumber)
|
|
return QStringLiteral("—");
|
|
|
|
// QLocale() = locale predefinito, che translator.cpp allinea alla lingua
|
|
// dell'app: separatore decimale col punto in inglese, con la virgola in
|
|
// italiano (QString::number darebbe sempre il punto).
|
|
return QLocale().toString(d, 'f', decimals < 0 ? 0 : decimals);
|
|
}
|
|
|
|
QString formatMoney(const QVariant &value, const QString ¤cyCode)
|
|
{
|
|
if (!value.isValid() || value.isNull())
|
|
return QStringLiteral("—");
|
|
|
|
bool isNumber = false;
|
|
const double d = value.toDouble(&isNumber);
|
|
if (!isNumber)
|
|
return QStringLiteral("—");
|
|
|
|
// Posizione del simbolo e separatori seguono la lingua dell'app, non la
|
|
// valuta: "21,78 €" in italiano, "€21.78" in inglese.
|
|
const QString code = currencyCode.trimmed().toUpper();
|
|
QString symbol;
|
|
if (code == QLatin1String("EUR"))
|
|
symbol = QString::fromUtf8("€");
|
|
else if (!code.isEmpty())
|
|
symbol = code; // valuta senza simbolo noto: codice ISO
|
|
return QLocale().toCurrencyString(d, symbol);
|
|
}
|
|
|
|
QString monthLabel(const QString &yearMonth)
|
|
{
|
|
// "2026-10" -> "ottobre 2026": il nome del mese segue la lingua dell'app.
|
|
const QStringList parti = yearMonth.split(QLatin1Char('-'));
|
|
if (parti.size() != 2)
|
|
return QString();
|
|
|
|
bool annoOk = false;
|
|
bool meseOk = false;
|
|
const int anno = parti.at(0).toInt(&annoOk);
|
|
const int mese = parti.at(1).toInt(&meseOk);
|
|
if (!annoOk || !meseOk || mese < 1 || mese > 12)
|
|
return QString();
|
|
|
|
return QStringLiteral("%1 %2").arg(QLocale().monthName(mese, QLocale::LongFormat)).arg(anno);
|
|
}
|
|
|
|
QByteArray commandPayload(const QString &id, const QVariant &value)
|
|
{
|
|
QJsonObject o;
|
|
o.insert(QStringLiteral("id"), id);
|
|
if (value.isValid() && !value.isNull())
|
|
o.insert(QStringLiteral("value"), QJsonValue::fromVariant(value));
|
|
return QJsonDocument(o).toJson(QJsonDocument::Compact);
|
|
}
|
|
|
|
QString errorMessage(const QByteArray &body, int httpStatus)
|
|
{
|
|
QJsonParseError err;
|
|
const QJsonDocument doc = QJsonDocument::fromJson(body, &err);
|
|
if (err.error == QJsonParseError::NoError && doc.isObject()) {
|
|
const QString msg = doc.object().value(QStringLiteral("error")).toString();
|
|
if (!msg.isEmpty())
|
|
return msg;
|
|
}
|
|
if (httpStatus == 0)
|
|
return QCoreApplication::translate("AiraJson", "server unreachable");
|
|
return QCoreApplication::translate("AiraJson", "HTTP error %1").arg(httpStatus);
|
|
}
|
|
|
|
} // namespace AiraJson
|