diff --git a/libnymea-app/models/logsmodel.cpp b/libnymea-app/models/logsmodel.cpp index 2fa965ba..4ef69603 100644 --- a/libnymea-app/models/logsmodel.cpp +++ b/libnymea-app/models/logsmodel.cpp @@ -38,6 +38,10 @@ #include "types/logentry.h" #include "logmanager.h" +#include "logging.h" +NYMEA_LOGGING_CATEGORY(dcLogEngine, "LogEngine") + + LogsModel::LogsModel(QObject *parent) : QAbstractListModel(parent) { @@ -151,6 +155,7 @@ void LogsModel::setTypeIds(const QStringList &typeIds) if (m_typeIds != fixedTypeIds) { m_typeIds = fixedTypeIds; emit typeIdsChanged(); + qCDebug(dcLogEngine()) << "Resetting model because type ids changed"; beginResetModel(); qDeleteAll(m_list); m_list.clear(); @@ -261,6 +266,9 @@ void LogsModel::logsReply(int /*commandId*/, const QVariantMap &data) foreach (const QVariant &logEntryVariant, logEntries) { QVariantMap entryMap = logEntryVariant.toMap(); QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong()); + if (!m_viewStartTime.isNull() && timeStamp < m_viewStartTime) { + continue; + } QString thingId = entryMap.value("thingId").toString(); QString typeId = entryMap.value("typeId").toString(); QMetaEnum sourceEnum = QMetaEnum::fromType(); @@ -273,7 +281,7 @@ void LogsModel::logsReply(int /*commandId*/, const QVariantMap &data) newBlock.append(entry); } -// qDebug() << "Received logs from" << offset << "to" << offset + count << "Actual count:" << newBlock.count(); +// qCDebug(dcLogEngine()) << objectName() << "Received logs from" << offset << "to" << offset + count << "Actual count:" << newBlock.count(); if (count < m_blockSize) { m_canFetchMore = false; @@ -288,9 +296,11 @@ void LogsModel::logsReply(int /*commandId*/, const QVariantMap &data) beginInsertRows(QModelIndex(), offset, offset + newBlock.count() - 1); for (int i = 0; i < newBlock.count(); i++) { +// qCDebug(dcLogEngine()) << objectName() << "Inserting: list count" << m_list.count() << "blockSize" << newBlock.count() << "insterting at:" << offset + i; LogEntry *entry = newBlock.at(i); m_list.insert(offset + i, entry); emit logEntryAdded(entry); +// qCDebug(dcLogEngine()) << objectName() << "done"; } endInsertRows(); emit countChanged(); @@ -298,7 +308,7 @@ void LogsModel::logsReply(int /*commandId*/, const QVariantMap &data) m_busyInternal = false; if (m_viewStartTime.isValid() && m_list.count() > 0 && m_list.last()->timestamp() > m_viewStartTime && m_canFetchMore) { -// qDebug() << "Fetching more because of viewStartTime" << m_viewStartTime.toString() << "last" << m_list.last()->timestamp().toString(); + qCDebug(dcLogEngine()) << objectName() << "Fetching more because of viewStartTime" << m_viewStartTime.toString() << "last" << m_list.last()->timestamp().toString(); fetchMore(); } else { m_busy = false; @@ -311,7 +321,7 @@ void LogsModel::fetchMore(const QModelIndex &parent) Q_UNUSED(parent) if (!m_engine) { - qWarning() << "Cannot update. Engine not set"; + qCWarning(dcLogEngine()) << objectName() << "Cannot update. Engine not set"; return; } if (m_busyInternal) { @@ -319,7 +329,7 @@ void LogsModel::fetchMore(const QModelIndex &parent) } if ((!m_startTime.isNull() && m_endTime.isNull()) || (m_startTime.isNull() && !m_endTime.isNull())) { - qDebug() << "Need neither or both, startTime and endTime set"; + qCDebug(dcLogEngine()) << objectName() << "Need neither or both, startTime and endTime set"; return; } diff --git a/libnymea-app/models/logsmodelng.cpp b/libnymea-app/models/logsmodelng.cpp index 2f472916..818fe697 100644 --- a/libnymea-app/models/logsmodelng.cpp +++ b/libnymea-app/models/logsmodelng.cpp @@ -39,7 +39,7 @@ #include "logmanager.h" #include "logging.h" -NYMEA_LOGGING_CATEGORY(dcLogEngine, "LogEngine") +Q_DECLARE_LOGGING_CATEGORY(dcLogEngine) LogsModelNg::LogsModelNg(QObject *parent) : QAbstractListModel(parent) { diff --git a/libnymea-app/models/xyseriesadapter.cpp b/libnymea-app/models/xyseriesadapter.cpp index 5c26253e..a84e9e5a 100644 --- a/libnymea-app/models/xyseriesadapter.cpp +++ b/libnymea-app/models/xyseriesadapter.cpp @@ -2,6 +2,9 @@ #include +#include +Q_DECLARE_LOGGING_CATEGORY(dcLogEngine) + XYSeriesAdapter::XYSeriesAdapter(QObject *parent) : QObject(parent) { @@ -156,12 +159,11 @@ void XYSeriesAdapter::logEntryAdded(LogEntry *entry) return; } - ensureSamples(entry->timestamp(), entry->timestamp()); int idx = entry->timestamp().secsTo(m_newestSample) / m_sampleRate; if (idx > m_samples.count()) { - qWarning() << "Overflowing integer size for XYSeriesAdapter!"; + qCWarning(dcLogEngine) << "Overflowing integer size for XYSeriesAdapter!"; return; } Sample *sample = m_samples.at(static_cast(idx)); diff --git a/libnymea-app/things.cpp b/libnymea-app/things.cpp index 47ac1430..1bba7a59 100644 --- a/libnymea-app/things.cpp +++ b/libnymea-app/things.cpp @@ -61,6 +61,11 @@ Thing *Things::getThing(const QUuid &thingId) const return nullptr; } +int Things::indexOf(Thing *thing) const +{ + return m_things.indexOf(thing); +} + int Things::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) diff --git a/libnymea-app/things.h b/libnymea-app/things.h index f6b518a1..0ac1f083 100644 --- a/libnymea-app/things.h +++ b/libnymea-app/things.h @@ -59,6 +59,7 @@ public: Q_INVOKABLE Thing *get(int index) const; Q_INVOKABLE Thing *getThing(const QUuid &thingId) const; + Q_INVOKABLE int indexOf(Thing *thing) const; int rowCount(const QModelIndex & parent = QModelIndex()) const override; QVariant data(const QModelIndex & index, int role = RoleName) const override; diff --git a/libnymea-app/thingsproxy.cpp b/libnymea-app/thingsproxy.cpp index fcb6828f..f826ec90 100644 --- a/libnymea-app/thingsproxy.cpp +++ b/libnymea-app/thingsproxy.cpp @@ -424,6 +424,22 @@ Thing *ThingsProxy::getThing(const QUuid &thingId) const return nullptr; } +int ThingsProxy::indexOf(Thing *thing) const +{ + Things *t = qobject_cast(sourceModel()); + ThingsProxy *tp = qobject_cast(sourceModel()); + int idx = -1; + if (t) { + idx = t->indexOf(thing); + } else if (tp) { + idx = tp->indexOf(thing); + } else { + return -1; + } + QModelIndex sourceIndex = sourceModel()->index(idx, 0); + return mapFromSource(sourceIndex).row(); +} + Thing *ThingsProxy::getInternal(int source_index) const { Things* d = qobject_cast(sourceModel()); diff --git a/libnymea-app/thingsproxy.h b/libnymea-app/thingsproxy.h index 8645009d..1fb32467 100644 --- a/libnymea-app/thingsproxy.h +++ b/libnymea-app/thingsproxy.h @@ -148,6 +148,7 @@ public: Q_INVOKABLE Thing *get(int index) const; Q_INVOKABLE Thing *getThing(const QUuid &thingId) const; + Q_INVOKABLE int indexOf(Thing *thing) const; signals: void engineChanged(); diff --git a/nymea-app/ui/components/SmartMeterChart.qml b/nymea-app/ui/components/SmartMeterChart.qml index 2a832177..e854dc3a 100644 --- a/nymea-app/ui/components/SmartMeterChart.qml +++ b/nymea-app/ui/components/SmartMeterChart.qml @@ -74,6 +74,12 @@ ChartView { unknownConsumerEnergy = rootMeter.stateByName("totalEnergyConsumed").value } + if (rootMeter) { + var slice = pieSeries.append(qsTr("Unknown"), unknownConsumerEnergy) + slice.color = Style.accentColor + d.sliceMap[slice] = rootMeter + } + for (var i = 0; i < meters.count; i++) { var thing = meters.get(i); var value = 0; @@ -100,11 +106,6 @@ ChartView { d.sliceMap[slice] = thing unknownConsumerEnergy -= value } - if (rootMeter) { - var slice = pieSeries.append(qsTr("Unknown"), unknownConsumerEnergy) - slice.color = Style.accentColor - d.sliceMap[slice] = rootMeter - } } PieSeries { @@ -127,7 +128,7 @@ ChartView { font.pixelSize: app.largeFont Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter - text: root.rootMeter + text: root.rootMeterTotalConsumedEnergyState ? root.rootMeterTotalConsumedEnergyState.value.toFixed(2) : Math.round(pieSeries.sum * 1000) / 1000 } diff --git a/nymea-app/ui/mainviews/EnergyView.qml b/nymea-app/ui/mainviews/EnergyView.qml index d27dda02..49181f97 100644 --- a/nymea-app/ui/mainviews/EnergyView.qml +++ b/nymea-app/ui/mainviews/EnergyView.qml @@ -45,6 +45,7 @@ MainViewBase { engine: _engine shownInterfaces: ["energymeter"] } + readonly property Thing rootMeter: energyMeters.count > 0 ? energyMeters.get(0) : null ThingsProxy { id: consumers @@ -58,12 +59,14 @@ MainViewBase { shownInterfaces: ["smartmeterproducer"] } + Flickable { anchors.fill: parent anchors.margins: app.margins / 2 contentHeight: energyGrid.childrenRect.height visible: energyMeters.count > 0 + GridLayout { id: energyGrid width: parent.width @@ -74,15 +77,14 @@ MainViewBase { SmartMeterChart { Layout.fillWidth: true // Layout.preferredWidth: energyGrid.width / energyGrid.columns - Layout.preferredHeight: width * .7 + Layout.preferredHeight: (energyGrid.width / energyGrid.columns) * .7 // FIXME: multiple root meters... Not exactly a use case, still possible tho - rootMeter: energyMeters.count > 0 ? energyMeters.get(0) : null + rootMeter: root.rootMeter meters: consumers title: qsTr("Total consumed energy") visible: consumers.count > 0 } - ChartView { id: chartView Layout.fillWidth: true @@ -90,9 +92,11 @@ MainViewBase { Layout.preferredHeight: width * .7 legend.alignment: Qt.AlignBottom legend.font.pixelSize: app.smallFont - legend.visible: false +// legend.visible: false + legend.labelColor: Style.foregroundColor backgroundColor: Style.tileBackgroundColor backgroundRoundness: Style.cornerRadius + theme: ChartView.ChartThemeLight titleColor: Style.foregroundColor title: qsTr("Power usage history") @@ -109,9 +113,67 @@ MainViewBase { running: visible } + LogsModel { + id: rootMeterLogsModel + objectName: "Root meter model" + engine: rootMeter ? _engine : null // Don't start fetching before we know what we want + thingId: rootMeter ? rootMeter.id : "" + typeIds: rootMeter ? [rootMeter.thingClass.stateTypes.findByName("currentPower").id] : [] + viewStartTime: xAxis.min + live: true + } + XYSeriesAdapter { + id: rootMeterSeriesAdapter + logsModel: rootMeterLogsModel + sampleRate: chartView.sampleRate + xySeries: rootMeterSeries + Component.onCompleted: ensureSamples(xAxis.min, xAxis.max) + } + Connections { + target: xAxis + onMinChanged: rootMeterSeriesAdapter.ensureSamples(xAxis.min, xAxis.max) + onMaxChanged: rootMeterSeriesAdapter.ensureSamples(xAxis.min, xAxis.max) + } + + AreaSeries { + id: rootMeterAreaSeries + color: Style.accentColor + borderWidth: 0 + axisX: xAxis + axisY: yAxis + name: qsTr("Unknown") + useOpenGL: true + lowerSeries: LineSeries { + id: rootMeterLowerSeries + XYPoint { x: xAxis.max.getTime(); y: 0 } + XYPoint { x: xAxis.min.getTime(); y: 0 } + } + // HACK: We want this to be created (added to the chart) *before* the repeater Series below... + // That might not be the case for a reason I don't understand. Most likely due to a mix of the declarative + // approach here and the imperative approach using chartView.createSeries() below. + // So hacking around by blocking the repeater from loading until this one is done + property bool ready: false + Component.onCompleted: ready = true + + upperSeries: LineSeries { + id: rootMeterSeries + + onPointAdded: { + var newPoint = rootMeterSeries.at(index) + + if (newPoint.x > rootMeterLowerSeries.at(0).x) { + rootMeterLowerSeries.replace(0, newPoint.x, 0) + } + if (newPoint.x < rootMeterLowerSeries.at(1).x) { + rootMeterLowerSeries.replace(1, newPoint.x, 0) + } + } + } + } + Repeater { id: consumersRepeater - model: consumers + model: rootMeterAreaSeries.ready && !engine.thingManager.fetchingData ? consumers : null delegate: Item { id: consumer @@ -119,6 +181,7 @@ MainViewBase { property var model: LogsModel { id: logsModel + objectName: consumer.thing.name engine: _engine thingId: consumer.thing.id typeIds: [consumer.thing.thingClass.stateTypes.findByName("currentPower").id] @@ -163,9 +226,11 @@ MainViewBase { } Component.onCompleted: { - print("creating series", consumer.thing.name, index) + var indexInModel = consumers.indexOf(consumer.thing) + print("creating series", consumer.thing.name, index, indexInModel) seriesAdapter.ensureSamples(xAxis.min, xAxis.max) var areaSeries = chartView.createSeries(ChartView.SeriesTypeArea, consumer.thing.name, xAxis, yAxis) + areaSeries.useOpenGL = true areaSeries.upperSeries = upperSeries; if (index > 0) { areaSeries.lowerSeries = consumersRepeater.itemAt(index - 1).lineSeries @@ -175,8 +240,8 @@ MainViewBase { } var color = Style.accentColor - for (var j = 0; j < index; j+=2) { - if (index % 2 == 0) { + for (var j = 0; j <= indexInModel; j+=2) { + if (indexInModel % 2 == 0) { color = Qt.lighter(color, 1.2); } else { color = Qt.darker(color, 1.2) @@ -191,9 +256,13 @@ MainViewBase { ValueAxis { id: yAxis - readonly property XYSeriesAdapter adapter: consumersRepeater.count > 0 ? consumersRepeater.itemAt(consumersRepeater.count - 1).adapter : null - max: 7// adapter ? Math.ceil(Math.max(adapter.maxValue * 0.95, adapter.maxValue * 1.05)) : 1 - min: adapter ? Math.floor(Math.min(adapter.minValue * 0.95, adapter.minValue * 1.05)) : 0 + readonly property XYSeriesAdapter highestSeriesAdapter: consumersRepeater.count > 0 ? consumersRepeater.itemAt(consumersRepeater.count - 1).adapter : null + property double rawMax: rootMeter ? rootMeterSeriesAdapter.maxValue + : highestSeriesAdapter ? highestSeriesAdapter.maxValue : 1 + property double rawMin: rootMeter ? rootMeterSeriesAdapter.minValue + : highestSeriesAdapter ? highestSeriesAdapter.minValue : 0 + max: Math.ceil(Math.max(rawMax * 0.9, rawMax * 1.1)) + min: Math.floor(Math.min(rawMin * 0.9, rawMin * 1.1)) // This seems to crash occationally // onMinChanged: applyNiceNumbers(); // onMaxChanged: applyNiceNumbers(); @@ -369,10 +438,10 @@ MainViewBase { Layout.preferredHeight: width * .7 backgroundColor: Style.tileBackgroundColor backgroundRoundness: Style.cornerRadius - rootMeter: energyMeters.count > 0 ? energyMeters.get(0) : null + rootMeter: root.rootMeter meters: producers title: qsTr("Total produced energy") - visible: producers.count > 0 + visible: root.rootMeter || producers.count > 0 multiplier: -1 } }