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