From 176baa8ede22330b5ab8329cc968e984e413ea44 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 31 Aug 2019 00:12:25 +0200 Subject: [PATCH] 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("