More work on the energy view

pull/653/head
Michael Zanetti 2021-08-20 12:49:04 +02:00
parent 4a91dbd334
commit 417edc4e58
9 changed files with 131 additions and 26 deletions

View File

@ -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<LogEntry::LoggingSource>();
@ -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;
}

View File

@ -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)
{

View File

@ -2,6 +2,9 @@
#include <QDebug>
#include <QLoggingCategory>
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<int>(idx));

View File

@ -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)

View File

@ -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;

View File

@ -424,6 +424,22 @@ Thing *ThingsProxy::getThing(const QUuid &thingId) const
return nullptr;
}
int ThingsProxy::indexOf(Thing *thing) const
{
Things *t = qobject_cast<Things*>(sourceModel());
ThingsProxy *tp = qobject_cast<ThingsProxy*>(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<Things*>(sourceModel());

View File

@ -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();

View File

@ -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
}

View File

@ -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
}
}