Add last status update timestamp and fetch update from the database

This commit is contained in:
Simon Stürz 2025-12-17 14:04:39 +01:00
parent 79d8d661f6
commit 2c235b75aa
3 changed files with 75 additions and 30 deletions

View File

@ -135,6 +135,7 @@ class DashboardApp {
'chargers.columns.energyManagerMode': 'Energy manager mode',
'chargers.columns.connected': 'Connected',
'chargers.columns.status': 'Status',
'chargers.columns.lastStatusUpdate': 'Last status update',
'chargers.columns.chargingCurrent': 'Charging current',
'chargers.columns.chargingPhases': 'Charging phases',
'chargers.columns.currentPower': 'Current power',
@ -283,6 +284,7 @@ class DashboardApp {
'chargers.columns.energyManagerMode': 'Energiemanager-Modus',
'chargers.columns.connected': 'Verbunden',
'chargers.columns.status': 'Status',
'chargers.columns.lastStatusUpdate': 'Letzte Statusaktualisierung',
'chargers.columns.chargingCurrent': 'Ladestrom',
'chargers.columns.chargingPhases': 'Ladephasen',
'chargers.columns.currentPower': 'Aktuelle Leistung',
@ -1306,6 +1308,7 @@ class DashboardApp {
return;
const items = [
{ label: this.t('chargers.columns.lastStatusUpdate'), key: 'lastStatusUpdate' },
{ label: this.t('chargers.columns.version'), key: 'version' },
{ label: this.t('chargers.columns.temperature'), key: 'temperature' },
{ label: this.t('chargers.columns.digitalInputMode'), key: 'digitalInputMode' }
@ -1593,6 +1596,27 @@ class DashboardApp {
return Number.isFinite(value) ? this.t('value.unknownWithValue', { value }) : '—';
}
if (key === 'lastStatusUpdate') {
const numeric = typeof value === 'string' ? Number.parseFloat(value) : value;
if (!Number.isFinite(numeric))
return '—';
const ms = numeric > 1e12 ? numeric : numeric * 1000;
const date = new Date(ms);
if (Number.isNaN(date.getTime()))
return '—';
const pad = part => String(part).padStart(2, '0');
const day = pad(date.getDate());
const month = pad(date.getMonth() + 1);
const year = date.getFullYear();
const hours = pad(date.getHours());
const minutes = pad(date.getMinutes());
const seconds = pad(date.getSeconds());
return `${day}.${month}.${year} ${hours}:${minutes}:${seconds}`;
}
if (key === 'currentPower' || key === 'sessionEnergy') {
const numericValue = this.coerceFiniteNumber(value);
if (numericValue === null)

View File

@ -196,6 +196,7 @@ void EvDashEngine::onThingAdded(Thing *thing)
m_chargers.append(thing);
monitorChargerThing(thing);
sendNotification("ChargerAdded", packCharger(thing));
verifyChargerStatusChanged(thing);
}
if (isCarThing(thing)) {
@ -210,8 +211,9 @@ void EvDashEngine::onThingRemoved(const ThingId &thingId)
foreach (Thing *thing, m_chargers) {
if (thing->id() == thingId) {
qCDebug(dcEvDashExperience()) << "Charger has been removed.";
m_chargers.removeAll(thing);
sendNotification("ChargerRemoved", packCharger(thing));
m_chargers.removeAll(thing);
m_chargersStatusChangedCache.remove(thing);
break;
}
}
@ -224,12 +226,15 @@ void EvDashEngine::onThingRemoved(const ThingId &thingId)
break;
}
}
}
void EvDashEngine::onThingChanged(Thing *thing)
{
if (isChargerThing(thing))
if (isChargerThing(thing)) {
sendNotification("ChargerChanged", packCharger(thing));
verifyChargerStatusChanged(thing);
}
if (isCarThing(thing))
sendNotification("CarChanged", packCar(thing));
@ -261,6 +266,37 @@ void EvDashEngine::monitorCarThing(Thing *thing)
});
}
void EvDashEngine::verifyChargerStatusChanged(Thing *charger)
{
QString stateName = "status";
QString source = QString("state-%1-%2").arg(charger->id().toString(QUuid::WithBraces), stateName);
LogFetchJob *job = m_logEngine->fetchLogEntries({source}, {stateName}, {}, {}, {}, Types::SampleRateAny, Qt::DescendingOrder, 0, 1);
connect(job, &LogFetchJob::finished, charger, [this, charger, stateName](const LogEntries &entries) {
if (entries.isEmpty()) {
qCDebug(dcEvDashExperience()) << "Last state change of" << charger->name() << stateName << "unknown";
// Forget any cached values, the database did not return any information...
m_chargersStatusChangedCache.remove(charger);
return;
}
qint64 lastChangeTimestamp = entries.first().timestamp().toSecsSinceEpoch();
qCDebug(dcEvDashExperience()) << "Last state change" << charger->name() << stateName << entries.first().timestamp().toString() << entries.first().values().first();
if (!m_chargersStatusChangedCache.contains(charger)) {
m_chargersStatusChangedCache.insert(charger, lastChangeTimestamp);
sendNotification("ChargerChanged", packCharger(charger));
return;
}
if (m_chargersStatusChangedCache.value(charger) != lastChangeTimestamp) {
m_chargersStatusChangedCache[charger] = lastChangeTimestamp;
sendNotification("ChargerChanged", packCharger(charger));
return;
}
});
}
bool EvDashEngine::startWebSocketServer(quint16 port)
{
if (m_webSocketServer->isListening()) {
@ -451,7 +487,6 @@ void EvDashEngine::sendReply(QWebSocket *socket, QJsonObject response) const
void EvDashEngine::sendNotification(const QString &notification, QJsonObject payload) const
{
// Send to all active clients
for (QWebSocket *client : qAsConst(m_clients)) {
if (m_authenticatedClients.value(client).isEmpty())
continue;
@ -521,28 +556,6 @@ QJsonObject EvDashEngine::packCharger(Thing *charger) const
chargerObject.insert("pluggedIn", charger->stateValue("pluggedIn").toBool());
chargerObject.insert("chargingAllowed", charger->stateValue("power").toBool());
QString stateName = "currentPower";
chargerObject.insert(stateName, charger->stateValue(stateName).toString());
QString source = QString("state-%1-%2").arg(charger->id().toString(QUuid::WithBraces), stateName);
LogFetchJob *job = m_logEngine->fetchLogEntries(
{source},
{stateName},
{}, {}, {}, // start/end/filter
Types::SampleRateAny,
Qt::DescendingOrder,
0, 1 // offset, limit
);
connect(job, &LogFetchJob::finished, this, [charger, stateName](const LogEntries &entries) {
if (entries.isEmpty()) {
qCDebug(dcEvDashExperience()) << "##### Last state change of" << charger->name() << stateName << "unknwon";
return;
}
//qint64 lastChangeMs = entries.first().timestamp().toMSecsSinceEpoch();
qCDebug(dcEvDashExperience()) << "##### Last state change" << charger->name() << stateName << entries.first().timestamp().toString() << entries.first().values().first();
});
if (charger->hasState("currentVersion"))
chargerObject.insert("version", charger->stateValue("currentVersion").toDouble());
@ -559,10 +572,11 @@ QJsonObject EvDashEngine::packCharger(Thing *charger) const
if (charger->hasState("error"))
chargerObject.insert("error", charger->stateValue("error").toString());
if (charger->hasState("status")) {
if (charger->hasState("status"))
chargerObject.insert("status", charger->stateValue("status").toString());
}
if (m_chargersStatusChangedCache.contains(charger))
chargerObject.insert("lastStatusUpdate", m_chargersStatusChangedCache.value(charger));
if (charger->hasState("digitalInputMode"))
chargerObject.insert("digitalInputMode", charger->stateValue("digitalInputMode").toInt());
@ -618,6 +632,7 @@ QStringList EvDashEngine::carThingIdsForCharger(const QString &chargerId) const
const QString assignedCarId = chargingInfo.value(QStringLiteral("assignedCarId")).toString();
if (!assignedCarId.isEmpty())
carThingIds.append(assignedCarId);
break;
}
@ -640,6 +655,7 @@ void EvDashEngine::onSessionsReceived(const QList<QVariantMap> &sessions)
QPointer<QWebSocket> socket = m_pendingChargingSessionsRequests.take(requestId);
if (!socket)
continue;
sendReply(socket, createSuccessResponse(requestId, payload));
}
@ -655,6 +671,7 @@ void EvDashEngine::onSessionsError(const QString &errorMessage)
QPointer<QWebSocket> socket = m_pendingChargingSessionsRequests.take(requestId);
if (!socket)
continue;
sendReply(socket, createErrorResponse(requestId, errorMessage));
}
}

View File

@ -86,11 +86,15 @@ private:
QList<QWebSocket *> m_clients;
QHash<QWebSocket *, QString> m_authenticatedClients;
QList<Thing *> m_chargers;
void monitorChargerThing(Thing *thing);
QList<Thing *> m_cars;
QList<Thing *> m_chargers;
void monitorChargerThing(Thing *thing);
void monitorCarThing(Thing *thing);
QHash<Thing *, qint64> m_chargersStatusChangedCache;
void verifyChargerStatusChanged(Thing *charger);
// Pending requests waiting for charging sessions data to return
QHash<QString, QPointer<QWebSocket>> m_pendingChargingSessionsRequests;
QStringList carThingIdsForCharger(const QString &chargerId) const;