add guh-plugins-merkur package

add merkur boards plugins
temprary remove coap test (server down)
This commit is contained in:
Simon Stürz 2016-04-11 12:33:38 +02:00 committed by Michael Zanetti
parent 6a60dbe2af
commit 1a9284b89f
41 changed files with 2572 additions and 106 deletions

21
debian/control vendored
View File

@ -113,11 +113,26 @@ Depends: libguh1 (= ${binary:Version}),
${shlibs:Depends},
${misc:Depends}
Recommends:
nmap,
wakeonlan
nmap
Description: Plugins for guh IoT server
guh is an open source IoT (Internet of Things) server,
which allows to control a lot of different devices from many different
manufacturers. 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.
for your environment. This package contains the main plugins.
Package: guh-plugins-merkur
Section: libs
Architecture: any
Depends: libguh1 (= ${binary:Version}),
guhd (= ${binary:Version}),
${shlibs:Depends},
${misc:Depends}
Description: Plugins for guh IoT server - 6LoWPAN Merkur boards
guh is an open source IoT (Internet of Things) server,
which allows to control a lot of different devices from many different
manufacturers. 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 contains plugins for the 6LoWPAN Merkur
board.

1
debian/guh-plugins-merkur.dirs vendored Normal file
View File

@ -0,0 +1 @@
usr/lib/guh/plugins

4
debian/guh-plugins-merkur.install vendored Normal file
View File

@ -0,0 +1,4 @@
usr/lib/guh/plugins/libguh_devicepluginosdomotics.so
usr/lib/guh/plugins/libguh_deviceplugindollhouse.so
usr/lib/guh/plugins/libguh_devicepluginplantcare.so
usr/lib/guh/plugins/libguh_devicepluginws2812.so

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *
@ -28,7 +28,8 @@
The Coap class provides a signal solt based communication with a \l{https://tools.ietf.org/html/rfc7252}{CoAP (Constrained Application Protocol)}
server. The API of this class was inspired by the \l{http://doc.qt.io/qt-5/qnetworkaccessmanager.html}{QNetworkAccessManager} and was
written according to the \l{https://tools.ietf.org/html/rfc7252}{RFC7252}.
This class supports also blockwise transfere according to the \l{https://tools.ietf.org/html/draft-ietf-core-block-18}{IETF V18} specifications.
This class supports also blockwise transfere according to the \l{https://tools.ietf.org/html/draft-ietf-core-block-18}{IETF V18} specifications and
observing resources according to the \l{https://tools.ietf.org/html/rfc7641}{RFC7641}.
\sa CoapReply, CoapRequest
@ -50,6 +51,8 @@
{
if (reply->error() != CoapReply::NoError) {
qWarning() << "Reply finished with error" << reply->errorString();
reply->deleteLater();
return;
}
qDebug() << "Reply finished" << reply;
@ -71,6 +74,8 @@
#include "coappdu.h"
#include "coapoption.h"
Q_LOGGING_CATEGORY(dcCoap, "Coap")
/*! Constructs a coap access manager with the given \a parent and \a port. */
Coap::Coap(QObject *parent, const quint16 &port) :
QObject(parent),
@ -79,7 +84,7 @@ Coap::Coap(QObject *parent, const quint16 &port) :
m_socket = new QUdpSocket(this);
if (!m_socket->bind(QHostAddress::Any, port))
qWarning() << "Could not bind to port" << port << m_socket->errorString();
qCWarning(dcCoap) << "Could not bind to port" << port << m_socket->errorString();
connect(m_socket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
}
@ -134,7 +139,6 @@ CoapReply *Coap::get(const CoapRequest &request)
} else {
m_replyQueue.enqueue(reply);
}
return reply;
}
@ -211,7 +215,7 @@ CoapReply *Coap::deleteResource(const CoapRequest &request)
}
// check if there is a request running
if (m_reply == 0) {
if (m_reply.isNull()) {
m_reply = reply;
lookupHost();
} else {
@ -241,7 +245,7 @@ CoapReply *Coap::enableResourceNotifications(const CoapRequest &request)
}
// check if there is a request running
if (m_reply == 0) {
if (m_reply.isNull()) {
m_reply = reply;
lookupHost();
} else {
@ -359,6 +363,8 @@ void Coap::sendRequest(CoapReply *reply, const bool &lookedUp)
reply->m_lockedUp = lookedUp;
reply->m_timer->start();
qCDebug(dcCoap) << "--->" << pdu;
// send the data
if (reply->request().messageType() == CoapPdu::NonConfirmable) {
sendData(reply->hostAddress(), reply->port(), pduData);
@ -375,17 +381,16 @@ void Coap::sendData(const QHostAddress &hostAddress, const quint16 &port, const
void Coap::sendCoapPdu(const QHostAddress &hostAddress, const quint16 &port, const CoapPdu &pdu)
{
qDebug() << "--->" << pdu;
m_socket->writeDatagram(pdu.pack(), hostAddress, port);
}
void Coap::processResponse(const CoapPdu &pdu, const QHostAddress &address, const quint16 &port)
{
// if we are waiting for a response
// if we are waiting for a reply response
if (m_reply) {
qDebug() << "<---" << QString("%1:%2").arg(address.toString()).arg(QString::number(port)) << pdu;
qCDebug(dcCoap) << "<---" << QString("%1:%2").arg(address.toString()).arg(QString::number(port)) << pdu;
if (!pdu.isValid()) {
qWarning() << "Got invalid PDU";
qCWarning(dcCoap) << "Got invalid PDU";
m_reply->setError(CoapReply::InvalidPduError);
m_reply->setFinished();
return;
@ -403,19 +408,25 @@ void Coap::processResponse(const CoapPdu &pdu, const QHostAddress &address, cons
return;
}
}
// check if this is a notification from a known observed resource
if (m_observerReply) {
processBlock2Notification(m_observerReply, pdu);
return;
}
// check if this is a notification
if (m_observeResources.keys().contains(pdu.token())) {
processNotification(pdu, address, port);
return;
}
qWarning() << "Got message without request or registered observe resource.";
qCDebug(dcCoap) << "Got message without request or registered observe resource." << endl << "<---" << pdu;
CoapPdu responsePdu;
responsePdu.setMessageType(CoapPdu::Reset);
responsePdu.setMessageId(pdu.messageId());
responsePdu.setToken(pdu.token());
sendCoapPdu(address, port, responsePdu);
}
void Coap::processIdBasedResponse(CoapReply *reply, const CoapPdu &pdu)
@ -423,7 +434,7 @@ void Coap::processIdBasedResponse(CoapReply *reply, const CoapPdu &pdu)
// check if this is an empty ACK response (which indicates a separated response)
if (pdu.statusCode() == CoapPdu::Empty && pdu.messageType() == CoapPdu::Acknowledgement) {
reply->m_timer->stop();
qDebug() << "Got empty ACK. Data will be sent separated.";
qCDebug(dcCoap) << "Got empty ACK. Data will be sent separated.";
return;
}
@ -463,12 +474,95 @@ void Coap::processTokenBasedResponse(CoapReply *reply, const CoapPdu &pdu)
void Coap::processNotification(const CoapPdu &pdu, const QHostAddress &address, const quint16 &port)
{
CoapObserveResource resource = m_observeResources.value(pdu.token());
qCDebug(dcCoap) << "<--- Notification" << endl << pdu;
// check if it is a blockwise notification
if (pdu.hasOption(CoapOption::Block2)) {
if (!m_observeReplyResource.values().contains(resource)) {
qCDebug(dcCoap) << "Got first part of blocked notification";
// First part of the blocked notification
// respond with ACK
CoapPdu responsePdu;
responsePdu.setMessageType(CoapPdu::Acknowledgement);
responsePdu.setStatusCode(CoapPdu::Empty);
responsePdu.setMessageId(pdu.messageId());
responsePdu.setToken(pdu.token());
qCDebug(dcCoap) << "---> Notification" << endl << responsePdu;
sendCoapPdu(address, port, responsePdu);
// create reply for blockwise transfere
if (!m_observerReply.isNull()) {
m_observeBlockwise.remove(m_observerReply);
m_observerReply->deleteLater();
}
m_observerReply = new CoapReply(CoapRequest(resource.url()), this);
m_observerReply->setRequestMethod(CoapPdu::Get);
m_observerReply->appendPayloadData(pdu.payload());
// Lets store the observation number
int notificationNumber = 0;
foreach (const CoapOption &option, pdu.options()) {
if (option.option() == CoapOption::Observe) {
notificationNumber = option.data().toHex().toInt(0, 16);
}
}
m_observeReplyResource.insert(m_observerReply, resource);
m_observeBlockwise.insert(m_observerReply, notificationNumber);
connect(m_observerReply, &CoapReply::timeout, this, &Coap::onReplyTimeout);
connect(m_observerReply, &CoapReply::finished, this, &Coap::onReplyFinished);
CoapPdu pdu;
pdu.setMessageType(m_observerReply->request().messageType());
pdu.setStatusCode(m_observerReply->requestMethod());
pdu.createMessageId();
pdu.createToken();
// Add the options in correct order
// Option number 3
pdu.addOption(CoapOption::UriHost, m_observerReply->request().url().host().toUtf8());
QStringList urlTokens = m_observerReply->request().url().path().split("/");
urlTokens.removeAll(QString());
// Option number 11
foreach (const QString &token, urlTokens)
pdu.addOption(CoapOption::UriPath, token.toUtf8());
// Option number 15
if (m_observerReply->request().url().hasQuery())
pdu.addOption(CoapOption::UriQuery, m_observerReply->request().url().query().toUtf8());
// Option number 23
pdu.addOption(CoapOption::Block2, CoapPduBlock::createBlock(1, 2, true));
QByteArray pduData = pdu.pack();
m_observerReply->setRequestData(pduData);
m_observerReply->setHostAddress(address);
m_observerReply->setPort(port);
m_observerReply->m_timer->start();
qCDebug(dcCoap) << "---> Notification" << endl << pdu;
sendData(address, port, pduData);
return;
}
}
// respond with ACK
CoapPdu responsePdu;
responsePdu.setMessageType(CoapPdu::Acknowledgement);
responsePdu.setStatusCode(CoapPdu::Empty);
responsePdu.setMessageId(pdu.messageId());
responsePdu.setToken(pdu.token());
qCDebug(dcCoap) << "---> Notification" << endl << responsePdu;
sendCoapPdu(address, port, responsePdu);
int notificationNumber = 0;
@ -478,13 +572,12 @@ void Coap::processNotification(const CoapPdu &pdu, const QHostAddress &address,
}
}
CoapObserveResource resource = m_observeResources.value(pdu.token());
emit notificationReceived(resource, notificationNumber, pdu.payload());
}
void Coap::processBlock1Response(CoapReply *reply, const CoapPdu &pdu)
{
qDebug() << "sent successfully block #" << pdu.block().blockNumber();
qCDebug(dcCoap) << "Sent successfully block #" << pdu.block().blockNumber();
// create next block
int index = (pdu.block().blockNumber() * 64) + 64;
@ -542,7 +635,7 @@ void Coap::processBlock1Response(CoapReply *reply, const CoapPdu &pdu)
reply->setMessageId(nextBlockRequest.messageId());
qDebug() << "--->" << nextBlockRequest;
qCDebug(dcCoap) << "--->" << nextBlockRequest;
sendData(reply->hostAddress(), reply->port(), pduData);
}
@ -594,7 +687,80 @@ void Coap::processBlock2Response(CoapReply *reply, const CoapPdu &pdu)
reply->setMessageId(nextBlockRequest.messageId());
qDebug() << "--->" << nextBlockRequest;
qCDebug(dcCoap) << "--->" << nextBlockRequest;
sendData(reply->hostAddress(), reply->port(), pduData);
}
void Coap::processBlock2Notification(CoapReply *reply, const CoapPdu &pdu)
{
if (!m_observeReplyResource.contains(reply)) {
qCWarning(dcCoap) << "Could not find observation resource for" << reply;
return;
}
CoapObserveResource resource = m_observeReplyResource.value(reply);
// respond Block2
// check if this was the last block
if (!pdu.block().moreFlag()) {
// respond with ACK
CoapPdu responsePdu;
responsePdu.setMessageType(CoapPdu::Acknowledgement);
responsePdu.setStatusCode(CoapPdu::Empty);
responsePdu.setMessageId(pdu.messageId());
responsePdu.setToken(pdu.token());
qCDebug(dcCoap) << "---> Notification" << endl << responsePdu;
sendCoapPdu(reply->hostAddress(), reply->port(), responsePdu);
reply->appendPayloadData(pdu.payload());
emit notificationReceived(resource, m_observeBlockwise.take(reply), reply->payload());
m_observeReplyResource.remove(m_observerReply);
m_observerReply->deleteLater();
m_observerReply.clear();
return;
}
reply->appendPayloadData(pdu.payload());
CoapPdu nextBlockRequest;
nextBlockRequest.setContentType(reply->request().contentType());
nextBlockRequest.setMessageType(reply->request().messageType());
nextBlockRequest.setStatusCode(reply->requestMethod());
nextBlockRequest.setMessageId(pdu.messageId() + 1);
nextBlockRequest.setToken(pdu.token());
// Add the options in correct order
// Option number 3
if (reply->m_lockedUp)
nextBlockRequest.addOption(CoapOption::UriHost, reply->request().url().host().toUtf8());
// Option number 7
if (reply->port() != 5683)
nextBlockRequest.addOption(CoapOption::UriPort, QByteArray::number(reply->request().url().port()));
QStringList urlTokens = reply->request().url().path().split("/");
urlTokens.removeAll(QString());
// Option number 11
foreach (const QString &token, urlTokens)
nextBlockRequest.addOption(CoapOption::UriPath, token.toUtf8());
// Option number 15
if (reply->request().url().hasQuery())
nextBlockRequest.addOption(CoapOption::UriQuery, reply->request().url().query().toUtf8());
// Option number 23
nextBlockRequest.addOption(CoapOption::Block2, CoapPduBlock::createBlock(pdu.block().blockNumber() + 1, 2, false));
QByteArray pduData = nextBlockRequest.pack();
reply->setRequestData(pduData);
reply->m_timer->start();
reply->setMessageId(nextBlockRequest.messageId());
qCDebug(dcCoap) << "---> Notification" << endl << nextBlockRequest;
sendData(reply->hostAddress(), reply->port(), pduData);
}
@ -604,7 +770,7 @@ void Coap::hostLookupFinished(const QHostInfo &hostInfo)
reply->setPort(reply->request().url().port(5683));
if (hostInfo.error() != QHostInfo::NoError) {
qDebug() << "Host lookup for" << reply->request().url().host() << "failed:" << hostInfo.errorString();
qCDebug(dcCoap) << "Host lookup for" << reply->request().url().host() << "failed:" << hostInfo.errorString();
reply->setError(CoapReply::HostNotFoundError);
reply->setFinished();
return;
@ -615,7 +781,7 @@ void Coap::hostLookupFinished(const QHostInfo &hostInfo)
// check if the url had to be looked up
if (reply->request().url().host() != hostAddress.toString()) {
qDebug() << reply->request().url().host() << " -> " << hostAddress.toString();
qCDebug(dcCoap) << reply->request().url().host() << " -> " << hostAddress.toString();
sendRequest(reply, true);
} else {
sendRequest(reply);
@ -641,7 +807,7 @@ void Coap::onReplyTimeout()
{
CoapReply *reply = qobject_cast<CoapReply *>(sender());
if (reply->m_retransmissions < 5) {
qDebug() << QString("Reply timeout: resending message %1/4").arg(reply->m_retransmissions);
qCDebug(dcCoap) << QString("Reply timeout: resending message %1/4").arg(reply->m_retransmissions);
}
reply->resend();
m_socket->writeDatagram(reply->requestData(), reply->hostAddress(), reply->port());
@ -651,12 +817,20 @@ void Coap::onReplyFinished()
{
CoapReply *reply = qobject_cast<CoapReply *>(sender());
if (reply == m_observerReply) {
m_observeReplyResource.remove(m_observerReply);
m_observerReply->deleteLater();
m_observerReply.clear();
qCWarning(dcCoap) << "Notification reply finished wirh error" << reply->errorString();
return;
}
if (reply != m_reply)
qWarning() << "This should never happen!! Please report a bug if you get this message!";
qCWarning(dcCoap) << "This should never happen!! Please report a bug if you get this message!";
emit replyFinished(reply);
m_reply.clear();
m_reply = 0;
// check if there is a request in the queue
if (!m_replyQueue.isEmpty()) {
m_reply = m_replyQueue.dequeue();

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *
@ -25,9 +25,10 @@
#include <QHostInfo>
#include <QUdpSocket>
#include <QHostAddress>
#include <QLoggingCategory>
#include <QPointer>
#include <QQueue>
#include "libguh.h"
#include "coaprequest.h"
#include "coapreply.h"
#include "coapobserveresource.h"
@ -41,12 +42,13 @@
*
*/
class LIBGUH_EXPORT Coap : public QObject
Q_DECLARE_LOGGING_CATEGORY(dcCoap)
class Coap : public QObject
{
Q_OBJECT
public:
explicit Coap(QObject *parent = 0, const quint16 &port = 5683);
Coap(QObject *parent = 0, const quint16 &port = 5683);
CoapReply *ping(const CoapRequest &request);
CoapReply *get(const CoapRequest &request);
@ -58,15 +60,23 @@ public:
CoapReply *enableResourceNotifications(const CoapRequest &request);
CoapReply *disableNotifications(const CoapRequest &request);
private:
QUdpSocket *m_socket;
CoapReply *m_reply;
QHash<int, CoapReply *> m_runningHostLookups;
QHash<QByteArray, CoapObserveResource> m_observeResources;
QPointer<CoapReply> m_reply;
QQueue<CoapReply *> m_replyQueue;
QHash<int, CoapReply *> m_runningHostLookups;
QHash<QByteArray, CoapObserveResource> m_observeResources; // token | resource
// Blockwise notifications
QPointer<CoapReply> m_observerReply;
QHash<CoapReply *, CoapObserveResource> m_observeReplyResource; // observe reply | resource
QHash<CoapReply *, int> m_observeBlockwise; // observe reply | observe nr.
void lookupHost();
void sendRequest(CoapReply *reply, const bool &lookedUp = false);
void sendData(const QHostAddress &hostAddress, const quint16 &port, const QByteArray &data);
@ -81,6 +91,9 @@ private:
void processBlock1Response(CoapReply *reply, const CoapPdu &pdu);
void processBlock2Response(CoapReply *reply, const CoapPdu &pdu);
void processBlock2Notification(CoapReply *reply, const CoapPdu &pdu);
signals:
void replyFinished(CoapReply *reply);
void notificationReceived(const CoapObserveResource &resource, const int &notificationNumber, const QByteArray &payload);
@ -92,5 +105,4 @@ private slots:
void onReplyFinished();
};
#endif // COAP_H

30
libguh/coap/coap.pri Normal file
View File

@ -0,0 +1,30 @@
QT += network
QMAKE_CXXFLAGS += -Werror -std=c++11
QMAKE_LFLAGS += -std=c++11
INCLUDEPATH += $$PWD
DEPENDPATH += $$PWD
HEADERS += \
$$PWD/coap.h \
$$PWD/coappdu.h \
$$PWD/coapoption.h \
$$PWD/coaprequest.h \
$$PWD/coapreply.h \
$$PWD/coappdublock.h \
$$PWD/corelinkparser.h \
$$PWD/corelink.h \
$$PWD/coapobserveresource.h
SOURCES += \
$$PWD/coap.cpp \
$$PWD/coappdu.cpp \
$$PWD/coapoption.cpp \
$$PWD/coaprequest.cpp \
$$PWD/coapreply.cpp \
$$PWD/coappdublock.cpp \
$$PWD/corelinkparser.cpp \
$$PWD/corelink.cpp \
$$PWD/coapobserveresource.cpp

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *
@ -22,11 +22,10 @@
#define COAPOBSERVERESOURCE_H
#include <QObject>
#include <QHash>
#include <QUrl>
#include "libguh.h"
class LIBGUH_EXPORT CoapObserveResource
class CoapObserveResource
{
public:
@ -43,4 +42,15 @@ private:
};
inline bool operator==(const CoapObserveResource &r1, const CoapObserveResource &r2)
{
return r1.url() == r2.url() && r1.token() == r2.token();
}
inline uint qHash(const CoapObserveResource &key, uint seed)
{
return qHash(key.url().toString(), seed);
}
#endif // COAPOBSERVERESOURCE_H

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *
@ -20,12 +20,12 @@
/*!
\class CoapOption
\brief Represents the option of a Coap PDU.
\brief Represents the option of a \l{CoapPdu}.
\ingroup coap
\inmodule libguh
The CoapOption class provides an easy way to create / parse CoAP options of a CoAP PDU (Protocol Data Unit). An options
The CoapOption class provides an easy way to create / parse CoAP options of a \l{CoapPdu} (Protocol Data Unit). An options
can be compared with a HTTP header.
*/

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *
@ -25,9 +25,7 @@
#include <QObject>
#include <QByteArray>
#include "libguh.h"
class LIBGUH_EXPORT CoapOption
class CoapOption
{
Q_GADGET
Q_ENUMS(Option)

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *
@ -317,6 +317,7 @@ QList<CoapOption> CoapPdu::options() const
return m_options;
}
/*! Adds the given \a option with the given \a data to this \l{CoapPdu}.
\sa CoapOption
@ -492,7 +493,6 @@ void CoapPdu::unpack(const QByteArray &data)
// create a CoapPDU
if (data.length() < 4) {
m_error = InvalidPduSizeError;
qWarning() << "pdu to small" << data.length();
}
quint8 *rawData = (quint8 *)data.data();
@ -502,7 +502,6 @@ void CoapPdu::unpack(const QByteArray &data)
if (tokenLength > 8) {
m_error = InvalidTokenError;
qWarning() << "PDU token to long";
}
setToken(QByteArray((const char *)rawData + 4, tokenLength));

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *
@ -27,8 +27,6 @@
#include "coapoption.h"
#include "coappdublock.h"
#include "libguh.h"
// PDU = Protocol Data Unit
/* 0 1 2 3
@ -44,7 +42,7 @@
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
class LIBGUH_EXPORT CoapPdu : public QObject
class CoapPdu : public QObject
{
Q_OBJECT
Q_ENUMS(MessageType)
@ -151,6 +149,7 @@ public:
void clear();
bool isValid() const;
bool isNull() const;
QByteArray pack() const;

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *
@ -23,9 +23,7 @@
#include <QByteArray>
#include "libguh.h"
class LIBGUH_EXPORT CoapPduBlock
class CoapPduBlock
{
public:
CoapPduBlock();

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *
@ -45,6 +45,8 @@
{
if (reply->error() != CoapReply::NoError) {
qWarning() << "Reply finished with error" << reply->errorString();
reply->deleteLater();
return;
}
qDebug() << "Reply finished" << reply;
@ -52,7 +54,6 @@
}
\endcode
\sa Coap, CoapRequest
*/

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *
@ -24,13 +24,11 @@
#include <QObject>
#include <QTimer>
#include "libguh.h"
#include "coappdu.h"
#include "coapoption.h"
#include "coaprequest.h"
class LIBGUH_EXPORT CoapReply : public QObject
class CoapReply : public QObject
{
friend class Coap;

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *
@ -24,14 +24,12 @@
#include <QUrl>
#include <QHostAddress>
#include "libguh.h"
#include "coappdu.h"
#include "coapoption.h"
//class Coap;
class LIBGUH_EXPORT CoapRequest
class CoapRequest
{
// friend class Coap;
public:

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *
@ -24,10 +24,9 @@
#include <QObject>
#include <QDebug>
#include "libguh.h"
#include "coappdu.h"
class LIBGUH_EXPORT CoreLink
class CoreLink
{
public:
CoreLink();

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *
@ -52,12 +52,9 @@ CoreLinkParser::CoreLinkParser(const QByteArray &data, QObject *parent) :
{
QList<QByteArray> linkList = data.split(',');
foreach (const QByteArray &linkLine, linkList) {
qDebug() << "-------------------------------------";
qDebug() << linkLine;
QList<QByteArray> valueList = linkLine.split(';');
CoreLink link;
foreach (const QByteArray &value, valueList) {
qDebug() << value;
if (value.startsWith("<")) {
link.setPath(QString(value.mid(1, value.length() - 2)));
} else if (value.startsWith("rt=")) {
@ -74,7 +71,6 @@ CoreLinkParser::CoreLinkParser(const QByteArray &data, QObject *parent) :
link.setObservable(true);
}
}
qDebug() << endl << link;
m_links.append(link);
}
}
@ -84,5 +80,3 @@ QList<CoreLink> CoreLinkParser::links() const
{
return m_links;
}

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of QtCoap. *
* *
@ -23,12 +23,11 @@
#include <QObject>
#include "libguh.h"
#include "corelink.h"
// Constrained RESTful Environments (CoRE) Link Format : http://tools.ietf.org/html/rfc6690
class LIBGUH_EXPORT CoreLinkParser : public QObject
class CoreLinkParser : public QObject
{
Q_OBJECT
public:

View File

@ -1,4 +1,5 @@
include(../guh.pri)
include(coap/coap.pri)
TARGET = guh
TEMPLATE = lib
@ -39,15 +40,6 @@ SOURCES += devicemanager.cpp \
network/upnpdiscovery/upnpdiscoveryrequest.cpp \
network/networkmanager.cpp \
network/oauth2.cpp \
coap/coap.cpp \
coap/coappdu.cpp \
coap/coapoption.cpp \
coap/coaprequest.cpp \
coap/coapreply.cpp \
coap/coappdublock.cpp \
coap/corelinkparser.cpp \
coap/corelink.cpp \
coap/coapobserveresource.cpp \
types/action.cpp \
types/actiontype.cpp \
types/state.cpp \
@ -85,15 +77,6 @@ HEADERS += devicemanager.h \
network/upnpdiscovery/upnpdiscoveryrequest.h \
network/networkmanager.h \
network/oauth2.h \
coap/coap.h \
coap/coappdu.h \
coap/coapoption.h \
coap/coaprequest.h \
coap/coapreply.h \
coap/coappdublock.h \
coap/corelinkparser.h \
coap/corelink.h \
coap/coapobserveresource.h \
types/action.h \
types/actiontype.h \
types/state.h \

View File

@ -23,6 +23,9 @@
#include <QLoggingCategory>
// Include dcCoap
#include "coap/coap.h"
// Core / libguh
Q_DECLARE_LOGGING_CATEGORY(dcApplication)
Q_DECLARE_LOGGING_CATEGORY(dcDeviceManager)

View File

@ -15,10 +15,12 @@ SUBDIRS += elro \
commandlauncher \
unitec \
leynew \
#tune \
udpcommander \
kodi \
elgato \
awattar \
netatmo \
#osdomotics \
dollhouse \
plantcare \
osdomotics \
ws2812 \

View File

@ -0,0 +1,375 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of guh. *
* *
* Guh 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. *
* *
* Guh 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 guh. If not, see <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#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("RPL address").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("name").toString());
light->setHostAddress(device->paramValue("address").toString());
light->setConnectionUuid(device->paramValue("connection uuid").toString());
light->setLightId(device->paramValue("light id").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("address").toString());
url.setPath("/a/ws2812");
url.setQuery(query);
if (action.actionTypeId() == colorActionTypeId) {
QColor color = action.param("color").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("power").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("brightness").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("RPL address").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("address", m_houseAddress.toString()));
params.append(Param("light id", i));
params.append(Param("connection uuid", device->id()));
switch (i) {
case 0:
params.append(Param("name", "Living room"));
break;
case 1:
params.append(Param("name", "Kitchen"));
break;
case 2:
params.append(Param("name", "Under the bed"));
break;
case 3:
params.append(Param("name", "Bedroom"));
break;
case 4:
params.append(Param("name", "Dining room"));
break;
default:
params.append(Param("name", 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("power").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("color").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("brightness").value().toInt();
light->setBrightness(brightness);
m_lights.key(light)->setStateValue(brightnessStateTypeId, brightness);
}
}
reply->deleteLater();
}

View File

@ -0,0 +1,74 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2016 Simon Stuerz <simon.stuerz@guh.guru> *
* *
* This file is part of guh. *
* *
* Guh 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. *
* *
* Guh 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 guh. If not, see <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef DEVICEPLUGINDOLLHOUSE_H
#define DEVICEPLUGINDOLLHOUSE_H
#include "plugin/deviceplugin.h"
#include "devicemanager.h"
#include "coap/coap.h"
#include "dollhouselight.h"
#include <QHash>
class DevicePluginDollHouse : public DevicePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "deviceplugindollhouse.json")
Q_INTERFACES(DevicePlugin)
public:
DevicePluginDollHouse();
DeviceManager::HardwareResources requiredHardware() const override;
DeviceManager::DeviceSetupStatus setupDevice(Device *device) override;
void deviceRemoved(Device *device) override;
void guhTimer() override;
DeviceManager::DeviceError executeAction(Device *device, const Action &action) override;
void networkManagerReplyReady(QNetworkReply *reply) override;
private:
Coap *m_coap;
QHash<int, Device *> m_asyncSetup;
QHash<QNetworkReply *, Device *> m_asyncNodeScan;
QHash<CoapReply *, Action> m_asyncActions;
QHash<ActionId, DollhouseLight *> m_asyncActionLights;
QList<CoapReply *> m_asyncPings;
QHostAddress m_houseAddress;
bool m_houseReachable;
QHash<Device *, DollhouseLight *> m_lights;
void scanNodes(Device *device);
void parseNode(Device *device, const QByteArray &data);
private slots:
void hostLockupFinished(const QHostInfo &info);
void coapReplyFinished(CoapReply *reply);
};
#endif // DEVICEPLUGINDOLLHOUSE_H

View File

@ -0,0 +1,106 @@
{
"name": "Smart Dollhouse",
"idName": "Dollhouse",
"id": "ee62716c-64b5-4ec5-a877-9a8d9c1042cb",
"vendors": [
{
"name": "guh",
"idName": "guh",
"id": "2062d64d-3232-433c-88bc-0d33c0ba2ba6",
"deviceClasses": [
{
"name": "Dollhouse Connection",
"idName": "connection",
"deviceClassId": "44e12a1c-9711-4780-9913-53bb38264e1e",
"createMethods": ["user"],
"basicTags": [
"Device",
"Gateway"
],
"paramTypes": [
{
"name": "name",
"type": "QString",
"defaultValue": "Dollhouse Connection"
},
{
"name": "RPL address",
"type": "QString",
"inputType": "TextLine",
"defaultValue": "fdaa:e9b8:d03a::ff:fe00:1"
}
]
},
{
"name": "Dollhouse Light",
"idName": "light",
"deviceClassId": "b4dd5f10-36d4-4232-867a-6d3b04a08bad",
"createMethods": ["auto"],
"basicTags": [
"Device",
"Lighting"
],
"paramTypes": [
{
"name": "name",
"type": "QString",
"defaultValue": "Dollhouse Light"
},
{
"name": "address",
"type": "QString"
},
{
"name": "light id",
"type": "int",
"defaultValue": 0,
"readOnly": true
},
{
"name": "connection uuid",
"type": "QString",
"inputType": "TextLine",
"readOnly": true
}
],
"stateTypes": [
{
"id": "e4885a73-9fe1-48ca-b773-aeb93f34cd54",
"idName": "color",
"name": "color",
"type": "QColor",
"defaultValue": "#fff30a",
"writable": true
},
{
"id": "e78c9136-c10a-45c9-b6bc-2d937f09cdec",
"idName": "brightness",
"name": "brightness",
"type": "int",
"unit": "Percentage",
"minValue": 0,
"maxValue": 100,
"defaultValue": 100,
"writable": true
},
{
"id": "f6ac30a0-77b8-4f1f-8c44-4c2e6d542663",
"idName": "power",
"name": "power",
"type": "bool",
"defaultValue": false,
"writable": true
},
{
"id": "93c539b4-50d8-431e-8be4-5ebba89452b7",
"idName": "reachable",
"name": "reachable",
"type": "bool",
"defaultValue": false
}
]
}
]
}
]
}

View File

@ -0,0 +1,11 @@
include(../../plugins.pri)
TARGET = $$qtLibraryTarget(guh_deviceplugindollhouse)
SOURCES += \
deviceplugindollhouse.cpp \
dollhouselight.cpp
HEADERS += \
deviceplugindollhouse.h \
dollhouselight.h

View File

@ -0,0 +1,81 @@
#include "dollhouselight.h"
DollhouseLight::DollhouseLight(QObject *parent) :
QObject(parent),
m_color(QColor("#fff30a")),
m_brightness(100),
m_power(false)
{
}
QString DollhouseLight::name() const
{
return m_name;
}
void DollhouseLight::setName(const QString &name)
{
m_name = name;
}
QString DollhouseLight::connectionUuid() const
{
return m_connectionUuid;
}
void DollhouseLight::setConnectionUuid(const QString &connectionUuid)
{
m_connectionUuid = connectionUuid;
}
QString DollhouseLight::hostAddress() const
{
return m_hostAddress;
}
void DollhouseLight::setHostAddress(const QString &address)
{
m_hostAddress = address;
}
int DollhouseLight::lightId() const
{
return m_lightId;
}
void DollhouseLight::setLightId(const int &lightId)
{
m_lightId = lightId;
}
QColor DollhouseLight::color() const
{
return m_color;
}
void DollhouseLight::setColor(const QColor &color)
{
m_color = color;
}
int DollhouseLight::brightness() const
{
return m_brightness;
}
void DollhouseLight::setBrightness(const int &brightness)
{
m_brightness = brightness;
}
bool DollhouseLight::power() const
{
return m_power;
}
void DollhouseLight::setPower(const bool &power)
{
m_power = power;
}

View File

@ -0,0 +1,47 @@
#ifndef DOLLHOUSELIGHT_H
#define DOLLHOUSELIGHT_H
#include <QObject>
#include <QColor>
class DollhouseLight : public QObject
{
Q_OBJECT
public:
explicit DollhouseLight(QObject *parent = 0);
QString name() const;
void setName(const QString &name);
QString connectionUuid() const;
void setConnectionUuid(const QString &connectionUuid);
QString hostAddress() const;
void setHostAddress(const QString &address);
int lightId() const;
void setLightId(const int &lightId);
// properties
QColor color() const;
void setColor(const QColor &color);
int brightness() const;
void setBrightness(const int &brightness);
bool power() const;
void setPower(const bool &power);
private:
QString m_name;
QString m_connectionUuid;
QString m_hostAddress;
int m_lightId;
QColor m_color;
int m_brightness;
bool m_power;
};
#endif // DOLLHOUSELIGHT_H

View File

@ -0,0 +1,561 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2016 Bernhard Trinnes <bernhard.trinnes@guh.guru> *
* *
* This file is part of guh. *
* *
* Guh 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. *
* *
* Guh 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 guh. If not, see <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "devicepluginplantcare.h"
#include "plugin/device.h"
#include "plugininfo.h"
DevicePluginPlantCare::DevicePluginPlantCare()
{
}
DeviceManager::HardwareResources DevicePluginPlantCare::requiredHardware() const
{
// We need the NetworkManager for node discovery and the timer for ping requests
return DeviceManager::HardwareResourceNetworkManager | DeviceManager::HardwareResourceTimer;
}
DeviceManager::DeviceSetupStatus DevicePluginPlantCare::setupDevice(Device *device)
{
qCDebug(dcPlantCare) << "Setup Plant Care" << device->name() << device->params();
// Check if device already added with this address
if (deviceAlreadyAdded(QHostAddress(device->paramValue("host").toString()))) {
qCWarning(dcPlantCare) << "Device with this address already added.";
return DeviceManager::DeviceSetupStatusFailure;
}
// Create the CoAP socket if not already created
if (m_coap.isNull()) {
m_coap = new Coap(this);
connect(m_coap.data(), SIGNAL(replyFinished(CoapReply*)), this, SLOT(coapReplyFinished(CoapReply*)));
connect(m_coap.data(), SIGNAL(notificationReceived(CoapObserveResource,int,QByteArray)), this, SLOT(onNotificationReceived(CoapObserveResource,int,QByteArray)));
}
return DeviceManager::DeviceSetupStatusSuccess;
}
void DevicePluginPlantCare::deviceRemoved(Device *device)
{
Q_UNUSED(device)
// Delete the CoAP socket if there are no devices left
if (myDevices().isEmpty()) {
m_coap->deleteLater();
}
}
DeviceManager::DeviceError DevicePluginPlantCare::discoverDevices(const DeviceClassId &deviceClassId, const ParamList &params)
{
Q_UNUSED(params)
// Perform a HTTP GET on the RPL router address
QHostAddress address(configuration().paramValue("RPL address").toString());
qCDebug(dcPlantCare) << "Scan for new nodes on RPL" << address.toString();
QUrl url;
url.setScheme("http");
url.setHost(address.toString());
m_asyncNodeScans.insert(networkManagerGet(QNetworkRequest(url)), deviceClassId);
return DeviceManager::DeviceErrorAsync;
}
void DevicePluginPlantCare::networkManagerReplyReady(QNetworkReply *reply)
{
if (m_asyncNodeScans.keys().contains(reply)) {
DeviceClassId deviceClassId = m_asyncNodeScans.take(reply);
// Check HTTP status code
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
qCWarning(dcPlantCare) << "Node scan reply HTTP error:" << reply->errorString();
emit devicesDiscovered(deviceClassId, QList<DeviceDescriptor>());
reply->deleteLater();
return;
}
QByteArray data = reply->readAll();
qCDebug(dcPlantCare) << "Node discovery finished:" << endl << data;
QList<DeviceDescriptor> deviceDescriptors;
QList<QByteArray> lines = data.split('\n');
qCDebug(dcPlantCare) << lines;
foreach (const QByteArray &line, lines) {
if (line.isEmpty())
continue;
QHostAddress address(QString(line.left(line.length() - 4)));
if (address.isNull())
continue;
qCDebug(dcPlantCare) << "Found node" << address.toString();
// Create a deviceDescriptor for each found address
DeviceDescriptor descriptor(deviceClassId, "Plant Care", address.toString());
ParamList params;
params.append(Param("host", address.toString()));
descriptor.setParams(params);
deviceDescriptors.append(descriptor);
}
// Inform the user which devices were found
emit devicesDiscovered(deviceClassId, deviceDescriptors);
}
// Delete the HTTP reply
reply->deleteLater();
}
void DevicePluginPlantCare::postSetupDevice(Device *device)
{
// Try to ping the device after a successful setup
pingDevice(device);
}
void DevicePluginPlantCare::guhTimer()
{
// Try to ping each device every 10 seconds to make sure it is still reachable
foreach (Device *device, myDevices()) {
if (device->deviceClassId() == plantCareDeviceClassId) {
pingDevice(device);
}
}
}
DeviceManager::DeviceError DevicePluginPlantCare::executeAction(Device *device, const Action &action)
{
if (device->deviceClassId() != plantCareDeviceClassId)
return DeviceManager::DeviceErrorDeviceClassNotFound;
qCDebug(dcPlantCare) << "Execute action" << device->name() << action.params();
// Check if the device is reachable
if (!device->stateValue(reachableStateTypeId).toBool()) {
qCWarning(dcPlantCare) << "Device not reachable.";
return DeviceManager::DeviceErrorHardwareNotAvailable;
}
// Check which action sould be executed
if (action.actionTypeId() == toggleLedActionTypeId) {
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
url.setPath("/a/toggle");
CoapReply *reply = m_coap->post(CoapRequest(url));
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
qCWarning(dcPlantCare) << "CoAP reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return DeviceManager::DeviceErrorHardwareFailure;
}
m_toggleLightRequests.insert(reply, action);
m_asyncActions.insert(action.id(), device);
return DeviceManager::DeviceErrorAsync;
} else if(action.actionTypeId() == brightnessActionTypeId) {
int light = qRound(action.param("brightness").value().toInt() * 255.0 / 100.0);
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
url.setPath("/a/light");
QByteArray payload = QString("pwm=%1").arg(QString::number(light)).toUtf8();
qCDebug(dcPlantCare()) << "Sending" << payload << url.path();
CoapReply *reply = m_coap->post(CoapRequest(url), payload);
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
qCWarning(dcPlantCare) << "CoAP reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return DeviceManager::DeviceErrorHardwareFailure;
}
m_setBrightness.insert(reply, action);
m_asyncActions.insert(action.id(), device);
return DeviceManager::DeviceErrorAsync;
} else if(action.actionTypeId() == waterPumpActionTypeId) {
bool pump = action.param("water pump power").value().toBool();
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
url.setPath("/a/pump");
QByteArray payload = QString("mode=%1").arg(QString::number((int)pump)).toUtf8();
qCDebug(dcPlantCare()) << "Sending" << payload;
CoapReply *reply = m_coap->post(CoapRequest(url), payload);
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
qCWarning(dcPlantCare) << "CoAP reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return DeviceManager::DeviceErrorHardwareFailure;
}
m_setPumpPower.insert(reply, action);
m_asyncActions.insert(action.id(), device);
return DeviceManager::DeviceErrorAsync;
}
return DeviceManager::DeviceErrorActionTypeNotFound;
}
void DevicePluginPlantCare::pingDevice(Device *device)
{
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
m_pingReplies.insert(m_coap->ping(CoapRequest(url)), device);
}
void DevicePluginPlantCare::updateBattery(Device *device)
{
qCDebug(dcPlantCare) << "Update" << device->name() << "battery value";
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
url.setPath("/s/battery");
CoapReply *reply = m_coap->get(CoapRequest(url));
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
qCWarning(dcPlantCare) << "CoAP reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return;
}
m_updateReplies.insert(reply, device);
}
void DevicePluginPlantCare::updateMoisture(Device *device)
{
qCDebug(dcPlantCare) << "Update" << device->name() << "moisture value";
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
url.setPath("/s/moisture");
CoapReply *reply = m_coap->get(CoapRequest(url));
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
qCWarning(dcPlantCare) << "CoAP reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return;
}
m_updateReplies.insert(reply, device);
}
void DevicePluginPlantCare::updateWater(Device *device)
{
qCDebug(dcPlantCare) << "Update" << device->name() << "water value";
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
url.setPath("/s/water");
CoapReply *reply = m_coap->get(CoapRequest(url));
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
qCWarning(dcPlantCare) << "CoAP reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return;
}
m_updateReplies.insert(reply, device);
}
void DevicePluginPlantCare::updateBrightness(Device *device)
{
qCDebug(dcPlantCare) << "Update" << device->name() << "brightness value";
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
url.setPath("/a/light");
CoapReply *reply = m_coap->get(CoapRequest(url));
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
qCWarning(dcPlantCare) << "CoAP reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return;
}
m_updateReplies.insert(reply, device);
}
void DevicePluginPlantCare::updatePump(Device *device)
{
qCDebug(dcPlantCare) << "Update" << device->name() << "pump value";
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
url.setPath("/a/pump");
CoapReply *reply = m_coap->get(CoapRequest(url));
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
qCWarning(dcPlantCare) << "CoAP reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return;
}
m_updateReplies.insert(reply, device);
}
void DevicePluginPlantCare::enableNotifications(Device *device)
{
qCDebug(dcPlantCare) << "Enable" << device->name() << "notifications";
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
url.setPath("/s/water");
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
url.setPath("/s/moisture");
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
url.setPath("/s/battery");
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
url.setPath("/a/light");
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
url.setPath("/a/pump");
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
}
void DevicePluginPlantCare::setReachable(Device *device, const bool &reachable)
{
if (device->stateValue(reachableStateTypeId).toBool() != reachable) {
if (!reachable) {
// Warn just once that the device is not reachable
qCWarning(dcPlantCare()) << device->name() << "reachable changed" << reachable;
} else {
qCDebug(dcPlantCare()) << device->name() << "reachable changed" << reachable;
// Get current state values after a reconnect
updateBattery(device);
updateBrightness(device);
updateMoisture(device);
updateWater(device);
updatePump(device);
// Make sure the notifications are enabled
enableNotifications(device);
}
}
device->setStateValue(reachableStateTypeId, reachable);
}
bool DevicePluginPlantCare::deviceAlreadyAdded(const QHostAddress &address)
{
// Check if we already have a device with the given address
foreach (Device *device, myDevices()) {
if (device->paramValue("host").toString() == address.toString()) {
return true;
}
}
return false;
}
Device *DevicePluginPlantCare::findDevice(const QHostAddress &address)
{
// Return the device pointer with the given address (otherwise 0)
foreach (Device *device, myDevices()) {
if (device->paramValue("host").toString() == address.toString()) {
return device;
}
}
return NULL;
}
void DevicePluginPlantCare::coapReplyFinished(CoapReply *reply)
{
if (m_pingReplies.contains(reply)) {
Device *device = m_pingReplies.take(reply);
// Check CoAP reply error
if (reply->error() != CoapReply::NoError) {
if (device->stateValue(reachableStateTypeId).toBool())
qCWarning(dcPlantCare) << "Ping device" << reply->request().url().toString() << "reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return;
}
setReachable(device, true);
} else if (m_updateReplies.contains(reply)) {
Device *device = m_updateReplies.take(reply);
QString urlPath = reply->request().url().path();
// Check CoAP reply error
if (reply->error() != CoapReply::NoError) {
qCWarning(dcPlantCare) << "Update resource" << urlPath << "reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return;
}
// Check CoAP status code
if (reply->statusCode() != CoapPdu::Content) {
qCWarning(dcPlantCare) << "Update resource" << urlPath << "status code error:" << reply;
reply->deleteLater();
return;
}
// Update corresponding device state
if (urlPath == "/s/moisture") {
qCDebug(dcPlantCare()) << "Updated moisture value:" << reply->payload();
device->setStateValue(moistureStateTypeId, qRound(reply->payload().toInt() * 100.0 / 1023.0));
} else if (urlPath == "/s/water") {
qCDebug(dcPlantCare()) << "Updated water value:" << reply->payload();
device->setStateValue(waterStateTypeId, QVariant(reply->payload().toInt()).toBool());
} else if (urlPath == "/s/battery") {
qCDebug(dcPlantCare()) << "Updated battery value:" << reply->payload();
device->setStateValue(batteryStateTypeId, reply->payload().toDouble());
} else if (urlPath == "/a/pump") {
qCDebug(dcPlantCare()) << "Updated pump value:" << reply->payload();
device->setStateValue(waterPumpStateTypeId, QVariant(reply->payload().toInt()).toBool());
} else if (urlPath == "/a/light") {
qCDebug(dcPlantCare()) << "Updated brightness value:" << reply->payload();
device->setStateValue(brightnessStateTypeId, qRound(reply->payload().toInt() * 100.0 / 255.0));
}
} else if (m_toggleLightRequests.contains(reply)) {
Action action = m_toggleLightRequests.take(reply);
Device *device = m_asyncActions.take(action.id());
// Check CoAP reply error
if (reply->error() != CoapReply::NoError) {
qCWarning(dcPlantCare) << "CoAP reply toggle light finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
return;
}
// Check CoAP status code
if (reply->statusCode() != CoapPdu::Content) {
qCWarning(dcPlantCare) << "Toggle light status code error:" << reply;
reply->deleteLater();
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
return;
}
// Tell the user about the action execution result
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorNoError);
} else if (m_setBrightness.contains(reply)) {
Action action = m_setBrightness.take(reply);
Device *device = m_asyncActions.take(action.id());
// check CoAP reply error
if (reply->error() != CoapReply::NoError) {
qCWarning(dcPlantCare) << "CoAP set brightness reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
return;
}
// Check CoAP status code
if (reply->statusCode() != CoapPdu::Content) {
qCWarning(dcPlantCare) << "Set brightness status code error:" << reply;
reply->deleteLater();
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
return;
}
// Update the state here, so we don't have to wait for the notification
device->setStateValue(brightnessStateTypeId, action.param("brightness").value().toInt());
// Tell the user about the action execution result
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorNoError);
} else if (m_setPumpPower.contains(reply)) {
Action action = m_setPumpPower.take(reply);
Device *device = m_asyncActions.take(action.id());
// check CoAP reply error
if (reply->error() != CoapReply::NoError) {
qCWarning(dcPlantCare) << "CoAP set pump power reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
return;
}
// Check CoAP status code
if (reply->statusCode() != CoapPdu::Content) {
qCWarning(dcPlantCare) << "Set pump power status code error:" << reply;
reply->deleteLater();
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
return;
}
// Update the state here, so we don't have to wait for the notification
device->setStateValue(waterPumpStateTypeId, action.param("water pump power").value().toBool());
// Tell the user about the action execution result
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorNoError);
} else if (m_enableNotification.contains(reply)) {
Device *device = m_enableNotification.take(reply);
// check CoAP reply error
if (reply->error() != CoapReply::NoError) {
qCWarning(dcPlantCare) << "Enable notifications for" << reply->request().url().toString() << "reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return;
}
// Check CoAP status code
if (reply->statusCode() != CoapPdu::Content) {
qCWarning(dcPlantCare) << "Enable notifications for" << reply->request().url().toString() << "reply status code error" << reply->errorString();
reply->deleteLater();
return;
}
qCDebug(dcPlantCare()) << "Enabled successfully notifications for" << device->name() << reply->request().url().path();
}
// Delete the CoAP reply
reply->deleteLater();
}
void DevicePluginPlantCare::onNotificationReceived(const CoapObserveResource &resource, const int &notificationNumber, const QByteArray &payload)
{
qCDebug(dcPlantCare) << " --> Got notification nr." << notificationNumber << resource.url().toString() << payload;
Device *device = findDevice(QHostAddress(resource.url().host()));
if (!device) {
qCWarning(dcPlantCare()) << "Could not find device for this notification";
return;
}
// Update the corresponding device state
if (resource.url().path() == "/s/moisture") {
device->setStateValue(moistureStateTypeId, qRound(payload.toInt() * 100.0 / 1023.0));
} else if (resource.url().path() == "/s/water") {
device->setStateValue(waterStateTypeId, QVariant(payload.toInt()).toBool());
} else if (resource.url().path() == "/s/battery") {
device->setStateValue(batteryStateTypeId, payload.toDouble());
} else if (resource.url().path() == "/a/pump") {
device->setStateValue(waterPumpStateTypeId, QVariant(payload.toInt()).toBool());
} else if (resource.url().path() == "/a/light") {
device->setStateValue(brightnessStateTypeId, qRound(payload.toInt() * 100.0 / 255.0));
}
}

View File

@ -0,0 +1,87 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015 Bernhard Trinnes <bernhard.trinnes@guh.guru> *
* *
* This file is part of guh. *
* *
* Guh 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. *
* *
* Guh 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 guh. If not, see <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef DEVICEPLUGINPLANTCARE_H
#define DEVICEPLUGINPLANTCARE_H
#include "plugin/deviceplugin.h"
#include "types/action.h"
#include "coap/coap.h"
#include <QHash>
class DevicePluginPlantCare : public DevicePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "devicepluginplantcare.json")
Q_INTERFACES(DevicePlugin)
public:
explicit DevicePluginPlantCare();
DeviceManager::HardwareResources requiredHardware() const override;
DeviceManager::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList &params) override;
DeviceManager::DeviceSetupStatus setupDevice(Device *device) override;
void deviceRemoved(Device *device) override;
void networkManagerReplyReady(QNetworkReply *reply) override;
void postSetupDevice(Device *device) override;
void guhTimer() override;
DeviceManager::DeviceError executeAction(Device *device, const Action &action) override;
private:
QPointer<Coap> m_coap;
QHash<QNetworkReply *, DeviceClassId> m_asyncNodeScans;
QHash<CoapReply *, Device *> m_enableNotification;
QHash<CoapReply *, Device *> m_pingReplies;
// State updates
QHash<CoapReply *, Device *> m_updateReplies;
// Actions
QHash<ActionId, Device *> m_asyncActions;
QHash<CoapReply *, Action> m_toggleLightRequests;
QHash<CoapReply *, Action> m_setBrightness;
QHash<CoapReply *, Action> m_setPumpPower;
void pingDevice(Device *device);
void updateBattery(Device *device);
void updateMoisture(Device *device);
void updateWater(Device *device);
void updateBrightness(Device *device);
void updatePump(Device *device);
void enableNotifications(Device *device);
void setReachable(Device *device, const bool &reachable);
bool deviceAlreadyAdded(const QHostAddress &address);
Device *findDevice(const QHostAddress &address);
private slots:
void coapReplyFinished(CoapReply *reply);
void onNotificationReceived(const CoapObserveResource &resource, const int &notificationNumber, const QByteArray &payload);
};
#endif // DEVICEPLUGINPLANTCARE_H

View File

@ -0,0 +1,97 @@
{
"name": "Plant Care",
"idName": "PlantCare",
"id": "80ea115f-2a9f-49b3-9575-9e36d137c8d6",
"paramTypes": [
{
"name": "RPL address",
"type": "QString",
"inputType": "TextLine",
"defaultValue": "fdaa:e9b8:d03a::ff:fe00:1"
}
],
"vendors": [
{
"name": "guh",
"idName": "guh",
"id": "2062d64d-3232-433c-88bc-0d33c0ba2ba6",
"deviceClasses": [
{
"deviceClassId": "3245db46-5c6f-42d6-9001-753150763385",
"name": "Plant Care",
"idName": "plantCare",
"createMethods": ["discovery"],
"basicTags": [
"Device",
"Gateway"
],
"paramTypes": [
{
"name": "host",
"type": "QString",
"inputType": "TextLine"
}
],
"stateTypes": [
{
"id": "2b4b5123-14b7-4aa1-9c83-27cef333395d",
"idName": "battery",
"name": "battery voltage",
"type": "double",
"unit": "Volt",
"defaultValue": 0
},
{
"id": "8a7efda1-b6b7-41c8-ad07-4d892a532d1c",
"idName": "moisture",
"name": "moisture",
"type": "int",
"unit": "Percentage",
"defaultValue": 0
},
{
"id": "26deeba1-487a-4dee-940a-a3bd48344f33",
"idName": "water",
"name": "water empty",
"type": "bool",
"defaultValue": false
},
{
"id": "21b51fc9-1bfd-4970-a50d-e697a58df1bf",
"idName": "brightness",
"name": "brightness",
"unit": "Percentage",
"type": "int",
"minValue": 0,
"maxValue": 100,
"defaultValue": 0,
"writable": true
},
{
"id": "6c65ae32-18ce-400f-afd6-47fb5b805ccd",
"idName": "waterPump",
"name": "water pump power",
"type": "bool",
"defaultValue": false,
"writable": true
},
{
"id": "8384e07b-8b91-492a-b6c8-e72b325ba0b4",
"idName": "reachable",
"name": "reachable",
"type": "bool",
"defaultValue": false
}
],
"actionTypes": [
{
"id": "9afbcc80-1c97-4c18-bc08-5209d6297f2e",
"idName": "toggleLed",
"name": "toggle led"
}
]
}
]
}
]
}

View File

@ -0,0 +1,9 @@
include(../../plugins.pri)
TARGET = $$qtLibraryTarget(guh_devicepluginplantcare)
SOURCES += \
devicepluginplantcare.cpp
HEADERS += \
devicepluginplantcare.h

View File

@ -0,0 +1,604 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2016 Bernhard Trinnes <bernhard.trinnes@guh.guru> *
* *
* This file is part of guh. *
* *
* Guh 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. *
* *
* Guh 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 guh. If not, see <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "devicepluginws2812.h"
#include "plugin/device.h"
#include "plugininfo.h"
#include <QDebug>
#include <QStringList>
#include <QColor>
DevicePluginWs2812::DevicePluginWs2812()
{
}
DeviceManager::HardwareResources DevicePluginWs2812::requiredHardware() const
{
// We need the NetworkManager for node discovery and the timer for ping requests
return DeviceManager::HardwareResourceNetworkManager | DeviceManager::HardwareResourceTimer;
}
DeviceManager::DeviceSetupStatus DevicePluginWs2812::setupDevice(Device *device)
{
qCDebug(dcWs2812) << "Setup Plant Care" << device->name() << device->params();
// Check if device already added with this address
if (deviceAlreadyAdded(QHostAddress(device->paramValue("host").toString()))) {
qCWarning(dcWs2812) << "Device with this address already added.";
return DeviceManager::DeviceSetupStatusFailure;
}
// Create the CoAP socket if not already created
if (m_coap.isNull()) {
m_coap = new Coap(this);
connect(m_coap.data(), SIGNAL(replyFinished(CoapReply*)), this, SLOT(coapReplyFinished(CoapReply*)));
connect(m_coap.data(), SIGNAL(notificationReceived(CoapObserveResource,int,QByteArray)), this, SLOT(onNotificationReceived(CoapObserveResource,int,QByteArray)));
}
return DeviceManager::DeviceSetupStatusSuccess;
}
void DevicePluginWs2812::deviceRemoved(Device *device)
{
Q_UNUSED(device)
// Delete the CoAP socket if there are no devices left
if (myDevices().isEmpty()) {
m_coap->deleteLater();
}
}
DeviceManager::DeviceError DevicePluginWs2812::discoverDevices(const DeviceClassId &deviceClassId, const ParamList &params)
{
Q_UNUSED(params)
// Perform a HTTP GET on the RPL router address
QHostAddress address(configuration().paramValue("RPL address").toString());
qCDebug(dcWs2812) << "Scan for new nodes on RPL" << address.toString();
QUrl url;
url.setScheme("http");
url.setHost(address.toString());
m_asyncNodeScans.insert(networkManagerGet(QNetworkRequest(url)), deviceClassId);
return DeviceManager::DeviceErrorAsync;
}
void DevicePluginWs2812::networkManagerReplyReady(QNetworkReply *reply)
{
if (m_asyncNodeScans.keys().contains(reply)) {
DeviceClassId deviceClassId = m_asyncNodeScans.take(reply);
// Check HTTP status code
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
qCWarning(dcWs2812) << "Node scan reply HTTP error:" << reply->errorString();
emit devicesDiscovered(deviceClassId, QList<DeviceDescriptor>());
reply->deleteLater();
return;
}
QByteArray data = reply->readAll();
qCDebug(dcWs2812) << "Node discovery finished:" << endl << data;
QList<DeviceDescriptor> deviceDescriptors;
QList<QByteArray> lines = data.split('\n');
qCDebug(dcWs2812) << lines;
foreach (const QByteArray &line, lines) {
if (line.isEmpty())
continue;
QHostAddress address(QString(line.left(line.length() - 4)));
if (address.isNull())
continue;
qCDebug(dcWs2812) << "Found node" << address.toString();
// Create a deviceDescriptor for each found address
DeviceDescriptor descriptor(deviceClassId, "ws2812", address.toString());
ParamList params;
params.append(Param("host", address.toString()));
descriptor.setParams(params);
deviceDescriptors.append(descriptor);
}
// Inform the user which devices were found
emit devicesDiscovered(deviceClassId, deviceDescriptors);
}
// Delete the HTTP reply
reply->deleteLater();
}
void DevicePluginWs2812::postSetupDevice(Device *device)
{
// Try to ping the device after a successful setup
pingDevice(device);
}
void DevicePluginWs2812::guhTimer()
{
// Try to ping each device every 10 seconds to make sure it is still reachable
foreach (Device *device, myDevices()) {
if (device->deviceClassId() == ws2812DeviceClassId) {
pingDevice(device);
}
}
}
DeviceManager::DeviceError DevicePluginWs2812::executeAction(Device *device, const Action &action)
{
if (device->deviceClassId() != ws2812DeviceClassId)
return DeviceManager::DeviceErrorDeviceClassNotFound;
qCDebug(dcWs2812) << "Execute action" << device->name() << action.params();
// Check if the device is reachable
if (!device->stateValue(reachableStateTypeId).toBool()) {
qCWarning(dcWs2812) << "Device not reachable.";
return DeviceManager::DeviceErrorHardwareNotAvailable;
}
if(action.actionTypeId() == effectColorActionTypeId) {
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
url.setPath("/a/color");
QColor newColor = action.param("color").value().value<QColor>().toRgb();
QByteArray message = "color=" + newColor.name().remove("#").toUtf8();
qCDebug(dcWs2812) << "Sending" << url.toString() << message;
CoapReply *reply = m_coap->post(CoapRequest(url), message);
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
qCWarning(dcWs2812) << "CoAP reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return DeviceManager::DeviceErrorHardwareFailure;
}
m_setColor.insert(reply, action);
m_asyncActions.insert(action.id(), device);
return DeviceManager::DeviceErrorAsync;
} else if(action.actionTypeId() == maxPixActionTypeId) {
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
url.setPath("/p/maxpix");
//QColor color = action.param("color").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();
int max = action.param("leds").value().toInt();
qCDebug(dcWs2812) << "Max Pix" << max;
QByteArray message = QString("max=%1").arg(QString::number(max)).toUtf8();
//QByteArray message = "max=" + action.param("maxPix").value().toByteArray();
qCDebug(dcWs2812) << "Sending" << url.toString() << message;
CoapReply *reply = m_coap->post(CoapRequest(url), message);
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
qCWarning(dcWs2812) << "CoAP reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return DeviceManager::DeviceErrorHardwareFailure;
}
m_setPix.insert(reply, action);
m_asyncActions.insert(action.id(), device);
return DeviceManager::DeviceErrorAsync;
} else if(action.actionTypeId() == effectModeActionTypeId) {
//int effectmode = createSetColorRequest(action.param("color").value().value<QColor>());
int effectmode = 0;
QString effectModeString = action.param("effect").value().toString();
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
url.setPath("/a/effect");
/*"Off",
"Color On",
"Color Wave",
"Color Fade",
"Color Flash",
"Rainbow Wave",
"Rainbow Flash",
"Knight Rider",
"Fire"
*/
qCDebug(dcWs2812) << "Set effect mode to:" << effectModeString;
if(effectModeString == "Off") {
effectmode = 0;
} else if (effectModeString == "Color On") {
effectmode = 1;
} else if (effectModeString == "Color Wave") {
effectmode = 2;
} else if (effectModeString == "Color Fade") {
effectmode = 3;
}else if (effectModeString == "Color Flash") {
effectmode = 4;
}else if (effectModeString == "Rainbow Wave") {
effectmode = 5;
}else if (effectModeString == "Rainbow Flash") {
effectmode = 6;
}else if (effectModeString == "Knight Rider") {
effectmode = 7;
}else if (effectModeString == "Fire") {
effectmode = 8;
}
QByteArray payload = QString("mode=%1").arg(QString::number(effectmode)).toUtf8();
qCDebug(dcWs2812()) << "Sending" << payload << url.path();
CoapReply *reply = m_coap->post(CoapRequest(url), payload);
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
qCWarning(dcWs2812) << "CoAP reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return DeviceManager::DeviceErrorHardwareFailure;
}
m_setEffect.insert(reply, action);
m_asyncActions.insert(action.id(), device);
return DeviceManager::DeviceErrorAsync;
}
return DeviceManager::DeviceErrorActionTypeNotFound;
}
void DevicePluginWs2812::pingDevice(Device *device)
{
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
m_pingReplies.insert(m_coap->ping(CoapRequest(url)), device);
}
void DevicePluginWs2812::updateBattery(Device *device)
{
qCDebug(dcWs2812) << "Update" << device->name() << "battery value";
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
url.setPath("/s/battery");
CoapReply *reply = m_coap->get(CoapRequest(url));
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
qCWarning(dcWs2812) << "CoAP reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return;
}
m_updateReplies.insert(reply, device);
}
void DevicePluginWs2812::updateColor(Device *device)
{
qCDebug(dcWs2812) << "Update" << device->name() << "color value";
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
url.setPath("/a/color");
CoapReply *reply = m_coap->get(CoapRequest(url));
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
qCWarning(dcWs2812) << "CoAP reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return;
}
m_updateReplies.insert(reply, device);
}
void DevicePluginWs2812::updateEffect(Device *device)
{
qCDebug(dcWs2812) << "Update" << device->name() << "effect mode";
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
url.setPath("/a/effect");
CoapReply *reply = m_coap->get(CoapRequest(url));
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
qCWarning(dcWs2812) << "CoAP reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return;
}
m_updateReplies.insert(reply, device);
}
void DevicePluginWs2812::updateMaxPix(Device *device)
{
qCDebug(dcWs2812) << "Update" << device->name() << "max pix";
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
url.setPath("/p/maxpix");
CoapReply *reply = m_coap->get(CoapRequest(url));
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
qCWarning(dcWs2812) << "CoAP reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return;
}
m_updateReplies.insert(reply, device);
}
void DevicePluginWs2812::enableNotifications(Device *device)
{
qCDebug(dcWs2812) << "Enable" << device->name() << "notifications";
QUrl url;
url.setScheme("coap");
url.setHost(device->paramValue("host").toString());
url.setPath("/s/battery");
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
url.setPath("/p/maxpix");
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
url.setPath("/a/color");
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
url.setPath("/a/effect");
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
}
void DevicePluginWs2812::setReachable(Device *device, const bool &reachable)
{
if (device->stateValue(reachableStateTypeId).toBool() != reachable) {
if (!reachable) {
// Warn just once that the device is not reachable
qCWarning(dcWs2812()) << device->name() << "reachable changed" << reachable;
} else {
qCDebug(dcWs2812()) << device->name() << "reachable changed" << reachable;
// Get current state values after a reconnect
updateBattery(device);
updateColor(device);
updateEffect(device);
updateMaxPix(device);
// Make sure the notifications are enabled
enableNotifications(device);
}
}
device->setStateValue(reachableStateTypeId, reachable);
}
bool DevicePluginWs2812::deviceAlreadyAdded(const QHostAddress &address)
{
// Check if we already have a device with the given address
foreach (Device *device, myDevices()) {
if (device->paramValue("host").toString() == address.toString()) {
return true;
}
}
return false;
}
Device *DevicePluginWs2812::findDevice(const QHostAddress &address)
{
// Return the device pointer with the given address (otherwise 0)
foreach (Device *device, myDevices()) {
if (device->paramValue("host").toString() == address.toString()) {
return device;
}
}
return NULL;
}
void DevicePluginWs2812::coapReplyFinished(CoapReply *reply)
{
if (m_pingReplies.contains(reply)) {
Device *device = m_pingReplies.take(reply);
// Check CoAP reply error
if (reply->error() != CoapReply::NoError) {
if (device->stateValue(reachableStateTypeId).toBool())
qCWarning(dcWs2812) << "Ping device" << reply->request().url().toString() << "reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return;
}
setReachable(device, true);
} else if (m_updateReplies.contains(reply)) {
Device *device = m_updateReplies.take(reply);
QString urlPath = reply->request().url().path();
// Check CoAP reply error
if (reply->error() != CoapReply::NoError) {
qCWarning(dcWs2812) << "Update resource" << urlPath << "reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return;
}
// Check CoAP status code
if (reply->statusCode() != CoapPdu::Content) {
qCWarning(dcWs2812) << "Update resource" << urlPath << "status code error:" << reply;
reply->deleteLater();
return;
}
// Update corresponding device state
if (urlPath == "/s/battery") {
qCDebug(dcWs2812()) << "Updated battery value:" << reply->payload();
device->setStateValue(batteryStateTypeId, reply->payload().toDouble());
} else if (urlPath == "/a/color") {
qCDebug(dcWs2812()) << "Updated color value:" << reply->payload();
device->setStateValue(effectColorStateTypeId, reply->payload());
} else if (urlPath == "/p/maxpix") {
qCDebug(dcWs2812()) << "Updated max pix value:" << reply->payload();
device->setStateValue(maxPixStateTypeId, reply->payload());
}
} else if (m_setEffect.contains(reply)) {
Action action = m_setEffect.take(reply);
Device *device = m_asyncActions.take(action.id());
// Check CoAP reply error
if (reply->error() != CoapReply::NoError) {
qCWarning(dcWs2812) << "CoAP reply toggle light finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
return;
}
// Check CoAP status code
if (reply->statusCode() != CoapPdu::Content) {
qCWarning(dcWs2812) << "Set effect code error:" << reply;
reply->deleteLater();
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
return;
}
// Tell the user about the action execution result
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorNoError);
} else if (m_setColor.contains(reply)) {
Action action = m_setColor.take(reply);
Device *device = m_asyncActions.take(action.id());
// check CoAP reply error
if (reply->error() != CoapReply::NoError) {
qCWarning(dcWs2812) << "CoAP set color reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
return;
}
// Check CoAP status code
if (reply->statusCode() != CoapPdu::Content) {
qCWarning(dcWs2812) << "Set color status code error:" << reply;
reply->deleteLater();
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
return;
}
// Update the state here, so we don't have to wait for the notification
device->setStateValue(effectColorStateTypeId, action.param("color").value().toInt());
// Tell the user about the action execution result
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorNoError);
} else if (m_setPix.contains(reply)) {
Action action = m_setPix.take(reply);
Device *device = m_asyncActions.take(action.id());
// check CoAP reply error
if (reply->error() != CoapReply::NoError) {
qCWarning(dcWs2812) << "CoAP set maxpix reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
return;
}
// Check CoAP status code
if (reply->statusCode() != CoapPdu::Content) {
qCWarning(dcWs2812) << "Set max pix code error:" << reply;
reply->deleteLater();
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
return;
}
// Update the state here, so we don't have to wait for the notification
device->setStateValue(maxPixStateTypeId, action.param("maxPix").value().toInt());
// Tell the user about the action execution result
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorNoError);
} else if (m_enableNotification.contains(reply)) {
Device *device = m_enableNotification.take(reply);
// check CoAP reply error
if (reply->error() != CoapReply::NoError) {
qCWarning(dcWs2812) << "Enable notifications for" << reply->request().url().toString() << "reply finished with error" << reply->errorString();
setReachable(device, false);
reply->deleteLater();
return;
}
// Check CoAP status code
if (reply->statusCode() != CoapPdu::Content) {
qCWarning(dcWs2812) << "Enable notifications for" << reply->request().url().toString() << "reply status code error" << reply->errorString();
reply->deleteLater();
return;
}
qCDebug(dcWs2812()) << "Enabled successfully notifications for" << device->name() << reply->request().url().path();
}
// Delete the CoAP reply
reply->deleteLater();
}
void DevicePluginWs2812::onNotificationReceived(const CoapObserveResource &resource, const int &notificationNumber, const QByteArray &payload)
{
qCDebug(dcWs2812) << " --> Got notification nr." << notificationNumber << resource.url().toString() << payload;
Device *device = findDevice(QHostAddress(resource.url().host()));
if (!device) {
qCWarning(dcWs2812()) << "Could not find device for this notification";
return;
}
// Update the corresponding device state
if (resource.url().path() == "/s/battery") {
device->setStateValue(batteryStateTypeId, payload.toDouble());
}else if (resource.url().path() == "/a/color") {
device->setStateValue(effectColorStateTypeId, QColor(QString(payload)).toRgb());
}else if (resource.url().path() == "/p/maxpix") {
device->setStateValue(maxPixStateTypeId, payload.toInt());
}
}

View File

@ -0,0 +1,86 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
* Copyright (C) 2015 Bernhard Trinnes <bernhard.trinnes@guh.guru> *
* *
* This file is part of guh. *
* *
* Guh 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. *
* *
* Guh 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 guh. If not, see <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef DEVICEPLUGINWS2812_H
#define DEVICEPLUGINWS2812_H
#include "plugin/deviceplugin.h"
#include "types/action.h"
#include "coap/coap.h"
#include <QHash>
class DevicePluginWs2812 : public DevicePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "devicepluginws2812.json")
Q_INTERFACES(DevicePlugin)
public:
explicit DevicePluginWs2812();
DeviceManager::HardwareResources requiredHardware() const override;
DeviceManager::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList &params) override;
DeviceManager::DeviceSetupStatus setupDevice(Device *device) override;
void deviceRemoved(Device *device) override;
void networkManagerReplyReady(QNetworkReply *reply) override;
void postSetupDevice(Device *device) override;
void guhTimer() override;
DeviceManager::DeviceError executeAction(Device *device, const Action &action) override;
private:
QPointer<Coap> m_coap;
QHash<QNetworkReply *, DeviceClassId> m_asyncNodeScans;
QHash<CoapReply *, Device *> m_enableNotification;
QHash<CoapReply *, Device *> m_pingReplies;
// State updates
QHash<CoapReply *, Device *> m_updateReplies;
// Actions
QHash<ActionId, Device *> m_asyncActions;
QHash<CoapReply *, Action> m_setColor;
QHash<CoapReply *, Action> m_setEffect;
QHash<CoapReply *, Action> m_setPix;
void pingDevice(Device *device);
void updateBattery(Device *device);
void updateColor(Device *device);
void updateEffect(Device *device);
void updateMaxPix(Device *device);
void enableNotifications(Device *device);
void setReachable(Device *device, const bool &reachable);
bool deviceAlreadyAdded(const QHostAddress &address);
Device *findDevice(const QHostAddress &address);
private slots:
void coapReplyFinished(CoapReply *reply);
void onNotificationReceived(const CoapObserveResource &resource, const int &notificationNumber, const QByteArray &payload);
};
#endif // DEVICEPLUGINWS2812_H

View File

@ -0,0 +1,101 @@
{
"name": "WS2812",
"idName": "Ws2812",
"id": "60b68c75-3261-4bf1-8912-798d2d3bbd3b",
"paramTypes": [
{
"name": "RPL address",
"type": "QString",
"inputType": "TextLine",
"defaultValue": "fdaa:e9b8:d03a::ff:fe00:1"
}
],
"vendors": [
{
"name": "guh",
"idName": "guh",
"id": "419435fa-58bf-4ba0-b6f5-156ce081bb68",
"deviceClasses": [
{
"deviceClassId": "3242db46-5c6f-42d6-9001-753150763385",
"name": "WS2812",
"idName": "ws2812",
"createMethods": ["discovery"],
"basicTags": [
"Device",
"Gateway"
],
"paramTypes": [
{
"name": "host",
"type": "QString",
"inputType": "TextLine"
}
],
"stateTypes": [
{
"id": "c9f6042f-1193-4075-94b1-37b29641ec24",
"idName": "battery",
"name": "battery voltage",
"type": "double",
"unit": "Volt",
"defaultValue": 0
},
{
"id": "08091cde-983d-42f7-bdd7-d89c312ccbed",
"idName": "reachable",
"name": "reachable",
"type": "bool",
"defaultValue": false
},
{
"id": "8a73c60a-1da9-4228-8dba-2ffccc6531a6",
"idName": "effectColor",
"name": "color",
"type": "QColor",
"defaultValue": "#000000",
"writable": true
},
{
"id": "ac372b72-c2ae-4f0f-9fca-bd7cf654603a",
"idName": "maxPix",
"name": "leds",
"type": "int",
"unit": "None",
"defaultValue": 0,
"minValue": 0,
"maxValue": 240,
"writable": true
}
],
"actionTypes": [
{
"id": "f4fe5d31-1edb-4944-9ddb-c89e7da8bee7",
"idName": "effectMode",
"name": "set effect mode",
"paramTypes": [
{
"name": "effect",
"type": "QString",
"allowedValues": [
"Off",
"Color On",
"Color Wave",
"Color Fade",
"Color Flash",
"Rainbow Wave",
"Rainbow Flash",
"Knight Rider",
"Fire"
],
"defaultValue": "Off"
}
]
}
]
}
]
}
]
}

View File

@ -0,0 +1,9 @@
include(../../plugins.pri)
TARGET = $$qtLibraryTarget(guh_devicepluginws2812)
SOURCES += \
devicepluginws2812.cpp
HEADERS += \
devicepluginws2812.h

View File

@ -117,6 +117,7 @@ int main(int argc, char *argv[])
s_loggingFilters.insert("JsonRpc", true);
s_loggingFilters.insert("Rest", true);
s_loggingFilters.insert("OAuth2", false);
s_loggingFilters.insert("Coap", false);
QHash<QString, bool> loggingFiltersPlugins;
foreach (const QJsonObject &pluginMetadata, DeviceManager::pluginsMetadata()) {

View File

@ -15,7 +15,7 @@ SUBDIRS = versioning \
restvendors \
restrules \
websocketserver \
coap \
logging \
restlogging \
#coap \