diff --git a/libnymea-app/types/interfaces.cpp b/libnymea-app/types/interfaces.cpp index a9efdec5..c4686643 100644 --- a/libnymea-app/types/interfaces.cpp +++ b/libnymea-app/types/interfaces.cpp @@ -278,6 +278,9 @@ Interfaces::Interfaces(QObject *parent) : QAbstractListModel(parent) addInterface("presencesensor", tr("Presence sensors"), {"sensor"}); addStateType("presencesensor", "isPresent", QVariant::Bool, false, tr("Is present"), tr("Presence changed")); + addInterface("vibrationsensor", tr("Vibration sensors"), {"sensor"}); + addEventType("vibrationsensor", "vibrationDetected", tr("Vibration detected"), new ParamTypes()); + addInterface("pressuresensor", tr("Pressure sensors"), {"sensor"}); addStateType("pressuresensor", "pressure", QVariant::Double, false, tr("Pressure"), tr("Pressure changed")); diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc index b6fe22ac..bd532586 100644 --- a/nymea-app/images.qrc +++ b/nymea-app/images.qrc @@ -298,5 +298,6 @@ ui/images/arrow-up.svg ui/images/plus.svg ui/images/minus.svg + ui/images/sensors/vibration.svg diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index 26308795..7d55201a 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -323,6 +323,8 @@ ApplicationWindow { return Qt.resolvedUrl("images/sensors/windspeed.svg") case "watersensor": return Qt.resolvedUrl("images/sensors/water.svg") + case "vibrationsensor": + return Qt.resolvedUrl("images/sensors/vibration.svg") case "waterlevelsensor": return Qt.resolvedUrl("images/sensors/water.svg") case "firesensor": @@ -509,6 +511,9 @@ ApplicationWindow { case "presencesensor": //: Select ... return qsTr("presence sensor") + case "vibrationsensor": + //: Select ... + return qsTr("vibration sensor"); case "doorbell": //: Select ... return qsTr("doorbell") diff --git a/nymea-app/ui/StyleBase.qml b/nymea-app/ui/StyleBase.qml index d73e83dc..9d44bb75 100644 --- a/nymea-app/ui/StyleBase.qml +++ b/nymea-app/ui/StyleBase.qml @@ -102,6 +102,7 @@ Item { "gassensor": orange, "daylightsensor": yellow, "presencesensor": darkBlue, + "vibrationsensor": orange, "closablesensor": green, "smartmeterconsumer": orange, "smartmeterproducer": lime, diff --git a/nymea-app/ui/components/NymeaItemDelegate.qml b/nymea-app/ui/components/NymeaItemDelegate.qml index 7503693b..0a63f523 100644 --- a/nymea-app/ui/components/NymeaItemDelegate.qml +++ b/nymea-app/ui/components/NymeaItemDelegate.qml @@ -71,8 +71,6 @@ ItemDelegate { signal deleteClicked() signal secondaryIconClicked() - onPressAndHold: swipe.open(SwipeDelegate.Right) - QtObject { id: d property var deleteContextOption: [{ diff --git a/nymea-app/ui/components/NymeaSwipeDelegate.qml b/nymea-app/ui/components/NymeaSwipeDelegate.qml index 1e80e73f..3043815d 100644 --- a/nymea-app/ui/components/NymeaSwipeDelegate.qml +++ b/nymea-app/ui/components/NymeaSwipeDelegate.qml @@ -202,7 +202,7 @@ SwipeDelegate { swipe.enabled: { for (var i = 0; i < d.finalContextOptions.length; i++) { - if (d.finalContextOptions[i].visible) { + if (!d.finalContextOptions[i].hasOwnProperty("visible") || d.finalContextOptions[i].visible) { return true } } @@ -215,7 +215,16 @@ SwipeDelegate { id: swipeComponent Item { height: parent.height - width: height * d.finalContextOptions.length + width: { + var count = 0 + for (var i = 0; i < d.finalContextOptions.length; i++) { + if (!d.finalContextOptions[i].hasOwnProperty("visible") || d.finalContextOptions[i].visible) { + count++; + } + } + return height * count + } + anchors.right: parent.right Item { anchors { diff --git a/nymea-app/ui/customviews/GenericTypeLogView.qml b/nymea-app/ui/customviews/GenericTypeLogView.qml index 092bb318..a98a953b 100644 --- a/nymea-app/ui/customviews/GenericTypeLogView.qml +++ b/nymea-app/ui/customviews/GenericTypeLogView.qml @@ -53,12 +53,6 @@ Item { SwipeDelegateGroup {} - onContentYChanged: { - if (!logsModel.busy && contentY - originY < 5 * height) { - logsModel.fetchEarlier(24) - } - } - delegate: NymeaSwipeDelegate { id: logEntryDelegate width: parent.width diff --git a/nymea-app/ui/delegates/InterfaceTile.qml b/nymea-app/ui/delegates/InterfaceTile.qml index 8a3b5e23..c6b34d54 100644 --- a/nymea-app/ui/delegates/InterfaceTile.qml +++ b/nymea-app/ui/delegates/InterfaceTile.qml @@ -314,6 +314,7 @@ MainPageTile { ListElement { ifaceName: "pressuresensor"; stateName: "pressure" } ListElement { ifaceName: "daylightsensor"; stateName: "daylight" } ListElement { ifaceName: "presencesensor"; stateName: "isPresent" } + ListElement { ifaceName: "vibfrationsensor"; stateName: "" } ListElement { ifaceName: "closablesensor"; stateName: "closed" } ListElement { ifaceName: "lightsensor"; stateName: "lightIntensity" } ListElement { ifaceName: "watersensor"; stateName: "waterDetected" } diff --git a/nymea-app/ui/delegates/ThingTile.qml b/nymea-app/ui/delegates/ThingTile.qml index ec0ca55e..24cbe5a1 100644 --- a/nymea-app/ui/delegates/ThingTile.qml +++ b/nymea-app/ui/delegates/ThingTile.qml @@ -256,6 +256,9 @@ MainPageTile { if (thing.thingClass.interfaces.indexOf("presencesensor") >= 0) { tmp.push({iface: "presencesensor", state: "isPresent"}); } + if (thing.thingClass.interfaces.indexOf("vibrationsensor") >= 0) { + tmp.push({iface: "vibrationsensor", state: "vibrationDetected"}); + } if (thing.thingClass.interfaces.indexOf("phsensor") >= 0) { tmp.push({iface: "phsensor", state: "ph"}) } diff --git a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml index b15c74df..0dc4070b 100644 --- a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml @@ -65,7 +65,15 @@ ThingsListPageBase { Layout.preferredWidth: contentGrid.width / contentGrid.columns thing: root.thingsProxy.getThing(model.id) - onClicked: enterPage(index) + onClicked: { + // we show all "sensors" in shownInterfaces in here so the "sensors" view would be the best match, but for sensors + // that should show the input trigger view instead, we need to override that + if (thing.thingClass.interfaces.indexOf("vibrationsensor") >= 0) { + enterPage(index, "inputtrigger") + } else { + enterPage(index) + } + } contentItem: GridLayout { id: dataGrid @@ -85,6 +93,7 @@ ThingsListPageBase { ListElement { interfaceName: "gassensor"; stateName: "gasLevel" } ListElement { interfaceName: "daylightsensor"; stateName: "daylight" } ListElement { interfaceName: "presencesensor"; stateName: "isPresent" } + ListElement { interfaceName: "vibrationsensor"; stateName: ""; eventName: "vibrationDetected" } ListElement { interfaceName: "closablesensor"; stateName: "closed" } ListElement { interfaceName: "heating"; stateName: "power" } ListElement { interfaceName: "thermostat"; stateName: "targetTemperature" } @@ -109,6 +118,16 @@ ThingsListPageBase { property StateType stateType: itemDelegate.thing.thingClass.stateTypes.findByName(model.stateName) property State stateValue: stateType ? itemDelegate.thing.states.getState(stateType.id) : null + property EventType eventType: itemDelegate.thing.thingClass.eventTypes.findByName(model.eventName) + LogsModel { + id: eventLogsModel + engine: sensorValueDelegate.eventType != null ? _engine : null + thingId: itemDelegate.thing.id + typeIds: sensorValueDelegate.eventType != null ? [sensorValueDelegate.eventType.id] : [] + live: true + fetchBlockSize: 1 + } + ColorIcon { Layout.preferredHeight: Style.iconSize Layout.preferredWidth: height @@ -150,6 +169,13 @@ ThingsListPageBase { 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"); + case "vibrationsensor": { + if (eventLogsModel.count > 0) { + return qsTr("Last vibration: %1").arg(eventLogsModel.get(0).timestamp.toLocaleString(Qt.locale(), Locale.ShortFormat)) + } else { + return qsTr("Not moved yet") + } + } default: return sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool" ? sensorValueDelegate.stateType.displayName @@ -171,6 +197,7 @@ ThingsListPageBase { Layout.preferredWidth: led.width visible: led.visible } + } } } diff --git a/nymea-app/ui/devicepages/InputTriggerDevicePage.qml b/nymea-app/ui/devicepages/InputTriggerDevicePage.qml index ec591239..fedc60a0 100644 --- a/nymea-app/ui/devicepages/InputTriggerDevicePage.qml +++ b/nymea-app/ui/devicepages/InputTriggerDevicePage.qml @@ -38,52 +38,93 @@ import "../customviews" ThingPageBase { id: root - GenericTypeLogView { - id: logView - anchors.fill: parent + property var interfaceEventMap: { + "inputtrigger": "triggered", + "vibrationsensor": "vibrationDetected" + } - logsModel: logsModelNg - LogsModelNg { - id: logsModelNg - engine: _engine - thingId: root.thing.id - live: true - typeIds: [root.thing.thingClass.eventTypes.findByName("triggered").id]; + readonly property string interfaceName: { + var supportedInterfaces = Object.keys(interfaceEventMap) + for (var i = 0; i < supportedInterfaces.length; i++) { + if (root.thing.thingClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) { + return supportedInterfaces[i] + } + } + return "" + } + + readonly property string eventName: interfaceEventMap[interfaceName] + + GridLayout { + anchors.fill: parent + columns: app.landscape ? 2 : 1 + rowSpacing: 0 + columnSpacing: 0 + + Item { + Layout.fillWidth: true + Layout.preferredHeight: width + Layout.margins: Style.hugeMargins + + CircleBackground { + id: iconView + width: Math.min(parent.width, parent.height) + height: width + anchors.centerIn: parent + iconSource: app.interfaceToIcon(root.interfaceName) + onColor: app.interfaceToColor(root.interfaceName) + on: false + Timer { + running: parent.on + repeat: false + interval: 1000 + onTriggered: { + parent.on = false + } + } + } } -// delegate: NymeaSwipeDelegate { -// width: parent.width -// iconName: app.interfaceToIcon("inputtrigger") -// text: model.value.trim() -// subText: Qt.formatDateTime(model.timestamp) -// progressive: false -// onClicked: { -// print("a", model.value.trim()) -// var parts = model.value.trim().split(', ') -// print("b", parts) -// var popup = detailsPopup.createObject(root, {timestamp: model.timestamp, notificationTitle: parts[1], notificationBody: parts[0]}); -// popup.open(); -// } -// } + GenericTypeLogView { + id: logView + Layout.fillWidth: true + Layout.fillHeight: true - onAddRuleClicked: { - var value = logView.logsModel.get(index).value - var typeId = logView.logsModel.get(index).typeId - var rule = engine.ruleManager.createNewRule(); - var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor(); - eventDescriptor.thingId = thing.id; - var eventType = root.thing.thingClass.eventTypes.getEventType(typeId); - eventDescriptor.eventTypeId = eventType.id; - rule.name = root.thing.name + " - " + eventType.displayName; - if (eventType.paramTypes.count === 1) { - var paramType = eventType.paramTypes.get(0); - eventDescriptor.paramDescriptors.setParamDescriptor(paramType.id, value, ParamDescriptor.ValueOperatorEquals); + logsModel: logsModel + LogsModel { + id: logsModel + engine: _engine + thingId: root.thing.id + live: true + typeIds: [root.thing.thingClass.eventTypes.findByName(root.eventName).id]; + onCountChanged: { + if (!logsModel.busy) { + iconView.on = true + } + } + } + + onAddRuleClicked: { + var value = logView.logsModel.get(index).value + var typeId = logView.logsModel.get(index).typeId + var rule = engine.ruleManager.createNewRule(); + var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor(); + eventDescriptor.thingId = thing.id; + var eventType = root.thing.thingClass.eventTypes.getEventType(typeId); + eventDescriptor.eventTypeId = eventType.id; + rule.name = root.thing.name + " - " + eventType.displayName; + if (eventType.paramTypes.count === 1) { + var paramType = eventType.paramTypes.get(0); + eventDescriptor.paramDescriptors.setParamDescriptor(paramType.id, value, ParamDescriptor.ValueOperatorEquals); + } rule.eventDescriptors.addEventDescriptor(eventDescriptor); rule.name = rule.name + " - " + value + var rulePage = pageStack.push(Qt.resolvedUrl("../magic/ThingRulesPage.qml"), {thing: root.thing}); + rulePage.addRule(rule); } - var rulePage = pageStack.push(Qt.resolvedUrl("../magic/ThingRulesPage.qml"), {thing: root.thing}); - rulePage.addRule(rule); } } + + } diff --git a/nymea-app/ui/images/sensors/vibration.svg b/nymea-app/ui/images/sensors/vibration.svg new file mode 100644 index 00000000..ce89df59 --- /dev/null +++ b/nymea-app/ui/images/sensors/vibration.svg @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/utils/NymeaUtils.qml b/nymea-app/ui/utils/NymeaUtils.qml index 84a147c0..5f0e45f9 100644 --- a/nymea-app/ui/utils/NymeaUtils.qml +++ b/nymea-app/ui/utils/NymeaUtils.qml @@ -39,6 +39,8 @@ Item { page = "CoolingThingPage.qml"; } else if (interfaceList.indexOf("thermostat") >= 0) { page = "ThermostatDevicePage.qml"; + } else if (interfaceList.indexOf("vibrationsensor") >= 0) { + page = "InputTriggerDevicePage.qml"; } else if (interfaceList.indexOf("sensor") >= 0) { page = "SensorDevicePage.qml"; } else if (interfaceList.indexOf("inputtrigger") >= 0) {