added fronius

This commit is contained in:
Boernsman 2020-06-16 12:07:24 +02:00
parent b444a313f6
commit b1f5c89944
17 changed files with 2806 additions and 0 deletions

View File

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

23
fronius/fronius.pro Normal file
View File

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

135
fronius/froniusinverter.cpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "froniusinverter.h"
#include <QJsonDocument>
#include "extern-plugininfo.h"
#include <QDateTime>
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");
}
}

56
fronius/froniusinverter.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef FRONIUSINVERTER_H
#define FRONIUSINVERTER_H
#include <QObject>
#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

157
fronius/froniuslogger.cpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "froniuslogger.h"
#include "extern-plugininfo.h"
#include <QJsonDocument>
#include <QDateTime>
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);
}

54
fronius/froniuslogger.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef FRONIUSLOGGER_H
#define FRONIUSLOGGER_H
#include <QObject>
#include <QUrl>
#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

130
fronius/froniusmeter.cpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "froniusmeter.h"
#include "extern-plugininfo.h"
#include <QJsonDocument>
#include <QDateTime>
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");
//}
}

55
fronius/froniusmeter.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef FRONIUSMETER_H
#define FRONIUSMETER_H
#include <QObject>
#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

138
fronius/froniusstorage.cpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "froniusstorage.h"
#include <QJsonDocument>
#include "extern-plugininfo.h"
#include <QDateTime>
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");
}
}

60
fronius/froniusstorage.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef FRONIUSSTORAGE_H
#define FRONIUSSTORAGE_H
#include <QObject>
#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

92
fronius/froniusthing.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#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;
}

81
fronius/froniusthing.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef FRONIUSTHING_H
#define FRONIUSTHING_H
#include "extern-plugininfo.h"
#include "integrations/thing.h"
#include <QObject>
#include <QUrl>
#include <QUrlQuery>
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

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "integrationpluginfronius.h"
#include "plugintimer.h"
#include "network/networkaccessmanager.h"
#include <QUrl>
#include <QUrlQuery>
#include <QJsonDocument>
#include <QPointer>
#include "plugininfo.h"
#include <QDebug>
// 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<ThingDescriptor> 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;
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef INTEGRATIONPLUGINFRONIUS_H
#define INTEGRATIONPLUGINFRONIUS_H
#include "integrations/integrationplugin.h"
#include "froniuslogger.h"
#include "sunspecthing.h"
#include <QHash>
#include <QNetworkReply>
#include <QTimer>
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<FroniusLogger *, Thing *> m_froniusLoggers;
QHash<FroniusInverter *, Thing *> m_froniusInverters;
QHash<FroniusMeter *, Thing *> m_froniusMeters;
QHash<FroniusStorage *, Thing *> m_froniusStorages;
QHash<SunspecThing *, Thing *> m_sunspecThings;
QHash<ThingActionInfo *, Thing *> m_asyncActions;
void updateThingStates(Thing *thing);
void searchNewThings(FroniusLogger *logger);
bool existingThing(ParamTypeId thingParamId, QString thingId);
};
#endif // INTEGRATIONPLUGINFRONIUS_H

View File

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

461
fronius/sunspecthing.cpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <QMetaObject>
#include <QMetaEnum>
#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<StorageState>(data[11]));
m_thing->setStateValue(sunspecStorageStorageStateStateTypeId, storageStateToString(static_cast<StorageState>(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();
}

91
fronius/sunspecthing.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef SUNSPECTHING_H
#define SUNSPECTHING_H
#include "integrations/thing.h"
#include <QObject>
#include <QHostAddress>
#include <QByteArray>
#include <QBitArray>
#include <modbus/modbus.h>
#include <modbus/modbus-tcp.h>
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