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 \