From f41f06537f9ea6bbd2c98a8f3b2fff03e187a3ca Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 24 Sep 2018 23:37:56 +0200 Subject: [PATCH] improve notifications view and rule action templates for it --- libnymea-app-core/connection/awsclient.cpp | 4 +- libnymea-app-core/connection/awsclient.h | 4 +- .../ruletemplates/eventdescriptortemplate.cpp | 8 +- .../ruletemplates/eventdescriptortemplate.h | 4 + .../ruletemplates/ruleactiontemplate.cpp | 6 +- .../ruletemplates/ruleactiontemplate.h | 2 +- .../ruletemplates/ruletemplate.cpp | 8 +- .../ruletemplates/ruletemplate.h | 5 +- .../ruletemplates/ruletemplates.cpp | 190 +++++++++++----- .../ruletemplates/ruletemplates.h | 6 +- .../ruletemplates/statedescriptortemplate.cpp | 8 +- .../ruletemplates/statedescriptortemplate.h | 13 +- libnymea-common/types/param.h | 2 +- libnymea-common/types/ruleactionparam.cpp | 7 + libnymea-common/types/ruleactionparam.h | 1 + nymea-app/nymea-app.pro | 8 +- nymea-app/resources.qrc | 1 + nymea-app/ruletemplates.qrc | 6 + nymea-app/ruletemplates/buttontemplates.json | 169 ++++++++++++++ .../ruletemplates/notificationtemplates.json | 97 ++++++++ nymea-app/ui/Nymea.qml | 10 +- .../ui/customviews/GenericTypeLogView.qml | 2 + .../devicepages/NotificationsDevicePage.qml | 149 ++++++++++++ nymea-app/ui/magic/DeviceRulesPage.qml | 39 ++-- nymea-app/ui/magic/EditRulePage.qml | 31 ++- nymea-app/ui/magic/NewThingMagicPage.qml | 213 ++++++++++-------- nymea-app/ui/magic/SelectThingPage.qml | 19 +- 27 files changed, 820 insertions(+), 192 deletions(-) create mode 100644 nymea-app/ruletemplates.qrc create mode 100644 nymea-app/ruletemplates/buttontemplates.json create mode 100644 nymea-app/ruletemplates/notificationtemplates.json create mode 100644 nymea-app/ui/devicepages/NotificationsDevicePage.qml diff --git a/libnymea-app-core/connection/awsclient.cpp b/libnymea-app-core/connection/awsclient.cpp index 58e05a51..dca966c2 100644 --- a/libnymea-app-core/connection/awsclient.cpp +++ b/libnymea-app-core/connection/awsclient.cpp @@ -127,9 +127,9 @@ QString AWSClient::username() const return m_username; } -QByteArray AWSClient::userId() const +QString AWSClient::userId() const { - return m_identityId; + return m_userId; } AWSDevices *AWSClient::awsDevices() const diff --git a/libnymea-app-core/connection/awsclient.h b/libnymea-app-core/connection/awsclient.h index e202dd19..2f83f57e 100644 --- a/libnymea-app-core/connection/awsclient.h +++ b/libnymea-app-core/connection/awsclient.h @@ -79,7 +79,7 @@ class AWSClient : public QObject Q_PROPERTY(bool isLoggedIn READ isLoggedIn NOTIFY isLoggedInChanged) Q_PROPERTY(QString username READ username NOTIFY isLoggedInChanged) Q_PROPERTY(bool confirmationPending READ confirmationPending NOTIFY confirmationPendingChanged) - Q_PROPERTY(QByteArray userId READ userId NOTIFY isLoggedInChanged) + Q_PROPERTY(QString userId READ userId NOTIFY isLoggedInChanged) Q_PROPERTY(QByteArray idToken READ idToken NOTIFY isLoggedInChanged) Q_PROPERTY(AWSDevices* awsDevices READ awsDevices CONSTANT) @@ -100,7 +100,7 @@ public: bool isLoggedIn() const; QString username() const; - QByteArray userId() const; + QString userId() const; AWSDevices* awsDevices() const; bool confirmationPending() const; diff --git a/libnymea-app-core/ruletemplates/eventdescriptortemplate.cpp b/libnymea-app-core/ruletemplates/eventdescriptortemplate.cpp index 0c3796e6..bf0470d0 100644 --- a/libnymea-app-core/ruletemplates/eventdescriptortemplate.cpp +++ b/libnymea-app-core/ruletemplates/eventdescriptortemplate.cpp @@ -5,7 +5,8 @@ EventDescriptorTemplate::EventDescriptorTemplate(const QString &interfaceName, c m_interfaceName(interfaceName), m_interfaceEvent(interfaceEvent), m_selectionId(selectionId), - m_selectionMode(selectionMode) + m_selectionMode(selectionMode), + m_paramDescriptors(new ParamDescriptors(this)) { } @@ -29,3 +30,8 @@ EventDescriptorTemplate::SelectionMode EventDescriptorTemplate::selectionMode() { return m_selectionMode; } + +ParamDescriptors *EventDescriptorTemplate::paramDescriptors() const +{ + return m_paramDescriptors; +} diff --git a/libnymea-app-core/ruletemplates/eventdescriptortemplate.h b/libnymea-app-core/ruletemplates/eventdescriptortemplate.h index e59734ce..ccbb83fa 100644 --- a/libnymea-app-core/ruletemplates/eventdescriptortemplate.h +++ b/libnymea-app-core/ruletemplates/eventdescriptortemplate.h @@ -2,6 +2,7 @@ #define EVENTDESCRIPTORTEMPLATE_H #include +#include "types/paramdescriptors.h" class EventDescriptorTemplate : public QObject { @@ -10,6 +11,7 @@ class EventDescriptorTemplate : public QObject Q_PROPERTY(QString interfaceEvent READ interfaceEvent CONSTANT) Q_PROPERTY(int selectionId READ selectionId CONSTANT) Q_PROPERTY(SelectionMode selectionMode READ selectionMode CONSTANT) + Q_PROPERTY(ParamDescriptors* paramDescriptors READ paramDescriptors CONSTANT) public: enum SelectionMode { SelectionModeAny, @@ -24,12 +26,14 @@ public: QString interfaceEvent() const; int selectionId() const; SelectionMode selectionMode() const; + ParamDescriptors* paramDescriptors() const; private: QString m_interfaceName; QString m_interfaceEvent; int m_selectionId = 0; SelectionMode m_selectionMode = SelectionModeAny; + ParamDescriptors *m_paramDescriptors = nullptr; }; #include diff --git a/libnymea-app-core/ruletemplates/ruleactiontemplate.cpp b/libnymea-app-core/ruletemplates/ruleactiontemplate.cpp index add78c99..3686c8b1 100644 --- a/libnymea-app-core/ruletemplates/ruleactiontemplate.cpp +++ b/libnymea-app-core/ruletemplates/ruleactiontemplate.cpp @@ -1,15 +1,15 @@ #include "ruleactiontemplate.h" -RuleActionTemplate::RuleActionTemplate(const QString &interfaceName, const QString &interfaceAction, int selectionId, SelectionMode selectionMode, QObject *parent): +RuleActionTemplate::RuleActionTemplate(const QString &interfaceName, const QString &interfaceAction, int selectionId, RuleActionTemplate::SelectionMode selectionMode, RuleActionParams *params, QObject *parent): QObject(parent), m_interfaceName(interfaceName), m_interfaceAction(interfaceAction), m_selectionId(selectionId), m_selectionMode(selectionMode), - m_ruleActionParams(new RuleActionParams(this)) + m_ruleActionParams(params ? params : new RuleActionParams()) { - + m_ruleActionParams->setParent(this); } QString RuleActionTemplate::interfaceName() const diff --git a/libnymea-app-core/ruletemplates/ruleactiontemplate.h b/libnymea-app-core/ruletemplates/ruleactiontemplate.h index 6935a1e8..726f0454 100644 --- a/libnymea-app-core/ruletemplates/ruleactiontemplate.h +++ b/libnymea-app-core/ruletemplates/ruleactiontemplate.h @@ -22,7 +22,7 @@ public: }; Q_ENUM(SelectionMode) - explicit RuleActionTemplate(const QString &interfaceName, const QString &interfaceAction, int selectionId, SelectionMode selectionMode = SelectionModeAny, QObject *parent = nullptr); + explicit RuleActionTemplate(const QString &interfaceName, const QString &interfaceAction, int selectionId, SelectionMode selectionMode = SelectionModeAny, RuleActionParams *params = nullptr, QObject *parent = nullptr); QString interfaceName() const; QString interfaceAction() const; diff --git a/libnymea-app-core/ruletemplates/ruletemplate.cpp b/libnymea-app-core/ruletemplates/ruletemplate.cpp index 5df8a6b1..7255f076 100644 --- a/libnymea-app-core/ruletemplates/ruletemplate.cpp +++ b/libnymea-app-core/ruletemplates/ruletemplate.cpp @@ -3,8 +3,9 @@ #include "stateevaluatortemplate.h" #include "ruleactiontemplate.h" -RuleTemplate::RuleTemplate(const QString &description, const QString &ruleNameTemplate, QObject *parent): +RuleTemplate::RuleTemplate(const QString &interfaceName, const QString &description, const QString &ruleNameTemplate, QObject *parent): QObject(parent), + m_interfaceName(interfaceName), m_description(description), m_ruleNameTemplate(ruleNameTemplate), m_eventDescriptorTemplates(new EventDescriptorTemplates(this)), @@ -13,6 +14,11 @@ RuleTemplate::RuleTemplate(const QString &description, const QString &ruleNameTe { } +QString RuleTemplate::interfaceName() const +{ + return m_interfaceName; +} + QString RuleTemplate::description() const { return m_description; diff --git a/libnymea-app-core/ruletemplates/ruletemplate.h b/libnymea-app-core/ruletemplates/ruletemplate.h index 2e46386b..c9f33b26 100644 --- a/libnymea-app-core/ruletemplates/ruletemplate.h +++ b/libnymea-app-core/ruletemplates/ruletemplate.h @@ -10,6 +10,7 @@ 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(EventDescriptorTemplates* eventDescriptorTemplates READ eventDescriptorTemplates CONSTANT) @@ -18,8 +19,9 @@ class RuleTemplate : public QObject Q_PROPERTY(RuleActionTemplates* ruleExitActionTemplates READ ruleExitActionTemplates CONSTANT) public: - explicit RuleTemplate(const QString &description, const QString &ruleNameTemplate, QObject *parent = nullptr); + explicit RuleTemplate(const QString &interfaceName, const QString &description, const QString &ruleNameTemplate, QObject *parent = nullptr); + QString interfaceName() const; QString description() const; QString ruleNameTemplate() const; @@ -30,6 +32,7 @@ public: RuleActionTemplates* ruleExitActionTemplates() const; private: + QString m_interfaceName; QString m_description; QString m_ruleNameTemplate; EventDescriptorTemplates* m_eventDescriptorTemplates = nullptr; diff --git a/libnymea-app-core/ruletemplates/ruletemplates.cpp b/libnymea-app-core/ruletemplates/ruletemplates.cpp index 92626348..4f0d61f3 100644 --- a/libnymea-app-core/ruletemplates/ruletemplates.cpp +++ b/libnymea-app-core/ruletemplates/ruletemplates.cpp @@ -9,50 +9,121 @@ #include "types/ruleactionparams.h" #include +#include +#include +#include RuleTemplates::RuleTemplates(QObject *parent) : QAbstractListModel(parent) { + RuleTemplate* t; EventDescriptorTemplate* evt; + ParamDescriptor* evpt; StateEvaluatorTemplate* set; RuleActionTemplate* rat; RuleActionTemplate* reat; // exit + RuleActionParams* raps; - t = new RuleTemplate("Switch a light", "%0 switches %1", this); - evt = new EventDescriptorTemplate("button", "pressed", 0, EventDescriptorTemplate::SelectionModeDevice); - t->eventDescriptorTemplates()->addEventDescriptorTemplate(evt); - set = new StateEvaluatorTemplate(new StateDescriptorTemplate("light", "power", 1, StateDescriptorTemplate::ValueOperatorEquals, false)); - t->setStateEvaluatorTemplate(set); - rat = new RuleActionTemplate("light", "power", 1, RuleActionTemplate::SelectionModeDevice); - rat->ruleActionParams()->setRuleActionParamByName("power", true); - t->ruleActionTemplates()->addRuleActionTemplate(rat); - reat = new RuleActionTemplate("light", "power", 1, RuleActionTemplate::SelectionModeDevice); - reat->ruleActionParams()->setRuleActionParamByName("power", false); - t->ruleExitActionTemplates()->addRuleActionTemplate(reat); - m_list.append(t); + QDir ruleTemplatesDir(":/ruletemplates"); - t = new RuleTemplate("Intelligent blinds", "Intelligent blinds %1", this); - set = new StateEvaluatorTemplate(new StateDescriptorTemplate("temperaturesensor", "temperature", 0, StateDescriptorTemplate::ValueOperatorGreater, 20)); - t->setStateEvaluatorTemplate(set); - rat = new RuleActionTemplate("simpleclosable", "close", 1, RuleActionTemplate::SelectionModeDevice); - t->ruleActionTemplates()->addRuleActionTemplate(rat); - reat = new RuleActionTemplate("simpleclosable", "open", 1, RuleActionTemplate::SelectionModeDevice); - t->ruleExitActionTemplates()->addRuleActionTemplate(reat); - m_list.append(t); + foreach (const QString &templateFile, ruleTemplatesDir.entryList({"*.json"})) { + qDebug() << "Loading rule template:" << ruleTemplatesDir.absoluteFilePath(templateFile); + QFile f(ruleTemplatesDir.absoluteFilePath(templateFile)); + if (!f.open(QFile::ReadOnly)) { + qWarning() << "Cannot open rule template file for reading:" << ruleTemplatesDir.absoluteFilePath(templateFile); + continue; + } + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(f.readAll(), &error); + f.close(); + if (error.error != QJsonParseError::NoError) { + qWarning() << "Error reading rule template json from file:" << ruleTemplatesDir.absoluteFilePath(templateFile) << error.offset << error.errorString(); + continue; + } + foreach (const QVariant &ruleTemplateVariant, jsonDoc.toVariant().toMap().value("templates").toList()) { + QVariantMap ruleTemplate = ruleTemplateVariant.toMap(); - t = new RuleTemplate("Leave home - This will turn of everything when you press a button.", "Leave home", this); - evt = new EventDescriptorTemplate("button", "pressed", 0, EventDescriptorTemplate::SelectionModeDevice); - t->eventDescriptorTemplates()->addEventDescriptorTemplate(evt); - rat = new RuleActionTemplate("power", "power", 1, RuleActionTemplate::SelectionModeInterface); - t->ruleActionTemplates()->addRuleActionTemplate(rat); - m_list.append(t); + // RuleTemplate base + t = new RuleTemplate(ruleTemplate.value("interfaceName").toString(), ruleTemplate.value("description").toString(), ruleTemplate.value("ruleNameTemplate").toString(), this); + // EventDescriptorTemplate + foreach (const QVariant &eventDescriptorVariant, ruleTemplate.value("eventDescriptorTemplates").toList()) { + QVariantMap eventDescriptorTemplate = eventDescriptorVariant.toMap(); + evt = new EventDescriptorTemplate( + eventDescriptorTemplate.value("interfaceName").toString(), + eventDescriptorTemplate.value("interfaceEvent").toString(), + eventDescriptorTemplate.value("selectionId").toInt(), + EventDescriptorTemplate::SelectionModeDevice); + foreach (const QVariant &eventDescriptorParamVariant, eventDescriptorTemplate.value("params").toList()) { + QVariantMap eventDescriptorParamTemplate = eventDescriptorParamVariant.toMap(); + evpt = new ParamDescriptor(); + evpt->setParamName(eventDescriptorParamTemplate.value("name").toString()); + if (eventDescriptorParamTemplate.contains("value")) { + evpt->setValue(eventDescriptorParamTemplate.value("value")); + } + evt->paramDescriptors()->addParamDescriptor(evpt); + } + t->eventDescriptorTemplates()->addEventDescriptorTemplate(evt); + } - t = new RuleTemplate("Remind me to water my plant", "Remind me to water my %0 plant", this); - evt = new EventDescriptorTemplate("humiditysensor", "humidity", 0, EventDescriptorTemplate::SelectionModeDevice); - t->eventDescriptorTemplates()->addEventDescriptorTemplate(evt); - m_list.append(t); + // 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 + } + // RuleActionTemplates + foreach (const QVariant &ruleActionVariant, ruleTemplate.value("ruleActionTemplates").toList()) { + QVariantMap ruleActionTemplate = ruleActionVariant.toMap(); + raps = new RuleActionParams(); + foreach (const QVariant &ruleActionParamVariant, ruleActionTemplate.value("params").toList()) { + QVariantMap ruleActionParamTemplate = ruleActionParamVariant.toMap(); + raps->addRuleActionParam(new RuleActionParam(ruleActionParamTemplate.value("name").toString(), ruleActionParamTemplate.value("value"))); + } + QMetaEnum selectionModeEnum = QMetaEnum::fromType(); + rat = new RuleActionTemplate( + ruleActionTemplate.value("interfaceName").toString(), + ruleActionTemplate.value("interfaceAction").toString(), + ruleActionTemplate.value("selectionId").toInt(), + static_cast(selectionModeEnum.keyToValue(ruleActionTemplate.value("selectionMode", "SelectionModeDevice").toByteArray().data())), + raps); + t->ruleActionTemplates()->addRuleActionTemplate(rat); + } + + // RuleExitActionTemplates + foreach (const QVariant &ruleActionVariant, ruleTemplate.value("ruleExitActionTemplates").toList()) { + QVariantMap ruleActionTemplate = ruleActionVariant.toMap(); + raps = new RuleActionParams(); + foreach (const QVariant &ruleActionParamVariant, ruleActionTemplate.value("params").toList()) { + QVariantMap ruleActionParamTemplate = ruleActionParamVariant.toMap(); + raps->addRuleActionParam(new RuleActionParam(ruleActionParamTemplate.value("name").toString(), ruleActionParamTemplate.value("value"))); + } + QMetaEnum selectionModeEnum = QMetaEnum::fromType(); + rat = new RuleActionTemplate( + ruleActionTemplate.value("interfaceName").toString(), + ruleActionTemplate.value("interfaceAction").toString(), + ruleActionTemplate.value("selectionId").toInt(), + static_cast(selectionModeEnum.keyToValue(ruleActionTemplate.value("selectionMode", "SelectionModeDevice").toByteArray().data())), + raps); + t->ruleExitActionTemplates()->addRuleActionTemplate(rat); + } + + qDebug() << "Added rule template:" << t->ruleActionTemplates()->rowCount(); + m_list.append(t); + } + } } int RuleTemplates::rowCount(const QModelIndex &parent) const @@ -92,37 +163,40 @@ bool RuleTemplatesFilterModel::filterAcceptsRow(int source_row, const QModelInde return false; } RuleTemplate *t = m_ruleTemplates->get(source_row); - qDebug() << "---------------" << t->description() << m_filterInterfaceNames; + qDebug() << "Checking interface" << t->description() << t->interfaceName() << "for usage with:" << m_filterInterfaceNames; if (!m_filterInterfaceNames.isEmpty()) { - 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) { + if (!m_filterInterfaceNames.contains(t->interfaceName())) { 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 0a25dd3e..4ef180a5 100644 --- a/libnymea-app-core/ruletemplates/ruletemplates.h +++ b/libnymea-app-core/ruletemplates/ruletemplates.h @@ -36,14 +36,15 @@ private: class RuleTemplatesFilterModel: public QSortFilterProxyModel { Q_OBJECT + 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) 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();}} + 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();} + void setFilterInterfaceNames(const QStringList &filterInterfaceNames) { if (m_filterInterfaceNames != filterInterfaceNames) { m_filterInterfaceNames = filterInterfaceNames; emit filterInterfaceNamesChanged(); invalidateFilter(); emit countChanged(); }} Q_INVOKABLE RuleTemplate* get(int index) { if (index < 0 || index >= rowCount()) { return nullptr; @@ -56,6 +57,7 @@ protected: signals: void ruleTemplatesChanged(); void filterInterfaceNamesChanged(); + void countChanged(); private: RuleTemplates* m_ruleTemplates = nullptr; QStringList m_filterInterfaceNames; diff --git a/libnymea-app-core/ruletemplates/statedescriptortemplate.cpp b/libnymea-app-core/ruletemplates/statedescriptortemplate.cpp index 25872431..405a8ba8 100644 --- a/libnymea-app-core/ruletemplates/statedescriptortemplate.cpp +++ b/libnymea-app-core/ruletemplates/statedescriptortemplate.cpp @@ -1,10 +1,11 @@ #include "statedescriptortemplate.h" -StateDescriptorTemplate::StateDescriptorTemplate(const QString &interfaceName, const QString &interfaceState, int selectionId, StateDescriptorTemplate::ValueOperator valueOperator, const QVariant &value, QObject *parent): +StateDescriptorTemplate::StateDescriptorTemplate(const QString &interfaceName, const QString &interfaceState, int selectionId, StateDescriptorTemplate::SelectionMode selectionMode, StateDescriptorTemplate::ValueOperator valueOperator, const QVariant &value, QObject *parent): QObject(parent), m_interfaceName(interfaceName), m_interfaceState(interfaceState), m_selectionId(selectionId), + m_selectionMode(selectionMode), m_valueOperator(valueOperator), m_value(value) { @@ -26,6 +27,11 @@ int StateDescriptorTemplate::selectionId() const return m_selectionId; } +StateDescriptorTemplate::SelectionMode StateDescriptorTemplate::selectionMode() const +{ + return m_selectionMode; +} + StateDescriptorTemplate::ValueOperator StateDescriptorTemplate::valueOperator() const { return m_valueOperator; diff --git a/libnymea-app-core/ruletemplates/statedescriptortemplate.h b/libnymea-app-core/ruletemplates/statedescriptortemplate.h index b6906c94..bc406ae5 100644 --- a/libnymea-app-core/ruletemplates/statedescriptortemplate.h +++ b/libnymea-app-core/ruletemplates/statedescriptortemplate.h @@ -10,10 +10,17 @@ class StateDescriptorTemplate : public QObject Q_PROPERTY(QString interfaceName READ interfaceName CONSTANT) Q_PROPERTY(QString interfaceState READ interfaceState CONSTANT) Q_PROPERTY(int selectionId READ selectionId CONSTANT) + Q_PROPERTY(SelectionMode selectionMode READ selectionMode CONSTANT) Q_PROPERTY(ValueOperator valueOperator READ valueOperator CONSTANT) Q_PROPERTY(QVariant value READ value CONSTANT) public: + enum SelectionMode { + SelectionModeAny, + SelectionModeDevice, + SelectionModeInterface, + }; + Q_ENUM(SelectionMode) enum ValueOperator { ValueOperatorEquals, ValueOperatorNotEquals, @@ -24,20 +31,22 @@ public: }; Q_ENUM(ValueOperator) - explicit StateDescriptorTemplate(const QString &interfaceName, const QString &interfaceState, int selectionId, ValueOperator valueOperator = ValueOperatorEquals, const QVariant &value = QVariant(), QObject *parent = nullptr); + explicit StateDescriptorTemplate(const QString &interfaceName, const QString &interfaceState, int selectionId, SelectionMode selectionMode, ValueOperator valueOperator = ValueOperatorEquals, const QVariant &value = QVariant(), QObject *parent = nullptr); QString interfaceName() const; QString interfaceState() const; int selectionId() const; + SelectionMode selectionMode() const; ValueOperator valueOperator() const; QVariant value() const; private: QString m_interfaceName; QString m_interfaceState; + int m_selectionId = 0; + SelectionMode m_selectionMode = SelectionModeAny; ValueOperator m_valueOperator = ValueOperatorEquals; QVariant m_value; - int m_selectionId = 0; }; #endif // STATEDESCRIPTORTEMPLATE_H diff --git a/libnymea-common/types/param.h b/libnymea-common/types/param.h index 4e6af188..149c98a8 100644 --- a/libnymea-common/types/param.h +++ b/libnymea-common/types/param.h @@ -34,7 +34,7 @@ class Param : public QObject Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged) public: - Param(const QString ¶mTypeId = QString(), const QVariant &value = QVariant(), QObject *parent = 0); + Param(const QString ¶mTypeId = QString(), const QVariant &value = QVariant(), QObject *parent = nullptr); Param(QObject *parent); QString paramTypeId() const; diff --git a/libnymea-common/types/ruleactionparam.cpp b/libnymea-common/types/ruleactionparam.cpp index a5dd8aca..8214fcbb 100644 --- a/libnymea-common/types/ruleactionparam.cpp +++ b/libnymea-common/types/ruleactionparam.cpp @@ -1,5 +1,12 @@ #include "ruleactionparam.h" +RuleActionParam::RuleActionParam(const QString ¶mName, const QVariant &value, QObject *parent): + Param(parent), + m_paramName(paramName) +{ + setValue(value); +} + RuleActionParam::RuleActionParam(QObject *parent) : Param(parent) { diff --git a/libnymea-common/types/ruleactionparam.h b/libnymea-common/types/ruleactionparam.h index 45bb3c98..0684f594 100644 --- a/libnymea-common/types/ruleactionparam.h +++ b/libnymea-common/types/ruleactionparam.h @@ -14,6 +14,7 @@ class RuleActionParam : public Param Q_PROPERTY(QString eventTypeId READ eventTypeId WRITE setEventTypeId NOTIFY eventTypeIdChanged) Q_PROPERTY(QString eventParamTypeId READ eventParamTypeId WRITE setEventParamTypeId NOTIFY eventParamTypeIdChanged) public: + explicit RuleActionParam(const QString ¶mName, const QVariant &value, QObject *parent = nullptr); explicit RuleActionParam(QObject *parent = nullptr); QString paramName() const; diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index ef3b9a70..2e157f87 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -30,7 +30,8 @@ SOURCES += main.cpp \ OTHER_FILES += $$files(*.qml, true) -RESOURCES += resources.qrc +RESOURCES += resources.qrc \ + ruletemplates.qrc equals(STYLES_PATH, "") { RESOURCES += styles.qrc } else { @@ -121,4 +122,7 @@ target.path = /usr/bin INSTALLS += target DISTFILES += \ - ui/components/BusyOverlay.qml + ui/components/BusyOverlay.qml \ + ui/devicepages/NotificationsDevicePage.qml \ + ruletemplates/buttontemplates.json \ + ruletemplates/notificationtemplates.json diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index c58b97fe..9cd6c08a 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -248,5 +248,6 @@ ui/appsettings/AppSettingsPage.qml ui/appsettings/DeveloperOptionsPage.qml ui/appsettings/CloudLoginPage.qml + ui/devicepages/NotificationsDevicePage.qml diff --git a/nymea-app/ruletemplates.qrc b/nymea-app/ruletemplates.qrc new file mode 100644 index 00000000..ad6c56d1 --- /dev/null +++ b/nymea-app/ruletemplates.qrc @@ -0,0 +1,6 @@ + + + ruletemplates/buttontemplates.json + ruletemplates/notificationtemplates.json + + diff --git a/nymea-app/ruletemplates/buttontemplates.json b/nymea-app/ruletemplates/buttontemplates.json new file mode 100644 index 00000000..94922a72 --- /dev/null +++ b/nymea-app/ruletemplates/buttontemplates.json @@ -0,0 +1,169 @@ +{ + "templates": [ + { + "interfaceName": "simplemultibutton", + "description": "Switch a light", + "ruleNameTemplate": "%0 switches %1", + "eventDescriptorTemplates": [ + { + "interfaceName": "simplemultibutton", + "interfaceEvent": "pressed", + "selectionId": 0, + "params": [ + { + "name": "buttonName" + } + ] + } + ], + "stateEvaluatorTemplate": { + "stateDescriptorTemplate": { + "interfaceName": "light", + "interfaceState": "power", + "operator": "ValueOperatorEquals", + "value": false, + "selectionId": 1, + "selectionMode": "SelectionModeDevice" + } + }, + "ruleActionTemplates": [ + { + "interfaceName": "light", + "interfaceAction": "power", + "selectionId": 1, + "selectionMode": "SelectionModeDevice", + "params": [ + { + "name": "power", + "value": true + } + ] + } + ], + "ruleExitActionTemplates": [ + { + "interfaceName": "light", + "interfaceAction": "power", + "selectionId": 1, + "selectionMode": "SelectionModeDevice", + "params": [ + { + "name": "power", + "value": false + } + ] + } + ] + }, + { + "interfaceName": "simplebutton", + "description": "Switch a light", + "ruleNameTemplate": "%0 switches %1", + "eventDescriptorTemplates": [ + { + "interfaceName": "simplebutton", + "interfaceEvent": "pressed", + "selectionId": 0 + } + ], + "stateEvaluatorTemplate": { + "stateDescriptorTemplate": { + "interfaceName": "light", + "interfaceState": "power", + "operator": "ValueOperatorEquals", + "value": false, + "selectionId": 1, + "selectionMode": "SelectionModeDevice" + } + }, + "ruleActionTemplates": [ + { + "interfaceName": "light", + "interfaceAction": "power", + "selectionId": 1, + "selectionMode": "SelectionModeDevice", + "params": [ + { + "name": "power", + "value": true + } + ] + } + ], + "ruleExitActionTemplates": [ + { + "interfaceName": "light", + "interfaceAction": "power", + "selectionId": 1, + "selectionMode": "SelectionModeDevice", + "params": [ + { + "name": "power", + "value": false + } + ] + } + ] + }, + { + "interfaceName": "simplemultibutton", + "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", + "interfaceEvent": "pressed", + "selectionId": 0, + "selectionMode": "SelectionModeDevice" + } + ], + "ruleActionTemplates": [ + { + "interfaceName": "light", + "interfaceAction": "power", + "selectionId": 1, + "selectionMode": "SelectionModeInterface", + "params": [ + { + "name": "power", + "value": false + } + ] + } + ] + } + ] +} + diff --git a/nymea-app/ruletemplates/notificationtemplates.json b/nymea-app/ruletemplates/notificationtemplates.json new file mode 100644 index 00000000..4d6cb469 --- /dev/null +++ b/nymea-app/ruletemplates/notificationtemplates.json @@ -0,0 +1,97 @@ +{ + "templates": [ + { + "interfaceName": "notifications", + "description": "Notify me when a device runs out of battery", + "ruleNameTemplate": "Low battery alert for %0", + "stateEvaluatorTemplate": { + "stateDescriptorTemplate": { + "interfaceName": "battery", + "interfaceState": "batteryCritical", + "operator": "ValueOperatorEquals", + "value": true, + "selectionId": 0 + } + }, + "ruleActionTemplates": [ + { + "interfaceName": "notifications", + "interfaceAction": "notify", + "selectionId": 1, + "params": [ + { + "name": "title", + "value": "Low battery alert" + }, + { + "name": "body", + "value": "%0 runs out of battery" + } + ] + } + ] + }, + { + "interfaceName": "notifications", + "description": "Notify me when a thing gets disconnected", + "ruleNameTemplate": "Disconnect alert for %0", + "stateEvaluatorTemplate": { + "stateDescriptorTemplate": { + "interfaceName": "connectable", + "interfaceState": "connected", + "selectionId": 0, + "operator": "ValueOperatorEquals", + "value": false + } + }, + "ruleActionTemplates": [ + { + "interfaceName": "notifications", + "interfaceAction": "notify", + "selectionId": 1, + "params": [ + { + "name": "title", + "value": "Disconnect alert" + }, + { + "name": "body", + "value": "%0 has disconnected" + } + ] + } + ] + }, + { + "interfaceName": "notifications", + "description": "Notify me when a thing connects", + "ruleNameTemplate": "Connection notification for %0", + "stateEvaluatorTemplate": { + "stateDescriptorTemplate": { + "interfaceName": "connectable", + "interfaceState": "connected", + "selectionId": 0, + "operator": "ValueOperatorEquals", + "value": true + } + }, + "ruleActionTemplates": [ + { + "interfaceName": "notifications", + "interfaceAction": "notify", + "selectionId": 1, + "params": [ + { + "name": "title", + "value": "Thin connected" + }, + { + "name": "body", + "value": "%0 has connected" + } + ] + } + ] + } + ] +} diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index 791222ab..e78c6866 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -6,8 +6,6 @@ import Qt.labs.settings 1.0 import QtQuick.Window 2.3 import Nymea 1.0 -//import QtFirebase 1.0 - ApplicationWindow { id: app visible: true @@ -347,6 +345,12 @@ ApplicationWindow { return "button"; case "sensor": return qsTr("sensor") + case "battery": + return qsTr("battery powered thing") + case "connectable": + return qsTr("connectable thing") + default: + console.warn("Unhandled interfaceToDisplayName:", name) } } @@ -370,6 +374,8 @@ ApplicationWindow { page = "ShutterDevicePage.qml"; } else if (interfaceList.indexOf("extendedawning") >= 0) { page = "AwningDevicePage.qml"; + } else if (interfaceList.indexOf("notifications") >= 0) { + page = "NotificationsDevicePage.qml"; } else { page = "GenericDevicePage.qml"; } diff --git a/nymea-app/ui/customviews/GenericTypeLogView.qml b/nymea-app/ui/customviews/GenericTypeLogView.qml index ac87052f..351dc66e 100644 --- a/nymea-app/ui/customviews/GenericTypeLogView.qml +++ b/nymea-app/ui/customviews/GenericTypeLogView.qml @@ -13,6 +13,8 @@ Item { property var logsModel: null + property alias delegate: listView.delegate + ColumnLayout { anchors.fill: parent diff --git a/nymea-app/ui/devicepages/NotificationsDevicePage.qml b/nymea-app/ui/devicepages/NotificationsDevicePage.qml new file mode 100644 index 00000000..9bc41041 --- /dev/null +++ b/nymea-app/ui/devicepages/NotificationsDevicePage.qml @@ -0,0 +1,149 @@ +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 + + property bool inputVisible: false + + ColumnLayout { + anchors.fill: parent + + Item { + Layout.fillWidth: true + Layout.preferredHeight: root.inputVisible ? inputColumn.implicitHeight : 0 + Behavior on Layout.preferredHeight { NumberAnimation { duration: 130; easing.type: Easing.InOutQuad } } + + ColumnLayout { + id: inputColumn + anchors { left: parent.left; bottom: parent.bottom; right: parent.right } + + TextField { + id: titleTextField + Layout.fillWidth: true + Layout.topMargin: app.margins + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + placeholderText: qsTr("Title") + } + + TextArea { + id: bodyTextField + Layout.fillWidth: true + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + placeholderText: qsTr("Text") + wrapMode: Text.WordWrap + } + } + } + + + + + Button { + Layout.fillWidth: true + Layout.margins: app.margins + text: !root.inputVisible ? + qsTr("Send a notification") + : titleTextField.displayText.length > 0 ? + qsTr("Send now") + : qsTr("Cancel") + onClicked: { + if (root.inputVisible && titleTextField.displayText.length > 0) { + var actionType = root.deviceClass.actionTypes.findByName("notify") + var params = [] + var titleParam = {} + titleParam["paramTypeId"] = actionType.paramTypes.findByName("title").id + titleParam["value"] = titleTextField.displayText + params.push(titleParam) + var bodyParam = {} + bodyParam["paramTypeId"] = actionType.paramTypes.findByName("body").id + bodyParam["value"] = bodyTextField.text + params.push(bodyParam) + Engine.deviceManager.executeAction(root.device.id, actionType.id, params) + } + root.inputVisible = !root.inputVisible + } + } + + ThinDivider {} + + GenericTypeLogView { + Layout.fillHeight: true + Layout.fillWidth: true + text: qsTr("%1 notifications sent to this device in the last 24 hours.") + + logsModel: LogsModel { + deviceId: root.device.id + live: true + Component.onCompleted: update() + typeIds: [root.deviceClass.actionTypes.findByName("notify").id]; + + } + + delegate: MeaListItemDelegate { + width: parent.width + iconName: "../images/notification.svg" + text: model.value.trim() + subText: Qt.formatDateTime(model.timestamp) + progressive: false + + onClicked: { + print("a", model.value.trim()) + var parts = model.value.trim().split(', ') + print("b", parts) + var popup = detailsPopup.createObject(root, {timestamp: model.timestamp, notificationTitle: parts[1], notificationBody: parts[0]}); + popup.open(); + } + } + } + } + + Component { + id: detailsPopup + MeaDialog { + id: detailsDialog + property string timestamp + property string notificationTitle + property string notificationBody + title: qsTr("Notification details") + Label { + Layout.fillWidth: true + text: qsTr("Date sent") + font.bold: true + } + + Label { + Layout.fillWidth: true + text: Qt.formatDateTime(detailsDialog.timestamp) + } + Label { + Layout.topMargin: app.margins + Layout.fillWidth: true + text: qsTr("Title") + font.bold: true + } + + Label { + Layout.fillWidth: true + text: detailsDialog.notificationTitle + wrapMode: Text.WordWrap + } + Label { + Layout.topMargin: app.margins + Layout.fillWidth: true + text: qsTr("Text") + font.bold: true + } + + Label { + Layout.fillWidth: true + text: detailsDialog.notificationBody + wrapMode: Text.WordWrap + } + } + } +} diff --git a/nymea-app/ui/magic/DeviceRulesPage.qml b/nymea-app/ui/magic/DeviceRulesPage.qml index e313de4c..55588ccb 100644 --- a/nymea-app/ui/magic/DeviceRulesPage.qml +++ b/nymea-app/ui/magic/DeviceRulesPage.qml @@ -23,20 +23,31 @@ Page { } } + RuleTemplatesFilterModel { + id: ruleTemplatesModel + ruleTemplates: RuleTemplates {} + readonly property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(root.device.deviceClassId) : null + filterInterfaceNames: deviceClass ? deviceClass.interfaces : [] + } + // Rule is optional and might be initialized with anything wanted. A new, empty one will be created if null // This Page will take ownership of the rule and delete it eventually. function addRule(rule) { if (rule === null || rule === undefined) { - d.editRulePage = pageStack.push(Qt.resolvedUrl("NewThingMagicPage.qml"), {device: root.device}); - d.editRulePage.manualCreation.connect(function() { - pageStack.pop(); - rule = Engine.ruleManager.createNewRule(); - addRule(rule) - }) - d.editRulePage.done.connect(function() {pageStack.pop(root);}); - return; + print("creating new rule. have", ruleTemplatesModel.count, "templates", ruleTemplatesModel.deviceClass.interfaces) + if (ruleTemplatesModel.count > 0) { + d.editRulePage = pageStack.push(Qt.resolvedUrl("NewThingMagicPage.qml"), {device: root.device}); + d.editRulePage.manualCreation.connect(function() { + pageStack.pop(); + rule = Engine.ruleManager.createNewRule(); + addRule(rule) + }) + d.editRulePage.done.connect(function() {pageStack.pop(root);}); + return; + } + rule = Engine.ruleManager.createNewRule(); } - d.editRulePage = pageStack.push(Qt.resolvedUrl("EditRulePage.qml"), {rule: rule}); + d.editRulePage = pageStack.push(Qt.resolvedUrl("EditRulePage.qml"), {rule: rule, initialDeviceToBeAdded: root.device}); d.editRulePage.StackView.onRemoved.connect(function() { rule.destroy(); }) @@ -48,11 +59,11 @@ Page { pageStack.pop(); }) -// if (rule.eventDescriptors.count === 0) { -// var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor(); -// eventDescriptor.deviceId = device.id; -// page.selectEventDescriptorData(eventDescriptor); -// } + // if (rule.eventDescriptors.count === 0) { + // var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor(); + // eventDescriptor.deviceId = device.id; + // page.selectEventDescriptorData(eventDescriptor); + // } } diff --git a/nymea-app/ui/magic/EditRulePage.qml b/nymea-app/ui/magic/EditRulePage.qml index 38fff95c..1b9df230 100644 --- a/nymea-app/ui/magic/EditRulePage.qml +++ b/nymea-app/ui/magic/EditRulePage.qml @@ -8,6 +8,8 @@ Page { id: root property var rule: null + property var initialDeviceToBeAdded: null + property bool busy: false readonly property bool isEventBased: rule.eventDescriptors.count > 0 || rule.timeDescriptor.timeEventItems.count > 0 @@ -128,9 +130,7 @@ Page { }) statePage.done.connect(function() { root.rule.setStateEvaluator(stateEvaluator) - pageStack.pop(); - pageStack.pop(); - pageStack.pop(); + pageStack.pop(root); }) } @@ -437,7 +437,12 @@ Page { text: eventsRepeater.count == 0 && timeEventRepeater.count === 0 ? qsTr("Configure...") : qsTr("Add another...") visible: !root.isStateBased onClicked: { - if (root.rule.timeDescriptor.calendarItems.count > 0) { + if (root.initialDeviceToBeAdded !== null) { + var eventDescriptor = root.rule.eventDescriptors.createNewEventDescriptor(); + eventDescriptor.deviceId = root.initialDeviceToBeAdded.id; + root.initialDeviceToBeAdded = null; + selectEventDescriptorData(eventDescriptor); + } else if (root.rule.timeDescriptor.calendarItems.count > 0) { root.addEventDescriptor() } else { pageStack.push(eventQuestionPageComponent) @@ -526,7 +531,12 @@ Page { qsTr("Add another...") visible: root.rule.timeDescriptor.timeEventItems.count === 0 || root.rule.stateEvaluator === null onClicked: { - if (root.rule.timeDescriptor.timeEventItems.count > 0) { + if (root.initialDeviceToBeAdded !== null) { + var stateEvaluator = root.rule.createStateEvaluator(); + stateEvaluator.stateDescriptor.deviceId = root.initialDeviceToBeAdded.id + root.initialDeviceToBeAdded = null; + selectStateDescriptorData(stateEvaluator) + } else if (root.rule.timeDescriptor.timeEventItems.count > 0) { root.rule.setStateEvaluator(root.rule.createStateEvaluator()); } else if (root.rule.stateEvaluator !== null) { root.addCalendarItem(); @@ -578,11 +588,18 @@ Page { Layout.margins: app.margins text: root.isEmpty ? qsTr("Configure...") : actionsRepeater.count == 0 ? qsTr("Add an action...") : qsTr("Add another action...") - onClicked: { + onClicked: { if (root.isEmpty) { root.rule.executable = true; } - var page = pageStack.push(ruleActionQuestionPageComponent, {exitAction: false}); + if (root.initialDeviceToBeAdded !== null) { + var ruleAction = root.rule.actions.createNewRuleAction(); + ruleAction.deviceId = root.initialDeviceToBeAdded.id; + root.initialDeviceToBeAdded = null; + selectRuleActionData(root.rule.actions, ruleAction) + } else { + var page = pageStack.push(ruleActionQuestionPageComponent, {exitAction: false}); + } } visible: root.actionsVisible } diff --git a/nymea-app/ui/magic/NewThingMagicPage.qml b/nymea-app/ui/magic/NewThingMagicPage.qml index 28cb2d79..4e4ea871 100644 --- a/nymea-app/ui/magic/NewThingMagicPage.qml +++ b/nymea-app/ui/magic/NewThingMagicPage.qml @@ -22,37 +22,25 @@ Page { // Fill in all EventDescriptors for (var i = rule.eventDescriptors.count; i < ruleTemplate.eventDescriptorTemplates.count; i++) { var eventDescriptorTemplate = ruleTemplate.eventDescriptorTemplates.get(i); + print("RuleFromTemplate: Filling eventDescriptor:", eventDescriptorTemplate.interfaceName, eventDescriptorTemplate.interfaceEvent, eventDescriptorTemplate.selectionId) // If we already have a thing selected for this selectionIndex, use that if (selectedThings.length > eventDescriptorTemplate.selectionId) { var device = Engine.deviceManager.devices.getDevice(selectedThings[eventDescriptorTemplate.selectionId]); - var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); - var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor(); - eventDescriptor.deviceId = device.id - eventDescriptor.eventTypeId = deviceClass.eventTypes.findByName(eventDescriptorTemplate.interfaceEvent).id - fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + createEventDescriptor(rule, ruleTemplate, selectedThings, 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 (selectedThings.indexOf(root.device.id) === -1 && root.deviceClass.interfaces.indexOf(eventDescriptorTemplate.interfaceName) >= 0) { - var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor(); - eventDescriptor.deviceId = root.device.id; - eventDescriptor.eventTypeId = root.deviceClass.eventTypes.findByName(eventDescriptorTemplate.interfaceEvent).id - rule.eventDescriptors.addEventDescriptor(eventDescriptor); + if (selectedThings.indexOf(root.device.id) === -1 && root.deviceClass.interfaces.indexOf(eventDescriptorTemplate.interfaceName) >= 0 && eventDescriptorTemplate.interfaceName === ruleTemplate.interfaceName) { selectedThings.push(root.device.id); - fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + createEventDescriptor(rule, ruleTemplate, selectedThings, root.device, eventDescriptorTemplate) return; } // We need to pick a thing var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [eventDescriptorTemplate.interfaceName]}); page.thingSelected.connect(function(device) { - var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor(); - eventDescriptor.deviceId = device.id; - var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); - eventDescriptor.eventTypeId = deviceClass.eventTypes.findByName(eventDescriptorTemplate.interfaceEvent).id; - rule.eventDescriptors.addEventDescriptor(eventDescriptor); selectedThings.push(device.id); - fullRuleFromTemplate(rule, ruleTemplate, selectedThings); + createEventDescriptor(rule, ruleTemplate, selectedThings, device, eventDescriptorTemplate) return; }) page.backPressed.connect(function() {rule.destroy(); root.done();}) @@ -76,58 +64,42 @@ Page { for (var i = rule.actions.count; i < ruleTemplate.ruleActionTemplates.count; i++) { var ruleActionTemplate = ruleTemplate.ruleActionTemplates.get(i); - // Did we pick a thing for this index before? - if (selectedThings.length > ruleActionTemplate.selectionId) { + if (ruleActionTemplate.selectionMode === RuleActionTemplate.SelectionModeInterface) { + // TODO: Implement blacklist for interface based actions var ruleAction = rule.actions.createNewRuleAction(); - var deviceId = selectedThings[ruleActionTemplate.selectionId]; - var device = Engine.deviceManager.devices.getDevice(deviceId); - var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); - ruleAction.deviceId = deviceId; - ruleAction.actionTypeId = deviceClass.actionTypes.findByName(ruleActionTemplate.interfaceAction).id + ruleAction.interfaceName = ruleActionTemplate.interfaceName; + ruleAction.interfaceAction = ruleActionTemplate.interfaceAction; for (var j = 0; j < ruleActionTemplate.ruleActionParams.count; j++) { var ruleActionParam = ruleActionTemplate.ruleActionParams.get(j) - var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId); - var paramType = actionType.paramTypes.findByName(ruleActionParam.paramName); - ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParam.value) + ruleAction.ruleActionParams.setRuleActionParamByName(ruleActionParam.paramName, ruleActionParam.value) } rule.actions.addRuleAction(ruleAction); fillRuleFromTemplate(rule, ruleTemplate, selectedThings); return; } + // Did we pick a thing for this index before? + if (selectedThings.length > ruleActionTemplate.selectionId) { + var device = Engine.deviceManager.devices.getDevice(selectedThings[ruleActionTemplate.selectionId]); + createRuleAction(rule, ruleTemplate, selectedThings, rule.actions, device, ruleActionTemplate) + return; + } + // Did we already use the thing we opened this page from? - if (selectedThings.indexOf(root.device.id) === -1 && root.deviceClass.interfaces.indexOf(ruleActionTemplate.interfaceName) >= 0) { - var ruleAction = rule.actions.createNewRuleAction(); - ruleAction.deviceId = root.device.id; - ruleAction.actionTypeId = root.deviceClass.actionTypes.findByName(ruleActionTemplate.interfaceAction).id - for (var j = 0; j < ruleActionTemplate.ruleActionParams.count; j++) { - var ruleActionParam = ruleActionTemplate.ruleActionParams.get(j) - var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId); - var paramType = actionType.paramTypes.findByName(ruleActionParam.paramName); - ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParam.value) - } - rule.actions.addRuleAction(ruleAction); + if (selectedThings.indexOf(root.device.id) === -1 && root.deviceClass.interfaces.indexOf(ruleActionTemplate.interfaceName) >= 0 && ruleActionTemplate.interfaceName === ruleTemplate.interfaceName) { selectedThings.push(root.device.id); - fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + createRuleAction(rule, ruleTemplate, selectedThings, rule.actions, root.device, ruleActionTemplate) return; } // Ok, we need to pick a thing + print("Need to select a thing") var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [ruleActionTemplate.interfaceName]}); page.thingSelected.connect(function(device) { - var ruleAction = rule.actions.createNewRuleAction(); - ruleAction.deviceId = device.id; - var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); - ruleAction.actionTypeId = deviceClass.actionTypes.findByName(ruleActionTemplate.interfaceAction).id; - for (var j = 0; j < ruleActionTemplate.ruleActionParams.count; j++) { - var ruleActionParam = ruleActionTemplate.ruleActionParams.get(j) - var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId); - var paramType = actionType.paramTypes.findByName(ruleActionParam.paramName); - ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParam.value) - } - rule.actions.addRuleAction(ruleAction); + print("selected device", device.name) + print("template is", ruleActionTemplate.interfaceName) selectedThings.push(device.id); - fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + createRuleAction(rule, ruleTemplate, selectedThings, rule.actions, device, ruleActionTemplate) return; }) page.backPressed.connect(function() {rule.destroy(); root.done();}) @@ -138,58 +110,39 @@ Page { for (var i = rule.exitActions.count; i < ruleTemplate.ruleExitActionTemplates.count; i++) { var ruleExitActionTemplate = ruleTemplate.ruleExitActionTemplates.get(i); - // Did we pick a thing for this index before? - if (selectedThings.length > ruleExitActionTemplate.selectionId) { - var ruleAction = rule.exitActions.createNewRuleAction(); - var deviceId = selectedThings[ruleExitActionTemplate.selectionId]; - var device = Engine.deviceManager.devices.getDevice(deviceId); - var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); - ruleAction.deviceId = deviceId; - ruleAction.actionTypeId = deviceClass.actionTypes.findByName(ruleExitActionTemplate.interfaceAction).id + if (ruleExitActionTemplate.selectionMode === RuleActionTemplate.SelectionModeInterface) { + // TODO: Implement blacklist for interface based actions + var ruleExitAction = rule.exitActions.createNewRuleAction(); + ruleExitAction.interfaceName = ruleExitActionTemplate.interfaceName; + ruleExitAction.interfaceAction = ruleExitActionTemplate.interfaceAction; for (var j = 0; j < ruleExitActionTemplate.ruleActionParams.count; j++) { var ruleActionParam = ruleExitActionTemplate.ruleActionParams.get(j) - var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId); - var paramType = actionType.paramTypes.findByName(ruleActionParam.paramName); - ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParam.value) + ruleExitAction.ruleActionParams.setRuleActionParam(ruleActionParam.paramName, ruleActionParam.value) } rule.exitActions.addRuleAction(ruleAction); fillRuleFromTemplate(rule, ruleTemplate, selectedThings); return; } + // Did we pick a thing for this index before? + if (selectedThings.length > ruleExitActionTemplate.selectionId) { + var device = Engine.deviceManager.devices.getDevice(selectedThings[ruleExitActionTemplate.selectionId]); + createRuleAction(rule, ruleTemplate, selectedThings, rule.exitActions, device, ruleExitActionTemplate); + return; + } + // Did we already use the thing we opened this page from? - if (selectedThings.indexOf(root.device.id) === -1 && root.deviceClass.interfaces.indexOf(ruleExitActionTemplate.interfaceName) >= 0) { - var ruleAction = rule.exitActions.createNewRuleAction(); - ruleAction.deviceId = root.device.id; - ruleAction.actionTypeId = root.deviceClass.actionTypes.findByName(ruleExitActionTemplate.interfaceAction).id - for (var j = 0; j < ruleExitActionTemplate.ruleActionParams.count; j++) { - var ruleActionParam = ruleExitActionTemplate.ruleActionParams.get(j) - var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId); - var paramType = actionType.paramTypes.findByName(ruleActionParam.paramName); - ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParam.value) - } - rule.exitActions.addRuleAction(ruleAction); + if (selectedThings.indexOf(root.device.id) === -1 && root.deviceClass.interfaces.indexOf(ruleExitActionTemplate.interfaceName) >= 0 && ruleExitActionTemplate.interfaceName === ruleTemplate.interfaceName) { selectedThings.push(root.device.id); - fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + createRuleAction(rule, ruleTemplate, selectedThings, rule.exitActions, root.device, ruleExitActionTemplate); return; } // Ok, we need to pick a thing var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [ruleExitActionTemplate.interfaceName]}); page.thingSelected.connect(function(device) { - var ruleAction = rule.exitActions.createNewRuleAction(); - ruleAction.deviceId = device.id; - var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); - ruleAction.actionTypeId = deviceClass.actionTypes.findByName(ruleExitActionTemplate.interfaceAction).id; - for (var j = 0; j < ruleExitActionTemplate.ruleActionParams.count; j++) { - var ruleActionParam = ruleExitActionTemplate.ruleActionParams.get(j) - var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId); - var paramType = actionType.paramTypes.findByName(ruleActionParam.paramName); - ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParam.value) - } - rule.exitActions.addRuleAction(ruleAction); selectedThings.push(device.id); - fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + createRuleAction(rule, ruleTemplate, selectedThings, rule.exitActions, device, ruleExitActionTemplate); return; }) page.backPressed.connect(function() {rule.destroy(); root.done();}) @@ -197,10 +150,31 @@ Page { } + // Now replace %i in title and action params with selectedThings[i].name rule.name = ruleTemplate.ruleNameTemplate; for (var i = 0; i < selectedThings.length; i++) { var device = Engine.deviceManager.devices.getDevice(selectedThings[i]); rule.name = rule.name.arg(device.name) + + for (var j = 0; j < rule.actions.count; j++) { + var action = rule.actions.get(j); + for(var k = 0; k < action.ruleActionParams.count; k++) { + var actionParam = action.ruleActionParams.get(k); + print("replacing args", typeof actionParam.value) + if (typeof actionParam.value === "string") { + actionParam.value = actionParam.value.arg(device.name); + } + } + } + for (var j = 0; j < rule.exitActions.count; j++) { + var action = rule.exitActions.get(j); + for(var k = 0; k < action.ruleActionParams.count; k++) { + var actionParam = action.ruleActionParams.get(k); + if (typeof actionParam.value === "string") { + actionParam.value = actionParam.value.arg(device.name); + } + } + } } print("Rule complete!") @@ -210,8 +184,19 @@ Page { } function fillStateEvaluatorFromTemplate(rule, ruleTemplate, stateEvaluator, stateEvaluatorTemplate, selectedThings) { - if (stateEvaluatorTemplate.stateDescriptorTemplate !== null && selectedThings.indexOf(stateEvaluator.stateDescriptor.deviceId) === -1) { + if (stateEvaluatorTemplate.stateDescriptorTemplate !== null && selectedThings.indexOf(stateEvaluator.stateDescriptor.deviceId) === -1 && stateEvaluator.stateDescriptor.interfaceName.length === 0) { // need to fill stateDescriptor + + print("filling in state evaluator for selection mode:", stateEvaluatorTemplate.stateDescriptorTemplate.selectionMode) + if (stateEvaluatorTemplate.stateDescriptorTemplate.selectionMode === StateDescriptor.SelectionModeInterface) { + stateEvaluator.stateDescriptor.interfaceName = stateEvaluatorTemplate.stateDescriptorTemplate.interfaceName; + stateEvaluator.stateDescriptor.interfaceState = stateEvaluatorTemplate.stateDescriptorTemplate.interfaceState; + stateEvaluator.stateDescriptor.valueOperator = stateEvaluatorTemplate.stateDescriptorTemplate.valueOperator; + stateEvaluator.stateDescriptor.value = stateEvaluatorTemplate.stateDescriptorTemplate.value; + fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + return true; + } + // did we pick a thing for this index before? if (selectedThings.length > stateEvaluatorTemplate.stateDescriptorTemplate.selectionId) { var deviceId = selectedThings[stateEvaluatorTemplate.stateDescriptorTemplate.selectionId] @@ -224,7 +209,7 @@ Page { fillRuleFromTemplate(rule, ruleTemplate, selectedThings); return true; } - if (selectedThings.indexOf(root.device.id) === -1 && root.deviceClass.interfaces.indexOf(stateEvaluatorTemplate.stateDescriptorTemplate.interfaceName) >= 0) { + if (selectedThings.indexOf(root.device.id) === -1 && root.deviceClass.interfaces.indexOf(stateEvaluatorTemplate.stateDescriptorTemplate.interfaceName) >= 0 && stateEvaluatorTemplate.stateDescriptorTemplate.interfaceName === ruleTemplate.interfaceName) { stateEvaluator.stateDescriptor.deviceId = root.device.id; stateEvaluator.stateDescriptor.stateTypeId = root.deviceClass.stateTypes.findByName(stateEvaluatorTemplate.stateDescriptorTemplate.interfaceState).id stateEvaluator.stateDescriptor.valueOperator = stateEvaluatorTemplate.stateDescriptorTemplate.valueOperator; @@ -233,7 +218,9 @@ Page { fillRuleFromTemplate(rule, ruleTemplate, selectedThings); return true; } - var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [stateEvaluatorTemplate.stateDescriptorTemplate.interfaceName]}); + print("opening SelectThingPage for shownInterfaces:") + print("..", stateEvaluatorTemplate.stateDescriptorTemplate.interfaceName) + var page = pageStack.push(Qt.resolvedUrl("SelectThingPage.qml"), {shownInterfaces: [stateEvaluatorTemplate.stateDescriptorTemplate.interfaceName], allowSelectAny: stateEvaluatorTemplate.stateDescriptorTemplate.selectionMode === StateDescriptorTemplate.SelectionModeAny}); page.thingSelected.connect(function(device) { var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); stateEvaluator.stateDescriptor.deviceId = device.id; @@ -243,6 +230,13 @@ Page { selectedThings.push(device.id); fillRuleFromTemplate(rule, ruleTemplate, selectedThings) }) + page.onAnySelected.connect(function() { + stateEvaluator.stateDescriptor.interfaceName = stateEvaluatorTemplate.stateDescriptorTemplate.interfaceName; + stateEvaluator.stateDescriptor.interfaceState = stateEvaluatorTemplate.stateDescriptorTemplate.interfaceState; + stateEvaluator.stateDescriptor.valueOperator = stateEvaluatorTemplate.stateDescriptorTemplate.valueOperator; + stateEvaluator.stateDescriptor.value = stateEvaluatorTemplate.stateDescriptorTemplate.value; + fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + }) page.backPressed.connect(function() {rule.destroy(); root.done();}) return true; } @@ -256,6 +250,47 @@ Page { return false; } + function createEventDescriptor(rule, ruleTemplate, selectedThings, device, eventDescriptorTemplate) { + var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor(); + eventDescriptor.deviceId = device.id; + 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) { + eventDescriptor.paramDescriptors.addParamDescriptor(paramDescriptorTemplate.paramName, paramDescriptorTemplate.value); + } else { + needsParams = true; + } + } + if (needsParams) { + var page = pageStack.push(Qt.resolvedUrl("SelectEventDescriptorParamsPage.qml"), { eventDescriptor: eventDescriptor }) + page.completed.connect(function() { + rule.eventDescriptors.addEventDescriptor(eventDescriptor); + fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + }) + return; + } + rule.eventDescriptors.addEventDescriptor(eventDescriptor); + fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + } + + function createRuleAction(rule, ruleTemplate, selectedThings, ruleActions, device, ruleActionTemplate) { + var ruleAction = ruleActions.createNewRuleAction(); + var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + ruleAction.deviceId = device.id; + ruleAction.actionTypeId = deviceClass.actionTypes.findByName(ruleActionTemplate.interfaceAction).id + for (var j = 0; j < ruleActionTemplate.ruleActionParams.count; j++) { + var ruleActionParam = ruleActionTemplate.ruleActionParams.get(j) + var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId); + var paramType = actionType.paramTypes.findByName(ruleActionParam.paramName); + ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParam.value) + } + ruleActions.addRuleAction(ruleAction); + fillRuleFromTemplate(rule, ruleTemplate, selectedThings); + } + header: GuhHeader { text: qsTr("New magic") onBackPressed: root.done() diff --git a/nymea-app/ui/magic/SelectThingPage.qml b/nymea-app/ui/magic/SelectThingPage.qml index e529274e..56f6cf58 100644 --- a/nymea-app/ui/magic/SelectThingPage.qml +++ b/nymea-app/ui/magic/SelectThingPage.qml @@ -9,13 +9,16 @@ Page { id: root property bool selectInterface: false - signal backPressed(); - signal thingSelected(var device); - signal interfaceSelected(string interfaceName); property alias showEvents: interfacesProxy.showEvents property alias showActions: interfacesProxy.showActions property alias showStates: interfacesProxy.showStates property alias shownInterfaces: devicesProxy.shownInterfaces + property bool allowSelectAny: false + + signal backPressed(); + signal thingSelected(var device); + signal interfaceSelected(string interfaceName); + signal anySelected(); header: GuhHeader { text: root.selectInterface ? @@ -37,6 +40,16 @@ Page { ColumnLayout { anchors.fill: parent + MeaListItemDelegate { + Layout.fillWidth: true + text: qsTr("Any %1").arg(app.interfaceToDisplayName(root.shownInterfaces[0])) + visible: root.allowSelectAny + onClicked: { + root.anySelected(); + } + } + ThinDivider { visible: root.allowSelectAny } + ListView { Layout.fillWidth: true Layout.fillHeight: true