From c84cd88d76b352d9929a6d4478210400f6146d5b Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 23 Oct 2018 23:07:57 +0200 Subject: [PATCH 01/10] work on LogsModelNg with support for 1.10 jsonrpc's pagination --- libnymea-app-core/jsonrpc/jsonrpcclient.cpp | 4 +- libnymea-app-core/models/logsmodel.cpp | 4 +- libnymea-app-core/models/logsmodel.h | 5 + libnymea-app-core/models/logsmodelng.cpp | 282 ++++++++---------- libnymea-app-core/models/logsmodelng.h | 35 ++- .../ui/customviews/GenericTypeLogView.qml | 158 ++++------ nymea-app/ui/delegates/ActionDelegate.qml | 1 + nymea-app/ui/devicepages/ButtonDevicePage.qml | 22 +- .../FingerprintReaderDevicePage.qml | 20 +- .../ui/devicepages/InputTriggerDevicePage.qml | 14 +- .../devicepages/NotificationsDevicePage.qml | 21 +- nymea-app/ui/devicepages/StateLogPage.qml | 21 +- nymea-app/ui/system/LogViewerPage.qml | 17 +- 13 files changed, 287 insertions(+), 317 deletions(-) diff --git a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp index eddce39f..69656d5d 100644 --- a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp +++ b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp @@ -303,7 +303,7 @@ void JsonRpcClient::sendRequest(const QVariantMap &request) { QVariantMap newRequest = request; newRequest.insert("token", m_token); - qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson()); +// qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson()); m_connection->sendData(QJsonDocument::fromVariant(newRequest).toJson(QJsonDocument::Compact) + "\n"); } @@ -340,7 +340,7 @@ void JsonRpcClient::dataReceived(const QByteArray &data) // qWarning() << "Could not parse json data from nymea" << m_receiveBuffer.left(splitIndex) << error.errorString(); return; } - qDebug() << "received response" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); +// qDebug() << "received response" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); m_receiveBuffer = m_receiveBuffer.right(m_receiveBuffer.length() - splitIndex - 1); if (!m_receiveBuffer.isEmpty()) { staticMetaObject.invokeMethod(this, "dataReceived", Qt::QueuedConnection, Q_ARG(QByteArray, QByteArray())); diff --git a/libnymea-app-core/models/logsmodel.cpp b/libnymea-app-core/models/logsmodel.cpp index 3e08301c..d7ec31ef 100644 --- a/libnymea-app-core/models/logsmodel.cpp +++ b/libnymea-app-core/models/logsmodel.cpp @@ -213,7 +213,7 @@ void LogsModel::fetchEarlier(int hours) void LogsModel::logsReply(const QVariantMap &data) { - qDebug() << "logs reply";// << data; +// qDebug() << "logs reply" << data; beginResetModel(); qDeleteAll(m_list); m_list.clear(); @@ -242,7 +242,7 @@ void LogsModel::logsReply(const QVariantMap &data) void LogsModel::fetchEarlierReply(const QVariantMap &data) { - qDebug() << "logs reply";// << data; +// qDebug() << "logs reply" << data; QList logEntries = data.value("params").toMap().value("logEntries").toList(); QList newEntries; diff --git a/libnymea-app-core/models/logsmodel.h b/libnymea-app-core/models/logsmodel.h index 1d8b26f1..ee731d5a 100644 --- a/libnymea-app-core/models/logsmodel.h +++ b/libnymea-app-core/models/logsmodel.h @@ -19,6 +19,7 @@ class LogsModel : public QAbstractListModel Q_PROPERTY(QStringList typeIds READ typeIds WRITE setTypeIds NOTIFY typeIdsChanged) Q_PROPERTY(QDateTime startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged) Q_PROPERTY(QDateTime endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged) +// Q_PROPERTY(int paginationCount READ paginationCount WRITE setPaginationCount NOTIFY paginationCountChanged) Q_PROPERTY(bool live READ live WRITE setLive NOTIFY liveChanged) @@ -56,6 +57,9 @@ public: QDateTime endTime() const; void setEndTime(const QDateTime &endTime); +// int paginationCount() const; +// void setPaginationCount(int paginationCount); + Q_INVOKABLE LogEntry* get(int index) const; Q_INVOKABLE void notificationReceived(const QVariantMap &data); @@ -69,6 +73,7 @@ signals: void typeIdsChanged(); void startTimeChanged(); void endTimeChanged(); +// void paginationCountChanged(); public slots: virtual void update(); diff --git a/libnymea-app-core/models/logsmodelng.cpp b/libnymea-app-core/models/logsmodelng.cpp index ec8b3891..5e0f89cc 100644 --- a/libnymea-app-core/models/logsmodelng.cpp +++ b/libnymea-app-core/models/logsmodelng.cpp @@ -5,22 +5,24 @@ #include "engine.h" #include "types/logentry.h" +#include "logmanager.h" LogsModelNg::LogsModelNg(QObject *parent) : QAbstractListModel(parent) { } -JsonRpcClient *LogsModelNg::jsonRpcClient() const +Engine *LogsModelNg::engine() const { - return m_jsonRpcClient; + return m_engine; } -void LogsModelNg::setJsonRpcClient(JsonRpcClient *jsonRpcClient) +void LogsModelNg::setEngine(Engine *engine) { - if (m_jsonRpcClient != jsonRpcClient) { - m_jsonRpcClient = jsonRpcClient; - emit jsonRpcClientChanged(); + if (m_engine != engine) { + m_engine = engine; + connect(engine->logManager(), &LogManager::logEntryReceived, this, &LogsModelNg::newLogEntryReceived); + emit engineChanged(); } } @@ -92,16 +94,16 @@ void LogsModelNg::setDeviceId(const QString &deviceId) } } -QString LogsModelNg::typeId() const +QStringList LogsModelNg::typeIds() const { - return m_typeId; + return m_typeIds; } -void LogsModelNg::setTypeId(const QString &typeId) +void LogsModelNg::setTypeIds(const QStringList &typeIds) { - if (m_typeId != typeId) { - m_typeId = typeId; - emit typeIdChanged(); + if (m_typeIds != typeIds) { + m_typeIds = typeIds; + emit typeIdsChanged(); } } @@ -115,7 +117,6 @@ void LogsModelNg::setStartTime(const QDateTime &startTime) if (m_startTime != startTime) { m_startTime = startTime; emit startTimeChanged(); - update(); } } @@ -129,13 +130,57 @@ void LogsModelNg::setEndTime(const QDateTime &endTime) if (m_endTime != endTime) { m_endTime = endTime; emit endTimeChanged(); - update(); } } -void LogsModelNg::update() +void LogsModelNg::logsReply(const QVariantMap &data) { - if (!m_jsonRpcClient) { + qDebug() << "logs reply" << data; + + m_busy = false; + emit busyChanged(); + + int offset = data.value("params").toMap().value("offset").toInt(); + int count = data.value("params").toMap().value("count").toInt(); + + QList newBlock; + QList logEntries = data.value("params").toMap().value("logEntries").toList(); + foreach (const QVariant &logEntryVariant, logEntries) { + QVariantMap entryMap = logEntryVariant.toMap(); + QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong()); + QString deviceId = entryMap.value("deviceId").toString(); + QString typeId = entryMap.value("typeId").toString(); + QMetaEnum sourceEnum = QMetaEnum::fromType(); + LogEntry::LoggingSource loggingSource = static_cast(sourceEnum.keyToValue(entryMap.value("source").toByteArray())); + QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType(); + LogEntry::LoggingEventType loggingEventType = static_cast(loggingEventTypeEnum.keyToValue(entryMap.value("eventType").toByteArray())); + QVariant value = loggingEventType == LogEntry::LoggingEventTypeActiveChange ? entryMap.value("active").toBool() : entryMap.value("value"); + LogEntry *entry = new LogEntry(timeStamp, value, deviceId, typeId, loggingSource, loggingEventType, this); + newBlock.append(entry); + } + + if (count < m_blockSize) { + m_canFetchMore = false; + } + + if (newBlock.isEmpty()) { + return; + } + + beginInsertRows(QModelIndex(), offset, offset + newBlock.count() - 1); + for (int i = 0; i < newBlock.count(); i++) { + m_list.insert(offset + i, newBlock.at(i)); + } + endInsertRows(); + emit countChanged(); +} + +void LogsModelNg::fetchMore(const QModelIndex &parent) +{ + Q_UNUSED(parent) + qDebug() << "fetchMore called"; + + if (!m_engine->jsonRpcClient()) { qWarning() << "Cannot update. JsonRpcClient not set"; return; } @@ -143,66 +188,11 @@ void LogsModelNg::update() return; } - if (m_startTime.isNull() || m_endTime.isNull()) { - // Need both, startTime and endTime set + if ((!m_startTime.isNull() && m_endTime.isNull()) || (m_startTime.isNull() && !m_endTime.isNull())) { + // Need neither or both, startTime and endTime set return; } - m_currentFetchStartTime = QDateTime(); - m_currentFetchEndTime = QDateTime(); - bool haveData = false; - for(int i = 0; i < m_fetchedPeriods.length(); i++) { - if (m_fetchedPeriods.at(i).first < m_startTime) { - if (m_fetchedPeriods.at(i).second == true) { - haveData = true; - continue; - } - if (m_fetchedPeriods.at(i).second == false) { - haveData = false; - continue; - } - } - if (m_fetchedPeriods.at(i).first == m_startTime) { - if (m_fetchedPeriods.at(i).second == true) { - haveData = true; - continue; - } - if (m_fetchedPeriods.at(i).second == false) { - m_currentFetchStartTime = m_startTime; - continue; - } - } - if (m_fetchedPeriods.at(i).first > m_startTime) { - if (m_fetchedPeriods.at(i).second == true) { - if (m_currentFetchStartTime.isNull()) { - m_currentFetchStartTime = m_startTime; - } - m_currentFetchEndTime = m_fetchedPeriods.at(i).first; - break; - } - if (m_fetchedPeriods.at(i).second == false) { - if (m_currentFetchStartTime.isNull()) { - haveData = false; - m_currentFetchStartTime = m_fetchedPeriods.at(i).first; - } - continue; - } - } - } - if (haveData) { - qDebug() << "all the data is fetched"; - m_busy = false; - emit busyChanged(); - return; - } - if (m_currentFetchStartTime.isNull()) { - m_currentFetchStartTime = m_startTime; - } - if (m_currentFetchEndTime.isNull()) { - m_currentFetchEndTime = m_endTime; - } - qDebug() << "Fetching from" << m_currentFetchStartTime << "to" << m_currentFetchEndTime; - m_busy = true; emit busyChanged(); @@ -212,103 +202,65 @@ void LogsModelNg::update() deviceIds.append(m_deviceId); params.insert("deviceIds", deviceIds); } - if (!m_typeId.isEmpty()) { + if (!m_typeIds.isEmpty()) { QVariantList typeIds; - typeIds.append(m_typeId); + foreach (const QString &typeId, m_typeIds) { + typeIds.append(typeId); + } params.insert("typeIds", typeIds); } - QVariantList timeFilters; - QVariantMap timeFilter; - timeFilter.insert("startDate", m_currentFetchStartTime.toSecsSinceEpoch()); - timeFilter.insert("endDate", m_currentFetchEndTime.toSecsSinceEpoch()); - timeFilters.append(timeFilter); - params.insert("timeFilters", timeFilters); - m_jsonRpcClient->sendCommand("Logging.GetLogEntries", params, this, "logsReply"); + if (!m_startTime.isNull() && !m_endTime.isNull()) { + QVariantList timeFilters; + QVariantMap timeFilter; + timeFilter.insert("startDate", m_currentFetchStartTime.toSecsSinceEpoch()); + timeFilter.insert("endDate", m_currentFetchEndTime.toSecsSinceEpoch()); + timeFilters.append(timeFilter); + params.insert("timeFilters", timeFilters); + } + + params.insert("limit", m_blockSize); + params.insert("offset", m_list.count()); + + m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply"); + qDebug() << "GetLogEntries called"; } -void LogsModelNg::logsReply(const QVariantMap &data) +bool LogsModelNg::canFetchMore(const QModelIndex &parent) const { - qDebug() << "logs reply";// << data; - - // First update the fetched periods information - int insertIndex = -1; - bool noNeedToInsert = false; - for (int i = 0; i < m_fetchedPeriods.count(); i++) { - if (m_fetchedPeriods.at(i).first < m_currentFetchStartTime) { - // skip it - insertIndex = i+1; - continue; - } - if (m_fetchedPeriods.at(i).first == m_currentFetchStartTime) { - if (m_fetchedPeriods.at(i).second == false) { - // Have an end marker where we start inserting. We can drop the existing end marker and just update the end marker - if (m_fetchedPeriods.count() > i+1) { - if (m_fetchedPeriods.at(i+1).first < m_currentFetchEndTime) { - qWarning() << "Overlap detected!"; - } else if (m_fetchedPeriods.at(i+1).first == m_currentFetchEndTime) { - if (m_fetchedPeriods.at(i+1).second == true) { - m_fetchedPeriods.removeAt(i+1); - } - } - } - m_fetchedPeriods.removeAt(i); - noNeedToInsert = true; - break; - } - } - if (m_fetchedPeriods.at(i).first > m_currentFetchStartTime) { - break; - } - } - - if (!noNeedToInsert) { - if (insertIndex == -1) { - insertIndex = 0; - } - m_fetchedPeriods.insert(insertIndex, qMakePair(m_currentFetchStartTime, true)); - m_fetchedPeriods.insert(insertIndex+1, qMakePair(m_currentFetchEndTime, false)); - } - qDebug() << "new fetched periods:" << m_fetchedPeriods << "insertIndex:" << insertIndex; - m_busy = false; - emit busyChanged(); - - - QList newBlock; - QList logEntries = data.value("params").toMap().value("logEntries").toList(); - foreach (const QVariant &logEntryVariant, logEntries) { - QVariantMap entryMap = logEntryVariant.toMap(); - QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong()); - QString deviceId = entryMap.value("deviceId").toString(); - QString typeId = entryMap.value("typeId").toString(); - QMetaEnum sourceEnum = QMetaEnum::fromType(); - LogEntry::LoggingSource loggingSource = (LogEntry::LoggingSource)sourceEnum.keyToValue(entryMap.value("source").toByteArray()); - QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType(); - LogEntry::LoggingEventType loggingEventType = (LogEntry::LoggingEventType)loggingEventTypeEnum.keyToValue(entryMap.value("eventType").toByteArray()); - QVariant value = loggingEventType == LogEntry::LoggingEventTypeActiveChange ? entryMap.value("active").toBool() : entryMap.value("value"); - LogEntry *entry = new LogEntry(timeStamp, value, deviceId, typeId, loggingSource, loggingEventType, this); - newBlock.append(entry); - } - - // Now let's find where to insert stuff in the model - if (!newBlock.isEmpty()) { - int indexToInsert = 0; - for (int i = 0; i < m_list.count(); i++) { - LogEntry *entry = m_list.at(i); - if (entry->timestamp() < newBlock.first()->timestamp()) { - continue; - } - indexToInsert = i; - break; - } - - beginInsertRows(QModelIndex(), indexToInsert, indexToInsert + newBlock.count() - 1); - for (int i = 0; i < newBlock.count(); i++) { - m_list.insert(indexToInsert + i, newBlock.at(i)); - } - endInsertRows(); - emit countChanged(); - } - - update(); + Q_UNUSED(parent) + qDebug() << "canFetchMore" << m_canFetchMore; + return m_canFetchMore; } +void LogsModelNg::newLogEntryReceived(const QVariantMap &data) +{ + if (!m_live) { + return; + } + + QVariantMap entryMap = data; + QString deviceId = entryMap.value("deviceId").toString(); + if (!m_deviceId.isNull() && deviceId != m_deviceId) { + return; + } + + QString typeId = entryMap.value("typeId").toString(); + if (!m_typeIds.isEmpty() && !m_typeIds.contains(typeId)) { + return; + } + + beginInsertRows(QModelIndex(), 0, 0); + QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong()); + QMetaEnum sourceEnum = QMetaEnum::fromType(); + LogEntry::LoggingSource loggingSource = static_cast(sourceEnum.keyToValue(entryMap.value("source").toByteArray())); + QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType(); + LogEntry::LoggingEventType loggingEventType = static_cast(loggingEventTypeEnum.keyToValue(entryMap.value("eventType").toByteArray())); + QVariant value = loggingEventType == LogEntry::LoggingEventTypeActiveChange ? entryMap.value("active").toBool() : entryMap.value("value"); + LogEntry *entry = new LogEntry(timeStamp, value, deviceId, typeId, loggingSource, loggingEventType, this); + m_list.prepend(entry); + endInsertRows(); + emit countChanged(); + +} + + diff --git a/libnymea-app-core/models/logsmodelng.h b/libnymea-app-core/models/logsmodelng.h index eb89632a..8881fb1d 100644 --- a/libnymea-app-core/models/logsmodelng.h +++ b/libnymea-app-core/models/logsmodelng.h @@ -6,16 +6,17 @@ #include class LogEntry; -class JsonRpcClient; +class Engine; class LogsModelNg : public QAbstractListModel { Q_OBJECT - Q_PROPERTY(JsonRpcClient* jsonRpcClient READ jsonRpcClient WRITE setJsonRpcClient NOTIFY jsonRpcClientChanged) + Q_PROPERTY(Engine* engine READ engine WRITE setEngine NOTIFY engineChanged) Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) + Q_PROPERTY(bool live READ live WRITE setLive NOTIFY liveChanged) Q_PROPERTY(int count READ rowCount NOTIFY countChanged) Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) - Q_PROPERTY(QString typeId READ typeId WRITE setTypeId NOTIFY typeIdChanged) + Q_PROPERTY(QStringList typeIds READ typeIds WRITE setTypeIds NOTIFY typeIdsChanged) Q_PROPERTY(QDateTime startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged) Q_PROPERTY(QDateTime endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged) @@ -31,8 +32,8 @@ public: explicit LogsModelNg(QObject *parent = nullptr); - JsonRpcClient *jsonRpcClient() const; - void setJsonRpcClient(JsonRpcClient* jsonRpcClient); + Engine *engine() const; + void setEngine(Engine* jsonRpcClient); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; @@ -46,8 +47,8 @@ public: QString deviceId() const; void setDeviceId(const QString &deviceId); - QString typeId() const; - void setTypeId(const QString &typeId); + QStringList typeIds() const; + void setTypeIds(const QStringList &typeId); QDateTime startTime() const; void setStartTime(const QDateTime &startTime); @@ -55,34 +56,40 @@ public: QDateTime endTime() const; void setEndTime(const QDateTime &endTime); +protected: + virtual void fetchMore(const QModelIndex &parent = QModelIndex()) override; + virtual bool canFetchMore(const QModelIndex &parent = QModelIndex()) const override; signals: void busyChanged(); void liveChanged(); void deviceIdChanged(); - void typeIdChanged(); + void typeIdsChanged(); void countChanged(); void startTimeChanged(); void endTimeChanged(); - void jsonRpcClientChanged(); + void engineChanged(); + +private slots: + void newLogEntryReceived(const QVariantMap &data); + void logsReply(const QVariantMap &data); private: QList m_list; - JsonRpcClient *m_jsonRpcClient = nullptr; + Engine *m_engine = nullptr; bool m_busy = false; bool m_live = false; QString m_deviceId; - QString m_typeId; + QStringList m_typeIds; QDateTime m_startTime; QDateTime m_endTime; QDateTime m_currentFetchStartTime; QDateTime m_currentFetchEndTime; + int m_blockSize = 100; + bool m_canFetchMore = true; QList > m_fetchedPeriods; - - void update(); - Q_INVOKABLE void logsReply(const QVariantMap &data); }; diff --git a/nymea-app/ui/customviews/GenericTypeLogView.qml b/nymea-app/ui/customviews/GenericTypeLogView.qml index dc136765..954ecc0a 100644 --- a/nymea-app/ui/customviews/GenericTypeLogView.qml +++ b/nymea-app/ui/customviews/GenericTypeLogView.qml @@ -6,8 +6,6 @@ import "../components" Item { id: root - // %1 will be replaced with count - property string text signal addRuleClicked(var value) @@ -15,122 +13,74 @@ Item { property alias delegate: listView.delegate - property bool autoscroll: true - - ColumnLayout { + ListView { + id: listView anchors.fill: parent + model: logsModel + clip: true - Label { - id: titleLabel - Layout.fillWidth: true - Layout.margins: app.margins - wrapMode: Text.WordWrap - text: root.text.arg(logsModel.count).arg((logsModel.endTime.getTime() - logsModel.startTime.getTime())/ 1000 / 60 / 60 /24) - } + ScrollBar.vertical: ScrollBar {} - ThinDivider {} - - RulesFilterModel { - id: rulesFilterModel - rules: engine.ruleManager.rules - filterDeviceId: root.logsModel.deviceId - } - - ListView { - id: listView - Layout.fillWidth: true - Layout.fillHeight: true - model: logsModel - clip: true - onCountChanged: { - if (root.autoscroll) { - positionViewAtEnd() - } - } - - onContentYChanged: { + onContentYChanged: { + if (!engine.jsonRpcClient.ensureServerVersion("1.10")) { if (!logsModel.busy && contentY - originY < 5 * height) { logsModel.fetchEarlier(24) } } + } - delegate: SwipeDelegate { - id: logEntryDelegate - width: parent.width - implicitHeight: app.delegateHeight - property var device: engine.deviceManager.devices.getDevice(model.deviceId) - property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) - contentItem: RowLayout { - ColorIcon { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: height - name: "../images/event.svg" - color: app.accentColor - } - - ColumnLayout { - Label { - id: timeStampLabel - Layout.fillWidth: true - text: Qt.formatDateTime(model.timestamp,"dd.MM.yy - hh:mm:ss") - } - Label { - Layout.fillWidth: true - text: "%1: %2".arg(deviceClass.eventTypes.getEventType(model.typeId).displayName).arg(model.value.trim()) - elide: Text.ElideRight - font.pixelSize: app.smallFont - } - } -// ColorIcon { -// Layout.preferredWidth: app.iconSize -// Layout.preferredHeight: width -// name: "../images/magic.svg" -// color: { -// for (var i = 0; i < rulesFilterModel.count; i++) { -// var rule = rulesFilterModel.get(i); -// for (var j = 0; j < rule.eventDescriptors.count; j++) { -// var eventDescriptor = rule.eventDescriptors.get(j); -// if (eventDescriptor.eventTypeId === root.logsModel.typeId) { -// var matching = true; -// for (var k = 0; k < eventDescriptor.paramDescriptors.count; k++) { -// var paramDescriptor = eventDescriptor.paramDescriptors.get(k); -// if (paramDescriptor.value === model.value) { -// return app.accentColor; -// } -// } -// } -// } -// } -// return keyColor; -// } - -// } + delegate: SwipeDelegate { + id: logEntryDelegate + width: parent.width + implicitHeight: app.delegateHeight + property var device: engine.deviceManager.devices.getDevice(model.deviceId) + property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) + contentItem: RowLayout { + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: height + name: "../images/event.svg" + color: app.accentColor } - swipe.right: MouseArea { - height: logEntryDelegate.height - width: height - anchors.right: parent.right - ColorIcon { - anchors.fill: parent - anchors.margins: app.margins - name: "../images/magic.svg" + + ColumnLayout { + Label { + id: timeStampLabel + Layout.fillWidth: true + text: Qt.formatDateTime(model.timestamp,"dd.MM.yy - hh:mm:ss") } - onClicked: root.addRuleClicked(model.value) - } - onClicked: { - if (swipe.complete) { - swipe.close() - } else { - swipe.open(SwipeDelegate.Right) + Label { + Layout.fillWidth: true + text: "%1: %2".arg(deviceClass.eventTypes.getEventType(model.typeId).displayName).arg(model.value.trim()) + elide: Text.ElideRight + font.pixelSize: app.smallFont } } } - - BusyIndicator { - anchors.centerIn: parent - visible: root.logsModel.busy - running: visible + swipe.right: MouseArea { + height: logEntryDelegate.height + width: height + anchors.right: parent.right + ColorIcon { + anchors.fill: parent + anchors.margins: app.margins + name: "../images/magic.svg" + } + onClicked: root.addRuleClicked(model.value) } + onClicked: { + if (swipe.complete) { + swipe.close() + } else { + swipe.open(SwipeDelegate.Right) + } + } + } + + BusyIndicator { + anchors.centerIn: parent + visible: root.logsModel.busy + running: visible } } } diff --git a/nymea-app/ui/delegates/ActionDelegate.qml b/nymea-app/ui/delegates/ActionDelegate.qml index de099023..7da1f75f 100644 --- a/nymea-app/ui/delegates/ActionDelegate.qml +++ b/nymea-app/ui/delegates/ActionDelegate.qml @@ -304,6 +304,7 @@ ItemDelegate { property var value: null text: root.actionType.displayName onClicked: { + print("ActionDelegate: Button clicked") var params = []; print("fooo", root.actionType.paramTypes.count) for (var i = 0; i < root.actionType.paramTypes.count; i++) { diff --git a/nymea-app/ui/devicepages/ButtonDevicePage.qml b/nymea-app/ui/devicepages/ButtonDevicePage.qml index 7a8ee002..08e9184f 100644 --- a/nymea-app/ui/devicepages/ButtonDevicePage.qml +++ b/nymea-app/ui/devicepages/ButtonDevicePage.qml @@ -5,15 +5,29 @@ import Nymea 1.0 import "../components" import "../customviews" -GenericDevicePage { +DevicePageBase { id: root - GenericTypeLogView { anchors.fill: parent - text: qsTr("This button has been pressed %1 times in the last %2 days.") - logsModel: LogsModel { + logsModel: engine.jsonRpcClient.ensureServerVersion("1.10") ? logsModelNg : logsModel + LogsModelNg { + id: logsModelNg + engine: _engine + deviceId: root.device.id + live: true + typeIds: { + var ret = []; + ret.push(root.deviceClass.eventTypes.findByName("pressed").id) + if (root.deviceClass.eventTypes.findByName("longPressed")) { + ret.push(root.deviceClass.eventTypes.findByName("longPressed").id) + } + return ret; + } + } + LogsModel { + id: logsModel engine: _engine deviceId: root.device.id live: true diff --git a/nymea-app/ui/devicepages/FingerprintReaderDevicePage.qml b/nymea-app/ui/devicepages/FingerprintReaderDevicePage.qml index 4c395fbe..d3d51c5f 100644 --- a/nymea-app/ui/devicepages/FingerprintReaderDevicePage.qml +++ b/nymea-app/ui/devicepages/FingerprintReaderDevicePage.qml @@ -26,14 +26,28 @@ DevicePageBase { } } -// ThinDivider {} + ThinDivider {} + + Label { + Layout.fillWidth: true + Layout.margins: app.margins + text: qsTr("Access log:") + } GenericTypeLogView { Layout.fillHeight: true Layout.fillWidth: true - text: qsTr("%1 fingerprints recognized on this device in the last %2 days.") - logsModel: LogsModel { + logsModel: engine.jsonRpcClient.ensureServerVersion("1.10") ? logsModelNg : logsModel + LogsModelNg { + id: logsModelNg + deviceId: root.device.id + engine: _engine + live: true + typeIds: [root.accessGrantedEventType.id, root.accessDeniedEventType.id]; + } + LogsModel { + id: logsModel deviceId: root.device.id engine: _engine live: true diff --git a/nymea-app/ui/devicepages/InputTriggerDevicePage.qml b/nymea-app/ui/devicepages/InputTriggerDevicePage.qml index 0c011755..bc331295 100644 --- a/nymea-app/ui/devicepages/InputTriggerDevicePage.qml +++ b/nymea-app/ui/devicepages/InputTriggerDevicePage.qml @@ -5,14 +5,22 @@ import Nymea 1.0 import "../components" import "../customviews" -GenericDevicePage { +DevicePageBase { id: root GenericTypeLogView { anchors.fill: parent - text: qsTr("This event has appeared %1 times in the last %2 days.") - logsModel: LogsModel { + logsModel: engine.jsonRpcClient.ensureServerVersion("1.10") ? logsModelNg : logsModel + LogsModelNg { + id: logsModelNg + engine: _engine + deviceId: root.device.id + live: true + typeIds: [root.deviceClass.eventTypes.findByName("triggered").id]; + } + LogsModel { + id: logsModel engine: _engine deviceId: root.device.id live: true diff --git a/nymea-app/ui/devicepages/NotificationsDevicePage.qml b/nymea-app/ui/devicepages/NotificationsDevicePage.qml index 5ac06f94..4f7cd7e8 100644 --- a/nymea-app/ui/devicepages/NotificationsDevicePage.qml +++ b/nymea-app/ui/devicepages/NotificationsDevicePage.qml @@ -71,18 +71,33 @@ DevicePageBase { ThinDivider {} + Label { + Layout.fillWidth: true + Layout.margins: app.margins + wrapMode: Text.WordWrap + text: qsTr("Sent notifications:") + } + + GenericTypeLogView { Layout.fillHeight: true Layout.fillWidth: true - text: qsTr("%1 notifications sent to this device in the last %2 days.") - logsModel: LogsModel { + logsModel: engine.jsonRpcClient.ensureServerVersion("1.10") ? logsModelNg : logsModel + LogsModelNg { + id: logsModelNg + deviceId: root.device.id + engine: _engine + typeIds: [root.deviceClass.actionTypes.findByName("notify").id]; + } + + LogsModel { + id: logsModel deviceId: root.device.id live: true engine: _engine Component.onCompleted: update() typeIds: [root.deviceClass.actionTypes.findByName("notify").id]; - } delegate: MeaListItemDelegate { diff --git a/nymea-app/ui/devicepages/StateLogPage.qml b/nymea-app/ui/devicepages/StateLogPage.qml index a5c3e02e..393361f7 100644 --- a/nymea-app/ui/devicepages/StateLogPage.qml +++ b/nymea-app/ui/devicepages/StateLogPage.qml @@ -37,17 +37,13 @@ Page { typeIds: [root.stateType.id] } -// LogsModelNg { -// id: logsModelNg -// deviceId: root.device.id -// typeId: root.stateType.id -// startTime: { -// var date = new Date(); -// date.setHours(new Date().getHours() - 24) -// return date; -// } -// endTime: new Date(); -// } + LogsModelNg { + id: logsModelNg + engine: _engine + deviceId: root.device.id + typeIds: [root.stateType.id] + live: true + } ColumnLayout { anchors.fill: parent @@ -75,9 +71,8 @@ Page { id: logView width: swipeView.width height: swipeView.height - text: qsTr("%1, %2 has changed %3 times in the last %4 days").arg(device.name).arg(stateType.displayName) - logsModel: logsModel + logsModel: engine.jsonRpcClient.ensureServerVersion("1.10") ? logsModelNg : logsModel onAddRuleClicked: { var rule = engine.ruleManager.createNewRule(); diff --git a/nymea-app/ui/system/LogViewerPage.qml b/nymea-app/ui/system/LogViewerPage.qml index 8ede8b8b..52d06372 100644 --- a/nymea-app/ui/system/LogViewerPage.qml +++ b/nymea-app/ui/system/LogViewerPage.qml @@ -33,7 +33,6 @@ Page { } endTime: new Date() live: true - Component.onCompleted: update() onCountChanged: { if (root.autoScroll) { listView.positionViewAtEnd() @@ -41,6 +40,12 @@ Page { } } + LogsModelNg { + id: logsModelNg + engine: _engine + live: true + } + BusyIndicator { anchors.centerIn: listView visible: logsModel.busy @@ -48,11 +53,13 @@ Page { ListView { id: listView - model: logsModel + model: engine.jsonRpcClient.ensureServerVersion("1.10") ? logsModelNg : logsModel anchors.fill: parent clip: true headerPositioning: ListView.OverlayHeader + Component.onCompleted: model.update() + onDraggingChanged: { if (dragging) { root.autoScroll = false; @@ -62,8 +69,10 @@ Page { ScrollBar.vertical: ScrollBar {} onContentYChanged: { - if (!logsModel.busy && contentY - originY < 5 * height) { - logsModel.fetchEarlier(1) + if (!engine.jsonRpcClient.ensureServerVersion("1.10")) { + if (!logsModel.busy && contentY - originY < 5 * height) { + logsModel.fetchEarlier(1) + } } } From 10cbc9941b5b56ae06104d0544e543d58cf9837e Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 24 Oct 2018 11:06:31 +0200 Subject: [PATCH 02/10] initial take on QtCharts --- libnymea-app-core/libnymea-app-core.pro | 2 +- libnymea-app-core/models/logsmodelng.cpp | 17 ++++++- libnymea-app-core/models/logsmodelng.h | 9 ++++ nymea-app/main.cpp | 6 +-- nymea-app/nymea-app.pro | 2 +- nymea-app/ruletemplates/template.json | 58 +++++++++++++++++++++ nymea-app/ui/devicepages/StateLogPage.qml | 62 +++++++++++++++++++++++ 7 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 nymea-app/ruletemplates/template.json diff --git a/libnymea-app-core/libnymea-app-core.pro b/libnymea-app-core/libnymea-app-core.pro index a0a3e3a0..514e2cce 100644 --- a/libnymea-app-core/libnymea-app-core.pro +++ b/libnymea-app-core/libnymea-app-core.pro @@ -15,7 +15,7 @@ include(../nymea-remoteproxy/libnymea-remoteproxyclient/libnymea-remoteproxyclie QT -= gui -QT += network websockets bluetooth +QT += network websockets bluetooth charts LIBS += -lssl -lcrypto diff --git a/libnymea-app-core/models/logsmodelng.cpp b/libnymea-app-core/models/logsmodelng.cpp index 5e0f89cc..d1a28c27 100644 --- a/libnymea-app-core/models/logsmodelng.cpp +++ b/libnymea-app-core/models/logsmodelng.cpp @@ -7,7 +7,8 @@ #include "types/logentry.h" #include "logmanager.h" -LogsModelNg::LogsModelNg(QObject *parent) : QAbstractListModel(parent) +LogsModelNg::LogsModelNg(QObject *parent) : QAbstractListModel(parent), + m_lineSeries(new QtCharts::QLineSeries(this)) { } @@ -133,9 +134,19 @@ void LogsModelNg::setEndTime(const QDateTime &endTime) } } +QtCharts::QLineSeries *LogsModelNg::lineSeries() const +{ + return m_lineSeries; +} + +void LogsModelNg::setLineSeries(QtCharts::QLineSeries *lineSeries) +{ + m_lineSeries = lineSeries; +} + void LogsModelNg::logsReply(const QVariantMap &data) { - qDebug() << "logs reply" << data; +// qDebug() << "logs reply" << data; m_busy = false; emit busyChanged(); @@ -170,6 +181,8 @@ void LogsModelNg::logsReply(const QVariantMap &data) beginInsertRows(QModelIndex(), offset, offset + newBlock.count() - 1); for (int i = 0; i < newBlock.count(); i++) { m_list.insert(offset + i, newBlock.at(i)); + qDebug() << "Adding line series point:" << i << newBlock.at(i)->timestamp().toSecsSinceEpoch() << newBlock.at(i)->value().toReal(); + m_lineSeries->insert(offset + i, QPointF(newBlock.at(i)->timestamp().toSecsSinceEpoch(), newBlock.at(i)->value().toReal())); } endInsertRows(); emit countChanged(); diff --git a/libnymea-app-core/models/logsmodelng.h b/libnymea-app-core/models/logsmodelng.h index 8881fb1d..ad0910af 100644 --- a/libnymea-app-core/models/logsmodelng.h +++ b/libnymea-app-core/models/logsmodelng.h @@ -4,6 +4,7 @@ #include #include #include +#include class LogEntry; class Engine; @@ -20,6 +21,8 @@ class LogsModelNg : public QAbstractListModel Q_PROPERTY(QDateTime startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged) Q_PROPERTY(QDateTime endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged) + Q_PROPERTY(QtCharts::QLineSeries *lineSeries READ lineSeries WRITE setLineSeries NOTIFY lineSeriesChanged) + public: enum Roles { RoleTimestamp, @@ -56,6 +59,9 @@ public: QDateTime endTime() const; void setEndTime(const QDateTime &endTime); + QtCharts::QLineSeries *lineSeries() const; + void setLineSeries(QtCharts::QLineSeries *lineSeries); + protected: virtual void fetchMore(const QModelIndex &parent = QModelIndex()) override; virtual bool canFetchMore(const QModelIndex &parent = QModelIndex()) const override; @@ -69,6 +75,7 @@ signals: void startTimeChanged(); void endTimeChanged(); void engineChanged(); + void lineSeriesChanged(); private slots: void newLogEntryReceived(const QVariantMap &data); @@ -89,6 +96,8 @@ private: int m_blockSize = 100; bool m_canFetchMore = true; + QtCharts::QLineSeries *m_lineSeries = nullptr; + QList > m_fetchedPeriods; }; diff --git a/nymea-app/main.cpp b/nymea-app/main.cpp index b317e307..b6561961 100644 --- a/nymea-app/main.cpp +++ b/nymea-app/main.cpp @@ -18,7 +18,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#include +#include #include #include #include @@ -58,7 +58,7 @@ int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - QGuiApplication application(argc, argv); + QApplication application(argc, argv); application.setApplicationName("nymea-app"); application.setOrganizationName("nymea"); @@ -71,7 +71,7 @@ int main(int argc, char *argv[]) applicationFont.setCapitalization(QFont::MixedCase); applicationFont.setPixelSize(16); applicationFont.setWeight(QFont::Normal); - QGuiApplication::setFont(applicationFont); + QApplication::setFont(applicationFont); QTranslator qtTranslator; qtTranslator.load("qt_" + QLocale::system().name(), diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index 5f2313bc..75da82e2 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -2,7 +2,7 @@ TEMPLATE=app TARGET=nymea-app include(../config.pri) -QT += network qml quick quickcontrols2 svg websockets bluetooth +QT += network qml quick quickcontrols2 svg websockets bluetooth charts INCLUDEPATH += $$top_srcdir/libnymea-common \ $$top_srcdir/libnymea-app-core diff --git a/nymea-app/ruletemplates/template.json b/nymea-app/ruletemplates/template.json new file mode 100644 index 00000000..b3079327 --- /dev/null +++ b/nymea-app/ruletemplates/template.json @@ -0,0 +1,58 @@ +{ + "templates": [ + { + "interfaceName": "", + "description": "", + "ruleNameTemplate": "%0 ...", + "eventDescriptorTemplates": [ // optional + { + "interfaceName": "", + "interfaceEvent": "", + "selectionId": 0, + "params": [ // optional + { + "name": "", + "value": "", //optional + } + ] + } + ], + "stateEvaluatorTemplate": { + "stateDescriptorTemplate": { + "interfaceName": "", + "interfaceState": "", + "selectionId": 1, + "operator": "ValueOperatorEquals", + "value": true + } + }, + "ruleActionTemplates": [ + { + "interfaceName": "", + "interfaceAction": "", + "selectionId": 2, + "params": [ + { + "name": "", + "value": "" + } + ] + } + ], + "ruleExitActionTemplates": [ + { + "interfaceName": "", + "interfaceAction": "", + "selectionId": 2, + "params": [ + { + "name": "", + "value": "" + } + ] + } + ] + } + ] +} + diff --git a/nymea-app/ui/devicepages/StateLogPage.qml b/nymea-app/ui/devicepages/StateLogPage.qml index 393361f7..0547c8ba 100644 --- a/nymea-app/ui/devicepages/StateLogPage.qml +++ b/nymea-app/ui/devicepages/StateLogPage.qml @@ -4,6 +4,7 @@ import QtQuick.Layouts 1.1 import Nymea 1.0 import "../components" import "../customviews" +import QtCharts 2.2 Page { id: root @@ -43,6 +44,7 @@ Page { deviceId: root.device.id typeIds: [root.stateType.id] live: true + lineSeries: lineSeries1 } ColumnLayout { @@ -58,6 +60,9 @@ Page { TabButton { text: qsTr("Graph") } + TabButton { + text: qsTr("Graph NG") + } } SwipeView { @@ -163,6 +168,63 @@ Page { } } + + Item { + width: swipeView.width + height: swipeView.height + + ChartView { + id: chartView + anchors.fill: parent + ValueAxis { + id: yAxis + min: 0 + max: 50 + } + + ValueAxis { + id: xAxis + min: Math.floor(startTime.getTime() / 1000) + max: Math.floor(endTime.getTime() / 1000) + + property date startTime: { + var date = new Date(); + date.setHours(date.getHours() - 6); + return date; + } + + property date endTime: new Date() + Component.onCompleted: print("creating axis:", startTime, endTime) + } + + LineSeries { + id: lineSeries1 + axisX: xAxis + axisY: yAxis + } + + MouseArea { + anchors.fill: parent + property int lastX: 0 + property int lastY: 0 + onPressed: { + lastX = mouse.x + lastY = mouse.y + } + + onPositionChanged: { + if (lastX !== mouse.x) { + chartView.scrollRight(lastX - mouse.x) + lastX = mouse.x + } + if (lastY !== mouse.y) { + chartView.scrollDown(lastY - mouse.y) + lastY = mouse.y + } + } + } + } + } } } } From 37c4ed0261ebd0624f4b18b4558e58dd3387a692 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Thu, 25 Oct 2018 10:54:26 +0200 Subject: [PATCH 03/10] more work on using qtcharts --- libnymea-app-core/models/logsmodelng.cpp | 78 ++++++++-- libnymea-app-core/models/logsmodelng.h | 27 +++- nymea-app/images.qrc | 2 + nymea-app/ui/devicepages/StateLogPage.qml | 155 ++++++++++++++++---- nymea-app/ui/images/zoom-in.svg | 171 ++++++++++++++++++++++ nymea-app/ui/images/zoom-out.svg | 171 ++++++++++++++++++++++ 6 files changed, 554 insertions(+), 50 deletions(-) create mode 100644 nymea-app/ui/images/zoom-in.svg create mode 100644 nymea-app/ui/images/zoom-out.svg diff --git a/libnymea-app-core/models/logsmodelng.cpp b/libnymea-app-core/models/logsmodelng.cpp index d1a28c27..eccfafba 100644 --- a/libnymea-app-core/models/logsmodelng.cpp +++ b/libnymea-app-core/models/logsmodelng.cpp @@ -7,8 +7,7 @@ #include "types/logentry.h" #include "logmanager.h" -LogsModelNg::LogsModelNg(QObject *parent) : QAbstractListModel(parent), - m_lineSeries(new QtCharts::QLineSeries(this)) +LogsModelNg::LogsModelNg(QObject *parent) : QAbstractListModel(parent) { } @@ -134,14 +133,44 @@ void LogsModelNg::setEndTime(const QDateTime &endTime) } } -QtCharts::QLineSeries *LogsModelNg::lineSeries() const +QtCharts::QXYSeries *LogsModelNg::graphSeries() const { - return m_lineSeries; + return m_graphSeries; } -void LogsModelNg::setLineSeries(QtCharts::QLineSeries *lineSeries) +void LogsModelNg::setGraphSeries(QtCharts::QXYSeries *graphSeries) { - m_lineSeries = lineSeries; + m_graphSeries = graphSeries; +} + +QDateTime LogsModelNg::viewStartTime() const +{ + return m_viewStartTime; +} + +void LogsModelNg::setViewStartTime(const QDateTime &viewStartTime) +{ + if (m_viewStartTime != viewStartTime) { + m_viewStartTime = viewStartTime; + emit viewStartTimeChanged(); + if (m_list.count() == 0 || m_list.last()->timestamp() > m_viewStartTime) { + if (canFetchMore()) { + fetchMore(); + } + } + } +} + +QVariant LogsModelNg::minValue() const +{ + qDebug() << "returning min value" << m_minValue; + return m_minValue; +} + +QVariant LogsModelNg::maxValue() const +{ + qDebug() << "returning max value" << m_maxValue; + return m_maxValue; } void LogsModelNg::logsReply(const QVariantMap &data) @@ -179,13 +208,37 @@ void LogsModelNg::logsReply(const QVariantMap &data) } beginInsertRows(QModelIndex(), offset, offset + newBlock.count() - 1); + QVariant newMin = m_minValue; + QVariant newMax = m_maxValue; for (int i = 0; i < newBlock.count(); i++) { - m_list.insert(offset + i, newBlock.at(i)); - qDebug() << "Adding line series point:" << i << newBlock.at(i)->timestamp().toSecsSinceEpoch() << newBlock.at(i)->value().toReal(); - m_lineSeries->insert(offset + i, QPointF(newBlock.at(i)->timestamp().toSecsSinceEpoch(), newBlock.at(i)->value().toReal())); + LogEntry *entry = newBlock.at(i); + m_list.insert(offset + i, entry); + qDebug() << "Adding line series point:" << i << entry->timestamp().toSecsSinceEpoch() << entry->value().toReal(); + if (m_graphSeries) { + m_graphSeries->insert(offset + i, QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toReal())); + } + if (!newMin.isValid() || newMin > entry->value()) { + newMin = entry->value().toReal(); + } + if (!newMax.isValid() || newMax < entry->value()) { + newMax = entry->value().toReal(); + } } endInsertRows(); emit countChanged(); + qDebug() << "min" << m_minValue << "max" << m_maxValue << "newMin" << newMin << "newMax" << newMax; + if (m_minValue != newMin) { + m_minValue = newMin; + emit minValueChanged(); + } + if (m_maxValue != newMax) { + m_maxValue = newMax; + emit maxValueChanged(); + } + + if (m_list.count() > 0 && m_list.last()->timestamp() > m_viewStartTime && canFetchMore()) { + fetchMore(); + } } void LogsModelNg::fetchMore(const QModelIndex &parent) @@ -225,8 +278,8 @@ void LogsModelNg::fetchMore(const QModelIndex &parent) if (!m_startTime.isNull() && !m_endTime.isNull()) { QVariantList timeFilters; QVariantMap timeFilter; - timeFilter.insert("startDate", m_currentFetchStartTime.toSecsSinceEpoch()); - timeFilter.insert("endDate", m_currentFetchEndTime.toSecsSinceEpoch()); + timeFilter.insert("startDate", m_startTime.toSecsSinceEpoch()); + timeFilter.insert("endDate", m_endTime.toSecsSinceEpoch()); timeFilters.append(timeFilter); params.insert("timeFilters", timeFilters); } @@ -271,6 +324,9 @@ void LogsModelNg::newLogEntryReceived(const QVariantMap &data) QVariant value = loggingEventType == LogEntry::LoggingEventTypeActiveChange ? entryMap.value("active").toBool() : entryMap.value("value"); LogEntry *entry = new LogEntry(timeStamp, value, deviceId, typeId, loggingSource, loggingEventType, this); m_list.prepend(entry); + if (m_graphSeries) { + m_graphSeries->insert(0, QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toReal())); + } endInsertRows(); emit countChanged(); diff --git a/libnymea-app-core/models/logsmodelng.h b/libnymea-app-core/models/logsmodelng.h index ad0910af..120ea6ce 100644 --- a/libnymea-app-core/models/logsmodelng.h +++ b/libnymea-app-core/models/logsmodelng.h @@ -20,8 +20,11 @@ class LogsModelNg : public QAbstractListModel Q_PROPERTY(QStringList typeIds READ typeIds WRITE setTypeIds NOTIFY typeIdsChanged) Q_PROPERTY(QDateTime startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged) Q_PROPERTY(QDateTime endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged) + Q_PROPERTY(QVariant minValue READ minValue NOTIFY minValueChanged) + Q_PROPERTY(QVariant maxValue READ maxValue NOTIFY maxValueChanged) - Q_PROPERTY(QtCharts::QLineSeries *lineSeries READ lineSeries WRITE setLineSeries NOTIFY lineSeriesChanged) + Q_PROPERTY(QtCharts::QXYSeries *graphSeries READ graphSeries WRITE setGraphSeries NOTIFY graphSeriesChanged) + Q_PROPERTY(QDateTime viewStartTime READ viewStartTime WRITE setViewStartTime NOTIFY viewStartTimeChanged) public: enum Roles { @@ -59,8 +62,14 @@ public: QDateTime endTime() const; void setEndTime(const QDateTime &endTime); - QtCharts::QLineSeries *lineSeries() const; - void setLineSeries(QtCharts::QLineSeries *lineSeries); + QtCharts::QXYSeries *graphSeries() const; + void setGraphSeries(QtCharts::QXYSeries *lineSeries); + + QDateTime viewStartTime() const; + void setViewStartTime(const QDateTime &viewStartTime); + + QVariant minValue() const; + QVariant maxValue() const; protected: virtual void fetchMore(const QModelIndex &parent = QModelIndex()) override; @@ -75,7 +84,10 @@ signals: void startTimeChanged(); void endTimeChanged(); void engineChanged(); - void lineSeriesChanged(); + void graphSeriesChanged(); + void viewStartTimeChanged(); + void minValueChanged(); + void maxValueChanged(); private slots: void newLogEntryReceived(const QVariantMap &data); @@ -91,12 +103,13 @@ private: QStringList m_typeIds; QDateTime m_startTime; QDateTime m_endTime; - QDateTime m_currentFetchStartTime; - QDateTime m_currentFetchEndTime; int m_blockSize = 100; bool m_canFetchMore = true; + QDateTime m_viewStartTime; + QVariant m_minValue; + QVariant m_maxValue; - QtCharts::QLineSeries *m_lineSeries = nullptr; + QtCharts::QXYSeries *m_graphSeries = nullptr; QList > m_fetchedPeriods; }; diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc index 0c1fbb22..337433fe 100644 --- a/nymea-app/images.qrc +++ b/nymea-app/images.qrc @@ -148,5 +148,7 @@ ui/images/view-collapse.svg ui/images/view-expand.svg ui/images/weather-app-symbolic.svg + ui/images/zoom-out.svg + ui/images/zoom-in.svg diff --git a/nymea-app/ui/devicepages/StateLogPage.qml b/nymea-app/ui/devicepages/StateLogPage.qml index 0547c8ba..3da7fb25 100644 --- a/nymea-app/ui/devicepages/StateLogPage.qml +++ b/nymea-app/ui/devicepages/StateLogPage.qml @@ -1,5 +1,6 @@ -import QtQuick 2.5 -import QtQuick.Controls 2.1 +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 import QtQuick.Layouts 1.1 import Nymea 1.0 import "../components" @@ -44,7 +45,8 @@ Page { deviceId: root.device.id typeIds: [root.stateType.id] live: true - lineSeries: lineSeries1 + graphSeries: lineSeries1 + viewStartTime: xAxis.min } ColumnLayout { @@ -163,7 +165,7 @@ Page { engine: _engine // Live doesn't work yet with ValueLogsProxyModel - // live: true + // live: true } } } @@ -173,57 +175,146 @@ Page { width: swipeView.width height: swipeView.height - ChartView { - id: chartView + ColumnLayout { anchors.fill: parent - ValueAxis { - id: yAxis - min: 0 - max: 50 + RowLayout { + Layout.alignment: Qt.AlignRight + HeaderButton { + imageSource: "../images/zoom-in.svg" + onClicked: { + var diff = xAxis.max.getTime() - xAxis.min.getTime() + var newTime = new Date(xAxis.min.getTime() + (diff / 4)) + xAxis.min = newTime; + } + } + HeaderButton { + imageSource: "../images/zoom-out.svg" + onClicked: { + var diff = xAxis.max.getTime() - xAxis.min.getTime() + var newTime = new Date(xAxis.min.getTime() - (diff / 4)) + xAxis.min = newTime; + } + } } - ValueAxis { - id: xAxis - min: Math.floor(startTime.getTime() / 1000) - max: Math.floor(endTime.getTime() / 1000) + ChartView { + id: chartView + Layout.fillWidth: true + Layout.fillHeight: true + margins.top: 0 + margins.bottom: 0 + margins.left: 0 + margins.right: 0 + backgroundColor: Material.background + animationDuration: 300 + animationOptions: ChartView.SeriesAnimations - property date startTime: { - var date = new Date(); - date.setHours(date.getHours() - 6); - return date; + ValueAxis { + id: yAxis + min: logsModelNg.minValue + max: logsModelNg.maxValue + labelsFont.pixelSize: app.smallFont + tickCount: chartView.height / 40 } - property date endTime: new Date() - Component.onCompleted: print("creating axis:", startTime, endTime) - } + DateTimeAxis { + id: xAxis + gridVisible: false + tickCount: chartView.width / 70 + labelsFont.pixelSize: app.smallFont + format: { + var timeDiff = (xAxis.max.getTime() - xAxis.min.getTime()) / 1000 + if (timeDiff < 60) { // one minute + return "mm:ss" + } + if (timeDiff < 60 * 60) { // one hour + return "hh:mm" + } + if (timeDiff < 60 * 60 * 24 * 2) { // two day + return "hh:mm" + } + if (timeDiff < 60 * 60 * 24 * 7) { // one week + return "ddd hh:mm" + } + if (timeDiff < 60 * 60 * 24 * 7 * 30) { // one month + return "dd.MM." + } + return "MMM yy" + } - LineSeries { - id: lineSeries1 - axisX: xAxis - axisY: yAxis - } + min: { + var date = new Date(); + date.setHours(date.getHours() - 6); + return date; + } + max: new Date() + } - MouseArea { + AreaSeries { + axisX: xAxis + axisY: yAxis + name: root.stateType.displayName + borderColor: app.accentColor + borderWidth: 4 + upperSeries: LineSeries { + id: lineSeries1 + width: 4 + } + color: Qt.rgba(app.accentColor.r, app.accentColor.g, app.accentColor.b, .3) + } + + MouseArea { anchors.fill: parent property int lastX: 0 property int lastY: 0 + + function scrollRightLimited(dx) { + chartView.animationOptions = ChartView.NoAnimation + var now = new Date() + // if we're already at the limit, don't even start scrolling + if (dx < 0 || xAxis.max < now) { + chartView.scrollRight(dx) + } + // figure out if we scrolled too far + var overshoot = xAxis.max.getTime() - now.getTime() + print("overshoot is:", overshoot, "oldMax", xAxis.max, "newMax", now, "oldMin", xAxis.min, "newMin", new Date(xAxis.min.getTime() - overshoot)) + if (overshoot > 0) { + var range = xAxis.max - xAxis.min + xAxis.max = now + xAxis.min = new Date(xAxis.max.getTime() - range) + } + chartView.animationOptions = ChartView.SeriesAnimations + } + + function zoomInLimited(dy) { + chartView.animationOptions = ChartView.NoAnimation + var oldMax = xAxis.max; + chartView.scrollRight(dy); + var timeDiff = xAxis.max.getTime() - oldMax.getTime() + xAxis.min = new Date(xAxis.min.getTime() - timeDiff * 2) + chartView.animationOptions = ChartView.SeriesAnimations + } + onPressed: { lastX = mouse.x lastY = mouse.y } + onWheel: { + scrollRightLimited(-wheel.pixelDelta.x) +// zoomInLimited(wheel.pixelDelta.y) + } + onPositionChanged: { if (lastX !== mouse.x) { - chartView.scrollRight(lastX - mouse.x) + scrollRightLimited(lastX - mouseX) lastX = mouse.x } - if (lastY !== mouse.y) { - chartView.scrollDown(lastY - mouse.y) - lastY = mouse.y - } } } + } } + } } } diff --git a/nymea-app/ui/images/zoom-in.svg b/nymea-app/ui/images/zoom-in.svg new file mode 100644 index 00000000..57e89dc4 --- /dev/null +++ b/nymea-app/ui/images/zoom-in.svg @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/zoom-out.svg b/nymea-app/ui/images/zoom-out.svg new file mode 100644 index 00000000..38a04c3e --- /dev/null +++ b/nymea-app/ui/images/zoom-out.svg @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + From 65d983d5e3ab09771a641b50f42f3bfed1fd54d4 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Fri, 26 Oct 2018 12:11:25 +0200 Subject: [PATCH 04/10] more work on qtcharts --- libnymea-app-core/models/logsmodelng.cpp | 44 ++- nymea-app/resources.qrc | 2 + nymea-app/ui/customviews/GenericTypeGraph.qml | 252 ++++++++++++++++++ .../ui/customviews/GenericTypeGraphPre110.qml | 84 ++++++ nymea-app/ui/devicepages/StateLogPage.qml | 232 +--------------- 5 files changed, 385 insertions(+), 229 deletions(-) create mode 100644 nymea-app/ui/customviews/GenericTypeGraph.qml create mode 100644 nymea-app/ui/customviews/GenericTypeGraphPre110.qml diff --git a/libnymea-app-core/models/logsmodelng.cpp b/libnymea-app-core/models/logsmodelng.cpp index eccfafba..cfb02fa4 100644 --- a/libnymea-app-core/models/logsmodelng.cpp +++ b/libnymea-app-core/models/logsmodelng.cpp @@ -213,9 +213,39 @@ void LogsModelNg::logsReply(const QVariantMap &data) for (int i = 0; i < newBlock.count(); i++) { LogEntry *entry = newBlock.at(i); m_list.insert(offset + i, entry); - qDebug() << "Adding line series point:" << i << entry->timestamp().toSecsSinceEpoch() << entry->value().toReal(); + if (m_graphSeries) { - m_graphSeries->insert(offset + i, QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toReal())); + Device *dev = m_engine->deviceManager()->devices()->getDevice(entry->deviceId()); + if (dev && dev->deviceClass()->stateTypes()->getStateType(entry->typeId())->type() == "Bool") { + // We don't want bools painting triangles, add a toggle point to keep lines straight + if (i > 0) { + LogEntry *newerEntry = newBlock.at(i - 1); + if (newerEntry->value().toBool() != entry->value().toBool()) { + qDebug() << "Adding bool line series point:" << (newerEntry->timestamp().addSecs(-1)) << newerEntry->timestamp().addSecs(-1).toMSecsSinceEpoch() << (entry->value().toBool() ? 1 : 0) << "(correction)"; + m_graphSeries->append(QPointF(newerEntry->timestamp().addSecs(-1).toMSecsSinceEpoch(), entry->value().toBool() ? 1 : 0)); + } + } + if (m_graphSeries->count() == 0) { + qDebug() << "Adding bool line series point:" << QDateTime::currentDateTime() << QDateTime::currentDateTime().toMSecsSinceEpoch() - 1 << (entry->value().toBool() ? 1 : 0) << "(beginning)"; + m_graphSeries->append(QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), 1));// entry->value().toBool() ? 1 : 0)); + m_graphSeries->append(QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), entry->value().toBool() ? 1 : 0)); + } + qDebug() << "Adding bool line series point:" << entry->timestamp() << entry->timestamp().toMSecsSinceEpoch() << (entry->value().toBool() ? 1 : 0); + m_graphSeries->append(QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toBool() ? 1 : 0)); + } else { +// if (i > 0) { +// LogEntry *newerEntry = newBlock.at(i - 1); +// if (newerEntry->value() != entry->value()) { +// qDebug() << "Adding line series point:" << (offset + i) << newerEntry->timestamp().toMSecsSinceEpoch() - 1 << (entry->value().toReal()) << "(correction)"; +// m_graphSeries->append(QPointF(newerEntry->timestamp().toMSecsSinceEpoch() - 1, entry->value().toReal())); +// } +// } + if (m_graphSeries->count() == 0) { + m_graphSeries->insert(0, QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), entry->value().toReal())); + } + qDebug() << "Adding line series point:" << (offset + i) << entry->timestamp().toMSecsSinceEpoch() << (entry->value().toReal()); + m_graphSeries->append(QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toReal())); + } } if (!newMin.isValid() || newMin > entry->value()) { newMin = entry->value().toReal(); @@ -236,7 +266,7 @@ void LogsModelNg::logsReply(const QVariantMap &data) emit maxValueChanged(); } - if (m_list.count() > 0 && m_list.last()->timestamp() > m_viewStartTime && canFetchMore()) { + if (m_viewStartTime.isValid() && m_list.count() > 0 && m_list.last()->timestamp() > m_viewStartTime && canFetchMore()) { fetchMore(); } } @@ -246,8 +276,8 @@ void LogsModelNg::fetchMore(const QModelIndex &parent) Q_UNUSED(parent) qDebug() << "fetchMore called"; - if (!m_engine->jsonRpcClient()) { - qWarning() << "Cannot update. JsonRpcClient not set"; + if (!m_engine) { + qWarning() << "Cannot update. Engine not set"; return; } if (m_busy) { @@ -294,8 +324,8 @@ void LogsModelNg::fetchMore(const QModelIndex &parent) bool LogsModelNg::canFetchMore(const QModelIndex &parent) const { Q_UNUSED(parent) - qDebug() << "canFetchMore" << m_canFetchMore; - return m_canFetchMore; + qDebug() << "canFetchMore" << (m_engine && m_canFetchMore); + return m_engine && m_canFetchMore; } void LogsModelNg::newLogEntryReceived(const QVariantMap &data) diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 0489361d..f855504b 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -127,5 +127,7 @@ translations/nymea-app-de_DE.qm translations/nymea-app-en_US.qm ../LICENSE + ui/customviews/GenericTypeGraphPre110.qml + ui/customviews/GenericTypeGraph.qml diff --git a/nymea-app/ui/customviews/GenericTypeGraph.qml b/nymea-app/ui/customviews/GenericTypeGraph.qml new file mode 100644 index 00000000..c5eeebdc --- /dev/null +++ b/nymea-app/ui/customviews/GenericTypeGraph.qml @@ -0,0 +1,252 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" +import "../customviews" +import QtCharts 2.2 + +Item { + id: root + + property var device: null + property var stateType: null + readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + readonly property bool hasConnectable: deviceClass.interfaces.indexOf("connectable") >= 0 + readonly property var connectedStateType: hasConnectable ? deviceClass.stateTypes.findByName("connected") : null + + LogsModelNg { + id: logsModelNg + engine: _engine + deviceId: root.device.id + typeIds: [root.stateType.id] + live: true + graphSeries: lineSeries1 + viewStartTime: xAxis.min + } + + LogsModelNg { + id: connectedLogsModel + engine: root.hasConnectable ? _engine : null // don't even try to poll if we don't have a connectable interface + deviceId: root.device.id + typeIds: [root.connectedStateType.id] + live: true + graphSeries: connectedLineSeries + viewStartTime: xAxis.min + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + RowLayout { + Layout.alignment: Qt.AlignHCenter + HeaderButton { + imageSource: "../images/zoom-out.svg" + onClicked: { + var diff = xAxis.max.getTime() - xAxis.min.getTime() + var newTime = new Date(xAxis.min.getTime() - (diff / 4)) + xAxis.min = newTime; + } + } + + Label { + Layout.preferredWidth: 100 + horizontalAlignment: Text.AlignHCenter + text: { + var timeDiff = (xAxis.max.getTime() - xAxis.min.getTime()) / 1000; + if (timeDiff < 60) { + return qsTr("%1 seconds").arg(Math.round(timeDiff)); + } + timeDiff = timeDiff / 60 + if (timeDiff < 60) { + return qsTr("%1 minutes").arg(Math.round(timeDiff)); + } + timeDiff = timeDiff / 60 + if (timeDiff < 48) { + return qsTr("%1 hours").arg(Math.round(timeDiff)); + } + timeDiff = timeDiff / 24; + if (timeDiff < 14) { + return qsTr("%1 days").arg(Math.round(timeDiff)); + } + timeDiff = timeDiff / 7 + if (timeDiff < 5) { + return qsTr("%1 weeks").arg(Math.round(timeDiff)); + } + timeDiff * timeDiff * 7 / 30 + if (timeDiff < 24) { + return qsTr("%1 months").arg(Math.round(timeDiff)); + } + timeDiff = timeDiff * 30 / 356 + return qsTr("%1 years").arg(Math.round(timeDiff)) + } + } + + HeaderButton { + imageSource: "../images/zoom-in.svg" + onClicked: { + var diff = xAxis.max.getTime() - xAxis.min.getTime() + var newTime = new Date(xAxis.min.getTime() + (diff / 4)) + xAxis.min = newTime; + } + } + } + + ChartView { + id: chartView + Layout.fillWidth: true + Layout.fillHeight: true + margins.top: 0 + margins.bottom: 0 + margins.left: 0 + margins.right: 0 + backgroundColor: Material.background + legend.labelColor: app.foregroundColor + + animationDuration: 300 + animationOptions: ChartView.SeriesAnimations + + ValueAxis { + id: yAxis + min: logsModelNg.minValue + max: logsModelNg.maxValue + labelsFont.pixelSize: app.smallFont + labelsColor: app.foregroundColor + tickCount: chartView.height / 40 + color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .2) + gridLineColor: color + } + + ValueAxis { + id: connectedAxis + min: 0 + max: 1 + visible: false + } + + DateTimeAxis { + id: xAxis + gridVisible: false + color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .2) + tickCount: chartView.width / 70 + labelsFont.pixelSize: app.smallFont + labelsColor: app.foregroundColor + titleText: { + if (xAxis.min.getYear() === xAxis.max.getYear() + && xAxis.min.getMonth() === xAxis.max.getMonth() + && xAxis.min.getDate() === xAxis.max.getDate()) { + return Qt.formatDate(xAxis.min) + } + return Qt.formatDate(xAxis.min) + " - " + Qt.formatDate(xAxis.max) + } + titleBrush: app.foregroundColor + format: { + var timeDiff = (xAxis.max.getTime() - xAxis.min.getTime()) / 1000 + if (timeDiff < 60) { // one minute + return "mm:ss" + } + if (timeDiff < 60 * 60) { // one hour + return "hh:mm" + } + if (timeDiff < 60 * 60 * 24 * 2) { // two day + return "hh:mm" + } + if (timeDiff < 60 * 60 * 24 * 7) { // one week + return "ddd hh:mm" + } + if (timeDiff < 60 * 60 * 24 * 7 * 30) { // one month + return "dd.MM." + } + return "MMM yy" + } + + min: { + var date = new Date(); + date.setHours(date.getHours() - 6); + return date; + } + max: new Date() + } + + AreaSeries { + axisX: xAxis + axisY: connectedAxis + name: qsTr("Not connected") + visible: root.hasConnectable + upperSeries: LineSeries { + XYPoint {x: xAxis.min.getTime(); y: 1} + XYPoint {x: xAxis.max.getTime(); y: 1} + } + + lowerSeries: LineSeries { + id: connectedLineSeries + } + color: "#55ff0000" + } + + AreaSeries { + axisX: xAxis + axisY: yAxis + name: root.stateType.displayName + borderColor: app.accentColor + borderWidth: 4 + upperSeries: LineSeries { + id: lineSeries1 + } + color: Qt.rgba(app.accentColor.r, app.accentColor.g, app.accentColor.b, .3) + } + + + MouseArea { + anchors.fill: parent + property int lastX: 0 + property int lastY: 0 + + function scrollRightLimited(dx) { + chartView.animationOptions = ChartView.NoAnimation + var now = new Date() + // if we're already at the limit, don't even start scrolling + if (dx < 0 || xAxis.max < now) { + chartView.scrollRight(dx) + } + // figure out if we scrolled too far + var overshoot = xAxis.max.getTime() - now.getTime() +// print("overshoot is:", overshoot, "oldMax", xAxis.max, "newMax", now, "oldMin", xAxis.min, "newMin", new Date(xAxis.min.getTime() - overshoot)) + if (overshoot > 0) { + var range = xAxis.max - xAxis.min + xAxis.max = now + xAxis.min = new Date(xAxis.max.getTime() - range) + } + chartView.animationOptions = ChartView.SeriesAnimations + } + + function zoomInLimited(dy) { + chartView.animationOptions = ChartView.NoAnimation + var oldMax = xAxis.max; + chartView.scrollRight(dy); + var timeDiff = xAxis.max.getTime() - oldMax.getTime() + xAxis.min = new Date(xAxis.min.getTime() - timeDiff * 2) + chartView.animationOptions = ChartView.SeriesAnimations + } + + onPressed: { + lastX = mouse.x + lastY = mouse.y + } + + onWheel: { + scrollRightLimited(-wheel.pixelDelta.x) +// zoomInLimited(wheel.pixelDelta.y) + } + + onPositionChanged: { + if (lastX !== mouse.x) { + scrollRightLimited(lastX - mouseX) + lastX = mouse.x + } + } + } + } + } +} diff --git a/nymea-app/ui/customviews/GenericTypeGraphPre110.qml b/nymea-app/ui/customviews/GenericTypeGraphPre110.qml new file mode 100644 index 00000000..1a66b780 --- /dev/null +++ b/nymea-app/ui/customviews/GenericTypeGraphPre110.qml @@ -0,0 +1,84 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" +import "../customviews" + +ColumnLayout { + id: root + + property var device: null + property var stateType: null + + TabBar { + id: zoomTabBar + Layout.fillWidth: true + TabButton { + text: qsTr("6 h") + property int avg: ValueLogsProxyModel.AverageQuarterHour + property date startTime: { + var date = new Date(); + date.setHours(new Date().getHours() - 6) + date.setMinutes(0) + date.setSeconds(0) + return date; + } + } + TabButton { + text: qsTr("24 h") + property int avg: ValueLogsProxyModel.AverageHourly + property date startTime: { + var date = new Date(); + date.setHours(new Date().getHours() - 24); + date.setMinutes(0) + date.setSeconds(0) + return date; + } + } + TabButton { + text: qsTr("7 d") + property int avg: ValueLogsProxyModel.AverageDayTime + property date startTime: { + var date = new Date(); + date.setDate(new Date().getDate() - 7); + date.setHours(0) + date.setMinutes(0) + date.setSeconds(0) + return date; + } + } + } + + Graph { + Layout.fillWidth: true + Layout.fillHeight: true + mode: settings.graphStyle + color: app.accentColor + + Timer { + id: updateTimer + interval: 10 + repeat: false + onTriggered: { + graphModel.update() + } + } + + model: ValueLogsProxyModel { + id: graphModel + deviceId: root.device.id + typeIds: [stateType.id] + average: zoomTabBar.currentItem.avg + startTime: zoomTabBar.currentItem.startTime + Component.onCompleted: updateTimer.start(); + onAverageChanged: updateTimer.start() + onStartTimeChanged: updateTimer.start(); + engine: _engine + + // Live doesn't work yet with ValueLogsProxyModel + // live: true + } + } +} diff --git a/nymea-app/ui/devicepages/StateLogPage.qml b/nymea-app/ui/devicepages/StateLogPage.qml index 3da7fb25..0420b84c 100644 --- a/nymea-app/ui/devicepages/StateLogPage.qml +++ b/nymea-app/ui/devicepages/StateLogPage.qml @@ -5,7 +5,6 @@ import QtQuick.Layouts 1.1 import Nymea 1.0 import "../components" import "../customviews" -import QtCharts 2.2 Page { id: root @@ -24,7 +23,7 @@ Page { } header: GuhHeader { - text: qsTr("History") + text: qsTr("History for %1").arg(root.stateType.displayName) onBackPressed: pageStack.pop() } @@ -45,8 +44,6 @@ Page { deviceId: root.device.id typeIds: [root.stateType.id] live: true - graphSeries: lineSeries1 - viewStartTime: xAxis.min } ColumnLayout { @@ -62,9 +59,6 @@ Page { TabButton { text: qsTr("Graph") } - TabButton { - text: qsTr("Graph NG") - } } SwipeView { @@ -96,225 +90,19 @@ Page { } } - ColumnLayout { + Loader { + id: graphLoader width: swipeView.width height: swipeView.height - TabBar { - id: zoomTabBar - Layout.fillWidth: true - TabButton { - text: qsTr("6 h") - property int avg: ValueLogsProxyModel.AverageQuarterHour - property date startTime: { - var date = new Date(); - date.setHours(new Date().getHours() - 6) - date.setMinutes(0) - date.setSeconds(0) - return date; - } - } - TabButton { - text: qsTr("24 h") - property int avg: ValueLogsProxyModel.AverageHourly - property date startTime: { - var date = new Date(); - date.setHours(new Date().getHours() - 24); - date.setMinutes(0) - date.setSeconds(0) - return date; - } - } - TabButton { - text: qsTr("7 d") - property int avg: ValueLogsProxyModel.AverageDayTime - property date startTime: { - var date = new Date(); - date.setDate(new Date().getDate() - 7); - date.setHours(0) - date.setMinutes(0) - date.setSeconds(0) - return date; - } + Component.onCompleted: { + var source; + if (engine.jsonRpcClient.ensureServerVersion("1.10")) { + source = Qt.resolvedUrl("../customviews/GenericTypeGraph.qml"); + } else { + source = Qt.resolvedUrl("../customviews/GenericTypeGraphPre110.qml"); } + setSource(source, {device: root.device, stateType: root.stateType}) } - - Graph { - Layout.fillWidth: true - Layout.fillHeight: true - mode: settings.graphStyle - color: app.accentColor - - Timer { - id: updateTimer - interval: 10 - repeat: false - onTriggered: { - graphModel.update() - } - } - - model: ValueLogsProxyModel { - id: graphModel - deviceId: root.device.id - typeIds: [stateType.id] - average: zoomTabBar.currentItem.avg - startTime: zoomTabBar.currentItem.startTime - Component.onCompleted: updateTimer.start(); - onAverageChanged: updateTimer.start() - onStartTimeChanged: updateTimer.start(); - engine: _engine - - // Live doesn't work yet with ValueLogsProxyModel - // live: true - } - } - } - - - Item { - width: swipeView.width - height: swipeView.height - - ColumnLayout { - anchors.fill: parent - RowLayout { - Layout.alignment: Qt.AlignRight - HeaderButton { - imageSource: "../images/zoom-in.svg" - onClicked: { - var diff = xAxis.max.getTime() - xAxis.min.getTime() - var newTime = new Date(xAxis.min.getTime() + (diff / 4)) - xAxis.min = newTime; - } - } - HeaderButton { - imageSource: "../images/zoom-out.svg" - onClicked: { - var diff = xAxis.max.getTime() - xAxis.min.getTime() - var newTime = new Date(xAxis.min.getTime() - (diff / 4)) - xAxis.min = newTime; - } - } - } - - ChartView { - id: chartView - Layout.fillWidth: true - Layout.fillHeight: true - margins.top: 0 - margins.bottom: 0 - margins.left: 0 - margins.right: 0 - backgroundColor: Material.background - animationDuration: 300 - animationOptions: ChartView.SeriesAnimations - - ValueAxis { - id: yAxis - min: logsModelNg.minValue - max: logsModelNg.maxValue - labelsFont.pixelSize: app.smallFont - tickCount: chartView.height / 40 - } - - DateTimeAxis { - id: xAxis - gridVisible: false - tickCount: chartView.width / 70 - labelsFont.pixelSize: app.smallFont - format: { - var timeDiff = (xAxis.max.getTime() - xAxis.min.getTime()) / 1000 - if (timeDiff < 60) { // one minute - return "mm:ss" - } - if (timeDiff < 60 * 60) { // one hour - return "hh:mm" - } - if (timeDiff < 60 * 60 * 24 * 2) { // two day - return "hh:mm" - } - if (timeDiff < 60 * 60 * 24 * 7) { // one week - return "ddd hh:mm" - } - if (timeDiff < 60 * 60 * 24 * 7 * 30) { // one month - return "dd.MM." - } - return "MMM yy" - } - - min: { - var date = new Date(); - date.setHours(date.getHours() - 6); - return date; - } - max: new Date() - } - - AreaSeries { - axisX: xAxis - axisY: yAxis - name: root.stateType.displayName - borderColor: app.accentColor - borderWidth: 4 - upperSeries: LineSeries { - id: lineSeries1 - width: 4 - } - color: Qt.rgba(app.accentColor.r, app.accentColor.g, app.accentColor.b, .3) - } - - MouseArea { - anchors.fill: parent - property int lastX: 0 - property int lastY: 0 - - function scrollRightLimited(dx) { - chartView.animationOptions = ChartView.NoAnimation - var now = new Date() - // if we're already at the limit, don't even start scrolling - if (dx < 0 || xAxis.max < now) { - chartView.scrollRight(dx) - } - // figure out if we scrolled too far - var overshoot = xAxis.max.getTime() - now.getTime() - print("overshoot is:", overshoot, "oldMax", xAxis.max, "newMax", now, "oldMin", xAxis.min, "newMin", new Date(xAxis.min.getTime() - overshoot)) - if (overshoot > 0) { - var range = xAxis.max - xAxis.min - xAxis.max = now - xAxis.min = new Date(xAxis.max.getTime() - range) - } - chartView.animationOptions = ChartView.SeriesAnimations - } - - function zoomInLimited(dy) { - chartView.animationOptions = ChartView.NoAnimation - var oldMax = xAxis.max; - chartView.scrollRight(dy); - var timeDiff = xAxis.max.getTime() - oldMax.getTime() - xAxis.min = new Date(xAxis.min.getTime() - timeDiff * 2) - chartView.animationOptions = ChartView.SeriesAnimations - } - - onPressed: { - lastX = mouse.x - lastY = mouse.y - } - - onWheel: { - scrollRightLimited(-wheel.pixelDelta.x) -// zoomInLimited(wheel.pixelDelta.y) - } - - onPositionChanged: { - if (lastX !== mouse.x) { - scrollRightLimited(lastX - mouseX) - lastX = mouse.x - } - } - } - } - } - } } } From b6e46613aea738f501d31687b26757020bf3cce4 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 27 Oct 2018 00:09:35 +0200 Subject: [PATCH 05/10] more work on qtcharts, weather and sensor pages migrated --- nymea-app/resources.qrc | 5 + nymea-app/ui/NewDeviceWizard.qml | 9 + nymea-app/ui/components/HeaderButton.qml | 1 + nymea-app/ui/customviews/GenericTypeGraph.qml | 169 ++++++++++++------ nymea-app/ui/customviews/SensorChart.qml | 28 +++ nymea-app/ui/devicepages/SensorDevicePage.qml | 26 +-- .../devicepages/SensorDevicePagePost110.qml | 27 +++ .../ui/devicepages/SensorDevicePagePre110.qml | 31 ++++ .../ui/devicepages/WeatherDevicePage.qml | 36 +--- .../devicepages/WeatherDevicePagePost110.qml | 43 +++++ .../devicepages/WeatherDevicePagePre110.qml | 44 +++++ 11 files changed, 320 insertions(+), 99 deletions(-) create mode 100644 nymea-app/ui/customviews/SensorChart.qml create mode 100644 nymea-app/ui/devicepages/SensorDevicePagePost110.qml create mode 100644 nymea-app/ui/devicepages/SensorDevicePagePre110.qml create mode 100644 nymea-app/ui/devicepages/WeatherDevicePagePost110.qml create mode 100644 nymea-app/ui/devicepages/WeatherDevicePagePre110.qml diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index f855504b..ee60f6c4 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -61,8 +61,12 @@ ui/devicepages/ButtonDevicePage.qml ui/devicepages/GenericDeviceStateDetailsPage.qml ui/devicepages/GenericDevicePage.qml + ui/devicepages/WeatherDevicePagePre110.qml + ui/devicepages/WeatherDevicePagePost110.qml ui/devicepages/WeatherDevicePage.qml ui/devicepages/SensorDevicePage.qml + ui/devicepages/SensorDevicePagePre110.qml + ui/devicepages/SensorDevicePagePost110.qml ui/devicepages/DevicePageBase.qml ui/devicepages/ConfigureThingPage.qml ui/devicepages/InputTriggerDevicePage.qml @@ -129,5 +133,6 @@ ../LICENSE ui/customviews/GenericTypeGraphPre110.qml ui/customviews/GenericTypeGraph.qml + ui/customviews/SensorChart.qml diff --git a/nymea-app/ui/NewDeviceWizard.qml b/nymea-app/ui/NewDeviceWizard.qml index c4e90538..121683c7 100644 --- a/nymea-app/ui/NewDeviceWizard.qml +++ b/nymea-app/ui/NewDeviceWizard.qml @@ -36,6 +36,7 @@ Page { Connections { target: engine.deviceManager onPairDeviceReply: { + busyOverlay.shown = false switch (params["setupMethod"]) { case "SetupMethodPushButton": d.pairingTransactionId = params["pairingTransactionId"]; @@ -52,9 +53,11 @@ Page { } } onConfirmPairingReply: { + busyOverlay.shown = false internalPageStack.push(resultsPage, {success: params["deviceError"] === "DeviceErrorNoError", deviceId: params["deviceId"]}) } onAddDeviceReply: { + busyOverlay.shown = false; internalPageStack.push(resultsPage, {success: params["deviceError"] === "DeviceErrorNoError", deviceId: params["deviceId"]}) } } @@ -358,6 +361,8 @@ Page { break; } + busyOverlay.shown = true; + } } } @@ -448,4 +453,8 @@ Page { } } } + + BusyOverlay { + id: busyOverlay + } } diff --git a/nymea-app/ui/components/HeaderButton.qml b/nymea-app/ui/components/HeaderButton.qml index 74d46cdf..d38d58d3 100644 --- a/nymea-app/ui/components/HeaderButton.qml +++ b/nymea-app/ui/components/HeaderButton.qml @@ -13,6 +13,7 @@ ToolButton { id: image anchors.fill: parent anchors.margins: app.margins / 2 + opacity: enabled ? 1 : .5 } } } diff --git a/nymea-app/ui/customviews/GenericTypeGraph.qml b/nymea-app/ui/customviews/GenericTypeGraph.qml index c5eeebdc..f138505c 100644 --- a/nymea-app/ui/customviews/GenericTypeGraph.qml +++ b/nymea-app/ui/customviews/GenericTypeGraph.qml @@ -12,10 +12,14 @@ Item { property var device: null property var stateType: null + property var valueState: device.states.getState(stateType.id) readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); readonly property bool hasConnectable: deviceClass.interfaces.indexOf("connectable") >= 0 readonly property var connectedStateType: hasConnectable ? deviceClass.stateTypes.findByName("connected") : null + property color color: app.accentColor + property string iconSource: "" + LogsModelNg { id: logsModelNg engine: _engine @@ -40,54 +44,34 @@ Item { anchors.fill: parent spacing: 0 RowLayout { - Layout.alignment: Qt.AlignHCenter - HeaderButton { - imageSource: "../images/zoom-out.svg" - onClicked: { - var diff = xAxis.max.getTime() - xAxis.min.getTime() - var newTime = new Date(xAxis.min.getTime() - (diff / 4)) - xAxis.min = newTime; - } + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: app.iconSize + name: root.iconSource + visible: root.iconSource.length > 0 + color: root.color } Label { - Layout.preferredWidth: 100 - horizontalAlignment: Text.AlignHCenter - text: { - var timeDiff = (xAxis.max.getTime() - xAxis.min.getTime()) / 1000; - if (timeDiff < 60) { - return qsTr("%1 seconds").arg(Math.round(timeDiff)); - } - timeDiff = timeDiff / 60 - if (timeDiff < 60) { - return qsTr("%1 minutes").arg(Math.round(timeDiff)); - } - timeDiff = timeDiff / 60 - if (timeDiff < 48) { - return qsTr("%1 hours").arg(Math.round(timeDiff)); - } - timeDiff = timeDiff / 24; - if (timeDiff < 14) { - return qsTr("%1 days").arg(Math.round(timeDiff)); - } - timeDiff = timeDiff / 7 - if (timeDiff < 5) { - return qsTr("%1 weeks").arg(Math.round(timeDiff)); - } - timeDiff * timeDiff * 7 / 30 - if (timeDiff < 24) { - return qsTr("%1 months").arg(Math.round(timeDiff)); - } - timeDiff = timeDiff * 30 / 356 - return qsTr("%1 years").arg(Math.round(timeDiff)) + Layout.fillWidth: true + text: root.valueState.value + " " + root.stateType.unitString + font.pixelSize: app.largeFont + } + + HeaderButton { + imageSource: "../images/zoom-out.svg" + onClicked: { + var newTime = new Date(xAxis.min.getTime() - (xAxis.timeDiff / 4)) + xAxis.min = newTime; } } HeaderButton { imageSource: "../images/zoom-in.svg" + enabled: xAxis.timeDiff > (1000 * 60 * 30) onClicked: { - var diff = xAxis.max.getTime() - xAxis.min.getTime() - var newTime = new Date(xAxis.min.getTime() + (diff / 4)) + var newTime = new Date(Math.min(xAxis.min.getTime() + (xAxis.timeDiff / 4), xAxis.max.getTime() - (1000 * 60 * 30))) xAxis.min = newTime; } } @@ -102,6 +86,7 @@ Item { margins.left: 0 margins.right: 0 backgroundColor: Material.background + legend.visible: false legend.labelColor: app.foregroundColor animationDuration: 300 @@ -109,8 +94,8 @@ Item { ValueAxis { id: yAxis - min: logsModelNg.minValue - max: logsModelNg.maxValue + min: logsModelNg.minValue - logsModelNg.minValue * .01 + max: logsModelNg.maxValue + logsModelNg.maxValue * .01 labelsFont.pixelSize: app.smallFont labelsColor: app.foregroundColor tickCount: chartView.height / 40 @@ -132,30 +117,60 @@ Item { tickCount: chartView.width / 70 labelsFont.pixelSize: app.smallFont labelsColor: app.foregroundColor + property int timeDiff: xAxis.max.getTime() - xAxis.min.getTime() + + function getTimeSpanString() { + var td = timeDiff / 1000 + if (td < 60) { + return qsTr("%1 seconds").arg(Math.round(td)); + } + td = td / 60 + if (td < 60) { + return qsTr("%1 minutes").arg(Math.round(td)); + } + td = td / 60 + if (td < 48) { + return qsTr("%1 hours").arg(Math.round(td)); + } + td = td / 24; + if (td < 14) { + return qsTr("%1 days").arg(Math.round(td)); + } + td = td / 7 + if (td < 9) { + return qsTr("%1 weeks").arg(Math.round(td)); + } + td = td * 7 / 30 + if (td < 24) { + return qsTr("%1 months").arg(Math.round(td)); + } + td = td * 30 / 356 + return qsTr("%1 years").arg(Math.round(td)) + } + titleText: { if (xAxis.min.getYear() === xAxis.max.getYear() && xAxis.min.getMonth() === xAxis.max.getMonth() && xAxis.min.getDate() === xAxis.max.getDate()) { - return Qt.formatDate(xAxis.min) + return Qt.formatDate(xAxis.min) + " (" + getTimeSpanString() + ")" } - return Qt.formatDate(xAxis.min) + " - " + Qt.formatDate(xAxis.max) + return Qt.formatDate(xAxis.min) + " - " + Qt.formatDate(xAxis.max) + " (" + getTimeSpanString() + ")" } titleBrush: app.foregroundColor format: { - var timeDiff = (xAxis.max.getTime() - xAxis.min.getTime()) / 1000 - if (timeDiff < 60) { // one minute + if (timeDiff < 1000 * 60) { // one minute return "mm:ss" } - if (timeDiff < 60 * 60) { // one hour + if (timeDiff < 1000 * 60 * 60) { // one hour return "hh:mm" } - if (timeDiff < 60 * 60 * 24 * 2) { // two day + if (timeDiff < 1000 * 60 * 60 * 24 * 2) { // two day return "hh:mm" } - if (timeDiff < 60 * 60 * 24 * 7) { // one week + if (timeDiff < 1000 * 60 * 60 * 24 * 7) { // one week return "ddd hh:mm" } - if (timeDiff < 60 * 60 * 24 * 7 * 30) { // one month + if (timeDiff < 1000 * 60 * 60 * 24 * 7 * 30) { // one month return "dd.MM." } return "MMM yy" @@ -186,17 +201,62 @@ Item { } AreaSeries { + id: mainSeries axisX: xAxis axisY: yAxis name: root.stateType.displayName - borderColor: app.accentColor + borderColor: root.color borderWidth: 4 upperSeries: LineSeries { id: lineSeries1 } - color: Qt.rgba(app.accentColor.r, app.accentColor.g, app.accentColor.b, .3) + color: Qt.rgba(root.color.r, root.color.g, root.color.b, .3) + onHovered: { + markClosestPoint(point) + } + + function markClosestPoint(point) { + var found = false; + if (lineSeries1.count == 1) { + selectedHighlights.removePoints(0, selectedHighlights.count) + selectedHighlights.append(lineSeries1.at(0).x, lineSeries1.at(1).y) + return; + } + + var searchIndex = Math.floor(lineSeries1.count / 2) + var previousIndex = 0; + var nextIndex = lineSeries1.count - 1; + + while (previousIndex + 1 != nextIndex) { + if (point.x < lineSeries1.at(searchIndex).x) { + previousIndex = searchIndex; + } else if (point.x > lineSeries1.at(searchIndex).x) { + nextIndex = searchIndex; + } + searchIndex = previousIndex + Math.floor((nextIndex - previousIndex) / 2); + } + var diffToPrevious = Math.abs(point.x - lineSeries1.at(previousIndex).x) + var diffToNext = Math.abs(point.x - lineSeries1.at(nextIndex).x) + var closestPoint = diffToPrevious < diffToNext ? lineSeries1.at(previousIndex) : lineSeries1.at(nextIndex); + + selectedHighlights.removePoints(0, selectedHighlights.count) + selectedHighlights.append(closestPoint.x, closestPoint.y) + } } + ScatterSeries { + id: selectedHighlights + color: root.color + markerSize: 10 + borderWidth: 2 + borderColor: root.color + axisX: xAxis + axisY: yAxis + pointLabelsVisible: true + pointLabelsColor: app.foregroundColor + pointLabelsFont.pixelSize: app.smallFont + pointLabelsFormat: "@yPoint" + } MouseArea { anchors.fill: parent @@ -225,19 +285,20 @@ Item { chartView.animationOptions = ChartView.NoAnimation var oldMax = xAxis.max; chartView.scrollRight(dy); - var timeDiff = xAxis.max.getTime() - oldMax.getTime() - xAxis.min = new Date(xAxis.min.getTime() - timeDiff * 2) + xAxis.min = new Date(xAxis.min.getTime() - xAxis.timeDiff * 2) chartView.animationOptions = ChartView.SeriesAnimations } onPressed: { lastX = mouse.x lastY = mouse.y + var pt = chartView.mapToValue(Qt.point(mouse.x, mouse.y), mainSeries) + mainSeries.markClosestPoint(pt) } onWheel: { scrollRightLimited(-wheel.pixelDelta.x) -// zoomInLimited(wheel.pixelDelta.y) +// zoomInLimited(wheel.pixelDelta.y) } onPositionChanged: { diff --git a/nymea-app/ui/customviews/SensorChart.qml b/nymea-app/ui/customviews/SensorChart.qml new file mode 100644 index 00000000..d2cadaf8 --- /dev/null +++ b/nymea-app/ui/customviews/SensorChart.qml @@ -0,0 +1,28 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.3 +import "../components" +import Nymea 1.0 + +CustomViewBase { + id: root + implicitHeight: width * .6 + property string interfaceName + + readonly property string stateTypeName: { + switch (interfaceName) { + case "lightsensor": + return "lightIntensity"; + default: + return interfaceName.replace("sensor", ""); + } + } + GenericTypeGraph { + anchors { left: parent.left; top: parent.top; right: parent.right; bottom: parent.bottom } + device: root.device + stateType: root.deviceClass.stateTypes.findByName(root.stateTypeName) + color: app.interfaceToColor(root.interfaceName) + iconSource: app.interfaceToIcon(root.interfaceName) + } +} diff --git a/nymea-app/ui/devicepages/SensorDevicePage.qml b/nymea-app/ui/devicepages/SensorDevicePage.qml index 65998a15..e50610dc 100644 --- a/nymea-app/ui/devicepages/SensorDevicePage.qml +++ b/nymea-app/ui/devicepages/SensorDevicePage.qml @@ -8,24 +8,16 @@ import "../customviews" DevicePageBase { id: root - ListView { - anchors { fill: parent } - model: ListModel { - Component.onCompleted: { - var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor"] - for (var i = 0; i < supportedInterfaces.length; i++) { - print("checking", root.deviceClass.name, root.deviceClass.interfaces) - if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) { - append({name: supportedInterfaces[i]}); - } - } + Loader { + anchors.fill: parent + Component.onCompleted: { + var src + if (engine.jsonRpcClient.ensureServerVersion("1.10")) { + src = "SensorDevicePagePost110.qml" + } else { + src = "SensorDevicePagePre110.qml" } - } - delegate: SensorView { - width: parent.width - interfaceName: modelData - device: root.device - deviceClass: root.deviceClass + setSource(Qt.resolvedUrl(src), {device: root.device, deviceClass: root.deviceClass}) } } } diff --git a/nymea-app/ui/devicepages/SensorDevicePagePost110.qml b/nymea-app/ui/devicepages/SensorDevicePagePost110.qml new file mode 100644 index 00000000..135bef73 --- /dev/null +++ b/nymea-app/ui/devicepages/SensorDevicePagePost110.qml @@ -0,0 +1,27 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" +import "../customviews" + +ListView { + anchors { fill: parent } + model: ListModel { + Component.onCompleted: { + var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor"] + for (var i = 0; i < supportedInterfaces.length; i++) { + print("checking", root.deviceClass.name, root.deviceClass.interfaces) + if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) { + append({name: supportedInterfaces[i]}); + } + } + } + } + delegate: SensorChart { + width: parent.width + interfaceName: modelData + device: root.device + deviceClass: root.deviceClass + } +} diff --git a/nymea-app/ui/devicepages/SensorDevicePagePre110.qml b/nymea-app/ui/devicepages/SensorDevicePagePre110.qml new file mode 100644 index 00000000..3b15028f --- /dev/null +++ b/nymea-app/ui/devicepages/SensorDevicePagePre110.qml @@ -0,0 +1,31 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" +import "../customviews" + +ListView { + anchors { fill: parent } + + property var device + property var deviceClass + + model: ListModel { + Component.onCompleted: { + var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor"] + for (var i = 0; i < supportedInterfaces.length; i++) { + print("checking", root.deviceClass.name, root.deviceClass.interfaces) + if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) { + append({name: supportedInterfaces[i]}); + } + } + } + } + delegate: SensorView { + width: parent.width + interfaceName: modelData + device: root.device + deviceClass: root.deviceClass + } +} diff --git a/nymea-app/ui/devicepages/WeatherDevicePage.qml b/nymea-app/ui/devicepages/WeatherDevicePage.qml index 6a7c7543..363faf3b 100644 --- a/nymea-app/ui/devicepages/WeatherDevicePage.qml +++ b/nymea-app/ui/devicepages/WeatherDevicePage.qml @@ -8,36 +8,16 @@ import "../customviews" DevicePageBase { id: root - Flickable { + Loader { anchors.fill: parent - clip: true - contentHeight: content.implicitHeight - ColumnLayout { - id: content - width: parent.width - WeatherView { - Layout.fillWidth: true - device: root.device - deviceClass: root.deviceClass - } - SensorView { - Layout.fillWidth: true - device: root.device - deviceClass: root.deviceClass - interfaceName: "temperaturesensor" - } - SensorView { - Layout.fillWidth: true - device: root.device - deviceClass: root.deviceClass - interfaceName: "humiditysensor" - } - SensorView { - Layout.fillWidth: true - device: root.device - deviceClass: root.deviceClass - interfaceName: "pressuresensor" + Component.onCompleted: { + var src + if (engine.jsonRpcClient.ensureServerVersion("1.10")) { + src = "WeatherDevicePagePost110.qml" + } else { + src = "WeatherDevicePagePre110.qml" } + setSource(Qt.resolvedUrl(src), {device: root.device, deviceClass: root.deviceClass}) } } } diff --git a/nymea-app/ui/devicepages/WeatherDevicePagePost110.qml b/nymea-app/ui/devicepages/WeatherDevicePagePost110.qml new file mode 100644 index 00000000..04f08ca3 --- /dev/null +++ b/nymea-app/ui/devicepages/WeatherDevicePagePost110.qml @@ -0,0 +1,43 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" +import "../customviews" + +Flickable { + anchors.fill: parent + clip: true + contentHeight: content.implicitHeight + + property var device + property var deviceClass + + ColumnLayout { + id: content + width: parent.width + WeatherView { + Layout.fillWidth: true + device: root.device + deviceClass: root.deviceClass + } + SensorChart { + Layout.fillWidth: true + device: root.device + deviceClass: root.deviceClass + interfaceName: "temperaturesensor" + } + SensorChart { + Layout.fillWidth: true + device: root.device + deviceClass: root.deviceClass + interfaceName: "humiditysensor" + } + SensorChart { + Layout.fillWidth: true + device: root.device + deviceClass: root.deviceClass + interfaceName: "pressuresensor" + } + } +} diff --git a/nymea-app/ui/devicepages/WeatherDevicePagePre110.qml b/nymea-app/ui/devicepages/WeatherDevicePagePre110.qml new file mode 100644 index 00000000..3feaa8e5 --- /dev/null +++ b/nymea-app/ui/devicepages/WeatherDevicePagePre110.qml @@ -0,0 +1,44 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" +import "../customviews" + + +Flickable { + anchors.fill: parent + clip: true + contentHeight: content.implicitHeight + + property var device + property var deviceClass + + ColumnLayout { + id: content + width: parent.width + WeatherView { + Layout.fillWidth: true + device: root.device + deviceClass: root.deviceClass + } + SensorView { + Layout.fillWidth: true + device: root.device + deviceClass: root.deviceClass + interfaceName: "temperaturesensor" + } + SensorView { + Layout.fillWidth: true + device: root.device + deviceClass: root.deviceClass + interfaceName: "humiditysensor" + } + SensorView { + Layout.fillWidth: true + device: root.device + deviceClass: root.deviceClass + interfaceName: "pressuresensor" + } + } +} From a3a33595d53a85bfefc702a784e67d0f2d1abc69 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 27 Oct 2018 17:28:08 +0200 Subject: [PATCH 06/10] some fixes --- libnymea-app-core/models/logsmodelng.cpp | 10 ++++- nymea-app/ui/customviews/GenericTypeGraph.qml | 37 ++++++++++++------- nymea-app/ui/devicepages/StateLogPage.qml | 2 + 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/libnymea-app-core/models/logsmodelng.cpp b/libnymea-app-core/models/logsmodelng.cpp index cfb02fa4..11b821eb 100644 --- a/libnymea-app-core/models/logsmodelng.cpp +++ b/libnymea-app-core/models/logsmodelng.cpp @@ -226,12 +226,20 @@ void LogsModelNg::logsReply(const QVariantMap &data) } } if (m_graphSeries->count() == 0) { + // If it's the first one, make sure we add an ending point at 1 qDebug() << "Adding bool line series point:" << QDateTime::currentDateTime() << QDateTime::currentDateTime().toMSecsSinceEpoch() - 1 << (entry->value().toBool() ? 1 : 0) << "(beginning)"; - m_graphSeries->append(QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), 1));// entry->value().toBool() ? 1 : 0)); + m_graphSeries->append(QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), 1)); m_graphSeries->append(QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), entry->value().toBool() ? 1 : 0)); + } else if (i == 0) { + // Adding a new batch... remove the last appended 1 from the previous batch + m_graphSeries->remove(m_graphSeries->count() - 1); } qDebug() << "Adding bool line series point:" << entry->timestamp() << entry->timestamp().toMSecsSinceEpoch() << (entry->value().toBool() ? 1 : 0); m_graphSeries->append(QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toBool() ? 1 : 0)); + if (i == newBlock.count() - 1) { + // End the batch at 1 again + m_graphSeries->append(QPointF(entry->timestamp().addSecs(-1).toMSecsSinceEpoch(), 1)); + } } else { // if (i > 0) { // LogEntry *newerEntry = newBlock.at(i - 1); diff --git a/nymea-app/ui/customviews/GenericTypeGraph.qml b/nymea-app/ui/customviews/GenericTypeGraph.qml index f138505c..4b8c3a21 100644 --- a/nymea-app/ui/customviews/GenericTypeGraph.qml +++ b/nymea-app/ui/customviews/GenericTypeGraph.qml @@ -62,16 +62,16 @@ Item { HeaderButton { imageSource: "../images/zoom-out.svg" onClicked: { - var newTime = new Date(xAxis.min.getTime() - (xAxis.timeDiff / 4)) + var newTime = new Date(xAxis.min.getTime() - (xAxis.timeDiff * 1000 / 4)) xAxis.min = newTime; } } HeaderButton { imageSource: "../images/zoom-in.svg" - enabled: xAxis.timeDiff > (1000 * 60 * 30) + enabled: xAxis.timeDiff > (60 * 30) onClicked: { - var newTime = new Date(Math.min(xAxis.min.getTime() + (xAxis.timeDiff / 4), xAxis.max.getTime() - (1000 * 60 * 30))) + var newTime = new Date(Math.min(xAxis.min.getTime() + (xAxis.timeDiff * 1000 / 4), xAxis.max.getTime() - (1000 * 60 * 30))) xAxis.min = newTime; } } @@ -94,8 +94,8 @@ Item { ValueAxis { id: yAxis - min: logsModelNg.minValue - logsModelNg.minValue * .01 - max: logsModelNg.maxValue + logsModelNg.maxValue * .01 + min: logsModelNg.minValue - logsModelNg.minValue * .05 + max: logsModelNg.maxValue + logsModelNg.maxValue * .05 labelsFont.pixelSize: app.smallFont labelsColor: app.foregroundColor tickCount: chartView.height / 40 @@ -117,10 +117,11 @@ Item { tickCount: chartView.width / 70 labelsFont.pixelSize: app.smallFont labelsColor: app.foregroundColor - property int timeDiff: xAxis.max.getTime() - xAxis.min.getTime() + property int timeDiff: (xAxis.max.getTime() - xAxis.min.getTime()) / 1000 + onTimeDiffChanged: print("timeDiff is:", timeDiff) function getTimeSpanString() { - var td = timeDiff / 1000 + var td = timeDiff if (td < 60) { return qsTr("%1 seconds").arg(Math.round(td)); } @@ -158,19 +159,19 @@ Item { } titleBrush: app.foregroundColor format: { - if (timeDiff < 1000 * 60) { // one minute + if (timeDiff < 60) { // one minute return "mm:ss" } - if (timeDiff < 1000 * 60 * 60) { // one hour + if (timeDiff < 60 * 60) { // one hour return "hh:mm" } - if (timeDiff < 1000 * 60 * 60 * 24 * 2) { // two day + if (timeDiff < 60 * 60 * 24 * 2) { // two day return "hh:mm" } - if (timeDiff < 1000 * 60 * 60 * 24 * 7) { // one week + if (timeDiff < 60 * 60 * 24 * 7) { // one week return "ddd hh:mm" } - if (timeDiff < 1000 * 60 * 60 * 24 * 7 * 30) { // one month + if (timeDiff < 60 * 60 * 24 * 7 * 30) { // one month return "dd.MM." } return "MMM yy" @@ -198,6 +199,7 @@ Item { id: connectedLineSeries } color: "#55ff0000" + borderWidth: 0 } AreaSeries { @@ -256,8 +258,15 @@ Item { pointLabelsColor: app.foregroundColor pointLabelsFont.pixelSize: app.smallFont pointLabelsFormat: "@yPoint" + pointLabelsClipping: false } + BusyIndicator { + anchors.centerIn: parent + visible: logsModelNg.busy + } + + MouseArea { anchors.fill: parent property int lastX: 0 @@ -285,13 +294,15 @@ Item { chartView.animationOptions = ChartView.NoAnimation var oldMax = xAxis.max; chartView.scrollRight(dy); - xAxis.min = new Date(xAxis.min.getTime() - xAxis.timeDiff * 2) + xAxis.min = new Date(xAxis.min.getTime() - xAxis.timeDiff * 1000 * 2) chartView.animationOptions = ChartView.SeriesAnimations } onPressed: { lastX = mouse.x lastY = mouse.y + } + onClicked: { var pt = chartView.mapToValue(Qt.point(mouse.x, mouse.y), mainSeries) mainSeries.markClosestPoint(pt) } diff --git a/nymea-app/ui/devicepages/StateLogPage.qml b/nymea-app/ui/devicepages/StateLogPage.qml index 0420b84c..35a7b62f 100644 --- a/nymea-app/ui/devicepages/StateLogPage.qml +++ b/nymea-app/ui/devicepages/StateLogPage.qml @@ -17,6 +17,8 @@ Page { case "Int": case "Double": return true; + case "Bool": + return engine.jsonRpcClient.ensureServerVersion("1.10") } print("not showing graph for", root.stateType.type) return false; From a974096dbd7f0c3ebfd99d14e6475062f168ca9c Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 29 Oct 2018 22:41:45 +0100 Subject: [PATCH 07/10] smart-meter support done --- libnymea-app-core/models/logsmodelng.cpp | 36 ++-- nymea-app/images.qrc | 1 + nymea-app/resources.qrc | 3 +- nymea-app/ui/Nymea.qml | 22 +- nymea-app/ui/customviews/GenericTypeGraph.qml | 53 ++++- nymea-app/ui/customviews/SensorChart.qml | 28 --- .../devicepages/SensorDevicePagePost110.qml | 17 +- .../ui/devicepages/SmartMeterDevicePage.qml | 44 ++++ nymea-app/ui/images/smartmeter.svg | 202 ++++++++++++++++++ 9 files changed, 351 insertions(+), 55 deletions(-) delete mode 100644 nymea-app/ui/customviews/SensorChart.qml create mode 100644 nymea-app/ui/devicepages/SmartMeterDevicePage.qml create mode 100644 nymea-app/ui/images/smartmeter.svg diff --git a/libnymea-app-core/models/logsmodelng.cpp b/libnymea-app-core/models/logsmodelng.cpp index 11b821eb..71b8bdc1 100644 --- a/libnymea-app-core/models/logsmodelng.cpp +++ b/libnymea-app-core/models/logsmodelng.cpp @@ -163,13 +163,13 @@ void LogsModelNg::setViewStartTime(const QDateTime &viewStartTime) QVariant LogsModelNg::minValue() const { - qDebug() << "returning min value" << m_minValue; +// qDebug() << "returning min value" << m_minValue; return m_minValue; } QVariant LogsModelNg::maxValue() const { - qDebug() << "returning max value" << m_maxValue; +// qDebug() << "returning max value" << m_maxValue; return m_maxValue; } @@ -177,8 +177,6 @@ void LogsModelNg::logsReply(const QVariantMap &data) { // qDebug() << "logs reply" << data; - m_busy = false; - emit busyChanged(); int offset = data.value("params").toMap().value("offset").toInt(); int count = data.value("params").toMap().value("count").toInt(); @@ -221,20 +219,20 @@ void LogsModelNg::logsReply(const QVariantMap &data) if (i > 0) { LogEntry *newerEntry = newBlock.at(i - 1); if (newerEntry->value().toBool() != entry->value().toBool()) { - qDebug() << "Adding bool line series point:" << (newerEntry->timestamp().addSecs(-1)) << newerEntry->timestamp().addSecs(-1).toMSecsSinceEpoch() << (entry->value().toBool() ? 1 : 0) << "(correction)"; +// qDebug() << "Adding bool line series point:" << (newerEntry->timestamp().addSecs(-1)) << newerEntry->timestamp().addSecs(-1).toMSecsSinceEpoch() << (entry->value().toBool() ? 1 : 0) << "(correction)"; m_graphSeries->append(QPointF(newerEntry->timestamp().addSecs(-1).toMSecsSinceEpoch(), entry->value().toBool() ? 1 : 0)); } } if (m_graphSeries->count() == 0) { // If it's the first one, make sure we add an ending point at 1 - qDebug() << "Adding bool line series point:" << QDateTime::currentDateTime() << QDateTime::currentDateTime().toMSecsSinceEpoch() - 1 << (entry->value().toBool() ? 1 : 0) << "(beginning)"; +// qDebug() << "Adding bool line series point:" << QDateTime::currentDateTime() << QDateTime::currentDateTime().toMSecsSinceEpoch() - 1 << (entry->value().toBool() ? 1 : 0) << "(beginning)"; m_graphSeries->append(QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), 1)); m_graphSeries->append(QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), entry->value().toBool() ? 1 : 0)); } else if (i == 0) { // Adding a new batch... remove the last appended 1 from the previous batch m_graphSeries->remove(m_graphSeries->count() - 1); } - qDebug() << "Adding bool line series point:" << entry->timestamp() << entry->timestamp().toMSecsSinceEpoch() << (entry->value().toBool() ? 1 : 0); +// qDebug() << "Adding bool line series point:" << entry->timestamp() << entry->timestamp().toMSecsSinceEpoch() << (entry->value().toBool() ? 1 : 0); m_graphSeries->append(QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toBool() ? 1 : 0)); if (i == newBlock.count() - 1) { // End the batch at 1 again @@ -248,8 +246,10 @@ void LogsModelNg::logsReply(const QVariantMap &data) // m_graphSeries->append(QPointF(newerEntry->timestamp().toMSecsSinceEpoch() - 1, entry->value().toReal())); // } // } + if (m_graphSeries->count() == 0) { - m_graphSeries->insert(0, QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), entry->value().toReal())); + qDebug() << "Adding 1st line series point:" << (offset + i) << QDateTime::currentDateTime().toMSecsSinceEpoch() << entry->value().toReal(); + m_graphSeries->append(QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), entry->value().toReal())); } qDebug() << "Adding line series point:" << (offset + i) << entry->timestamp().toMSecsSinceEpoch() << (entry->value().toReal()); m_graphSeries->append(QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toReal())); @@ -264,7 +264,7 @@ void LogsModelNg::logsReply(const QVariantMap &data) } endInsertRows(); emit countChanged(); - qDebug() << "min" << m_minValue << "max" << m_maxValue << "newMin" << newMin << "newMax" << newMax; +// qDebug() << "min" << m_minValue << "max" << m_maxValue << "newMin" << newMin << "newMax" << newMax; if (m_minValue != newMin) { m_minValue = newMin; emit minValueChanged(); @@ -274,6 +274,9 @@ void LogsModelNg::logsReply(const QVariantMap &data) emit maxValueChanged(); } + m_busy = false; + emit busyChanged(); + if (m_viewStartTime.isValid() && m_list.count() > 0 && m_list.last()->timestamp() > m_viewStartTime && canFetchMore()) { fetchMore(); } @@ -282,7 +285,7 @@ void LogsModelNg::logsReply(const QVariantMap &data) void LogsModelNg::fetchMore(const QModelIndex &parent) { Q_UNUSED(parent) - qDebug() << "fetchMore called"; +// qDebug() << "fetchMore called"; if (!m_engine) { qWarning() << "Cannot update. Engine not set"; @@ -326,13 +329,13 @@ void LogsModelNg::fetchMore(const QModelIndex &parent) params.insert("offset", m_list.count()); m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply"); - qDebug() << "GetLogEntries called"; +// qDebug() << "GetLogEntries called"; } bool LogsModelNg::canFetchMore(const QModelIndex &parent) const { Q_UNUSED(parent) - qDebug() << "canFetchMore" << (m_engine && m_canFetchMore); +// qDebug() << "canFetchMore" << (m_engine && m_canFetchMore); return m_engine && m_canFetchMore; } @@ -364,6 +367,15 @@ void LogsModelNg::newLogEntryReceived(const QVariantMap &data) m_list.prepend(entry); if (m_graphSeries) { m_graphSeries->insert(0, QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toReal())); + if (m_minValue > entry->value().toReal()) { + m_minValue = entry->value().toReal(); + emit minValueChanged(); + } + if (m_maxValue < entry->value().toReal()) { + m_maxValue = entry->value().toReal(); + emit maxValueChanged(); + } + } endInsertRows(); emit countChanged(); diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc index 337433fe..37ca322f 100644 --- a/nymea-app/images.qrc +++ b/nymea-app/images.qrc @@ -150,5 +150,6 @@ ui/images/weather-app-symbolic.svg ui/images/zoom-out.svg ui/images/zoom-in.svg + ui/images/smartmeter.svg diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index ee60f6c4..21499768 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -57,6 +57,7 @@ ui/customviews/MediaControllerView.qml ui/customviews/SensorView.qml ui/customviews/NotificationsView.qml + ui/customviews/ExtendedVolumeController.qml ui/devicepages/MediaDevicePage.qml ui/devicepages/ButtonDevicePage.qml ui/devicepages/GenericDeviceStateDetailsPage.qml @@ -133,6 +134,6 @@ ../LICENSE ui/customviews/GenericTypeGraphPre110.qml ui/customviews/GenericTypeGraph.qml - ui/customviews/SensorChart.qml + ui/devicepages/SmartMeterDevicePage.qml diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index dd88c856..65116fc8 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -53,7 +53,7 @@ ApplicationWindow { rootItem.handleCloseEvent(close) } - property var supportedInterfaces: ["light", "weather", "sensor", "media", "garagegate", "awning", "shutter", "blind", "accesscontrol", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"] + property var supportedInterfaces: ["light", "weather", "sensor", "media", "garagegate", "awning", "shutter", "blind", "smartmeter", "accesscontrol", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"] function interfaceToString(name) { switch(name) { case "light": @@ -93,6 +93,8 @@ ApplicationWindow { return qsTr("Garage gates"); case "accesscontrol": return qsTr("Access control"); + case "smartmeter": + return qsTr("Smart meter"); case "uncategorized": return qsTr("Uncategorized") default: @@ -149,8 +151,6 @@ ApplicationWindow { return Qt.resolvedUrl("images/network-wired-symbolic.svg") case "notifications": return Qt.resolvedUrl("images/notification.svg") - case "connectable": - return Qt.resolvedUrl("images/stock_link.svg") case "inputtrigger": return Qt.resolvedUrl("images/attention.svg") case "outputtrigger": @@ -175,6 +175,14 @@ ApplicationWindow { return Qt.resolvedUrl("images/fingerprint.svg") case "accesscontrol": return Qt.resolvedUrl("images/network-secure.svg"); + case "smartmeter": + case "smartmeterconsumer": + case "smartmeterproducer": + case "extendedsmartmeterconsumer": + case "extendedsmartmeterproducer": + return Qt.resolvedUrl("images/smartmeter.svg") + case "connectable": + return Qt.resolvedUrl("images/stock_link.svg") default: console.warn("InterfaceToIcon: Unhandled interface", name) } @@ -187,7 +195,11 @@ ApplicationWindow { "moisturesensor":"blue", "lightsensor": "orange", "conductivitysensor": "green", - "pressuresensor": "grey" + "pressuresensor": "grey", + "smartmeterproducer": "lightgreen", + "smartmeterconsumer": "orange", + "extendedsmartmeterproducer": "blue", + "extendedsmartmeterconsumer": "blue" } function interfaceToColor(name) { @@ -244,6 +256,8 @@ ApplicationWindow { page = "NotificationsDevicePage.qml"; } else if (interfaceList.indexOf("fingerprintreader") >= 0) { page = "FingerprintReaderDevicePage.qml"; + } else if (interfaceList.indexOf("smartmeter") >= 0) { + page = "SmartMeterDevicePage.qml" } else { page = "GenericDevicePage.qml"; } diff --git a/nymea-app/ui/customviews/GenericTypeGraph.qml b/nymea-app/ui/customviews/GenericTypeGraph.qml index 4b8c3a21..964b8827 100644 --- a/nymea-app/ui/customviews/GenericTypeGraph.qml +++ b/nymea-app/ui/customviews/GenericTypeGraph.qml @@ -9,10 +9,11 @@ import QtCharts 2.2 Item { id: root + implicitHeight: width * .6 property var device: null property var stateType: null - property var valueState: device.states.getState(stateType.id) + readonly property var valueState: device.states.getState(stateType.id) readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); readonly property bool hasConnectable: deviceClass.interfaces.indexOf("connectable") >= 0 readonly property var connectedStateType: hasConnectable ? deviceClass.stateTypes.findByName("connected") : null @@ -94,8 +95,8 @@ Item { ValueAxis { id: yAxis - min: logsModelNg.minValue - logsModelNg.minValue * .05 - max: logsModelNg.maxValue + logsModelNg.maxValue * .05 + max: logsModelNg.maxValue + Math.abs(logsModelNg.maxValue * .05) + min: logsModelNg.minValue - Math.abs(logsModelNg.minValue * .05) labelsFont.pixelSize: app.smallFont labelsColor: app.foregroundColor tickCount: chartView.height / 40 @@ -179,10 +180,14 @@ Item { min: { var date = new Date(); - date.setHours(date.getHours() - 6); + date.setTime(date.getTime() - (1000 * 60 * 60 * 6) + 2000); + return date; + } + max: { + var date = new Date(); + date.setTime(date.getTime() + 2000) return date; } - max: new Date() } AreaSeries { @@ -209,8 +214,38 @@ Item { name: root.stateType.displayName borderColor: root.color borderWidth: 4 + lowerSeries: LineSeries { + id: lineSeries0 + XYPoint { x: xAxis.max.getTime(); y: 0 } + XYPoint { x: xAxis.min.getTime(); y: 0 } + } + upperSeries: LineSeries { id: lineSeries1 + onPointAdded: { + var newPoint = lineSeries1.at(index) + + if (newPoint.x > lineSeries0.at(0).x) { + lineSeries0.replace(0, newPoint.x, 0) + } + if (newPoint.x < lineSeries0.at(1).x) { + lineSeries0.replace(1, newPoint.x, 0) + } + + if (newPoint.x <= xAxis.max.getTime() || logsModelNg.busy) { + return; + } + + var diffMaxToNew = newPoint.x - xAxis.max.getTime(); + print("diffToNew is", diffMaxToNew) + if (diffMaxToNew < 1000 * 60 * 5) { + chartView.animationOptions = ChartView.NoAnimation + var newMin = xAxis.min.getTime() + diffMaxToNew; + xAxis.max = new Date(newPoint.x); + xAxis.min = new Date(newMin) + chartView.animationOptions = ChartView.SeriesAnimations + } + } } color: Qt.rgba(root.color.r, root.color.g, root.color.b, .3) onHovered: { @@ -268,9 +303,13 @@ Item { MouseArea { - anchors.fill: parent + x: chartView.plotArea.x + y: chartView.plotArea.y + width: chartView.plotArea.width + height: chartView.plotArea.height property int lastX: 0 property int lastY: 0 + preventStealing: false function scrollRightLimited(dx) { chartView.animationOptions = ChartView.NoAnimation @@ -303,7 +342,7 @@ Item { lastY = mouse.y } onClicked: { - var pt = chartView.mapToValue(Qt.point(mouse.x, mouse.y), mainSeries) + var pt = chartView.mapToValue(Qt.point(mouse.x + chartView.plotArea.x, mouse.y + chartView.plotArea.y), mainSeries) mainSeries.markClosestPoint(pt) } diff --git a/nymea-app/ui/customviews/SensorChart.qml b/nymea-app/ui/customviews/SensorChart.qml deleted file mode 100644 index d2cadaf8..00000000 --- a/nymea-app/ui/customviews/SensorChart.qml +++ /dev/null @@ -1,28 +0,0 @@ -import QtQuick 2.5 -import QtQuick.Controls 2.1 -import QtQuick.Controls.Material 2.1 -import QtQuick.Layouts 1.3 -import "../components" -import Nymea 1.0 - -CustomViewBase { - id: root - implicitHeight: width * .6 - property string interfaceName - - readonly property string stateTypeName: { - switch (interfaceName) { - case "lightsensor": - return "lightIntensity"; - default: - return interfaceName.replace("sensor", ""); - } - } - GenericTypeGraph { - anchors { left: parent.left; top: parent.top; right: parent.right; bottom: parent.bottom } - device: root.device - stateType: root.deviceClass.stateTypes.findByName(root.stateTypeName) - color: app.interfaceToColor(root.interfaceName) - iconSource: app.interfaceToIcon(root.interfaceName) - } -} diff --git a/nymea-app/ui/devicepages/SensorDevicePagePost110.qml b/nymea-app/ui/devicepages/SensorDevicePagePost110.qml index 135bef73..bd4330df 100644 --- a/nymea-app/ui/devicepages/SensorDevicePagePost110.qml +++ b/nymea-app/ui/devicepages/SensorDevicePagePost110.qml @@ -18,10 +18,21 @@ ListView { } } } - delegate: SensorChart { + delegate: GenericTypeGraph { width: parent.width - interfaceName: modelData device: root.device - deviceClass: root.deviceClass + stateType: root.deviceClass.stateTypes.findByName(stateTypeName) + color: app.interfaceToColor(modelData) + iconSource: app.interfaceToIcon(modelData) + + implicitHeight: width * .6 + property string stateTypeName: { + switch (modelData) { + case "lightsensor": + return "lightIntensity"; + default: + return modelData.replace("sensor", ""); + } + } } } diff --git a/nymea-app/ui/devicepages/SmartMeterDevicePage.qml b/nymea-app/ui/devicepages/SmartMeterDevicePage.qml new file mode 100644 index 00000000..961f7dee --- /dev/null +++ b/nymea-app/ui/devicepages/SmartMeterDevicePage.qml @@ -0,0 +1,44 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" +import "../customviews" + +DevicePageBase { + id: root + + ListView { + anchors { fill: parent } + model: ListModel { + Component.onCompleted: { + if (root.deviceClass.interfaces.indexOf("extendedsmartmeterproducer") >= 0 + || root.deviceClass.interfaces.indexOf("extendedsmartmeterconsumer") >= 0) { + append( {interface: "extendedsmartmeterproducer", stateTypeName: "currentPower" }) + } + if (root.deviceClass.interfaces.indexOf("smartmeterproducer") >= 0) { + append( {interface: "smartmeterproducer", stateTypeName: "totalEnergyProduced" }) + } + if (root.deviceClass.interfaces.indexOf("smartmeterconsumer") >= 0) { + append( {interface: "smartmeterconsumer", stateTypeName: "totalEnergyConsumed" }) + } + print("shown graphs are", count) + } + } + delegate: ColumnLayout { + width: parent.width + Label { + Layout.fillWidth: true + Layout.leftMargin: app.margins; Layout.topMargin: app.margins; Layout.rightMargin: app.rightMargins; + text: root.deviceClass.stateTypes.findByName(model.stateTypeName).displayName + } + GenericTypeGraph { + Layout.fillWidth: true + device: root.device + stateType: root.deviceClass.stateTypes.findByName(model.stateTypeName) + color: app.interfaceToColor(model.interface) + iconSource: app.interfaceToIcon(model.interface) + } + } + } +} diff --git a/nymea-app/ui/images/smartmeter.svg b/nymea-app/ui/images/smartmeter.svg new file mode 100644 index 00000000..4bd777f8 --- /dev/null +++ b/nymea-app/ui/images/smartmeter.svg @@ -0,0 +1,202 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From eb17a1ace2df5951ccbe13535fabcd8ecfac1691 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 31 Oct 2018 12:48:43 +0100 Subject: [PATCH 08/10] some fixes --- libnymea-app-core/jsonrpc/jsonrpcclient.cpp | 4 +- libnymea-app-core/models/logsmodelng.cpp | 6 +- nymea-app/resources.qrc | 1 + nymea-app/ui/appsettings/AppSettingsPage.qml | 17 --- .../SmartMeterDeviceListPage.qml | 119 ++++++++++++++++++ .../ui/mainviews/DevicesPageDelegate.qml | 7 ++ 6 files changed, 133 insertions(+), 21 deletions(-) create mode 100644 nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml diff --git a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp index 69656d5d..eddce39f 100644 --- a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp +++ b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp @@ -303,7 +303,7 @@ void JsonRpcClient::sendRequest(const QVariantMap &request) { QVariantMap newRequest = request; newRequest.insert("token", m_token); -// qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson()); + qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson()); m_connection->sendData(QJsonDocument::fromVariant(newRequest).toJson(QJsonDocument::Compact) + "\n"); } @@ -340,7 +340,7 @@ void JsonRpcClient::dataReceived(const QByteArray &data) // qWarning() << "Could not parse json data from nymea" << m_receiveBuffer.left(splitIndex) << error.errorString(); return; } -// qDebug() << "received response" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); + qDebug() << "received response" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); m_receiveBuffer = m_receiveBuffer.right(m_receiveBuffer.length() - splitIndex - 1); if (!m_receiveBuffer.isEmpty()) { staticMetaObject.invokeMethod(this, "dataReceived", Qt::QueuedConnection, Q_ARG(QByteArray, QByteArray())); diff --git a/libnymea-app-core/models/logsmodelng.cpp b/libnymea-app-core/models/logsmodelng.cpp index 71b8bdc1..af341100 100644 --- a/libnymea-app-core/models/logsmodelng.cpp +++ b/libnymea-app-core/models/logsmodelng.cpp @@ -202,6 +202,8 @@ void LogsModelNg::logsReply(const QVariantMap &data) } if (newBlock.isEmpty()) { + m_busy = false; + emit busyChanged(); return; } @@ -248,10 +250,10 @@ void LogsModelNg::logsReply(const QVariantMap &data) // } if (m_graphSeries->count() == 0) { - qDebug() << "Adding 1st line series point:" << (offset + i) << QDateTime::currentDateTime().toMSecsSinceEpoch() << entry->value().toReal(); +// qDebug() << "Adding 1st line series point:" << (offset + i) << QDateTime::currentDateTime().toMSecsSinceEpoch() << entry->value().toReal(); m_graphSeries->append(QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), entry->value().toReal())); } - qDebug() << "Adding line series point:" << (offset + i) << entry->timestamp().toMSecsSinceEpoch() << (entry->value().toReal()); +// qDebug() << "Adding line series point:" << (offset + i) << entry->timestamp().toMSecsSinceEpoch() << (entry->value().toReal()); m_graphSeries->append(QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toReal())); } } diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 21499768..e3ef4ff8 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -135,5 +135,6 @@ ui/customviews/GenericTypeGraphPre110.qml ui/customviews/GenericTypeGraph.qml ui/devicepages/SmartMeterDevicePage.qml + ui/devicelistpages/SmartMeterDeviceListPage.qml diff --git a/nymea-app/ui/appsettings/AppSettingsPage.qml b/nymea-app/ui/appsettings/AppSettingsPage.qml index b2389bc8..9d26fb33 100644 --- a/nymea-app/ui/appsettings/AppSettingsPage.qml +++ b/nymea-app/ui/appsettings/AppSettingsPage.qml @@ -81,23 +81,6 @@ Page { } } - RowLayout { - Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins - Label { - Layout.fillWidth: true - text: qsTr("Graph style") - } - RadioButton { - checked: settings.graphStyle === "bars" - text: qsTr("Bars") - onClicked: settings.graphStyle = "bars" - } - RadioButton { - checked: settings.graphStyle === "bezier" - text: qsTr("Lines") - onClicked: settings.graphStyle = "bezier" - } - } CheckDelegate { Layout.fillWidth: true text: qsTr("Return to home on idle") diff --git a/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml b/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml new file mode 100644 index 00000000..eceb262f --- /dev/null +++ b/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml @@ -0,0 +1,119 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" + +DeviceListPageBase { + id: root + + header: GuhHeader { + text: qsTr("Smart meters") + onBackPressed: pageStack.pop() + } + + ListView { + anchors.fill: parent + model: root.devicesProxy + + delegate: ItemDelegate { + id: itemDelegate + width: parent.width + + property bool inline: width > 500 + + property var device: devicesProxy.get(index); + property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + + bottomPadding: index === ListView.view.count - 1 ? topPadding : 0 + contentItem: Pane { + id: contentItem + Material.elevation: 2 + leftPadding: 0 + rightPadding: 0 + topPadding: 0 + bottomPadding: 0 + + contentItem: ItemDelegate { + leftPadding: 0 + rightPadding: 0 + topPadding: 0 + bottomPadding: 0 + contentItem: ColumnLayout { + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: app.mediumFont + app.margins + color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .05) + RowLayout { + anchors { verticalCenter: parent.verticalCenter; left: parent.left; right: parent.right; margins: app.margins } + Label { + Layout.fillWidth: true + text: model.name + elide: Text.ElideRight + } + ColorIcon { + Layout.preferredHeight: app.iconSize * .5 + Layout.preferredWidth: height + name: "../images/battery/battery-020.svg" + visible: itemDelegate.deviceClass.interfaces.indexOf("battery") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("batteryCritical").id).value === true + } + ColorIcon { + Layout.preferredHeight: app.iconSize * .5 + Layout.preferredWidth: height + name: "../images/dialog-warning-symbolic.svg" + visible: itemDelegate.deviceClass.interfaces.indexOf("connectable") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("connected").id).value === false + color: "red" + } + } + + } + GridLayout { + id: dataGrid + columns: Math.floor(contentItem.width / 120) + Layout.margins: app.margins + Repeater { + model: ListModel { + ListElement { interfaceName: "smartmeterproducer"; stateName: "totalEnergyProduced" } + ListElement { interfaceName: "smartmeterconsumer"; stateName: "totalEnergyConsumed" } + ListElement { interfaceName: "extendedsmartmeterproducer"; stateName: "currentPower" } + } + + delegate: RowLayout { + id: sensorValueDelegate + visible: itemDelegate.deviceClass.interfaces.indexOf(model.interfaceName) >= 0 + Layout.preferredWidth: contentItem.width / dataGrid.columns + + property var stateType: itemDelegate.deviceClass.stateTypes.findByName(model.stateName) + property var stateValue: stateType ? itemDelegate.device.states.getState(stateType.id) : null + + ColorIcon { + Layout.preferredHeight: app.iconSize * .8 + Layout.preferredWidth: height + Layout.alignment: Qt.AlignVCenter + color: app.interfaceToColor(model.interfaceName) + name: app.interfaceToIcon(model.interfaceName) + } + + Label { + Layout.fillWidth: true + text: sensorValueDelegate.stateValue + ? "%1 %2".arg(sensorValueDelegate.stateValue.value).arg(sensorValueDelegate.stateType.unitString) + : "" + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + font.pixelSize: app.smallFont + } + } + } + } + + } + onClicked: { + enterPage(index, false) + } + } + } + } + } +} diff --git a/nymea-app/ui/mainviews/DevicesPageDelegate.qml b/nymea-app/ui/mainviews/DevicesPageDelegate.qml index 99af5924..83b9de65 100644 --- a/nymea-app/ui/mainviews/DevicesPageDelegate.qml +++ b/nymea-app/ui/mainviews/DevicesPageDelegate.qml @@ -25,6 +25,9 @@ MainPageTile { case "light": page = "LightsDeviceListPage.qml" break; + case "smartmeter": + page ="SmartMeterDeviceListPage.qml"; + break; default: page = "GenericDeviceListPage.qml" } @@ -65,6 +68,10 @@ MainPageTile { switch (model.name) { case "sensor": case "weather": + case "smartmeterconsumer": + case "smartmeterproducer": + case "extendedsmartmeterconsumer": + case "extendedsmartmeterproducer": return labelComponent; case "light": From 8244c4dc5e55476d050c93d5e108364bc62756d7 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 31 Oct 2018 16:31:23 +0100 Subject: [PATCH 09/10] more tuning on smartmeter stuff --- libnymea-app-core/devices.cpp | 9 + libnymea-app-core/jsonrpc/jsonrpcclient.cpp | 4 +- nymea-app/images.qrc | 2 + nymea-app/nymea-app.pro | 3 + nymea-app/ruletemplates.qrc | 1 + .../ruletemplates/smartmetertemplates.json | 84 + nymea-app/ui/Nymea.qml | 18 +- nymea-app/ui/customviews/GenericTypeGraph.qml | 3 +- .../SmartMeterDeviceListPage.qml | 25 +- .../ui/devicepages/SmartMeterDevicePage.qml | 1 + nymea-app/ui/images/ev-charger.svg | 6537 +++++++++++++++++ nymea-app/ui/images/radiator.svg | 71 + 12 files changed, 6746 insertions(+), 12 deletions(-) create mode 100644 nymea-app/ruletemplates/smartmetertemplates.json create mode 100644 nymea-app/ui/images/ev-charger.svg create mode 100644 nymea-app/ui/images/radiator.svg diff --git a/libnymea-app-core/devices.cpp b/libnymea-app-core/devices.cpp index d6398282..2b41170a 100644 --- a/libnymea-app-core/devices.cpp +++ b/libnymea-app-core/devices.cpp @@ -118,6 +118,15 @@ QVariant Devices::data(const QModelIndex &index, int role) const if (interfaces.contains("notifications")) { return "notifications"; } + if (interfaces.contains("smartmeter")) { + return "smartmeter"; + } + if (interfaces.contains("heating")) { + return "heating"; + } + if (interfaces.contains("evcharger")) { + return "evcharger"; + } return "uncategorized"; } diff --git a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp index eddce39f..69656d5d 100644 --- a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp +++ b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp @@ -303,7 +303,7 @@ void JsonRpcClient::sendRequest(const QVariantMap &request) { QVariantMap newRequest = request; newRequest.insert("token", m_token); - qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson()); +// qDebug() << "Sending request" << qUtf8Printable(QJsonDocument::fromVariant(newRequest).toJson()); m_connection->sendData(QJsonDocument::fromVariant(newRequest).toJson(QJsonDocument::Compact) + "\n"); } @@ -340,7 +340,7 @@ void JsonRpcClient::dataReceived(const QByteArray &data) // qWarning() << "Could not parse json data from nymea" << m_receiveBuffer.left(splitIndex) << error.errorString(); return; } - qDebug() << "received response" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); +// qDebug() << "received response" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); m_receiveBuffer = m_receiveBuffer.right(m_receiveBuffer.length() - splitIndex - 1); if (!m_receiveBuffer.isEmpty()) { staticMetaObject.invokeMethod(this, "dataReceived", Qt::QueuedConnection, Q_ARG(QByteArray, QByteArray())); diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc index 37ca322f..eac9c6ad 100644 --- a/nymea-app/images.qrc +++ b/nymea-app/images.qrc @@ -151,5 +151,7 @@ ui/images/zoom-out.svg ui/images/zoom-in.svg ui/images/smartmeter.svg + ui/images/radiator.svg + ui/images/ev-charger.svg diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index 75da82e2..241270c3 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -124,3 +124,6 @@ BR=$$BRANDING target.path = /usr/bin INSTALLS += target +DISTFILES += \ + ruletemplates/smartmetertemplates.json + diff --git a/nymea-app/ruletemplates.qrc b/nymea-app/ruletemplates.qrc index ee47f538..24df3a96 100644 --- a/nymea-app/ruletemplates.qrc +++ b/nymea-app/ruletemplates.qrc @@ -3,5 +3,6 @@ ruletemplates/buttontemplates.json ruletemplates/notificationtemplates.json ruletemplates/accesscontroltemplates.json + ruletemplates/smartmetertemplates.json diff --git a/nymea-app/ruletemplates/smartmetertemplates.json b/nymea-app/ruletemplates/smartmetertemplates.json new file mode 100644 index 00000000..99485521 --- /dev/null +++ b/nymea-app/ruletemplates/smartmetertemplates.json @@ -0,0 +1,84 @@ +{ + "templates": [ + { + "interfaceName": "extendedsmartmeterproducer", + "description": "Charge my car while producing energy.", + "ruleNameTemplate": "Smart car charging", + "stateEvaluatorTemplate": { + "stateDescriptorTemplate": { + "interfaceName": "extendedsmartmeterproducer", + "interfaceState": "currentPower", + "operator": "ValueOperatorGreater", + "value": 0, + "selectionId": 0 + } + }, + "ruleActionTemplates": [ + { + "interfaceName": "evcharger", + "interfaceAction": "power", + "selectionId": 1, + "params": [ + { + "name": "power", + "value": true + } + ] + } + ], + "ruleExitActionTemplates": [ + { + "interfaceName": "evcharger", + "interfaceAction": "power", + "selectionId": 1, + "params": [ + { + "name": "power", + "value": false + } + ] + } + ] + }, + { + "interfaceName": "extendedsmartmeterproducer", + "description": "Turn on heating while producing energy.", + "ruleNameTemplate": "Smart heating", + "stateEvaluatorTemplate": { + "stateDescriptorTemplate": { + "interfaceName": "extendedsmartmeterproducer", + "interfaceState": "currentPower", + "operator": "ValueOperatorGreater", + "value": 0, + "selectionId": 0 + } + }, + "ruleActionTemplates": [ + { + "interfaceName": "heating", + "interfaceAction": "power", + "selectionId": 1, + "params": [ + { + "name": "power", + "value": true + } + ] + } + ], + "ruleExitActionTemplates": [ + { + "interfaceName": "heating", + "interfaceAction": "power", + "selectionId": 1, + "params": [ + { + "name": "power", + "value": false + } + ] + } + ] + } + ] +} diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index 65116fc8..2ef02af4 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -53,7 +53,7 @@ ApplicationWindow { rootItem.handleCloseEvent(close) } - property var supportedInterfaces: ["light", "weather", "sensor", "media", "garagegate", "awning", "shutter", "blind", "smartmeter", "accesscontrol", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"] + property var supportedInterfaces: ["light", "weather", "sensor", "media", "garagegate", "awning", "shutter", "blind", "heating", "smartmeter", "evcharger", "accesscontrol", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"] function interfaceToString(name) { switch(name) { case "light": @@ -94,7 +94,15 @@ ApplicationWindow { case "accesscontrol": return qsTr("Access control"); case "smartmeter": - return qsTr("Smart meter"); + case "smartmeterproducer": + case "smartmeterconsumer": + case "extendedsmartmeterproducer": + case "extendedsmartmeterconsumer": + return qsTr("Smart meters"); + case "heating": + return qsTr("Heatings"); + case "evcharger": + return qsTr("EV-chargers"); case "uncategorized": return qsTr("Uncategorized") default: @@ -181,6 +189,12 @@ ApplicationWindow { case "extendedsmartmeterconsumer": case "extendedsmartmeterproducer": return Qt.resolvedUrl("images/smartmeter.svg") + case "heating": + case "extendedheating": + return Qt.resolvedUrl("images/radiator.svg") + case "evcharger": + case "extendedevcharger": + return Qt.resolvedUrl("images/ev-charger.svg") case "connectable": return Qt.resolvedUrl("images/stock_link.svg") default: diff --git a/nymea-app/ui/customviews/GenericTypeGraph.qml b/nymea-app/ui/customviews/GenericTypeGraph.qml index 964b8827..cca5f4bc 100644 --- a/nymea-app/ui/customviews/GenericTypeGraph.qml +++ b/nymea-app/ui/customviews/GenericTypeGraph.qml @@ -13,6 +13,7 @@ Item { property var device: null property var stateType: null + property int roundTo: 2 readonly property var valueState: device.states.getState(stateType.id) readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); readonly property bool hasConnectable: deviceClass.interfaces.indexOf("connectable") >= 0 @@ -56,7 +57,7 @@ Item { Label { Layout.fillWidth: true - text: root.valueState.value + " " + root.stateType.unitString + text: 1.0 * Math.round(root.valueState.value * Math.pow(10, root.roundTo)) / Math.pow(10, root.roundTo) + " " + root.stateType.unitString font.pixelSize: app.largeFont } diff --git a/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml b/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml index eceb262f..69aca325 100644 --- a/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml @@ -74,14 +74,26 @@ DeviceListPageBase { Layout.margins: app.margins Repeater { model: ListModel { - ListElement { interfaceName: "smartmeterproducer"; stateName: "totalEnergyProduced" } - ListElement { interfaceName: "smartmeterconsumer"; stateName: "totalEnergyConsumed" } - ListElement { interfaceName: "extendedsmartmeterproducer"; stateName: "currentPower" } + Component.onCompleted: { + if (itemDelegate.deviceClass.interfaces.indexOf("smartmeterproducer") >= 0) { + append( {interfaceName: "smartmeterproducer", stateName: "totalEnergyProduced" }) + } + if (itemDelegate.deviceClass.interfaces.indexOf("smartmeterconsumer") >= 0) { + append( {interfaceName: "smartmeterconsumer", stateName: "totalEnergyConsumed" }) + } + var added = false; + if (itemDelegate.deviceClass.interfaces.indexOf("extendedsmartmeterproducer") >= 0) { + append({interfaceName: "extendedsmartmeterconsumer", stateName: "currentPower"}); + added = true; + } + if (!added && itemDelegate.deviceClass.interfaces.indexOf("extendedsmartmeterconsumer") >= 0) { + append({interfaceName: "extendedsmartmeterconsumer", stateName: "currentPower"}); + } + } } delegate: RowLayout { id: sensorValueDelegate - visible: itemDelegate.deviceClass.interfaces.indexOf(model.interfaceName) >= 0 Layout.preferredWidth: contentItem.width / dataGrid.columns property var stateType: itemDelegate.deviceClass.stateTypes.findByName(model.stateName) @@ -98,7 +110,7 @@ DeviceListPageBase { Label { Layout.fillWidth: true text: sensorValueDelegate.stateValue - ? "%1 %2".arg(sensorValueDelegate.stateValue.value).arg(sensorValueDelegate.stateType.unitString) + ? "%1 %2".arg(1.0 * Math.round(sensorValueDelegate.stateValue.value * 100000) / 100000).arg(sensorValueDelegate.stateType.unitString) : "" elide: Text.ElideRight verticalAlignment: Text.AlignVCenter @@ -106,8 +118,7 @@ DeviceListPageBase { } } } - } - + } } onClicked: { enterPage(index, false) diff --git a/nymea-app/ui/devicepages/SmartMeterDevicePage.qml b/nymea-app/ui/devicepages/SmartMeterDevicePage.qml index 961f7dee..c93a10bb 100644 --- a/nymea-app/ui/devicepages/SmartMeterDevicePage.qml +++ b/nymea-app/ui/devicepages/SmartMeterDevicePage.qml @@ -38,6 +38,7 @@ DevicePageBase { stateType: root.deviceClass.stateTypes.findByName(model.stateTypeName) color: app.interfaceToColor(model.interface) iconSource: app.interfaceToIcon(model.interface) + roundTo: 5 } } } diff --git a/nymea-app/ui/images/ev-charger.svg b/nymea-app/ui/images/ev-charger.svg new file mode 100644 index 00000000..6cd918c7 --- /dev/null +++ b/nymea-app/ui/images/ev-charger.svg @@ -0,0 +1,6537 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nymea-app/ui/images/radiator.svg b/nymea-app/ui/images/radiator.svg new file mode 100644 index 00000000..9ff0d994 --- /dev/null +++ b/nymea-app/ui/images/radiator.svg @@ -0,0 +1,71 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + From 1f07c915a161ec114e743d4d4e6ef194cad0ebb8 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Fri, 2 Nov 2018 08:38:25 +0100 Subject: [PATCH 10/10] add qtcharts to snapcraft deps --- snapcraft.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/snapcraft.yaml b/snapcraft.yaml index 32ee2c43..99f49300 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -49,6 +49,7 @@ parts: - qtbase5-dev - libavahi-client-dev - libavahi-common-dev + - libqt5charts5-dev - libqt5svg5-dev - libqt5websockets5-dev - qtconnectivity5-dev @@ -60,6 +61,7 @@ parts: - libqt5websockets5 - libqt5bluetooth5 - libgl1-mesa-dri + - qml-module-qtcharts - qml-module-qtquick2 - qml-module-qtquick-controls2 - qml-module-qtquick-layouts