From 8d8b72790bb3bd9d5ebbfea96cdd30edb5aef3f1 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Fri, 30 Aug 2019 02:47:48 +0200 Subject: [PATCH 1/5] Add support for the doorbell interface --- .../discovery/zeroconfdiscovery.cpp | 8 +- .../connection/nymeaconnection.cpp | 4 +- libnymea-app-core/devicemanager.cpp | 16 +++ libnymea-app-core/devicemanager.h | 25 ++++ libnymea-app-core/jsonrpc/jsonhandler.h | 2 +- libnymea-common/types/device.h | 1 + libnymea-common/types/ruleactionparams.cpp | 10 ++ libnymea-common/types/ruleactionparams.h | 2 + nymea-app/nymea-app.pro | 1 + nymea-app/resources.qrc | 1 + nymea-app/ruletemplates.qrc | 1 + .../ruletemplates/doorbellruletemplates.json | 24 ++++ nymea-app/ui/Nymea.qml | 12 +- nymea-app/ui/components/NymeaHeader.qml | 1 - .../ui/components/NymeaListItemDelegate.qml | 2 + .../ui/devicepages/DoorbellDevicePage.qml | 111 ++++++++++++++++++ nymea-app/ui/magic/DeviceRulesPage.qml | 4 +- nymea-app/ui/magic/NewThingMagicPage.qml | 27 ++++- .../ui/magic/SelectRuleActionParamsPage.qml | 2 + nymea-app/ui/magic/SelectThingPage.qml | 3 + 20 files changed, 243 insertions(+), 14 deletions(-) create mode 100644 nymea-app/ruletemplates/doorbellruletemplates.json create mode 100644 nymea-app/ui/devicepages/DoorbellDevicePage.qml diff --git a/libnymea-app-core/connection/discovery/zeroconfdiscovery.cpp b/libnymea-app-core/connection/discovery/zeroconfdiscovery.cpp index 8dba6774..966ba20d 100644 --- a/libnymea-app-core/connection/discovery/zeroconfdiscovery.cpp +++ b/libnymea-app-core/connection/discovery/zeroconfdiscovery.cpp @@ -69,7 +69,7 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry) return; } - qDebug() << "zeroconf service discovered" << entry->type() << entry->name() << " IP:" << entry->ip().toString() << entry->txt(); +// qDebug() << "zeroconf service discovered" << entry->type() << entry->name() << " IP:" << entry->ip().toString() << entry->txt(); QString uuid; bool sslEnabled = false; @@ -97,7 +97,7 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry) if (!host) { host = new NymeaHost(m_nymeaHosts); host->setUuid(uuid); - qDebug() << "ZeroConf: Adding new host:" << serverName << uuid; +// qDebug() << "ZeroConf: Adding new host:" << serverName << uuid; m_nymeaHosts->addHost(host); } host->setName(serverName); @@ -113,14 +113,14 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry) url.setPort(entry->port()); Connection *connection = host->connections()->find(url); if (!connection) { - qDebug() << "Zeroconf: Adding new connection to host:" << host->name() << url.toString(); +// qDebug() << "Zeroconf: Adding new connection to host:" << host->name() << url.toString(); QString displayName = QString("%1:%2").arg(url.host()).arg(url.port()); Connection::BearerType bearerType = QHostAddress(url.host()).isLoopback() ? Connection::BearerTypeLoopback : Connection::BearerTypeLan; connection = new Connection(url, bearerType, sslEnabled, displayName); connection->setOnline(true); host->connections()->addConnection(connection); } else { - qDebug() << "Zeroconf: Setting connection online:" << host->name() << url.toString(); +// qDebug() << "Zeroconf: Setting connection online:" << host->name() << url.toString(); connection->setOnline(true); } } diff --git a/libnymea-app-core/connection/nymeaconnection.cpp b/libnymea-app-core/connection/nymeaconnection.cpp index ecd2c195..3c539bde 100644 --- a/libnymea-app-core/connection/nymeaconnection.cpp +++ b/libnymea-app-core/connection/nymeaconnection.cpp @@ -412,11 +412,11 @@ void NymeaConnection::updateActiveBearers() } if (m_availableBearerTypes != availableBearerTypes) { - qDebug() << "Available Bearer Types changed:" << availableBearerTypes; +// qDebug() << "Available Bearer Types changed:" << availableBearerTypes; m_availableBearerTypes = availableBearerTypes; emit availableBearerTypesChanged(); } else { - qDebug() << "Available Bearer Types:" << availableBearerTypes; +// qDebug() << "Available Bearer Types:" << availableBearerTypes; } if (!m_currentHost) { diff --git a/libnymea-app-core/devicemanager.cpp b/libnymea-app-core/devicemanager.cpp index e5f8c036..10680c44 100644 --- a/libnymea-app-core/devicemanager.cpp +++ b/libnymea-app-core/devicemanager.cpp @@ -34,6 +34,22 @@ DeviceManager::DeviceManager(JsonRpcClient* jsonclient, QObject *parent) : m_jsonClient(jsonclient) { m_jsonClient->registerNotificationHandler(this, "notificationReceived"); + EventHandler *eventHandler = new EventHandler(this); + m_jsonClient->registerNotificationHandler(eventHandler, "notificationReceived"); + connect(eventHandler, &EventHandler::eventReceived, this, [this](const QVariantMap event) { + QUuid deviceId = event.value("deviceId").toUuid(); + QUuid eventTypeId = event.value("eventTypeId").toUuid(); + + Device *dev = m_devices->getDevice(deviceId); + if (!dev) { + qWarning() << "received an event from a device we don't know..." << deviceId << event; + return; + } + qDebug() << "Event received" << deviceId.toString() << eventTypeId.toString(); + dev->eventTriggered(eventTypeId.toString(), event.value("params").toMap()); + emit eventTriggered(deviceId.toString(), eventTypeId.toString(), event.value("params").toMap()); + + }); } void DeviceManager::clear() diff --git a/libnymea-app-core/devicemanager.h b/libnymea-app-core/devicemanager.h index 49623473..375662b4 100644 --- a/libnymea-app-core/devicemanager.h +++ b/libnymea-app-core/devicemanager.h @@ -120,6 +120,8 @@ signals: void fetchingDataChanged(); void notificationReceived(const QString &deviceId, const QString &eventTypeId, const QVariantList ¶ms); + void eventTriggered(const QString &deviceId, const QString &eventTypeId, const QVariantMap params); + private: Vendors *m_vendors; Plugins *m_plugins; @@ -135,6 +137,29 @@ private: QHash > m_browsingRequests; QHash > m_browserDetailsRequests; + + +}; + +// TODO: Kinda shitty that Device Events are not sent from the Devices namespace... +class EventHandler: public JsonHandler { + Q_OBJECT + +public: + EventHandler(QObject *parent = nullptr): JsonHandler(parent) {} + QString nameSpace() const override { + return "Events"; + } + +signals: + void eventReceived(const QVariantMap &event); + +private: + Q_INVOKABLE void notificationReceived(const QVariantMap &data) { + qDebug() << "event rece" << data; + emit eventReceived(data.value("params").toMap().value("event").toMap()); + } + }; #endif // DEVICEMANAGER_H diff --git a/libnymea-app-core/jsonrpc/jsonhandler.h b/libnymea-app-core/jsonrpc/jsonhandler.h index 15057a5e..7c94fe0f 100644 --- a/libnymea-app-core/jsonrpc/jsonhandler.h +++ b/libnymea-app-core/jsonrpc/jsonhandler.h @@ -28,7 +28,7 @@ class JsonHandler : public QObject { Q_OBJECT public: - JsonHandler(QObject *parent = 0); + JsonHandler(QObject *parent = nullptr); virtual QString nameSpace() const = 0; }; diff --git a/libnymea-common/types/device.h b/libnymea-common/types/device.h index e5617f32..99a4388e 100644 --- a/libnymea-common/types/device.h +++ b/libnymea-common/types/device.h @@ -90,6 +90,7 @@ signals: void paramsChanged(); void settingsChanged(); void statesChanged(); + void eventTriggered(const QString &eventTypeId, const QVariantMap ¶ms); }; diff --git a/libnymea-common/types/ruleactionparams.cpp b/libnymea-common/types/ruleactionparams.cpp index c4f4ae48..a0df977d 100644 --- a/libnymea-common/types/ruleactionparams.cpp +++ b/libnymea-common/types/ruleactionparams.cpp @@ -115,6 +115,16 @@ RuleActionParam *RuleActionParams::get(int index) const return m_list.at(index); } +bool RuleActionParams::hasRuleActionParam(const QString ¶mTypeId) const +{ + for (int i = 0; i < m_list.count(); i++) { + if (m_list.at(i)->paramTypeId() == paramTypeId) { + return true; + } + } + return false; +} + bool RuleActionParams::operator==(RuleActionParams *other) const { if (rowCount() != other->rowCount()) { diff --git a/libnymea-common/types/ruleactionparams.h b/libnymea-common/types/ruleactionparams.h index 846e3263..7294211f 100644 --- a/libnymea-common/types/ruleactionparams.h +++ b/libnymea-common/types/ruleactionparams.h @@ -33,6 +33,8 @@ public: Q_INVOKABLE RuleActionParam* get(int index) const; + Q_INVOKABLE bool hasRuleActionParam(const QString ¶mTypeId) const; + bool operator==(RuleActionParams *other) const; signals: diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index 20817a78..54c05c60 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -132,6 +132,7 @@ target.path = /usr/bin INSTALLS += target DISTFILES += \ + ruletemplates/doorbellruletemplates.json \ ruletemplates/smartmetertemplates.json \ ruletemplates/presencesensortemplates.json \ ruletemplates/daylightsensor.json \ diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index b8ab2b13..e01cf871 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -193,5 +193,6 @@ ui/delegates/BrowserItemDelegate.qml ui/components/NavigationPad.qml ui/components/KeypadButton.qml + ui/devicepages/DoorbellDevicePage.qml diff --git a/nymea-app/ruletemplates.qrc b/nymea-app/ruletemplates.qrc index e5c6de98..115382d3 100644 --- a/nymea-app/ruletemplates.qrc +++ b/nymea-app/ruletemplates.qrc @@ -7,5 +7,6 @@ ruletemplates/presencesensortemplates.json ruletemplates/daylightsensor.json ruletemplates/lighttemplates.json + ruletemplates/doorbellruletemplates.json diff --git a/nymea-app/ruletemplates/doorbellruletemplates.json b/nymea-app/ruletemplates/doorbellruletemplates.json new file mode 100644 index 00000000..d29fb2a9 --- /dev/null +++ b/nymea-app/ruletemplates/doorbellruletemplates.json @@ -0,0 +1,24 @@ +{ + "templates": [ + { + "interfaceName": "doorbell", + "description": "Alert on doorbell ring", + "ruleNameTemplate": "Alert %1 when someone is at %0", + "eventDescriptorTemplates": [ + { + "interfaceName": "doorbell", + "interfaceEvent": "doorbellPressed", + "selectionId": 0 + } + ], + "ruleActionTemplates": [ + { + "interfaceName": "alert", + "interfaceAction": "alert", + "selectionId": 1 + } + ] + } + ] +} + diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index 9abc3ebb..98273935 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -65,7 +65,7 @@ ApplicationWindow { } property alias _discovery: discovery - property var supportedInterfaces: ["light", "weather", "media", "garagegate", "awning", "shutter", "blind", "powersocket", "heating", "sensor", "smartmeter", "evcharger", "accesscontrol", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"] + property var supportedInterfaces: ["light", "weather", "media", "garagegate", "awning", "shutter", "blind", "powersocket", "heating", "doorbell", "sensor", "smartmeter", "evcharger", "accesscontrol", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"] function interfaceToString(name) { switch(name) { case "light": @@ -122,6 +122,8 @@ ApplicationWindow { return qsTr("EV-chargers"); case "powersocket": return qsTr("Power sockets") + case "doorbell": + return qsTr("Doorbells"); case "uncategorized": return qsTr("Uncategorized") default: @@ -226,6 +228,8 @@ ApplicationWindow { case "evcharger": case "extendedevcharger": return Qt.resolvedUrl("images/ev-charger.svg") + case "doorbell": + return Qt.resolvedUrl("images/notification.svg") case "connectable": return Qt.resolvedUrl("images/stock_link.svg") default: @@ -285,6 +289,10 @@ ApplicationWindow { return qsTr("daylight sensor") case "presencesensor": return qsTr("presence sensor") + case "doorbell": + return qsTr("doorbell") + case "alert": + return qsTr("alert") default: console.warn("Unhandled interfaceToDisplayName:", name) } @@ -322,6 +330,8 @@ ApplicationWindow { page = "SmartMeterDevicePage.qml" } else if (interfaceList.indexOf("powersocket") >= 0) { page = "PowersocketDevicePage.qml"; + } else if (interfaceList.indexOf("doorbell") >= 0) { + page = "DoorbellDevicePage.qml"; } else { page = "GenericDevicePage.qml"; } diff --git a/nymea-app/ui/components/NymeaHeader.qml b/nymea-app/ui/components/NymeaHeader.qml index a0035316..77a69633 100644 --- a/nymea-app/ui/components/NymeaHeader.qml +++ b/nymea-app/ui/components/NymeaHeader.qml @@ -91,7 +91,6 @@ Item { Label { text: infoPane.text - visible: infoPane.alertState font.pixelSize: app.smallFont color: "white" } diff --git a/nymea-app/ui/components/NymeaListItemDelegate.qml b/nymea-app/ui/components/NymeaListItemDelegate.qml index f649d962..9d1a4c16 100644 --- a/nymea-app/ui/components/NymeaListItemDelegate.qml +++ b/nymea-app/ui/components/NymeaListItemDelegate.qml @@ -13,6 +13,7 @@ SwipeDelegate { property bool wrapTexts: true property bool prominentSubText: true + property int textAlignment: Text.AlignLeft property string iconName property string thumbnail @@ -81,6 +82,7 @@ SwipeDelegate { maximumLineCount: root.wrapTexts ? 2 : 1 elide: Text.ElideRight verticalAlignment: Text.AlignVCenter + horizontalAlignment: root.textAlignment } Label { Layout.fillWidth: true diff --git a/nymea-app/ui/devicepages/DoorbellDevicePage.qml b/nymea-app/ui/devicepages/DoorbellDevicePage.qml new file mode 100644 index 00000000..c1e34865 --- /dev/null +++ b/nymea-app/ui/devicepages/DoorbellDevicePage.qml @@ -0,0 +1,111 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" +import "../customviews" + +DevicePageBase { + id: root + + readonly property EventType doorbellPressedType: deviceClass.eventTypes.findByName("doorbellPressed") + + GridLayout { + anchors.fill: parent + anchors.topMargin: app.margins + columns: app.landscape ? 2 : 1 + columnSpacing: app.margins + rowSpacing: app.margins + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + ColorIcon { + id: doorbellIcon + anchors.centerIn: parent + height: Math.min(parent.width, parent.height) + width: height + name: "../images/notification.svg" + + color: keyColor + + SequentialAnimation { + id: ringAnimation + ColorAnimation { target: doorbellIcon; property: "color"; from: doorbellIcon.keyColor; to: app.accentColor; duration: 200 } + ColorAnimation { target: doorbellIcon; property: "color"; from: app.accentColor; to: doorbellIcon.keyColor; duration: 300 } + ColorAnimation { target: doorbellIcon; property: "color"; from: doorbellIcon.keyColor; to: app.accentColor; duration: 200 } + ColorAnimation { target: doorbellIcon; property: "color"; from: app.accentColor; to: doorbellIcon.keyColor; duration: 300 } + ColorAnimation { target: doorbellIcon; property: "color"; from: doorbellIcon.keyColor; to: app.accentColor; duration: 200 } + ColorAnimation { target: doorbellIcon; property: "color"; from: app.accentColor; to: doorbellIcon.keyColor; duration: 300 } + ColorAnimation { target: doorbellIcon; property: "color"; from: doorbellIcon.keyColor; to: app.accentColor; duration: 200 } + ColorAnimation { target: doorbellIcon; property: "color"; from: app.accentColor; to: doorbellIcon.keyColor; duration: 300 } + } + + Connections { + target: root.device + onEventTriggered: { + print("evenEmitted", params) + if (eventTypeId == root.doorbellPressedType.id) { + ringAnimation.start(); + } + } + } + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + ColumnLayout { + anchors.fill: parent + spacing: app.margins + + ThinDivider { + visible: !app.landscape + } + + RowLayout { + spacing: app.margins + + Label { + Layout.fillWidth: true + } + + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: app.iconSize + name: "../images/alarm-clock.svg" + color: app.accentColor + } + + Label { + text: qsTr("History") + } + Label { + Layout.fillWidth: true + } + } + + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + + model: LogsModelNg { + engine: _engine + live: true + deviceId: root.device.id + typeIds: [root.doorbellPressedType.id] + } + delegate: NymeaListItemDelegate { + width: parent.width + text: Qt.formatDateTime(model.timestamp) + progressive: false + textAlignment: Text.AlignHCenter + } + } + } + } + } +} diff --git a/nymea-app/ui/magic/DeviceRulesPage.qml b/nymea-app/ui/magic/DeviceRulesPage.qml index 7a3f886f..3ca49bca 100644 --- a/nymea-app/ui/magic/DeviceRulesPage.qml +++ b/nymea-app/ui/magic/DeviceRulesPage.qml @@ -78,7 +78,6 @@ Page { Connections { target: engine.ruleManager onAddRuleReply: { - d.editRulePage.busy = false; if (ruleError == "RuleErrorNoError") { pageStack.pop(root); } else { @@ -86,10 +85,10 @@ Page { var popup = errorDialog.createObject(root, {errorCode: ruleError }) popup.open(); } + d.editRulePage.busy = false; } onEditRuleReply: { - d.editRulePage.busy = false; if (ruleError == "RuleErrorNoError") { pageStack.pop(root); } else { @@ -97,6 +96,7 @@ Page { var popup = errorDialog.createObject(root, {errorCode: ruleError }) popup.open(); } + d.editRulePage.busy = false; } } diff --git a/nymea-app/ui/magic/NewThingMagicPage.qml b/nymea-app/ui/magic/NewThingMagicPage.qml index f0cb8022..d22ef5fa 100644 --- a/nymea-app/ui/magic/NewThingMagicPage.qml +++ b/nymea-app/ui/magic/NewThingMagicPage.qml @@ -300,12 +300,15 @@ Page { function createRuleAction(rule, ruleTemplate, ruleActions, device, ruleActionTemplate) { var ruleAction = ruleActions.createNewRuleAction(); var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + + ruleAction.actionTypeId = deviceClass.actionTypes.findByName(ruleActionTemplate.interfaceAction).id ruleAction.deviceId = device.id; print("Creating action", ruleActionTemplate.interfaceAction, "on device class", deviceClass.displayName) - ruleAction.actionTypeId = deviceClass.actionTypes.findByName(ruleActionTemplate.interfaceAction).id + + var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId); + for (var j = 0; j < ruleActionTemplate.ruleActionParamTemplates.count; j++) { var ruleActionParamTemplate = ruleActionTemplate.ruleActionParamTemplates.get(j) - var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId); var paramType = actionType.paramTypes.findByName(ruleActionParamTemplate.paramName); if (ruleActionParamTemplate.value !== undefined) { ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParamTemplate.value) @@ -335,8 +338,26 @@ Page { } else { console.warn("Invalid RuleActionParamTemplate. Has neither value nor event spec") } - } + // Check if the action has more paramTypes than there are defined in the ruleActionTemplate + for (var i = 0; i < actionType.paramTypes.count; i++) { + var paramType = actionType.paramTypes.get(i); + if (!ruleAction.ruleActionParams.hasRuleActionParam(paramType.id)) { + print("Missing param!", paramType.name) + var paramsPage = pageStack.push(Qt.resolvedUrl("SelectRuleActionParamsPage.qml"), {ruleAction: ruleAction, rule: rule}) + paramsPage.onBackPressed.connect(function() { + ruleAction.destroy() + pageStack.pop(); + }); + paramsPage.onCompleted.connect(function() { + pageStack.pop(); + ruleActions.addRuleAction(ruleAction); + fillRuleFromTemplate(rule, ruleTemplate); + }) + return; + } + } + ruleActions.addRuleAction(ruleAction); fillRuleFromTemplate(rule, ruleTemplate); } diff --git a/nymea-app/ui/magic/SelectRuleActionParamsPage.qml b/nymea-app/ui/magic/SelectRuleActionParamsPage.qml index acb949fb..a548fd13 100644 --- a/nymea-app/ui/magic/SelectRuleActionParamsPage.qml +++ b/nymea-app/ui/magic/SelectRuleActionParamsPage.qml @@ -187,7 +187,9 @@ Page { for (var i = 0; i < delegateRepeater.count; i++) { var paramDelegate = delegateRepeater.itemAt(i); if (paramDelegate.type === "static") { + print("Setting static value", paramDelegate.value) if (root.device) { + print("setting", paramDelegate.paramType.id) root.ruleAction.ruleActionParams.setRuleActionParam(paramDelegate.paramType.id, paramDelegate.value) } else if (root.iface) { root.ruleAction.ruleActionParams.setRuleActionParamByName(root.actionType.paramTypes.get(i).name, paramDelegate.value) diff --git a/nymea-app/ui/magic/SelectThingPage.qml b/nymea-app/ui/magic/SelectThingPage.qml index ad9dfb94..ee45c3ba 100644 --- a/nymea-app/ui/magic/SelectThingPage.qml +++ b/nymea-app/ui/magic/SelectThingPage.qml @@ -43,6 +43,9 @@ Page { engine: _engine groupByInterface: true nameFilter: filterInput.shown ? filterInput.text : "" + Component.onCompleted: { + print("showing devices for interfaces", devicesProxy.shownInterfaces) + } } ColumnLayout { From 176baa8ede22330b5ab8329cc968e984e413ea44 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 31 Aug 2019 00:12:25 +0200 Subject: [PATCH 2/5] Improve rule creation --- libnymea-app-core/devicemanager.cpp | 2 +- libnymea-app-core/devicemanager.h | 1 - libnymea-app-core/devicesproxy.cpp | 1 + .../ruletemplates/eventdescriptortemplate.cpp | 10 +++ .../ruletemplates/eventdescriptortemplate.h | 4 + .../ruletemplates/ruleactiontemplate.cpp | 10 +++ .../ruletemplates/ruleactiontemplate.h | 2 + .../ruletemplates/ruletemplate.cpp | 17 ++-- .../ruletemplates/ruletemplate.h | 4 +- .../ruletemplates/ruletemplates.cpp | 64 ++++++++------- .../ruletemplates/ruletemplates.h | 10 ++- .../ruletemplates/stateevaluatortemplate.cpp | 16 ++++ .../ruletemplates/stateevaluatortemplate.h | 2 + libnymea-common/types/paramdescriptors.cpp | 26 ++++++ libnymea-common/types/paramdescriptors.h | 3 + nymea-app/resources.qrc | 1 + nymea-app/ruletemplates/buttontemplates.json | 76 +++++------------ nymea-app/ruletemplates/daylightsensor.json | 6 +- .../ruletemplates/doorbellruletemplates.json | 1 - nymea-app/ruletemplates/lighttemplates.json | 82 ------------------- .../ruletemplates/notificationtemplates.json | 41 ++++++++-- .../presencesensortemplates.json | 20 ++--- nymea-app/ruletemplates/template.json | 1 - nymea-app/ui/MagicPage.qml | 35 ++++++-- nymea-app/ui/Nymea.qml | 14 ++++ nymea-app/ui/magic/DeviceRulesPage.qml | 1 + nymea-app/ui/magic/NewMagicPage.qml | 37 +++++++++ nymea-app/ui/magic/NewThingMagicPage.qml | 47 +++++++---- .../ui/mainviews/DevicesPageDelegate.qml | 1 + 29 files changed, 315 insertions(+), 220 deletions(-) create mode 100644 nymea-app/ui/magic/NewMagicPage.qml diff --git a/libnymea-app-core/devicemanager.cpp b/libnymea-app-core/devicemanager.cpp index 10680c44..8943d754 100644 --- a/libnymea-app-core/devicemanager.cpp +++ b/libnymea-app-core/devicemanager.cpp @@ -45,7 +45,7 @@ DeviceManager::DeviceManager(JsonRpcClient* jsonclient, QObject *parent) : qWarning() << "received an event from a device we don't know..." << deviceId << event; return; } - qDebug() << "Event received" << deviceId.toString() << eventTypeId.toString(); +// qDebug() << "Event received" << deviceId.toString() << eventTypeId.toString(); dev->eventTriggered(eventTypeId.toString(), event.value("params").toMap()); emit eventTriggered(deviceId.toString(), eventTypeId.toString(), event.value("params").toMap()); diff --git a/libnymea-app-core/devicemanager.h b/libnymea-app-core/devicemanager.h index 375662b4..b4ec1512 100644 --- a/libnymea-app-core/devicemanager.h +++ b/libnymea-app-core/devicemanager.h @@ -156,7 +156,6 @@ signals: private: Q_INVOKABLE void notificationReceived(const QVariantMap &data) { - qDebug() << "event rece" << data; emit eventReceived(data.value("params").toMap().value("event").toMap()); } diff --git a/libnymea-app-core/devicesproxy.cpp b/libnymea-app-core/devicesproxy.cpp index b3a08153..5cfd1a67 100644 --- a/libnymea-app-core/devicesproxy.cpp +++ b/libnymea-app-core/devicesproxy.cpp @@ -251,6 +251,7 @@ bool DevicesProxy::filterAcceptsRow(int source_row, const QModelIndex &source_pa } } DeviceClass *deviceClass = m_engine->deviceManager()->deviceClasses()->getDeviceClass(device->deviceClassId()); +// qDebug() << "Checking device" << deviceClass->name() << deviceClass->interfaces(); if (!m_shownInterfaces.isEmpty()) { bool foundMatch = false; foreach (const QString &filterInterface, m_shownInterfaces) { diff --git a/libnymea-app-core/ruletemplates/eventdescriptortemplate.cpp b/libnymea-app-core/ruletemplates/eventdescriptortemplate.cpp index bf0470d0..d2575eaf 100644 --- a/libnymea-app-core/ruletemplates/eventdescriptortemplate.cpp +++ b/libnymea-app-core/ruletemplates/eventdescriptortemplate.cpp @@ -35,3 +35,13 @@ ParamDescriptors *EventDescriptorTemplate::paramDescriptors() const { return m_paramDescriptors; } + +QStringList EventDescriptorTemplates::interfaces() const +{ + QStringList ret; + for (int i = 0; i < m_list.count(); i++) { + ret.append(m_list.at(i)->interfaceName()); + } + ret.removeDuplicates(); + return ret; +} diff --git a/libnymea-app-core/ruletemplates/eventdescriptortemplate.h b/libnymea-app-core/ruletemplates/eventdescriptortemplate.h index ccbb83fa..8dd85cd7 100644 --- a/libnymea-app-core/ruletemplates/eventdescriptortemplate.h +++ b/libnymea-app-core/ruletemplates/eventdescriptortemplate.h @@ -42,9 +42,11 @@ class EventDescriptorTemplates: public QAbstractListModel { Q_OBJECT Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + Q_PROPERTY(QStringList interfaces READ interfaces CONSTANT) public: EventDescriptorTemplates(QObject *parent = nullptr): QAbstractListModel(parent) {} + QStringList interfaces() const; int rowCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent); return m_list.count(); } QVariant data(const QModelIndex &index, int role) const override { Q_UNUSED(index); Q_UNUSED(role); return QVariant(); } @@ -63,6 +65,8 @@ public: return m_list.at(index); } + + signals: void countChanged(); diff --git a/libnymea-app-core/ruletemplates/ruleactiontemplate.cpp b/libnymea-app-core/ruletemplates/ruleactiontemplate.cpp index 14444232..eacd728e 100644 --- a/libnymea-app-core/ruletemplates/ruleactiontemplate.cpp +++ b/libnymea-app-core/ruletemplates/ruleactiontemplate.cpp @@ -36,3 +36,13 @@ RuleActionParamTemplates *RuleActionTemplate::ruleActionParamTemplates() const { return m_ruleActionParamTemplates; } + +QStringList RuleActionTemplates::interfaces() const +{ + QStringList ret; + for (int i = 0; i < m_list.count(); i++) { + ret.append(m_list.at(i)->interfaceName()); + } + ret.removeDuplicates(); + return ret; +} diff --git a/libnymea-app-core/ruletemplates/ruleactiontemplate.h b/libnymea-app-core/ruletemplates/ruleactiontemplate.h index 22881dc6..95e857c5 100644 --- a/libnymea-app-core/ruletemplates/ruleactiontemplate.h +++ b/libnymea-app-core/ruletemplates/ruleactiontemplate.h @@ -44,10 +44,12 @@ class RuleActionTemplates: public QAbstractListModel { Q_OBJECT Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + Q_PROPERTY(QStringList interfaces READ interfaces CONSTANT) public: RuleActionTemplates(QObject *parent = nullptr): QAbstractListModel(parent) {} int rowCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent); return m_list.count(); } QVariant data(const QModelIndex &index, int role) const override { Q_UNUSED(index); Q_UNUSED(role); return QVariant(); } + QStringList interfaces() const; void addRuleActionTemplate(RuleActionTemplate* ruleActionTemplate) { ruleActionTemplate->setParent(this); diff --git a/libnymea-app-core/ruletemplates/ruletemplate.cpp b/libnymea-app-core/ruletemplates/ruletemplate.cpp index 7255f076..c47238db 100644 --- a/libnymea-app-core/ruletemplates/ruletemplate.cpp +++ b/libnymea-app-core/ruletemplates/ruletemplate.cpp @@ -14,11 +14,6 @@ RuleTemplate::RuleTemplate(const QString &interfaceName, const QString &descript { } -QString RuleTemplate::interfaceName() const -{ - return m_interfaceName; -} - QString RuleTemplate::description() const { return m_description; @@ -29,6 +24,18 @@ QString RuleTemplate::ruleNameTemplate() const return m_ruleNameTemplate; } +QStringList RuleTemplate::interfaces() const +{ + QStringList ret; + ret.append(m_eventDescriptorTemplates->interfaces()); + if (m_stateEvaluatorTemplate) { + ret.append(m_stateEvaluatorTemplate->interfaces()); + } + ret.append(m_ruleActionTemplates->interfaces()); + ret.append(m_ruleExitActionTemplates->interfaces()); + return ret; +} + EventDescriptorTemplates *RuleTemplate::eventDescriptorTemplates() const { return m_eventDescriptorTemplates; diff --git a/libnymea-app-core/ruletemplates/ruletemplate.h b/libnymea-app-core/ruletemplates/ruletemplate.h index c9f33b26..bcd4af36 100644 --- a/libnymea-app-core/ruletemplates/ruletemplate.h +++ b/libnymea-app-core/ruletemplates/ruletemplate.h @@ -10,9 +10,9 @@ class StateEvaluatorTemplate; class RuleTemplate : public QObject { Q_OBJECT - Q_PROPERTY(QString interfaceName READ interfaceName CONSTANT) Q_PROPERTY(QString description READ description CONSTANT) Q_PROPERTY(QString ruleNameTemplate READ ruleNameTemplate CONSTANT) + Q_PROPERTY(QStringList interfaces READ interfaces CONSTANT) Q_PROPERTY(EventDescriptorTemplates* eventDescriptorTemplates READ eventDescriptorTemplates CONSTANT) Q_PROPERTY(StateEvaluatorTemplate* stateEvaluatorTemplate READ stateEvaluatorTemplate CONSTANT) Q_PROPERTY(RuleActionTemplates* ruleActionTemplates READ ruleActionTemplates CONSTANT) @@ -21,9 +21,9 @@ class RuleTemplate : public QObject public: explicit RuleTemplate(const QString &interfaceName, const QString &description, const QString &ruleNameTemplate, QObject *parent = nullptr); - QString interfaceName() const; QString description() const; QString ruleNameTemplate() const; + QStringList interfaces() const; EventDescriptorTemplates* eventDescriptorTemplates() const; StateEvaluatorTemplate* stateEvaluatorTemplate() const; diff --git a/libnymea-app-core/ruletemplates/ruletemplates.cpp b/libnymea-app-core/ruletemplates/ruletemplates.cpp index f543aaed..10a26e8b 100644 --- a/libnymea-app-core/ruletemplates/ruletemplates.cpp +++ b/libnymea-app-core/ruletemplates/ruletemplates.cpp @@ -8,6 +8,7 @@ #include "types/ruleactionparam.h" #include "types/ruleactionparams.h" +#include "devicesproxy.h" #include #include @@ -167,6 +168,8 @@ QVariant RuleTemplates::data(const QModelIndex &index, int role) const switch (role) { case RoleDescription: return m_list.at(index.row())->description(); + case RoleInterfaces: + return m_list.at(index.row())->interfaces(); } return QVariant(); } @@ -175,6 +178,7 @@ QHash RuleTemplates::roleNames() const { QHash roles; roles.insert(RoleDescription, "description"); + roles.insert(RoleInterfaces, "interfaces"); return roles; } @@ -193,40 +197,38 @@ bool RuleTemplatesFilterModel::filterAcceptsRow(int source_row, const QModelInde return false; } RuleTemplate *t = m_ruleTemplates->get(source_row); -// qDebug() << "Checking interface" << t->description() << t->interfaceName() << "for usage with:" << m_filterInterfaceNames; +// qDebug() << "Checking interface" << t->description() << t->interfaces() << "for usage with:" << m_filterInterfaceNames; + + + // Make sure we have a device to be used with any of the template's interfaces + if (m_filterDevicesProxy) { + foreach (const QString &toBeFound, t->interfaces()) { + bool found = false; + for (int i = 0; i < m_filterDevicesProxy->rowCount(); i++) { +// qDebug() << "Checking device:" << m_filterDevicesProxy->get(i)->deviceClass()->interfaces(); + if (m_filterDevicesProxy->get(i)->deviceClass()->interfaces().contains(toBeFound)) { + found = true; + break; + } + } + if (!found) { + qDebug() << "Filtering out" << t->description() << "because required devices are not provided in filter proxy"; + return false; + } + } + } + if (!m_filterInterfaceNames.isEmpty()) { - if (!m_filterInterfaceNames.contains(t->interfaceName())) { + bool found = false; + foreach (const QString toBeFound, m_filterInterfaceNames) { + if (t->interfaces().contains(toBeFound)) { + found = true; + break; + } + } + if (!found) { return false; } -// bool found = false; -// for (int i = 0; i < t->eventDescriptorTemplates()->rowCount(); i++) { -// if (m_filterInterfaceNames.contains(t->eventDescriptorTemplates()->get(i)->interfaceName())) { -// found = true; -// break; -// } -// } -// if (!found && t->stateEvaluatorTemplate() && stateEvaluatorTemplateContainsInterface(t->stateEvaluatorTemplate(), m_filterInterfaceNames)) { -// found = true; -// } -// if (!found) { -// for (int i = 0; i < t->ruleActionTemplates()->rowCount(); i++) { -// if (m_filterInterfaceNames.contains(t->ruleActionTemplates()->get(i)->interfaceName())) { -// found = true; -// break; -// } -// } -// } -// if (!found) { -// for (int i = 0; i < t->ruleExitActionTemplates()->rowCount(); i++) { -// if (m_filterInterfaceNames.contains(t->ruleExitActionTemplates()->get(i)->interfaceName())) { -// found = true; -// break; -// } -// } -// } -// if (!found) { -// return false; -// } } return true; } diff --git a/libnymea-app-core/ruletemplates/ruletemplates.h b/libnymea-app-core/ruletemplates/ruletemplates.h index 4ef180a5..d327847d 100644 --- a/libnymea-app-core/ruletemplates/ruletemplates.h +++ b/libnymea-app-core/ruletemplates/ruletemplates.h @@ -5,6 +5,7 @@ class RuleTemplate; class StateEvaluatorTemplate; +class DevicesProxy; class RuleTemplates : public QAbstractListModel { @@ -12,7 +13,8 @@ class RuleTemplates : public QAbstractListModel Q_PROPERTY(int count READ rowCount NOTIFY countChanged) public: enum Roles { - RoleDescription + RoleDescription, + RoleInterfaces }; Q_ENUM(Roles) @@ -39,12 +41,16 @@ class RuleTemplatesFilterModel: public QSortFilterProxyModel Q_PROPERTY(int count READ rowCount NOTIFY countChanged) Q_PROPERTY(RuleTemplates* ruleTemplates READ ruleTemplates WRITE setRuleTemplates NOTIFY ruleTemplatesChanged) Q_PROPERTY(QStringList filterInterfaceNames READ filterInterfaceNames WRITE setFilterInterfaceNames NOTIFY filterInterfaceNamesChanged) + Q_PROPERTY(DevicesProxy* filterByDevices READ filterByDevices WRITE setFilterByDevices NOTIFY filterByDevicesChanged) + public: RuleTemplatesFilterModel(QObject *parent = nullptr): QSortFilterProxyModel(parent) {} RuleTemplates* ruleTemplates() const { return m_ruleTemplates; } void setRuleTemplates(RuleTemplates* ruleTemplates) { if (m_ruleTemplates != ruleTemplates) { m_ruleTemplates = ruleTemplates; setSourceModel(ruleTemplates); emit ruleTemplatesChanged(); invalidateFilter(); emit countChanged();}} QStringList filterInterfaceNames() const { return m_filterInterfaceNames; } void setFilterInterfaceNames(const QStringList &filterInterfaceNames) { if (m_filterInterfaceNames != filterInterfaceNames) { m_filterInterfaceNames = filterInterfaceNames; emit filterInterfaceNamesChanged(); invalidateFilter(); emit countChanged(); }} + DevicesProxy* filterByDevices() const { return m_filterDevicesProxy; } + void setFilterByDevices(DevicesProxy* filterDevicesProxy) {if (m_filterDevicesProxy != filterDevicesProxy) { m_filterDevicesProxy = filterDevicesProxy; emit filterByDevicesChanged(); invalidateFilter(); }} Q_INVOKABLE RuleTemplate* get(int index) { if (index < 0 || index >= rowCount()) { return nullptr; @@ -57,10 +63,12 @@ protected: signals: void ruleTemplatesChanged(); void filterInterfaceNamesChanged(); + void filterByDevicesChanged(); void countChanged(); private: RuleTemplates* m_ruleTemplates = nullptr; QStringList m_filterInterfaceNames; + DevicesProxy* m_filterDevicesProxy = nullptr; }; #endif // RULETEMPLATES_H diff --git a/libnymea-app-core/ruletemplates/stateevaluatortemplate.cpp b/libnymea-app-core/ruletemplates/stateevaluatortemplate.cpp index 7ecd0cca..4217c27b 100644 --- a/libnymea-app-core/ruletemplates/stateevaluatortemplate.cpp +++ b/libnymea-app-core/ruletemplates/stateevaluatortemplate.cpp @@ -23,3 +23,19 @@ StateEvaluatorTemplates *StateEvaluatorTemplate::childEvaluatorTemplates() const { return m_childEvaluatorTemplates; } + +QStringList StateEvaluatorTemplate::interfaces() const +{ + QStringList ret; + + if (m_stateDescriptorTemplate) { + ret.append(stateDescriptorTemplate()->interfaceName()); + } + + for (int i = 0; i < m_childEvaluatorTemplates->rowCount(); i++) { + ret.append(m_childEvaluatorTemplates->get(i)->interfaces()); + } + + ret.removeDuplicates(); + return ret; +} diff --git a/libnymea-app-core/ruletemplates/stateevaluatortemplate.h b/libnymea-app-core/ruletemplates/stateevaluatortemplate.h index 22b02c51..855e773c 100644 --- a/libnymea-app-core/ruletemplates/stateevaluatortemplate.h +++ b/libnymea-app-core/ruletemplates/stateevaluatortemplate.h @@ -13,6 +13,7 @@ class StateEvaluatorTemplate : public QObject Q_PROPERTY(StateDescriptorTemplate* stateDescriptorTemplate READ stateDescriptorTemplate CONSTANT) Q_PROPERTY(StateOperator stateOperator READ stateOperator CONSTANT) Q_PROPERTY(StateEvaluatorTemplates* childEvaluatorTemplates READ childEvaluatorTemplates CONSTANT) + Q_PROPERTY(QStringList interfaces READ interfaces CONSTANT) public: enum StateOperator { @@ -26,6 +27,7 @@ public: StateDescriptorTemplate* stateDescriptorTemplate() const; StateOperator stateOperator() const; StateEvaluatorTemplates* childEvaluatorTemplates() const; + QStringList interfaces() const; private: StateDescriptorTemplate* m_stateDescriptorTemplate = nullptr; diff --git a/libnymea-common/types/paramdescriptors.cpp b/libnymea-common/types/paramdescriptors.cpp index fd0df0ee..4ecadd1a 100644 --- a/libnymea-common/types/paramdescriptors.cpp +++ b/libnymea-common/types/paramdescriptors.cpp @@ -1,6 +1,8 @@ #include "paramdescriptors.h" #include "paramdescriptor.h" +#include + ParamDescriptors::ParamDescriptors(QObject *parent) : QAbstractListModel(parent) { @@ -99,6 +101,30 @@ void ParamDescriptors::clear() emit countChanged(); } +ParamDescriptor *ParamDescriptors::getParamDescriptor(const QString ¶mTypeId) const +{ + qDebug() << "getParamDescriptor" << paramTypeId; + for (int i = 0; i < m_list.count(); i++) { + qDebug() << "have param descriptor:" << m_list.at(i)->paramTypeId(); + if (m_list.at(i)->paramTypeId() == paramTypeId) { + return m_list.at(i); + } + } + return nullptr; +} + +ParamDescriptor *ParamDescriptors::getParamDescriptorByName(const QString ¶mName) const +{ + qDebug() << "getParamDescriptorByName" << paramName; + for (int i = 0; i < m_list.count(); i++) { + qDebug() << "have param descriptor:" << m_list.at(i)->paramName(); + if (m_list.at(i)->paramName() == paramName) { + return m_list.at(i); + } + } + return nullptr; +} + bool ParamDescriptors::operator==(ParamDescriptors *other) const { if (rowCount() != other->rowCount()) { diff --git a/libnymea-common/types/paramdescriptors.h b/libnymea-common/types/paramdescriptors.h index 07406243..cc076ab6 100644 --- a/libnymea-common/types/paramdescriptors.h +++ b/libnymea-common/types/paramdescriptors.h @@ -42,6 +42,9 @@ public: Q_INVOKABLE void setParamDescriptorByName(const QString ¶mName, const QVariant &value, ValueOperator operatorType); Q_INVOKABLE void clear(); + Q_INVOKABLE ParamDescriptor *getParamDescriptor(const QString ¶mTypeId) const; + Q_INVOKABLE ParamDescriptor *getParamDescriptorByName(const QString ¶mName) const; + bool operator==(ParamDescriptors *other) const; signals: diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index e01cf871..d9575e88 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -194,5 +194,6 @@ ui/components/NavigationPad.qml ui/components/KeypadButton.qml ui/devicepages/DoorbellDevicePage.qml + ui/magic/NewMagicPage.qml diff --git a/nymea-app/ruletemplates/buttontemplates.json b/nymea-app/ruletemplates/buttontemplates.json index 94922a72..cbf81f2a 100644 --- a/nymea-app/ruletemplates/buttontemplates.json +++ b/nymea-app/ruletemplates/buttontemplates.json @@ -1,31 +1,15 @@ { "templates": [ { - "interfaceName": "simplemultibutton", - "description": "Switch a light", - "ruleNameTemplate": "%0 switches %1", + "description": "Turn on a light", + "ruleNameTemplate": "%0 turns on %1", "eventDescriptorTemplates": [ { - "interfaceName": "simplemultibutton", + "interfaceName": "button", "interfaceEvent": "pressed", - "selectionId": 0, - "params": [ - { - "name": "buttonName" - } - ] + "selectionId": 0 } ], - "stateEvaluatorTemplate": { - "stateDescriptorTemplate": { - "interfaceName": "light", - "interfaceState": "power", - "operator": "ValueOperatorEquals", - "value": false, - "selectionId": 1, - "selectionMode": "SelectionModeDevice" - } - }, "ruleActionTemplates": [ { "interfaceName": "light", @@ -39,8 +23,19 @@ } ] } + ] + }, + { + "description": "Turn off a light", + "ruleNameTemplate": "%0 turns off %1", + "eventDescriptorTemplates": [ + { + "interfaceName": "button", + "interfaceEvent": "pressed", + "selectionId": 0 + } ], - "ruleExitActionTemplates": [ + "ruleActionTemplates": [ { "interfaceName": "light", "interfaceAction": "power", @@ -56,12 +51,11 @@ ] }, { - "interfaceName": "simplebutton", "description": "Switch a light", "ruleNameTemplate": "%0 switches %1", "eventDescriptorTemplates": [ { - "interfaceName": "simplebutton", + "interfaceName": "button", "interfaceEvent": "pressed", "selectionId": 0 } @@ -106,44 +100,12 @@ ] }, { - "interfaceName": "simplemultibutton", + "interfaceName": "button", "description": "Turn off all lights", "ruleNameTemplate": "Turn off everything with %0", "eventDescriptorTemplates": [ { - "interfaceName": "simplemultibutton", - "interfaceEvent": "pressed", - "selectionId": 0, - "selectionMode": "SelectionModeDevice", - "params": [ - { - "name": "buttonName" - } - ] - } - ], - "ruleActionTemplates": [ - { - "interfaceName": "light", - "interfaceAction": "power", - "selectionId": 1, - "selectionMode": "SelectionModeInterface", - "params": [ - { - "name": "power", - "value": false - } - ] - } - ] - }, - { - "interfaceName": "simplebutton", - "description": "Turn off all lights", - "ruleNameTemplate": "Turn off everything with %0", - "eventDescriptorTemplates": [ - { - "interfaceName": "simplebutton", + "interfaceName": "button", "interfaceEvent": "pressed", "selectionId": 0, "selectionMode": "SelectionModeDevice" diff --git a/nymea-app/ruletemplates/daylightsensor.json b/nymea-app/ruletemplates/daylightsensor.json index 4d1f251d..f9dd8a51 100644 --- a/nymea-app/ruletemplates/daylightsensor.json +++ b/nymea-app/ruletemplates/daylightsensor.json @@ -15,7 +15,7 @@ }, "ruleActionTemplates": [ { - "interfaceName": "power", + "interfaceName": "light", "interfaceAction": "power", "selectionId": 1, "params": [ @@ -28,7 +28,7 @@ ], "ruleExitActionTemplates": [ { - "interfaceName": "power", + "interfaceName": "light", "interfaceAction": "power", "selectionId": 1, "params": [ @@ -43,7 +43,7 @@ { "interfaceName": "daylightsensor", "description": "Turn on a light when it gets dark outside.", - "ruleNameTemplate": "Turn on %1 it gets dark outside (%0).", + "ruleNameTemplate": "Turn on %1 when it gets dark outside (%0).", "eventDescriptorTemplates": [ { "interfaceName": "daylightsensor", diff --git a/nymea-app/ruletemplates/doorbellruletemplates.json b/nymea-app/ruletemplates/doorbellruletemplates.json index d29fb2a9..71c84244 100644 --- a/nymea-app/ruletemplates/doorbellruletemplates.json +++ b/nymea-app/ruletemplates/doorbellruletemplates.json @@ -1,7 +1,6 @@ { "templates": [ { - "interfaceName": "doorbell", "description": "Alert on doorbell ring", "ruleNameTemplate": "Alert %1 when someone is at %0", "eventDescriptorTemplates": [ diff --git a/nymea-app/ruletemplates/lighttemplates.json b/nymea-app/ruletemplates/lighttemplates.json index 633aeb52..bfd870e4 100644 --- a/nymea-app/ruletemplates/lighttemplates.json +++ b/nymea-app/ruletemplates/lighttemplates.json @@ -1,85 +1,3 @@ { - "templates": [ - { - "interfaceName": "power", - "description": "Turn on while its dark outside.", - "ruleNameTemplate": "Turn on %1 while it's dark outside", - "stateEvaluatorTemplate": { - "stateDescriptorTemplate": { - "interfaceName": "daylightsensor", - "interfaceState": "daylight", - "selectionId": 0, - "operator": "ValueOperatorEquals", - "value": false - } - }, - "ruleActionTemplates": [ - { - "interfaceName": "power", - "interfaceAction": "power", - "selectionId": 1, - "params": [ - { - "name": "power", - "value": "true" - } - ] - } - ], - "ruleExitActionTemplates": [ - { - "interfaceName": "power", - "interfaceAction": "power", - "selectionId": 1, - "params": [ - { - "name": "power", - "value": "false" - } - ] - } - ] - }, - { - "interfaceName": "power", - "description": "Turn on while someone is around.", - "ruleNameTemplate": "Turn on %1 while %0 is present", - "stateEvaluatorTemplate": { - "stateDescriptorTemplate": { - "interfaceName": "presencesensor", - "interfaceState": "isPresent", - "selectionId": 0, - "operator": "ValueOperatorEquals", - "value": true - } - }, - "ruleActionTemplates": [ - { - "interfaceName": "power", - "interfaceAction": "power", - "selectionId": 1, - "params": [ - { - "name": "power", - "value": "true" - } - ] - } - ], - "ruleExitActionTemplates": [ - { - "interfaceName": "power", - "interfaceAction": "power", - "selectionId": 1, - "params": [ - { - "name": "power", - "value": "false" - } - ] - } - ] - } - ] } diff --git a/nymea-app/ruletemplates/notificationtemplates.json b/nymea-app/ruletemplates/notificationtemplates.json index 7e123001..71560b16 100644 --- a/nymea-app/ruletemplates/notificationtemplates.json +++ b/nymea-app/ruletemplates/notificationtemplates.json @@ -1,8 +1,7 @@ { "templates": [ { - "interfaceName": "notifications", - "description": "Notify me when a device runs out of battery", + "description": "Notify me when a device runs out of battery.", "ruleNameTemplate": "Low battery alert for %0", "stateEvaluatorTemplate": { "stateDescriptorTemplate": { @@ -33,8 +32,38 @@ ] }, { - "interfaceName": "notifications", - "description": "Notify me when a thing gets disconnected", + "description": "Notify me when something runs dry.", + "ruleNameTemplate": "Notify %1 when %0 runs dry", + "stateEvaluatorTemplate": { + "stateDescriptorTemplate": { + "interfaceName": "moisturesensor", + "interfaceState": "moisture", + "operator": "ValueOperatorLess", + "selectionId": 0, + "value": 20 + } + }, + "ruleActionTemplates": [ + { + "interfaceName": "notifications", + "interfaceAction": "notify", + "selectionId": 1, + "selectionMode": "SelectionModeDevice", + "params": [ + { + "name": "title", + "value": "Water alert" + }, + { + "name": "body", + "value": "%0 runs dry" + } + ] + } + ] + }, + { + "description": "Notify me when a thing gets disconnected.", "ruleNameTemplate": "Disconnect alert for %0", "stateEvaluatorTemplate": { "stateDescriptorTemplate": { @@ -67,7 +96,7 @@ }, { "interfaceName": "notifications", - "description": "Notify me when a thing connects", + "description": "Notify me when a thing connects.", "ruleNameTemplate": "Connection notification for %0", "stateEvaluatorTemplate": { "stateDescriptorTemplate": { @@ -88,7 +117,7 @@ "params": [ { "name": "title", - "value": "Thin connected" + "value": "Thing connected" }, { "name": "body", diff --git a/nymea-app/ruletemplates/presencesensortemplates.json b/nymea-app/ruletemplates/presencesensortemplates.json index 14cccfd3..87461153 100644 --- a/nymea-app/ruletemplates/presencesensortemplates.json +++ b/nymea-app/ruletemplates/presencesensortemplates.json @@ -2,8 +2,8 @@ "templates": [ { "interfaceName": "presencesensor", - "description": "Turn on something while this device is present...", - "ruleNameTemplate": "Turn on %1 while %0 is present", + "description": "Turn on something while being present.", + "ruleNameTemplate": "Turn on %1 while %0 reports presence", "stateEvaluatorTemplate": { "stateDescriptorTemplate": { "interfaceName": "presencesensor", @@ -42,8 +42,8 @@ }, { "interfaceName": "presencesensor", - "description": "Turn off something when this device leaves...", - "ruleNameTemplate": "Turn off %1 when %0 leaves", + "description": "Turn off something when leaving.", + "ruleNameTemplate": "Turn off %1 when %0 reports leaving", "eventDescriptorTemplates": [ { "interfaceName": "presencesensor", @@ -74,8 +74,8 @@ }, { "interfaceName": "presencesensor", - "description": "Turn off everything when this device leaves...", - "ruleNameTemplate": "Turn off everything when %0 leaves", + "description": "Turn off everything when leaving.", + "ruleNameTemplate": "Turn off everything when %0 reports leaving", "eventDescriptorTemplates": [ { "interfaceName": "presencesensor", @@ -107,8 +107,8 @@ }, { "interfaceName": "presencesensor", - "description": "Turn off all lights when this device leaves...", - "ruleNameTemplate": "Turn off all lights when %0 leaves", + "description": "Turn off all lights when leaving.", + "ruleNameTemplate": "Turn off all lights when %0 reports leaving", "eventDescriptorTemplates": [ { "interfaceName": "presencesensor", @@ -140,8 +140,8 @@ }, { "interfaceName": "presencesensor", - "description": "Turn on something when this device arrives...", - "ruleNameTemplate": "Turn on %1 when %0 arrives", + "description": "Turn on something when arriving.", + "ruleNameTemplate": "Turn on %1 when %0 reports arriving", "eventDescriptorTemplates": [ { "interfaceName": "presencesensor", diff --git a/nymea-app/ruletemplates/template.json b/nymea-app/ruletemplates/template.json index b3079327..7d8306dc 100644 --- a/nymea-app/ruletemplates/template.json +++ b/nymea-app/ruletemplates/template.json @@ -1,7 +1,6 @@ { "templates": [ { - "interfaceName": "", "description": "", "ruleNameTemplate": "%0 ...", "eventDescriptorTemplates": [ // optional diff --git a/nymea-app/ui/MagicPage.qml b/nymea-app/ui/MagicPage.qml index 049b44aa..59e84387 100644 --- a/nymea-app/ui/MagicPage.qml +++ b/nymea-app/ui/MagicPage.qml @@ -18,7 +18,32 @@ Page { } } + RuleTemplatesFilterModel { + id: ruleTemplatesModel + ruleTemplates: RuleTemplates {} + readonly property var deviceClass: device ? engine.deviceManager.deviceClasses.getDeviceClass(root.device.deviceClassId) : null + filterByDevices: DevicesProxy { engine: _engine } + filterInterfaceNames: deviceClass ? deviceClass.interfaces : [] + } + function addRule() { + if (ruleTemplatesModel.count > 0) { + d.editRulePage = pageStack.push(Qt.resolvedUrl("magic/NewThingMagicPage.qml")) + d.editRulePage.done.connect(function() { + print("add rule done") + pageStack.pop(root); + }) + + d.editRulePage.manualCreation.connect(function() { + pageStack.pop(root); + manualAddRule(); + }) + } else { + manualAddRule(); + } + } + + function manualAddRule() { var newRule = engine.ruleManager.createNewRule(); d.editRulePage = pageStack.push(Qt.resolvedUrl("magic/EditRulePage.qml"), {rule: newRule }); d.editRulePage.StackView.onRemoved.connect(function() { @@ -41,20 +66,19 @@ Page { Connections { target: engine.ruleManager onAddRuleReply: { - d.editRulePage.busy = false; if (ruleError == "RuleErrorNoError") { // print("should tag rule now:", d.editRulePage.rule.id, d.editRulePage.ruleIcon, d.editRulePage.ruleColor) - engine.tagsManager.tagRule(ruleId, "color", d.editRulePage.ruleColor) - engine.tagsManager.tagRule(ruleId, "icon", d.editRulePage.ruleIcon) - pageStack.pop(); +// engine.tagsManager.tagRule(ruleId, "color", d.editRulePage.ruleColor) +// engine.tagsManager.tagRule(ruleId, "icon", d.editRulePage.ruleIcon) + pageStack.pop(root); } else { var popup = errorDialog.createObject(app, {errorCode: ruleError }) popup.open(); } + d.editRulePage.busy = false; } onEditRuleReply: { - d.editRulePage.busy = false; if (ruleError == "RuleErrorNoError") { // print("should tag rule now:", d.editRulePage.ruleIcon, d.editRulePage.ruleColor) engine.tagsManager.tagRule(d.editRulePage.rule.id, "color", d.editRulePage.ruleColor) @@ -64,6 +88,7 @@ Page { var popup = errorDialog.createObject(app, {errorCode: ruleError }) popup.open(); } + d.editRulePage.busy = false; } } diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index 98273935..1ee43b5d 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -147,6 +147,7 @@ ApplicationWindow { case "light": case "colorlight": case "dimmablelight": + case "colortemperaturelight": return Qt.resolvedUrl("images/light-on.svg") case "sensor": return Qt.resolvedUrl("images/sensors.svg") @@ -232,6 +233,8 @@ ApplicationWindow { return Qt.resolvedUrl("images/notification.svg") case "connectable": return Qt.resolvedUrl("images/stock_link.svg") + case "power": + return Qt.resolvedUrl("images/system-shutdown.svg") default: console.warn("InterfaceToIcon: Unhandled interface", name) } @@ -293,6 +296,17 @@ ApplicationWindow { return qsTr("doorbell") case "alert": return qsTr("alert") + case "simplemultibutton": + case "simplebutton": + return qsTr("button") + case "accesscotrol": + return qsTr("access control") + case "smartmeter": + case "smartmeterproducer": + case "smartmeterconsumer": + case "extendedsmartmeterproducer": + case "extendedsmartmeterconsumer": + return qsTr("smart meter"); default: console.warn("Unhandled interfaceToDisplayName:", name) } diff --git a/nymea-app/ui/magic/DeviceRulesPage.qml b/nymea-app/ui/magic/DeviceRulesPage.qml index 3ca49bca..2c4ee00b 100644 --- a/nymea-app/ui/magic/DeviceRulesPage.qml +++ b/nymea-app/ui/magic/DeviceRulesPage.qml @@ -27,6 +27,7 @@ Page { id: ruleTemplatesModel ruleTemplates: RuleTemplates {} readonly property var deviceClass: device ? engine.deviceManager.deviceClasses.getDeviceClass(root.device.deviceClassId) : null + filterByDevices: DevicesProxy { engine: _engine } filterInterfaceNames: deviceClass ? deviceClass.interfaces : [] } diff --git a/nymea-app/ui/magic/NewMagicPage.qml b/nymea-app/ui/magic/NewMagicPage.qml new file mode 100644 index 00000000..e1de9220 --- /dev/null +++ b/nymea-app/ui/magic/NewMagicPage.qml @@ -0,0 +1,37 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import Nymea 1.0 +import "../components" + +Page { + id: root + + + header: NymeaHeader { + text: qsTr("New magic") + onBackPressed: pageStack.pop() + } + + RuleTemplates { + id: ruleTemplates + } + + ColumnLayout { + anchors.fill: parent + + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + + model: RuleTemplatesFilterModel { + ruleTemplates: ruleTemplates + } + + delegate: NymeaListItemDelegate { + width: parent.width + text: model.description + } + } + } +} diff --git a/nymea-app/ui/magic/NewThingMagicPage.qml b/nymea-app/ui/magic/NewThingMagicPage.qml index d22ef5fa..eb9f5287 100644 --- a/nymea-app/ui/magic/NewThingMagicPage.qml +++ b/nymea-app/ui/magic/NewThingMagicPage.qml @@ -7,8 +7,8 @@ import "../components" Page { id: root - property var device: null - readonly property var deviceClass: device ? engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null + property Device device: null + readonly property DeviceClass deviceClass: device ? device.deviceClass : null property bool busy: false signal done(); @@ -27,7 +27,7 @@ Page { property var selectedInterfaces: ({}) function fillRuleFromTemplate(rule, ruleTemplate) { - print("Filling rule") + print("***** Filling rule") // Fill in all EventDescriptors for (var i = rule.eventDescriptors.count; i < ruleTemplate.eventDescriptorTemplates.count; i++) { @@ -36,17 +36,20 @@ Page { // If we already have a thing selected for this selectionIndex, use that if (eventDescriptorTemplate.selectionId in selectedThings) { var device = engine.deviceManager.devices.getDevice(selectedThings[eventDescriptorTemplate.selectionId]); + print("Already have a device for selectionId", eventDescriptorTemplate.selectionId, ":", device.name) createEventDescriptor(rule, ruleTemplate, device, eventDescriptorTemplate) return; } // Ok, we didn't pick a thing for this selectionId before. Did we already use the one we opened this page from? - if (!deviceIsUsed(root.device.id) && root.deviceClass.interfaces.indexOf(eventDescriptorTemplate.interfaceName) >= 0 && eventDescriptorTemplate.interfaceName === ruleTemplate.interfaceName) { + if (root.device && !deviceIsUsed(root.device.id) && root.deviceClass.interfaces.indexOf(eventDescriptorTemplate.interfaceName) >= 0) { + print("Root device is matching and not used. Using for selectionId", eventDescriptorTemplate.selectionId, ":", root.device.name) selectedThings[eventDescriptorTemplate.selectionId] = root.device.id; createEventDescriptor(rule, ruleTemplate, root.device, eventDescriptorTemplate) return; } // We need to pick a thing + print("We need to pick a new device for selectionId", eventDescriptorTemplate.selectionId) var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [eventDescriptorTemplate.interfaceName]}); page.thingSelected.connect(function(device) { selectedThings[eventDescriptorTemplate.selectionId] = device.id; @@ -59,6 +62,7 @@ Page { // Fill in StateEvaluator if (ruleTemplate.stateEvaluatorTemplate !== null) { + print("RuleFromTemplate: Filling stateEvaluator") if (rule.stateEvaluator === null) { var stateEvaluator = rule.createStateEvaluator(); rule.setStateEvaluator(stateEvaluator); @@ -73,6 +77,7 @@ Page { for (var i = rule.actions.count; i < ruleTemplate.ruleActionTemplates.count; i++) { var ruleActionTemplate = ruleTemplate.ruleActionTemplates.get(i); + print("RuleFromTemplate: Filling ruleAction:", ruleActionTemplate.interfaceName, ruleActionTemplate.interfaceAction, ruleActionTemplate.selectionId) if (ruleActionTemplate.selectionMode === RuleActionTemplate.SelectionModeInterface) { // TODO: Implement blacklist for interface based actions @@ -92,19 +97,21 @@ Page { // Did we pick a thing for this index before? if (ruleActionTemplate.selectionId in selectedThings) { var device = engine.deviceManager.devices.getDevice(selectedThings[ruleActionTemplate.selectionId]); + print("Already have a device for selectionId", ruleActionTemplate.selectionId, ":", device.name) createRuleAction(rule, ruleTemplate, rule.actions, device, ruleActionTemplate) return; } // Did we already use the thing we opened this page from? - if (!deviceIsUsed(root.device.id) && root.deviceClass.interfaces.indexOf(ruleActionTemplate.interfaceName) >= 0 && ruleActionTemplate.interfaceName === ruleTemplate.interfaceName) { + if (root.device && !deviceIsUsed(root.device.id) && root.deviceClass.interfaces.indexOf(ruleActionTemplate.interfaceName) >= 0) { + print("Root device is matching and not used. Using for selectionId", ruleActionTemplate.selectionId, ":", root.device.name) selectedThings[ruleActionTemplate.selectionId] = root.device.id; createRuleAction(rule, ruleTemplate, rule.actions, root.device, ruleActionTemplate) return; } // Ok, we need to pick a thing - print("Need to select a thing") +// print("Need to select a thing.") var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [ruleActionTemplate.interfaceName]}); page.thingSelected.connect(function(device) { selectedThings[ruleActionTemplate.selectionId] = device.id; @@ -142,7 +149,7 @@ Page { } // Did we already use the thing we opened this page from? - if (!deviceIsUsed(root.device.id) && root.deviceClass.interfaces.indexOf(ruleExitActionTemplate.interfaceName) >= 0 && ruleExitActionTemplate.interfaceName === ruleTemplate.interfaceName) { + if (root.device && !deviceIsUsed(root.device.id) && root.deviceClass.interfaces.indexOf(ruleExitActionTemplate.interfaceName) >= 0) { selectedThings[ruleExitActionTemplate.selectionId] = root.device.id; createRuleAction(rule, ruleTemplate, rule.exitActions, root.device, ruleExitActionTemplate); return; @@ -194,6 +201,7 @@ Page { print("Rule complete!") engine.ruleManager.addRule(rule); rule.destroy(); + print("emitting done") root.done(); } @@ -224,7 +232,7 @@ Page { fillRuleFromTemplate(rule, ruleTemplate); return true; } - if (!deviceIsUsed(root.device.id) && root.deviceClass.interfaces.indexOf(stateEvaluatorTemplate.stateDescriptorTemplate.interfaceName) >= 0 && stateEvaluatorTemplate.stateDescriptorTemplate.interfaceName === ruleTemplate.interfaceName) { + if (root.device && !deviceIsUsed(root.device.id) && root.deviceClass.interfaces.indexOf(stateEvaluatorTemplate.stateDescriptorTemplate.interfaceName) >= 0) { stateEvaluator.stateDescriptor.deviceId = root.device.id; stateEvaluator.stateDescriptor.stateTypeId = root.deviceClass.stateTypes.findByName(stateEvaluatorTemplate.stateDescriptorTemplate.interfaceState).id stateEvaluator.stateDescriptor.valueOperator = stateEvaluatorTemplate.stateDescriptorTemplate.valueOperator; @@ -272,15 +280,20 @@ Page { var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); eventDescriptor.eventTypeId = deviceClass.eventTypes.findByName(eventDescriptorTemplate.interfaceEvent).id var needsParams = false; - for (var j = 0; j < eventDescriptorTemplate.paramDescriptors.count; j++) { - var paramDescriptorTemplate = eventDescriptorTemplate.paramDescriptors.get(j); - if (paramDescriptorTemplate.value !== undefined) { - print("Adding operator:", paramDescriptorTemplate.operatorType) + + var eventType = deviceClass.eventTypes.getEventType(eventDescriptor.eventTypeId); + for (var j = 0; j < eventType.paramTypes.count; j++) { + var paramType = eventType.paramTypes.get(j); + var paramDescriptorTemplate = eventDescriptorTemplate.paramDescriptors.getParamDescriptorByName(paramType.name) + // has the template a value for this? If so, set it, otherwise flag as needsParams + print("template:", paramType.id, eventDescriptorTemplate.paramDescriptors.count) + if (paramDescriptorTemplate && paramDescriptorTemplate.value !== undefined) { eventDescriptor.paramDescriptors.setParamDescriptorByName(paramDescriptorTemplate.paramName, paramDescriptorTemplate.value, paramDescriptorTemplate.operatorType); } else { needsParams = true; } } + if (needsParams) { var page = pageStack.push(Qt.resolvedUrl("SelectEventDescriptorParamsPage.qml"), { eventDescriptor: eventDescriptor }) page.completed.connect(function() { @@ -373,7 +386,7 @@ Page { } header: NymeaHeader { - text: qsTr("New magic") + text: root.device ? qsTr("New magic for %1").arg(root.device.name) : qsTr("New magic") onBackPressed: root.done() } @@ -382,14 +395,20 @@ Page { ListView { Layout.fillWidth: true Layout.fillHeight: true + clip: true model: RuleTemplatesFilterModel { id: ruleTemplatesModel ruleTemplates: RuleTemplates {} - filterInterfaceNames: root.deviceClass ? root.deviceClass.interfaces : [] + filterByDevices: DevicesProxy { + engine: _engine + } + + filterInterfaceNames: root.device ? root.device.deviceClass.interfaces : [] } delegate: NymeaListItemDelegate { width: parent.width text: model.description + iconName: app.interfacesToIcon(model.interfaces) onClicked: { var ruleTemplate = ruleTemplatesModel.get(index); diff --git a/nymea-app/ui/mainviews/DevicesPageDelegate.qml b/nymea-app/ui/mainviews/DevicesPageDelegate.qml index 105f3fa9..1345cb0a 100644 --- a/nymea-app/ui/mainviews/DevicesPageDelegate.qml +++ b/nymea-app/ui/mainviews/DevicesPageDelegate.qml @@ -58,6 +58,7 @@ MainPageTile { if (model.name === "uncategorized") { pageStack.push(Qt.resolvedUrl("../devicelistpages/" + page), {hiddenInterfaces: app.supportedInterfaces}) } else { + print(" Date: Sat, 31 Aug 2019 02:26:25 +0200 Subject: [PATCH 3/5] Align template punctuation --- .../ruletemplates/accesscontroltemplates.json | 3 --- nymea-app/ruletemplates/buttontemplates.json | 1 - nymea-app/ruletemplates/daylightsensor.json | 13 +++++-------- .../ruletemplates/notificationtemplates.json | 9 ++++----- .../ruletemplates/presencesensortemplates.json | 15 +++++---------- nymea-app/ruletemplates/smartmetertemplates.json | 6 ++---- 6 files changed, 16 insertions(+), 31 deletions(-) diff --git a/nymea-app/ruletemplates/accesscontroltemplates.json b/nymea-app/ruletemplates/accesscontroltemplates.json index 715891ac..8216429b 100644 --- a/nymea-app/ruletemplates/accesscontroltemplates.json +++ b/nymea-app/ruletemplates/accesscontroltemplates.json @@ -1,7 +1,6 @@ { "templates": [ { - "interfaceName": "accesscontrol", "description": "Alert me on denied access attempts", "ruleNameTemplate": "Denied access attempt on %0", "eventDescriptorTemplates": [ @@ -30,7 +29,6 @@ ] }, { - "interfaceName": "accesscontrol", "description": "Notify my about access", "ruleNameTemplate": "Access granted on %0", "eventDescriptorTemplates": [ @@ -59,7 +57,6 @@ ] }, { - "interfaceName": "useraccesscontrol", "description": "Notify my about user access", "ruleNameTemplate": "Access granted to user on %0", "eventDescriptorTemplates": [ diff --git a/nymea-app/ruletemplates/buttontemplates.json b/nymea-app/ruletemplates/buttontemplates.json index cbf81f2a..16a147d7 100644 --- a/nymea-app/ruletemplates/buttontemplates.json +++ b/nymea-app/ruletemplates/buttontemplates.json @@ -100,7 +100,6 @@ ] }, { - "interfaceName": "button", "description": "Turn off all lights", "ruleNameTemplate": "Turn off everything with %0", "eventDescriptorTemplates": [ diff --git a/nymea-app/ruletemplates/daylightsensor.json b/nymea-app/ruletemplates/daylightsensor.json index f9dd8a51..e41f7ae4 100644 --- a/nymea-app/ruletemplates/daylightsensor.json +++ b/nymea-app/ruletemplates/daylightsensor.json @@ -1,8 +1,7 @@ { "templates": [ { - "interfaceName": "daylightsensor", - "description": "Turn on a light while it's dark outside.", + "description": "Turn on a light while it's dark outside", "ruleNameTemplate": "Turn on %1 while it's dark outside", "stateEvaluatorTemplate": { "stateDescriptorTemplate": { @@ -41,9 +40,8 @@ ] }, { - "interfaceName": "daylightsensor", - "description": "Turn on a light when it gets dark outside.", - "ruleNameTemplate": "Turn on %1 when it gets dark outside (%0).", + "description": "Turn on a light when it gets dark outside", + "ruleNameTemplate": "Turn on %1 when it gets dark outside (%0)", "eventDescriptorTemplates": [ { "interfaceName": "daylightsensor", @@ -73,9 +71,8 @@ ] }, { - "interfaceName": "daylightsensor", - "description": "Turn on all lights when it gets dark outside.", - "ruleNameTemplate": "Turn on all lights when it gets dark outside.", + "description": "Turn on all lights when it gets dark outside", + "ruleNameTemplate": "Turn on all lights when it gets dark outside", "eventDescriptorTemplates": [ { "interfaceName": "daylightsensor", diff --git a/nymea-app/ruletemplates/notificationtemplates.json b/nymea-app/ruletemplates/notificationtemplates.json index 71560b16..6feacebb 100644 --- a/nymea-app/ruletemplates/notificationtemplates.json +++ b/nymea-app/ruletemplates/notificationtemplates.json @@ -1,7 +1,7 @@ { "templates": [ { - "description": "Notify me when a device runs out of battery.", + "description": "Notify me when a device runs out of battery", "ruleNameTemplate": "Low battery alert for %0", "stateEvaluatorTemplate": { "stateDescriptorTemplate": { @@ -32,7 +32,7 @@ ] }, { - "description": "Notify me when something runs dry.", + "description": "Notify me when something runs dry", "ruleNameTemplate": "Notify %1 when %0 runs dry", "stateEvaluatorTemplate": { "stateDescriptorTemplate": { @@ -63,7 +63,7 @@ ] }, { - "description": "Notify me when a thing gets disconnected.", + "description": "Notify me when a thing gets disconnected", "ruleNameTemplate": "Disconnect alert for %0", "stateEvaluatorTemplate": { "stateDescriptorTemplate": { @@ -95,8 +95,7 @@ ] }, { - "interfaceName": "notifications", - "description": "Notify me when a thing connects.", + "description": "Notify me when a thing connects", "ruleNameTemplate": "Connection notification for %0", "stateEvaluatorTemplate": { "stateDescriptorTemplate": { diff --git a/nymea-app/ruletemplates/presencesensortemplates.json b/nymea-app/ruletemplates/presencesensortemplates.json index 87461153..4a143baf 100644 --- a/nymea-app/ruletemplates/presencesensortemplates.json +++ b/nymea-app/ruletemplates/presencesensortemplates.json @@ -1,8 +1,7 @@ { "templates": [ { - "interfaceName": "presencesensor", - "description": "Turn on something while being present.", + "description": "Turn on something while being present", "ruleNameTemplate": "Turn on %1 while %0 reports presence", "stateEvaluatorTemplate": { "stateDescriptorTemplate": { @@ -41,8 +40,7 @@ ] }, { - "interfaceName": "presencesensor", - "description": "Turn off something when leaving.", + "description": "Turn off something when leaving", "ruleNameTemplate": "Turn off %1 when %0 reports leaving", "eventDescriptorTemplates": [ { @@ -73,8 +71,7 @@ ] }, { - "interfaceName": "presencesensor", - "description": "Turn off everything when leaving.", + "description": "Turn off everything when leaving", "ruleNameTemplate": "Turn off everything when %0 reports leaving", "eventDescriptorTemplates": [ { @@ -106,8 +103,7 @@ ] }, { - "interfaceName": "presencesensor", - "description": "Turn off all lights when leaving.", + "description": "Turn off all lights when leaving", "ruleNameTemplate": "Turn off all lights when %0 reports leaving", "eventDescriptorTemplates": [ { @@ -139,8 +135,7 @@ ] }, { - "interfaceName": "presencesensor", - "description": "Turn on something when arriving.", + "description": "Turn on something when arriving", "ruleNameTemplate": "Turn on %1 when %0 reports arriving", "eventDescriptorTemplates": [ { diff --git a/nymea-app/ruletemplates/smartmetertemplates.json b/nymea-app/ruletemplates/smartmetertemplates.json index 99485521..dc7c0078 100644 --- a/nymea-app/ruletemplates/smartmetertemplates.json +++ b/nymea-app/ruletemplates/smartmetertemplates.json @@ -1,8 +1,7 @@ { "templates": [ { - "interfaceName": "extendedsmartmeterproducer", - "description": "Charge my car while producing energy.", + "description": "Charge my car while producing energy", "ruleNameTemplate": "Smart car charging", "stateEvaluatorTemplate": { "stateDescriptorTemplate": { @@ -41,8 +40,7 @@ ] }, { - "interfaceName": "extendedsmartmeterproducer", - "description": "Turn on heating while producing energy.", + "description": "Turn on heating while producing energy", "ruleNameTemplate": "Smart heating", "stateEvaluatorTemplate": { "stateDescriptorTemplate": { From 01277053f92a4e660dc04179c5d0979803e44259 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 31 Aug 2019 04:13:30 +0200 Subject: [PATCH 4/5] add thermostat and media rule templates --- .../ruletemplates/ruletemplates.cpp | 49 ++++++++----- .../ruletemplates/ruletemplates.h | 5 ++ libnymea-common/types/stateevaluators.h | 2 +- nymea-app/ruletemplates.qrc | 3 +- nymea-app/ruletemplates/lighttemplates.json | 3 - nymea-app/ruletemplates/mediatemplates.json | 69 +++++++++++++++++++ .../ruletemplates/thermostattemplates.json | 38 ++++++++++ nymea-app/ui/Nymea.qml | 3 + nymea-app/ui/magic/NewThingMagicPage.qml | 6 +- 9 files changed, 154 insertions(+), 24 deletions(-) delete mode 100644 nymea-app/ruletemplates/lighttemplates.json create mode 100644 nymea-app/ruletemplates/mediatemplates.json create mode 100644 nymea-app/ruletemplates/thermostattemplates.json diff --git a/libnymea-app-core/ruletemplates/ruletemplates.cpp b/libnymea-app-core/ruletemplates/ruletemplates.cpp index 10a26e8b..6ec4fd20 100644 --- a/libnymea-app-core/ruletemplates/ruletemplates.cpp +++ b/libnymea-app-core/ruletemplates/ruletemplates.cpp @@ -21,10 +21,7 @@ RuleTemplates::RuleTemplates(QObject *parent) : QAbstractListModel(parent) RuleTemplate* t; EventDescriptorTemplate* evt; ParamDescriptor* evpt; - StateEvaluatorTemplate* set; RuleActionTemplate* rat; - RuleActionTemplate* reat; // exit - RuleActionParamTemplate* rapt; RuleActionParamTemplates* rapts; QDir ruleTemplatesDir(":/ruletemplates"); @@ -77,20 +74,7 @@ RuleTemplates::RuleTemplates(QObject *parent) : QAbstractListModel(parent) // StateEvaluatorTemplate if (ruleTemplate.contains("stateEvaluatorTemplate")) { - QVariantMap stateEvaluatorTemplate = ruleTemplate.value("stateEvaluatorTemplate").toMap(); - QVariantMap stateDescriptorTemplate = stateEvaluatorTemplate.value("stateDescriptorTemplate").toMap(); - QMetaEnum selectionModeEnum = QMetaEnum::fromType(); - QMetaEnum operatorEnum = QMetaEnum::fromType(); - set = new StateEvaluatorTemplate( - new StateDescriptorTemplate( - stateDescriptorTemplate.value("interfaceName").toString(), - stateDescriptorTemplate.value("interfaceState").toString(), - stateDescriptorTemplate.value("selectionId").toInt(), - static_cast(selectionModeEnum.keyToValue(stateDescriptorTemplate.value("selectionMode", "SelectionModeAny").toByteArray().data())), - static_cast(operatorEnum.keyToValue(stateDescriptorTemplate.value("operator").toByteArray().data())), - stateDescriptorTemplate.value("value"))); - t->setStateEvaluatorTemplate(set); - // TODO: Child evaluators not supported yet + t->setStateEvaluatorTemplate(loadStateEvaluatorTemplate(ruleTemplate.value("stateEvaluatorTemplate").toMap())); } // RuleActionTemplates @@ -190,6 +174,35 @@ RuleTemplate *RuleTemplates::get(int index) const return m_list.at(index); } +StateEvaluatorTemplate *RuleTemplates::loadStateEvaluatorTemplate(const QVariantMap &stateEvaluatorTemplate) const +{ + QVariantMap stateDescriptorTemplate = stateEvaluatorTemplate.value("stateDescriptorTemplate").toMap(); + QMetaEnum selectionModeEnum = QMetaEnum::fromType(); + QMetaEnum stateOperatorEnum = QMetaEnum::fromType(); + QMetaEnum valueOperatorEnum = QMetaEnum::fromType(); + StateEvaluatorTemplate::StateOperator stateOperator = StateEvaluatorTemplate::StateOperatorAnd; + if (stateEvaluatorTemplate.contains("stateOperatorTemplate")) { + stateOperator = static_cast(stateOperatorEnum.keyToValue(stateEvaluatorTemplate.value("stateOperatorTemplate").toByteArray().data())); + } + + StateEvaluatorTemplate *set = new StateEvaluatorTemplate( + new StateDescriptorTemplate( + stateDescriptorTemplate.value("interfaceName").toString(), + stateDescriptorTemplate.value("interfaceState").toString(), + stateDescriptorTemplate.value("selectionId").toInt(), + static_cast(selectionModeEnum.keyToValue(stateDescriptorTemplate.value("selectionMode", "SelectionModeAny").toByteArray().data())), + static_cast(valueOperatorEnum.keyToValue(stateDescriptorTemplate.value("operator").toByteArray().data())), + stateDescriptorTemplate.value("value")), + stateOperator + ); + foreach (const QVariant &childVariant, stateEvaluatorTemplate.value("childEvaluatorTemplates").toList()) { + QVariantMap childMap = childVariant.toMap(); + set->childEvaluatorTemplates()->addStateEvaluatorTemplate(loadStateEvaluatorTemplate(childMap.value("stateEvaluatorTemplate").toMap())); + } + + return set; +} + bool RuleTemplatesFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { Q_UNUSED(source_parent) @@ -212,7 +225,7 @@ bool RuleTemplatesFilterModel::filterAcceptsRow(int source_row, const QModelInde } } if (!found) { - qDebug() << "Filtering out" << t->description() << "because required devices are not provided in filter proxy"; + qDebug() << "Filtering out" << t->description() << "because required no device in the provided filter proxy implements" << toBeFound; return false; } } diff --git a/libnymea-app-core/ruletemplates/ruletemplates.h b/libnymea-app-core/ruletemplates/ruletemplates.h index d327847d..a7777716 100644 --- a/libnymea-app-core/ruletemplates/ruletemplates.h +++ b/libnymea-app-core/ruletemplates/ruletemplates.h @@ -28,6 +28,9 @@ public: signals: void countChanged(); +private: + StateEvaluatorTemplate* loadStateEvaluatorTemplate(const QVariantMap &stateEvaluatorTemplate) const; + private: QList m_list; @@ -65,6 +68,8 @@ signals: void filterInterfaceNamesChanged(); void filterByDevicesChanged(); void countChanged(); + + private: RuleTemplates* m_ruleTemplates = nullptr; QStringList m_filterInterfaceNames; diff --git a/libnymea-common/types/stateevaluators.h b/libnymea-common/types/stateevaluators.h index 07c52e09..fbb15301 100644 --- a/libnymea-common/types/stateevaluators.h +++ b/libnymea-common/types/stateevaluators.h @@ -17,7 +17,7 @@ public: QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; - void addStateEvaluator(StateEvaluator* stateEvaluator); + Q_INVOKABLE void addStateEvaluator(StateEvaluator* stateEvaluator); Q_INVOKABLE StateEvaluator* get(int index) const; // Caller takes ownership, is responsible for deleting diff --git a/nymea-app/ruletemplates.qrc b/nymea-app/ruletemplates.qrc index 115382d3..e86cba88 100644 --- a/nymea-app/ruletemplates.qrc +++ b/nymea-app/ruletemplates.qrc @@ -6,7 +6,8 @@ ruletemplates/smartmetertemplates.json ruletemplates/presencesensortemplates.json ruletemplates/daylightsensor.json - ruletemplates/lighttemplates.json + ruletemplates/thermostattemplates.json + ruletemplates/mediatemplates.json ruletemplates/doorbellruletemplates.json diff --git a/nymea-app/ruletemplates/lighttemplates.json b/nymea-app/ruletemplates/lighttemplates.json deleted file mode 100644 index bfd870e4..00000000 --- a/nymea-app/ruletemplates/lighttemplates.json +++ /dev/null @@ -1,3 +0,0 @@ -{ -} - diff --git a/nymea-app/ruletemplates/mediatemplates.json b/nymea-app/ruletemplates/mediatemplates.json new file mode 100644 index 00000000..bd17f1cd --- /dev/null +++ b/nymea-app/ruletemplates/mediatemplates.json @@ -0,0 +1,69 @@ +{ + "templates": [ + { + "description": "Dim light while watching TV", + "ruleNameTemplate": "%0 dims %1 for movie time", + "stateEvaluatorTemplate": { + "stateDescriptorTemplate": { + "interfaceName": "mediaplayer", + "interfaceState": "playbackStatus", + "selectionId": 0, + "operator": "ValueOperatorEquals", + "value": "Playing" + }, + "stateOperatorTemplate": "StateOperatorAnd", + "childEvaluatorTemplates": [ + { + "stateEvaluatorTemplate": { + "stateDescriptorTemplate": { + "interfaceName": "mediaplayer", + "interfaceState": "playerType", + "selectionId": 0, + "operator": "ValueOperatorEquals", + "value": "video" + } + } + } + ] + }, + "ruleActionTemplates": [ + { + "interfaceName": "dimmablelight", + "interfaceAction": "power", + "selectionId": 1, + "params": [ + { + "name": "power", + "value": false + } + ] + } + ], + "ruleExitActionTemplates": [ + { + "interfaceName": "dimmablelight", + "interfaceAction": "power", + "selectionId": 1, + "params": [ + { + "name": "power", + "value": true + } + ] + }, + { + "interfaceName": "dimmablelight", + "interfaceAction": "brightness", + "selectionId": 1, + "params": [ + { + "name": "brightness", + "value": "50" + } + ] + } + ] + } + ] +} + diff --git a/nymea-app/ruletemplates/thermostattemplates.json b/nymea-app/ruletemplates/thermostattemplates.json new file mode 100644 index 00000000..252462d1 --- /dev/null +++ b/nymea-app/ruletemplates/thermostattemplates.json @@ -0,0 +1,38 @@ +{ + "templates": [ + { + "description": "Set temperature while I'm home", + "ruleNameTemplate": "Set temperature while I'm home", + "stateEvaluatorTemplate": { + "stateDescriptorTemplate": { + "interfaceName": "presencesensor", + "interfaceState": "isPresent", + "selectionId": 0, + "operator": "ValueOperatorEquals", + "value": true + } + }, + "ruleActionTemplates": [ + { + "interfaceName": "thermostat", + "interfaceAction": "targetTemperature", + "selectionId": 1 + } + ], + "ruleExitActionTemplates": [ + { + "interfaceName": "thermostat", + "interfaceAction": "targetTemperature", + "selectionId": 1, + "params": [ + { + "name": "targetTemperature", + "value": "16" + } + ] + } + ] + } + ] +} + diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index 1ee43b5d..7e03e682 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -277,6 +277,9 @@ ApplicationWindow { function interfaceToDisplayName(name) { switch (name) { case "light": + case "dimmablelight": + case "colorlight": + case "colortemperaturelight": return qsTr("light") case "button": return "button"; diff --git a/nymea-app/ui/magic/NewThingMagicPage.qml b/nymea-app/ui/magic/NewThingMagicPage.qml index eb9f5287..02fdbf41 100644 --- a/nymea-app/ui/magic/NewThingMagicPage.qml +++ b/nymea-app/ui/magic/NewThingMagicPage.qml @@ -265,10 +265,14 @@ Page { return true; } stateEvaluator.stateOperator = stateEvaluatorTemplate.stateOperator; + print("Added stateOperator", stateEvaluator.stateOperator) if (stateEvaluatorTemplate.childEvaluatorTemplates.count > stateEvaluator.childEvaluators.count) { + print("Adding more child evaluators. Have:", stateEvaluator.childEvaluators.count, "need:", stateEvaluatorTemplate.childEvaluatorTemplates.count) + print("getting", stateEvaluator.childEvaluators.count) + print("getting", stateEvaluatorTemplate.childEvaluatorTemplates.get(stateEvaluator.childEvaluators.count)) var childEvaluator = rule.createStateEvaluator(); - var more = fillStateEvaluatorFromTemplate(rule, ruleTemplate, childEvaluator, stateEvaluatorTemplate.childEvaluatorTemplates.get(stateEvaluator.childEvaluators.count)) stateEvaluator.childEvaluators.addStateEvaluator(childEvaluator); + var more = fillStateEvaluatorFromTemplate(rule, ruleTemplate, childEvaluator, stateEvaluatorTemplate.childEvaluatorTemplates.get(stateEvaluator.childEvaluators.count-1)) return more; } return false; From 9d4697d7965fca080d1d7c5361b6ae96c70f6a62 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 31 Aug 2019 22:08:22 +0200 Subject: [PATCH 5/5] Make rule templates translatable --- .../ruletemplates/ruletemplates.cpp | 9 +- messages.sh | 45 +++ nymea-app/main.cpp | 2 +- nymea-app/nymea-app.pro | 11 +- nymea-app/ruletemplates/messages.h | 54 +++ nymea-app/translations/nymea-app-de.ts | 307 ++++++++++++++++ nymea-app/translations/nymea-app-en_US.ts | 307 ++++++++++++++++ nymea-app/translations/nymea-app-ko.ts | 332 +++++++++++++++++- nymea-app/ui/Nymea.qml | 4 + 9 files changed, 1051 insertions(+), 20 deletions(-) create mode 100755 messages.sh create mode 100644 nymea-app/ruletemplates/messages.h diff --git a/libnymea-app-core/ruletemplates/ruletemplates.cpp b/libnymea-app-core/ruletemplates/ruletemplates.cpp index 6ec4fd20..6c0b345e 100644 --- a/libnymea-app-core/ruletemplates/ruletemplates.cpp +++ b/libnymea-app-core/ruletemplates/ruletemplates.cpp @@ -14,6 +14,7 @@ #include #include #include +#include RuleTemplates::RuleTemplates(QObject *parent) : QAbstractListModel(parent) { @@ -44,7 +45,13 @@ RuleTemplates::RuleTemplates(QObject *parent) : QAbstractListModel(parent) QVariantMap ruleTemplate = ruleTemplateVariant.toMap(); // RuleTemplate base - t = new RuleTemplate(ruleTemplate.value("interfaceName").toString(), ruleTemplate.value("description").toString(), ruleTemplate.value("ruleNameTemplate").toString(), this); + QString descriptionContext = QString("description for %0").arg(QFileInfo(templateFile).baseName()); + QString nameTemplateContext = QString("ruleNameTemplate for %0").arg(QFileInfo(templateFile).baseName()); + t = new RuleTemplate(ruleTemplate.value("interfaceName").toString(), + qApp->translate(descriptionContext.toUtf8(), ruleTemplate.value("description").toByteArray()), + qApp->translate(nameTemplateContext.toUtf8(), ruleTemplate.value("ruleNameTemplate").toByteArray()), + this); + qDebug() << "Loading rule template" << ruleTemplate.value("description").toString() << tr(ruleTemplate.value("description").toByteArray()); // EventDescriptorTemplate foreach (const QVariant &eventDescriptorVariant, ruleTemplate.value("eventDescriptorTemplates").toList()) { diff --git a/messages.sh b/messages.sh new file mode 100755 index 00000000..a6dc4bc7 --- /dev/null +++ b/messages.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Script to update translation files +# Generates ruletemplates/messages.h and calls lupdate on the entire project +# Ideally this would eventually be called in the CI when building the packages +# However, this will break not find all translations when ran with Qt < 5.9 +# so for now this is ran manually all the time and results are committed to the +# repository + +OUT="nymea-app/ruletemplates/messages.h" + +echo "// This file is generated. Update it using ./messages.sh in the root source directory" > $OUT +echo "#include " >> $OUT +echo "const QString translations[] {" >> $OUT + + +for INPUTFILE in `ls nymea-app/ruletemplates/*json`; do + echo "Extracting strings from ruletemplate file $INPUTFILE" + + FILEBASENAME=$(basename -- "$INPUTFILE") + FILEBASENAME="${FILEBASENAME%.*}" + + while IFS= read -r LINE + do + if [[ $LINE == *"\"description\""* ]] || [[ $LINE == *"\"ruleNameTemplate\""* ]]; then + if [ $HASCONTENT -eq 1 ]; then + echo "," >> $OUT + fi + TYPE=`echo $LINE | cut -d ":" -f 1 | sed 's/"//g'` + STRING=`echo $LINE | cut -d ":" -f 2 | sed 's/,$//'` + echo -n "QT_TRANSLATE_NOOP(" >> $OUT + echo -n "\"$TYPE for $FILEBASENAME\", " >> $OUT + echo -n $STRING >> $OUT + echo -n ")" >> $OUT + HASCONTENT=1 + fi + done < "$INPUTFILE" + +done + +echo "" >> $OUT +echo "};" >> $OUT + + +lupdate nymea-app.pro diff --git a/nymea-app/main.cpp b/nymea-app/main.cpp index 6c356838..7c394a40 100644 --- a/nymea-app/main.cpp +++ b/nymea-app/main.cpp @@ -41,7 +41,7 @@ #include "stylecontroller.h" #include "pushnotifications.h" #include "applogcontroller.h" - +#include "ruletemplates/messages.h" QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index 54c05c60..bc4a0a50 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -22,7 +22,8 @@ HEADERS += \ pushnotifications.h \ platformhelper.h \ platformintegration/generic/platformhelpergeneric.h \ - applogcontroller.h + applogcontroller.h \ + ruletemplates/messages.h SOURCES += main.cpp \ platformintegration/generic/raspberrypihelper.cpp \ @@ -130,11 +131,3 @@ BR=$$BRANDING target.path = /usr/bin INSTALLS += target - -DISTFILES += \ - ruletemplates/doorbellruletemplates.json \ - ruletemplates/smartmetertemplates.json \ - ruletemplates/presencesensortemplates.json \ - ruletemplates/daylightsensor.json \ - ruletemplates/lighttemplates.json - diff --git a/nymea-app/ruletemplates/messages.h b/nymea-app/ruletemplates/messages.h new file mode 100644 index 00000000..005eae99 --- /dev/null +++ b/nymea-app/ruletemplates/messages.h @@ -0,0 +1,54 @@ +// This file is generated. Update it using ./messages.sh in the root source directory +#include +const QString translations[] { +QT_TRANSLATE_NOOP("description for accesscontroltemplates", "Alert me on denied access attempts"), +QT_TRANSLATE_NOOP("ruleNameTemplate for accesscontroltemplates", "Denied access attempt on %0"), +QT_TRANSLATE_NOOP("description for accesscontroltemplates", "Notify my about access"), +QT_TRANSLATE_NOOP("ruleNameTemplate for accesscontroltemplates", "Access granted on %0"), +QT_TRANSLATE_NOOP("description for accesscontroltemplates", "Notify my about user access"), +QT_TRANSLATE_NOOP("ruleNameTemplate for accesscontroltemplates", "Access granted to user on %0"), +QT_TRANSLATE_NOOP("description for buttontemplates", "Turn on a light"), +QT_TRANSLATE_NOOP("ruleNameTemplate for buttontemplates", "%0 turns on %1"), +QT_TRANSLATE_NOOP("description for buttontemplates", "Turn off a light"), +QT_TRANSLATE_NOOP("ruleNameTemplate for buttontemplates", "%0 turns off %1"), +QT_TRANSLATE_NOOP("description for buttontemplates", "Switch a light"), +QT_TRANSLATE_NOOP("ruleNameTemplate for buttontemplates", "%0 switches %1"), +QT_TRANSLATE_NOOP("description for buttontemplates", "Turn off all lights"), +QT_TRANSLATE_NOOP("ruleNameTemplate for buttontemplates", "Turn off everything with %0"), +QT_TRANSLATE_NOOP("description for daylightsensor", "Turn on a light while it's dark outside"), +QT_TRANSLATE_NOOP("ruleNameTemplate for daylightsensor", "Turn on %1 while it's dark outside"), +QT_TRANSLATE_NOOP("description for daylightsensor", "Turn on a light when it gets dark outside"), +QT_TRANSLATE_NOOP("ruleNameTemplate for daylightsensor", "Turn on %1 when it gets dark outside (%0)"), +QT_TRANSLATE_NOOP("description for daylightsensor", "Turn on all lights when it gets dark outside"), +QT_TRANSLATE_NOOP("ruleNameTemplate for daylightsensor", "Turn on all lights when it gets dark outside"), +QT_TRANSLATE_NOOP("description for doorbellruletemplates", "Alert on doorbell ring"), +QT_TRANSLATE_NOOP("ruleNameTemplate for doorbellruletemplates", "Alert %1 when someone is at %0"), +QT_TRANSLATE_NOOP("description for mediatemplates", "Dim light while watching TV"), +QT_TRANSLATE_NOOP("ruleNameTemplate for mediatemplates", "%0 dims %1 for movie time"), +QT_TRANSLATE_NOOP("description for notificationtemplates", "Notify me when a device runs out of battery"), +QT_TRANSLATE_NOOP("ruleNameTemplate for notificationtemplates", "Low battery alert for %0"), +QT_TRANSLATE_NOOP("description for notificationtemplates", "Notify me when something runs dry"), +QT_TRANSLATE_NOOP("ruleNameTemplate for notificationtemplates", "Notify %1 when %0 runs dry"), +QT_TRANSLATE_NOOP("description for notificationtemplates", "Notify me when a thing gets disconnected"), +QT_TRANSLATE_NOOP("ruleNameTemplate for notificationtemplates", "Disconnect alert for %0"), +QT_TRANSLATE_NOOP("description for notificationtemplates", "Notify me when a thing connects"), +QT_TRANSLATE_NOOP("ruleNameTemplate for notificationtemplates", "Connection notification for %0"), +QT_TRANSLATE_NOOP("description for presencesensortemplates", "Turn on something while being present"), +QT_TRANSLATE_NOOP("ruleNameTemplate for presencesensortemplates", "Turn on %1 while %0 reports presence"), +QT_TRANSLATE_NOOP("description for presencesensortemplates", "Turn off something when leaving"), +QT_TRANSLATE_NOOP("ruleNameTemplate for presencesensortemplates", "Turn off %1 when %0 reports leaving"), +QT_TRANSLATE_NOOP("description for presencesensortemplates", "Turn off everything when leaving"), +QT_TRANSLATE_NOOP("ruleNameTemplate for presencesensortemplates", "Turn off everything when %0 reports leaving"), +QT_TRANSLATE_NOOP("description for presencesensortemplates", "Turn off all lights when leaving"), +QT_TRANSLATE_NOOP("ruleNameTemplate for presencesensortemplates", "Turn off all lights when %0 reports leaving"), +QT_TRANSLATE_NOOP("description for presencesensortemplates", "Turn on something when arriving"), +QT_TRANSLATE_NOOP("ruleNameTemplate for presencesensortemplates", "Turn on %1 when %0 reports arriving"), +QT_TRANSLATE_NOOP("description for smartmetertemplates", "Charge my car while producing energy"), +QT_TRANSLATE_NOOP("ruleNameTemplate for smartmetertemplates", "Smart car charging"), +QT_TRANSLATE_NOOP("description for smartmetertemplates", "Turn on heating while producing energy"), +QT_TRANSLATE_NOOP("ruleNameTemplate for smartmetertemplates", "Smart heating"), +QT_TRANSLATE_NOOP("description for template", ""), +QT_TRANSLATE_NOOP("ruleNameTemplate for template", "%0 ..."), +QT_TRANSLATE_NOOP("description for thermostattemplates", "Set temperature while I'm home"), +QT_TRANSLATE_NOOP("ruleNameTemplate for thermostattemplates", "Set temperature while I'm home") +}; diff --git a/nymea-app/translations/nymea-app-de.ts b/nymea-app/translations/nymea-app-de.ts index d68bdc48..00a2cd0d 100644 --- a/nymea-app/translations/nymea-app-de.ts +++ b/nymea-app/translations/nymea-app-de.ts @@ -716,6 +716,14 @@ Log in to %1:cloud in order to connect to %1:core systems from anywhere. Melde Dich bei %1:cloud an um von überall aus auf Deine %1:core Systeme zugreifen zu können. + + Failed to connect to the login server. Please mase sure your network connection is working. + Fehler beim verbinden zum Login-Server. Bitte stelle sicher, dass Deine Netzwerkverbindung funktioniert. + + + An unexpected error happened. Please report this isse. Error code: + Ein Unerwartetet Fehler ist aufgetreten. Bitte benachrichtige uns darüber. Fehler code: + CloudSettingsPage @@ -1345,6 +1353,13 @@ %1 installiert + + DoorbellDevicePage + + History + Verlauf + + EditCalendarItemPage @@ -3338,6 +3353,13 @@ Möchtest Du fortfahren? OK + + NewMagicPage + + New magic + Neue Magie + + NewThingMagicPage @@ -3348,6 +3370,10 @@ Möchtest Du fortfahren? Create some magic manually Magie manuell erstellen + + New magic for %1 + Neue Magie für %1 + NewThingPage @@ -3564,6 +3590,34 @@ Möchtest Du fortfahren? presence sensor Anwesenheitssensor + + Doorbells + Türklingeln + + + doorbell + Türklingel + + + alert + Alarm + + + button + Taster + + + access control + Zugangskontrolle + + + smart meter + Smart Meter + + + media player + Medienabspielgerät + NymeaConnection @@ -5005,6 +5059,129 @@ Bitte warte bis diese abgeschlossen ist. Verbinde zu %1:core + + description for accesscontroltemplates + + Alert me on denied access attempts + Benachritige mich bei Fehlgeschlagenen Zutrittsversuchen + + + Notify my about access + Benachrichtige me über Zugänge + + + Notify my about user access + Benachrichtige mich über Benutzerzugang + + + + description for buttontemplates + + Turn on a light + Ein Licht einschalten + + + Turn off a light + Ein Licht ausschalten + + + Switch a light + Ein Licht schalten + + + Turn off all lights + Alle Lichter ausschalten + + + + description for daylightsensor + + Turn on a light while it's dark outside + Ein Licht einschalten während es dunkel ist + + + Turn on a light when it gets dark outside + Ein Licht einschalten wenn es dunkel wird + + + Turn on all lights when it gets dark outside + Alle Lichter einschlalten wenn es dunkel wird + + + + description for doorbellruletemplates + + Alert on doorbell ring + Signal bei Türklingelbetätigung + + + + description for mediatemplates + + Dim light while watching TV + Licht dimmen während TV-Wiedergabe + + + + description for notificationtemplates + + Notify me when a device runs out of battery + Benachrichtige mich wenn sich die Batterie eines Gerätes zu Ende neight + + + Notify me when something runs dry + Benachrichte mich wenn etwas austrocknet + + + Notify me when a thing gets disconnected + Benachrichtuge mich wenn ein Gerät die Verbindung verliert + + + Notify me when a thing connects + Benachrichtige mich wenn sich ein Gerät verbindet + + + + description for presencesensortemplates + + Turn on something while being present + Etwas einschalten während Anwesenheit + + + Turn off something when leaving + Etwas ausschalten beim Verlassen + + + Turn off everything when leaving + Alles ausschalten beim Verlassen + + + Turn off all lights when leaving + Alle Lichter beim Verlassen ausschalten + + + Turn on something when arriving + Etwas einschalten bei Ankunft + + + + description for smartmetertemplates + + Charge my car while producing energy + Fahrzeug laden während Strom erzeugt wird + + + Turn on heating while producing energy + Heizung einschalten während Strom erzeugt wird + + + + description for thermostattemplates + + Set temperature while I'm home + Temperatur einstellen während ich zuhause bin + + main @@ -5064,4 +5241,134 @@ Bitte warte bis diese abgeschlossen ist. OK + + ruleNameTemplate for accesscontroltemplates + + Denied access attempt on %0 + Verweigerter Zutrittsversuch bei %0 + + + Access granted on %0 + Zugang erteikt bei %0 + + + Access granted to user on %0 + Zuggang für Benutzer bei %0 erlaubt + + + + ruleNameTemplate for buttontemplates + + %0 turns on %1 + 0% schaltet %1 ein + + + %0 turns off %1 + %0 schaltet %1 aus + + + %0 switches %1 + %0 schaltet %1 + + + Turn off everything with %0 + Alles ausschalten mit %0 + + + + ruleNameTemplate for daylightsensor + + Turn on %1 while it's dark outside + %1 einschalten während es drausen dunkel ist + + + Turn on %1 when it gets dark outside (%0) + Schalte %1 ein wenn es drausen dunkel wird (%0) + + + Turn on all lights when it gets dark outside + Alle Lichter einschalten wenn es drausen dunkel wird + + + + ruleNameTemplate for doorbellruletemplates + + Alert %1 when someone is at %0 + Signal bei %1 wenn jemand bei %0 ist + + + + ruleNameTemplate for mediatemplates + + %0 dims %1 for movie time + %0 dimmt %1 für Fernsehzeit + + + + ruleNameTemplate for notificationtemplates + + Low battery alert for %0 + Batterieladung kritisch bei %0 + + + Notify %1 when %0 runs dry + Benachrichtige %1 wenn %0 austrocknet + + + Disconnect alert for %0 + %0 wurde getrennt + + + Connection notification for %0 + %0 wurde verbunden + + + + ruleNameTemplate for presencesensortemplates + + Turn on %1 while %0 reports presence + %1 einschalten während %0 Anwesenheit registriert + + + Turn off %1 when %0 reports leaving + %1 ausschalten wenn %0 verlassen wird + + + Turn off everything when %0 reports leaving + Alles ausschalten wenn %0 verlassen wird + + + Turn off all lights when %0 reports leaving + Alle Lichter ausschalten wenn %0 verlassen wird + + + Turn on %1 when %0 reports arriving + %1 einschalten %0 Ankunft registiert + + + + ruleNameTemplate for smartmetertemplates + + Smart car charging + Intelligentes Fahrzeugladen + + + Smart heating + Intelligente Heizung + + + + ruleNameTemplate for template + + %0 ... + + + + + ruleNameTemplate for thermostattemplates + + Set temperature while I'm home + Setze Temperatur während ich zuhause bin + + diff --git a/nymea-app/translations/nymea-app-en_US.ts b/nymea-app/translations/nymea-app-en_US.ts index 2294bf46..5711de6a 100644 --- a/nymea-app/translations/nymea-app-en_US.ts +++ b/nymea-app/translations/nymea-app-en_US.ts @@ -496,6 +496,14 @@ Log in to %1:cloud in order to connect to %1:core systems from anywhere. + + Failed to connect to the login server. Please mase sure your network connection is working. + + + + An unexpected error happened. Please report this isse. Error code: + + CloudSettingsPage @@ -926,6 +934,13 @@ + + DoorbellDevicePage + + History + + + EditCalendarItemPage @@ -2572,6 +2587,13 @@ Please try again. + + NewMagicPage + + New magic + + + NewThingMagicPage @@ -2582,6 +2604,10 @@ Please try again. Create some magic manually + + New magic for %1 + + NewThingPage @@ -2782,6 +2808,34 @@ Please try again. presence sensor + + Doorbells + + + + doorbell + + + + alert + + + + button + + + + access control + + + + smart meter + + + + media player + + NymeaConnection @@ -3792,4 +3846,257 @@ Do you want to proceed? + + description for accesscontroltemplates + + Alert me on denied access attempts + + + + Notify my about access + + + + Notify my about user access + + + + + description for buttontemplates + + Turn on a light + + + + Turn off a light + + + + Switch a light + + + + Turn off all lights + + + + + description for daylightsensor + + Turn on a light while it's dark outside + + + + Turn on a light when it gets dark outside + + + + Turn on all lights when it gets dark outside + + + + + description for doorbellruletemplates + + Alert on doorbell ring + + + + + description for mediatemplates + + Dim light while watching TV + + + + + description for notificationtemplates + + Notify me when a device runs out of battery + + + + Notify me when something runs dry + + + + Notify me when a thing gets disconnected + + + + Notify me when a thing connects + + + + + description for presencesensortemplates + + Turn on something while being present + + + + Turn off something when leaving + + + + Turn off everything when leaving + + + + Turn off all lights when leaving + + + + Turn on something when arriving + + + + + description for smartmetertemplates + + Charge my car while producing energy + + + + Turn on heating while producing energy + + + + + description for thermostattemplates + + Set temperature while I'm home + + + + + ruleNameTemplate for accesscontroltemplates + + Denied access attempt on %0 + + + + Access granted on %0 + + + + Access granted to user on %0 + + + + + ruleNameTemplate for buttontemplates + + %0 turns on %1 + + + + %0 turns off %1 + + + + %0 switches %1 + + + + Turn off everything with %0 + + + + + ruleNameTemplate for daylightsensor + + Turn on %1 while it's dark outside + + + + Turn on %1 when it gets dark outside (%0) + + + + Turn on all lights when it gets dark outside + + + + + ruleNameTemplate for doorbellruletemplates + + Alert %1 when someone is at %0 + + + + + ruleNameTemplate for mediatemplates + + %0 dims %1 for movie time + + + + + ruleNameTemplate for notificationtemplates + + Low battery alert for %0 + + + + Notify %1 when %0 runs dry + + + + Disconnect alert for %0 + + + + Connection notification for %0 + + + + + ruleNameTemplate for presencesensortemplates + + Turn on %1 while %0 reports presence + + + + Turn off %1 when %0 reports leaving + + + + Turn off everything when %0 reports leaving + + + + Turn off all lights when %0 reports leaving + + + + Turn on %1 when %0 reports arriving + + + + + ruleNameTemplate for smartmetertemplates + + Smart car charging + + + + Smart heating + + + + + ruleNameTemplate for template + + %0 ... + + + + + ruleNameTemplate for thermostattemplates + + Set temperature while I'm home + + + diff --git a/nymea-app/translations/nymea-app-ko.ts b/nymea-app/translations/nymea-app-ko.ts index 68ca91ca..b5c2ab40 100644 --- a/nymea-app/translations/nymea-app-ko.ts +++ b/nymea-app/translations/nymea-app-ko.ts @@ -331,13 +331,14 @@ There are %n boxes connected to your cloud There is %n box connected to your cloud. - There are %n boxes connected to your cloud. There are %n boxes connected to your cloud. - %n 상자(장치)가 클라우드에 연결되어 있습니다. - + + %n 상자(장치)가 클라우드에 연결되어 있습니다. + + Cloud login 클라우드 로그인 @@ -480,8 +481,10 @@ There are %n %1:core systems connected to your cloud. - %n %1:코어 시스템이 클라우드에 연결되어 있습니다. - + + %n %1:코어 시스템이 클라우드에 연결되어 있습니다. + + Sorry to see you go. If you log out you won't be able to connect to %1:core systems remotely any more. However, you can come back any time, we'll keep your user account. If you whish to completely delete your account and all the data associated with it, check the box below before hitting ok. If you decide to delete your account, all your personal information will be removed from %1:cloud and cannot be restored. 아쉽군요. 로그아웃하면 더 이상 원격으로 %1:코어 시스템에 연결할 수 없습니다. 하지만 언제든지 돌아올 수 있어요, 사용자 계정은 안전하게 보관 됩니다. 계정 및 관련된 모든 데이터를 완전히 삭제하려면 확인을 누르기 전에 아래의 확인란을 선택하십시오. 계정을 삭제하기로 결정하면 모든 개인 정보가 %1:클라우드에서 제거되므로 복원할 수 없습니다. @@ -490,6 +493,14 @@ Log in to %1:cloud in order to connect to %1:core systems from anywhere. 어디에서나 %1:core 시스템에 연결하려면 %1:cloud에 로그인하십시오. + + Failed to connect to the login server. Please mase sure your network connection is working. + + + + An unexpected error happened. Please report this isse. Error code: + + CloudSettingsPage @@ -920,6 +931,13 @@ %1 열림 + + DoorbellDevicePage + + History + + + EditCalendarItemPage @@ -2245,8 +2263,10 @@ Please try again. %n system update(s) available - %n 시스템 업데이트 사용 가능 - + + %n 시스템 업데이트 사용 가능 + + System settings 시스템 설정 @@ -2570,6 +2590,13 @@ Please try again. WiFi를 비활성화하면 WiFi를 통해 연결된 모든 클라이언트의 연결이 끊어집니다. LAN 케이블이 연결되어 있지 않으면 이 %1 시스템과 더 이상 원격으로 상호 작용할 수 없다는 점에 유의하십시오. + + NewMagicPage + + New magic + 신 마법 + + NewThingMagicPage @@ -2580,6 +2607,10 @@ Please try again. Create some magic manually 수동으로 마법 만들기 + + New magic for %1 + + NewThingPage @@ -2780,6 +2811,34 @@ Please try again. presence sensor 움직임 감지 센서 + + Doorbells + + + + doorbell + + + + alert + + + + button + + + + access control + + + + smart meter + + + + media player + + NymeaConnection @@ -3515,8 +3574,10 @@ Please only use this if you are sure you want this and consider reporting the is %n update(s) available - %n 업데이트 사용 가능 - + + %n 업데이트 사용 가능 + + Check again 다시 확인 @@ -3788,4 +3849,257 @@ Do you want to proceed? %1:코어에 연결 + + description for accesscontroltemplates + + Alert me on denied access attempts + + + + Notify my about access + + + + Notify my about user access + + + + + description for buttontemplates + + Turn on a light + + + + Turn off a light + + + + Switch a light + + + + Turn off all lights + + + + + description for daylightsensor + + Turn on a light while it's dark outside + + + + Turn on a light when it gets dark outside + + + + Turn on all lights when it gets dark outside + + + + + description for doorbellruletemplates + + Alert on doorbell ring + + + + + description for mediatemplates + + Dim light while watching TV + + + + + description for notificationtemplates + + Notify me when a device runs out of battery + + + + Notify me when something runs dry + + + + Notify me when a thing gets disconnected + + + + Notify me when a thing connects + + + + + description for presencesensortemplates + + Turn on something while being present + + + + Turn off something when leaving + + + + Turn off everything when leaving + + + + Turn off all lights when leaving + + + + Turn on something when arriving + + + + + description for smartmetertemplates + + Charge my car while producing energy + + + + Turn on heating while producing energy + + + + + description for thermostattemplates + + Set temperature while I'm home + + + + + ruleNameTemplate for accesscontroltemplates + + Denied access attempt on %0 + + + + Access granted on %0 + + + + Access granted to user on %0 + + + + + ruleNameTemplate for buttontemplates + + %0 turns on %1 + + + + %0 turns off %1 + + + + %0 switches %1 + + + + Turn off everything with %0 + + + + + ruleNameTemplate for daylightsensor + + Turn on %1 while it's dark outside + + + + Turn on %1 when it gets dark outside (%0) + + + + Turn on all lights when it gets dark outside + + + + + ruleNameTemplate for doorbellruletemplates + + Alert %1 when someone is at %0 + + + + + ruleNameTemplate for mediatemplates + + %0 dims %1 for movie time + + + + + ruleNameTemplate for notificationtemplates + + Low battery alert for %0 + + + + Notify %1 when %0 runs dry + + + + Disconnect alert for %0 + + + + Connection notification for %0 + + + + + ruleNameTemplate for presencesensortemplates + + Turn on %1 while %0 reports presence + + + + Turn off %1 when %0 reports leaving + + + + Turn off everything when %0 reports leaving + + + + Turn off all lights when %0 reports leaving + + + + Turn on %1 when %0 reports arriving + + + + + ruleNameTemplate for smartmetertemplates + + Smart car charging + + + + Smart heating + + + + + ruleNameTemplate for template + + %0 ... + + + + + ruleNameTemplate for thermostattemplates + + Set temperature while I'm home + + + diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index 7e03e682..7d9fcc05 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -175,6 +175,7 @@ ApplicationWindow { return Qt.resolvedUrl("images/sensors/closable.svg") case "media": case "mediacontroller": + case "mediaplayer": return Qt.resolvedUrl("images/mediaplayer-app-symbolic.svg") case "powersocket": return Qt.resolvedUrl("images/powersocket.svg") @@ -310,6 +311,9 @@ ApplicationWindow { case "extendedsmartmeterproducer": case "extendedsmartmeterconsumer": return qsTr("smart meter"); + case "media": + case "mediaplayer": + return qsTr("media player") default: console.warn("Unhandled interfaceToDisplayName:", name) }