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/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..cead44a7 100644
--- a/nymea-plugins.pro
+++ b/nymea-plugins.pro
@@ -53,6 +53,7 @@ PLUGIN_DIRS = \
senic \
serialportcommander \
simulation \
+ sma \
somfytahoma \
sonos \
sunposition \
diff --git a/sma/README.md b/sma/README.md
new file mode 100644
index 00000000..63ffbd03
--- /dev/null
+++ b/sma/README.md
@@ -0,0 +1,14 @@
+# 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/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp
new file mode 100644
index 00000000..6dfa1835
--- /dev/null
+++ b/sma/integrationpluginsma.cpp
@@ -0,0 +1,194 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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"
+
+#include "network/networkdevicediscovery.h"
+
+IntegrationPluginSma::IntegrationPluginSma()
+{
+
+}
+
+void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info)
+{
+ 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;
+ }
+
+ 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;
+
+ QString title = networkDeviceInfo.hostName() + " (" + networkDeviceInfo.address().toString() + ")";
+ QString description;
+ if (networkDeviceInfo.macAddressManufacturer().isEmpty()) {
+ description = networkDeviceInfo.macAddress();
+ } else {
+ description = networkDeviceInfo.macAddress() + " (" + networkDeviceInfo.macAddressManufacturer() + ")";
+ }
+
+ ThingDescriptor descriptor(sunnyWebBoxThingClassId, title, description);
+
+ // Check for reconfiguration
+ foreach (Thing *existingThing, myThings()) {
+ if (existingThing->paramValue(sunnyWebBoxThingMacAddressParamTypeId).toString() == networkDeviceInfo.macAddress()) {
+ descriptor.setThingId(existingThing->id());
+ break;
+ }
+ }
+
+ ParamList params;
+ params << Param(sunnyWebBoxThingHostParamTypeId, networkDeviceInfo.address().toString());
+ params << Param(sunnyWebBoxThingMacAddressParamTypeId, networkDeviceInfo.macAddress());
+ descriptor.setParams(params);
+ descriptors.append(descriptor);
+ }
+ info->addThingDescriptors(descriptors);
+ info->finish(Thing::ThingErrorNoError);
+ });
+
+}
+
+void IntegrationPluginSma::setupThing(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+ qCDebug(dcSma()) << "Setup thing" << thing << thing->params();
+
+ if (thing->thingClassId() == sunnyWebBoxThingClassId) {
+ // 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";
+ 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);
+ }
+ });
+ } else {
+ Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
+ }
+}
+
+void IntegrationPluginSma::postSetupThing(Thing *thing)
+{
+ qCDebug(dcSma()) << "Post setup thing" << thing->name();
+ if (thing->thingClassId() == sunnyWebBoxThingClassId) {
+ SunnyWebBox *sunnyWebBox = m_sunnyWebBoxes.value(thing);
+ if (!sunnyWebBox)
+ return;
+ sunnyWebBox->getPlantOverview();
+ thing->setStateValue(sunnyWebBoxConnectedStateTypeId, true);
+ }
+}
+
+void IntegrationPluginSma::thingRemoved(Thing *thing)
+{
+ if (thing->thingClassId() == sunnyWebBoxThingClassId) {
+ m_sunnyWebBoxes.take(thing)->deleteLater();
+ }
+
+ if (myThings().isEmpty()) {
+ qCDebug(dcSma()) << "Stopping timer";
+ hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer);
+ m_refreshTimer = nullptr;
+ }
+}
+
+void IntegrationPluginSma::onRefreshTimer()
+{
+ Q_FOREACH(Thing *thing, myThings().filterByThingClassId(sunnyWebBoxThingClassId)) {
+ SunnyWebBox *sunnyWebBox = m_sunnyWebBoxes.value(thing);
+ sunnyWebBox->getPlantOverview();
+ }
+}
+
+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)
+{
+ Q_UNUSED(messageId)
+
+ qCDebug(dcSma()) << "Plant overview received" << overview.status;
+ Thing *thing = m_sunnyWebBoxes.key(static_cast(sender()));
+ if (!thing)
+ return;
+
+ thing->setStateValue(sunnyWebBoxCurrentPowerStateTypeId, overview.power);
+ 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;
+ thing->setStateValue(sunnyWebBoxErrorStateTypeId, overview.error);
+ }
+}
diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h
new file mode 100644
index 00000000..3c20919f
--- /dev/null
+++ b/sma/integrationpluginsma.h
@@ -0,0 +1,64 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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
+
+class IntegrationPluginSma: public IntegrationPlugin {
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginsma.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ explicit IntegrationPluginSma();
+
+ void discoverThings(ThingDiscoveryInfo *info) override;
+ void setupThing(ThingSetupInfo *info) override;
+ void postSetupThing(Thing *thing) override;
+ void thingRemoved(Thing *thing) override;
+
+private slots:
+ void onRefreshTimer();
+
+ void onConnectedChanged(bool connected);
+ void onPlantOverviewReceived(const QString &messageId, SunnyWebBox::Overview overview);
+
+private:
+ PluginTimer *m_refreshTimer = nullptr;
+ QHash m_sunnyWebBoxes;
+};
+
+#endif // INTEGRATIONPLUGINSMA_H
diff --git a/sma/integrationpluginsma.json b/sma/integrationpluginsma.json
new file mode 100644
index 00000000..0a542fe4
--- /dev/null
+++ b/sma/integrationpluginsma.json
@@ -0,0 +1,93 @@
+{
+ "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": ["smartmeterproducer"],
+ "paramTypes": [
+ {
+ "id": "864d4162-e3ce-48b8-b8ac-c1b971b52d42",
+ "name": "host",
+ "displayName": "Host address",
+ "type": "QString",
+ "inputType": "IPv4Address",
+ "defaultValue": "192.168.0.168"
+ },
+ {
+ "id": "03f32361-4e13-4597-a346-af8d16a986b3",
+ "name": "macAddress",
+ "displayName": "hardware address",
+ "type": "QString",
+ "inputType": "TextLine",
+ "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": "dayEnergyProduced",
+ "displayName": "Day energy produced",
+ "displayNameEvent": "Day energy produced changed",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0
+ },
+ {
+ "id": "0bb4e227-7e38-49ca-9b32-ce4621c9305b",
+ "name": "totalEnergyProduced",
+ "displayName": "Total energy produced",
+ "displayNameEvent": "Total energy produced 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"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
+
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 00000000..3273ac85
Binary files /dev/null and b/sma/sma.png differ
diff --git a/sma/sma.pro b/sma/sma.pro
new file mode 100644
index 00000000..5e124771
--- /dev/null
+++ b/sma/sma.pro
@@ -0,0 +1,11 @@
+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..f2b2c56a
--- /dev/null
+++ b/sma/sunnywebbox.cpp
@@ -0,0 +1,332 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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"
+#include "QJsonArray"
+
+SunnyWebBox::SunnyWebBox(NetworkAccessManager *networkAccessManager, const QHostAddress &hostAddress, QObject *parrent) :
+ QObject(parrent),
+ m_hostAddresss(hostAddress),
+ m_networkManager(networkAccessManager)
+{
+ qCDebug(dcSma()) << "SunnyWebBox: Creating Sunny Web Box connection";
+}
+
+SunnyWebBox::~SunnyWebBox()
+{
+ qCDebug(dcSma()) << "SunnyWebBox: Deleting Sunny Web Box connection";
+}
+
+QString SunnyWebBox::getPlantOverview()
+{
+ return sendMessage(m_hostAddresss, "GetPlantOverview");
+}
+
+QString SunnyWebBox::getDevices()
+{
+ return sendMessage(m_hostAddresss, "GetDevices");
+}
+
+QString SunnyWebBox::getProcessDataChannels(const QString &deviceId)
+{
+ QJsonObject params;
+ params["device"] = deviceId;
+ return sendMessage(m_hostAddresss, "GetProcessDataChannels", params);
+}
+
+QString SunnyWebBox::getProcessData(const QStringList &deviceKeys)
+{
+ 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)
+{
+ QJsonObject paramsObj;
+ QJsonArray devicesArray;
+ QJsonObject deviceObj;
+ deviceObj["key"] = deviceKey;
+ devicesArray.append(deviceObj);
+ paramsObj["devices"] = devicesArray;
+ return sendMessage(m_hostAddresss, "GetParameterChannels", paramsObj);
+}
+
+QString SunnyWebBox::getParameters(const QStringList &deviceKeys)
+{
+ QJsonObject paramsObj;
+ QJsonArray devicesArray;
+ Q_FOREACH(QString key, deviceKeys) {
+ QJsonObject deviceObj;
+ deviceObj["key"] = key;
+ devicesArray.append(deviceObj);
+ }
+ paramsObj["devices"] = devicesArray;
+ return sendMessage(m_hostAddresss, "GetParameter", paramsObj);
+}
+
+QString 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 sendMessage(m_hostAddresss, "SetParameter", paramsObj);
+}
+
+QHostAddress SunnyWebBox::hostAddress() const
+{
+ return m_hostAddresss;
+}
+
+void SunnyWebBox::setHostAddress(const QHostAddress &address)
+{
+ qCDebug(dcSma()) << "SunnyWebBox: Setting host address to" << address.toString();
+ m_hostAddresss = address;
+}
+
+QString SunnyWebBox::macAddress() const
+{
+ return m_macAddress;
+}
+
+void SunnyWebBox::setMacAddress(const QString &macAddress)
+{
+ m_macAddress = macAddress;
+}
+
+void SunnyWebBox::parseMessage(const QString &messageId, const QString &messageType, const QVariantMap &result)
+{
+ 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"].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().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().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;
+ } else if (map["meta"].toString() == "Msg") {
+ overview.error = map["value"].toString();
+ qCDebug(dcSma()) << "SunnyWebBox: - Error" << overview.error;
+ }
+ }
+ emit plantOverviewReceived(messageId, overview);
+
+ } else if (messageType == "GetDevices") {
+ QList devices;
+ QVariantList deviceList = result.value("devices").toList();
+ qCDebug(dcSma()) << "SunnyWebBox: GetDevices" << result.value("totalDevicesReturned").toInt();
+ 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;
+ 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" ||
+ 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();
+ qCDebug(dcSma()) << "SunnyWebBox: GetProcessData response received";
+ 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"|| 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()) << "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);
+ }
+}
+
+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)
+{
+ qCDebug(dcSma()) << "SunnyWebBox: Send message to" << address.toString() << "Procedure:" << procedure << "Params:" << params;
+ 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]{
+
+ if (reply->error() != QNetworkReply::NoError) {
+ setConnectionStatus(false);
+ return;
+ }
+ setConnectionStatus(true);
+
+ QByteArray data = reply->readAll();
+ qCDebug(dcSma()) << "SunnyWebBox: Received reply" << data;
+
+ QJsonParseError error;
+ QJsonDocument doc = QJsonDocument::fromJson(data, &error);
+ if (error.error != QJsonParseError::NoError) {
+ qCWarning(dcSma()) << "SunnyWebBox: Could not parse JSON" << error.errorString();
+ return;
+ }
+ if (!doc.isObject()) {
+ qCWarning(dcSma()) << "SunnyWebBox: JSON is not an Object";
+ return;
+ }
+ QVariantMap map = doc.toVariant().toMap();
+ if (map["version"] != "1.0") {
+ qCWarning(dcSma()) << "SunnyWebBox: 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 if (map.contains("proc") && map.contains("error")) {
+ } else {
+ qCWarning(dcSma()) << "SunnyWebBox: Missing proc or result value";
+ }
+ });
+ return requestId;
+}
+
diff --git a/sma/sunnywebbox.h b/sma/sunnywebbox.h
new file mode 100644
index 00000000..ba1d669a
--- /dev/null
+++ b/sma/sunnywebbox.h
@@ -0,0 +1,115 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "network/networkaccessmanager.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;
+ };
+
+ struct Channel {
+ QString meta;
+ QString name;
+ QVariant value;
+ QString unit;
+ };
+
+ struct Parameter {
+ QString meta;
+ QString name;
+ QString unit;
+ double min;
+ double max;
+ double value;
+ };
+
+ 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.
+ 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
+
+ QHostAddress hostAddress() const;
+ void setHostAddress(const QHostAddress &address);
+
+ 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);
+ 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);
+
+ 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/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..6d411ddd
--- /dev/null
+++ b/sma/translations/b8442bbf-9d3f-4aa2-9443-b3a31ae09bac-en_US.ts
@@ -0,0 +1,132 @@
+
+
+
+
+ IntegrationPluginSma
+
+
+ Unable to discover devices in your network.
+
+
+
+
+ sma
+
+
+
+ Connected
+ 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 ({c05e6a1a-252c-4f2b-8b31-09cf113d01c1}) of ThingClass sunnyWebBox
+
+
+
+
+
+ Current power
+ 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 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 produced changed
+ The name of the EventType ({16f34c5c-8dbb-4dcc-9faa-4b782d57226c}) of ThingClass sunnyWebBox
+
+
+
+
+
+ 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})
+
+
+
+
+
+ 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 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})
+
+
+
+