From d2f769bbf72cdf2be41a07e5426ab6e556b704b5 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 13 Dec 2021 00:41:59 +0100 Subject: [PATCH] Update energy logs to latest log api --- libnymea-app/energy/energylogs.cpp | 6 -- libnymea-app/energy/powerbalancelogs.cpp | 10 +++ libnymea-app/energy/thingpowerlogs.cpp | 61 ++++++++++++++++++- libnymea-app/energy/thingpowerlogs.h | 10 ++- .../ui/mainviews/energy/ConsumerStats.qml | 46 ++++++++++---- .../ui/mainviews/energy/ConsumersHistory.qml | 2 +- .../ui/mainviews/energy/ConsumersPieChart.qml | 2 +- .../ui/mainviews/energy/PowerBalanceStats.qml | 23 ++++--- 8 files changed, 128 insertions(+), 32 deletions(-) diff --git a/libnymea-app/energy/energylogs.cpp b/libnymea-app/energy/energylogs.cpp index b05bbb8a..f7a2e38b 100644 --- a/libnymea-app/energy/energylogs.cpp +++ b/libnymea-app/energy/energylogs.cpp @@ -227,12 +227,6 @@ void EnergyLogs::notificationReceivedInternal(const QVariantMap &data) return; } - QMetaEnum sampleRateEnum = QMetaEnum::fromType(); - SampleRate sampleRate = static_cast(sampleRateEnum.keyToValue(data.value("params").toMap().value("sampleRate").toByteArray())); - if (sampleRate != m_sampleRate) { - return; - } - notificationReceived(data); } diff --git a/libnymea-app/energy/powerbalancelogs.cpp b/libnymea-app/energy/powerbalancelogs.cpp index deed7c2b..1ff3b68b 100644 --- a/libnymea-app/energy/powerbalancelogs.cpp +++ b/libnymea-app/energy/powerbalancelogs.cpp @@ -1,5 +1,7 @@ #include "powerbalancelogs.h" +#include + PowerBalanceLogEntry::PowerBalanceLogEntry(QObject *parent): EnergyLogEntry(parent) { @@ -180,6 +182,14 @@ void PowerBalanceLogs::notificationReceived(const QVariantMap &data) { QString notification = data.value("notification").toString(); QVariantMap params = data.value("params").toMap(); + + QMetaEnum sampleRateEnum = QMetaEnum::fromType(); + SampleRate sampleRate = static_cast(sampleRateEnum.keyToValue(data.value("params").toMap().value("sampleRate").toByteArray())); + + if (sampleRate != this->sampleRate()) { + return; + } + if (notification == "Energy.PowerBalanceLogEntryAdded") { QVariantMap map = params.value("powerBalanceLogEntry").toMap(); QDateTime timestamp = QDateTime::fromSecsSinceEpoch(map.value("timestamp").toLongLong()); diff --git a/libnymea-app/energy/thingpowerlogs.cpp b/libnymea-app/energy/thingpowerlogs.cpp index 4144bb31..77f987ef 100644 --- a/libnymea-app/energy/thingpowerlogs.cpp +++ b/libnymea-app/energy/thingpowerlogs.cpp @@ -1,5 +1,7 @@ #include "thingpowerlogs.h" +#include + ThingPowerLogEntry::ThingPowerLogEntry(QObject *parent): EnergyLogEntry(parent) { @@ -69,7 +71,7 @@ double ThingPowerLogs::maxValue() const return m_maxValue; } -EnergyLogEntry *ThingPowerLogs::find(const QUuid &thingId, const QDateTime ×tamp) +ThingPowerLogEntry *ThingPowerLogs::find(const QUuid &thingId, const QDateTime ×tamp) { // TODO: Can we do a binary search even if they key we're looking for is not unique (but still sorted)? // For now, 365 * consumers items is the max we'll have here which seems on the edge for doing a stupid linear search... @@ -90,6 +92,11 @@ EnergyLogEntry *ThingPowerLogs::find(const QUuid &thingId, const QDateTime &time return nullptr; } +ThingPowerLogEntry *ThingPowerLogs::liveEntry(const QUuid &thingId) +{ + return m_liveEntries.value(thingId); +} + void ThingPowerLogs::addEntry(ThingPowerLogEntry *entry) { appendEntry(entry); @@ -104,6 +111,16 @@ void ThingPowerLogs::addEntries(const QList &entries) appendEntries(energyLogEntries); } +ThingPowerLogEntry *ThingPowerLogs::unpack(const QVariantMap &map) +{ + QDateTime timestamp = QDateTime::fromSecsSinceEpoch(map.value("timestamp").toLongLong()); + QUuid thingId = map.value("thingId").toUuid(); + double currentPower = map.value("currentPower").toDouble(); + double totalConsumption = map.value("totalConsumption").toDouble(); + double totalProduction = map.value("totalProduction").toDouble(); + return new ThingPowerLogEntry(timestamp, thingId, currentPower, totalConsumption, totalProduction, this); +} + QString ThingPowerLogs::logsName() const { return "ThingPowerLogs"; @@ -117,11 +134,22 @@ QVariantMap ThingPowerLogs::fetchParams() const } QVariantMap ret; ret.insert("thingIds", thingIdsStrings); + ret.insert("includeCurrent", true); return ret; } void ThingPowerLogs::logEntriesReceived(const QVariantMap ¶ms) { + foreach (const QVariant &variant, params.value("currentEntries").toList()) { + QVariantMap map = variant.toMap(); + ThingPowerLogEntry *entry = unpack(map); + if (m_liveEntries.contains(entry->thingId())) { + m_liveEntries[entry->thingId()]->deleteLater(); + } + m_liveEntries[entry->thingId()] = entry; + emit liveEntryChanged(entry); + } + // Grouping them so when the UI gets entriesAdded, the whole set for this timstamp will be available at once QList groupForTimestamp; foreach (const QVariant &variant, params.value("thingPowerLogEntries").toList()) { @@ -155,6 +183,32 @@ void ThingPowerLogs::notificationReceived(const QVariantMap &data) { QString notification = data.value("notification").toString(); QVariantMap params = data.value("params").toMap(); + + QMetaEnum sampleRateEnum = QMetaEnum::fromType(); + SampleRate sampleRate = static_cast(sampleRateEnum.keyToValue(data.value("params").toMap().value("sampleRate").toByteArray())); + QVariantMap entryMap = params.value("thingPowerLogEntry").toMap(); + QUuid thingId = entryMap.value("thingId").toUuid(); + + if (!m_thingIds.isEmpty() && !m_thingIds.contains(thingId)) { + // Not watching this thing... + return; + } + + // We'll use 1 Min samples in any case for the live value + if (sampleRate == EnergyLogs::SampleRate1Min) { + ThingPowerLogEntry *liveEntry = unpack(params.value("thingPowerLogEntry").toMap()); + if (m_liveEntries.contains(thingId)) { + m_liveEntries.value(thingId)->deleteLater(); + } + m_liveEntries[thingId] = liveEntry; + emit liveEntryChanged(liveEntry); + } + + // And append the sample rate we're interested in + if (sampleRate != this->sampleRate()) { + return; + } + if (notification == "Energy.ThingPowerLogEntryAdded") { QVariantMap map = params.value("thingPowerLogEntry").toMap(); QDateTime timestamp = QDateTime::fromSecsSinceEpoch(map.value("timestamp").toLongLong()); @@ -166,6 +220,11 @@ void ThingPowerLogs::notificationReceived(const QVariantMap &data) double totalConsumption = map.value("totalConsumption").toDouble(); double totalProduction = map.value("totalProduction").toDouble(); ThingPowerLogEntry *entry = new ThingPowerLogEntry(timestamp, thingId, currentPower, totalConsumption, totalProduction, this); + + // In order to be easier on resources, we'll batch notifications by grouping them by timestamp + // While the timestamp is the same, just cache the changes. Once the timestamp changes, we'll finalize the + // batch and actually append them. + // Also if we're not getting any more notification for a while and still have cached entries, we'll process the batch if (m_cachedEntries.isEmpty()) { m_cachedEntries.append(entry); } else if (entry->timestamp() == m_cachedEntries.first()->timestamp()) { diff --git a/libnymea-app/energy/thingpowerlogs.h b/libnymea-app/energy/thingpowerlogs.h index e262f589..acd768d9 100644 --- a/libnymea-app/energy/thingpowerlogs.h +++ b/libnymea-app/energy/thingpowerlogs.h @@ -44,7 +44,9 @@ public: double minValue() const; double maxValue() const; - Q_INVOKABLE EnergyLogEntry *find(const QUuid &thingId, const QDateTime ×tamp); + Q_INVOKABLE ThingPowerLogEntry *find(const QUuid &thingId, const QDateTime ×tamp); + + Q_INVOKABLE ThingPowerLogEntry *liveEntry(const QUuid &thingId); signals: void thingIdsChanged(); @@ -52,6 +54,8 @@ signals: void minValueChanged(); void maxValueChanged(); + void liveEntryChanged(ThingPowerLogEntry *entry); + protected: QString logsName() const override; QVariantMap fetchParams() const override; @@ -62,12 +66,16 @@ private: void addEntry(ThingPowerLogEntry *entry); void addEntries(const QList &entries); + ThingPowerLogEntry *unpack(const QVariantMap &map); + QList m_thingIds; double m_minValue = 0; double m_maxValue = 0; QList m_cachedEntries; QTimer m_cacheTimer; + + QHash m_liveEntries; }; #endif // THINGPOWERLOGS_H diff --git a/nymea-app/ui/mainviews/energy/ConsumerStats.qml b/nymea-app/ui/mainviews/energy/ConsumerStats.qml index f4215d49..0694fcd5 100644 --- a/nymea-app/ui/mainviews/energy/ConsumerStats.qml +++ b/nymea-app/ui/mainviews/energy/ConsumerStats.qml @@ -113,8 +113,9 @@ StatsBase { var liveEntry = {} for (var j = 0; j < consumers.count; j++) { var consumer = consumers.get(j) -// print("Got consumer:", consumer.id, consumer.name) - var value = consumer.stateByName("totalEnergyConsumed").value; + var liveLogEntry = powerLogs.liveEntry(consumer.id) +// print("Got consumer:", consumer.id, consumer.name, liveLogEntry ? liveLogEntry.timestamp : "-") + var value = liveLogEntry ? liveLogEntry.totalConsumption : 0; if (groupedEntry) { value -= groupedEntry.hasOwnProperty(consumer.id) ? groupedEntry[consumer.id] : 0 } @@ -205,20 +206,41 @@ StatsBase { var entry = entries[i] var thing = engine.thingManager.things.getThing(entry.thingId) // print("Adding new sample. thing:", thing.name); -// print("Timestamp:", entry.timestamp) - var totalEnergyConsumedState = thing.stateByName("totalEnergyConsumed") -// print("current total:", entry.totalConsumption) +// print("Timestamp:", entry.timestamp, entry.totalConsumption) + // update current last + var barSet = barSeries.thingBarSetMap[thing.id] + var lastTimestamp = categoryAxis.timestamps[categoryAxis.count - 1] + var previous = powerLogs.find(entry.thingId, lastTimestamp) + var previousValue = previous ? previous.totalConsumption : 0 +// print("previousValue:", previousValue, "newValue:", entry.totalConsumption, "diff", entry.totalConsumption - previousValue) + barSet.replace(barSet.count - 1, entry.totalConsumption - previousValue) - var consumptionValue = totalEnergyConsumedState.value - entry.totalConsumption -// print("new slot total:", consumptionValue) + // remove the oldest + barSet.remove(0, 1) - categoryAxis.categories = configs[selectionTabs.currentValue.config].sampleListNames() - barSeries.thingBarSetMap[thing].append(consumptionValue) - barSeries.thingBarSetMap[thing].remove(0, 1); + // and add a new one (always 0 for a start) + barSet.append(0) } + var labels = categoryAxis.timestamps + labels.splice(0, 1) + labels.push(entries[0].timestamp) + categoryAxis.timestamps = labels + chartView.animationOptions = ChartView.SeriesAnimations } + + onLiveEntryChanged: { + if (powerLogs.fetchingData) { + return + } + +// print("live entry changed", entry.thingId, entry.timestamp) + var previous = powerLogs.find(entry.thingId, new Date(categoryAxis.timestamps[categoryAxis.timestamps.length - 1])) + var previousValue = previous ? previous.totalConsumption : 0 + var barSet = barSeries.thingBarSetMap[entry.thingId] + barSet.replace(barSet.count - 1, entry.totalConsumption - previousValue) + } } ColumnLayout { @@ -385,7 +407,7 @@ StatsBase { margins: Style.smallMargins } Label { - text: categoryAxis.timestamps.length > toolTip.idx ? root.configs[selectionTabs.currentValue.config].toLongLabel(categoryAxis.timestamps[toolTip.idx]) : "" + text: toolTip.idx >= 0 && categoryAxis.timestamps.length > toolTip.idx ? root.configs[selectionTabs.currentValue.config].toLongLabel(categoryAxis.timestamps[toolTip.idx]) : "" font: Style.smallFont } @@ -395,7 +417,7 @@ StatsBase { Rectangle { width: Style.extraSmallFont.pixelSize height: width - color: root.colors[index % root.colors.length] + color: index >= 0 ? root.colors[index % root.colors.length] : "white" } Label { text: barSeries.thingBarSetMap.hasOwnProperty(model.id) ? "%1: %2 kWh".arg(model.name).arg(barSeries.thingBarSetMap[model.id].at(toolTip.idx).toFixed(2)) : "" diff --git a/nymea-app/ui/mainviews/energy/ConsumersHistory.qml b/nymea-app/ui/mainviews/energy/ConsumersHistory.qml index 1832c461..738107a8 100644 --- a/nymea-app/ui/mainviews/energy/ConsumersHistory.qml +++ b/nymea-app/ui/mainviews/energy/ConsumersHistory.qml @@ -319,7 +319,7 @@ ChartView { Rectangle { width: Style.extraSmallFont.pixelSize height: width - color: root.colors[index % root.colors.length] + color: index >= 0 ? root.colors[index % root.colors.length] : "white" } Label { diff --git a/nymea-app/ui/mainviews/energy/ConsumersPieChart.qml b/nymea-app/ui/mainviews/energy/ConsumersPieChart.qml index 21cf4385..c42f07a1 100644 --- a/nymea-app/ui/mainviews/energy/ConsumersPieChart.qml +++ b/nymea-app/ui/mainviews/energy/ConsumersPieChart.qml @@ -135,7 +135,7 @@ ChartView { } delegate: ColumnLayout { id: consumerDelegate - width: parent.width + width: parent ? parent.width : 0 spacing: 0 property Thing consumer: consumers.getThing(model.id) property State currentPowerState: consumer ? consumer.stateByName("currentPower") : null diff --git a/nymea-app/ui/mainviews/energy/PowerBalanceStats.qml b/nymea-app/ui/mainviews/energy/PowerBalanceStats.qml index e3d75158..aaeca501 100644 --- a/nymea-app/ui/mainviews/energy/PowerBalanceStats.qml +++ b/nymea-app/ui/mainviews/energy/PowerBalanceStats.qml @@ -52,7 +52,7 @@ StatsBase { } var config = root.configs[currentValue.config] - print("config:", config.startTime(), config.sampleRate) +// print("config:", config.startTime(), config.sampleRate) powerBalanceLogs.loadingInhibited = true powerBalanceLogs.sampleRate = config.sampleRate @@ -89,7 +89,7 @@ StatsBase { onPowerBalanceChanged: { var start = powerBalanceLogs.get(powerBalanceLogs.count - 1 ) // print("balance changed:", d.consumptionSet, powerBalanceLogs, powerBalanceLogs.count) -// print("updating", start.timestamp, start.totalConsumption, root.energyManager.totalConsumption, root.energyManager.totalConsumption - (start ? start.totalConsumption : 0)) +// print("updating", start ? start.timestamp : "", start ? start.totalConsumption : 0, root.energyManager.totalConsumption, root.energyManager.totalConsumption - (start ? start.totalConsumption : 0)) d.consumptionSet.replace(d.consumptionSet.count - 1, root.energyManager.totalConsumption - (start ? start.totalConsumption : 0)) d.productionSet.replace(d.productionSet.count - 1, root.energyManager.totalProduction - (start ? start.totalProduction : 0)) d.acquisitionSet.replace(d.acquisitionSet.count - 1, root.energyManager.totalAcquisition - (start ? start.totalAcquisition : 0)) @@ -106,7 +106,7 @@ StatsBase { if (!fetchingData) { chartView.animationOptions = ChartView.NoAnimation - print("Logs fetched") +// print("Logs fetched") var config = root.configs[selectionTabs.currentValue.config] var labels = [] @@ -130,7 +130,7 @@ StatsBase { liveEntry.acquisition -= entry.totalAcquisition liveEntry.returned -= entry.totalReturn } - print("Adding live entry:", liveEntry.consumption, root.energyManager.totalConsumption, entry ? entry.totalConsumption : 0) +// print("Adding live entry:", liveEntry.consumption, root.energyManager.totalConsumption, entry ? entry.totalConsumption : 0) entries.unshift(liveEntry) valueAxis.adjustMax(liveEntry.consumption) valueAxis.adjustMax(liveEntry.production) @@ -206,6 +206,7 @@ StatsBase { return } +// print("Entry added") var config = root.configs[selectionTabs.currentValue.config] @@ -219,18 +220,20 @@ StatsBase { chartView.animationOptions = ChartView.NoAnimation var timestamps = categoryAxis.timestamps; - timestamps.splice(0, 1) timestamps.push(entry.timestamp) + timestamps.splice(0, 1) categoryAxis.timestamps = timestamps + d.consumptionSet.remove(0, 1); + d.productionSet.remove(0, 1); + d.acquisitionSet.remove(0, 1); + d.returnSet.remove(0, 1); + d.consumptionSet.append(consumptionValue) d.productionSet.append(productionValue) d.acquisitionSet.append(acquisitionValue) d.returnSet.append(returnValue) - d.consumptionSet.remove(0, 1); - d.productionSet.remove(0, 1); - d.acquisitionSet.remove(0, 1); - d.returnSet.remove(0, 1); + chartView.animationOptions = ChartView.SeriesAnimations } } @@ -359,7 +362,7 @@ StatsBase { } Label { - text: categoryAxis.timestamps.length > toolTip.idx ? root.configs[selectionTabs.currentValue.config].toLongLabel(categoryAxis.timestamps[toolTip.idx]) : "" + text: toolTip.idx >= 0 && categoryAxis.timestamps.length > toolTip.idx ? root.configs[selectionTabs.currentValue.config].toLongLabel(categoryAxis.timestamps[toolTip.idx]) : "" font: Style.smallFont }