From b1f5c89944fd8d966e459770786e6060ac22f3bc Mon Sep 17 00:00:00 2001 From: Boernsman Date: Tue, 16 Jun 2020 12:07:24 +0200 Subject: [PATCH] added fronius --- debian/nymea-plugin-fronius.install.in | 1 + fronius/fronius.pro | 23 + fronius/froniusinverter.cpp | 135 ++++++ fronius/froniusinverter.h | 56 +++ fronius/froniuslogger.cpp | 157 +++++++ fronius/froniuslogger.h | 54 +++ fronius/froniusmeter.cpp | 130 +++++ fronius/froniusmeter.h | 55 +++ fronius/froniusstorage.cpp | 138 ++++++ fronius/froniusstorage.h | 60 +++ fronius/froniusthing.cpp | 92 ++++ fronius/froniusthing.h | 81 ++++ fronius/integrationpluginfronius.cpp | 628 +++++++++++++++++++++++++ fronius/integrationpluginfronius.h | 78 +++ fronius/integrationpluginfronius.json | 566 ++++++++++++++++++++++ fronius/sunspecthing.cpp | 461 ++++++++++++++++++ fronius/sunspecthing.h | 91 ++++ 17 files changed, 2806 insertions(+) create mode 100644 debian/nymea-plugin-fronius.install.in create mode 100644 fronius/fronius.pro create mode 100644 fronius/froniusinverter.cpp create mode 100644 fronius/froniusinverter.h create mode 100644 fronius/froniuslogger.cpp create mode 100644 fronius/froniuslogger.h create mode 100644 fronius/froniusmeter.cpp create mode 100644 fronius/froniusmeter.h create mode 100644 fronius/froniusstorage.cpp create mode 100644 fronius/froniusstorage.h create mode 100644 fronius/froniusthing.cpp create mode 100644 fronius/froniusthing.h create mode 100644 fronius/integrationpluginfronius.cpp create mode 100644 fronius/integrationpluginfronius.h create mode 100644 fronius/integrationpluginfronius.json create mode 100644 fronius/sunspecthing.cpp create mode 100644 fronius/sunspecthing.h diff --git a/debian/nymea-plugin-fronius.install.in b/debian/nymea-plugin-fronius.install.in new file mode 100644 index 0000000..82fff67 --- /dev/null +++ b/debian/nymea-plugin-fronius.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_devicepluginfronius.so diff --git a/fronius/fronius.pro b/fronius/fronius.pro new file mode 100644 index 0000000..7ee3bf0 --- /dev/null +++ b/fronius/fronius.pro @@ -0,0 +1,23 @@ +include(../plugins.pri) + +QT += \ + serialbus \ + network \ + +SOURCES += \ + integrationpluginfronius.cpp \ + froniusthing.cpp \ + froniuslogger.cpp \ + froniusinverter.cpp \ + froniusstorage.cpp \ + froniusmeter.cpp \ + sunspecthing.cpp + +HEADERS += \ + integrationpluginfronius.h \ + froniusthing.h \ + froniuslogger.h \ + froniusinverter.h \ + froniusstorage.h \ + froniusmeter.h \ + sunspecthing.h diff --git a/fronius/froniusinverter.cpp b/fronius/froniusinverter.cpp new file mode 100644 index 0000000..0f8f7ba --- /dev/null +++ b/fronius/froniusinverter.cpp @@ -0,0 +1,135 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "froniusinverter.h" +#include +#include "extern-plugininfo.h" +#include + +FroniusInverter::FroniusInverter(Thing *thing, QObject *parent) : FroniusThing(thing, parent) +{ + +} + +QString FroniusInverter::activity() const +{ + return m_activity; +} + +void FroniusInverter::setActivity(const QString &activity) +{ + m_activity = activity; +} + +QUrl FroniusInverter::updateUrl() +{ + QUrl requestUrl; + requestUrl.setScheme("http"); + QUrlQuery query; + requestUrl.setHost(hostAddress()); + requestUrl.setPath(baseUrl() + "GetInverterRealtimeData.cgi"); + query.addQueryItem("Scope", "Device"); + query.addQueryItem("DeviceId", thingId().toString()); + query.addQueryItem("DataCollection", "CommonInverterData"); + requestUrl.setQuery(query); + + return requestUrl; +} + +void FroniusInverter::updateThingInfo(const QByteArray &data) +{ + // Convert the rawdata to a JSON document + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcFronius()) << "FroniusInverter: Failed to parse JSON data" << data << ":" << error.errorString(); + pluginThing()->setStateValue(inverterConnectedStateTypeId,false); + return; + } + + // Parse the data and update the states of our device + QVariantMap dataMap = jsonDoc.toVariant().toMap().value("Body").toMap().value("Data").toMap(); + QVariantMap headMap = jsonDoc.toVariant().toMap().value("Head").toMap(); + + // Set the inverter device state + if (dataMap.contains("PAC")) { + if(dataMap.value("PAC").toMap().values().at(0) == "W") + pluginThing()->setStateValue(inverterCurrentPowerStateTypeId, dataMap.value("PAC").toMap().values().at(1).toInt()); + } + + if (dataMap.contains("DAY_ENERGY")) { + if (dataMap.value("DAY_ENERGY").toMap().values().at(0) == "Wh") + pluginThing()->setStateValue(inverterEdayStateTypeId, dataMap.value("DAY_ENERGY").toMap().values().at(1).toDouble()/1000); + } + + if (dataMap.contains("YEAR_ENERGY")) { + if(dataMap.value("YEAR_ENERGY").toMap().values().at(0) == "Wh") + pluginThing()->setStateValue(inverterEyearStateTypeId, dataMap.value("YEAR_ENERGY").toMap().values().at(1).toInt()/1000); + } + + if (dataMap.contains("TOTAL_ENERGY")) { + if(dataMap.value("TOTAL_ENERGY").toMap().values().at(0) == "Wh") + pluginThing()->setStateValue(inverterTotalEnergyProducedStateTypeId, dataMap.value("TOTAL_ENERGY").toMap().values().at(1).toInt()/1000); + } + + //update successful + pluginThing()->setStateValue(inverterConnectedStateTypeId,true); + +} + +QUrl FroniusInverter::activityUrl() +{ + QUrl requestUrl; + requestUrl.setScheme("http"); + requestUrl.setHost(hostAddress()); + requestUrl.setPath(baseUrl()+"GetPowerFlowRealtimeData.fcgi"); + + return requestUrl; +} + +void FroniusInverter::updateActivityInfo(const QByteArray &data) +{ + // Convert the rawdata to a json document + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if(error.error != QJsonParseError::NoError) { + qCWarning(dcFronius()) << "FroniusInverter: Failed to parse JSON data" << data << ":" << error.errorString(); + return; + } + + // create StorageInfo list map + QVariantMap dataMap = jsonDoc.toVariant().toMap().value("Body").toMap().value("Data").toMap(); + + if (dataMap.value("Site").toMap().value("P_PV").toFloat() > 0) { + pluginThing()->setStateValue(inverterActiveStateTypeId, "production"); + } else { + pluginThing()->setStateValue(inverterActiveStateTypeId, "inactive"); + } +} diff --git a/fronius/froniusinverter.h b/fronius/froniusinverter.h new file mode 100644 index 0000000..0b67e59 --- /dev/null +++ b/fronius/froniusinverter.h @@ -0,0 +1,56 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 FRONIUSINVERTER_H +#define FRONIUSINVERTER_H + +#include +#include "froniusthing.h" + +class FroniusInverter : public FroniusThing +{ + Q_OBJECT + +public: + explicit FroniusInverter(Thing *thing, QObject *parent = 0); + + QString activity() const; + void setActivity(const QString &activity); + Thing* inverterThing() const; + QUrl updateUrl(); + void updateThingInfo(const QByteArray &data); + QUrl activityUrl(); + void updateActivityInfo(const QByteArray &data); + +private: + QString m_activity; +}; + +#endif // FRONIUSINVERTER_H diff --git a/fronius/froniuslogger.cpp b/fronius/froniuslogger.cpp new file mode 100644 index 0000000..b05f730 --- /dev/null +++ b/fronius/froniuslogger.cpp @@ -0,0 +1,157 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "froniuslogger.h" +#include "extern-plugininfo.h" + +#include +#include + +FroniusLogger::FroniusLogger(Thing *thing, QObject *parent) : FroniusThing(thing, parent) +{ + +} + +QUrl FroniusLogger::updateUrl() +{ + QUrl requestUrl; + requestUrl.setScheme("http"); + requestUrl.setHost(hostAddress()); + requestUrl.setPath(baseUrl() + "GetLoggerInfo.cgi"); + + return requestUrl; +} + + +QUrl FroniusLogger::updateRelayStateUrl() +{ + QUrl requestUrl; + requestUrl.setScheme("http"); + requestUrl.setHost(hostAddress()); + requestUrl.setPath("/status/emrs/"); + + return requestUrl; +} + + +void FroniusLogger::updateThingInfo(const QByteArray &data) +{ + // Convert the rawdata to a json document + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + // qCWarning(dcFroniusSolar()) << "Failed to parse JSON data" << data << ":" << error.errorString(); + pluginThing()->setStateValue(dataloggerConnectedStateTypeId,false); + return; + } + + // Parse the data and update the states of our device + QVariantMap dataMap = jsonDoc.toVariant().toMap().value("Body").toMap().value("Data").toMap(); + QVariantMap headMap = jsonDoc.toVariant().toMap().value("Head").toMap(); + QVariantMap bodyMap = jsonDoc.toVariant().toMap().value("Body").toMap(); + + // print the fetched data in dataMap format to stdout + //qCDebug(dcFroniusSolar()) << dataMap; + + // create LoggerInfo list Map + QVariantMap LoggerInfoMap = bodyMap.value("LoggerInfo").toMap(); + + // copy retrieved information to device states + if (LoggerInfoMap.contains("ProductID")) + pluginThing()->setStateValue(dataloggerProductidStateTypeId, LoggerInfoMap.value("ProductID").toString()); + + if (LoggerInfoMap.contains("PlatformID")) + pluginThing()->setStateValue(dataloggerPlatformidStateTypeId, LoggerInfoMap.value("PlatformID").toString()); + + if (LoggerInfoMap.contains("HWVersion")) + pluginThing()->setStateValue(dataloggerHwversionStateTypeId, LoggerInfoMap.value("HWVersion").toString()); + + if (LoggerInfoMap.contains("SWVersion")) + pluginThing()->setStateValue(dataloggerSwversionStateTypeId, LoggerInfoMap.value("SWVersion").toString()); + + if (LoggerInfoMap.contains("TimezoneLocation")) + pluginThing()->setStateValue(dataloggerTzonelocStateTypeId, LoggerInfoMap.value("TimezoneLocation").toString()); + + if (LoggerInfoMap.contains("TimezoneName")) + pluginThing()->setStateValue(dataloggerTzoneStateTypeId, LoggerInfoMap.value("TimezoneName").toString()); + + if (LoggerInfoMap.contains("DefaultLanguage")) + pluginThing()->setStateValue(dataloggerDefaultlangStateTypeId, LoggerInfoMap.value("DefaultLanguage").toString()); + + if (LoggerInfoMap.contains("CashFactor")) + pluginThing()->setStateValue(dataloggerCashfactorStateTypeId, LoggerInfoMap.value("CashFactor").toDouble()); + + if (LoggerInfoMap.contains("CashCurrency")) + pluginThing()->setStateValue(dataloggerCashcurrencyStateTypeId, LoggerInfoMap.value("CashCurrency").toString()); + + if (LoggerInfoMap.contains("CO2Factor")) + pluginThing()->setStateValue(dataloggerCo2factorStateTypeId, LoggerInfoMap.value("CO2Factor").toDouble()); + + if (LoggerInfoMap.contains("CO2Unit")) + pluginThing()->setStateValue(dataloggerCo2unitStateTypeId, LoggerInfoMap.value("CO2Unit").toString()); + + //update successful + pluginThing()->setStateValue(dataloggerConnectedStateTypeId,true); +} + +void FroniusLogger::updatePowerRelayState(const QByteArray &data) +{ + // Convert the rawdata to a json document + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + // qCWarning(dcFroniusSolar()) << "Failed to parse JSON data" << data << ":" << error.errorString(); + pluginThing()->setStateValue(dataloggerConnectedStateTypeId,false); + return; + } + + // Parse the data and update the states of our device + QVariantMap bodyMap = jsonDoc.toVariant().toMap().value("Body").toMap(); + QVariantMap dataMap = bodyMap.value("Data").toMap(); + QVariantMap emrsMap = dataMap.value("emrs").toMap(); + + // create LoggerInfo list Map + QVariantMap GpiosMap = emrsMap.value("gpios").toMap(); + //qCDebug(dcFroniusSolar()) << "Body: " << GpiosMap; + + // copy retrieved information to device states + if (GpiosMap.contains("Reason")) { + qCDebug(dcFronius()) << "Power Relay State Reason: " << GpiosMap.value("Reason").toString(); + pluginThing()->setStateValue(dataloggerPowerManagmentRelayReasonStateTypeId, GpiosMap.value("Reason").toString()); + } + + if (GpiosMap.contains("State")) { + qCDebug(dcFronius()) << "Power Relay State: " << GpiosMap.value("State").toString(); + pluginThing()->setStateValue(dataloggerPowerManagmentRelayStateTypeId, GpiosMap.value("State").toBool()); + } + + //update successful + pluginThing()->setStateValue(dataloggerConnectedStateTypeId,true); +} diff --git a/fronius/froniuslogger.h b/fronius/froniuslogger.h new file mode 100644 index 0000000..bfdf6fe --- /dev/null +++ b/fronius/froniuslogger.h @@ -0,0 +1,54 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 FRONIUSLOGGER_H +#define FRONIUSLOGGER_H + +#include +#include +#include "froniusinverter.h" +#include "froniusmeter.h" +#include "froniusstorage.h" + +class FroniusLogger : public FroniusThing +{ + Q_OBJECT + +public: + explicit FroniusLogger(Thing *thing, QObject *parent = 0); + + QUrl updateUrl(); + QUrl updateRelayStateUrl(); + + void updateThingInfo(const QByteArray &data); + void updatePowerRelayState(const QByteArray &data); +}; + +#endif // FRONIUSLOGGER_H diff --git a/fronius/froniusmeter.cpp b/fronius/froniusmeter.cpp new file mode 100644 index 0000000..46d9be8 --- /dev/null +++ b/fronius/froniusmeter.cpp @@ -0,0 +1,130 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "froniusmeter.h" +#include "extern-plugininfo.h" + +#include +#include + +FroniusMeter::FroniusMeter(Thing* thing, QObject *parent) : FroniusThing(thing, parent) +{ + +} + +QString FroniusMeter::activity() const +{ + return m_activity; +} + +void FroniusMeter::setActivity(const QString &activity) +{ + m_activity = activity; +} + +QUrl FroniusMeter::updateUrl() +{ + QUrl requestUrl; + requestUrl.setScheme("http"); + QUrlQuery query; + requestUrl.setHost(hostAddress()); + requestUrl.setPath(baseUrl() + "GetMeterRealtimeData.cgi"); + query.addQueryItem("Scope", "Thing"); + query.addQueryItem("ThingId", thingId().toString()); + requestUrl.setQuery(query); + return requestUrl; +} + +void FroniusMeter::updateThingInfo(const QByteArray &data) +{ + // Convert the rawdata to a JSON document + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcFronius()) << "FroniusMeter: Failed to parse JSON data" << data << ":" << error.errorString(); + pluginThing()->setStateValue(inverterConnectedStateTypeId,false); + return; + } + + qCDebug(dcFronius()) << "FroniusMeter: ThingInfo received:" << qUtf8Printable(data); + // Parse the data and update the states of our thing + QVariantMap dataMap = jsonDoc.toVariant().toMap().value("Body").toMap().value("Data").toMap(); + //QVariantMap headMap = jsonDoc.toVariant().toMap().value("Head").toMap(); + + //Request from Gerald Aigner, add Smart meter with following states: „PowerReal_P_Sum“, „EnergyReal_WAC_Sum_Produced“, „EnergyReal_WAC_Sum_Consumed“ + + // Set the inverter thing state + if (dataMap.contains("PowerReal_P_Sum")) { + pluginThing()->setStateValue(meterCurrentPowerStateTypeId, dataMap.value("PowerReal_P_Sum").toInt()); + } + + if (dataMap.contains("EnergyReal_WAC_Sum_Produced")) { + pluginThing()->setStateValue(meterTotalEnergyProducedStateTypeId, dataMap.value("EnergyReal_WAC_Sum_Produced").toInt()/1000); + } + + if (dataMap.contains("EnergyReal_WAC_Sum_Consumed")) { + pluginThing()->setStateValue(meterTotalEnergyConsumedStateTypeId, dataMap.value("EnergyReal_WAC_Sum_Consumed").toInt()/1000); + } + + + //update successful + pluginThing()->setStateValue(meterConnectedStateTypeId,true); + +} + +QUrl FroniusMeter::activityUrl() +{ + QUrl requestUrl; + requestUrl.setScheme("http"); + requestUrl.setHost(hostAddress()); + requestUrl.setPath(baseUrl()+"GetPowerFlowRealtimeData.fcgi"); + + return requestUrl; +} + +void FroniusMeter::updateActivityInfo(const QByteArray &data) +{ + // Convert the rawdata to a json document + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if(error.error != QJsonParseError::NoError) { + qCWarning(dcFronius()) << "FroniusMeter: Failed to parse JSON data" << data << ":" << error.errorString(); + return; + } + + // create Meter Info list map + //QVariantMap dataMap = jsonDoc.toVariant().toMap().value("Body").toMap().value("Data").toMap(); + + //if (dataMap.value("Site").toMap().value("P_PV").toFloat() > 0) { + // pluginThing()->setStateValue(inverteractivStateTypeId, "production"); + //} else { + // pluginThing()->setStateValue(inverteractivStateTypeId, "inactive"); + //} +} diff --git a/fronius/froniusmeter.h b/fronius/froniusmeter.h new file mode 100644 index 0000000..a629d8a --- /dev/null +++ b/fronius/froniusmeter.h @@ -0,0 +1,55 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 FRONIUSMETER_H +#define FRONIUSMETER_H + +#include +#include "froniusthing.h" + +class FroniusMeter : public FroniusThing +{ + Q_OBJECT +public: + explicit FroniusMeter(Thing* thing, QObject *parent = 0); + + QString activity() const; + void setActivity(const QString &activity); + Thing* inverterThing() const; + QUrl updateUrl(); + void updateThingInfo(const QByteArray &data); + QUrl activityUrl(); + void updateActivityInfo(const QByteArray &data); + +private: + QString m_activity; +}; + +#endif // FRONIUSMETER_H diff --git a/fronius/froniusstorage.cpp b/fronius/froniusstorage.cpp new file mode 100644 index 0000000..4647326 --- /dev/null +++ b/fronius/froniusstorage.cpp @@ -0,0 +1,138 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "froniusstorage.h" +#include +#include "extern-plugininfo.h" +#include + +FroniusStorage::FroniusStorage(Thing* thing, QObject *parent) : FroniusThing(thing, parent) +{ + +} + +QString FroniusStorage::charging_state() const +{ + return m_charging_state; +} + +void FroniusStorage::setChargingState(const QString &charging_state) +{ + m_charging_state = charging_state; +} + +int FroniusStorage::charge() const +{ + return m_charge; +} + +void FroniusStorage::setCharge(const int &charge) +{ + m_charge = charge; +} + +QUrl FroniusStorage::updateUrl() +{ + QUrl requestUrl; + requestUrl.setScheme("http"); + QUrlQuery query; + requestUrl.setHost(hostAddress()); + requestUrl.setPath(baseUrl() + "GetStorageRealtimeData.cgi"); + query.addQueryItem("Scope", "Thing"); + query.addQueryItem("ThingId", thingId().toString()); + requestUrl.setQuery(query); + + return requestUrl; +} + +void FroniusStorage::updateThingInfo(const QByteArray &data) +{ + // Convert the rawdata to a JSON document + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + + if (error.error != QJsonParseError::NoError) { + qCWarning(dcFronius()) << "FroniusStorage: Failed to parse JSON data" << data << ":" << error.errorString(); + pluginThing()->setStateValue(storageConnectedStateTypeId,false); + return; + } + + // Parse the data and update the states of our thing + QVariantMap dataMap = jsonDoc.toVariant().toMap().value("Body").toMap().value("Data").toMap(); + // QVariantMap headMap = jsonDoc.toVariant().toMap().value("Head").toMap(); + + // create StorageInfo list map + QVariantMap storageInfoMap = dataMap.value("Controller").toMap(); + + // copy retrieved information to thing states + if (storageInfoMap.contains("StateOfCharge_Relative")) { + pluginThing()->setStateValue(storageBatteryLevelStateTypeId, storageInfoMap.value("StateOfCharge_Relative").toInt()); + pluginThing()->setStateValue(storageBatteryCriticalStateTypeId, storageInfoMap.value("StateOfCharge_Relative").toInt() < 5); + } + + if (storageInfoMap.contains("Temperature_Cell")) + pluginThing()->setStateValue(storageCellTemperatureStateTypeId, storageInfoMap.value("Temperature_Cell").toDouble()); + + //update successful + pluginThing()->setStateValue(storageConnectedStateTypeId,true); +} + +QUrl FroniusStorage::activityUrl() +{ + QUrl requestUrl; + requestUrl.setScheme("http"); + requestUrl.setHost(hostAddress()); + requestUrl.setPath(baseUrl()+"GetPowerFlowRealtimeData.fcgi"); + + return requestUrl; +} + +void FroniusStorage::updateActivityInfo(const QByteArray &data) +{ + // Convert the rawdata to a json document + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if(error.error != QJsonParseError::NoError) { + qCWarning(dcFronius()) << "FroniusStorage: Failed to parse JSON data" << data << ":" << error.errorString(); + return; + } + + // create StorageInfo list map + QVariantMap dataMap = jsonDoc.toVariant().toMap().value("Body").toMap().value("Data").toMap(); + + float charge_akku = dataMap.value("Site").toMap().value("P_Akku").toFloat(); + if (charge_akku == 0) { + pluginThing()->setStateValue(storageChargingStateTypeId, "inactive"); + } else if (charge_akku < 0) { + pluginThing()->setStateValue(storageChargingStateTypeId, "charging"); + } else { + pluginThing()->setStateValue(storageChargingStateTypeId, "discharging"); + } +} diff --git a/fronius/froniusstorage.h b/fronius/froniusstorage.h new file mode 100644 index 0000000..fd61dd4 --- /dev/null +++ b/fronius/froniusstorage.h @@ -0,0 +1,60 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 FRONIUSSTORAGE_H +#define FRONIUSSTORAGE_H + +#include +#include "froniusthing.h" + +class FroniusStorage : public FroniusThing +{ + Q_OBJECT + +public: + explicit FroniusStorage(Thing *thing, QObject *parent = 0); + + QString charging_state() const; + void setChargingState(const QString &charging_state); + + int charge() const; + void setCharge(const int &charge); + + QUrl updateUrl(); + void updateThingInfo(const QByteArray &data); + QUrl activityUrl(); + void updateActivityInfo(const QByteArray &data); + +private: + QString m_charging_state; + int m_charge; + +}; +#endif // FRONIUSSTORAGE_H diff --git a/fronius/froniusthing.cpp b/fronius/froniusthing.cpp new file mode 100644 index 0000000..5a6458b --- /dev/null +++ b/fronius/froniusthing.cpp @@ -0,0 +1,92 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2016 Christian Stachowitz * + * * + * This file is part of guh. * + * * + * Guh is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, version 2 of the License. * + * * + * Guh 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with guh. If not, see . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "froniusthing.h" + +FroniusThing::FroniusThing(Thing *thing, QObject *parent) : + QObject(parent) +{ + m_thing = thing; +} + +QString FroniusThing::name() const +{ + return m_name; +} + +void FroniusThing::setName(const QString &name) +{ + m_name = name; +} + +ThingId FroniusThing::hostId() const +{ + return m_hostId; +} + +void FroniusThing::setHostId(const ThingId &hostId) +{ + m_hostId = hostId; +} + +QString FroniusThing::hostAddress() const +{ + return m_hostAddress; +} + +void FroniusThing::setHostAddress(const QString &hostAddress) +{ + m_hostAddress = hostAddress; +} + +QString FroniusThing::baseUrl() const +{ + return m_baseUrl; +} + +void FroniusThing::setBaseUrl(const QString &baseUrl) +{ + m_baseUrl = baseUrl; +} + +QString FroniusThing::uniqueId() const +{ + return m_uniqueId; +} + +void FroniusThing::setUniqueId(const QString &uniqueId) +{ + m_uniqueId = uniqueId; +} + +ThingId FroniusThing::thingId() const +{ + return m_thingId; +} + +void FroniusThing::setThingId(const ThingId &thingId) +{ + m_thingId = thingId; +} + +Thing* FroniusThing::pluginThing() const +{ + return m_thing; +} diff --git a/fronius/froniusthing.h b/fronius/froniusthing.h new file mode 100644 index 0000000..af2bc05 --- /dev/null +++ b/fronius/froniusthing.h @@ -0,0 +1,81 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 FRONIUSTHING_H +#define FRONIUSTHING_H + +#include "extern-plugininfo.h" +#include "integrations/thing.h" + +#include +#include +#include + +class FroniusThing : public QObject +{ + Q_OBJECT + +public: + explicit FroniusThing(Thing *thing, QObject *parrent = 0); + + QString name() const; + void setName(const QString &name); + + ThingId hostId() const; + void setHostId(const ThingId &hostId); + + QString hostAddress() const; + void setHostAddress(const QString &hostAddress); + + QString baseUrl() const; + void setBaseUrl(const QString &baseUrl); + + QString uniqueId() const; + void setUniqueId(const QString &uniqueId); + + ThingId thingId() const; + void setThingId(const ThingId &thingId); + + Thing* pluginThing() const; + +private: + + Thing* m_thing; + + QString m_name; + ThingId m_hostId; + QString m_hostAddress; + QString m_apiVersion; + QString m_baseUrl; + QString m_uniqueId; + ThingId m_thingId; +}; + +#endif // FRONIUSTHING_H diff --git a/fronius/integrationpluginfronius.cpp b/fronius/integrationpluginfronius.cpp new file mode 100644 index 0000000..b2a47e7 --- /dev/null +++ b/fronius/integrationpluginfronius.cpp @@ -0,0 +1,628 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "integrationpluginfronius.h" +#include "plugintimer.h" +#include "network/networkaccessmanager.h" + +#include +#include +#include +#include +#include "plugininfo.h" +#include + +// Notes: Test IPs: 93.82.221.82 | 88.117.152.99 + +IntegrationPluginFronius::IntegrationPluginFronius(QObject *parent): IntegrationPlugin(parent){ + +} + +void IntegrationPluginFronius::setupThing(ThingSetupInfo *info) +{ + qCDebug(dcFronius()) << "Setting up a new thing:" << info->thing()->name(); + + Thing *thing = info->thing(); + + if (thing->thingClassId() == dataloggerThingClassId) { + //check if a data logger is already added with this IPv4Address + foreach(FroniusLogger *logger, m_froniusLoggers.keys()){ + if(logger->hostAddress() == thing->paramValue(dataloggerThingLoggerHostParamTypeId).toString()){ + //this logger at this IPv4 address is already added + qCWarning(dcFronius()) << "thing at " << thing->paramValue(dataloggerThingLoggerHostParamTypeId).toString() << " already added!"; + info->finish(Thing::ThingErrorThingInUse); + return; + } + } + + // Perform a HTTP request on the given IPv4Address to find things + QUrl url(QString("http://%1/solar_api/GetAPIVersion.cgi").arg(thing->paramValue(dataloggerThingLoggerHostParamTypeId).toString())); + qCDebug(dcFronius()) << "Search at address" << url.toString(); + + QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url)); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, [this, info, thing, reply]() { + QByteArray data = reply->readAll(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcFronius()) << "Fronius: Network request error:" << reply->error() << reply->errorString(); + info->finish(Thing::ThingErrorHardwareNotAvailable, tr("Device not reachable")); + return; + } + + // Convert the rawdata to a JSON document + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcFronius()) << "Fronius: Failed to parse JSON data" << data << ":" << error.errorString() << data; + info->finish(Thing::ThingErrorHardwareFailure, tr("Please try again")); + return; + } + + FroniusLogger *newLogger = new FroniusLogger(thing, this); + newLogger->setBaseUrl(jsonDoc.toVariant().toMap().value("BaseURL").toString()); + newLogger->setHostAddress(thing->paramValue(dataloggerThingLoggerHostParamTypeId).toString()); + m_froniusLoggers.insert(newLogger, thing); + + info->finish(Thing::ThingErrorNoError); + // FIX ME: remove after testing + //searchNewThings(m_froniusLoggers.key(thing)); + //updateThingStates(thing); + }); + //Async Setup + } else if (thing->thingClassId() == inverterThingClassId) { + + FroniusInverter *newInverter = new FroniusInverter(thing,this); + newInverter->setThingId(thing->paramValue(inverterThingIdParamTypeId)); + newInverter->setName(thing->paramValue(inverterThingNameParamTypeId).toString()); + newInverter->setBaseUrl(thing->paramValue(inverterThingBaseParamTypeId).toString()); + newInverter->setHostId(thing->paramValue(inverterThingHostIdParamTypeId)); + newInverter->setHostAddress(thing->paramValue(inverterThingHostParamTypeId).toString()); + + m_froniusInverters.insert(newInverter,thing); + + thing->setParentId(newInverter->hostId()); + thing->setName(newInverter->name()); + + // get inverter unique ID + QUrl requestUrl; + requestUrl.setScheme("http"); + requestUrl.setHost(newInverter->hostAddress()); + requestUrl.setPath(newInverter->baseUrl() + "GetInverterInfo.cgi"); + + QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(requestUrl)); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, [this, info, newInverter, reply]() { + QByteArray data = reply->readAll(); + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcFronius()) << "Fronius: Network request error:" << reply->error() << reply->errorString(); + info->finish(Thing::ThingErrorHardwareNotAvailable, "Device not reachable"); + return; + } + + // Convert the rawdata to a json document + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcFronius()) << "Fronius: Failed to parse JSON data" << data << ":" << error.errorString(); + info->finish(Thing::ThingErrorHardwareNotAvailable, "Please try again"); + return; + } + + // Check reply information + QVariantMap dataMap = jsonDoc.toVariant().toMap().value("Body").toMap().value("Data").toMap(); + // check for thing id in reply + if (dataMap.contains(newInverter->thingId())) { + qCDebug(dcFronius()) << "Found Thing with unique:" << dataMap.value(newInverter->thingId()).toMap().value("UniqueID").toString(); + newInverter->setUniqueId(dataMap.value(newInverter->thingId()).toMap().value("UniqueID").toString()); + newInverter->pluginThing()->setParamValue(inverterThingUniqueIdParamTypeId,newInverter->uniqueId()); + qCDebug(dcFronius()) << "Stored unique ID:" << newInverter->uniqueId(); + } + info->finish(Thing::ThingErrorNoError); + }); + // Async Setup + } else if (thing->thingClassId() == storageThingClassId) { + + FroniusStorage *newStorage = new FroniusStorage(thing, this); + newStorage->setThingId(thing->paramValue(storageThingIdParamTypeId).toString()); + newStorage->setName(thing->paramValue(storageThingNameParamTypeId).toString()); + newStorage->setBaseUrl(thing->paramValue(storageThingBaseParamTypeId).toString()); + newStorage->setHostId(thing->paramValue(storageThingHostIdParamTypeId).toString()); + newStorage->setHostAddress(thing->paramValue(storageThingHostParamTypeId).toString()); + + m_froniusStorages.insert(newStorage,thing); + + thing->setParentId(newStorage->hostId()); + thing->setName(newStorage->name()); + + // Get storage manufacturer and maximum capacity + QUrlQuery query; + QUrl requestUrl; + requestUrl.setScheme("http"); + requestUrl.setHost(newStorage->hostAddress()); + requestUrl.setPath(newStorage->baseUrl() + "GetStorageRealtimeData.cgi"); + query.addQueryItem("Scope","Thing"); + query.addQueryItem("ThingId",newStorage->thingId()); + requestUrl.setQuery(query); + qCDebug(dcFronius()) << "Get Storage Data at address" << requestUrl.toString(); + QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(requestUrl)); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, [this, info, newStorage, reply]() { + QByteArray data = reply->readAll(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcFronius()) << "Fronius: Network request error:" << reply->error() << reply->errorString(); + info->finish(Thing::ThingErrorNoError); + return; + } + + // Convert the rawdata to a json document + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcFronius()) << "Fronius: Failed to parse JSON data" << data << ":" << error.errorString(); + info->finish(Thing::ThingErrorHardwareFailure, tr("Please try again")); + return; + } + + // Create StorageInfo list map + QVariantMap storageInfoMap = jsonDoc.toVariant().toMap().value("Body").toMap().value("Data").toMap().value("Controller").toMap(); + + newStorage->pluginThing()->setParamValue(storageThingManufacturerParamTypeId, storageInfoMap.value("Details").toMap().value("Manufacturer").toString()); + newStorage->pluginThing()->setParamValue(storageThingCapacityParamTypeId, storageInfoMap.value("Capacity_Maximum").toInt()); + info->finish(Thing::ThingErrorNoError); + }); + //Async Setup + } else if (thing->thingClassId() == meterThingClassId) { + + FroniusMeter *newMeter = new FroniusMeter(thing, this);; + newMeter->setThingId(thing->paramValue(meterThingIdParamTypeId).toString()); + newMeter->setName(thing->paramValue(meterThingNameParamTypeId).toString()); + newMeter->setBaseUrl(thing->paramValue(meterThingBaseParamTypeId).toString()); + newMeter->setHostId(thing->paramValue(meterThingHostIdParamTypeId).toString()); + newMeter->setHostAddress(thing->paramValue(meterThingHostParamTypeId).toString()); + + m_froniusMeters.insert(newMeter,thing); + + thing->setParentId(newMeter->hostId()); + thing->setName(newMeter->name()); + + info->finish(Thing::ThingErrorNoError); + //Async setup + } else if (thing->thingClassId() == sunspecStorageThingClassId) { + SunspecThing *sunspecThings = new SunspecThing(thing, this); + m_sunspecThings.insert(sunspecThings, thing); + info->finish(Thing::ThingErrorNoError); + } else { + Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); + } +} + +void IntegrationPluginFronius::init() +{ + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(30); + connect(m_pluginTimer, &PluginTimer::timeout, this, [this]() { + foreach (Thing *logger, m_froniusLoggers) + updateThingStates(logger); + + foreach (Thing *inverter, m_froniusInverters) + updateThingStates(inverter); + + foreach (Thing *meter, m_froniusMeters) + updateThingStates(meter); + + foreach (Thing *storage, m_froniusStorages) + updateThingStates(storage); + + foreach (SunspecThing *sunspecThing, m_sunspecThings.keys()) + sunspecThing->update(); + }); +} + +void IntegrationPluginFronius::postSetupThing(Thing *thing) +{ + qCDebug(dcFronius()) << "Post setup" << thing->name(); + + if (thing->thingClassId() == dataloggerThingClassId) { + searchNewThings(m_froniusLoggers.key(thing)); + updateThingStates(thing); + } else if (thing->thingClassId() == inverterThingClassId) { + updateThingStates(thing); + } else if (thing->thingClassId() == meterThingClassId) { + updateThingStates(thing); + } else if (thing->thingClassId() == storageThingClassId) { + updateThingStates(thing); + } else if (thing->thingClassId() == sunspecStorageThingClassId) { + SunspecThing *sunspecThing = m_sunspecThings.key(thing); + sunspecThing->update(); + } else { + Q_ASSERT_X(false, "postSetupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); + } +} + +void IntegrationPluginFronius::thingRemoved(Thing *thing) +{ + // Remove things + + // Data Logger + if (thing->thingClassId() == dataloggerThingClassId) { + FroniusLogger *logger = m_froniusLoggers.key(thing); + m_froniusLoggers.remove(logger); + logger->deleteLater(); + return; + } else if (thing->thingClassId() == inverterThingClassId) { + FroniusInverter *inverter = m_froniusInverters.key(thing); + m_froniusInverters.remove(inverter); + inverter->deleteLater(); + return; + } else if (thing->thingClassId() == meterThingClassId) { + FroniusMeter *meter = m_froniusMeters.key(thing); + m_froniusMeters.remove(meter); + meter->deleteLater(); + return; + } else if (thing->thingClassId() == storageThingClassId) { + FroniusStorage *storage = m_froniusStorages.key(thing); + m_froniusStorages.remove(storage); + storage->deleteLater(); + return; + } else if (thing->thingClassId() == sunspecStorageThingClassId) { + qCDebug(dcFronius()) << "Remove sunspec thing"; + SunspecThing *sunspecThing = m_sunspecThings.key(thing); + m_sunspecThings.remove(sunspecThing); + delete sunspecThing; + qCDebug(dcFronius()) << "Sunspec thing deleted"; + return; + } else { + Q_ASSERT_X(false, "thingRemoved", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); + } + + if (myThings().isEmpty()) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); + m_pluginTimer = nullptr; + } +} + + +void IntegrationPluginFronius::executeAction(ThingActionInfo *info) +{ + Action action = info->action(); + Thing *thing = info->thing(); + qCDebug(dcFronius()) << "Execute action" << thing->name() << action.actionTypeId().toString(); + + if (thing->thingClassId() == inverterThingClassId || + thing->thingClassId() == dataloggerThingClassId || + thing->thingClassId() == storageThingClassId) { + + if (action.actionTypeId() == dataloggerSearchThingsActionTypeId) { + searchNewThings(m_froniusLoggers.key(thing)); + info->finish(ThingManager::ThingErrorNoError); + } else { + Q_ASSERT_X(false, "executeAction", QString("Unhandled action: %1").arg(action.actionTypeId().toString()).toUtf8()); + } + } else if (thing->thingClassId() == sunspecStorageThingClassId) { + SunspecThing *sunspecThing = m_sunspecThings.key(thing); + if (!sunspecThing) { + qWarning(dcFronius()) << "Could not find sunspec instance for thing"; + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } + + if (action.actionTypeId() == sunspecStorageGridChargingActionTypeId) { + if (sunspecThing->setGridCharging(action.param(sunspecStorageGridChargingActionGridChargingParamTypeId).value().toBool())){ + info->finish(Thing::ThingErrorNoError); + } else { + info->finish(Thing::ThingErrorHardwareFailure); + } + } else if (action.actionTypeId() == sunspecStorageEnableChargingLimitActionTypeId) { + int value = (action.param(sunspecStorageEnableChargingLimitActionEnableChargingLimitParamTypeId).value().toBool() << 1) | thing->stateValue(sunspecStorageEnableDischargingLimitStateTypeId).toBool(); + if (sunspecThing->setStorageControlMode(value)) { + info->finish(Thing::ThingErrorNoError); + } else { + info->finish(Thing::ThingErrorHardwareFailure); + } + } else if (action.actionTypeId() == sunspecStorageChargingRateActionTypeId) { + if (sunspecThing->setChargingRate(action.param(sunspecStorageChargingRateActionChargingRateParamTypeId).value().toInt())) { + info->finish(Thing::ThingErrorNoError); + } else { + info->finish(Thing::ThingErrorHardwareFailure); + } + } else if (action.actionTypeId() == sunspecStorageEnableDischargingLimitActionTypeId) { + int value = (action.param(sunspecStorageEnableDischargingLimitActionEnableDischargingLimitParamTypeId).value().toBool() << 1) | thing->stateValue(sunspecStorageEnableChargingLimitStateTypeId).toBool(); + if (sunspecThing->setStorageControlMode(value)) { + info->finish(Thing::ThingErrorNoError); + } else { + info->finish(Thing::ThingErrorHardwareFailure); + } + } else if (action.actionTypeId() == sunspecStorageDischargingRateActionTypeId) { + if (sunspecThing->setDischargingRate(action.param(sunspecStorageDischargingRateActionDischargingRateParamTypeId).value().toInt())) { + info->finish(Thing::ThingErrorNoError); + } else { + info->finish(Thing::ThingErrorHardwareFailure); + } + } else { + Q_ASSERT_X(false, "executeAction", QString("Unhandled action: %1").arg(action.actionTypeId().toString()).toUtf8()); + } + } else { + Q_ASSERT_X(false, "executeAction", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); + } +} + +void IntegrationPluginFronius::updateThingStates(Thing *thing) +{ + qCDebug(dcFronius()) << "Update thing values for" << thing->name(); + + if(thing->thingClassId() == inverterThingClassId) { + QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(m_froniusInverters.key(thing)->updateUrl())); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, [this, thing, reply]() { + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcFronius()) << "Network request error:" << reply->error() << reply->errorString(); + return; + } + QByteArray data = reply->readAll(); + if(m_froniusInverters.values().contains(thing)){ // check if thing was not removed before reply was received + m_froniusInverters.key(thing)->updateThingInfo(data); + } + }); + QNetworkReply *next_reply = hardwareManager()->networkManager()->get(QNetworkRequest(m_froniusInverters.key(thing)->activityUrl())); + connect(next_reply, &QNetworkReply::finished, next_reply, &QNetworkReply::deleteLater); + connect(next_reply, &QNetworkReply::finished, [this, thing, next_reply]() { + if (next_reply->error() != QNetworkReply::NoError) { + qCWarning(dcFronius()) << "Network request error:" << next_reply->error() << next_reply->errorString(); + return; + } + QByteArray data = next_reply->readAll(); + if(m_froniusInverters.values().contains(thing)){ // check if thing was not removed before reply was received + m_froniusInverters.key(thing)->updateActivityInfo(data); + } + }); + } else if (thing->thingClassId() == dataloggerThingClassId) { + QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(m_froniusLoggers.key(thing)->updateUrl())); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, [this, thing, reply]() { + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcFronius()) << "Network request error:" << reply->error() << reply->errorString(); + return; + } + QByteArray data = reply->readAll(); + if(m_froniusLoggers.values().contains(thing)){ // check if thing was not removed before reply was received + m_froniusLoggers.key(thing)->updateThingInfo(data); + } + }); + + reply = hardwareManager()->networkManager()->get(QNetworkRequest(m_froniusLoggers.key(thing)->updateRelayStateUrl())); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, [this, thing, reply]() { + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcFronius()) << "Network request error:" << reply->error() << reply->errorString(); + return; + } + QByteArray data = reply->readAll(); + if(m_froniusLoggers.values().contains(thing)){ // check if thing was not removed before reply was received + m_froniusLoggers.key(thing)->updatePowerRelayState(data); + } + }); + + } else if (thing->thingClassId() == meterThingClassId) { + QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(m_froniusMeters.key(thing)->updateUrl())); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, [this, thing, reply]() { + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcFronius()) << "Network request error:" << reply->error() << reply->errorString(); + return; + } + QByteArray data = reply->readAll(); + if(m_froniusMeters.values().contains(thing)){ // check if thing was not removed before reply was received + m_froniusMeters.key(thing)->updateThingInfo(data); + } + }); + QNetworkReply *next_reply = hardwareManager()->networkManager()->get(QNetworkRequest(m_froniusMeters.key(thing)->activityUrl())); + connect(next_reply, &QNetworkReply::finished, next_reply, &QNetworkReply::deleteLater); + connect(next_reply, &QNetworkReply::finished, [this, thing, next_reply]() { + + if (next_reply->error() != QNetworkReply::NoError) { + qCWarning(dcFronius()) << "Network request error:" << next_reply->error() << next_reply->errorString(); + return; + } + QByteArray data = next_reply->readAll(); + if(m_froniusMeters.values().contains(thing)){ // check if thing was not removed before reply was received + m_froniusMeters.key(thing)->updateActivityInfo(data); + } + }); + } else if (thing->thingClassId() == storageThingClassId) { + QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(m_froniusStorages.key(thing)->updateUrl())); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, [this, thing, reply]() { + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcFronius()) << "Network request error:" << reply->error() << reply->errorString(); + return; + } + QByteArray data = reply->readAll(); + if(m_froniusStorages.values().contains(thing)){ // check if thing was not removed before reply was received + m_froniusStorages.key(thing)->updateThingInfo(data); + } + }); + QNetworkReply *next_reply = hardwareManager()->networkManager()->get(QNetworkRequest(m_froniusStorages.key(thing)->activityUrl())); + connect(next_reply, &QNetworkReply::finished, next_reply, &QNetworkReply::deleteLater); + connect(next_reply, &QNetworkReply::finished, [this, thing, next_reply]() { + next_reply->deleteLater(); + if (next_reply->error() != QNetworkReply::NoError) { + qCWarning(dcFronius()) << "Network request error:" << next_reply->error() << next_reply->errorString(); + return; + } + QByteArray data = next_reply->readAll(); + if(m_froniusStorages.values().contains(thing)){ // check if thing was not removed before reply was received + m_froniusStorages.key(thing)->updateActivityInfo(data); + } + }); + } else { + Q_ASSERT_X(false, "updateThingState", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); + + } +} + +void IntegrationPluginFronius::searchNewThings(FroniusLogger *logger) +{ + QUrl url; QUrlQuery query; + query.addQueryItem("ThingClass", "System"); + url.setScheme("http"); + url.setHost(logger->hostAddress()); + url.setPath(logger->baseUrl() + "GetActiveThingInfo.cgi"); + url.setQuery(query); + + qCDebug(dcFronius()) << "Search Things at address" << url.toString(); + + QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url)); + connect(reply, &QNetworkReply::finished, [this, logger, reply]() { + + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcFronius()) << "Network request error:" << reply->error() << reply->errorString(); + return; + } + + Thing *thing = m_froniusLoggers.value(logger); + if (!thing) + return; + + QByteArray data = reply->readAll(); + + // Convert the rawdata to a json document + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcFronius()) << "Fronius: Failed to parse JSON data" << data << ":" << error.errorString(); + return; + } + + // Parse the data for thing information + QList thingDescriptors; + + // Check reply information + QVariantMap bodyMap = jsonDoc.toVariant().toMap().value("Body").toMap(); + + // Parse reply for inverters at the host address + QVariantMap inverterMap = bodyMap.value("Data").toMap().value("Inverter").toMap(); + foreach (QString inverterId, inverterMap.keys()) { + //check if thing already connected to logger + if(!existingThing(inverterThingIdParamTypeId,inverterId)) { + QString thingName = thing->name() + " Inverter " + inverterId; + ThingDescriptor descriptor(inverterThingClassId, thingName, "Fronius Solar Inverter"); + ParamList params; + params.append(Param(inverterThingNameParamTypeId, thingName)); + params.append(Param(inverterThingHostParamTypeId, m_froniusLoggers.key(thing)->hostAddress())); + params.append(Param(inverterThingBaseParamTypeId, m_froniusLoggers.key(thing)->baseUrl())); + params.append(Param(inverterThingIdParamTypeId, inverterId)); + params.append(Param(inverterThingUniqueIdParamTypeId, "")); + params.append(Param(inverterThingHostIdParamTypeId, thing->id())); + descriptor.setParams(params); + thingDescriptors.append(descriptor); + } + } + + if (!thingDescriptors.empty()) { + emit autoThingsAppeared(thingDescriptors); + thingDescriptors.clear(); + } + + // parse reply for meter things at the host address + QVariantMap meterMap = bodyMap.value("Data").toMap().value("Meter").toMap(); + foreach (QString meterId, meterMap.keys()) { + //check if thing already connected to logger + if(!existingThing(meterThingIdParamTypeId,meterId)) { + QString thingName = thing->name() + " Meter " + meterId; + ThingDescriptor descriptor(meterThingClassId, thingName, "Fronius Solar Meter"); + ParamList params; + params.append(Param(meterThingNameParamTypeId, thingName)); + //params.append(Param(meterThingManufParamTypeId, "")); + //params.append(Param(meterThingCapacityParamTypeId, "")); + params.append(Param(meterThingHostParamTypeId, m_froniusLoggers.key(thing)->hostAddress())); + params.append(Param(meterThingBaseParamTypeId, m_froniusLoggers.key(thing)->baseUrl())); + params.append(Param(meterThingIdParamTypeId, meterId)); + params.append(Param(meterThingUniqueIdParamTypeId, meterMap.value(meterId).toMap().value("Serial").toString())); + params.append(Param(meterThingHostIdParamTypeId, thing->id())); + descriptor.setParams(params); + thingDescriptors.append(descriptor); + } + } + + if (!thingDescriptors.empty()) { + emit autoThingsAppeared(thingDescriptors); + thingDescriptors.clear(); + } + + + // parse reply for storage things at the host address + QVariantMap storageMap = bodyMap.value("Data").toMap().value("Storage").toMap(); + foreach (QString storageId, storageMap.keys()) { + //check if thing already connected to logger + if(!existingThing(storageThingIdParamTypeId,storageId)) { + QString thingName = thing->name() + " Storage " + storageId; + ThingDescriptor descriptor(storageThingClassId, thingName, "Fronius Solar Storage"); + ParamList params; + params.append(Param(storageThingNameParamTypeId, thingName)); + params.append(Param(storageThingManufacturerParamTypeId, "")); + params.append(Param(storageThingCapacityParamTypeId, "")); + params.append(Param(storageThingHostParamTypeId, m_froniusLoggers.key(thing)->hostAddress())); + params.append(Param(storageThingBaseParamTypeId, m_froniusLoggers.key(thing)->baseUrl())); + params.append(Param(storageThingIdParamTypeId, storageId)); + params.append(Param(storageThingUniqueIdParamTypeId, storageMap.value(storageId).toMap().value("Serial").toString())); + params.append(Param(storageThingHostIdParamTypeId, thing->id())); + descriptor.setParams(params); + thingDescriptors.append(descriptor); + } + } + + if (!thingDescriptors.empty()) { + emit autoThingsAppeared(thingDescriptors); + thingDescriptors.clear(); + } + + }); + +} + +bool IntegrationPluginFronius::existingThing(ParamTypeId thingParamId, QString thingId) +{ + foreach(Thing *thing, myThings()) { + if(thing->paramValue(thingParamId).toString() == thingId) { + return true; + } + } + return false; +} + diff --git a/fronius/integrationpluginfronius.h b/fronius/integrationpluginfronius.h new file mode 100644 index 0000000..10d080b --- /dev/null +++ b/fronius/integrationpluginfronius.h @@ -0,0 +1,78 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 INTEGRATIONPLUGINFRONIUS_H +#define INTEGRATIONPLUGINFRONIUS_H + +#include "integrations/integrationplugin.h" +#include "froniuslogger.h" +#include "sunspecthing.h" + +#include +#include +#include + +class PluginTimer; + +class IntegrationPluginFronius : public IntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginfronius.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginFronius(QObject *parent = nullptr); + + void init() override; + void setupThing(ThingSetupInfo *thing) override; + void postSetupThing(Thing* thing) override; + void executeAction(ThingActionInfo *info) override; + void thingRemoved(Thing* thing) override; + + void startMonitoringAutoThings() override; + +private: + PluginTimer *m_pluginTimer = nullptr; + + QHash m_froniusLoggers; + QHash m_froniusInverters; + QHash m_froniusMeters; + QHash m_froniusStorages; + QHash m_sunspecThings; + + QHash m_asyncActions; + + void updateThingStates(Thing *thing); + + void searchNewThings(FroniusLogger *logger); + bool existingThing(ParamTypeId thingParamId, QString thingId); +}; + +#endif // INTEGRATIONPLUGINFRONIUS_H diff --git a/fronius/integrationpluginfronius.json b/fronius/integrationpluginfronius.json new file mode 100644 index 0000000..d4b3854 --- /dev/null +++ b/fronius/integrationpluginfronius.json @@ -0,0 +1,566 @@ +{ + "id": "02319cfc-8b55-49ba-99bc-0588bbfab063", + "name": "fronius", + "displayName": "Fronius Solar", + "vendors": [ + { + "id": "2286fc38-afd9-4128-ab7e-0fba527d53ba", + "name": "Fronius", + "displayName": "Fronius", + "thingClasses": [ + { + "id": "4fd79fed-42f1-4df9-be64-3df7b2e0bda2", + "name": "datalogger", + "displayName": "Fronius Solar Connection", + "createMethods": ["user"], + "interfaces": ["gateway"], + "paramTypes": [ + { + "id": "52da0197-4b78-4fec-aa72-70f949e26edc", + "name": "loggerHost", + "displayName": "Host address", + "type": "QString", + "inputType": "IPv4Address", + "defaultValue": "88.117.152.99" + } + ], + "stateTypes": [ + { + "id": "98e4476f-e745-4a7f-b795-19269cb70c40", + "name": "connected", + "displayName": "Reachable", + "displayNameEvent": "logger reachable changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "b22052ef-14da-43d2-982b-f2c2d8c03206", + "name": "productid", + "displayName": "Product ID", + "displayNameEvent": "Product ID changed", + "type": "QString", + "defaultValue": "-" + }, + { + "id": "65c068e6-4a0b-4672-9724-ae95216c4c9c", + "name": "platformid", + "displayName": "Platform ID", + "displayNameEvent": "Platform ID changed", + "type": "QString", + "defaultValue": "-" + }, + { + "id": "3b4206e5-74c7-4708-96b8-2abfab0c41d6", + "name": "hwversion", + "displayName": "Hardware version", + "displayNameEvent": "Hardware version changed", + "type": "QString", + "defaultValue": "-" + }, + { + "id": "31743ca5-4353-4f26-b2ad-5da43e5b9d86", + "name": "swversion", + "displayName": "Software version", + "displayNameEvent": "Software version changed", + "type": "QString", + "defaultValue": "-" + }, + { + "id": "d034f59d-dc34-450a-a6f3-68264767a3e4", + "name": "tzoneloc", + "displayName": "Timezone location", + "displayNameEvent": "Timezone location changed", + "type": "QString", + "defaultValue": "-" + }, + { + "id": "6bdfeeda-7a47-4043-a011-5eb96308a7d6", + "name": "tzone", + "displayName": "Time zone", + "displayNameEvent": "Time zone changed", + "type": "QString", + "defaultValue": "-" + }, + { + "id": "18b250e2-080a-4991-b368-177c4da83eca", + "name": "defaultlang", + "displayName": "Default language", + "displayNameEvent": "Default language changed", + "type": "QString", + "defaultValue": "-" + }, + { + "id": "bc18595b-17c7-4a1f-8002-b908a3d9239d", + "name": "cashfactor", + "displayName": "Cash factor", + "displayNameEvent": "Cash factor changed", + "type": "double", + "defaultValue": "-" + }, + { + "id": "84da30c8-a7fb-49c6-884c-9521f9f62bbc", + "name": "cashcurrency", + "displayName": "Cash currency", + "displayNameEvent": "Cash Currency changed", + "type": "QString", + "defaultValue": "-" + }, + { + "id": "8ab01225-7be5-4482-a99b-314108ae0e2b", + "name": "co2factor", + "displayName": "CO2 factor", + "displayNameEvent": "CO2 factor changed", + "type": "double", + "defaultValue": "-" + }, + { + "id": "b0e655f8-27d0-4add-918b-461cadc8efcc", + "name": "co2unit", + "displayName": "CO2 unit", + "displayNameEvent": "CO2 unit changed", + "type": "QString", + "defaultValue": "-" + }, + { + "id": "b217acf6-0c5e-4a3e-a50c-4c0133c871c2", + "name": "powerManagmentRelay", + "displayName": "Power management relay", + "displayNameEvent": "Power management relay status changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "5650ce9b-0d7d-4c52-b410-ea618889b4bb", + "name": "powerManagmentRelayReason", + "displayName": "Power management relay reason", + "displayNameEvent": "Power management relay reason changed", + "type": "QString", + "defaultValue": "" + } + ], + "actionTypes": [ + { + "id": "c217fdc1-de18-41dc-b5d8-8072f84e7b6c", + "name": "searchDevices", + "displayName": "Search new devices" + } + ] + }, + { + "id": "540aa956-8b8f-4982-9f58-343a76cea846", + "name": "inverter", + "displayName": "Fronius Solar Inverter", + "createMethods": ["auto"], + "interfaces" : ["extendedsmartmeterproducer", "connectable"], + "paramTypes": [ + { + "id": "2aa06052-bb2f-4868-9c2d-f221fb6f1ed8", + "name": "name", + "displayName": "Name", + "type": "QString", + "inputType": "TextLine" + }, + { + "id": "1aa82e12-ee8c-4142-8b89-a4f1e85556d0", + "name": "host", + "displayName": "Host address", + "type": "QString", + "inputType": "TextLine" + }, + { + "id": "ec1f792a-b453-49f0-8ea6-677ad3c25a5c", + "name": "base", + "displayName": "Base url", + "type": "QString", + "inputType": "TextLine" + }, + { + "id": "f2f8c2f5-dd6a-4786-b336-82fc84e5bb98", + "name": "id", + "displayName": "Device id", + "type": "QString", + "inputType": "TextLine" + }, + { + "id": "8fadc0e8-9d69-4b9d-b493-b6ac3eb59c49", + "name": "uniqueId", + "displayName": "Uique id", + "type": "QString", + "inputType": "TextLine" + }, + { + "id": "e329ecd2-6893-432f-9f14-069593c996e9", + "name": "hostId", + "displayName": "host id", + "type": "QString", + "inputType": "TextLine" + } + ], + "stateTypes": [ + { + "id": "eda29c50-73ac-40e0-9c92-26fee352e688", + "name": "connected", + "displayName": "Reachable", + "displayNameEvent": "Reachable changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "e763baa7-5eaf-438c-83f0-4fa6c0f7eeb0", + "name": "active", + "displayName": "Inverter active", + "displayNameEvent": "Inverter active changed", + "type": "QString", + "defaultValue": "-" + }, + { + "id": "788accbc-b86e-471b-b37f-14c9c6411526", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current Power changed", + "type": "double", + "unit": "Watt", + "defaultValue": "0" + }, + { + "id": "b6af1bf5-753d-47b6-a151-e4d801fe6ff8", + "name": "eday", + "displayName": "Energy of current day", + "displayNameEvent": "Energy of day changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": "0" + }, + { + "id": "7fd2fa28-9bcc-4f01-a823-459437d185f6", + "name": "eyear", + "displayName": "Energy of current year", + "displayNameEvent": "Energy of year changed", + "type": "int", + "unit": "KiloWattHour", + "defaultValue": "0" + }, + { + "id": "d6dbb879-4cbc-4db3-830e-b92ba91a13e5", + "name": "totalEnergyProduced", + "displayName": "Energy total", + "displayNameEvent": "Energy total changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": "0" + } + ] + }, + { + "id": "c3cb53a4-32dd-434d-9d9c-aada41f8129c", + "name": "meter", + "displayName": "Fronius Smart Meter", + "createMethods": ["auto"], + "interfaces": [ "extendedsmartmeterconsumer", "extendedsmartmeterproducer", "connectable" ], + "paramTypes": [ + { + "id": "8ce0ba4f-e490-4b85-9c34-7da8586a8d1c", + "name": "name", + "displayName": "Name", + "type": "QString", + "inputType": "TextLine" + }, + { + "id": "ddcb8689-b0b8-4b94-b022-4ce4cf9e0ec2", + "name": "host", + "displayName": "host address", + "type": "QString", + "inputType": "TextLine" + }, + { + "id": "2cb4acd6-a663-48c3-8366-ab538c7b4e7d", + "name": "base", + "displayName": "base url", + "type": "QString", + "inputType": "TextLine" + }, + { + "id": "cf3a7025-d368-475a-8f48-efc1344a8409", + "name": "id", + "displayName": "device id", + "type": "QString", + "inputType": "TextLine" + }, + { + "id": "285eabb2-47c8-4406-8123-6621b21558c1", + "name": "uniqueId", + "displayName": "uique id", + "type": "QString", + "inputType": "TextLine" + }, + { + "id": "b4b86bfc-598a-4b5c-9350-52b944556ccc", + "name": "hostId", + "displayName": "host id", + "type": "QString", + "inputType": "TextLine" + } + ], + "stateTypes": [ + { + "id": "b70b61a4-54cb-47ec-b62a-b498eb1f650e", + "name": "connected", + "displayName": "reachable", + "displayNameEvent": "Meter reachable changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "e5056ea1-88a2-410b-9c5e-6322aca4cb17", + "name": "currentPower", + "displayName": "Total current power", + "displayNameEvent": "total current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": "0" + }, + { + "id": "ca14cca5-d9f0-49c5-a8f7-907d4c0825f0", + "name": "totalEnergyProduced", + "displayName": "Energy Produced", + "displayNameEvent": "energy production changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": "0" + }, + { + "id": "f3451818-48d2-42a5-94fd-ad094c06967f", + "name": "totalEnergyConsumed", + "displayName": "Energy Consumed", + "displayNameEvent": "energy consumption changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": "0" + } + ] + }, + { + "id": "b00139fa-7386-48b1-8697-2fdd21a57ced", + "name": "storage", + "displayName": "Fronius Solar Storage", + "createMethods": ["auto"], + "interfaces": [ "connectable", "batterylevel"], + "paramTypes": [ + { + "id": "e511c738-15d1-4881-b1c0-c284740564ba", + "name": "name", + "displayName": "displayName", + "type": "QString", + "defaultValue": "TextLine" + }, + { + "id": "9665c38b-c13a-428f-b741-1470239c63dc", + "name": "manufacturer", + "displayName": "manufacturer", + "type": "QString", + "defaultValue": "TextLine" + }, + { + "id": "59a68e91-1aad-46b7-b351-03b7b2216366", + "name": "capacity", + "displayName": "maxmimum capacity", + "type": "QString", + "defaultValue": "TextLine" + }, + { + "id": "84bd8a41-2411-4bb0-87a9-ab7e01044b10", + "name": "host", + "displayName": "host address", + "type": "QString", + "inputType": "TextLine" + }, + { + "id": "d19b5d81-4e62-48be-bad6-287b0019274a", + "name": "base", + "displayName": "base url", + "type": "QString", + "inputType": "TextLine" + }, + { + "id": "49087f31-abf5-4bb8-946b-a3626ee80566", + "name": "id", + "displayName": "device id", + "type": "QString", + "inputType": "TextLine" + }, + { + "id": "0d62432a-38bc-48b8-99d2-895f17fcf0b2", + "name": "uniqueId", + "displayName": "unique id", + "type": "QString", + "inputType": "TextLine" + }, + { + "id": "79daa7f4-2551-4b89-b34a-2d75d1441a55", + "name": "hostId", + "displayName": "Host ID", + "type": "QString", + "inputType": "TextLine" + } + ], + "stateTypes": [ + { + "id": "2f7e1267-b0be-4b78-9aa3-832b86c4efad", + "name": "connected", + "displayName": "reachable", + "displayNameEvent": "Storage reachable changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "2de34a1f-de2e-43ad-8998-8a5460dff9ae", + "name": "charging", + "displayName": "state of charge", + "displayNameEvent": "charge state changed", + "type": "QString", + "defaultValue": "-" + }, + { + "id": "5c6da672-9662-41bc-8c8c-aa0f32481251", + "name": "batteryLevel", + "displayName": "current charge", + "displayNameEvent": "current charge changed", + "type": "int", + "unit": "Percentage", + "defaultValue": "0", + "minValue": 0, + "maxValue": 100 + }, + { + "id": "4417499c-1757-4309-868a-be5cf3455c4a", + "name": "cellTemperature", + "displayName": "cell temperature", + "displayNameEvent": "cell temperature changed", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": "0" + }, + { + "id": "e5396312-b50e-4d6f-b628-5b51448971d3", + "name": "batteryCritical", + "displayName": "Battery level critical", + "displayNameEvent": "Battery level critical changed", + "type": "bool", + "defaultValue": false + } + + ] + }, + { + "id": "e14d622f-5d8f-4788-b189-0774a6382a9b", + "name": "sunspecStorage", + "displayName": "SunSpec Storage", + "createMethods": ["user"], + "interfaces": [ "connectable" ], + "paramTypes": [ + { + "id": "830a3cc6-ae0a-4cc3-94d6-86575e410e49", + "name": "modbusHost", + "displayName": "Host address", + "type": "QString", + "inputType": "IPv4Address", + "defaultValue": "88.117.152.99" + } + ], + "stateTypes": [ + { + "id": "50ed3a6f-6ad3-445f-950b-eb6d1b7e7ef7", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "connected changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "0bf53f80-97f8-488b-b514-58f9fe08c183", + "name": "energy", + "displayName": "Energy", + "displayNameEvent": "storage energy changed", + "type": "double", + "unit": "Percentage", + "defaultValue": 0 + }, + { + "id": "da2b19c5-0f48-49d1-93f0-abdc0051407d", + "name": "storageState", + "displayName": "State", + "displayNameEvent": "Storage state changed", + "type": "QString", + "possibleValues": [ + "Off", + "Empty", + "Discharging", + "Charging", + "Full", + "Holding", + "Testing" + ], + "defaultValue": "Off" + }, + { + "id": "221a2ef6-0a92-4ff0-87fe-7bd920dbec0b", + "name": "gridCharging", + "displayName": "Grid charging", + "displayNameEvent": "Charging from grid changed", + "type": "bool", + "defaultValue": false, + "writable": true, + "displayNameAction": "Set charging from grid" + }, + { + "id": "1f530f79-c0d2-466b-90e1-79149e34d92f", + "name": "enableChargingLimit", + "displayName": "Turn Charging Limit", + "displayNameEvent": "Charging limit", + "type": "bool", + "defaultValue": false, + "writable": true, + "displayNameAction": "Enable Charging Limit" + }, + { + "id": "7f469bbc-64a5-4045-8d5f-9a1a85039851", + "name": "chargingRate", + "displayName": "Charging rate", + "displayNameEvent": "Charging rate changed", + "type": "int", + "minValue": -100, + "maxValue": 100, + "unit": "Percentage", + "defaultValue": false, + "writable": true, + "displayNameAction": "Set charging rate" + }, + { + "id": "bc99a159-815a-40ab-a6e8-b46f315305f7", + "name": "enableDischargingLimit", + "displayName": "Turn Discharging Limit", + "displayNameEvent": "Discharging limit", + "type": "bool", + "defaultValue": false, + "writable": true, + "displayNameAction": "Enable Discharging Limit" + }, + { + "id": "6068f030-acce-44a2-b95f-bd00dd5ca760", + "name": "dischargingRate", + "displayName": "Discharging rate", + "displayNameEvent": "Discharging rate changed", + "type": "int", + "minValue": -100, + "maxValue": 100, + "unit": "Percentage", + "defaultValue": false, + "writable": true, + "displayNameAction": "Set discharging rate" + } + ] + } + ] + } + ] +} diff --git a/fronius/sunspecthing.cpp b/fronius/sunspecthing.cpp new file mode 100644 index 0000000..e757836 --- /dev/null +++ b/fronius/sunspecthing.cpp @@ -0,0 +1,461 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 +#include + +#include "sunspecthing.h" + +#include "extern-plugininfo.h" + +SunspecThing::SunspecThing(Thing *thing, QObject *parent) : + QObject(parent), + m_thing(thing), + m_modbus(nullptr) +{ + +} + +SunspecThing::~SunspecThing() +{ + modbus_free(m_modbus); + m_modbus = nullptr; +} + +void SunspecThing::destroyModbus() +{ + if (m_thing) + m_thing->setStateValue(sunspecStorageConnectedStateTypeId, false); + + if (!m_modbus) + return; + + modbus_free(m_modbus); + m_modbus = nullptr; +} + +void SunspecThing::readCommonBlock() +{ + if (!m_modbus) + return; + + qCDebug(dcFronius()) << "Successfully set slave to" << m_slaveId; + + int address = 40001 - 1; + uint16_t data[70]; + + // Read common block + if (modbus_read_registers(m_modbus, address, 70, data) < 0) { + qCWarning(dcFronius()) << "Could not read register address:" << modbus_strerror(errno); + destroyModbus(); + return; + } + + qCDebug(dcFronius()) << "Sunspec Identification:" << convertModbusRegisters(data, 0, 2); + + if (convertModbusRegisters(data, 0, 2) != "SunS") { + qCWarning(dcFronius()) << "Could not find SunS value at" << address << "on" << address; + destroyModbus(); + return; + } + + // ID: 40003 + qCDebug(dcFronius()) << "Id:" << data[2]; + + // Manufacturer: 40005 | 16 + qCDebug(dcFronius()) << "Manufacturer:" << QString::fromLatin1(convertModbusRegisters(data, 4, 16)); + + // Thing model: 40021 | 16 + qCDebug(dcFronius()) << "Thing model:" << QString::fromLatin1(convertModbusRegisters(data, 20, 16)); + + // Data manager version: 40037 | 8 + qCDebug(dcFronius()) << "Data manager version:" << QString::fromLatin1(convertModbusRegisters(data, 36, 8)); + + // Inverter Version: 40045 | 8 + qCDebug(dcFronius()) << "Inverter version:" << QString::fromLatin1(convertModbusRegisters(data, 44, 8)); + + // Serial Number: 40053 | 16 + qCDebug(dcFronius()) << "Serial number:" << QString::fromLatin1(convertModbusRegisters(data, 52, 16)); + + // Modbus thing address : 40069 | 1 + qCDebug(dcFronius()) << "Thing modbus address:" << data[67]; + + /*Sunspec Model Type + zum Auswählen des Datentyps von Datenmodellen für Wechselrichter + (3d) float + Darstellung als Gleitkommazahlen + SunSpec Inverter Model I111, I112 oder I113 + (3e) int+SF + Darstellung als ganze Zahlen mit Skalierungsfaktoren + SunSpec Inverter Model I101, I102 oder I103 + WICHTIG! Da die verschiedenen Modelle über unterschiedliche Anzahlen an Re- + gistern verfügen, ändern sich durch den Wechsel des Datentyps auch die Regis- + teradressen aller nachfolgenden Modelle.*/ + qCDebug(dcFronius()) << "SunSpec Inverter Modbus Map:" << data[69]; + if (data[69] > 110){ + m_floatingPointRepresentation = true; + } else { + m_floatingPointRepresentation = false; + } + + readStorageBlock(); +} + +void SunspecThing::readStorageBlock() +{ + if (!m_modbus) + return; + + + qCDebug(dcFronius()) << "Storage"; + + int address; + + if (m_floatingPointRepresentation) { + address = 40313; + } else { + address = 40303; + } + /* Startadresse: + - bei Einstellung „float“: 40313 + - bei Einstellung „int+SF“: 40303 + */ + uint16_t data[26]; + + if (modbus_read_registers(m_modbus, address, 26, data) < 0) { + qCWarning(dcFronius()) << "Could not read register address" << address << ":" << modbus_strerror(errno); + destroyModbus(); + return; + } + + // ID + qCDebug(dcFronius()) << "Id:" << data[0]; + + if (data[0] != 124) { + qCWarning(dcFronius()) << "Invalid id in register address" << address << ":" << data[0]; + destroyModbus(); + return; + } + + // L + qCDebug(dcFronius()) << "Register count:" << data[1]; + + // WchaMax + qCDebug(dcFronius()) << "Setpoint of maximum charge:" << data[2] << "W"; + + // WchaGra + qCDebug(dcFronius()) << "Setpoint for maximum charge:" << data[3] << "[s]"; + + // WdisChaGra + qCDebug(dcFronius()) << "Setpoint for maximum discharge rate:" << data[4] << "[s]"; + + // StorCtl_Mod: Activate hold/discharge/charge storage control mode. Bit0: charge, Bit 1: discharge + QBitArray storageControlBits = convertModbusRegisterBits(data[5]); + + qCDebug(dcFronius()) << "Charging control mode:" << (storageControlBits.testBit(0) ? "On" : "Off"); + m_thing->setStateValue(sunspecStorageEnableChargingLimitStateTypeId, storageControlBits.testBit(0)); + + qCDebug(dcFronius()) << "Discharging control mode:" << (storageControlBits.testBit(1) ? "On" : "Off"); + m_thing->setStateValue(sunspecStorageEnableDischargingLimitStateTypeId, storageControlBits.testBit(1)); + + // MinRsvPct + qCDebug(dcFronius()) << "Setpoint for minimum reserve:" << data[7] << "[%]"; + + // ChaState + qCDebug(dcFronius()) << "Current energy:" << ((double)data[8] / 100.0) << "[%]"; + m_thing->setStateValue(sunspecStorageEnergyStateTypeId, (double)data[8] / 100.0); + + // ChaSt + //Charge status of storage thing. Enumerated + qCDebug(dcFronius()) << "Charge state" << storageStateToString(static_cast(data[11])); + m_thing->setStateValue(sunspecStorageStorageStateStateTypeId, storageStateToString(static_cast(data[11]))); + + // OutWRte + m_thing->setStateValue(sunspecStorageDischargingRateStateTypeId, (int16_t)data[12] / 100); + qCDebug(dcFronius()) << "Percent of max. discharge rate:" << ((int16_t)data[12] / 100) << "[%]"; + + // InWRte + m_thing->setStateValue(sunspecStorageChargingRateStateTypeId, (int16_t)data[13] / 100); + qCDebug(dcFronius()) << "Percent of max. charge rate:" << ((int16_t)data[13] / 100) << "[%]"; + + // ChaGriSet + m_thing->setStateValue(sunspecStorageGridChargingStateTypeId, data[18]); + qCDebug(dcFronius()) << "Charging from grid:" << (data[18] == 0 ? "disabled" : "enabled"); + +} + +QByteArray SunspecThing::convertModbusRegister(const uint16_t &modbusData) +{ + uint8_t data[2]; + data[0] = modbusData >> 8; + data[1] = modbusData & 0xFF; + //qCDebug(dcFronius()) << (char)data[0] << (char)data[1]; + return QByteArray().append((char)data[0]).append((char)data[1]); +} + +QBitArray SunspecThing::convertModbusRegisterBits(const uint16_t &modbusData) +{ + QByteArray data = convertModbusRegister(modbusData); + QBitArray bits(data.count() * 8); + + // Convert from QByteArray to QBitArray + for(int i = 0; i < data.count(); ++i) { + for(int b = 0; b < 8; b++) { + bits.setBit(i * 8 + b, data.at(i) & (1 << ( 7 - b))); + } + } + return bits; +} + +QByteArray SunspecThing::convertModbusRegisters(uint16_t *modbusData, const int &offset, const int &size) +{ + QByteArray bytes; + for (int i = offset; i < offset + size; i++) + bytes.append(convertModbusRegister(modbusData[i])); + + return bytes; +} + +QString SunspecThing::storageStateToString(const SunspecThing::StorageState &state) +{ + QMetaObject metaObject ; + metaObject= SunspecThing::staticMetaObject; + QMetaEnum metaEnum = metaObject.enumerator(metaObject.indexOfEnumerator("StorageState" ) ); + return QString(metaEnum.valueToKey(state)); +} + +bool SunspecThing::connectModbus() +{ + if (m_modbus) + destroyModbus(); + + if (m_thing->paramValue(sunspecStorageThingModbusHostParamTypeId).toString().isEmpty()) { + qCWarning(dcFronius()) << "Empty ip address"; + return false; + } + + m_modbus = modbus_new_tcp(m_thing->paramValue(sunspecStorageThingModbusHostParamTypeId).toString().toStdString().c_str(), 502); + if (modbus_connect(m_modbus) == -1) { + qCWarning(dcFronius()) << "Connection failed:" << modbus_strerror(errno); + destroyModbus(); + return false; + } + + //TODO + //timeval response_timeout; + //response_timeout.tv_sec = 3; + //response_timeout.tv_usec = 0; + + //timeval byte_timeout; + //byte_timeout.tv_sec = 3; + //byte_timeout.tv_usec = 0; + //modbus_set_byte_timeout(m_modbus, &byte_timeout); + //modbus_set_response_timeout(m_modbus, &response_timeout); + + qCDebug(dcFronius()) << "Connected successfully" << m_thing->paramValue(sunspecStorageThingModbusHostParamTypeId).toString(); + m_thing->setStateValue(sunspecStorageConnectedStateTypeId, true); + + + if (modbus_set_slave(m_modbus, m_slaveId) != 0) { + qCWarning(dcFronius()) << "Could not set Slave Id to" << m_slaveId << ":" << modbus_strerror(errno); + destroyModbus(); + return false; + } + + readCommonBlock(); + + return true; +} + +void SunspecThing::disconnectModbus() +{ + if (!m_modbus) + return; + + destroyModbus(); +} + +bool SunspecThing::setGridCharging(const bool &charging) +{ + if (!m_modbus) { + connectModbus(); + return false; + } + + /* Start address: + - for “float” setting: 40313 + - for “int+SF” setting: 40303 */ + + // 40313 + Offset 18 - 1 + // Name ChaGriSet + /* Setpoint to enable/dis- + able charging from grid + PV (charging from grid 0 disabled) + GRID (charging from 1 grid enabled*/ + + int registerAddress; + if (m_floatingPointRepresentation) { + registerAddress = 40313 + 18 - 1; + } else { + registerAddress = 40303 + 18 - 1; + } + + uint16_t value = 0; + if(modbus_read_registers(m_modbus, registerAddress, 1, &value) == -1){ + qCWarning(dcFronius()) << "Could not read register address:" << registerAddress << (int16_t)value << modbus_strerror(errno); + return false; + }else { + qDebug(dcFronius()) << "Succesfully read register:" << registerAddress << (int16_t)value ; + } + + + if (charging) { + value = 1; + if (modbus_write_register(m_modbus, registerAddress, value) == -1) { + qCWarning(dcFronius()) << "Could not write register address:" << registerAddress << value << modbus_strerror(errno); + return false; + } + } else { + value = 0; + if (modbus_write_register(m_modbus, registerAddress, value) == -1) { + qCWarning(dcFronius()) << "Could not write register address:" << registerAddress << value << modbus_strerror(errno); + return false; + } + } + + readStorageBlock(); + return true; +} + +bool SunspecThing::setChargingRate(const int &charging) +{ + // 40313 + Offset 14 - 1 + //Register Name InWRte + /* Defines the maximum charge rate (charge limit). Default is 100% */ + + if (!m_modbus) { + connectModbus(); + return false; + } + + int registerAddress; + if (m_floatingPointRepresentation) { + registerAddress = 40313 + 14 - 1; + } else { + registerAddress = 40303 + 14 - 1; + } + + int16_t value = charging * 100; + if (modbus_write_register(m_modbus, registerAddress, value) == -1) { + qCWarning(dcFronius()) << "Could not write register address:" << registerAddress << value << modbus_strerror(errno); + return false; + } + + readStorageBlock(); + return true; +} + +bool SunspecThing::setStorageControlMode(const int &charging) +{ + // 40313 + Offset 6 - 1 + // Set charge bit to enable charge limit, set discharge bit to enable discharge limit, set both bits to enable both limits + + if (!m_modbus) { + connectModbus(); + return false; + } + + if (modbus_set_slave(m_modbus, m_slaveId) != 0) { + qCWarning(dcFronius()) << "Could not set slave to" << m_slaveId << ":" << modbus_strerror(errno); + destroyModbus(); + return false; + } + + int registerAddress; + if (m_floatingPointRepresentation) { + registerAddress = 40313 + 6 - 1; + } else { + registerAddress = 40303 + 6 - 1; + } + + uint16_t value = charging; + if (modbus_write_register(m_modbus, registerAddress, value) == -1) { + qCWarning(dcFronius()) << "Could not write register address:" << registerAddress << value << modbus_strerror(errno); + return false; + } + + readStorageBlock(); + return true; +} + +bool SunspecThing::setDischargingRate(const int &charging) +{ + // 40313 + Offset 13 - 1 + //Register Name OutWRte + /*Defines the maximum discharge rate (discharge limit). Default is 100% */ + + if (!m_modbus) { + connectModbus(); + return false; + } + + if (modbus_set_slave(m_modbus, m_slaveId) != 0) { + qCWarning(dcFronius()) << "Could not set slave to" << m_slaveId << ":" << modbus_strerror(errno); + destroyModbus(); + return false; + } + + int registerAddress; + if (m_floatingPointRepresentation) { + registerAddress = 40313 + 13 - 1; + } else { + registerAddress = 40303 + 13 - 1; + } + + int16_t value = charging * 100; + if (modbus_write_register(m_modbus, registerAddress, value) == -1) { + qCWarning(dcFronius()) << "Could not write register address:" << registerAddress << value << modbus_strerror(errno); + return false; + } + + readStorageBlock(); + return true; +} + + +void SunspecThing::update() +{ + if (!m_modbus) { + connectModbus(); + return; + } + + readCommonBlock(); +} diff --git a/fronius/sunspecthing.h b/fronius/sunspecthing.h new file mode 100644 index 0000000..7025177 --- /dev/null +++ b/fronius/sunspecthing.h @@ -0,0 +1,91 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 SUNSPECTHING_H +#define SUNSPECTHING_H + +#include "integrations/thing.h" + +#include +#include +#include +#include + +#include +#include + +class SunspecThing : public QObject +{ + Q_OBJECT +public: + enum StorageState { + Off = 1, + Empty = 2, + Discharging = 3, + Charging = 4, + Full = 5, + Holding = 6, + Testing = 7 + }; + Q_ENUM(StorageState) + + explicit SunspecThing(Thing *thing, QObject *parent = nullptr); + ~SunspecThing(); + +private: + Thing *m_thing; + modbus_t *m_modbus; + int m_slaveId = 1; + bool m_floatingPointRepresentation = false; + + void destroyModbus(); + + void readCommonBlock(); + void readStorageBlock(); + + QByteArray convertModbusRegister(const uint16_t &modbusData); + QBitArray convertModbusRegisterBits(const uint16_t &modbusData); + QByteArray convertModbusRegisters(uint16_t *modbusData, const int &offset, const int &size); + + QString storageStateToString(const StorageState &state); + +public slots: + bool connectModbus(); + void disconnectModbus(); + + bool setGridCharging(const bool &charging); + bool setDischargingRate(const int &charging); + bool setChargingRate(const int &charging); + bool setStorageControlMode(const int &charging); + + void update(); +}; + +#endif // SUNSPECTHING_H