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