Merge PR #301: New PlugIn: SMA

This commit is contained in:
Jenkins nymea 2021-09-07 17:35:45 +02:00
commit e3cfd9c539
13 changed files with 987 additions and 0 deletions

17
debian/control vendored
View File

@ -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,

1
debian/nymea-plugin-sma.install.in vendored Normal file
View File

@ -0,0 +1 @@
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginsma.so

View File

@ -53,6 +53,7 @@ PLUGIN_DIRS = \
senic \
serialportcommander \
simulation \
sma \
somfytahoma \
sonos \
sunposition \

14
sma/README.md Normal file
View File

@ -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/

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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<SunnyWebBox *>(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<SunnyWebBox *>(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);
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <QDebug>
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<Thing *, SunnyWebBox *> m_sunnyWebBoxes;
};
#endif // INTEGRATIONPLUGINSMA_H

View File

@ -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"
}
]
}
]
}
]
}

13
sma/meta.json Normal file
View File

@ -0,0 +1,13 @@
{
"title": "SMA",
"tagline": "Connect to SMA solar equipment.",
"icon": "sma.png",
"stability": "consumer",
"offline": true,
"technologies": [
"network"
],
"categories": [
]
}

BIN
sma/sma.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

11
sma/sma.pro Normal file
View File

@ -0,0 +1,11 @@
include(../plugins.pri)
QT += network
SOURCES += \
integrationpluginsma.cpp \
sunnywebbox.cpp
HEADERS += \
integrationpluginsma.h \
sunnywebbox.h

332
sma/sunnywebbox.cpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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<QString, QVariant> &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<Device> 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<Device> 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<QString, QVariant> 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<Device> 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<Parameter> 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 &params)
{
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;
}

115
sma/sunnywebbox.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <QObject>
#include <QHostAddress>
#include <QUdpSocket>
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<Device> 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<QString, QVariant> &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 &params);
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<Device> devices);
void processDataChannelsReceived(const QString &messageId, const QString &deviceKey, QStringList processDataChanels);
void processDataReceived(const QString &messageId, const QString &deviceKey, const QHash<QString, QVariant> &channels);
void parameterChannelsReceived(const QString &messageId, const QString &deviceKey, QStringList parameterChannels);
void parametersReceived(const QString &messageId, const QString &deviceKey, const QList<Parameter> &parameters);
};
#endif // SUNNYWEBBOX_H

View File

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>IntegrationPluginSma</name>
<message>
<location filename="../integrationpluginsma.cpp" line="45"/>
<source>Unable to discover devices in your network.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>sma</name>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="48"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="51"/>
<source>Connected</source>
<extracomment>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</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="54"/>
<source>Connected changed</source>
<extracomment>The name of the EventType ({c05e6a1a-252c-4f2b-8b31-09cf113d01c1}) of ThingClass sunnyWebBox</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="57"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="60"/>
<source>Current power</source>
<extracomment>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</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="63"/>
<source>Current power changed</source>
<extracomment>The name of the EventType ({ff4ff872-2f0f-4ca4-9fe2-220eeaf16cc2}) of ThingClass sunnyWebBox</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="66"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="69"/>
<source>Day energy produced</source>
<extracomment>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</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="72"/>
<source>Day energy produced changed</source>
<extracomment>The name of the EventType ({16f34c5c-8dbb-4dcc-9faa-4b782d57226c}) of ThingClass sunnyWebBox</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="75"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="78"/>
<source>Error</source>
<extracomment>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</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="81"/>
<source>Error changed</source>
<extracomment>The name of the EventType ({4e64f9ca-7e5a-4897-8035-6f2ae88fde89}) of ThingClass sunnyWebBox</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="84"/>
<source>Host address</source>
<extracomment>The name of the ParamType (ThingClass: sunnyWebBox, Type: thing, ID: {864d4162-e3ce-48b8-b8ac-c1b971b52d42})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="87"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="90"/>
<source>Mode</source>
<extracomment>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</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="93"/>
<source>Mode changed</source>
<extracomment>The name of the EventType ({1974550b-6059-4b0e-83f4-70177e20dac3}) of ThingClass sunnyWebBox</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="96"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="99"/>
<source>SMA</source>
<extracomment>The name of the vendor ({16d5a4a3-36d5-46c0-b7dd-df166ddf5981})
----------
The name of the plugin sma ({b8442bbf-9d3f-4aa2-9443-b3a31ae09bac})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="105"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="108"/>
<source>Total energy produced</source>
<extracomment>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</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="111"/>
<source>Total energy produced changed</source>
<extracomment>The name of the EventType ({0bb4e227-7e38-49ca-9b32-ce4621c9305b}) of ThingClass sunnyWebBox</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="102"/>
<source>Sunny WebBox</source>
<extracomment>The name of the ThingClass ({49304127-ce9b-45dd-8511-05030a4ac003})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="114"/>
<source>hardware address</source>
<extracomment>The name of the ParamType (ThingClass: sunnyWebBox, Type: thing, ID: {03f32361-4e13-4597-a346-af8d16a986b3})</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>