From 2dc3d62623f47a3b739ec77cd628055d30ea9aaa Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Fri, 24 Jul 2020 15:07:54 +0200 Subject: [PATCH 01/34] added sma file structure --- debian/nymea-plugin-sma.install.in | 1 + nymea-plugins.pro | 2 ++ sma/integrationpluginsma.cpp | 0 sma/integrationpluginsma.h | 0 sma/integrationpluginsma.json | 0 sma/sma.cpp | 0 sma/sma.h | 0 sma/sma.pro | 0 8 files changed, 3 insertions(+) create mode 100644 debian/nymea-plugin-sma.install.in create mode 100644 sma/integrationpluginsma.cpp create mode 100644 sma/integrationpluginsma.h create mode 100644 sma/integrationpluginsma.json create mode 100644 sma/sma.cpp create mode 100644 sma/sma.h create mode 100644 sma/sma.pro diff --git a/debian/nymea-plugin-sma.install.in b/debian/nymea-plugin-sma.install.in new file mode 100644 index 00000000..106718a1 --- /dev/null +++ b/debian/nymea-plugin-sma.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginsma.so diff --git a/nymea-plugins.pro b/nymea-plugins.pro index eb04e664..8d056c67 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -53,6 +53,8 @@ PLUGIN_DIRS = \ senic \ serialportcommander \ simulation \ + sma \ + snapd \ somfytahoma \ sonos \ sunposition \ diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp new file mode 100644 index 00000000..e69de29b diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h new file mode 100644 index 00000000..e69de29b diff --git a/sma/integrationpluginsma.json b/sma/integrationpluginsma.json new file mode 100644 index 00000000..e69de29b diff --git a/sma/sma.cpp b/sma/sma.cpp new file mode 100644 index 00000000..e69de29b diff --git a/sma/sma.h b/sma/sma.h new file mode 100644 index 00000000..e69de29b diff --git a/sma/sma.pro b/sma/sma.pro new file mode 100644 index 00000000..e69de29b From f8df49845b2e243022107c83494de8d53053b564 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 28 Jul 2020 16:03:07 +0200 Subject: [PATCH 02/34] added sunny webbox --- sma/integrationpluginsma.cpp | 114 ++++++++++++++++++++++ sma/integrationpluginsma.h | 69 +++++++++++++ sma/integrationpluginsma.json | 164 +++++++++++++++++++++++++++++++ sma/sma.cpp | 0 sma/sma.h | 0 sma/sma.pro | 12 +++ sma/sunnywebbox.cpp | 177 ++++++++++++++++++++++++++++++++++ sma/sunnywebbox.h | 90 +++++++++++++++++ 8 files changed, 626 insertions(+) delete mode 100644 sma/sma.cpp delete mode 100644 sma/sma.h create mode 100644 sma/sunnywebbox.cpp create mode 100644 sma/sunnywebbox.h diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index e69de29b..efb46a06 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -0,0 +1,114 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "integrationpluginsma.h" +#include "plugininfo.h" + +IntegrationPluginSma::IntegrationPluginSma() +{ + +} + +void IntegrationPluginSma::setupThing(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + + if (!m_udpSocket) { + m_udpSocket = new QUdpSocket(this); + } + + if (thing->thingClassId() == sunnyWebBoxThingClassId) { + //check if a Sunny WebBox is already added with this IPv4Address + foreach(SunnyWebBox *sunnyWebBox, m_sunnyWebBoxes.values()) { + if(sunnyWebBox->hostAddress().toString() == thing->paramValue(sunnyWebBoxThingHostParamTypeId).toString()){ + //this logger at this IPv4 address is already added + qCWarning(dcSma()) << "thing at " << thing->paramValue(sunnyWebBoxThingHostParamTypeId).toString() << " already added!"; + info->finish(Thing::ThingErrorThingInUse); + return; + } + } + + } else if (thing->thingClassId() == inverterThingClassId) { + Thing *parentThing = myThings().findById(thing->parentId()); + if (!parentThing) { + qCWarning(dcSma()) << "Could not find parentThing for thing " << thing->name(); + return info->finish(Thing::ThingErrorHardwareNotAvailable, "Please try again"); + } + if (!parentThing->setupComplete()) { + //wait for the parent to finish the setup process + connect(parentThing, &Thing::setupStatusChanged, info, [this, info, parentThing] { + + if (parentThing->setupComplete()) + setupChild(info, parentThing); + }); + return; + } + setupChild(info, parentThing); + } else { + Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); + } +} + +void IntegrationPluginSma::postSetupThing(Thing *thing) +{ + if (thing->thingClassId() == sunnyWebBoxThingClassId) { + SunnyWebBox *sunnyWebBox = m_sunnyWebBoxes.value(thing); + if (!sunnyWebBox) + return; + sunnyWebBox->getDevices(); + } else if (thing->thingClassId() == inverterThingClassId) { + } +} + +void IntegrationPluginSma::thingRemoved(Thing *thing) +{ + if (thing->thingClassId() == sunnyWebBoxThingClassId) { + m_sunnyWebBoxes.take(thing)->deleteLater(); + } + + if (myThings().filterByThingClassId(sunnyWebBoxThingClassId).isEmpty()) { + m_udpSocket->deleteLater(); + m_udpSocket = nullptr; + } +} + +SunnyWebBox * IntegrationPluginSma::createSunnyWebBoxConnection(Thing *thing) +{ + SunnyWebBox *sunnyWebBox = new SunnyWebBox(m_udpSocket, this); + m_sunnyWebBoxes.insert(thing, sunnyWebBox); + //connect(); + return sunnyWebBox; +} + +void IntegrationPluginSma::setupChild(ThingSetupInfo *info, Thing *parentThing) +{ + Q_UNUSED(info) + Q_UNUSED(parentThing) +} diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index e69de29b..821cec70 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -0,0 +1,69 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef INTEGRATIONPLUGINSMA_H +#define INTEGRATIONPLUGINSMA_H + +#include "integrations/integrationplugin.h" +#include "plugintimer.h" +#include "sunnywebbox.h" + +#include +#include +#include + + +class IntegrationPluginSma: public IntegrationPlugin { + Q_OBJECT + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginsma.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginSma(); + + void setupThing(ThingSetupInfo *info) override; + void postSetupThing(Thing *thing) override; + void thingRemoved(Thing *thing) override; + +private slots: + void onRefreshTimer(); + +private: + PluginTimer *m_refreshTimer = nullptr; + QHash m_sunnyWebBoxes; + QHash m_asyncSetup; + QUdpSocket *m_udpSocket = nullptr; + + SunnyWebBox * createSunnyWebBoxConnection(Thing *thing); + void setupChild(ThingSetupInfo *info, Thing *parentThing); + void getData(Thing *thing); +}; + +#endif // INTEGRATIONPLUGINSMA_H diff --git a/sma/integrationpluginsma.json b/sma/integrationpluginsma.json index e69de29b..c823b3f0 100644 --- a/sma/integrationpluginsma.json +++ b/sma/integrationpluginsma.json @@ -0,0 +1,164 @@ +{ + "id": "b8442bbf-9d3f-4aa2-9443-b3a31ae09bac", + "name": "sma", + "displayName": "SMA", + "vendors": [ + { + "id": "16d5a4a3-36d5-46c0-b7dd-df166ddf5981", + "name": "Sma", + "displayName": "SMA", + "thingClasses": [ + { + "id": "49304127-ce9b-45dd-8511-05030a4ac003", + "name": "sunnyWebBox", + "displayName": "Sunny WebBox", + "createMethods": ["user", "discovery"], + "interfaces": ["gateway"], + "paramTypes": [ + { + "id": "864d4162-e3ce-48b8-b8ac-c1b971b52d42", + "name": "host", + "displayName": "Host address", + "type": "QString", + "inputType": "IPv4Address", + "defaultValue": "192.168.0.168" + }, + { + "id": "a6df3d28-1b02-42dd-8301-81754c47e55f", + "name": "id", + "displayName": "Device ID", + "type": "QString", + "defaultValue": "-", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "c05e6a1a-252c-4f2b-8b31-09cf113d01c1", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "ff4ff872-2f0f-4ca4-9fe2-220eeaf16cc2", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "16f34c5c-8dbb-4dcc-9faa-4b782d57226c", + "name": "dayEnergy", + "displayName": "Day energy", + "displayNameEvent": "Day energy changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "0bb4e227-7e38-49ca-9b32-ce4621c9305b", + "name": "totalEnergy", + "displayName": "Total energy", + "displayNameEvent": "Total energy changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "1974550b-6059-4b0e-83f4-70177e20dac3", + "name": "mode", + "displayName": "Mode", + "displayNameEvent": "Mode changed", + "type": "QString", + "defaultValue": "MPP" + }, + { + "id": "4e64f9ca-7e5a-4897-8035-6f2ae88fde89", + "name": "error", + "displayName": "Error", + "displayNameEvent": "Error changed", + "type": "QString", + "defaultValue": "None" + } + ], + "actionTypes": [ + { + "id": "15fc3dac-1868-4490-ba41-7c2f545926ad", + "name": "searchDevices", + "displayName": "Search new devices" + } + ] + }, + { + "id": "9cb72321-042e-4912-b23e-18516b6bbe96", + "name": "inverter", + "displayName": "Solar Inverter", + "createMethods": ["auto"], + "interfaces" : ["extendedsmartmeterproducer", "connectable"], + "paramTypes": [ + { + "id": "f43e8159-7337-4bd0-ba74-b6630e554e43", + "name": "id", + "displayName": "Device ID", + "type": "QString", + "inputType": "TextLine", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "eda29c50-73ac-40e0-9c92-26fee352e688", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "a804eabf-d5b8-4c83-84e4-8ec994875950", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current Power changed", + "type": "double", + "unit": "Watt", + "defaultValue": "0" + }, + { + "id": "cd3a0abc-37cc-4f9c-8ff2-c7ccc2513880", + "name": "eday", + "displayName": "Energy of current day", + "displayNameEvent": "Energy of day changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": "0" + }, + { + "id": "341596ec-690b-4bf6-b6ed-0f92fa1c7d6c", + "name": "eyear", + "displayName": "Energy of current year", + "displayNameEvent": "Energy of year changed", + "type": "int", + "unit": "KiloWattHour", + "defaultValue": "0" + }, + { + "id": "6b6c1ddb-692f-400f-8b8d-38e0e0ae34ba", + "name": "totalEnergyProduced", + "displayName": "Energy total", + "displayNameEvent": "Energy total changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": "0" + } + ] + } + ] + } + ] +} + diff --git a/sma/sma.cpp b/sma/sma.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/sma/sma.h b/sma/sma.h deleted file mode 100644 index e69de29b..00000000 diff --git a/sma/sma.pro b/sma/sma.pro index e69de29b..5408af6e 100644 --- a/sma/sma.pro +++ b/sma/sma.pro @@ -0,0 +1,12 @@ +include(../plugins.pri) + +QT += \ + network \ + +SOURCES += \ + integrationpluginsma.cpp \ + sunnywebbox.cpp \ + +HEADERS += \ + integrationpluginsma.h \ + sunnywebbox.h \ diff --git a/sma/sunnywebbox.cpp b/sma/sunnywebbox.cpp new file mode 100644 index 00000000..0f131fad --- /dev/null +++ b/sma/sunnywebbox.cpp @@ -0,0 +1,177 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "sunnywebbox.h" +#include "extern-plugininfo.h" + +#include "QJsonDocument" +#include "QJsonObject" + +SunnyWebBox::SunnyWebBox(QUdpSocket *udpSocket, QObject *parrent) : + QObject(parrent), + m_udpSocket(udpSocket) +{ + connect(m_udpSocket, &QUdpSocket::stateChanged, this, [this](QAbstractSocket::SocketState state) { + emit connectedChanged((state == QAbstractSocket::SocketState::ConnectedState)); + }); + + connect(m_udpSocket, &QUdpSocket::readyRead, this, [this] { + //m_udpSocket->readDatagram(QByteArray()) + qCDebug(dcSma()) << "Received datagram" << m_udpSocket->readAll(); + }); +} + +int SunnyWebBox::getPlantOverview() +{ + return sendMessage("GetPlantOverview"); +} + +int SunnyWebBox::getDevices() +{ + return sendMessage("GetDevices"); +} + +int SunnyWebBox::getProcessDataChannels(const QString &deviceId) +{ + QJsonObject params; + params["device"] = deviceId; + return sendMessage("GetProcessDataChannels", params); +} + +int SunnyWebBox::sendMessage(const QString &procedure) +{ + int requestId = qrand(); + + QJsonDocument doc; + QJsonObject obj; + obj["version"] = "1.0"; + obj["proc"] = procedure; + obj["id"] = requestId; + obj["format"] = "JSON"; + m_udpSocket->writeDatagram(doc.toJson(), m_hostAddresss, m_port); + return requestId; +} + +int SunnyWebBox::sendMessage(const QString &procedure, const QJsonObject ¶ms) +{ + int requestId = qrand(); + + QJsonDocument doc; + QJsonObject obj; + obj["version"] = "1.0"; + obj["proc"] = procedure; + obj["id"] = requestId; + obj["format"] = "JSON"; + if (!params.isEmpty()) { + obj.insert("params", params); + } + m_udpSocket->writeDatagram(doc.toJson(), m_hostAddresss, m_port); + return requestId; +} + +void SunnyWebBox::onDatagramReceived(const QByteArray &data) +{ + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcSma()) << "Could not parse JSON" << error.errorString(); + return; + } + if (!doc.isObject()) { + qCWarning(dcSma()) << "JSON is not an Object"; + return; + } + QVariantMap map = doc.toVariant().toMap(); + if (map["version"] != "1.0") { + qCWarning(dcSma()) << "API version not supported" << map["version"]; + return; + } + + if (map.contains("proc") && map.contains("result")) { + QString requestType = map["proc"].toString(); + int requestId = map["id"].toInt(); + QVariantMap result = map.value("result").toMap(); + emit messageResponseReceived(requestId, requestType, result); + } else { + qCWarning(dcSma()) << "Missing proc or result value"; + } +} + +void SunnyWebBox::parseMessageReponse(int messageId, const QString &messageType, const QVariantMap &result) +{ + if (messageType == "GetPlantOverview") { + Overview overview; + QVariantList overviewList = result.value("overview").toList(); + Q_FOREACH(QVariant value, overviewList) { + QVariantMap map = value.toMap(); + if (map["meta"].toString() == "GriPwr") { + overview.power = map["value"].toInt(); + } else if (map["meta"].toString() == "GriEgyTdy") { + overview.dailyYield = map["value"].toInt(); + } else if (map["meta"].toString() == "GriEgyTot") { + overview.totalYield = map["value"].toInt(); + } else if (map["meta"].toString() == "OpStt") { + overview.status = map["value"].toString(); + } else if (map["meta"].toString() == "Msg") { + overview.error = map["value"].toString(); + } + } + emit plantOverviewReceived(messageId, overview); + + } else if (messageType == "GetDevices") { + QList devices; + QVariantList deviceList = result.value("devices").toList(); + Q_FOREACH(QVariant value, deviceList) { + Device device; + QVariantMap map = value.toMap(); + device.name = map["name"].toString(); + device.key = map["key"].toString(); + + QVariantList childrenList = map["children"].toList(); + Q_FOREACH(QVariant childValue, childrenList) { + Device child; + QVariantMap childMap = childValue.toMap(); + device.name = childMap["name"].toString(); + device.key = childMap["key"].toString(); + device.childrens.append(child); + } + devices.append(device); + } + if (!devices.isEmpty()) + emit devicesReceived(messageId, devices); + } else if (messageType == "GetProcessDataChannels") { + } else if (messageType == "GetProcessData") { + } else if (messageType == "GetParameterChannels") { + } else if (messageType == "GetParameter") { + } else if (messageType == "SetParameter") { + } else { + qCWarning(dcSma()) << "Unknown message type" << messageType; + } +} diff --git a/sma/sunnywebbox.h b/sma/sunnywebbox.h new file mode 100644 index 00000000..5be8c307 --- /dev/null +++ b/sma/sunnywebbox.h @@ -0,0 +1,90 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef SUNNYWEBBOX_H +#define SUNNYWEBBOX_H + +#include "integrations/thing.h" + +#include +#include +#include + +class SunnyWebBox : public QObject +{ + Q_OBJECT + +public: + struct Overview { + int power; + double dailyYield; + int totalYield; + QString status; + QString error; + }; + + struct Device { + QString key; + QString name; + QList childrens; + }; + + explicit SunnyWebBox(QUdpSocket *udpSocket, QObject *parrent = 0); + + int getPlantOverview(); + int getDevices(); + int getProcessDataChannels(const QString &deviceKey); + int getProcessData(const QStringList &deviceKeys); + int getParameterChannels(const QString &deviceKey); + int getParameters(const QStringList &deviceKeys); + + void setHostAddress(); + QHostAddress hostAddress(); + +private: + int m_port = 34268; + QHostAddress m_hostAddresss; + QUdpSocket *m_udpSocket = nullptr; + + int sendMessage(const QString &procedure); + int sendMessage(const QString &procedure, const QJsonObject ¶ms); + +public slots: + void onDatagramReceived(const QByteArray &data); + void parseMessageReponse(int messageId, const QString &messageType, const QVariantMap &result); + +signals: + void connectedChanged(bool connected); + void messageResponseReceived(int messageId, const QString &messageType, const QVariantMap &result); + void plantOverviewReceived(int messageId, Overview overview); + void devicesReceived(int messageId, QList devices); +}; + +#endif // SUNNYWEBBOX_H From f0310205ad552117c2887abfff9003954a7c7e33 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Thu, 30 Jul 2020 10:47:01 +0200 Subject: [PATCH 03/34] restructured to proper message allocation --- sma/integrationpluginsma.cpp | 91 ++++++++++++++++++++++-- sma/integrationpluginsma.h | 8 ++- sma/sma.pro | 2 + sma/sunnywebbox.cpp | 80 +++------------------ sma/sunnywebbox.h | 14 ++-- sma/sunnywebboxcommunication.cpp | 118 +++++++++++++++++++++++++++++++ sma/sunnywebboxcommunication.h | 58 +++++++++++++++ 7 files changed, 286 insertions(+), 85 deletions(-) create mode 100644 sma/sunnywebboxcommunication.cpp create mode 100644 sma/sunnywebboxcommunication.h diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index efb46a06..fb5a9440 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -36,12 +36,25 @@ IntegrationPluginSma::IntegrationPluginSma() } +void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) +{ + if (info->thingClassId() == sunnyWebBoxThingClassId) { + + info->finish(Thing::ThingErrorNoError); + } +} + void IntegrationPluginSma::setupThing(ThingSetupInfo *info) { Thing *thing = info->thing(); - if (!m_udpSocket) { - m_udpSocket = new QUdpSocket(this); + if (!m_sunnyWebBoxCommunication) { + m_sunnyWebBoxCommunication = new SunnyWebBoxCommunication(this); + } + + if (!m_refreshTimer) { + m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(10); + connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSma::onRefreshTimer); } if (thing->thingClassId() == sunnyWebBoxThingClassId) { @@ -54,6 +67,10 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) return; } } + SunnyWebBox *sunnyWebBox = new SunnyWebBox(m_sunnyWebBoxCommunication, QHostAddress(thing->paramValue(sunnyWebBoxThingHostParamTypeId).toString()), this); + m_sunnyWebBoxes.insert(thing, sunnyWebBox); + //TODO m_asyncSetup + return info->finish(Thing::ThingErrorNoError); } else if (thing->thingClassId() == inverterThingClassId) { Thing *parentThing = myThings().findById(thing->parentId()); @@ -84,6 +101,26 @@ void IntegrationPluginSma::postSetupThing(Thing *thing) return; sunnyWebBox->getDevices(); } else if (thing->thingClassId() == inverterThingClassId) { + + } +} + +void IntegrationPluginSma::executeAction(ThingActionInfo *info) +{ + Thing *thing = info->thing(); + Action action = info->action(); + + if (thing->thingClassId() == sunnyWebBoxThingClassId) { + SunnyWebBox *sunnyWebBox = m_sunnyWebBoxes.value(thing); + if (!sunnyWebBox) + return; + if (action.actionTypeId() == sunnyWebBoxSearchDevicesActionTypeId) { + sunnyWebBox->getDevices(); + } else { + //Unhandled actionTypeId + } + } else { + Q_ASSERT_X(false, "executeAction", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); } } @@ -94,16 +131,58 @@ void IntegrationPluginSma::thingRemoved(Thing *thing) } if (myThings().filterByThingClassId(sunnyWebBoxThingClassId).isEmpty()) { - m_udpSocket->deleteLater(); - m_udpSocket = nullptr; + m_sunnyWebBoxCommunication->deleteLater(); + m_sunnyWebBoxCommunication = nullptr; } } +void IntegrationPluginSma::onRefreshTimer() +{ + Q_FOREACH(SunnyWebBox *sunnyWebBox, m_sunnyWebBoxes) { + sunnyWebBox->getPlantOverview(); + } +} + +void IntegrationPluginSma::onPlantOverviewReceived(int messageId, SunnyWebBox::Overview overview) +{ + Q_UNUSED(messageId) + + Thing *thing = m_sunnyWebBoxes.key(static_cast(sender())); + if (!thing) + return; + + thing->setStateValue(sunnyWebBoxConnectedStateTypeId, true); + thing->setStateValue(sunnyWebBoxCurrentPowerStateTypeId, overview.power); + thing->setStateValue(sunnyWebBoxDayEnergyStateTypeId, overview.dailyYield); + thing->setStateValue(sunnyWebBoxTotalEnergyStateTypeId, overview.totalYield); + thing->setStateValue(sunnyWebBoxModeStateTypeId, overview.status); + if (!overview.error.isEmpty()){ + qCDebug(dcSma()) << "Received error" << overview.error; + thing->setStateValue(sunnyWebBoxErrorStateTypeId, overview.error); + } +} + +void IntegrationPluginSma::onDevicesReceived(int messageId, QList devices) +{ + Q_UNUSED(messageId) + + Thing *thing = m_sunnyWebBoxes.key(static_cast(sender())); + if (!thing) + return; + + ThingDescriptors descriptors; + Q_FOREACH(SunnyWebBox::Device device, devices){ + ThingDescriptor descriptor(inverterThingClassId, device.name, device.key ,thing->id()); + descriptors.append(descriptor); + } + emit autoThingsAppeared(descriptors); +} + SunnyWebBox * IntegrationPluginSma::createSunnyWebBoxConnection(Thing *thing) { - SunnyWebBox *sunnyWebBox = new SunnyWebBox(m_udpSocket, this); + SunnyWebBox *sunnyWebBox = new SunnyWebBox(m_sunnyWebBoxCommunication, QHostAddress(thing->paramValue(sunnyWebBoxThingHostParamTypeId).toString()), this); m_sunnyWebBoxes.insert(thing, sunnyWebBox); - //connect(); + connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, this, &IntegrationPluginSma::onPlantOverviewReceived); return sunnyWebBox; } diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 821cec70..90f44134 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -34,6 +34,7 @@ #include "integrations/integrationplugin.h" #include "plugintimer.h" #include "sunnywebbox.h" +#include "sunnywebboxcommunication.h" #include #include @@ -48,18 +49,23 @@ class IntegrationPluginSma: public IntegrationPlugin { public: explicit IntegrationPluginSma(); + void discoverThings(ThingDiscoveryInfo *info) override; void setupThing(ThingSetupInfo *info) override; void postSetupThing(Thing *thing) override; + void executeAction(ThingActionInfo *info) override; void thingRemoved(Thing *thing) override; private slots: void onRefreshTimer(); + void onPlantOverviewReceived(int messageId, SunnyWebBox::Overview overview); + void onDevicesReceived(int messageId, QList devices); + private: PluginTimer *m_refreshTimer = nullptr; QHash m_sunnyWebBoxes; QHash m_asyncSetup; - QUdpSocket *m_udpSocket = nullptr; + SunnyWebBoxCommunication *m_sunnyWebBoxCommunication = nullptr; SunnyWebBox * createSunnyWebBoxConnection(Thing *thing); void setupChild(ThingSetupInfo *info, Thing *parentThing); diff --git a/sma/sma.pro b/sma/sma.pro index 5408af6e..9f70bbdb 100644 --- a/sma/sma.pro +++ b/sma/sma.pro @@ -6,7 +6,9 @@ QT += \ SOURCES += \ integrationpluginsma.cpp \ sunnywebbox.cpp \ + sunnywebboxcommunication.cpp \ HEADERS += \ integrationpluginsma.h \ sunnywebbox.h \ + sunnywebboxcommunication.h \ diff --git a/sma/sunnywebbox.cpp b/sma/sunnywebbox.cpp index 0f131fad..61feb1ac 100644 --- a/sma/sunnywebbox.cpp +++ b/sma/sunnywebbox.cpp @@ -34,98 +34,38 @@ #include "QJsonDocument" #include "QJsonObject" -SunnyWebBox::SunnyWebBox(QUdpSocket *udpSocket, QObject *parrent) : +SunnyWebBox::SunnyWebBox(SunnyWebBoxCommunication *communication, const QHostAddress &hostAddress, QObject *parrent) : QObject(parrent), - m_udpSocket(udpSocket) + m_hostAddresss(hostAddress), + m_communication(communication) { - connect(m_udpSocket, &QUdpSocket::stateChanged, this, [this](QAbstractSocket::SocketState state) { - emit connectedChanged((state == QAbstractSocket::SocketState::ConnectedState)); - }); - - connect(m_udpSocket, &QUdpSocket::readyRead, this, [this] { - //m_udpSocket->readDatagram(QByteArray()) - qCDebug(dcSma()) << "Received datagram" << m_udpSocket->readAll(); - }); + //TODO connect communication with socket state; + connect(m_communication, &SunnyWebBoxCommunication::messageReceived, this, &SunnyWebBox::onMessageReceived); } int SunnyWebBox::getPlantOverview() { - return sendMessage("GetPlantOverview"); + return m_communication->sendMessage(m_hostAddresss, "GetPlantOverview"); } int SunnyWebBox::getDevices() { - return sendMessage("GetDevices"); + return m_communication->sendMessage(m_hostAddresss, "GetDevices"); } int SunnyWebBox::getProcessDataChannels(const QString &deviceId) { QJsonObject params; params["device"] = deviceId; - return sendMessage("GetProcessDataChannels", params); + return m_communication->sendMessage(m_hostAddresss, "GetProcessDataChannels", params); } -int SunnyWebBox::sendMessage(const QString &procedure) +void SunnyWebBox::onMessageReceived(const QHostAddress &address, int messageId, const QString &messageType, const QVariantMap &result) { - int requestId = qrand(); - - QJsonDocument doc; - QJsonObject obj; - obj["version"] = "1.0"; - obj["proc"] = procedure; - obj["id"] = requestId; - obj["format"] = "JSON"; - m_udpSocket->writeDatagram(doc.toJson(), m_hostAddresss, m_port); - return requestId; -} - -int SunnyWebBox::sendMessage(const QString &procedure, const QJsonObject ¶ms) -{ - int requestId = qrand(); - - QJsonDocument doc; - QJsonObject obj; - obj["version"] = "1.0"; - obj["proc"] = procedure; - obj["id"] = requestId; - obj["format"] = "JSON"; - if (!params.isEmpty()) { - obj.insert("params", params); - } - m_udpSocket->writeDatagram(doc.toJson(), m_hostAddresss, m_port); - return requestId; -} - -void SunnyWebBox::onDatagramReceived(const QByteArray &data) -{ - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(data, &error); - if (error.error != QJsonParseError::NoError) { - qCWarning(dcSma()) << "Could not parse JSON" << error.errorString(); - return; - } - if (!doc.isObject()) { - qCWarning(dcSma()) << "JSON is not an Object"; - return; - } - QVariantMap map = doc.toVariant().toMap(); - if (map["version"] != "1.0") { - qCWarning(dcSma()) << "API version not supported" << map["version"]; + if (address != m_hostAddresss) { return; } - if (map.contains("proc") && map.contains("result")) { - QString requestType = map["proc"].toString(); - int requestId = map["id"].toInt(); - QVariantMap result = map.value("result").toMap(); - emit messageResponseReceived(requestId, requestType, result); - } else { - qCWarning(dcSma()) << "Missing proc or result value"; - } -} - -void SunnyWebBox::parseMessageReponse(int messageId, const QString &messageType, const QVariantMap &result) -{ if (messageType == "GetPlantOverview") { Overview overview; QVariantList overviewList = result.value("overview").toList(); diff --git a/sma/sunnywebbox.h b/sma/sunnywebbox.h index 5be8c307..f039e4c4 100644 --- a/sma/sunnywebbox.h +++ b/sma/sunnywebbox.h @@ -32,6 +32,7 @@ #define SUNNYWEBBOX_H #include "integrations/thing.h" +#include "sunnywebboxcommunication.h" #include #include @@ -56,7 +57,7 @@ public: QList childrens; }; - explicit SunnyWebBox(QUdpSocket *udpSocket, QObject *parrent = 0); + explicit SunnyWebBox(SunnyWebBoxCommunication *communication, const QHostAddress &hostAddress, QObject *parrent = 0); int getPlantOverview(); int getDevices(); @@ -69,20 +70,17 @@ public: QHostAddress hostAddress(); private: - int m_port = 34268; - QHostAddress m_hostAddresss; - QUdpSocket *m_udpSocket = nullptr; - int sendMessage(const QString &procedure); - int sendMessage(const QString &procedure, const QJsonObject ¶ms); + QHostAddress m_hostAddresss; + SunnyWebBoxCommunication *m_communication = nullptr; public slots: void onDatagramReceived(const QByteArray &data); - void parseMessageReponse(int messageId, const QString &messageType, const QVariantMap &result); + void onMessageReceived(const QHostAddress &address, int messageId, const QString &messageType, const QVariantMap &result); signals: void connectedChanged(bool connected); - void messageResponseReceived(int messageId, const QString &messageType, const QVariantMap &result); + void plantOverviewReceived(int messageId, Overview overview); void devicesReceived(int messageId, QList devices); }; diff --git a/sma/sunnywebboxcommunication.cpp b/sma/sunnywebboxcommunication.cpp new file mode 100644 index 00000000..8edce319 --- /dev/null +++ b/sma/sunnywebboxcommunication.cpp @@ -0,0 +1,118 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "sunnywebboxcommunication.h" +#include "extern-plugininfo.h" + +#include "QJsonDocument" +#include "QJsonObject" + +SunnyWebBoxCommunication::SunnyWebBoxCommunication(QObject *parent) : QObject(parent) +{ + m_udpSocket = new QUdpSocket(this); + m_udpSocket->bind(QHostAddress::LocalHost, m_port); + + connect(m_udpSocket, &QUdpSocket::stateChanged, this, [this](QAbstractSocket::SocketState state) { + emit socketConnected(state == QAbstractSocket::SocketState::ConnectedState); + }); + + connect(m_udpSocket, &QUdpSocket::readyRead, this, [this] { + + QHostAddress address; + quint16 port; + QByteArray data; + while (m_udpSocket->hasPendingDatagrams()) { + qCDebug(dcSma()) << "Received datagram"; + int receivedBytes = m_udpSocket->readDatagram(data.data(), 1000, &address, &port); + if (receivedBytes == -1) { + qCWarning(dcSma()) << "Error reading pending datagram"; + } + } + }); +} + +int SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QString &procedure) +{ + int requestId = qrand(); + + QJsonDocument doc; + QJsonObject obj; + obj["version"] = "1.0"; + obj["proc"] = procedure; + obj["id"] = requestId; + obj["format"] = "JSON"; + m_udpSocket->writeDatagram(doc.toJson(), address, m_port); + return requestId; +} + +int SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms) +{ + int requestId = qrand(); + + QJsonDocument doc; + QJsonObject obj; + obj["version"] = "1.0"; + obj["proc"] = procedure; + obj["id"] = requestId; + obj["format"] = "JSON"; + if (!params.isEmpty()) { + obj.insert("params", params); + } + m_udpSocket->writeDatagram(doc.toJson(), address, m_port); + return requestId; +} + +void SunnyWebBoxCommunication::datagramReceived(const QHostAddress &address, const QByteArray &data) +{ + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcSma()) << "Could not parse JSON" << error.errorString(); + return; + } + if (!doc.isObject()) { + qCWarning(dcSma()) << "JSON is not an Object"; + return; + } + QVariantMap map = doc.toVariant().toMap(); + if (map["version"] != "1.0") { + qCWarning(dcSma()) << "API version not supported" << map["version"]; + return; + } + + if (map.contains("proc") && map.contains("result")) { + QString requestType = map["proc"].toString(); + int requestId = map["id"].toInt(); + QVariantMap result = map.value("result").toMap(); + emit messageReceived(address, requestId, requestType, result); + } else { + qCWarning(dcSma()) << "Missing proc or result value"; + } +} diff --git a/sma/sunnywebboxcommunication.h b/sma/sunnywebboxcommunication.h new file mode 100644 index 00000000..cec4175c --- /dev/null +++ b/sma/sunnywebboxcommunication.h @@ -0,0 +1,58 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef SUNNYWEBBOXCOMMUNICATION_H +#define SUNNYWEBBOXCOMMUNICATION_H + +#include +#include + +class SunnyWebBoxCommunication : public QObject +{ + Q_OBJECT +public: + explicit SunnyWebBoxCommunication(QObject *parent = nullptr); + + int sendMessage(const QHostAddress &address, const QString &procedure); + int sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms); + +private: + int m_port = 34268; + QUdpSocket *m_udpSocket; + + void datagramReceived(const QHostAddress &address, const QByteArray &data); + +signals: + void socketConnected(bool connected); + void messageReceived(const QHostAddress &address, int messageId, const QString &messageType, const QVariantMap &result); + +}; + +#endif // SUNNYWEBBOXCOMMUNICATION_H From 84bb9a61b920907c4d04382c4aa6f7fdf55dd3b5 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Thu, 30 Jul 2020 13:12:18 +0200 Subject: [PATCH 04/34] added meta and readme --- debian/control | 17 +++++++++++++++++ sma/README.md | 15 +++++++++++++++ sma/meta.json | 13 +++++++++++++ sma/sma.png | Bin 0 -> 41281 bytes 4 files changed, 45 insertions(+) create mode 100644 sma/README.md create mode 100644 sma/meta.json create mode 100644 sma/sma.png diff --git a/debian/control b/debian/control index ccb78d60..b6e5585c 100644 --- a/debian/control +++ b/debian/control @@ -1035,6 +1035,22 @@ Description: nymea.io plugin to send and receive strings over a serial port . This package will install the nymea.io plugin for serial ports + +Package: nymea-plugin-sma +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, + nymea-plugins-translations, +Description: nymea.io plugin for SMA PV-Inverter + The nymea daemon is a plugin based IoT (Internet of Things) server. The + server works like a translator for devices, things and services and + allows them to interact. + 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 will install the nymea.io plugin for SMA PV-Inverter + + Package: nymea-plugin-systemmonitor Architecture: any Depends: ${shlibs:Depends}, @@ -1231,6 +1247,7 @@ Depends: nymea-plugin-anel, nymea-plugin-shelly, nymea-plugin-senic, nymea-plugin-somfytahoma, + nymea-plugin-sma, nymea-plugin-sonos, nymea-plugin-solarlog, nymea-plugin-tado, diff --git a/sma/README.md b/sma/README.md new file mode 100644 index 00000000..b4b47cdb --- /dev/null +++ b/sma/README.md @@ -0,0 +1,15 @@ +# SMA + +nymea plug-in for SMA solar equipment. + +## Supported Things + +* Sunny WebBox + +## Requirements + +* The package "nymea-plugin-sma" must be installed. + +## More + +https://www.sma.de/en/ diff --git a/sma/meta.json b/sma/meta.json new file mode 100644 index 00000000..b2bee247 --- /dev/null +++ b/sma/meta.json @@ -0,0 +1,13 @@ +{ + "title": "SMA", + "tagline": "Connect to SMA solar equipment.", + "icon": "sma.png", + "stability": "consumer", + "offline": true, + "technologies": [ + "network" + ], + "categories": [ + + ] +} diff --git a/sma/sma.png b/sma/sma.png new file mode 100644 index 0000000000000000000000000000000000000000..3273ac8549dcf43bd49062a7c02406298c63b81d GIT binary patch literal 41281 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 Date: Tue, 4 Aug 2020 10:16:41 +0200 Subject: [PATCH 05/34] added first parsing --- sma/integrationpluginsma.cpp | 31 +++++++++++++++++++++++++++++++ sma/integrationpluginsma.h | 2 ++ sma/sunnywebbox.cpp | 5 +++++ sma/sunnywebbox.h | 9 +++++++++ 4 files changed, 47 insertions(+) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index fb5a9440..15a4e048 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -178,11 +178,42 @@ void IntegrationPluginSma::onDevicesReceived(int messageId, QList &channels) +{ + Thing *thing = m_sunnyWebBoxes.key(static_cast(sender())); + if (!thing) + return; + + qCDebug(dcSma()) << "Process data received" << deviceKey; + + Q_FOREACH(Thing *childThing, myThings().filterByParentId(thing->id())) { + if (childThing->paramValue(inverterThingIdParamTypeId).toString() == deviceKey) { + if (channels.contains("E-Total")) { + //TODO set total energy + } + break; + } + } +} + +void IntegrationPluginSma::onParameterChannelsReceived(int messageId, const QString &deviceKey, QStringList parameterChannels) +{ + Q_UNUSED(messageId) + + Thing *thing = m_sunnyWebBoxes.key(static_cast(sender())); + if (!thing) + return; + + qCDebug(dcSma()) << "Parameter channels received" << deviceKey << parameterChannels; +} + SunnyWebBox * IntegrationPluginSma::createSunnyWebBoxConnection(Thing *thing) { SunnyWebBox *sunnyWebBox = new SunnyWebBox(m_sunnyWebBoxCommunication, QHostAddress(thing->paramValue(sunnyWebBoxThingHostParamTypeId).toString()), this); m_sunnyWebBoxes.insert(thing, sunnyWebBox); connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, this, &IntegrationPluginSma::onPlantOverviewReceived); + connect(sunnyWebBox, &SunnyWebBox::parameterChannelsReceived, this, &IntegrationPluginSma::onParameterChannelsReceived); + //connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, this, &IntegrationPluginSma::onPlantOverviewReceived); return sunnyWebBox; } diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 90f44134..e968dc17 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -60,6 +60,8 @@ private slots: void onPlantOverviewReceived(int messageId, SunnyWebBox::Overview overview); void onDevicesReceived(int messageId, QList devices); + void onProcessDataReceived(int messageId, const QString &deviceKey, const QHash &channels); + void onParameterChannelsReceived(int messageId, const QString &deviceKey, QStringList parameterChannels); private: PluginTimer *m_refreshTimer = nullptr; diff --git a/sma/sunnywebbox.cpp b/sma/sunnywebbox.cpp index 61feb1ac..90738b1d 100644 --- a/sma/sunnywebbox.cpp +++ b/sma/sunnywebbox.cpp @@ -109,6 +109,11 @@ void SunnyWebBox::onMessageReceived(const QHostAddress &address, int messageId, } else if (messageType == "GetProcessDataChannels") { } else if (messageType == "GetProcessData") { } else if (messageType == "GetParameterChannels") { + Q_FOREACH(QString deviceKey, result.keys()) { + QStringList parameterChannels = result.value(deviceKey).toStringList(); + if (!parameterChannels.isEmpty()) + emit parameterChannelsReceived(messageId, deviceKey, parameterChannels); + } } else if (messageType == "GetParameter") { } else if (messageType == "SetParameter") { } else { diff --git a/sma/sunnywebbox.h b/sma/sunnywebbox.h index f039e4c4..3b4cf367 100644 --- a/sma/sunnywebbox.h +++ b/sma/sunnywebbox.h @@ -57,6 +57,13 @@ public: QList childrens; }; + struct Channel { + QString meta; + QString name; + QVariant value; + QString unit; + }; + explicit SunnyWebBox(SunnyWebBoxCommunication *communication, const QHostAddress &hostAddress, QObject *parrent = 0); int getPlantOverview(); @@ -83,6 +90,8 @@ signals: void plantOverviewReceived(int messageId, Overview overview); void devicesReceived(int messageId, QList devices); + void processDataReceived(int messageId, const QString &deviceKey, const QHash &channels); + void parameterChannelsReceived(int messageId, const QString &deviceKey, QStringList parameterChannels); }; #endif // SUNNYWEBBOX_H From e35f1577188e4e68303a43e131f19c95ff65c19e Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 4 Aug 2020 20:20:36 +0200 Subject: [PATCH 06/34] added missing parsing methods --- sma/integrationpluginsma.cpp | 1 + sma/sunnywebbox.cpp | 62 ++++++++++++++++++++++++++++++++++-- sma/sunnywebbox.h | 24 ++++++++++---- 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 15a4e048..64644a63 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -180,6 +180,7 @@ void IntegrationPluginSma::onDevicesReceived(int messageId, QList &channels) { + Q_UNUSED(messageId) Thing *thing = m_sunnyWebBoxes.key(static_cast(sender())); if (!thing) return; diff --git a/sma/sunnywebbox.cpp b/sma/sunnywebbox.cpp index 90738b1d..12716fac 100644 --- a/sma/sunnywebbox.cpp +++ b/sma/sunnywebbox.cpp @@ -33,6 +33,7 @@ #include "QJsonDocument" #include "QJsonObject" +#include "QJsonArray" SunnyWebBox::SunnyWebBox(SunnyWebBoxCommunication *communication, const QHostAddress &hostAddress, QObject *parrent) : QObject(parrent), @@ -60,6 +61,25 @@ int SunnyWebBox::getProcessDataChannels(const QString &deviceId) return m_communication->sendMessage(m_hostAddresss, "GetProcessDataChannels", params); } +int SunnyWebBox::setParameters(const QString &deviceKey, const QHash &channels) +{ + QJsonObject paramsObj; + QJsonArray devicesArray; + QJsonObject deviceObj; + deviceObj["key"] = deviceKey; + QJsonArray channelsArray; + Q_FOREACH(QString key, channels.keys()) { + QJsonObject channelObj; + channelObj["meta"] = key; + channelObj["value"] = channels.value(key).toString(); + channelsArray.append(channelObj); + } + deviceObj["channels"] = channelsArray; + devicesArray.append(deviceObj); + paramsObj["devices"] = devicesArray; + return m_communication->sendMessage(m_hostAddresss, "SetParameter", paramsObj); +} + void SunnyWebBox::onMessageReceived(const QHostAddress &address, int messageId, const QString &messageType, const QVariantMap &result) { if (address != m_hostAddresss) { @@ -106,16 +126,52 @@ void SunnyWebBox::onMessageReceived(const QHostAddress &address, int messageId, } if (!devices.isEmpty()) emit devicesReceived(messageId, devices); - } else if (messageType == "GetProcessDataChannels") { + } else if (messageType == "GetProcessDataChannels" || + messageType == "GetProDataChannels") { + Q_FOREACH(QString deviceKey, result.keys()) { + QStringList processDataChannels = result.value(deviceKey).toStringList(); + if (!processDataChannels.isEmpty()) + emit processDataChannelsReceived(messageId, deviceKey, processDataChannels); + } } else if (messageType == "GetProcessData") { + QList devices; + QVariantList devicesList = result.value("devices").toList(); + Q_FOREACH(QVariant value, devicesList) { + + QString key = value.toMap().value("key").toString(); + QVariantList channelsList = value.toMap().value("channels").toList(); + QHash channels; + Q_FOREACH(QVariant channel, channelsList) { + channels.insert(channel.toMap().value("meta").toString(), channel.toMap().value("value")); + } + emit processDataReceived(messageId, key, channels); + } } else if (messageType == "GetParameterChannels") { Q_FOREACH(QString deviceKey, result.keys()) { QStringList parameterChannels = result.value(deviceKey).toStringList(); if (!parameterChannels.isEmpty()) emit parameterChannelsReceived(messageId, deviceKey, parameterChannels); } - } else if (messageType == "GetParameter") { - } else if (messageType == "SetParameter") { + } else if (messageType == "GetParameter"|| messageType == "SetParameter") { + QList devices; + QVariantList devicesList = result.value("devices").toList(); + Q_FOREACH(QVariant value, devicesList) { + + QString key = value.toMap().value("key").toString(); + QVariantList channelsList = value.toMap().value("channels").toList(); + QList parameters; + Q_FOREACH(QVariant channel, channelsList) { + Parameter parameter; + parameter.meta = channel.toMap().value("meta").toString(); + parameter.name = channel.toMap().value("name").toString(); + parameter.unit = channel.toMap().value("unit").toString(); + parameter.min = channel.toMap().value("min").toDouble(); + parameter.max = channel.toMap().value("max").toDouble(); + parameter.value = channel.toMap().value("value").toDouble(); + parameters.append(parameter); + } + emit parametersReceived(messageId, key, parameters); + } } else { qCWarning(dcSma()) << "Unknown message type" << messageType; } diff --git a/sma/sunnywebbox.h b/sma/sunnywebbox.h index 3b4cf367..9b6b2fe5 100644 --- a/sma/sunnywebbox.h +++ b/sma/sunnywebbox.h @@ -64,14 +64,24 @@ public: QString unit; }; + struct Parameter { + QString meta; + QString name; + QString unit; + double min; + double max; + double value; + }; + explicit SunnyWebBox(SunnyWebBoxCommunication *communication, const QHostAddress &hostAddress, QObject *parrent = 0); - int getPlantOverview(); - int getDevices(); - int getProcessDataChannels(const QString &deviceKey); - int getProcessData(const QStringList &deviceKeys); - int getParameterChannels(const QString &deviceKey); - int getParameters(const QStringList &deviceKeys); + int getPlantOverview(); // Returns an object with the following plant data: PAC, E-TODAY, E-TOTAL, MODE, ERROR + int getDevices(); //Returns a hierarchical list of all detected plant devices. + int getProcessDataChannels(const QString &deviceKey); //Returns a list with the meta names of the available process data channels for a particular device type. + int getProcessData(const QStringList &deviceKeys); //Returns process data for up to 5 devices per request. + int getParameterChannels(const QString &deviceKey); //Returns a list with the meta names of the available parameter channels for a particular device type + int getParameters(const QStringList &deviceKeys); //Returns the parameter values of up to 5 devices + int setParameters(const QString &deviceKeys, const QHash &channels); //Sets parameter values void setHostAddress(); QHostAddress hostAddress(); @@ -90,8 +100,10 @@ signals: void plantOverviewReceived(int messageId, Overview overview); void devicesReceived(int messageId, QList devices); + void processDataChannelsReceived(int messageId, const QString &deviceKey, QStringList processDataChanels); void processDataReceived(int messageId, const QString &deviceKey, const QHash &channels); void parameterChannelsReceived(int messageId, const QString &deviceKey, QStringList parameterChannels); + void parametersReceived(int messageId, const QString &deviceKey, const QList ¶meters); }; #endif // SUNNYWEBBOX_H From 5bd077122e59012fc693c3a5ce1fce5479b79265 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 18 Aug 2020 10:29:56 +0200 Subject: [PATCH 07/34] added discovery --- sma/discovery.cpp | 271 ++++++++++++++++++++++++++++++++++ sma/discovery.h | 77 ++++++++++ sma/host.cpp | 93 ++++++++++++ sma/host.h | 69 +++++++++ sma/integrationpluginsma.cpp | 27 ++++ sma/integrationpluginsma.h | 1 + sma/integrationpluginsma.json | 8 +- sma/sma.pro | 4 + 8 files changed, 546 insertions(+), 4 deletions(-) create mode 100644 sma/discovery.cpp create mode 100644 sma/discovery.h create mode 100644 sma/host.cpp create mode 100644 sma/host.h diff --git a/sma/discovery.cpp b/sma/discovery.cpp new file mode 100644 index 00000000..fca14ef5 --- /dev/null +++ b/sma/discovery.cpp @@ -0,0 +1,271 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "discovery.h" +#include "extern-plugininfo.h" + +#include +#include +#include +#include +#include + +Discovery::Discovery(QObject *parent) : QObject(parent) +{ + connect(&m_timeoutTimer, &QTimer::timeout, this, &Discovery::onTimeout); +} + +void Discovery::discoverHosts(int timeout) +{ + if (isRunning()) { + qWarning(dcSma()) << "Discovery already running. Cannot start twice."; + return; + } + m_timeoutTimer.start(timeout * 1000); + + foreach (const QString &target, getDefaultTargets()) { + QProcess *discoveryProcess = new QProcess(this); + m_discoveryProcesses.append(discoveryProcess); + connect(discoveryProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(discoveryFinished(int,QProcess::ExitStatus))); + + QStringList arguments; + arguments << "-oX" << "-" << "-n" << "-sn"; + arguments << target; + + qCDebug(dcSma()) << "Scanning network:" << "nmap" << arguments.join(" "); + discoveryProcess->start(QStringLiteral("nmap"), arguments); + } + +} + +void Discovery::abort() +{ + foreach (QProcess *discoveryProcess, m_discoveryProcesses) { + if (discoveryProcess->state() == QProcess::Running) { + qCDebug(dcSma()) << "Kill running discovery process"; + discoveryProcess->terminate(); + discoveryProcess->waitForFinished(5000); + } + } + foreach (QProcess *p, m_pendingArpLookups.keys()) { + p->terminate(); + delete p; + } + m_pendingArpLookups.clear(); + m_pendingNameLookups.clear(); + qDeleteAll(m_scanResults); + m_scanResults.clear(); +} + +bool Discovery::isRunning() const +{ + return !m_discoveryProcesses.isEmpty() || !m_pendingArpLookups.isEmpty() || !m_pendingNameLookups.isEmpty(); +} + +void Discovery::discoveryFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + QProcess *discoveryProcess = static_cast(sender()); + + if (exitCode != 0 || exitStatus != QProcess::NormalExit) { + qCWarning(dcSma()) << "Nmap error failed. Is nmap installed correctly?"; + m_discoveryProcesses.removeAll(discoveryProcess); + discoveryProcess->deleteLater(); + discoveryProcess = nullptr; + finishDiscovery(); + return; + } + + QByteArray data = discoveryProcess->readAll(); + m_discoveryProcesses.removeAll(discoveryProcess); + discoveryProcess->deleteLater(); + discoveryProcess = nullptr; + + QXmlStreamReader reader(data); + + int foundHosts = 0; + + while (!reader.atEnd() && !reader.hasError()) { + QXmlStreamReader::TokenType token = reader.readNext(); + if(token == QXmlStreamReader::StartDocument) + continue; + + if(token == QXmlStreamReader::StartElement && reader.name() == "host") { + bool isUp = false; + QString address; + QString macAddress; + QString vendor; + while (!reader.atEnd() && !reader.hasError() && !(token == QXmlStreamReader::EndElement && reader.name() == "host")) { + token = reader.readNext(); + + if (reader.name() == "address") { + QString addr = reader.attributes().value("addr").toString(); + QString type = reader.attributes().value("addrtype").toString(); + if (type == "ipv4" && !addr.isEmpty()) { + address = addr; + } else if (type == "mac") { + macAddress = addr; + vendor = reader.attributes().value("vendor").toString(); + } + } + + if (reader.name() == "status") { + QString state = reader.attributes().value("state").toString(); + if (!state.isEmpty()) + isUp = state == "up"; + } + } + + if (isUp) { + foundHosts++; + qCDebug(dcSma()) << "Have host:" << address; + + Host *host = new Host(); + host->setAddress(address); + + if (!macAddress.isEmpty()) { + host->setMacAddress(macAddress); + } else { + QProcess *arpLookup = new QProcess(this); + m_pendingArpLookups.insert(arpLookup, host); + connect(arpLookup, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(arpLookupDone(int,QProcess::ExitStatus))); + arpLookup->start("arp", {"-vn"}); + } + + host->setHostName(vendor); + QHostInfo::lookupHost(address, this, SLOT(hostLookupDone(QHostInfo))); + m_pendingNameLookups.insert(address, host); + + m_scanResults.append(host); + } + } + } + + if (foundHosts == 0 && m_discoveryProcesses.isEmpty()) { + qCDebug(dcSma()) << "Network scan successful but no hosts found in this network"; + finishDiscovery(); + } +} + +void Discovery::hostLookupDone(const QHostInfo &info) +{ + Host *host = m_pendingNameLookups.take(info.addresses().first().toString()); + if (!host) { + // Probably aborted... + return; + } + if (info.error() != QHostInfo::NoError) { + qWarning(dcSma()) << "Host lookup failed:" << info.errorString(); + } + if (host->hostName().isEmpty() || info.hostName() != host->address()) { + host->setHostName(info.hostName()); + } + + finishDiscovery(); +} + +void Discovery::arpLookupDone(int exitCode, QProcess::ExitStatus exitStatus) +{ + QProcess *p = static_cast(sender()); + p->deleteLater(); + + Host *host = m_pendingArpLookups.take(p); + + if (exitCode != 0 || exitStatus != QProcess::NormalExit) { + qCWarning(dcSma()) << "ARP lookup process failed for host" << host->address(); + finishDiscovery(); + return; + } + + QString data = QString::fromLatin1(p->readAll()); + foreach (QString line, data.split('\n')) { + line.replace(QRegExp("[ ]{1,}"), " "); + QStringList parts = line.split(" "); + if (parts.count() >= 3 && parts.first() == host->address() && parts.at(1) == "ether") { + host->setMacAddress(parts.at(2)); + break; + } + } + finishDiscovery(); +} + +void Discovery::onTimeout() +{ + qWarning(dcSma()) << "Timeout hit. Stopping discovery"; + while (!m_discoveryProcesses.isEmpty()) { + QProcess *discoveryProcess = m_discoveryProcesses.takeFirst(); + disconnect(this, SLOT(discoveryFinished(int,QProcess::ExitStatus))); + discoveryProcess->terminate(); + delete discoveryProcess; + } + foreach (QProcess *p, m_pendingArpLookups.keys()) { + p->terminate(); + m_scanResults.removeAll(m_pendingArpLookups.value(p)); + delete p; + } + m_pendingArpLookups.clear(); + m_pendingNameLookups.clear(); + finishDiscovery(); +} + +QStringList Discovery::getDefaultTargets() +{ + QStringList targets; + foreach (const QHostAddress &interface, QNetworkInterface::allAddresses()) { + if (!interface.isLoopback() && interface.scopeId().isEmpty() && interface.protocol() == QAbstractSocket::IPv4Protocol) { + QPair pair = QHostAddress::parseSubnet(interface.toString() + "/24"); + QString newTarget = QString("%1/%2").arg(pair.first.toString()).arg(pair.second); + if (!targets.contains(newTarget)) { + targets.append(newTarget); + } + } + } + return targets; +} + +void Discovery::finishDiscovery() +{ + if (m_discoveryProcesses.count() > 0 || m_pendingNameLookups.count() > 0 || m_pendingArpLookups.count() > 0) { + // Still busy... + return; + } + + QList hosts; + foreach (Host *host, m_scanResults) { + if (!host->macAddress().isEmpty()) { + hosts.append(*host); + } + } + qDeleteAll(m_scanResults); + m_scanResults.clear(); + + qCDebug(dcSma()) << "Emitting thing discovered for" << hosts.count() << "devices"; + m_timeoutTimer.stop(); + emit finished(hosts); +} diff --git a/sma/discovery.h b/sma/discovery.h new file mode 100644 index 00000000..b74fe2cb --- /dev/null +++ b/sma/discovery.h @@ -0,0 +1,77 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DISCOVERY_H +#define DISCOVERY_H + +#include +#include +#include +#include + +#include "host.h" + +class Discovery : public QObject +{ + Q_OBJECT +public: + explicit Discovery(QObject *parent = nullptr); + + void discoverHosts(int timeout); + void abort(); + + bool isRunning() const; + + +signals: + void finished(QList hosts); + +private: + QStringList getDefaultTargets(); + + void finishDiscovery(); + +private slots: + void discoveryFinished(int exitCode, QProcess::ExitStatus exitStatus); + void hostLookupDone(const QHostInfo &info); + void arpLookupDone(int exitCode, QProcess::ExitStatus exitStatus); + void onTimeout(); + +private: + QList m_discoveryProcesses; + QTimer m_timeoutTimer; + + QHash m_pendingArpLookups; + QHash m_pendingNameLookups; + QList m_scanResults; + +}; + +#endif // DISCOVERY_H diff --git a/sma/host.cpp b/sma/host.cpp new file mode 100644 index 00000000..2aec4ab6 --- /dev/null +++ b/sma/host.cpp @@ -0,0 +1,93 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "host.h" + +Host::Host() +{ + qRegisterMetaType(); + qRegisterMetaType >(); +} + +QString Host::macAddress() const +{ + return m_macAddress; +} + +void Host::setMacAddress(const QString &macAddress) +{ + m_macAddress = macAddress; +} + +QString Host::hostName() const +{ + return m_hostName; +} + +void Host::setHostName(const QString &hostName) +{ + m_hostName = hostName; +} + +QString Host::address() const +{ + return m_address; +} + +void Host::setAddress(const QString &address) +{ + m_address = address; +} + +void Host::seen() +{ + m_lastSeenTime = QDateTime::currentDateTime(); +} + +QDateTime Host::lastSeenTime() const +{ + return m_lastSeenTime; +} + +bool Host::reachable() const +{ + return m_reachable; +} + +void Host::setReachable(bool reachable) +{ + m_reachable = reachable; +} + +QDebug operator<<(QDebug dbg, const Host &host) +{ + dbg.nospace() << "Host(" << host.macAddress() << "," << host.hostName() << ", " << host.address() << ", " << (host.reachable() ? "up" : "down") << ")"; + return dbg.space(); +} diff --git a/sma/host.h b/sma/host.h new file mode 100644 index 00000000..84e48b0d --- /dev/null +++ b/sma/host.h @@ -0,0 +1,69 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef HOST_H +#define HOST_H + +#include +#include +#include + +class Host +{ +public: + Host(); + + QString macAddress() const; + void setMacAddress(const QString &macAddress); + + QString hostName() const; + void setHostName(const QString &hostName); + + QString address() const; + void setAddress(const QString &address); + + void seen(); + QDateTime lastSeenTime() const; + + bool reachable() const; + void setReachable(bool reachable); + +private: + QString m_macAddress; + QString m_hostName; + QString m_address; + QDateTime m_lastSeenTime; + bool m_reachable; +}; +Q_DECLARE_METATYPE(Host) + +QDebug operator<<(QDebug dbg, const Host &host); + +#endif // HOST_H diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 64644a63..ac0e8ac8 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -40,6 +40,33 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) { if (info->thingClassId() == sunnyWebBoxThingClassId) { + Discovery *discovery = new Discovery(this); + discovery->discoverHosts(25); + + // clean up discovery object when this discovery info is deleted + connect(info, &ThingDiscoveryInfo::destroyed, discovery, &Discovery::deleteLater); + connect(discovery, &Discovery::finished, info, [this, info](const QList &hosts) { + qCDebug(dcSma()) << "Discovery finished. Found" << hosts.count() << "devices"; + foreach (const Host &host, hosts) { + if (host.hostName().contains("SMA Regelsysteme Gmbh")){ + ThingDescriptor descriptor(info->thingClassId(), host.hostName(), host.address() + " (" + host.macAddress() + ")"); + + foreach (Thing *existingThing, myThings()) { + if (existingThing->paramValue(sunnyWebBoxThingMacAddressParamTypeId).toString() == host.macAddress()) { + descriptor.setThingId(existingThing->id()); + break; + } + } + ParamList params; + params << Param(sunnyWebBoxThingMacAddressParamTypeId, host.macAddress()); + params << Param(sunnyWebBoxThingHostParamTypeId, host.address()); + descriptor.setParams(params); + + info->addThingDescriptor(descriptor); + } + } + info->finish(Thing::ThingErrorNoError); + }); info->finish(Thing::ThingErrorNoError); } } diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index e968dc17..76bc0324 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -35,6 +35,7 @@ #include "plugintimer.h" #include "sunnywebbox.h" #include "sunnywebboxcommunication.h" +#include "discovery.h" #include #include diff --git a/sma/integrationpluginsma.json b/sma/integrationpluginsma.json index c823b3f0..6b9a9920 100644 --- a/sma/integrationpluginsma.json +++ b/sma/integrationpluginsma.json @@ -24,11 +24,11 @@ "defaultValue": "192.168.0.168" }, { - "id": "a6df3d28-1b02-42dd-8301-81754c47e55f", - "name": "id", - "displayName": "Device ID", + "id": "03f32361-4e13-4597-a346-af8d16a986b3", + "name": "macAddress", + "displayName": "hardware address", "type": "QString", - "defaultValue": "-", + "inputType": "TextLine", "readOnly": true } ], diff --git a/sma/sma.pro b/sma/sma.pro index 9f70bbdb..fcf5dcec 100644 --- a/sma/sma.pro +++ b/sma/sma.pro @@ -7,8 +7,12 @@ SOURCES += \ integrationpluginsma.cpp \ sunnywebbox.cpp \ sunnywebboxcommunication.cpp \ + host.cpp \ + discovery.cpp HEADERS += \ integrationpluginsma.h \ sunnywebbox.h \ sunnywebboxcommunication.h \ + host.h \ + discovery.h From b80c3e4fbafd5a7e300908e9ad79d315d6d34954 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 18 Aug 2020 11:05:23 +0200 Subject: [PATCH 08/34] fixed missing symbols --- sma/integrationpluginsma.cpp | 5 +++++ sma/sunnywebbox.cpp | 25 +++++++++++++++++++++++++ sma/sunnywebbox.h | 3 +-- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index ac0e8ac8..19f3e6d2 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -250,3 +250,8 @@ void IntegrationPluginSma::setupChild(ThingSetupInfo *info, Thing *parentThing) Q_UNUSED(info) Q_UNUSED(parentThing) } + +void IntegrationPluginSma::getData(Thing *thing) +{ + Q_UNUSED(thing) +} diff --git a/sma/sunnywebbox.cpp b/sma/sunnywebbox.cpp index 12716fac..7bd9f3c1 100644 --- a/sma/sunnywebbox.cpp +++ b/sma/sunnywebbox.cpp @@ -61,6 +61,21 @@ int SunnyWebBox::getProcessDataChannels(const QString &deviceId) return m_communication->sendMessage(m_hostAddresss, "GetProcessDataChannels", params); } +int SunnyWebBox::getProcessData(const QStringList &deviceKeys) +{ + Q_UNUSED(deviceKeys) +} + +int SunnyWebBox::getParameterChannels(const QString &deviceKey) +{ + Q_UNUSED(deviceKey) +} + +int SunnyWebBox::getParameters(const QStringList &deviceKeys) +{ + Q_UNUSED(deviceKeys) +} + int SunnyWebBox::setParameters(const QString &deviceKey, const QHash &channels) { QJsonObject paramsObj; @@ -80,6 +95,16 @@ int SunnyWebBox::setParameters(const QString &deviceKey, const QHashsendMessage(m_hostAddresss, "SetParameter", paramsObj); } +void SunnyWebBox::setHostAddress(const QHostAddress &address) +{ + m_hostAddresss = address; +} + +QHostAddress SunnyWebBox::hostAddress() +{ + return m_hostAddresss; +} + void SunnyWebBox::onMessageReceived(const QHostAddress &address, int messageId, const QString &messageType, const QVariantMap &result) { if (address != m_hostAddresss) { diff --git a/sma/sunnywebbox.h b/sma/sunnywebbox.h index 9b6b2fe5..aae27eb0 100644 --- a/sma/sunnywebbox.h +++ b/sma/sunnywebbox.h @@ -83,7 +83,7 @@ public: int getParameters(const QStringList &deviceKeys); //Returns the parameter values of up to 5 devices int setParameters(const QString &deviceKeys, const QHash &channels); //Sets parameter values - void setHostAddress(); + void setHostAddress(const QHostAddress &address); QHostAddress hostAddress(); private: @@ -92,7 +92,6 @@ private: SunnyWebBoxCommunication *m_communication = nullptr; public slots: - void onDatagramReceived(const QByteArray &data); void onMessageReceived(const QHostAddress &address, int messageId, const QString &messageType, const QVariantMap &result); signals: From 74f9f2889543c3be8e674556961ec1e39e7b7a93 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 18 Aug 2020 11:54:59 +0200 Subject: [PATCH 09/34] fixed missing symbols --- sma/integrationpluginsma.cpp | 1 + sma/integrationpluginsma.h | 2 +- sma/sunnywebbox.cpp | 20 +++++++++++++++++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 19f3e6d2..01eb5bac 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -199,6 +199,7 @@ void IntegrationPluginSma::onDevicesReceived(int messageId, QListid()); descriptors.append(descriptor); } diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 76bc0324..523ae9e6 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -70,7 +70,7 @@ private: QHash m_asyncSetup; SunnyWebBoxCommunication *m_sunnyWebBoxCommunication = nullptr; - SunnyWebBox * createSunnyWebBoxConnection(Thing *thing); + SunnyWebBox *createSunnyWebBoxConnection(Thing *thing); void setupChild(ThingSetupInfo *info, Thing *parentThing); void getData(Thing *thing); }; diff --git a/sma/sunnywebbox.cpp b/sma/sunnywebbox.cpp index 7bd9f3c1..85da7f9b 100644 --- a/sma/sunnywebbox.cpp +++ b/sma/sunnywebbox.cpp @@ -63,17 +63,31 @@ int SunnyWebBox::getProcessDataChannels(const QString &deviceId) int SunnyWebBox::getProcessData(const QStringList &deviceKeys) { - Q_UNUSED(deviceKeys) + QJsonObject params; + params["device"] = deviceKeys.first(); //TODO + return m_communication->sendMessage(m_hostAddresss, "GetProcessData", params); } int SunnyWebBox::getParameterChannels(const QString &deviceKey) { - Q_UNUSED(deviceKey) + QJsonObject paramsObj; + QJsonArray devicesArray; + QJsonObject deviceObj; + deviceObj["key"] = deviceKey; + devicesArray.append(deviceObj); + paramsObj["devices"] = devicesArray; + return m_communication->sendMessage(m_hostAddresss, "GetParameterChannels", paramsObj); } int SunnyWebBox::getParameters(const QStringList &deviceKeys) { - Q_UNUSED(deviceKeys) + QJsonObject paramsObj; + QJsonArray devicesArray; + QJsonObject deviceObj; + deviceObj["key"] = deviceKeys.first(); //TODO + devicesArray.append(deviceObj); + paramsObj["devices"] = devicesArray; + return m_communication->sendMessage(m_hostAddresss, "GetParameter", paramsObj); } int SunnyWebBox::setParameters(const QString &deviceKey, const QHash &channels) From 9e3cf02e3ceae894c1c97cf7a622e3be226c2ac7 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 18 Aug 2020 12:28:23 +0200 Subject: [PATCH 10/34] added async setup --- sma/integrationpluginsma.cpp | 29 ++++++++++++++++++++++------- sma/integrationpluginsma.h | 3 ++- sma/sunnywebboxcommunication.cpp | 4 ++++ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 01eb5bac..0e880ff6 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -48,7 +48,7 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) connect(discovery, &Discovery::finished, info, [this, info](const QList &hosts) { qCDebug(dcSma()) << "Discovery finished. Found" << hosts.count() << "devices"; foreach (const Host &host, hosts) { - if (host.hostName().contains("SMA Regelsysteme Gmbh")){ + if (host.hostName().contains("SMA")){ ThingDescriptor descriptor(info->thingClassId(), host.hostName(), host.address() + " (" + host.macAddress() + ")"); foreach (Thing *existingThing, myThings()) { @@ -67,7 +67,6 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) } info->finish(Thing::ThingErrorNoError); }); - info->finish(Thing::ThingErrorNoError); } } @@ -95,8 +94,14 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) } } SunnyWebBox *sunnyWebBox = new SunnyWebBox(m_sunnyWebBoxCommunication, QHostAddress(thing->paramValue(sunnyWebBoxThingHostParamTypeId).toString()), this); + connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, this, &IntegrationPluginSma::onPlantOverviewReceived); + connect(sunnyWebBox, &SunnyWebBox::devicesReceived, this, &IntegrationPluginSma::onDevicesReceived); + connect(sunnyWebBox, &SunnyWebBox::processDataReceived, this, &IntegrationPluginSma::onProcessDataReceived); + connect(sunnyWebBox, &SunnyWebBox::parameterChannelsReceived, this, &IntegrationPluginSma::onParameterChannelsReceived); m_sunnyWebBoxes.insert(thing, sunnyWebBox); - //TODO m_asyncSetup + connect(info, &ThingSetupInfo::aborted, this, [thing, this] { m_sunnyWebBoxes.remove(thing);}); + int requestId = sunnyWebBox->getPlantOverview(); + m_asyncSetup.insert(requestId, info); return info->finish(Thing::ThingErrorNoError); } else if (thing->thingClassId() == inverterThingClassId) { @@ -142,9 +147,11 @@ void IntegrationPluginSma::executeAction(ThingActionInfo *info) if (!sunnyWebBox) return; if (action.actionTypeId() == sunnyWebBoxSearchDevicesActionTypeId) { - sunnyWebBox->getDevices(); + int requestId = sunnyWebBox->getDevices(); + m_asyncActions.insert(requestId, info); + connect(info, &ThingActionInfo::aborted, info, [requestId, this] {m_asyncActions.remove(requestId);}); } else { - //Unhandled actionTypeId + Q_ASSERT_X(false, "executeAction", QString("Unhandled actionTypeId: %1").arg(action.actionTypeId().toString()).toUtf8()); } } else { Q_ASSERT_X(false, "executeAction", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); @@ -172,12 +179,16 @@ void IntegrationPluginSma::onRefreshTimer() void IntegrationPluginSma::onPlantOverviewReceived(int messageId, SunnyWebBox::Overview overview) { - Q_UNUSED(messageId) + if (m_asyncSetup.contains(messageId)) { + ThingSetupInfo *info = m_asyncSetup.value(messageId); + info->finish(Thing::ThingErrorNoError); + } Thing *thing = m_sunnyWebBoxes.key(static_cast(sender())); if (!thing) return; + qCDebug(dcSma()) << "Plant overview received" << overview.status; thing->setStateValue(sunnyWebBoxConnectedStateTypeId, true); thing->setStateValue(sunnyWebBoxCurrentPowerStateTypeId, overview.power); thing->setStateValue(sunnyWebBoxDayEnergyStateTypeId, overview.dailyYield); @@ -191,7 +202,11 @@ void IntegrationPluginSma::onPlantOverviewReceived(int messageId, SunnyWebBox::O void IntegrationPluginSma::onDevicesReceived(int messageId, QList devices) { - Q_UNUSED(messageId) + if (m_asyncActions.contains(messageId)) { + ThingActionInfo *info = m_asyncActions.value(messageId); + info->finish(Thing::ThingErrorNoError); + } + Thing *thing = m_sunnyWebBoxes.key(static_cast(sender())); if (!thing) diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 523ae9e6..7bcf1695 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -67,7 +67,8 @@ private slots: private: PluginTimer *m_refreshTimer = nullptr; QHash m_sunnyWebBoxes; - QHash m_asyncSetup; + QHash m_asyncSetup; + QHash m_asyncActions; SunnyWebBoxCommunication *m_sunnyWebBoxCommunication = nullptr; SunnyWebBox *createSunnyWebBoxConnection(Thing *thing); diff --git a/sma/sunnywebboxcommunication.cpp b/sma/sunnywebboxcommunication.cpp index 8edce319..b3ae8449 100644 --- a/sma/sunnywebboxcommunication.cpp +++ b/sma/sunnywebboxcommunication.cpp @@ -55,6 +55,7 @@ SunnyWebBoxCommunication::SunnyWebBoxCommunication(QObject *parent) : QObject(pa qCWarning(dcSma()) << "Error reading pending datagram"; } } + datagramReceived(address, data); }); } @@ -68,6 +69,7 @@ int SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QSt obj["proc"] = procedure; obj["id"] = requestId; obj["format"] = "JSON"; + qCDebug(dcSma()) << "Send message" << doc.toJson() << address << m_port; m_udpSocket->writeDatagram(doc.toJson(), address, m_port); return requestId; } @@ -85,12 +87,14 @@ int SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QSt if (!params.isEmpty()) { obj.insert("params", params); } + qCDebug(dcSma()) << "Send message" << doc.toJson() << address << m_port; m_udpSocket->writeDatagram(doc.toJson(), address, m_port); return requestId; } void SunnyWebBoxCommunication::datagramReceived(const QHostAddress &address, const QByteArray &data) { + qCDebug(dcSma()) << "Datagram received" << data; QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(data, &error); if (error.error != QJsonParseError::NoError) { From f3481b84d3f380ee0e14084b4077d4838fb67d0c Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 18 Aug 2020 13:12:07 +0200 Subject: [PATCH 11/34] fixed udp port --- sma/sunnywebboxcommunication.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sma/sunnywebboxcommunication.cpp b/sma/sunnywebboxcommunication.cpp index b3ae8449..0d1c7d54 100644 --- a/sma/sunnywebboxcommunication.cpp +++ b/sma/sunnywebboxcommunication.cpp @@ -37,7 +37,7 @@ SunnyWebBoxCommunication::SunnyWebBoxCommunication(QObject *parent) : QObject(parent) { m_udpSocket = new QUdpSocket(this); - m_udpSocket->bind(QHostAddress::LocalHost, m_port); + m_udpSocket->bind(QHostAddress::Any, m_port); connect(m_udpSocket, &QUdpSocket::stateChanged, this, [this](QAbstractSocket::SocketState state) { emit socketConnected(state == QAbstractSocket::SocketState::ConnectedState); @@ -48,12 +48,10 @@ SunnyWebBoxCommunication::SunnyWebBoxCommunication(QObject *parent) : QObject(pa QHostAddress address; quint16 port; QByteArray data; - while (m_udpSocket->hasPendingDatagrams()) { - qCDebug(dcSma()) << "Received datagram"; - int receivedBytes = m_udpSocket->readDatagram(data.data(), 1000, &address, &port); - if (receivedBytes == -1) { - qCWarning(dcSma()) << "Error reading pending datagram"; - } + data.resize(m_udpSocket->pendingDatagramSize()); + int receivedBytes = m_udpSocket->readDatagram(data.data(), data.size(), &address, &port); + if (receivedBytes == -1) { + qCWarning(dcSma()) << "Error reading pending datagram"; } datagramReceived(address, data); }); @@ -69,6 +67,7 @@ int SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QSt obj["proc"] = procedure; obj["id"] = requestId; obj["format"] = "JSON"; + doc.setObject(obj); qCDebug(dcSma()) << "Send message" << doc.toJson() << address << m_port; m_udpSocket->writeDatagram(doc.toJson(), address, m_port); return requestId; @@ -87,6 +86,7 @@ int SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QSt if (!params.isEmpty()) { obj.insert("params", params); } + doc.setObject(obj); qCDebug(dcSma()) << "Send message" << doc.toJson() << address << m_port; m_udpSocket->writeDatagram(doc.toJson(), address, m_port); return requestId; From 837751c06c2616c5bfa43891d1ae2192783872c6 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 18 Aug 2020 14:58:29 +0200 Subject: [PATCH 12/34] moved from qrand to Quuid --- sma/integrationpluginsma.cpp | 12 ++++----- sma/integrationpluginsma.h | 12 ++++----- sma/sunnywebbox.cpp | 18 ++++++------- sma/sunnywebbox.h | 28 ++++++++++---------- sma/sunnywebboxcommunication.cpp | 45 +++++++++++++++++++------------- sma/sunnywebboxcommunication.h | 7 ++--- 6 files changed, 66 insertions(+), 56 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 0e880ff6..0a79e200 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -100,7 +100,7 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) connect(sunnyWebBox, &SunnyWebBox::parameterChannelsReceived, this, &IntegrationPluginSma::onParameterChannelsReceived); m_sunnyWebBoxes.insert(thing, sunnyWebBox); connect(info, &ThingSetupInfo::aborted, this, [thing, this] { m_sunnyWebBoxes.remove(thing);}); - int requestId = sunnyWebBox->getPlantOverview(); + QUuid requestId = sunnyWebBox->getPlantOverview(); m_asyncSetup.insert(requestId, info); return info->finish(Thing::ThingErrorNoError); @@ -147,7 +147,7 @@ void IntegrationPluginSma::executeAction(ThingActionInfo *info) if (!sunnyWebBox) return; if (action.actionTypeId() == sunnyWebBoxSearchDevicesActionTypeId) { - int requestId = sunnyWebBox->getDevices(); + QUuid requestId = sunnyWebBox->getDevices(); m_asyncActions.insert(requestId, info); connect(info, &ThingActionInfo::aborted, info, [requestId, this] {m_asyncActions.remove(requestId);}); } else { @@ -177,7 +177,7 @@ void IntegrationPluginSma::onRefreshTimer() } } -void IntegrationPluginSma::onPlantOverviewReceived(int messageId, SunnyWebBox::Overview overview) +void IntegrationPluginSma::onPlantOverviewReceived(const QUuid &messageId, SunnyWebBox::Overview overview) { if (m_asyncSetup.contains(messageId)) { ThingSetupInfo *info = m_asyncSetup.value(messageId); @@ -200,7 +200,7 @@ void IntegrationPluginSma::onPlantOverviewReceived(int messageId, SunnyWebBox::O } } -void IntegrationPluginSma::onDevicesReceived(int messageId, QList devices) +void IntegrationPluginSma::onDevicesReceived(const QUuid &messageId, QList devices) { if (m_asyncActions.contains(messageId)) { ThingActionInfo *info = m_asyncActions.value(messageId); @@ -221,7 +221,7 @@ void IntegrationPluginSma::onDevicesReceived(int messageId, QList &channels) +void IntegrationPluginSma::onProcessDataReceived(const QUuid &messageId, const QString &deviceKey, const QHash &channels) { Q_UNUSED(messageId) Thing *thing = m_sunnyWebBoxes.key(static_cast(sender())); @@ -240,7 +240,7 @@ void IntegrationPluginSma::onProcessDataReceived(int messageId, const QString &d } } -void IntegrationPluginSma::onParameterChannelsReceived(int messageId, const QString &deviceKey, QStringList parameterChannels) +void IntegrationPluginSma::onParameterChannelsReceived(const QUuid &messageId, const QString &deviceKey, QStringList parameterChannels) { Q_UNUSED(messageId) diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 7bcf1695..01185063 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -59,16 +59,16 @@ public: private slots: void onRefreshTimer(); - void onPlantOverviewReceived(int messageId, SunnyWebBox::Overview overview); - void onDevicesReceived(int messageId, QList devices); - void onProcessDataReceived(int messageId, const QString &deviceKey, const QHash &channels); - void onParameterChannelsReceived(int messageId, const QString &deviceKey, QStringList parameterChannels); + void onPlantOverviewReceived(const QUuid &messageId, SunnyWebBox::Overview overview); + void onDevicesReceived(const QUuid &messageId, QList devices); + void onProcessDataReceived(const QUuid &messageId, const QString &deviceKey, const QHash &channels); + void onParameterChannelsReceived(const QUuid &messageId, const QString &deviceKey, QStringList parameterChannels); private: PluginTimer *m_refreshTimer = nullptr; QHash m_sunnyWebBoxes; - QHash m_asyncSetup; - QHash m_asyncActions; + QHash m_asyncSetup; + QHash m_asyncActions; SunnyWebBoxCommunication *m_sunnyWebBoxCommunication = nullptr; SunnyWebBox *createSunnyWebBoxConnection(Thing *thing); diff --git a/sma/sunnywebbox.cpp b/sma/sunnywebbox.cpp index 85da7f9b..ad4871c4 100644 --- a/sma/sunnywebbox.cpp +++ b/sma/sunnywebbox.cpp @@ -44,31 +44,31 @@ SunnyWebBox::SunnyWebBox(SunnyWebBoxCommunication *communication, const QHostAdd connect(m_communication, &SunnyWebBoxCommunication::messageReceived, this, &SunnyWebBox::onMessageReceived); } -int SunnyWebBox::getPlantOverview() +QUuid SunnyWebBox::getPlantOverview() { return m_communication->sendMessage(m_hostAddresss, "GetPlantOverview"); } -int SunnyWebBox::getDevices() +QUuid SunnyWebBox::getDevices() { return m_communication->sendMessage(m_hostAddresss, "GetDevices"); } -int SunnyWebBox::getProcessDataChannels(const QString &deviceId) +QUuid SunnyWebBox::getProcessDataChannels(const QString &deviceId) { QJsonObject params; params["device"] = deviceId; return m_communication->sendMessage(m_hostAddresss, "GetProcessDataChannels", params); } -int SunnyWebBox::getProcessData(const QStringList &deviceKeys) +QUuid SunnyWebBox::getProcessData(const QStringList &deviceKeys) { QJsonObject params; - params["device"] = deviceKeys.first(); //TODO + params["device"] = deviceKeys.first(); return m_communication->sendMessage(m_hostAddresss, "GetProcessData", params); } -int SunnyWebBox::getParameterChannels(const QString &deviceKey) +QUuid SunnyWebBox::getParameterChannels(const QString &deviceKey) { QJsonObject paramsObj; QJsonArray devicesArray; @@ -79,7 +79,7 @@ int SunnyWebBox::getParameterChannels(const QString &deviceKey) return m_communication->sendMessage(m_hostAddresss, "GetParameterChannels", paramsObj); } -int SunnyWebBox::getParameters(const QStringList &deviceKeys) +QUuid SunnyWebBox::getParameters(const QStringList &deviceKeys) { QJsonObject paramsObj; QJsonArray devicesArray; @@ -90,7 +90,7 @@ int SunnyWebBox::getParameters(const QStringList &deviceKeys) return m_communication->sendMessage(m_hostAddresss, "GetParameter", paramsObj); } -int SunnyWebBox::setParameters(const QString &deviceKey, const QHash &channels) +QUuid SunnyWebBox::setParameters(const QString &deviceKey, const QHash &channels) { QJsonObject paramsObj; QJsonArray devicesArray; @@ -119,7 +119,7 @@ QHostAddress SunnyWebBox::hostAddress() return m_hostAddresss; } -void SunnyWebBox::onMessageReceived(const QHostAddress &address, int messageId, const QString &messageType, const QVariantMap &result) +void SunnyWebBox::onMessageReceived(const QHostAddress &address, const QUuid &messageId, const QString &messageType, const QVariantMap &result) { if (address != m_hostAddresss) { return; diff --git a/sma/sunnywebbox.h b/sma/sunnywebbox.h index aae27eb0..608771d2 100644 --- a/sma/sunnywebbox.h +++ b/sma/sunnywebbox.h @@ -75,13 +75,13 @@ public: explicit SunnyWebBox(SunnyWebBoxCommunication *communication, const QHostAddress &hostAddress, QObject *parrent = 0); - int getPlantOverview(); // Returns an object with the following plant data: PAC, E-TODAY, E-TOTAL, MODE, ERROR - int getDevices(); //Returns a hierarchical list of all detected plant devices. - int getProcessDataChannels(const QString &deviceKey); //Returns a list with the meta names of the available process data channels for a particular device type. - int getProcessData(const QStringList &deviceKeys); //Returns process data for up to 5 devices per request. - int getParameterChannels(const QString &deviceKey); //Returns a list with the meta names of the available parameter channels for a particular device type - int getParameters(const QStringList &deviceKeys); //Returns the parameter values of up to 5 devices - int setParameters(const QString &deviceKeys, const QHash &channels); //Sets parameter values + QUuid getPlantOverview(); // Returns an object with the following plant data: PAC, E-TODAY, E-TOTAL, MODE, ERROR + QUuid getDevices(); //Returns a hierarchical list of all detected plant devices. + QUuid getProcessDataChannels(const QString &deviceKey); //Returns a list with the meta names of the available process data channels for a particular device type. + QUuid getProcessData(const QStringList &deviceKeys); //Returns process data for up to 5 devices per request. + QUuid getParameterChannels(const QString &deviceKey); //Returns a list with the meta names of the available parameter channels for a particular device type + QUuid getParameters(const QStringList &deviceKeys); //Returns the parameter values of up to 5 devices + QUuid setParameters(const QString &deviceKeys, const QHash &channels); //Sets parameter values void setHostAddress(const QHostAddress &address); QHostAddress hostAddress(); @@ -92,17 +92,17 @@ private: SunnyWebBoxCommunication *m_communication = nullptr; public slots: - void onMessageReceived(const QHostAddress &address, int messageId, const QString &messageType, const QVariantMap &result); + void onMessageReceived(const QHostAddress &address, const QUuid &messageId, const QString &messageType, const QVariantMap &result); signals: void connectedChanged(bool connected); - void plantOverviewReceived(int messageId, Overview overview); - void devicesReceived(int messageId, QList devices); - void processDataChannelsReceived(int messageId, const QString &deviceKey, QStringList processDataChanels); - void processDataReceived(int messageId, const QString &deviceKey, const QHash &channels); - void parameterChannelsReceived(int messageId, const QString &deviceKey, QStringList parameterChannels); - void parametersReceived(int messageId, const QString &deviceKey, const QList ¶meters); + void plantOverviewReceived(const QUuid &messageId, Overview overview); + void devicesReceived(const QUuid &messageId, QList devices); + void processDataChannelsReceived(const QUuid &messageId, const QString &deviceKey, QStringList processDataChanels); + void processDataReceived(const QUuid &messageId, const QString &deviceKey, const QHash &channels); + void parameterChannelsReceived(const QUuid &messageId, const QString &deviceKey, QStringList parameterChannels); + void parametersReceived(const QUuid &messageId, const QString &deviceKey, const QList ¶meters); }; #endif // SUNNYWEBBOX_H diff --git a/sma/sunnywebboxcommunication.cpp b/sma/sunnywebboxcommunication.cpp index 0d1c7d54..de7ef31d 100644 --- a/sma/sunnywebboxcommunication.cpp +++ b/sma/sunnywebboxcommunication.cpp @@ -57,46 +57,55 @@ SunnyWebBoxCommunication::SunnyWebBoxCommunication(QObject *parent) : QObject(pa }); } -int SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QString &procedure) +QUuid SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QString &procedure) { - int requestId = qrand(); + QUuid requestId = QUuid::createUuid(); QJsonDocument doc; QJsonObject obj; - obj["version"] = "1.0"; - obj["proc"] = procedure; - obj["id"] = requestId; obj["format"] = "JSON"; + obj["id"] = requestId.toString().remove('{').remove('}'); + obj["proc"] = procedure; + obj["version"] = "1.0"; doc.setObject(obj); - qCDebug(dcSma()) << "Send message" << doc.toJson() << address << m_port; - m_udpSocket->writeDatagram(doc.toJson(), address, m_port); + QByteArray data = doc.toJson(QJsonDocument::JsonFormat::Compact); + qCDebug(dcSma()) << "Send message" << data << address << m_port; + m_udpSocket->writeDatagram(data, address, m_port); return requestId; } -int SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms) +QUuid SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms) { - int requestId = qrand(); + QUuid requestId = QUuid::createUuid(); QJsonDocument doc; QJsonObject obj; - obj["version"] = "1.0"; - obj["proc"] = procedure; - obj["id"] = requestId; - obj["format"] = "JSON"; + if (!params.isEmpty()) { obj.insert("params", params); } + obj["format"] = "JSON"; + obj["id"] = requestId.toString().remove('{').remove('}'); + obj["proc"] = procedure; + obj["version"] = "1.0"; doc.setObject(obj); - qCDebug(dcSma()) << "Send message" << doc.toJson() << address << m_port; - m_udpSocket->writeDatagram(doc.toJson(), address, m_port); + QByteArray data = doc.toJson(QJsonDocument::JsonFormat::Compact); + qCDebug(dcSma()) << "Send message" << data << address << m_port; + m_udpSocket->writeDatagram(data, address, m_port); return requestId; } void SunnyWebBoxCommunication::datagramReceived(const QHostAddress &address, const QByteArray &data) { - qCDebug(dcSma()) << "Datagram received" << data; + QList arrayList = data.split('\x00'); + QByteArray cleanData; + Q_FOREACH(QByteArray i, arrayList) { + //Removing all '\0' characters + cleanData.append(i); + } + qCDebug(dcSma()) << "Datagram received" << cleanData; QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(data, &error); + QJsonDocument doc = QJsonDocument::fromJson(cleanData, &error); if (error.error != QJsonParseError::NoError) { qCWarning(dcSma()) << "Could not parse JSON" << error.errorString(); return; @@ -113,7 +122,7 @@ void SunnyWebBoxCommunication::datagramReceived(const QHostAddress &address, con if (map.contains("proc") && map.contains("result")) { QString requestType = map["proc"].toString(); - int requestId = map["id"].toInt(); + QUuid requestId = QUuid(map["id"].toString()); QVariantMap result = map.value("result").toMap(); emit messageReceived(address, requestId, requestType, result); } else { diff --git a/sma/sunnywebboxcommunication.h b/sma/sunnywebboxcommunication.h index cec4175c..8ceb6ee9 100644 --- a/sma/sunnywebboxcommunication.h +++ b/sma/sunnywebboxcommunication.h @@ -33,6 +33,7 @@ #include #include +#include class SunnyWebBoxCommunication : public QObject { @@ -40,8 +41,8 @@ class SunnyWebBoxCommunication : public QObject public: explicit SunnyWebBoxCommunication(QObject *parent = nullptr); - int sendMessage(const QHostAddress &address, const QString &procedure); - int sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms); + QUuid sendMessage(const QHostAddress &address, const QString &procedure); + QUuid sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms); private: int m_port = 34268; @@ -51,7 +52,7 @@ private: signals: void socketConnected(bool connected); - void messageReceived(const QHostAddress &address, int messageId, const QString &messageType, const QVariantMap &result); + void messageReceived(const QHostAddress &address, QUuid messageId, const QString &messageType, const QVariantMap &result); }; From f2a401aedf363cbc31f71507e4422c28dfeaf299 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 18 Aug 2020 15:06:46 +0200 Subject: [PATCH 13/34] moved from qrand to Quuid --- sma/integrationpluginsma.cpp | 12 ++++++------ sma/integrationpluginsma.h | 12 ++++++------ sma/sunnywebbox.cpp | 16 ++++++++-------- sma/sunnywebbox.h | 28 ++++++++++++++-------------- sma/sunnywebboxcommunication.cpp | 15 ++++++++------- sma/sunnywebboxcommunication.h | 7 +++---- 6 files changed, 45 insertions(+), 45 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 0a79e200..399046e3 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -100,7 +100,7 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) connect(sunnyWebBox, &SunnyWebBox::parameterChannelsReceived, this, &IntegrationPluginSma::onParameterChannelsReceived); m_sunnyWebBoxes.insert(thing, sunnyWebBox); connect(info, &ThingSetupInfo::aborted, this, [thing, this] { m_sunnyWebBoxes.remove(thing);}); - QUuid requestId = sunnyWebBox->getPlantOverview(); + QString requestId = sunnyWebBox->getPlantOverview(); m_asyncSetup.insert(requestId, info); return info->finish(Thing::ThingErrorNoError); @@ -147,7 +147,7 @@ void IntegrationPluginSma::executeAction(ThingActionInfo *info) if (!sunnyWebBox) return; if (action.actionTypeId() == sunnyWebBoxSearchDevicesActionTypeId) { - QUuid requestId = sunnyWebBox->getDevices(); + QString requestId = sunnyWebBox->getDevices(); m_asyncActions.insert(requestId, info); connect(info, &ThingActionInfo::aborted, info, [requestId, this] {m_asyncActions.remove(requestId);}); } else { @@ -177,7 +177,7 @@ void IntegrationPluginSma::onRefreshTimer() } } -void IntegrationPluginSma::onPlantOverviewReceived(const QUuid &messageId, SunnyWebBox::Overview overview) +void IntegrationPluginSma::onPlantOverviewReceived(const QString &messageId, SunnyWebBox::Overview overview) { if (m_asyncSetup.contains(messageId)) { ThingSetupInfo *info = m_asyncSetup.value(messageId); @@ -200,7 +200,7 @@ void IntegrationPluginSma::onPlantOverviewReceived(const QUuid &messageId, Sunny } } -void IntegrationPluginSma::onDevicesReceived(const QUuid &messageId, QList devices) +void IntegrationPluginSma::onDevicesReceived(const QString &messageId, QList devices) { if (m_asyncActions.contains(messageId)) { ThingActionInfo *info = m_asyncActions.value(messageId); @@ -221,7 +221,7 @@ void IntegrationPluginSma::onDevicesReceived(const QUuid &messageId, QList &channels) +void IntegrationPluginSma::onProcessDataReceived(const QString &messageId, const QString &deviceKey, const QHash &channels) { Q_UNUSED(messageId) Thing *thing = m_sunnyWebBoxes.key(static_cast(sender())); @@ -240,7 +240,7 @@ void IntegrationPluginSma::onProcessDataReceived(const QUuid &messageId, const Q } } -void IntegrationPluginSma::onParameterChannelsReceived(const QUuid &messageId, const QString &deviceKey, QStringList parameterChannels) +void IntegrationPluginSma::onParameterChannelsReceived(const QString &messageId, const QString &deviceKey, QStringList parameterChannels) { Q_UNUSED(messageId) diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 01185063..8d445d31 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -59,16 +59,16 @@ public: private slots: void onRefreshTimer(); - void onPlantOverviewReceived(const QUuid &messageId, SunnyWebBox::Overview overview); - void onDevicesReceived(const QUuid &messageId, QList devices); - void onProcessDataReceived(const QUuid &messageId, const QString &deviceKey, const QHash &channels); - void onParameterChannelsReceived(const QUuid &messageId, const QString &deviceKey, QStringList parameterChannels); + void onPlantOverviewReceived(const QString &messageId, SunnyWebBox::Overview overview); + void onDevicesReceived(const QString &messageId, QList devices); + void onProcessDataReceived(const QString &messageId, const QString &deviceKey, const QHash &channels); + void onParameterChannelsReceived(const QString &messageId, const QString &deviceKey, QStringList parameterChannels); private: PluginTimer *m_refreshTimer = nullptr; QHash m_sunnyWebBoxes; - QHash m_asyncSetup; - QHash m_asyncActions; + QHash m_asyncSetup; + QHash m_asyncActions; SunnyWebBoxCommunication *m_sunnyWebBoxCommunication = nullptr; SunnyWebBox *createSunnyWebBoxConnection(Thing *thing); diff --git a/sma/sunnywebbox.cpp b/sma/sunnywebbox.cpp index ad4871c4..c687affb 100644 --- a/sma/sunnywebbox.cpp +++ b/sma/sunnywebbox.cpp @@ -44,31 +44,31 @@ SunnyWebBox::SunnyWebBox(SunnyWebBoxCommunication *communication, const QHostAdd connect(m_communication, &SunnyWebBoxCommunication::messageReceived, this, &SunnyWebBox::onMessageReceived); } -QUuid SunnyWebBox::getPlantOverview() +QString SunnyWebBox::getPlantOverview() { return m_communication->sendMessage(m_hostAddresss, "GetPlantOverview"); } -QUuid SunnyWebBox::getDevices() +QString SunnyWebBox::getDevices() { return m_communication->sendMessage(m_hostAddresss, "GetDevices"); } -QUuid SunnyWebBox::getProcessDataChannels(const QString &deviceId) +QString SunnyWebBox::getProcessDataChannels(const QString &deviceId) { QJsonObject params; params["device"] = deviceId; return m_communication->sendMessage(m_hostAddresss, "GetProcessDataChannels", params); } -QUuid SunnyWebBox::getProcessData(const QStringList &deviceKeys) +QString SunnyWebBox::getProcessData(const QStringList &deviceKeys) { QJsonObject params; params["device"] = deviceKeys.first(); return m_communication->sendMessage(m_hostAddresss, "GetProcessData", params); } -QUuid SunnyWebBox::getParameterChannels(const QString &deviceKey) +QString SunnyWebBox::getParameterChannels(const QString &deviceKey) { QJsonObject paramsObj; QJsonArray devicesArray; @@ -79,7 +79,7 @@ QUuid SunnyWebBox::getParameterChannels(const QString &deviceKey) return m_communication->sendMessage(m_hostAddresss, "GetParameterChannels", paramsObj); } -QUuid SunnyWebBox::getParameters(const QStringList &deviceKeys) +QString SunnyWebBox::getParameters(const QStringList &deviceKeys) { QJsonObject paramsObj; QJsonArray devicesArray; @@ -90,7 +90,7 @@ QUuid SunnyWebBox::getParameters(const QStringList &deviceKeys) return m_communication->sendMessage(m_hostAddresss, "GetParameter", paramsObj); } -QUuid SunnyWebBox::setParameters(const QString &deviceKey, const QHash &channels) +QString SunnyWebBox::setParameters(const QString &deviceKey, const QHash &channels) { QJsonObject paramsObj; QJsonArray devicesArray; @@ -119,7 +119,7 @@ QHostAddress SunnyWebBox::hostAddress() return m_hostAddresss; } -void SunnyWebBox::onMessageReceived(const QHostAddress &address, const QUuid &messageId, const QString &messageType, const QVariantMap &result) +void SunnyWebBox::onMessageReceived(const QHostAddress &address, const QString &messageId, const QString &messageType, const QVariantMap &result) { if (address != m_hostAddresss) { return; diff --git a/sma/sunnywebbox.h b/sma/sunnywebbox.h index 608771d2..ef20e0c4 100644 --- a/sma/sunnywebbox.h +++ b/sma/sunnywebbox.h @@ -75,13 +75,13 @@ public: explicit SunnyWebBox(SunnyWebBoxCommunication *communication, const QHostAddress &hostAddress, QObject *parrent = 0); - QUuid getPlantOverview(); // Returns an object with the following plant data: PAC, E-TODAY, E-TOTAL, MODE, ERROR - QUuid getDevices(); //Returns a hierarchical list of all detected plant devices. - QUuid getProcessDataChannels(const QString &deviceKey); //Returns a list with the meta names of the available process data channels for a particular device type. - QUuid getProcessData(const QStringList &deviceKeys); //Returns process data for up to 5 devices per request. - QUuid getParameterChannels(const QString &deviceKey); //Returns a list with the meta names of the available parameter channels for a particular device type - QUuid getParameters(const QStringList &deviceKeys); //Returns the parameter values of up to 5 devices - QUuid setParameters(const QString &deviceKeys, const QHash &channels); //Sets parameter values + QString getPlantOverview(); // Returns an object with the following plant data: PAC, E-TODAY, E-TOTAL, MODE, ERROR + QString getDevices(); //Returns a hierarchical list of all detected plant devices. + QString getProcessDataChannels(const QString &deviceKey); //Returns a list with the meta names of the available process data channels for a particular device type. + QString getProcessData(const QStringList &deviceKeys); //Returns process data for up to 5 devices per request. + QString getParameterChannels(const QString &deviceKey); //Returns a list with the meta names of the available parameter channels for a particular device type + QString getParameters(const QStringList &deviceKeys); //Returns the parameter values of up to 5 devices + QString setParameters(const QString &deviceKeys, const QHash &channels); //Sets parameter values void setHostAddress(const QHostAddress &address); QHostAddress hostAddress(); @@ -92,17 +92,17 @@ private: SunnyWebBoxCommunication *m_communication = nullptr; public slots: - void onMessageReceived(const QHostAddress &address, const QUuid &messageId, const QString &messageType, const QVariantMap &result); + void onMessageReceived(const QHostAddress &address, const QString &messageId, const QString &messageType, const QVariantMap &result); signals: void connectedChanged(bool connected); - void plantOverviewReceived(const QUuid &messageId, Overview overview); - void devicesReceived(const QUuid &messageId, QList devices); - void processDataChannelsReceived(const QUuid &messageId, const QString &deviceKey, QStringList processDataChanels); - void processDataReceived(const QUuid &messageId, const QString &deviceKey, const QHash &channels); - void parameterChannelsReceived(const QUuid &messageId, const QString &deviceKey, QStringList parameterChannels); - void parametersReceived(const QUuid &messageId, const QString &deviceKey, const QList ¶meters); + void plantOverviewReceived(const QString &messageId, Overview overview); + void devicesReceived(const QString &messageId, QList devices); + void processDataChannelsReceived(const QString &messageId, const QString &deviceKey, QStringList processDataChanels); + void processDataReceived(const QString &messageId, const QString &deviceKey, const QHash &channels); + void parameterChannelsReceived(const QString &messageId, const QString &deviceKey, QStringList parameterChannels); + void parametersReceived(const QString &messageId, const QString &deviceKey, const QList ¶meters); }; #endif // SUNNYWEBBOX_H diff --git a/sma/sunnywebboxcommunication.cpp b/sma/sunnywebboxcommunication.cpp index de7ef31d..4be38276 100644 --- a/sma/sunnywebboxcommunication.cpp +++ b/sma/sunnywebboxcommunication.cpp @@ -33,6 +33,7 @@ #include "QJsonDocument" #include "QJsonObject" +#include SunnyWebBoxCommunication::SunnyWebBoxCommunication(QObject *parent) : QObject(parent) { @@ -57,14 +58,14 @@ SunnyWebBoxCommunication::SunnyWebBoxCommunication(QObject *parent) : QObject(pa }); } -QUuid SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QString &procedure) +QString SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QString &procedure) { - QUuid requestId = QUuid::createUuid(); + QString requestId = QUuid::createUuid().toString().remove('{').left(14); QJsonDocument doc; QJsonObject obj; obj["format"] = "JSON"; - obj["id"] = requestId.toString().remove('{').remove('}'); + obj["id"] = requestId; obj["proc"] = procedure; obj["version"] = "1.0"; doc.setObject(obj); @@ -74,9 +75,9 @@ QUuid SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const Q return requestId; } -QUuid SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms) +QString SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms) { - QUuid requestId = QUuid::createUuid(); + QString requestId = QUuid::createUuid().toString().remove('{').left(14); QJsonDocument doc; QJsonObject obj; @@ -85,7 +86,7 @@ QUuid SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const Q obj.insert("params", params); } obj["format"] = "JSON"; - obj["id"] = requestId.toString().remove('{').remove('}'); + obj["id"] = requestId; obj["proc"] = procedure; obj["version"] = "1.0"; doc.setObject(obj); @@ -122,7 +123,7 @@ void SunnyWebBoxCommunication::datagramReceived(const QHostAddress &address, con if (map.contains("proc") && map.contains("result")) { QString requestType = map["proc"].toString(); - QUuid requestId = QUuid(map["id"].toString()); + QString requestId = map["id"].toString(); QVariantMap result = map.value("result").toMap(); emit messageReceived(address, requestId, requestType, result); } else { diff --git a/sma/sunnywebboxcommunication.h b/sma/sunnywebboxcommunication.h index 8ceb6ee9..c6a72a24 100644 --- a/sma/sunnywebboxcommunication.h +++ b/sma/sunnywebboxcommunication.h @@ -33,7 +33,6 @@ #include #include -#include class SunnyWebBoxCommunication : public QObject { @@ -41,8 +40,8 @@ class SunnyWebBoxCommunication : public QObject public: explicit SunnyWebBoxCommunication(QObject *parent = nullptr); - QUuid sendMessage(const QHostAddress &address, const QString &procedure); - QUuid sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms); + QString sendMessage(const QHostAddress &address, const QString &procedure); + QString sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms); private: int m_port = 34268; @@ -52,7 +51,7 @@ private: signals: void socketConnected(bool connected); - void messageReceived(const QHostAddress &address, QUuid messageId, const QString &messageType, const QVariantMap &result); + void messageReceived(const QHostAddress &address, const QString &messageId, const QString &messageType, const QVariantMap &result); }; From 015349d2e5a4594212033f8dc471d129aa743050 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 18 Aug 2020 15:51:31 +0200 Subject: [PATCH 14/34] added command queue --- sma/integrationpluginsma.cpp | 3 +++ sma/sunnywebboxcommunication.cpp | 37 ++++++++++++++++++++++++++------ sma/sunnywebboxcommunication.h | 1 + 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 399046e3..0ec79dfe 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -148,6 +148,9 @@ void IntegrationPluginSma::executeAction(ThingActionInfo *info) return; if (action.actionTypeId() == sunnyWebBoxSearchDevicesActionTypeId) { QString requestId = sunnyWebBox->getDevices(); + if (requestId.isEmpty()) { + return info->finish(Thing::ThingErrorHardwareNotAvailable); + } m_asyncActions.insert(requestId, info); connect(info, &ThingActionInfo::aborted, info, [requestId, this] {m_asyncActions.remove(requestId);}); } else { diff --git a/sma/sunnywebboxcommunication.cpp b/sma/sunnywebboxcommunication.cpp index 4be38276..08962dcf 100644 --- a/sma/sunnywebboxcommunication.cpp +++ b/sma/sunnywebboxcommunication.cpp @@ -60,10 +60,11 @@ SunnyWebBoxCommunication::SunnyWebBoxCommunication(QObject *parent) : QObject(pa QString SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QString &procedure) { - QString requestId = QUuid::createUuid().toString().remove('{').left(14); + QString requestId = QUuid::createUuid().toString().remove('{').remove('-').left(14); QJsonDocument doc; QJsonObject obj; + obj.insert("params", QJsonObject()); obj["format"] = "JSON"; obj["id"] = requestId; obj["proc"] = procedure; @@ -71,20 +72,27 @@ QString SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const doc.setObject(obj); QByteArray data = doc.toJson(QJsonDocument::JsonFormat::Compact); qCDebug(dcSma()) << "Send message" << data << address << m_port; - m_udpSocket->writeDatagram(data, address, m_port); + if(m_messageQueue.value(address).isEmpty()) { + m_udpSocket->writeDatagram(data, address, m_port); + } else { + if (m_messageQueue[address].length() < 40) { + m_messageQueue[address].append(data); + } else { + qCDebug(dcSma()) << "Message queue overflow"; + return ""; + } + } return requestId; } QString SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms) { - QString requestId = QUuid::createUuid().toString().remove('{').left(14); + QString requestId = QUuid::createUuid().toString().remove('{').remove('-').left(14); QJsonDocument doc; QJsonObject obj; - if (!params.isEmpty()) { - obj.insert("params", params); - } + obj.insert("params", params); obj["format"] = "JSON"; obj["id"] = requestId; obj["proc"] = procedure; @@ -92,12 +100,27 @@ QString SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const doc.setObject(obj); QByteArray data = doc.toJson(QJsonDocument::JsonFormat::Compact); qCDebug(dcSma()) << "Send message" << data << address << m_port; - m_udpSocket->writeDatagram(data, address, m_port); + if(m_messageQueue.value(address).isEmpty()) { + m_udpSocket->writeDatagram(data, address, m_port); + } else { + if (m_messageQueue[address].length() < 40) { + m_messageQueue[address].append(data); + } else { + qCDebug(dcSma()) << "Message queue overflow"; + return ""; + } + } return requestId; } void SunnyWebBoxCommunication::datagramReceived(const QHostAddress &address, const QByteArray &data) { + if(!m_messageQueue.value(address).isEmpty()) { + QByteArray data = m_messageQueue[address].takeFirst(); + qCDebug(dcSma()) << "Send message from queue" << data << address << m_port; + m_udpSocket->writeDatagram(data, address, m_port); + } + QList arrayList = data.split('\x00'); QByteArray cleanData; Q_FOREACH(QByteArray i, arrayList) { diff --git a/sma/sunnywebboxcommunication.h b/sma/sunnywebboxcommunication.h index c6a72a24..d87633c5 100644 --- a/sma/sunnywebboxcommunication.h +++ b/sma/sunnywebboxcommunication.h @@ -46,6 +46,7 @@ public: private: int m_port = 34268; QUdpSocket *m_udpSocket; + QHash> m_messageQueue; void datagramReceived(const QHostAddress &address, const QByteArray &data); From 8a5f32c47971d2aa7ef54ca303280103cca9cd91 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 18 Aug 2020 16:27:41 +0200 Subject: [PATCH 15/34] added command queue --- sma/sunnywebboxcommunication.cpp | 15 ++++++++++----- sma/sunnywebboxcommunication.h | 1 + 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/sma/sunnywebboxcommunication.cpp b/sma/sunnywebboxcommunication.cpp index 08962dcf..6ed22249 100644 --- a/sma/sunnywebboxcommunication.cpp +++ b/sma/sunnywebboxcommunication.cpp @@ -34,6 +34,7 @@ #include "QJsonDocument" #include "QJsonObject" #include +#include SunnyWebBoxCommunication::SunnyWebBoxCommunication(QObject *parent) : QObject(parent) { @@ -72,13 +73,14 @@ QString SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const doc.setObject(obj); QByteArray data = doc.toJson(QJsonDocument::JsonFormat::Compact); qCDebug(dcSma()) << "Send message" << data << address << m_port; - if(m_messageQueue.value(address).isEmpty()) { + if(m_messageResponsePending) { m_udpSocket->writeDatagram(data, address, m_port); + m_messageResponsePending = true; } else { if (m_messageQueue[address].length() < 40) { m_messageQueue[address].append(data); } else { - qCDebug(dcSma()) << "Message queue overflow"; + qCWarning(dcSma()) << "Message queue overflow"; return ""; } } @@ -100,8 +102,9 @@ QString SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const doc.setObject(obj); QByteArray data = doc.toJson(QJsonDocument::JsonFormat::Compact); qCDebug(dcSma()) << "Send message" << data << address << m_port; - if(m_messageQueue.value(address).isEmpty()) { + if(m_messageResponsePending) { m_udpSocket->writeDatagram(data, address, m_port); + m_messageResponsePending = true; } else { if (m_messageQueue[address].length() < 40) { m_messageQueue[address].append(data); @@ -118,9 +121,11 @@ void SunnyWebBoxCommunication::datagramReceived(const QHostAddress &address, con if(!m_messageQueue.value(address).isEmpty()) { QByteArray data = m_messageQueue[address].takeFirst(); qCDebug(dcSma()) << "Send message from queue" << data << address << m_port; - m_udpSocket->writeDatagram(data, address, m_port); + //The interval between two queries should not be less than 30 seconds. + QTimer::singleShot(3000, this, [this, data, address]{m_udpSocket->writeDatagram(data, address, m_port);}); + } else { + m_messageResponsePending = false; } - QList arrayList = data.split('\x00'); QByteArray cleanData; Q_FOREACH(QByteArray i, arrayList) { diff --git a/sma/sunnywebboxcommunication.h b/sma/sunnywebboxcommunication.h index d87633c5..37f42a6d 100644 --- a/sma/sunnywebboxcommunication.h +++ b/sma/sunnywebboxcommunication.h @@ -45,6 +45,7 @@ public: private: int m_port = 34268; + bool m_messageResponsePending = false; QUdpSocket *m_udpSocket; QHash> m_messageQueue; From 1459ab4d8f0fc20e705503267262150a2a69214f Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Tue, 18 Aug 2020 19:03:16 +0200 Subject: [PATCH 16/34] testing http instead of udp --- sma/integrationpluginsma.cpp | 4 ++-- sma/sunnywebboxcommunication.cpp | 37 ++++++++++++++++++++++++++------ sma/sunnywebboxcommunication.h | 5 ++++- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 0ec79dfe..142fa7d5 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -75,11 +75,11 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) Thing *thing = info->thing(); if (!m_sunnyWebBoxCommunication) { - m_sunnyWebBoxCommunication = new SunnyWebBoxCommunication(this); + m_sunnyWebBoxCommunication = new SunnyWebBoxCommunication(hardwareManager()->networkManager(), this); } if (!m_refreshTimer) { - m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(10); + m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(30); connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSma::onRefreshTimer); } diff --git a/sma/sunnywebboxcommunication.cpp b/sma/sunnywebboxcommunication.cpp index 6ed22249..38882fd7 100644 --- a/sma/sunnywebboxcommunication.cpp +++ b/sma/sunnywebboxcommunication.cpp @@ -35,8 +35,12 @@ #include "QJsonObject" #include #include +#include +#include -SunnyWebBoxCommunication::SunnyWebBoxCommunication(QObject *parent) : QObject(parent) +SunnyWebBoxCommunication::SunnyWebBoxCommunication(NetworkAccessManager *networkAccessManager, QObject *parent) : + QObject(parent), + m_networkManager(networkAccessManager) { m_udpSocket = new QUdpSocket(this); m_udpSocket->bind(QHostAddress::Any, m_port); @@ -65,25 +69,43 @@ QString SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QJsonDocument doc; QJsonObject obj; - obj.insert("params", QJsonObject()); obj["format"] = "JSON"; obj["id"] = requestId; obj["proc"] = procedure; obj["version"] = "1.0"; doc.setObject(obj); + + QUrl url; + url.setHost(address.toString()); + url.setPath("/rpc"); + url.setScheme("http"); + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); QByteArray data = doc.toJson(QJsonDocument::JsonFormat::Compact); - qCDebug(dcSma()) << "Send message" << data << address << m_port; - if(m_messageResponsePending) { + + QNetworkReply *reply = m_networkManager->post(request, data); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [this, address, requestId, reply]{ + + //int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QByteArray data = reply->readAll(); + qCDebug(dcSma()) << "Received reply" << data; + datagramReceived(address, data); + }); + + /* if(!m_messageResponsePending) { + qCDebug(dcSma()) << "Send message" << data << address << m_port; m_udpSocket->writeDatagram(data, address, m_port); m_messageResponsePending = true; } else { if (m_messageQueue[address].length() < 40) { + qCDebug(dcSma()) << "Adding message to queue" << data << address << m_port; m_messageQueue[address].append(data); } else { qCWarning(dcSma()) << "Message queue overflow"; return ""; } - } + }*/ return requestId; } @@ -101,12 +123,13 @@ QString SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const obj["version"] = "1.0"; doc.setObject(obj); QByteArray data = doc.toJson(QJsonDocument::JsonFormat::Compact); - qCDebug(dcSma()) << "Send message" << data << address << m_port; - if(m_messageResponsePending) { + if(!m_messageResponsePending) { + qCDebug(dcSma()) << "Send message" << data << address << m_port; m_udpSocket->writeDatagram(data, address, m_port); m_messageResponsePending = true; } else { if (m_messageQueue[address].length() < 40) { + qCDebug(dcSma()) << "Adding message to queue" << data << address << m_port; m_messageQueue[address].append(data); } else { qCDebug(dcSma()) << "Message queue overflow"; diff --git a/sma/sunnywebboxcommunication.h b/sma/sunnywebboxcommunication.h index 37f42a6d..b026f8a1 100644 --- a/sma/sunnywebboxcommunication.h +++ b/sma/sunnywebboxcommunication.h @@ -34,16 +34,19 @@ #include #include +#include "network/networkaccessmanager.h" + class SunnyWebBoxCommunication : public QObject { Q_OBJECT public: - explicit SunnyWebBoxCommunication(QObject *parent = nullptr); + explicit SunnyWebBoxCommunication(NetworkAccessManager *networkAccessManager, QObject *parent = nullptr); QString sendMessage(const QHostAddress &address, const QString &procedure); QString sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms); private: + NetworkAccessManager *m_networkManager; int m_port = 34268; bool m_messageResponsePending = false; QUdpSocket *m_udpSocket; From fda9c23728d807811ae3f2fa92b54075262a5edc Mon Sep 17 00:00:00 2001 From: Boernsman Date: Wed, 24 Feb 2021 12:57:14 +0100 Subject: [PATCH 17/34] added translation --- ...42bbf-9d3f-4aa2-9443-b3a31ae09bac-en_US.ts | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 sma/translations/b8442bbf-9d3f-4aa2-9443-b3a31ae09bac-en_US.ts diff --git a/sma/translations/b8442bbf-9d3f-4aa2-9443-b3a31ae09bac-en_US.ts b/sma/translations/b8442bbf-9d3f-4aa2-9443-b3a31ae09bac-en_US.ts new file mode 100644 index 00000000..60334163 --- /dev/null +++ b/sma/translations/b8442bbf-9d3f-4aa2-9443-b3a31ae09bac-en_US.ts @@ -0,0 +1,208 @@ + + + + + sma + + + + + + Connected + The name of the ParamType (ThingClass: inverter, EventType: connected, ID: {eda29c50-73ac-40e0-9c92-26fee352e688}) +---------- +The name of the StateType ({eda29c50-73ac-40e0-9c92-26fee352e688}) of ThingClass inverter +---------- +The name of the ParamType (ThingClass: sunnyWebBox, EventType: connected, ID: {c05e6a1a-252c-4f2b-8b31-09cf113d01c1}) +---------- +The name of the StateType ({c05e6a1a-252c-4f2b-8b31-09cf113d01c1}) of ThingClass sunnyWebBox + + + + + + Connected changed + The name of the EventType ({eda29c50-73ac-40e0-9c92-26fee352e688}) of ThingClass inverter +---------- +The name of the EventType ({c05e6a1a-252c-4f2b-8b31-09cf113d01c1}) of ThingClass sunnyWebBox + + + + + Current Power changed + The name of the EventType ({a804eabf-d5b8-4c83-84e4-8ec994875950}) of ThingClass inverter + + + + + + + + Current power + The name of the ParamType (ThingClass: inverter, EventType: currentPower, ID: {a804eabf-d5b8-4c83-84e4-8ec994875950}) +---------- +The name of the StateType ({a804eabf-d5b8-4c83-84e4-8ec994875950}) of ThingClass inverter +---------- +The name of the ParamType (ThingClass: sunnyWebBox, EventType: currentPower, ID: {ff4ff872-2f0f-4ca4-9fe2-220eeaf16cc2}) +---------- +The name of the StateType ({ff4ff872-2f0f-4ca4-9fe2-220eeaf16cc2}) of ThingClass sunnyWebBox + + + + + Current power changed + The name of the EventType ({ff4ff872-2f0f-4ca4-9fe2-220eeaf16cc2}) of ThingClass sunnyWebBox + + + + + + Day energy + The name of the ParamType (ThingClass: sunnyWebBox, EventType: dayEnergy, ID: {16f34c5c-8dbb-4dcc-9faa-4b782d57226c}) +---------- +The name of the StateType ({16f34c5c-8dbb-4dcc-9faa-4b782d57226c}) of ThingClass sunnyWebBox + + + + + Day energy changed + The name of the EventType ({16f34c5c-8dbb-4dcc-9faa-4b782d57226c}) of ThingClass sunnyWebBox + + + + + Device ID + The name of the ParamType (ThingClass: inverter, Type: thing, ID: {f43e8159-7337-4bd0-ba74-b6630e554e43}) + + + + + + Energy of current day + The name of the ParamType (ThingClass: inverter, EventType: eday, ID: {cd3a0abc-37cc-4f9c-8ff2-c7ccc2513880}) +---------- +The name of the StateType ({cd3a0abc-37cc-4f9c-8ff2-c7ccc2513880}) of ThingClass inverter + + + + + + Energy of current year + The name of the ParamType (ThingClass: inverter, EventType: eyear, ID: {341596ec-690b-4bf6-b6ed-0f92fa1c7d6c}) +---------- +The name of the StateType ({341596ec-690b-4bf6-b6ed-0f92fa1c7d6c}) of ThingClass inverter + + + + + Energy of day changed + The name of the EventType ({cd3a0abc-37cc-4f9c-8ff2-c7ccc2513880}) of ThingClass inverter + + + + + Energy of year changed + The name of the EventType ({341596ec-690b-4bf6-b6ed-0f92fa1c7d6c}) of ThingClass inverter + + + + + + Energy total + The name of the ParamType (ThingClass: inverter, EventType: totalEnergyProduced, ID: {6b6c1ddb-692f-400f-8b8d-38e0e0ae34ba}) +---------- +The name of the StateType ({6b6c1ddb-692f-400f-8b8d-38e0e0ae34ba}) of ThingClass inverter + + + + + Energy total changed + The name of the EventType ({6b6c1ddb-692f-400f-8b8d-38e0e0ae34ba}) of ThingClass inverter + + + + + + Error + The name of the ParamType (ThingClass: sunnyWebBox, EventType: error, ID: {4e64f9ca-7e5a-4897-8035-6f2ae88fde89}) +---------- +The name of the StateType ({4e64f9ca-7e5a-4897-8035-6f2ae88fde89}) of ThingClass sunnyWebBox + + + + + Error changed + The name of the EventType ({4e64f9ca-7e5a-4897-8035-6f2ae88fde89}) of ThingClass sunnyWebBox + + + + + Host address + The name of the ParamType (ThingClass: sunnyWebBox, Type: thing, ID: {864d4162-e3ce-48b8-b8ac-c1b971b52d42}) + + + + + + Mode + The name of the ParamType (ThingClass: sunnyWebBox, EventType: mode, ID: {1974550b-6059-4b0e-83f4-70177e20dac3}) +---------- +The name of the StateType ({1974550b-6059-4b0e-83f4-70177e20dac3}) of ThingClass sunnyWebBox + + + + + Mode changed + The name of the EventType ({1974550b-6059-4b0e-83f4-70177e20dac3}) of ThingClass sunnyWebBox + + + + + + SMA + The name of the vendor ({16d5a4a3-36d5-46c0-b7dd-df166ddf5981}) +---------- +The name of the plugin sma ({b8442bbf-9d3f-4aa2-9443-b3a31ae09bac}) + + + + + Search new devices + The name of the ActionType ({15fc3dac-1868-4490-ba41-7c2f545926ad}) of ThingClass sunnyWebBox + + + + + Solar Inverter + The name of the ThingClass ({9cb72321-042e-4912-b23e-18516b6bbe96}) + + + + + Sunny WebBox + The name of the ThingClass ({49304127-ce9b-45dd-8511-05030a4ac003}) + + + + + + Total energy + The name of the ParamType (ThingClass: sunnyWebBox, EventType: totalEnergy, ID: {0bb4e227-7e38-49ca-9b32-ce4621c9305b}) +---------- +The name of the StateType ({0bb4e227-7e38-49ca-9b32-ce4621c9305b}) of ThingClass sunnyWebBox + + + + + Total energy changed + The name of the EventType ({0bb4e227-7e38-49ca-9b32-ce4621c9305b}) of ThingClass sunnyWebBox + + + + + hardware address + The name of the ParamType (ThingClass: sunnyWebBox, Type: thing, ID: {03f32361-4e13-4597-a346-af8d16a986b3}) + + + + From cd122e89b0793a500d097cac399261b496ada227 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Wed, 24 Feb 2021 14:13:33 +0100 Subject: [PATCH 18/34] moved to http based RPC API --- sma/integrationpluginsma.cpp | 49 ++++++++-------- sma/integrationpluginsma.h | 1 - sma/sma.pro | 2 - sma/sunnywebbox.cpp | 99 +++++++++++++++++++++++++++----- sma/sunnywebbox.h | 10 ++-- sma/sunnywebboxcommunication.cpp | 45 +-------------- 6 files changed, 114 insertions(+), 92 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 142fa7d5..184ab7d5 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -73,12 +73,10 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) void IntegrationPluginSma::setupThing(ThingSetupInfo *info) { Thing *thing = info->thing(); - - if (!m_sunnyWebBoxCommunication) { - m_sunnyWebBoxCommunication = new SunnyWebBoxCommunication(hardwareManager()->networkManager(), this); - } + qCDebug(dcSma()) << "Setup thing" << thing->name(); if (!m_refreshTimer) { + qCDebug(dcSma()) << "Starting refresh timer"; m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(30); connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSma::onRefreshTimer); } @@ -93,15 +91,23 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) return; } } - SunnyWebBox *sunnyWebBox = new SunnyWebBox(m_sunnyWebBoxCommunication, QHostAddress(thing->paramValue(sunnyWebBoxThingHostParamTypeId).toString()), this); - connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, this, &IntegrationPluginSma::onPlantOverviewReceived); - connect(sunnyWebBox, &SunnyWebBox::devicesReceived, this, &IntegrationPluginSma::onDevicesReceived); - connect(sunnyWebBox, &SunnyWebBox::processDataReceived, this, &IntegrationPluginSma::onProcessDataReceived); - connect(sunnyWebBox, &SunnyWebBox::parameterChannelsReceived, this, &IntegrationPluginSma::onParameterChannelsReceived); - m_sunnyWebBoxes.insert(thing, sunnyWebBox); - connect(info, &ThingSetupInfo::aborted, this, [thing, this] { m_sunnyWebBoxes.remove(thing);}); + if (m_sunnyWebBoxes.contains(thing)) { + qCDebug(dcSma()) << "Setup after reconfiguration, cleaning up..."; + m_sunnyWebBoxes.take(thing)->deleteLater(); + } + SunnyWebBox *sunnyWebBox = new SunnyWebBox(hardwareManager()->networkManager(), QHostAddress(thing->paramValue(sunnyWebBoxThingHostParamTypeId).toString()), this); + connect(info, &ThingSetupInfo::aborted, sunnyWebBox, &SunnyWebBox::deleteLater); + connect(sunnyWebBox, &SunnyWebBox::destroyed, this, [thing, this] { m_sunnyWebBoxes.remove(thing);}); QString requestId = sunnyWebBox->getPlantOverview(); - m_asyncSetup.insert(requestId, info); + connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, info, [sunnyWebBox, info, this] { + qCDebug(dcSma()) << "Received plant overview, finishing setup"; + info->finish(Thing::ThingErrorNoError); + connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, this, &IntegrationPluginSma::onPlantOverviewReceived); + connect(sunnyWebBox, &SunnyWebBox::devicesReceived, this, &IntegrationPluginSma::onDevicesReceived); + connect(sunnyWebBox, &SunnyWebBox::processDataReceived, this, &IntegrationPluginSma::onProcessDataReceived); + connect(sunnyWebBox, &SunnyWebBox::parameterChannelsReceived, this, &IntegrationPluginSma::onParameterChannelsReceived); + m_sunnyWebBoxes.insert(info->thing(), sunnyWebBox); + }); return info->finish(Thing::ThingErrorNoError); } else if (thing->thingClassId() == inverterThingClassId) { @@ -127,6 +133,7 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) void IntegrationPluginSma::postSetupThing(Thing *thing) { + qCDebug(dcSma()) << "Post setup thing" << thing->name(); if (thing->thingClassId() == sunnyWebBoxThingClassId) { SunnyWebBox *sunnyWebBox = m_sunnyWebBoxes.value(thing); if (!sunnyWebBox) @@ -167,9 +174,10 @@ void IntegrationPluginSma::thingRemoved(Thing *thing) m_sunnyWebBoxes.take(thing)->deleteLater(); } - if (myThings().filterByThingClassId(sunnyWebBoxThingClassId).isEmpty()) { - m_sunnyWebBoxCommunication->deleteLater(); - m_sunnyWebBoxCommunication = nullptr; + if (myThings().isEmpty()) { + qCDebug(dcSma()) << "Stopping timer"; + hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer); + m_refreshTimer = nullptr; } } @@ -210,7 +218,6 @@ void IntegrationPluginSma::onDevicesReceived(const QString &messageId, QListfinish(Thing::ThingErrorNoError); } - Thing *thing = m_sunnyWebBoxes.key(static_cast(sender())); if (!thing) return; @@ -254,16 +261,6 @@ void IntegrationPluginSma::onParameterChannelsReceived(const QString &messageId, qCDebug(dcSma()) << "Parameter channels received" << deviceKey << parameterChannels; } -SunnyWebBox * IntegrationPluginSma::createSunnyWebBoxConnection(Thing *thing) -{ - SunnyWebBox *sunnyWebBox = new SunnyWebBox(m_sunnyWebBoxCommunication, QHostAddress(thing->paramValue(sunnyWebBoxThingHostParamTypeId).toString()), this); - m_sunnyWebBoxes.insert(thing, sunnyWebBox); - connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, this, &IntegrationPluginSma::onPlantOverviewReceived); - connect(sunnyWebBox, &SunnyWebBox::parameterChannelsReceived, this, &IntegrationPluginSma::onParameterChannelsReceived); - //connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, this, &IntegrationPluginSma::onPlantOverviewReceived); - return sunnyWebBox; -} - void IntegrationPluginSma::setupChild(ThingSetupInfo *info, Thing *parentThing) { Q_UNUSED(info) diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 8d445d31..4f3b30cd 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -71,7 +71,6 @@ private: QHash m_asyncActions; SunnyWebBoxCommunication *m_sunnyWebBoxCommunication = nullptr; - SunnyWebBox *createSunnyWebBoxConnection(Thing *thing); void setupChild(ThingSetupInfo *info, Thing *parentThing); void getData(Thing *thing); }; diff --git a/sma/sma.pro b/sma/sma.pro index fcf5dcec..168f97f3 100644 --- a/sma/sma.pro +++ b/sma/sma.pro @@ -6,13 +6,11 @@ QT += \ SOURCES += \ integrationpluginsma.cpp \ sunnywebbox.cpp \ - sunnywebboxcommunication.cpp \ host.cpp \ discovery.cpp HEADERS += \ integrationpluginsma.h \ sunnywebbox.h \ - sunnywebboxcommunication.h \ host.h \ discovery.h diff --git a/sma/sunnywebbox.cpp b/sma/sunnywebbox.cpp index c687affb..bc9968b6 100644 --- a/sma/sunnywebbox.cpp +++ b/sma/sunnywebbox.cpp @@ -35,37 +35,42 @@ #include "QJsonObject" #include "QJsonArray" -SunnyWebBox::SunnyWebBox(SunnyWebBoxCommunication *communication, const QHostAddress &hostAddress, QObject *parrent) : +SunnyWebBox::SunnyWebBox(NetworkAccessManager *networkAccessManager, const QHostAddress &hostAddress, QObject *parrent) : QObject(parrent), m_hostAddresss(hostAddress), - m_communication(communication) + m_networkManager(networkAccessManager) { + qCDebug(dcSma()) << "SunnyWebBox: Creating Sunny Web Box connection"; //TODO connect communication with socket state; - connect(m_communication, &SunnyWebBoxCommunication::messageReceived, this, &SunnyWebBox::onMessageReceived); +} + +SunnyWebBox::~SunnyWebBox() +{ + qCDebug(dcSma()) << "SunnyWebBox: Deleting Sunny Web Box connection"; } QString SunnyWebBox::getPlantOverview() { - return m_communication->sendMessage(m_hostAddresss, "GetPlantOverview"); + return sendMessage(m_hostAddresss, "GetPlantOverview"); } QString SunnyWebBox::getDevices() { - return m_communication->sendMessage(m_hostAddresss, "GetDevices"); + return sendMessage(m_hostAddresss, "GetDevices"); } QString SunnyWebBox::getProcessDataChannels(const QString &deviceId) { QJsonObject params; params["device"] = deviceId; - return m_communication->sendMessage(m_hostAddresss, "GetProcessDataChannels", params); + return sendMessage(m_hostAddresss, "GetProcessDataChannels", params); } QString SunnyWebBox::getProcessData(const QStringList &deviceKeys) { QJsonObject params; params["device"] = deviceKeys.first(); - return m_communication->sendMessage(m_hostAddresss, "GetProcessData", params); + return sendMessage(m_hostAddresss, "GetProcessData", params); } QString SunnyWebBox::getParameterChannels(const QString &deviceKey) @@ -76,7 +81,7 @@ QString SunnyWebBox::getParameterChannels(const QString &deviceKey) deviceObj["key"] = deviceKey; devicesArray.append(deviceObj); paramsObj["devices"] = devicesArray; - return m_communication->sendMessage(m_hostAddresss, "GetParameterChannels", paramsObj); + return sendMessage(m_hostAddresss, "GetParameterChannels", paramsObj); } QString SunnyWebBox::getParameters(const QStringList &deviceKeys) @@ -87,7 +92,7 @@ QString SunnyWebBox::getParameters(const QStringList &deviceKeys) deviceObj["key"] = deviceKeys.first(); //TODO devicesArray.append(deviceObj); paramsObj["devices"] = devicesArray; - return m_communication->sendMessage(m_hostAddresss, "GetParameter", paramsObj); + return sendMessage(m_hostAddresss, "GetParameter", paramsObj); } QString SunnyWebBox::setParameters(const QString &deviceKey, const QHash &channels) @@ -106,11 +111,12 @@ QString SunnyWebBox::setParameters(const QString &deviceKey, const QHashsendMessage(m_hostAddresss, "SetParameter", paramsObj); + return sendMessage(m_hostAddresss, "SetParameter", paramsObj); } void SunnyWebBox::setHostAddress(const QHostAddress &address) { + qCDebug(dcSma()) << "SunnyWebBox: Setting host address to" << address.toString(); m_hostAddresss = address; } @@ -119,12 +125,8 @@ QHostAddress SunnyWebBox::hostAddress() return m_hostAddresss; } -void SunnyWebBox::onMessageReceived(const QHostAddress &address, const QString &messageId, const QString &messageType, const QVariantMap &result) +void SunnyWebBox::parseMessage(const QString &messageId, const QString &messageType, const QVariantMap &result) { - if (address != m_hostAddresss) { - return; - } - if (messageType == "GetPlantOverview") { Overview overview; QVariantList overviewList = result.value("overview").toList(); @@ -215,3 +217,70 @@ void SunnyWebBox::onMessageReceived(const QHostAddress &address, const QString & qCWarning(dcSma()) << "Unknown message type" << messageType; } } + +QString SunnyWebBox::sendMessage(const QHostAddress &address, const QString &procedure) +{ + return sendMessage(address, procedure, QJsonObject()); +} + +QString SunnyWebBox::sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms) +{ + QString requestId = QUuid::createUuid().toString().remove('{').remove('-').left(14); + + QJsonDocument doc; + QJsonObject obj; + obj["format"] = "JSON"; + obj["id"] = requestId; + obj["proc"] = procedure; + obj["version"] = "1.0"; + + if (!params.isEmpty()) { + obj.insert("params", params); + } + doc.setObject(obj); + + QUrl url; + url.setHost(address.toString()); + url.setPath("/rpc"); + url.setPort(80); + url.setScheme("http"); + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + QByteArray data = doc.toJson(QJsonDocument::JsonFormat::Compact); + data.prepend("RPC="); + QNetworkReply *reply = m_networkManager->post(request, data); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, this, [this, address, requestId, reply]{ + + //int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QByteArray data = reply->readAll(); + qCDebug(dcSma()) << "Received reply" << data; + + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcSma()) << "Could not parse JSON" << error.errorString(); + return; + } + if (!doc.isObject()) { + qCWarning(dcSma()) << "JSON is not an Object"; + return; + } + QVariantMap map = doc.toVariant().toMap(); + if (map["version"] != "1.0") { + qCWarning(dcSma()) << "API version not supported" << map["version"]; + return; + } + + if (map.contains("proc") && map.contains("result")) { + QString requestType = map["proc"].toString(); + QString requestId = map["id"].toString(); + QVariantMap result = map.value("result").toMap(); + parseMessage(requestId, requestType, result); + } else { + qCWarning(dcSma()) << "Missing proc or result value"; + } + }); + return requestId; +} + diff --git a/sma/sunnywebbox.h b/sma/sunnywebbox.h index ef20e0c4..51d0076b 100644 --- a/sma/sunnywebbox.h +++ b/sma/sunnywebbox.h @@ -73,7 +73,8 @@ public: double value; }; - explicit SunnyWebBox(SunnyWebBoxCommunication *communication, const QHostAddress &hostAddress, QObject *parrent = 0); + explicit SunnyWebBox(NetworkAccessManager *networkAccessManager, const QHostAddress &hostAddress, QObject *parrent = 0); + ~SunnyWebBox(); QString getPlantOverview(); // Returns an object with the following plant data: PAC, E-TODAY, E-TOTAL, MODE, ERROR QString getDevices(); //Returns a hierarchical list of all detected plant devices. @@ -89,10 +90,11 @@ public: private: QHostAddress m_hostAddresss; - SunnyWebBoxCommunication *m_communication = nullptr; + NetworkAccessManager *m_networkManager = nullptr; -public slots: - void onMessageReceived(const QHostAddress &address, const QString &messageId, const QString &messageType, const QVariantMap &result); + QString sendMessage(const QHostAddress &address, const QString &procedure); + QString sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms); + void parseMessage(const QString &messageId, const QString &messageType, const QVariantMap &result); signals: void connectedChanged(bool connected); diff --git a/sma/sunnywebboxcommunication.cpp b/sma/sunnywebboxcommunication.cpp index 38882fd7..9e19e32d 100644 --- a/sma/sunnywebboxcommunication.cpp +++ b/sma/sunnywebboxcommunication.cpp @@ -42,6 +42,7 @@ SunnyWebBoxCommunication::SunnyWebBoxCommunication(NetworkAccessManager *network QObject(parent), m_networkManager(networkAccessManager) { + qCDebug(dcSma()) << "Creating SunnyWebBoxCommunictaion"; m_udpSocket = new QUdpSocket(this); m_udpSocket->bind(QHostAddress::Any, m_port); @@ -63,51 +64,7 @@ SunnyWebBoxCommunication::SunnyWebBoxCommunication(NetworkAccessManager *network }); } -QString SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QString &procedure) -{ - QString requestId = QUuid::createUuid().toString().remove('{').remove('-').left(14); - QJsonDocument doc; - QJsonObject obj; - obj["format"] = "JSON"; - obj["id"] = requestId; - obj["proc"] = procedure; - obj["version"] = "1.0"; - doc.setObject(obj); - - QUrl url; - url.setHost(address.toString()); - url.setPath("/rpc"); - url.setScheme("http"); - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); - QByteArray data = doc.toJson(QJsonDocument::JsonFormat::Compact); - - QNetworkReply *reply = m_networkManager->post(request, data); - connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); - connect(reply, &QNetworkReply::finished, this, [this, address, requestId, reply]{ - - //int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - QByteArray data = reply->readAll(); - qCDebug(dcSma()) << "Received reply" << data; - datagramReceived(address, data); - }); - - /* if(!m_messageResponsePending) { - qCDebug(dcSma()) << "Send message" << data << address << m_port; - m_udpSocket->writeDatagram(data, address, m_port); - m_messageResponsePending = true; - } else { - if (m_messageQueue[address].length() < 40) { - qCDebug(dcSma()) << "Adding message to queue" << data << address << m_port; - m_messageQueue[address].append(data); - } else { - qCWarning(dcSma()) << "Message queue overflow"; - return ""; - } - }*/ - return requestId; -} QString SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms) { From 877658dd2ca850243bc26efc5ef3c0563fac6b40 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Wed, 24 Feb 2021 15:18:57 +0100 Subject: [PATCH 19/34] added more debug output --- sma/integrationpluginsma.cpp | 6 ++++-- sma/sunnywebbox.cpp | 40 ++++++++++++++++++++++++++++-------- sma/sunnywebbox.h | 3 ++- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 184ab7d5..59b1a863 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -139,6 +139,7 @@ void IntegrationPluginSma::postSetupThing(Thing *thing) if (!sunnyWebBox) return; sunnyWebBox->getDevices(); + thing->setStateValue(sunnyWebBoxConnectedStateTypeId, true); } else if (thing->thingClassId() == inverterThingClassId) { } @@ -190,6 +191,7 @@ void IntegrationPluginSma::onRefreshTimer() void IntegrationPluginSma::onPlantOverviewReceived(const QString &messageId, SunnyWebBox::Overview overview) { + qCDebug(dcSma()) << "Plant overview received" << overview.status; if (m_asyncSetup.contains(messageId)) { ThingSetupInfo *info = m_asyncSetup.value(messageId); info->finish(Thing::ThingErrorNoError); @@ -199,7 +201,6 @@ void IntegrationPluginSma::onPlantOverviewReceived(const QString &messageId, Sun if (!thing) return; - qCDebug(dcSma()) << "Plant overview received" << overview.status; thing->setStateValue(sunnyWebBoxConnectedStateTypeId, true); thing->setStateValue(sunnyWebBoxCurrentPowerStateTypeId, overview.power); thing->setStateValue(sunnyWebBoxDayEnergyStateTypeId, overview.dailyYield); @@ -213,6 +214,7 @@ void IntegrationPluginSma::onPlantOverviewReceived(const QString &messageId, Sun void IntegrationPluginSma::onDevicesReceived(const QString &messageId, QList devices) { + qCDebug(dcSma()) << "Devices received, count:" << devices.count(); if (m_asyncActions.contains(messageId)) { ThingActionInfo *info = m_asyncActions.value(messageId); info->finish(Thing::ThingErrorNoError); @@ -224,7 +226,7 @@ void IntegrationPluginSma::onDevicesReceived(const QString &messageId, QListid()); descriptors.append(descriptor); } diff --git a/sma/sunnywebbox.cpp b/sma/sunnywebbox.cpp index bc9968b6..7d120684 100644 --- a/sma/sunnywebbox.cpp +++ b/sma/sunnywebbox.cpp @@ -41,7 +41,6 @@ SunnyWebBox::SunnyWebBox(NetworkAccessManager *networkAccessManager, const QHost m_networkManager(networkAccessManager) { qCDebug(dcSma()) << "SunnyWebBox: Creating Sunny Web Box connection"; - //TODO connect communication with socket state; } SunnyWebBox::~SunnyWebBox() @@ -130,18 +129,24 @@ void SunnyWebBox::parseMessage(const QString &messageId, const QString &messageT if (messageType == "GetPlantOverview") { Overview overview; QVariantList overviewList = result.value("overview").toList(); + qCDebug(dcSma()) << "SunnyWebBox: GetPlantOverview"; Q_FOREACH(QVariant value, overviewList) { QVariantMap map = value.toMap(); if (map["meta"].toString() == "GriPwr") { overview.power = map["value"].toInt(); + qCDebug(dcSma()) << "SunnyWebBox: - Power" << overview.power; } else if (map["meta"].toString() == "GriEgyTdy") { overview.dailyYield = map["value"].toInt(); + qCDebug(dcSma()) << "SunnyWebBox: - Daily yield" << overview.dailyYield; } else if (map["meta"].toString() == "GriEgyTot") { overview.totalYield = map["value"].toInt(); + qCDebug(dcSma()) << "SunnyWebBox: - Total yield" << overview.totalYield; } else if (map["meta"].toString() == "OpStt") { overview.status = map["value"].toString(); + qCDebug(dcSma()) << "SunnyWebBox: - Status" << overview.status; } else if (map["meta"].toString() == "Msg") { overview.error = map["value"].toString(); + qCDebug(dcSma()) << "SunnyWebBox: - Error" << overview.error; } } emit plantOverviewReceived(messageId, overview); @@ -149,12 +154,14 @@ void SunnyWebBox::parseMessage(const QString &messageId, const QString &messageT } else if (messageType == "GetDevices") { QList devices; QVariantList deviceList = result.value("devices").toList(); + qCDebug(dcSma()) << "SunnyWebBox: GetDevices"; Q_FOREACH(QVariant value, deviceList) { Device device; QVariantMap map = value.toMap(); device.name = map["name"].toString(); + qCDebug(dcSma()) << "SunnyWebBox: - Name" << device.name; device.key = map["key"].toString(); - + qCDebug(dcSma()) << "SunnyWebBox: - Key" << device.key; QVariantList childrenList = map["children"].toList(); Q_FOREACH(QVariant childValue, childrenList) { Device child; @@ -214,7 +221,16 @@ void SunnyWebBox::parseMessage(const QString &messageId, const QString &messageT emit parametersReceived(messageId, key, parameters); } } else { - qCWarning(dcSma()) << "Unknown message type" << messageType; + qCWarning(dcSma()) << "SunnyWebBox: Unknown message type" << messageType; + } +} + +void SunnyWebBox::setConnectionStatus(bool connected) +{ + if (m_connected != connected) { + qCDebug(dcSma()) << "SunnyWebBox: Connection status changed" << connected; + m_connected = connected; + emit connectedChanged(m_connected); } } @@ -225,6 +241,7 @@ QString SunnyWebBox::sendMessage(const QHostAddress &address, const QString &pro QString SunnyWebBox::sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms) { + qCDebug(dcSma()) << "SunnyWebBox: Send message to" << address.toString() << "Procedure:" << procedure << "Params:" << params; QString requestId = QUuid::createUuid().toString().remove('{').remove('-').left(14); QJsonDocument doc; @@ -252,23 +269,28 @@ QString SunnyWebBox::sendMessage(const QHostAddress &address, const QString &pro connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); connect(reply, &QNetworkReply::finished, this, [this, address, requestId, reply]{ - //int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (reply->error() != QNetworkReply::NoError) { + setConnectionStatus(false); + return; + } + setConnectionStatus(true); + QByteArray data = reply->readAll(); - qCDebug(dcSma()) << "Received reply" << data; + qCDebug(dcSma()) << "SunnyWebBox: Received reply" << data; QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(data, &error); if (error.error != QJsonParseError::NoError) { - qCWarning(dcSma()) << "Could not parse JSON" << error.errorString(); + qCWarning(dcSma()) << "SunnyWebBox: Could not parse JSON" << error.errorString(); return; } if (!doc.isObject()) { - qCWarning(dcSma()) << "JSON is not an Object"; + qCWarning(dcSma()) << "SunnyWebBox: JSON is not an Object"; return; } QVariantMap map = doc.toVariant().toMap(); if (map["version"] != "1.0") { - qCWarning(dcSma()) << "API version not supported" << map["version"]; + qCWarning(dcSma()) << "SunnyWebBox: API version not supported" << map["version"]; return; } @@ -278,7 +300,7 @@ QString SunnyWebBox::sendMessage(const QHostAddress &address, const QString &pro QVariantMap result = map.value("result").toMap(); parseMessage(requestId, requestType, result); } else { - qCWarning(dcSma()) << "Missing proc or result value"; + qCWarning(dcSma()) << "SunnyWebBox: Missing proc or result value"; } }); return requestId; diff --git a/sma/sunnywebbox.h b/sma/sunnywebbox.h index 51d0076b..8928210e 100644 --- a/sma/sunnywebbox.h +++ b/sma/sunnywebbox.h @@ -88,7 +88,7 @@ public: QHostAddress hostAddress(); private: - + bool m_connected = false; QHostAddress m_hostAddresss; NetworkAccessManager *m_networkManager = nullptr; @@ -96,6 +96,7 @@ private: QString sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms); void parseMessage(const QString &messageId, const QString &messageType, const QVariantMap &result); + void setConnectionStatus(bool connected); signals: void connectedChanged(bool connected); From 74b33b2d2cbca6a6ba30188c333266cf59d552f0 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Wed, 24 Feb 2021 15:35:41 +0100 Subject: [PATCH 20/34] fixed getplantoverview conversion --- sma/integrationpluginsma.cpp | 1 - sma/sunnywebbox.cpp | 11 ++++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 59b1a863..19688e45 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -108,7 +108,6 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) connect(sunnyWebBox, &SunnyWebBox::parameterChannelsReceived, this, &IntegrationPluginSma::onParameterChannelsReceived); m_sunnyWebBoxes.insert(info->thing(), sunnyWebBox); }); - return info->finish(Thing::ThingErrorNoError); } else if (thing->thingClassId() == inverterThingClassId) { Thing *parentThing = myThings().findById(thing->parentId()); diff --git a/sma/sunnywebbox.cpp b/sma/sunnywebbox.cpp index 7d120684..250f9378 100644 --- a/sma/sunnywebbox.cpp +++ b/sma/sunnywebbox.cpp @@ -133,13 +133,14 @@ void SunnyWebBox::parseMessage(const QString &messageId, const QString &messageT Q_FOREACH(QVariant value, overviewList) { QVariantMap map = value.toMap(); if (map["meta"].toString() == "GriPwr") { - overview.power = map["value"].toInt(); - qCDebug(dcSma()) << "SunnyWebBox: - Power" << overview.power; + overview.power = map["value"].toString().toInt(); + QString unit = map["unit"].toString(); + qCDebug(dcSma()) << "SunnyWebBox: - Power" << overview.power << unit; } else if (map["meta"].toString() == "GriEgyTdy") { - overview.dailyYield = map["value"].toInt(); + overview.dailyYield = map["value"].toString().toInt(); qCDebug(dcSma()) << "SunnyWebBox: - Daily yield" << overview.dailyYield; } else if (map["meta"].toString() == "GriEgyTot") { - overview.totalYield = map["value"].toInt(); + overview.totalYield = map["value"].toString().toInt(); qCDebug(dcSma()) << "SunnyWebBox: - Total yield" << overview.totalYield; } else if (map["meta"].toString() == "OpStt") { overview.status = map["value"].toString(); @@ -154,7 +155,7 @@ void SunnyWebBox::parseMessage(const QString &messageId, const QString &messageT } else if (messageType == "GetDevices") { QList devices; QVariantList deviceList = result.value("devices").toList(); - qCDebug(dcSma()) << "SunnyWebBox: GetDevices"; + qCDebug(dcSma()) << "SunnyWebBox: GetDevices" << result.value("totalDevicesReturned").toInt(); Q_FOREACH(QVariant value, deviceList) { Device device; QVariantMap map = value.toMap(); From 1d2bd72cad82b7d277272c33fd89e1f7baba4d3d Mon Sep 17 00:00:00 2001 From: Boernsman Date: Wed, 24 Feb 2021 15:45:27 +0100 Subject: [PATCH 21/34] fixed auto device setup --- sma/integrationpluginsma.cpp | 3 +++ sma/sunnywebbox.cpp | 11 +++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 19688e45..1637431b 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -227,6 +227,9 @@ void IntegrationPluginSma::onDevicesReceived(const QString &messageId, QListid()); + ParamList params; + params << Param(inverterThingIdParamTypeId, device.key); + descriptor.setParams(params); descriptors.append(descriptor); } emit autoThingsAppeared(descriptors); diff --git a/sma/sunnywebbox.cpp b/sma/sunnywebbox.cpp index 250f9378..8323b8e0 100644 --- a/sma/sunnywebbox.cpp +++ b/sma/sunnywebbox.cpp @@ -132,16 +132,19 @@ void SunnyWebBox::parseMessage(const QString &messageId, const QString &messageT qCDebug(dcSma()) << "SunnyWebBox: GetPlantOverview"; Q_FOREACH(QVariant value, overviewList) { QVariantMap map = value.toMap(); + if (map["meta"].toString() == "GriPwr") { overview.power = map["value"].toString().toInt(); QString unit = map["unit"].toString(); qCDebug(dcSma()) << "SunnyWebBox: - Power" << overview.power << unit; } else if (map["meta"].toString() == "GriEgyTdy") { - overview.dailyYield = map["value"].toString().toInt(); - qCDebug(dcSma()) << "SunnyWebBox: - Daily yield" << overview.dailyYield; + overview.dailyYield = map["value"].toString().toDouble(); + QString unit = map["unit"].toString(); + qCDebug(dcSma()) << "SunnyWebBox: - Daily yield" << overview.dailyYield << unit; } else if (map["meta"].toString() == "GriEgyTot") { - overview.totalYield = map["value"].toString().toInt(); - qCDebug(dcSma()) << "SunnyWebBox: - Total yield" << overview.totalYield; + overview.totalYield = map["value"].toString().toDouble(); + QString unit = map["unit"].toString(); + qCDebug(dcSma()) << "SunnyWebBox: - Total yield" << overview.totalYield << unit; } else if (map["meta"].toString() == "OpStt") { overview.status = map["value"].toString(); qCDebug(dcSma()) << "SunnyWebBox: - Status" << overview.status; From 4a0541bf1813ced61b8da8420ba66d22dde153d1 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Wed, 24 Feb 2021 16:21:16 +0100 Subject: [PATCH 22/34] fixed inverter thing setup --- sma/integrationpluginsma.cpp | 43 ++++++++++++++++++++++++++--------- sma/integrationpluginsma.h | 3 ++- sma/integrationpluginsma.json | 3 +-- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 1637431b..952951cf 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -102,6 +102,7 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, info, [sunnyWebBox, info, this] { qCDebug(dcSma()) << "Received plant overview, finishing setup"; info->finish(Thing::ThingErrorNoError); + connect(sunnyWebBox, &SunnyWebBox::connectedChanged, this, &IntegrationPluginSma::onConnectedChanged); connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, this, &IntegrationPluginSma::onPlantOverviewReceived); connect(sunnyWebBox, &SunnyWebBox::devicesReceived, this, &IntegrationPluginSma::onDevicesReceived); connect(sunnyWebBox, &SunnyWebBox::processDataReceived, this, &IntegrationPluginSma::onProcessDataReceived); @@ -115,16 +116,17 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) qCWarning(dcSma()) << "Could not find parentThing for thing " << thing->name(); return info->finish(Thing::ThingErrorHardwareNotAvailable, "Please try again"); } - if (!parentThing->setupComplete()) { + if (parentThing->setupComplete()) { + info->finish(Thing::ThingErrorNoError); + } else { //wait for the parent to finish the setup process connect(parentThing, &Thing::setupStatusChanged, info, [this, info, parentThing] { if (parentThing->setupComplete()) - setupChild(info, parentThing); + info->finish(Thing::ThingErrorNoError); }); return; } - setupChild(info, parentThing); } else { Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); } @@ -140,7 +142,11 @@ void IntegrationPluginSma::postSetupThing(Thing *thing) sunnyWebBox->getDevices(); thing->setStateValue(sunnyWebBoxConnectedStateTypeId, true); } else if (thing->thingClassId() == inverterThingClassId) { - + SunnyWebBox *sunnyWebBox = m_sunnyWebBoxes.value(myThings().findById(thing->parentId())); + if (!sunnyWebBox) + return; + QString key = thing->paramValue(inverterThingIdParamTypeId).toString(); + sunnyWebBox->getParameters(QStringList() << key); } } @@ -188,6 +194,14 @@ void IntegrationPluginSma::onRefreshTimer() } } +void IntegrationPluginSma::onConnectedChanged(bool connected) +{ + Thing *thing = m_sunnyWebBoxes.key(static_cast(sender())); + if (!thing) + return; + thing->setStateValue(sunnyWebBoxConnectedStateTypeId, connected); +} + void IntegrationPluginSma::onPlantOverviewReceived(const QString &messageId, SunnyWebBox::Overview overview) { qCDebug(dcSma()) << "Plant overview received" << overview.status; @@ -200,7 +214,6 @@ void IntegrationPluginSma::onPlantOverviewReceived(const QString &messageId, Sun if (!thing) return; - thing->setStateValue(sunnyWebBoxConnectedStateTypeId, true); thing->setStateValue(sunnyWebBoxCurrentPowerStateTypeId, overview.power); thing->setStateValue(sunnyWebBoxDayEnergyStateTypeId, overview.dailyYield); thing->setStateValue(sunnyWebBoxTotalEnergyStateTypeId, overview.totalYield); @@ -226,6 +239,8 @@ void IntegrationPluginSma::onDevicesReceived(const QString &messageId, QListid()); ParamList params; params << Param(inverterThingIdParamTypeId, device.key); @@ -235,6 +250,18 @@ void IntegrationPluginSma::onDevicesReceived(const QString &messageId, QList ¶meters) +{ + Q_UNUSED(messageId); + Thing *thing = myThings().findByParams(ParamList() << Param(inverterThingIdParamTypeId, deviceKey)); + if (!thing) + return; + qCDebug(dcSma()) << "Parameters received"; + Q_FOREACH(SunnyWebBox::Parameter parameter, parameters) { + qCDebug(dcSma()) << " - " << parameter.name << parameter.value << parameter.unit; + } +} + void IntegrationPluginSma::onProcessDataReceived(const QString &messageId, const QString &deviceKey, const QHash &channels) { Q_UNUSED(messageId) @@ -265,12 +292,6 @@ void IntegrationPluginSma::onParameterChannelsReceived(const QString &messageId, qCDebug(dcSma()) << "Parameter channels received" << deviceKey << parameterChannels; } -void IntegrationPluginSma::setupChild(ThingSetupInfo *info, Thing *parentThing) -{ - Q_UNUSED(info) - Q_UNUSED(parentThing) -} - void IntegrationPluginSma::getData(Thing *thing) { Q_UNUSED(thing) diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 4f3b30cd..8e572c60 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -59,8 +59,10 @@ public: private slots: void onRefreshTimer(); + void onConnectedChanged(bool connected); void onPlantOverviewReceived(const QString &messageId, SunnyWebBox::Overview overview); void onDevicesReceived(const QString &messageId, QList devices); + void onParametersReceived(const QString &messageId, const QString &deviceKey, const QList ¶meters); void onProcessDataReceived(const QString &messageId, const QString &deviceKey, const QHash &channels); void onParameterChannelsReceived(const QString &messageId, const QString &deviceKey, QStringList parameterChannels); @@ -71,7 +73,6 @@ private: QHash m_asyncActions; SunnyWebBoxCommunication *m_sunnyWebBoxCommunication = nullptr; - void setupChild(ThingSetupInfo *info, Thing *parentThing); void getData(Thing *thing); }; diff --git a/sma/integrationpluginsma.json b/sma/integrationpluginsma.json index 6b9a9920..bb40003a 100644 --- a/sma/integrationpluginsma.json +++ b/sma/integrationpluginsma.json @@ -105,8 +105,7 @@ "name": "id", "displayName": "Device ID", "type": "QString", - "inputType": "TextLine", - "readOnly": true + "readOnly": true } ], "stateTypes": [ From d777f9c7f44db2a765865d644e59bcaa3dd8ba7d Mon Sep 17 00:00:00 2001 From: Boernsman Date: Wed, 24 Feb 2021 16:23:17 +0100 Subject: [PATCH 23/34] fixed inverter thing setup --- sma/integrationpluginsma.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 952951cf..ddc7cfc9 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -107,6 +107,7 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) connect(sunnyWebBox, &SunnyWebBox::devicesReceived, this, &IntegrationPluginSma::onDevicesReceived); connect(sunnyWebBox, &SunnyWebBox::processDataReceived, this, &IntegrationPluginSma::onProcessDataReceived); connect(sunnyWebBox, &SunnyWebBox::parameterChannelsReceived, this, &IntegrationPluginSma::onParameterChannelsReceived); + connect(sunnyWebBox, &SunnyWebBox::parametersReceived, this, &IntegrationPluginSma::onParametersReceived); m_sunnyWebBoxes.insert(info->thing(), sunnyWebBox); }); From ef5c791658d29a45d1429d268d516c6170d8ecf4 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Fri, 26 Feb 2021 20:29:30 +0100 Subject: [PATCH 24/34] added get process data call --- sma/README.md | 1 - sma/integrationpluginsma.cpp | 30 +++++++++++++++++++----------- sma/integrationpluginsma.h | 2 -- sma/integrationpluginsma.json | 14 +++++++------- sma/sunnywebbox.cpp | 1 + 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/sma/README.md b/sma/README.md index b4b47cdb..63ffbd03 100644 --- a/sma/README.md +++ b/sma/README.md @@ -11,5 +11,4 @@ nymea plug-in for SMA solar equipment. * The package "nymea-plugin-sma" must be installed. ## More - https://www.sma.de/en/ diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index ddc7cfc9..4d679db9 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -121,7 +121,7 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) info->finish(Thing::ThingErrorNoError); } else { //wait for the parent to finish the setup process - connect(parentThing, &Thing::setupStatusChanged, info, [this, info, parentThing] { + connect(parentThing, &Thing::setupStatusChanged, info, [info, parentThing] { if (parentThing->setupComplete()) info->finish(Thing::ThingErrorNoError); @@ -148,6 +148,7 @@ void IntegrationPluginSma::postSetupThing(Thing *thing) return; QString key = thing->paramValue(inverterThingIdParamTypeId).toString(); sunnyWebBox->getParameters(QStringList() << key); + sunnyWebBox->getProcessData(QStringList() << key); } } @@ -190,8 +191,14 @@ void IntegrationPluginSma::thingRemoved(Thing *thing) void IntegrationPluginSma::onRefreshTimer() { - Q_FOREACH(SunnyWebBox *sunnyWebBox, m_sunnyWebBoxes) { + Q_FOREACH(Thing *thing, myThings().filterByThingClassId(sunnyWebBoxThingClassId)) { + SunnyWebBox *sunnyWebBox = m_sunnyWebBoxes.value(thing); sunnyWebBox->getPlantOverview(); + + Q_FOREACH(Thing *child, myThings().filterByParentId(thing->id())) { + QString key = child->paramValue(inverterThingIdParamTypeId).toString(); + sunnyWebBox->getProcessData(QStringList() << key); + } } } @@ -201,6 +208,11 @@ void IntegrationPluginSma::onConnectedChanged(bool connected) if (!thing) return; thing->setStateValue(sunnyWebBoxConnectedStateTypeId, connected); + if (!connected) { + Q_FOREACH(Thing *child, myThings().filterByParentId(thing->id())) { + child->setStateValue(inverterConnectedStateTypeId, false); + } + } } void IntegrationPluginSma::onPlantOverviewReceived(const QString &messageId, SunnyWebBox::Overview overview) @@ -216,8 +228,8 @@ void IntegrationPluginSma::onPlantOverviewReceived(const QString &messageId, Sun return; thing->setStateValue(sunnyWebBoxCurrentPowerStateTypeId, overview.power); - thing->setStateValue(sunnyWebBoxDayEnergyStateTypeId, overview.dailyYield); - thing->setStateValue(sunnyWebBoxTotalEnergyStateTypeId, overview.totalYield); + thing->setStateValue(sunnyWebBoxDayEnergyProducedStateTypeId, overview.dailyYield); + thing->setStateValue(sunnyWebBoxTotalEnergyProducedStateTypeId, overview.totalYield); thing->setStateValue(sunnyWebBoxModeStateTypeId, overview.status); if (!overview.error.isEmpty()){ qCDebug(dcSma()) << "Received error" << overview.error; @@ -274,8 +286,9 @@ void IntegrationPluginSma::onProcessDataReceived(const QString &messageId, const Q_FOREACH(Thing *childThing, myThings().filterByParentId(thing->id())) { if (childThing->paramValue(inverterThingIdParamTypeId).toString() == deviceKey) { - if (channels.contains("E-Total")) { - //TODO set total energy + Q_FOREACH(QString channel, channels.keys()) { + qCDebug(dcSma()) << " - Channel received" << channel << channels.value(channel); + //TODO parse data } break; } @@ -292,8 +305,3 @@ void IntegrationPluginSma::onParameterChannelsReceived(const QString &messageId, qCDebug(dcSma()) << "Parameter channels received" << deviceKey << parameterChannels; } - -void IntegrationPluginSma::getData(Thing *thing) -{ - Q_UNUSED(thing) -} diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 8e572c60..5fef831c 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -72,8 +72,6 @@ private: QHash m_asyncSetup; QHash m_asyncActions; SunnyWebBoxCommunication *m_sunnyWebBoxCommunication = nullptr; - - void getData(Thing *thing); }; #endif // INTEGRATIONPLUGINSMA_H diff --git a/sma/integrationpluginsma.json b/sma/integrationpluginsma.json index bb40003a..babbc720 100644 --- a/sma/integrationpluginsma.json +++ b/sma/integrationpluginsma.json @@ -13,7 +13,7 @@ "name": "sunnyWebBox", "displayName": "Sunny WebBox", "createMethods": ["user", "discovery"], - "interfaces": ["gateway"], + "interfaces": ["extendedsmartmeterproducer", "gateway"], "paramTypes": [ { "id": "864d4162-e3ce-48b8-b8ac-c1b971b52d42", @@ -52,18 +52,18 @@ }, { "id": "16f34c5c-8dbb-4dcc-9faa-4b782d57226c", - "name": "dayEnergy", - "displayName": "Day energy", - "displayNameEvent": "Day energy changed", + "name": "dayEnergyProduced", + "displayName": "Day energy produced", + "displayNameEvent": "Day energy produced changed", "type": "double", "unit": "KiloWattHour", "defaultValue": 0 }, { "id": "0bb4e227-7e38-49ca-9b32-ce4621c9305b", - "name": "totalEnergy", - "displayName": "Total energy", - "displayNameEvent": "Total energy changed", + "name": "totalEnergyProduced", + "displayName": "Total energy produced", + "displayNameEvent": "Total energy produced changed", "type": "double", "unit": "KiloWattHour", "defaultValue": 0 diff --git a/sma/sunnywebbox.cpp b/sma/sunnywebbox.cpp index 8323b8e0..7d455803 100644 --- a/sma/sunnywebbox.cpp +++ b/sma/sunnywebbox.cpp @@ -188,6 +188,7 @@ void SunnyWebBox::parseMessage(const QString &messageId, const QString &messageT } else if (messageType == "GetProcessData") { QList devices; QVariantList devicesList = result.value("devices").toList(); + qCDebug(dcSma()) << "SunnyWebBox: GetProcessData response received"; Q_FOREACH(QVariant value, devicesList) { QString key = value.toMap().value("key").toString(); From 1c7ed8a09b398ab0dd62906bfeba2fe40fe5723d Mon Sep 17 00:00:00 2001 From: Boernsman Date: Fri, 26 Feb 2021 21:13:32 +0100 Subject: [PATCH 25/34] fixed get process data call --- sma/sunnywebbox.cpp | 21 +++-- sma/sunnywebboxcommunication.cpp | 140 ------------------------------- sma/sunnywebboxcommunication.h | 63 -------------- 3 files changed, 15 insertions(+), 209 deletions(-) delete mode 100644 sma/sunnywebboxcommunication.cpp delete mode 100644 sma/sunnywebboxcommunication.h diff --git a/sma/sunnywebbox.cpp b/sma/sunnywebbox.cpp index 7d455803..94490875 100644 --- a/sma/sunnywebbox.cpp +++ b/sma/sunnywebbox.cpp @@ -67,9 +67,15 @@ QString SunnyWebBox::getProcessDataChannels(const QString &deviceId) QString SunnyWebBox::getProcessData(const QStringList &deviceKeys) { - QJsonObject params; - params["device"] = deviceKeys.first(); - return sendMessage(m_hostAddresss, "GetProcessData", params); + QJsonObject paramsObj; + QJsonArray devicesArray; + Q_FOREACH(QString key, deviceKeys) { + QJsonObject deviceObj; + deviceObj["key"] = key; + devicesArray.append(deviceObj); + } + paramsObj["devices"] = devicesArray; + return sendMessage(m_hostAddresss, "GetProcessData", paramsObj); } QString SunnyWebBox::getParameterChannels(const QString &deviceKey) @@ -87,9 +93,11 @@ QString SunnyWebBox::getParameters(const QStringList &deviceKeys) { QJsonObject paramsObj; QJsonArray devicesArray; - QJsonObject deviceObj; - deviceObj["key"] = deviceKeys.first(); //TODO - devicesArray.append(deviceObj); + Q_FOREACH(QString key, deviceKeys) { + QJsonObject deviceObj; + deviceObj["key"] = key; + devicesArray.append(deviceObj); + } paramsObj["devices"] = devicesArray; return sendMessage(m_hostAddresss, "GetParameter", paramsObj); } @@ -304,6 +312,7 @@ QString SunnyWebBox::sendMessage(const QHostAddress &address, const QString &pro QString requestId = map["id"].toString(); QVariantMap result = map.value("result").toMap(); parseMessage(requestId, requestType, result); + } else if (map.contains("proc") && map.contains("error")) { } else { qCWarning(dcSma()) << "SunnyWebBox: Missing proc or result value"; } diff --git a/sma/sunnywebboxcommunication.cpp b/sma/sunnywebboxcommunication.cpp deleted file mode 100644 index 9e19e32d..00000000 --- a/sma/sunnywebboxcommunication.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, nymea GmbH -* Contact: contact@nymea.io -* -* This file is part of nymea. -* This project including source code and documentation is protected by -* copyright law, and remains the property of nymea GmbH. All rights, including -* reproduction, publication, editing and translation, are reserved. The use of -* this project is subject to the terms of a license agreement to be concluded -* with nymea GmbH in accordance with the terms of use of nymea GmbH, available -* under https://nymea.io/license -* -* GNU Lesser General Public License Usage -* Alternatively, this project may be redistributed and/or modified under the -* terms of the GNU Lesser General Public License as published by the Free -* Software Foundation; version 3. This project is distributed in the hope that -* it will be useful, but WITHOUT ANY WARRANTY; without even the implied -* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public License -* along with this project. If not, see . -* -* For any further details and any questions please contact us under -* contact@nymea.io or see our FAQ/Licensing Information on -* https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#include "sunnywebboxcommunication.h" -#include "extern-plugininfo.h" - -#include "QJsonDocument" -#include "QJsonObject" -#include -#include -#include -#include - -SunnyWebBoxCommunication::SunnyWebBoxCommunication(NetworkAccessManager *networkAccessManager, QObject *parent) : - QObject(parent), - m_networkManager(networkAccessManager) -{ - qCDebug(dcSma()) << "Creating SunnyWebBoxCommunictaion"; - m_udpSocket = new QUdpSocket(this); - m_udpSocket->bind(QHostAddress::Any, m_port); - - connect(m_udpSocket, &QUdpSocket::stateChanged, this, [this](QAbstractSocket::SocketState state) { - emit socketConnected(state == QAbstractSocket::SocketState::ConnectedState); - }); - - connect(m_udpSocket, &QUdpSocket::readyRead, this, [this] { - - QHostAddress address; - quint16 port; - QByteArray data; - data.resize(m_udpSocket->pendingDatagramSize()); - int receivedBytes = m_udpSocket->readDatagram(data.data(), data.size(), &address, &port); - if (receivedBytes == -1) { - qCWarning(dcSma()) << "Error reading pending datagram"; - } - datagramReceived(address, data); - }); -} - - - -QString SunnyWebBoxCommunication::sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms) -{ - QString requestId = QUuid::createUuid().toString().remove('{').remove('-').left(14); - - QJsonDocument doc; - QJsonObject obj; - - obj.insert("params", params); - obj["format"] = "JSON"; - obj["id"] = requestId; - obj["proc"] = procedure; - obj["version"] = "1.0"; - doc.setObject(obj); - QByteArray data = doc.toJson(QJsonDocument::JsonFormat::Compact); - if(!m_messageResponsePending) { - qCDebug(dcSma()) << "Send message" << data << address << m_port; - m_udpSocket->writeDatagram(data, address, m_port); - m_messageResponsePending = true; - } else { - if (m_messageQueue[address].length() < 40) { - qCDebug(dcSma()) << "Adding message to queue" << data << address << m_port; - m_messageQueue[address].append(data); - } else { - qCDebug(dcSma()) << "Message queue overflow"; - return ""; - } - } - return requestId; -} - -void SunnyWebBoxCommunication::datagramReceived(const QHostAddress &address, const QByteArray &data) -{ - if(!m_messageQueue.value(address).isEmpty()) { - QByteArray data = m_messageQueue[address].takeFirst(); - qCDebug(dcSma()) << "Send message from queue" << data << address << m_port; - //The interval between two queries should not be less than 30 seconds. - QTimer::singleShot(3000, this, [this, data, address]{m_udpSocket->writeDatagram(data, address, m_port);}); - } else { - m_messageResponsePending = false; - } - QList arrayList = data.split('\x00'); - QByteArray cleanData; - Q_FOREACH(QByteArray i, arrayList) { - //Removing all '\0' characters - cleanData.append(i); - } - qCDebug(dcSma()) << "Datagram received" << cleanData; - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(cleanData, &error); - if (error.error != QJsonParseError::NoError) { - qCWarning(dcSma()) << "Could not parse JSON" << error.errorString(); - return; - } - if (!doc.isObject()) { - qCWarning(dcSma()) << "JSON is not an Object"; - return; - } - QVariantMap map = doc.toVariant().toMap(); - if (map["version"] != "1.0") { - qCWarning(dcSma()) << "API version not supported" << map["version"]; - return; - } - - if (map.contains("proc") && map.contains("result")) { - QString requestType = map["proc"].toString(); - QString requestId = map["id"].toString(); - QVariantMap result = map.value("result").toMap(); - emit messageReceived(address, requestId, requestType, result); - } else { - qCWarning(dcSma()) << "Missing proc or result value"; - } -} diff --git a/sma/sunnywebboxcommunication.h b/sma/sunnywebboxcommunication.h deleted file mode 100644 index b026f8a1..00000000 --- a/sma/sunnywebboxcommunication.h +++ /dev/null @@ -1,63 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, nymea GmbH -* Contact: contact@nymea.io -* -* This file is part of nymea. -* This project including source code and documentation is protected by -* copyright law, and remains the property of nymea GmbH. All rights, including -* reproduction, publication, editing and translation, are reserved. The use of -* this project is subject to the terms of a license agreement to be concluded -* with nymea GmbH in accordance with the terms of use of nymea GmbH, available -* under https://nymea.io/license -* -* GNU Lesser General Public License Usage -* Alternatively, this project may be redistributed and/or modified under the -* terms of the GNU Lesser General Public License as published by the Free -* Software Foundation; version 3. This project is distributed in the hope that -* it will be useful, but WITHOUT ANY WARRANTY; without even the implied -* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public License -* along with this project. If not, see . -* -* For any further details and any questions please contact us under -* contact@nymea.io or see our FAQ/Licensing Information on -* https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#ifndef SUNNYWEBBOXCOMMUNICATION_H -#define SUNNYWEBBOXCOMMUNICATION_H - -#include -#include - -#include "network/networkaccessmanager.h" - -class SunnyWebBoxCommunication : public QObject -{ - Q_OBJECT -public: - explicit SunnyWebBoxCommunication(NetworkAccessManager *networkAccessManager, QObject *parent = nullptr); - - QString sendMessage(const QHostAddress &address, const QString &procedure); - QString sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms); - -private: - NetworkAccessManager *m_networkManager; - int m_port = 34268; - bool m_messageResponsePending = false; - QUdpSocket *m_udpSocket; - QHash> m_messageQueue; - - void datagramReceived(const QHostAddress &address, const QByteArray &data); - -signals: - void socketConnected(bool connected); - void messageReceived(const QHostAddress &address, const QString &messageId, const QString &messageType, const QVariantMap &result); - -}; - -#endif // SUNNYWEBBOXCOMMUNICATION_H From ab26a22d002620b76a98b975391ecc5ad2d0f3ea Mon Sep 17 00:00:00 2001 From: Boernsman Date: Sun, 28 Feb 2021 21:47:57 +0100 Subject: [PATCH 26/34] reduced feature set --- sma/integrationpluginsma.cpp | 139 +--------------------------------- sma/integrationpluginsma.h | 12 --- sma/integrationpluginsma.json | 72 +----------------- sma/sunnywebbox.h | 12 +-- 4 files changed, 9 insertions(+), 226 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 4d679db9..75bedc95 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -104,30 +104,8 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) info->finish(Thing::ThingErrorNoError); connect(sunnyWebBox, &SunnyWebBox::connectedChanged, this, &IntegrationPluginSma::onConnectedChanged); connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, this, &IntegrationPluginSma::onPlantOverviewReceived); - connect(sunnyWebBox, &SunnyWebBox::devicesReceived, this, &IntegrationPluginSma::onDevicesReceived); - connect(sunnyWebBox, &SunnyWebBox::processDataReceived, this, &IntegrationPluginSma::onProcessDataReceived); - connect(sunnyWebBox, &SunnyWebBox::parameterChannelsReceived, this, &IntegrationPluginSma::onParameterChannelsReceived); - connect(sunnyWebBox, &SunnyWebBox::parametersReceived, this, &IntegrationPluginSma::onParametersReceived); m_sunnyWebBoxes.insert(info->thing(), sunnyWebBox); }); - - } else if (thing->thingClassId() == inverterThingClassId) { - Thing *parentThing = myThings().findById(thing->parentId()); - if (!parentThing) { - qCWarning(dcSma()) << "Could not find parentThing for thing " << thing->name(); - return info->finish(Thing::ThingErrorHardwareNotAvailable, "Please try again"); - } - if (parentThing->setupComplete()) { - info->finish(Thing::ThingErrorNoError); - } else { - //wait for the parent to finish the setup process - connect(parentThing, &Thing::setupStatusChanged, info, [info, parentThing] { - - if (parentThing->setupComplete()) - info->finish(Thing::ThingErrorNoError); - }); - return; - } } else { Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); } @@ -142,37 +120,6 @@ void IntegrationPluginSma::postSetupThing(Thing *thing) return; sunnyWebBox->getDevices(); thing->setStateValue(sunnyWebBoxConnectedStateTypeId, true); - } else if (thing->thingClassId() == inverterThingClassId) { - SunnyWebBox *sunnyWebBox = m_sunnyWebBoxes.value(myThings().findById(thing->parentId())); - if (!sunnyWebBox) - return; - QString key = thing->paramValue(inverterThingIdParamTypeId).toString(); - sunnyWebBox->getParameters(QStringList() << key); - sunnyWebBox->getProcessData(QStringList() << key); - } -} - -void IntegrationPluginSma::executeAction(ThingActionInfo *info) -{ - Thing *thing = info->thing(); - Action action = info->action(); - - if (thing->thingClassId() == sunnyWebBoxThingClassId) { - SunnyWebBox *sunnyWebBox = m_sunnyWebBoxes.value(thing); - if (!sunnyWebBox) - return; - if (action.actionTypeId() == sunnyWebBoxSearchDevicesActionTypeId) { - QString requestId = sunnyWebBox->getDevices(); - if (requestId.isEmpty()) { - return info->finish(Thing::ThingErrorHardwareNotAvailable); - } - m_asyncActions.insert(requestId, info); - connect(info, &ThingActionInfo::aborted, info, [requestId, this] {m_asyncActions.remove(requestId);}); - } else { - Q_ASSERT_X(false, "executeAction", QString("Unhandled actionTypeId: %1").arg(action.actionTypeId().toString()).toUtf8()); - } - } else { - Q_ASSERT_X(false, "executeAction", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); } } @@ -194,11 +141,6 @@ void IntegrationPluginSma::onRefreshTimer() Q_FOREACH(Thing *thing, myThings().filterByThingClassId(sunnyWebBoxThingClassId)) { SunnyWebBox *sunnyWebBox = m_sunnyWebBoxes.value(thing); sunnyWebBox->getPlantOverview(); - - Q_FOREACH(Thing *child, myThings().filterByParentId(thing->id())) { - QString key = child->paramValue(inverterThingIdParamTypeId).toString(); - sunnyWebBox->getProcessData(QStringList() << key); - } } } @@ -208,21 +150,13 @@ void IntegrationPluginSma::onConnectedChanged(bool connected) if (!thing) return; thing->setStateValue(sunnyWebBoxConnectedStateTypeId, connected); - if (!connected) { - Q_FOREACH(Thing *child, myThings().filterByParentId(thing->id())) { - child->setStateValue(inverterConnectedStateTypeId, false); - } - } } void IntegrationPluginSma::onPlantOverviewReceived(const QString &messageId, SunnyWebBox::Overview overview) { - qCDebug(dcSma()) << "Plant overview received" << overview.status; - if (m_asyncSetup.contains(messageId)) { - ThingSetupInfo *info = m_asyncSetup.value(messageId); - info->finish(Thing::ThingErrorNoError); - } + Q_UNUSED(messageId) + qCDebug(dcSma()) << "Plant overview received" << overview.status; Thing *thing = m_sunnyWebBoxes.key(static_cast(sender())); if (!thing) return; @@ -236,72 +170,3 @@ void IntegrationPluginSma::onPlantOverviewReceived(const QString &messageId, Sun thing->setStateValue(sunnyWebBoxErrorStateTypeId, overview.error); } } - -void IntegrationPluginSma::onDevicesReceived(const QString &messageId, QList devices) -{ - qCDebug(dcSma()) << "Devices received, count:" << devices.count(); - if (m_asyncActions.contains(messageId)) { - ThingActionInfo *info = m_asyncActions.value(messageId); - info->finish(Thing::ThingErrorNoError); - } - - Thing *thing = m_sunnyWebBoxes.key(static_cast(sender())); - if (!thing) - return; - - ThingDescriptors descriptors; - Q_FOREACH(SunnyWebBox::Device device, devices){ - qCDebug(dcSma()) << " - Device received" << device.name << device.key; - if (myThings().findByParams(ParamList() << Param(inverterThingIdParamTypeId, device.key))) - continue; - ThingDescriptor descriptor(inverterThingClassId, device.name, device.key ,thing->id()); - ParamList params; - params << Param(inverterThingIdParamTypeId, device.key); - descriptor.setParams(params); - descriptors.append(descriptor); - } - emit autoThingsAppeared(descriptors); -} - -void IntegrationPluginSma::onParametersReceived(const QString &messageId, const QString &deviceKey, const QList ¶meters) -{ - Q_UNUSED(messageId); - Thing *thing = myThings().findByParams(ParamList() << Param(inverterThingIdParamTypeId, deviceKey)); - if (!thing) - return; - qCDebug(dcSma()) << "Parameters received"; - Q_FOREACH(SunnyWebBox::Parameter parameter, parameters) { - qCDebug(dcSma()) << " - " << parameter.name << parameter.value << parameter.unit; - } -} - -void IntegrationPluginSma::onProcessDataReceived(const QString &messageId, const QString &deviceKey, const QHash &channels) -{ - Q_UNUSED(messageId) - Thing *thing = m_sunnyWebBoxes.key(static_cast(sender())); - if (!thing) - return; - - qCDebug(dcSma()) << "Process data received" << deviceKey; - - Q_FOREACH(Thing *childThing, myThings().filterByParentId(thing->id())) { - if (childThing->paramValue(inverterThingIdParamTypeId).toString() == deviceKey) { - Q_FOREACH(QString channel, channels.keys()) { - qCDebug(dcSma()) << " - Channel received" << channel << channels.value(channel); - //TODO parse data - } - break; - } - } -} - -void IntegrationPluginSma::onParameterChannelsReceived(const QString &messageId, const QString &deviceKey, QStringList parameterChannels) -{ - Q_UNUSED(messageId) - - Thing *thing = m_sunnyWebBoxes.key(static_cast(sender())); - if (!thing) - return; - - qCDebug(dcSma()) << "Parameter channels received" << deviceKey << parameterChannels; -} diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 5fef831c..348de1d5 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -34,13 +34,9 @@ #include "integrations/integrationplugin.h" #include "plugintimer.h" #include "sunnywebbox.h" -#include "sunnywebboxcommunication.h" #include "discovery.h" #include -#include -#include - class IntegrationPluginSma: public IntegrationPlugin { Q_OBJECT @@ -53,7 +49,6 @@ public: void discoverThings(ThingDiscoveryInfo *info) override; void setupThing(ThingSetupInfo *info) override; void postSetupThing(Thing *thing) override; - void executeAction(ThingActionInfo *info) override; void thingRemoved(Thing *thing) override; private slots: @@ -61,17 +56,10 @@ private slots: void onConnectedChanged(bool connected); void onPlantOverviewReceived(const QString &messageId, SunnyWebBox::Overview overview); - void onDevicesReceived(const QString &messageId, QList devices); - void onParametersReceived(const QString &messageId, const QString &deviceKey, const QList ¶meters); - void onProcessDataReceived(const QString &messageId, const QString &deviceKey, const QHash &channels); - void onParameterChannelsReceived(const QString &messageId, const QString &deviceKey, QStringList parameterChannels); private: PluginTimer *m_refreshTimer = nullptr; QHash m_sunnyWebBoxes; - QHash m_asyncSetup; - QHash m_asyncActions; - SunnyWebBoxCommunication *m_sunnyWebBoxCommunication = nullptr; }; #endif // INTEGRATIONPLUGINSMA_H diff --git a/sma/integrationpluginsma.json b/sma/integrationpluginsma.json index babbc720..58b5c921 100644 --- a/sma/integrationpluginsma.json +++ b/sma/integrationpluginsma.json @@ -13,7 +13,7 @@ "name": "sunnyWebBox", "displayName": "Sunny WebBox", "createMethods": ["user", "discovery"], - "interfaces": ["extendedsmartmeterproducer", "gateway"], + "interfaces": ["extendedsmartmeterproducer"], "paramTypes": [ { "id": "864d4162-e3ce-48b8-b8ac-c1b971b52d42", @@ -84,76 +84,6 @@ "type": "QString", "defaultValue": "None" } - ], - "actionTypes": [ - { - "id": "15fc3dac-1868-4490-ba41-7c2f545926ad", - "name": "searchDevices", - "displayName": "Search new devices" - } - ] - }, - { - "id": "9cb72321-042e-4912-b23e-18516b6bbe96", - "name": "inverter", - "displayName": "Solar Inverter", - "createMethods": ["auto"], - "interfaces" : ["extendedsmartmeterproducer", "connectable"], - "paramTypes": [ - { - "id": "f43e8159-7337-4bd0-ba74-b6630e554e43", - "name": "id", - "displayName": "Device ID", - "type": "QString", - "readOnly": true - } - ], - "stateTypes": [ - { - "id": "eda29c50-73ac-40e0-9c92-26fee352e688", - "name": "connected", - "displayName": "Connected", - "displayNameEvent": "Connected changed", - "type": "bool", - "defaultValue": false, - "cached": false - }, - { - "id": "a804eabf-d5b8-4c83-84e4-8ec994875950", - "name": "currentPower", - "displayName": "Current power", - "displayNameEvent": "Current Power changed", - "type": "double", - "unit": "Watt", - "defaultValue": "0" - }, - { - "id": "cd3a0abc-37cc-4f9c-8ff2-c7ccc2513880", - "name": "eday", - "displayName": "Energy of current day", - "displayNameEvent": "Energy of day changed", - "type": "double", - "unit": "KiloWattHour", - "defaultValue": "0" - }, - { - "id": "341596ec-690b-4bf6-b6ed-0f92fa1c7d6c", - "name": "eyear", - "displayName": "Energy of current year", - "displayNameEvent": "Energy of year changed", - "type": "int", - "unit": "KiloWattHour", - "defaultValue": "0" - }, - { - "id": "6b6c1ddb-692f-400f-8b8d-38e0e0ae34ba", - "name": "totalEnergyProduced", - "displayName": "Energy total", - "displayNameEvent": "Energy total changed", - "type": "double", - "unit": "KiloWattHour", - "defaultValue": "0" - } ] } ] diff --git a/sma/sunnywebbox.h b/sma/sunnywebbox.h index 8928210e..8b72a2ef 100644 --- a/sma/sunnywebbox.h +++ b/sma/sunnywebbox.h @@ -32,7 +32,7 @@ #define SUNNYWEBBOX_H #include "integrations/thing.h" -#include "sunnywebboxcommunication.h" +#include "network/networkaccessmanager.h" #include #include @@ -77,11 +77,11 @@ public: ~SunnyWebBox(); QString getPlantOverview(); // Returns an object with the following plant data: PAC, E-TODAY, E-TOTAL, MODE, ERROR - QString getDevices(); //Returns a hierarchical list of all detected plant devices. + QString getDevices(); // Returns a hierarchical list of all detected plant devices. QString getProcessDataChannels(const QString &deviceKey); //Returns a list with the meta names of the available process data channels for a particular device type. - QString getProcessData(const QStringList &deviceKeys); //Returns process data for up to 5 devices per request. - QString getParameterChannels(const QString &deviceKey); //Returns a list with the meta names of the available parameter channels for a particular device type - QString getParameters(const QStringList &deviceKeys); //Returns the parameter values of up to 5 devices + QString getProcessData(const QStringList &deviceKeys); //Returns process data for up to 5 devices per request. + QString getParameterChannels(const QString &deviceKey); //Returns a list with the meta names of the available parameter channels for a particular device type + QString getParameters(const QStringList &deviceKeys); //Returns the parameter values of up to 5 devices QString setParameters(const QString &deviceKeys, const QHash &channels); //Sets parameter values void setHostAddress(const QHostAddress &address); @@ -95,8 +95,8 @@ private: QString sendMessage(const QHostAddress &address, const QString &procedure); QString sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms); void parseMessage(const QString &messageId, const QString &messageType, const QVariantMap &result); - void setConnectionStatus(bool connected); + signals: void connectedChanged(bool connected); From 93bb41caf9c5bae66223c76b44692ecdbc8a4aac Mon Sep 17 00:00:00 2001 From: Boernsman Date: Sun, 28 Feb 2021 22:18:29 +0100 Subject: [PATCH 27/34] shortened polling interval --- sma/integrationpluginsma.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 75bedc95..242bebe4 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -77,7 +77,7 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) if (!m_refreshTimer) { qCDebug(dcSma()) << "Starting refresh timer"; - m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(30); + m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(1); connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSma::onRefreshTimer); } @@ -118,7 +118,7 @@ void IntegrationPluginSma::postSetupThing(Thing *thing) SunnyWebBox *sunnyWebBox = m_sunnyWebBoxes.value(thing); if (!sunnyWebBox) return; - sunnyWebBox->getDevices(); + sunnyWebBox->getPlantOverview(); thing->setStateValue(sunnyWebBoxConnectedStateTypeId, true); } } From c1f6e42d9c8dfeb576e8b604701314e132780bd8 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Sat, 6 Mar 2021 10:48:23 +0100 Subject: [PATCH 28/34] removed unintended snapd .pro entry --- nymea-plugins.pro | 1 - 1 file changed, 1 deletion(-) diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 8d056c67..cead44a7 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -54,7 +54,6 @@ PLUGIN_DIRS = \ serialportcommander \ simulation \ sma \ - snapd \ somfytahoma \ sonos \ sunposition \ From 05331143ddeee11cdf85a6682da1384eb0da8d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 18 Mar 2021 11:19:09 +0100 Subject: [PATCH 29/34] Create timer after setup to prevent crash on startup --- sma/integrationpluginsma.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 242bebe4..1e07df4e 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -75,12 +75,6 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) Thing *thing = info->thing(); qCDebug(dcSma()) << "Setup thing" << thing->name(); - if (!m_refreshTimer) { - qCDebug(dcSma()) << "Starting refresh timer"; - m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(1); - connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSma::onRefreshTimer); - } - if (thing->thingClassId() == sunnyWebBoxThingClassId) { //check if a Sunny WebBox is already added with this IPv4Address foreach(SunnyWebBox *sunnyWebBox, m_sunnyWebBoxes.values()) { @@ -106,6 +100,13 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, this, &IntegrationPluginSma::onPlantOverviewReceived); m_sunnyWebBoxes.insert(info->thing(), sunnyWebBox); }); + + if (!m_refreshTimer) { + qCDebug(dcSma()) << "Starting refresh timer"; + m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(1); + connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSma::onRefreshTimer); + } + } else { Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); } From b25ad6e35b5da2f02efe11a36f49cff27f771a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 18 Mar 2021 11:33:39 +0100 Subject: [PATCH 30/34] Fix lambda and start timer after setup finished --- sma/integrationpluginsma.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 1e07df4e..6a8b2cff 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -93,20 +93,21 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) connect(info, &ThingSetupInfo::aborted, sunnyWebBox, &SunnyWebBox::deleteLater); connect(sunnyWebBox, &SunnyWebBox::destroyed, this, [thing, this] { m_sunnyWebBoxes.remove(thing);}); QString requestId = sunnyWebBox->getPlantOverview(); - connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, info, [sunnyWebBox, info, this] { - qCDebug(dcSma()) << "Received plant overview, finishing setup"; + connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, info, [=] (const QString &messageId, SunnyWebBox::Overview overview) { + qCDebug(dcSma()) << "Received plant overview" << messageId << "Finish setup"; + Q_UNUSED(overview) + info->finish(Thing::ThingErrorNoError); connect(sunnyWebBox, &SunnyWebBox::connectedChanged, this, &IntegrationPluginSma::onConnectedChanged); connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, this, &IntegrationPluginSma::onPlantOverviewReceived); m_sunnyWebBoxes.insert(info->thing(), sunnyWebBox); + + if (!m_refreshTimer) { + qCDebug(dcSma()) << "Starting refresh timer"; + m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(1); + connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSma::onRefreshTimer); + } }); - - if (!m_refreshTimer) { - qCDebug(dcSma()) << "Starting refresh timer"; - m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(1); - connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSma::onRefreshTimer); - } - } else { Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); } From 4f6c29acdf2686de46751c64e5402838f332be4c Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 27 Jun 2021 23:13:43 +0200 Subject: [PATCH 31/34] Update to interface changes --- sma/integrationpluginsma.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sma/integrationpluginsma.json b/sma/integrationpluginsma.json index 58b5c921..0a542fe4 100644 --- a/sma/integrationpluginsma.json +++ b/sma/integrationpluginsma.json @@ -13,7 +13,7 @@ "name": "sunnyWebBox", "displayName": "Sunny WebBox", "createMethods": ["user", "discovery"], - "interfaces": ["extendedsmartmeterproducer"], + "interfaces": ["smartmeterproducer"], "paramTypes": [ { "id": "864d4162-e3ce-48b8-b8ac-c1b971b52d42", From ae77bcef7c40aac903cebb78ddb3ab67a588eb3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 2 Sep 2021 11:26:01 +0200 Subject: [PATCH 32/34] Update SMA plugin to internal network discovery --- sma/discovery.cpp | 271 ----------------------------------- sma/discovery.h | 77 ---------- sma/host.cpp | 93 ------------ sma/host.h | 69 --------- sma/integrationpluginsma.cpp | 64 +++++---- sma/integrationpluginsma.h | 1 - sma/sma.pro | 11 +- 7 files changed, 43 insertions(+), 543 deletions(-) delete mode 100644 sma/discovery.cpp delete mode 100644 sma/discovery.h delete mode 100644 sma/host.cpp delete mode 100644 sma/host.h diff --git a/sma/discovery.cpp b/sma/discovery.cpp deleted file mode 100644 index fca14ef5..00000000 --- a/sma/discovery.cpp +++ /dev/null @@ -1,271 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, nymea GmbH -* Contact: contact@nymea.io -* -* This file is part of nymea. -* This project including source code and documentation is protected by -* copyright law, and remains the property of nymea GmbH. All rights, including -* reproduction, publication, editing and translation, are reserved. The use of -* this project is subject to the terms of a license agreement to be concluded -* with nymea GmbH in accordance with the terms of use of nymea GmbH, available -* under https://nymea.io/license -* -* GNU Lesser General Public License Usage -* Alternatively, this project may be redistributed and/or modified under the -* terms of the GNU Lesser General Public License as published by the Free -* Software Foundation; version 3. This project is distributed in the hope that -* it will be useful, but WITHOUT ANY WARRANTY; without even the implied -* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public License -* along with this project. If not, see . -* -* For any further details and any questions please contact us under -* contact@nymea.io or see our FAQ/Licensing Information on -* https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#include "discovery.h" -#include "extern-plugininfo.h" - -#include -#include -#include -#include -#include - -Discovery::Discovery(QObject *parent) : QObject(parent) -{ - connect(&m_timeoutTimer, &QTimer::timeout, this, &Discovery::onTimeout); -} - -void Discovery::discoverHosts(int timeout) -{ - if (isRunning()) { - qWarning(dcSma()) << "Discovery already running. Cannot start twice."; - return; - } - m_timeoutTimer.start(timeout * 1000); - - foreach (const QString &target, getDefaultTargets()) { - QProcess *discoveryProcess = new QProcess(this); - m_discoveryProcesses.append(discoveryProcess); - connect(discoveryProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(discoveryFinished(int,QProcess::ExitStatus))); - - QStringList arguments; - arguments << "-oX" << "-" << "-n" << "-sn"; - arguments << target; - - qCDebug(dcSma()) << "Scanning network:" << "nmap" << arguments.join(" "); - discoveryProcess->start(QStringLiteral("nmap"), arguments); - } - -} - -void Discovery::abort() -{ - foreach (QProcess *discoveryProcess, m_discoveryProcesses) { - if (discoveryProcess->state() == QProcess::Running) { - qCDebug(dcSma()) << "Kill running discovery process"; - discoveryProcess->terminate(); - discoveryProcess->waitForFinished(5000); - } - } - foreach (QProcess *p, m_pendingArpLookups.keys()) { - p->terminate(); - delete p; - } - m_pendingArpLookups.clear(); - m_pendingNameLookups.clear(); - qDeleteAll(m_scanResults); - m_scanResults.clear(); -} - -bool Discovery::isRunning() const -{ - return !m_discoveryProcesses.isEmpty() || !m_pendingArpLookups.isEmpty() || !m_pendingNameLookups.isEmpty(); -} - -void Discovery::discoveryFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - QProcess *discoveryProcess = static_cast(sender()); - - if (exitCode != 0 || exitStatus != QProcess::NormalExit) { - qCWarning(dcSma()) << "Nmap error failed. Is nmap installed correctly?"; - m_discoveryProcesses.removeAll(discoveryProcess); - discoveryProcess->deleteLater(); - discoveryProcess = nullptr; - finishDiscovery(); - return; - } - - QByteArray data = discoveryProcess->readAll(); - m_discoveryProcesses.removeAll(discoveryProcess); - discoveryProcess->deleteLater(); - discoveryProcess = nullptr; - - QXmlStreamReader reader(data); - - int foundHosts = 0; - - while (!reader.atEnd() && !reader.hasError()) { - QXmlStreamReader::TokenType token = reader.readNext(); - if(token == QXmlStreamReader::StartDocument) - continue; - - if(token == QXmlStreamReader::StartElement && reader.name() == "host") { - bool isUp = false; - QString address; - QString macAddress; - QString vendor; - while (!reader.atEnd() && !reader.hasError() && !(token == QXmlStreamReader::EndElement && reader.name() == "host")) { - token = reader.readNext(); - - if (reader.name() == "address") { - QString addr = reader.attributes().value("addr").toString(); - QString type = reader.attributes().value("addrtype").toString(); - if (type == "ipv4" && !addr.isEmpty()) { - address = addr; - } else if (type == "mac") { - macAddress = addr; - vendor = reader.attributes().value("vendor").toString(); - } - } - - if (reader.name() == "status") { - QString state = reader.attributes().value("state").toString(); - if (!state.isEmpty()) - isUp = state == "up"; - } - } - - if (isUp) { - foundHosts++; - qCDebug(dcSma()) << "Have host:" << address; - - Host *host = new Host(); - host->setAddress(address); - - if (!macAddress.isEmpty()) { - host->setMacAddress(macAddress); - } else { - QProcess *arpLookup = new QProcess(this); - m_pendingArpLookups.insert(arpLookup, host); - connect(arpLookup, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(arpLookupDone(int,QProcess::ExitStatus))); - arpLookup->start("arp", {"-vn"}); - } - - host->setHostName(vendor); - QHostInfo::lookupHost(address, this, SLOT(hostLookupDone(QHostInfo))); - m_pendingNameLookups.insert(address, host); - - m_scanResults.append(host); - } - } - } - - if (foundHosts == 0 && m_discoveryProcesses.isEmpty()) { - qCDebug(dcSma()) << "Network scan successful but no hosts found in this network"; - finishDiscovery(); - } -} - -void Discovery::hostLookupDone(const QHostInfo &info) -{ - Host *host = m_pendingNameLookups.take(info.addresses().first().toString()); - if (!host) { - // Probably aborted... - return; - } - if (info.error() != QHostInfo::NoError) { - qWarning(dcSma()) << "Host lookup failed:" << info.errorString(); - } - if (host->hostName().isEmpty() || info.hostName() != host->address()) { - host->setHostName(info.hostName()); - } - - finishDiscovery(); -} - -void Discovery::arpLookupDone(int exitCode, QProcess::ExitStatus exitStatus) -{ - QProcess *p = static_cast(sender()); - p->deleteLater(); - - Host *host = m_pendingArpLookups.take(p); - - if (exitCode != 0 || exitStatus != QProcess::NormalExit) { - qCWarning(dcSma()) << "ARP lookup process failed for host" << host->address(); - finishDiscovery(); - return; - } - - QString data = QString::fromLatin1(p->readAll()); - foreach (QString line, data.split('\n')) { - line.replace(QRegExp("[ ]{1,}"), " "); - QStringList parts = line.split(" "); - if (parts.count() >= 3 && parts.first() == host->address() && parts.at(1) == "ether") { - host->setMacAddress(parts.at(2)); - break; - } - } - finishDiscovery(); -} - -void Discovery::onTimeout() -{ - qWarning(dcSma()) << "Timeout hit. Stopping discovery"; - while (!m_discoveryProcesses.isEmpty()) { - QProcess *discoveryProcess = m_discoveryProcesses.takeFirst(); - disconnect(this, SLOT(discoveryFinished(int,QProcess::ExitStatus))); - discoveryProcess->terminate(); - delete discoveryProcess; - } - foreach (QProcess *p, m_pendingArpLookups.keys()) { - p->terminate(); - m_scanResults.removeAll(m_pendingArpLookups.value(p)); - delete p; - } - m_pendingArpLookups.clear(); - m_pendingNameLookups.clear(); - finishDiscovery(); -} - -QStringList Discovery::getDefaultTargets() -{ - QStringList targets; - foreach (const QHostAddress &interface, QNetworkInterface::allAddresses()) { - if (!interface.isLoopback() && interface.scopeId().isEmpty() && interface.protocol() == QAbstractSocket::IPv4Protocol) { - QPair pair = QHostAddress::parseSubnet(interface.toString() + "/24"); - QString newTarget = QString("%1/%2").arg(pair.first.toString()).arg(pair.second); - if (!targets.contains(newTarget)) { - targets.append(newTarget); - } - } - } - return targets; -} - -void Discovery::finishDiscovery() -{ - if (m_discoveryProcesses.count() > 0 || m_pendingNameLookups.count() > 0 || m_pendingArpLookups.count() > 0) { - // Still busy... - return; - } - - QList hosts; - foreach (Host *host, m_scanResults) { - if (!host->macAddress().isEmpty()) { - hosts.append(*host); - } - } - qDeleteAll(m_scanResults); - m_scanResults.clear(); - - qCDebug(dcSma()) << "Emitting thing discovered for" << hosts.count() << "devices"; - m_timeoutTimer.stop(); - emit finished(hosts); -} diff --git a/sma/discovery.h b/sma/discovery.h deleted file mode 100644 index b74fe2cb..00000000 --- a/sma/discovery.h +++ /dev/null @@ -1,77 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, nymea GmbH -* Contact: contact@nymea.io -* -* This file is part of nymea. -* This project including source code and documentation is protected by -* copyright law, and remains the property of nymea GmbH. All rights, including -* reproduction, publication, editing and translation, are reserved. The use of -* this project is subject to the terms of a license agreement to be concluded -* with nymea GmbH in accordance with the terms of use of nymea GmbH, available -* under https://nymea.io/license -* -* GNU Lesser General Public License Usage -* Alternatively, this project may be redistributed and/or modified under the -* terms of the GNU Lesser General Public License as published by the Free -* Software Foundation; version 3. This project is distributed in the hope that -* it will be useful, but WITHOUT ANY WARRANTY; without even the implied -* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public License -* along with this project. If not, see . -* -* For any further details and any questions please contact us under -* contact@nymea.io or see our FAQ/Licensing Information on -* https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#ifndef DISCOVERY_H -#define DISCOVERY_H - -#include -#include -#include -#include - -#include "host.h" - -class Discovery : public QObject -{ - Q_OBJECT -public: - explicit Discovery(QObject *parent = nullptr); - - void discoverHosts(int timeout); - void abort(); - - bool isRunning() const; - - -signals: - void finished(QList hosts); - -private: - QStringList getDefaultTargets(); - - void finishDiscovery(); - -private slots: - void discoveryFinished(int exitCode, QProcess::ExitStatus exitStatus); - void hostLookupDone(const QHostInfo &info); - void arpLookupDone(int exitCode, QProcess::ExitStatus exitStatus); - void onTimeout(); - -private: - QList m_discoveryProcesses; - QTimer m_timeoutTimer; - - QHash m_pendingArpLookups; - QHash m_pendingNameLookups; - QList m_scanResults; - -}; - -#endif // DISCOVERY_H diff --git a/sma/host.cpp b/sma/host.cpp deleted file mode 100644 index 2aec4ab6..00000000 --- a/sma/host.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, nymea GmbH -* Contact: contact@nymea.io -* -* This file is part of nymea. -* This project including source code and documentation is protected by -* copyright law, and remains the property of nymea GmbH. All rights, including -* reproduction, publication, editing and translation, are reserved. The use of -* this project is subject to the terms of a license agreement to be concluded -* with nymea GmbH in accordance with the terms of use of nymea GmbH, available -* under https://nymea.io/license -* -* GNU Lesser General Public License Usage -* Alternatively, this project may be redistributed and/or modified under the -* terms of the GNU Lesser General Public License as published by the Free -* Software Foundation; version 3. This project is distributed in the hope that -* it will be useful, but WITHOUT ANY WARRANTY; without even the implied -* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public License -* along with this project. If not, see . -* -* For any further details and any questions please contact us under -* contact@nymea.io or see our FAQ/Licensing Information on -* https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#include "host.h" - -Host::Host() -{ - qRegisterMetaType(); - qRegisterMetaType >(); -} - -QString Host::macAddress() const -{ - return m_macAddress; -} - -void Host::setMacAddress(const QString &macAddress) -{ - m_macAddress = macAddress; -} - -QString Host::hostName() const -{ - return m_hostName; -} - -void Host::setHostName(const QString &hostName) -{ - m_hostName = hostName; -} - -QString Host::address() const -{ - return m_address; -} - -void Host::setAddress(const QString &address) -{ - m_address = address; -} - -void Host::seen() -{ - m_lastSeenTime = QDateTime::currentDateTime(); -} - -QDateTime Host::lastSeenTime() const -{ - return m_lastSeenTime; -} - -bool Host::reachable() const -{ - return m_reachable; -} - -void Host::setReachable(bool reachable) -{ - m_reachable = reachable; -} - -QDebug operator<<(QDebug dbg, const Host &host) -{ - dbg.nospace() << "Host(" << host.macAddress() << "," << host.hostName() << ", " << host.address() << ", " << (host.reachable() ? "up" : "down") << ")"; - return dbg.space(); -} diff --git a/sma/host.h b/sma/host.h deleted file mode 100644 index 84e48b0d..00000000 --- a/sma/host.h +++ /dev/null @@ -1,69 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, nymea GmbH -* Contact: contact@nymea.io -* -* This file is part of nymea. -* This project including source code and documentation is protected by -* copyright law, and remains the property of nymea GmbH. All rights, including -* reproduction, publication, editing and translation, are reserved. The use of -* this project is subject to the terms of a license agreement to be concluded -* with nymea GmbH in accordance with the terms of use of nymea GmbH, available -* under https://nymea.io/license -* -* GNU Lesser General Public License Usage -* Alternatively, this project may be redistributed and/or modified under the -* terms of the GNU Lesser General Public License as published by the Free -* Software Foundation; version 3. This project is distributed in the hope that -* it will be useful, but WITHOUT ANY WARRANTY; without even the implied -* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public License -* along with this project. If not, see . -* -* For any further details and any questions please contact us under -* contact@nymea.io or see our FAQ/Licensing Information on -* https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#ifndef HOST_H -#define HOST_H - -#include -#include -#include - -class Host -{ -public: - Host(); - - QString macAddress() const; - void setMacAddress(const QString &macAddress); - - QString hostName() const; - void setHostName(const QString &hostName); - - QString address() const; - void setAddress(const QString &address); - - void seen(); - QDateTime lastSeenTime() const; - - bool reachable() const; - void setReachable(bool reachable); - -private: - QString m_macAddress; - QString m_hostName; - QString m_address; - QDateTime m_lastSeenTime; - bool m_reachable; -}; -Q_DECLARE_METATYPE(Host) - -QDebug operator<<(QDebug dbg, const Host &host); - -#endif // HOST_H diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 6a8b2cff..1968c28a 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -31,6 +31,8 @@ #include "integrationpluginsma.h" #include "plugininfo.h" +#include "network/networkdevicediscovery.h" + IntegrationPluginSma::IntegrationPluginSma() { @@ -38,36 +40,50 @@ IntegrationPluginSma::IntegrationPluginSma() void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) { - if (info->thingClassId() == sunnyWebBoxThingClassId) { + if (!hardwareManager()->networkDeviceDiscovery()->available()) { + qCWarning(dcSma()) << "Failed to discover network devices. The network device discovery is not available."; + info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Unable to discover devices in your network.")); + return; + } - Discovery *discovery = new Discovery(this); - discovery->discoverHosts(25); + qCDebug(dcSma()) << "Starting network discovery..."; + NetworkDeviceDiscoveryReply *discoveryReply = hardwareManager()->networkDeviceDiscovery()->discover(); + connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){ + ThingDescriptors descriptors; + qCDebug(dcSma()) << "Discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "devices"; + foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) { + // Filter for sma hosts + if (!networkDeviceInfo.hostName().toLower().contains("sma")) + continue; - // clean up discovery object when this discovery info is deleted - connect(info, &ThingDiscoveryInfo::destroyed, discovery, &Discovery::deleteLater); - connect(discovery, &Discovery::finished, info, [this, info](const QList &hosts) { - qCDebug(dcSma()) << "Discovery finished. Found" << hosts.count() << "devices"; - foreach (const Host &host, hosts) { - if (host.hostName().contains("SMA")){ - ThingDescriptor descriptor(info->thingClassId(), host.hostName(), host.address() + " (" + host.macAddress() + ")"); + QString title = networkDeviceInfo.hostName() + " (" + networkDeviceInfo.address().toString() + ")"; + QString description; + if (networkDeviceInfo.macAddressManufacturer().isEmpty()) { + description = networkDeviceInfo.macAddress(); + } else { + description = networkDeviceInfo.macAddress() + " (" + networkDeviceInfo.macAddressManufacturer() + ")"; + } - foreach (Thing *existingThing, myThings()) { - if (existingThing->paramValue(sunnyWebBoxThingMacAddressParamTypeId).toString() == host.macAddress()) { - descriptor.setThingId(existingThing->id()); - break; - } - } - ParamList params; - params << Param(sunnyWebBoxThingMacAddressParamTypeId, host.macAddress()); - params << Param(sunnyWebBoxThingHostParamTypeId, host.address()); - descriptor.setParams(params); + ThingDescriptor descriptor(sunnyWebBoxThingClassId, title, description); - info->addThingDescriptor(descriptor); + // Check for reconfiguration + foreach (Thing *existingThing, myThings()) { + if (existingThing->paramValue(sunnyWebBoxThingMacAddressParamTypeId).toString() == networkDeviceInfo.macAddress()) { + descriptor.setThingId(existingThing->id()); + break; } } - info->finish(Thing::ThingErrorNoError); - }); - } + + ParamList params; + params << Param(sunnyWebBoxThingMacAddressParamTypeId, networkDeviceInfo.macAddress()); + params << Param(sunnyWebBoxThingHostParamTypeId, networkDeviceInfo.address().toString()); + descriptor.setParams(params); + descriptors.append(descriptor); + } + info->addThingDescriptors(descriptors); + info->finish(Thing::ThingErrorNoError); + }); + } void IntegrationPluginSma::setupThing(ThingSetupInfo *info) diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 348de1d5..3c20919f 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -34,7 +34,6 @@ #include "integrations/integrationplugin.h" #include "plugintimer.h" #include "sunnywebbox.h" -#include "discovery.h" #include diff --git a/sma/sma.pro b/sma/sma.pro index 168f97f3..5e124771 100644 --- a/sma/sma.pro +++ b/sma/sma.pro @@ -1,16 +1,11 @@ include(../plugins.pri) -QT += \ - network \ +QT += network SOURCES += \ integrationpluginsma.cpp \ - sunnywebbox.cpp \ - host.cpp \ - discovery.cpp + sunnywebbox.cpp HEADERS += \ integrationpluginsma.h \ - sunnywebbox.h \ - host.h \ - discovery.h + sunnywebbox.h From 4ee701511716560b2e677199fe27edbeef3bd9d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 2 Sep 2021 11:32:15 +0200 Subject: [PATCH 33/34] Update duplicated sma setup check --- sma/integrationpluginsma.cpp | 18 +++++++++++------- sma/sunnywebbox.cpp | 14 ++++++++++++-- sma/sunnywebbox.h | 6 +++++- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 1968c28a..6dfa1835 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -75,8 +75,8 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) } ParamList params; - params << Param(sunnyWebBoxThingMacAddressParamTypeId, networkDeviceInfo.macAddress()); params << Param(sunnyWebBoxThingHostParamTypeId, networkDeviceInfo.address().toString()); + params << Param(sunnyWebBoxThingMacAddressParamTypeId, networkDeviceInfo.macAddress()); descriptor.setParams(params); descriptors.append(descriptor); } @@ -89,25 +89,29 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) void IntegrationPluginSma::setupThing(ThingSetupInfo *info) { Thing *thing = info->thing(); - qCDebug(dcSma()) << "Setup thing" << thing->name(); + qCDebug(dcSma()) << "Setup thing" << thing << thing->params(); if (thing->thingClassId() == sunnyWebBoxThingClassId) { - //check if a Sunny WebBox is already added with this IPv4Address - foreach(SunnyWebBox *sunnyWebBox, m_sunnyWebBoxes.values()) { - if(sunnyWebBox->hostAddress().toString() == thing->paramValue(sunnyWebBoxThingHostParamTypeId).toString()){ - //this logger at this IPv4 address is already added - qCWarning(dcSma()) << "thing at " << thing->paramValue(sunnyWebBoxThingHostParamTypeId).toString() << " already added!"; + // Check if a Sunny WebBox is already added with this mac address + foreach (SunnyWebBox *sunnyWebBox, m_sunnyWebBoxes.values()) { + if (sunnyWebBox->macAddress() == thing->paramValue(sunnyWebBoxThingMacAddressParamTypeId).toString()){ + qCWarning(dcSma()) << "Thing with mac address" << thing->paramValue(sunnyWebBoxThingMacAddressParamTypeId).toString() << " already added!"; info->finish(Thing::ThingErrorThingInUse); return; } } + if (m_sunnyWebBoxes.contains(thing)) { qCDebug(dcSma()) << "Setup after reconfiguration, cleaning up..."; m_sunnyWebBoxes.take(thing)->deleteLater(); } + SunnyWebBox *sunnyWebBox = new SunnyWebBox(hardwareManager()->networkManager(), QHostAddress(thing->paramValue(sunnyWebBoxThingHostParamTypeId).toString()), this); + sunnyWebBox->setMacAddress(thing->paramValue(sunnyWebBoxThingMacAddressParamTypeId).toString()); + connect(info, &ThingSetupInfo::aborted, sunnyWebBox, &SunnyWebBox::deleteLater); connect(sunnyWebBox, &SunnyWebBox::destroyed, this, [thing, this] { m_sunnyWebBoxes.remove(thing);}); + QString requestId = sunnyWebBox->getPlantOverview(); connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, info, [=] (const QString &messageId, SunnyWebBox::Overview overview) { qCDebug(dcSma()) << "Received plant overview" << messageId << "Finish setup"; diff --git a/sma/sunnywebbox.cpp b/sma/sunnywebbox.cpp index 94490875..f2b2c56a 100644 --- a/sma/sunnywebbox.cpp +++ b/sma/sunnywebbox.cpp @@ -121,15 +121,25 @@ QString SunnyWebBox::setParameters(const QString &deviceKey, const QHash &channels); //Sets parameter values + QHostAddress hostAddress() const; void setHostAddress(const QHostAddress &address); - QHostAddress hostAddress(); + + QString macAddress() const; + void setMacAddress(const QString &macAddress); private: bool m_connected = false; QHostAddress m_hostAddresss; + QString m_macAddress; NetworkAccessManager *m_networkManager = nullptr; QString sendMessage(const QHostAddress &address, const QString &procedure); From 78d9137311566f1d0daa6838b728d7d582703946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 2 Sep 2021 11:34:03 +0200 Subject: [PATCH 34/34] Update translations --- ...42bbf-9d3f-4aa2-9443-b3a31ae09bac-en_US.ts | 166 +++++------------- 1 file changed, 45 insertions(+), 121 deletions(-) diff --git a/sma/translations/b8442bbf-9d3f-4aa2-9443-b3a31ae09bac-en_US.ts b/sma/translations/b8442bbf-9d3f-4aa2-9443-b3a31ae09bac-en_US.ts index 60334163..6d411ddd 100644 --- a/sma/translations/b8442bbf-9d3f-4aa2-9443-b3a31ae09bac-en_US.ts +++ b/sma/translations/b8442bbf-9d3f-4aa2-9443-b3a31ae09bac-en_US.ts @@ -1,128 +1,64 @@ + + IntegrationPluginSma + + + Unable to discover devices in your network. + + + sma - - - - + + Connected - The name of the ParamType (ThingClass: inverter, EventType: connected, ID: {eda29c50-73ac-40e0-9c92-26fee352e688}) ----------- -The name of the StateType ({eda29c50-73ac-40e0-9c92-26fee352e688}) of ThingClass inverter ----------- -The name of the ParamType (ThingClass: sunnyWebBox, EventType: connected, ID: {c05e6a1a-252c-4f2b-8b31-09cf113d01c1}) + The name of the ParamType (ThingClass: sunnyWebBox, EventType: connected, ID: {c05e6a1a-252c-4f2b-8b31-09cf113d01c1}) ---------- The name of the StateType ({c05e6a1a-252c-4f2b-8b31-09cf113d01c1}) of ThingClass sunnyWebBox - - + Connected changed - The name of the EventType ({eda29c50-73ac-40e0-9c92-26fee352e688}) of ThingClass inverter ----------- -The name of the EventType ({c05e6a1a-252c-4f2b-8b31-09cf113d01c1}) of ThingClass sunnyWebBox + The name of the EventType ({c05e6a1a-252c-4f2b-8b31-09cf113d01c1}) of ThingClass sunnyWebBox - - Current Power changed - The name of the EventType ({a804eabf-d5b8-4c83-84e4-8ec994875950}) of ThingClass inverter - - - - - - - + + Current power - The name of the ParamType (ThingClass: inverter, EventType: currentPower, ID: {a804eabf-d5b8-4c83-84e4-8ec994875950}) ----------- -The name of the StateType ({a804eabf-d5b8-4c83-84e4-8ec994875950}) of ThingClass inverter ----------- -The name of the ParamType (ThingClass: sunnyWebBox, EventType: currentPower, ID: {ff4ff872-2f0f-4ca4-9fe2-220eeaf16cc2}) + The name of the ParamType (ThingClass: sunnyWebBox, EventType: currentPower, ID: {ff4ff872-2f0f-4ca4-9fe2-220eeaf16cc2}) ---------- The name of the StateType ({ff4ff872-2f0f-4ca4-9fe2-220eeaf16cc2}) of ThingClass sunnyWebBox - + Current power changed The name of the EventType ({ff4ff872-2f0f-4ca4-9fe2-220eeaf16cc2}) of ThingClass sunnyWebBox - - - Day energy - The name of the ParamType (ThingClass: sunnyWebBox, EventType: dayEnergy, ID: {16f34c5c-8dbb-4dcc-9faa-4b782d57226c}) + + + Day energy produced + The name of the ParamType (ThingClass: sunnyWebBox, EventType: dayEnergyProduced, ID: {16f34c5c-8dbb-4dcc-9faa-4b782d57226c}) ---------- The name of the StateType ({16f34c5c-8dbb-4dcc-9faa-4b782d57226c}) of ThingClass sunnyWebBox - - Day energy changed + + Day energy produced changed The name of the EventType ({16f34c5c-8dbb-4dcc-9faa-4b782d57226c}) of ThingClass sunnyWebBox - - Device ID - The name of the ParamType (ThingClass: inverter, Type: thing, ID: {f43e8159-7337-4bd0-ba74-b6630e554e43}) - - - - - - Energy of current day - The name of the ParamType (ThingClass: inverter, EventType: eday, ID: {cd3a0abc-37cc-4f9c-8ff2-c7ccc2513880}) ----------- -The name of the StateType ({cd3a0abc-37cc-4f9c-8ff2-c7ccc2513880}) of ThingClass inverter - - - - - - Energy of current year - The name of the ParamType (ThingClass: inverter, EventType: eyear, ID: {341596ec-690b-4bf6-b6ed-0f92fa1c7d6c}) ----------- -The name of the StateType ({341596ec-690b-4bf6-b6ed-0f92fa1c7d6c}) of ThingClass inverter - - - - - Energy of day changed - The name of the EventType ({cd3a0abc-37cc-4f9c-8ff2-c7ccc2513880}) of ThingClass inverter - - - - - Energy of year changed - The name of the EventType ({341596ec-690b-4bf6-b6ed-0f92fa1c7d6c}) of ThingClass inverter - - - - - - Energy total - The name of the ParamType (ThingClass: inverter, EventType: totalEnergyProduced, ID: {6b6c1ddb-692f-400f-8b8d-38e0e0ae34ba}) ----------- -The name of the StateType ({6b6c1ddb-692f-400f-8b8d-38e0e0ae34ba}) of ThingClass inverter - - - - - Energy total changed - The name of the EventType ({6b6c1ddb-692f-400f-8b8d-38e0e0ae34ba}) of ThingClass inverter - - - - - + + Error The name of the ParamType (ThingClass: sunnyWebBox, EventType: error, ID: {4e64f9ca-7e5a-4897-8035-6f2ae88fde89}) ---------- @@ -130,20 +66,20 @@ The name of the StateType ({4e64f9ca-7e5a-4897-8035-6f2ae88fde89}) of ThingClass - + Error changed The name of the EventType ({4e64f9ca-7e5a-4897-8035-6f2ae88fde89}) of ThingClass sunnyWebBox - + Host address The name of the ParamType (ThingClass: sunnyWebBox, Type: thing, ID: {864d4162-e3ce-48b8-b8ac-c1b971b52d42}) - - + + Mode The name of the ParamType (ThingClass: sunnyWebBox, EventType: mode, ID: {1974550b-6059-4b0e-83f4-70177e20dac3}) ---------- @@ -151,14 +87,14 @@ The name of the StateType ({1974550b-6059-4b0e-83f4-70177e20dac3}) of ThingClass - + Mode changed The name of the EventType ({1974550b-6059-4b0e-83f4-70177e20dac3}) of ThingClass sunnyWebBox - - + + SMA The name of the vendor ({16d5a4a3-36d5-46c0-b7dd-df166ddf5981}) ---------- @@ -166,40 +102,28 @@ The name of the plugin sma ({b8442bbf-9d3f-4aa2-9443-b3a31ae09bac}) - - Search new devices - The name of the ActionType ({15fc3dac-1868-4490-ba41-7c2f545926ad}) of ThingClass sunnyWebBox - - - - - Solar Inverter - The name of the ThingClass ({9cb72321-042e-4912-b23e-18516b6bbe96}) - - - - - Sunny WebBox - The name of the ThingClass ({49304127-ce9b-45dd-8511-05030a4ac003}) - - - - - - Total energy - The name of the ParamType (ThingClass: sunnyWebBox, EventType: totalEnergy, ID: {0bb4e227-7e38-49ca-9b32-ce4621c9305b}) + + + Total energy produced + The name of the ParamType (ThingClass: sunnyWebBox, EventType: totalEnergyProduced, ID: {0bb4e227-7e38-49ca-9b32-ce4621c9305b}) ---------- The name of the StateType ({0bb4e227-7e38-49ca-9b32-ce4621c9305b}) of ThingClass sunnyWebBox - - Total energy changed + + Total energy produced changed The name of the EventType ({0bb4e227-7e38-49ca-9b32-ce4621c9305b}) of ThingClass sunnyWebBox - + + Sunny WebBox + The name of the ThingClass ({49304127-ce9b-45dd-8511-05030a4ac003}) + + + + hardware address The name of the ParamType (ThingClass: sunnyWebBox, Type: thing, ID: {03f32361-4e13-4597-a346-af8d16a986b3})