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}) + + + +