diff --git a/debian/control b/debian/control index 378525e1..99bed371 100644 --- a/debian/control +++ b/debian/control @@ -216,6 +216,21 @@ Description: nymea.io plugin for denon This package will install the nymea.io plugin for denon +Package: nymea-plugin-doorbird +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, + nymea-plugins-translations, +Description: nymea.io plugin for DoorBird + The nymea daemon is a plugin based IoT (Internet of Things) server. The + server works like a translator for devices, things and services and + allows them to interact. + With the powerful rule engine you are able to connect any device available + in the system and create individual scenes and behaviors for your environment. + . + This package will install the nymea.io plugin for DoorBird + + Package: nymea-plugin-dweetio Architecture: any Depends: ${shlibs:Depends}, @@ -998,6 +1013,7 @@ Depends: nymea-plugin-anel, nymea-plugin-datetime, nymea-plugin-daylightsensor, nymea-plugin-denon, + nymea-plugin-doorbird, nymea-plugin-eq-3, nymea-plugin-flowercare, nymea-plugin-kodi, diff --git a/debian/nymea-plugin-doorbird.install.in b/debian/nymea-plugin-doorbird.install.in new file mode 100644 index 00000000..47568ef1 --- /dev/null +++ b/debian/nymea-plugin-doorbird.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationplugindoorbird.so diff --git a/doorbird/README.md b/doorbird/README.md new file mode 100644 index 00000000..36130e22 --- /dev/null +++ b/doorbird/README.md @@ -0,0 +1,28 @@ +# DoorBird + +This plugin integrates DoorBird video doorbells into nymea. All the communication between nymea and the DoorBird device happens locally and will work without internet connection. + +## Supported Things + +* All video doorbells + * Auto discovery + * Doorbell presses + * Motion events + * Enable/disable IR light + * Switching door relays + * No internet connection required + +NOTE: This plug-in does not handle any video- or audio stream. + +## Requirements + +* The DoorBird device must be in the same local area network as nymea. +* The router must not block ZeroConf/mDNS multicast messages. +* TCP Sockets on port 80 must not be blocked by the router. +* The user must have the permission to act as DoorBird API-operator. + * You can check the permissions in the DoorBird app. +* The package "nymea-plugin-doorbird" must be installed. + +## More + +https://www.doorbird.com diff --git a/doorbird/doorbird.cpp b/doorbird/doorbird.cpp new file mode 100644 index 00000000..74225d16 --- /dev/null +++ b/doorbird/doorbird.cpp @@ -0,0 +1,474 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "doorbird.h" +#include "extern-plugininfo.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Doorbird::Doorbird(const QHostAddress &address, QObject *parent) : + QObject(parent), + m_address(address) +{ + m_networkAccessManager = new QNetworkAccessManager(this); +} + +QHostAddress Doorbird::address() +{ + return m_address; +} + +void Doorbird::setAddress(const QHostAddress &address) +{ + m_address = address; +} + +QUuid Doorbird::getSession(const QString &username, const QString &password) +{ + QUrl url; + url.setHost(m_address.toString()); + url.setScheme("http"); + url.setPath("/bha-api/getsession.cgi"); + url.setUserName(username); + url.setPassword(password); + QNetworkRequest request(url); + qCDebug(dcDoorBird) << "Sending request:" << request.url(); + QNetworkReply *reply = m_networkAccessManager->get(request); + QUuid requestId = QUuid::createUuid(); + + connect(reply, &QNetworkReply::finished, this, [this, reply, requestId](){ + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcDoorBird) << "Error DoorBird thing:" << reply->errorString(); + emit requestSent(requestId, false); + return; + } + emit requestSent(requestId, true); + QByteArray data = reply->readAll(); + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcDoorBird()) << "Error parsing json:" << data; + return; + } + QVariantMap map = jsonDoc.toVariant().toMap().value("BHA").toMap(); + if (map.contains("SESSIONID")) { + QString sessionId = map.value("SESSIONID").toString(); + qCDebug(dcDoorBird) << "Got sessionId" << sessionId; + emit sessionIdReceived(sessionId); + } + }); + + return requestId; +} + +QUuid Doorbird::openDoor(int value) +{ + QNetworkRequest request(QString("http://%1/bha-api/open-door.cgi?r=%2").arg(m_address.toString()).arg(QString::number(value))); + qCDebug(dcDoorBird) << "Sending request:" << request.url(); + QNetworkReply *reply = m_networkAccessManager->get(request); + QUuid requestId = QUuid::createUuid(); + connect(reply, &QNetworkReply::finished, this, [this, reply, requestId](){ + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcDoorBird) << "Error opening DoorBird " << reply->error() << reply->errorString(); + emit requestSent(requestId, false); + return; + } + qCDebug(dcDoorBird) << "DoorBird unlatched:"; + emit requestSent(requestId, true); + }); + return requestId; +} + +QUuid Doorbird::lightOn() +{ + QNetworkRequest request(QString("http://%1/bha-api/light-on.cgi").arg(m_address.toString())); + qCDebug(dcDoorBird) << "Sending request:" << request.url(); + QNetworkReply *reply = m_networkAccessManager->get(request); + QUuid requestId = QUuid::createUuid(); + connect(reply, &QNetworkReply::finished, this, [this, reply, requestId](){ + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcDoorBird) << "Error light on" << reply->error() << reply->errorString(); + emit requestSent(requestId, false); + return; + } + qCDebug(dcDoorBird) << "DoorBird light on:"; + emit requestSent(requestId, true); + }); + return requestId; +} + +QUuid Doorbird::liveVideoRequest() +{ + QNetworkRequest request(QString("http://%1/bha-api/video.cgi").arg(m_address.toString())); + qCDebug(dcDoorBird) << "Sending request:" << request.url(); + QNetworkReply *reply = m_networkAccessManager->get(request); + QUuid requestId = QUuid::createUuid(); + connect(reply, &QNetworkReply::finished, this, [this, reply, requestId](){ + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcDoorBird) << "Error live video request" << reply->error() << reply->errorString(); + emit requestSent(requestId, false); + return; + } + qCDebug(dcDoorBird) << "DoorBird live video request" ; + emit requestSent(requestId, true); + }); + return requestId; +} + +QUuid Doorbird::liveImageRequest() +{ + QNetworkRequest request(QString("http://%1/bha-api/image.cgi").arg(m_address.toString())); + qCDebug(dcDoorBird) << "Sending request:" << request.url(); + QNetworkReply *reply = m_networkAccessManager->get(request); + QUuid requestId = QUuid::createUuid(); + connect(reply, &QNetworkReply::finished, this, [this, reply, requestId](){ + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcDoorBird) << "Error live image request" << reply->error() << reply->errorString(); + emit requestSent(requestId, false); + return; + } + + QByteArrayList tokens = reply->readAll().split('\n'); + QImage image = QImage::fromData(tokens.last()); + //image.save("testfile"); + emit liveImageReceived(image); + qCDebug(dcDoorBird) << "DoorBird live image request:"; + emit requestSent(requestId, true); + }); + return requestId; +} + +QUuid Doorbird::historyImageRequest(int index) +{ + QUrl url(QString("http://%1/bha-api/history.cgi").arg(m_address.toString())); + QUrlQuery query; + query.addQueryItem("index", QString::number(index)); + url.setQuery(query); + + qCDebug(dcDoorBird) << "Sending request:" << url; + QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(url)); + QUuid requestId = QUuid::createUuid(); + connect(reply, &QNetworkReply::finished, this, [this, reply, requestId](){ + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcDoorBird) << "Error history image request" << reply->error() << reply->errorString(); + emit requestSent(requestId, false); + return; + } + qCDebug(dcDoorBird) << "DoorBird history image received:"; + emit requestSent(requestId, true); + }); + return requestId; +} + +QUuid Doorbird::liveAudioReceive() +{ + QNetworkRequest request(QString("http://%1/bha-api/audio-receive.cgi").arg(m_address.toString())); + qCDebug(dcDoorBird) << "Sending request:" << request.url(); + QNetworkReply *reply = m_networkAccessManager->get(request); + QUuid requestId = QUuid::createUuid(); + connect(reply, &QNetworkReply::finished, this, [this, reply, requestId](){ + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcDoorBird) << "Error live audio receive"; + emit requestSent(requestId, false); + return; + } + qCDebug(dcDoorBird) << "DoorBird live audio receive"; + emit requestSent(requestId, true); + }); + return requestId; +} + +QUuid Doorbird::liveAudioTransmit() +{ + return QUuid::createUuid(); +} + +QUuid Doorbird::infoRequest() +{ + QNetworkRequest request(QString("http://%1/bha-api/info.cgi").arg(m_address.toString())); + qCDebug(dcDoorBird) << "Sending request:" << request.url(); + QNetworkReply *reply = m_networkAccessManager->get(request); + QUuid requestId = QUuid::createUuid(); + connect(reply, &QNetworkReply::finished, this, [this, reply, requestId](){ + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcDoorBird) << "Error DoorBird" << reply->error() << reply->errorString(); + emit requestSent(requestId, false); + return; + } + qCDebug(dcDoorBird) << "DoorBird info:" << reply->readAll() ; + emit requestSent(requestId, true); + }); + return requestId; +} + +QUuid Doorbird::listFavorites() +{ + QNetworkRequest request(QString("http://%1/bha-api/favorites.cgi").arg(m_address.toString())); + qCDebug(dcDoorBird) << "Sending request:" << request.url(); + QNetworkReply *reply = m_networkAccessManager->get(request); + QUuid requestId = QUuid::createUuid(); + connect(reply, &QNetworkReply::finished, this, [this, reply, requestId](){ + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcDoorBird) << "Error DoorBird device" << reply->error() << reply->errorString(); + emit requestSent(requestId, false); + return; + } + emit requestSent(requestId, true); + }); + return requestId; +} + +QUuid Doorbird::addFavorite(FavoriteType type, const QString &name, const QUrl &commandUrl, int id) +{ + QUrl url(QString("http://%1/bha-api/favorites.cgi").arg(m_address.toString())); + QUrlQuery query; + query.addQueryItem("action", "save"); + if (type == FavoriteType::Http) { + query.addQueryItem("type", "http"); + } else { + query.addQueryItem("type", "sip"); + } + query.addQueryItem("title", name); + query.addQueryItem("value", commandUrl.toString()); + query.addQueryItem("id", QString::number(id)); + url.setQuery(query); + + QNetworkReply *reply = m_networkAccessManager->get(QNetworkRequest(url)); + QUuid requestId = QUuid::createUuid(); + connect(reply, &QNetworkReply::finished, this, [this, reply, requestId](){ + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcDoorBird) << "Error DoorBird device" << reply->error() << reply->errorString(); + emit requestSent(requestId, false); + return; + } + emit requestSent(requestId, true); + }); + return requestId; +} + +QUuid Doorbird::listSchedules() +{ + QNetworkRequest request(QString("http://%1/bha-api/schedule.cgi").arg(m_address.toString())); + qCDebug(dcDoorBird) << "Sending request:" << request.url(); + QNetworkReply *reply = m_networkAccessManager->get(request); + QUuid requestId = QUuid::createUuid(); + connect(reply, &QNetworkReply::finished, this, [this, reply, requestId](){ + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcDoorBird) << "Error DoorBird device" << reply->error() << reply->errorString(); + emit requestSent(requestId, false); + return; + } + emit requestSent(requestId, true); + }); + return requestId; +} + +QUuid Doorbird::restart() +{ + QNetworkRequest request(QString("http://%1/bha-api/restart.cgi").arg(m_address.toString())); + qCDebug(dcDoorBird) << "Sending request:" << request.url(); + QNetworkReply *reply = m_networkAccessManager->get(request); + QUuid requestId = QUuid::createUuid(); + connect(reply, &QNetworkReply::finished, this, [this, reply, requestId](){ + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcDoorBird) << "Error restarting DoorBird device" << reply; + emit requestSent(requestId, false); + return; + } + qCDebug(dcDoorBird) << "DoorBird restarting"; + emit requestSent(requestId, true); + }); + return requestId; +} + +void Doorbird::connectToEventMonitor() +{ + qCDebug(dcDoorBird) << "Starting monitoring"; + + QNetworkRequest request(QString("http://%1/bha-api/monitor.cgi?ring=doorbell,motionsensor").arg(m_address.toString())); + QNetworkReply *reply = m_networkAccessManager->get(request); + + connect(reply, &QNetworkReply::downloadProgress, this, [this, reply](qint64 bytesReceived, qint64 bytesTotal){ + Q_UNUSED(bytesReceived) + Q_UNUSED(bytesTotal); + + emit deviceConnected(true); + m_readBuffer.append(reply->readAll()); + qCDebug(dcDoorBird) << "Event received" << m_readBuffer; + + // Input data looks like: + // "--ioboundary\r\nContent-Type: text/plain\r\n\r\ndoorbell:H\r\n\r\n" + + while (!m_readBuffer.isEmpty()) { + // find next --ioboundary + QString boundary = QStringLiteral("--ioboundary"); + int startIndex = m_readBuffer.indexOf(boundary); + if (startIndex == -1) { + qCWarning(dcDoorBird) << "No meaningful data in buffer:" << m_readBuffer; + if (m_readBuffer.size() > 1024) { + qCWarning(dcDoorBird) << "Buffer size > 1KB and still no meaningful data. Discarding buffer..."; + m_readBuffer.clear(); + } + // Assuming we don't have enough data yet... + return; + } + + QByteArray contentType = QByteArrayLiteral("Content-Type: text/plain"); + int contentTypeIndex = m_readBuffer.indexOf(contentType); + if (contentTypeIndex == -1) { + qCWarning(dcDoorBird) << "Cannot find Content-Type in buffer:" << m_readBuffer; + if (m_readBuffer.size() > startIndex + 50) { + qCWarning(dcDoorBird) << boundary << "found but unexpected data follows. Skipping this element..."; + m_readBuffer.remove(0, startIndex + boundary.length()); + continue; + } + // Assuming we don't have enough data yet... + return; + } + + // At this point we have the boundary and Content-Type. Remove all of that and take the entire string to either end or next boundary + m_readBuffer.remove(0, contentTypeIndex + contentType.length()); + int nextStartIndex = m_readBuffer.indexOf(boundary); + QByteArray data; + if (nextStartIndex == -1) { + data = m_readBuffer; + m_readBuffer.clear(); + } else { + data = m_readBuffer.left(nextStartIndex); + m_readBuffer.remove(0, nextStartIndex); + } + + QString message = data.trimmed(); + QStringList parts = message.split(":"); + if (parts.count() != 2) { + qCWarning(dcDoorBird) << "Message has invalid format:" << message << "Expected device:state"; + continue; + } + if (parts.first() == "doorbell") { + if (parts.at(1) == "H") { + qCDebug(dcDoorBird) << "Doorbell ringing!"; + emit eventReveiced(EventType::Doorbell, true); + } else { + emit eventReveiced(EventType::Doorbell, false); + } + } else if (parts.first() == "motionsensor") { + if (parts.at(1) == "H") { + qCDebug(dcDoorBird) << "Motion sensor detected a person"; + emit eventReveiced(EventType::Motion, true); + } else { + emit eventReveiced(EventType::Motion, false); + } + } else { + qCWarning(dcDoorBird) << "Unhandled DoorBird data:" << message; + } + } + }); + + connect(reply, &QNetworkReply::finished, this, [this, reply]() { + reply->deleteLater(); + + emit deviceConnected(false); + qCDebug(dcDoorBird) << "Monitor request finished:" << reply->error(); + + QTimer::singleShot(2000, this, [this] { + connectToEventMonitor(); + }); + }); +} + +void Doorbird::onUdpBroadcast(const QByteArray &data) +{ + Q_UNUSED(data); + //TODO decryption +} + + +/*NotifyBroadcastCiphertext decryptBroadcastNotification(const NotifyBroadcast* notification, const + StretchedPassword* password) { + NotifyBroadcastCiphertext decrypted = {{0},{0},0}; + if(crypto_aead_chacha20poly1305_decrypt((unsigned char*)&decrypted, NULL, NULL, notification->ciphertext, + sizeof(notification->ciphertext), NULL, 0, notification->nonce, password->key)!=0){ + LOGGING("crypto_aead_chacha20poly1305_decrypt() failed"); + } + return decrypted; +} + +unsigned char* stretchPasswordArgon(const char *password, unsigned char *salt, unsigned* oplimit, unsigned* memlimit) { + if (sodium_is_zero(salt, CRYPTO_SALT_BYTES) && random_bytes(salt, CRYPTO_SALT_BYTES)) { + return NULL; + } + unsigned char* key = malloc(CRYPTO_ARGON_OUT_SIZE); + if (!*oplimit) { + *oplimit = crypto_pwhash_argon2i_OPSLIMIT_INTERACTIVE; + } + if (!*memlimit) { + *memlimit = crypto_pwhash_MEMLIMIT_MIN; + } + if (crypto_pwhash(key, CRYPTO_ARGON_OUT_SIZE, password, str_len(password), salt, *oplimit, *memlimit, crypto_pwhash_ALG_ARGON2I13)) { + LOGGING("Argon2 Failed"); + *oplimit = 0; + *memlimit = 0; + CLEAN(key); + return NULL; + } + return key; +}*/ diff --git a/doorbird/doorbird.h b/doorbird/doorbird.h new file mode 100644 index 00000000..a09bb788 --- /dev/null +++ b/doorbird/doorbird.h @@ -0,0 +1,117 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DOORBIRD_H +#define DOORBIRD_H + +#include +#include +#include +#include +#include + +#include "network/networkaccessmanager.h" + +class Doorbird : public QObject +{ + Q_OBJECT +public: + explicit Doorbird(const QHostAddress &address, QObject *parent = nullptr); + + enum EventType { + Doorbell, + Motion, + Input, + Rfid + }; + enum FavoriteType { + Http, + Sip + }; + + struct FavoriteObject { + FavoriteType type; + QString title; + QUrl value; + int id; + }; + + QHostAddress address(); + void setAddress(const QHostAddress &address); + QUuid getSession(const QString &username, const QString &password); + QUuid openDoor(int value); + QUuid lightOn(); + QUuid liveVideoRequest(); + QUuid liveImageRequest(); + QUuid historyImageRequest(int index); + + QUuid liveAudioReceive(); + QUuid liveAudioTransmit(); + QUuid infoRequest(); + + QUuid listFavorites(); + QUuid addFavorite(FavoriteType type, const QString &name, const QUrl &url, int id); + QUuid deleteFavorite(); + + QUuid listSchedules(); + QUuid addScheduleEntry(EventType event, int favoriteNumber, bool enabled); + QUuid deleteScheduleEntry(); + + QUuid restart(); + + void connectToEventMonitor(); +private: + QHostAddress m_address; + QNetworkAccessManager *m_networkAccessManager; + QByteArray m_readBuffer; + + + QList m_networkRequests; + + QList m_pendingAuthentications; + + QString m_username; + QString m_password; + +signals: + void deviceConnected(bool status); + void requestSent(QUuid requestId, bool success); + + void eventReveiced(EventType eventType, bool status); + void favoritesReceived(QList favourites); + + void sessionIdReceived(const QString &sessionId); + void liveImageReceived(QImage image); + +public slots: + void onUdpBroadcast(const QByteArray &data); +}; + +#endif // DOORBIRD_H diff --git a/doorbird/doorbird.jpg b/doorbird/doorbird.jpg new file mode 100644 index 00000000..6819bf14 Binary files /dev/null and b/doorbird/doorbird.jpg differ diff --git a/doorbird/doorbird.pro b/doorbird/doorbird.pro new file mode 100644 index 00000000..7efb2342 --- /dev/null +++ b/doorbird/doorbird.pro @@ -0,0 +1,11 @@ +include(../plugins.pri) + +QT += network + +SOURCES += \ + integrationplugindoorbird.cpp \ + doorbird.cpp \ + +HEADERS += \ + integrationplugindoorbird.h \ + doorbird.h \ diff --git a/doorbird/integrationplugindoorbird.cpp b/doorbird/integrationplugindoorbird.cpp new file mode 100644 index 00000000..ddce2f09 --- /dev/null +++ b/doorbird/integrationplugindoorbird.cpp @@ -0,0 +1,306 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "integrationplugindoorbird.h" +#include "plugininfo.h" + +#include "platform/platformzeroconfcontroller.h" +#include "network/zeroconf/zeroconfservicebrowser.h" +#include "network/zeroconf/zeroconfserviceentry.h" + +#include +#include +#include +#include + +IntegrationPluginDoorbird::IntegrationPluginDoorbird() +{ +} + + +void IntegrationPluginDoorbird::discoverThings(ThingDiscoveryInfo *info) +{ + if (info->thingClassId() == doorBirdThingClassId) { + ZeroConfServiceBrowser *serviceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_axis-video._tcp"); + connect(info, &QObject::destroyed, serviceBrowser, &QObject::deleteLater); + + QTimer::singleShot(5000, this, [this, info, serviceBrowser](){ + foreach (const ZeroConfServiceEntry serviceEntry, serviceBrowser->serviceEntries()) { + if (serviceEntry.hostName().startsWith("bha-")) { + qCDebug(dcDoorBird) << "Found DoorBird Thing, name: " << serviceEntry.name() << "\n host address:" << serviceEntry.hostAddress().toString() << "\n text:" << serviceEntry.txt() << serviceEntry.protocol() << serviceEntry.serviceType(); + ThingDescriptor ThingDescriptor(doorBirdThingClassId, serviceEntry.name(), serviceEntry.hostAddress().toString()); + ParamList params; + QString macAddress; + if (serviceEntry.txt().length() == 0) { + qCWarning(dcDoorBird()) << "Discovery failed, service entry missing"; + continue; + } + + if (serviceEntry.txt().first().split("=").length() == 2) { + macAddress = serviceEntry.txt().first().split("=").last(); + } else { + qCWarning(dcDoorBird()) << "Could not parse MAC Address" << serviceEntry.txt().first(); + continue; + } + if (!myThings().filterByParam(doorBirdThingSerialnumberParamTypeId, macAddress).isEmpty()) { + Thing *existingThing = myThings().filterByParam(doorBirdThingSerialnumberParamTypeId, macAddress).first(); + ThingDescriptor.setThingId(existingThing->id()); + } + params.append(Param(doorBirdThingSerialnumberParamTypeId, macAddress)); + params.append(Param(doorBirdThingAddressParamTypeId, serviceEntry.hostAddress().toString())); + ThingDescriptor.setParams(params); + info->addThingDescriptor(ThingDescriptor); + } + } + serviceBrowser->deleteLater(); + info->finish(Thing::ThingErrorNoError); + }); + return; + } else { + qCWarning(dcDoorBird()) << "Cannot discover for ThingClassId" << info->thingClassId(); + info->finish(Thing::ThingErrorThingClassNotFound); + } +} + + +void IntegrationPluginDoorbird::startPairing(ThingPairingInfo *info) +{ + if (info->thingClassId() == doorBirdThingClassId) { + info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter username and password for the DoorBird Thing")); + return; + } else { + qCWarning(dcDoorBird()) << "StartPairing unhandled ThingClassId" << info->thingClassId(); + info->finish(Thing::ThingErrorThingClassNotFound); + } +} + + +void IntegrationPluginDoorbird::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &password) +{ + if (info->thingClassId() == doorBirdThingClassId) { + QHostAddress address = QHostAddress(info->params().paramValue(doorBirdThingAddressParamTypeId).toString()); + + Doorbird *doorbird = new Doorbird(address, this); + connect(doorbird, &Doorbird::deviceConnected, this, &IntegrationPluginDoorbird::onDoorBirdConnected); + connect(doorbird, &Doorbird::eventReveiced, this, &IntegrationPluginDoorbird::onDoorBirdEvent); + connect(doorbird, &Doorbird::requestSent, this, &IntegrationPluginDoorbird::onDoorBirdRequestSent); + connect(doorbird, &Doorbird::sessionIdReceived, this, &IntegrationPluginDoorbird::onSessionIdReceived); + m_doorbirdConnections.insert(info->thingId(), doorbird); + m_pendingPairings.insert(doorbird, info); + doorbird->getSession(username, password); + connect(info, &ThingPairingInfo::aborted, this, [this, info]{ + if (m_pendingPairings.values().contains(info)) { + Doorbird *doorbird = m_pendingPairings.key(info); + m_pendingPairings.remove(doorbird); + doorbird->deleteLater(); + } + m_doorbirdConnections.remove(info->thingId()); + }); + + pluginStorage()->beginGroup(info->thingId().toString()); + pluginStorage()->setValue("username", username); + pluginStorage()->setValue("password", password); + pluginStorage()->endGroup(); + } else { + qCWarning(dcDoorBird()) << "Confirm pairing ThingClassNotFound" << info->thingClassId(); + info->finish(Thing::ThingErrorThingClassNotFound); + } +} + + +void IntegrationPluginDoorbird::setupThing(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + + if (thing->thingClassId() == doorBirdThingClassId) { + QHostAddress address = QHostAddress(thing->paramValue(doorBirdThingAddressParamTypeId).toString()); + + if (m_doorbirdConnections.contains(thing->id())) { + info->finish(Thing::ThingErrorNoError); + } else { + pluginStorage()->beginGroup(thing->id().toString()); + QString username = pluginStorage()->value("username").toString(); + QString password = pluginStorage()->value("password").toString(); + pluginStorage()->endGroup(); + + qCDebug(dcDoorBird()) << "Thing setup" << thing->name() << username << password; + Doorbird *doorbird = new Doorbird(address, this); + connect(doorbird, &Doorbird::deviceConnected, this, &IntegrationPluginDoorbird::onDoorBirdConnected); + connect(doorbird, &Doorbird::eventReveiced, this, &IntegrationPluginDoorbird::onDoorBirdEvent); + connect(doorbird, &Doorbird::requestSent, this, &IntegrationPluginDoorbird::onDoorBirdRequestSent); + connect(doorbird, &Doorbird::sessionIdReceived, this, &IntegrationPluginDoorbird::onSessionIdReceived); + m_doorbirdConnections.insert(thing->id(), doorbird); + m_pendingThingSetups.insert(doorbird, info); + doorbird->getSession(username, password); + connect(info, &ThingSetupInfo::aborted, this, [thing, doorbird, this] { + if (!doorbird) { + doorbird->deleteLater(); + } + m_doorbirdConnections.remove(thing->id()); + m_pendingPairings.remove(doorbird); + }); + } + } else { + qCWarning(dcDoorBird()) << "Unhandled Thing class" << info->thing()->thingClass(); + info->finish(Thing::ThingErrorThingClassNotFound); + } +} + + +void IntegrationPluginDoorbird::postSetupThing(Thing *thing) +{ + if (thing->thingClassId() == doorBirdThingClassId) { + thing->setStateValue(doorBirdConnectedStateTypeId, true); //since we checked the connection in the ThingSetup + Doorbird *doorbird = m_doorbirdConnections.value(thing->id()); + doorbird->connectToEventMonitor(); + doorbird->infoRequest(); + doorbird->listFavorites(); + doorbird->listSchedules(); + } +} + + +void IntegrationPluginDoorbird::executeAction(ThingActionInfo *info) +{ + Thing *thing = info->thing(); + Action action = info->action(); + + if (thing->thingClassId() == doorBirdThingClassId) { + Doorbird *doorbird = m_doorbirdConnections.value(thing->id()); + if (!doorbird) { + qCWarning(dcDoorBird()) << "Doorbird object not found" << thing->name(); + info->finish(Thing::ThingErrorHardwareFailure); + return; + } + if (action.actionTypeId() == doorBirdOpenDoorActionTypeId) { + int number = action.param(doorBirdOpenDoorActionNumberParamTypeId).value().toInt(); + QUuid requestId = doorbird->openDoor(number); + m_asyncActions.insert(requestId, info); + connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); + return; + } else if (action.actionTypeId() == doorBirdLightOnActionTypeId) { + QUuid requestId = doorbird->lightOn(); + m_asyncActions.insert(requestId, info); + connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); + return; + } else if (action.actionTypeId() == doorBirdRestartActionTypeId) { + QUuid requestId = doorbird->restart(); + m_asyncActions.insert(requestId, info); + connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); + return; + } else { + qCWarning(dcDoorBird()) << "Unhandled ActionTypeId:" << action.actionTypeId(); + info->finish(Thing::ThingErrorActionTypeNotFound); + } + } else { + qCWarning(dcDoorBird()) << "Execute action, unhandled Thing class" << thing->thingClass(); + info->finish(Thing::ThingErrorThingClassNotFound); + } +} + +void IntegrationPluginDoorbird::thingRemoved(Thing *thing) +{ + if (thing->thingClassId() == doorBirdThingClassId) { + Doorbird *doorbirdConnection = m_doorbirdConnections.take(thing->id()); + doorbirdConnection->deleteLater(); + } +} + +void IntegrationPluginDoorbird::onDoorBirdConnected(bool status) +{ + Doorbird *doorbird = static_cast(sender()); + Thing *thing = myThings().findById(m_doorbirdConnections.key(doorbird)); + if (!thing) + return; + + thing->setStateValue(doorBirdConnectedStateTypeId, status); +} + +void IntegrationPluginDoorbird::onDoorBirdEvent(Doorbird::EventType eventType, bool status) +{ + Doorbird *doorbird = static_cast(sender()); + Thing *thing = myThings().findById(m_doorbirdConnections.key(doorbird)); + if (!thing) + return; + + switch (eventType) { + case Doorbird::EventType::Rfid: + break; + case Doorbird::EventType::Input: + break; + case Doorbird::EventType::Motion: + thing->setStateValue(doorBirdIsPresentStateTypeId, status); + if (status) { + thing->setStateValue(doorBirdLastSeenTimeStateTypeId, QDateTime::currentDateTime().toTime_t()); + } + break; + case Doorbird::EventType::Doorbell: + if (status) { + emit emitEvent(Event(doorBirdDoorbellPressedEventTypeId ,thing->id())); + } + break; + } +} + +void IntegrationPluginDoorbird::onDoorBirdRequestSent(QUuid requestId, bool success) +{ + Doorbird *doorbird = static_cast(sender()); + + if (m_asyncActions.contains(requestId)) { + ThingActionInfo* actionInfo = m_asyncActions.take(requestId); + actionInfo->finish(success ? Thing::ThingErrorNoError : Thing::ThingErrorInvalidParameter); + } + + if (m_pendingPairings.contains(doorbird) && !success) { + ThingPairingInfo *info = m_pendingPairings.take(doorbird); + info->finish(Thing::ThingErrorAuthenticationFailure, tr("Wrong username or password")); + } + + if (m_pendingThingSetups.contains(doorbird) && !success) { + ThingSetupInfo *info = m_pendingThingSetups.take(doorbird); + info->finish(Thing::ThingErrorAuthenticationFailure, tr("Wrong username or password")); + } +} + +void IntegrationPluginDoorbird::onSessionIdReceived(const QString &sessionId) +{ + Q_UNUSED(sessionId); + Doorbird *doorbird = static_cast(sender()); + + if (m_pendingPairings.contains(doorbird)) { + ThingPairingInfo *info = m_pendingPairings.take(doorbird); + info->finish(Thing::ThingErrorNoError); + } + + if (m_pendingThingSetups.contains(doorbird)) { + ThingSetupInfo *info = m_pendingThingSetups.take(doorbird); + info->finish(Thing::ThingErrorNoError); + } +} diff --git a/doorbird/integrationplugindoorbird.h b/doorbird/integrationplugindoorbird.h new file mode 100644 index 00000000..3168785d --- /dev/null +++ b/doorbird/integrationplugindoorbird.h @@ -0,0 +1,76 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef INTEGRATIONPLUGINDOORBIRD_H +#define INTEGRATIONPLUGINDOORBIRD_H + +#include + +#include "integrations/integrationplugin.h" +#include "doorbird.h" + +class QNetworkAccessManager; +class QNetworkReply; + +class IntegrationPluginDoorbird: public IntegrationPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationplugindoorbird.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginDoorbird(); + + void discoverThings(ThingDiscoveryInfo *info) override; + void setupThing(ThingSetupInfo *info) override; + void postSetupThing(Thing *thing) override; + void executeAction(ThingActionInfo *info) override; + + void startPairing(ThingPairingInfo *info) override; + void confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) override; + + void thingRemoved(Thing *thing)override; + +private: + QHash m_doorbirdConnections; + QHash m_pendingPairings; + QHash m_pendingThingSetups; + + QHash m_asyncActions; + +private slots: + void onDoorBirdConnected(bool status); + void onDoorBirdEvent(Doorbird::EventType eventType, bool status); + void onDoorBirdRequestSent(QUuid requestId, bool success); + void onSessionIdReceived(const QString &sessionId); +}; + +#endif // INTEGRATIONPLUGINDOORBIRD_H diff --git a/doorbird/integrationplugindoorbird.json b/doorbird/integrationplugindoorbird.json new file mode 100644 index 00000000..9c9ba049 --- /dev/null +++ b/doorbird/integrationplugindoorbird.json @@ -0,0 +1,101 @@ +{ + "name": "doorBird", + "displayName": "DoorBird", + "id": "6fe1614a-fc47-4eb2-a47c-13c50f1798ee", + "vendors": [ + { + "name": "doorBird", + "displayName": "DoorBird", + "id": "2da07435-571e-4956-a387-6caa51d6e845", + "thingClasses": [ + { + "id": "0485eb61-2a22-42ba-9dd2-a5961485bf08", + "name": "doorBird", + "displayName": "DoorBird", + "createMethods": ["discovery", "user" ], + "interfaces": [ "doorbell", "presencesensor", "connectable" ], + "setupMethod": "userandpassword", + "paramTypes": [ + { + "id": "8873b17d-526e-408d-95d8-6439b501f489", + "name": "address", + "displayName": "IP address", + "type": "QString" + }, + { + "id": "67ea5534-330a-4291-93b5-0237034e15fa", + "name": "serialnumber", + "displayName": "Serial number", + "type": "QString" + } + ], + "actionTypes": [ + { + "id": "b6c3377b-91de-411a-9d48-8b509c39d67c", + "name": "openDoor", + "displayName": "Open door", + "paramTypes": [ + { + "id": "95dd35d7-0bc3-49e1-af96-d8da8ea5244d", + "name": "number", + "displayName": "Relay number", + "type": "QString", + "allowedValues": [ + "1", + "2" + ], + "defaultValue": 1 + } + ] + }, + { + "id": "3a6cfc5d-804c-4d21-91b5-999913d4f1a5", + "name": "lightOn", + "displayName": "Light on" + }, + { + "id": "e874242e-5acb-4d98-94c7-0a70db65150c", + "name": "restart", + "displayName": "Restart" + } + ], + "stateTypes": [ + { + "id": "186c270b-923c-46e4-a7da-33e45427cdbb", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "0f5eb200-6c0d-45c5-9156-3060fd66d332", + "name": "isPresent", + "displayName": "Motion sensor presence", + "displayNameEvent": "Motion sensor presence detected", + "type": "bool", + "defaultValue": false + }, + { + "id": "295c9700-b598-4681-898f-d63e2889cedf", + "name": "lastSeenTime", + "displayName": "Motion sensor last seen time", + "displayNameEvent": "Motion sensor last seen time changed", + "type": "int", + "unit": "UnixTime", + "defaultValue": 0 + } + ], + "eventTypes": [ + { + "id": "9bc89937-a2ab-4e8e-af0e-a9ba41caa89b", + "name": "doorbellPressed", + "displayName": "Doorbell pressed" + } + ] + } + ] + } + ] +} diff --git a/doorbird/meta.json b/doorbird/meta.json new file mode 100644 index 00000000..aeda49d0 --- /dev/null +++ b/doorbird/meta.json @@ -0,0 +1,13 @@ +{ + "title": "DoorBird", + "tagline": "Integrates DoorBird doorstations with nymea.", + "icon": "doorbird.jpg", + "stability": "consumer", + "offline": true, + "technologies": [ + "network" + ], + "categories": [ + "sensor" + ] +} diff --git a/doorbird/translations/6fe1614a-fc47-4eb2-a47c-13c50f1798ee-de.ts b/doorbird/translations/6fe1614a-fc47-4eb2-a47c-13c50f1798ee-de.ts new file mode 100644 index 00000000..2369c8ad --- /dev/null +++ b/doorbird/translations/6fe1614a-fc47-4eb2-a47c-13c50f1798ee-de.ts @@ -0,0 +1,114 @@ + + + + + DevicePluginDoorbird + + + Please enter the user credentials + Bitte geben Sie die Benutzeranmeldeinformationen ein + + + + doorBird + + + + Connected + The name of the ParamType (DeviceClass: doorBird, EventType: connected, ID: {186c270b-923c-46e4-a7da-33e45427cdbb}) +---------- +The name of the StateType ({186c270b-923c-46e4-a7da-33e45427cdbb}) of DeviceClass doorBird + Verbunden + + + + Connected changed + The name of the EventType ({186c270b-923c-46e4-a7da-33e45427cdbb}) of DeviceClass doorBird + Verbunden geändert + + + + + + DoorBird + The name of the DeviceClass ({0485eb61-2a22-42ba-9dd2-a5961485bf08}) +---------- +The name of the vendor ({2da07435-571e-4956-a387-6caa51d6e845}) +---------- +The name of the plugin doorBird ({6fe1614a-fc47-4eb2-a47c-13c50f1798ee}) + DoorBird + + + + Doorbell pressed + The name of the EventType ({9bc89937-a2ab-4e8e-af0e-a9ba41caa89b}) of DeviceClass doorBird + Türklingel gedrückt + + + + IP address + The name of the ParamType (DeviceClass: doorBird, Type: device, ID: {8873b17d-526e-408d-95d8-6439b501f489}) + IP Adresse + + + + Light on + The name of the ActionType ({3a6cfc5d-804c-4d21-91b5-999913d4f1a5}) of DeviceClass doorBird + Licht ein + + + + + Motion sensor last seen time + The name of the ParamType (DeviceClass: doorBird, EventType: lastSeenTime, ID: {295c9700-b598-4681-898f-d63e2889cedf}) +---------- +The name of the StateType ({295c9700-b598-4681-898f-d63e2889cedf}) of DeviceClass doorBird + Bewegungssensor zuletzt gesehene Zeit + + + + Motion sensor last seen time changedd + The name of the EventType ({295c9700-b598-4681-898f-d63e2889cedf}) of DeviceClass doorBird + Bewegungssensor zuletzt gesehene Zeit geändert + + + + + Motion sensor presence + The name of the ParamType (DeviceClass: doorBird, EventType: isPresent, ID: {0f5eb200-6c0d-45c5-9156-3060fd66d332}) +---------- +The name of the StateType ({0f5eb200-6c0d-45c5-9156-3060fd66d332}) of DeviceClass doorBird + Bewegungssensors Aktivität + + + + Motion sensor presence detected + The name of the EventType ({0f5eb200-6c0d-45c5-9156-3060fd66d332}) of DeviceClass doorBird + Bewegungssensor Aktivität geändert + + + + Open door + The name of the ActionType ({b6c3377b-91de-411a-9d48-8b509c39d67c}) of DeviceClass doorBird + Tür öffnen + + + + Relay number + The name of the ParamType (DeviceClass: doorBird, ActionType: openDoor, ID: {95dd35d7-0bc3-49e1-af96-d8da8ea5244d}) + Relaisnummer + + + + Restart + The name of the ActionType ({e874242e-5acb-4d98-94c7-0a70db65150c}) of DeviceClass doorBird + Neustarten + + + + Serial number + The name of the ParamType (DeviceClass: doorBird, Type: device, ID: {67ea5534-330a-4291-93b5-0237034e15fa}) + Seriennummer + + + diff --git a/doorbird/translations/6fe1614a-fc47-4eb2-a47c-13c50f1798ee-en_US.ts b/doorbird/translations/6fe1614a-fc47-4eb2-a47c-13c50f1798ee-en_US.ts new file mode 100644 index 00000000..13499b31 --- /dev/null +++ b/doorbird/translations/6fe1614a-fc47-4eb2-a47c-13c50f1798ee-en_US.ts @@ -0,0 +1,120 @@ + + + + + IntegrationPluginDoorbird + + + Please enter username and password for the DoorBird Thing + + + + + + Wrong username or password + + + + + doorBird + + + + Connected + The name of the ParamType (ThingClass: doorBird, EventType: connected, ID: {186c270b-923c-46e4-a7da-33e45427cdbb}) +---------- +The name of the StateType ({186c270b-923c-46e4-a7da-33e45427cdbb}) of ThingClass doorBird + + + + + Connected changed + The name of the EventType ({186c270b-923c-46e4-a7da-33e45427cdbb}) of ThingClass doorBird + + + + + + + DoorBird + The name of the ThingClass ({0485eb61-2a22-42ba-9dd2-a5961485bf08}) +---------- +The name of the vendor ({2da07435-571e-4956-a387-6caa51d6e845}) +---------- +The name of the plugin doorBird ({6fe1614a-fc47-4eb2-a47c-13c50f1798ee}) + + + + + Doorbell pressed + The name of the EventType ({9bc89937-a2ab-4e8e-af0e-a9ba41caa89b}) of ThingClass doorBird + + + + + IP address + The name of the ParamType (ThingClass: doorBird, Type: thing, ID: {8873b17d-526e-408d-95d8-6439b501f489}) + + + + + Light on + The name of the ActionType ({3a6cfc5d-804c-4d21-91b5-999913d4f1a5}) of ThingClass doorBird + + + + + + Motion sensor last seen time + The name of the ParamType (ThingClass: doorBird, EventType: lastSeenTime, ID: {295c9700-b598-4681-898f-d63e2889cedf}) +---------- +The name of the StateType ({295c9700-b598-4681-898f-d63e2889cedf}) of ThingClass doorBird + + + + + Motion sensor last seen time changed + The name of the EventType ({295c9700-b598-4681-898f-d63e2889cedf}) of ThingClass doorBird + + + + + + Motion sensor presence + The name of the ParamType (ThingClass: doorBird, EventType: isPresent, ID: {0f5eb200-6c0d-45c5-9156-3060fd66d332}) +---------- +The name of the StateType ({0f5eb200-6c0d-45c5-9156-3060fd66d332}) of ThingClass doorBird + + + + + Motion sensor presence detected + The name of the EventType ({0f5eb200-6c0d-45c5-9156-3060fd66d332}) of ThingClass doorBird + + + + + Open door + The name of the ActionType ({b6c3377b-91de-411a-9d48-8b509c39d67c}) of ThingClass doorBird + + + + + Relay number + The name of the ParamType (ThingClass: doorBird, ActionType: openDoor, ID: {95dd35d7-0bc3-49e1-af96-d8da8ea5244d}) + + + + + Restart + The name of the ActionType ({e874242e-5acb-4d98-94c7-0a70db65150c}) of ThingClass doorBird + + + + + Serial number + The name of the ParamType (ThingClass: doorBird, Type: thing, ID: {67ea5534-330a-4291-93b5-0237034e15fa}) + + + + diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 61ee4220..08b0c5a9 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -13,6 +13,7 @@ PLUGIN_DIRS = \ datetime \ daylightsensor \ denon \ + doorbird \ dweetio \ dynatrace \ elgato \