diff --git a/libnymea-app/connection/discovery/upnpdiscovery.cpp b/libnymea-app/connection/discovery/upnpdiscovery.cpp
index 35c9a636..1de798ca 100644
--- a/libnymea-app/connection/discovery/upnpdiscovery.cpp
+++ b/libnymea-app/connection/discovery/upnpdiscovery.cpp
@@ -140,7 +140,7 @@ void UpnpDiscovery::readData()
data.resize(socket->pendingDatagramSize());
socket->readDatagram(data.data(), data.size(), &hostAddress, &port);
- qDebug() << "Received UPnP datagram:" << data;
+// qDebug() << "Received UPnP datagram:" << data;
// if the data contains the HTTP OK header...
if (data.contains("HTTP/1.1 200 OK")) {
diff --git a/libnymea-app/models/logsmodel.cpp b/libnymea-app/models/logsmodel.cpp
index 89d53d23..b4baa62a 100644
--- a/libnymea-app/models/logsmodel.cpp
+++ b/libnymea-app/models/logsmodel.cpp
@@ -322,7 +322,7 @@ void LogsModel::fetchMore(const QModelIndex &parent)
params.insert("limit", m_blockSize);
params.insert("offset", m_list.count());
- qDebug() << "Fetching logs from" << m_startTime.toString() << "to" << m_endTime.toString() << "with offset" << m_list.count() << "and limit" << m_blockSize;
+// qDebug() << "Fetching logs from" << m_startTime.toString() << "to" << m_endTime.toString() << "with offset" << m_list.count() << "and limit" << m_blockSize;
m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply");
// qDebug() << "GetLogEntries called";
diff --git a/libnymea-app/models/logsmodelng.cpp b/libnymea-app/models/logsmodelng.cpp
index 972546ab..037d0c66 100644
--- a/libnymea-app/models/logsmodelng.cpp
+++ b/libnymea-app/models/logsmodelng.cpp
@@ -419,7 +419,7 @@ void LogsModelNg::fetchMore(const QModelIndex &parent)
params.insert("limit", m_blockSize);
params.insert("offset", m_list.count());
- qDebug() << "Fetching logs:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson());
+// qDebug() << "Fetching logs:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson());
m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply");
// qDebug() << "GetLogEntries called";
diff --git a/libnymea-app/models/xyseriesadapter.cpp b/libnymea-app/models/xyseriesadapter.cpp
index 325df9d5..3727e472 100644
--- a/libnymea-app/models/xyseriesadapter.cpp
+++ b/libnymea-app/models/xyseriesadapter.cpp
@@ -46,12 +46,34 @@ void XYSeriesAdapter::setBaseSeries(QtCharts::QXYSeries *series)
connect(m_baseSeries, &QtCharts::QXYSeries::pointAdded, this, [=](int index){
if (m_series->count() > index) {
- m_series->replace(index, m_series->at(index).x(), calculateSampleValue(index));
+ qreal value = calculateSampleValue(index);
+ m_series->replace(index, m_series->at(index).x(), value);
+ if (value < m_minValue) {
+ m_minValue = value;
+ qDebug() << "New min:" << m_minValue;
+ emit minValueChanged();
+ }
+ if (value > m_maxValue) {
+ m_maxValue = value;
+ qDebug() << "New max:" << m_maxValue;
+ emit maxValueChanged();
+ }
}
});
connect(m_baseSeries, &QtCharts::QXYSeries::pointReplaced, this, [=](int index){
if (m_series->count() > index) {
- m_series->replace(index, m_series->at(index).x(), calculateSampleValue(index));
+ qreal value = calculateSampleValue(index);
+ m_series->replace(index, m_series->at(index).x(), value);
+ if (value < m_minValue) {
+ m_minValue = value;
+ qDebug() << "New min:" << m_minValue;
+ emit minValueChanged();
+ }
+ if (value > m_maxValue) {
+ m_maxValue = value;
+ qDebug() << "New max:" << m_maxValue;
+ emit maxValueChanged();
+ }
}
});
}
@@ -155,10 +177,12 @@ void XYSeriesAdapter::logEntryAdded(LogEntry *entry)
if (value < m_minValue) {
m_minValue = value;
+// qDebug() << "New min:" << m_minValue;
emit minValueChanged();
}
if (value > m_maxValue) {
m_maxValue = value;
+// qDebug() << "New max:" << m_maxValue;
emit maxValueChanged();
}
}
diff --git a/libnymea-app/types/interfaces.cpp b/libnymea-app/types/interfaces.cpp
index 8ddd2fa8..83e95886 100644
--- a/libnymea-app/types/interfaces.cpp
+++ b/libnymea-app/types/interfaces.cpp
@@ -297,6 +297,8 @@ Interfaces::Interfaces(QObject *parent) : QAbstractListModel(parent)
addInterface("wirelessconnectable", tr("Wireless devices"), {"connectable"});
addStateType("wirelessconnectable", "signalStrength", QVariant::UInt, false, tr("Signal strength"), tr("Signal strength changed"));
+ addInterface("watersensor", tr("Water sensors"), {"sensor"});
+ addStateType("watersensor", "watterDetected", QVariant::Double, false, tr("Water detected"), tr("Water detected changed"));
}
int Interfaces::rowCount(const QModelIndex &parent) const
diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc
index 9ee9ca9c..792e16ed 100644
--- a/nymea-app/images.qrc
+++ b/nymea-app/images.qrc
@@ -253,5 +253,6 @@
ui/images/media/ambeo.svg
ui/images/thermostat/cooling.svg
ui/images/thermostat/heating.svg
+ ui/images/sensors/water.svg
diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml
index 11982c8e..41494ca3 100644
--- a/nymea-app/ui/Nymea.qml
+++ b/nymea-app/ui/Nymea.qml
@@ -291,6 +291,8 @@ ApplicationWindow {
return Qt.resolvedUrl("images/sensors/closable.svg")
case "windspeedsensor":
return Qt.resolvedUrl("images/sensors/windspeed.svg")
+ case "watersensor":
+ return Qt.resolvedUrl("images/sensors/water.svg")
case "media":
case "mediacontroller":
case "mediaplayer":
@@ -390,6 +392,31 @@ ApplicationWindow {
id: styleBase
}
+ function stateColor(stateName) {
+ // Try to load color map from style
+ if (Style.stateColors[stateName]) {
+ return Style.stateColors[stateName];
+ }
+
+ if (styleBase.stateColors[stateName]) {
+ return styleBase.stateColors[stateName];
+ }
+ console.warn("stateColor(): Color not set for state", stateName)
+ return "grey";
+ }
+
+ function stateIcon(stateName) {
+ var iconMap = {
+ "currentPower": "energy.svg",
+ "totalEnergyConsumed": "smartmeter.svg",
+ "totalEnergyProduced": "smartmeter.svg",
+ }
+ if (!iconMap[stateName]) {
+ console.warn("stateIcon(): Icon not set for state", stateName)
+ }
+ return Qt.resolvedUrl("images/" + iconMap[stateName]);
+ }
+
function interfaceToColor(name) {
// Try to load color map from style
if (Style.interfaceColors[name]) {
diff --git a/nymea-app/ui/StyleBase.qml b/nymea-app/ui/StyleBase.qml
index 0c057361..ef40cfb4 100644
--- a/nymea-app/ui/StyleBase.qml
+++ b/nymea-app/ui/StyleBase.qml
@@ -35,13 +35,20 @@ Item {
"presencesensor": "darkblue",
"closablesensor": "green",
"smartmeterproducer": "lightgreen",
- "smartmeterconsumer": "orange",
- "extendedsmartmeterproducer": "blue",
- "extendedsmartmeterconsumer": "blue",
+ "extendedsmartmeterproducer": "lightgreen",
+ "smartmeterconsumer": "deepskyblue",
+ "extendedsmartmeterconsumer": "deepskyblue",
"heating" : "gainsboro",
"thermostat": "dodgerblue",
"irrigation": "lightblue",
"windspeedsensor": "blue",
- "ventilation": "lightblue"
+ "ventilation": "lightblue",
+ "watersensor": "aqua"
+ }
+
+ property var stateColors: {
+ "totalEnergyConsumed": "orange",
+ "totalEnergyProduced": "lightgreen",
+ "currentPower": "deepskyblue",
}
}
diff --git a/nymea-app/ui/components/HeaderButton.qml b/nymea-app/ui/components/HeaderButton.qml
index fe0f7cf1..92723c4d 100644
--- a/nymea-app/ui/components/HeaderButton.qml
+++ b/nymea-app/ui/components/HeaderButton.qml
@@ -36,8 +36,8 @@ ToolButton {
property alias color: image.color
contentItem: Item {
- height: 20
- width: 20
+ height: app.iconSize
+ width: app.iconSize
ColorIcon {
id: image
anchors.fill: parent
diff --git a/nymea-app/ui/components/SmartMeterChart.qml b/nymea-app/ui/components/SmartMeterChart.qml
index 1d14fee1..49b46b74 100644
--- a/nymea-app/ui/components/SmartMeterChart.qml
+++ b/nymea-app/ui/components/SmartMeterChart.qml
@@ -37,7 +37,8 @@ import Nymea 1.0
ChartView {
id: chart
- backgroundColor: Style.backgroundColor
+ backgroundColor: Style.tileBackgroundColor
+ backgroundRoundness: Style.tileRadius
theme: ChartView.ChartThemeLight
legend.labelColor: Style.foregroundColor
legend.font.pixelSize: app.smallFont
diff --git a/nymea-app/ui/customviews/GenericTypeGraph.qml b/nymea-app/ui/customviews/GenericTypeGraph.qml
index f9bb17b4..0d6240e4 100644
--- a/nymea-app/ui/customviews/GenericTypeGraph.qml
+++ b/nymea-app/ui/customviews/GenericTypeGraph.qml
@@ -44,13 +44,15 @@ Item {
property Device device: null
property StateType stateType: null
property int roundTo: 2
+ property color color: Style.accentColor
+ property string iconSource: ""
+ property alias title: titleLabel.text
+
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
- property color color: Style.accentColor
- property string iconSource: ""
LogsModelNg {
id: logsModelNg
@@ -72,11 +74,27 @@ Item {
viewStartTime: xAxis.min
}
- ColumnLayout {
+ ChartView {
+ id: chartView
anchors.fill: parent
- spacing: 0
+ margins.top: app.iconSize + app.margins
+ margins.bottom: app.margins / 2
+ margins.left: 0
+ margins.right: 0
+ backgroundColor: Style.tileBackgroundColor
+ backgroundRoundness: Style.tileRadius
+ legend.visible: false
+ legend.labelColor: Style.foregroundColor
+
+ titleColor: Style.foregroundColor
+ titleFont.pixelSize: app.largeFont
+
+ animationDuration: 300
+ animationOptions: ChartView.SeriesAnimations
+
RowLayout {
- Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
+ anchors { left: parent.left; top: parent.top; right: parent.right; topMargin: app.margins / 2; leftMargin: app.margins * 1.5; rightMargin: app.margins }
+
ColorIcon {
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: app.iconSize
@@ -84,21 +102,14 @@ Item {
visible: root.iconSource.length > 0
color: root.color
}
-
- Led {
- visible: root.stateType.type.toLowerCase() === "bool"
- state: root.valueState.value === true ? "on" : "off"
- }
-
Label {
+ id: titleLabel
Layout.fillWidth: true
text: root.stateType.type.toLowerCase() === "bool"
- ? root.stateType.displayName
- : 1.0 * Math.round(Types.toUiValue(root.valueState.value, root.stateType.unit) * Math.pow(10, root.roundTo)) / Math.pow(10, root.roundTo) + " " + Types.toUiUnit(root.stateType.unit)
+ ? root.stateType.displayName
+ : 1.0 * Math.round(Types.toUiValue(root.valueState.value, root.stateType.unit) * Math.pow(10, root.roundTo)) / Math.pow(10, root.roundTo) + " " + Types.toUiUnit(root.stateType.unit)
font.pixelSize: app.largeFont
}
-
-
HeaderButton {
imageSource: "../images/zoom-out.svg"
onClicked: {
@@ -106,7 +117,6 @@ Item {
xAxis.min = newTime;
}
}
-
HeaderButton {
imageSource: "../images/zoom-in.svg"
enabled: xAxis.timeDiff > (60 * 30)
@@ -117,333 +127,318 @@ Item {
}
}
- 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.visible: false
- legend.labelColor: Style.foregroundColor
-
- animationDuration: 300
- animationOptions: ChartView.SeriesAnimations
-
- ValueAxis {
- id: yAxis
- max: {
- switch (root.stateType.type.toLowerCase()) {
- case "bool":
- return 1;
- default:
- Math.ceil(logsModelNg.maxValue + Math.abs(logsModelNg.maxValue * .05))
- }
- }
- min: Math.floor(logsModelNg.minValue - Math.abs(logsModelNg.minValue * .05))
-// onMinChanged: applyNiceNumbers();
-// onMaxChanged: applyNiceNumbers();
- labelsFont.pixelSize: app.smallFont
- labelFormat: {
- switch (root.stateType.type.toLowerCase()) {
- case "bool":
- return "x";
- default:
- return "%d";
- }
- }
- labelsColor: Style.foregroundColor
- tickCount: root.stateType.type.toLowerCase() === "bool" ? 2 : chartView.height / 40
- color: Qt.rgba(Style.foregroundColor.r, Style.foregroundColor.g, Style.foregroundColor.b, .2)
- gridLineColor: color
- }
-
- ValueAxis {
- id: connectedAxis
- min: 0
- max: 1
- visible: false
- }
-
- DateTimeAxis {
- id: xAxis
- gridVisible: false
- color: Qt.rgba(Style.foregroundColor.r, Style.foregroundColor.g, Style.foregroundColor.b, .2)
- tickCount: chartView.width / 70
- labelsFont.pixelSize: app.smallFont
- labelsColor: Style.foregroundColor
- property int timeDiff: (xAxis.max.getTime() - xAxis.min.getTime()) / 1000
-
- function getTimeSpanString() {
- var td = Math.round(timeDiff)
- if (td < 60) {
- return qsTr("%n seconds", "", td);
- }
- td = Math.round(td / 60)
- if (td < 60) {
- return qsTr("%n minutes", "", td);
- }
- td = Math.round(td / 60)
- if (td < 48) {
- return qsTr("%n hours", "", td);
- }
- td = Math.round(td / 24);
- if (td < 14) {
- return qsTr("%n days", "", td);
- }
- td = Math.round(td / 7)
- if (td < 9) {
- return qsTr("%n weeks", "", td);
- }
- td = Math.round(td * 7 / 30)
- if (td < 24) {
- return qsTr("%n months", "", td);
- }
- td = Math.round(td * 30 / 356)
- return qsTr("%n years", "", 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) + " (" + getTimeSpanString() + ")"
- }
- return Qt.formatDate(xAxis.min) + " - " + Qt.formatDate(xAxis.max) + " (" + getTimeSpanString() + ")"
- }
- titleBrush: Style.foregroundColor
- format: {
- 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.setTime(date.getTime() - (1000 * 60 * 60 * 6) + 2000);
- return date;
- }
- max: {
- var date = new Date();
- date.setTime(date.getTime() + 2000)
- return date;
+ ValueAxis {
+ id: yAxis
+ max: {
+ switch (root.stateType.type.toLowerCase()) {
+ case "bool":
+ return 1;
+ default:
+ Math.ceil(logsModelNg.maxValue + Math.abs(logsModelNg.maxValue * .05))
}
}
-
- 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}
+ min: Math.floor(logsModelNg.minValue - Math.abs(logsModelNg.minValue * .05))
+ // onMinChanged: applyNiceNumbers();
+ // onMaxChanged: applyNiceNumbers();
+ labelsFont.pixelSize: app.smallFont
+ labelFormat: {
+ switch (root.stateType.type.toLowerCase()) {
+ case "bool":
+ return "x";
+ default:
+ return "%d";
}
+ }
+ labelsColor: Style.foregroundColor
+ tickCount: root.stateType.type.toLowerCase() === "bool" ? 2 : chartView.height / 40
+ color: Qt.rgba(Style.foregroundColor.r, Style.foregroundColor.g, Style.foregroundColor.b, .2)
+ gridLineColor: color
+ }
- lowerSeries: LineSeries {
- id: connectedLineSeries
- onPointAdded: {
- var newPoint = connectedLineSeries.at(index)
-// print("pointadded", newPoint.x, newPoint.y)
- }
+ ValueAxis {
+ id: connectedAxis
+ min: 0
+ max: 1
+ visible: false
+ }
+ DateTimeAxis {
+ id: xAxis
+ gridVisible: false
+ color: Qt.rgba(Style.foregroundColor.r, Style.foregroundColor.g, Style.foregroundColor.b, .2)
+ tickCount: chartView.width / 70
+ labelsFont.pixelSize: app.smallFont
+ labelsColor: Style.foregroundColor
+ property int timeDiff: (xAxis.max.getTime() - xAxis.min.getTime()) / 1000
+
+ function getTimeSpanString() {
+ var td = Math.round(timeDiff)
+ if (td < 60) {
+ return qsTr("%n seconds", "", td);
}
- color: "#55ff0000"
- borderWidth: 0
+ td = Math.round(td / 60)
+ if (td < 60) {
+ return qsTr("%n minutes", "", td);
+ }
+ td = Math.round(td / 60)
+ if (td < 48) {
+ return qsTr("%n hours", "", td);
+ }
+ td = Math.round(td / 24);
+ if (td < 14) {
+ return qsTr("%n days", "", td);
+ }
+ td = Math.round(td / 7)
+ if (td < 9) {
+ return qsTr("%n weeks", "", td);
+ }
+ td = Math.round(td * 7 / 30)
+ if (td < 24) {
+ return qsTr("%n months", "", td);
+ }
+ td = Math.round(td * 30 / 356)
+ return qsTr("%n years", "", td)
}
- AreaSeries {
- id: mainSeries
- axisX: xAxis
- axisY: yAxis
- 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 }
+ 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) + " (" + getTimeSpanString() + ")"
+ }
+ return Qt.formatDate(xAxis.min) + " - " + Qt.formatDate(xAxis.max) + " (" + getTimeSpanString() + ")"
+ }
+ titleBrush: Style.foregroundColor
+ format: {
+ 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.setTime(date.getTime() - (1000 * 60 * 60 * 6) + 2000);
+ return date;
+ }
+ max: {
+ var date = new Date();
+ date.setTime(date.getTime() + 2000)
+ return 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
+ onPointAdded: {
+ var newPoint = connectedLineSeries.at(index)
+ // print("pointadded", newPoint.x, newPoint.y)
}
- upperSeries: LineSeries {
- id: lineSeries1
- onPointAdded: {
- var newPoint = lineSeries1.at(index)
-// print("pointadded", newPoint.x, newPoint.y)
+ }
+ color: "#55ff0000"
+ borderWidth: 0
+ }
- 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)
- }
+ AreaSeries {
+ id: mainSeries
+ axisX: xAxis
+ axisY: yAxis
+ 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 }
+ }
- if (newPoint.x <= xAxis.max.getTime() || logsModelNg.busy) {
- return;
- }
-
- var diffMaxToNew = newPoint.x - xAxis.max.getTime();
- 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
- }
+ upperSeries: LineSeries {
+ id: lineSeries1
+ onPointAdded: {
+ var newPoint = lineSeries1.at(index)
+ // print("pointadded", newPoint.x, newPoint.y)
+ 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)
}
- }
- color: Qt.rgba(root.color.r, root.color.g, root.color.b, .3)
- onHovered: {
- markClosestPoint(point)
- }
- function markClosestPoint(point) {
- if (lineSeries1.count == 0) {
+ if (newPoint.x <= xAxis.max.getTime() || logsModelNg.busy) {
return;
}
- if (lineSeries1.count == 1) {
- selectedHighlights.removePoints(0, selectedHighlights.count)
- selectedHighlights.append(lineSeries1.at(0).x, lineSeries1.at(1).y)
- return;
+ var diffMaxToNew = newPoint.x - xAxis.max.getTime();
+ 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
}
- var searchIndex = Math.floor(lineSeries1.count / 2)
- var previousIndex = 0;
- var nextIndex = lineSeries1.count - 1;
+ }
+ }
+ color: Qt.rgba(root.color.r, root.color.g, root.color.b, .3)
+ onHovered: {
+ markClosestPoint(point)
+ }
- 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);
+ function markClosestPoint(point) {
+ if (lineSeries1.count == 0) {
+ return;
+ }
+ if (lineSeries1.count == 1) {
selectedHighlights.removePoints(0, selectedHighlights.count)
- selectedHighlights.append(closestPoint.x, closestPoint.y)
+ selectedHighlights.append(lineSeries1.at(0).x, lineSeries1.at(1).y)
+ return;
}
- }
- ScatterSeries {
- id: selectedHighlights
- color: root.color
- markerSize: 10
- borderWidth: 2
- borderColor: root.color
- axisX: xAxis
- axisY: yAxis
- pointLabelsVisible: root.stateType.type.toLowerCase() !== "bool"
- pointLabelsColor: Style.foregroundColor
- pointLabelsFont.pixelSize: app.smallFont
- pointLabelsFormat: "@yPoint"
- pointLabelsClipping: false
- }
+ var searchIndex = Math.floor(lineSeries1.count / 2)
+ var previousIndex = 0;
+ var nextIndex = lineSeries1.count - 1;
- BusyIndicator {
- anchors.centerIn: parent
- visible: logsModelNg.busy
- }
-
-
- MouseArea {
- id: scrollMouseArea
- x: chartView.plotArea.x
- y: chartView.plotArea.y
- width: chartView.plotArea.width
- height: chartView.plotArea.height
- property int lastX: 0
- property int startX: 0
- preventStealing: false
-
- property bool autoScroll: true
-
- 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)
+ while (previousIndex + 1 != nextIndex) {
+ if (point.x < lineSeries1.at(searchIndex).x) {
+ previousIndex = searchIndex;
+ } else if (point.x > lineSeries1.at(searchIndex).x) {
+ nextIndex = searchIndex;
}
- // 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)
- }
- // If the user scrolled closer than 5 pixels to the right edge, enable autoscroll
- autoScroll = overshoot > -5;
-
- chartView.animationOptions = ChartView.SeriesAnimations
+ 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);
- function zoomInLimited(dy) {
- chartView.animationOptions = ChartView.NoAnimation
- var oldMax = xAxis.max;
- chartView.scrollRight(dy);
- xAxis.min = new Date(xAxis.min.getTime() - xAxis.timeDiff * 1000 * 2)
- chartView.animationOptions = ChartView.SeriesAnimations
+ 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: root.stateType.type.toLowerCase() !== "bool"
+ pointLabelsColor: Style.foregroundColor
+ pointLabelsFont.pixelSize: app.smallFont
+ pointLabelsFormat: "@yPoint"
+ pointLabelsClipping: false
+ }
+
+ BusyIndicator {
+ anchors.centerIn: parent
+ visible: logsModelNg.busy
+ }
+
+
+ MouseArea {
+ id: scrollMouseArea
+ x: chartView.plotArea.x
+ y: chartView.plotArea.y
+ width: chartView.plotArea.width
+ height: chartView.plotArea.height
+ property int lastX: 0
+ property int startX: 0
+ preventStealing: false
+
+ property bool autoScroll: true
+
+ 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)
+ }
+ // If the user scrolled closer than 5 pixels to the right edge, enable autoscroll
+ autoScroll = overshoot > -5;
- onPressed: {
+ chartView.animationOptions = ChartView.SeriesAnimations
+ }
+
+ function zoomInLimited(dy) {
+ chartView.animationOptions = ChartView.NoAnimation
+ var oldMax = xAxis.max;
+ chartView.scrollRight(dy);
+ xAxis.min = new Date(xAxis.min.getTime() - xAxis.timeDiff * 1000 * 2)
+ chartView.animationOptions = ChartView.SeriesAnimations
+ }
+
+ onPressed: {
+ lastX = mouse.x
+ startX = mouse.x
+ }
+ onClicked: {
+ var pt = chartView.mapToValue(Qt.point(mouse.x + chartView.plotArea.x, mouse.y + chartView.plotArea.y), mainSeries)
+ mainSeries.markClosestPoint(pt)
+ }
+
+ onWheel: {
+ scrollRightLimited(-wheel.pixelDelta.x)
+ // zoomInLimited(wheel.pixelDelta.y)
+ }
+
+ onPositionChanged: {
+ if (lastX !== mouse.x) {
+ scrollRightLimited(lastX - mouseX)
lastX = mouse.x
- startX = mouse.x
- }
- onClicked: {
- var pt = chartView.mapToValue(Qt.point(mouse.x + chartView.plotArea.x, mouse.y + chartView.plotArea.y), mainSeries)
- mainSeries.markClosestPoint(pt)
}
- onWheel: {
- scrollRightLimited(-wheel.pixelDelta.x)
-// zoomInLimited(wheel.pixelDelta.y)
+ if (Math.abs(startX - mouse.x) > 10) {
+ preventStealing = true;
}
+ }
- onPositionChanged: {
- if (lastX !== mouse.x) {
- scrollRightLimited(lastX - mouseX)
- lastX = mouse.x
- }
-
- if (Math.abs(startX - mouse.x) > 10) {
- preventStealing = true;
- }
- }
-
- onReleased: preventStealing = false;
+ onReleased: preventStealing = false;
- Timer {
- running: scrollMouseArea.autoScroll
- interval: 1000
- repeat: true
- onTriggered: {
- scrollMouseArea.scrollRightLimited(10)
- }
+ Timer {
+ running: scrollMouseArea.autoScroll
+ interval: 1000
+ repeat: true
+ onTriggered: {
+ scrollMouseArea.scrollRightLimited(10)
}
}
}
}
}
+
diff --git a/nymea-app/ui/delegates/InterfaceTile.qml b/nymea-app/ui/delegates/InterfaceTile.qml
index ee04f920..fbf478fe 100644
--- a/nymea-app/ui/delegates/InterfaceTile.qml
+++ b/nymea-app/ui/delegates/InterfaceTile.qml
@@ -627,6 +627,7 @@ MainPageTile {
ListElement { ifaceName: "presencesensor"; stateName: "isPresent" }
ListElement { ifaceName: "closablesensor"; stateName: "closed" }
ListElement { ifaceName: "lightsensor"; stateName: "lightIntensity" }
+ ListElement { ifaceName: "watersensor"; stateName: "waterDetected" }
ListElement { ifaceName: "co2sensor"; stateName: "co2" }
ListElement { ifaceName: "conductivity"; stateName: "conductivity" }
ListElement { ifaceName: "noisesensor"; stateName: "noise" }
@@ -717,6 +718,5 @@ MainPageTile {
}
}
}
-
}
}
diff --git a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml
index 3261c2dc..f6443a81 100644
--- a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml
+++ b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml
@@ -85,6 +85,7 @@ DeviceListPageBase {
ListElement { interfaceName: "closablesensor"; stateName: "closed" }
ListElement { interfaceName: "heating"; stateName: "power" }
ListElement { interfaceName: "thermostat"; stateName: "targetTemperature" }
+ ListElement { interfaceName: "watersensor"; stateName: "waterDetected" }
}
delegate: RowLayout {
@@ -128,6 +129,8 @@ DeviceListPageBase {
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Presence") : qsTr("Vacant");
case "daylightsensor":
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Daytime") : qsTr("Nighttime");
+ case "watersensor":
+ return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Wet") : qsTr("Dry");
case "heating":
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("On") : qsTr("Off");
default:
@@ -144,7 +147,7 @@ DeviceListPageBase {
}
Led {
id: led
- visible: sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool" && ["presencesensor", "daylightsensor", "heating", "closablesensor"].indexOf(model.interfaceName) < 0
+ visible: sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool" && ["presencesensor", "daylightsensor", "heating", "closablesensor", "watersensor"].indexOf(model.interfaceName) < 0
state: visible && sensorValueDelegate.stateValue.value === true ? "on" : "off"
}
Item {
diff --git a/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml b/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml
index df5688af..cf0dcef3 100644
--- a/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml
+++ b/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml
@@ -103,8 +103,8 @@ DeviceListPageBase {
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: height
Layout.alignment: Qt.AlignVCenter
- color: app.interfaceToColor(model.interfaceName)
- name: app.interfaceToIcon(model.interfaceName)
+ color: app.stateColor(model.stateName)
+ name: app.stateIcon(model.stateName)
}
Label {
diff --git a/nymea-app/ui/devicepages/SensorDevicePage.qml b/nymea-app/ui/devicepages/SensorDevicePage.qml
index 224379a8..46998c90 100644
--- a/nymea-app/ui/devicepages/SensorDevicePage.qml
+++ b/nymea-app/ui/devicepages/SensorDevicePage.qml
@@ -41,18 +41,22 @@ DevicePageBase {
Flickable {
id: listView
anchors { fill: parent }
+ topMargin: app.margins / 2
interactive: contentHeight > height
contentHeight: contentGrid.implicitHeight
GridLayout {
id: contentGrid
- width: parent.width
+ width: parent.width - app.margins
+ anchors.horizontalCenter: parent.horizontalCenter
columns: Math.ceil(width / 600)
+ rowSpacing: 0
+ columnSpacing: 0
Repeater {
model: ListModel {
Component.onCompleted: {
- var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor", "noisesensor", "co2sensor", "presencesensor", "daylightsensor", "closablesensor"]
+ var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor", "noisesensor", "co2sensor", "presencesensor", "daylightsensor", "closablesensor", "watersensor"]
for (var i = 0; i < supportedInterfaces.length; i++) {
if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) {
append({name: supportedInterfaces[i]});
@@ -67,6 +71,7 @@ DevicePageBase {
Layout.preferredHeight: item.implicitHeight
property StateType stateType: root.deviceClass.stateTypes.findByName(interfaceStateMap[modelData])
+ property State state: root.thing.stateByName(interfaceStateMap[modelData])
property string interfaceName: modelData
// sourceComponent: stateType && stateType.type.toLowerCase() === "bool" ? boolComponent : graphComponent
@@ -83,7 +88,8 @@ DevicePageBase {
"co2sensor": "co2",
"presencesensor": "isPresent",
"daylightsensor": "daylight",
- "closablesensor": "closed"
+ "closablesensor": "closed",
+ "watersensor": "waterDetected"
}
}
@@ -96,12 +102,32 @@ DevicePageBase {
id: graphComponent
GenericTypeGraph {
+ id: graph
device: root.device
color: app.interfaceToColor(interfaceName)
iconSource: app.interfaceToIcon(interfaceName)
implicitHeight: width * .6
property string interfaceName: parent.interfaceName
stateType: parent.stateType
+ property State state: parent.state
+
+ Binding {
+ target: graph
+ property: "title"
+ when: ["presencesensor", "daylightsensor", "closablesensor", "watersensor"].indexOf(graph.interfaceName) >= 0
+ value: {
+ switch (graph.interfaceName) {
+ case "presencesensor":
+ return graph.state.value === true ? qsTr("Presence") : qsTr("Vacant")
+ case "daylightsensor":
+ return graph.state.value === true ? qsTr("Daytimet") : qsTr("Nighttime")
+ case "closablesensor":
+ return graph.state.value === true ? qsTr("Closed") : qsTr("Open")
+ case "watersensor":
+ return graph.state.value === true ? qsTr("Wet") : qsTr("Dry")
+ }
+ }
+ }
}
}
diff --git a/nymea-app/ui/devicepages/SmartMeterDevicePage.qml b/nymea-app/ui/devicepages/SmartMeterDevicePage.qml
index 30733fc8..73f76fb3 100644
--- a/nymea-app/ui/devicepages/SmartMeterDevicePage.qml
+++ b/nymea-app/ui/devicepages/SmartMeterDevicePage.qml
@@ -38,41 +38,67 @@ import "../customviews"
DevicePageBase {
id: root
+ readonly property State totalEnergyConsumedState: root.thing.stateByName("totalEnergyConsumed")
+ readonly property StateType totalEnergyConsumedStateType: root.thing.thingClass.stateTypes.findByName("totalEnergyConsumed")
+ readonly property State totalEnergyProducedState: root.thing.stateByName("totalEnergyProduced")
+ readonly property StateType totalEnergyProducedStateType: root.thing.thingClass.stateTypes.findByName("totalEnergyProduced")
+
Flickable {
anchors.fill: parent
+ topMargin: app.margins / 2
contentHeight: contentGrid.height
interactive: contentHeight > height
GridLayout {
id: contentGrid
- width: parent.width
- columns: Math.min(width / 300, contentModel.count)
+ width: parent.width - app.margins
+ anchors.horizontalCenter: parent.horizontalCenter
+ columns: 1
- Repeater {
- model: ListModel {
- id: contentModel
- 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)
+ BigTile {
+ Layout.preferredWidth: contentGrid.width / contentGrid.columns
+ showHeader: true
+ header: Label {
+ text: qsTr("Total energy consumption")
+ }
+
+ contentItem: RowLayout {
+ ColorIcon {
+ Layout.preferredHeight: app.iconSize
+ Layout.preferredWidth: app.iconSize
+ name: app.stateIcon("totalEnergyConsumed")
+ color: app.stateColor("totalEnergyConsumed")
+ }
+
+ Label {
+ Layout.fillWidth: true
+ text: root.totalEnergyConsumedState.value.toFixed(2) + " " + root.totalEnergyConsumedStateType.unitString
+ font.pixelSize: app.largeFont
+ }
+ ColorIcon {
+ Layout.preferredHeight: app.iconSize
+ Layout.preferredWidth: app.iconSize
+ name: app.stateIcon("totalEnergyProduced")
+ color: app.stateColor("totalEnergyProduced")
+ visible: root.totalEnergyProducedState !== null
+ }
+
+ Label {
+ Layout.fillWidth: true
+ text: root.totalEnergyProducedState.value.toFixed(2) + " " + root.totalEnergyProducedStateType.unitString
+ font.pixelSize: app.largeFont
+ visible: root.totalEnergyProducedState !== null
}
}
- delegate: GenericTypeGraph {
- Layout.preferredWidth: contentGrid.width / contentGrid.columns
- device: root.device
- stateType: root.deviceClass.stateTypes.findByName(model.stateTypeName)
- color: app.interfaceToColor(model.interface)
- iconSource: app.interfaceToIcon(model.interface)
- roundTo: 5
- }
+ }
+
+ GenericTypeGraph {
+ Layout.preferredWidth: contentGrid.width / contentGrid.columns
+ device: root.device
+ stateType: root.deviceClass.stateTypes.findByName("currentPower")
+ color: app.stateColor("currentPower")
+ iconSource: app.stateIcon("currentPower")
+ roundTo: 5
}
}
}
diff --git a/nymea-app/ui/images/sensors/water.svg b/nymea-app/ui/images/sensors/water.svg
new file mode 100644
index 00000000..fee3e80a
--- /dev/null
+++ b/nymea-app/ui/images/sensors/water.svg
@@ -0,0 +1,177 @@
+
+
diff --git a/nymea-app/ui/mainviews/EnergyView.qml b/nymea-app/ui/mainviews/EnergyView.qml
index f6d34874..168d40a7 100644
--- a/nymea-app/ui/mainviews/EnergyView.qml
+++ b/nymea-app/ui/mainviews/EnergyView.qml
@@ -79,7 +79,8 @@ MainViewBase {
legend.alignment: Qt.AlignBottom
legend.font.pixelSize: app.smallFont
legend.visible: false
- backgroundColor: Style.backgroundColor
+ backgroundColor: Style.tileBackgroundColor
+ backgroundRoundness: Style.tileRadius
titleColor: Style.foregroundColor
title: qsTr("Power usage history")
@@ -150,7 +151,7 @@ MainViewBase {
}
Component.onCompleted: {
- print("creating series")
+ print("creating series", consumer.thing.name, index)
seriesAdapter.ensureSamples(xAxis.min, xAxis.max)
var areaSeries = chartView.createSeries(ChartView.SeriesTypeArea, consumer.thing.name, xAxis, yAxis)
areaSeries.upperSeries = upperSeries;
@@ -179,8 +180,8 @@ MainViewBase {
ValueAxis {
id: yAxis
readonly property XYSeriesAdapter adapter: consumersRepeater.itemAt(consumersRepeater.count - 1).adapter;
- max: Math.ceil(adapter.maxValue + Math.abs(adapter.maxValue * .05))
- min: Math.floor(adapter.minValue - Math.abs(adapter.minValue * .05))
+ max: Math.ceil(Math.max(adapter.maxValue * 0.95, adapter.maxValue * 1.05))
+ min: Math.floor(Math.min(adapter.minValue * 0.95, adapter.minValue * 1.05))
// This seems to crash occationally
// onMinChanged: applyNiceNumbers();
// onMaxChanged: applyNiceNumbers();
@@ -354,6 +355,8 @@ MainViewBase {
SmartMeterChart {
Layout.fillWidth: true
Layout.preferredHeight: width * .7
+ backgroundColor: Style.tileBackgroundColor
+ backgroundRoundness: Style.tileRadius
meters: producers
title: qsTr("Total produced energy")
visible: producers.count > 0