From 3e31724f90a503646d582cfaa18704290ee5635c Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 29 Nov 2021 14:32:23 +0100 Subject: [PATCH] More work and fixes on the energy dashboard --- libnymea-app/energy/energylogs.cpp | 27 - libnymea-app/energy/energylogs.h | 6 - libnymea-app/energy/powerbalancelogs.cpp | 5 +- libnymea-app/energy/thingpowerlogs.cpp | 8 +- libnymea-app/models/logsmodel.cpp | 4 + libnymea-app/models/packagesfiltermodel.cpp | 2 +- libnymea-app/tagsmanager.cpp | 1 + libnymea-app/tagsmanager.h | 2 +- libnymea-app/thingsproxy.cpp | 117 ++-- libnymea-app/thingsproxy.h | 15 + nymea-app/resources.qrc | 2 + nymea-app/ui/StyleBase.qml | 2 + nymea-app/ui/mainviews/EnergyView.qml | 98 ++-- .../ui/mainviews/energy/ConsumerStats.qml | 323 ++++++----- .../ui/mainviews/energy/ConsumersBarChart.qml | 10 +- .../ui/mainviews/energy/ConsumersHistory.qml | 34 +- .../mainviews/energy/EnergySettingsPage.qml | 84 +++ .../ui/mainviews/energy/PowerBalanceStats.qml | 545 +++++++----------- nymea-app/ui/mainviews/energy/StatsBase.qml | 177 ++++++ nymea-app/ui/system/PackageListPage.qml | 6 +- 20 files changed, 871 insertions(+), 597 deletions(-) create mode 100644 nymea-app/ui/mainviews/energy/EnergySettingsPage.qml create mode 100644 nymea-app/ui/mainviews/energy/StatsBase.qml diff --git a/libnymea-app/energy/energylogs.cpp b/libnymea-app/energy/energylogs.cpp index 0a26cb83..03101c8a 100644 --- a/libnymea-app/energy/energylogs.cpp +++ b/libnymea-app/energy/energylogs.cpp @@ -84,32 +84,6 @@ void EnergyLogs::setSampleRate(SampleRate sampleRate) } } -bool EnergyLogs::fetchPowerBalance() const -{ - return m_fetchPowerBalance; -} - -void EnergyLogs::setFetchPowerBalance(bool fetchPowerBalance) -{ - if (m_fetchPowerBalance != fetchPowerBalance) { - m_fetchPowerBalance = fetchPowerBalance; - emit fetchPowerBalanceChanged(); - } -} - -QList EnergyLogs::thingIds() const -{ - return m_thingIds; -} - -void EnergyLogs::setThingIds(const QList &thingIds) -{ - if (m_thingIds != thingIds) { - m_thingIds = thingIds; - emit thingIdsChanged(); - } -} - QDateTime EnergyLogs::startTime() const { return m_startTime; @@ -118,7 +92,6 @@ QDateTime EnergyLogs::startTime() const void EnergyLogs::setStartTime(const QDateTime &startTime) { if (m_startTime != startTime) { - qCDebug(dcEnergyLogs()) << "Setting startTime"; m_startTime = startTime; emit startTimeChanged(); } diff --git a/libnymea-app/energy/energylogs.h b/libnymea-app/energy/energylogs.h index 11209dbf..c2600b28 100644 --- a/libnymea-app/energy/energylogs.h +++ b/libnymea-app/energy/energylogs.h @@ -57,12 +57,6 @@ public: SampleRate sampleRate() const; void setSampleRate(SampleRate sampleRate); - bool fetchPowerBalance() const; - void setFetchPowerBalance(bool fetchPowerBalance); - - QList thingIds() const; - void setThingIds(const QList &thingIds); - QDateTime startTime() const; void setStartTime(const QDateTime &startTime); diff --git a/libnymea-app/energy/powerbalancelogs.cpp b/libnymea-app/energy/powerbalancelogs.cpp index b0ca183a..ff14c1a2 100644 --- a/libnymea-app/energy/powerbalancelogs.cpp +++ b/libnymea-app/energy/powerbalancelogs.cpp @@ -126,7 +126,7 @@ EnergyLogEntry *PowerBalanceLogs::find(const QDateTime ×tamp) const int newest = rowCount() - 1; EnergyLogEntry *entry = nullptr; int step = 0; - while (oldest < newest && step < rowCount()) { + while (oldest <= newest && step < rowCount()) { EnergyLogEntry *oldestEntry = get(oldest); EnergyLogEntry *newestEntry = get(newest); int middle = (newest - oldest) / 2 + oldest; @@ -155,7 +155,6 @@ EnergyLogEntry *PowerBalanceLogs::find(const QDateTime ×tamp) const step++; } return entry; - } void PowerBalanceLogs::logEntriesReceived(const QVariantMap ¶ms) @@ -172,7 +171,7 @@ void PowerBalanceLogs::logEntriesReceived(const QVariantMap ¶ms) double totalAcquisition = map.value("totalAcquisition").toDouble(); double totalReturn = map.value("totalReturn").toDouble(); PowerBalanceLogEntry *entry = new PowerBalanceLogEntry(timestamp, consumption, production, acquisition, storage, totalConsumption, totalProduction, totalAcquisition, totalReturn, this); - qCritical() << "Adding entry:" << entry->timestamp() << entry->totalConsumption(); +// qCritical() << "Adding entry:" << entry->timestamp() << entry->totalConsumption(); addEntry(entry); } diff --git a/libnymea-app/energy/thingpowerlogs.cpp b/libnymea-app/energy/thingpowerlogs.cpp index e16aa58e..4144bb31 100644 --- a/libnymea-app/energy/thingpowerlogs.cpp +++ b/libnymea-app/energy/thingpowerlogs.cpp @@ -73,15 +73,17 @@ EnergyLogEntry *ThingPowerLogs::find(const QUuid &thingId, const QDateTime &time { // 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... +// qWarning() << "Finding item for" << thingId.toString() << timestamp.toString(); for (int i = rowCount() - 1; i >= 0; i--) { ThingPowerLogEntry *entry = static_cast(get(i)); if (entry->thingId() != thingId) { continue; } +// qWarning() << "comparing" << entry->timestamp().toString(); if (timestamp == entry->timestamp()) { return entry; } - if (timestamp < entry->timestamp()) { + if (timestamp > entry->timestamp()) { return nullptr; // Giving up, entry is not here } } @@ -130,6 +132,7 @@ void ThingPowerLogs::logEntriesReceived(const QVariantMap ¶ms) double totalConsumption = map.value("totalConsumption").toDouble(); double totalProduction = map.value("totalProduction").toDouble(); ThingPowerLogEntry *entry = new ThingPowerLogEntry(timestamp, thingId, currentPower, totalConsumption, totalProduction, this); +// qWarning() << "Adding entry:" << entry->thingId() << entry->timestamp().toString() << entry->totalConsumption(); if (groupForTimestamp.isEmpty()) { groupForTimestamp.append(entry); @@ -156,6 +159,9 @@ void ThingPowerLogs::notificationReceived(const QVariantMap &data) QVariantMap map = params.value("thingPowerLogEntry").toMap(); QDateTime timestamp = QDateTime::fromSecsSinceEpoch(map.value("timestamp").toLongLong()); QUuid thingId = map.value("thingId").toUuid(); + if (!m_thingIds.isEmpty() && !m_thingIds.contains(thingId)) { + return; + } double currentPower = map.value("currentPower").toDouble(); double totalConsumption = map.value("totalConsumption").toDouble(); double totalProduction = map.value("totalProduction").toDouble(); diff --git a/libnymea-app/models/logsmodel.cpp b/libnymea-app/models/logsmodel.cpp index bf14a307..d95a928a 100644 --- a/libnymea-app/models/logsmodel.cpp +++ b/libnymea-app/models/logsmodel.cpp @@ -233,10 +233,14 @@ LogEntry *LogsModel::get(int index) const LogEntry *LogsModel::findClosest(const QDateTime &dateTime) { qWarning() << "********************Finding closest for:" << dateTime.time().toString(); + if (m_list.isEmpty()) { + return nullptr; + } int newest = 0; int oldest = m_list.count() - 1; LogEntry *entry = nullptr; int step = 0; + LogEntry *allTimeOldestEntry = m_list.at(oldest); if (dateTime < allTimeOldestEntry->timestamp()) { return nullptr; diff --git a/libnymea-app/models/packagesfiltermodel.cpp b/libnymea-app/models/packagesfiltermodel.cpp index 82a834c1..eaa55623 100644 --- a/libnymea-app/models/packagesfiltermodel.cpp +++ b/libnymea-app/models/packagesfiltermodel.cpp @@ -98,7 +98,7 @@ bool PackagesFilterModel::filterAcceptsRow(int source_row, const QModelIndex &so } } if (!m_nameFilter.isEmpty()) { - if (!m_packages->get(source_row)->displayName().contains(m_nameFilter)) { + if (!m_packages->get(source_row)->displayName().toLower().contains(m_nameFilter.toLower())) { return false; } } diff --git a/libnymea-app/tagsmanager.cpp b/libnymea-app/tagsmanager.cpp index 07129bea..bc4ec3b5 100644 --- a/libnymea-app/tagsmanager.cpp +++ b/libnymea-app/tagsmanager.cpp @@ -164,6 +164,7 @@ void TagsManager::getTagsResponse(int /*commandId*/, const QVariantMap ¶ms) m_tags->addTags(tags); m_busy = false; + qWarning() << "Tags busy changed to false"; emit busyChanged(); } diff --git a/libnymea-app/tagsmanager.h b/libnymea-app/tagsmanager.h index 6f7d8f9e..08902306 100644 --- a/libnymea-app/tagsmanager.h +++ b/libnymea-app/tagsmanager.h @@ -80,7 +80,7 @@ private: JsonRpcClient *m_jsonClient = nullptr; Tags *m_tags = nullptr; - bool m_busy = false; + bool m_busy = true; }; #endif // TAGSMANAGER_H diff --git a/libnymea-app/thingsproxy.cpp b/libnymea-app/thingsproxy.cpp index 838aa397..21c54f87 100644 --- a/libnymea-app/thingsproxy.cpp +++ b/libnymea-app/thingsproxy.cpp @@ -56,7 +56,7 @@ void ThingsProxy::setEngine(Engine *engine) return; } - connect(m_engine->tagsManager()->tags(), &Tags::countChanged, this, &ThingsProxy::invalidateFilter); + connect(m_engine->tagsManager()->tags(), &Tags::countChanged, this, &ThingsProxy::invalidateFilterInternal); if (!sourceModel()) { setSourceModel(m_engine->thingManager()->things()); @@ -65,10 +65,8 @@ void ThingsProxy::setEngine(Engine *engine) sort(0, sortOrder()); connect(sourceModel(), SIGNAL(countChanged()), this, SIGNAL(countChanged())); connect(sourceModel(), &QAbstractItemModel::dataChanged, this, [this]() { - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); }); - } } } @@ -92,8 +90,7 @@ void ThingsProxy::setParentProxy(ThingsProxy *parentProxy) connect(m_parentProxy, SIGNAL(countChanged()), this, SIGNAL(countChanged())); connect(m_parentProxy, &QAbstractItemModel::dataChanged, this, [this]() { if (m_engine) { - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } }); @@ -116,8 +113,7 @@ void ThingsProxy::setFilterTagId(const QString &filterTag) if (m_filterTagId != filterTag) { m_filterTagId = filterTag; emit filterTagIdChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -131,8 +127,35 @@ void ThingsProxy::setFilterTagValue(const QString &tagValue) if (m_filterTagValue != tagValue) { m_filterTagValue = tagValue; emit filterTagValueChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); + } +} + +QString ThingsProxy::hideTagId() const +{ + return m_hideTagId; +} + +void ThingsProxy::setHideTagId(const QString &tagId) +{ + if (m_hideTagId != tagId) { + m_hideTagId = tagId; + emit hideTagIdChanged(); + invalidateFilterInternal(); + } +} + +QString ThingsProxy::hideTagValue() const +{ + return m_hideTagValue; +} + +void ThingsProxy::setHideTagValue(const QString &tagValue) +{ + if (m_hideTagValue != tagValue) { + m_hideTagValue = tagValue; + emit hideTagValueChanged(); + invalidateFilterInternal(); } } @@ -146,8 +169,7 @@ void ThingsProxy::setFilterThingId(const QString &filterThingId) if (m_filterThingId != filterThingId) { m_filterThingId = filterThingId; emit filterThingIdChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -161,8 +183,7 @@ void ThingsProxy::setShownInterfaces(const QStringList &shownInterfaces) if (m_shownInterfaces != shownInterfaces) { m_shownInterfaces = shownInterfaces; emit shownInterfacesChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -176,8 +197,7 @@ void ThingsProxy::setHiddenInterfaces(const QStringList &hiddenInterfaces) if (m_hiddenInterfaces != hiddenInterfaces) { m_hiddenInterfaces = hiddenInterfaces; emit hiddenInterfacesChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -191,8 +211,7 @@ void ThingsProxy::setNameFilter(const QString &nameFilter) if (m_nameFilter != nameFilter) { m_nameFilter = nameFilter; emit nameFilterChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -206,8 +225,7 @@ void ThingsProxy::setShownThingClassIds(const QList &shownThingClassIds) if (m_shownThingClassIds != shownThingClassIds) { m_shownThingClassIds = shownThingClassIds; emit shownThingClassIdsChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -222,8 +240,7 @@ void ThingsProxy::setHiddenThingClassIds(const QList &hiddenThingClassIds if (m_hiddenThingClassIds != hiddenThingClassIds) { m_hiddenThingClassIds = hiddenThingClassIds; emit hiddenThingClassIdsChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -237,8 +254,7 @@ void ThingsProxy::setRequiredEventName(const QString &requiredEventName) if (m_requiredEventName != requiredEventName) { m_requiredEventName = requiredEventName; emit requiredEventNameChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -252,8 +268,7 @@ void ThingsProxy::setRequiredStateName(const QString &requiredStateName) if (m_requiredStateName != requiredStateName) { m_requiredStateName = requiredStateName; emit requiredStateNameChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -267,8 +282,7 @@ void ThingsProxy::setRequiredActionName(const QString &requiredActionName) if (m_requiredActionName != requiredActionName) { m_requiredActionName = requiredActionName; emit requiredActionNameChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -282,8 +296,7 @@ void ThingsProxy::setShowDigitalInputs(bool showDigitalInputs) if (m_showDigitalInputs != showDigitalInputs) { m_showDigitalInputs = showDigitalInputs; emit showDigitalInputsChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -297,8 +310,7 @@ void ThingsProxy::setShowDigitalOutputs(bool showDigitalOutputs) if (m_showDigitalOutputs != showDigitalOutputs) { m_showDigitalOutputs = showDigitalOutputs; emit showDigitalOutputsChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -312,8 +324,7 @@ void ThingsProxy::setShowAnalogInputs(bool showAnalogInputs) if (m_showAnalogInputs != showAnalogInputs) { m_showAnalogInputs = showAnalogInputs; emit showAnalogInputsChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -327,8 +338,7 @@ void ThingsProxy::setShowAnalogOutputs(bool showAnalogOutputs) if (m_showAnalogOutputs != showAnalogOutputs) { m_showAnalogOutputs = showAnalogOutputs; emit showAnalogOutputsChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -342,8 +352,7 @@ void ThingsProxy::setFilterBatteryCritical(bool filterBatteryCritical) if (m_filterBatteryCritical != filterBatteryCritical) { m_filterBatteryCritical = filterBatteryCritical; emit filterBatteryCriticalChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -357,8 +366,7 @@ void ThingsProxy::setFilterDisconnected(bool filterDisconnected) if (m_filterDisconnected != filterDisconnected) { m_filterDisconnected = filterDisconnected; emit filterDisconnectedChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -372,8 +380,7 @@ void ThingsProxy::setFilterSetupFailed(bool filterSetupFailed) if (m_filterSetupFailed != filterSetupFailed) { m_filterSetupFailed = filterSetupFailed; emit filterSetupFailedChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -387,8 +394,7 @@ void ThingsProxy::setFilterUpdates(bool filterUpdates) if (m_filterUpdates != filterUpdates) { m_filterUpdates = filterUpdates; emit filterUpdatesChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -403,8 +409,7 @@ void ThingsProxy::setParamsFilter(const QVariantMap ¶msFilter) m_paramsFilter = paramsFilter; emit paramsFilterChanged(); - invalidateFilter(); - emit countChanged(); + invalidateFilterInternal(); } } @@ -477,6 +482,15 @@ int ThingsProxy::indexOf(Thing *thing) const return mapFromSource(sourceIndex).row(); } +void ThingsProxy::invalidateFilterInternal() +{ + int oldCount = rowCount(); + invalidateFilter(); + if (oldCount != rowCount()) { + emit countChanged(); + } +} + Thing *ThingsProxy::getInternal(int source_index) const { Things* d = qobject_cast(sourceModel()); @@ -543,11 +557,22 @@ bool ThingsProxy::filterAcceptsRow(int source_row, const QModelIndex &source_par return false; } } + if (!m_hideTagId.isEmpty()) { + Tag *tag = m_engine->tagsManager()->tags()->findThingTag(thing->id().toString(), m_hideTagId); + if (tag && m_hideTagValue.isEmpty()) { + return false; + } + if (tag && tag->value() == m_hideTagValue) { + return false; + } + } + if (!m_filterThingId.isEmpty()) { if (thing->id() != QUuid(m_filterThingId)) { return false; } } + ThingClass *thingClass = m_engine->thingManager()->thingClasses()->getThingClass(thing->thingClassId()); // qDebug() << "Checking thing" << thingClass->name() << thingClass->interfaces(); if (!m_shownInterfaces.isEmpty()) { diff --git a/libnymea-app/thingsproxy.h b/libnymea-app/thingsproxy.h index 2ae4718f..dab6f1d5 100644 --- a/libnymea-app/thingsproxy.h +++ b/libnymea-app/thingsproxy.h @@ -48,6 +48,8 @@ class ThingsProxy : public QSortFilterProxyModel Q_PROPERTY(QString filterTagId READ filterTagId WRITE setFilterTagId NOTIFY filterTagIdChanged) Q_PROPERTY(QString filterTagValue READ filterTagValue WRITE setFilterTagValue NOTIFY filterTagValueChanged) Q_PROPERTY(QString filterThingId READ filterThingId WRITE setFilterThingId NOTIFY filterThingIdChanged) + Q_PROPERTY(QString hideTagId READ hideTagId WRITE setHideTagId NOTIFY hideTagIdChanged) + Q_PROPERTY(QString hideTagValue READ hideTagValue WRITE setHideTagValue NOTIFY hideTagValueChanged) Q_PROPERTY(QStringList shownInterfaces READ shownInterfaces WRITE setShownInterfaces NOTIFY shownInterfacesChanged) Q_PROPERTY(QStringList hiddenInterfaces READ hiddenInterfaces WRITE setHiddenInterfaces NOTIFY hiddenInterfacesChanged) Q_PROPERTY(QString nameFilter READ nameFilter WRITE setNameFilter NOTIFY nameFilterChanged) @@ -101,6 +103,12 @@ public: QString filterTagValue() const; void setFilterTagValue(const QString &tagValue); + QString hideTagId() const; + void setHideTagId(const QString &tagId); + + QString hideTagValue() const; + void setHideTagValue(const QString &tagValue); + QString filterThingId() const; void setFilterThingId(const QString &filterThingId); @@ -172,6 +180,8 @@ signals: void parentProxyChanged(); void filterTagIdChanged(); void filterTagValueChanged(); + void hideTagIdChanged(); + void hideTagValueChanged(); void filterThingIdChanged(); void shownInterfacesChanged(); void hiddenInterfacesChanged(); @@ -195,6 +205,9 @@ signals: void sortOrderChanged(); void countChanged(); +private slots: + void invalidateFilterInternal(); + private: Thing *getInternal(int source_index) const; @@ -202,6 +215,8 @@ private: ThingsProxy *m_parentProxy = nullptr; QString m_filterTagId; QString m_filterTagValue; + QString m_hideTagId; + QString m_hideTagValue; QString m_filterThingId; QStringList m_shownInterfaces; QStringList m_hiddenInterfaces; diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 78355e9c..cad4d04c 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -269,5 +269,7 @@ ui/mainviews/energy/CurrentConsumptionBalancePieChart.qml ui/mainviews/energy/ConsumerStats.qml ui/system/PackageListPage.qml + ui/mainviews/energy/StatsBase.qml + ui/mainviews/energy/EnergySettingsPage.qml diff --git a/nymea-app/ui/StyleBase.qml b/nymea-app/ui/StyleBase.qml index 752d3865..37377df4 100644 --- a/nymea-app/ui/StyleBase.qml +++ b/nymea-app/ui/StyleBase.qml @@ -110,6 +110,8 @@ Item { property color darkGray: "darkGray" property color blue: "deepskyblue" property color orange: "#f6a625" + property color purple: "#6d5fd5" + property color lime: "#99ca53" readonly property int fastAnimationDuration: 100 readonly property int animationDuration: 150 diff --git a/nymea-app/ui/mainviews/EnergyView.qml b/nymea-app/ui/mainviews/EnergyView.qml index 7e69341a..b782043a 100644 --- a/nymea-app/ui/mainviews/EnergyView.qml +++ b/nymea-app/ui/mainviews/EnergyView.qml @@ -44,11 +44,25 @@ MainViewBase { contentY: flickable.contentY + topMargin + headerButtons: [ + { + iconSource: "/ui/images/configure.svg", + color: Style.iconColor, + trigger: function() { + pageStack.push("energy/EnergySettingsPage.qml", {energyManager: energyManager}); + }, + visible: true + } + ] + EnergyManager { id: energyManager engine: _engine } + property var thingColors: [Style.blue, Style.green, Style.red, Style.yellow, Style.purple, Style.yellow, Style.lime] + + ThingsProxy { id: energyMeters engine: _engine @@ -60,6 +74,7 @@ MainViewBase { id: consumers engine: _engine shownInterfaces: ["smartmeterconsumer"] + hideTagId: "hiddenInEnergyView" } ThingsProxy { @@ -97,57 +112,60 @@ MainViewBase { columnSpacing: 0 -// CurrentConsumptionBalancePieChart { -// Layout.fillWidth: true -// Layout.preferredHeight: width -// energyManager: energyManager -// visible: producers.count > 0 -// } -// CurrentProductionBalancePieChart { -// Layout.fillWidth: true -// Layout.preferredHeight: width -// energyManager: energyManager -// visible: producers.count > 0 -// } + CurrentConsumptionBalancePieChart { + Layout.fillWidth: true + Layout.preferredHeight: width + energyManager: energyManager + } + CurrentProductionBalancePieChart { + Layout.fillWidth: true + Layout.preferredHeight: width + energyManager: energyManager + visible: producers.count > 0 + } -// PowerConsumptionBalanceHistory { -// Layout.fillWidth: true -// Layout.preferredHeight: width -// visible: producers.count > 0 -// } + PowerConsumptionBalanceHistory { + Layout.fillWidth: true + Layout.preferredHeight: width + } -// PowerProductionBalanceHistory { -// Layout.fillWidth: true -// Layout.preferredHeight: width -// visible: producers.count > 0 -// } + PowerProductionBalanceHistory { + Layout.fillWidth: true + Layout.preferredHeight: width + visible: producers.count > 0 + } -// ConsumersBarChart { -// Layout.fillWidth: true -// Layout.preferredHeight: width -// energyManager: energyManager -// visible: consumers.count > 0 -// } -// ConsumersHistory { -// Layout.fillWidth: true -// Layout.preferredHeight: width -// visible: consumers.count > 0 -// } + ConsumersBarChart { + Layout.fillWidth: true + Layout.preferredHeight: width + energyManager: energyManager + visible: consumers.count > 0 + colors: root.thingColors + consumers: consumers + } + ConsumersHistory { + Layout.fillWidth: true + Layout.preferredHeight: width + visible: consumers.count > 0 + colors: root.thingColors + consumers: consumers + } PowerBalanceStats { Layout.fillWidth: true Layout.preferredHeight: width energyManager: energyManager } -// ConsumerStats { -// Layout.fillWidth: true -// Layout.preferredHeight: width -// energyManager: energyManager -// visible: consumers.count > 0 -// } + ConsumerStats { + Layout.fillWidth: true + Layout.preferredHeight: width + energyManager: energyManager + visible: consumers.count > 0 + colors: root.thingColors + consumers: consumers + } } } - } EmptyViewPlaceholder { diff --git a/nymea-app/ui/mainviews/energy/ConsumerStats.qml b/nymea-app/ui/mainviews/energy/ConsumerStats.qml index 9a58bbdb..b4f6430c 100644 --- a/nymea-app/ui/mainviews/energy/ConsumerStats.qml +++ b/nymea-app/ui/mainviews/energy/ConsumerStats.qml @@ -3,180 +3,241 @@ import QtQuick.Layouts 1.2 import QtQuick.Controls 2.3 import QtCharts 2.3 import Nymea 1.0 +import "qrc:/ui/components/" -ChartView { +StatsBase { id: root - backgroundColor: "transparent" - legend.alignment: Qt.AlignBottom - legend.font: Style.extraSmallFont - legend.labelColor: !powerLogs.fetchingData && powerLogs.count > 0 ? Style.foregroundColor : Style.gray - -// margins.left: 0 - margins.right: 0 - margins.bottom: 0 - margins.top: 0 - - title: qsTr("Consumer statistics") - titleColor: Style.foregroundColor property EnergyManager energyManager: null + property var colors: null + property ThingsProxy consumers: null - readonly property date dayStart: { - var d = new Date(); - d.setHours(0,0,0,0); - return d; + Connections { + target: consumers + onCountChanged: root.update() } - readonly property var daysList: { - var ret = [] - for (var i = 6; i >= 0; i--) { - var last = new Date(dayStart) - ret.push(last.setDate(last.getDate() - i)) - } - return ret; - } - - readonly property var daysListNames: { - var ret = [] - for (var i = 0; i < daysList.length; i++) { - ret.push(new Date(daysList[i]).toLocaleString(Qt.locale(), "ddd")) - } - return ret; - } - - readonly property date weekStart: { - var d = new Date(); - d.setHours(0, 0, 0, 0); - d.setDate(d.getDate() - d.getDay()); - return d - } - readonly property date monthStart: { - var d = new Date(); - d.setHours(0,0,0,0); - d.setDate(1); - return d; - } - readonly property date yearStart: { - var d = new Date(); - d.setHours(0,0,0,0); - d.setDate(1); - d.setMonth(0); - return d; - } - - - ThingsProxy { - id: consumers - engine: _engine - shownInterfaces: ["smartmeterconsumer"] - sortStateName: "totalEnergyConsumed" - sortOrder: Qt.DescendingOrder - - } Connections { target: engine.thingManager - onFetchingDataChanged: { - var thingIds = [] - for (var i = 0; i < consumers.count; i++) { - thingIds.push(consumers.get(i).id) - } - powerLogs.thingIds = thingIds + onFetchingDataChanged: root.update() + } + Connections { + target: engine.tagsManager + onBusyChanged: root.update() + } + + function update() { + if (engine.thingManager.fetchingData || engine.tagsManager.busy) { + return } + powerLogs.loadingInhibited = true + + var thingIds = [] + for (var i = 0; i < consumers.count; i++) { + thingIds.push(consumers.get(i).id) + } + powerLogs.thingIds = thingIds + + var config = root.configs[selectionTabs.currentValue.config] +// print("config:", config.startTime(), config.sampleList(), config.sampleListNames()) + + powerLogs.sampleRate = config.sampleRate + powerLogs.startTime = config.startTime() + powerLogs.sampleList = config.sampleList() + + barSeries.clear(); + barSeries.thingBarSetMap = ({}) + + valueAxis.max = 0 + categoryAxis.categories = config.sampleListNames() + + chartView.animationOptions = ChartView.SeriesAnimations + + powerLogs.loadingInhibited = false } ThingPowerLogs { id: powerLogs engine: _engine - sampleRate: EnergyLogs.SampleRate1Day - startTime: root.yearStart - loadingInhibited: thingIds.length === 0 + loadingInhibited: true + + property var sampleList: null onFetchingDataChanged: { if (!fetchingData) { barSeries.clear(); for (var j = 0; j < consumers.count; j++) { - var consumer = consumers.get(j) + // Note: Needs to be let, not var so the lambda capture below copies it instead of capturing the reference + let consumer = consumers.get(j) +// print("ConsumerStats: Adding thing:", consumer.name) + let totalEnergyConsumedState = consumer.stateByName("totalEnergyConsumed") +// print("Adding consumer:", consumer.name, consumer.id) var consumptionValues = [] - for (var i = 0; i < daysList.length; i++) { - var start = powerLogs.find(consumer.id, new Date(daysList[i])) + for (var i = 0; i < sampleList.length; i++) { + var start = powerLogs.find(consumer.id, new Date(sampleList[i])) var startValue = start !== null ? start.totalConsumption : 0 - var end = i < daysList.length -1 ? powerLogs.find(consumer.id, new Date(daysList[i+1])) : null - var endValue = end !== null ? end.totalConsumption : start !== null ? consumer.stateByName("totalEnergyConsumed").value : 0 + var end = i < sampleList.length -1 ? powerLogs.find(consumer.id, new Date(sampleList[i+1])) : null + var endValue = end !== null ? end.totalConsumption : 0 + if (i == sampleList.length - 1) { + endValue = totalEnergyConsumedState.value + } + +// print("adding sample", new Date(sampleList[i]), start ? start.timestamp : "X", " - ", end ? end.timestamp : "X") +// print("values. start:", startValue, "end", endValue, "diff", endValue - startValue) var consumptionValue = endValue - startValue +// print("Value", consumptionValue) consumptionValues.push(consumptionValue) valueAxis.adjustMax(consumptionValue) } - var barSet = barSeries.append(consumer.name, consumptionValues) + let barSet = barSeries.append(consumer.name, consumptionValues) + barSet.color = root.colors[j % root.colors.length] barSet.borderWidth = 0 barSet.borderColor = barSet.color + barSeries.thingBarSetMap[consumer] = barSet + + totalEnergyConsumedState.onValueChanged.connect(function() { + var sampleList = root.configs[selectionTabs.currentValue.config].sampleList() + var lastSample = sampleList[sampleList.length - 1] +// print("sampleList:", powerLogs.sampleList) + var start = powerLogs.find(consumer.id, new Date(lastSample)) +// print("consumer value changed:", consumer.name, totalEnergyConsumedState.value, start.timestamp, start.totalConsumption) + var barSet = barSeries.thingBarSetMap[consumer] + barSet.replace(barSet.count - 1, totalEnergyConsumedState.value - start.totalConsumption) + }) } } } - } - Item { - id: labelsLayout - x: Style.smallMargins - y: root.plotArea.y - height: root.plotArea.height - width: plotArea.x - x - Repeater { - model: valueAxis.tickCount - delegate: Label { - y: parent.height / (valueAxis.tickCount - 1) * index - font.pixelSize / 2 - width: parent.width - Style.smallMargins - horizontalAlignment: Text.AlignRight - text: ((valueAxis.max - (index * valueAxis.max / (valueAxis.tickCount - 1)))).toFixed(0) + "kWh" - verticalAlignment: Text.AlignTop - font: Style.extraSmallFont - color: !powerLogs.fetchingData && powerLogs.count > 0 ? Style.foregroundColor : Style.gray + onEntriesAdded: { + if (fetchingData) { + return } + + chartView.animationOptions = ChartView.NoAnimation + + for (var i = 0; i < entries.length; i++) { + 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) + + var consumptionValue = totalEnergyConsumedState.value - entry.totalConsumption +// print("new slot total:", consumptionValue) + + categoryAxis.categories = configs[selectionTabs.currentValue.config].sampleListNames() + barSeries.thingBarSetMap[thing].append(consumptionValue) + barSeries.thingBarSetMap[thing].remove(0, 1); + } + + chartView.animationOptions = ChartView.SeriesAnimations } } - BarSeries { - id: barSeries - axisX: BarCategoryAxis { - id: categoryAxis - categories: daysListNames - labelsColor: !powerLogs.fetchingData && powerLogs.count > 0 ? Style.foregroundColor : Style.gray - labelsFont: Style.extraSmallFont - gridVisible: false - gridLineColor: Style.tileOverlayColor - lineVisible: false - titleVisible: false - shadesVisible: false + ColumnLayout { + anchors.fill: parent + + Label { + Layout.fillWidth: true + Layout.margins: Style.smallMargins + horizontalAlignment: Text.AlignHCenter + text: qsTr("Consumers statistics") } - axisY: ValueAxis { - id: valueAxis - min: 0 - gridLineColor: Style.tileOverlayColor - labelsVisible: false - labelsColor: Style.foregroundColor - labelsFont: Style.extraSmallFont - lineVisible: false - titleVisible: false - shadesVisible: false - function adjustMax(newValue) { - if (max < newValue) { - max = Math.ceil(newValue) + SelectionTabs { + id: selectionTabs + Layout.fillWidth: true + Layout.leftMargin: Style.smallMargins + Layout.rightMargin: Style.smallMargins + currentIndex: 0 + model: ListModel { + Component.onCompleted: { + append({modelData: qsTr("Months"), config: "months" }) + append({modelData: qsTr("Weeks"), config: "weeks" }) + append({modelData: qsTr("Days"), config: "days" }) + append({modelData: qsTr("Hours"), config: "hours" }) +// append({modelData: qsTr("Minutes"), config: "minutes" }) + + selectionTabs.currentIndex = 2 } } + onCurrentValueChanged: { + root.update() + } } - } - Label { - x: root.plotArea.x - y: root.plotArea.y - width: root.plotArea.width - height: root.plotArea.height - wrapMode: Text.WordWrap - text: qsTr("No data available") - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - visible: !powerLogs.fetchingData && powerLogs.count == 0 + + ChartView { + id: chartView + Layout.fillWidth: true + Layout.fillHeight: true + + // margins.left: 0 + margins.right: 0 + margins.bottom: 0 + margins.top: 0 + + backgroundColor: "transparent" + legend.alignment: Qt.AlignBottom + legend.font: Style.extraSmallFont + legend.labelColor: Style.foregroundColor + + + Item { + id: labelsLayout + x: Style.smallMargins + y: chartView.plotArea.y + height: chartView.plotArea.height + width: chartView.plotArea.x - x + Repeater { + model: valueAxis.tickCount + delegate: Label { + y: parent.height / (valueAxis.tickCount - 1) * index - font.pixelSize / 2 + width: parent.width - Style.smallMargins + horizontalAlignment: Text.AlignRight + text: ((valueAxis.max - (index * valueAxis.max / (valueAxis.tickCount - 1)))).toFixed(1) + "kWh" + verticalAlignment: Text.AlignTop + font: Style.extraSmallFont + color: Style.foregroundColor + } + } + } + + BarSeries { + id: barSeries + axisX: BarCategoryAxis { + id: categoryAxis + labelsColor: Style.foregroundColor + labelsFont: Style.extraSmallFont + gridVisible: false + gridLineColor: Style.tileOverlayColor + lineVisible: false + titleVisible: false + shadesVisible: false + } + axisY: ValueAxis { + id: valueAxis + min: 0 + gridLineColor: Style.tileOverlayColor + labelsVisible: false + labelsColor: Style.foregroundColor + labelsFont: Style.extraSmallFont + lineVisible: false + titleVisible: false + shadesVisible: false + + function adjustMax(newValue) { + if (max < newValue) { + max = Math.ceil(newValue) + } + } + } + + property var thingBarSetMap: ({}) + } + } } } diff --git a/nymea-app/ui/mainviews/energy/ConsumersBarChart.qml b/nymea-app/ui/mainviews/energy/ConsumersBarChart.qml index 58be1390..2000f714 100644 --- a/nymea-app/ui/mainviews/energy/ConsumersBarChart.qml +++ b/nymea-app/ui/mainviews/energy/ConsumersBarChart.qml @@ -10,6 +10,13 @@ Item { property EnergyManager energyManager: null + property ThingsProxy consumers: ThingsProxy { + engine: _engine + shownInterfaces: ["smartmeterconsumer"] + } + + property var colors: null + property int tickCount: 5 property int labelsWidth: 40 @@ -148,13 +155,12 @@ Item { ColorIcon { anchors.centerIn: parent name: consumerDelegate.thing ? app.interfacesToIcon(consumerDelegate.thing.thingClass.interfaces) : "energy" + color: root.colors[index % root.colors.length] } } - } } } } } - } diff --git a/nymea-app/ui/mainviews/energy/ConsumersHistory.qml b/nymea-app/ui/mainviews/energy/ConsumersHistory.qml index ba0b02af..bf5abe6b 100644 --- a/nymea-app/ui/mainviews/energy/ConsumersHistory.qml +++ b/nymea-app/ui/mainviews/energy/ConsumersHistory.qml @@ -12,6 +12,8 @@ ChartView { margins.bottom: 0 margins.top: 0 + property var colors: null + title: qsTr("Consumers history") titleColor: Style.foregroundColor @@ -19,6 +21,17 @@ ChartView { legend.labelColor: Style.foregroundColor legend.font: Style.extraSmallFont + property ThingsProxy consumers: null + + Connections { + target: consumers + onCountChanged: d.updateConsumers() + } + Connections { + target: engine.tagsManager + onBusyChanged: d.updateConsumers() + } + ThingPowerLogs { id: thingPowerLogs engine: _engine @@ -75,14 +88,14 @@ ChartView { } } - ThingsProxy { - id: consumers - engine: _engine - shownInterfaces: ["smartmeterconsumer"] - } Connections { target: engine.thingManager onFetchingDataChanged: d.updateConsumers() + onThingAdded: { + if (thing.thingClass.interfaces.indexOf("smartmeterconsumer") >= 0) { + d.updateConsumers(); + } + } } @@ -100,9 +113,10 @@ ChartView { property var thingsSeriesMap: ({}) function updateConsumers() { - if (engine.thingManager.fetchingData) { + if (engine.thingManager.fetchingData || engine.tagsManager.busy) { return; } + thingPowerLogs.loadingInhibited = true; for (var thingId in d.thingsSeriesMap) { root.removeSeries(d.thingsSeriesMap[thingId]) @@ -116,20 +130,22 @@ ChartView { var baseSeries = zeroSeries; if (i > 0) { baseSeries = d.thingsSeriesMap[consumerThingIds[i-1]].upperSeries - print("base for:", thing.name, "is", engine.thingManager.things.getThing(consumerThingIds[i-1]).name) +// print("base for:", thing.name, "is", engine.thingManager.things.getThing(consumerThingIds[i-1]).name) } var series = root.createSeries(ChartView.SeriesTypeArea, thing.name, dateTimeAxis, valueAxis) series.lowerSeries = baseSeries series.upperSeries = lineSeriesComponent.createObject(series) + series.color = root.colors[i % root.colors.length] series.borderWidth = 0; series.borderColor = series.color - print("Adding thingId series", thing.id, thing.name) +// print("Adding thingId series", thing.id, thing.name) d.thingsSeriesMap[thing.id] = series consumerThingIds.push(thing.id) } thingPowerLogs.thingIds = consumerThingIds; + thingPowerLogs.loadingInhibited = false; } } @@ -197,7 +213,7 @@ ChartView { id: consumptionSeries axisX: dateTimeAxis axisY: valueAxis -// color: Style.accentColor + color: Style.gray borderWidth: 0 borderColor: color name: qsTr("Unknown") diff --git a/nymea-app/ui/mainviews/energy/EnergySettingsPage.qml b/nymea-app/ui/mainviews/energy/EnergySettingsPage.qml new file mode 100644 index 00000000..14a0c35e --- /dev/null +++ b/nymea-app/ui/mainviews/energy/EnergySettingsPage.qml @@ -0,0 +1,84 @@ +import QtQuick 2.3 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.3 +import Nymea 1.0 +import "qrc:/ui/components" + +SettingsPageBase { + id: root + title: qsTr("Energy settings") + + property EnergyManager energyManager: null + + + + SettingsPageSectionHeader { + text: qsTr("General") + visible: rootMeterProxy.count > 1 + } + + Label { + Layout.fillWidth: true + Layout.leftMargin: Style.margins + Layout.rightMargin: Style.margins + wrapMode: Text.WordWrap + text: qsTr("Multiple energy meters are installed in the system. Please select the one you'd like to use as the root meter. That is, the one measuring the entire household.") + visible: rootMeterProxy.count > 1 + } + + RowLayout { + Layout.fillWidth: true + visible: rootMeterProxy.count > 1 + Layout.leftMargin: Style.margins + Layout.rightMargin: Style.margins + + Label { + text: qsTr("Root meter") + } + ComboBox { + Layout.fillWidth: true + model: ThingsProxy { + id: rootMeterProxy + engine: _engine + shownInterfaces: ["energymeter"] + } + + textRole: "name" + currentIndex: rootMeterProxy.indexOf(rootMeterProxy.getThing(energyManager.rootMeterId)) + Component.onCompleted: print("root meter id:", energyManager.rootMeterId) + onActivated: { + energyManager.setRootMeterId(rootMeterProxy.get(index).id) + } + } + } + + SettingsPageSectionHeader { + text: qsTr("Consumers") + } + + Repeater { + model: ThingsProxy { + engine: _engine + shownInterfaces: ["smartmeterconsumer"] + } + delegate: CheckDelegate { + Layout.fillWidth: true + text: model.name + checked: !tagWatcher.tag + onToggled: { + if (checked) { + engine.tagsManager.untagThing(model.id, "hiddenInEnergyView") + } else { + engine.tagsManager.tagThing(model.id, "hiddenInEnergyView", "1") + } + } + + TagWatcher { + id: tagWatcher + tags: engine.tagsManager.tags + thingId: model.id + tagId: "hiddenInEnergyView" + } + } + } +} diff --git a/nymea-app/ui/mainviews/energy/PowerBalanceStats.qml b/nymea-app/ui/mainviews/energy/PowerBalanceStats.qml index 863538f1..3eab9d1c 100644 --- a/nymea-app/ui/mainviews/energy/PowerBalanceStats.qml +++ b/nymea-app/ui/mainviews/energy/PowerBalanceStats.qml @@ -5,341 +5,234 @@ import QtCharts 2.3 import Nymea 1.0 import "qrc:/ui/components/" -ColumnLayout { +StatsBase { id: root property EnergyManager energyManager: null - readonly property date minuteStart: { - var d = new Date(); - d.setSeconds(0, 0) - return d; - } - readonly property var minutesList: { - var ret = [] - for (var i = 15; i >= 0; i--) { - var last = new Date(minuteStart) - ret.push(last.setTime(last.getTime() - i * 60 * 1000)) - } - return ret; - } - readonly property var minutesListNames: { - var ret = [] - for (var i = 0; i < minutesList.length; i++) { - ret.push(new Date(minutesList[i]).toLocaleString(Qt.locale(), "hh:mm")) - } - return ret; + QtObject { + id: d + property BarSet consumptionSet: null + property BarSet productionSet: null + property BarSet acquisitionSet: null + property BarSet returnSet: null } - readonly property date hourStart: { - var d = new Date(); - d.setMinutes(0, 0, 0); - return d; - } - - readonly property var hoursList: { - var ret = [] - for (var i = 24; i >= 0; i--) { - var last = new Date(hourStart) - } - return ret; - } - readonly property var hoursListNames: { - var ret = []; - for (var i = 0; i < hoursList.length; i++) { - ret.push(new Date(hoursList[i]).toLocaleString(Qt.locale(), "dd")); - } - return ret; - } - - readonly property date dayStart: { - var d = new Date(); - d.setHours(0,0,0,0); - return d; - } - - readonly property var daysList: { - var ret = [] - for (var i = 6; i >= 0; i--) { - var last = new Date(dayStart) - ret.push(last.setDate(last.getDate() - i)) - } - return ret; - } - - readonly property var daysListNames: { - var ret = [] - for (var i = 0; i < daysList.length; i++) { - ret.push(new Date(daysList[i]).toLocaleString(Qt.locale(), "ddd")) - } - return ret; - } - - - readonly property date weekStart: { - var d = new Date(); - d.setHours(0, 0, 0, 0); - d.setDate(d.getDate() - d.getDay()); - return d - } - readonly property date monthStart: { - var d = new Date(); - d.setHours(0,0,0,0); - d.setDate(1); - return d; - } - readonly property date yearStart: { - var d = new Date(); - d.setHours(0,0,0,0); - d.setDate(1); - d.setMonth(0); - return d; - } - - - - Label { - Layout.fillWidth: true - Layout.margins: Style.smallMargins - horizontalAlignment: Text.AlignHCenter - text: qsTr("Energy consumption statistics") - - } - - SelectionTabs { - id: selectionTabs - Layout.fillWidth: true - Layout.leftMargin: Style.smallMargins - Layout.rightMargin: Style.smallMargins - model: ListModel { -// ListElement { -// modelData: qsTr("Year") -// sampleRate: EnergyLogs.SampleRate1Year -// } -// ListElement { -// text: qsTr("Month") -// sampleRate: EnergyLogs.SampleRate1Month -// } -// ListElement { -// text: qsTr("Week") -// sampleRate: EnergyLogs.SampleRate1Week -// } - - Component.onCompleted: { - append({modelData: qsTr("Day"), config: "days", sampleRate: EnergyLogs.SampleRate1Day, startTime: yearStart, sampleList: daysList, sampleListNames: daysListNames }) - append({modelData: qsTr("Hour"), config: "hours", sampleRate: EnergyLogs.SampleRate1Hour, startTime: monthStart, sampleList: hoursList, sampleListNames: hoursListNames }) - } - -// ListElement { -// modelData: qsTr("Day") -// sampleRate: EnergyLogs.SampleRate1Day -// startTime: weekStart -// } -// ListElement { -// modelData: qsTr("Hour") -// sampleRate: EnergyLogs.SampleRate1Hour -// startTime: dayStart -// } - } -// currentIndex: 3 - onCurrentValueChanged: { - print("Selecging model:", currentValue) - powerBalanceLogs.loadingInhibited = true - powerBalanceLogs.sampleRate = currentValue.sampleRate - powerBalanceLogs.startTime = currentValue.startTime - powerBalanceLogs.loadingInhibited = false - - consumptionSeries.remove(0, consumptionSeries.count-1) - productionSeries.remove(0, productionSeries.count-1) - acquisitionSeries.remove(0, acquisitionSeries.count-1) - returnSeries.remove(0, returnSeries.count-1) - - print("sample list names:", currentValue.sampleListNames) - categoryAxis.categories = currentValue.sampleListNames - - } - } - - Connections { - target: energyManager - onPowerBalanceChanged: { - var start = powerBalanceLogs.get(powerBalanceLogs.count - 1) - print("updating", start.timestamp, root.energyManager.totalConsumption - (start ? start.totalConsumption : 0)) - consumptionSeries.replace(consumptionSeries.count - 1, root.energyManager.totalConsumption - (start ? start.totalConsumption : 0)) - productionSeries.replace(productionSeries.count - 1, root.energyManager.totalProduction - (start ? start.totalProduction : 0)) - acquisitionSeries.replace(acquisitionSeries.count - 1, root.energyManager.totalAcquisition - (start ? start.totalAcquisition : 0)) - returnSeries.replace(returnSeries.count - 1, root.energyManager.totalReturn - (start ? start.totalReturn : 0)) - } - } - - PowerBalanceLogs { - id: powerBalanceLogs - engine: _engine -// sampleRate: EnergyLogs.SampleRate1Day -// startTime: root.yearStart; - sampleRate: selectionTabs.currentValue.sampleRate// EnergyLogs.SampleRate1Min - startTime: selectionTabs.currentValue.startTime // root.weekStart; - - property var sampleList: minutesList - - onFetchingDataChanged: { - if (!fetchingData) { - for (var i = 0; i < sampleList.length; i++) { - var start = powerBalanceLogs.find(new Date(sampleList[i])) - var end = null; - if (i+1 < sampleList.length) { - end = powerBalanceLogs.find(new Date(sampleList[i+1])) - } - print("** stats for:", new Date(sampleList[i]), start.timestamp, start.totalConsumption) - var consumptionValue = (end != null ? end.totalConsumption : root.energyManager.totalConsumption) - (start ? start.totalConsumption : 0) - var productionValue = (end != null ? end.totalProduction : root.energyManager.totalProduction) - (start ? start.totalProduction : 0) - var acquisitionValue = (end != null ? end.totalAcquisition : root.energyManager.totalAcquisition) - (start ? start.totalAcquisition : 0) - var returnValue = (end != null ? end.totalReturn : root.energyManager.totalReturn) - (start ? start.totalReturn : 0) - consumptionSeries.append(consumptionValue) - productionSeries.append(productionValue) - acquisitionSeries.append(acquisitionValue) - returnSeries.append(returnValue) - - valueAxis.adjustMax(consumptionValue) - valueAxis.adjustMax(productionValue) - valueAxis.adjustMax(acquisitionValue) - valueAxis.adjustMax(returnValue) - } - } - } - - onEntryAdded: { - if (fetchingData) { - return - } - - var start = entry - var consumptionValue = root.energyManager.totalConsumption - (start ? start.totalConsumption : 0) - var productionValue = root.energyManager.totalProduction - (start ? start.totalProduction : 0) - var acquisitionValue = root.energyManager.totalAcquisition - (start ? start.totalAcquisition : 0) - var returnValue = root.energyManager.totalReturn - (start ? start.totalReturn : 0) - consumptionSeries.append(consumptionValue) - productionSeries.append(productionValue) - acquisitionSeries.append(acquisitionValue) - returnSeries.append(returnValue) - consumptionSeries.remove(0, 1); - productionSeries.remove(0, 1); - acquisitionSeries.remove(0, 1); - returnSeries.remove(0, 1); - } - } - - - ChartView { - id: chartView - Layout.fillWidth: true - Layout.fillHeight: true - - backgroundColor: "transparent" - legend.alignment: Qt.AlignBottom - legend.font: Style.extraSmallFont - legend.labelColor: !powerBalanceLogs.fetchingData && powerBalanceLogs.count > 0 ? Style.foregroundColor : Style.gray - - // margins.left: 0 - margins.right: 0 - margins.bottom: 0 - margins.top: 0 - -// title: qsTr("Energy consumption statistics") -// titleColor: "red"// Style.foregroundColor - - Item { - id: labelsLayout - x: Style.smallMargins - y: chartView.plotArea.y - height: chartView.plotArea.height - width: chartView.plotArea.x - x - enabled: !powerBalanceLogs.fetchingData && powerBalanceLogs.count > 0 - - Repeater { - model: valueAxis.tickCount - delegate: Label { - y: parent.height / (valueAxis.tickCount - 1) * index - font.pixelSize / 2 - width: parent.width - Style.smallMargins - horizontalAlignment: Text.AlignRight - text: ((valueAxis.max - (index * valueAxis.max / (valueAxis.tickCount - 1)))).toFixed(0) + "kWh" - verticalAlignment: Text.AlignTop - font: Style.extraSmallFont - } - } - } - - BarSeries { - axisX: BarCategoryAxis { - id: categoryAxis - // categories: daysListNames -// categories: minutesListNames - labelsColor: !powerBalanceLogs.fetchingData && powerBalanceLogs.count > 0 ? Style.foregroundColor : Style.gray - - labelsFont: Style.extraSmallFont - gridVisible: false - gridLineColor: Style.tileOverlayColor - lineVisible: false - titleVisible: false - shadesVisible: false - - } - axisY: ValueAxis { - id: valueAxis - min: 0 - gridLineColor: Style.tileOverlayColor - labelsVisible: false - labelsColor: Style.foregroundColor - labelsFont: Style.extraSmallFont - lineVisible: false - titleVisible: false - shadesVisible: false - - function adjustMax(newValue) { - if (max < newValue) { - max = newValue // Math.ceil(newValue / 100) * 100 - } - } - } - - BarSet { - id: consumptionSeries - label: qsTr("Consumed") - borderWidth: 0 - } - BarSet { - id: productionSeries - label: qsTr("Produced") - color: Style.green - borderWidth: 0 - borderColor: color - } - BarSet { - id: acquisitionSeries - label: qsTr("From grid") - color: Style.red - borderWidth: 0 - borderColor: color - } - BarSet { - id: returnSeries - label: qsTr("To grid") - color: Style.orange - borderWidth: 0 - borderColor: color - } - } + ColumnLayout { + anchors.fill: parent Label { - x: chartView.plotArea.x - y: chartView.plotArea.y - width: chartView.plotArea.width - height: chartView.plotArea.height - wrapMode: Text.WordWrap - text: qsTr("No data available") + Layout.fillWidth: true + Layout.margins: Style.smallMargins horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - visible: !powerBalanceLogs.fetchingData && powerBalanceLogs.count == 0 + text: qsTr("Energy consumption statistics") + + } + + SelectionTabs { + id: selectionTabs + Layout.fillWidth: true + Layout.leftMargin: Style.smallMargins + Layout.rightMargin: Style.smallMargins + model: ListModel { + Component.onCompleted: { + append({modelData: qsTr("Months"), config: "months" }) + append({modelData: qsTr("Weeks"), config: "weeks" }) + append({modelData: qsTr("Days"), config: "days" }) + append({modelData: qsTr("Hours"), config: "hours" }) +// append({modelData: qsTr("Minutes"), config: "minutes" }) + + selectionTabs.currentIndex = 2 + } + } + onCurrentValueChanged: { + var config = root.configs[currentValue.config] + print("config:", config.startTime(), config.sampleList(), config.sampleListNames()) + + powerBalanceLogs.loadingInhibited = true + powerBalanceLogs.sampleRate = config.sampleRate + powerBalanceLogs.startTime = config.startTime() + powerBalanceLogs.sampleList = config.sampleList() + powerBalanceLogs.loadingInhibited = false + + barSeries.clear(); + + d.consumptionSet = barSeries.append(qsTr("Consumed"), []) + d.consumptionSet.color = Style.blue + d.consumptionSet.borderWidth = 0 + d.productionSet = barSeries.append(qsTr("Produced"), []) + d.productionSet.color = Style.green + d.productionSet.borderWidth = 0 + d.acquisitionSet = barSeries.append(qsTr("From grid"), []) + d.acquisitionSet.color = Style.red + d.acquisitionSet.borderWidth = 0 + d.returnSet = barSeries.append(qsTr("To grid"), []) + d.returnSet.color = Style.orange + d.returnSet.borderWidth = 0 + + + valueAxis.max = 0 + categoryAxis.categories = config.sampleListNames() + + chartView.animationOptions = ChartView.SeriesAnimations + } + } + + Connections { + target: energyManager + onPowerBalanceChanged: { + var start = powerBalanceLogs.get(powerBalanceLogs.count - 1) + // print("balance changed:", d.consumptionSet, powerBalanceLogs, powerBalanceLogs.count) + // print("updating", start.timestamp, 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)) + d.returnSet.replace(d.returnSet.count - 1, root.energyManager.totalReturn - (start ? start.totalReturn : 0)) + } + } + + PowerBalanceLogs { + id: powerBalanceLogs + engine: _engine + loadingInhibited: true + + property var sampleList: minutesList + + onFetchingDataChanged: { + if (!fetchingData) { + if (powerBalanceLogs.count == 0) { + valueAxis.adjustMax(root.energyManager.totalConsumption) + valueAxis.adjustMax(root.energyManager.totalAcquisition) + valueAxis.adjustMax(root.energyManager.totalProduction) + valueAxis.adjustMax(root.energyManager.totalReturn) + + for (var i = 0; i < sampleList.length; i++) { + d.consumptionSet.append(i == sampleList.length - 1 ? root.energyManager.totalConsumption : 0) + d.productionSet.append(i == sampleList.length - 1 ? root.energyManager.totalProduction : 0) + d.acquisitionSet.append(i == sampleList.length - 1 ? root.energyManager.totalAcquisition : 0) + d.returnSet.append(i == sampleList.length - 1 ? root.energyManager.totalReturn : 0) + } + return; + } + + for (var i = 0; i < sampleList.length; i++) { + var start = powerBalanceLogs.find(new Date(sampleList[i])) + var end = null; + if (i+1 < sampleList.length) { + end = powerBalanceLogs.find(new Date(sampleList[i+1])) + } +// print("** stats for:", new Date(sampleList[i]), /*start, end, */"start:", start ? start.totalConsumption : 0, "end:", end ? end.totalConsumption : root.energyManager.totalConsumption) + var consumptionValue = (end != null ? end.totalConsumption : root.energyManager.totalConsumption) - (start ? start.totalConsumption : 0) + var productionValue = (end != null ? end.totalProduction : root.energyManager.totalProduction) - (start ? start.totalProduction : 0) + var acquisitionValue = (end != null ? end.totalAcquisition : root.energyManager.totalAcquisition) - (start ? start.totalAcquisition : 0) + var returnValue = (end != null ? end.totalReturn : root.energyManager.totalReturn) - (start ? start.totalReturn : 0) + + valueAxis.adjustMax(consumptionValue) + valueAxis.adjustMax(productionValue) + valueAxis.adjustMax(acquisitionValue) + valueAxis.adjustMax(returnValue) + + d.consumptionSet.append(consumptionValue) + d.productionSet.append(productionValue) + d.acquisitionSet.append(acquisitionValue) + d.returnSet.append(returnValue) + + } + } + } + + onEntryAdded: { + if (fetchingData) { + return + } + + var start = entry + var consumptionValue = root.energyManager.totalConsumption - (start ? start.totalConsumption : 0) + var productionValue = root.energyManager.totalProduction - (start ? start.totalProduction : 0) + var acquisitionValue = root.energyManager.totalAcquisition - (start ? start.totalAcquisition : 0) + var returnValue = root.energyManager.totalReturn - (start ? start.totalReturn : 0) + + chartView.animationOptions = ChartView.NoAnimation + categoryAxis.categories = configs[selectionTabs.currentValue.config].sampleListNames() + 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 + } + } + + + ChartView { + id: chartView + Layout.fillWidth: true + Layout.fillHeight: true + animationOptions: ChartView.NoAnimations + + backgroundColor: "transparent" + legend.alignment: Qt.AlignBottom + legend.font: Style.extraSmallFont + legend.labelColor: Style.foregroundColor + + // margins.left: 0 + margins.right: 0 + margins.bottom: 0 + margins.top: 0 + + Item { + id: labelsLayout + x: Style.smallMargins + y: chartView.plotArea.y + height: chartView.plotArea.height + width: chartView.plotArea.x - x + + Repeater { + model: valueAxis.tickCount + delegate: Label { + y: parent.height / (valueAxis.tickCount - 1) * index - font.pixelSize / 2 + width: parent.width - Style.smallMargins + horizontalAlignment: Text.AlignRight + text: ((valueAxis.max - (index * valueAxis.max / (valueAxis.tickCount - 1)))).toFixed(1) + "kWh" + verticalAlignment: Text.AlignTop + font: Style.extraSmallFont + } + } + } + + BarSeries { + id: barSeries + axisX: BarCategoryAxis { + id: categoryAxis + labelsColor: Style.foregroundColor + labelsFont: Style.extraSmallFont + gridVisible: false + gridLineColor: Style.tileOverlayColor + lineVisible: false + titleVisible: false + shadesVisible: false + } + axisY: ValueAxis { + id: valueAxis + min: 0 + gridLineColor: Style.tileOverlayColor + labelsVisible: false + labelsColor: Style.foregroundColor + labelsFont: Style.extraSmallFont + lineVisible: false + titleVisible: false + shadesVisible: false + + function adjustMax(newValue) { + if (max < newValue) { + max = newValue // Math.ceil(newValue / 100) * 100 + } + } + } + } } } } diff --git a/nymea-app/ui/mainviews/energy/StatsBase.qml b/nymea-app/ui/mainviews/energy/StatsBase.qml new file mode 100644 index 00000000..45059172 --- /dev/null +++ b/nymea-app/ui/mainviews/energy/StatsBase.qml @@ -0,0 +1,177 @@ +import QtQuick 2.0 +import Nymea 1.0 + +Item { + id: root + + property int minutesCount: 10 + property int hoursCount: 12 + property int daysCount: 7 + property int weeksCount: 12 + property int monthsCount: 12 + + property var configs: ({ + minutes: { + startTime: minutesStart, + sampleRate: EnergyLogs.SampleRate1Min, + sampleList: minutesList, + sampleListNames: minutesListNames + }, + hours: { + startTime: hoursStart, + sampleRate: EnergyLogs.SampleRate1Hour, + sampleList: hoursList, + sampleListNames: hoursListNames + }, + days: { + startTime: daysStart, + sampleRate: EnergyLogs.SampleRate1Day, + sampleList: daysList, + sampleListNames: daysListNames + }, + weeks: { + startTime: weeksStart, + sampleRate: EnergyLogs.SampleRate1Week, + sampleList: weeksList, + sampleListNames: weeksListNames + }, + months: { + startTime: monthsStart, + sampleRate: EnergyLogs.SampleRate1Month, + sampleList: monthsList, + sampleListNames: monthsListNames + } + }) + + function minutesStart() { + var d = new Date(); + d.setMinutes(d.getMinutes() - minutesCount + 1, 0, 0) + return d; + } + function minutesList() { + var ret = [] + var startTime = minutesStart(); + for (var i = 0; i < minutesCount; i++) { + var last = new Date(startTime) + ret.push(last.setTime(last.getTime() + i * 60 * 1000)) + } + return ret; + } + function minutesListNames() { + var ret = [] + var list = minutesList() + for (var i = 0; i < list.length; i++) { + ret.push(new Date(list[i]).toLocaleString(Qt.locale(), "hh:mm")) + } + return ret; + } + + function hoursStart() { + var d = new Date(); + d.setHours(d.getHours() - hoursCount + 1, 0, 0, 0) + return d; + } + function hoursList() { + var ret = [] + var startTime = hoursStart(); + for (var i = 0; i < hoursCount; i++) { + var last = new Date(startTime) + ret.push(last.setTime(last.getTime() + i * 60 * 60 * 1000)) + } + return ret; + } + function hoursListNames() { + var ret = []; + var list = hoursList(); + for (var i = 0; i < list.length; i++) { + ret.push(new Date(list[i]).toLocaleString(Qt.locale(), "hh")); + } + return ret; + } + + function daysStart() { + var d = new Date(); + d.setHours(0,0,0,0); + d.setDate(d.getDate() - daysCount + 1); + return d; + } + + function daysList() { + var ret = [] + var startTime = daysStart(); + for (var i = 0; i < daysCount; i++) { + var last = new Date(startTime) + ret.push(last.setDate(last.getDate() + i)) + } + return ret; + } + + function daysListNames() { + var ret = [] + var list = daysList(); + for (var i = 0; i < list.length; i++) { + ret.push(new Date(list[i]).toLocaleString(Qt.locale(), "ddd")) + } + return ret; + } + + function weeksStart() { + var d = new Date(); + d.setHours(0, 0, 0, 0); + d.setDate(d.getDate() - d.getDay() - weeksCount * 7); + return d + } + function weeksList() { + var ret = [] + var startTime = weeksStart(); + for (var i = 0; i < weeksCount; i++) { + var last = new Date(startTime) + ret.push(last.setDate(last.getDate() + i * 7)) + } + return ret; + } + function weeksListNames() { + var ret = [] + var list = weeksList(); + for (var i = 0; i < list.length; i++) { + var d = new Date(list[i]) + var dayNum = d.getDay() || 7; + d.setDate(d.getDate() + 4 - dayNum); + ret.push(Math.ceil((((d - yearStart()) / 86400000) + 1)/7)) + } + return ret; + } + + function monthsStart() { + var d = new Date(); + d.setHours(0,0,0,0); + d.setMonth(d.getMonth() - monthsCount + 1, 1); + return d; + } + function monthsList() { + var ret = [] + var startTime = monthsStart(); + for (var i = 0; i < monthsCount; i++) { + var last = new Date(startTime) + ret.push(last.setMonth(last.getMonth() + i)) + } + return ret; + } + function monthsListNames() { + var ret = [] + var list = monthsList(); + for (var i = 0; i < list.length; i++) { + ret.push(new Date(list[i]).toLocaleString(Qt.locale(), "MMM")) + } + return ret; + } + + function yearStart() { + var d = new Date(); + d.setHours(0,0,0,0); + d.setDate(1); + d.setMonth(0); + return d; + } + +} diff --git a/nymea-app/ui/system/PackageListPage.qml b/nymea-app/ui/system/PackageListPage.qml index eea80863..94fd2c1f 100644 --- a/nymea-app/ui/system/PackageListPage.qml +++ b/nymea-app/ui/system/PackageListPage.qml @@ -40,7 +40,7 @@ SettingsPageBase { title: qsTr("All packages") property Packages packages: engine.systemController.packages - property string filter: "" + property alias filter: filterTextField.text ColumnLayout { Layout.fillWidth: true @@ -53,8 +53,6 @@ SettingsPageBase { TextField { id: filterTextField Layout.fillWidth: true - text: packageListPage.filter - onTextChanged: packageListPage.filter = text } ColorIcon { name: "close" @@ -78,7 +76,7 @@ SettingsPageBase { model: PackagesFilterModel { id: filterModel packages: packageListPage.packages - nameFilter: packageListPage.filter + nameFilter: filterTextField.displayText } delegate: NymeaSwipeDelegate {