diff --git a/libnymea-app/types/interfaces.cpp b/libnymea-app/types/interfaces.cpp
index 72ee9c26..a6147d45 100644
--- a/libnymea-app/types/interfaces.cpp
+++ b/libnymea-app/types/interfaces.cpp
@@ -269,6 +269,8 @@ Interfaces::Interfaces(QObject *parent) : QAbstractListModel(parent)
addInterface("thermostat", tr("Thermostats"));
addStateType("thermostat", "targetTemperature", QVariant::Double, true, tr("Target temperature"), tr("Target temperature changed"), tr("Set target temperature"));
+ addInterface("ventilation", tr("Ventilation"), {"power"});
+
addInterface("volumecontroller", tr("Speakers"));
addActionType("volumecontroller", "increaseVolume", tr("Increase volume"), new ParamTypes());
addActionType("volumecontroller", "decreaseVolume", tr("Decrease volume"), new ParamTypes());
diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc
index 9668cfce..91494793 100644
--- a/nymea-app/images.qrc
+++ b/nymea-app/images.qrc
@@ -220,5 +220,6 @@
ui/images/key.svg
ui/images/browser/MediaBrowserIconRadioParadise.svg
ui/images/io-connections.svg
+ ui/images/sensors/windspeed.svg
diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc
index 3a5d434d..9c20fd4f 100644
--- a/nymea-app/resources.qrc
+++ b/nymea-app/resources.qrc
@@ -214,5 +214,6 @@
ui/components/SettingsPageSectionHeader.qml
ui/grouping/GroupInterfacesPage.qml
ui/connection/CertificateErrorDialog.qml
+ ui/devicepages/VentilationDevicePage.qml
diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml
index 98d5ac1c..b7a75163 100644
--- a/nymea-app/ui/Nymea.qml
+++ b/nymea-app/ui/Nymea.qml
@@ -106,7 +106,32 @@ ApplicationWindow {
}
property alias _discovery: discovery
- property var supportedInterfaces: ["light", "weather", "media", "garagegate", "awning", "shutter", "blind", "powersocket", "heating", "doorbell", "sensor", "irrigation", "smartmeter", "evcharger", "fingerprintreader", "smartlock", "button", "notifications", "inputtrigger", "outputtrigger", "gateway", "account"]
+ property var supportedInterfaces: [
+ "light",
+ "weather",
+ "media",
+ "garagegate",
+ "awning",
+ "shutter",
+ "blind",
+ "powersocket",
+ "heating",
+ "doorbell",
+ "sensor",
+ "irrigation",
+ "ventilation",
+ "smartmeter",
+ "evcharger",
+ "fingerprintreader",
+ "smartlock",
+ "button",
+ "notifications",
+ "inputtrigger",
+ "outputtrigger",
+ "gateway",
+ "account"
+ ]
+
function interfaceToString(name) {
switch(name) {
case "light":
@@ -173,6 +198,8 @@ ApplicationWindow {
return qsTr("Smartlocks")
case "irrigation":
return qsTr("Irrigation");
+ case "ventilation":
+ return qsTr("Ventilation")
case "uncategorized":
return qsTr("Uncategorized")
default:
@@ -222,6 +249,8 @@ ApplicationWindow {
return Qt.resolvedUrl("images/sensors/presence.svg")
case "closablesensor":
return Qt.resolvedUrl("images/sensors/closable.svg")
+ case "windspeedsensor":
+ return Qt.resolvedUrl("images/sensors/windspeed.svg")
case "media":
case "mediacontroller":
case "mediaplayer":
@@ -285,6 +314,8 @@ ApplicationWindow {
return Qt.resolvedUrl("images/stock_link.svg")
case "irrigation":
return Qt.resolvedUrl("images/irrigation.svg")
+ case "ventilation":
+ return Qt.resolvedUrl("images/ventilation.svg")
case "power":
return Qt.resolvedUrl("images/system-shutdown.svg")
case "account":
@@ -315,7 +346,9 @@ ApplicationWindow {
"extendedsmartmeterconsumer": "blue",
"heating" : "gainsboro",
"thermostat": "dodgerblue",
- "irrigation": "lightblue"
+ "irrigation": "lightblue",
+ "windspeedsensor": "blue",
+ "ventilation": "lightblue"
}
function interfaceToColor(name) {
@@ -351,6 +384,9 @@ ApplicationWindow {
case "irrigation":
//: Select ...
return qsTr("irrigation");
+ case "ventilation":
+ //: Select ...
+ return qsTr("ventilation");
case "power":
//: Select ...
return qsTr("switchable thing")
@@ -435,6 +471,8 @@ ApplicationWindow {
page = "DoorbellDevicePage.qml";
} else if (interfaceList.indexOf("irrigation") >= 0) {
page = "IrrigationDevicePage.qml";
+ } else if (interfaceList.indexOf("ventilation") >= 0) {
+ page = "VentilationDevicePage.qml";
} else {
page = "GenericDevicePage.qml";
}
diff --git a/nymea-app/ui/customviews/GenericTypeGraph.qml b/nymea-app/ui/customviews/GenericTypeGraph.qml
index b081db59..c228cff6 100644
--- a/nymea-app/ui/customviews/GenericTypeGraph.qml
+++ b/nymea-app/ui/customviews/GenericTypeGraph.qml
@@ -350,7 +350,7 @@ Item {
borderColor: root.color
axisX: xAxis
axisY: yAxis
- pointLabelsVisible: true
+ pointLabelsVisible: root.stateType.type.toLowerCase() !== "bool"
pointLabelsColor: app.foregroundColor
pointLabelsFont.pixelSize: app.smallFont
pointLabelsFormat: "@yPoint"
diff --git a/nymea-app/ui/customviews/WeatherView.qml b/nymea-app/ui/customviews/WeatherView.qml
index e95c4646..38b89ac4 100644
--- a/nymea-app/ui/customviews/WeatherView.qml
+++ b/nymea-app/ui/customviews/WeatherView.qml
@@ -106,6 +106,7 @@ CustomViewBase {
Layout.preferredWidth: app.largeFont * 4
Layout.preferredHeight: app.largeFont * 4
name: weatherConditionState ? "../images/weathericons/weather-" + weatherConditionState.value + ".svg" : ""
+ color: "white"
}
Item {
@@ -128,6 +129,7 @@ CustomViewBase {
name: "../images/weathericons/wind.svg"
width: app.iconSize
height: width
+ color: app.interfaceToColor("windspeedsensor")
}
Label {
diff --git a/nymea-app/ui/devicepages/SensorDevicePagePost110.qml b/nymea-app/ui/devicepages/SensorDevicePagePost110.qml
index f30e316b..c0132e65 100644
--- a/nymea-app/ui/devicepages/SensorDevicePagePost110.qml
+++ b/nymea-app/ui/devicepages/SensorDevicePagePost110.qml
@@ -35,43 +35,59 @@ import Nymea 1.0
import "../components"
import "../customviews"
-ListView {
+Flickable {
id: listView
anchors { fill: parent }
interactive: contentHeight > height
- model: ListModel {
- Component.onCompleted: {
- var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor", "noisesensor", "co2sensor", "presencesensor", "daylightsensor", "closablesensor"]
- for (var i = 0; i < supportedInterfaces.length; i++) {
- if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) {
- append({name: supportedInterfaces[i]});
+ contentHeight: contentGrid.implicitHeight
+
+ GridLayout {
+ id: contentGrid
+ width: parent.width
+ columns: width / 300
+
+ Repeater {
+ model: ListModel {
+ Component.onCompleted: {
+ var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor", "noisesensor", "co2sensor", "presencesensor", "daylightsensor", "closablesensor"]
+ for (var i = 0; i < supportedInterfaces.length; i++) {
+ if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) {
+ append({name: supportedInterfaces[i]});
+ }
+ }
}
}
+
+ delegate: Loader {
+ id: loader
+ Layout.fillWidth: true
+ Layout.preferredHeight: item.implicitHeight
+
+ property StateType stateType: root.deviceClass.stateTypes.findByName(interfaceStateMap[modelData])
+ property string interfaceName: modelData
+
+// sourceComponent: stateType && stateType.type.toLowerCase() === "bool" ? boolComponent : graphComponent
+ sourceComponent: graphComponent
+
+ property var interfaceStateMap: {
+ "temperaturesensor": "temperature",
+ "humiditysensor": "humidity",
+ "pressuresensor": "pressure",
+ "moisturesensor": "moisture",
+ "lightsensor": "lightIntensity",
+ "conductivitysensor": "conductivity",
+ "noisesensor": "noise",
+ "co2sensor": "co2",
+ "presencesensor": "isPresent",
+ "daylightsensor": "daylight",
+ "closablesensor": "closed"
+ }
+ }
+
}
}
- delegate: Loader {
- id: loader
- width: parent.width
- property StateType stateType: root.deviceClass.stateTypes.findByName(interfaceStateMap[modelData])
- property string interfaceName: modelData
- sourceComponent: stateType && stateType.type.toLowerCase() === "bool" ? boolComponent : graphComponent
-
- property var interfaceStateMap: {
- "temperaturesensor": "temperature",
- "humiditysensor": "humidity",
- "pressuresensor": "pressure",
- "moisturesensor": "moisture",
- "lightsensor": "lightIntensity",
- "conductivitysensor": "conductivity",
- "noisesensor": "noise",
- "co2sensor": "co2",
- "presencesensor": "isPresent",
- "daylightsensor": "daylight",
- "closablesensor": "closed"
- }
- }
Component {
id: graphComponent
diff --git a/nymea-app/ui/devicepages/SmartMeterDevicePage.qml b/nymea-app/ui/devicepages/SmartMeterDevicePage.qml
index 884ae3a3..30733fc8 100644
--- a/nymea-app/ui/devicepages/SmartMeterDevicePage.qml
+++ b/nymea-app/ui/devicepages/SmartMeterDevicePage.qml
@@ -38,37 +38,41 @@ 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 {
+ Flickable {
+ anchors.fill: parent
+ contentHeight: contentGrid.height
+ interactive: contentHeight > height
+
+ GridLayout {
+ id: contentGrid
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)
- roundTo: 5
+ columns: Math.min(width / 300, contentModel.count)
+
+ 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)
+ }
+ }
+ 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
+ }
}
}
}
diff --git a/nymea-app/ui/devicepages/VentilationDevicePage.qml b/nymea-app/ui/devicepages/VentilationDevicePage.qml
new file mode 100644
index 00000000..63cef114
--- /dev/null
+++ b/nymea-app/ui/devicepages/VentilationDevicePage.qml
@@ -0,0 +1,104 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2020, nymea GmbH
+* Contact: contact@nymea.io
+*
+* This file is part of nymea.
+* This project including source code and documentation is protected by
+* copyright law, and remains the property of nymea GmbH. All rights, including
+* reproduction, publication, editing and translation, are reserved. The use of
+* this project is subject to the terms of a license agreement to be concluded
+* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
+* under https://nymea.io/license
+*
+* GNU General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU General Public License as published by the Free Software
+* Foundation, GNU version 3. This project is distributed in the hope that it
+* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+* Public License for more details.
+*
+* You should have received a copy of the GNU General Public License along with
+* this project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+import QtQuick 2.5
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.1
+import QtQuick.Controls.Material 2.1
+import Nymea 1.0
+import "../components"
+
+DevicePageBase {
+ id: root
+
+ readonly property var powerStateType: deviceClass.stateTypes.findByName("power")
+ readonly property var powerState: device.states.getState(powerStateType.id)
+ readonly property var powerActionType: deviceClass.actionTypes.findByName("power");
+
+ GridLayout {
+ anchors.fill: parent
+ anchors.margins: app.margins
+ columns: app.landscape ? 2 : 1
+ rowSpacing: app.margins
+ columnSpacing: app.margins
+ Layout.alignment: Qt.AlignCenter
+
+ Item {
+ Layout.preferredWidth: Math.max(app.iconSize * 6, parent.width / 5)
+ Layout.preferredHeight: width
+ Layout.topMargin: app.margins
+ Layout.bottomMargin: app.landscape ? app.margins : 0
+ Layout.alignment: Qt.AlignCenter
+ Layout.rowSpan: app.landscape ? 4 : 1
+ Layout.fillHeight: true
+
+ AbstractButton {
+ height: Math.min(parent.height, parent.width)
+ width: height
+ anchors.centerIn: parent
+ Rectangle {
+ anchors.fill: parent
+ color: "transparent"
+ border.color: root.powerState.value === true ? app.accentColor : bulbIcon.keyColor
+ border.width: 4
+ radius: width / 2
+ }
+
+ ColorIcon {
+ id: bulbIcon
+ anchors.fill: parent
+ anchors.margins: app.margins * 1.5
+ name: "../images/ventilation.svg"
+ color: root.powerState.value === true ? app.accentColor : keyColor
+
+ PropertyAnimation on rotation {
+ running: root.powerState.value === true
+ duration: 2000
+ from: 0
+ to: 360
+ loops: Animation.Infinite
+ onDurationChanged: {
+ running = false;
+ running = true;
+ }
+ }
+ }
+ onClicked: {
+ var params = []
+ var param = {}
+ param["paramTypeId"] = root.powerActionType.paramTypes.get(0).id;
+ param["value"] = !root.powerState.value;
+ params.push(param)
+ engine.deviceManager.executeAction(root.device.id, root.powerStateType.id, params);
+ }
+ }
+ }
+ }
+}
diff --git a/nymea-app/ui/devicepages/WeatherDevicePagePost110.qml b/nymea-app/ui/devicepages/WeatherDevicePagePost110.qml
index e9c794e5..a51f9238 100644
--- a/nymea-app/ui/devicepages/WeatherDevicePagePost110.qml
+++ b/nymea-app/ui/devicepages/WeatherDevicePagePost110.qml
@@ -38,39 +38,55 @@ import "../customviews"
Flickable {
anchors.fill: parent
clip: true
- contentHeight: content.implicitHeight
+ contentHeight: contentColumn.implicitHeight
property var device
property var deviceClass
ColumnLayout {
- id: content
+ id: contentColumn
width: parent.width
+
WeatherView {
Layout.fillWidth: true
device: root.device
deviceClass: root.deviceClass
}
- GenericTypeGraph {
+
+ GridLayout {
+ id: content
Layout.fillWidth: true
- device: root.device
- stateType: root.deviceClass.stateTypes.findByName("temperature")
- iconSource: app.interfaceToIcon("temperaturesensor")
- color: app.interfaceToColor("temperaturesensor")
- }
- GenericTypeGraph {
- Layout.fillWidth: true
- device: root.device
- stateType: root.deviceClass.stateTypes.findByName("humidity")
- iconSource: app.interfaceToIcon("humiditysensor")
- color: app.interfaceToColor("humiditysensor")
- }
- GenericTypeGraph {
- Layout.fillWidth: true
- device: root.device
- stateType: root.deviceClass.stateTypes.findByName("pressure")
- iconSource: app.interfaceToIcon("pressuresensor")
- color: app.interfaceToColor("pressuresensor")
+ columns: Math.min(width / 300, 4)
+
+ GenericTypeGraph {
+ Layout.fillWidth: true
+ device: root.device
+ stateType: root.deviceClass.stateTypes.findByName("temperature")
+ iconSource: app.interfaceToIcon("temperaturesensor")
+ color: app.interfaceToColor("temperaturesensor")
+ }
+ GenericTypeGraph {
+ Layout.fillWidth: true
+ device: root.device
+ stateType: root.deviceClass.stateTypes.findByName("humidity")
+ iconSource: app.interfaceToIcon("humiditysensor")
+ color: app.interfaceToColor("humiditysensor")
+ }
+ GenericTypeGraph {
+ Layout.fillWidth: true
+ device: root.device
+ stateType: root.deviceClass.stateTypes.findByName("pressure")
+ iconSource: app.interfaceToIcon("pressuresensor")
+ color: app.interfaceToColor("pressuresensor")
+ }
+ GenericTypeGraph {
+ Layout.fillWidth: true
+ device: root.device
+ stateType: root.deviceClass.stateTypes.findByName("windSpeed")
+ iconSource: app.interfaceToIcon("windspeedsensor")
+ color: app.interfaceToColor("windspeedsensor")
+ }
}
}
+
}
diff --git a/nymea-app/ui/images/sensors/windspeed.svg b/nymea-app/ui/images/sensors/windspeed.svg
new file mode 100644
index 00000000..e5661abc
--- /dev/null
+++ b/nymea-app/ui/images/sensors/windspeed.svg
@@ -0,0 +1,165 @@
+
+
+
+
diff --git a/nymea-app/ui/mainviews/DevicesPageDelegate.qml b/nymea-app/ui/mainviews/DevicesPageDelegate.qml
index b8b4c7b1..f051b645 100644
--- a/nymea-app/ui/mainviews/DevicesPageDelegate.qml
+++ b/nymea-app/ui/mainviews/DevicesPageDelegate.qml
@@ -150,6 +150,8 @@ MainPageTile {
case "awning":
case "extendedawning":
case "powersocket":
+ case "irrigation":
+ case "ventilation":
return buttonComponent
case "media":
return mediaControlComponent
@@ -239,6 +241,8 @@ MainPageTile {
case "media":
return devicesProxy.get(0).name;
case "light":
+ case "irrigation":
+ case "ventilation":
case "powersocket":
var count = 0;
for (var i = 0; i < devicesProxy.count; i++) {
@@ -294,6 +298,8 @@ MainPageTile {
switch (iface.name) {
case "media":
case "light":
+ case "irrigation":
+ case "ventilation":
return ""
case "garagegate":
case "blind":
@@ -314,6 +320,8 @@ MainPageTile {
switch (iface.name) {
case "light":
case "media":
+ case "irrigation":
+ case "ventilation":
break;
case "garagegate":
case "shutter":
@@ -350,6 +358,8 @@ MainPageTile {
switch (iface.name) {
case "media":
case "light":
+ case "irrigation":
+ case "ventilation":
return ""
case "garagegate":
case "blind":
@@ -370,6 +380,8 @@ MainPageTile {
switch (iface.name) {
case "light":
case "media":
+ case "irrigation":
+ case "ventilation":
break;
case "garagegate":
case "shutter":
@@ -414,6 +426,8 @@ MainPageTile {
""
case "light":
case "powersocket":
+ case "irrigation":
+ case "ventilation":
return "../images/system-shutdown.svg"
case "garagegate":
case "blind":
@@ -433,6 +447,8 @@ MainPageTile {
switch (iface.name) {
case "light":
case "powersocket":
+ case "irrigation":
+ case "ventilation":
var allOff = true;
for (var i = 0; i < devicesProxy.count; i++) {
var device = devicesProxy.get(i);