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