From dfd8186c19f540f6773c139f481b8a04453b53ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 14 Jan 2016 14:46:41 +0100 Subject: [PATCH] add coap to awattar plugin --- plugins/deviceplugins/awattar/awattar.pro | 6 +- .../awattar/devicepluginawattar.cpp | 97 +++++++++++- .../awattar/devicepluginawattar.h | 12 +- .../awattar/devicepluginawattar.json | 28 ++++ plugins/deviceplugins/awattar/heatpump.cpp | 147 ++++++++++++++++++ plugins/deviceplugins/awattar/heatpump.h | 66 ++++++++ 6 files changed, 352 insertions(+), 4 deletions(-) create mode 100644 plugins/deviceplugins/awattar/heatpump.cpp create mode 100644 plugins/deviceplugins/awattar/heatpump.h diff --git a/plugins/deviceplugins/awattar/awattar.pro b/plugins/deviceplugins/awattar/awattar.pro index 11914d6b..13b50390 100644 --- a/plugins/deviceplugins/awattar/awattar.pro +++ b/plugins/deviceplugins/awattar/awattar.pro @@ -3,9 +3,11 @@ include(../../plugins.pri) TARGET = $$qtLibraryTarget(guh_devicepluginawattar) SOURCES += \ - devicepluginawattar.cpp + devicepluginawattar.cpp \ + heatpump.cpp HEADERS += \ - devicepluginawattar.h + devicepluginawattar.h \ + heatpump.h diff --git a/plugins/deviceplugins/awattar/devicepluginawattar.cpp b/plugins/deviceplugins/awattar/devicepluginawattar.cpp index e72803d9..1039d769 100644 --- a/plugins/deviceplugins/awattar/devicepluginawattar.cpp +++ b/plugins/deviceplugins/awattar/devicepluginawattar.cpp @@ -93,6 +93,23 @@ DeviceManager::DeviceSetupStatus DevicePluginAwattar::setupDevice(Device *device return DeviceManager::DeviceSetupStatusAsync; } +void DevicePluginAwattar::startMonitoringAutoDevices() +{ + QHostAddress rplAddress = QHostAddress(configuration().paramValue("RPL address").toString()); + + if (rplAddress.isNull()) { + qCWarning(dcAwattar) << "Invalid RPL address" << configuration().paramValue("RPL address").toString(); + return; + } + + qCDebug(dcAwattar) << "Search heat pump" << rplAddress.toString(); + + QNetworkRequest request(QUrl(QString("http://[%1]").arg(rplAddress.toString()))); + QNetworkReply *reply = networkManagerGet(request); + + m_searchPumpReplies.append(reply); +} + void DevicePluginAwattar::deviceRemoved(Device *device) { Q_UNUSED(device) @@ -173,7 +190,20 @@ void DevicePluginAwattar::networkManagerReplyReady(QNetworkReply *reply) } processUserData(device, jsonDoc.toVariant().toMap()); + } else if (m_searchPumpReplies.contains(reply)) { + + m_searchPumpReplies.removeAll(reply); + + // check HTTP status code + if (status != 200) { + qCWarning(dcAwattar) << "Search pump reply HTTP error:" << status << reply->errorString(); + reply->deleteLater(); + return; + } + + processPumpSearchData(reply->readAll()); } + reply->deleteLater(); } @@ -185,6 +215,21 @@ void DevicePluginAwattar::guhTimer() } } +DeviceManager::DeviceError DevicePluginAwattar::executeAction(Device *device, const Action &action) +{ + Q_UNUSED(device) + + if (action.actionTypeId() == ledPowerActionTypeId) { + foreach (HeatPump *pump, m_heatPumps) { + if (!pump->reachable()) + return DeviceManager::DeviceErrorHardwareNotAvailable; + + pump->setLed(action.param("led power").value().toBool()); + } + } + return DeviceManager::DeviceErrorNoError; +} + void DevicePluginAwattar::processPriceData(Device *device, const QVariantMap &data, const bool &fromSetup) { if (!data.contains("data")) { @@ -304,7 +349,38 @@ void DevicePluginAwattar::processUserData(Device *device, const QVariantMap &dat break; } - // todo: send sg mode to 6LoWPAN node + foreach (HeatPump *pump, m_heatPumps) { + pump->setSgMode(sgMode); + } + } + } +} + +void DevicePluginAwattar::processPumpSearchData(const QByteArray &data) +{ + //qCDebug(dcAwattar) << "Search result:" << endl << data; + + QList lines = data.split('\n'); + foreach (const QByteArray &line, lines) { + if (line.isEmpty()) + continue; + + // remove the '/128' from the address + QHostAddress pumpAddress(QString(data.left(line.length() - 4))); + if (!pumpAddress.isNull()) { + qCDebug(dcAwattar) << "Found heat pump at" << pumpAddress.toString(); + + // check if we already created this heat pump + if (heatPumpExists(pumpAddress)) + continue; + + HeatPump *pump = new HeatPump(pumpAddress, this); + connect(pump, SIGNAL(reachableChanged()), this, SLOT(onHeatPumpReachableChanged())); + + m_heatPumps.append(pump); + + } else { + qCWarning(dcAwattar) << "Could not read pump address" << line; } } } @@ -335,3 +411,22 @@ void DevicePluginAwattar::updateDevice(Device *device) QNetworkReply *priceReply = requestPriceData(device->paramValue("token").toString()); m_updatePrice.insert(priceReply, device); } + +bool DevicePluginAwattar::heatPumpExists(const QHostAddress &pumpAddress) +{ + foreach (HeatPump *pump, m_heatPumps) { + if (pump->address() == pumpAddress) { + return true; + } + } + return false; +} + +void DevicePluginAwattar::onHeatPumpReachableChanged() +{ + HeatPump *pump = static_cast(sender()); + + foreach (Device *device, myDevices()) { + device->setStateValue(reachableStateTypeId, pump->reachable()); + } +} diff --git a/plugins/deviceplugins/awattar/devicepluginawattar.h b/plugins/deviceplugins/awattar/devicepluginawattar.h index a16f440b..384c2ad2 100644 --- a/plugins/deviceplugins/awattar/devicepluginawattar.h +++ b/plugins/deviceplugins/awattar/devicepluginawattar.h @@ -22,6 +22,7 @@ #define DEVICEPLUGINAWATTAR_H #include "plugin/deviceplugin.h" +#include "heatpump.h" #include #include @@ -39,26 +40,35 @@ public: DeviceManager::HardwareResources requiredHardware() const override; DeviceManager::DeviceSetupStatus setupDevice(Device *device) override; + void startMonitoringAutoDevices() override; + void deviceRemoved(Device *device) override; void networkManagerReplyReady(QNetworkReply *reply) override; void guhTimer() override; + DeviceManager::DeviceError executeAction(Device *device, const Action &action) override; + private: QHash m_asyncSetup; QHash m_updatePrice; QHash m_updateUserData; + QList m_searchPumpReplies; + + QList m_heatPumps; void processPriceData(Device *device, const QVariantMap &data, const bool &fromSetup = false); void processUserData(Device *device, const QVariantMap &data); + void processPumpSearchData(const QByteArray &data); QNetworkReply *requestPriceData(const QString& token); QNetworkReply *requestUserData(const QString& token, const QString &userId); void updateDevice(Device *device); + bool heatPumpExists(const QHostAddress &pumpAddress); private slots: - void onTimeout(); + void onHeatPumpReachableChanged(); }; diff --git a/plugins/deviceplugins/awattar/devicepluginawattar.json b/plugins/deviceplugins/awattar/devicepluginawattar.json index 27860d0b..caeb0563 100644 --- a/plugins/deviceplugins/awattar/devicepluginawattar.json +++ b/plugins/deviceplugins/awattar/devicepluginawattar.json @@ -2,6 +2,14 @@ "name": "aWATTar", "idName": "Awattar", "id": "9c261c33-d44e-461e-8ec1-68803cb73f12", + "paramTypes": [ + { + "name": "RPL address", + "type": "QString", + "inputType": "TextLine", + "defaultValue": "fdaa:e9b8:d03a::ff:fe00:1" + } + ], "vendors": [ { "name": "aWATTar", @@ -85,6 +93,13 @@ "unit": "EuroPerMegaWattHour", "defaultValue": 0 }, + { + "id": "1c9d139a-6618-4a39-bc83-37f80942017d", + "idName": "reachable", + "name": "pump reachable", + "type": "bool", + "defaultValue": false + }, { "id": "b83d3533-aeae-4a9b-95d8-28466bf6c0cf", "idName": "sgMode", @@ -98,6 +113,19 @@ ], "defaultValue": "1 - Off" } + ], + "actionTypes": [ + { + "id": "5be2f57f-a22d-4766-856a-a31481bcf6d6", + "idName": "ledPower", + "name": "led power", + "paramTypes": [ + { + "name": "power", + "type": "bool" + } + ] + } ] } ] diff --git a/plugins/deviceplugins/awattar/heatpump.cpp b/plugins/deviceplugins/awattar/heatpump.cpp new file mode 100644 index 00000000..056171a9 --- /dev/null +++ b/plugins/deviceplugins/awattar/heatpump.cpp @@ -0,0 +1,147 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Simon Stuerz * + * * + * This file is part of guh. * + * * + * Guh is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * Guh is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "heatpump.h" +#include "coap/corelinkparser.h" + +#include "extern-plugininfo.h" + +HeatPump::HeatPump(QHostAddress address, QObject *parent) : + QObject(parent), + m_address(address), + m_reachable(false), + m_sgMode(1) +{ + m_coap = new Coap(this); + + connect(m_coap, SIGNAL(replyFinished(CoapReply*)), this, SLOT(onReplyFinished(CoapReply*))); + + QUrl url; + url.setScheme("coap"); + url.setHost(m_address.toString()); + url.setPath("/.well-known/core"); + + qCDebug(dcAwattar) << "Discover pump resources on" << url.toString(); + m_discoverReplies.append(m_coap->get(CoapRequest(url))); +} + +QHostAddress HeatPump::address() const +{ + return m_address; +} + +bool HeatPump::reachable() const +{ + return m_reachable; +} + +void HeatPump::setSgMode(const int &sgMode) +{ + QUrl url; + url.setScheme("coap"); + url.setHost(m_address.toString()); + url.setPath("/a/sg_mode"); + + m_sgModeReplies.append(m_coap->post(CoapRequest(url), QByteArray::number(sgMode))); +} + +void HeatPump::setLed(const bool &power) +{ + QUrl url; + url.setScheme("coap"); + url.setHost(m_address.toString()); + url.setPath("/a/led"); + + if (power) { + m_ledReplies.append(m_coap->post(CoapRequest(url), "mode=on")); + } else { + m_ledReplies.append(m_coap->post(CoapRequest(url), "mode=off")); + } +} + +void HeatPump::setReachable(const bool &reachable) +{ + m_reachable = reachable; + emit reachableChanged(); +} + +void HeatPump::onReplyFinished(CoapReply *reply) +{ + if (m_discoverReplies.contains(reply)) { + m_discoverReplies.removeAll(reply); + + if (reply->error() != CoapReply::NoError) { + qCWarning(dcAwattar()) << "CoAP resource discovery reply error" << reply->errorString(); + setReachable(false); + reply->deleteLater(); + return; + } + + qCDebug(dcAwattar) << "Discovered successfully the resources"; + CoreLinkParser parser(reply->payload()); + foreach (const CoreLink &link, parser.links()) { + qCDebug(dcAwattar) << link << endl; + } + + } else if (m_sgModeReplies.contains(reply)) { + m_sgModeReplies.removeAll(reply); + + if (reply->error() != CoapReply::NoError) { + if (reachable()) + qCWarning(dcAwattar()) << "CoAP sg-mode reply error" << reply->errorString(); + + setReachable(false); + reply->deleteLater(); + return; + } + + if (!reachable()) + qCDebug(dcAwattar) << "Set sg-mode successfully."; + + } else if (m_ledReplies.contains(reply)) { + m_ledReplies.removeAll(reply); + + if (reply->error() != CoapReply::NoError) { + if (reachable()) + qCWarning(dcAwattar()) << "CoAP set led power reply error" << reply->errorString(); + + setReachable(false); + reply->deleteLater(); + return; + } + + qCDebug(dcAwattar) << "Set led power successfully."; + + } else { + // unhandled reply + if (reply->error() != CoapReply::NoError) { + qCWarning(dcAwattar()) << "CoAP reply error" << reply->errorString(); + setReachable(false); + reply->deleteLater(); + return; + } + + qCDebug(dcAwattar) << reply; + } + + // the reply had no error until now, so make sure the resource is reachable + setReachable(true); + reply->deleteLater(); +} diff --git a/plugins/deviceplugins/awattar/heatpump.h b/plugins/deviceplugins/awattar/heatpump.h new file mode 100644 index 00000000..6f08c8cf --- /dev/null +++ b/plugins/deviceplugins/awattar/heatpump.h @@ -0,0 +1,66 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Simon Stuerz * + * * + * This file is part of guh. * + * * + * Guh is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * Guh is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef HEATPUMP_H +#define HEATPUMP_H + +#include +#include + + +#include "coap/coap.h" +#include "coap/coapreply.h" +#include "coap/coaprequest.h" + +class HeatPump : public QObject +{ + Q_OBJECT +public: + explicit HeatPump(QHostAddress address, QObject *parent = 0); + + QHostAddress address() const; + bool reachable() const; + + void setSgMode(const int &sgMode); + void setLed(const bool &power); + +private: + QHostAddress m_address; + bool m_reachable; + int m_sgMode; + + Coap *m_coap; + + QList m_discoverReplies; + QList m_sgModeReplies; + QList m_ledReplies; + + void setReachable(const bool &reachable); + +private slots: + void onReplyFinished(CoapReply *reply); + +signals: + void reachableChanged(); + + +}; + +#endif // HEATPUMP_H