nymea/plugins/deviceplugins/dollhouse/deviceplugindollhouse.cpp

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();
}