diff --git a/debian/control b/debian/control index 4573873f..e9fe26ad 100644 --- a/debian/control +++ b/debian/control @@ -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. + diff --git a/debian/guh-plugins-merkur.dirs b/debian/guh-plugins-merkur.dirs new file mode 100644 index 00000000..0a803bb8 --- /dev/null +++ b/debian/guh-plugins-merkur.dirs @@ -0,0 +1 @@ +usr/lib/guh/plugins diff --git a/debian/guh-plugins-merkur.install b/debian/guh-plugins-merkur.install new file mode 100644 index 00000000..17ce7d38 --- /dev/null +++ b/debian/guh-plugins-merkur.install @@ -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 diff --git a/libguh/coap/coap.cpp b/libguh/coap/coap.cpp index 2f5c0214..b4abd3c3 100644 --- a/libguh/coap/coap.cpp +++ b/libguh/coap/coap.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * 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(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(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(); diff --git a/libguh/coap/coap.h b/libguh/coap/coap.h index e4b60004..37c6238f 100644 --- a/libguh/coap/coap.h +++ b/libguh/coap/coap.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * This file is part of QtCoap. * * * @@ -25,9 +25,10 @@ #include #include #include +#include +#include #include -#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 m_runningHostLookups; - QHash m_observeResources; - + QPointer m_reply; QQueue m_replyQueue; + + QHash m_runningHostLookups; + + QHash m_observeResources; // token | resource + + // Blockwise notifications + QPointer m_observerReply; + QHash m_observeReplyResource; // observe reply | resource + QHash 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 ¬ificationNumber, const QByteArray &payload); @@ -92,5 +105,4 @@ private slots: void onReplyFinished(); }; - #endif // COAP_H diff --git a/libguh/coap/coap.pri b/libguh/coap/coap.pri new file mode 100644 index 00000000..4d8045c1 --- /dev/null +++ b/libguh/coap/coap.pri @@ -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 + diff --git a/libguh/coap/coapobserveresource.cpp b/libguh/coap/coapobserveresource.cpp index e7a6aed9..99398d46 100644 --- a/libguh/coap/coapobserveresource.cpp +++ b/libguh/coap/coapobserveresource.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * This file is part of QtCoap. * * * diff --git a/libguh/coap/coapobserveresource.h b/libguh/coap/coapobserveresource.h index 19d09b0d..ec9ab0a0 100644 --- a/libguh/coap/coapobserveresource.h +++ b/libguh/coap/coapobserveresource.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * This file is part of QtCoap. * * * @@ -22,11 +22,10 @@ #define COAPOBSERVERESOURCE_H #include +#include #include -#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 diff --git a/libguh/coap/coapoption.cpp b/libguh/coap/coapoption.cpp index 7eb41480..f061cb97 100644 --- a/libguh/coap/coapoption.cpp +++ b/libguh/coap/coapoption.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * 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. */ diff --git a/libguh/coap/coapoption.h b/libguh/coap/coapoption.h index 53ace930..cca635cd 100644 --- a/libguh/coap/coapoption.h +++ b/libguh/coap/coapoption.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * This file is part of QtCoap. * * * @@ -25,9 +25,7 @@ #include #include -#include "libguh.h" - -class LIBGUH_EXPORT CoapOption +class CoapOption { Q_GADGET Q_ENUMS(Option) diff --git a/libguh/coap/coappdu.cpp b/libguh/coap/coappdu.cpp index 778a233e..b8841d6a 100644 --- a/libguh/coap/coappdu.cpp +++ b/libguh/coap/coappdu.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * This file is part of QtCoap. * * * @@ -317,6 +317,7 @@ QList 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)); diff --git a/libguh/coap/coappdu.h b/libguh/coap/coappdu.h index db418bfd..796c5fc6 100644 --- a/libguh/coap/coappdu.h +++ b/libguh/coap/coappdu.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * 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; diff --git a/libguh/coap/coappdublock.cpp b/libguh/coap/coappdublock.cpp index 8517c6eb..5a7ba6e4 100644 --- a/libguh/coap/coappdublock.cpp +++ b/libguh/coap/coappdublock.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * This file is part of QtCoap. * * * diff --git a/libguh/coap/coappdublock.h b/libguh/coap/coappdublock.h index 3ae85ab0..459d1a22 100644 --- a/libguh/coap/coappdublock.h +++ b/libguh/coap/coappdublock.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * This file is part of QtCoap. * * * @@ -23,9 +23,7 @@ #include -#include "libguh.h" - -class LIBGUH_EXPORT CoapPduBlock +class CoapPduBlock { public: CoapPduBlock(); diff --git a/libguh/coap/coapreply.cpp b/libguh/coap/coapreply.cpp index 4ad6938c..77ef5c59 100644 --- a/libguh/coap/coapreply.cpp +++ b/libguh/coap/coapreply.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * 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 */ diff --git a/libguh/coap/coapreply.h b/libguh/coap/coapreply.h index 33911dc4..5897149d 100644 --- a/libguh/coap/coapreply.h +++ b/libguh/coap/coapreply.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * This file is part of QtCoap. * * * @@ -24,13 +24,11 @@ #include #include -#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; diff --git a/libguh/coap/coaprequest.cpp b/libguh/coap/coaprequest.cpp index ad05c01a..de0454eb 100644 --- a/libguh/coap/coaprequest.cpp +++ b/libguh/coap/coaprequest.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * This file is part of QtCoap. * * * diff --git a/libguh/coap/coaprequest.h b/libguh/coap/coaprequest.h index 9738c6e6..b61992e6 100644 --- a/libguh/coap/coaprequest.h +++ b/libguh/coap/coaprequest.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * This file is part of QtCoap. * * * @@ -24,14 +24,12 @@ #include #include -#include "libguh.h" - #include "coappdu.h" #include "coapoption.h" //class Coap; -class LIBGUH_EXPORT CoapRequest +class CoapRequest { // friend class Coap; public: diff --git a/libguh/coap/corelink.cpp b/libguh/coap/corelink.cpp index 26f32c2e..1c04721e 100644 --- a/libguh/coap/corelink.cpp +++ b/libguh/coap/corelink.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * This file is part of QtCoap. * * * diff --git a/libguh/coap/corelink.h b/libguh/coap/corelink.h index 7c3e65de..b57be375 100644 --- a/libguh/coap/corelink.h +++ b/libguh/coap/corelink.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * This file is part of QtCoap. * * * @@ -24,10 +24,9 @@ #include #include -#include "libguh.h" #include "coappdu.h" -class LIBGUH_EXPORT CoreLink +class CoreLink { public: CoreLink(); diff --git a/libguh/coap/corelinkparser.cpp b/libguh/coap/corelinkparser.cpp index 7cb44528..b46335a5 100644 --- a/libguh/coap/corelinkparser.cpp +++ b/libguh/coap/corelinkparser.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * This file is part of QtCoap. * * * @@ -52,12 +52,9 @@ CoreLinkParser::CoreLinkParser(const QByteArray &data, QObject *parent) : { QList linkList = data.split(','); foreach (const QByteArray &linkLine, linkList) { - qDebug() << "-------------------------------------"; - qDebug() << linkLine; QList 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 CoreLinkParser::links() const { return m_links; } - - diff --git a/libguh/coap/corelinkparser.h b/libguh/coap/corelinkparser.h index 6a2f91dc..799787a1 100644 --- a/libguh/coap/corelinkparser.h +++ b/libguh/coap/corelinkparser.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright (C) 2015 Simon Stuerz * + * Copyright (C) 2015-2016 Simon Stuerz * * * * This file is part of QtCoap. * * * @@ -23,12 +23,11 @@ #include -#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: diff --git a/libguh/libguh.pro b/libguh/libguh.pro index 95e0e2ad..1bf717ec 100644 --- a/libguh/libguh.pro +++ b/libguh/libguh.pro @@ -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 \ diff --git a/libguh/loggingcategories.h b/libguh/loggingcategories.h index 5b174b38..a846c180 100644 --- a/libguh/loggingcategories.h +++ b/libguh/loggingcategories.h @@ -23,6 +23,9 @@ #include +// Include dcCoap +#include "coap/coap.h" + // Core / libguh Q_DECLARE_LOGGING_CATEGORY(dcApplication) Q_DECLARE_LOGGING_CATEGORY(dcDeviceManager) diff --git a/plugins/deviceplugins/deviceplugins.pro b/plugins/deviceplugins/deviceplugins.pro index 7674b88a..a6207372 100644 --- a/plugins/deviceplugins/deviceplugins.pro +++ b/plugins/deviceplugins/deviceplugins.pro @@ -15,10 +15,12 @@ SUBDIRS += elro \ commandlauncher \ unitec \ leynew \ - #tune \ udpcommander \ kodi \ elgato \ awattar \ netatmo \ - #osdomotics \ + dollhouse \ + plantcare \ + osdomotics \ + ws2812 \ diff --git a/plugins/deviceplugins/dollhouse/deviceplugindollhouse.cpp b/plugins/deviceplugins/dollhouse/deviceplugindollhouse.cpp new file mode 100644 index 00000000..59be1248 --- /dev/null +++ b/plugins/deviceplugins/dollhouse/deviceplugindollhouse.cpp @@ -0,0 +1,375 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Simon Stuerz * + * * + * 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 . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "deviceplugindollhouse.h" +#include "plugininfo.h" + +#include + +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().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 lines = data.split('\n'); + QList 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
") + 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 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();
+            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();
+}
+
diff --git a/plugins/deviceplugins/dollhouse/deviceplugindollhouse.h b/plugins/deviceplugins/dollhouse/deviceplugindollhouse.h
new file mode 100644
index 00000000..26fde728
--- /dev/null
+++ b/plugins/deviceplugins/dollhouse/deviceplugindollhouse.h
@@ -0,0 +1,74 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ *                                                                         *
+ *  Copyright (C) 2016 Simon Stuerz                 *
+ *                                                                         *
+ *  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 .            *
+ *                                                                         *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef DEVICEPLUGINDOLLHOUSE_H
+#define DEVICEPLUGINDOLLHOUSE_H
+
+#include "plugin/deviceplugin.h"
+#include "devicemanager.h"
+
+#include "coap/coap.h"
+#include "dollhouselight.h"
+
+#include 
+
+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 m_asyncSetup;
+    QHash m_asyncNodeScan;
+
+    QHash m_asyncActions;
+    QHash m_asyncActionLights;
+
+    QList m_asyncPings;
+
+    QHostAddress m_houseAddress;
+    bool m_houseReachable;
+
+    QHash 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
diff --git a/plugins/deviceplugins/dollhouse/deviceplugindollhouse.json b/plugins/deviceplugins/dollhouse/deviceplugindollhouse.json
new file mode 100644
index 00000000..c3650d28
--- /dev/null
+++ b/plugins/deviceplugins/dollhouse/deviceplugindollhouse.json
@@ -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
+                        }
+                    ]
+                }
+            ]
+        }
+    ]
+}
diff --git a/plugins/deviceplugins/dollhouse/dollhouse.pro b/plugins/deviceplugins/dollhouse/dollhouse.pro
new file mode 100644
index 00000000..f6e6fad9
--- /dev/null
+++ b/plugins/deviceplugins/dollhouse/dollhouse.pro
@@ -0,0 +1,11 @@
+include(../../plugins.pri)
+
+TARGET = $$qtLibraryTarget(guh_deviceplugindollhouse)
+
+SOURCES += \
+    deviceplugindollhouse.cpp \
+    dollhouselight.cpp
+
+HEADERS += \
+    deviceplugindollhouse.h \
+    dollhouselight.h
diff --git a/plugins/deviceplugins/dollhouse/dollhouselight.cpp b/plugins/deviceplugins/dollhouse/dollhouselight.cpp
new file mode 100644
index 00000000..99a5392a
--- /dev/null
+++ b/plugins/deviceplugins/dollhouse/dollhouselight.cpp
@@ -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;
+}
+
diff --git a/plugins/deviceplugins/dollhouse/dollhouselight.h b/plugins/deviceplugins/dollhouse/dollhouselight.h
new file mode 100644
index 00000000..65b842e4
--- /dev/null
+++ b/plugins/deviceplugins/dollhouse/dollhouselight.h
@@ -0,0 +1,47 @@
+#ifndef DOLLHOUSELIGHT_H
+#define DOLLHOUSELIGHT_H
+
+#include 
+#include 
+
+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
diff --git a/plugins/deviceplugins/plantcare/devicepluginplantcare.cpp b/plugins/deviceplugins/plantcare/devicepluginplantcare.cpp
new file mode 100644
index 00000000..898a8515
--- /dev/null
+++ b/plugins/deviceplugins/plantcare/devicepluginplantcare.cpp
@@ -0,0 +1,561 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ *                                                                         *
+ *  Copyright (C) 2015 Simon Stuerz                 *
+ *  Copyright (C) 2016 Bernhard Trinnes         *
+ *                                                                         *
+ *  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 .            *
+ *                                                                         *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+
+#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 ¶ms)
+{
+    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());
+            reply->deleteLater();
+            return;
+        }
+
+        QByteArray data = reply->readAll();
+        qCDebug(dcPlantCare) << "Node discovery finished:" << endl << data;
+
+        QList deviceDescriptors;
+        QList 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 ¬ificationNumber, 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));
+    }
+}
diff --git a/plugins/deviceplugins/plantcare/devicepluginplantcare.h b/plugins/deviceplugins/plantcare/devicepluginplantcare.h
new file mode 100644
index 00000000..80649f5f
--- /dev/null
+++ b/plugins/deviceplugins/plantcare/devicepluginplantcare.h
@@ -0,0 +1,87 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ *                                                                         *
+ *  Copyright (C) 2015 Simon Stuerz                 *
+ *  Copyright (C) 2015 Bernhard Trinnes         *
+ *                                                                         *
+ *  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 .            *
+ *                                                                         *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef DEVICEPLUGINPLANTCARE_H
+#define DEVICEPLUGINPLANTCARE_H
+
+#include "plugin/deviceplugin.h"
+#include "types/action.h"
+#include "coap/coap.h"
+
+#include 
+
+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 ¶ms) 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 m_coap;
+    QHash m_asyncNodeScans;
+    QHash m_enableNotification;
+    QHash m_pingReplies;
+
+    // State updates
+    QHash m_updateReplies;
+
+    // Actions
+    QHash m_asyncActions;
+
+    QHash m_toggleLightRequests;
+    QHash m_setBrightness;
+    QHash 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 ¬ificationNumber, const QByteArray &payload);
+};
+
+#endif // DEVICEPLUGINPLANTCARE_H
diff --git a/plugins/deviceplugins/plantcare/devicepluginplantcare.json b/plugins/deviceplugins/plantcare/devicepluginplantcare.json
new file mode 100644
index 00000000..d5fcd9a8
--- /dev/null
+++ b/plugins/deviceplugins/plantcare/devicepluginplantcare.json
@@ -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"
+                        }
+                    ]
+                }
+            ]
+        }
+    ]
+}
diff --git a/plugins/deviceplugins/plantcare/plantcare.pro b/plugins/deviceplugins/plantcare/plantcare.pro
new file mode 100644
index 00000000..b6f35ef8
--- /dev/null
+++ b/plugins/deviceplugins/plantcare/plantcare.pro
@@ -0,0 +1,9 @@
+include(../../plugins.pri)
+
+TARGET = $$qtLibraryTarget(guh_devicepluginplantcare)
+
+SOURCES += \
+    devicepluginplantcare.cpp
+
+HEADERS += \
+    devicepluginplantcare.h
diff --git a/plugins/deviceplugins/ws2812/devicepluginws2812.cpp b/plugins/deviceplugins/ws2812/devicepluginws2812.cpp
new file mode 100644
index 00000000..0f4afe77
--- /dev/null
+++ b/plugins/deviceplugins/ws2812/devicepluginws2812.cpp
@@ -0,0 +1,604 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ *                                                                         *
+ *  Copyright (C) 2015 Simon Stuerz                 *
+ *  Copyright (C) 2016 Bernhard Trinnes         *
+ *                                                                         *
+ *  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 .            *
+ *                                                                         *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+
+#include "devicepluginws2812.h"
+#include "plugin/device.h"
+#include "plugininfo.h"
+
+
+#include 
+#include 
+#include 
+
+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 ¶ms)
+{
+    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());
+            reply->deleteLater();
+            return;
+        }
+
+        QByteArray data = reply->readAll();
+        qCDebug(dcWs2812) << "Node discovery finished:" << endl << data;
+
+        QList deviceDescriptors;
+        QList 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().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().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());
+
+        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 ¬ificationNumber, 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());
+    }
+}
diff --git a/plugins/deviceplugins/ws2812/devicepluginws2812.h b/plugins/deviceplugins/ws2812/devicepluginws2812.h
new file mode 100644
index 00000000..9bb3344f
--- /dev/null
+++ b/plugins/deviceplugins/ws2812/devicepluginws2812.h
@@ -0,0 +1,86 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ *                                                                         *
+ *  Copyright (C) 2015 Simon Stuerz                 *
+ *  Copyright (C) 2015 Bernhard Trinnes         *
+ *                                                                         *
+ *  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 .            *
+ *                                                                         *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef DEVICEPLUGINWS2812_H
+#define DEVICEPLUGINWS2812_H
+
+#include "plugin/deviceplugin.h"
+#include "types/action.h"
+#include "coap/coap.h"
+
+#include 
+
+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 ¶ms) 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 m_coap;
+    QHash m_asyncNodeScans;
+    QHash m_enableNotification;
+    QHash m_pingReplies;
+
+    // State updates
+    QHash m_updateReplies;
+
+    // Actions
+    QHash m_asyncActions;
+
+    QHash m_setColor;
+    QHash m_setEffect;
+    QHash 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 ¬ificationNumber, const QByteArray &payload);
+};
+
+#endif // DEVICEPLUGINWS2812_H
diff --git a/plugins/deviceplugins/ws2812/devicepluginws2812.json b/plugins/deviceplugins/ws2812/devicepluginws2812.json
new file mode 100644
index 00000000..a0e53137
--- /dev/null
+++ b/plugins/deviceplugins/ws2812/devicepluginws2812.json
@@ -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"
+                                }
+                            ]
+                        }
+                    ]
+                }
+            ]
+        }
+    ]
+}
diff --git a/plugins/deviceplugins/ws2812/ws2812.pro b/plugins/deviceplugins/ws2812/ws2812.pro
new file mode 100644
index 00000000..42d34ed0
--- /dev/null
+++ b/plugins/deviceplugins/ws2812/ws2812.pro
@@ -0,0 +1,9 @@
+include(../../plugins.pri)
+
+TARGET = $$qtLibraryTarget(guh_devicepluginws2812)
+
+SOURCES += \
+    devicepluginws2812.cpp
+
+HEADERS += \
+    devicepluginws2812.h
diff --git a/server/main.cpp b/server/main.cpp
index 3d7c11ca..2e8514c7 100644
--- a/server/main.cpp
+++ b/server/main.cpp
@@ -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 loggingFiltersPlugins;
     foreach (const QJsonObject &pluginMetadata, DeviceManager::pluginsMetadata()) {
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
index f7ac36cf..9208234c 100644
--- a/tests/auto/auto.pro
+++ b/tests/auto/auto.pro
@@ -15,7 +15,7 @@ SUBDIRS = versioning \
         restvendors \
         restrules \
         websocketserver \
-        coap \
         logging \
         restlogging \
+        #coap \