add guh-plugins-merkur package
add merkur boards plugins temprary remove coap test (server down)
This commit is contained in:
parent
6a60dbe2af
commit
1a9284b89f
21
debian/control
vendored
21
debian/control
vendored
@ -113,11 +113,26 @@ Depends: libguh1 (= ${binary:Version}),
|
||||
${shlibs:Depends},
|
||||
${misc:Depends}
|
||||
Recommends:
|
||||
nmap,
|
||||
wakeonlan
|
||||
nmap
|
||||
Description: Plugins for guh IoT server
|
||||
guh is an open source IoT (Internet of Things) server,
|
||||
which allows to control a lot of different devices from many different
|
||||
manufacturers. With the powerful rule engine you are able to connect any
|
||||
device available in the system and create individual scenes and behaviors
|
||||
for your environment.
|
||||
for your environment. This package contains the main plugins.
|
||||
|
||||
Package: guh-plugins-merkur
|
||||
Section: libs
|
||||
Architecture: any
|
||||
Depends: libguh1 (= ${binary:Version}),
|
||||
guhd (= ${binary:Version}),
|
||||
${shlibs:Depends},
|
||||
${misc:Depends}
|
||||
Description: Plugins for guh IoT server - 6LoWPAN Merkur boards
|
||||
guh is an open source IoT (Internet of Things) server,
|
||||
which allows to control a lot of different devices from many different
|
||||
manufacturers. With the powerful rule engine you are able to connect any
|
||||
device available in the system and create individual scenes and behaviors
|
||||
for your environment. This package contains plugins for the 6LoWPAN Merkur
|
||||
board.
|
||||
|
||||
|
||||
1
debian/guh-plugins-merkur.dirs
vendored
Normal file
1
debian/guh-plugins-merkur.dirs
vendored
Normal file
@ -0,0 +1 @@
|
||||
usr/lib/guh/plugins
|
||||
4
debian/guh-plugins-merkur.install
vendored
Normal file
4
debian/guh-plugins-merkur.install
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
usr/lib/guh/plugins/libguh_devicepluginosdomotics.so
|
||||
usr/lib/guh/plugins/libguh_deviceplugindollhouse.so
|
||||
usr/lib/guh/plugins/libguh_devicepluginplantcare.so
|
||||
usr/lib/guh/plugins/libguh_devicepluginws2812.so
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
@ -28,7 +28,8 @@
|
||||
The Coap class provides a signal solt based communication with a \l{https://tools.ietf.org/html/rfc7252}{CoAP (Constrained Application Protocol)}
|
||||
server. The API of this class was inspired by the \l{http://doc.qt.io/qt-5/qnetworkaccessmanager.html}{QNetworkAccessManager} and was
|
||||
written according to the \l{https://tools.ietf.org/html/rfc7252}{RFC7252}.
|
||||
This class supports also blockwise transfere according to the \l{https://tools.ietf.org/html/draft-ietf-core-block-18}{IETF V18} specifications.
|
||||
This class supports also blockwise transfere according to the \l{https://tools.ietf.org/html/draft-ietf-core-block-18}{IETF V18} specifications and
|
||||
observing resources according to the \l{https://tools.ietf.org/html/rfc7641}{RFC7641}.
|
||||
|
||||
\sa CoapReply, CoapRequest
|
||||
|
||||
@ -50,6 +51,8 @@
|
||||
{
|
||||
if (reply->error() != CoapReply::NoError) {
|
||||
qWarning() << "Reply finished with error" << reply->errorString();
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Reply finished" << reply;
|
||||
@ -71,6 +74,8 @@
|
||||
#include "coappdu.h"
|
||||
#include "coapoption.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(dcCoap, "Coap")
|
||||
|
||||
/*! Constructs a coap access manager with the given \a parent and \a port. */
|
||||
Coap::Coap(QObject *parent, const quint16 &port) :
|
||||
QObject(parent),
|
||||
@ -79,7 +84,7 @@ Coap::Coap(QObject *parent, const quint16 &port) :
|
||||
m_socket = new QUdpSocket(this);
|
||||
|
||||
if (!m_socket->bind(QHostAddress::Any, port))
|
||||
qWarning() << "Could not bind to port" << port << m_socket->errorString();
|
||||
qCWarning(dcCoap) << "Could not bind to port" << port << m_socket->errorString();
|
||||
|
||||
connect(m_socket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
|
||||
}
|
||||
@ -134,7 +139,6 @@ CoapReply *Coap::get(const CoapRequest &request)
|
||||
} else {
|
||||
m_replyQueue.enqueue(reply);
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
@ -211,7 +215,7 @@ CoapReply *Coap::deleteResource(const CoapRequest &request)
|
||||
}
|
||||
|
||||
// check if there is a request running
|
||||
if (m_reply == 0) {
|
||||
if (m_reply.isNull()) {
|
||||
m_reply = reply;
|
||||
lookupHost();
|
||||
} else {
|
||||
@ -241,7 +245,7 @@ CoapReply *Coap::enableResourceNotifications(const CoapRequest &request)
|
||||
}
|
||||
|
||||
// check if there is a request running
|
||||
if (m_reply == 0) {
|
||||
if (m_reply.isNull()) {
|
||||
m_reply = reply;
|
||||
lookupHost();
|
||||
} else {
|
||||
@ -359,6 +363,8 @@ void Coap::sendRequest(CoapReply *reply, const bool &lookedUp)
|
||||
reply->m_lockedUp = lookedUp;
|
||||
reply->m_timer->start();
|
||||
|
||||
qCDebug(dcCoap) << "--->" << pdu;
|
||||
|
||||
// send the data
|
||||
if (reply->request().messageType() == CoapPdu::NonConfirmable) {
|
||||
sendData(reply->hostAddress(), reply->port(), pduData);
|
||||
@ -375,17 +381,16 @@ void Coap::sendData(const QHostAddress &hostAddress, const quint16 &port, const
|
||||
|
||||
void Coap::sendCoapPdu(const QHostAddress &hostAddress, const quint16 &port, const CoapPdu &pdu)
|
||||
{
|
||||
qDebug() << "--->" << pdu;
|
||||
m_socket->writeDatagram(pdu.pack(), hostAddress, port);
|
||||
}
|
||||
|
||||
void Coap::processResponse(const CoapPdu &pdu, const QHostAddress &address, const quint16 &port)
|
||||
{
|
||||
// if we are waiting for a response
|
||||
// if we are waiting for a reply response
|
||||
if (m_reply) {
|
||||
qDebug() << "<---" << QString("%1:%2").arg(address.toString()).arg(QString::number(port)) << pdu;
|
||||
qCDebug(dcCoap) << "<---" << QString("%1:%2").arg(address.toString()).arg(QString::number(port)) << pdu;
|
||||
if (!pdu.isValid()) {
|
||||
qWarning() << "Got invalid PDU";
|
||||
qCWarning(dcCoap) << "Got invalid PDU";
|
||||
m_reply->setError(CoapReply::InvalidPduError);
|
||||
m_reply->setFinished();
|
||||
return;
|
||||
@ -403,19 +408,25 @@ void Coap::processResponse(const CoapPdu &pdu, const QHostAddress &address, cons
|
||||
return;
|
||||
}
|
||||
}
|
||||
// check if this is a notification from a known observed resource
|
||||
|
||||
|
||||
if (m_observerReply) {
|
||||
processBlock2Notification(m_observerReply, pdu);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if this is a notification
|
||||
if (m_observeResources.keys().contains(pdu.token())) {
|
||||
processNotification(pdu, address, port);
|
||||
return;
|
||||
}
|
||||
|
||||
qWarning() << "Got message without request or registered observe resource.";
|
||||
qCDebug(dcCoap) << "Got message without request or registered observe resource." << endl << "<---" << pdu;
|
||||
CoapPdu responsePdu;
|
||||
responsePdu.setMessageType(CoapPdu::Reset);
|
||||
responsePdu.setMessageId(pdu.messageId());
|
||||
responsePdu.setToken(pdu.token());
|
||||
sendCoapPdu(address, port, responsePdu);
|
||||
|
||||
}
|
||||
|
||||
void Coap::processIdBasedResponse(CoapReply *reply, const CoapPdu &pdu)
|
||||
@ -423,7 +434,7 @@ void Coap::processIdBasedResponse(CoapReply *reply, const CoapPdu &pdu)
|
||||
// check if this is an empty ACK response (which indicates a separated response)
|
||||
if (pdu.statusCode() == CoapPdu::Empty && pdu.messageType() == CoapPdu::Acknowledgement) {
|
||||
reply->m_timer->stop();
|
||||
qDebug() << "Got empty ACK. Data will be sent separated.";
|
||||
qCDebug(dcCoap) << "Got empty ACK. Data will be sent separated.";
|
||||
return;
|
||||
}
|
||||
|
||||
@ -463,12 +474,95 @@ void Coap::processTokenBasedResponse(CoapReply *reply, const CoapPdu &pdu)
|
||||
|
||||
void Coap::processNotification(const CoapPdu &pdu, const QHostAddress &address, const quint16 &port)
|
||||
{
|
||||
CoapObserveResource resource = m_observeResources.value(pdu.token());
|
||||
qCDebug(dcCoap) << "<--- Notification" << endl << pdu;
|
||||
|
||||
// check if it is a blockwise notification
|
||||
if (pdu.hasOption(CoapOption::Block2)) {
|
||||
if (!m_observeReplyResource.values().contains(resource)) {
|
||||
|
||||
qCDebug(dcCoap) << "Got first part of blocked notification";
|
||||
|
||||
// First part of the blocked notification
|
||||
// respond with ACK
|
||||
CoapPdu responsePdu;
|
||||
responsePdu.setMessageType(CoapPdu::Acknowledgement);
|
||||
responsePdu.setStatusCode(CoapPdu::Empty);
|
||||
responsePdu.setMessageId(pdu.messageId());
|
||||
responsePdu.setToken(pdu.token());
|
||||
|
||||
qCDebug(dcCoap) << "---> Notification" << endl << responsePdu;
|
||||
sendCoapPdu(address, port, responsePdu);
|
||||
|
||||
// create reply for blockwise transfere
|
||||
if (!m_observerReply.isNull()) {
|
||||
m_observeBlockwise.remove(m_observerReply);
|
||||
m_observerReply->deleteLater();
|
||||
}
|
||||
|
||||
m_observerReply = new CoapReply(CoapRequest(resource.url()), this);
|
||||
m_observerReply->setRequestMethod(CoapPdu::Get);
|
||||
m_observerReply->appendPayloadData(pdu.payload());
|
||||
|
||||
// Lets store the observation number
|
||||
int notificationNumber = 0;
|
||||
foreach (const CoapOption &option, pdu.options()) {
|
||||
if (option.option() == CoapOption::Observe) {
|
||||
notificationNumber = option.data().toHex().toInt(0, 16);
|
||||
}
|
||||
}
|
||||
|
||||
m_observeReplyResource.insert(m_observerReply, resource);
|
||||
m_observeBlockwise.insert(m_observerReply, notificationNumber);
|
||||
|
||||
connect(m_observerReply, &CoapReply::timeout, this, &Coap::onReplyTimeout);
|
||||
connect(m_observerReply, &CoapReply::finished, this, &Coap::onReplyFinished);
|
||||
|
||||
CoapPdu pdu;
|
||||
pdu.setMessageType(m_observerReply->request().messageType());
|
||||
pdu.setStatusCode(m_observerReply->requestMethod());
|
||||
pdu.createMessageId();
|
||||
pdu.createToken();
|
||||
|
||||
// Add the options in correct order
|
||||
// Option number 3
|
||||
pdu.addOption(CoapOption::UriHost, m_observerReply->request().url().host().toUtf8());
|
||||
|
||||
QStringList urlTokens = m_observerReply->request().url().path().split("/");
|
||||
urlTokens.removeAll(QString());
|
||||
|
||||
// Option number 11
|
||||
foreach (const QString &token, urlTokens)
|
||||
pdu.addOption(CoapOption::UriPath, token.toUtf8());
|
||||
|
||||
// Option number 15
|
||||
if (m_observerReply->request().url().hasQuery())
|
||||
pdu.addOption(CoapOption::UriQuery, m_observerReply->request().url().query().toUtf8());
|
||||
|
||||
// Option number 23
|
||||
pdu.addOption(CoapOption::Block2, CoapPduBlock::createBlock(1, 2, true));
|
||||
|
||||
QByteArray pduData = pdu.pack();
|
||||
m_observerReply->setRequestData(pduData);
|
||||
m_observerReply->setHostAddress(address);
|
||||
m_observerReply->setPort(port);
|
||||
m_observerReply->m_timer->start();
|
||||
|
||||
qCDebug(dcCoap) << "---> Notification" << endl << pdu;
|
||||
sendData(address, port, pduData);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// respond with ACK
|
||||
CoapPdu responsePdu;
|
||||
responsePdu.setMessageType(CoapPdu::Acknowledgement);
|
||||
responsePdu.setStatusCode(CoapPdu::Empty);
|
||||
responsePdu.setMessageId(pdu.messageId());
|
||||
responsePdu.setToken(pdu.token());
|
||||
|
||||
qCDebug(dcCoap) << "---> Notification" << endl << responsePdu;
|
||||
sendCoapPdu(address, port, responsePdu);
|
||||
|
||||
int notificationNumber = 0;
|
||||
@ -478,13 +572,12 @@ void Coap::processNotification(const CoapPdu &pdu, const QHostAddress &address,
|
||||
}
|
||||
}
|
||||
|
||||
CoapObserveResource resource = m_observeResources.value(pdu.token());
|
||||
emit notificationReceived(resource, notificationNumber, pdu.payload());
|
||||
}
|
||||
|
||||
void Coap::processBlock1Response(CoapReply *reply, const CoapPdu &pdu)
|
||||
{
|
||||
qDebug() << "sent successfully block #" << pdu.block().blockNumber();
|
||||
qCDebug(dcCoap) << "Sent successfully block #" << pdu.block().blockNumber();
|
||||
|
||||
// create next block
|
||||
int index = (pdu.block().blockNumber() * 64) + 64;
|
||||
@ -542,7 +635,7 @@ void Coap::processBlock1Response(CoapReply *reply, const CoapPdu &pdu)
|
||||
|
||||
reply->setMessageId(nextBlockRequest.messageId());
|
||||
|
||||
qDebug() << "--->" << nextBlockRequest;
|
||||
qCDebug(dcCoap) << "--->" << nextBlockRequest;
|
||||
sendData(reply->hostAddress(), reply->port(), pduData);
|
||||
}
|
||||
|
||||
@ -594,7 +687,80 @@ void Coap::processBlock2Response(CoapReply *reply, const CoapPdu &pdu)
|
||||
|
||||
reply->setMessageId(nextBlockRequest.messageId());
|
||||
|
||||
qDebug() << "--->" << nextBlockRequest;
|
||||
qCDebug(dcCoap) << "--->" << nextBlockRequest;
|
||||
sendData(reply->hostAddress(), reply->port(), pduData);
|
||||
}
|
||||
|
||||
void Coap::processBlock2Notification(CoapReply *reply, const CoapPdu &pdu)
|
||||
{
|
||||
if (!m_observeReplyResource.contains(reply)) {
|
||||
qCWarning(dcCoap) << "Could not find observation resource for" << reply;
|
||||
return;
|
||||
}
|
||||
|
||||
CoapObserveResource resource = m_observeReplyResource.value(reply);
|
||||
|
||||
// respond Block2
|
||||
// check if this was the last block
|
||||
if (!pdu.block().moreFlag()) {
|
||||
// respond with ACK
|
||||
CoapPdu responsePdu;
|
||||
responsePdu.setMessageType(CoapPdu::Acknowledgement);
|
||||
responsePdu.setStatusCode(CoapPdu::Empty);
|
||||
responsePdu.setMessageId(pdu.messageId());
|
||||
responsePdu.setToken(pdu.token());
|
||||
|
||||
qCDebug(dcCoap) << "---> Notification" << endl << responsePdu;
|
||||
sendCoapPdu(reply->hostAddress(), reply->port(), responsePdu);
|
||||
|
||||
reply->appendPayloadData(pdu.payload());
|
||||
|
||||
emit notificationReceived(resource, m_observeBlockwise.take(reply), reply->payload());
|
||||
m_observeReplyResource.remove(m_observerReply);
|
||||
m_observerReply->deleteLater();
|
||||
m_observerReply.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
reply->appendPayloadData(pdu.payload());
|
||||
|
||||
CoapPdu nextBlockRequest;
|
||||
nextBlockRequest.setContentType(reply->request().contentType());
|
||||
nextBlockRequest.setMessageType(reply->request().messageType());
|
||||
nextBlockRequest.setStatusCode(reply->requestMethod());
|
||||
nextBlockRequest.setMessageId(pdu.messageId() + 1);
|
||||
nextBlockRequest.setToken(pdu.token());
|
||||
|
||||
// Add the options in correct order
|
||||
// Option number 3
|
||||
if (reply->m_lockedUp)
|
||||
nextBlockRequest.addOption(CoapOption::UriHost, reply->request().url().host().toUtf8());
|
||||
|
||||
// Option number 7
|
||||
if (reply->port() != 5683)
|
||||
nextBlockRequest.addOption(CoapOption::UriPort, QByteArray::number(reply->request().url().port()));
|
||||
|
||||
QStringList urlTokens = reply->request().url().path().split("/");
|
||||
urlTokens.removeAll(QString());
|
||||
|
||||
// Option number 11
|
||||
foreach (const QString &token, urlTokens)
|
||||
nextBlockRequest.addOption(CoapOption::UriPath, token.toUtf8());
|
||||
|
||||
// Option number 15
|
||||
if (reply->request().url().hasQuery())
|
||||
nextBlockRequest.addOption(CoapOption::UriQuery, reply->request().url().query().toUtf8());
|
||||
|
||||
// Option number 23
|
||||
nextBlockRequest.addOption(CoapOption::Block2, CoapPduBlock::createBlock(pdu.block().blockNumber() + 1, 2, false));
|
||||
|
||||
QByteArray pduData = nextBlockRequest.pack();
|
||||
reply->setRequestData(pduData);
|
||||
reply->m_timer->start();
|
||||
|
||||
reply->setMessageId(nextBlockRequest.messageId());
|
||||
|
||||
qCDebug(dcCoap) << "---> Notification" << endl << nextBlockRequest;
|
||||
sendData(reply->hostAddress(), reply->port(), pduData);
|
||||
}
|
||||
|
||||
@ -604,7 +770,7 @@ void Coap::hostLookupFinished(const QHostInfo &hostInfo)
|
||||
reply->setPort(reply->request().url().port(5683));
|
||||
|
||||
if (hostInfo.error() != QHostInfo::NoError) {
|
||||
qDebug() << "Host lookup for" << reply->request().url().host() << "failed:" << hostInfo.errorString();
|
||||
qCDebug(dcCoap) << "Host lookup for" << reply->request().url().host() << "failed:" << hostInfo.errorString();
|
||||
reply->setError(CoapReply::HostNotFoundError);
|
||||
reply->setFinished();
|
||||
return;
|
||||
@ -615,7 +781,7 @@ void Coap::hostLookupFinished(const QHostInfo &hostInfo)
|
||||
|
||||
// check if the url had to be looked up
|
||||
if (reply->request().url().host() != hostAddress.toString()) {
|
||||
qDebug() << reply->request().url().host() << " -> " << hostAddress.toString();
|
||||
qCDebug(dcCoap) << reply->request().url().host() << " -> " << hostAddress.toString();
|
||||
sendRequest(reply, true);
|
||||
} else {
|
||||
sendRequest(reply);
|
||||
@ -641,7 +807,7 @@ void Coap::onReplyTimeout()
|
||||
{
|
||||
CoapReply *reply = qobject_cast<CoapReply *>(sender());
|
||||
if (reply->m_retransmissions < 5) {
|
||||
qDebug() << QString("Reply timeout: resending message %1/4").arg(reply->m_retransmissions);
|
||||
qCDebug(dcCoap) << QString("Reply timeout: resending message %1/4").arg(reply->m_retransmissions);
|
||||
}
|
||||
reply->resend();
|
||||
m_socket->writeDatagram(reply->requestData(), reply->hostAddress(), reply->port());
|
||||
@ -651,12 +817,20 @@ void Coap::onReplyFinished()
|
||||
{
|
||||
CoapReply *reply = qobject_cast<CoapReply *>(sender());
|
||||
|
||||
if (reply == m_observerReply) {
|
||||
m_observeReplyResource.remove(m_observerReply);
|
||||
m_observerReply->deleteLater();
|
||||
m_observerReply.clear();
|
||||
qCWarning(dcCoap) << "Notification reply finished wirh error" << reply->errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply != m_reply)
|
||||
qWarning() << "This should never happen!! Please report a bug if you get this message!";
|
||||
qCWarning(dcCoap) << "This should never happen!! Please report a bug if you get this message!";
|
||||
|
||||
emit replyFinished(reply);
|
||||
m_reply.clear();
|
||||
|
||||
m_reply = 0;
|
||||
// check if there is a request in the queue
|
||||
if (!m_replyQueue.isEmpty()) {
|
||||
m_reply = m_replyQueue.dequeue();
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
@ -25,9 +25,10 @@
|
||||
#include <QHostInfo>
|
||||
#include <QUdpSocket>
|
||||
#include <QHostAddress>
|
||||
#include <QLoggingCategory>
|
||||
#include <QPointer>
|
||||
#include <QQueue>
|
||||
|
||||
#include "libguh.h"
|
||||
#include "coaprequest.h"
|
||||
#include "coapreply.h"
|
||||
#include "coapobserveresource.h"
|
||||
@ -41,12 +42,13 @@
|
||||
*
|
||||
*/
|
||||
|
||||
class LIBGUH_EXPORT Coap : public QObject
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcCoap)
|
||||
|
||||
class Coap : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Coap(QObject *parent = 0, const quint16 &port = 5683);
|
||||
Coap(QObject *parent = 0, const quint16 &port = 5683);
|
||||
|
||||
CoapReply *ping(const CoapRequest &request);
|
||||
CoapReply *get(const CoapRequest &request);
|
||||
@ -58,15 +60,23 @@ public:
|
||||
CoapReply *enableResourceNotifications(const CoapRequest &request);
|
||||
CoapReply *disableNotifications(const CoapRequest &request);
|
||||
|
||||
|
||||
private:
|
||||
QUdpSocket *m_socket;
|
||||
|
||||
CoapReply *m_reply;
|
||||
QHash<int, CoapReply *> m_runningHostLookups;
|
||||
QHash<QByteArray, CoapObserveResource> m_observeResources;
|
||||
|
||||
QPointer<CoapReply> m_reply;
|
||||
QQueue<CoapReply *> m_replyQueue;
|
||||
|
||||
|
||||
QHash<int, CoapReply *> m_runningHostLookups;
|
||||
|
||||
QHash<QByteArray, CoapObserveResource> m_observeResources; // token | resource
|
||||
|
||||
// Blockwise notifications
|
||||
QPointer<CoapReply> m_observerReply;
|
||||
QHash<CoapReply *, CoapObserveResource> m_observeReplyResource; // observe reply | resource
|
||||
QHash<CoapReply *, int> m_observeBlockwise; // observe reply | observe nr.
|
||||
|
||||
void lookupHost();
|
||||
void sendRequest(CoapReply *reply, const bool &lookedUp = false);
|
||||
void sendData(const QHostAddress &hostAddress, const quint16 &port, const QByteArray &data);
|
||||
@ -81,6 +91,9 @@ private:
|
||||
void processBlock1Response(CoapReply *reply, const CoapPdu &pdu);
|
||||
void processBlock2Response(CoapReply *reply, const CoapPdu &pdu);
|
||||
|
||||
void processBlock2Notification(CoapReply *reply, const CoapPdu &pdu);
|
||||
|
||||
|
||||
signals:
|
||||
void replyFinished(CoapReply *reply);
|
||||
void notificationReceived(const CoapObserveResource &resource, const int ¬ificationNumber, const QByteArray &payload);
|
||||
@ -92,5 +105,4 @@ private slots:
|
||||
void onReplyFinished();
|
||||
};
|
||||
|
||||
|
||||
#endif // COAP_H
|
||||
|
||||
30
libguh/coap/coap.pri
Normal file
30
libguh/coap/coap.pri
Normal file
@ -0,0 +1,30 @@
|
||||
QT += network
|
||||
|
||||
QMAKE_CXXFLAGS += -Werror -std=c++11
|
||||
QMAKE_LFLAGS += -std=c++11
|
||||
|
||||
INCLUDEPATH += $$PWD
|
||||
DEPENDPATH += $$PWD
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/coap.h \
|
||||
$$PWD/coappdu.h \
|
||||
$$PWD/coapoption.h \
|
||||
$$PWD/coaprequest.h \
|
||||
$$PWD/coapreply.h \
|
||||
$$PWD/coappdublock.h \
|
||||
$$PWD/corelinkparser.h \
|
||||
$$PWD/corelink.h \
|
||||
$$PWD/coapobserveresource.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/coap.cpp \
|
||||
$$PWD/coappdu.cpp \
|
||||
$$PWD/coapoption.cpp \
|
||||
$$PWD/coaprequest.cpp \
|
||||
$$PWD/coapreply.cpp \
|
||||
$$PWD/coappdublock.cpp \
|
||||
$$PWD/corelinkparser.cpp \
|
||||
$$PWD/corelink.cpp \
|
||||
$$PWD/coapobserveresource.cpp
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
@ -22,11 +22,10 @@
|
||||
#define COAPOBSERVERESOURCE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QHash>
|
||||
#include <QUrl>
|
||||
|
||||
#include "libguh.h"
|
||||
|
||||
class LIBGUH_EXPORT CoapObserveResource
|
||||
class CoapObserveResource
|
||||
{
|
||||
|
||||
public:
|
||||
@ -43,4 +42,15 @@ private:
|
||||
|
||||
};
|
||||
|
||||
inline bool operator==(const CoapObserveResource &r1, const CoapObserveResource &r2)
|
||||
{
|
||||
return r1.url() == r2.url() && r1.token() == r2.token();
|
||||
}
|
||||
|
||||
inline uint qHash(const CoapObserveResource &key, uint seed)
|
||||
{
|
||||
return qHash(key.url().toString(), seed);
|
||||
}
|
||||
|
||||
|
||||
#endif // COAPOBSERVERESOURCE_H
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
@ -20,12 +20,12 @@
|
||||
|
||||
/*!
|
||||
\class CoapOption
|
||||
\brief Represents the option of a Coap PDU.
|
||||
\brief Represents the option of a \l{CoapPdu}.
|
||||
|
||||
\ingroup coap
|
||||
\inmodule libguh
|
||||
|
||||
The CoapOption class provides an easy way to create / parse CoAP options of a CoAP PDU (Protocol Data Unit). An options
|
||||
The CoapOption class provides an easy way to create / parse CoAP options of a \l{CoapPdu} (Protocol Data Unit). An options
|
||||
can be compared with a HTTP header.
|
||||
|
||||
*/
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
@ -25,9 +25,7 @@
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
|
||||
#include "libguh.h"
|
||||
|
||||
class LIBGUH_EXPORT CoapOption
|
||||
class CoapOption
|
||||
{
|
||||
Q_GADGET
|
||||
Q_ENUMS(Option)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
@ -317,6 +317,7 @@ QList<CoapOption> CoapPdu::options() const
|
||||
return m_options;
|
||||
}
|
||||
|
||||
|
||||
/*! Adds the given \a option with the given \a data to this \l{CoapPdu}.
|
||||
|
||||
\sa CoapOption
|
||||
@ -492,7 +493,6 @@ void CoapPdu::unpack(const QByteArray &data)
|
||||
// create a CoapPDU
|
||||
if (data.length() < 4) {
|
||||
m_error = InvalidPduSizeError;
|
||||
qWarning() << "pdu to small" << data.length();
|
||||
}
|
||||
|
||||
quint8 *rawData = (quint8 *)data.data();
|
||||
@ -502,7 +502,6 @@ void CoapPdu::unpack(const QByteArray &data)
|
||||
|
||||
if (tokenLength > 8) {
|
||||
m_error = InvalidTokenError;
|
||||
qWarning() << "PDU token to long";
|
||||
}
|
||||
|
||||
setToken(QByteArray((const char *)rawData + 4, tokenLength));
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
@ -27,8 +27,6 @@
|
||||
#include "coapoption.h"
|
||||
#include "coappdublock.h"
|
||||
|
||||
#include "libguh.h"
|
||||
|
||||
// PDU = Protocol Data Unit
|
||||
|
||||
/* 0 1 2 3
|
||||
@ -44,7 +42,7 @@
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
|
||||
class LIBGUH_EXPORT CoapPdu : public QObject
|
||||
class CoapPdu : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_ENUMS(MessageType)
|
||||
@ -151,6 +149,7 @@ public:
|
||||
|
||||
void clear();
|
||||
bool isValid() const;
|
||||
bool isNull() const;
|
||||
|
||||
QByteArray pack() const;
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
@ -23,9 +23,7 @@
|
||||
|
||||
#include <QByteArray>
|
||||
|
||||
#include "libguh.h"
|
||||
|
||||
class LIBGUH_EXPORT CoapPduBlock
|
||||
class CoapPduBlock
|
||||
{
|
||||
public:
|
||||
CoapPduBlock();
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
@ -45,6 +45,8 @@
|
||||
{
|
||||
if (reply->error() != CoapReply::NoError) {
|
||||
qWarning() << "Reply finished with error" << reply->errorString();
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Reply finished" << reply;
|
||||
@ -52,7 +54,6 @@
|
||||
}
|
||||
\endcode
|
||||
|
||||
|
||||
\sa Coap, CoapRequest
|
||||
|
||||
*/
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
@ -24,13 +24,11 @@
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include "libguh.h"
|
||||
|
||||
#include "coappdu.h"
|
||||
#include "coapoption.h"
|
||||
#include "coaprequest.h"
|
||||
|
||||
class LIBGUH_EXPORT CoapReply : public QObject
|
||||
class CoapReply : public QObject
|
||||
{
|
||||
friend class Coap;
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
@ -24,14 +24,12 @@
|
||||
#include <QUrl>
|
||||
#include <QHostAddress>
|
||||
|
||||
#include "libguh.h"
|
||||
|
||||
#include "coappdu.h"
|
||||
#include "coapoption.h"
|
||||
|
||||
//class Coap;
|
||||
|
||||
class LIBGUH_EXPORT CoapRequest
|
||||
class CoapRequest
|
||||
{
|
||||
// friend class Coap;
|
||||
public:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
@ -24,10 +24,9 @@
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
|
||||
#include "libguh.h"
|
||||
#include "coappdu.h"
|
||||
|
||||
class LIBGUH_EXPORT CoreLink
|
||||
class CoreLink
|
||||
{
|
||||
public:
|
||||
CoreLink();
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
@ -52,12 +52,9 @@ CoreLinkParser::CoreLinkParser(const QByteArray &data, QObject *parent) :
|
||||
{
|
||||
QList<QByteArray> linkList = data.split(',');
|
||||
foreach (const QByteArray &linkLine, linkList) {
|
||||
qDebug() << "-------------------------------------";
|
||||
qDebug() << linkLine;
|
||||
QList<QByteArray> valueList = linkLine.split(';');
|
||||
CoreLink link;
|
||||
foreach (const QByteArray &value, valueList) {
|
||||
qDebug() << value;
|
||||
if (value.startsWith("<")) {
|
||||
link.setPath(QString(value.mid(1, value.length() - 2)));
|
||||
} else if (value.startsWith("rt=")) {
|
||||
@ -74,7 +71,6 @@ CoreLinkParser::CoreLinkParser(const QByteArray &data, QObject *parent) :
|
||||
link.setObservable(true);
|
||||
}
|
||||
}
|
||||
qDebug() << endl << link;
|
||||
m_links.append(link);
|
||||
}
|
||||
}
|
||||
@ -84,5 +80,3 @@ QList<CoreLink> CoreLinkParser::links() const
|
||||
{
|
||||
return m_links;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015-2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of QtCoap. *
|
||||
* *
|
||||
@ -23,12 +23,11 @@
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "libguh.h"
|
||||
#include "corelink.h"
|
||||
|
||||
// Constrained RESTful Environments (CoRE) Link Format : http://tools.ietf.org/html/rfc6690
|
||||
|
||||
class LIBGUH_EXPORT CoreLinkParser : public QObject
|
||||
class CoreLinkParser : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
@ -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 \
|
||||
|
||||
@ -23,6 +23,9 @@
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
// Include dcCoap
|
||||
#include "coap/coap.h"
|
||||
|
||||
// Core / libguh
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcApplication)
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcDeviceManager)
|
||||
|
||||
@ -15,10 +15,12 @@ SUBDIRS += elro \
|
||||
commandlauncher \
|
||||
unitec \
|
||||
leynew \
|
||||
#tune \
|
||||
udpcommander \
|
||||
kodi \
|
||||
elgato \
|
||||
awattar \
|
||||
netatmo \
|
||||
#osdomotics \
|
||||
dollhouse \
|
||||
plantcare \
|
||||
osdomotics \
|
||||
ws2812 \
|
||||
|
||||
375
plugins/deviceplugins/dollhouse/deviceplugindollhouse.cpp
Normal file
375
plugins/deviceplugins/dollhouse/deviceplugindollhouse.cpp
Normal file
@ -0,0 +1,375 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of guh. *
|
||||
* *
|
||||
* Guh is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, version 2 of the License. *
|
||||
* *
|
||||
* Guh is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with guh. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "deviceplugindollhouse.h"
|
||||
#include "plugininfo.h"
|
||||
|
||||
#include <QUrlQuery>
|
||||
|
||||
DevicePluginDollHouse::DevicePluginDollHouse() :
|
||||
m_houseReachable(false)
|
||||
{
|
||||
m_coap = new Coap(this);
|
||||
connect(m_coap, SIGNAL(replyFinished(CoapReply*)), this, SLOT(coapReplyFinished(CoapReply*)));
|
||||
}
|
||||
|
||||
DeviceManager::HardwareResources DevicePluginDollHouse::requiredHardware() const
|
||||
{
|
||||
return DeviceManager::HardwareResourceNetworkManager | DeviceManager::HardwareResourceTimer;
|
||||
}
|
||||
|
||||
DeviceManager::DeviceSetupStatus DevicePluginDollHouse::setupDevice(Device *device)
|
||||
{
|
||||
qCDebug(dcDollhouse) << "Setup" << device->name() << device->params();
|
||||
|
||||
if (device->deviceClassId() == connectionDeviceClassId) {
|
||||
|
||||
foreach (Device *device, myDevices()) {
|
||||
if (device->deviceClassId() == connectionDeviceClassId) {
|
||||
qCWarning(dcDollhouse) << "Dollhouse connection allready configured.";
|
||||
return DeviceManager::DeviceSetupStatusFailure;
|
||||
}
|
||||
}
|
||||
|
||||
int lookupId = QHostInfo::lookupHost(device->paramValue("RPL address").toString(), this, SLOT(hostLockupFinished(QHostInfo)));
|
||||
m_asyncSetup.insert(lookupId, device);
|
||||
|
||||
return DeviceManager::DeviceSetupStatusAsync;
|
||||
|
||||
} else if (device->deviceClassId() == lightDeviceClassId) {
|
||||
|
||||
DollhouseLight *light = new DollhouseLight(this);
|
||||
light->setName(device->paramValue("name").toString());
|
||||
light->setHostAddress(device->paramValue("address").toString());
|
||||
light->setConnectionUuid(device->paramValue("connection uuid").toString());
|
||||
light->setLightId(device->paramValue("light id").toInt());
|
||||
|
||||
device->setParentId(DeviceId(light->connectionUuid()));
|
||||
|
||||
m_lights.insert(device, light);
|
||||
|
||||
return DeviceManager::DeviceSetupStatusSuccess;
|
||||
}
|
||||
|
||||
return DeviceManager::DeviceSetupStatusFailure;
|
||||
}
|
||||
|
||||
|
||||
void DevicePluginDollHouse::deviceRemoved(Device *device)
|
||||
{
|
||||
if (device->deviceClassId() == lightDeviceClassId) {
|
||||
DollhouseLight *light = m_lights.take(device);
|
||||
light->deleteLater();
|
||||
}
|
||||
|
||||
m_houseAddress.clear();
|
||||
m_houseReachable = false;
|
||||
}
|
||||
|
||||
void DevicePluginDollHouse::guhTimer()
|
||||
{
|
||||
foreach (Device *device, myDevices()) {
|
||||
if (device->deviceClassId() == connectionDeviceClassId && m_houseAddress.isNull()) {
|
||||
scanNodes(device);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_houseAddress.isNull()) {
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(m_houseAddress.toString());
|
||||
url.setPort(5683);
|
||||
url.setPath("/a/ws2812");
|
||||
m_asyncPings.append(m_coap->ping(CoapRequest(url)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DeviceManager::DeviceError DevicePluginDollHouse::executeAction(Device *device, const Action &action)
|
||||
{
|
||||
if (device->deviceClassId() == lightDeviceClassId) {
|
||||
if (!device->stateValue(reachableStateTypeId).toBool())
|
||||
return DeviceManager::DeviceErrorHardwareNotAvailable;
|
||||
|
||||
DollhouseLight *light = m_lights.value(device);
|
||||
|
||||
// Create URL for action
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("number", QString::number(light->lightId()));
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("address").toString());
|
||||
url.setPath("/a/ws2812");
|
||||
url.setQuery(query);
|
||||
|
||||
if (action.actionTypeId() == colorActionTypeId) {
|
||||
QColor color = action.param("color").value().value<QColor>().toHsv();
|
||||
QColor newColor = QColor::fromHsv(color.hue(), color.saturation(), 100 * light->brightness() / 255.0);
|
||||
QByteArray message = "color=" + newColor.toRgb().name().remove("#").toUtf8();
|
||||
|
||||
qCDebug(dcDollhouse) << "Sending" << url.toString() << message;
|
||||
|
||||
CoapReply *reply = m_coap->post(CoapRequest(url), message);
|
||||
m_asyncActions.insert(reply, action);
|
||||
m_asyncActionLights.insert(action.id(), light);
|
||||
|
||||
return DeviceManager::DeviceErrorAsync;
|
||||
|
||||
} else if (action.actionTypeId() == powerActionTypeId) {
|
||||
|
||||
QByteArray message;
|
||||
if (action.param("power").value().toBool()) {
|
||||
QColor color = light->color().toHsv();
|
||||
QColor newColor = QColor::fromHsv(color.hue(), color.saturation(), 100 * light->brightness() / 255.0);
|
||||
message = "color=" + newColor.toRgb().name().remove("#").toUtf8();
|
||||
} else {
|
||||
message.append("color=000000");
|
||||
}
|
||||
|
||||
qCDebug(dcDollhouse) << "Sending" << url.toString() << message;
|
||||
|
||||
CoapReply *reply = m_coap->post(CoapRequest(url), message);
|
||||
m_asyncActions.insert(reply, action);
|
||||
m_asyncActionLights.insert(action.id(), light);
|
||||
|
||||
return DeviceManager::DeviceErrorAsync;
|
||||
} else if (action.actionTypeId() == brightnessActionTypeId) {
|
||||
|
||||
int brightness = action.param("brightness").value().toInt();
|
||||
|
||||
QColor color = light->color().toHsv();
|
||||
QColor newColor = QColor::fromHsv(color.hue(), color.saturation(), 100 * brightness / 255.0);
|
||||
|
||||
QByteArray message = "color=" + newColor.toRgb().name().remove("#").toUtf8();
|
||||
|
||||
qCDebug(dcDollhouse) << "Sending" << url.toString() << message;
|
||||
|
||||
CoapReply *reply = m_coap->post(CoapRequest(url), message);
|
||||
m_asyncActions.insert(reply, action);
|
||||
m_asyncActionLights.insert(action.id(), light);
|
||||
|
||||
return DeviceManager::DeviceErrorAsync;
|
||||
}
|
||||
}
|
||||
|
||||
return DeviceManager::DeviceErrorNoError;
|
||||
}
|
||||
|
||||
void DevicePluginDollHouse::networkManagerReplyReady(QNetworkReply *reply)
|
||||
{
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
// create user finished
|
||||
if (m_asyncNodeScan.contains(reply)) {
|
||||
Device *device = m_asyncNodeScan.take(reply);
|
||||
|
||||
// check HTTP status code
|
||||
if (status != 200) {
|
||||
qCWarning(dcDollhouse) << "Node scan reply HTTP error:" << reply->errorString();
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
parseNode(device, reply->readAll());
|
||||
}
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
void DevicePluginDollHouse::scanNodes(Device *device)
|
||||
{
|
||||
QUrl url;
|
||||
url.setScheme("http");
|
||||
url.setHost(device->paramValue("RPL address").toString());
|
||||
|
||||
QNetworkReply *reply = networkManagerGet(QNetworkRequest(url));
|
||||
m_asyncNodeScan.insert(reply, device);
|
||||
}
|
||||
|
||||
void DevicePluginDollHouse::parseNode(Device *device, const QByteArray &data)
|
||||
{
|
||||
|
||||
QList<QByteArray> lines = data.split('\n');
|
||||
QList<QHostAddress> addresses;
|
||||
foreach (const QByteArray &line, lines) {
|
||||
if (line.isEmpty())
|
||||
continue;
|
||||
|
||||
// remove the '/128' from the address
|
||||
QHostAddress address(QString(data.left(line.length() - 4)));
|
||||
|
||||
if (!address.isNull())
|
||||
addresses.append(address);
|
||||
|
||||
}
|
||||
|
||||
// int index = data.indexOf("Routes<pre>") + 11;
|
||||
// int delta = data.indexOf("/128",index);
|
||||
// QHostAddress houseAddress = QHostAddress(QString(data.mid(index, delta - index)));
|
||||
|
||||
if (addresses.isEmpty())
|
||||
return;
|
||||
|
||||
QHostAddress houseAddress = addresses.first();
|
||||
if (houseAddress != m_houseAddress && !houseAddress.isNull()) {
|
||||
m_houseAddress = houseAddress;
|
||||
qCDebug(dcDollhouse) << "Found house at" << m_houseAddress.toString();
|
||||
|
||||
if (!m_lights.isEmpty())
|
||||
return;
|
||||
|
||||
QList<DeviceDescriptor> deviceDescriptorList;
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
DeviceDescriptor descriptor(lightDeviceClassId, "Light", QString::number(i));
|
||||
ParamList params;
|
||||
params.append(Param("address", m_houseAddress.toString()));
|
||||
params.append(Param("light id", i));
|
||||
params.append(Param("connection uuid", device->id()));
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
params.append(Param("name", "Living room"));
|
||||
break;
|
||||
case 1:
|
||||
params.append(Param("name", "Kitchen"));
|
||||
break;
|
||||
case 2:
|
||||
params.append(Param("name", "Under the bed"));
|
||||
break;
|
||||
case 3:
|
||||
params.append(Param("name", "Bedroom"));
|
||||
break;
|
||||
case 4:
|
||||
params.append(Param("name", "Dining room"));
|
||||
break;
|
||||
default:
|
||||
params.append(Param("name", QString("Light %1").arg(QString::number(i))));
|
||||
break;
|
||||
}
|
||||
|
||||
descriptor.setParams(params);
|
||||
deviceDescriptorList.append(descriptor);
|
||||
}
|
||||
|
||||
if (!deviceDescriptorList.isEmpty())
|
||||
emit autoDevicesAppeared(lightDeviceClassId, deviceDescriptorList);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void DevicePluginDollHouse::hostLockupFinished(const QHostInfo &info)
|
||||
{
|
||||
Device *device = m_asyncSetup.value(info.lookupId());
|
||||
if (!device)
|
||||
return;
|
||||
|
||||
if (info.error() != QHostInfo::NoError) {
|
||||
qCWarning(dcDollhouse) << "Could not look up host" << info.hostName() << info.errorString();
|
||||
emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusFailure);
|
||||
}
|
||||
|
||||
qCDebug(dcDollhouse) << "Looked up successfully" << info.hostName();
|
||||
emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusSuccess);
|
||||
|
||||
scanNodes(device);
|
||||
}
|
||||
|
||||
void DevicePluginDollHouse::coapReplyFinished(CoapReply *reply)
|
||||
{
|
||||
if (m_asyncPings.contains(reply)) {
|
||||
m_asyncPings.removeAll(reply);
|
||||
|
||||
if (reply->error() != CoapReply::NoError || reply->statusCode() != CoapPdu::Empty) {
|
||||
|
||||
if (m_houseReachable) {
|
||||
qCWarning(dcDollhouse) << "Could not ping Dollhouse:" << reply->errorString();
|
||||
m_houseReachable = false;
|
||||
foreach (Device *device, myDevices()) {
|
||||
if (device->deviceClassId() == lightDeviceClassId) {
|
||||
device->setStateValue(reachableStateTypeId, m_houseReachable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (!m_houseReachable) {
|
||||
qCDebug(dcDollhouse) << "Dollhouse reachable";
|
||||
m_houseReachable = true;
|
||||
foreach (Device *device, myDevices()) {
|
||||
if (device->deviceClassId() == lightDeviceClassId) {
|
||||
device->setStateValue(reachableStateTypeId, m_houseReachable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (m_asyncActions.contains(reply)) {
|
||||
Action action = m_asyncActions.take(reply);
|
||||
DollhouseLight *light = m_asyncActionLights.take(action.id());
|
||||
|
||||
if (reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcDollhouse) << "Got action response with error" << reply->errorString();
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply->statusCode() != CoapPdu::Content) {
|
||||
qCWarning(dcDollhouse) << "Got action response with status code" << reply;
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorNoError);
|
||||
|
||||
// Set the states
|
||||
if (action.actionTypeId() == powerActionTypeId) {
|
||||
bool power = action.param("power").value().toBool();
|
||||
light->setPower(power);
|
||||
m_lights.key(light)->setStateValue(powerStateTypeId, power);
|
||||
|
||||
} else if (action.actionTypeId() == colorActionTypeId) {
|
||||
if (!light->power()) {
|
||||
light->setPower(true);
|
||||
m_lights.key(light)->setStateValue(powerStateTypeId, true);
|
||||
}
|
||||
|
||||
QColor color = action.param("color").value().value<QColor>();
|
||||
light->setColor(color);
|
||||
m_lights.key(light)->setStateValue(colorStateTypeId, color);
|
||||
|
||||
} else if (action.actionTypeId() == brightnessActionTypeId) {
|
||||
if (!light->power()) {
|
||||
light->setPower(true);
|
||||
m_lights.key(light)->setStateValue(powerStateTypeId, true);
|
||||
}
|
||||
|
||||
int brightness = action.param("brightness").value().toInt();
|
||||
light->setBrightness(brightness);
|
||||
m_lights.key(light)->setStateValue(brightnessStateTypeId, brightness);
|
||||
}
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
74
plugins/deviceplugins/dollhouse/deviceplugindollhouse.h
Normal file
74
plugins/deviceplugins/dollhouse/deviceplugindollhouse.h
Normal file
@ -0,0 +1,74 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2016 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* *
|
||||
* This file is part of guh. *
|
||||
* *
|
||||
* Guh is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, version 2 of the License. *
|
||||
* *
|
||||
* Guh is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with guh. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef DEVICEPLUGINDOLLHOUSE_H
|
||||
#define DEVICEPLUGINDOLLHOUSE_H
|
||||
|
||||
#include "plugin/deviceplugin.h"
|
||||
#include "devicemanager.h"
|
||||
|
||||
#include "coap/coap.h"
|
||||
#include "dollhouselight.h"
|
||||
|
||||
#include <QHash>
|
||||
|
||||
class DevicePluginDollHouse : public DevicePlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "deviceplugindollhouse.json")
|
||||
Q_INTERFACES(DevicePlugin)
|
||||
|
||||
public:
|
||||
DevicePluginDollHouse();
|
||||
|
||||
DeviceManager::HardwareResources requiredHardware() const override;
|
||||
DeviceManager::DeviceSetupStatus setupDevice(Device *device) override;
|
||||
void deviceRemoved(Device *device) override;
|
||||
|
||||
void guhTimer() override;
|
||||
DeviceManager::DeviceError executeAction(Device *device, const Action &action) override;
|
||||
|
||||
void networkManagerReplyReady(QNetworkReply *reply) override;
|
||||
private:
|
||||
Coap *m_coap;
|
||||
|
||||
QHash<int, Device *> m_asyncSetup;
|
||||
QHash<QNetworkReply *, Device *> m_asyncNodeScan;
|
||||
|
||||
QHash<CoapReply *, Action> m_asyncActions;
|
||||
QHash<ActionId, DollhouseLight *> m_asyncActionLights;
|
||||
|
||||
QList<CoapReply *> m_asyncPings;
|
||||
|
||||
QHostAddress m_houseAddress;
|
||||
bool m_houseReachable;
|
||||
|
||||
QHash<Device *, DollhouseLight *> m_lights;
|
||||
|
||||
void scanNodes(Device *device);
|
||||
void parseNode(Device *device, const QByteArray &data);
|
||||
|
||||
private slots:
|
||||
void hostLockupFinished(const QHostInfo &info);
|
||||
void coapReplyFinished(CoapReply *reply);
|
||||
|
||||
};
|
||||
|
||||
#endif // DEVICEPLUGINDOLLHOUSE_H
|
||||
106
plugins/deviceplugins/dollhouse/deviceplugindollhouse.json
Normal file
106
plugins/deviceplugins/dollhouse/deviceplugindollhouse.json
Normal file
@ -0,0 +1,106 @@
|
||||
{
|
||||
"name": "Smart Dollhouse",
|
||||
"idName": "Dollhouse",
|
||||
"id": "ee62716c-64b5-4ec5-a877-9a8d9c1042cb",
|
||||
"vendors": [
|
||||
{
|
||||
"name": "guh",
|
||||
"idName": "guh",
|
||||
"id": "2062d64d-3232-433c-88bc-0d33c0ba2ba6",
|
||||
"deviceClasses": [
|
||||
{
|
||||
"name": "Dollhouse Connection",
|
||||
"idName": "connection",
|
||||
"deviceClassId": "44e12a1c-9711-4780-9913-53bb38264e1e",
|
||||
"createMethods": ["user"],
|
||||
"basicTags": [
|
||||
"Device",
|
||||
"Gateway"
|
||||
],
|
||||
"paramTypes": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "QString",
|
||||
"defaultValue": "Dollhouse Connection"
|
||||
},
|
||||
{
|
||||
"name": "RPL address",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"defaultValue": "fdaa:e9b8:d03a::ff:fe00:1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Dollhouse Light",
|
||||
"idName": "light",
|
||||
"deviceClassId": "b4dd5f10-36d4-4232-867a-6d3b04a08bad",
|
||||
"createMethods": ["auto"],
|
||||
"basicTags": [
|
||||
"Device",
|
||||
"Lighting"
|
||||
],
|
||||
"paramTypes": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "QString",
|
||||
"defaultValue": "Dollhouse Light"
|
||||
},
|
||||
{
|
||||
"name": "address",
|
||||
"type": "QString"
|
||||
},
|
||||
{
|
||||
"name": "light id",
|
||||
"type": "int",
|
||||
"defaultValue": 0,
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"name": "connection uuid",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"readOnly": true
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "e4885a73-9fe1-48ca-b773-aeb93f34cd54",
|
||||
"idName": "color",
|
||||
"name": "color",
|
||||
"type": "QColor",
|
||||
"defaultValue": "#fff30a",
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "e78c9136-c10a-45c9-b6bc-2d937f09cdec",
|
||||
"idName": "brightness",
|
||||
"name": "brightness",
|
||||
"type": "int",
|
||||
"unit": "Percentage",
|
||||
"minValue": 0,
|
||||
"maxValue": 100,
|
||||
"defaultValue": 100,
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "f6ac30a0-77b8-4f1f-8c44-4c2e6d542663",
|
||||
"idName": "power",
|
||||
"name": "power",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "93c539b4-50d8-431e-8be4-5ebba89452b7",
|
||||
"idName": "reachable",
|
||||
"name": "reachable",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
11
plugins/deviceplugins/dollhouse/dollhouse.pro
Normal file
11
plugins/deviceplugins/dollhouse/dollhouse.pro
Normal file
@ -0,0 +1,11 @@
|
||||
include(../../plugins.pri)
|
||||
|
||||
TARGET = $$qtLibraryTarget(guh_deviceplugindollhouse)
|
||||
|
||||
SOURCES += \
|
||||
deviceplugindollhouse.cpp \
|
||||
dollhouselight.cpp
|
||||
|
||||
HEADERS += \
|
||||
deviceplugindollhouse.h \
|
||||
dollhouselight.h
|
||||
81
plugins/deviceplugins/dollhouse/dollhouselight.cpp
Normal file
81
plugins/deviceplugins/dollhouse/dollhouselight.cpp
Normal file
@ -0,0 +1,81 @@
|
||||
#include "dollhouselight.h"
|
||||
|
||||
DollhouseLight::DollhouseLight(QObject *parent) :
|
||||
QObject(parent),
|
||||
m_color(QColor("#fff30a")),
|
||||
m_brightness(100),
|
||||
m_power(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QString DollhouseLight::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void DollhouseLight::setName(const QString &name)
|
||||
{
|
||||
m_name = name;
|
||||
}
|
||||
|
||||
QString DollhouseLight::connectionUuid() const
|
||||
{
|
||||
return m_connectionUuid;
|
||||
}
|
||||
|
||||
void DollhouseLight::setConnectionUuid(const QString &connectionUuid)
|
||||
{
|
||||
m_connectionUuid = connectionUuid;
|
||||
}
|
||||
|
||||
QString DollhouseLight::hostAddress() const
|
||||
{
|
||||
return m_hostAddress;
|
||||
}
|
||||
|
||||
void DollhouseLight::setHostAddress(const QString &address)
|
||||
{
|
||||
m_hostAddress = address;
|
||||
}
|
||||
|
||||
int DollhouseLight::lightId() const
|
||||
{
|
||||
return m_lightId;
|
||||
}
|
||||
|
||||
void DollhouseLight::setLightId(const int &lightId)
|
||||
{
|
||||
m_lightId = lightId;
|
||||
}
|
||||
|
||||
QColor DollhouseLight::color() const
|
||||
{
|
||||
return m_color;
|
||||
}
|
||||
|
||||
void DollhouseLight::setColor(const QColor &color)
|
||||
{
|
||||
m_color = color;
|
||||
}
|
||||
|
||||
int DollhouseLight::brightness() const
|
||||
{
|
||||
return m_brightness;
|
||||
}
|
||||
|
||||
void DollhouseLight::setBrightness(const int &brightness)
|
||||
{
|
||||
m_brightness = brightness;
|
||||
}
|
||||
|
||||
bool DollhouseLight::power() const
|
||||
{
|
||||
return m_power;
|
||||
}
|
||||
|
||||
void DollhouseLight::setPower(const bool &power)
|
||||
{
|
||||
m_power = power;
|
||||
}
|
||||
|
||||
47
plugins/deviceplugins/dollhouse/dollhouselight.h
Normal file
47
plugins/deviceplugins/dollhouse/dollhouselight.h
Normal file
@ -0,0 +1,47 @@
|
||||
#ifndef DOLLHOUSELIGHT_H
|
||||
#define DOLLHOUSELIGHT_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QColor>
|
||||
|
||||
class DollhouseLight : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DollhouseLight(QObject *parent = 0);
|
||||
|
||||
QString name() const;
|
||||
void setName(const QString &name);
|
||||
|
||||
QString connectionUuid() const;
|
||||
void setConnectionUuid(const QString &connectionUuid);
|
||||
|
||||
QString hostAddress() const;
|
||||
void setHostAddress(const QString &address);
|
||||
|
||||
int lightId() const;
|
||||
void setLightId(const int &lightId);
|
||||
|
||||
// properties
|
||||
QColor color() const;
|
||||
void setColor(const QColor &color);
|
||||
|
||||
int brightness() const;
|
||||
void setBrightness(const int &brightness);
|
||||
|
||||
bool power() const;
|
||||
void setPower(const bool &power);
|
||||
|
||||
private:
|
||||
QString m_name;
|
||||
QString m_connectionUuid;
|
||||
QString m_hostAddress;
|
||||
int m_lightId;
|
||||
|
||||
QColor m_color;
|
||||
int m_brightness;
|
||||
bool m_power;
|
||||
|
||||
};
|
||||
|
||||
#endif // DOLLHOUSELIGHT_H
|
||||
561
plugins/deviceplugins/plantcare/devicepluginplantcare.cpp
Normal file
561
plugins/deviceplugins/plantcare/devicepluginplantcare.cpp
Normal file
@ -0,0 +1,561 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2016 Bernhard Trinnes <bernhard.trinnes@guh.guru> *
|
||||
* *
|
||||
* This file is part of guh. *
|
||||
* *
|
||||
* Guh is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, version 2 of the License. *
|
||||
* *
|
||||
* Guh is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with guh. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
#include "devicepluginplantcare.h"
|
||||
#include "plugin/device.h"
|
||||
#include "plugininfo.h"
|
||||
|
||||
DevicePluginPlantCare::DevicePluginPlantCare()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
DeviceManager::HardwareResources DevicePluginPlantCare::requiredHardware() const
|
||||
{
|
||||
// We need the NetworkManager for node discovery and the timer for ping requests
|
||||
return DeviceManager::HardwareResourceNetworkManager | DeviceManager::HardwareResourceTimer;
|
||||
}
|
||||
|
||||
DeviceManager::DeviceSetupStatus DevicePluginPlantCare::setupDevice(Device *device)
|
||||
{
|
||||
qCDebug(dcPlantCare) << "Setup Plant Care" << device->name() << device->params();
|
||||
|
||||
// Check if device already added with this address
|
||||
if (deviceAlreadyAdded(QHostAddress(device->paramValue("host").toString()))) {
|
||||
qCWarning(dcPlantCare) << "Device with this address already added.";
|
||||
return DeviceManager::DeviceSetupStatusFailure;
|
||||
}
|
||||
|
||||
// Create the CoAP socket if not already created
|
||||
if (m_coap.isNull()) {
|
||||
m_coap = new Coap(this);
|
||||
connect(m_coap.data(), SIGNAL(replyFinished(CoapReply*)), this, SLOT(coapReplyFinished(CoapReply*)));
|
||||
connect(m_coap.data(), SIGNAL(notificationReceived(CoapObserveResource,int,QByteArray)), this, SLOT(onNotificationReceived(CoapObserveResource,int,QByteArray)));
|
||||
}
|
||||
|
||||
return DeviceManager::DeviceSetupStatusSuccess;
|
||||
}
|
||||
|
||||
void DevicePluginPlantCare::deviceRemoved(Device *device)
|
||||
{
|
||||
Q_UNUSED(device)
|
||||
|
||||
// Delete the CoAP socket if there are no devices left
|
||||
if (myDevices().isEmpty()) {
|
||||
m_coap->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
DeviceManager::DeviceError DevicePluginPlantCare::discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶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<DeviceDescriptor>());
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
qCDebug(dcPlantCare) << "Node discovery finished:" << endl << data;
|
||||
|
||||
QList<DeviceDescriptor> deviceDescriptors;
|
||||
QList<QByteArray> lines = data.split('\n');
|
||||
qCDebug(dcPlantCare) << lines;
|
||||
foreach (const QByteArray &line, lines) {
|
||||
if (line.isEmpty())
|
||||
continue;
|
||||
|
||||
QHostAddress address(QString(line.left(line.length() - 4)));
|
||||
if (address.isNull())
|
||||
continue;
|
||||
|
||||
qCDebug(dcPlantCare) << "Found node" << address.toString();
|
||||
// Create a deviceDescriptor for each found address
|
||||
DeviceDescriptor descriptor(deviceClassId, "Plant Care", address.toString());
|
||||
ParamList params;
|
||||
params.append(Param("host", address.toString()));
|
||||
descriptor.setParams(params);
|
||||
deviceDescriptors.append(descriptor);
|
||||
}
|
||||
// Inform the user which devices were found
|
||||
emit devicesDiscovered(deviceClassId, deviceDescriptors);
|
||||
}
|
||||
|
||||
// Delete the HTTP reply
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
void DevicePluginPlantCare::postSetupDevice(Device *device)
|
||||
{
|
||||
// Try to ping the device after a successful setup
|
||||
pingDevice(device);
|
||||
}
|
||||
|
||||
void DevicePluginPlantCare::guhTimer()
|
||||
{
|
||||
// Try to ping each device every 10 seconds to make sure it is still reachable
|
||||
foreach (Device *device, myDevices()) {
|
||||
if (device->deviceClassId() == plantCareDeviceClassId) {
|
||||
pingDevice(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DeviceManager::DeviceError DevicePluginPlantCare::executeAction(Device *device, const Action &action)
|
||||
{
|
||||
if (device->deviceClassId() != plantCareDeviceClassId)
|
||||
return DeviceManager::DeviceErrorDeviceClassNotFound;
|
||||
|
||||
qCDebug(dcPlantCare) << "Execute action" << device->name() << action.params();
|
||||
|
||||
// Check if the device is reachable
|
||||
if (!device->stateValue(reachableStateTypeId).toBool()) {
|
||||
qCWarning(dcPlantCare) << "Device not reachable.";
|
||||
return DeviceManager::DeviceErrorHardwareNotAvailable;
|
||||
}
|
||||
|
||||
// Check which action sould be executed
|
||||
if (action.actionTypeId() == toggleLedActionTypeId) {
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
url.setPath("/a/toggle");
|
||||
|
||||
CoapReply *reply = m_coap->post(CoapRequest(url));
|
||||
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcPlantCare) << "CoAP reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return DeviceManager::DeviceErrorHardwareFailure;
|
||||
}
|
||||
|
||||
m_toggleLightRequests.insert(reply, action);
|
||||
m_asyncActions.insert(action.id(), device);
|
||||
return DeviceManager::DeviceErrorAsync;
|
||||
|
||||
} else if(action.actionTypeId() == brightnessActionTypeId) {
|
||||
int light = qRound(action.param("brightness").value().toInt() * 255.0 / 100.0);
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
url.setPath("/a/light");
|
||||
|
||||
QByteArray payload = QString("pwm=%1").arg(QString::number(light)).toUtf8();
|
||||
qCDebug(dcPlantCare()) << "Sending" << payload << url.path();
|
||||
CoapReply *reply = m_coap->post(CoapRequest(url), payload);
|
||||
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcPlantCare) << "CoAP reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return DeviceManager::DeviceErrorHardwareFailure;
|
||||
}
|
||||
|
||||
m_setBrightness.insert(reply, action);
|
||||
m_asyncActions.insert(action.id(), device);
|
||||
return DeviceManager::DeviceErrorAsync;
|
||||
|
||||
} else if(action.actionTypeId() == waterPumpActionTypeId) {
|
||||
bool pump = action.param("water pump power").value().toBool();
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
url.setPath("/a/pump");
|
||||
|
||||
QByteArray payload = QString("mode=%1").arg(QString::number((int)pump)).toUtf8();
|
||||
qCDebug(dcPlantCare()) << "Sending" << payload;
|
||||
CoapReply *reply = m_coap->post(CoapRequest(url), payload);
|
||||
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcPlantCare) << "CoAP reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return DeviceManager::DeviceErrorHardwareFailure;
|
||||
}
|
||||
|
||||
m_setPumpPower.insert(reply, action);
|
||||
m_asyncActions.insert(action.id(), device);
|
||||
return DeviceManager::DeviceErrorAsync;
|
||||
}
|
||||
return DeviceManager::DeviceErrorActionTypeNotFound;
|
||||
}
|
||||
|
||||
void DevicePluginPlantCare::pingDevice(Device *device)
|
||||
{
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
m_pingReplies.insert(m_coap->ping(CoapRequest(url)), device);
|
||||
}
|
||||
|
||||
void DevicePluginPlantCare::updateBattery(Device *device)
|
||||
{
|
||||
qCDebug(dcPlantCare) << "Update" << device->name() << "battery value";
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
url.setPath("/s/battery");
|
||||
CoapReply *reply = m_coap->get(CoapRequest(url));
|
||||
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcPlantCare) << "CoAP reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
m_updateReplies.insert(reply, device);
|
||||
}
|
||||
|
||||
void DevicePluginPlantCare::updateMoisture(Device *device)
|
||||
{
|
||||
qCDebug(dcPlantCare) << "Update" << device->name() << "moisture value";
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
url.setPath("/s/moisture");
|
||||
CoapReply *reply = m_coap->get(CoapRequest(url));
|
||||
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcPlantCare) << "CoAP reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
m_updateReplies.insert(reply, device);
|
||||
}
|
||||
|
||||
void DevicePluginPlantCare::updateWater(Device *device)
|
||||
{
|
||||
qCDebug(dcPlantCare) << "Update" << device->name() << "water value";
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
url.setPath("/s/water");
|
||||
CoapReply *reply = m_coap->get(CoapRequest(url));
|
||||
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcPlantCare) << "CoAP reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
m_updateReplies.insert(reply, device);
|
||||
}
|
||||
|
||||
void DevicePluginPlantCare::updateBrightness(Device *device)
|
||||
{
|
||||
qCDebug(dcPlantCare) << "Update" << device->name() << "brightness value";
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
url.setPath("/a/light");
|
||||
CoapReply *reply = m_coap->get(CoapRequest(url));
|
||||
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcPlantCare) << "CoAP reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
m_updateReplies.insert(reply, device);
|
||||
}
|
||||
|
||||
void DevicePluginPlantCare::updatePump(Device *device)
|
||||
{
|
||||
qCDebug(dcPlantCare) << "Update" << device->name() << "pump value";
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
url.setPath("/a/pump");
|
||||
CoapReply *reply = m_coap->get(CoapRequest(url));
|
||||
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcPlantCare) << "CoAP reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
m_updateReplies.insert(reply, device);
|
||||
}
|
||||
|
||||
void DevicePluginPlantCare::enableNotifications(Device *device)
|
||||
{
|
||||
qCDebug(dcPlantCare) << "Enable" << device->name() << "notifications";
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
|
||||
url.setPath("/s/water");
|
||||
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
|
||||
|
||||
url.setPath("/s/moisture");
|
||||
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
|
||||
|
||||
url.setPath("/s/battery");
|
||||
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
|
||||
|
||||
url.setPath("/a/light");
|
||||
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
|
||||
|
||||
url.setPath("/a/pump");
|
||||
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
|
||||
}
|
||||
|
||||
void DevicePluginPlantCare::setReachable(Device *device, const bool &reachable)
|
||||
{
|
||||
if (device->stateValue(reachableStateTypeId).toBool() != reachable) {
|
||||
if (!reachable) {
|
||||
// Warn just once that the device is not reachable
|
||||
qCWarning(dcPlantCare()) << device->name() << "reachable changed" << reachable;
|
||||
} else {
|
||||
qCDebug(dcPlantCare()) << device->name() << "reachable changed" << reachable;
|
||||
|
||||
// Get current state values after a reconnect
|
||||
updateBattery(device);
|
||||
updateBrightness(device);
|
||||
updateMoisture(device);
|
||||
updateWater(device);
|
||||
updatePump(device);
|
||||
|
||||
// Make sure the notifications are enabled
|
||||
enableNotifications(device);
|
||||
}
|
||||
}
|
||||
|
||||
device->setStateValue(reachableStateTypeId, reachable);
|
||||
}
|
||||
|
||||
bool DevicePluginPlantCare::deviceAlreadyAdded(const QHostAddress &address)
|
||||
{
|
||||
// Check if we already have a device with the given address
|
||||
foreach (Device *device, myDevices()) {
|
||||
if (device->paramValue("host").toString() == address.toString()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Device *DevicePluginPlantCare::findDevice(const QHostAddress &address)
|
||||
{
|
||||
// Return the device pointer with the given address (otherwise 0)
|
||||
foreach (Device *device, myDevices()) {
|
||||
if (device->paramValue("host").toString() == address.toString()) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DevicePluginPlantCare::coapReplyFinished(CoapReply *reply)
|
||||
{
|
||||
if (m_pingReplies.contains(reply)) {
|
||||
Device *device = m_pingReplies.take(reply);
|
||||
|
||||
// Check CoAP reply error
|
||||
if (reply->error() != CoapReply::NoError) {
|
||||
if (device->stateValue(reachableStateTypeId).toBool())
|
||||
qCWarning(dcPlantCare) << "Ping device" << reply->request().url().toString() << "reply finished with error" << reply->errorString();
|
||||
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
setReachable(device, true);
|
||||
|
||||
} else if (m_updateReplies.contains(reply)) {
|
||||
Device *device = m_updateReplies.take(reply);
|
||||
QString urlPath = reply->request().url().path();
|
||||
|
||||
// Check CoAP reply error
|
||||
if (reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcPlantCare) << "Update resource" << urlPath << "reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check CoAP status code
|
||||
if (reply->statusCode() != CoapPdu::Content) {
|
||||
qCWarning(dcPlantCare) << "Update resource" << urlPath << "status code error:" << reply;
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// Update corresponding device state
|
||||
if (urlPath == "/s/moisture") {
|
||||
qCDebug(dcPlantCare()) << "Updated moisture value:" << reply->payload();
|
||||
device->setStateValue(moistureStateTypeId, qRound(reply->payload().toInt() * 100.0 / 1023.0));
|
||||
} else if (urlPath == "/s/water") {
|
||||
qCDebug(dcPlantCare()) << "Updated water value:" << reply->payload();
|
||||
device->setStateValue(waterStateTypeId, QVariant(reply->payload().toInt()).toBool());
|
||||
} else if (urlPath == "/s/battery") {
|
||||
qCDebug(dcPlantCare()) << "Updated battery value:" << reply->payload();
|
||||
device->setStateValue(batteryStateTypeId, reply->payload().toDouble());
|
||||
} else if (urlPath == "/a/pump") {
|
||||
qCDebug(dcPlantCare()) << "Updated pump value:" << reply->payload();
|
||||
device->setStateValue(waterPumpStateTypeId, QVariant(reply->payload().toInt()).toBool());
|
||||
} else if (urlPath == "/a/light") {
|
||||
qCDebug(dcPlantCare()) << "Updated brightness value:" << reply->payload();
|
||||
device->setStateValue(brightnessStateTypeId, qRound(reply->payload().toInt() * 100.0 / 255.0));
|
||||
}
|
||||
|
||||
} else if (m_toggleLightRequests.contains(reply)) {
|
||||
Action action = m_toggleLightRequests.take(reply);
|
||||
Device *device = m_asyncActions.take(action.id());
|
||||
|
||||
// Check CoAP reply error
|
||||
if (reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcPlantCare) << "CoAP reply toggle light finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check CoAP status code
|
||||
if (reply->statusCode() != CoapPdu::Content) {
|
||||
qCWarning(dcPlantCare) << "Toggle light status code error:" << reply;
|
||||
reply->deleteLater();
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
// Tell the user about the action execution result
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorNoError);
|
||||
|
||||
} else if (m_setBrightness.contains(reply)) {
|
||||
Action action = m_setBrightness.take(reply);
|
||||
Device *device = m_asyncActions.take(action.id());
|
||||
|
||||
// check CoAP reply error
|
||||
if (reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcPlantCare) << "CoAP set brightness reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check CoAP status code
|
||||
if (reply->statusCode() != CoapPdu::Content) {
|
||||
qCWarning(dcPlantCare) << "Set brightness status code error:" << reply;
|
||||
reply->deleteLater();
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the state here, so we don't have to wait for the notification
|
||||
device->setStateValue(brightnessStateTypeId, action.param("brightness").value().toInt());
|
||||
// Tell the user about the action execution result
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorNoError);
|
||||
|
||||
} else if (m_setPumpPower.contains(reply)) {
|
||||
Action action = m_setPumpPower.take(reply);
|
||||
Device *device = m_asyncActions.take(action.id());
|
||||
|
||||
// check CoAP reply error
|
||||
if (reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcPlantCare) << "CoAP set pump power reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check CoAP status code
|
||||
if (reply->statusCode() != CoapPdu::Content) {
|
||||
qCWarning(dcPlantCare) << "Set pump power status code error:" << reply;
|
||||
reply->deleteLater();
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the state here, so we don't have to wait for the notification
|
||||
device->setStateValue(waterPumpStateTypeId, action.param("water pump power").value().toBool());
|
||||
// Tell the user about the action execution result
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorNoError);
|
||||
|
||||
} else if (m_enableNotification.contains(reply)) {
|
||||
Device *device = m_enableNotification.take(reply);
|
||||
|
||||
// check CoAP reply error
|
||||
if (reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcPlantCare) << "Enable notifications for" << reply->request().url().toString() << "reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check CoAP status code
|
||||
if (reply->statusCode() != CoapPdu::Content) {
|
||||
qCWarning(dcPlantCare) << "Enable notifications for" << reply->request().url().toString() << "reply status code error" << reply->errorString();
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(dcPlantCare()) << "Enabled successfully notifications for" << device->name() << reply->request().url().path();
|
||||
}
|
||||
|
||||
// Delete the CoAP reply
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
void DevicePluginPlantCare::onNotificationReceived(const CoapObserveResource &resource, const int ¬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));
|
||||
}
|
||||
}
|
||||
87
plugins/deviceplugins/plantcare/devicepluginplantcare.h
Normal file
87
plugins/deviceplugins/plantcare/devicepluginplantcare.h
Normal file
@ -0,0 +1,87 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015 Bernhard Trinnes <bernhard.trinnes@guh.guru> *
|
||||
* *
|
||||
* This file is part of guh. *
|
||||
* *
|
||||
* Guh is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, version 2 of the License. *
|
||||
* *
|
||||
* Guh is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with guh. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef DEVICEPLUGINPLANTCARE_H
|
||||
#define DEVICEPLUGINPLANTCARE_H
|
||||
|
||||
#include "plugin/deviceplugin.h"
|
||||
#include "types/action.h"
|
||||
#include "coap/coap.h"
|
||||
|
||||
#include <QHash>
|
||||
|
||||
class DevicePluginPlantCare : public DevicePlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "devicepluginplantcare.json")
|
||||
Q_INTERFACES(DevicePlugin)
|
||||
|
||||
public:
|
||||
explicit DevicePluginPlantCare();
|
||||
|
||||
DeviceManager::HardwareResources requiredHardware() const override;
|
||||
DeviceManager::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶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<Coap> m_coap;
|
||||
QHash<QNetworkReply *, DeviceClassId> m_asyncNodeScans;
|
||||
QHash<CoapReply *, Device *> m_enableNotification;
|
||||
QHash<CoapReply *, Device *> m_pingReplies;
|
||||
|
||||
// State updates
|
||||
QHash<CoapReply *, Device *> m_updateReplies;
|
||||
|
||||
// Actions
|
||||
QHash<ActionId, Device *> m_asyncActions;
|
||||
|
||||
QHash<CoapReply *, Action> m_toggleLightRequests;
|
||||
QHash<CoapReply *, Action> m_setBrightness;
|
||||
QHash<CoapReply *, Action> m_setPumpPower;
|
||||
|
||||
void pingDevice(Device *device);
|
||||
|
||||
void updateBattery(Device *device);
|
||||
void updateMoisture(Device *device);
|
||||
void updateWater(Device *device);
|
||||
void updateBrightness(Device *device);
|
||||
void updatePump(Device *device);
|
||||
|
||||
void enableNotifications(Device *device);
|
||||
|
||||
void setReachable(Device *device, const bool &reachable);
|
||||
|
||||
bool deviceAlreadyAdded(const QHostAddress &address);
|
||||
Device *findDevice(const QHostAddress &address);
|
||||
|
||||
private slots:
|
||||
void coapReplyFinished(CoapReply *reply);
|
||||
void onNotificationReceived(const CoapObserveResource &resource, const int ¬ificationNumber, const QByteArray &payload);
|
||||
};
|
||||
|
||||
#endif // DEVICEPLUGINPLANTCARE_H
|
||||
97
plugins/deviceplugins/plantcare/devicepluginplantcare.json
Normal file
97
plugins/deviceplugins/plantcare/devicepluginplantcare.json
Normal file
@ -0,0 +1,97 @@
|
||||
{
|
||||
"name": "Plant Care",
|
||||
"idName": "PlantCare",
|
||||
"id": "80ea115f-2a9f-49b3-9575-9e36d137c8d6",
|
||||
"paramTypes": [
|
||||
{
|
||||
"name": "RPL address",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"defaultValue": "fdaa:e9b8:d03a::ff:fe00:1"
|
||||
}
|
||||
],
|
||||
"vendors": [
|
||||
{
|
||||
"name": "guh",
|
||||
"idName": "guh",
|
||||
"id": "2062d64d-3232-433c-88bc-0d33c0ba2ba6",
|
||||
"deviceClasses": [
|
||||
{
|
||||
"deviceClassId": "3245db46-5c6f-42d6-9001-753150763385",
|
||||
"name": "Plant Care",
|
||||
"idName": "plantCare",
|
||||
"createMethods": ["discovery"],
|
||||
"basicTags": [
|
||||
"Device",
|
||||
"Gateway"
|
||||
],
|
||||
"paramTypes": [
|
||||
{
|
||||
"name": "host",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine"
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "2b4b5123-14b7-4aa1-9c83-27cef333395d",
|
||||
"idName": "battery",
|
||||
"name": "battery voltage",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "8a7efda1-b6b7-41c8-ad07-4d892a532d1c",
|
||||
"idName": "moisture",
|
||||
"name": "moisture",
|
||||
"type": "int",
|
||||
"unit": "Percentage",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "26deeba1-487a-4dee-940a-a3bd48344f33",
|
||||
"idName": "water",
|
||||
"name": "water empty",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "21b51fc9-1bfd-4970-a50d-e697a58df1bf",
|
||||
"idName": "brightness",
|
||||
"name": "brightness",
|
||||
"unit": "Percentage",
|
||||
"type": "int",
|
||||
"minValue": 0,
|
||||
"maxValue": 100,
|
||||
"defaultValue": 0,
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "6c65ae32-18ce-400f-afd6-47fb5b805ccd",
|
||||
"idName": "waterPump",
|
||||
"name": "water pump power",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"writable": true
|
||||
},
|
||||
{
|
||||
"id": "8384e07b-8b91-492a-b6c8-e72b325ba0b4",
|
||||
"idName": "reachable",
|
||||
"name": "reachable",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
}
|
||||
],
|
||||
"actionTypes": [
|
||||
{
|
||||
"id": "9afbcc80-1c97-4c18-bc08-5209d6297f2e",
|
||||
"idName": "toggleLed",
|
||||
"name": "toggle led"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
9
plugins/deviceplugins/plantcare/plantcare.pro
Normal file
9
plugins/deviceplugins/plantcare/plantcare.pro
Normal file
@ -0,0 +1,9 @@
|
||||
include(../../plugins.pri)
|
||||
|
||||
TARGET = $$qtLibraryTarget(guh_devicepluginplantcare)
|
||||
|
||||
SOURCES += \
|
||||
devicepluginplantcare.cpp
|
||||
|
||||
HEADERS += \
|
||||
devicepluginplantcare.h
|
||||
604
plugins/deviceplugins/ws2812/devicepluginws2812.cpp
Normal file
604
plugins/deviceplugins/ws2812/devicepluginws2812.cpp
Normal file
@ -0,0 +1,604 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2016 Bernhard Trinnes <bernhard.trinnes@guh.guru> *
|
||||
* *
|
||||
* This file is part of guh. *
|
||||
* *
|
||||
* Guh is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, version 2 of the License. *
|
||||
* *
|
||||
* Guh is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with guh. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
|
||||
#include "devicepluginws2812.h"
|
||||
#include "plugin/device.h"
|
||||
#include "plugininfo.h"
|
||||
|
||||
|
||||
#include <QDebug>
|
||||
#include <QStringList>
|
||||
#include <QColor>
|
||||
|
||||
DevicePluginWs2812::DevicePluginWs2812()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
DeviceManager::HardwareResources DevicePluginWs2812::requiredHardware() const
|
||||
{
|
||||
// We need the NetworkManager for node discovery and the timer for ping requests
|
||||
return DeviceManager::HardwareResourceNetworkManager | DeviceManager::HardwareResourceTimer;
|
||||
}
|
||||
|
||||
DeviceManager::DeviceSetupStatus DevicePluginWs2812::setupDevice(Device *device)
|
||||
{
|
||||
qCDebug(dcWs2812) << "Setup Plant Care" << device->name() << device->params();
|
||||
|
||||
// Check if device already added with this address
|
||||
if (deviceAlreadyAdded(QHostAddress(device->paramValue("host").toString()))) {
|
||||
qCWarning(dcWs2812) << "Device with this address already added.";
|
||||
return DeviceManager::DeviceSetupStatusFailure;
|
||||
}
|
||||
|
||||
// Create the CoAP socket if not already created
|
||||
if (m_coap.isNull()) {
|
||||
m_coap = new Coap(this);
|
||||
connect(m_coap.data(), SIGNAL(replyFinished(CoapReply*)), this, SLOT(coapReplyFinished(CoapReply*)));
|
||||
connect(m_coap.data(), SIGNAL(notificationReceived(CoapObserveResource,int,QByteArray)), this, SLOT(onNotificationReceived(CoapObserveResource,int,QByteArray)));
|
||||
}
|
||||
|
||||
return DeviceManager::DeviceSetupStatusSuccess;
|
||||
}
|
||||
|
||||
void DevicePluginWs2812::deviceRemoved(Device *device)
|
||||
{
|
||||
Q_UNUSED(device)
|
||||
|
||||
// Delete the CoAP socket if there are no devices left
|
||||
if (myDevices().isEmpty()) {
|
||||
m_coap->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
DeviceManager::DeviceError DevicePluginWs2812::discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶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<DeviceDescriptor>());
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
qCDebug(dcWs2812) << "Node discovery finished:" << endl << data;
|
||||
|
||||
QList<DeviceDescriptor> deviceDescriptors;
|
||||
QList<QByteArray> lines = data.split('\n');
|
||||
qCDebug(dcWs2812) << lines;
|
||||
foreach (const QByteArray &line, lines) {
|
||||
if (line.isEmpty())
|
||||
continue;
|
||||
|
||||
QHostAddress address(QString(line.left(line.length() - 4)));
|
||||
if (address.isNull())
|
||||
continue;
|
||||
|
||||
qCDebug(dcWs2812) << "Found node" << address.toString();
|
||||
// Create a deviceDescriptor for each found address
|
||||
DeviceDescriptor descriptor(deviceClassId, "ws2812", address.toString());
|
||||
ParamList params;
|
||||
params.append(Param("host", address.toString()));
|
||||
descriptor.setParams(params);
|
||||
deviceDescriptors.append(descriptor);
|
||||
}
|
||||
// Inform the user which devices were found
|
||||
emit devicesDiscovered(deviceClassId, deviceDescriptors);
|
||||
}
|
||||
|
||||
// Delete the HTTP reply
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
void DevicePluginWs2812::postSetupDevice(Device *device)
|
||||
{
|
||||
// Try to ping the device after a successful setup
|
||||
pingDevice(device);
|
||||
}
|
||||
|
||||
void DevicePluginWs2812::guhTimer()
|
||||
{
|
||||
// Try to ping each device every 10 seconds to make sure it is still reachable
|
||||
foreach (Device *device, myDevices()) {
|
||||
if (device->deviceClassId() == ws2812DeviceClassId) {
|
||||
pingDevice(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DeviceManager::DeviceError DevicePluginWs2812::executeAction(Device *device, const Action &action)
|
||||
{
|
||||
if (device->deviceClassId() != ws2812DeviceClassId)
|
||||
return DeviceManager::DeviceErrorDeviceClassNotFound;
|
||||
|
||||
qCDebug(dcWs2812) << "Execute action" << device->name() << action.params();
|
||||
|
||||
// Check if the device is reachable
|
||||
if (!device->stateValue(reachableStateTypeId).toBool()) {
|
||||
qCWarning(dcWs2812) << "Device not reachable.";
|
||||
return DeviceManager::DeviceErrorHardwareNotAvailable;
|
||||
}
|
||||
|
||||
if(action.actionTypeId() == effectColorActionTypeId) {
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
url.setPath("/a/color");
|
||||
|
||||
QColor newColor = action.param("color").value().value<QColor>().toRgb();
|
||||
QByteArray message = "color=" + newColor.name().remove("#").toUtf8();
|
||||
|
||||
qCDebug(dcWs2812) << "Sending" << url.toString() << message;
|
||||
|
||||
CoapReply *reply = m_coap->post(CoapRequest(url), message);
|
||||
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcWs2812) << "CoAP reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return DeviceManager::DeviceErrorHardwareFailure;
|
||||
}
|
||||
m_setColor.insert(reply, action);
|
||||
m_asyncActions.insert(action.id(), device);
|
||||
return DeviceManager::DeviceErrorAsync;
|
||||
|
||||
|
||||
} else if(action.actionTypeId() == maxPixActionTypeId) {
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
url.setPath("/p/maxpix");
|
||||
|
||||
//QColor color = action.param("color").value().value<QColor>().toHsv();
|
||||
//QColor newColor = QColor::fromHsv(color.hue(), color.saturation(), 100 * light->brightness() / 255.0);
|
||||
//QByteArray message = "color=" + newColor.toRgb().name().remove("#").toUtf8();
|
||||
|
||||
int max = action.param("leds").value().toInt();
|
||||
|
||||
qCDebug(dcWs2812) << "Max Pix" << max;
|
||||
|
||||
QByteArray message = QString("max=%1").arg(QString::number(max)).toUtf8();
|
||||
|
||||
//QByteArray message = "max=" + action.param("maxPix").value().toByteArray();
|
||||
|
||||
qCDebug(dcWs2812) << "Sending" << url.toString() << message;
|
||||
|
||||
CoapReply *reply = m_coap->post(CoapRequest(url), message);
|
||||
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcWs2812) << "CoAP reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return DeviceManager::DeviceErrorHardwareFailure;
|
||||
}
|
||||
m_setPix.insert(reply, action);
|
||||
m_asyncActions.insert(action.id(), device);
|
||||
return DeviceManager::DeviceErrorAsync;
|
||||
|
||||
|
||||
} else if(action.actionTypeId() == effectModeActionTypeId) {
|
||||
|
||||
//int effectmode = createSetColorRequest(action.param("color").value().value<QColor>());
|
||||
|
||||
int effectmode = 0;
|
||||
|
||||
QString effectModeString = action.param("effect").value().toString();
|
||||
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
url.setPath("/a/effect");
|
||||
|
||||
|
||||
/*"Off",
|
||||
"Color On",
|
||||
"Color Wave",
|
||||
"Color Fade",
|
||||
"Color Flash",
|
||||
"Rainbow Wave",
|
||||
"Rainbow Flash",
|
||||
"Knight Rider",
|
||||
"Fire"
|
||||
*/
|
||||
|
||||
qCDebug(dcWs2812) << "Set effect mode to:" << effectModeString;
|
||||
|
||||
if(effectModeString == "Off") {
|
||||
effectmode = 0;
|
||||
} else if (effectModeString == "Color On") {
|
||||
effectmode = 1;
|
||||
} else if (effectModeString == "Color Wave") {
|
||||
effectmode = 2;
|
||||
} else if (effectModeString == "Color Fade") {
|
||||
effectmode = 3;
|
||||
}else if (effectModeString == "Color Flash") {
|
||||
effectmode = 4;
|
||||
}else if (effectModeString == "Rainbow Wave") {
|
||||
effectmode = 5;
|
||||
}else if (effectModeString == "Rainbow Flash") {
|
||||
effectmode = 6;
|
||||
}else if (effectModeString == "Knight Rider") {
|
||||
effectmode = 7;
|
||||
}else if (effectModeString == "Fire") {
|
||||
effectmode = 8;
|
||||
}
|
||||
|
||||
QByteArray payload = QString("mode=%1").arg(QString::number(effectmode)).toUtf8();
|
||||
qCDebug(dcWs2812()) << "Sending" << payload << url.path();
|
||||
|
||||
CoapReply *reply = m_coap->post(CoapRequest(url), payload);
|
||||
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcWs2812) << "CoAP reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return DeviceManager::DeviceErrorHardwareFailure;
|
||||
}
|
||||
|
||||
m_setEffect.insert(reply, action);
|
||||
m_asyncActions.insert(action.id(), device);
|
||||
return DeviceManager::DeviceErrorAsync;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return DeviceManager::DeviceErrorActionTypeNotFound;
|
||||
}
|
||||
|
||||
void DevicePluginWs2812::pingDevice(Device *device)
|
||||
{
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
m_pingReplies.insert(m_coap->ping(CoapRequest(url)), device);
|
||||
}
|
||||
|
||||
void DevicePluginWs2812::updateBattery(Device *device)
|
||||
{
|
||||
qCDebug(dcWs2812) << "Update" << device->name() << "battery value";
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
url.setPath("/s/battery");
|
||||
CoapReply *reply = m_coap->get(CoapRequest(url));
|
||||
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcWs2812) << "CoAP reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
m_updateReplies.insert(reply, device);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void DevicePluginWs2812::updateColor(Device *device)
|
||||
{
|
||||
qCDebug(dcWs2812) << "Update" << device->name() << "color value";
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
url.setPath("/a/color");
|
||||
CoapReply *reply = m_coap->get(CoapRequest(url));
|
||||
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcWs2812) << "CoAP reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
m_updateReplies.insert(reply, device);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void DevicePluginWs2812::updateEffect(Device *device)
|
||||
{
|
||||
qCDebug(dcWs2812) << "Update" << device->name() << "effect mode";
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
url.setPath("/a/effect");
|
||||
CoapReply *reply = m_coap->get(CoapRequest(url));
|
||||
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcWs2812) << "CoAP reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
m_updateReplies.insert(reply, device);
|
||||
}
|
||||
|
||||
|
||||
void DevicePluginWs2812::updateMaxPix(Device *device)
|
||||
{
|
||||
qCDebug(dcWs2812) << "Update" << device->name() << "max pix";
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
url.setPath("/p/maxpix");
|
||||
CoapReply *reply = m_coap->get(CoapRequest(url));
|
||||
if (reply->isFinished() && reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcWs2812) << "CoAP reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
m_updateReplies.insert(reply, device);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void DevicePluginWs2812::enableNotifications(Device *device)
|
||||
{
|
||||
qCDebug(dcWs2812) << "Enable" << device->name() << "notifications";
|
||||
QUrl url;
|
||||
url.setScheme("coap");
|
||||
url.setHost(device->paramValue("host").toString());
|
||||
|
||||
|
||||
url.setPath("/s/battery");
|
||||
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
|
||||
|
||||
url.setPath("/p/maxpix");
|
||||
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
|
||||
|
||||
url.setPath("/a/color");
|
||||
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
|
||||
|
||||
url.setPath("/a/effect");
|
||||
m_enableNotification.insert(m_coap->enableResourceNotifications(CoapRequest(url)), device);
|
||||
|
||||
}
|
||||
|
||||
void DevicePluginWs2812::setReachable(Device *device, const bool &reachable)
|
||||
{
|
||||
if (device->stateValue(reachableStateTypeId).toBool() != reachable) {
|
||||
if (!reachable) {
|
||||
// Warn just once that the device is not reachable
|
||||
qCWarning(dcWs2812()) << device->name() << "reachable changed" << reachable;
|
||||
} else {
|
||||
qCDebug(dcWs2812()) << device->name() << "reachable changed" << reachable;
|
||||
|
||||
// Get current state values after a reconnect
|
||||
updateBattery(device);
|
||||
updateColor(device);
|
||||
updateEffect(device);
|
||||
updateMaxPix(device);
|
||||
|
||||
// Make sure the notifications are enabled
|
||||
enableNotifications(device);
|
||||
}
|
||||
}
|
||||
|
||||
device->setStateValue(reachableStateTypeId, reachable);
|
||||
}
|
||||
|
||||
bool DevicePluginWs2812::deviceAlreadyAdded(const QHostAddress &address)
|
||||
{
|
||||
// Check if we already have a device with the given address
|
||||
foreach (Device *device, myDevices()) {
|
||||
if (device->paramValue("host").toString() == address.toString()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Device *DevicePluginWs2812::findDevice(const QHostAddress &address)
|
||||
{
|
||||
// Return the device pointer with the given address (otherwise 0)
|
||||
foreach (Device *device, myDevices()) {
|
||||
if (device->paramValue("host").toString() == address.toString()) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DevicePluginWs2812::coapReplyFinished(CoapReply *reply)
|
||||
{
|
||||
if (m_pingReplies.contains(reply)) {
|
||||
Device *device = m_pingReplies.take(reply);
|
||||
|
||||
// Check CoAP reply error
|
||||
if (reply->error() != CoapReply::NoError) {
|
||||
if (device->stateValue(reachableStateTypeId).toBool())
|
||||
qCWarning(dcWs2812) << "Ping device" << reply->request().url().toString() << "reply finished with error" << reply->errorString();
|
||||
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
setReachable(device, true);
|
||||
|
||||
} else if (m_updateReplies.contains(reply)) {
|
||||
Device *device = m_updateReplies.take(reply);
|
||||
QString urlPath = reply->request().url().path();
|
||||
|
||||
// Check CoAP reply error
|
||||
if (reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcWs2812) << "Update resource" << urlPath << "reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check CoAP status code
|
||||
if (reply->statusCode() != CoapPdu::Content) {
|
||||
qCWarning(dcWs2812) << "Update resource" << urlPath << "status code error:" << reply;
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// Update corresponding device state
|
||||
if (urlPath == "/s/battery") {
|
||||
qCDebug(dcWs2812()) << "Updated battery value:" << reply->payload();
|
||||
device->setStateValue(batteryStateTypeId, reply->payload().toDouble());
|
||||
} else if (urlPath == "/a/color") {
|
||||
qCDebug(dcWs2812()) << "Updated color value:" << reply->payload();
|
||||
device->setStateValue(effectColorStateTypeId, reply->payload());
|
||||
} else if (urlPath == "/p/maxpix") {
|
||||
qCDebug(dcWs2812()) << "Updated max pix value:" << reply->payload();
|
||||
device->setStateValue(maxPixStateTypeId, reply->payload());
|
||||
}
|
||||
|
||||
} else if (m_setEffect.contains(reply)) {
|
||||
Action action = m_setEffect.take(reply);
|
||||
Device *device = m_asyncActions.take(action.id());
|
||||
|
||||
// Check CoAP reply error
|
||||
if (reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcWs2812) << "CoAP reply toggle light finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check CoAP status code
|
||||
if (reply->statusCode() != CoapPdu::Content) {
|
||||
qCWarning(dcWs2812) << "Set effect code error:" << reply;
|
||||
reply->deleteLater();
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
// Tell the user about the action execution result
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorNoError);
|
||||
|
||||
} else if (m_setColor.contains(reply)) {
|
||||
Action action = m_setColor.take(reply);
|
||||
Device *device = m_asyncActions.take(action.id());
|
||||
|
||||
// check CoAP reply error
|
||||
if (reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcWs2812) << "CoAP set color reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check CoAP status code
|
||||
if (reply->statusCode() != CoapPdu::Content) {
|
||||
qCWarning(dcWs2812) << "Set color status code error:" << reply;
|
||||
reply->deleteLater();
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the state here, so we don't have to wait for the notification
|
||||
device->setStateValue(effectColorStateTypeId, action.param("color").value().toInt());
|
||||
// Tell the user about the action execution result
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorNoError);
|
||||
|
||||
} else if (m_setPix.contains(reply)) {
|
||||
Action action = m_setPix.take(reply);
|
||||
Device *device = m_asyncActions.take(action.id());
|
||||
|
||||
// check CoAP reply error
|
||||
if (reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcWs2812) << "CoAP set maxpix reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check CoAP status code
|
||||
if (reply->statusCode() != CoapPdu::Content) {
|
||||
qCWarning(dcWs2812) << "Set max pix code error:" << reply;
|
||||
reply->deleteLater();
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the state here, so we don't have to wait for the notification
|
||||
device->setStateValue(maxPixStateTypeId, action.param("maxPix").value().toInt());
|
||||
// Tell the user about the action execution result
|
||||
emit actionExecutionFinished(action.id(), DeviceManager::DeviceErrorNoError);
|
||||
|
||||
} else if (m_enableNotification.contains(reply)) {
|
||||
Device *device = m_enableNotification.take(reply);
|
||||
|
||||
// check CoAP reply error
|
||||
if (reply->error() != CoapReply::NoError) {
|
||||
qCWarning(dcWs2812) << "Enable notifications for" << reply->request().url().toString() << "reply finished with error" << reply->errorString();
|
||||
setReachable(device, false);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check CoAP status code
|
||||
if (reply->statusCode() != CoapPdu::Content) {
|
||||
qCWarning(dcWs2812) << "Enable notifications for" << reply->request().url().toString() << "reply status code error" << reply->errorString();
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(dcWs2812()) << "Enabled successfully notifications for" << device->name() << reply->request().url().path();
|
||||
}
|
||||
|
||||
// Delete the CoAP reply
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
|
||||
void DevicePluginWs2812::onNotificationReceived(const CoapObserveResource &resource, const int ¬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());
|
||||
}
|
||||
}
|
||||
86
plugins/deviceplugins/ws2812/devicepluginws2812.h
Normal file
86
plugins/deviceplugins/ws2812/devicepluginws2812.h
Normal file
@ -0,0 +1,86 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2015 Simon Stuerz <simon.stuerz@guh.guru> *
|
||||
* Copyright (C) 2015 Bernhard Trinnes <bernhard.trinnes@guh.guru> *
|
||||
* *
|
||||
* This file is part of guh. *
|
||||
* *
|
||||
* Guh is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, version 2 of the License. *
|
||||
* *
|
||||
* Guh is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with guh. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef DEVICEPLUGINWS2812_H
|
||||
#define DEVICEPLUGINWS2812_H
|
||||
|
||||
#include "plugin/deviceplugin.h"
|
||||
#include "types/action.h"
|
||||
#include "coap/coap.h"
|
||||
|
||||
#include <QHash>
|
||||
|
||||
class DevicePluginWs2812 : public DevicePlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "devicepluginws2812.json")
|
||||
Q_INTERFACES(DevicePlugin)
|
||||
|
||||
public:
|
||||
explicit DevicePluginWs2812();
|
||||
|
||||
DeviceManager::HardwareResources requiredHardware() const override;
|
||||
DeviceManager::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶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<Coap> m_coap;
|
||||
QHash<QNetworkReply *, DeviceClassId> m_asyncNodeScans;
|
||||
QHash<CoapReply *, Device *> m_enableNotification;
|
||||
QHash<CoapReply *, Device *> m_pingReplies;
|
||||
|
||||
// State updates
|
||||
QHash<CoapReply *, Device *> m_updateReplies;
|
||||
|
||||
// Actions
|
||||
QHash<ActionId, Device *> m_asyncActions;
|
||||
|
||||
QHash<CoapReply *, Action> m_setColor;
|
||||
QHash<CoapReply *, Action> m_setEffect;
|
||||
QHash<CoapReply *, Action> m_setPix;
|
||||
|
||||
void pingDevice(Device *device);
|
||||
|
||||
void updateBattery(Device *device);
|
||||
void updateColor(Device *device);
|
||||
void updateEffect(Device *device);
|
||||
void updateMaxPix(Device *device);
|
||||
|
||||
void enableNotifications(Device *device);
|
||||
|
||||
void setReachable(Device *device, const bool &reachable);
|
||||
|
||||
bool deviceAlreadyAdded(const QHostAddress &address);
|
||||
Device *findDevice(const QHostAddress &address);
|
||||
|
||||
private slots:
|
||||
void coapReplyFinished(CoapReply *reply);
|
||||
void onNotificationReceived(const CoapObserveResource &resource, const int ¬ificationNumber, const QByteArray &payload);
|
||||
};
|
||||
|
||||
#endif // DEVICEPLUGINWS2812_H
|
||||
101
plugins/deviceplugins/ws2812/devicepluginws2812.json
Normal file
101
plugins/deviceplugins/ws2812/devicepluginws2812.json
Normal file
@ -0,0 +1,101 @@
|
||||
{
|
||||
"name": "WS2812",
|
||||
"idName": "Ws2812",
|
||||
"id": "60b68c75-3261-4bf1-8912-798d2d3bbd3b",
|
||||
"paramTypes": [
|
||||
{
|
||||
"name": "RPL address",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"defaultValue": "fdaa:e9b8:d03a::ff:fe00:1"
|
||||
}
|
||||
],
|
||||
"vendors": [
|
||||
{
|
||||
"name": "guh",
|
||||
"idName": "guh",
|
||||
"id": "419435fa-58bf-4ba0-b6f5-156ce081bb68",
|
||||
"deviceClasses": [
|
||||
{
|
||||
"deviceClassId": "3242db46-5c6f-42d6-9001-753150763385",
|
||||
"name": "WS2812",
|
||||
"idName": "ws2812",
|
||||
"createMethods": ["discovery"],
|
||||
"basicTags": [
|
||||
"Device",
|
||||
"Gateway"
|
||||
],
|
||||
"paramTypes": [
|
||||
{
|
||||
"name": "host",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine"
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "c9f6042f-1193-4075-94b1-37b29641ec24",
|
||||
"idName": "battery",
|
||||
"name": "battery voltage",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "08091cde-983d-42f7-bdd7-d89c312ccbed",
|
||||
"idName": "reachable",
|
||||
"name": "reachable",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "8a73c60a-1da9-4228-8dba-2ffccc6531a6",
|
||||
"idName": "effectColor",
|
||||
"name": "color",
|
||||
"type": "QColor",
|
||||
"defaultValue": "#000000",
|
||||
"writable": true
|
||||
|
||||
},
|
||||
{
|
||||
"id": "ac372b72-c2ae-4f0f-9fca-bd7cf654603a",
|
||||
"idName": "maxPix",
|
||||
"name": "leds",
|
||||
"type": "int",
|
||||
"unit": "None",
|
||||
"defaultValue": 0,
|
||||
"minValue": 0,
|
||||
"maxValue": 240,
|
||||
"writable": true
|
||||
}
|
||||
],
|
||||
"actionTypes": [
|
||||
{
|
||||
"id": "f4fe5d31-1edb-4944-9ddb-c89e7da8bee7",
|
||||
"idName": "effectMode",
|
||||
"name": "set effect mode",
|
||||
"paramTypes": [
|
||||
{
|
||||
"name": "effect",
|
||||
"type": "QString",
|
||||
"allowedValues": [
|
||||
"Off",
|
||||
"Color On",
|
||||
"Color Wave",
|
||||
"Color Fade",
|
||||
"Color Flash",
|
||||
"Rainbow Wave",
|
||||
"Rainbow Flash",
|
||||
"Knight Rider",
|
||||
"Fire"
|
||||
],
|
||||
"defaultValue": "Off"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
9
plugins/deviceplugins/ws2812/ws2812.pro
Normal file
9
plugins/deviceplugins/ws2812/ws2812.pro
Normal file
@ -0,0 +1,9 @@
|
||||
include(../../plugins.pri)
|
||||
|
||||
TARGET = $$qtLibraryTarget(guh_devicepluginws2812)
|
||||
|
||||
SOURCES += \
|
||||
devicepluginws2812.cpp
|
||||
|
||||
HEADERS += \
|
||||
devicepluginws2812.h
|
||||
@ -117,6 +117,7 @@ int main(int argc, char *argv[])
|
||||
s_loggingFilters.insert("JsonRpc", true);
|
||||
s_loggingFilters.insert("Rest", true);
|
||||
s_loggingFilters.insert("OAuth2", false);
|
||||
s_loggingFilters.insert("Coap", false);
|
||||
|
||||
QHash<QString, bool> loggingFiltersPlugins;
|
||||
foreach (const QJsonObject &pluginMetadata, DeviceManager::pluginsMetadata()) {
|
||||
|
||||
@ -15,7 +15,7 @@ SUBDIRS = versioning \
|
||||
restvendors \
|
||||
restrules \
|
||||
websocketserver \
|
||||
coap \
|
||||
logging \
|
||||
restlogging \
|
||||
#coap \
|
||||
|
||||
|
||||
Reference in New Issue
Block a user