diff --git a/libnymea-app/types/interfaces.cpp b/libnymea-app/types/interfaces.cpp
index 9b287486..a9efdec5 100644
--- a/libnymea-app/types/interfaces.cpp
+++ b/libnymea-app/types/interfaces.cpp
@@ -316,7 +316,10 @@ Interfaces::Interfaces(QObject *parent) : QAbstractListModel(parent)
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"));
+ addStateType("watersensor", "waterDetected", QVariant::Double, false, tr("Water detected"), tr("Water detected changed"));
+
+ addInterface("firesensor", tr("Fire sensors"), {"sensor"});
+ addStateType("firesensor", "fireDetected", QVariant::Double, false, tr("Fire detected"), tr("Fire detected changed"));
}
int Interfaces::rowCount(const QModelIndex &parent) const
diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc
index 83ffcd02..56d4b97f 100644
--- a/nymea-app/images.qrc
+++ b/nymea-app/images.qrc
@@ -273,5 +273,6 @@
ui/images/contact-group.svg
ui/images/zigbee/TI.svg
ui/images/car.svg
+ ui/images/sensors/fire.svg
diff --git a/nymea-app/ui/MainPage.qml b/nymea-app/ui/MainPage.qml
index 33ed2d8c..acd17b56 100644
--- a/nymea-app/ui/MainPage.qml
+++ b/nymea-app/ui/MainPage.qml
@@ -91,6 +91,7 @@ Page {
width: visible ? implicitWidth : 0
HeaderButton {
+ id: button
imageSource: "../images/system-update.svg"
color: Style.accentColor
visible: updatesModel.count > 0 || engine.systemController.updateRunning
@@ -101,7 +102,7 @@ Page {
duration: 2000
loops: Animation.Infinite
running: engine.systemController.updateRunning
- onStopped: icon.rotation = 0;
+ onStopped: button.rotation = 0;
}
PackagesFilterModel {
id: updatesModel
diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml
index 01cb1805..af7eadec 100644
--- a/nymea-app/ui/Nymea.qml
+++ b/nymea-app/ui/Nymea.qml
@@ -311,6 +311,8 @@ ApplicationWindow {
return Qt.resolvedUrl("images/sensors/water.svg")
case "waterlevelsensor":
return Qt.resolvedUrl("images/sensors/water.svg")
+ case "firesensor":
+ return Qt.resolvedUrl("images/sensors/fire.svg")
case "o2sensor":
return Qt.resolvedUrl("images/sensors/o2.svg")
case "phsensor":
@@ -450,14 +452,11 @@ ApplicationWindow {
function interfaceToColor(name) {
// Try to load color map from style
- print("checking color for name", name)
if (Style.interfaceColors[name]) {
- print("have color for name", name, Style.interfaceColors[name])
return Style.interfaceColors[name];
}
if (styleBase.interfaceColors[name]) {
- print("have color for name", name, styleBase.interfaceColors[name])
return styleBase.interfaceColors[name];
}
diff --git a/nymea-app/ui/StyleBase.qml b/nymea-app/ui/StyleBase.qml
index fb4c9271..a425bffa 100644
--- a/nymea-app/ui/StyleBase.qml
+++ b/nymea-app/ui/StyleBase.qml
@@ -111,6 +111,7 @@ Item {
"ventilation": lightBlue,
"watersensor": lightBlue,
"waterlevelsensor": lightBlue,
+ "firesensor": red,
"phsensor": green,
"o2sensor": lightBlue,
"orpsensor": yellow,
diff --git a/nymea-app/ui/components/CircleBackground.qml b/nymea-app/ui/components/CircleBackground.qml
index b5a467f1..2722800d 100644
--- a/nymea-app/ui/components/CircleBackground.qml
+++ b/nymea-app/ui/components/CircleBackground.qml
@@ -44,6 +44,11 @@ Item {
property bool on: false
property alias showOnGradient: opacityMask.visible
+ property bool showProgress: false
+ property double progressFrom: 0
+ property double progressTo: 100
+ property double progress: 50
+
readonly property Item contentItem: background
signal clicked()
@@ -95,7 +100,6 @@ Item {
source: gradient
maskSource: mask
Behavior on opacity { NumberAnimation { duration: Style.animationDuration } }
-
}
Item {
diff --git a/nymea-app/ui/customviews/ThermostatController.qml b/nymea-app/ui/customviews/ThermostatController.qml
index 5aa6dbdc..15b34f2e 100644
--- a/nymea-app/ui/customviews/ThermostatController.qml
+++ b/nymea-app/ui/customviews/ThermostatController.qml
@@ -134,22 +134,22 @@ Item {
}
ctx.beginPath();
- ctx.font = "" + app.hugeFont + "px " + Style.fontFamily;
+ ctx.font = "" + Style.hugeFont.pixelSize + "px " + Style.fontFamily;
ctx.fillStyle = Style.foregroundColor;
var roundedTargetTemp = Types.toUiValue(currentValue, root.targetTemperatureStateType.unit)
roundedTargetTemp = roundToPrecision(roundedTargetTemp).toFixed(1) + "°"
var size = ctx.measureText(roundedTargetTemp)
- ctx.text(roundedTargetTemp, center.x - size.width / 2, center.y + app.hugeFont / 2);
+ ctx.text(roundedTargetTemp, center.x - size.width / 2, center.y + Style.hugeFont.pixelSize / 2);
ctx.fill();
ctx.closePath();
if (root.temperatureState) {
ctx.beginPath();
- ctx.font = "" + app.largeFont + "px " + Style.fontFamily;
+ ctx.font = "" + Style.bigFont.pixelSize + "px " + Style.fontFamily;
var roundedTemp = Types.toUiValue(root.temperatureState.value, root.temperatureStateType.unit)
roundedTemp = roundToPrecision(roundedTemp) + "°"
size = ctx.measureText(roundedTemp)
- ctx.text(roundedTemp, center.x - size.width / 2, center.y + app.hugeFont + app.margins);
+ ctx.text(roundedTemp, center.x - size.width / 2, center.y + Style.hugeFont.pixelSize + Style.margins);
ctx.fill();
ctx.closePath();
}
diff --git a/nymea-app/ui/delegates/InterfaceTile.qml b/nymea-app/ui/delegates/InterfaceTile.qml
index 1982f4ec..3a988f31 100644
--- a/nymea-app/ui/delegates/InterfaceTile.qml
+++ b/nymea-app/ui/delegates/InterfaceTile.qml
@@ -318,6 +318,7 @@ MainPageTile {
ListElement { ifaceName: "lightsensor"; stateName: "lightIntensity" }
ListElement { ifaceName: "watersensor"; stateName: "waterDetected" }
ListElement { ifaceName: "waterlevelsensor"; stateName: "waterLevel" }
+ ListElement { ifaceName: "firesensor"; stateName: "fireDetected" }
ListElement { ifaceName: "cosensor"; stateName: "co" }
ListElement { ifaceName: "co2sensor"; stateName: "co2" }
ListElement { ifaceName: "gassensor"; stateName: "gas" }
diff --git a/nymea-app/ui/delegates/ThingTile.qml b/nymea-app/ui/delegates/ThingTile.qml
index 0d759e2d..22e806fd 100644
--- a/nymea-app/ui/delegates/ThingTile.qml
+++ b/nymea-app/ui/delegates/ThingTile.qml
@@ -274,6 +274,9 @@ MainPageTile {
if (thing.thingClass.interfaces.indexOf("waterlevelsensor") >= 0) {
tmp.push({iface: "waterlevelsensor", state: "waterLevel"})
}
+ if (thing.thingClass.interfaces.indexOf("firesensor") >= 0) {
+ tmp.push({iface: "firesensor", state: "fireDetected"})
+ }
if (thing.thingClass.interfaces.indexOf("weather") >= 0) {
tmp.push({iface: "temperaturesensor", state: "temperature"});
diff --git a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml
index 42a11d24..8109035c 100644
--- a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml
+++ b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml
@@ -90,6 +90,7 @@ ThingsListPageBase {
ListElement { interfaceName: "thermostat"; stateName: "targetTemperature" }
ListElement { interfaceName: "watersensor"; stateName: "waterDetected" }
ListElement { interfaceName: "waterlevelsensor"; stateName: "waterLevel" }
+ ListElement { interfaceName: "firesensor"; stateName: "fireDetected" }
ListElement { interfaceName: "o2sensor"; stateName: "o2saturation" }
ListElement { interfaceName: "phsensor"; stateName: "ph" }
ListElement { interfaceName: "orpsensor"; stateName: "orp" }
@@ -111,6 +112,8 @@ ThingsListPageBase {
switch (model.interfaceName) {
case "closablesensor":
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? Style.green : Style.red;
+ case "firesensor":
+ return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? Style.red : Style.iconColor;
default:
return app.interfaceToColor(model.interfaceName)
}
@@ -138,6 +141,8 @@ ThingsListPageBase {
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 "firesensor":
+ return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Fire") : qsTr("No fire");
case "heating":
return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("On") : qsTr("Off");
default:
@@ -154,7 +159,7 @@ ThingsListPageBase {
}
Led {
id: led
- visible: sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool" && ["presencesensor", "daylightsensor", "heating", "closablesensor", "watersensor"].indexOf(model.interfaceName) < 0
+ visible: sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool" && ["presencesensor", "daylightsensor", "heating", "closablesensor", "watersensor", "firesensor"].indexOf(model.interfaceName) < 0
state: visible && sensorValueDelegate.stateValue.value === true ? "on" : "off"
}
Item {
diff --git a/nymea-app/ui/devicepages/SensorDevicePage.qml b/nymea-app/ui/devicepages/SensorDevicePage.qml
index a6a0858e..84b8672a 100644
--- a/nymea-app/ui/devicepages/SensorDevicePage.qml
+++ b/nymea-app/ui/devicepages/SensorDevicePage.qml
@@ -38,6 +38,42 @@ import "../customviews"
ThingPageBase {
id: root
+ property var interfaceStateMap: {
+ "temperaturesensor": "temperature",
+ "humiditysensor": "humidity",
+ "pressuresensor": "pressure",
+ "moisturesensor": "moisture",
+ "lightsensor": "lightIntensity",
+ "conductivitysensor": "conductivity",
+ "noisesensor": "noise",
+ "cosensor": "co",
+ "co2sensor": "co2",
+ "gassensor": "gas",
+ "presencesensor": "isPresent",
+ "daylightsensor": "daylight",
+ "closablesensor": "closed",
+ "watersensor": "waterDetected",
+ "firesensor": "fireDetected",
+ "waterlevelsensor": "waterLevel",
+ "phsensor": "ph",
+ "o2sensor": "o2saturation",
+ "orpsensor": "orp",
+ "airquality": "airQuality",
+ "indoorairquality": "indoorAirQuality"
+ }
+
+ ListModel {
+ id: sensorsModel
+ Component.onCompleted: {
+ var supportedInterfaces = Object.keys(interfaceStateMap)
+ for (var i = 0; i < supportedInterfaces.length; i++) {
+ if (root.thingClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) {
+ append({name: supportedInterfaces[i]});
+ }
+ }
+ }
+ }
+
Flickable {
id: listView
anchors { fill: parent }
@@ -45,65 +81,129 @@ ThingPageBase {
interactive: contentHeight > height
contentHeight: contentGrid.implicitHeight
- GridLayout {
+ ColumnLayout {
id: contentGrid
- width: parent.width - app.margins
- anchors.horizontalCenter: parent.horizontalCenter
- columns: Math.ceil(width / 600)
- rowSpacing: 0
- columnSpacing: 0
+ width: parent.width
+ spacing: Style.margins
- Repeater {
- model: ListModel {
- Component.onCompleted: {
- var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor", "noisesensor", "cosensor", "co2sensor", "gassensor", "presencesensor", "daylightsensor", "closablesensor", "watersensor", "phsensor", "o2sensor", "orpsensor", "waterlevelsensor"]
- for (var i = 0; i < supportedInterfaces.length; i++) {
- if (root.thingClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) {
- append({name: supportedInterfaces[i]});
+ Flow {
+ id: flow
+ Layout.fillWidth: true
+ Layout.leftMargin: sensorsModel.count == 1 ? Style.hugeMargins : Style.bigMargins
+ Layout.rightMargin: sensorsModel.count == 1 ? Style.hugeMargins : Style.bigMargins
+ Layout.preferredHeight: cellWidth * Math.min(400, Math.ceil(flowRepeater.count / columns))
+
+ property int columns: Math.min(flowRepeater.count, Math.floor(listView.width / 150))
+ property int cellWidth: width / columns
+ property int totalRows: flowRepeater.count / columns
+
+// columns: 2// Math.ceil(width / 600)
+// columnSpacing: Style.margins
+// rowSpacing: Style.margins
+
+ Repeater {
+ id: flowRepeater
+ model: sensorsModel
+
+ delegate: Item {
+ width: Math.floor(flow.width / itemsInRow)
+ height: Math.min(400, flow.cellWidth)
+ property int row: Math.floor(index / flow.columns)
+ property int itemsInRow: row < flow.totalRows ? flow.columns : (flowRepeater.count % flow.columns)
+
+ CircleBackground {
+ id: background
+ anchors.centerIn: parent
+ width: Math.min(parent.width, parent.height) - Style.margins
+ height: width
+
+ readonly property StateType sensorStateType: root.thing.thingClass.stateTypes.findByName(interfaceStateMap[modelData])
+ readonly property State sensorState: root.thing.stateByName(interfaceStateMap[modelData])
+
+ onColor: app.interfaceToColor(modelData)
+ on: sensorStateType.type.toLowerCase() == "bool" && sensorState.value === true
+ iconSource: [
+ "closablesensor",
+ "presencesensor",
+ "daylightsensor",
+ "firesensor",
+ "watersensor"
+ ].indexOf(modelData) >= 0 ? app.interfaceToIcon(modelData) : ""
+
+ Loader {
+ anchors.centerIn: parent
+ width: background.contentItem.width
+ height: background.contentItem.height
+ property StateType stateType: root.thingClass.stateTypes.findByName(interfaceStateMap[modelData])
+ property State state: root.thing.stateByName(interfaceStateMap[modelData])
+ property string interfaceName: modelData
+ property var minValue: {
+ if (["temperaturesensor"].indexOf(modelData) >= 0) {
+ return Types.toUiValue(-50, Types.UnitDegreeCelsius)
+ }
+ return state.minValue
+ }
+ property var maxValue: {
+ if (["temperaturesensor"].indexOf(modelData) >= 0) {
+ return Types.toUiValue(50, Types.UnitDegreeCelsius)
+ }
+ return state.maxValue
+ }
+
+ sourceComponent: {
+ var handledInterfaces = [
+ "humiditysensor",
+ "o2sensor",
+ "temperaturesensor",
+ "moisturesensor",
+ "co2sensor",
+ "conductivitysensor",
+ "cosensor",
+ "co2sensor",
+ "gassensor",
+ "lightsensor",
+ "orpsensor",
+ "phsensor",
+ "pressuresensor",
+ "waterlevelsensor",
+ "windspeedsensor"
+ ]
+ if (handledInterfaces.indexOf(modelData) >= 0) {
+ return progressComponent
+ }
+ }
}
}
}
}
+ }
- delegate: Loader {
- id: loader
- Layout.fillWidth: true
- Layout.preferredHeight: item.implicitHeight
- property StateType stateType: root.thingClass.stateTypes.findByName(interfaceStateMap[modelData])
- property State state: root.thing.stateByName(interfaceStateMap[modelData])
- property string interfaceName: modelData
+ GridLayout {
+ columns: Math.ceil(width / 600)
+ rowSpacing: 0
+ columnSpacing: 0
- // sourceComponent: stateType && stateType.type.toLowerCase() === "bool" ? boolComponent : graphComponent
- sourceComponent: graphComponent
+ Repeater {
+ model: sensorsModel
+
+ delegate: Loader {
+ id: loader
+ Layout.fillWidth: true
+ Layout.preferredHeight: item.implicitHeight
+
+ property StateType stateType: root.thingClass.stateTypes.findByName(interfaceStateMap[modelData])
+ property State state: root.thing.stateByName(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",
- "cosensor": "co",
- "co2sensor": "co2",
- "gassensor": "gas",
- "presencesensor": "isPresent",
- "daylightsensor": "daylight",
- "closablesensor": "closed",
- "watersensor": "waterDetected",
- "waterlevelsensor": "waterLevel",
- "phsensor": "ph",
- "o2sensor": "o2saturation",
- "orpsensor": "orp"
}
}
-
}
}
-
-
Component {
id: graphComponent
@@ -120,7 +220,7 @@ ThingPageBase {
Binding {
target: graph
property: "title"
- when: ["presencesensor", "daylightsensor", "closablesensor", "watersensor"].indexOf(graph.interfaceName) >= 0
+ when: ["presencesensor", "daylightsensor", "closablesensor", "watersensor", "firesensor"].indexOf(graph.interfaceName) >= 0
value: {
switch (graph.interfaceName) {
case "presencesensor":
@@ -131,6 +231,8 @@ ThingPageBase {
return graph.state.value === true ? qsTr("Closed") : qsTr("Open")
case "watersensor":
return graph.state.value === true ? qsTr("Wet") : qsTr("Dry")
+ case "firesensor":
+ return graph.state.value === true ? qsTr("Fire") : qsTr("No fire")
}
}
}
@@ -225,5 +327,67 @@ ThingPageBase {
}
}
}
+
+ Component {
+ id: progressComponent
+ Canvas {
+ id: progressCanvas
+ property string interfaceName: parent.interfaceName
+ property StateType stateType: parent.stateType
+ property State state: parent.state
+ property var minValue: parent.minValue
+ property var maxValue: parent.maxValue
+
+ Label {
+ anchors.centerIn: parent
+ width: parent.width * 0.6
+ text: Types.toUiValue(progressCanvas.state.value, progressCanvas.stateType.unit).toFixed(1) + " " + Types.toUiUnit(progressCanvas.stateType.unit)
+ font.pixelSize: Math.min(Style.hugeFont.pixelSize, parent.height / 6)
+ wrapMode: Text.WordWrap
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ onPaint: {
+ var ctx = getContext("2d");
+ ctx.reset();
+
+ ctx.beginPath()
+ ctx.fillStyle = Style.foregroundColor
+
+ ctx.translate(width / 2, height / 2)
+ ctx.rotate(135 * Math.PI / 180)
+
+ ctx.lineCap = "round"
+ ctx.lineWidth = width * .1
+
+ ctx.beginPath()
+ ctx.strokeStyle = Style.tileOverlayColor
+ var startAngle = 0
+ var endAngle = 270
+ var radStart = startAngle * Math.PI/180;
+ var radEnd = endAngle * Math.PI/180;
+ ctx.arc(0, 0, width / 2 - ctx.lineWidth / 2, radStart, radEnd)
+ ctx.stroke()
+ ctx.closePath()
+
+ ctx.beginPath()
+ ctx.strokeStyle = app.interfaceToColor(progressCanvas.interfaceName)
+ var progress = (progressCanvas.state.value - progressCanvas.minValue) / (progressCanvas.maxValue - progressCanvas.minValue)
+ radEnd *= progress
+ ctx.arc(0, 0, width / 2 - ctx.lineWidth / 2, radStart, radEnd)
+ ctx.stroke()
+ ctx.closePath()
+ }
+
+ ColorIcon {
+ anchors.centerIn: parent
+ anchors.verticalCenterOffset: progressCanvas.height / 2 - height
+ name: app.interfaceToIcon(progressCanvas.interfaceName)
+ size: Math.min(Style.bigIconSize, parent.height / 5)
+ color: app.interfaceToColor(progressCanvas.interfaceName)
+ }
+ }
+ }
+
}
diff --git a/nymea-app/ui/images/sensors/fire.svg b/nymea-app/ui/images/sensors/fire.svg
new file mode 100644
index 00000000..bff79b13
--- /dev/null
+++ b/nymea-app/ui/images/sensors/fire.svg
@@ -0,0 +1,193 @@
+
+