moved files from doorbird branch
parent
b14927fc4b
commit
d62e130cb1
|
|
@ -0,0 +1,216 @@
|
||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2019 Bernhard Trinnes <bernhard.trinnes@nymea.io> *
|
||||||
|
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of nymea. *
|
||||||
|
* *
|
||||||
|
* nymea is free software: you can redistribute it and/or modify *
|
||||||
|
* it under the terms of the GNU General Public License as published by *
|
||||||
|
* the Free Software Foundation, version 2 of the License. *
|
||||||
|
* *
|
||||||
|
* nymea 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 General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU General Public License *
|
||||||
|
* along with nymea. If not, see <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#include "deviceplugindoorbird.h"
|
||||||
|
#include "plugininfo.h"
|
||||||
|
|
||||||
|
#include "platform/platformzeroconfcontroller.h"
|
||||||
|
#include "network/zeroconf/zeroconfservicebrowser.h"
|
||||||
|
#include "network/zeroconf/zeroconfserviceentry.h"
|
||||||
|
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QAuthenticator>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
DevicePluginDoorbird::DevicePluginDoorbird()
|
||||||
|
{
|
||||||
|
m_nam = new QNetworkAccessManager(this);
|
||||||
|
connect(m_nam, &QNetworkAccessManager::authenticationRequired, this, [this](QNetworkReply *reply, QAuthenticator *authenticator) {
|
||||||
|
Device *dev = m_networkRequests.value(reply);
|
||||||
|
if (!myDevices().contains(dev)) {
|
||||||
|
qCWarning(dcDoorBird) << "Credentials requested for a device which doesn't exist any more";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qCDebug(dcDoorBird) << "Credentials requested for device:" << dev->name();
|
||||||
|
authenticator->setUser(dev->paramValue(doorBirdDeviceUsernameParamTypeId).toString());
|
||||||
|
authenticator->setPassword(dev->paramValue(doorBirdDevicePasswordParamTypeId).toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
DevicePluginDoorbird::~DevicePluginDoorbird()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Device::DeviceError DevicePluginDoorbird::discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶ms)
|
||||||
|
{
|
||||||
|
Q_UNUSED(deviceClassId)
|
||||||
|
Q_UNUSED(params)
|
||||||
|
|
||||||
|
// NOTE: Discovery is currently disabled in json file because we don't support discovery & login as parameters in combination
|
||||||
|
// and there isn't any setupMethod which would allow us to enter user & password.
|
||||||
|
|
||||||
|
ZeroConfServiceBrowser *serviceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_xbmc-jsonrpc._tcp");
|
||||||
|
QTimer::singleShot(5000, this, [this, serviceBrowser](){
|
||||||
|
QList<DeviceDescriptor> deviceDescriptors;
|
||||||
|
foreach (const ZeroConfServiceEntry serviceEntry, serviceBrowser->serviceEntries()) {
|
||||||
|
if (serviceEntry.serviceType() == "_axis-video._tcp" && serviceEntry.hostName().startsWith("bha-")) {
|
||||||
|
qCDebug(dcDoorBird) << "Found DoorBird device";
|
||||||
|
DeviceDescriptor deviceDescriptor(doorBirdDeviceClassId, serviceEntry.name(), serviceEntry.hostAddress().toString());
|
||||||
|
ParamList params;
|
||||||
|
//TODO add rediscovery
|
||||||
|
params.append(Param(doorBirdDeviceAddressParamTypeId, serviceEntry.hostAddress().toString()));
|
||||||
|
deviceDescriptor.setParams(params);
|
||||||
|
deviceDescriptors.append(deviceDescriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit devicesDiscovered(doorBirdDeviceClassId, deviceDescriptors);
|
||||||
|
serviceBrowser->deleteLater();
|
||||||
|
});
|
||||||
|
return Device::DeviceErrorAsync;
|
||||||
|
}
|
||||||
|
|
||||||
|
Device::DeviceSetupStatus DevicePluginDoorbird::setupDevice(Device *device)
|
||||||
|
{
|
||||||
|
connectToEventMonitor(device);
|
||||||
|
return Device::DeviceSetupStatusSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Device::DeviceError DevicePluginDoorbird::executeAction(Device *device, const Action &action)
|
||||||
|
{
|
||||||
|
if (action.actionTypeId() == doorBirdUnlatchActionTypeId) {
|
||||||
|
QNetworkRequest request(QString("http://%1/bha-api/open-door.cgi?r=1").arg(device->paramValue(doorBirdDeviceAddressParamTypeId).toString()));
|
||||||
|
qCDebug(dcDoorBird) << "Sending request:" << request.url();
|
||||||
|
QNetworkReply *reply = m_nam->get(request);
|
||||||
|
m_networkRequests.insert(reply, device);
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [this, reply, device, action](){
|
||||||
|
reply->deleteLater();
|
||||||
|
m_networkRequests.remove(reply);
|
||||||
|
if (!myDevices().contains(device)) {
|
||||||
|
// Device must have been removed in the meantime
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
qCWarning(dcDoorBird) << "Error unlatching DoorBird device" << device->name();
|
||||||
|
emit actionExecutionFinished(action.id(), Device::DeviceErrorHardwareFailure);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qCDebug(dcDoorBird) << "DoorBird unlatched:" << reply->error() << reply->errorString();
|
||||||
|
emit actionExecutionFinished(action.id(), Device::DeviceErrorNoError);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Device::DeviceErrorDeviceClassNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginDoorbird::connectToEventMonitor(Device *device)
|
||||||
|
{
|
||||||
|
qCDebug(dcDoorBird) << "Starting monitoring" << device->name();
|
||||||
|
|
||||||
|
QNetworkRequest request(QString("http://%1/bha-api/monitor.cgi?ring=doorbell,motionsensor").arg(device->paramValue(doorBirdDeviceAddressParamTypeId).toString()));
|
||||||
|
QNetworkReply *reply = m_nam->get(request);
|
||||||
|
m_networkRequests.insert(reply, device);
|
||||||
|
connect(reply, &QNetworkReply::downloadProgress, this, [this, device, reply](qint64 bytesReceived, qint64 bytesTotal){
|
||||||
|
Q_UNUSED(bytesReceived)
|
||||||
|
Q_UNUSED(bytesTotal);
|
||||||
|
if (!myDevices().contains(device)) {
|
||||||
|
qCWarning(dcDoorBird) << "Device disappeared for monitor stream.";
|
||||||
|
reply->abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
device->setStateValue(doorBirdConnectedStateTypeId, true);
|
||||||
|
m_readBuffers[device].append(reply->readAll());
|
||||||
|
// qCDebug(dcDoorBird) << "Monitor data for" << device->name();
|
||||||
|
// qCDebug(dcDoorBird) << m_readBuffers[device];
|
||||||
|
|
||||||
|
// Input data looks like:
|
||||||
|
// "--ioboundary\r\nContent-Type: text/plain\r\n\r\ndoorbell:H\r\n\r\n"
|
||||||
|
|
||||||
|
while (!m_readBuffers[device].isEmpty()) {
|
||||||
|
// find next --ioboundary
|
||||||
|
QString boundary = QStringLiteral("--ioboundary");
|
||||||
|
int startIndex = m_readBuffers[device].indexOf(boundary);
|
||||||
|
if (startIndex == -1) {
|
||||||
|
qCWarning(dcDoorBird) << "No meaningful data in buffer:" << m_readBuffers[device];
|
||||||
|
if (m_readBuffers[device].size() > 1024) {
|
||||||
|
qCWarning(dcDoorBird) << "Buffer size > 1KB and still no meaningful data. Discarding buffer...";
|
||||||
|
m_readBuffers[device].clear();
|
||||||
|
}
|
||||||
|
// Assuming we don't have enough data yet...
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray contentType = QByteArrayLiteral("Content-Type: text/plain");
|
||||||
|
int contentTypeIndex = m_readBuffers[device].indexOf(contentType);
|
||||||
|
if (contentTypeIndex == -1) {
|
||||||
|
qCWarning(dcDoorBird) << "Cannot find Content-Type in buffer:" << m_readBuffers[device];
|
||||||
|
if (m_readBuffers[device].size() > startIndex + 50) {
|
||||||
|
qCWarning(dcDoorBird) << boundary << "found but unexpected data follows. Skipping this element...";
|
||||||
|
m_readBuffers[device].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_readBuffers[device].remove(0, contentTypeIndex + contentType.length());
|
||||||
|
int nextStartIndex = m_readBuffers[device].indexOf(boundary);
|
||||||
|
QByteArray data;
|
||||||
|
if (nextStartIndex == -1) {
|
||||||
|
data = m_readBuffers[device];
|
||||||
|
m_readBuffers[device].clear();
|
||||||
|
} else {
|
||||||
|
data = m_readBuffers[device].left(nextStartIndex);
|
||||||
|
m_readBuffers[device].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!";
|
||||||
|
emitEvent(Event(doorBirdTriggeredEventTypeId, device->id()));
|
||||||
|
}
|
||||||
|
} else if (parts.first() == "motionsensor") {
|
||||||
|
if (parts.at(1) == "H") {
|
||||||
|
qCDebug(dcDoorBird) << "Motion sensor detected a person";
|
||||||
|
emitEvent(Event(doorBirdMotionDetectedEventTypeId, device->id()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qCWarning(dcDoorBird) << "Unhandled DoorBird data:" << message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [this, device, reply](){
|
||||||
|
reply->deleteLater();
|
||||||
|
m_networkRequests.remove(reply);
|
||||||
|
|
||||||
|
if (!myDevices().contains(device)) {
|
||||||
|
qCWarning(dcDoorBird) << "Device has disappeared. Exiting monitor.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
device->setStateValue(doorBirdConnectedStateTypeId, false);
|
||||||
|
qCDebug(dcDoorBird) << "Monitor request finished:" << reply->error();
|
||||||
|
|
||||||
|
QTimer::singleShot(2000, this, [this, device] {
|
||||||
|
if (!myDevices().contains(device)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
connectToEventMonitor(device);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2019 Michael Zanetti <michael.zanetti@nymea.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of nymea. *
|
||||||
|
* *
|
||||||
|
* nymea is free software: you can redistribute it and/or modify *
|
||||||
|
* it under the terms of the GNU General Public License as published by *
|
||||||
|
* the Free Software Foundation, version 2 of the License. *
|
||||||
|
* *
|
||||||
|
* nymea 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 General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU General Public License *
|
||||||
|
* along with nymea. If not, see <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#ifndef DEVICEPLUGINDOORBIRD_H
|
||||||
|
#define DEVICEPLUGINDOORBIRD_H
|
||||||
|
|
||||||
|
#include "devices/deviceplugin.h"
|
||||||
|
#include "devices/devicemanager.h"
|
||||||
|
|
||||||
|
class QNetworkAccessManager;
|
||||||
|
class QNetworkReply;
|
||||||
|
|
||||||
|
class DevicePluginDoorbird: public DevicePlugin
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "deviceplugindoorbird.json")
|
||||||
|
Q_INTERFACES(DevicePlugin)
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DevicePluginDoorbird();
|
||||||
|
~DevicePluginDoorbird() override;
|
||||||
|
|
||||||
|
Device::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶ms) override;
|
||||||
|
|
||||||
|
Device::DeviceSetupStatus setupDevice(Device *device) override;
|
||||||
|
Device::DeviceError executeAction(Device *device, const Action &action) override;
|
||||||
|
|
||||||
|
void connectToEventMonitor(Device *device);
|
||||||
|
private:
|
||||||
|
|
||||||
|
QNetworkAccessManager *m_nam = nullptr;
|
||||||
|
|
||||||
|
QHash<QNetworkReply*, Device*> m_networkRequests;
|
||||||
|
QHash<Device*, QByteArray> m_readBuffers;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DEVICEPLUGINDOORBIRD_H
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
{
|
||||||
|
"name": "doorBird",
|
||||||
|
"displayName": "DoorBird",
|
||||||
|
"id": "6fe1614a-fc47-4eb2-a47c-13c50f1798ee",
|
||||||
|
"vendors": [
|
||||||
|
{
|
||||||
|
"name": "doorBird",
|
||||||
|
"displayName": "DoorBird",
|
||||||
|
"id": "2da07435-571e-4956-a387-6caa51d6e845",
|
||||||
|
"deviceClasses": [
|
||||||
|
{
|
||||||
|
"id": "0485eb61-2a22-42ba-9dd2-a5961485bf08",
|
||||||
|
"name": "doorBird",
|
||||||
|
"displayName": "DoorBird",
|
||||||
|
"createMethods": ["discovery", "user" ],
|
||||||
|
"interfaces": [ "inputtrigger", "connectable" ],
|
||||||
|
"paramTypes": [
|
||||||
|
{
|
||||||
|
"id": "8873b17d-526e-408d-95d8-6439b501f489",
|
||||||
|
"name": "address",
|
||||||
|
"displayName": "IP address",
|
||||||
|
"type": "QString"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7ccd8f3a-2a5f-4b90-8042-92899d0ee32a",
|
||||||
|
"name": "username",
|
||||||
|
"displayName": "Username",
|
||||||
|
"type": "QString"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ea285a57-47c5-43f1-b0d6-e0a4d6230f3c",
|
||||||
|
"name": "password",
|
||||||
|
"displayName": "Password",
|
||||||
|
"type": "QString"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actionTypes": [
|
||||||
|
{
|
||||||
|
"id": "b6c3377b-91de-411a-9d48-8b509c39d67c",
|
||||||
|
"name": "unlatch",
|
||||||
|
"displayName": "Unlatch the door"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateTypes": [
|
||||||
|
{
|
||||||
|
"id": "186c270b-923c-46e4-a7da-33e45427cdbb",
|
||||||
|
"name": "connected",
|
||||||
|
"displayName": "Connected",
|
||||||
|
"displayNameEvent": "Connected changed",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"eventTypes": [
|
||||||
|
{
|
||||||
|
"id": "9bc89937-a2ab-4e8e-af0e-a9ba41caa89b",
|
||||||
|
"name": "triggered",
|
||||||
|
"displayName": "Doorbell pressed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e9bb229b-8776-4110-a813-9c0dc67375db",
|
||||||
|
"name": "motionDetected",
|
||||||
|
"displayName": "Motion detected"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
include(../plugins.pri)
|
||||||
|
|
||||||
|
QT += network
|
||||||
|
|
||||||
|
TARGET = $$qtLibraryTarget(nymea_deviceplugindoorbird)
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
deviceplugindoorbird.cpp \
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
deviceplugindoorbird.h \
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1">
|
||||||
|
</TS>
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1">
|
||||||
|
</TS>
|
||||||
|
|
@ -12,6 +12,7 @@ PLUGIN_DIRS = \
|
||||||
datetime \
|
datetime \
|
||||||
daylightsensor \
|
daylightsensor \
|
||||||
denon \
|
denon \
|
||||||
|
doorbird \
|
||||||
dweetio \
|
dweetio \
|
||||||
elgato \
|
elgato \
|
||||||
elro \
|
elro \
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue