mirror of https://github.com/nymea/nymea.git
397 lines
14 KiB
C++
397 lines
14 KiB
C++
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
* *
|
|
* Copyright (C) 2016 Simon Stürz <simon.stuerz@guh.io> *
|
|
* *
|
|
* This file is part of guh. *
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Lesser General Public *
|
|
* License as published by the Free Software Foundation; either *
|
|
* version 2.1 of the License, or (at your option) any later version. *
|
|
* *
|
|
* This library 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 library; If not, see *
|
|
* <http://www.gnu.org/licenses/>. *
|
|
* *
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
/*!
|
|
\page dollhouse.html
|
|
\title Dollhouse
|
|
\brief Plugin for a guh demo booth based on 6LoWPAN networking.
|
|
|
|
\ingroup plugins
|
|
\ingroup guh-plugins-merkur
|
|
|
|
The plugin for the guh-dollhouse demo booth.
|
|
|
|
\chapter Plugin properties
|
|
Following JSON file contains the definition and the description of all available \l{DeviceClass}{DeviceClasses}
|
|
and \l{Vendor}{Vendors} of this \l{DevicePlugin}.
|
|
|
|
For more details how to read this JSON file please check out the documentation for \l{The plugin JSON File}.
|
|
|
|
\quotefile plugins/deviceplugins/dollhouse/deviceplugindollhouse.json
|
|
*/
|
|
|
|
#include "deviceplugindollhouse.h"
|
|
#include "plugininfo.h"
|
|
|
|
#include <QUrlQuery>
|
|
|
|
DevicePluginDollHouse::DevicePluginDollHouse() :
|
|
m_houseReachable(false)
|
|
{
|
|
m_coap = new Coap(this);
|
|
connect(m_coap, SIGNAL(replyFinished(CoapReply*)), this, SLOT(coapReplyFinished(CoapReply*)));
|
|
}
|
|
|
|
DeviceManager::HardwareResources DevicePluginDollHouse::requiredHardware() const
|
|
{
|
|
return DeviceManager::HardwareResourceNetworkManager | DeviceManager::HardwareResourceTimer;
|
|
}
|
|
|
|
DeviceManager::DeviceSetupStatus DevicePluginDollHouse::setupDevice(Device *device)
|
|
{
|
|
qCDebug(dcDollhouse) << "Setup" << device->name() << device->params();
|
|
|
|
if (device->deviceClassId() == connectionDeviceClassId) {
|
|
|
|
foreach (Device *device, myDevices()) {
|
|
if (device->deviceClassId() == connectionDeviceClassId) {
|
|
qCWarning(dcDollhouse) << "Dollhouse connection allready configured.";
|
|
return DeviceManager::DeviceSetupStatusFailure;
|
|
}
|
|
}
|
|
|
|
int lookupId = QHostInfo::lookupHost(device->paramValue(rplParamTypeId).toString(), this, SLOT(hostLockupFinished(QHostInfo)));
|
|
m_asyncSetup.insert(lookupId, device);
|
|
|
|
return DeviceManager::DeviceSetupStatusAsync;
|
|
|
|
} else if (device->deviceClassId() == lightDeviceClassId) {
|
|
|
|
DollhouseLight *light = new DollhouseLight(this);
|
|
light->setName(device->paramValue(nameParamTypeId).toString());
|
|
light->setHostAddress(device->paramValue(addressParamTypeId).toString());
|
|
light->setConnectionUuid(device->paramValue(connectionUuidParamTypeId).toString());
|
|
light->setLightId(device->paramValue(lightIdParamTypeId).toInt());
|
|
|
|
device->setParentId(DeviceId(light->connectionUuid()));
|
|
|
|
m_lights.insert(device, light);
|
|
|
|
return DeviceManager::DeviceSetupStatusSuccess;
|
|
}
|
|
|
|
return DeviceManager::DeviceSetupStatusFailure;
|
|
}
|
|
|
|
|
|
void DevicePluginDollHouse::deviceRemoved(Device *device)
|
|
{
|
|
if (device->deviceClassId() == lightDeviceClassId) {
|
|
DollhouseLight *light = m_lights.take(device);
|
|
light->deleteLater();
|
|
}
|
|
|
|
m_houseAddress.clear();
|
|
m_houseReachable = false;
|
|
}
|
|
|
|
void DevicePluginDollHouse::guhTimer()
|
|
{
|
|
foreach (Device *device, myDevices()) {
|
|
if (device->deviceClassId() == connectionDeviceClassId && m_houseAddress.isNull()) {
|
|
scanNodes(device);
|
|
}
|
|
}
|
|
|
|
if (!m_houseAddress.isNull()) {
|
|
QUrl url;
|
|
url.setScheme("coap");
|
|
url.setHost(m_houseAddress.toString());
|
|
url.setPort(5683);
|
|
url.setPath("/a/ws2812");
|
|
m_asyncPings.append(m_coap->ping(CoapRequest(url)));
|
|
}
|
|
|
|
}
|
|
|
|
DeviceManager::DeviceError DevicePluginDollHouse::executeAction(Device *device, const Action &action)
|
|
{
|
|
if (device->deviceClassId() == lightDeviceClassId) {
|
|
if (!device->stateValue(reachableStateTypeId).toBool())
|
|
return DeviceManager::DeviceErrorHardwareNotAvailable;
|
|
|
|
DollhouseLight *light = m_lights.value(device);
|
|
|
|
// Create URL for action
|
|
QUrlQuery query;
|
|
query.addQueryItem("number", QString::number(light->lightId()));
|
|
|
|
QUrl url;
|
|
url.setScheme("coap");
|
|
url.setHost(device->paramValue(addressParamTypeId).toString());
|
|
url.setPath("/a/ws2812");
|
|
url.setQuery(query);
|
|
|
|
if (action.actionTypeId() == colorActionTypeId) {
|
|
QColor color = action.param(colorStateParamTypeId).value().value<QColor>().toHsv();
|
|
QColor newColor = QColor::fromHsv(color.hue(), color.saturation(), 100 * light->brightness() / 255.0);
|
|
QByteArray message = "color=" + newColor.toRgb().name().remove("#").toUtf8();
|
|
|
|
qCDebug(dcDollhouse) << "Sending" << url.toString() << message;
|
|
|
|
CoapReply *reply = m_coap->post(CoapRequest(url), message);
|
|
m_asyncActions.insert(reply, action);
|
|
m_asyncActionLights.insert(action.id(), light);
|
|
|
|
return DeviceManager::DeviceErrorAsync;
|
|
|
|
} else if (action.actionTypeId() == powerActionTypeId) {
|
|
|
|
QByteArray message;
|
|
if (action.param(powerStateParamTypeId).value().toBool()) {
|
|
QColor color = light->color().toHsv();
|
|
QColor newColor = QColor::fromHsv(color.hue(), color.saturation(), 100 * light->brightness() / 255.0);
|
|
message = "color=" + newColor.toRgb().name().remove("#").toUtf8();
|
|
} else {
|
|
message.append("color=000000");
|
|
}
|
|
|
|
qCDebug(dcDollhouse) << "Sending" << url.toString() << message;
|
|
|
|
CoapReply *reply = m_coap->post(CoapRequest(url), message);
|
|
m_asyncActions.insert(reply, action);
|
|
m_asyncActionLights.insert(action.id(), light);
|
|
|
|
return DeviceManager::DeviceErrorAsync;
|
|
} else if (action.actionTypeId() == brightnessActionTypeId) {
|
|
|
|
int brightness = action.param(brightnessStateParamTypeId).value().toInt();
|
|
|
|
QColor color = light->color().toHsv();
|
|
QColor newColor = QColor::fromHsv(color.hue(), color.saturation(), 100 * brightness / 255.0);
|
|
|
|
QByteArray message = "color=" + newColor.toRgb().name().remove("#").toUtf8();
|
|
|
|
qCDebug(dcDollhouse) << "Sending" << url.toString() << message;
|
|
|
|
CoapReply *reply = m_coap->post(CoapRequest(url), message);
|
|
m_asyncActions.insert(reply, action);
|
|
m_asyncActionLights.insert(action.id(), light);
|
|
|
|
return DeviceManager::DeviceErrorAsync;
|
|
}
|
|
}
|
|
|
|
return DeviceManager::DeviceErrorNoError;
|
|
}
|
|
|
|
void DevicePluginDollHouse::networkManagerReplyReady(QNetworkReply *reply)
|
|
{
|
|
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
|
|
// create user finished
|
|
if (m_asyncNodeScan.contains(reply)) {
|
|
Device *device = m_asyncNodeScan.take(reply);
|
|
|
|
// check HTTP status code
|
|
if (status != 200) {
|
|
qCWarning(dcDollhouse) << "Node scan reply HTTP error:" << reply->errorString();
|
|
reply->deleteLater();
|
|
return;
|
|
}
|
|
|
|
parseNode(device, reply->readAll());
|
|
}
|
|
reply->deleteLater();
|
|
}
|
|
|
|
void DevicePluginDollHouse::scanNodes(Device *device)
|
|
{
|
|
QUrl url;
|
|
url.setScheme("http");
|
|
url.setHost(device->paramValue(rplParamTypeId).toString());
|
|
|
|
QNetworkReply *reply = networkManagerGet(QNetworkRequest(url));
|
|
m_asyncNodeScan.insert(reply, device);
|
|
}
|
|
|
|
void DevicePluginDollHouse::parseNode(Device *device, const QByteArray &data)
|
|
{
|
|
|
|
QList<QByteArray> lines = data.split('\n');
|
|
QList<QHostAddress> addresses;
|
|
foreach (const QByteArray &line, lines) {
|
|
if (line.isEmpty())
|
|
continue;
|
|
|
|
// remove the '/128' from the address
|
|
QHostAddress address(QString(data.left(line.length() - 4)));
|
|
|
|
if (!address.isNull())
|
|
addresses.append(address);
|
|
|
|
}
|
|
|
|
// int index = data.indexOf("Routes<pre>") + 11;
|
|
// int delta = data.indexOf("/128",index);
|
|
// QHostAddress houseAddress = QHostAddress(QString(data.mid(index, delta - index)));
|
|
|
|
if (addresses.isEmpty())
|
|
return;
|
|
|
|
QHostAddress houseAddress = addresses.first();
|
|
if (houseAddress != m_houseAddress && !houseAddress.isNull()) {
|
|
m_houseAddress = houseAddress;
|
|
qCDebug(dcDollhouse) << "Found house at" << m_houseAddress.toString();
|
|
|
|
if (!m_lights.isEmpty())
|
|
return;
|
|
|
|
QList<DeviceDescriptor> deviceDescriptorList;
|
|
|
|
for (int i = 0; i < 5; i++) {
|
|
DeviceDescriptor descriptor(lightDeviceClassId, "Light", QString::number(i));
|
|
ParamList params;
|
|
params.append(Param(addressParamTypeId, m_houseAddress.toString()));
|
|
params.append(Param(lightIdParamTypeId, i));
|
|
params.append(Param(connectionUuidParamTypeId, device->id()));
|
|
|
|
switch (i) {
|
|
case 0:
|
|
params.append(Param(nameParamTypeId, "Living room"));
|
|
break;
|
|
case 1:
|
|
params.append(Param(nameParamTypeId, "Kitchen"));
|
|
break;
|
|
case 2:
|
|
params.append(Param(nameParamTypeId, "Under the bed"));
|
|
break;
|
|
case 3:
|
|
params.append(Param(nameParamTypeId, "Bedroom"));
|
|
break;
|
|
case 4:
|
|
params.append(Param(nameParamTypeId, "Dining room"));
|
|
break;
|
|
default:
|
|
params.append(Param(nameParamTypeId, QString("Light %1").arg(QString::number(i))));
|
|
break;
|
|
}
|
|
|
|
descriptor.setParams(params);
|
|
deviceDescriptorList.append(descriptor);
|
|
}
|
|
|
|
if (!deviceDescriptorList.isEmpty())
|
|
emit autoDevicesAppeared(lightDeviceClassId, deviceDescriptorList);
|
|
|
|
}
|
|
}
|
|
|
|
void DevicePluginDollHouse::hostLockupFinished(const QHostInfo &info)
|
|
{
|
|
Device *device = m_asyncSetup.value(info.lookupId());
|
|
if (!device)
|
|
return;
|
|
|
|
if (info.error() != QHostInfo::NoError) {
|
|
qCWarning(dcDollhouse) << "Could not look up host" << info.hostName() << info.errorString();
|
|
emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure);
|
|
}
|
|
|
|
qCDebug(dcDollhouse) << "Looked up successfully" << info.hostName();
|
|
emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusSuccess);
|
|
|
|
scanNodes(device);
|
|
}
|
|
|
|
void DevicePluginDollHouse::coapReplyFinished(CoapReply *reply)
|
|
{
|
|
if (m_asyncPings.contains(reply)) {
|
|
m_asyncPings.removeAll(reply);
|
|
|
|
if (reply->error() != CoapReply::NoError || reply->statusCode() != CoapPdu::Empty) {
|
|
|
|
if (m_houseReachable) {
|
|
qCWarning(dcDollhouse) << "Could not ping Dollhouse:" << reply->errorString();
|
|
m_houseReachable = false;
|
|
foreach (Device *device, myDevices()) {
|
|
if (device->deviceClassId() == lightDeviceClassId) {
|
|
device->setStateValue(reachableStateTypeId, m_houseReachable);
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!m_houseReachable) {
|
|
qCDebug(dcDollhouse) << "Dollhouse reachable";
|
|
m_houseReachable = true;
|
|
foreach (Device *device, myDevices()) {
|
|
if (device->deviceClassId() == lightDeviceClassId) {
|
|
device->setStateValue(reachableStateTypeId, m_houseReachable);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} else if (m_asyncActions.contains(reply)) {
|
|
Action action = m_asyncActions.take(reply);
|
|
DollhouseLight *light = m_asyncActionLights.take(action.id());
|
|
|
|
if (reply->error() != CoapReply::NoError) {
|
|
qCWarning(dcDollhouse) << "Got action response with error" << reply->errorString();
|
|
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
|
|
reply->deleteLater();
|
|
return;
|
|
}
|
|
|
|
if (reply->statusCode() != CoapPdu::Content) {
|
|
qCWarning(dcDollhouse) << "Got action response with status code" << reply;
|
|
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
|
|
reply->deleteLater();
|
|
return;
|
|
}
|
|
|
|
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorNoError);
|
|
|
|
// Set the states
|
|
if (action.actionTypeId() == powerActionTypeId) {
|
|
bool power = action.param(powerStateParamTypeId).value().toBool();
|
|
light->setPower(power);
|
|
m_lights.key(light)->setStateValue(powerStateTypeId, power);
|
|
|
|
} else if (action.actionTypeId() == colorActionTypeId) {
|
|
if (!light->power()) {
|
|
light->setPower(true);
|
|
m_lights.key(light)->setStateValue(powerStateTypeId, true);
|
|
}
|
|
|
|
QColor color = action.param(colorStateParamTypeId).value().value<QColor>();
|
|
light->setColor(color);
|
|
m_lights.key(light)->setStateValue(colorStateTypeId, color);
|
|
|
|
} else if (action.actionTypeId() == brightnessActionTypeId) {
|
|
if (!light->power()) {
|
|
light->setPower(true);
|
|
m_lights.key(light)->setStateValue(powerStateTypeId, true);
|
|
}
|
|
|
|
int brightness = action.param(brightnessStateParamTypeId).value().toInt();
|
|
light->setBrightness(brightness);
|
|
m_lights.key(light)->setStateValue(brightnessStateTypeId, brightness);
|
|
}
|
|
}
|
|
|
|
reply->deleteLater();
|
|
}
|
|
|