diff --git a/debian/control b/debian/control
index 4a3dc3c..8dfe067 100644
--- a/debian/control
+++ b/debian/control
@@ -50,7 +50,7 @@ Architecture: any
Section: libs
Depends: ${shlibs:Depends},
${misc:Depends},
- nymea-plugins-modbus-translations,
+ nymea-plugins-modbus-translations
Description: nymea.io plugin for my-pv heating rods
The nymea daemon is a plugin based IoT (Internet of Things) server. The
server works like a translator for devices, things and services and
@@ -61,6 +61,21 @@ Description: nymea.io plugin for my-pv heating rods
This package will install the nymea.io plugin for my-pv
+Package: nymea-plugin-sunspec
+Architecture: any
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+ nymea-plugins-modbus-translations
+Description: nymea.io plugin for SunSpec Modbus devices.
+ The nymea daemon is a plugin based IoT (Internet of Things) server. The
+ server works like a translator for devices, things and services and
+ allows them to interact.
+ With the powerful rule engine you are able to connect any device available
+ in the system and create individual scenes and behaviors for your environment.
+ .
+ This package will install the nymea.io plugin for SunSpec.
+
+
Package: nymea-plugin-wallbe
Architecture: any
Section: libs
@@ -89,4 +104,3 @@ Description: Translation files for nymea modbus plugins - translations
in the system and create individual scenes and behaviors for your environment.
.
This package provides the translation files for all nymea modbus plugins.
-
diff --git a/debian/nymea-plugin-sunspec.install.in b/debian/nymea-plugin-sunspec.install.in
new file mode 100644
index 0000000..aae234d
--- /dev/null
+++ b/debian/nymea-plugin-sunspec.install.in
@@ -0,0 +1 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginsunspec.so
diff --git a/mypv/integrationpluginmypv.cpp b/mypv/integrationpluginmypv.cpp
index 1dae803..f779210 100644
--- a/mypv/integrationpluginmypv.cpp
+++ b/mypv/integrationpluginmypv.cpp
@@ -195,7 +195,6 @@ void IntegrationPluginMyPv::executeAction(ThingActionInfo *info)
void IntegrationPluginMyPv::onRefreshTimer()
{
-
foreach (Thing *thing, myThings()) {
update(thing);
}
diff --git a/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro
index 7d5b93a..92c6b5c 100644
--- a/nymea-plugins-modbus.pro
+++ b/nymea-plugins-modbus.pro
@@ -4,6 +4,7 @@ PLUGIN_DIRS = \
drexelundweiss \
modbuscommander \
mypv \
+ sunspec \
wallbe \
message(============================================)
diff --git a/sunspec/README.md b/sunspec/README.md
new file mode 100644
index 0000000..64ffc1a
--- /dev/null
+++ b/sunspec/README.md
@@ -0,0 +1,24 @@
+# SunSpec
+
+Connect nymea to SunSpec devices over Modbus TCP
+
+## Supported Things
+
+* SunSpec Inverter
+ * Model ID 101 & 111 Single phase inverter
+ * Model ID 102 & 112 Split phase inverter
+ * Model ID 103 & 113 Three phase inverter
+* SunSpec Meter
+ * Model ID 201 & 211 Single phase meter
+ * Model ID 202 & 212 Split phase meter
+ * Model ID 203 & 213 Three phase meter
+* SunSpec Storage
+ * Model ID 124
+
+## Requirements
+
+* The package 'nymea-plugin-sunspec' must be installed.
+* The SunSpec device must support SunSpec over modbus TCP
+
+## More
+https://sunspec.org
diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp
new file mode 100644
index 0000000..c6a95ed
--- /dev/null
+++ b/sunspec/integrationpluginsunspec.cpp
@@ -0,0 +1,847 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "plugininfo.h"
+#include "integrationpluginsunspec.h"
+
+#include
+
+IntegrationPluginSunSpec::IntegrationPluginSunSpec()
+{
+
+}
+
+void IntegrationPluginSunSpec::init()
+{
+ m_connectedStateTypeIds.insert(sunspecConnectionThingClassId, sunspecConnectionConnectedStateTypeId);
+ m_connectedStateTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterConnectedStateTypeId);
+ m_connectedStateTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterConnectedStateTypeId);
+ m_connectedStateTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterConnectedStateTypeId);
+ m_connectedStateTypeIds.insert(sunspecStorageThingClassId, sunspecStorageConnectedStateTypeId);
+ m_connectedStateTypeIds.insert(sunspecSinglePhaseMeterThingClassId, sunspecSinglePhaseMeterConnectedStateTypeId);
+ m_connectedStateTypeIds.insert(sunspecSplitPhaseMeterThingClassId, sunspecSplitPhaseMeterConnectedStateTypeId);
+ m_connectedStateTypeIds.insert(sunspecThreePhaseMeterThingClassId, sunspecThreePhaseMeterConnectedStateTypeId);
+
+ m_modelIdParamTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterThingModelIdParamTypeId);
+ m_modelIdParamTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterThingModelIdParamTypeId);
+ m_modelIdParamTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterThingModelIdParamTypeId);
+ m_modelIdParamTypeIds.insert(sunspecStorageThingClassId, sunspecStorageThingModelIdParamTypeId);
+ m_modelIdParamTypeIds.insert(sunspecSinglePhaseMeterThingClassId, sunspecSinglePhaseMeterThingModelIdParamTypeId);
+ m_modelIdParamTypeIds.insert(sunspecSplitPhaseMeterThingClassId, sunspecSplitPhaseMeterThingModelIdParamTypeId);
+ m_modelIdParamTypeIds.insert(sunspecThreePhaseMeterThingClassId, sunspecThreePhaseMeterThingModelIdParamTypeId);
+
+ m_modbusAddressParamTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterThingModbusAddressParamTypeId);
+ m_modbusAddressParamTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterThingModbusAddressParamTypeId);
+ m_modbusAddressParamTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterThingModbusAddressParamTypeId);
+ m_modbusAddressParamTypeIds.insert(sunspecStorageThingClassId, sunspecStorageThingModbusAddressParamTypeId);
+ m_modbusAddressParamTypeIds.insert(sunspecSinglePhaseMeterThingClassId, sunspecSinglePhaseMeterThingModbusAddressParamTypeId);
+ m_modbusAddressParamTypeIds.insert(sunspecSplitPhaseMeterThingClassId, sunspecSplitPhaseMeterThingModbusAddressParamTypeId);
+ m_modbusAddressParamTypeIds.insert(sunspecThreePhaseMeterThingClassId, sunspecThreePhaseMeterThingModbusAddressParamTypeId);
+
+ m_inverterCurrentPowerStateTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterCurrentPowerStateTypeId);
+ m_inverterCurrentPowerStateTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterCurrentPowerStateTypeId);
+ m_inverterCurrentPowerStateTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterCurrentPowerStateTypeId);
+
+ m_inverterTotalEnergyProducedStateTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterTotalEnergyProducedStateTypeId);
+ m_inverterTotalEnergyProducedStateTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterTotalEnergyProducedStateTypeId);
+ m_inverterTotalEnergyProducedStateTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterTotalEnergyProducedStateTypeId);
+
+ m_inverterOperatingStateTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterOperatingStateStateTypeId);
+ m_inverterOperatingStateTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterOperatingStateStateTypeId);
+ m_inverterOperatingStateTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterOperatingStateStateTypeId);
+
+ m_inverterErrorStateTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterErrorStateTypeId);
+ m_inverterErrorStateTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterErrorStateTypeId);
+ m_inverterErrorStateTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterErrorStateTypeId);
+
+ m_inverterCabinetTemperatureStateTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterCabinetTemperatureStateTypeId);
+ m_inverterCabinetTemperatureStateTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterCabinetTemperatureStateTypeId);
+ m_inverterCabinetTemperatureStateTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterCabinetTemperatureStateTypeId);
+
+ m_inverterAcCurrentStateTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterTotalCurrentStateTypeId);
+ m_inverterAcCurrentStateTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterTotalCurrentStateTypeId);
+ m_inverterAcCurrentStateTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterTotalCurrentStateTypeId);
+
+ m_frequencyStateTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterFrequencyStateTypeId);
+ m_frequencyStateTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterFrequencyStateTypeId);
+ m_frequencyStateTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterFrequencyStateTypeId);
+
+ m_frequencyStateTypeIds.insert(sunspecSinglePhaseMeterThingClassId, sunspecSinglePhaseMeterFrequencyStateTypeId);
+ m_frequencyStateTypeIds.insert(sunspecSplitPhaseMeterThingClassId, sunspecSplitPhaseMeterFrequencyStateTypeId);
+ m_frequencyStateTypeIds.insert(sunspecThreePhaseMeterThingClassId, sunspecThreePhaseMeterFrequencyStateTypeId);
+}
+
+void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+ qCDebug(dcSunSpec()) << "Setup thing" << thing->name();
+ if (thing->thingClassId() == sunspecConnectionThingClassId) {
+
+ QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecConnectionThingIpAddressParamTypeId).toString());
+ int port = info->thing()->paramValue(sunspecConnectionThingPortParamTypeId).toInt();
+ int slaveId = info->thing()->paramValue(sunspecConnectionThingSlaveIdParamTypeId).toInt();
+
+ if (m_sunSpecConnections.contains(thing->id())) {
+ qCDebug(dcSunSpec()) << "Reconfigure SunSpec connection with new address" << address;
+ m_sunSpecConnections.take(thing->id())->deleteLater();
+ }
+ SunSpec *sunSpec;
+ sunSpec = new SunSpec(address, port, slaveId, this);
+ sunSpec->setTimeout(configValue(sunSpecPluginTimeoutParamTypeId).toUInt());
+ sunSpec->setNumberOfRetries(configValue(sunSpecPluginNumberOfRetriesParamTypeId).toUInt());
+ m_sunSpecConnections.insert(thing->id(), sunSpec);
+
+ if (!sunSpec->connectModbus()) {
+ qCWarning(dcSunSpec()) << "Error connecting to SunSpec device";
+ return info->finish(Thing::ThingErrorHardwareNotAvailable);
+ }
+ connect(sunSpec, &SunSpec::connectionStateChanged, info, [sunSpec, info] (bool status) {
+ qCDebug(dcSunSpec()) << "Modbus connection init finished" << status;
+ sunSpec->findBaseRegister();
+ connect(sunSpec, &SunSpec::foundBaseRegister, info, [info] (uint modbusAddress) {
+ qCDebug(dcSunSpec()) << "Found base register" << modbusAddress;
+ info->finish(Thing::ThingErrorNoError);
+ });
+ });
+
+ connect(info, &ThingSetupInfo::aborted, sunSpec, &SunSpec::deleteLater);
+ connect(sunSpec, &SunSpec::destroyed, [this, thing] {
+ m_sunSpecConnections.remove(thing->id());
+ });
+ connect(sunSpec, &SunSpec::foundSunSpecModel, this, &IntegrationPluginSunSpec::onFoundSunSpecModel);
+ connect(sunSpec, &SunSpec::sunspecModelSearchFinished, this, &IntegrationPluginSunSpec::onSunSpecModelSearchFinished);
+ connect(sunSpec, &SunSpec::commonModelReceived, thing, [thing] (const QString &manufacturer, const QString &deviceModel, const QString &serielNumber) {
+ thing->setStateValue(sunspecConnectionConnectedStateTypeId, true);
+ thing->setStateValue(sunspecConnectionManufacturerStateTypeId, manufacturer);
+ thing->setStateValue(sunspecConnectionDeviceModelStateTypeId, deviceModel);
+ thing->setStateValue(sunspecConnectionSerialNumberStateTypeId, serielNumber);
+ });
+
+ } else if (thing->thingClassId() == sunspecThreePhaseInverterThingClassId ||
+ thing->thingClassId() == sunspecSplitPhaseInverterThingClassId ||
+ thing->thingClassId() == sunspecSinglePhaseInverterThingClassId ) {
+
+ Thing *parent = myThings().findById(thing->parentId());
+ if (parent->setupStatus() == Thing::ThingSetupStatusComplete) {
+ setupInverter(info);
+ } else {
+ connect(parent, &Thing::setupStatusChanged, info, [this, info] {
+ setupInverter(info);
+ });
+ }
+ } else if (thing->thingClassId() == sunspecSinglePhaseMeterThingClassId ||
+ thing->thingClassId() == sunspecSplitPhaseMeterThingClassId ||
+ thing->thingClassId() == sunspecThreePhaseMeterThingClassId) {
+
+ Thing *parent = myThings().findById(thing->parentId());
+ if (parent->setupStatus() == Thing::ThingSetupStatusComplete) {
+ setupMeter(info);
+ } else {
+ connect(parent, &Thing::setupStatusChanged, info, [this, info] {
+ setupMeter(info);
+ });
+ }
+
+ } else if (info->thing()->thingClassId() == sunspecStorageThingClassId) {
+
+ Thing *parent = myThings().findById(thing->parentId());
+ if (parent->setupStatus() == Thing::ThingSetupStatusComplete) {
+ setupStorage(info);
+ } else {
+ connect(parent, &Thing::setupStatusChanged, info, [this, info] {
+ setupStorage(info);
+ });
+ }
+ } else {
+ Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(info->thing()->thingClassId().toString()).toUtf8());
+ }
+}
+
+void IntegrationPluginSunSpec::postSetupThing(Thing *thing)
+{
+ qCDebug(dcSunSpec()) << "Post setup thing" << thing->name();
+
+ if (!m_refreshTimer) {
+ qCDebug(dcSunSpec()) << "Starting refresh timer";
+ int refreshTime = configValue(sunSpecPluginUpdateIntervalParamTypeId).toInt();
+ m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(refreshTime);
+ connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSunSpec::onRefreshTimer);
+ }
+
+ if (thing->thingClassId() == sunspecConnectionThingClassId) {
+ SunSpec *connection = m_sunSpecConnections.value(thing->id());
+ if (!connection) {
+ qCDebug(dcSunSpec()) << "SunSpecConnection not found";
+ return;
+ }
+ connection->readCommonModel();
+ connection->findSunSpecModels(QList()); // Discover all models, without filter
+
+ } else if (thing->thingClassId() == sunspecSinglePhaseInverterThingClassId ||
+ thing->thingClassId() == sunspecSplitPhaseInverterThingClassId ||
+ thing->thingClassId() == sunspecThreePhaseInverterThingClassId) {
+
+ SunSpecInverter *sunSpecInverter = m_sunSpecInverters.value(thing);
+ if (!sunSpecInverter) {
+ qCDebug(dcSunSpec()) << "SunSpecInverter not found";
+ return;
+ }
+ sunSpecInverter->getInverterModelDataBlock();
+
+ } else if (thing->thingClassId() == sunspecSinglePhaseMeterThingClassId ||
+ thing->thingClassId() == sunspecSplitPhaseMeterThingClassId ||
+ thing->thingClassId() == sunspecThreePhaseMeterThingClassId) {
+
+ SunSpecMeter *sunSpecMeter = m_sunSpecMeters.value(thing);
+ if (!sunSpecMeter) {
+ qCDebug(dcSunSpec()) << "SunSpecMeter not found";
+ return;
+ }
+ sunSpecMeter->getMeterModelDataBlock();
+
+ } else if (thing->thingClassId() == sunspecStorageThingClassId) {
+ SunSpecStorage *sunSpecStorage = m_sunSpecStorages.value(thing);
+ if (!sunSpecStorage) {
+ qCDebug(dcSunSpec()) << "SunSpecStorage not found";
+ return;
+ }
+ sunSpecStorage->getStorageModelDataBlock();
+
+ } else {
+ Q_ASSERT_X(false, "postSetupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
+ }
+
+}
+
+void IntegrationPluginSunSpec::thingRemoved(Thing *thing)
+{
+ qCDebug(dcSunSpec()) << "Thing removed" << thing->name();
+
+ if (thing->thingClassId() == sunspecConnectionThingClassId) {
+ SunSpec *sunSpecConnection = m_sunSpecConnections.take(thing->id());
+ if (sunSpecConnection)
+ sunSpecConnection->deleteLater();
+
+ } else if (thing->thingClassId() == sunspecSinglePhaseInverterThingClassId ||
+ thing->thingClassId() == sunspecSplitPhaseInverterThingClassId ||
+ thing->thingClassId() == sunspecThreePhaseInverterThingClassId) {
+ SunSpecInverter *sunSpecInverter = m_sunSpecInverters.take(thing);
+ if (sunSpecInverter)
+ sunSpecInverter->deleteLater();
+
+ } else if (thing->thingClassId() == sunspecSinglePhaseMeterThingClassId ||
+ thing->thingClassId() == sunspecSplitPhaseMeterThingClassId ||
+ thing->thingClassId() == sunspecThreePhaseMeterThingClassId) {
+ SunSpecMeter *sunSpecMeter = m_sunSpecMeters.take(thing);
+ if (sunSpecMeter)
+ sunSpecMeter->deleteLater();
+
+ } else if (thing->thingClassId() == sunspecStorageThingClassId) {
+ SunSpecStorage *sunSpecStorage = m_sunSpecStorages.take(thing);
+ if (sunSpecStorage)
+ sunSpecStorage->deleteLater();
+
+ } else {
+ Q_ASSERT_X(false, "thingRemoved", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
+ }
+
+ if (myThings().isEmpty()) {
+ qCDebug(dcSunSpec()) << "Stopping refresh timer";
+ hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer);
+ m_refreshTimer = nullptr;
+ }
+}
+
+void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info)
+{
+ Thing *thing = info->thing();
+ Action action = info->action();
+
+ if (thing->thingClassId() == sunspecStorageThingClassId) {
+ SunSpecStorage *sunSpecStorage = m_sunSpecStorages.value(thing);
+ if (!sunSpecStorage) {
+ qWarning(dcSunSpec()) << "Could not find sunspec instance for thing";
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ return;
+ }
+
+ if (action.actionTypeId() == sunspecStorageGridChargingActionTypeId) {
+ QUuid requestId = sunSpecStorage->setGridCharging(action.param(sunspecStorageGridChargingActionGridChargingParamTypeId).value().toBool());
+ if (requestId.isNull()) {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ m_asyncActions.insert(requestId, info);
+ connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
+ }
+ } else if (action.actionTypeId() == sunspecStorageEnableChargingActionTypeId) {
+ bool charging = action.param(sunspecStorageEnableChargingActionEnableChargingParamTypeId).value().toBool();
+ bool discharging = thing->stateValue(sunspecStorageEnableDischargingStateTypeId).toBool();
+ QUuid requestId = sunSpecStorage->setStorageControlMode(charging, discharging);
+ if (requestId.isNull()) {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ m_asyncActions.insert(requestId, info);
+ connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
+ }
+ } else if (action.actionTypeId() == sunspecStorageChargingRateActionTypeId) {
+ QUuid requestId = sunSpecStorage->setChargingRate(action.param(sunspecStorageChargingRateActionChargingRateParamTypeId).value().toInt());
+ if (requestId.isNull()) {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ m_asyncActions.insert(requestId, info);
+ connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
+ }
+ } else if (action.actionTypeId() == sunspecStorageEnableDischargingActionTypeId) {
+ bool discharging = action.param(sunspecStorageEnableDischargingActionEnableDischargingParamTypeId).value().toBool();
+ bool charging = thing->stateValue(sunspecStorageEnableChargingStateTypeId).toBool();
+ QUuid requestId = sunSpecStorage->setStorageControlMode(charging, discharging);
+ if (requestId.isNull()) {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ m_asyncActions.insert(requestId, info);
+ connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
+ }
+ } else if (action.actionTypeId() == sunspecStorageDischargingRateActionTypeId) {
+ QUuid requestId = sunSpecStorage->setDischargingRate(action.param(sunspecStorageDischargingRateActionDischargingRateParamTypeId).value().toInt());
+ if (requestId.isNull()) {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ } else {
+ m_asyncActions.insert(requestId, info);
+ connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);});
+ }
+ } 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(info->thing()->thingClassId().toString()).toUtf8());
+ }
+}
+
+bool IntegrationPluginSunSpec::checkIfThingExists(uint modelId, uint modbusAddress)
+{
+ Q_FOREACH(Thing *thing, myThings()) {
+ if (thing->paramValue(m_modelIdParamTypeIds.value(thing->thingClassId())).toUInt() == modelId &&
+ thing->paramValue(m_modbusAddressParamTypeIds.value(thing->thingClassId())).toUInt() == modbusAddress) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void IntegrationPluginSunSpec::setupInverter(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+ uint modelId = thing->paramValue(m_modelIdParamTypeIds.value(thing->thingClassId())).toInt();
+ int modbusAddress = thing->paramValue(m_modbusAddressParamTypeIds.value(thing->thingClassId())).toInt();
+ SunSpec *connection = m_sunSpecConnections.value(thing->parentId());
+ if (!connection) {
+ qCWarning(dcSunSpec()) << "Could not find SunSpec connection";
+ return info->finish(Thing::ThingErrorHardwareNotAvailable);
+ }
+ SunSpecInverter *sunSpecInverter = new SunSpecInverter(connection, SunSpec::ModelId(modelId), modbusAddress);
+ sunSpecInverter->init();
+ connect(sunSpecInverter, &SunSpecInverter::initFinished, info, [this, sunSpecInverter, info] (bool success){
+ qCDebug(dcSunSpec()) << "Modbus Inverter init finished, success:" << success;
+ if (success) {
+ m_sunSpecInverters.insert(info->thing(), sunSpecInverter);
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ }
+ });
+
+ connect(info, &ThingSetupInfo::aborted, sunSpecInverter, &SunSpecInverter::deleteLater);
+ connect(sunSpecInverter, &SunSpecInverter::destroyed, thing, [thing, this] {m_sunSpecInverters.remove(thing);});
+ connect(sunSpecInverter, &SunSpecInverter::inverterDataReceived, this, &IntegrationPluginSunSpec::onInverterDataReceived);
+}
+
+void IntegrationPluginSunSpec::setupMeter(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+
+ uint modelId = thing->paramValue(m_modelIdParamTypeIds.value(thing->thingClassId())).toInt();
+ int modbusAddress = thing->paramValue(m_modbusAddressParamTypeIds.value(thing->thingClassId())).toInt();
+ SunSpec *connection = m_sunSpecConnections.value(thing->parentId());
+ if (!connection) {
+ qCWarning(dcSunSpec()) << "Could not find SunSpec connection";
+ return info->finish(Thing::ThingErrorHardwareNotAvailable);
+ }
+ SunSpecMeter *sunSpecMeter = new SunSpecMeter(connection, SunSpec::ModelId(modelId), modbusAddress);
+ sunSpecMeter->init();
+ connect(sunSpecMeter, &SunSpecMeter::initFinished, info, [this, sunSpecMeter, info] (bool success){
+ qCDebug(dcSunSpec()) << "Modbus meter init finished, success:" << success;
+ if (success) {
+ m_sunSpecMeters.insert(info->thing(), sunSpecMeter);
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ }
+ });
+
+ connect(info, &ThingSetupInfo::aborted, sunSpecMeter, &SunSpecMeter::deleteLater);
+ connect(sunSpecMeter, &SunSpecMeter::destroyed, thing, [thing, this] {m_sunSpecMeters.remove(thing);});
+ connect(sunSpecMeter, &SunSpecMeter::meterDataReceived, this, &IntegrationPluginSunSpec::onMeterDataReceived);
+}
+
+void IntegrationPluginSunSpec::setupStorage(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+ uint modelId = thing->paramValue(m_modelIdParamTypeIds.value(thing->thingClassId())).toInt();
+ int modbusAddress = thing->paramValue(m_modbusAddressParamTypeIds.value(thing->thingClassId())).toInt();
+ SunSpec *connection = m_sunSpecConnections.value(thing->parentId());
+ if (!connection) {
+ qCWarning(dcSunSpec()) << "Could not find SunSpec connection";
+ return info->finish(Thing::ThingErrorHardwareNotAvailable);
+ }
+ SunSpecStorage *sunSpecStorage = new SunSpecStorage(connection, SunSpec::ModelId(modelId), modbusAddress);
+ sunSpecStorage->init();
+ connect(sunSpecStorage, &SunSpecStorage::initFinished, info, [this, sunSpecStorage, info] (bool success){
+ qCDebug(dcSunSpec()) << "Modbus storage init finished, success:" << success;
+ if (success) {
+ m_sunSpecStorages.insert(info->thing(), sunSpecStorage);
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ }
+ });
+
+ connect(info, &ThingSetupInfo::aborted, sunSpecStorage, &SunSpecStorage::deleteLater);
+ connect(sunSpecStorage, &SunSpecStorage::destroyed, thing, [thing, this] {m_sunSpecStorages.remove(thing);});
+ connect(sunSpecStorage, &SunSpecStorage::storageDataReceived, this, &IntegrationPluginSunSpec::onStorageDataReceived);
+}
+
+void IntegrationPluginSunSpec::onRefreshTimer()
+{
+ foreach (SunSpec *connection, m_sunSpecConnections) {
+ connection->readCommonModel(); //check connection
+ }
+ foreach (SunSpecInverter *inverter, m_sunSpecInverters) {
+ inverter->getInverterModelDataBlock();
+ }
+ foreach (SunSpecMeter *meter, m_sunSpecMeters) {
+ meter->getMeterModelDataBlock();
+ }
+ foreach (SunSpecStorage *storage, m_sunSpecStorages) {
+ storage->getStorageModelDataBlock();
+ }
+}
+
+void IntegrationPluginSunSpec::onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value)
+{
+ // Check refresh schedule
+ if (paramTypeId == sunSpecPluginUpdateIntervalParamTypeId) {
+ qCDebug(dcSunSpec()) << "Update interval has changed" << value.toInt();
+ if (m_refreshTimer) {
+ int refreshTime = value.toInt();
+ m_refreshTimer->stop();
+ m_refreshTimer->startTimer(refreshTime);
+ }
+ } else if (paramTypeId == sunSpecPluginNumberOfRetriesParamTypeId) {
+ qCDebug(dcSunSpec()) << "Updating number of retries" << value.toUInt();
+ Q_FOREACH(SunSpec *connection, m_sunSpecConnections) {
+ connection->setNumberOfRetries(value.toUInt());
+ }
+ } else if (paramTypeId == sunSpecPluginTimeoutParamTypeId) {
+ qCDebug(dcSunSpec()) << "Updating timeout" << value.toUInt() << "[ms]";
+ Q_FOREACH(SunSpec *connection, m_sunSpecConnections) {
+ connection->setTimeout(value.toUInt());
+ }
+ } else {
+ qCWarning(dcSunSpec()) << "Unknown plugin configuration" << paramTypeId << "Value" << value;
+ }
+}
+
+void IntegrationPluginSunSpec::onConnectionStateChanged(bool status)
+{
+ SunSpec *connection = static_cast(sender());
+ Thing *thing = myThings().findById(m_sunSpecConnections.key(connection));
+ if (!thing)
+ return;
+ qCDebug(dcSunSpec()) << "Connection state changed" << status << thing->name();
+ if (thing->thingClassId() == sunspecConnectionConnectedStateTypeId) {
+ thing->setStateValue(sunspecConnectionConnectedStateTypeId, status);
+ }
+
+ Q_FOREACH(Thing *child, myThings().filterByParentId(thing->id())) {
+ child->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), status);
+ }
+}
+
+void IntegrationPluginSunSpec::onFoundSunSpecModel(SunSpec::ModelId modelId, int modbusStartRegister)
+{
+ SunSpec *connection = static_cast(sender());
+ Thing *thing = myThings().findById(m_sunSpecConnections.key(connection));
+ if (!thing) {
+ qCWarning(dcSunSpec()) << "Thing not found for SunSpec connection" << connection->deviceModel() << connection->serialNumber();
+ return;
+ }
+
+ qCDebug(dcSunSpec()) << "On model received" << modelId << "start register" << modbusStartRegister << "Thing:" << thing->name();
+ if (checkIfThingExists(modelId, modbusStartRegister)) {
+ return;
+ }
+ QString model = thing->stateValue(sunspecConnectionDeviceModelStateTypeId).toString();
+ switch (modelId) {
+ case SunSpec::ModelId::ModelIdInverterSinglePhase:
+ case SunSpec::ModelId::ModelIdInverterSinglePhaseFloat: {
+ ThingDescriptor descriptor(sunspecSinglePhaseInverterThingClassId, model+tr(" single phase inverter"), "", thing->id());
+ ParamList params;
+ params.append(Param(sunspecSinglePhaseInverterThingModelIdParamTypeId, modelId));
+ params.append(Param(sunspecSinglePhaseInverterThingModbusAddressParamTypeId, modbusStartRegister));
+ descriptor.setParams(params);
+ emit autoThingsAppeared({descriptor});
+ } break;
+ case SunSpec::ModelId::ModelIdInverterSplitPhase:
+ case SunSpec::ModelId::ModelIdInverterSplitPhaseFloat: {
+ ThingDescriptor descriptor(sunspecSplitPhaseInverterThingClassId, model+tr(" split phase inverter"), "", thing->id());
+ ParamList params;
+ params.append(Param(sunspecSplitPhaseInverterThingModelIdParamTypeId, modelId));
+ params.append(Param(sunspecSplitPhaseInverterThingModbusAddressParamTypeId, modbusStartRegister));
+ descriptor.setParams(params);
+ emit autoThingsAppeared({descriptor});
+ } break;
+ case SunSpec::ModelId::ModelIdInverterThreePhase:
+ case SunSpec::ModelId::ModelIdInverterThreePhaseFloat: {
+ ThingDescriptor descriptor(sunspecThreePhaseInverterThingClassId, model+tr(" three phase inverter"), "", thing->id());
+ ParamList params;
+ params.append(Param(sunspecThreePhaseInverterThingModelIdParamTypeId, modelId));
+ params.append(Param(sunspecThreePhaseInverterThingModbusAddressParamTypeId, modbusStartRegister));
+ descriptor.setParams(params);
+ emit autoThingsAppeared({descriptor});
+ } break;
+
+ case SunSpec::ModelIdSinglePhaseMeter:
+ case SunSpec::ModelIdSinglePhaseMeterFloat: {
+ ThingDescriptor descriptor(sunspecSinglePhaseMeterThingClassId, model+tr(" meter"), "", thing->id());
+ ParamList params;
+ params.append(Param(sunspecSinglePhaseMeterThingModelIdParamTypeId, modelId));
+ params.append(Param(sunspecSinglePhaseMeterThingModbusAddressParamTypeId, modbusStartRegister));
+ descriptor.setParams(params);
+ emit autoThingsAppeared({descriptor});
+ } break;
+ case SunSpec::ModelIdSplitSinglePhaseMeter:
+ case SunSpec::ModelIdSplitSinglePhaseMeterFloat: {
+ ThingDescriptor descriptor(sunspecSplitPhaseMeterThingClassId, model+tr(" meter"), "", thing->id());
+ ParamList params;
+ params.append(Param(sunspecSplitPhaseMeterThingModelIdParamTypeId, modelId));
+ params.append(Param(sunspecSplitPhaseMeterThingModbusAddressParamTypeId, modbusStartRegister));
+ descriptor.setParams(params);
+ emit autoThingsAppeared({descriptor});
+ } break;
+ case SunSpec::ModelIdWyeConnectThreePhaseMeter:
+ case SunSpec::ModelIdDeltaConnectThreePhaseMeter:
+ case SunSpec::ModelIdWyeConnectThreePhaseMeterFloat:
+ case SunSpec::ModelIdDeltaConnectThreePhaseMeterFloat: {
+ ThingDescriptor descriptor(sunspecThreePhaseMeterThingClassId, model+" meter", "", thing->id());
+ ParamList params;
+ params.append(Param(sunspecThreePhaseMeterThingModelIdParamTypeId, modelId));
+ params.append(Param(sunspecThreePhaseMeterThingModbusAddressParamTypeId, modbusStartRegister));
+ descriptor.setParams(params);
+ emit autoThingsAppeared({descriptor});
+ } break;
+ case SunSpec::ModelIdStorage: {
+ ThingDescriptor descriptor(sunspecStorageThingClassId, model+" storage", "", thing->id());
+ ParamList params;
+ params.append(Param(sunspecStorageThingModelIdParamTypeId, modelId));
+ params.append(Param(sunspecStorageThingModbusAddressParamTypeId, modbusStartRegister));
+ descriptor.setParams(params);
+ emit autoThingsAppeared({descriptor});
+ } break;
+ default:
+ qCDebug(dcSunSpec()) << "Model Id not handled";
+ }
+}
+
+void IntegrationPluginSunSpec::onSunSpecModelSearchFinished(const QHash &modelIds)
+{
+ SunSpec *connection = static_cast(sender());
+ Thing *thing = myThings().findById(m_sunSpecConnections.key(connection));
+ if (!thing) {
+ qCWarning(dcSunSpec()) << "Thing not found for SunSpec connection" << connection->deviceModel() << connection->serialNumber();
+ return;
+ }
+ qCDebug(dcSunSpec()) << "On sunspec model search finished, models:" << modelIds.count();
+}
+
+void IntegrationPluginSunSpec::onWriteRequestExecuted(QUuid requestId, bool success)
+{
+ qCDebug(dcSunSpec()) << "Write request executed" << requestId << success;
+ if (m_asyncActions.contains(requestId)) {
+ ThingActionInfo *info = m_asyncActions.take(requestId);
+ if (success) {
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ }
+ }
+}
+
+void IntegrationPluginSunSpec::onWriteRequestError(QUuid requestId, const QString &error)
+{
+ qCDebug(dcSunSpec()) << "Write request error" << requestId << error;
+}
+
+void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::InverterData &inverterData)
+{
+ SunSpecInverter *inverter = static_cast(sender());
+ Thing *thing = m_sunSpecInverters.key(inverter);
+
+ if(!thing) {
+ return;
+ }
+
+ qCDebug(dcSunSpec()) << "Inverter data received";
+ qCDebug(dcSunSpec()) << " - Total AC Current" << inverterData.acCurrent << "[A]";
+ qCDebug(dcSunSpec()) << " - Phase A Current" << inverterData.phaseACurrent << "[A]";
+ qCDebug(dcSunSpec()) << " - Phase B Current" << inverterData.phaseBCurrent << "[A]";
+ qCDebug(dcSunSpec()) << " - Phase C Current" << inverterData.phaseCCurrent << "[A]";
+ qCDebug(dcSunSpec()) << " - Phase voltage AB" << inverterData.phaseVoltageAB << "[V]";
+ qCDebug(dcSunSpec()) << " - Phase voltage BC" << inverterData.phaseVoltageBC << "[V]";
+ qCDebug(dcSunSpec()) << " - Phase voltage CA" << inverterData.phaseVoltageCA << "[V]";
+ qCDebug(dcSunSpec()) << " - Phase voltage AN" << inverterData.phaseVoltageAN << "[V]";
+ qCDebug(dcSunSpec()) << " - Phase voltage BN" << inverterData.phaseVoltageBN << "[V]";
+ qCDebug(dcSunSpec()) << " - Phase voltage CN" << inverterData.phaseVoltageCN << "[V]";
+ qCDebug(dcSunSpec()) << " - AC Power" << inverterData.acPower << "[W]";
+ qCDebug(dcSunSpec()) << " - Line frequency" << inverterData.lineFrequency << "[Hz]";
+ qCDebug(dcSunSpec()) << " - AC energy" << inverterData.acEnergy << "[Wh]";
+ qCDebug(dcSunSpec()) << " - Cabinet temperature" << inverterData.cabinetTemperature << "[°C]";
+ qCDebug(dcSunSpec()) << " - Operating state" << inverterData.operatingState;
+
+ thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true);
+ thing->setStateValue(m_inverterCurrentPowerStateTypeIds.value(thing->thingClassId()), inverterData.acPower);
+ thing->setStateValue(m_inverterTotalEnergyProducedStateTypeIds.value(thing->thingClassId()), inverterData.acEnergy/1000.00);
+ thing->setStateValue(m_frequencyStateTypeIds.value(thing->thingClassId()), inverterData.lineFrequency);
+
+ thing->setStateValue(m_inverterAcCurrentStateTypeIds.value(thing->thingClassId()), inverterData.acCurrent);
+ thing->setStateValue(m_inverterCabinetTemperatureStateTypeIds.value(thing->thingClassId()), inverterData.cabinetTemperature);
+
+ if (thing->thingClassId() == sunspecSinglePhaseInverterThingClassId) {
+
+ thing->setStateValue(sunspecSinglePhaseInverterPhaseVoltageStateTypeId, inverterData.phaseVoltageAN);
+
+ } else if (thing->thingClassId() == sunspecSplitPhaseInverterThingClassId) {
+
+ thing->setStateValue(sunspecSplitPhaseInverterPhaseANVoltageStateTypeId, inverterData.phaseVoltageAN);
+ thing->setStateValue(sunspecSplitPhaseInverterPhaseBNVoltageStateTypeId, inverterData.phaseVoltageBN);
+
+ thing->setStateValue(sunspecSplitPhaseInverterPhaseACurrentStateTypeId, inverterData.phaseACurrent);
+ thing->setStateValue(sunspecSplitPhaseInverterPhaseBCurrentStateTypeId, inverterData.phaseBCurrent);
+
+ } else if (thing->thingClassId() == sunspecThreePhaseInverterThingClassId) {
+
+ thing->setStateValue(sunspecThreePhaseInverterPhaseANVoltageStateTypeId, inverterData.phaseVoltageAN);
+ thing->setStateValue(sunspecThreePhaseInverterPhaseBNVoltageStateTypeId, inverterData.phaseVoltageBN);
+ thing->setStateValue(sunspecThreePhaseInverterPhaseCNVoltageStateTypeId, inverterData.phaseVoltageCN);
+
+ thing->setStateValue(sunspecThreePhaseInverterPhaseACurrentStateTypeId, inverterData.phaseACurrent);
+ thing->setStateValue(sunspecThreePhaseInverterPhaseBCurrentStateTypeId, inverterData.phaseBCurrent);
+ thing->setStateValue(sunspecThreePhaseInverterPhaseCCurrentStateTypeId, inverterData.phaseCCurrent);
+ }
+
+ switch(inverterData.operatingState) {
+ case SunSpec::SunSpecOperatingState::Off:
+ thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "Off");
+ break;
+ case SunSpec::SunSpecOperatingState::MPPT:
+ thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "MPPT");
+ break;
+ case SunSpec::SunSpecOperatingState::Fault:
+ thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "Fault");
+ break;
+ case SunSpec::SunSpecOperatingState::Standby:
+ thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "Standby");
+ break;
+ case SunSpec::SunSpecOperatingState::Sleeping:
+ thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "Sleeping");
+ break;
+ case SunSpec::SunSpecOperatingState::Starting:
+ thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "Starting");
+ break;
+ case SunSpec::SunSpecOperatingState::Throttled:
+ thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "Throttled");
+ break;
+ case SunSpec::SunSpecOperatingState::ShuttingDown:
+ thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "Shutting down");
+ break;
+ }
+
+ //FIXME: Event1 may have multiple states at once. Only one is stated in nymea
+ if (inverterData.event1.overTemperature) {
+ thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Over temperature");
+ } else if (inverterData.event1.underTemperature) {
+ thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Under temperature");
+ } else if (inverterData.event1.groundFault) {
+ thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Ground fault");
+ } else if (inverterData.event1.memoryLoss) {
+ thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Memory loss");
+ } else if (inverterData.event1.acOverVolt) {
+ thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "AC voltage above limit");
+ } else if (inverterData.event1.cabinetOpen) {
+ thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Cabinet open");
+ } else if (inverterData.event1.acDisconnect) {
+ thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "AC disconnect open");
+ } else if (inverterData.event1.acUnderVolt) {
+ thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "AC voltage under limit");
+ } else if (inverterData.event1.dcDicconnect) {
+ thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "DC disconnect open");
+ } else if (inverterData.event1.dcOverVoltage) {
+ thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "DC over voltage");
+ } else if (inverterData.event1.overFrequency) {
+ thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Frequency above limit");
+ } else if (inverterData.event1.gridDisconnect) {
+ thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Grid disconnect");
+ } else if (inverterData.event1.hwTestFailure) {
+ thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Hardware test failure");
+ } else if (inverterData.event1.manualShutdown) {
+ thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Manual shutdown");
+ } else if (inverterData.event1.underFrequency) {
+ thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Frequency under limit");
+ } else if (inverterData.event1.blownStringFuse) {
+ thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Blown string fuse on input");
+ } else {
+ thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "None");
+ }
+}
+
+void IntegrationPluginSunSpec::onStorageDataReceived(const SunSpecStorage::StorageData &mandatory, const SunSpecStorage::StorageDataOptional &optional)
+{
+ SunSpecStorage *storage = static_cast(sender());
+ Thing *thing = m_sunSpecStorages.key(storage);
+
+ if(!thing) {
+ return;
+ }
+
+ qCDebug(dcSunSpec()) << "Storage data received";
+ qCDebug(dcSunSpec()) << " - Setpoint for maximum charge" << mandatory.WChaMax << "[W]";
+ qCDebug(dcSunSpec()) << " - Setpoint for maximum charging rate." << mandatory.WChaGra << "[%]";
+ qCDebug(dcSunSpec()) << " - Setpoint for maximum discharging rate." << mandatory.WDisChaGra << "[%]";
+ qCDebug(dcSunSpec()) << " - Charging enabled" << mandatory.StorCtl_Mod_ChargingEnabled;
+ qCDebug(dcSunSpec()) << " - Discharging enabled" << mandatory.StorCtl_Mod_DischargingEnabled;
+ qCDebug(dcSunSpec()) << " - Storage status" << optional.ChaSt;
+ qCDebug(dcSunSpec()) << " - Currently available energy" << optional.ChaState << "[%]";
+ qCDebug(dcSunSpec()) << " - Grid charging enabled" << optional.ChaGriSet;
+
+ thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true);
+ thing->setStateValue(sunspecStorageChargingRateStateTypeId, mandatory.WChaGra);
+ thing->setStateValue(sunspecStorageDischargingRateStateTypeId, mandatory.WDisChaGra);
+ thing->setStateValue(sunspecStorageEnableChargingStateTypeId, mandatory.StorCtl_Mod_ChargingEnabled);
+ thing->setStateValue(sunspecStorageEnableDischargingStateTypeId, mandatory.StorCtl_Mod_DischargingEnabled);
+ thing->setStateValue(sunspecStorageGridChargingStateTypeId, optional.ChaGriSet);
+
+ bool charging = false;
+ switch (optional.ChaSt) {
+ case SunSpecStorage::ChargingStatusOff:
+ thing->setStateValue(sunspecStorageStorageStatusStateTypeId, "Off");
+ break;
+ case SunSpecStorage::ChargingStatusFull:
+ thing->setStateValue(sunspecStorageStorageStatusStateTypeId, "Full");
+ break;
+ case SunSpecStorage::ChargingStatusEmpty:
+ thing->setStateValue(sunspecStorageStorageStatusStateTypeId, "Empty");
+ break;
+ case SunSpecStorage::ChargingStatusHolding:
+ thing->setStateValue(sunspecStorageStorageStatusStateTypeId, "Holding");
+ break;
+ case SunSpecStorage::ChargingStatusTesting:
+ thing->setStateValue(sunspecStorageStorageStatusStateTypeId, "Testing");
+ break;
+ case SunSpecStorage::ChargingStatusCharging:
+ thing->setStateValue(sunspecStorageStorageStatusStateTypeId, "Charging");
+ break;
+ case SunSpecStorage::ChargingStatusDischarging:
+ thing->setStateValue(sunspecStorageStorageStatusStateTypeId, "Discharging");
+ break;
+ };
+ double batteryLevel = optional.ChaState;
+ thing->setStateValue(sunspecStorageBatteryLevelStateTypeId, batteryLevel);
+ thing->setStateValue(sunspecStorageBatteryCriticalStateTypeId, (batteryLevel < 5 && !charging));
+}
+
+void IntegrationPluginSunSpec::onMeterDataReceived(const SunSpecMeter::MeterData &meterData)
+{
+ SunSpecMeter *meter = static_cast(sender());
+ Thing *thing = m_sunSpecMeters.key(meter);
+
+ if (!thing) {
+ return;
+ }
+
+ qCDebug(dcSunSpec()) << "Meter data received";
+ qCDebug(dcSunSpec()) << " - Total AC Current" << meterData.totalAcCurrent << "[A]";
+ qCDebug(dcSunSpec()) << " - Phase A current" << meterData.phaseACurrent << "[A]";
+ qCDebug(dcSunSpec()) << " - Phase B current" << meterData.phaseBCurrent << "[A]";
+ qCDebug(dcSunSpec()) << " - Phase C current" << meterData.phaseCCurrent << "[A]";
+ qCDebug(dcSunSpec()) << " - Voltage LN" << meterData.voltageLN << "[V]";
+ qCDebug(dcSunSpec()) << " - Phase voltage AN" << meterData.phaseVoltageAN << "[V]";
+ qCDebug(dcSunSpec()) << " - Phase voltage BN" << meterData.phaseVoltageBN << "[V]";
+ qCDebug(dcSunSpec()) << " - Phase voltage CN" << meterData.phaseVoltageCN<< "[V]";
+ qCDebug(dcSunSpec()) << " - Voltage LL" << meterData.voltageLL << "[V]";
+ qCDebug(dcSunSpec()) << " - Phase voltage AB" << meterData.phaseVoltageAB << "[V]";
+ qCDebug(dcSunSpec()) << " - Phase voltage BC" << meterData.phaseVoltageBC << "[V]";
+ qCDebug(dcSunSpec()) << " - Phase voltage CA" << meterData.phaseVoltageCA << "[V]";
+ qCDebug(dcSunSpec()) << " - Frequency" << meterData.frequency << "[Hz]";
+ qCDebug(dcSunSpec()) << " - Total real power" << meterData.totalRealPower << "[W]";
+ qCDebug(dcSunSpec()) << " - Total real energy exported" << meterData.totalRealEnergyExported<< "[kWH]";
+ qCDebug(dcSunSpec()) << " - Total real energy imported" << meterData.totalRealEnergyImported<< "[kWH]";
+
+ thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true);
+ thing->setStateValue(m_frequencyStateTypeIds.value(thing->thingClassId()), meterData.frequency);
+
+ if (thing->thingClassId() == sunspecSinglePhaseMeterThingClassId) {
+ thing->setStateValue(sunspecSinglePhaseMeterPhaseACurrentStateTypeId, meterData.phaseACurrent);
+ thing->setStateValue(sunspecSinglePhaseMeterCurrentPowerEventTypeId, meterData.totalRealPower);
+ thing->setStateValue(sunspecSinglePhaseMeterLnACVoltageStateTypeId, meterData.voltageLN);
+ thing->setStateValue(sunspecSinglePhaseMeterTotalEnergyProducedStateTypeId, meterData.totalRealEnergyExported);
+ thing->setStateValue(sunspecSinglePhaseMeterTotalEnergyConsumedStateTypeId, meterData.totalRealEnergyImported);
+
+ } else if (thing->thingClassId() == sunspecSplitPhaseMeterThingClassId) {
+ thing->setStateValue(sunspecSplitPhaseMeterPhaseACurrentStateTypeId, meterData.phaseACurrent);
+ thing->setStateValue(sunspecSplitPhaseMeterPhaseBCurrentStateTypeId, meterData.phaseBCurrent);
+ thing->setStateValue(sunspecSplitPhaseMeterCurrentPowerEventTypeId, meterData.totalRealPower);
+ thing->setStateValue(sunspecSplitPhaseMeterLnACVoltageStateTypeId, meterData.voltageLN);
+ thing->setStateValue(sunspecSplitPhaseMeterPhaseANVoltageStateTypeId, meterData.phaseVoltageAN);
+ thing->setStateValue(sunspecSplitPhaseMeterPhaseBNVoltageStateTypeId, meterData.phaseVoltageBN);
+ thing->setStateValue(sunspecSplitPhaseMeterTotalEnergyProducedStateTypeId, meterData.totalRealEnergyExported);
+ thing->setStateValue(sunspecSplitPhaseMeterTotalEnergyConsumedStateTypeId, meterData.totalRealEnergyImported);
+
+ } else if (thing->thingClassId() == sunspecThreePhaseMeterThingClassId) {
+ thing->setStateValue(sunspecThreePhaseMeterPhaseACurrentStateTypeId, meterData.phaseACurrent);
+ thing->setStateValue(sunspecThreePhaseMeterPhaseBCurrentStateTypeId, meterData.phaseBCurrent);
+ thing->setStateValue(sunspecThreePhaseMeterPhaseCCurrentStateTypeId, meterData.phaseCCurrent);
+ thing->setStateValue(sunspecThreePhaseMeterLnACVoltageStateTypeId, meterData.voltageLN);
+ thing->setStateValue(sunspecThreePhaseMeterPhaseANVoltageStateTypeId, meterData.phaseVoltageAN);
+ thing->setStateValue(sunspecThreePhaseMeterPhaseBNVoltageStateTypeId, meterData.phaseVoltageBN);
+ thing->setStateValue(sunspecThreePhaseMeterPhaseCNVoltageStateTypeId, meterData.phaseVoltageCN);
+ thing->setStateValue(sunspecThreePhaseMeterCurrentPowerEventTypeId, meterData.totalRealPower);
+ thing->setStateValue(sunspecThreePhaseMeterTotalEnergyProducedStateTypeId, meterData.totalRealEnergyExported);
+ thing->setStateValue(sunspecThreePhaseMeterTotalEnergyConsumedStateTypeId, meterData.totalRealEnergyImported);
+ }
+}
diff --git a/sunspec/integrationpluginsunspec.h b/sunspec/integrationpluginsunspec.h
new file mode 100644
index 0000000..bdb8252
--- /dev/null
+++ b/sunspec/integrationpluginsunspec.h
@@ -0,0 +1,106 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 INTEGRATIONPLUGINSUNSPEC_H
+#define INTEGRATIONPLUGINSUNSPEC_H
+
+#include "integrations/integrationplugin.h"
+#include "plugintimer.h"
+
+#include "../modbus/modbustcpmaster.h"
+
+#include "sunspecinverter.h"
+#include "sunspecstorage.h"
+#include "sunspecmeter.h"
+
+#include
+
+class IntegrationPluginSunSpec: public IntegrationPlugin
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginsunspec.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ explicit IntegrationPluginSunSpec();
+ void init() override;
+ void setupThing(ThingSetupInfo *info) override;
+ void postSetupThing(Thing *thing) override;
+ void thingRemoved(Thing *thing) override;
+ void executeAction(ThingActionInfo *info) override;
+
+private:
+ QHash m_modelIdParamTypeIds;
+ QHash m_modbusAddressParamTypeIds;
+
+ QHash m_connectedStateTypeIds;
+ QHash m_frequencyStateTypeIds;
+
+ QHash m_inverterCurrentPowerStateTypeIds;
+ QHash m_inverterTotalEnergyProducedStateTypeIds;
+ QHash m_inverterOperatingStateTypeIds;
+ QHash m_inverterErrorStateTypeIds;
+ QHash m_inverterCabinetTemperatureStateTypeIds;
+ QHash m_inverterAcCurrentStateTypeIds;
+
+ PluginTimer *m_refreshTimer = nullptr;
+ QHash m_asyncActions;
+ QHash m_sunSpecConnections;
+
+ QHash m_sunSpecInverters;
+ QHash m_sunSpecStorages;
+ QHash m_sunSpecMeters;
+
+ bool checkIfThingExists(uint modelId, uint modbusAddress);
+
+ void setupInverter(ThingSetupInfo *info);
+ void setupMeter(ThingSetupInfo *info);
+ void setupStorage(ThingSetupInfo *info);
+
+private slots:
+ void onRefreshTimer();
+
+ void onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value);
+
+ void onConnectionStateChanged(bool status);
+
+ void onFoundSunSpecModel(SunSpec::ModelId modelId, int modbusStartRegister);
+ void onSunSpecModelSearchFinished(const QHash &modelIds);
+
+ void onWriteRequestExecuted(QUuid requestId, bool success);
+ void onWriteRequestError(QUuid requestId, const QString &error);
+
+ void onInverterDataReceived(const SunSpecInverter::InverterData &inverterData);
+ void onStorageDataReceived(const SunSpecStorage::StorageData &mandatory, const SunSpecStorage::StorageDataOptional &optional);
+ void onMeterDataReceived(const SunSpecMeter::MeterData &meterData);
+};
+#endif // INTEGRATIONPLUGINSUNSPEC_H
+
diff --git a/sunspec/integrationpluginsunspec.json b/sunspec/integrationpluginsunspec.json
new file mode 100644
index 0000000..df83b8b
--- /dev/null
+++ b/sunspec/integrationpluginsunspec.json
@@ -0,0 +1,1077 @@
+{
+ "name": "SunSpec",
+ "displayName": "sunspec",
+ "id": "cb4bdec6-cf2c-4a0f-9709-42d951ca2d8b",
+ "paramTypes":[
+ {
+ "id": "52da5222-9a94-47a2-9adc-004541d2f5ed",
+ "name": "updateInterval",
+ "displayName": "Update interval",
+ "type": "int",
+ "unit": "Seconds",
+ "defaultValue": 15
+ },
+ {
+ "id": "1a8895a0-c746-48af-9307-3a4636f24cc2",
+ "name": "timeout",
+ "displayName": "Timout",
+ "type": "uint",
+ "unit": "MilliSeconds",
+ "defaultValue": 500
+ },
+ {
+ "id": "9a4bfe01-315f-4ee7-98a9-f16b08ba12ad",
+ "name": "numberOfRetries",
+ "displayName": "Number of retries",
+ "type": "uint",
+ "defaultValue": 3,
+ "minValue": 1,
+ "maxValue": 10
+ }
+ ],
+ "vendors": [
+ {
+ "name": "sunspec",
+ "displayName": "SunSpec",
+ "id": "c143a7b4-a16c-4fff-86a3-9ffab3d6841d",
+ "thingClasses": [
+ {
+ "name": "sunspecConnection",
+ "displayName": "SunSpec connection",
+ "id": "f51853f3-8815-4cf3-b337-45cda1f3e6d5",
+ "createMethods": [ "User" ],
+ "interfaces": ["gateway"],
+ "paramTypes": [
+ {
+ "id": "6be6abc4-e2b2-4687-9343-0e5164ed0ab2",
+ "name":"ipAddress",
+ "displayName": "IP address",
+ "type": "QString",
+ "defaultValue": "127.0.0.1"
+ },
+ {
+ "id": "1fa4fc9c-f6be-47c7-928a-bcefc1142eec",
+ "name":"port",
+ "displayName": "Port",
+ "type": "int",
+ "defaultValue": 502
+ },
+ {
+ "id": "953064e0-4675-4538-a9a2-fa22ce2f347c",
+ "name":"slaveId",
+ "displayName": "Slave ID",
+ "type": "int",
+ "defaultValue": 1
+ }
+ ],
+ "stateTypes":[
+ {
+ "id": "3e767ad0-b4b3-4398-94c1-00579ea09ca8",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "04970315-ed3a-45ce-98fc-35ae3c4eb27b",
+ "name":"manufacturer",
+ "displayName": "Manufacturer",
+ "displayNameEvent": "Manufacturer changed",
+ "type": "QString",
+ "defaultValue": "Unkown"
+ },
+ {
+ "id": "58146c26-17d3-458e-a13f-d7f306c20c44",
+ "name":"deviceModel",
+ "displayName": "Device model",
+ "displayNameEvent": "Device model changed",
+ "type": "QString",
+ "defaultValue": "Unkown"
+ },
+ {
+ "id": "6ed498e1-37ca-4bb7-bac7-463509c7784e",
+ "name":"serialNumber",
+ "displayName": "Serial number",
+ "displayNameEvent": "Serial number changed",
+ "type": "QString",
+ "defaultValue": "Unkown"
+ }
+ ]
+ },
+ {
+ "name": "sunspecSinglePhaseInverter",
+ "displayName": "SunSpec single phase inverter",
+ "id": "c5d5204e-3375-4b92-8128-fab77a671fed",
+ "createMethods": [ "Auto" ],
+ "interfaces": ["extendedsmartmeterproducer", "connectable"],
+ "paramTypes": [
+ {
+ "id": "41715d00-a947-4f43-a475-cea05790e01d",
+ "name":"modelId",
+ "displayName": "Model",
+ "type": "int",
+ "readOnly": true
+ },
+ {
+ "id": "26ae9050-7090-453a-85a3-307bfebe6fed",
+ "name":"modbusAddress",
+ "displayName": "Modbus address",
+ "type": "uint",
+ "readOnly": true,
+ "defaultValue": 0
+ }
+ ],
+ "stateTypes":[
+ {
+ "id": "48bf31c4-fda7-41e5-a3ef-3011bf96e104",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "f02d1c99-9b43-45a6-8a06-2ed4d6e5d497",
+ "name": "totalCurrent",
+ "displayName": "Total AC current",
+ "displayNameEvent": "Total AC current changed",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "4ca086e9-82b9-461c-b168-1d61b542b884",
+ "name": "phaseVoltage",
+ "displayName": "Phase voltage",
+ "displayNameEvent": "Phase volatage changed",
+ "type": "double",
+ "unit": "Volt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "f49591d6-d759-4be3-bafc-b6a7a72cf023",
+ "name": "currentPower",
+ "displayName": "AC power",
+ "displayNameEvent": "AC power changed",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "611df2ce-2b9c-49f3-9fa7-5706776e812c",
+ "name": "frequency",
+ "displayName": "Frequency",
+ "displayNameEvent": "Frequency changed",
+ "type": "double",
+ "unit": "Hertz",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "4c0407b3-5cd5-438d-bfa8-9a8d6695b458",
+ "name": "totalEnergyProduced",
+ "displayName": "AC energy",
+ "displayNameEvent": "AC energy changed",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "51461bde-1a6b-4aa1-94cc-59829ea0a7c8",
+ "name": "cabinetTemperature",
+ "displayName": "Cabinet temperature",
+ "displayNameEvent": "Cabinet temperature changed",
+ "type": "double",
+ "unit": "DegreeCelsius",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "47543a7f-425f-406b-a458-b79c36b65f6c",
+ "name": "operatingState",
+ "displayName": "Operating state",
+ "displayNameEvent": "Operating state changed",
+ "type": "QString",
+ "possibleValues": [
+ "Off",
+ "Sleeping",
+ "Starting",
+ "MPPT",
+ "Throttled",
+ "Shutting down",
+ "Fault",
+ "Standby"
+ ],
+ "defaultValue": "Off"
+ },
+ {
+ "id": "49240259-d82a-4fe6-b3f5-1cd6a67c87a7",
+ "name": "error",
+ "displayName": "Error",
+ "displayNameEvent": "Error changed",
+ "type": "QString",
+ "possibleValues": [
+ "None",
+ "Ground fault",
+ "DC over voltage",
+ "AC disconnect open",
+ "DC disconnect open",
+ "Grid disconnect",
+ "Cabinet open",
+ "Manual shutdown",
+ "Over temperature",
+ "Frequency above limit",
+ "Frequency under limit",
+ "AC voltage above limit",
+ "AC voltage under limit",
+ "Blown string fuse on input",
+ "Under temperature",
+ "Communication error",
+ "Hardware test failure"
+ ],
+ "defaultValue": "None"
+ }
+ ]
+ },
+ {
+ "name": "sunspecSplitPhaseInverter",
+ "displayName": "SunSpec split phase inverter",
+ "id": "61b38f93-d331-42bf-b1ef-d3fb16ad1230",
+ "createMethods": [ "Auto" ],
+ "interfaces": ["extendedsmartmeterproducer", "connectable"],
+ "paramTypes": [
+ {
+ "id": "c42fb50e-210f-4b53-88eb-fa216e15f88f",
+ "name":"modelId",
+ "displayName": "Model",
+ "type": "int",
+ "readOnly": true
+ },
+ {
+ "id": "37582a96-f2f2-4845-abef-973c7dd0ad57",
+ "name":"modbusAddress",
+ "displayName": "Modbus address",
+ "type": "uint",
+ "readOnly": true,
+ "defaultValue": 0
+ }
+ ],
+ "stateTypes":[
+ {
+ "id": "27b49640-f58b-466e-a225-a4663cf3ed96",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "9dbd8da7-dc22-4c3a-b941-47520fde705f",
+ "name": "totalCurrent",
+ "displayName": "Total AC current",
+ "displayNameEvent": "Total AC current changed",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "be7b86b4-aeeb-49ba-9b6b-9792dceed6b5",
+ "name": "phaseACurrent",
+ "displayName": "Phase A current",
+ "displayNameEvent": "Phase A current changed",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "fc5df18d-cf2f-4944-97b7-e57dabef8778",
+ "name": "phaseBCurrent",
+ "displayName": "Phase B current",
+ "displayNameEvent": "Phase B current changed",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "17c24cfc-cb41-4873-82b4-19a20d6be146",
+ "name": "phaseANVoltage",
+ "displayName": "Phase AN voltage",
+ "displayNameEvent": "Phase AN volatage changed",
+ "type": "double",
+ "unit": "Volt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "f8645ee2-a1e6-4d09-8c20-f6fd02a9e896",
+ "name": "phaseBNVoltage",
+ "displayName": "Phase BN voltage",
+ "displayNameEvent": "Phase BN voltage changed",
+ "type": "double",
+ "unit": "Volt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "9235eb4b-906c-4557-8e18-bca268a367cc",
+ "name": "currentPower",
+ "displayName": "AC power",
+ "displayNameEvent": "AC power changed",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "874f5e4a-a009-4c28-b211-2af90a24b2ac",
+ "name": "frequency",
+ "displayName": "Line frequency",
+ "displayNameEvent": "Line frequency changed",
+ "type": "double",
+ "unit": "Hertz",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "fe3f8a65-121a-4ae1-b22a-ae325dc3e7e6",
+ "name": "totalEnergyProduced",
+ "displayName": "AC energy",
+ "displayNameEvent": "AC energy changed",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "6d314d50-b990-4a58-a37f-4a3da42c4407",
+ "name": "cabinetTemperature",
+ "displayName": "Cabinet temperature",
+ "displayNameEvent": "Cabinet temperature changed",
+ "type": "double",
+ "unit": "DegreeCelsius",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "6c1e2929-bc9a-4ce9-a405-6df2633a5131",
+ "name": "operatingState",
+ "displayName": "Operating state",
+ "displayNameEvent": "Operating state changed",
+ "type": "QString",
+ "possibleValues": [
+ "Off",
+ "Sleeping",
+ "Starting",
+ "MPPT",
+ "Throttled",
+ "Shutting down",
+ "Fault",
+ "Standby"
+ ],
+ "defaultValue": "Off"
+ },
+ {
+ "id": "5cbfccc9-6afb-404c-a85e-e0323659a25f",
+ "name": "error",
+ "displayName": "Error",
+ "displayNameEvent": "Error changed",
+ "type": "QString",
+ "possibleValues": [
+ "None",
+ "Ground fault",
+ "DC over voltage",
+ "AC disconnect open",
+ "DC disconnect open",
+ "Grid disconnect",
+ "Cabinet open",
+ "Manual shutdown",
+ "Over temperature",
+ "Frequency above limit",
+ "Frequency under limit",
+ "AC voltage above limit",
+ "AC voltage under limit",
+ "Blown string fuse on input",
+ "Under temperature",
+ "Communication error",
+ "Hardware test failure"
+ ],
+ "defaultValue": "None"
+ }
+ ]
+ },
+ {
+ "name": "sunspecThreePhaseInverter",
+ "displayName": "SunSpec three phase inverter",
+ "id": "2e4122ea-96a5-415c-b5e2-7d6012265a83",
+ "createMethods": [ "Auto" ],
+ "interfaces": ["extendedsmartmeterproducer", "connectable"],
+ "paramTypes": [
+ {
+ "id": "8d5b2b58-ce46-406d-844e-f53136afcf09",
+ "name":"modelId",
+ "displayName": "Model",
+ "type": "int",
+ "readOnly": true
+ },
+ {
+ "id": "e5465ede-9d3d-4558-b614-40dda743ddae",
+ "name":"modbusAddress",
+ "displayName": "Modbus address",
+ "type": "uint",
+ "readOnly": true,
+ "defaultValue": 0
+ }
+ ],
+ "stateTypes":[
+ {
+ "id": "4401468c-0385-40a9-b436-daf7ed6a50d5",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "26560dd8-6de4-445e-ba55-391d7241c370",
+ "name": "totalCurrent",
+ "displayName": "Total AC current",
+ "displayNameEvent": "Total AC current changed",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "3140ccd3-40cf-46c8-8bb2-8c3ea4582f84",
+ "name": "phaseACurrent",
+ "displayName": "Phase A current",
+ "displayNameEvent": "Phase A current changed",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "7ea1a53a-6fd9-4914-8283-b57aa1aaaebf",
+ "name": "phaseBCurrent",
+ "displayName": "Phase B current",
+ "displayNameEvent": "Phase B current changed",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "aa4b4cf5-43d0-4be5-9505-403918b5371d",
+ "name": "phaseCCurrent",
+ "displayName": "Phase C current",
+ "displayNameEvent": "Phase C current changed",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "f08521aa-9c38-4c31-95e1-acb616f6e9c6",
+ "name": "phaseANVoltage",
+ "displayName": "Phase AN voltage",
+ "displayNameEvent": "Phase AN volatage changed",
+ "type": "double",
+ "unit": "Volt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "739b8805-d522-4406-bede-d1e4200a3aa9",
+ "name": "phaseBNVoltage",
+ "displayName": "Phase BN voltage",
+ "displayNameEvent": "Phase BN voltage changed",
+ "type": "double",
+ "unit": "Volt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "949b797d-5566-4667-8982-e430d23548e2",
+ "name": "phaseCNVoltage",
+ "displayName": "Phase CN voltage",
+ "displayNameEvent": "Phase CN voltage changed",
+ "type": "int",
+ "unit": "Volt",
+ "defaultValue": 0
+ },
+ {
+ "id": "14036f44-25fd-4e93-8e8c-c677b06a2c34",
+ "name": "currentPower",
+ "displayName": "AC power",
+ "displayNameEvent": "AC power changed",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "faa45cae-ed28-4150-9036-fceddf9d6776",
+ "name": "frequency",
+ "displayName": "Frequency",
+ "displayNameEvent": "Frequency changed",
+ "type": "double",
+ "unit": "Hertz",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "d493880d-eb58-4530-8010-8ea4f6d63387",
+ "name": "totalEnergyProduced",
+ "displayName": "AC energy",
+ "displayNameEvent": "AC energy changed",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "44b0320f-89a7-4248-bad4-288ef898a5cc",
+ "name": "cabinetTemperature",
+ "displayName": "Cabinet temperature",
+ "displayNameEvent": "Cabinet temperature changed",
+ "type": "double",
+ "unit": "DegreeCelsius",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "cebdce98-42d1-4a28-8834-8960efc0e83f",
+ "name": "operatingState",
+ "displayName": "Operating state",
+ "displayNameEvent": "Operating state changed",
+ "type": "QString",
+ "possibleValues": [
+ "Off",
+ "Sleeping",
+ "Starting",
+ "MPPT",
+ "Throttled",
+ "Shutting down",
+ "Fault",
+ "Standby"
+ ],
+ "defaultValue": "Off"
+ },
+ {
+ "id": "4479af96-c499-4f15-abd6-4afdb18a9e09",
+ "name": "error",
+ "displayName": "Error",
+ "displayNameEvent": "Error changed",
+ "type": "QString",
+ "possibleValues": [
+ "None",
+ "Ground fault",
+ "DC over voltage",
+ "AC disconnect open",
+ "DC disconnect open",
+ "Grid disconnect",
+ "Cabinet open",
+ "Manual shutdown",
+ "Over temperature",
+ "Frequency above limit",
+ "Frequency under limit",
+ "AC voltage above limit",
+ "AC voltage under limit",
+ "Blown string fuse on input",
+ "Under temperature",
+ "Communication error",
+ "Hardware test failure"
+ ],
+ "defaultValue": "None"
+ }
+ ]
+ },
+ {
+ "name": "sunspecSinglePhaseMeter",
+ "displayName": "SunSpec single phase meter",
+ "id": "7ffa43b8-b56f-4435-8509-980e9d81dfa8",
+ "createMethods": [ "Auto" ],
+ "interfaces": ["extendedsmartmeterconsumer", "connectable"],
+ "paramTypes": [
+ {
+ "id": "7d6fcafb-c62e-4a21-aae2-f4041c487149",
+ "name":"modelId",
+ "displayName": "Model",
+ "type": "int",
+ "readOnly": true,
+ "defaultValue": 0
+ },
+ {
+ "id": "30b90ec0-429b-4e6c-88e9-155aa4bcad47",
+ "name":"modbusAddress",
+ "displayName": "Modbus address",
+ "type": "uint",
+ "readOnly": true,
+ "defaultValue": 0
+ }
+ ],
+ "stateTypes":[
+ {
+ "id": "d960e7b1-d4aa-4cab-8f54-6bcfdbb8be36",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "4a058e36-0b45-4388-9a26-0615f7aafa0d",
+ "name": "phaseACurrent",
+ "displayName": "Phase A current",
+ "displayNameEvent": "Phase A current changed",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "408e9c41-cfbf-456b-a9c2-b4adfde4a5b0",
+ "name": "lnACVoltage",
+ "displayName": "Line to Neutral AC Voltage",
+ "displayNameEvent": "Line to Neutral AC Voltage changed",
+ "type": "double",
+ "unit": "Volt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "8bfb8021-1b2e-4693-984c-0580f5665806",
+ "name": "phaseANVoltage",
+ "displayName": "Phase AN voltage",
+ "displayNameEvent": "Phase AN volatage changed",
+ "type": "double",
+ "unit": "Volt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "3a2ce51d-7fa0-4188-bbd6-00d25de90e15",
+ "name": "frequency",
+ "displayName": "Frequency",
+ "displayNameEvent": "Frequency changed",
+ "type": "double",
+ "unit": "Hertz",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "93cf8c6a-2620-42ed-9070-e0726d7b1dbc",
+ "name": "currentPower",
+ "displayName": "Total real power",
+ "displayNameEvent": "Total real power changed",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "ba275bdf-f418-4ef0-afbe-ac425c6f6783",
+ "name": "totalEnergyProduced",
+ "displayName": "Total real energy exported",
+ "displayNameEvent": "Total real energy exported changed",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "c51dc6cb-5c05-4078-b11a-26afb2f85541",
+ "name": "totalEnergyConsumed",
+ "displayName": "Total real energy imported",
+ "displayNameEvent": "Total real energy imported changed",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0.00
+ }
+ ]
+ },
+ {
+ "name": "sunspecSplitPhaseMeter",
+ "displayName": "SunSpec split phase meter",
+ "id": "b8a18e45-5ff5-4f43-915f-04ee216c809d",
+ "createMethods": [ "Auto" ],
+ "interfaces": ["extendedsmartmeterconsumer", "connectable"],
+ "paramTypes": [
+ {
+ "id": "89aeec6d-abeb-48b5-9594-214ad5db2d03",
+ "name":"modelId",
+ "displayName": "Model",
+ "type": "int",
+ "readOnly": true,
+ "defaultValue": 0
+ },
+ {
+ "id": "a56f198d-ed86-429f-b839-8e11a32da8c1",
+ "name":"modbusAddress",
+ "displayName": "Modbus address",
+ "type": "uint",
+ "readOnly": true,
+ "defaultValue": 0
+ }
+ ],
+ "stateTypes":[
+ {
+ "id": "34e34ec9-dab0-438c-9493-a3068bc401de",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "e85024af-5376-4ff1-813e-5a56990c11cc",
+ "name": "totalCurrent",
+ "displayName": "Total AC current",
+ "displayNameEvent": "Total AC current changed",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "e8c0f4bf-a704-46f2-80a0-cf490bd7871b",
+ "name": "phaseACurrent",
+ "displayName": "Phase A current",
+ "displayNameEvent": "Phase A current changed",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "4281f6fc-d5a0-4a22-ac61-6bec88efbc80",
+ "name": "phaseBCurrent",
+ "displayName": "Phase B current",
+ "displayNameEvent": "Phase B current changed",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "0ac79508-07c3-4d01-97a3-6edf121bdf32",
+ "name": "lnACVoltage",
+ "displayName": "Line to Neutral AC Voltage",
+ "displayNameEvent": "Line to Neutral AC Voltage changed",
+ "type": "double",
+ "unit": "Volt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "1bd7e53e-abf8-4d62-b87c-2c84c283567b",
+ "name": "phaseANVoltage",
+ "displayName": "Phase AN voltage",
+ "displayNameEvent": "Phase AN volatage changed",
+ "type": "double",
+ "unit": "Volt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "377b5279-ddb6-451d-8377-a9389c749393",
+ "name": "phaseBNVoltage",
+ "displayName": "Phase BN voltage",
+ "displayNameEvent": "Phase BN voltage changed",
+ "type": "double",
+ "unit": "Volt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "db977c04-a3e1-436f-a0cd-9ce5b7bc6b89",
+ "name": "frequency",
+ "displayName": "Frequency",
+ "displayNameEvent": "Frequency changed",
+ "type": "double",
+ "unit": "Hertz",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "ef4bc0f8-f516-49b7-aba8-d5f987485aca",
+ "name": "currentPower",
+ "displayName": "Total real power",
+ "displayNameEvent": "Total real power changed",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "8a63bd73-0546-4636-8da2-23238cc06fb2",
+ "name": "totalEnergyProduced",
+ "displayName": "Total real energy exported",
+ "displayNameEvent": "Total real energy exported changed",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "51ffb2ae-3920-40df-8290-bbf5b6e1a68f",
+ "name": "totalEnergyConsumed",
+ "displayName": "Total real energy imported",
+ "displayNameEvent": "Total real energy imported changed",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0.00
+ }
+ ]
+ },
+ {
+ "name": "sunspecThreePhaseMeter",
+ "displayName": "SunSpec three phase meter",
+ "id": "68f822f9-ff30-4275-b229-39a3674fead7",
+ "createMethods": [ "Auto" ],
+ "interfaces": ["extendedsmartmeterconsumer", "connectable"],
+ "paramTypes": [
+ {
+ "id": "a1960821-155c-4176-86fa-974429039182",
+ "name":"modelId",
+ "displayName": "Model",
+ "type": "int",
+ "readOnly": true,
+ "defaultValue": 0
+ },
+ {
+ "id": "6d5dbd35-1bf6-46db-bee9-90c679421b89",
+ "name":"modbusAddress",
+ "displayName": "Modbus address",
+ "type": "uint",
+ "readOnly": true,
+ "defaultValue": 0
+ }
+ ],
+ "stateTypes":[
+ {
+ "id": "36f861c7-afc1-4725-b41f-67113200d78f",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "7855d176-c6b8-439e-9f12-a71f01c1c7aa",
+ "name": "totalCurrent",
+ "displayName": "Total AC current",
+ "displayNameEvent": "Total AC current changed",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "da494d99-5de3-4d03-b7dd-33a33db32164",
+ "name": "phaseACurrent",
+ "displayName": "Phase A current",
+ "displayNameEvent": "Phase A current changed",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "023b6e5c-be3f-4d8c-adfd-e009c7bebffb",
+ "name": "phaseBCurrent",
+ "displayName": "Phase B current",
+ "displayNameEvent": "Phase B current changed",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "2676ad36-3d97-4691-8334-e13934cc58d1",
+ "name": "phaseCCurrent",
+ "displayName": "Phase C current",
+ "displayNameEvent": "Phase C current changed",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "bc54ca1e-1476-40eb-9974-9e3c2f893dd8",
+ "name": "lnACVoltage",
+ "displayName": "Line to Neutral AC Voltage",
+ "displayNameEvent": "Line to Neutral AC Voltage changed",
+ "type": "double",
+ "unit": "Volt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "4bd32d91-877a-4821-9db9-5981de20d21d",
+ "name": "phaseANVoltage",
+ "displayName": "Phase AN voltage",
+ "displayNameEvent": "Phase AN volatage changed",
+ "type": "double",
+ "unit": "Volt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "f090cb78-d7ed-44fd-a5ad-ea9016021c34",
+ "name": "phaseBNVoltage",
+ "displayName": "Phase BN voltage",
+ "displayNameEvent": "Phase BN voltage changed",
+ "type": "double",
+ "unit": "Volt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "fa5aa6f5-e67d-485a-835f-24e49298856c",
+ "name": "phaseCNVoltage",
+ "displayName": "Phase CN voltage",
+ "displayNameEvent": "Phase CN voltage changed",
+ "type": "double",
+ "unit": "Volt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "dfcf52f5-6279-4d25-b7c8-a93b92c39a0c",
+ "name": "frequency",
+ "displayName": "Frequency",
+ "displayNameEvent": "Frequency changed",
+ "type": "double",
+ "unit": "Hertz",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "c28c642f-46da-44de-ba0d-c4cbfadbf2cd",
+ "name": "currentPower",
+ "displayName": "Total real power",
+ "displayNameEvent": "Total real power changed",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "73ebf57f-1ad2-4d19-bfd9-9e0a514c1243",
+ "name": "totalEnergyProduced",
+ "displayName": "Total real energy exported",
+ "displayNameEvent": "Total real energy exported changed",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "63fa4721-1b0a-458c-b66c-17c161107f0d",
+ "name": "totalEnergyConsumed",
+ "displayName": "Total real energy imported",
+ "displayNameEvent": "Total real energy imported changed",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0.00
+ }
+ ]
+ },
+ {
+ "name": "sunspecStorage",
+ "displayName": "SunSpec Storage",
+ "id": "9a643ba8-346c-4127-a2f8-956a7133d75e",
+ "createMethods": [ "Auto" ],
+ "interfaces": ["connectable"],
+ "paramTypes": [
+ {
+ "id": "219beb96-b9fe-4dd2-a386-ecfbbab8786d",
+ "name":"modelId",
+ "displayName": "Model",
+ "type": "int",
+ "readOnly": true,
+ "defaultValue": 0
+ },
+ {
+ "id": "3f107844-00c5-4f39-86e5-485b3d1f5c1a",
+ "name":"modbusAddress",
+ "displayName": "Modbus address",
+ "type": "uint",
+ "readOnly": true,
+ "defaultValue": 0
+ }
+ ],
+ "stateTypes":[
+ {
+ "id": "25a1fb10-a6b9-4037-b7cf-ad481a65beb4",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "3171a6e0-43a7-4de8-8e20-f748e44af7ac",
+ "name": "batteryCritical",
+ "displayName": "Battery critical",
+ "displayNameEvent": "Battery critical changed",
+ "type": "bool",
+ "defaultValue": false
+ },
+ {
+ "id": "0bf53f80-97f8-488b-b514-58f9fe08c183",
+ "name": "batteryLevel",
+ "displayName": "Battery level",
+ "displayNameEvent": "Battery level changed",
+ "type": "double",
+ "unit": "Percentage",
+ "minValue": 0,
+ "maxValue": 100,
+ "defaultValue": 0
+ },
+ {
+ "id": "da2b19c5-0f48-49d1-93f0-abdc0051407d",
+ "name": "storageStatus",
+ "displayName": "Status",
+ "displayNameEvent": "Status 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": "Grid charging changed",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true,
+ "displayNameAction": "Set grid charging"
+ },
+ {
+ "id": "1f530f79-c0d2-466b-90e1-79149e34d92f",
+ "name": "enableCharging",
+ "displayName": "Charging",
+ "displayNameEvent": "Charging changed",
+ "displayNameAction": "Enable charging",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true
+ },
+ {
+ "id": "bc99a159-815a-40ab-a6e8-b46f315305f7",
+ "name": "enableDischarging",
+ "displayName": "Discharging",
+ "displayNameEvent": "Discharging changed",
+ "displayNameAction": "Enable discharging",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true
+ },
+ {
+ "id": "7f469bbc-64a5-4045-8d5f-9a1a85039851",
+ "name": "chargingRate",
+ "displayName": "Charging rate",
+ "displayNameEvent": "Charging rate changed",
+ "type": "int",
+ "minValue": 0,
+ "maxValue": 100,
+ "unit": "Percentage",
+ "defaultValue": false,
+ "writable": true,
+ "displayNameAction": "Set charging rate"
+ },
+ {
+ "id": "6068f030-acce-44a2-b95f-bd00dd5ca760",
+ "name": "dischargingRate",
+ "displayName": "Discharging rate",
+ "displayNameEvent": "Discharging rate changed",
+ "type": "int",
+ "minValue": 0,
+ "maxValue": 100,
+ "unit": "Percentage",
+ "defaultValue": false,
+ "writable": true,
+ "displayNameAction": "Set discharging rate"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/sunspec/meta.json b/sunspec/meta.json
new file mode 100644
index 0000000..d93e59c
--- /dev/null
+++ b/sunspec/meta.json
@@ -0,0 +1,12 @@
+{
+ "title": "SunSpec",
+ "tagline": "Connect to SunSpec devices.",
+ "icon": "sunspec.png",
+ "stability": "consumer",
+ "offline": true,
+ "technologies": [
+ "network"
+ ],
+ "categories": [
+ ]
+}
diff --git a/sunspec/sunspec.cpp b/sunspec/sunspec.cpp
new file mode 100644
index 0000000..262cf5f
--- /dev/null
+++ b/sunspec/sunspec.cpp
@@ -0,0 +1,427 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "sunspec.h"
+#include "extern-plugininfo.h"
+#include
+
+SunSpec::SunSpec(const QHostAddress &hostAddress, uint port, uint slaveId, QObject *parent) :
+ QObject(parent),
+ m_hostAddress(hostAddress),
+ m_port(port),
+ m_slaveId(slaveId)
+{
+ qCDebug(dcSunSpec()) << "SunSpec: Creating SunSpec connection";
+ m_modbusTcpClient = new QModbusTcpClient(this);
+ m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, m_port);
+ m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, m_hostAddress.toString());
+ m_modbusTcpClient->setTimeout(2000);
+ m_modbusTcpClient->setNumberOfRetries(3);
+
+ connect(m_modbusTcpClient, &QModbusTcpClient::stateChanged, this, &SunSpec::onModbusStateChanged);
+}
+
+SunSpec::~SunSpec()
+{
+ qCDebug(dcSunSpec()) << "SunSpec: Deleting SunSpec connection";
+}
+
+bool SunSpec::connectModbus()
+{
+ qCDebug(dcSunSpec()) << "SunSpec: Connect modbus";
+ return m_modbusTcpClient->connectDevice();
+}
+
+void SunSpec::setHostAddress(const QHostAddress &hostAddress)
+{
+ if (m_hostAddress != hostAddress) {
+ qCDebug(dcSunSpec()) << "SunSpec: Set host address" << hostAddress.toString();
+ m_hostAddress = hostAddress;
+ m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, m_hostAddress.toString());
+ }
+}
+
+void SunSpec::setPort(uint port)
+{
+ if (port != m_port) {
+ qCDebug(dcSunSpec()) << "SunSpec: Set Port" << port;
+ m_port = port;
+ m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, port);
+ }
+}
+
+void SunSpec::setSlaveId(uint slaveId)
+{
+ qCDebug(dcSunSpec()) << "SunSpec: Set slave id" << slaveId;
+ m_slaveId = slaveId;
+}
+
+void SunSpec::setTimeout(uint milliSeconds)
+{
+ qCDebug(dcSunSpec()) << "SunSpec: Set timeout" << milliSeconds << "[ms]";
+ m_modbusTcpClient->setTimeout(milliSeconds);
+}
+
+void SunSpec::setNumberOfRetries(uint retries)
+{
+ qCDebug(dcSunSpec()) << "SunSpec: Set number of retries" << retries;
+ m_modbusTcpClient->setNumberOfRetries(retries);
+}
+
+QHostAddress SunSpec::hostAddress() const
+{
+ return m_hostAddress;
+}
+
+uint SunSpec::port()
+{
+ return m_port;
+}
+
+uint SunSpec::slaveId()
+{
+ return m_slaveId;
+}
+
+QString SunSpec::manufacturer()
+{
+ return m_manufacturer;
+}
+
+QString SunSpec::deviceModel()
+{
+ return m_deviceModel;
+}
+
+QString SunSpec::serialNumber()
+{
+ return m_serialNumber;
+}
+
+void SunSpec::findBaseRegister()
+{
+ qCDebug(dcSunSpec()) << "SunSpec: Find base register";
+ QList validBaseRegisters;
+ validBaseRegisters.append(0);
+ validBaseRegisters.append(40000);
+ validBaseRegisters.append(50000);
+
+ Q_FOREACH (int baseRegister, validBaseRegisters) {
+ qCDebug(dcSunSpec()) << " - Searching address" << baseRegister;
+ QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, baseRegister, 2);
+ if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, m_slaveId)) {
+ if (!reply->isFinished()) {
+ connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
+ connect(reply, &QModbusReply::finished, this, [reply, baseRegister, this] {
+
+ if (reply->error() == QModbusDevice::NoError) {
+ const QModbusDataUnit unit = reply->result();
+ if ((unit.value(0) << 16 | unit.value(1)) == 0x53756e53) {
+ //Well-known value. Uniquely identifies this as a SunSpec Modbus model
+ qCDebug(dcSunSpec()) << "SunSpec: Found start of modbus model" << baseRegister;
+ m_baseRegister = baseRegister;
+ emit foundBaseRegister(baseRegister);
+ } else {
+ qCWarning(dcSunSpec()) << "SunSpec: Got reply on base register" << baseRegister << ", but value didn't mach 0x53756e53";
+ }
+ } else {
+ qCDebug(dcSunSpec()) << "SunSpec: Find base register not found at:" << baseRegister;
+ }
+ });
+ } else {
+ delete reply; // broadcast replies return immediately
+ return;
+ }
+ }
+ }
+}
+
+void SunSpec::findSunSpecModels(const QList &ids, uint modbusAddressOffset)
+{
+ qCDebug(dcSunSpec()) << "SunSpec: Find modbus model. Start register" << m_baseRegister+modbusAddressOffset;
+ QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_baseRegister+modbusAddressOffset, 2);
+
+ if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, m_slaveId)) {
+ if (!reply->isFinished()) {
+ connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
+ connect(reply, &QModbusReply::finished, this, [ids, reply, this] {
+
+ if (reply->error() == QModbusDevice::NoError) {
+ const QModbusDataUnit unit = reply->result();
+ uint modbusAddress = unit.startAddress();
+ ModelId modelId = ModelId(unit.value(0));
+ int modelLength = unit.value(1);
+ if (modelId == ModelIdEnd) {
+ qCDebug(dcSunSpec()) << "SunSpec: Model Id End";
+ sunspecModelSearchFinished(m_modelList);
+ return;
+ }
+
+ if (ids.isEmpty() || ids.contains(modelId)) {
+ // If ids is empty then emit all models
+ qCDebug(dcSunSpec()) << "SunSpec: Found model" << ModelId(modelId) << "with length" << modelLength;
+ m_modelList.insert(ModelId(modelId), modbusAddress);
+ foundSunSpecModel(ModelId(modelId), modbusAddress);
+ }
+ findSunSpecModels(ids, modbusAddress+2+modelLength-m_baseRegister); //read next model
+ } else {
+ qCWarning(dcSunSpec()) << "SunSpec: Find modbus model, read response error:" << reply->error();
+ }
+ });
+ } else {
+ delete reply; // broadcast replies return immediately
+ return;
+ }
+ } else {
+ qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString();
+ return;
+ }
+}
+
+void SunSpec::readModelHeader(uint modbusAddress)
+{
+ qCDebug(dcSunSpec()) << "SunSpec: Read model header, modbus address:" << modbusAddress << "Slave ID" << m_slaveId;
+ if (modbusAddress == 0 || modbusAddress == 40000 || modbusAddress == 50000)
+ modbusAddress += 2;
+
+ QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, modbusAddress, 2);
+
+ if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, m_slaveId)) {
+ if (!reply->isFinished()) {
+ connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
+ connect(reply, &QModbusReply::finished, this, [reply, this] {
+
+ if (reply->error() == QModbusDevice::NoError) {
+ const QModbusDataUnit unit = reply->result();
+ uint modbusAddress = unit.startAddress();
+ ModelId modelId = ModelId(unit.value(0));
+ int length = unit.value(1);
+ qCDebug(dcSunSpec()) << "SunSpec: Received model header response. Model ID:" << modelId << "length" << length;
+ modelHeaderReceived(modbusAddress, modelId, length);
+ } else {
+ qCWarning(dcSunSpec()) << "SunSpec: Read model header response error:" << reply->error();
+ }
+ });
+ connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) {
+ qCWarning(dcSunSpec()) << "SunSpec: Read model header, modbus reply error:" << error;
+ reply->finished(); // To make sure it will be deleted
+ });
+ } else {
+ qCWarning(dcSunSpec()) << "SunSpec: Read model header error: " << m_modbusTcpClient->errorString();
+ delete reply; // broadcast replies return immediately
+ return;
+ }
+ } else {
+ qCWarning(dcSunSpec()) << "SunSpec: Read model header error: " << m_modbusTcpClient->errorString();
+ return;
+ }
+}
+
+void SunSpec::readModelDataBlock(uint modbusAddress, uint length)
+{
+ qCDebug(dcSunSpec()) << "SunSpec: Read model, modbus address" << modbusAddress << "length" << length << ", Slave ID" << m_slaveId;
+
+ if (length > 125) { //Modbus register limit is 125
+ qCWarning(dcSunSpec()) << "SunSpec: Data block length is too long, max 125 register";
+ return;
+ }
+
+ QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, modbusAddress, length+2);
+
+ if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, m_slaveId)) {
+ if (!reply->isFinished()) {
+ connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
+ connect(reply, &QModbusReply::finished, this, [reply, this] {
+
+ if (reply->error() == QModbusDevice::NoError) {
+ const QModbusDataUnit unit = reply->result();
+ uint modbusAddress = unit.startAddress();
+ ModelId modelId = ModelId(unit.value(0));
+ uint length = unit.value(1);
+ qCDebug(dcSunSpec()) << "SunSpec: Received model. Modbus address" << modbusAddress << "model ID" << modelId << "length" << length;
+ emit modelDataBlockReceived(modelId, length, unit.values().mid(2));
+ } else {
+ qCWarning(dcSunSpec()) << "SunSpec: Read response error:" << reply->error();
+ }
+ });
+ connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) {
+ qCWarning(dcSunSpec()) << "SunSpec: Modbus reply error:" << error;
+ reply->finished(); // To make sure it will be deleted
+ });
+ } else {
+ qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString();
+ delete reply; // broadcast replies return immediately
+ return;
+ }
+ } else {
+ qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString();
+ return;
+ }
+}
+
+void SunSpec::readCommonModel()
+{
+ qCDebug(dcSunSpec()) << "SunSpec: Read common model header. Modbus Address" << m_baseRegister+2 << ", Slave ID" << m_slaveId;
+
+ QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_baseRegister+2, 66);
+
+ if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, m_slaveId)) {
+ if (!reply->isFinished()) {
+ connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
+ connect(reply, &QModbusReply::finished, this, [reply, this] {
+
+ if (reply->error() == QModbusDevice::NoError) {
+ const QModbusDataUnit unit = reply->result();
+ m_manufacturer = convertModbusRegisters(unit.values(), MandatoryRegistersModel1::Manufacturer, 16);
+ m_manufacturer.remove('\x00');
+ m_deviceModel = convertModbusRegisters(unit.values(), MandatoryRegistersModel1::Model, 16);
+ m_deviceModel.remove('\x00');
+ m_serialNumber = convertModbusRegisters(unit.values(), MandatoryRegistersModel1::SerialNumber, 16);
+ m_serialNumber.remove('\x00');
+ qCDebug(dcSunSpec()) << "SunSpec: Received common block response. Manufacturer" << m_manufacturer << "Model" << m_deviceModel << "Serial number" << m_serialNumber;
+ commonModelReceived(m_manufacturer, m_deviceModel, m_serialNumber);
+ } else {
+ qCWarning(dcSunSpec()) << "SunSpec: Read common model, read response error:" << reply->error();
+ }
+ });
+ } else {
+ qCWarning(dcSunSpec()) << "Sunspec: Read common model read error: " << m_modbusTcpClient->errorString();
+ delete reply; // broadcast replies return immediately
+ return;
+ }
+ } else {
+ qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString();
+ return;
+ }
+}
+
+QByteArray SunSpec::convertModbusRegister(const uint16_t &modbusData)
+{
+ uint8_t data[2];
+ data[0] = modbusData >> 8;
+ data[1] = modbusData & 0xFF;
+ //qCDebug(dcSunSpec()) << (char)data[0] << (char)data[1];
+ return QByteArray().append((char)data[0]).append((char)data[1]);
+}
+
+QBitArray SunSpec::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 SunSpec::convertModbusRegisters(const QVector &modbusData, int offset, int size)
+{
+ QByteArray bytes;
+ for (int i = offset; i < offset + size; i++)
+ bytes.append(convertModbusRegister(modbusData[i]));
+
+ return bytes;
+}
+
+float SunSpec::convertToFloatWithSSF(quint32 rawValue, quint16 sunssf)
+{
+ float value;
+ value = rawValue * pow(10, static_cast(sunssf));
+ return value;
+}
+
+quint32 SunSpec::convertFromFloatWithSSF(float value, quint16 sunssf)
+{
+ quint32 rawValue;
+ rawValue = value / pow(10, static_cast(sunssf));
+ return rawValue;
+}
+
+float SunSpec::convertFloatValues(quint16 rawValue0, quint16 rawValue1)
+{
+ suns_modbus_v32_t value;
+ value.u = (static_cast(rawValue0) << 16) + rawValue1;
+ return value.f;
+}
+
+void SunSpec::onModbusStateChanged(QModbusDevice::State state)
+{
+ bool connected = (state == QModbusDevice::ConnectedState);
+ if (!connected) {
+ //try to reconnect in 10 seconds
+ QTimer::singleShot(10000, m_modbusTcpClient, [this] {
+ if (m_modbusTcpClient->connectDevice()) {
+ qCDebug(dcSunSpec()) << "SunSpec: Could not reconnect";
+ }
+ });
+ }
+ emit connectionStateChanged(connected);
+}
+
+QUuid SunSpec::writeHoldingRegister(uint registerAddress, quint16 value)
+{
+ return writeHoldingRegisters(registerAddress, QVector() << value);
+}
+
+QUuid SunSpec::writeHoldingRegisters(uint registerAddress, const QVector &values)
+{
+ if (!m_modbusTcpClient) {
+ return "";
+ }
+ QUuid requestId = QUuid::createUuid();
+ QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, values.length());
+ request.setValues(values);
+
+ if (QModbusReply *reply = m_modbusTcpClient->sendWriteRequest(request, m_slaveId)) {
+ if (!reply->isFinished()) {
+ connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
+ connect(reply, &QModbusReply::finished, this, [reply, requestId, this] {
+
+ if (reply->error() != QModbusDevice::NoError) {
+ qCWarning(dcSunSpec()) << "SunSpec: Read response error:" << reply->error();
+ emit requestExecuted(requestId, false);
+ return;
+ }
+ emit requestExecuted(requestId, true);
+ });
+ } else {
+ delete reply; // broadcast replies return immediately
+ return "";
+ }
+ } else {
+ qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString();
+ return "";
+ }
+ return requestId;
+}
diff --git a/sunspec/sunspec.h b/sunspec/sunspec.h
new file mode 100644
index 0000000..45f58b4
--- /dev/null
+++ b/sunspec/sunspec.h
@@ -0,0 +1,221 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 SUNSPEC_H
+#define SUNSPEC_H
+
+#include
+#include
+#include
+#include
+
+class SunSpec : public QObject
+{
+ Q_OBJECT
+
+public:
+
+ typedef union {
+ int32_t s;
+ uint32_t u;
+ float f;
+ } suns_modbus_v32_t;
+
+ enum MandatoryRegistersModel1 {
+ Manufacturer = 2,
+ Model = 18,
+ SerialNumber = 50
+ };
+
+ enum OptionalRegistersModel1 {
+ Options = 34,
+ Version = 40,
+ DeviceAddress = 64,
+ ForceAlignment = 65
+ };
+
+ enum SunSpecOperatingState {
+ Off = 1,
+ Sleeping,
+ Starting,
+ MPPT,
+ Throttled,
+ ShuttingDown,
+ Fault,
+ Standby
+ };
+ Q_ENUM(SunSpecOperatingState)
+
+
+ enum ModelId {
+ ModelIdCommon = 1,
+ ModelIdBasicAggregator = 2,
+ ModelIdSecureDatasetReadRequest = 3,
+ ModelIdSecureDatasetReadResponse = 4,
+ ModelIdSecureWriteRequest = 5,
+ ModelIdSecureWriteSequentialRequest = 6,
+ ModelIdSecureWriteResponseModel = 7,
+ ModelIdGetDeviceSecurityCertificate = 8,
+ ModelIdSetOperatorSecurityCertificate = 9,
+ ModelIdCommunicationInterfaceHeader = 10,
+ ModelIdEthernetLinkLayer = 11,
+ ModelIdIPv4 = 12,
+ ModelIdIPv6 = 13,
+ ModelIdProxyServer = 14,
+ ModelIdInterfaceCountersModel = 15,
+ ModelIdSimpleIpNetwork = 16,
+ ModelIdSerialInterface = 17,
+ ModelIdCellularLink = 18,
+ ModelIdPPPLink = 19,
+ ModelIdInverterSinglePhase = 101,
+ ModelIdInverterSplitPhase = 102,
+ ModelIdInverterThreePhase = 103,
+ ModelIdInverterSinglePhaseFloat = 111,
+ ModelIdInverterSplitPhaseFloat = 112,
+ ModelIdInverterThreePhaseFloat = 113,
+ ModelIdNameplate = 120,
+ ModelIdBasicSettings = 121,
+ ModelIdMeasurementsStatus = 122,
+ ModelIdImmediateControls = 123,
+ ModelIdStorage = 124,
+ ModelIdPricing = 125,
+ ModelIdStaticVoltVAR = 126,
+ ModelIdFreqWattParam = 127,
+ ModelIdDynamicReactiveCurrent = 128,
+ ModelIdLVRTD = 129,
+ ModelIdHVRTD = 130,
+ ModelIdWattPF = 131,
+ ModelIdVoltWatt = 132,
+ ModelIdBasicScheduling = 133,
+ ModelIdFreqWattCrv = 134,
+ ModelIdLFRT = 135,
+ ModelIdHFRT = 136,
+ ModelIdLVRTC = 137,
+ ModelIdHVRTC = 138,
+ ModelIdMultipleMPPTInverterExtensionModel = 160,
+ ModelIdSinglePhaseMeter = 201,
+ ModelIdSplitSinglePhaseMeter = 202,
+ ModelIdWyeConnectThreePhaseMeter = 203,
+ ModelIdDeltaConnectThreePhaseMeter = 204,
+ ModelIdSinglePhaseMeterFloat = 211,
+ ModelIdSplitSinglePhaseMeterFloat = 212,
+ ModelIdWyeConnectThreePhaseMeterFloat = 213,
+ ModelIdDeltaConnectThreePhaseMeterFloat = 214,
+ ModelIdSecureACMeterSelectedReadings = 220,
+ ModelIdIrradianceModel = 302,
+ ModelIdBackOfModuleTemperatureModel = 303,
+ ModelIdInclinometerModel = 304,
+ ModelIdGPS = 305,
+ ModelIdReferencePointModel = 306,
+ ModelIdBaseMet = 307,
+ ModelIdMiniMetModel = 308,
+ ModelIdStringCombiner = 401,
+ ModelIdStringCombinerAdvanced = 402,
+ ModelIdStringCombinerCurrent = 403,
+ ModelIdStringCombinerCurrentAdvanced = 404,
+ ModelIdSolarModuleFloat = 501,
+ ModelIdSolarModule = 502,
+ ModelIdTrackerController = 601,
+ ModelIdEnergyStorageBaseModel = 801,
+ ModelIdBatteryBaseModel = 802,
+ ModelIdLithiumIonBatteryModel = 803,
+ ModelIdVerisStatusConfiguration = 64001,
+ ModelIdMersenGreenString = 64020,
+ ModelIdEltekInverterExtension = 64101,
+ ModelIdOutBackAXSDevice = 64110,
+ ModelIdBasicChargeController = 64111,
+ ModelIdOutBackFMChargeController = 64112,
+ ModelIdEnd = 65535
+ };
+ Q_ENUM(ModelId)
+
+ explicit SunSpec(const QHostAddress &hostAddress, uint port = 502, uint slaveId = 1, QObject *parent = 0);
+ ~SunSpec();
+ bool connectModbus();
+ void setHostAddress(const QHostAddress &hostAddress);
+ void setPort(uint port);
+ void setSlaveId(uint slaveId);
+ void setTimeout(uint milliSeconds);
+ void setNumberOfRetries(uint retries);
+
+ QHostAddress hostAddress() const;
+ uint port();
+ uint slaveId();
+ QString manufacturer();
+ QString deviceModel();
+ QString serialNumber();
+
+ QHostAddress m_hostAddress;
+ uint m_port;
+ QModbusTcpClient *m_modbusTcpClient = nullptr;
+ int m_slaveId = 1;
+ int m_baseRegister = 40000;
+ bool m_floatingPointRepresentation = false;
+ QString m_manufacturer = "Unknown";
+ QString m_deviceModel = "Unknown";
+ QString m_serialNumber = "Unknown";
+ QHash m_modelList;
+
+ void findBaseRegister();
+ void findSunSpecModels(const QList &modelIds, uint modbusAddressOffset = 2);
+
+ void readCommonModel();
+ void readModelHeader(uint modbusAddress);
+ void readModelDataBlock(uint modbusAddress, uint modelLength); //modbusAddress = model start address, model length is without header
+
+ float convertToFloatWithSSF(quint32 rawValue, quint16 sunssf);
+ quint32 convertFromFloatWithSSF(float value, quint16 sunssf);
+
+ float convertFloatValues(quint16 rawValue0, quint16 rawValue1);
+ QByteArray convertModbusRegister(const uint16_t &modbusData);
+ QBitArray convertModbusRegisterBits(const uint16_t &modbusData);
+ QByteArray convertModbusRegisters(const QVector &modbusData, int offset, int size);
+
+ QUuid writeHoldingRegister(uint registerAddress, quint16 value);
+ QUuid writeHoldingRegisters(uint registerAddress, const QVector &values);
+
+signals:
+ void connectionStateChanged(bool status);
+ void requestExecuted(const QUuid &requestId, bool success);
+
+ void foundBaseRegister(int modbusAddress);
+ void commonModelReceived(const QString &manufacturer, const QString &deviceModel, const QString &serialNumber);
+
+ void foundSunSpecModel(ModelId modelId, int modbusStartRegister);
+ void sunspecModelSearchFinished(const QHash &mapIds);
+
+ void modelHeaderReceived(uint modbusAddress, ModelId mapId, uint mapLength);
+ void modelDataBlockReceived(ModelId id, uint ength, QVector data);
+
+private slots:
+ void onModbusStateChanged(QModbusDevice::State state);
+};
+
+#endif // SUNSPEC_H
diff --git a/sunspec/sunspec.png b/sunspec/sunspec.png
new file mode 100644
index 0000000..7a52a45
Binary files /dev/null and b/sunspec/sunspec.png differ
diff --git a/sunspec/sunspec.pro b/sunspec/sunspec.pro
new file mode 100644
index 0000000..060a701
--- /dev/null
+++ b/sunspec/sunspec.pro
@@ -0,0 +1,19 @@
+include(../plugins.pri)
+
+QT += \
+ network \
+ serialbus \
+
+SOURCES += \
+ integrationpluginsunspec.cpp \
+ sunspec.cpp \
+ sunspecinverter.cpp \
+ sunspecmeter.cpp \
+ sunspecstorage.cpp
+
+HEADERS += \
+ integrationpluginsunspec.h \
+ sunspec.h \
+ sunspecinverter.h \
+ sunspecmeter.h \
+ sunspecstorage.h
diff --git a/sunspec/sunspecinverter.cpp b/sunspec/sunspecinverter.cpp
new file mode 100644
index 0000000..de61418
--- /dev/null
+++ b/sunspec/sunspecinverter.cpp
@@ -0,0 +1,172 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "sunspecinverter.h"
+#include "extern-plugininfo.h"
+
+#include
+
+SunSpecInverter::SunSpecInverter(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress) :
+ QObject(sunspec),
+ m_connection(sunspec),
+ m_id(modelId),
+ m_modelModbusStartRegister(modbusAddress)
+{
+ qCDebug(dcSunSpec()) << "SunSpecInverter: Setting up inverter";
+ connect(m_connection, &SunSpec::modelDataBlockReceived, this, &SunSpecInverter::onModelDataBlockReceived);
+}
+
+SunSpec::ModelId SunSpecInverter::modelId()
+{
+ return m_id;
+}
+
+void SunSpecInverter::init()
+{
+ qCDebug(dcSunSpec()) << "SunSpecInverter: Init";
+ m_connection->readModelHeader(m_modelModbusStartRegister);
+ connect(m_connection, &SunSpec::modelHeaderReceived, this, [this] (uint modbusAddress, SunSpec::ModelId modelId, uint length) {
+ if (modelId == m_id) {
+ qCDebug(dcSunSpec()) << "SunSpecInverter: Model Header received, modbus address:" << modbusAddress << "model Id:" << modelId << "length:" << length;
+ m_modelLength = length;
+ emit initFinished(true);
+ m_initFinishedSuccess = true;
+ }
+ });
+ QTimer::singleShot(10000, this,[this] {
+ if (!m_initFinishedSuccess) {
+ emit initFinished(false);
+ }
+ });
+}
+
+void SunSpecInverter::getInverterModelDataBlock()
+{
+ qCDebug(dcSunSpec()) << "SunSpecInverter: get inverter model data block, modbus register" << m_modelModbusStartRegister << "length" << m_modelLength;
+ m_connection->readModelDataBlock(m_modelModbusStartRegister, m_modelLength);
+}
+
+SunSpecInverter::SunSpecEvent1 SunSpecInverter::bitfieldToSunSpecEvent1(quint16 register0, quint16 register1)
+{
+ SunSpecEvent1 event1;
+ quint32 value = (static_cast(register0)<<16 | register1);
+ //qCDebug(dcSunSpec()) << "Event1" << QString::number(value, 16);
+ event1.groundFault = ((value & (0x01 << 0)) != 0);
+ event1.dcOverVoltage = ((value & (0x01 << 1)) != 0);
+ event1.acDisconnect = ((value & (0x01 << 2)) != 0);
+ event1.dcDicconnect = ((value & (0x01 << 3)) != 0);
+ event1.gridDisconnect = ((value & (0x01 << 4)) != 0);
+ event1.cabinetOpen = ((value & (0x01 << 5)) != 0);
+ event1.manualShutdown = ((value & (0x01 << 6)) != 0);
+ event1.overTemperature = ((value & (0x01 << 7)) != 0);
+ event1.overFrequency = ((value & (0x01 << 8)) != 0);
+ event1.underFrequency = ((value & (0x01 << 9)) != 0);
+ event1.acOverVolt = ((value & (0x01 << 10)) != 0);
+ event1.acUnderVolt = ((value & (0x01 << 11)) != 0);
+ event1.blownStringFuse = ((value & (0x01 << 12)) != 0);
+ event1.underTemperature = ((value & (0x01 << 13)) != 0);
+ event1.memoryLoss = ((value & (0x01 << 14)) != 0);
+ event1.hwTestFailure = ((value & (0x01 << 15)) != 0);
+ return event1;
+}
+
+void SunSpecInverter::getInverterModelHeader()
+{
+ qCDebug(dcSunSpec()) << "SunSpecInverter: get inverter model header, modbus register" << m_modelModbusStartRegister;
+ m_connection->readModelHeader(m_modelModbusStartRegister);
+}
+
+void SunSpecInverter::onModelDataBlockReceived(SunSpec::ModelId modelId, uint length, QVector data)
+{
+ Q_UNUSED(length)
+ if (modelId != m_id) {
+ return;
+ }
+ if (length < m_modelLength) {
+ qCDebug(dcSunSpec()) << "SunSpecInverter: on model data block received, model length is too short" << length;
+ return;
+ }
+ InverterData inverterData;
+
+ qCDebug(dcSunSpec()) << "SunSpecInverter: Received" << modelId;
+ switch (modelId) {
+ case SunSpec::ModelIdInverterSinglePhase:
+ case SunSpec::ModelIdInverterSplitPhase:
+ case SunSpec::ModelIdInverterThreePhase: {
+ inverterData.acCurrent= m_connection->convertToFloatWithSSF(data[Model10X::Model10XAcCurrent], data[Model10X::Model10XAmpereScaleFactor]);
+ inverterData.acPower = m_connection->convertToFloatWithSSF(data[Model10X::Model10XACPower], data[Model10X::Model10XWattScaleFactor]);
+ inverterData.lineFrequency = m_connection->convertToFloatWithSSF(data[Model10X::Model10XLineFrequency], data[Model10X::Model10XHerzScaleFactor]);
+
+ quint16 ampereScaleFactor = data[Model10X::Model10XAmpereScaleFactor];
+ inverterData.phaseACurrent = m_connection->convertToFloatWithSSF(data[Model10X::Model10XPhaseACurrent], ampereScaleFactor);
+ inverterData.phaseBCurrent = m_connection->convertToFloatWithSSF(data[Model10X::Model10XPhaseBCurrent], ampereScaleFactor);
+ inverterData.phaseCCurrent = m_connection->convertToFloatWithSSF(data[Model10X::Model10XPhaseCCurrent], ampereScaleFactor);
+ quint16 voltageScaleFactor = data[Model10X::Model10XVoltageScaleFactor];
+ inverterData.phaseVoltageAN = m_connection->convertToFloatWithSSF(data[Model10X::Model10XPhaseVoltageAN], voltageScaleFactor);
+ inverterData.phaseVoltageBN = m_connection->convertToFloatWithSSF(data[Model10X::Model10XPhaseVoltageBN], voltageScaleFactor);
+ inverterData.phaseVoltageCN = m_connection->convertToFloatWithSSF(data[Model10X::Model10XPhaseVoltageCN], voltageScaleFactor);
+
+ quint32 acEnergy = ((static_cast(data.value(Model10X::Model10XAcEnergy))<<16)|static_cast(data.value(Model10X::Model10XAcEnergy+1)));
+ inverterData.acEnergy = m_connection->convertToFloatWithSSF(acEnergy, data[Model10X::Model10XWattHoursScaleFactor]);
+
+ inverterData.cabinetTemperature = m_connection->convertToFloatWithSSF(data[Model10X::Model10XCabinetTemperature], data[Model10X::Model10XTemperatureScaleFactor]);
+ inverterData.event1 = bitfieldToSunSpecEvent1(data[Model10X::Model10XEvent1], data[Model10X::Model10XEvent1+1]);
+ inverterData.operatingState = SunSpec::SunSpecOperatingState(data[Model10X::Model10XOperatingState]);
+ emit inverterDataReceived(inverterData);
+
+ } break;
+ case SunSpec::ModelIdInverterThreePhaseFloat:
+ case SunSpec::ModelIdInverterSplitPhaseFloat:
+ case SunSpec::ModelIdInverterSinglePhaseFloat: {
+
+ inverterData.acCurrent = m_connection->convertFloatValues(data[Model11X::Model11XAcCurrent], data[Model11X::Model11XAcCurrent+1]);
+
+ inverterData.phaseVoltageAN = m_connection->convertFloatValues(data[Model11X::Model11XPhaseVoltageAN], data[Model11X::Model11XPhaseVoltageAN+1]);
+ inverterData.phaseVoltageBN = m_connection->convertFloatValues(data[Model11X::Model11XPhaseVoltageBN], data[Model11X::Model11XPhaseVoltageBN+1]);
+ inverterData.phaseVoltageCN = m_connection->convertFloatValues(data[Model11X::Model11XPhaseVoltageCN], data[Model11X::Model11XPhaseVoltageCN+1]);
+
+ inverterData.phaseACurrent = m_connection->convertFloatValues(data[Model11X::Model11XPhaseACurrent], data[Model11X::Model11XPhaseACurrent+1]);
+ inverterData.phaseBCurrent = m_connection->convertFloatValues(data[Model11X::Model11XPhaseBCurrent], data[Model11X::Model11XPhaseBCurrent+1]);
+ inverterData.phaseCCurrent = m_connection->convertFloatValues(data[Model11X::Model11XPhaseCCurrent], data[Model11X::Model11XPhaseCCurrent+1]);
+
+ inverterData.acPower = m_connection->convertFloatValues(data[Model11X::Model11XACPower], data[Model11X::Model11XACPower+1]);
+ inverterData.lineFrequency = m_connection->convertFloatValues(data[Model11X::Model11XLineFrequency], data[Model11X::Model11XLineFrequency+1]);
+
+ inverterData.acEnergy = m_connection->convertFloatValues(data[Model11X::Model11XAcEnergy], data[Model11X::Model11XAcEnergy+1]);
+ inverterData.cabinetTemperature = m_connection->convertFloatValues(data[Model11X::Model11XCabinetTemperature], data[Model11X::Model11XCabinetTemperature+1]);
+ inverterData.event1 = bitfieldToSunSpecEvent1(data[Model11X::Model11XEvent1], data[Model11X::Model11XEvent1+1]);
+ inverterData.operatingState = SunSpec::SunSpecOperatingState(data[Model11X::Model11XOperatingState]);
+ emit inverterDataReceived(inverterData);
+ } break;
+ default:
+ //ignore
+ break;
+ }
+}
diff --git a/sunspec/sunspecinverter.h b/sunspec/sunspecinverter.h
new file mode 100644
index 0000000..e668ca9
--- /dev/null
+++ b/sunspec/sunspecinverter.h
@@ -0,0 +1,187 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 SUNSPECINVERTER_H
+#define SUNSPECINVERTER_H
+
+#include
+#include "sunspec.h"
+
+class SunSpecInverter : public QObject
+{
+ Q_OBJECT
+public:
+
+ enum Model10X { // Mandatory register
+ Model10XAcCurrent = 0,
+ Model10XPhaseACurrent = 1,
+ Model10XPhaseBCurrent = 2,
+ Model10XPhaseCCurrent = 3,
+ Model10XAmpereScaleFactor = 4,
+ Model10XPhaseVoltageAN = 8,
+ Model10XPhaseVoltageBN = 9,
+ Model10XPhaseVoltageCN = 10,
+ Model10XVoltageScaleFactor = 11,
+ Model10XACPower = 12,
+ Model10XWattScaleFactor = 13,
+ Model10XLineFrequency = 14,
+ Model10XHerzScaleFactor = 15,
+ Model10XAcEnergy = 22,
+ Model10XWattHoursScaleFactor = 24,
+ Model10XCabinetTemperature = 31,
+ Model10XTemperatureScaleFactor = 35,
+ Model10XOperatingState = 36,
+ Model10XEvent1 = 38
+ };
+
+ enum Model11X { // Mandatory register
+ Model11XAcCurrent = 0,
+ Model11XPhaseACurrent = 2,
+ Model11XPhaseBCurrent = 4,
+ Model11XPhaseCCurrent = 6,
+ Model11XPhaseVoltageAN = 14,
+ Model11XPhaseVoltageBN = 16,
+ Model11XPhaseVoltageCN = 18,
+ Model11XACPower = 20,
+ Model11XLineFrequency = 22,
+ Model11XAcEnergy = 30,
+ Model11XCabinetTemperature = 38,
+ Model11XOperatingState = 46,
+ Model11XEvent1 = 48
+ };
+
+ enum Model10XOptional { // Optional register
+ Model10XPhaseVoltageAB = 5,
+ Model10XPhaseVoltageBC = 6,
+ Model10XPhaseVoltageCA = 7,
+ Model10XACApparentPower = 16,
+ Model10XACApparentPowerSF = 17,
+ Model10XACReactivePower = 18,
+ Model10XACReactivePowerSF = 19,
+ Model10XACPowerFactor = 20,
+ Model10XACPowerFactorSF = 21,
+ Model10XDCCurrent = 25,
+ Model10XDCCurrentSF = 26,
+ Model10XDCVoltage = 27,
+ Model10XDCVoltageSF = 28,
+ Model10XDCPower = 29,
+ Model10XDCPowerSF = 30,
+ Model10XHeatSinkTemperature = 32,
+ Model10XTransformerTemperature = 33,
+ Model10XOtherTemperature = 34,
+ Model10XVendorOperatingState = 37,
+ Model10XVendorEventBitfield1 = 42,
+ Model10XVendorEventBitfield2 = 44,
+ Model10XVendorEventBitfield3 = 46,
+ Model10XVendorEventBitfield4 = 48
+ };
+
+ enum Model11XOptional { // Optinal registers
+ Model11XPhaseVoltageAB = 8,
+ Model11XPhaseVoltageBC = 10,
+ Model11XPhaseVoltageCA = 12,
+ Model11XACApparentPower = 24,
+ Model11XACReactivePower = 26,
+ Model11XACPowerFactor = 28,
+ Model11XDCCurrent = 32,
+ Model11XDCVoltage = 34,
+ Model11XDCPower = 36,
+ Model11XHeatSinkTemperature = 40,
+ Model11XTransformerTemperature = 42,
+ Model11XOtherTemperature = 44,
+ Model11XVendorOperatingState = 47,
+ Model11XVendorEventBitfield1 = 52,
+ Model11XVendorEventBitfield2 = 54,
+ Model11XVendorEventBitfield3 = 56,
+ Model11XVendorEventBitfield4 = 58
+ };
+
+ struct SunSpecEvent1 {
+ bool groundFault;
+ bool dcOverVoltage;
+ bool acDisconnect;
+ bool dcDicconnect;
+ bool gridDisconnect;
+ bool cabinetOpen;
+ bool manualShutdown;
+ bool overTemperature;
+ bool overFrequency;
+ bool underFrequency;
+ bool acOverVolt;
+ bool acUnderVolt;
+ bool blownStringFuse;
+ bool underTemperature;
+ bool memoryLoss;
+ bool hwTestFailure;
+ };
+
+ struct InverterData {
+ float acCurrent; //in ampere
+ float phaseACurrent;
+ float phaseBCurrent;
+ float phaseCCurrent;
+ float phaseVoltageAB;
+ float phaseVoltageBC;
+ float phaseVoltageCA;
+ float phaseVoltageAN;
+ float phaseVoltageBN;
+ float phaseVoltageCN;
+ float acPower;
+ float lineFrequency;
+ float acEnergy;
+ float cabinetTemperature; // in degree Celsius
+ SunSpecEvent1 event1;
+ SunSpec::SunSpecOperatingState operatingState;
+ };
+
+ SunSpecInverter(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress);
+ SunSpec::ModelId modelId();
+ void init();
+ void getInverterModelDataBlock();
+
+private:
+ SunSpec *m_connection = nullptr;
+ SunSpec::ModelId m_id;
+ uint m_modelLength = 0;
+ uint m_modelModbusStartRegister = 40000;
+ bool m_initFinishedSuccess = false;
+
+ SunSpecEvent1 bitfieldToSunSpecEvent1(quint16 register0, quint16 register1);
+ void getInverterModelHeader();
+
+private slots:
+ void onModelDataBlockReceived(SunSpec::ModelId mapId, uint mapLength, QVector data);
+
+signals:
+ void initFinished(bool success);
+ void inverterDataReceived(InverterData data);
+};
+
+#endif // SUNSPECINVERTER_H
diff --git a/sunspec/sunspecmeter.cpp b/sunspec/sunspecmeter.cpp
new file mode 100644
index 0000000..a9bb23e
--- /dev/null
+++ b/sunspec/sunspecmeter.cpp
@@ -0,0 +1,152 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "sunspecmeter.h"
+#include "extern-plugininfo.h"
+
+SunSpecMeter::SunSpecMeter(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress) :
+ QObject(sunspec),
+ m_connection(sunspec),
+ m_id(modelId),
+ m_modelModbusStartRegister(modbusAddress)
+{
+ qCDebug(dcSunSpec()) << "SunSpecMeter: Setting up meter";
+ connect(m_connection, &SunSpec::modelDataBlockReceived, this, &SunSpecMeter::onModelDataBlockReceived);
+}
+
+SunSpec::ModelId SunSpecMeter::modelId()
+{
+
+ return m_id;
+}
+
+void SunSpecMeter::init()
+{
+ qCDebug(dcSunSpec()) << "SunSpecMeter: Init";
+ m_connection->readModelHeader(m_modelModbusStartRegister);
+ connect(m_connection, &SunSpec::modelHeaderReceived, this, [this] (uint modbusAddress, SunSpec::ModelId modelId, uint length) {
+ if (modelId == m_id) {
+ qCDebug(dcSunSpec()) << "SunSpecMeter: Model Header received, modbus address:" << modbusAddress << "model Id:" << modelId << "length:" << length;
+ m_modelLength = length;
+ emit initFinished(true);
+ m_initFinishedSuccess = true;
+ }
+ });
+ QTimer::singleShot(10000, this,[this] {
+ if (!m_initFinishedSuccess) {
+ emit initFinished(false);
+ }
+ });
+}
+
+void SunSpecMeter::getMeterModelDataBlock()
+{
+ qCDebug(dcSunSpec()) << "SunSpecMeter: get meter model data block, modbus register" << m_modelModbusStartRegister << "length" << m_modelLength;
+ m_connection->readModelDataBlock(m_modelModbusStartRegister, m_modelLength);
+}
+
+void SunSpecMeter::getMeterModelHeader()
+{
+ qCDebug(dcSunSpec()) << "SunSpecMeter: get meter model header, modbus register" << m_modelModbusStartRegister << "length" << m_modelLength;
+ m_connection->readModelHeader(m_modelModbusStartRegister);
+}
+
+void SunSpecMeter::onModelDataBlockReceived(SunSpec::ModelId modelId, uint length, QVector data)
+{
+ if (modelId != m_id) {
+ return;
+ }
+
+ if (length < m_modelLength) {
+ qCDebug(dcSunSpec()) << "SunSpecMeter: on model data block received, model length is too short" << length;
+ return;
+ }
+
+ qCDebug(dcSunSpec()) << "SunSpecMeter: Received" << modelId;
+ switch (modelId) {
+ case SunSpec::ModelIdSinglePhaseMeter:
+ case SunSpec::ModelIdSplitSinglePhaseMeter:
+ case SunSpec::ModelIdDeltaConnectThreePhaseMeter:
+ case SunSpec::ModelIdWyeConnectThreePhaseMeter: {
+
+ MeterData meterData;
+ quint16 currentScaleFactor = data[Model20XCurrentScaleFactor];
+ meterData.totalAcCurrent = m_connection->convertToFloatWithSSF(data[Model20XTotalAcCurrent], currentScaleFactor);
+ meterData.phaseACurrent = m_connection->convertToFloatWithSSF(data[Model20XPhaseACurrent], currentScaleFactor);
+ meterData.phaseBCurrent = m_connection->convertToFloatWithSSF(data[Model20XPhaseBCurrent], currentScaleFactor);
+ meterData.phaseCCurrent = m_connection->convertToFloatWithSSF(data[Model20XPhaseCCurrent], currentScaleFactor);
+ quint16 voltageScaleFactor = data[Model20XVoltageScaleFactor];
+ meterData.voltageLN = m_connection->convertToFloatWithSSF(data[Model20XVoltageLN], voltageScaleFactor);
+ meterData.phaseVoltageAN = m_connection->convertToFloatWithSSF(data[Model20XPhaseVoltageAN], voltageScaleFactor);
+ meterData.phaseVoltageBN = m_connection->convertToFloatWithSSF(data[Model20XPhaseVoltageBN], voltageScaleFactor);
+ meterData.phaseVoltageCN = m_connection->convertToFloatWithSSF(data[Model20XPhaseVoltageCN], voltageScaleFactor);
+ meterData.voltageLL = m_connection->convertToFloatWithSSF(data[Model20XVoltageLL], voltageScaleFactor);
+ meterData.phaseVoltageAB = m_connection->convertToFloatWithSSF(data[Model20XPhaseVoltageAB], voltageScaleFactor);
+ meterData.phaseVoltageBC = m_connection->convertToFloatWithSSF(data[Model20XPhaseVoltageBC], voltageScaleFactor);
+ meterData.phaseVoltageCA = m_connection->convertToFloatWithSSF(data[Model20XPhaseVoltageCA], voltageScaleFactor);
+ meterData.frequency = m_connection->convertToFloatWithSSF(data[Model20XFrequency], data[Model20XFrequencyScaleFactor]);
+ meterData.totalRealPower = m_connection->convertToFloatWithSSF(data[Model20XTotalRealPower], data[Model20XRealPowerScaleFactor]);
+ quint16 energyScaleFactor = data[Model20XRealEnergyScaleFactor];
+ meterData.totalRealEnergyExported = m_connection->convertToFloatWithSSF(data[Model20XTotalRealEnergyExported], energyScaleFactor);
+ meterData.totalRealEnergyImported = m_connection->convertToFloatWithSSF(data[Model20XTotalRealEnergyImported], energyScaleFactor);;
+ meterData.meterEventFlags = (static_cast(data[Model20XMeterEventFlags]) << 16) | data[Model20XMeterEventFlags+1];
+ emit meterDataReceived(meterData);
+
+ } break;
+ case SunSpec::ModelIdSinglePhaseMeterFloat:
+ case SunSpec::ModelIdSplitSinglePhaseMeterFloat:
+ case SunSpec::ModelIdDeltaConnectThreePhaseMeterFloat:
+ case SunSpec::ModelIdWyeConnectThreePhaseMeterFloat: {
+
+ MeterData meterData;
+ meterData.totalAcCurrent = m_connection->convertFloatValues(data[Model21XTotalAcCurrent], data[Model21XTotalAcCurrent+1]);
+ meterData.phaseACurrent = m_connection->convertFloatValues(data[Model21XPhaseACurrent], data[Model21XPhaseACurrent+1]);
+ meterData.phaseBCurrent = m_connection->convertFloatValues(data[Model21XPhaseBCurrent], data[Model21XPhaseBCurrent+1]);
+ meterData.phaseCCurrent = m_connection->convertFloatValues(data[Model21XPhaseCCurrent], data[Model21XPhaseCCurrent+1]);
+ meterData.voltageLN = m_connection->convertFloatValues(data[Model21XVoltageLN], data[Model21XVoltageLN+1]);
+ meterData.phaseVoltageAN = m_connection->convertFloatValues(data[Model21XPhaseVoltageAN], data[Model21XPhaseVoltageAN+1]);
+ meterData.phaseVoltageBN = m_connection->convertFloatValues(data[Model21XPhaseVoltageBN], data[Model21XPhaseVoltageBN+1]);
+ meterData.phaseVoltageCN = m_connection->convertFloatValues(data[Model21XPhaseVoltageCN], data[Model21XPhaseVoltageCN+1]);
+ meterData.voltageLL = m_connection->convertFloatValues(data[Model21XVoltageLL], data[Model21XVoltageLL+1]);
+ meterData.phaseVoltageAB = m_connection->convertFloatValues(data[Model21XPhaseVoltageAB], data[Model21XPhaseVoltageAB+1]);
+ meterData.phaseVoltageBC = m_connection->convertFloatValues(data[Model21XPhaseVoltageBC], data[Model21XPhaseVoltageBC+1]);
+ meterData.phaseVoltageCA = m_connection->convertFloatValues(data[Model21XPhaseVoltageCA], data[Model21XPhaseVoltageCA+1]);
+ meterData.frequency = m_connection->convertFloatValues(data[Model21XFrequency], data[Model21XFrequency+1]);
+ meterData.totalRealPower = m_connection->convertFloatValues(data[Model21XTotalRealPower], data[Model21XTotalRealPower+1]);
+ meterData.totalRealEnergyExported = m_connection->convertFloatValues(data[Model21XTotalRealEnergyExported], data[Model21XTotalRealEnergyExported+1]);
+ meterData.totalRealEnergyImported = m_connection->convertFloatValues(data[Model21XTotalRealEnergyImported], data[Model21XTotalRealEnergyImported+1]);
+ meterData.meterEventFlags = ((static_cast(data[Model21XMeterEventFlags]) << 16) | data[Model21XMeterEventFlags+1]);
+ emit meterDataReceived(meterData);
+
+ } break;
+ default:
+ break;
+ }
+}
diff --git a/sunspec/sunspecmeter.h b/sunspec/sunspecmeter.h
new file mode 100644
index 0000000..cc664d3
--- /dev/null
+++ b/sunspec/sunspecmeter.h
@@ -0,0 +1,167 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 SUNSPECMETER_H
+#define SUNSPECMETER_H
+
+#include
+#include "sunspec.h"
+
+class SunSpecMeter : public QObject
+{
+ Q_OBJECT
+public:
+
+ enum MeterEventFlags {
+ MeterEventPowerFailure = 2,
+ MeterEventUnderVoltage,
+ MeterEventLowPF,
+ MeterEventOverCurrent,
+ MeterEventOverVoltage,
+ MeterEventMissing_Sensor,
+ MeterEventReserved1,
+ MeterEventReserved2,
+ MeterEventReserved3,
+ MeterEventReserved4,
+ MeterEventReserved5,
+ MeterEventReserved6,
+ MeterEventReserved7,
+ MeterEventReserved8,
+ MeterEventOEM01,
+ MeterEventOEM02,
+ MeterEventOEM03,
+ MeterEventOEM04,
+ MeterEventOEM05,
+ MeterEventOEM06,
+ MeterEventOEM07,
+ MeterEventOEM08,
+ MeterEventOEM09,
+ MeterEventOEM10,
+ MeterEventOEM11,
+ MeterEventOEM12,
+ MeterEventOEM13,
+ MeterEventOEM14,
+ MeterEventOEM15
+ };
+
+ //Model 201 = Single phase meter SF
+ //Model 202 = Split phase meter SF
+ //Model 203 = Three phase meter SF
+ //Note: For example single phase inverters, Phase B current is optional then.
+ enum Model20X {
+ Model20XTotalAcCurrent = 0,
+ Model20XPhaseACurrent = 1,
+ Model20XPhaseBCurrent = 2,
+ Model20XPhaseCCurrent = 3,
+ Model20XCurrentScaleFactor = 4,
+ Model20XVoltageLN = 5,
+ Model20XPhaseVoltageAN = 6,
+ Model20XPhaseVoltageBN = 7,
+ Model20XPhaseVoltageCN = 8,
+ Model20XVoltageLL = 9,
+ Model20XPhaseVoltageAB = 10,
+ Model20XPhaseVoltageBC = 11,
+ Model20XPhaseVoltageCA = 12,
+ Model20XVoltageScaleFactor = 13,
+ Model20XFrequency = 14,
+ Model20XFrequencyScaleFactor = 15,
+ Model20XTotalRealPower = 16,
+ Model20XRealPowerScaleFactor = 20,
+ Model20XTotalRealEnergyExported = 36,
+ Model20XTotalRealEnergyImported = 44,
+ Model20XRealEnergyScaleFactor = 52,
+ Model20XMeterEventFlags = 103
+ };
+
+ //Model 211 = Single phase meter float
+ //Model 212 = Split phase meter float
+ //Model 213 = Three phase meter float
+ enum Model21X {
+ Model21XTotalAcCurrent = 0,
+ Model21XPhaseACurrent = 2,
+ Model21XPhaseBCurrent = 4,
+ Model21XPhaseCCurrent = 6,
+ Model21XVoltageLN = 8,
+ Model21XPhaseVoltageAN = 10,
+ Model21XPhaseVoltageBN = 12,
+ Model21XPhaseVoltageCN = 14,
+ Model21XVoltageLL = 16,
+ Model21XPhaseVoltageAB = 18,
+ Model21XPhaseVoltageBC = 20,
+ Model21XPhaseVoltageCA = 22,
+ Model21XFrequency = 24,
+ Model21XTotalRealPower = 26,
+ Model21XTotalRealEnergyExported = 58,
+ Model21XTotalRealEnergyImported = 66,
+ Model21XMeterEventFlags = 122
+ };
+
+ struct MeterData {
+ double totalAcCurrent; // [A]
+ double phaseACurrent; // [A]
+ double phaseBCurrent; // [A]
+ double phaseCCurrent; // [A]
+ double voltageLN; // [V]
+ double phaseVoltageAN; // [V]
+ double phaseVoltageBN; // [V]
+ double phaseVoltageCN; // [V]
+ double voltageLL; // [V]
+ double phaseVoltageAB; // [V]
+ double phaseVoltageBC; // [V]
+ double phaseVoltageCA; // [V]
+ double frequency; // [Hz]
+ double totalRealPower; // [W]
+ double totalRealEnergyExported; // [kWh]
+ double totalRealEnergyImported; // [kWh]
+ quint32 meterEventFlags; // MeterEventFlags
+ };
+
+ SunSpecMeter(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress);
+ SunSpec::ModelId modelId();
+ void init();
+ void getMeterModelHeader();
+ void getMeterModelDataBlock();
+
+private:
+ SunSpec *m_connection = nullptr;
+ SunSpec::ModelId m_id = SunSpec::ModelIdDeltaConnectThreePhaseMeter;
+ uint m_modelLength = 0;
+ uint m_modelModbusStartRegister = 40000;
+ bool m_initFinishedSuccess = false;
+
+private slots:
+ void onModelDataBlockReceived(SunSpec::ModelId modelId, uint length, QVector data);
+
+signals:
+ void initFinished(bool success);
+ void meterDataReceived(const MeterData &data);
+};
+
+#endif // SUNSPECMETER_H
diff --git a/sunspec/sunspecstorage.cpp b/sunspec/sunspecstorage.cpp
new file mode 100644
index 0000000..d5eba36
--- /dev/null
+++ b/sunspec/sunspecstorage.cpp
@@ -0,0 +1,175 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "sunspecstorage.h"
+#include "extern-plugininfo.h"
+
+SunSpecStorage::SunSpecStorage(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress) :
+ QObject(sunspec),
+ m_connection(sunspec),
+ m_id(modelId),
+ m_modelModbusStartRegister(modbusAddress)
+{
+ qCDebug(dcSunSpec()) << "SunSpecStorage: Setting up storage";
+ connect(m_connection, &SunSpec::modelDataBlockReceived, this, &SunSpecStorage::onModelDataBlockReceived);
+}
+
+SunSpec::ModelId SunSpecStorage::modelId()
+{
+ return m_id;
+}
+
+void SunSpecStorage::init()
+{
+ qCDebug(dcSunSpec()) << "SunSpecStorage: Init";
+ getStorageModelHeader();
+ connect(m_connection, &SunSpec::modelHeaderReceived, this, [this] (uint modbusAddress, SunSpec::ModelId modelId, uint length) {
+ if (modelId == m_id) {
+ qCDebug(dcSunSpec()) << "SunSpecStorager: Model header received, modbus address:" << modbusAddress << "model Id:" << modelId << "length:" << length;
+ m_modelLength = length;
+ emit initFinished(true);
+ m_initFinishedSuccess = true;
+ }
+ });
+ QTimer::singleShot(10000, this, [this] {
+ if (!m_initFinishedSuccess) {
+ emit initFinished(false);
+ }
+ });
+}
+
+void SunSpecStorage::getStorageModelDataBlock()
+{
+ qCDebug(dcSunSpec()) << "SunSpecStorage: get storage model data block, modbus register" << m_modelModbusStartRegister << "length" << m_modelLength;
+ m_connection->readModelDataBlock(m_modelModbusStartRegister, m_modelLength);
+}
+
+void SunSpecStorage::getStorageModelHeader()
+{
+ qCDebug(dcSunSpec()) << "SunSpecStorage: get storage model header, modbus register" << m_modelModbusStartRegister << "length" << m_modelLength;
+ m_connection->readModelHeader(m_modelModbusStartRegister);
+}
+
+QUuid SunSpecStorage::setGridCharging(bool enabled)
+{
+ // Name ChaGriSet
+ /* Setpoint to enable/dis-
+ able charging from grid
+ PV (charging from grid 0 disabled)
+ GRID (charging from 1 grid enabled*/
+
+ uint registerAddress = m_modelModbusStartRegister + Model124Optional::Model124ChaGriSet;
+ quint16 value = enabled;
+ return m_connection->writeHoldingRegister(registerAddress, value);
+}
+
+QUuid SunSpecStorage::setStorageControlMode(bool chargingEnabled, bool dischargingEnabled)
+{
+ // Set charge bit to enable charge limit, set discharge bit to enable discharge limit, set both bits to enable both limits
+ quint16 value = ((static_cast(chargingEnabled)) |
+ (static_cast(dischargingEnabled) << 1)) ;
+
+ uint modbusRegister = m_modelModbusStartRegister + Model124::Model124StorCtl_Mod;
+ return m_connection->writeHoldingRegister(modbusRegister, value);
+}
+
+QUuid SunSpecStorage::setChargingRate(float rate)
+{
+ if (!m_scaleFactorsSet) {
+ qCWarning(dcSunSpec()) << "SunSpecStorage: Set charging rate, scale factors are not set";
+ return "";
+ }
+ if (rate < 0.00 || rate > 100.00) {
+ qCWarning(dcSunSpec()) << "SunSpecStorage: Set charging rate, rate out of boundaries [0, 100]";
+ return "";
+ }
+ uint modbusRegister = m_modelModbusStartRegister + Model124::Model124WChaGra;
+ quint16 value = m_connection->convertFromFloatWithSSF(rate, m_WChaDisChaGra_SF);
+ return m_connection->writeHoldingRegister(modbusRegister, value);
+}
+
+QUuid SunSpecStorage::setDischargingRate(float rate)
+{
+ if (!m_scaleFactorsSet) {
+ qCWarning(dcSunSpec()) << "SunSpecStorage: Set discharging rate, scale factors are not set";
+ }
+ if (rate < 0.00 || rate > 100.00) {
+ qCWarning(dcSunSpec()) << "SunSpecStorage: Set doscharging rate, rate out of boundaries [0, 100]";
+ return "";
+ }
+ uint modbusRegister = m_modelModbusStartRegister + Model124::Model124WDisChaGra;
+ quint16 value = m_connection->convertFromFloatWithSSF(rate, m_WChaDisChaGra_SF);
+ return m_connection->writeHoldingRegister(modbusRegister, value);
+}
+
+void SunSpecStorage::onModelDataBlockReceived(SunSpec::ModelId modelId, uint length, const QVector &data)
+{
+ if (modelId != m_id) {
+ return;
+ }
+
+ if (length < m_modelLength) {
+ qCDebug(dcSunSpec()) << "SunSpecMeter: on model data block received, model length is too short" << length;
+ return;
+ }
+
+ qCDebug(dcSunSpec()) << "SunSpecStorage: Received" << modelId;
+ switch (modelId) {
+ case SunSpec::ModelIdStorage: {
+ StorageData mandatory;
+ mandatory.WChaMax = m_connection->convertToFloatWithSSF(data[Model124WChaMax], data[Model124WChaMax_SF]);
+ mandatory.WChaGra = m_connection->convertToFloatWithSSF(data[Model124WChaGra], data[Model124WChaDisChaGra_SF]);
+ mandatory.WDisChaGra = m_connection->convertToFloatWithSSF(data[Model124WDisChaGra], data[Model124WChaDisChaGra_SF]);
+ mandatory.StorCtl_Mod_ChargingEnabled = data[Model124StorCtl_Mod]&0x01;
+ mandatory.StorCtl_Mod_DischargingEnabled = data[Model124StorCtl_Mod]&0x02;
+
+ StorageDataOptional optional;
+ optional.VAChaMax = m_connection->convertToFloatWithSSF(data[Model124VAChaMax], data[Model124VAChaMax_SF]);
+ optional.MinRsvPct = m_connection->convertToFloatWithSSF(data[Model124MinRsvPct], data[Model124MinRsvPct_SF]);
+ optional.ChaState = m_connection->convertToFloatWithSSF(data[Model124ChaState], data[Model124ChaState_SF]);
+ optional.StorAval = m_connection->convertToFloatWithSSF(data[Model124StorAval], data[Model124StorAval_SF]);
+ optional.InBatV = m_connection->convertToFloatWithSSF(data[Model124InBatV], data[Model124InBatV_SF]);
+ optional.ChaSt = ChargingStatus(data[Model124ChaSt]);
+ optional.OutWRte = m_connection->convertToFloatWithSSF(data[Model124OutWRte], data[Model124InOutWRte_SF]);
+ optional.InWRte = m_connection->convertToFloatWithSSF(data[Model124InWRte], data[Model124InOutWRte_SF]);
+ optional.InOutWRte_WinTms = data[Model124InOutWRte_WinTms];
+ optional.InOutWRte_RvrtTms = data[Model124InOutWRte_RvrtTms];
+ optional.InOutWRte_RmpTms = data[Model124InOutWRte_RmpTms];
+ optional.ChaGriSet = GridCharge(data[Model124ChaGriSet]);
+ emit storageDataReceived(mandatory, optional);
+ } break;
+ case SunSpec::ModelIdBatteryBaseModel:
+ case SunSpec::ModelIdLithiumIonBatteryModel: {
+ qCDebug(dcSunSpec()) << "Model not yet supported";
+ }
+ default:
+ break;
+ }
+}
diff --git a/sunspec/sunspecstorage.h b/sunspec/sunspecstorage.h
new file mode 100644
index 0000000..4995753
--- /dev/null
+++ b/sunspec/sunspecstorage.h
@@ -0,0 +1,154 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 SUNSPECSTORAGE_H
+#define SUNSPECSTORAGE_H
+
+#include
+#include "sunspec.h"
+
+class SunSpecStorage : public QObject
+{
+ Q_OBJECT
+public:
+ SunSpecStorage(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress);
+
+ SunSpec::ModelId modelId();
+ void init();
+ void getStorageModelHeader();
+ void getStorageModelDataBlock();
+
+ QUuid setGridCharging(bool enabled);
+ QUuid setDischargingRate(float rate);
+ QUuid setChargingRate(float rate);
+ QUuid setStorageControlMode(bool chargingEnabled, bool dischargingEnabled);
+
+ enum StorageControl {
+ StorageControlHold = 0,
+ StorageControlCharge = 1,
+ StorageControlDischarge = 2,
+ };
+ Q_ENUM(StorageControl)
+
+ enum GridCharge {
+ PV = 0,
+ Grid = 1
+ };
+ Q_ENUM(GridCharge)
+
+ enum ChargingStatus {
+ ChargingStatusOff = 1,
+ ChargingStatusEmpty,
+ ChargingStatusDischarging,
+ ChargingStatusCharging,
+ ChargingStatusFull,
+ ChargingStatusHolding,
+ ChargingStatusTesting
+ };
+ Q_ENUM(ChargingStatus)
+
+ enum Model124 { // Mandatory registers
+ Model124WChaMax = 0,
+ Model124WChaGra = 1,
+ Model124WDisChaGra = 2,
+ Model124StorCtl_Mod = 3,
+ Model124WChaMax_SF = 16,
+ Model124WChaDisChaGra_SF = 17,
+ };
+
+ enum Model124Optional { // Optional registers
+ Model124VAChaMax = 4,
+ Model124MinRsvPct = 5,
+ Model124ChaState = 6,
+ Model124StorAval = 7,
+ Model124InBatV = 8,
+ Model124ChaSt = 9,
+ Model124OutWRte = 10,
+ Model124InWRte = 11,
+ Model124InOutWRte_WinTms = 12,
+ Model124InOutWRte_RvrtTms = 13,
+ Model124InOutWRte_RmpTms = 14,
+ Model124ChaGriSet = 15,
+ Model124VAChaMax_SF = 18,
+ Model124MinRsvPct_SF = 19,
+ Model124ChaState_SF = 20,
+ Model124StorAval_SF = 21,
+ Model124InBatV_SF = 22,
+ Model124InOutWRte_SF = 23
+ };
+ Q_ENUM(Model124Optional)
+
+ struct StorageData {
+ double WChaMax; // [W] Setpoint for maximum charge.
+ double WChaGra; // [%] Setpoint for maximum charging rate. Default is MaxChaRte.
+ double WDisChaGra; // [%] Setpoint for maximum discharge rate. Default is MaxDisChaRte.
+ bool StorCtl_Mod_ChargingEnabled;
+ bool StorCtl_Mod_DischargingEnabled;
+ };
+
+ struct StorageDataOptional {
+ double VAChaMax; // [VA] Setpoint for maximum charging VA.
+ double MinRsvPct; // [%]Setpoint for minimum reserve for storage as a percentage of the nominal maximum storage.
+ double ChaState; // [%] Currently available energy as a percent of the capacity rating.
+ double StorAval; // [Ah] State of charge (ChaState) minus storage reserve (MinRsvPct) times capacity rating (AhrRtg).
+ double InBatV; // [V] Internal battery voltage.
+ ChargingStatus ChaSt; // Charge status of storage device. Enumerated value.
+ double OutWRte; // [%] Percent of max discharge rate.
+ double InWRte; // [%] Percent of max charging rate.
+ uint InOutWRte_WinTms; // [s] Time window for charge/discharge rate change.
+ uint InOutWRte_RvrtTms; // [s] Timeout period for charge/discharge rate.
+ uint InOutWRte_RmpTms; // [s] Ramp time for moving from current setpoint to new setpoint.
+ GridCharge ChaGriSet; // 0 = PV, 1 = Grid
+ };
+
+private:
+ SunSpec *m_connection = nullptr;
+ SunSpec::ModelId m_id = SunSpec::ModelIdEnergyStorageBaseModel;
+ uint m_modelLength = 0;
+ uint m_modelModbusStartRegister = 40000;
+ bool m_initFinishedSuccess = false;
+
+ // Scale factors needed to perform write requests
+ bool m_scaleFactorsSet = false;
+ quint16 m_WChaMax_SF = 0;
+ quint16 m_WChaDisChaGra_SF = 0;
+ quint16 m_VAChaMax_SF = 0;
+ quint16 m_MinRsvPct_SF = 0;
+ quint16 m_InOutWRte_SF = 0;
+
+private slots:
+ void onModelDataBlockReceived(SunSpec::ModelId modelId, uint length, const QVector &data);
+
+signals:
+ void initFinished(bool success);
+ void storageDataReceived(const StorageData &mandatory, const StorageDataOptional &optional);
+};
+
+#endif // SUNSPECSTORAGE_H
diff --git a/sunspec/test/sunspec_server.sh b/sunspec/test/sunspec_server.sh
new file mode 100755
index 0000000..fb09118
--- /dev/null
+++ b/sunspec/test/sunspec_server.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+if ! command -v suns &> /dev/null
+then
+ echo "suns could not be found"
+ echo "... installing"
+ sudo apt update
+ sudo apt install libmodbus-dev flex bison git
+
+ if [ ! -d "sunspec"]; then
+ git clone https://github.com/Boernsman/sunspec.git
+ fi
+ cd ./sunspec/src
+ make
+ sudo make install
+fi
+
+echo "Startin sunspec test server"
+sudo suns -s -vvvv -m models/test/composite_superdevice.model
diff --git a/sunspec/translations/cb4bdec6-cf2c-4a0f-9709-42d951ca2d8b-en_US.ts b/sunspec/translations/cb4bdec6-cf2c-4a0f-9709-42d951ca2d8b-en_US.ts
new file mode 100644
index 0000000..b27ce33
--- /dev/null
+++ b/sunspec/translations/cb4bdec6-cf2c-4a0f-9709-42d951ca2d8b-en_US.ts
@@ -0,0 +1,1126 @@
+
+
+
+
+ IntegrationPluginSunSpec
+
+
+ single phase inverter
+
+
+
+
+ split phase inverter
+
+
+
+
+ three phase inverter
+
+
+
+
+
+ meter
+
+
+
+
+ SunSpec
+
+
+
+
+
+
+
+ AC energy
+ The name of the ParamType (ThingClass: sunspecThreePhaseInverter, EventType: totalEnergyProduced, ID: {d493880d-eb58-4530-8010-8ea4f6d63387})
+----------
+The name of the StateType ({d493880d-eb58-4530-8010-8ea4f6d63387}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseInverter, EventType: totalEnergyProduced, ID: {fe3f8a65-121a-4ae1-b22a-ae325dc3e7e6})
+----------
+The name of the StateType ({fe3f8a65-121a-4ae1-b22a-ae325dc3e7e6}) of ThingClass sunspecSplitPhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseInverter, EventType: totalEnergyProduced, ID: {4c0407b3-5cd5-438d-bfa8-9a8d6695b458})
+----------
+The name of the StateType ({4c0407b3-5cd5-438d-bfa8-9a8d6695b458}) of ThingClass sunspecSinglePhaseInverter
+
+
+
+
+
+
+ AC energy changed
+ The name of the EventType ({d493880d-eb58-4530-8010-8ea4f6d63387}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the EventType ({fe3f8a65-121a-4ae1-b22a-ae325dc3e7e6}) of ThingClass sunspecSplitPhaseInverter
+----------
+The name of the EventType ({4c0407b3-5cd5-438d-bfa8-9a8d6695b458}) of ThingClass sunspecSinglePhaseInverter
+
+
+
+
+
+
+
+
+
+ AC power
+ The name of the ParamType (ThingClass: sunspecThreePhaseInverter, EventType: currentPower, ID: {14036f44-25fd-4e93-8e8c-c677b06a2c34})
+----------
+The name of the StateType ({14036f44-25fd-4e93-8e8c-c677b06a2c34}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseInverter, EventType: currentPower, ID: {9235eb4b-906c-4557-8e18-bca268a367cc})
+----------
+The name of the StateType ({9235eb4b-906c-4557-8e18-bca268a367cc}) of ThingClass sunspecSplitPhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseInverter, EventType: currentPower, ID: {f49591d6-d759-4be3-bafc-b6a7a72cf023})
+----------
+The name of the StateType ({f49591d6-d759-4be3-bafc-b6a7a72cf023}) of ThingClass sunspecSinglePhaseInverter
+
+
+
+
+
+
+ AC power changed
+ The name of the EventType ({14036f44-25fd-4e93-8e8c-c677b06a2c34}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the EventType ({9235eb4b-906c-4557-8e18-bca268a367cc}) of ThingClass sunspecSplitPhaseInverter
+----------
+The name of the EventType ({f49591d6-d759-4be3-bafc-b6a7a72cf023}) of ThingClass sunspecSinglePhaseInverter
+
+
+
+
+
+ Battery critical
+ The name of the ParamType (ThingClass: sunspecStorage, EventType: batteryCritical, ID: {3171a6e0-43a7-4de8-8e20-f748e44af7ac})
+----------
+The name of the StateType ({3171a6e0-43a7-4de8-8e20-f748e44af7ac}) of ThingClass sunspecStorage
+
+
+
+
+ Battery critical changed
+ The name of the EventType ({3171a6e0-43a7-4de8-8e20-f748e44af7ac}) of ThingClass sunspecStorage
+
+
+
+
+
+ Battery level
+ The name of the ParamType (ThingClass: sunspecStorage, EventType: batteryLevel, ID: {0bf53f80-97f8-488b-b514-58f9fe08c183})
+----------
+The name of the StateType ({0bf53f80-97f8-488b-b514-58f9fe08c183}) of ThingClass sunspecStorage
+
+
+
+
+ Battery level changed
+ The name of the EventType ({0bf53f80-97f8-488b-b514-58f9fe08c183}) of ThingClass sunspecStorage
+
+
+
+
+
+
+
+
+
+ Cabinet temperature
+ The name of the ParamType (ThingClass: sunspecThreePhaseInverter, EventType: cabinetTemperature, ID: {44b0320f-89a7-4248-bad4-288ef898a5cc})
+----------
+The name of the StateType ({44b0320f-89a7-4248-bad4-288ef898a5cc}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseInverter, EventType: cabinetTemperature, ID: {6d314d50-b990-4a58-a37f-4a3da42c4407})
+----------
+The name of the StateType ({6d314d50-b990-4a58-a37f-4a3da42c4407}) of ThingClass sunspecSplitPhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseInverter, EventType: cabinetTemperature, ID: {51461bde-1a6b-4aa1-94cc-59829ea0a7c8})
+----------
+The name of the StateType ({51461bde-1a6b-4aa1-94cc-59829ea0a7c8}) of ThingClass sunspecSinglePhaseInverter
+
+
+
+
+
+
+ Cabinet temperature changed
+ The name of the EventType ({44b0320f-89a7-4248-bad4-288ef898a5cc}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the EventType ({6d314d50-b990-4a58-a37f-4a3da42c4407}) of ThingClass sunspecSplitPhaseInverter
+----------
+The name of the EventType ({51461bde-1a6b-4aa1-94cc-59829ea0a7c8}) of ThingClass sunspecSinglePhaseInverter
+
+
+
+
+
+
+ Charging
+ The name of the ParamType (ThingClass: sunspecStorage, ActionType: enableCharging, ID: {1f530f79-c0d2-466b-90e1-79149e34d92f})
+----------
+The name of the ParamType (ThingClass: sunspecStorage, EventType: enableCharging, ID: {1f530f79-c0d2-466b-90e1-79149e34d92f})
+----------
+The name of the StateType ({1f530f79-c0d2-466b-90e1-79149e34d92f}) of ThingClass sunspecStorage
+
+
+
+
+ Charging changed
+ The name of the EventType ({1f530f79-c0d2-466b-90e1-79149e34d92f}) of ThingClass sunspecStorage
+
+
+
+
+
+
+ Charging rate
+ The name of the ParamType (ThingClass: sunspecStorage, ActionType: chargingRate, ID: {7f469bbc-64a5-4045-8d5f-9a1a85039851})
+----------
+The name of the ParamType (ThingClass: sunspecStorage, EventType: chargingRate, ID: {7f469bbc-64a5-4045-8d5f-9a1a85039851})
+----------
+The name of the StateType ({7f469bbc-64a5-4045-8d5f-9a1a85039851}) of ThingClass sunspecStorage
+
+
+
+
+ Charging rate changed
+ The name of the EventType ({7f469bbc-64a5-4045-8d5f-9a1a85039851}) of ThingClass sunspecStorage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Connected
+ The name of the ParamType (ThingClass: sunspecStorage, EventType: connected, ID: {25a1fb10-a6b9-4037-b7cf-ad481a65beb4})
+----------
+The name of the StateType ({25a1fb10-a6b9-4037-b7cf-ad481a65beb4}) of ThingClass sunspecStorage
+----------
+The name of the ParamType (ThingClass: sunspecThreePhaseMeter, EventType: connected, ID: {36f861c7-afc1-4725-b41f-67113200d78f})
+----------
+The name of the StateType ({36f861c7-afc1-4725-b41f-67113200d78f}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseMeter, EventType: connected, ID: {34e34ec9-dab0-438c-9493-a3068bc401de})
+----------
+The name of the StateType ({34e34ec9-dab0-438c-9493-a3068bc401de}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseMeter, EventType: connected, ID: {d960e7b1-d4aa-4cab-8f54-6bcfdbb8be36})
+----------
+The name of the StateType ({d960e7b1-d4aa-4cab-8f54-6bcfdbb8be36}) of ThingClass sunspecSinglePhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecThreePhaseInverter, EventType: connected, ID: {4401468c-0385-40a9-b436-daf7ed6a50d5})
+----------
+The name of the StateType ({4401468c-0385-40a9-b436-daf7ed6a50d5}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseInverter, EventType: connected, ID: {27b49640-f58b-466e-a225-a4663cf3ed96})
+----------
+The name of the StateType ({27b49640-f58b-466e-a225-a4663cf3ed96}) of ThingClass sunspecSplitPhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseInverter, EventType: connected, ID: {48bf31c4-fda7-41e5-a3ef-3011bf96e104})
+----------
+The name of the StateType ({48bf31c4-fda7-41e5-a3ef-3011bf96e104}) of ThingClass sunspecSinglePhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecConnection, EventType: connected, ID: {3e767ad0-b4b3-4398-94c1-00579ea09ca8})
+----------
+The name of the StateType ({3e767ad0-b4b3-4398-94c1-00579ea09ca8}) of ThingClass sunspecConnection
+
+
+
+
+
+
+
+
+
+
+
+ Connected changed
+ The name of the EventType ({25a1fb10-a6b9-4037-b7cf-ad481a65beb4}) of ThingClass sunspecStorage
+----------
+The name of the EventType ({36f861c7-afc1-4725-b41f-67113200d78f}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the EventType ({34e34ec9-dab0-438c-9493-a3068bc401de}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the EventType ({d960e7b1-d4aa-4cab-8f54-6bcfdbb8be36}) of ThingClass sunspecSinglePhaseMeter
+----------
+The name of the EventType ({4401468c-0385-40a9-b436-daf7ed6a50d5}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the EventType ({27b49640-f58b-466e-a225-a4663cf3ed96}) of ThingClass sunspecSplitPhaseInverter
+----------
+The name of the EventType ({48bf31c4-fda7-41e5-a3ef-3011bf96e104}) of ThingClass sunspecSinglePhaseInverter
+----------
+The name of the EventType ({3e767ad0-b4b3-4398-94c1-00579ea09ca8}) of ThingClass sunspecConnection
+
+
+
+
+
+ Device model
+ The name of the ParamType (ThingClass: sunspecConnection, EventType: deviceModel, ID: {58146c26-17d3-458e-a13f-d7f306c20c44})
+----------
+The name of the StateType ({58146c26-17d3-458e-a13f-d7f306c20c44}) of ThingClass sunspecConnection
+
+
+
+
+ Device model changed
+ The name of the EventType ({58146c26-17d3-458e-a13f-d7f306c20c44}) of ThingClass sunspecConnection
+
+
+
+
+
+
+ Discharging
+ The name of the ParamType (ThingClass: sunspecStorage, ActionType: enableDischarging, ID: {bc99a159-815a-40ab-a6e8-b46f315305f7})
+----------
+The name of the ParamType (ThingClass: sunspecStorage, EventType: enableDischarging, ID: {bc99a159-815a-40ab-a6e8-b46f315305f7})
+----------
+The name of the StateType ({bc99a159-815a-40ab-a6e8-b46f315305f7}) of ThingClass sunspecStorage
+
+
+
+
+ Discharging changed
+ The name of the EventType ({bc99a159-815a-40ab-a6e8-b46f315305f7}) of ThingClass sunspecStorage
+
+
+
+
+
+
+ Discharging rate
+ The name of the ParamType (ThingClass: sunspecStorage, ActionType: dischargingRate, ID: {6068f030-acce-44a2-b95f-bd00dd5ca760})
+----------
+The name of the ParamType (ThingClass: sunspecStorage, EventType: dischargingRate, ID: {6068f030-acce-44a2-b95f-bd00dd5ca760})
+----------
+The name of the StateType ({6068f030-acce-44a2-b95f-bd00dd5ca760}) of ThingClass sunspecStorage
+
+
+
+
+ Discharging rate changed
+ The name of the EventType ({6068f030-acce-44a2-b95f-bd00dd5ca760}) of ThingClass sunspecStorage
+
+
+
+
+ Enable charging
+ The name of the ActionType ({1f530f79-c0d2-466b-90e1-79149e34d92f}) of ThingClass sunspecStorage
+
+
+
+
+ Enable discharging
+ The name of the ActionType ({bc99a159-815a-40ab-a6e8-b46f315305f7}) of ThingClass sunspecStorage
+
+
+
+
+
+
+
+
+
+ Error
+ The name of the ParamType (ThingClass: sunspecThreePhaseInverter, EventType: error, ID: {4479af96-c499-4f15-abd6-4afdb18a9e09})
+----------
+The name of the StateType ({4479af96-c499-4f15-abd6-4afdb18a9e09}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseInverter, EventType: error, ID: {5cbfccc9-6afb-404c-a85e-e0323659a25f})
+----------
+The name of the StateType ({5cbfccc9-6afb-404c-a85e-e0323659a25f}) of ThingClass sunspecSplitPhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseInverter, EventType: error, ID: {49240259-d82a-4fe6-b3f5-1cd6a67c87a7})
+----------
+The name of the StateType ({49240259-d82a-4fe6-b3f5-1cd6a67c87a7}) of ThingClass sunspecSinglePhaseInverter
+
+
+
+
+
+
+ Error changed
+ The name of the EventType ({4479af96-c499-4f15-abd6-4afdb18a9e09}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the EventType ({5cbfccc9-6afb-404c-a85e-e0323659a25f}) of ThingClass sunspecSplitPhaseInverter
+----------
+The name of the EventType ({49240259-d82a-4fe6-b3f5-1cd6a67c87a7}) of ThingClass sunspecSinglePhaseInverter
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Frequency
+ The name of the ParamType (ThingClass: sunspecThreePhaseMeter, EventType: frequency, ID: {dfcf52f5-6279-4d25-b7c8-a93b92c39a0c})
+----------
+The name of the StateType ({dfcf52f5-6279-4d25-b7c8-a93b92c39a0c}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseMeter, EventType: frequency, ID: {db977c04-a3e1-436f-a0cd-9ce5b7bc6b89})
+----------
+The name of the StateType ({db977c04-a3e1-436f-a0cd-9ce5b7bc6b89}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseMeter, EventType: frequency, ID: {3a2ce51d-7fa0-4188-bbd6-00d25de90e15})
+----------
+The name of the StateType ({3a2ce51d-7fa0-4188-bbd6-00d25de90e15}) of ThingClass sunspecSinglePhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecThreePhaseInverter, EventType: frequency, ID: {faa45cae-ed28-4150-9036-fceddf9d6776})
+----------
+The name of the StateType ({faa45cae-ed28-4150-9036-fceddf9d6776}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseInverter, EventType: frequency, ID: {611df2ce-2b9c-49f3-9fa7-5706776e812c})
+----------
+The name of the StateType ({611df2ce-2b9c-49f3-9fa7-5706776e812c}) of ThingClass sunspecSinglePhaseInverter
+
+
+
+
+
+
+
+
+ Frequency changed
+ The name of the EventType ({dfcf52f5-6279-4d25-b7c8-a93b92c39a0c}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the EventType ({db977c04-a3e1-436f-a0cd-9ce5b7bc6b89}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the EventType ({3a2ce51d-7fa0-4188-bbd6-00d25de90e15}) of ThingClass sunspecSinglePhaseMeter
+----------
+The name of the EventType ({faa45cae-ed28-4150-9036-fceddf9d6776}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the EventType ({611df2ce-2b9c-49f3-9fa7-5706776e812c}) of ThingClass sunspecSinglePhaseInverter
+
+
+
+
+
+
+ Grid charging
+ The name of the ParamType (ThingClass: sunspecStorage, ActionType: gridCharging, ID: {221a2ef6-0a92-4ff0-87fe-7bd920dbec0b})
+----------
+The name of the ParamType (ThingClass: sunspecStorage, EventType: gridCharging, ID: {221a2ef6-0a92-4ff0-87fe-7bd920dbec0b})
+----------
+The name of the StateType ({221a2ef6-0a92-4ff0-87fe-7bd920dbec0b}) of ThingClass sunspecStorage
+
+
+
+
+ Grid charging changed
+ The name of the EventType ({221a2ef6-0a92-4ff0-87fe-7bd920dbec0b}) of ThingClass sunspecStorage
+
+
+
+
+ IP address
+ The name of the ParamType (ThingClass: sunspecConnection, Type: thing, ID: {6be6abc4-e2b2-4687-9343-0e5164ed0ab2})
+
+
+
+
+
+ Line frequency
+ The name of the ParamType (ThingClass: sunspecSplitPhaseInverter, EventType: frequency, ID: {874f5e4a-a009-4c28-b211-2af90a24b2ac})
+----------
+The name of the StateType ({874f5e4a-a009-4c28-b211-2af90a24b2ac}) of ThingClass sunspecSplitPhaseInverter
+
+
+
+
+ Line frequency changed
+ The name of the EventType ({874f5e4a-a009-4c28-b211-2af90a24b2ac}) of ThingClass sunspecSplitPhaseInverter
+
+
+
+
+
+
+
+
+
+ Line to Neutral AC Voltage
+ The name of the ParamType (ThingClass: sunspecThreePhaseMeter, EventType: lnACVoltage, ID: {bc54ca1e-1476-40eb-9974-9e3c2f893dd8})
+----------
+The name of the StateType ({bc54ca1e-1476-40eb-9974-9e3c2f893dd8}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseMeter, EventType: lnACVoltage, ID: {0ac79508-07c3-4d01-97a3-6edf121bdf32})
+----------
+The name of the StateType ({0ac79508-07c3-4d01-97a3-6edf121bdf32}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseMeter, EventType: lnACVoltage, ID: {408e9c41-cfbf-456b-a9c2-b4adfde4a5b0})
+----------
+The name of the StateType ({408e9c41-cfbf-456b-a9c2-b4adfde4a5b0}) of ThingClass sunspecSinglePhaseMeter
+
+
+
+
+
+
+ Line to Neutral AC Voltage changed
+ The name of the EventType ({bc54ca1e-1476-40eb-9974-9e3c2f893dd8}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the EventType ({0ac79508-07c3-4d01-97a3-6edf121bdf32}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the EventType ({408e9c41-cfbf-456b-a9c2-b4adfde4a5b0}) of ThingClass sunspecSinglePhaseMeter
+
+
+
+
+
+ Manufacturer
+ The name of the ParamType (ThingClass: sunspecConnection, EventType: manufacturer, ID: {04970315-ed3a-45ce-98fc-35ae3c4eb27b})
+----------
+The name of the StateType ({04970315-ed3a-45ce-98fc-35ae3c4eb27b}) of ThingClass sunspecConnection
+
+
+
+
+ Manufacturer changed
+ The name of the EventType ({04970315-ed3a-45ce-98fc-35ae3c4eb27b}) of ThingClass sunspecConnection
+
+
+
+
+
+
+
+
+
+
+ Modbus address
+ The name of the ParamType (ThingClass: sunspecStorage, Type: thing, ID: {3f107844-00c5-4f39-86e5-485b3d1f5c1a})
+----------
+The name of the ParamType (ThingClass: sunspecThreePhaseMeter, Type: thing, ID: {6d5dbd35-1bf6-46db-bee9-90c679421b89})
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseMeter, Type: thing, ID: {a56f198d-ed86-429f-b839-8e11a32da8c1})
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseMeter, Type: thing, ID: {30b90ec0-429b-4e6c-88e9-155aa4bcad47})
+----------
+The name of the ParamType (ThingClass: sunspecThreePhaseInverter, Type: thing, ID: {e5465ede-9d3d-4558-b614-40dda743ddae})
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseInverter, Type: thing, ID: {37582a96-f2f2-4845-abef-973c7dd0ad57})
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseInverter, Type: thing, ID: {26ae9050-7090-453a-85a3-307bfebe6fed})
+
+
+
+
+
+
+
+
+
+
+ Model
+ The name of the ParamType (ThingClass: sunspecStorage, Type: thing, ID: {219beb96-b9fe-4dd2-a386-ecfbbab8786d})
+----------
+The name of the ParamType (ThingClass: sunspecThreePhaseMeter, Type: thing, ID: {a1960821-155c-4176-86fa-974429039182})
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseMeter, Type: thing, ID: {89aeec6d-abeb-48b5-9594-214ad5db2d03})
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseMeter, Type: thing, ID: {7d6fcafb-c62e-4a21-aae2-f4041c487149})
+----------
+The name of the ParamType (ThingClass: sunspecThreePhaseInverter, Type: thing, ID: {8d5b2b58-ce46-406d-844e-f53136afcf09})
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseInverter, Type: thing, ID: {c42fb50e-210f-4b53-88eb-fa216e15f88f})
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseInverter, Type: thing, ID: {41715d00-a947-4f43-a475-cea05790e01d})
+
+
+
+
+ Number of retries
+ The name of the ParamType (ThingClass: sunSpec, Type: plugin, ID: {9a4bfe01-315f-4ee7-98a9-f16b08ba12ad})
+
+
+
+
+
+
+
+
+
+ Operating state
+ The name of the ParamType (ThingClass: sunspecThreePhaseInverter, EventType: operatingState, ID: {cebdce98-42d1-4a28-8834-8960efc0e83f})
+----------
+The name of the StateType ({cebdce98-42d1-4a28-8834-8960efc0e83f}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseInverter, EventType: operatingState, ID: {6c1e2929-bc9a-4ce9-a405-6df2633a5131})
+----------
+The name of the StateType ({6c1e2929-bc9a-4ce9-a405-6df2633a5131}) of ThingClass sunspecSplitPhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseInverter, EventType: operatingState, ID: {47543a7f-425f-406b-a458-b79c36b65f6c})
+----------
+The name of the StateType ({47543a7f-425f-406b-a458-b79c36b65f6c}) of ThingClass sunspecSinglePhaseInverter
+
+
+
+
+
+
+ Operating state changed
+ The name of the EventType ({cebdce98-42d1-4a28-8834-8960efc0e83f}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the EventType ({6c1e2929-bc9a-4ce9-a405-6df2633a5131}) of ThingClass sunspecSplitPhaseInverter
+----------
+The name of the EventType ({47543a7f-425f-406b-a458-b79c36b65f6c}) of ThingClass sunspecSinglePhaseInverter
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Phase A current
+ The name of the ParamType (ThingClass: sunspecThreePhaseMeter, EventType: phaseACurrent, ID: {da494d99-5de3-4d03-b7dd-33a33db32164})
+----------
+The name of the StateType ({da494d99-5de3-4d03-b7dd-33a33db32164}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseMeter, EventType: phaseACurrent, ID: {e8c0f4bf-a704-46f2-80a0-cf490bd7871b})
+----------
+The name of the StateType ({e8c0f4bf-a704-46f2-80a0-cf490bd7871b}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseMeter, EventType: phaseACurrent, ID: {4a058e36-0b45-4388-9a26-0615f7aafa0d})
+----------
+The name of the StateType ({4a058e36-0b45-4388-9a26-0615f7aafa0d}) of ThingClass sunspecSinglePhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecThreePhaseInverter, EventType: phaseACurrent, ID: {3140ccd3-40cf-46c8-8bb2-8c3ea4582f84})
+----------
+The name of the StateType ({3140ccd3-40cf-46c8-8bb2-8c3ea4582f84}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseInverter, EventType: phaseACurrent, ID: {be7b86b4-aeeb-49ba-9b6b-9792dceed6b5})
+----------
+The name of the StateType ({be7b86b4-aeeb-49ba-9b6b-9792dceed6b5}) of ThingClass sunspecSplitPhaseInverter
+
+
+
+
+
+
+
+
+ Phase A current changed
+ The name of the EventType ({da494d99-5de3-4d03-b7dd-33a33db32164}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the EventType ({e8c0f4bf-a704-46f2-80a0-cf490bd7871b}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the EventType ({4a058e36-0b45-4388-9a26-0615f7aafa0d}) of ThingClass sunspecSinglePhaseMeter
+----------
+The name of the EventType ({3140ccd3-40cf-46c8-8bb2-8c3ea4582f84}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the EventType ({be7b86b4-aeeb-49ba-9b6b-9792dceed6b5}) of ThingClass sunspecSplitPhaseInverter
+
+
+
+
+
+
+
+
+ Phase AN volatage changed
+ The name of the EventType ({4bd32d91-877a-4821-9db9-5981de20d21d}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the EventType ({1bd7e53e-abf8-4d62-b87c-2c84c283567b}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the EventType ({8bfb8021-1b2e-4693-984c-0580f5665806}) of ThingClass sunspecSinglePhaseMeter
+----------
+The name of the EventType ({f08521aa-9c38-4c31-95e1-acb616f6e9c6}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the EventType ({17c24cfc-cb41-4873-82b4-19a20d6be146}) of ThingClass sunspecSplitPhaseInverter
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Phase AN voltage
+ The name of the ParamType (ThingClass: sunspecThreePhaseMeter, EventType: phaseANVoltage, ID: {4bd32d91-877a-4821-9db9-5981de20d21d})
+----------
+The name of the StateType ({4bd32d91-877a-4821-9db9-5981de20d21d}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseMeter, EventType: phaseANVoltage, ID: {1bd7e53e-abf8-4d62-b87c-2c84c283567b})
+----------
+The name of the StateType ({1bd7e53e-abf8-4d62-b87c-2c84c283567b}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseMeter, EventType: phaseANVoltage, ID: {8bfb8021-1b2e-4693-984c-0580f5665806})
+----------
+The name of the StateType ({8bfb8021-1b2e-4693-984c-0580f5665806}) of ThingClass sunspecSinglePhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecThreePhaseInverter, EventType: phaseANVoltage, ID: {f08521aa-9c38-4c31-95e1-acb616f6e9c6})
+----------
+The name of the StateType ({f08521aa-9c38-4c31-95e1-acb616f6e9c6}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseInverter, EventType: phaseANVoltage, ID: {17c24cfc-cb41-4873-82b4-19a20d6be146})
+----------
+The name of the StateType ({17c24cfc-cb41-4873-82b4-19a20d6be146}) of ThingClass sunspecSplitPhaseInverter
+
+
+
+
+
+
+
+
+
+
+
+ Phase B current
+ The name of the ParamType (ThingClass: sunspecThreePhaseMeter, EventType: phaseBCurrent, ID: {023b6e5c-be3f-4d8c-adfd-e009c7bebffb})
+----------
+The name of the StateType ({023b6e5c-be3f-4d8c-adfd-e009c7bebffb}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseMeter, EventType: phaseBCurrent, ID: {4281f6fc-d5a0-4a22-ac61-6bec88efbc80})
+----------
+The name of the StateType ({4281f6fc-d5a0-4a22-ac61-6bec88efbc80}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecThreePhaseInverter, EventType: phaseBCurrent, ID: {7ea1a53a-6fd9-4914-8283-b57aa1aaaebf})
+----------
+The name of the StateType ({7ea1a53a-6fd9-4914-8283-b57aa1aaaebf}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseInverter, EventType: phaseBCurrent, ID: {fc5df18d-cf2f-4944-97b7-e57dabef8778})
+----------
+The name of the StateType ({fc5df18d-cf2f-4944-97b7-e57dabef8778}) of ThingClass sunspecSplitPhaseInverter
+
+
+
+
+
+
+
+ Phase B current changed
+ The name of the EventType ({023b6e5c-be3f-4d8c-adfd-e009c7bebffb}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the EventType ({4281f6fc-d5a0-4a22-ac61-6bec88efbc80}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the EventType ({7ea1a53a-6fd9-4914-8283-b57aa1aaaebf}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the EventType ({fc5df18d-cf2f-4944-97b7-e57dabef8778}) of ThingClass sunspecSplitPhaseInverter
+
+
+
+
+
+
+
+
+
+
+
+ Phase BN voltage
+ The name of the ParamType (ThingClass: sunspecThreePhaseMeter, EventType: phaseBNVoltage, ID: {f090cb78-d7ed-44fd-a5ad-ea9016021c34})
+----------
+The name of the StateType ({f090cb78-d7ed-44fd-a5ad-ea9016021c34}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseMeter, EventType: phaseBNVoltage, ID: {377b5279-ddb6-451d-8377-a9389c749393})
+----------
+The name of the StateType ({377b5279-ddb6-451d-8377-a9389c749393}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecThreePhaseInverter, EventType: phaseBNVoltage, ID: {739b8805-d522-4406-bede-d1e4200a3aa9})
+----------
+The name of the StateType ({739b8805-d522-4406-bede-d1e4200a3aa9}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseInverter, EventType: phaseBNVoltage, ID: {f8645ee2-a1e6-4d09-8c20-f6fd02a9e896})
+----------
+The name of the StateType ({f8645ee2-a1e6-4d09-8c20-f6fd02a9e896}) of ThingClass sunspecSplitPhaseInverter
+
+
+
+
+
+
+
+ Phase BN voltage changed
+ The name of the EventType ({f090cb78-d7ed-44fd-a5ad-ea9016021c34}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the EventType ({377b5279-ddb6-451d-8377-a9389c749393}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the EventType ({739b8805-d522-4406-bede-d1e4200a3aa9}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the EventType ({f8645ee2-a1e6-4d09-8c20-f6fd02a9e896}) of ThingClass sunspecSplitPhaseInverter
+
+
+
+
+
+
+
+ Phase C current
+ The name of the ParamType (ThingClass: sunspecThreePhaseMeter, EventType: phaseCCurrent, ID: {2676ad36-3d97-4691-8334-e13934cc58d1})
+----------
+The name of the StateType ({2676ad36-3d97-4691-8334-e13934cc58d1}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecThreePhaseInverter, EventType: phaseCCurrent, ID: {aa4b4cf5-43d0-4be5-9505-403918b5371d})
+----------
+The name of the StateType ({aa4b4cf5-43d0-4be5-9505-403918b5371d}) of ThingClass sunspecThreePhaseInverter
+
+
+
+
+
+ Phase C current changed
+ The name of the EventType ({2676ad36-3d97-4691-8334-e13934cc58d1}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the EventType ({aa4b4cf5-43d0-4be5-9505-403918b5371d}) of ThingClass sunspecThreePhaseInverter
+
+
+
+
+
+
+
+ Phase CN voltage
+ The name of the ParamType (ThingClass: sunspecThreePhaseMeter, EventType: phaseCNVoltage, ID: {fa5aa6f5-e67d-485a-835f-24e49298856c})
+----------
+The name of the StateType ({fa5aa6f5-e67d-485a-835f-24e49298856c}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecThreePhaseInverter, EventType: phaseCNVoltage, ID: {949b797d-5566-4667-8982-e430d23548e2})
+----------
+The name of the StateType ({949b797d-5566-4667-8982-e430d23548e2}) of ThingClass sunspecThreePhaseInverter
+
+
+
+
+
+ Phase CN voltage changed
+ The name of the EventType ({fa5aa6f5-e67d-485a-835f-24e49298856c}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the EventType ({949b797d-5566-4667-8982-e430d23548e2}) of ThingClass sunspecThreePhaseInverter
+
+
+
+
+ Phase volatage changed
+ The name of the EventType ({4ca086e9-82b9-461c-b168-1d61b542b884}) of ThingClass sunspecSinglePhaseInverter
+
+
+
+
+
+ Phase voltage
+ The name of the ParamType (ThingClass: sunspecSinglePhaseInverter, EventType: phaseVoltage, ID: {4ca086e9-82b9-461c-b168-1d61b542b884})
+----------
+The name of the StateType ({4ca086e9-82b9-461c-b168-1d61b542b884}) of ThingClass sunspecSinglePhaseInverter
+
+
+
+
+ Port
+ The name of the ParamType (ThingClass: sunspecConnection, Type: thing, ID: {1fa4fc9c-f6be-47c7-928a-bcefc1142eec})
+
+
+
+
+
+ Serial number
+ The name of the ParamType (ThingClass: sunspecConnection, EventType: serialNumber, ID: {6ed498e1-37ca-4bb7-bac7-463509c7784e})
+----------
+The name of the StateType ({6ed498e1-37ca-4bb7-bac7-463509c7784e}) of ThingClass sunspecConnection
+
+
+
+
+ Serial number changed
+ The name of the EventType ({6ed498e1-37ca-4bb7-bac7-463509c7784e}) of ThingClass sunspecConnection
+
+
+
+
+ Set charging rate
+ The name of the ActionType ({7f469bbc-64a5-4045-8d5f-9a1a85039851}) of ThingClass sunspecStorage
+
+
+
+
+ Set discharging rate
+ The name of the ActionType ({6068f030-acce-44a2-b95f-bd00dd5ca760}) of ThingClass sunspecStorage
+
+
+
+
+ Set grid charging
+ The name of the ActionType ({221a2ef6-0a92-4ff0-87fe-7bd920dbec0b}) of ThingClass sunspecStorage
+
+
+
+
+ Slave ID
+ The name of the ParamType (ThingClass: sunspecConnection, Type: thing, ID: {953064e0-4675-4538-a9a2-fa22ce2f347c})
+
+
+
+
+
+ Status
+ The name of the ParamType (ThingClass: sunspecStorage, EventType: storageStatus, ID: {da2b19c5-0f48-49d1-93f0-abdc0051407d})
+----------
+The name of the StateType ({da2b19c5-0f48-49d1-93f0-abdc0051407d}) of ThingClass sunspecStorage
+
+
+
+
+ Status changed
+ The name of the EventType ({da2b19c5-0f48-49d1-93f0-abdc0051407d}) of ThingClass sunspecStorage
+
+
+
+
+ SunSpec
+ The name of the vendor ({c143a7b4-a16c-4fff-86a3-9ffab3d6841d})
+
+
+
+
+ SunSpec Storage
+ The name of the ThingClass ({9a643ba8-346c-4127-a2f8-956a7133d75e})
+
+
+
+
+ SunSpec connection
+ The name of the ThingClass ({f51853f3-8815-4cf3-b337-45cda1f3e6d5})
+
+
+
+
+ SunSpec single phase inverter
+ The name of the ThingClass ({c5d5204e-3375-4b92-8128-fab77a671fed})
+
+
+
+
+ SunSpec single phase meter
+ The name of the ThingClass ({7ffa43b8-b56f-4435-8509-980e9d81dfa8})
+
+
+
+
+ SunSpec split phase inverter
+ The name of the ThingClass ({61b38f93-d331-42bf-b1ef-d3fb16ad1230})
+
+
+
+
+ SunSpec split phase meter
+ The name of the ThingClass ({b8a18e45-5ff5-4f43-915f-04ee216c809d})
+
+
+
+
+ SunSpec three phase inverter
+ The name of the ThingClass ({2e4122ea-96a5-415c-b5e2-7d6012265a83})
+
+
+
+
+ SunSpec three phase meter
+ The name of the ThingClass ({68f822f9-ff30-4275-b229-39a3674fead7})
+
+
+
+
+ Timout
+ The name of the ParamType (ThingClass: sunSpec, Type: plugin, ID: {1a8895a0-c746-48af-9307-3a4636f24cc2})
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Total AC current
+ The name of the ParamType (ThingClass: sunspecThreePhaseMeter, EventType: totalCurrent, ID: {7855d176-c6b8-439e-9f12-a71f01c1c7aa})
+----------
+The name of the StateType ({7855d176-c6b8-439e-9f12-a71f01c1c7aa}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseMeter, EventType: totalCurrent, ID: {e85024af-5376-4ff1-813e-5a56990c11cc})
+----------
+The name of the StateType ({e85024af-5376-4ff1-813e-5a56990c11cc}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecThreePhaseInverter, EventType: totalCurrent, ID: {26560dd8-6de4-445e-ba55-391d7241c370})
+----------
+The name of the StateType ({26560dd8-6de4-445e-ba55-391d7241c370}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseInverter, EventType: totalCurrent, ID: {9dbd8da7-dc22-4c3a-b941-47520fde705f})
+----------
+The name of the StateType ({9dbd8da7-dc22-4c3a-b941-47520fde705f}) of ThingClass sunspecSplitPhaseInverter
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseInverter, EventType: totalCurrent, ID: {f02d1c99-9b43-45a6-8a06-2ed4d6e5d497})
+----------
+The name of the StateType ({f02d1c99-9b43-45a6-8a06-2ed4d6e5d497}) of ThingClass sunspecSinglePhaseInverter
+
+
+
+
+
+
+
+
+ Total AC current changed
+ The name of the EventType ({7855d176-c6b8-439e-9f12-a71f01c1c7aa}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the EventType ({e85024af-5376-4ff1-813e-5a56990c11cc}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the EventType ({26560dd8-6de4-445e-ba55-391d7241c370}) of ThingClass sunspecThreePhaseInverter
+----------
+The name of the EventType ({9dbd8da7-dc22-4c3a-b941-47520fde705f}) of ThingClass sunspecSplitPhaseInverter
+----------
+The name of the EventType ({f02d1c99-9b43-45a6-8a06-2ed4d6e5d497}) of ThingClass sunspecSinglePhaseInverter
+
+
+
+
+
+
+
+
+
+ Total real energy exported
+ The name of the ParamType (ThingClass: sunspecThreePhaseMeter, EventType: totalEnergyProduced, ID: {73ebf57f-1ad2-4d19-bfd9-9e0a514c1243})
+----------
+The name of the StateType ({73ebf57f-1ad2-4d19-bfd9-9e0a514c1243}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseMeter, EventType: totalEnergyProduced, ID: {8a63bd73-0546-4636-8da2-23238cc06fb2})
+----------
+The name of the StateType ({8a63bd73-0546-4636-8da2-23238cc06fb2}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseMeter, EventType: totalEnergyProduced, ID: {ba275bdf-f418-4ef0-afbe-ac425c6f6783})
+----------
+The name of the StateType ({ba275bdf-f418-4ef0-afbe-ac425c6f6783}) of ThingClass sunspecSinglePhaseMeter
+
+
+
+
+
+
+ Total real energy exported changed
+ The name of the EventType ({73ebf57f-1ad2-4d19-bfd9-9e0a514c1243}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the EventType ({8a63bd73-0546-4636-8da2-23238cc06fb2}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the EventType ({ba275bdf-f418-4ef0-afbe-ac425c6f6783}) of ThingClass sunspecSinglePhaseMeter
+
+
+
+
+
+
+
+
+
+ Total real energy imported
+ The name of the ParamType (ThingClass: sunspecThreePhaseMeter, EventType: totalEnergyConsumed, ID: {63fa4721-1b0a-458c-b66c-17c161107f0d})
+----------
+The name of the StateType ({63fa4721-1b0a-458c-b66c-17c161107f0d}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseMeter, EventType: totalEnergyConsumed, ID: {51ffb2ae-3920-40df-8290-bbf5b6e1a68f})
+----------
+The name of the StateType ({51ffb2ae-3920-40df-8290-bbf5b6e1a68f}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseMeter, EventType: totalEnergyConsumed, ID: {c51dc6cb-5c05-4078-b11a-26afb2f85541})
+----------
+The name of the StateType ({c51dc6cb-5c05-4078-b11a-26afb2f85541}) of ThingClass sunspecSinglePhaseMeter
+
+
+
+
+
+
+ Total real energy imported changed
+ The name of the EventType ({63fa4721-1b0a-458c-b66c-17c161107f0d}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the EventType ({51ffb2ae-3920-40df-8290-bbf5b6e1a68f}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the EventType ({c51dc6cb-5c05-4078-b11a-26afb2f85541}) of ThingClass sunspecSinglePhaseMeter
+
+
+
+
+
+
+
+
+
+ Total real power
+ The name of the ParamType (ThingClass: sunspecThreePhaseMeter, EventType: currentPower, ID: {c28c642f-46da-44de-ba0d-c4cbfadbf2cd})
+----------
+The name of the StateType ({c28c642f-46da-44de-ba0d-c4cbfadbf2cd}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSplitPhaseMeter, EventType: currentPower, ID: {ef4bc0f8-f516-49b7-aba8-d5f987485aca})
+----------
+The name of the StateType ({ef4bc0f8-f516-49b7-aba8-d5f987485aca}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the ParamType (ThingClass: sunspecSinglePhaseMeter, EventType: currentPower, ID: {93cf8c6a-2620-42ed-9070-e0726d7b1dbc})
+----------
+The name of the StateType ({93cf8c6a-2620-42ed-9070-e0726d7b1dbc}) of ThingClass sunspecSinglePhaseMeter
+
+
+
+
+
+
+ Total real power changed
+ The name of the EventType ({c28c642f-46da-44de-ba0d-c4cbfadbf2cd}) of ThingClass sunspecThreePhaseMeter
+----------
+The name of the EventType ({ef4bc0f8-f516-49b7-aba8-d5f987485aca}) of ThingClass sunspecSplitPhaseMeter
+----------
+The name of the EventType ({93cf8c6a-2620-42ed-9070-e0726d7b1dbc}) of ThingClass sunspecSinglePhaseMeter
+
+
+
+
+ Update interval
+ The name of the ParamType (ThingClass: sunSpec, Type: plugin, ID: {52da5222-9a94-47a2-9adc-004541d2f5ed})
+
+
+
+
+ sunspec
+ The name of the plugin SunSpec ({cb4bdec6-cf2c-4a0f-9709-42d951ca2d8b})
+
+
+
+