From 1682405447f2a981683e7116cc8cdf04db6dc387 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 10 May 2020 23:54:36 +0200 Subject: [PATCH 1/2] Improve thing pages with graphs --- nymea-app/images.qrc | 1 + nymea-app/ui/Nymea.qml | 5 +- nymea-app/ui/customviews/GenericTypeGraph.qml | 2 +- nymea-app/ui/customviews/WeatherView.qml | 2 + .../devicepages/SensorDevicePagePost110.qml | 72 +++++--- .../ui/devicepages/SmartMeterDevicePage.qml | 64 +++---- .../devicepages/WeatherDevicePagePost110.qml | 58 +++--- nymea-app/ui/images/sensors/windspeed.svg | 165 ++++++++++++++++++ 8 files changed, 288 insertions(+), 81 deletions(-) create mode 100644 nymea-app/ui/images/sensors/windspeed.svg 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/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index 98d5ac1c..1299f131 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -222,6 +222,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": @@ -315,7 +317,8 @@ ApplicationWindow { "extendedsmartmeterconsumer": "blue", "heating" : "gainsboro", "thermostat": "dodgerblue", - "irrigation": "lightblue" + "irrigation": "lightblue", + "windspeedsensor": "blue" } function interfaceToColor(name) { 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/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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + From 81bc57aff6d6581ef0733633e9de9d491138b00e Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 27 May 2020 11:33:42 +0200 Subject: [PATCH 2/2] Add support for the ventilation interface --- libnymea-app/types/interfaces.cpp | 2 + nymea-app/resources.qrc | 1 + nymea-app/ui/Nymea.qml | 39 ++++++- .../ui/devicepages/VentilationDevicePage.qml | 104 ++++++++++++++++++ .../ui/mainviews/DevicesPageDelegate.qml | 16 +++ 5 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 nymea-app/ui/devicepages/VentilationDevicePage.qml 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/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 1299f131..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: @@ -287,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": @@ -318,7 +347,8 @@ ApplicationWindow { "heating" : "gainsboro", "thermostat": "dodgerblue", "irrigation": "lightblue", - "windspeedsensor": "blue" + "windspeedsensor": "blue", + "ventilation": "lightblue" } function interfaceToColor(name) { @@ -354,6 +384,9 @@ ApplicationWindow { case "irrigation": //: Select ... return qsTr("irrigation"); + case "ventilation": + //: Select ... + return qsTr("ventilation"); case "power": //: Select ... return qsTr("switchable thing") @@ -438,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/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/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);