diff --git a/libnymea-app-core/connection/discovery/zeroconfdiscovery.cpp b/libnymea-app-core/connection/discovery/zeroconfdiscovery.cpp index 8dba6774..966ba20d 100644 --- a/libnymea-app-core/connection/discovery/zeroconfdiscovery.cpp +++ b/libnymea-app-core/connection/discovery/zeroconfdiscovery.cpp @@ -69,7 +69,7 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry) return; } - qDebug() << "zeroconf service discovered" << entry->type() << entry->name() << " IP:" << entry->ip().toString() << entry->txt(); +// qDebug() << "zeroconf service discovered" << entry->type() << entry->name() << " IP:" << entry->ip().toString() << entry->txt(); QString uuid; bool sslEnabled = false; @@ -97,7 +97,7 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry) if (!host) { host = new NymeaHost(m_nymeaHosts); host->setUuid(uuid); - qDebug() << "ZeroConf: Adding new host:" << serverName << uuid; +// qDebug() << "ZeroConf: Adding new host:" << serverName << uuid; m_nymeaHosts->addHost(host); } host->setName(serverName); @@ -113,14 +113,14 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry) url.setPort(entry->port()); Connection *connection = host->connections()->find(url); if (!connection) { - qDebug() << "Zeroconf: Adding new connection to host:" << host->name() << url.toString(); +// qDebug() << "Zeroconf: Adding new connection to host:" << host->name() << url.toString(); QString displayName = QString("%1:%2").arg(url.host()).arg(url.port()); Connection::BearerType bearerType = QHostAddress(url.host()).isLoopback() ? Connection::BearerTypeLoopback : Connection::BearerTypeLan; connection = new Connection(url, bearerType, sslEnabled, displayName); connection->setOnline(true); host->connections()->addConnection(connection); } else { - qDebug() << "Zeroconf: Setting connection online:" << host->name() << url.toString(); +// qDebug() << "Zeroconf: Setting connection online:" << host->name() << url.toString(); connection->setOnline(true); } } diff --git a/libnymea-app-core/connection/nymeaconnection.cpp b/libnymea-app-core/connection/nymeaconnection.cpp index ecd2c195..3c539bde 100644 --- a/libnymea-app-core/connection/nymeaconnection.cpp +++ b/libnymea-app-core/connection/nymeaconnection.cpp @@ -412,11 +412,11 @@ void NymeaConnection::updateActiveBearers() } if (m_availableBearerTypes != availableBearerTypes) { - qDebug() << "Available Bearer Types changed:" << availableBearerTypes; +// qDebug() << "Available Bearer Types changed:" << availableBearerTypes; m_availableBearerTypes = availableBearerTypes; emit availableBearerTypesChanged(); } else { - qDebug() << "Available Bearer Types:" << availableBearerTypes; +// qDebug() << "Available Bearer Types:" << availableBearerTypes; } if (!m_currentHost) { diff --git a/libnymea-app-core/devicemanager.cpp b/libnymea-app-core/devicemanager.cpp index e5f8c036..10680c44 100644 --- a/libnymea-app-core/devicemanager.cpp +++ b/libnymea-app-core/devicemanager.cpp @@ -34,6 +34,22 @@ DeviceManager::DeviceManager(JsonRpcClient* jsonclient, QObject *parent) : m_jsonClient(jsonclient) { m_jsonClient->registerNotificationHandler(this, "notificationReceived"); + EventHandler *eventHandler = new EventHandler(this); + m_jsonClient->registerNotificationHandler(eventHandler, "notificationReceived"); + connect(eventHandler, &EventHandler::eventReceived, this, [this](const QVariantMap event) { + QUuid deviceId = event.value("deviceId").toUuid(); + QUuid eventTypeId = event.value("eventTypeId").toUuid(); + + Device *dev = m_devices->getDevice(deviceId); + if (!dev) { + qWarning() << "received an event from a device we don't know..." << deviceId << event; + return; + } + qDebug() << "Event received" << deviceId.toString() << eventTypeId.toString(); + dev->eventTriggered(eventTypeId.toString(), event.value("params").toMap()); + emit eventTriggered(deviceId.toString(), eventTypeId.toString(), event.value("params").toMap()); + + }); } void DeviceManager::clear() diff --git a/libnymea-app-core/devicemanager.h b/libnymea-app-core/devicemanager.h index 49623473..375662b4 100644 --- a/libnymea-app-core/devicemanager.h +++ b/libnymea-app-core/devicemanager.h @@ -120,6 +120,8 @@ signals: void fetchingDataChanged(); void notificationReceived(const QString &deviceId, const QString &eventTypeId, const QVariantList ¶ms); + void eventTriggered(const QString &deviceId, const QString &eventTypeId, const QVariantMap params); + private: Vendors *m_vendors; Plugins *m_plugins; @@ -135,6 +137,29 @@ private: QHash > m_browsingRequests; QHash > m_browserDetailsRequests; + + +}; + +// TODO: Kinda shitty that Device Events are not sent from the Devices namespace... +class EventHandler: public JsonHandler { + Q_OBJECT + +public: + EventHandler(QObject *parent = nullptr): JsonHandler(parent) {} + QString nameSpace() const override { + return "Events"; + } + +signals: + void eventReceived(const QVariantMap &event); + +private: + Q_INVOKABLE void notificationReceived(const QVariantMap &data) { + qDebug() << "event rece" << data; + emit eventReceived(data.value("params").toMap().value("event").toMap()); + } + }; #endif // DEVICEMANAGER_H diff --git a/libnymea-app-core/jsonrpc/jsonhandler.h b/libnymea-app-core/jsonrpc/jsonhandler.h index 15057a5e..7c94fe0f 100644 --- a/libnymea-app-core/jsonrpc/jsonhandler.h +++ b/libnymea-app-core/jsonrpc/jsonhandler.h @@ -28,7 +28,7 @@ class JsonHandler : public QObject { Q_OBJECT public: - JsonHandler(QObject *parent = 0); + JsonHandler(QObject *parent = nullptr); virtual QString nameSpace() const = 0; }; diff --git a/libnymea-common/types/device.h b/libnymea-common/types/device.h index e5617f32..99a4388e 100644 --- a/libnymea-common/types/device.h +++ b/libnymea-common/types/device.h @@ -90,6 +90,7 @@ signals: void paramsChanged(); void settingsChanged(); void statesChanged(); + void eventTriggered(const QString &eventTypeId, const QVariantMap ¶ms); }; diff --git a/libnymea-common/types/ruleactionparams.cpp b/libnymea-common/types/ruleactionparams.cpp index c4f4ae48..a0df977d 100644 --- a/libnymea-common/types/ruleactionparams.cpp +++ b/libnymea-common/types/ruleactionparams.cpp @@ -115,6 +115,16 @@ RuleActionParam *RuleActionParams::get(int index) const return m_list.at(index); } +bool RuleActionParams::hasRuleActionParam(const QString ¶mTypeId) const +{ + for (int i = 0; i < m_list.count(); i++) { + if (m_list.at(i)->paramTypeId() == paramTypeId) { + return true; + } + } + return false; +} + bool RuleActionParams::operator==(RuleActionParams *other) const { if (rowCount() != other->rowCount()) { diff --git a/libnymea-common/types/ruleactionparams.h b/libnymea-common/types/ruleactionparams.h index 846e3263..7294211f 100644 --- a/libnymea-common/types/ruleactionparams.h +++ b/libnymea-common/types/ruleactionparams.h @@ -33,6 +33,8 @@ public: Q_INVOKABLE RuleActionParam* get(int index) const; + Q_INVOKABLE bool hasRuleActionParam(const QString ¶mTypeId) const; + bool operator==(RuleActionParams *other) const; signals: diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index 20817a78..54c05c60 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -132,6 +132,7 @@ target.path = /usr/bin INSTALLS += target DISTFILES += \ + ruletemplates/doorbellruletemplates.json \ ruletemplates/smartmetertemplates.json \ ruletemplates/presencesensortemplates.json \ ruletemplates/daylightsensor.json \ diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index b8ab2b13..e01cf871 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -193,5 +193,6 @@ ui/delegates/BrowserItemDelegate.qml ui/components/NavigationPad.qml ui/components/KeypadButton.qml + ui/devicepages/DoorbellDevicePage.qml diff --git a/nymea-app/ruletemplates.qrc b/nymea-app/ruletemplates.qrc index e5c6de98..115382d3 100644 --- a/nymea-app/ruletemplates.qrc +++ b/nymea-app/ruletemplates.qrc @@ -7,5 +7,6 @@ ruletemplates/presencesensortemplates.json ruletemplates/daylightsensor.json ruletemplates/lighttemplates.json + ruletemplates/doorbellruletemplates.json diff --git a/nymea-app/ruletemplates/doorbellruletemplates.json b/nymea-app/ruletemplates/doorbellruletemplates.json new file mode 100644 index 00000000..d29fb2a9 --- /dev/null +++ b/nymea-app/ruletemplates/doorbellruletemplates.json @@ -0,0 +1,24 @@ +{ + "templates": [ + { + "interfaceName": "doorbell", + "description": "Alert on doorbell ring", + "ruleNameTemplate": "Alert %1 when someone is at %0", + "eventDescriptorTemplates": [ + { + "interfaceName": "doorbell", + "interfaceEvent": "doorbellPressed", + "selectionId": 0 + } + ], + "ruleActionTemplates": [ + { + "interfaceName": "alert", + "interfaceAction": "alert", + "selectionId": 1 + } + ] + } + ] +} + diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index 9abc3ebb..98273935 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -65,7 +65,7 @@ ApplicationWindow { } property alias _discovery: discovery - property var supportedInterfaces: ["light", "weather", "media", "garagegate", "awning", "shutter", "blind", "powersocket", "heating", "sensor", "smartmeter", "evcharger", "accesscontrol", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"] + property var supportedInterfaces: ["light", "weather", "media", "garagegate", "awning", "shutter", "blind", "powersocket", "heating", "doorbell", "sensor", "smartmeter", "evcharger", "accesscontrol", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"] function interfaceToString(name) { switch(name) { case "light": @@ -122,6 +122,8 @@ ApplicationWindow { return qsTr("EV-chargers"); case "powersocket": return qsTr("Power sockets") + case "doorbell": + return qsTr("Doorbells"); case "uncategorized": return qsTr("Uncategorized") default: @@ -226,6 +228,8 @@ ApplicationWindow { case "evcharger": case "extendedevcharger": return Qt.resolvedUrl("images/ev-charger.svg") + case "doorbell": + return Qt.resolvedUrl("images/notification.svg") case "connectable": return Qt.resolvedUrl("images/stock_link.svg") default: @@ -285,6 +289,10 @@ ApplicationWindow { return qsTr("daylight sensor") case "presencesensor": return qsTr("presence sensor") + case "doorbell": + return qsTr("doorbell") + case "alert": + return qsTr("alert") default: console.warn("Unhandled interfaceToDisplayName:", name) } @@ -322,6 +330,8 @@ ApplicationWindow { page = "SmartMeterDevicePage.qml" } else if (interfaceList.indexOf("powersocket") >= 0) { page = "PowersocketDevicePage.qml"; + } else if (interfaceList.indexOf("doorbell") >= 0) { + page = "DoorbellDevicePage.qml"; } else { page = "GenericDevicePage.qml"; } diff --git a/nymea-app/ui/components/NymeaHeader.qml b/nymea-app/ui/components/NymeaHeader.qml index a0035316..77a69633 100644 --- a/nymea-app/ui/components/NymeaHeader.qml +++ b/nymea-app/ui/components/NymeaHeader.qml @@ -91,7 +91,6 @@ Item { Label { text: infoPane.text - visible: infoPane.alertState font.pixelSize: app.smallFont color: "white" } diff --git a/nymea-app/ui/components/NymeaListItemDelegate.qml b/nymea-app/ui/components/NymeaListItemDelegate.qml index f649d962..9d1a4c16 100644 --- a/nymea-app/ui/components/NymeaListItemDelegate.qml +++ b/nymea-app/ui/components/NymeaListItemDelegate.qml @@ -13,6 +13,7 @@ SwipeDelegate { property bool wrapTexts: true property bool prominentSubText: true + property int textAlignment: Text.AlignLeft property string iconName property string thumbnail @@ -81,6 +82,7 @@ SwipeDelegate { maximumLineCount: root.wrapTexts ? 2 : 1 elide: Text.ElideRight verticalAlignment: Text.AlignVCenter + horizontalAlignment: root.textAlignment } Label { Layout.fillWidth: true diff --git a/nymea-app/ui/devicepages/DoorbellDevicePage.qml b/nymea-app/ui/devicepages/DoorbellDevicePage.qml new file mode 100644 index 00000000..c1e34865 --- /dev/null +++ b/nymea-app/ui/devicepages/DoorbellDevicePage.qml @@ -0,0 +1,111 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" +import "../customviews" + +DevicePageBase { + id: root + + readonly property EventType doorbellPressedType: deviceClass.eventTypes.findByName("doorbellPressed") + + GridLayout { + anchors.fill: parent + anchors.topMargin: app.margins + columns: app.landscape ? 2 : 1 + columnSpacing: app.margins + rowSpacing: app.margins + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + ColorIcon { + id: doorbellIcon + anchors.centerIn: parent + height: Math.min(parent.width, parent.height) + width: height + name: "../images/notification.svg" + + color: keyColor + + SequentialAnimation { + id: ringAnimation + ColorAnimation { target: doorbellIcon; property: "color"; from: doorbellIcon.keyColor; to: app.accentColor; duration: 200 } + ColorAnimation { target: doorbellIcon; property: "color"; from: app.accentColor; to: doorbellIcon.keyColor; duration: 300 } + ColorAnimation { target: doorbellIcon; property: "color"; from: doorbellIcon.keyColor; to: app.accentColor; duration: 200 } + ColorAnimation { target: doorbellIcon; property: "color"; from: app.accentColor; to: doorbellIcon.keyColor; duration: 300 } + ColorAnimation { target: doorbellIcon; property: "color"; from: doorbellIcon.keyColor; to: app.accentColor; duration: 200 } + ColorAnimation { target: doorbellIcon; property: "color"; from: app.accentColor; to: doorbellIcon.keyColor; duration: 300 } + ColorAnimation { target: doorbellIcon; property: "color"; from: doorbellIcon.keyColor; to: app.accentColor; duration: 200 } + ColorAnimation { target: doorbellIcon; property: "color"; from: app.accentColor; to: doorbellIcon.keyColor; duration: 300 } + } + + Connections { + target: root.device + onEventTriggered: { + print("evenEmitted", params) + if (eventTypeId == root.doorbellPressedType.id) { + ringAnimation.start(); + } + } + } + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + ColumnLayout { + anchors.fill: parent + spacing: app.margins + + ThinDivider { + visible: !app.landscape + } + + RowLayout { + spacing: app.margins + + Label { + Layout.fillWidth: true + } + + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: app.iconSize + name: "../images/alarm-clock.svg" + color: app.accentColor + } + + Label { + text: qsTr("History") + } + Label { + Layout.fillWidth: true + } + } + + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + + model: LogsModelNg { + engine: _engine + live: true + deviceId: root.device.id + typeIds: [root.doorbellPressedType.id] + } + delegate: NymeaListItemDelegate { + width: parent.width + text: Qt.formatDateTime(model.timestamp) + progressive: false + textAlignment: Text.AlignHCenter + } + } + } + } + } +} diff --git a/nymea-app/ui/magic/DeviceRulesPage.qml b/nymea-app/ui/magic/DeviceRulesPage.qml index 7a3f886f..3ca49bca 100644 --- a/nymea-app/ui/magic/DeviceRulesPage.qml +++ b/nymea-app/ui/magic/DeviceRulesPage.qml @@ -78,7 +78,6 @@ Page { Connections { target: engine.ruleManager onAddRuleReply: { - d.editRulePage.busy = false; if (ruleError == "RuleErrorNoError") { pageStack.pop(root); } else { @@ -86,10 +85,10 @@ Page { var popup = errorDialog.createObject(root, {errorCode: ruleError }) popup.open(); } + d.editRulePage.busy = false; } onEditRuleReply: { - d.editRulePage.busy = false; if (ruleError == "RuleErrorNoError") { pageStack.pop(root); } else { @@ -97,6 +96,7 @@ Page { var popup = errorDialog.createObject(root, {errorCode: ruleError }) popup.open(); } + d.editRulePage.busy = false; } } diff --git a/nymea-app/ui/magic/NewThingMagicPage.qml b/nymea-app/ui/magic/NewThingMagicPage.qml index f0cb8022..d22ef5fa 100644 --- a/nymea-app/ui/magic/NewThingMagicPage.qml +++ b/nymea-app/ui/magic/NewThingMagicPage.qml @@ -300,12 +300,15 @@ Page { function createRuleAction(rule, ruleTemplate, ruleActions, device, ruleActionTemplate) { var ruleAction = ruleActions.createNewRuleAction(); var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + + ruleAction.actionTypeId = deviceClass.actionTypes.findByName(ruleActionTemplate.interfaceAction).id ruleAction.deviceId = device.id; print("Creating action", ruleActionTemplate.interfaceAction, "on device class", deviceClass.displayName) - ruleAction.actionTypeId = deviceClass.actionTypes.findByName(ruleActionTemplate.interfaceAction).id + + var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId); + for (var j = 0; j < ruleActionTemplate.ruleActionParamTemplates.count; j++) { var ruleActionParamTemplate = ruleActionTemplate.ruleActionParamTemplates.get(j) - var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId); var paramType = actionType.paramTypes.findByName(ruleActionParamTemplate.paramName); if (ruleActionParamTemplate.value !== undefined) { ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParamTemplate.value) @@ -335,8 +338,26 @@ Page { } else { console.warn("Invalid RuleActionParamTemplate. Has neither value nor event spec") } - } + // Check if the action has more paramTypes than there are defined in the ruleActionTemplate + for (var i = 0; i < actionType.paramTypes.count; i++) { + var paramType = actionType.paramTypes.get(i); + if (!ruleAction.ruleActionParams.hasRuleActionParam(paramType.id)) { + print("Missing param!", paramType.name) + var paramsPage = pageStack.push(Qt.resolvedUrl("SelectRuleActionParamsPage.qml"), {ruleAction: ruleAction, rule: rule}) + paramsPage.onBackPressed.connect(function() { + ruleAction.destroy() + pageStack.pop(); + }); + paramsPage.onCompleted.connect(function() { + pageStack.pop(); + ruleActions.addRuleAction(ruleAction); + fillRuleFromTemplate(rule, ruleTemplate); + }) + return; + } + } + ruleActions.addRuleAction(ruleAction); fillRuleFromTemplate(rule, ruleTemplate); } diff --git a/nymea-app/ui/magic/SelectRuleActionParamsPage.qml b/nymea-app/ui/magic/SelectRuleActionParamsPage.qml index acb949fb..a548fd13 100644 --- a/nymea-app/ui/magic/SelectRuleActionParamsPage.qml +++ b/nymea-app/ui/magic/SelectRuleActionParamsPage.qml @@ -187,7 +187,9 @@ Page { for (var i = 0; i < delegateRepeater.count; i++) { var paramDelegate = delegateRepeater.itemAt(i); if (paramDelegate.type === "static") { + print("Setting static value", paramDelegate.value) if (root.device) { + print("setting", paramDelegate.paramType.id) root.ruleAction.ruleActionParams.setRuleActionParam(paramDelegate.paramType.id, paramDelegate.value) } else if (root.iface) { root.ruleAction.ruleActionParams.setRuleActionParamByName(root.actionType.paramTypes.get(i).name, paramDelegate.value) diff --git a/nymea-app/ui/magic/SelectThingPage.qml b/nymea-app/ui/magic/SelectThingPage.qml index ad9dfb94..ee45c3ba 100644 --- a/nymea-app/ui/magic/SelectThingPage.qml +++ b/nymea-app/ui/magic/SelectThingPage.qml @@ -43,6 +43,9 @@ Page { engine: _engine groupByInterface: true nameFilter: filterInput.shown ? filterInput.text : "" + Component.onCompleted: { + print("showing devices for interfaces", devicesProxy.shownInterfaces) + } } ColumnLayout {