From c1cdaac571b5d711f0849ee11af4efaf080bee45 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Fri, 19 Jun 2020 11:52:22 +0200 Subject: [PATCH 01/16] added sunspec plugin --- debian/control | 19 +- debian/nymea-plugin-sunspec.install.in | 1 + mypv/integrationpluginmypv.cpp | 1 - nymea-plugins-modbus.pro | 1 + sunspec/README.md | 16 ++ sunspec/integrationpluginsunspec.cpp | 119 +++++++++++++ sunspec/integrationpluginsunspec.h | 80 +++++++++ sunspec/integrationpluginsunspec.json | 238 +++++++++++++++++++++++++ sunspec/meta.json | 12 ++ sunspec/sunspec.cpp | 0 sunspec/sunspec.h | 0 sunspec/sunspec.png | Bin 0 -> 8836 bytes sunspec/sunspec.pro | 15 ++ 13 files changed, 499 insertions(+), 3 deletions(-) create mode 100644 debian/nymea-plugin-sunspec.install.in create mode 100644 sunspec/README.md create mode 100644 sunspec/integrationpluginsunspec.cpp create mode 100644 sunspec/integrationpluginsunspec.h create mode 100644 sunspec/integrationpluginsunspec.json create mode 100644 sunspec/meta.json create mode 100644 sunspec/sunspec.cpp create mode 100644 sunspec/sunspec.h create mode 100644 sunspec/sunspec.png create mode 100644 sunspec/sunspec.pro diff --git a/debian/control b/debian/control index 4a3dc3c..bfaa5ac 100644 --- a/debian/control +++ b/debian/control @@ -35,6 +35,7 @@ Section: libs Depends: ${shlibs:Depends}, ${misc:Depends}, nymea-plugins-modbus-translations + nymea-plugins-translations Description: nymea.io plugin to send and receive generic modbus commands The nymea daemon is a plugin based IoT (Internet of Things) server. The server works like a translator for devices, things and services and @@ -50,7 +51,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 +62,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 manufactor independent sunspec devices. + + Package: nymea-plugin-wallbe Architecture: any Section: libs @@ -89,4 +105,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..03cc550 --- /dev/null +++ b/debian/nymea-plugin-sunspec.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_devicepluginsunspec.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..95286d7 --- /dev/null +++ b/sunspec/README.md @@ -0,0 +1,16 @@ +# SunSpec + +## Supported Things + +* SunSpec Inverter +* SunSpec PV Modules +* SunSpec Meter +* SunSpec Tracker +* SunSpec Storage + +## Requirements + +* The package 'nymea-plugin-sunspec' must be installed. + +## More +https://sunspec.org diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp new file mode 100644 index 0000000..7974f77 --- /dev/null +++ b/sunspec/integrationpluginsunspec.cpp @@ -0,0 +1,119 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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::discoverThings(ThingDiscoveryInfo *info) +{ + // Discovery possible? + Q_ASSERT_X(false, "discoverThing", QString("Unhandled thingClassId: %1").arg(info->thingClassId().toString()).toUtf8()); +} + +void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) +{ + if (info->thing()->thingClassId() == sunspecInverterThingClassId) { + + } else if (info->thing()->thingClassId() == sunspecPvModuleThingClassId) { + + } else if (info->thing()->thingClassId() == sunspecMeterThingClassId) { + + } else if (info->thing()->thingClassId() == sunspecTrackerThingClassId) { + + } else if (info->thing()->thingClassId() == sunspecStorageThingClassId) { + + } else { + Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(info->thing()->thingClassId().toString()).toUtf8()); + } +} + +void IntegrationPluginSunSpec::postSetupThing(Thing *thing) +{ + Q_UNUSED(thing) + + if (!m_refreshTimer) { + int refreshTime = configValue(sunSpecPluginUpdateIntervalParamTypeId).toInt(); + m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(refreshTime); + connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSunSpec::onRefreshTimer); + } +} + +void IntegrationPluginSunSpec::thingRemoved(Thing *thing) +{ + Q_UNUSED(thing) + + if (myThings().isEmpty()) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer); + m_refreshTimer = nullptr; + } +} + +void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info) +{ + Thing *thing = info->thing(); + //Action action = info->action(); + + if (thing->thingClassId() == sunspecInverterThingClassId) { + info->finish(Thing::ThingErrorActionTypeNotFound); + } else if (thing->thingClassId() == sunspecMeterThingClassId) { + info->finish(Thing::ThingErrorActionTypeNotFound); + } else if (thing->thingClassId() == sunspecTrackerThingClassId) { + info->finish(Thing::ThingErrorActionTypeNotFound); + } else if (thing->thingClassId() == sunspecPvModuleThingClassId) { + info->finish(Thing::ThingErrorActionTypeNotFound); + } else if (thing->thingClassId() == sunspecStorageThingClassId) { + info->finish(Thing::ThingErrorActionTypeNotFound); + } else { + Q_ASSERT_X(false, "executeAction", QString("Unhandled thingClassId: %1").arg(info->thing()->thingClassId().toString()).toUtf8()); + } +} + +void IntegrationPluginSunSpec::onRefreshTimer() +{ + //get data +} + +void IntegrationPluginSunSpec::onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value) +{ + // Check refresh schedule + if (paramTypeId == sunSpecPluginUpdateIntervalParamTypeId) { + if (m_refreshTimer) { + int refreshTime = value.toInt(); + m_refreshTimer->stop(); + m_refreshTimer->startTimer(refreshTime); + } + } +} diff --git a/sunspec/integrationpluginsunspec.h b/sunspec/integrationpluginsunspec.h new file mode 100644 index 0000000..85e62a9 --- /dev/null +++ b/sunspec/integrationpluginsunspec.h @@ -0,0 +1,80 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 + +class IntegrationPluginSunSpec: public IntegrationPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginsunspec.json") + Q_INTERFACES(IntegrationPlugin) + + +public: + explicit IntegrationPluginSunSpec(); + + void discoverThings(ThingDiscoveryInfo *info) override; + void setupThing(ThingSetupInfo *info) override; + void postSetupThing(Thing *thing) override; + void thingRemoved(Thing *thing) override; + void executeAction(ThingActionInfo *info) override; + +private: + + PluginTimer *m_refreshTimer = nullptr; + QHash m_modbusTcpMasters; + QHash m_asyncActions; + + void update(Thing *thing); + +private slots: + void onRefreshTimer(); + + void onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value); + + void onConnectionStateChanged(bool status); + void onWriteRequestExecuted(QUuid requestId, bool success); + void onWriteRequestError(QUuid requestId, const QString &error); + + void onReceivedHoldingRegister(quint32 slaveAddress, quint32 modbusRegister, const QVector &values); + void onReceivedInputRegister(quint32 slaveAddress, quint32 modbusRegister, const QVector &values); +}; + +#endif // INTEGRATIONPLUGINSUNSPEC_H + diff --git a/sunspec/integrationpluginsunspec.json b/sunspec/integrationpluginsunspec.json new file mode 100644 index 0000000..3a0ffbf --- /dev/null +++ b/sunspec/integrationpluginsunspec.json @@ -0,0 +1,238 @@ +{ + "name": "SunSpec", + "displayName": "sunspec", + "id": "73c7efcc-80d5-4166-ad97-2cbbeb129d91", + "paramTypes":[ + { + "id": "52da5222-9a94-47a2-9adc-004541d2f5ed", + "name": "updateInterval", + "displayName": "Update interval", + "type": "int", + "unit": "Seconds", + "defaultValue": 15 + } + ], + "vendors": [ + { + "name": "sunspec", + "displayName": "SunSpec", + "id": "c143a7b4-a16c-4fff-86a3-9ffab3d6841d", + "thingClasses": [ + { + "name": "sunspecInverter", + "displayName": "SunSpec Inverter", + "id": "2e4122ea-96a5-415c-b5e2-7d6012265a83", + "createMethods": [ "User" ], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "6ffaa694-a4f2-4936-b043-37679449a34b", + "name":"ipAddress", + "displayName": "IP address", + "type": "QString" + }, + { + "id": "08bf6597-976d-4fb9-8fc2-4372c07961d4", + "name":"serialNumber", + "displayName": "Serial number", + "type": "QString" + } + ], + "stateTypes":[ + { + "id": "4401468c-0385-40a9-b436-daf7ed6a50d5", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "9bdcc785-4738-437e-88a0-fc231095c2dd", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "displayNameAction": "Change power", + "type": "bool", + "defaultValue": 0, + "writable": true + } + ] + }, + { + "name": "sunspecPvModule", + "displayName": "SunSpec PV Module", + "id": "fb7ff0df-a745-4313-83e4-1e5007b06fe2", + "createMethods": [ "User" ], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "890e468b-fc76-4641-9569-93cd45e6bb8d", + "name":"ipAddress", + "displayName": "IP address", + "type": "QString" + }, + { + "id": "d5a9510d-720f-4a28-9d91-977eedd022bd", + "name":"serialNumber", + "displayName": "Serial number", + "type": "QString" + } + ], + "stateTypes":[ + { + "id": "edc032d2-022f-43f7-9131-831ad23ee62a", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "0b7485eb-3cf7-45b2-87cf-9ec85366b197", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "displayNameAction": "Change power", + "type": "bool", + "defaultValue": 0, + "writable": true + } + ] + }, + { + "name": "sunspecMeter", + "displayName": "SunSpec Meter", + "id": "68f822f9-ff30-4275-b229-39a3674fead7", + "createMethods": [ "User" ], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "66bdb89b-bbd4-4edf-96df-649014d94c42", + "name":"ipAddress", + "displayName": "IP address", + "type": "QString" + }, + { + "id": "89af6d27-4b81-42ef-9883-1e2ba2067884", + "name":"serialNumber", + "displayName": "Serial number", + "type": "QString" + } + ], + "stateTypes":[ + { + "id": "36f861c7-afc1-4725-b41f-67113200d78f", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "36f18720-69ca-4021-9328-262d87397f1b", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "displayNameAction": "Change power", + "type": "bool", + "defaultValue": 0, + "writable": true + } + ] + }, + { + "name": "sunspecTracker", + "displayName": "SunSpec Tracker", + "id": "9941da30-a6d6-475d-8244-3c2145b419e6", + "createMethods": [ "User" ], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "ccdcca78-7efa-4253-8fdb-8731e6880e9b", + "name":"ipAddress", + "displayName": "IP address", + "type": "QString" + }, + { + "id": "67ce05ff-14bf-4d43-94ba-d5f290278514", + "name":"serialNumber", + "displayName": "Serial number", + "type": "QString" + } + ], + "stateTypes":[ + { + "id": "fe57e465-e49e-4d50-b880-6d9a243783ff", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "d2bbba4b-998f-444e-935f-f958927afddd", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "displayNameAction": "Change power", + "type": "bool", + "defaultValue": 0, + "writable": true + } + ] + }, + { + "name": "sunspecStorage", + "displayName": "SunSpec Storage", + "id": "9a643ba8-346c-4127-a2f8-956a7133d75e", + "createMethods": [ "User" ], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "421cacb9-a94d-4ba9-923c-3bc30d8b94c8", + "name":"ipAddress", + "displayName": "IP address", + "type": "QString" + }, + { + "id": "ae76db81-7969-440f-85f0-28ed46d772db", + "name":"serialNumber", + "displayName": "Serial number", + "type": "QString" + } + ], + "stateTypes":[ + { + "id": "25a1fb10-a6b9-4037-b7cf-ad481a65beb4", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "9c0e0b08-4461-4e54-aaf3-cab9155028da", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "displayNameAction": "Change power", + "type": "bool", + "defaultValue": 0, + "writable": true + } + ] + } + ] + } + ] +} + + + + 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..e69de29 diff --git a/sunspec/sunspec.h b/sunspec/sunspec.h new file mode 100644 index 0000000..e69de29 diff --git a/sunspec/sunspec.png b/sunspec/sunspec.png new file mode 100644 index 0000000000000000000000000000000000000000..7a52a4529d365fccd4b06978df239f8736c553e5 GIT binary patch literal 8836 zcma)iWl)@5vn?SL+}(rAV8I;%41?R?P6l^(3naL^LvVKso&+ZWg3I7AxC97phqunD z`hMJd-g|#M&#qlv+q-+M>ROR%DzcbpWM~Ko2$=G6AjnHQ{LhDi^zyIKt<6I~phl1f zNosm89p@q&5)8pU)(O;pQ!n}Skyky6uz~f8tTU1aTI-s2!3NdBLlf49Dj`EV>-m+y8@)t;(xwwlH%g!Bf$H*-Rr5^4jjzua12AEMRN9@rk#vL(E$T^k)$6aLI z%7v?IXx}YxGOMg&SByKHLRS2p64@y)oB@SMSoT{X^VaJm0=6tPz_REfPRtaL$&=(0 zus|dW^gLYH7?s{gyG1+SSe;~T?_*MT;Z?SGe3wg;xpclR_lbIq0BBs$^XD#+v{0nX z(qs76M#3D=H);8wsPtrEtQW=6Y@|r@Arcr4C}de1|?mYvZ9-~ZF53l ztlY^W!#v6Ql~JzQMR-FLV^E!bdR-P;>UWnHk>E5^0)sYs%_-4BVISGVU&rV!-XT|h z<`YyM=(R@%%!=(2L<53Lx=5-P+l}F4*?mWkeKHa1?-NSZZHN2mjsGl_Y!0LM7T~9b zDAQ&NVDH8lcBSkrZWZufaB@PU>7J=*W4pQZpMVW^>}N6#%#+vRg?`s}(W`;qNE=Hu zUh%8vT`jyy)wfGzrUO=**j5@0nITQLj?=lugeFK)56?&QDXi|L1I944%^9$NQ>k&c zWbm0T(e8Y#U^=6ZQqaqA0Cstr1`D2Vn#V#MXG|rOsH4gqDaW}VnH&mQ{`$pfR5{Ci zqk`|gzOMQiP1zjn9Fs@FerHCqILP-Jf(E$V95xZ>c4 zPgot4z=B`^Ze3lAhhT(G+5YHn;8(h-Xj&znE@cizpUZeslfM4G>Y`(fD-RV-=S)kW zSD^9hFVs_dM%{I>-U)0F~5eFm)^zkn2~JHWsFa zB4AuMAb-+kj}s|b08n`I+9c3-OKM|m{nA<(ZueQ5H=e@}(-Mh;@wXnNnez4IRgNSB z$!k^mR9CChBgu<{TL1(HV8c7}fCrxJm~2aVqqo4IjhSTjDv3^xok&g5YiyNCF@Sg9 z&WUcMU-*~v*#<7XMmCVPbe|393G;E1Z*$ej8aW037w_FtQy+n9i{e&oYU`0jrG-R$x1$whCrQGbm;abJH9HgF)5tC#6c^y%SNlq#UI6UB9zbk zN~k+;OUD}Sb1~X>wC7~!qUMGmNrormn8omXnk%{BSJ)$;^nHe%r-9tFtLUcyYa=8~ zV8B5;b;mF5B-0CNK*4-`!NP#q^CZ1~KPh~EM-l4e9-{e1l8pI}+J?8Rj(uN+rXd9t z=azr3D_$Z@i5f#p9E|5?7MM&V!qcwm^JfWxptuUt5}-0r>sc^jz8X2($p^uGgGUUg z5SU{65P06~eZ;{Rw_56ivr^_&{Q%b>f65&5b53%x88?W2q*Nfooktwc@C@| zUXp~jq!30Gaa>6=3?opg8d6EBnk<#KQ)oy;i#VOWPpVK(1Tz*8IOY38o@fgc`n`58n?CGJd-5;bjG8D{dn zIQ`%>ToN$UOF5P`C1)&pJb!;*7B&-xx1Fp;WKKpS6lQu#dW`%OtisqJcrKMM41ec! zGi1GgwgHN3(4@o1C(vWj4RV7P!Cl=;2~hQBtJK(Wx$O+qO19CQDDnqM+x*d+CW?)^ z*7*W;k=bk@AlZ)iAo^lKlyjT!4l@jF?}lpsGLss~QqIr8q~peFyL+S)oUM>l7#d@X z;bj8QD2$qb=8(%H6gK@k?B_+0=BJB=vuOpnu8!*oV_0W!xzV2d8i^ zXB1oT0PBp#cCn39sk)f3$#>ic!I7dv=>y>n^_Zj@FlSeK9a&1R;9pOOa1ovg2MB-S{* zGnQYeaIRmpFp3GG1Wbw{v5Y&^iX|&m=i7yjC_1UH;iZ>5B^k1WU#(}0Y&Z29csKXJ zMycAp)Bm2a*G)Y}kK38!i5ShP6TwZnh8PMPEc6Gg?3y{yX7+K)UoOY|7kpr0mYQO* zBh>R9KLdZDqnP@R)m??c1!jaHk9~rm{Q?!Ijp1Iu{#IzUm2XQw4_Q==0e_#@AD1kh ziJ@gZ5#)5h(`PN%u&_D89AT1W@5+Z%^??XBY7`U*G8rb;<8*7&k1f;zLJj4E#}@7e z`48XfnJwd8?q{Lm1_Yj-;#t~tw%HFdY%1RO+Z0<2OiN$Bh$L=q2gp3pI%Z&LHI3qG zD*&G$DYtchhg(meg0HCM>cqU@#2RX-kj2B=^UpR3;D9|pqZ{Iokb3fn%hauB0o{?* z{;-&tJb3F#yn9qbqlVrR-c*;*EOQK5d&;Yd7xu!Rs;tmsttB(S`obKP@--3HM+lTL zVOSrjTPO&~G$PA*2npY^D&n=PFxl$$T!oR(>Eebn{UQG)9FR<+f9P2Dhd|%ulmFX! zCD=#E*`3cNFSz4L7&FN6LO!WNM-+i}^NFSNT+q)^xa%+yD)7uH4LNyY`D=cT*p!_?Ut$=`QlF zv~|MytKU+F6di8Tu=e#lC4y&;EXndE#IQkJyhZ6nQ3|)@Z4QZ^uR!T;gzYwQ1jrLs z^gJ*@3rF8{&HygZ*_~e755iCNr~(oImEaEVDIc zqQ$}}=kmY1d<|!{T^cnySuUn9ZBBY7A`F|BDrq5C^*+G_Qx;val4uvD4vX}PmS zM1Nn92P$;Fl2`AxGs=(y7Tw;MOf$m)`^h4GtQ>O7jP+lgu>sHo)lVnWyVZMhBeR0B zU{#6$ANu>w(EGm~aZ5wJIPT@8@XL;vg>f>`!;h__o7)#z(9^_1;JD3X1}vhC+cjZ0 zzFzxB2#Atcuw#PlB75(fm;`K<`Ni)&i&Yr zOKe$>G%h&2$9ifinCr;01epYD|)S zj`@8kbizA2){^di8|08%G90*CE_OM?-4MB{1vQA3vNW_xZSZI_GdH-#xUB+M-TwUi z!AVj6xzEIRxz-7lM6k9NUv0Q(oAV^^OCpK^jv)BV5_WR4J_B@p;_Qab!Us;?BKp=;;d zcEc1$1%;Z%f3gCs@o+o!hMycaz;Z@7#ZR;N+*%`~o%U2{HiXQA0y~cl?r$qE@c$0g z>B_XabWa!KXE@G-P@yQU@W_n20N7*ROrEdx97I zZmasgGb5OMyd>QiN1+IXCZ{m6@P}&u$Za=!H!@SIOAuI_fAa*-Z*fUVz0k5;8)2f> zl1xC736lDzkQCW+rMLCtH-2_~*rx?budNa$Uwom#3S|W>VE2skMnTU@Xt6ZT$ewg< zRfSBN<4MNn_T{#ip#jxRdA)FoH(q<5RDV$i2&3EesBGrL0`>2ByLiyt-D1-yPDAN^ zrg*naw@Jq(ev^;j77Z)3a<1^*1LvG5$C3_I^(D~cjSwnOLwM254cgeoKY#onh?rQX zKAO3=-ANmXek|4KJj`Y6)5;np&k;CwOh?8a&1W1qm42`!u9BEK)#d%O*nnBEO|nUo zSuJ15Y3nU!#SQ8Z5A{n@{86R=ouv&oH^*g)?d733d`s)&9smd)l`CpFxXYNdS@4O~ z>`(6XUm`8*yV1mq$eOGC$x&ny7fVNKK&S|HY-V2(W71>?4h>#F=!A9}!^edXE3P9; zxR#k8{9h2hBaPC~6}L>?tv(qCKAw%i=!Eet#LGwDKf1B#rx(y|+HZr*huW@smdonb zRQq4gM><|0gR!&Cg!zldi6nL{sL7zw54s<5a~6}JoDU>2es1G{b^dW`0hvYzFlH`+ z_Mg8#F;%+3tjf{Cl0ha1tY5o=+8dcW&7`A4VlRIliMh}qjzUhZ-wOp8yyS8!YgdQd9DwzTc~q_e#%zrS$ygs}jJ0)b02R%gijw>Fk?!Oe zzM@uF;i%;$FiR(%`Z3Z@8&7DER*n>>Se_;*)mkTz)-+p`4%d&2u%NKAudMCFGwVb| zkmSud_8I-Ch{M+upM8p0DaTs2;Z19ED7U_`Osg}SY!liDf33`EL^%X+yCQyB?SY5r z%_jcjxn*jTTxsjI&{o=n>~~*`VgSmNyLy##QU;F6_mXZ_wH9>rsVbycx_7!l<|6Sh z%{DCD7e=hx7^}z_{C#I@WQGi1O3S!klMt@Y%$?j-EU(^k{`V%&RH2Hi$~ zb$qE4v7X>N&1|wXV?-ri8)+(|7qpxZ=|3*+A)uVsN$K5W)eI|LsS)l9zOcfYg!hc2 zg%sUU2qxee0bE?6Fc|=m0I?q$!N44QqWOs$iYyl3)kntH%>#)4ODS%BZB#&rpEG`_ zC@eJ8i+RiDUVk1a6br`3pyb}8%~hFi{ld=1wrPyrKIyjp@k!^a;V1o}VnVdg;^_2R zjLXAq@(*TakK-tx@ghI_GqH6UNlFHbo%As+*nTy1^o&0-D$--qj_obb1iFlju#CIJ zZ;3UGRhy;rIrw)uwb<_)Us6xrV^g*IU$M=W)tZG~_4c=!gMnKLRiVKe%kFh!b9Hsaq%A587=XIuZoTeoZwEKq!5Aq4HuQO-%Jn9_fzxwn zLhgtR)(}{$_|gZT5Y;>~=f}I)c1>W5_1|*b4=pvs0nfqn4O`z8jV;V|s|_3MW#NB` zZT8bxb!u0&4I!IqusiFO2O0F8$Iac{dzCVVp7-yK3F|%epioGVk>c6%vZxuo+^k%j zq+};?(VEw^#mW{_$bbf;>XPtg=Z2OTIw9Jd4`WN1(TUb;0Sd~978t%*JlUre5B~52 zi51V&#WqL@q`_UUx1=*0A8%U+rx=%+m9?lwuj$FlJtWhw*}eCZYm+rutg+pNBjPKQ zll{d5A1$JCZ96wVkAbx%wJN@)ExJ^>DkPKji`#8DtG0tQY}3@qiFmQf>e`to`cXwu zKUE>J*Y7${^&z<|pw;nN?DzMA+j7gXU!$zMrz?J@WJzY!$s@mR9dCX&fx%!84|_-f z{^64dtW@2u&Ec%@jh(Fl7eD`;@PqO8-fLv+y5I8x!(dnyWtR1GutblnYYYgMuvS<1 ztY>F;=t`ON9=>%hr*z)f)^K`wrUeHJGxQs#> zi%OfQAYUNh6bovyIGnG^JJ>l$x)$mUxE}al8b-f_Y3;Req}D=8jc+_|DD4qgY!<(J|E| z>t)ZzW;1taf846rk4IDqBrRGCdTMtyO-V_CPUz}fpUw+V)I#}QseP-Wv=?Gw891?X zc#Q^PRWvdZW@Z6CX~$YZ?ad=Dm32}sM%hE9Hqyxi(DC(R6~xaCd?N8^6PY=NIqN|T z+$BA`*moR!C{qCI>A`!B;P(lu9Ra)d(^gMpDf#&ml0q~^rRjzoO#A}@@3^>do1I;w zh@wDgy&MMZKp~-lO|$!t#xqEFdP-`D;eC_FBW6vD5Q4tYqDmqG)5{J_=Ct|>3v%mc zI0x>UCdTIGk)fdPOoVcoO1@Fx&2U5N-sPmlzPm3?Wt35dsLvD^e{_jGw-6p0bax`Z z>2lX3u*EMeYsqRgWFWv+&+y=6=W4x-CE4?9d;5<6i4zzEhNipNym*yf>a^uV7A~*X zQ=zt8TrD~==f3{WgV7R2q6yG!^APp000Nq4ov(MZzsJUDbf2r+MYRhb_8?fQB)YF9 zNUF4Ve7s?)!$%q+3N=fKe{b)%==1MjG5m=qk3f);eGW0v-dr<9WmqN?1Cy80-62}r zw{JE0lfG{;gee0#3(XvQFCT8v@1Kos?q#c^LRHS6wNxg?N4R8XWu%Y9((MvLxl_pU z38lW_6U=FA%Y1!iUdy5O7Ny$VI!+DgwiV+t?s{j?=svL8-(MVf{Z_PVeSYNHMq>%v z%*;%DbO5`kEnK1%!?Vcs{!|j3mbMW5201^c(eiTi@|3g5b7e4B_#p@H%~a>z&GnQq zP`g6&Yrs7=$9pZNyKk=^TfZC&K0C$HJ!ypOp;hkg#*>R2+ys9L4^mWAbef(4Ouk`E zuE-HTF6)*{kW|fMY))h)@J7pgP>~mOAO5n$QCIoLTgt^sV$IphtF7`t%SR~RNKI`o z1e)~A$Ia^b4cPkqa?LVjflJ-X#3M+9K!foOpT?LLqrzfo@s40Lq2uThum>JZ3}4)6U@-J|Z7 z7A>v)jr~Qk&g5#;b{98-s1JYb^i1kp19WuKZ|WMb(LxcuXm1EwF_i>#<^gD-Q`EE6 zMA0L%E<-<)k|4+g$R5wjrwy$QgK_9jH<8KsGPIMu+)aVEnrua7o(%22lfj(y1M4ZYPx$No3AHtT+FZpX804-`5cZiCjGol7q9ON zB;xG5JPuVvlPk)8k{BFJ7Rl!I$I)DU_*I^``@2JWb1NSsgV!f3JJhEs-6KG9B5$Rx zz?&HnU;|K-x}j%exIZNMr6|(u$&;Nu{wbj8`C8iI|OH|i_mh#^T^SXiXK z?1#46p?z}`+I@6n$kX#twerIwVsp>>qsO+Z(8g?4jAdM0oKFX@m(LyV_vMk1hWf^K zF|h4{8;>Tp^MO8bYM>~O#4|FXw)dWFO7~$hf6N2zI1WNf3wC@#+P4k_` zn=@+0`cL?iAotbWv52$ zB}n5YCOEBI&)eXz`e?wsw4=NK`F6#8onp|U(WBIlQxQ+)NWX`#Z|~%!C0$nI@tKZ} zmfNS3M=eeyysfYA<8p&%+w7+L-+EpjZv~N$BjRiq6&H&(b@?9Om4DP?-T5lm#*EfW zywO2teVs)m*0EyMq#I?+)E+8TQQZL0uHySVHO1{V1`}E18ylME4ca~FMn}dx3Uc}L z<(1d@>Nbyn{X!SZk)wC#YPT;~hl36;Qc!*JS~gEOVCVLFYsffF!2%=r{mr0r!2GEPrF1SJUE<-r;_wxZE0m ztXH81&5b39Z4ZJ}q|6macQV=Y8+`7@z-%X#E5!zrbMf#5#3*FRsHntM@S&Gmeian4 z^6_q-;ZKzPvC?n+wgRg29C&nIRegsTDL*nswkbL?KF-zYe|vz@97N3Y@a~BlWeTOZ zwA51}=ypm>Kw$pipe2Z`81*VmnVb=}wAAHhWXw#US8LREW_8r9P(m9xvgN+_JCQ|+ zQ6oF~1x*|ypFq967)ktW0s^N(cwQQ(#$qZjkX2BbwX)0o7&4I?KN(^j2%&ueQuUEQKZt0qWx zLt5;N1z$3X9A3zjda4#1hKK=d_x}1g3le0$+=76BDDjUL0Cx4@v9tXnOt7?Ep@P`= zVh(|zN$ev~o9=^|!0HM2G2&APEy#a&6 zq{zt+0rYn2=uAT~w&!UAZEf*CCnmfkp&9%2=6H`&Hw-83;&{__`gXm{v*ctck7F8<6ma}k5xSV`L$Ls WJti%64eyJ(1VLU#1yn0#8uDMIk*;+B literal 0 HcmV?d00001 diff --git a/sunspec/sunspec.pro b/sunspec/sunspec.pro new file mode 100644 index 0000000..a9c3e6a --- /dev/null +++ b/sunspec/sunspec.pro @@ -0,0 +1,15 @@ +include(../plugins.pri) + +QT += \ + network \ + serialbus \ + +SOURCES += \ + integrationpluginsunspec.cpp \ + sunspec.cpp \ + ../modbus/modbustcpmaster.cpp \ + +HEADERS += \ + integrationpluginsunspec.h \ + sunspec.h \ + ../modbus/modbustcpmaster.h \ From 5a2be02340becd1c58c96ddbbbfe96e960789dc3 Mon Sep 17 00:00:00 2001 From: "bernhard.trinnes" Date: Fri, 18 Sep 2020 18:03:04 +0200 Subject: [PATCH 02/16] fixed rebase --- sunspec/integrationpluginsunspec.cpp | 335 +++++++++++++++- sunspec/integrationpluginsunspec.h | 20 +- sunspec/integrationpluginsunspec.json | 525 +++++++++++++++++++++++--- sunspec/sunspec.cpp | 413 ++++++++++++++++++++ sunspec/sunspec.h | 223 +++++++++++ sunspec/sunspec.pro | 12 +- sunspec/sunspecinverter.cpp | 129 +++++++ sunspec/sunspecinverter.h | 118 ++++++ sunspec/sunspecmeter.cpp | 61 +++ sunspec/sunspecmeter.h | 91 +++++ sunspec/sunspecstorage.cpp | 123 ++++++ sunspec/sunspecstorage.h | 109 ++++++ sunspec/sunspecstringcombiner.cpp | 89 +++++ sunspec/sunspecstringcombiner.h | 129 +++++++ sunspec/sunspectracker.cpp | 42 +++ sunspec/sunspectracker.h | 78 ++++ 16 files changed, 2418 insertions(+), 79 deletions(-) create mode 100644 sunspec/sunspecinverter.cpp create mode 100644 sunspec/sunspecinverter.h create mode 100644 sunspec/sunspecmeter.cpp create mode 100644 sunspec/sunspecmeter.h create mode 100644 sunspec/sunspecstorage.cpp create mode 100644 sunspec/sunspecstorage.h create mode 100644 sunspec/sunspecstringcombiner.cpp create mode 100644 sunspec/sunspecstringcombiner.h create mode 100644 sunspec/sunspectracker.cpp create mode 100644 sunspec/sunspectracker.h diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index 7974f77..378dbd3 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -35,25 +35,71 @@ IntegrationPluginSunSpec::IntegrationPluginSunSpec() { -} -void IntegrationPluginSunSpec::discoverThings(ThingDiscoveryInfo *info) -{ - // Discovery possible? - Q_ASSERT_X(false, "discoverThing", QString("Unhandled thingClassId: %1").arg(info->thingClassId().toString()).toUtf8()); } void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) { if (info->thing()->thingClassId() == sunspecInverterThingClassId) { + QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecInverterThingIpAddressParamTypeId).toString()); + SunSpecInverter *sunSpecInverter = new SunSpecInverter(address, 502, this); + m_sunSpecInverters.insert(info->thing(), sunSpecInverter); + if (!sunSpecInverter->connectModbus()) { + QTimer::singleShot(2000, info, [this, info] { + setupThing(info); + }); + qCWarning(dcSunSpec()) << "Error connecting to SunSpec device"; + return; + } + connect(sunSpecInverter, &SunSpecInverter::initFinished, info, [info] { + qCDebug(dcSunSpec()) << "Modbus Inverter init finished"; + info->finish(Thing::ThingErrorNoError); + }); - } else if (info->thing()->thingClassId() == sunspecPvModuleThingClassId) { + connect(info, &ThingSetupInfo::aborted, sunSpecInverter, [info, this] { + m_sunSpecInverters.take(info->thing())->deleteLater(); + }); + connect(sunSpecInverter, &SunSpecInverter::inverterDataReceived, this, &IntegrationPluginSunSpec::onInverterDataReceived); } else if (info->thing()->thingClassId() == sunspecMeterThingClassId) { + QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecMeterThingIpAddressParamTypeId).toString()); + SunSpecMeter *sunSpecMeter = new SunSpecMeter(address, 502, this); + m_sunSpecMeters.insert(info->thing(), sunSpecMeter); + if (!sunSpecMeter->connectModbus()) { + QTimer::singleShot(2000, info, [this, info] { + setupThing(info); + }); + } + + connect(info, &ThingSetupInfo::aborted, sunSpecMeter, [info, this] { + m_sunSpecMeters.take(info->thing())->deleteLater(); + }); } else if (info->thing()->thingClassId() == sunspecTrackerThingClassId) { + QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecTrackerThingIpAddressParamTypeId).toString()); + SunSpecTracker *sunSpecTracker = new SunSpecTracker(address, 502, this); + m_sunSpecTrackers.insert(info->thing(), sunSpecTracker); + if (!sunSpecTracker->connectModbus()) { + QTimer::singleShot(2000, info, [this, info] { + setupThing(info); + }); + } + connect(info, &ThingSetupInfo::aborted, sunSpecTracker, [info, this] { + m_sunSpecTrackers.take(info->thing())->deleteLater(); + }); } else if (info->thing()->thingClassId() == sunspecStorageThingClassId) { + QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecStorageThingIpAddressParamTypeId).toString()); + SunSpecStorage *sunSpecStorage = new SunSpecStorage(address, 502, this); + m_sunSpecStorages.insert(info->thing(), sunSpecStorage); + if (!sunSpecStorage->connectModbus()) { + QTimer::singleShot(2000, info, [this, info] { + setupThing(info); + }); + } + connect(info, &ThingSetupInfo::aborted, sunSpecStorage, [info, this] { + m_sunSpecStorages.take(info->thing())->deleteLater(); + }); } else { Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(info->thing()->thingClassId().toString()).toUtf8()); @@ -69,12 +115,77 @@ void IntegrationPluginSunSpec::postSetupThing(Thing *thing) m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(refreshTime); connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSunSpec::onRefreshTimer); } + + if (thing->thingClassId() == sunspecInverterThingClassId) { + SunSpecInverter *sunSpecInverter = m_sunSpecInverters.take(thing); + if (!sunSpecInverter) + return; + sunSpecInverter->getInverterMap(); + thing->setParamValue(sunspecInverterThingManufacturerParamTypeId, sunSpecInverter->manufacturer()); + thing->setParamValue(sunspecInverterThingSerialNumberParamTypeId, sunSpecInverter->serialNumber()); + thing->setParamValue(sunspecInverterThingDeviceModelParamTypeId, sunSpecInverter->deviceModel()); + + } else if (thing->thingClassId() == sunspecMeterThingClassId) { + SunSpecMeter *sunSpecMeter = m_sunSpecMeters.take(thing); + if (!sunSpecMeter) + return; + //TODO upate data + thing->setParamValue(sunspecMeterThingManufacturerParamTypeId, sunSpecMeter->manufacturer()); + thing->setParamValue(sunspecMeterThingSerialNumberParamTypeId, sunSpecMeter->serialNumber()); + thing->setParamValue(sunspecMeterThingDeviceModelParamTypeId, sunSpecMeter->deviceModel()); + + } else if (thing->thingClassId() == sunspecTrackerThingClassId) { + SunSpecTracker *sunSpecTracker = m_sunSpecTrackers.take(thing); + if (!sunSpecTracker) + return; + //TODO upate data + thing->setParamValue(sunspecTrackerThingManufacturerParamTypeId, sunSpecTracker->manufacturer()); + thing->setParamValue(sunspecTrackerThingSerialNumberParamTypeId, sunSpecTracker->serialNumber()); + thing->setParamValue(sunspecTrackerThingDeviceModelParamTypeId, sunSpecTracker->deviceModel()); + + } else if (thing->thingClassId() == sunspecStorageThingClassId) { + SunSpecStorage *sunSpecStorage = m_sunSpecStorages.take(thing); + if (!sunSpecStorage) + return; + //TODO upate data + thing->setParamValue(sunspecStorageThingManufacturerParamTypeId, sunSpecStorage->manufacturer()); + thing->setParamValue(sunspecStorageThingSerialNumberParamTypeId, sunSpecStorage->serialNumber()); + thing->setParamValue(sunspecStorageThingDeviceModelParamTypeId, sunSpecStorage->deviceModel()); + + } else { + Q_ASSERT_X(false, "postSetupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); + } + } void IntegrationPluginSunSpec::thingRemoved(Thing *thing) { Q_UNUSED(thing) + if (thing->thingClassId() == sunspecInverterThingClassId) { + SunSpecInverter *sunSpecInverter = m_sunSpecInverters.take(thing); + if (sunSpecInverter) + sunSpecInverter->deleteLater(); + + } else if (thing->thingClassId() == sunspecMeterThingClassId) { + SunSpecMeter *sunSpecMeter = m_sunSpecMeters.take(thing); + if (sunSpecMeter) + sunSpecMeter->deleteLater(); + + } else if (thing->thingClassId() == sunspecTrackerThingClassId) { + SunSpecTracker *sunSpecTracker = m_sunSpecTrackers.take(thing); + if (sunSpecTracker) + sunSpecTracker->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()) { hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer); m_refreshTimer = nullptr; @@ -84,18 +195,76 @@ void IntegrationPluginSunSpec::thingRemoved(Thing *thing) void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info) { Thing *thing = info->thing(); - //Action action = info->action(); + Action action = info->action(); if (thing->thingClassId() == sunspecInverterThingClassId) { + SunSpecInverter *sunSpecInverter = m_sunSpecInverters.value(thing); + if (!sunSpecInverter) { + qWarning(dcSunSpec()) << "Could not find SunSpec inverter for thing" << thing->name(); + info->finish(Thing::ThingErrorHardwareNotAvailable); + return; + } info->finish(Thing::ThingErrorActionTypeNotFound); + } else if (thing->thingClassId() == sunspecMeterThingClassId) { info->finish(Thing::ThingErrorActionTypeNotFound); + } else if (thing->thingClassId() == sunspecTrackerThingClassId) { info->finish(Thing::ThingErrorActionTypeNotFound); - } else if (thing->thingClassId() == sunspecPvModuleThingClassId) { - info->finish(Thing::ThingErrorActionTypeNotFound); + } else if (thing->thingClassId() == sunspecStorageThingClassId) { - info->finish(Thing::ThingErrorActionTypeNotFound); + 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() == sunspecStorageEnableChargingLimitActionTypeId) { + int value = (action.param(sunspecStorageEnableChargingLimitActionEnableChargingLimitParamTypeId).value().toBool() << 1) | thing->stateValue(sunspecStorageEnableDischargingLimitStateTypeId).toBool(); + QUuid requestId = sunSpecStorage->setStorageControlMode(value); + 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() == sunspecStorageEnableDischargingLimitActionTypeId) { + int value = (action.param(sunspecStorageEnableDischargingLimitActionEnableDischargingLimitParamTypeId).value().toBool() << 1) | thing->stateValue(sunspecStorageEnableChargingLimitStateTypeId).toBool(); + QUuid requestId = sunSpecStorage->setStorageControlMode(value); + 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()); } @@ -104,6 +273,18 @@ void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info) void IntegrationPluginSunSpec::onRefreshTimer() { //get data + foreach (SunSpecInverter *inverter, m_sunSpecInverters) { + inverter->getInverterMap(); + } + foreach (SunSpecMeter *meter, m_sunSpecMeters) { + Q_UNUSED(meter) //TODO + } + foreach (SunSpecTracker *tracker, m_sunSpecTrackers) { + Q_UNUSED(tracker) //TODO + } + foreach (SunSpecStorage *storage, m_sunSpecStorages) { + Q_UNUSED(storage) //TODO + } } void IntegrationPluginSunSpec::onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value) @@ -117,3 +298,137 @@ void IntegrationPluginSunSpec::onPluginConfigurationChanged(const ParamTypeId &p } } } + +void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::InverterData &inverterData) +{ + SunSpecInverter *inverter = static_cast(sender()); + Thing *thing = m_sunSpecInverters.key(inverter); + + if(!thing) { + return; + } + thing->setStateValue(sunspecInverterAcPowerStateTypeId, inverterData.acPower); + thing->setStateValue(sunspecInverterAcEnergyStateTypeId, inverterData.acEnergy/1000.00); + thing->setStateValue(sunspecInverterLineFrequencyStateTypeId, inverterData.lineFrequency); + thing->setStateValue(sunspecInverterTotalCurrentStateTypeId, inverterData.acCurrent); + thing->setStateValue(sunspecInverterCabinetTemperatureStateTypeId, inverterData.cabinetTemperature); + + thing->setStateValue(sunspecInverterPhaseACurrentStateTypeId, inverterData.phaseACurrent); + thing->setStateValue(sunspecInverterPhaseBCurrentStateTypeId, inverterData.phaseBCurrent); + thing->setStateValue(sunspecInverterPhaseCCurrentStateTypeId, inverterData.phaseCCurrent); + + thing->setStateValue(sunspecInverterPhaseANVoltageStateTypeId, inverterData.phaseVoltageAN); + thing->setStateValue(sunspecInverterPhaseBNVoltageStateTypeId, inverterData.phaseVoltageBN); + thing->setStateValue(sunspecInverterPhaseCNVoltageStateTypeId, inverterData.phaseVoltageCN); + + switch(inverterData.operatingState) { + case SunSpec::SunSpecOperatingState::Off: + thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Off"); + break; + case SunSpec::SunSpecOperatingState::MPPT: + thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "MPPT"); + break; + case SunSpec::SunSpecOperatingState::Fault: + thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Fault"); + break; + case SunSpec::SunSpecOperatingState::Standby: + thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Standby"); + break; + case SunSpec::SunSpecOperatingState::Sleeping: + thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Sleeping"); + break; + case SunSpec::SunSpecOperatingState::Starting: + thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Starting"); + break; + case SunSpec::SunSpecOperatingState::Throttled: + thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Throttled"); + break; + case SunSpec::SunSpecOperatingState::ShuttingDown: + thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Shutting down"); + break; + } + + switch(inverterData.event) { + case SunSpec::SunSpecEvent1::OVER_TEMP: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Over temperature"); + break; + case SunSpec::SunSpecEvent1::UNDER_TEMP: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Under temperature"); + break; + case SunSpec::SunSpecEvent1::GroundFault: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Ground fault"); + break; + case SunSpec::SunSpecEvent1::MEMORY_LOSS: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Memory loss"); + break; + case SunSpec::SunSpecEvent1::AC_OVER_VOLT: + thing->setStateValue(sunspecInverterErrorStateTypeId, "AC voltage above limit"); + break; + case SunSpec::SunSpecEvent1::CABINET_OPEN: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Cabinet open"); + break; + case SunSpec::SunSpecEvent1::AC_DISCONNECT: + thing->setStateValue(sunspecInverterErrorStateTypeId, "AC disconnect open"); + break; + case SunSpec::SunSpecEvent1::AC_UNDER_VOLT: + thing->setStateValue(sunspecInverterErrorStateTypeId, "AC voltage under limit"); + break; + case SunSpec::SunSpecEvent1::DC_DISCONNECT: + thing->setStateValue(sunspecInverterErrorStateTypeId, "DC disconnect open"); + break; + case SunSpec::SunSpecEvent1::DcOverVolatage: + thing->setStateValue(sunspecInverterErrorStateTypeId, "DC over voltage"); + break; + case SunSpec::SunSpecEvent1::OVER_FREQUENCY: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Frequency above limit"); + break; + case SunSpec::SunSpecEvent1::GRID_DISCONNECT: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Grid disconnect"); + break; + case SunSpec::SunSpecEvent1::HW_TEST_FAILURE: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Hardware test failure"); + break; + case SunSpec::SunSpecEvent1::MANUAL_SHUTDOWN: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Manual shutdown"); + break; + case SunSpec::SunSpecEvent1::UNDER_FREQUENCY: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Frequency under limit"); + break; + case SunSpec::SunSpecEvent1::BLOWN_STRING_FUSE: + thing->setStateValue(sunspecInverterErrorStateTypeId, "Blown string fuse on input"); + break; + } +} + +void IntegrationPluginSunSpec::onStorageDataReceived(const SunSpecStorage::StorageData &storageData) +{ + SunSpecStorage *storage = static_cast(sender()); + Thing *thing = m_sunSpecStorages.key(storage); + + if(!thing) { + return; + } + thing->setStateValue(sunspecStorageStorageStateStateTypeId, storageData.chargingState); +} + +void IntegrationPluginSunSpec::onMeterDataReceived(const SunSpecMeter::MeterData &meterData) +{ + SunSpecMeter *meter = static_cast(sender()); + Thing *thing = m_sunSpecMeters.key(meter); + + if(!thing) { + return; + } + //thing->setStateValue(sunspecMeterStorageStateStateTypeId, meterData.event); +} + +void IntegrationPluginSunSpec::onTrackerDataReceived(const SunSpecTracker::TrackerData &trackerData) +{ + SunSpecTracker *tracker = static_cast(sender()); + Thing *thing = m_sunSpecTrackers.key(tracker); + + if(!thing) { + return; + } + //thing->setStateValue(sunspecStorageStorageStateStateTypeId, storageData.chargingState); +} diff --git a/sunspec/integrationpluginsunspec.h b/sunspec/integrationpluginsunspec.h index 85e62a9..48c393a 100644 --- a/sunspec/integrationpluginsunspec.h +++ b/sunspec/integrationpluginsunspec.h @@ -36,6 +36,11 @@ #include "../modbus/modbustcpmaster.h" +#include "sunspecinverter.h" +#include "sunspecstorage.h" +#include "sunspecmeter.h" +#include "sunspectracker.h" + #include class IntegrationPluginSunSpec: public IntegrationPlugin @@ -45,22 +50,21 @@ class IntegrationPluginSunSpec: public IntegrationPlugin Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginsunspec.json") Q_INTERFACES(IntegrationPlugin) - public: explicit IntegrationPluginSunSpec(); - - void discoverThings(ThingDiscoveryInfo *info) override; void setupThing(ThingSetupInfo *info) override; void postSetupThing(Thing *thing) override; void thingRemoved(Thing *thing) override; void executeAction(ThingActionInfo *info) override; private: - PluginTimer *m_refreshTimer = nullptr; - QHash m_modbusTcpMasters; QHash m_asyncActions; + QHash m_sunSpecInverters; + QHash m_sunSpecStorages; + QHash m_sunSpecMeters; + QHash m_sunSpecTrackers; void update(Thing *thing); private slots: @@ -72,8 +76,10 @@ private slots: void onWriteRequestExecuted(QUuid requestId, bool success); void onWriteRequestError(QUuid requestId, const QString &error); - void onReceivedHoldingRegister(quint32 slaveAddress, quint32 modbusRegister, const QVector &values); - void onReceivedInputRegister(quint32 slaveAddress, quint32 modbusRegister, const QVector &values); + void onInverterDataReceived(const SunSpecInverter::InverterData &inverterData); + void onStorageDataReceived(const SunSpecStorage::StorageData &storageData); + void onMeterDataReceived(const SunSpecMeter::MeterData &meterData); + void onTrackerDataReceived(const SunSpecTracker::TrackerData &trackerData); }; #endif // INTEGRATIONPLUGINSUNSPEC_H diff --git a/sunspec/integrationpluginsunspec.json b/sunspec/integrationpluginsunspec.json index 3a0ffbf..f394fda 100644 --- a/sunspec/integrationpluginsunspec.json +++ b/sunspec/integrationpluginsunspec.json @@ -32,10 +32,28 @@ "type": "QString" }, { - "id": "08bf6597-976d-4fb9-8fc2-4372c07961d4", + "id": "418d90eb-16e9-47e4-83e3-a116a8fa4de7", + "name":"manufacturer", + "displayName": "Manufacturer", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "edf1bc0f-d143-4b75-a11f-635339fb30bf", + "name":"deviceModel", + "displayName": "Device model", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "d5181377-c746-4352-8683-6d90a4a83e6e", "name":"serialNumber", "displayName": "Serial number", - "type": "QString" + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" } ], "stateTypes":[ @@ -57,48 +75,150 @@ "type": "bool", "defaultValue": 0, "writable": true - } - ] - }, - { - "name": "sunspecPvModule", - "displayName": "SunSpec PV Module", - "id": "fb7ff0df-a745-4313-83e4-1e5007b06fe2", - "createMethods": [ "User" ], - "interfaces": ["connectable"], - "paramTypes": [ - { - "id": "890e468b-fc76-4641-9569-93cd45e6bb8d", - "name":"ipAddress", - "displayName": "IP address", - "type": "QString" }, { - "id": "d5a9510d-720f-4a28-9d91-977eedd022bd", - "name":"serialNumber", - "displayName": "Serial number", - "type": "QString" - } - ], - "stateTypes":[ - { - "id": "edc032d2-022f-43f7-9131-831ad23ee62a", - "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": "int", + "unit": "Ampere", + "defaultValue": 0 }, { - "id": "0b7485eb-3cf7-45b2-87cf-9ec85366b197", - "name": "power", - "displayName": "Power", - "displayNameEvent": "Power changed", - "displayNameAction": "Change power", - "type": "bool", - "defaultValue": 0, - "writable": true + "id": "3140ccd3-40cf-46c8-8bb2-8c3ea4582f84", + "name": "phaseACurrent", + "displayName": "Phase A current", + "displayNameEvent": "Phase A current changed", + "type": "int", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "7ea1a53a-6fd9-4914-8283-b57aa1aaaebf", + "name": "phaseBCurrent", + "displayName": "Phase B current", + "displayNameEvent": "Phase B current changed", + "type": "int", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "aa4b4cf5-43d0-4be5-9505-403918b5371d", + "name": "phaseCCurrent", + "displayName": "Phase C current", + "displayNameEvent": "Phase C current changed", + "type": "int", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "f08521aa-9c38-4c31-95e1-acb616f6e9c6", + "name": "phaseANVoltage", + "displayName": "Phase AN voltage", + "displayNameEvent": "Phase AN volatage changed", + "type": "int", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "739b8805-d522-4406-bede-d1e4200a3aa9", + "name": "phaseBNVoltage", + "displayName": "Phase BN voltage", + "displayNameEvent": "Phase BN voltage changed", + "type": "int", + "unit": "Volt", + "defaultValue": 0 + }, + { + "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": "acPower", + "displayName": "AC power", + "displayNameEvent": "AC power changed", + "type": "int", + "unit": "KiloWatt", + "defaultValue": 0 + }, + { + "id": "faa45cae-ed28-4150-9036-fceddf9d6776", + "name": "lineFrequency", + "displayName": "Line frequency", + "displayNameEvent": "Line frequency changed", + "type": "int", + "unit": "Hertz", + "defaultValue": 0 + }, + { + "id": "d493880d-eb58-4530-8010-8ea4f6d63387", + "name": "acEnergy", + "displayName": "AC energy", + "displayNameEvent": "AC energy changed", + "type": "int", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "44b0320f-89a7-4248-bad4-288ef898a5cc", + "name": "cabinetTemperature", + "displayName": "Cabinet temperature", + "displayNameEvent": "Cabinet temperature changed", + "type": "int", + "unit": "DegreeCelsius", + "defaultValue": 0 + }, + { + "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" } ] }, @@ -116,10 +236,28 @@ "type": "QString" }, { - "id": "89af6d27-4b81-42ef-9883-1e2ba2067884", + "id": "aa1ca51d-5e9c-4a1a-a4ec-e479d4f702d5", + "name":"manufacturer", + "displayName": "Manufacturer", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "52da5388-28d7-48a2-8ad6-4c1e852a4348", + "name":"deviceModel", + "displayName": "Device model", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "9fa7288c-e7a1-4163-baa1-6f484e1feb55", "name":"serialNumber", "displayName": "Serial number", - "type": "QString" + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" } ], "stateTypes":[ @@ -133,14 +271,113 @@ "cached": false }, { - "id": "36f18720-69ca-4021-9328-262d87397f1b", - "name": "power", - "displayName": "Power", - "displayNameEvent": "Power changed", - "displayNameAction": "Change power", - "type": "bool", - "defaultValue": 0, - "writable": true + "id": "7855d176-c6b8-439e-9f12-a71f01c1c7aa", + "name": "totalCurrent", + "displayName": "Total AC current", + "displayNameEvent": "Total AC current changed", + "type": "int", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "da494d99-5de3-4d03-b7dd-33a33db32164", + "name": "phaseACurrent", + "displayName": "Phase A current", + "displayNameEvent": "Phase A current changed", + "type": "int", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "023b6e5c-be3f-4d8c-adfd-e009c7bebffb", + "name": "phaseBCurrent", + "displayName": "Phase B current", + "displayNameEvent": "Phase B current changed", + "type": "int", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "2676ad36-3d97-4691-8334-e13934cc58d1", + "name": "phaseCCurrent", + "displayName": "Phase C current", + "displayNameEvent": "Phase C current changed", + "type": "int", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "bc54ca1e-1476-40eb-9974-9e3c2f893dd8", + "name": "lnACVoltage", + "displayName": "Line to Neutral AC Voltage", + "displayNameEvent": "Line to Neutral AC Voltage changed", + "type": "int", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "4bd32d91-877a-4821-9db9-5981de20d21d", + "name": "phaseANVoltage", + "displayName": "Phase AN voltage", + "displayNameEvent": "Phase AN volatage changed", + "type": "int", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "f090cb78-d7ed-44fd-a5ad-ea9016021c34", + "name": "phaseBNVoltage", + "displayName": "Phase BN voltage", + "displayNameEvent": "Phase BN voltage changed", + "type": "int", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "fa5aa6f5-e67d-485a-835f-24e49298856c", + "name": "phaseCNVoltage", + "displayName": "Phase CN voltage", + "displayNameEvent": "Phase CN voltage changed", + "type": "int", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "dfcf52f5-6279-4d25-b7c8-a93b92c39a0c", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "int", + "unit": "Hertz", + "defaultValue": 0 + }, + { + "id": "c28c642f-46da-44de-ba0d-c4cbfadbf2cd", + "name": "totalRealPower", + "displayName": "Total real power", + "displayNameEvent": "Total real power changed", + "type": "int", + "unit": "KiloWatt", + "defaultValue": 0 + }, + { + "id": "73ebf57f-1ad2-4d19-bfd9-9e0a514c1243 +", + "name": "energyExported", + "displayName": "Total real energy exported", + "displayNameEvent": "Total real energy exported changed", + "type": "int", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "63fa4721-1b0a-458c-b66c-17c161107f0d", + "name": "energyImported", + "displayName": "Total real energy imported", + "displayNameEvent": "Total real energy imported changed", + "type": "int", + "unit": "KiloWattHour", + "defaultValue": 0 } ] }, @@ -158,10 +395,28 @@ "type": "QString" }, { - "id": "67ce05ff-14bf-4d43-94ba-d5f290278514", + "id": "2795052c-5d63-423d-b44e-fdf02dfaac37", + "name":"manufacturer", + "displayName": "Manufacturer", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "7528c840-da8c-40ba-ab82-914cfc686a49", + "name":"deviceModel", + "displayName": "Device model", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "ce9cc598-947c-4efc-b04f-21e784c5d2df", "name":"serialNumber", "displayName": "Serial number", - "type": "QString" + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" } ], "stateTypes":[ @@ -200,10 +455,28 @@ "type": "QString" }, { - "id": "ae76db81-7969-440f-85f0-28ed46d772db", + "id": "1d8f1ef7-33ad-40ed-99dd-ec54f87c2f17", + "name":"manufacturer", + "displayName": "Manufacturer", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "2a7f8464-6f03-4113-ac1d-d8ebd8705695", + "name":"deviceModel", + "displayName": "Device model", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "2003acdb-38ad-4184-baf8-9ef8e67fc452", "name":"serialNumber", "displayName": "Serial number", - "type": "QString" + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" } ], "stateTypes":[ @@ -217,14 +490,146 @@ "cached": false }, { - "id": "9c0e0b08-4461-4e54-aaf3-cab9155028da", - "name": "power", - "displayName": "Power", - "displayNameEvent": "Power changed", - "displayNameAction": "Change power", + "id": "3171a6e0-43a7-4de8-8e20-f748e44af7ac", + "name": "batteryCritical", + "displayName": "Battery critical", + "displayNameEvent": "Battery critical changed", "type": "bool", - "defaultValue": 0, + "defaultValue": false + }, + { + "id": "0bf53f80-97f8-488b-b514-58f9fe08c183", + "name": "batteryLevel", + "displayName": "Battery level", + "displayNameEvent": "Battery level changed", + "type": "int", + "unit": "Percentage", + "minValue": 0, + "maxValue": 100, + "defaultValue": 0 + }, + { + "id": "da2b19c5-0f48-49d1-93f0-abdc0051407d", + "name": "storageState", + "displayName": "State", + "displayNameEvent": "State changed", + "type": "QString", + "possibleValues": [ + "Off", + "Empty", + "Discharging", + "Charging", + "Full", + "Holding", + "Testing" + ], + "defaultValue": "Off" + }, + { + "id": "221a2ef6-0a92-4ff0-87fe-7bd920dbec0b", + "name": "gridCharging", + "displayName": "Grid charging", + "displayNameEvent": "Grid charging changed", + "type": "bool", + "defaultValue": false, + "writable": true, + "displayNameAction": "Set grid charging" + }, + { + "id": "1f530f79-c0d2-466b-90e1-79149e34d92f", + "name": "enableChargingLimit", + "displayName": "Charging limit", + "displayNameEvent": "Charging limit changed", + "type": "bool", + "defaultValue": false, + "writable": true, + "displayNameAction": "Enable Charging Limit" + }, + { + "id": "7f469bbc-64a5-4045-8d5f-9a1a85039851", + "name": "chargingRate", + "displayName": "Charging rate", + "displayNameEvent": "Charging rate changed", + "type": "int", + "minValue": -100, + "maxValue": 100, + "unit": "Percentage", + "defaultValue": false, + "writable": true, + "displayNameAction": "Set charging rate" + }, + { + "id": "bc99a159-815a-40ab-a6e8-b46f315305f7", + "name": "enableDischargingLimit", + "displayName": "Discharging limit", + "displayNameEvent": "Discharging limit changed", + "displayNameAction": "Enable Discharging Limit", + "type": "bool", + "defaultValue": false, "writable": true + }, + { + "id": "6068f030-acce-44a2-b95f-bd00dd5ca760", + "name": "dischargingRate", + "displayName": "Discharging rate", + "displayNameEvent": "Discharging rate changed", + "type": "int", + "minValue": -100, + "maxValue": 100, + "unit": "Percentage", + "defaultValue": false, + "writable": true, + "displayNameAction": "Set discharging rate" + } + ] + }, + { + "name": "sunspecStringCombiner", + "displayName": "SunSpec String Combiner", + "id": "7787f238-f5a3-4ba5-b3ec-7d513b87bf71", + "createMethods": [ "User" ], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "226697c3-37bd-49c7-8c0e-40537573c18e", + "name":"ipAddress", + "displayName": "IP address", + "type": "QString" + }, + { + "id": "3f126e65-05e2-46a3-aeeb-bfacc0bdd9b0", + "name":"manufacturer", + "displayName": "Manufacturer", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "40666030-3609-467f-b26c-3a42d1c6dca1", + "name":"deviceModel", + "displayName": "Device model", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "f5046138-9271-4a91-a3eb-c2ecfa755ef5", + "name":"serialNumber", + "displayName": "Serial number", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + } + ], + "stateTypes":[ + { + "id": "3f791612-50fd-4a3c-ac19-27e01dd8c63a", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false } ] } diff --git a/sunspec/sunspec.cpp b/sunspec/sunspec.cpp index e69de29..37b22d0 100644 --- a/sunspec/sunspec.cpp +++ b/sunspec/sunspec.cpp @@ -0,0 +1,413 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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, QObject *parent) : + QObject(parent), + m_hostAddress(hostAddress), + m_port(port) +{ + 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() +{ + +} + +bool SunSpec::connectModbus() +{ + return m_modbusTcpClient->connectDevice();; +} + +void SunSpec::findBaseRegister() +{ + qCDebug(dcSunSpec()) << "find base register"; + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 40000, 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(); TODO + if ((unit.value(0) << 16 | unit.value(1)) == 0x53756e53) { + //Well-known value. Uniquely identifies this as a SunSpec Modbus Map + qCDebug(dcSunSpec()) << "Found start of modbus map"; + //emit foundBaseRegister(modbusAddress); + } + } else { + qCWarning(dcSunSpec()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { + qCWarning(dcSunSpec()) << "Modbus reply error:" << error; + reply->finished(); // To make sure it will be deleted + }); + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + delete reply; // broadcast replies return immediately + return; + } + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + return; + } +} + +void SunSpec::findModbusMap(const QList &ids, uint modbusAddressOffset) +{ + qCDebug(dcSunSpec()) << "Find modbus map. 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(); + BlockId blockId = BlockId(unit.value(0)); + int blockLength = unit.value(1); + if (blockId > 800 || blockId == BlockId::End) { + qCDebug(dcSunSpec()) << "Block id not found, Id:" << ids; + modbusMapSearchFinished(ids, 0, "Ids not found"); + return; + } + if (ids.contains(blockId)) { + qCDebug(dcSunSpec()) << "Found block" << BlockId(blockId); + foundModbusMap(BlockId(blockId), modbusAddress); + } else { + //read next block header + qCDebug(dcSunSpec()) << "Found Block" << blockId << "with block length" << blockLength; + findModbusMap(ids, modbusAddress+2+blockLength-m_baseRegister); //read next block + } + } else { + qCWarning(dcSunSpec()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { + qCWarning(dcSunSpec()) << "Modbus replay error:" << error; + reply->finished(); // To make sure it will be deleted + }); + } else { + delete reply; // broadcast replies return immediately + return; + } + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + return; + } +} + +void SunSpec::readMapHeader(uint modbusAddress) +{ + qCDebug(dcSunSpec()) << "Read block header. Modbus Address:" << modbusAddress << "Slave ID" << m_slaveId; + if (modbusAddress == 40000) + 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(); + BlockId mapId = BlockId(unit.value(0)); + int mapLength = unit.value(1); + qCDebug(dcSunSpec()) << "Received block header response. Map ID:" << mapId << "Map length" << mapLength; + mapHeaderReceived(modbusAddress, mapId, mapLength); + } else { + qCWarning(dcSunSpec()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { + qCWarning(dcSunSpec()) << "Modbus reply error:" << error; + reply->finished(); // To make sure it will be deleted + }); + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + delete reply; // broadcast replies return immediately + return; + } + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + return; + } +} + +void SunSpec::readMap(uint modbusAddress, uint modelLength) +{ + qCDebug(dcSunSpec()) << "Read map. Modbus Address" << modbusAddress << ", Slave ID" << m_slaveId; + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, modbusAddress, modelLength+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(); + BlockId mapId = BlockId(unit.value(0)); + uint mapLength = unit.value(1); + qCDebug(dcSunSpec()) << "Received map. Modbus address" << modbusAddress << "Map ID" << mapId << "Map Length" << mapLength; + emit mapReceived(mapId, mapLength, unit.values().mid(2)); + } else { + qCWarning(dcSunSpec()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { + qCWarning(dcSunSpec()) << "Modbus reply error:" << error; + reply->finished(); // To make sure it will be deleted + }); + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + delete reply; // broadcast replies return immediately + return; + } + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + return; + } +} + +void SunSpec::readCommonMap() +{ + qCDebug(dcSunSpec()) << "Read common block"; + + // Base and Alternate Base Register Addresses + // DeviceModbus maps begin at one of three well-­known Modbus base addresses. + // Preferred Base Register: 40000 + // Alternate Base Register: 50000 + // Alternate Base Register: 00000 + + // Common Model model-length is 66 + // First two registers are the SunSpec Modbus Map identifier + + qCDebug(dcSunSpec()) << "Read common block 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(); + uint modbusAddress = unit.startAddress(); + BlockId mapId = BlockId(unit.value(0)); + int mapLength = unit.value(1); + 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()) << "Received common block response. Manufacturer" << m_manufacturer << "Model" << m_deviceModel << "Serial number" << m_serialNumber; + mapHeaderReceived(modbusAddress, mapId, mapLength); + } else { + qCWarning(dcSunSpec()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { + qCWarning(dcSunSpec()) << "Modbus reply error:" << error; + reply->finished(); // To make sure it will be deleted + }); + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + delete reply; // broadcast replies return immediately + return; + } + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + return; + } +} + +void SunSpec::onReceivedHoldingRegister(quint32 slaveAddress, quint32 modbusRegister, const QVector &data) +{ + if (modbusRegister == 00000 || modbusRegister == 40000 || modbusRegister == 50000) { + // Common block, 66 registers long + 2 header registers + + qCDebug(dcSunSpec()) << "Sunspec Identification:" << convertModbusRegisters(data, 0, 2); + + if (convertModbusRegisters(data, 0, 2) != "SunS") { + qCWarning(dcSunSpec()) << "Could not find SunS value at" << modbusRegister << "at Slave" << slaveAddress; + return; + } + m_baseRegister = modbusRegister; + + //Mandatory SunSpec Registers + // ID: 40003 + qCDebug(dcSunSpec()) << "Id:" << data[MandatoryRegistersModel1::Manufacturer]; + + // Manufacturer: 40005 | 16 + qCDebug(dcSunSpec()) << "Manufacturer:" << QString::fromLatin1(convertModbusRegisters(data, MandatoryRegistersModel1::Manufacturer, 16)); + + // Thing model: 40021 | 16 + qCDebug(dcSunSpec()) << "Thing model:" << QString::fromLatin1(convertModbusRegisters(data, MandatoryRegistersModel1::Model, 16)); + + // Data manager version: 40037 | 8 + qCDebug(dcSunSpec()) << "Data manager version:" << QString::fromLatin1(convertModbusRegisters(data, 36, 8)); + + // Inverter Version: 40045 | 8 + qCDebug(dcSunSpec()) << "Inverter version:" << QString::fromLatin1(convertModbusRegisters(data, 44, 8)); + + // Serial Number: 40053 | 16 + qCDebug(dcSunSpec()) << "Serial number:" << QString::fromLatin1(convertModbusRegisters(data, MandatoryRegistersModel1::SerialNumber, 16)); + + // Modbus thing address : 40069 | 1 + qCDebug(dcSunSpec()) << "Thing modbus address:" << data[67]; + }; +} + + +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::convertValueWithSSF(quint16 rawValue, quint16 sunssf) +{ + float value; + value = rawValue * (10^static_cast(sunssf)); + return value; +} + +float SunSpec::convertFloatValues(quint16 rawValue0, quint16 rawValue1) +{ + float value; + uint32_t i = qFromLittleEndian(((uint32_t)rawValue0 << 16) + rawValue1); + memcpy(&value, &i, sizeof(float)); + return value; +} + +void SunSpec::onModbusStateChanged(QModbusDevice::State state) +{ + bool connected = (state != QModbusDevice::UnconnectedState); + if (!connected) { + //try to reconnect in 10 seconds + QTimer::singleShot(10000, m_modbusTcpClient, [this] { + if (m_modbusTcpClient->connectDevice()) { + qCDebug(dcSunSpec()) << "Could not reconnect"; + } + }); + } else { + readCommonMap(); + } + emit connectionStateChanged(connected); +} + +QUuid SunSpec::writeHoldingRegister(uint slaveAddress, uint registerAddress, quint16 value) +{ + return writeHoldingRegisters(slaveAddress, registerAddress, QVector() << value); +} + +QUuid SunSpec::writeHoldingRegisters(uint slaveAddress, 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, slaveAddress)) { + 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()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcSunSpec()) << "Modbus reply error:" << error; + reply->finished(); // To make sure it will be deleted + }); + QTimer::singleShot(200, reply, &QModbusReply::deleteLater); + } else { + delete reply; // broadcast replies return immediately + return ""; + } + } else { + qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + return ""; + } + return requestId; +} diff --git a/sunspec/sunspec.h b/sunspec/sunspec.h index e69de29..878003b 100644 --- a/sunspec/sunspec.h +++ b/sunspec/sunspec.h @@ -0,0 +1,223 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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: + + 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 SunSpecEvent1 { + GroundFault = 0, + DcOverVolatage, + AC_DISCONNECT, + DC_DISCONNECT, + GRID_DISCONNECT, + CABINET_OPEN, + MANUAL_SHUTDOWN, + OVER_TEMP, + OVER_FREQUENCY, + UNDER_FREQUENCY, + AC_OVER_VOLT, + AC_UNDER_VOLT, + BLOWN_STRING_FUSE, + UNDER_TEMP, + MEMORY_LOSS, + HW_TEST_FAILURE + }; + Q_ENUM(SunSpecEvent1) + + enum BlockId { + Common = 1, + BasicAggregator = 2, + SecureDatasetReadRequest = 3, + SecureDatasetReadResponse = 4, + SecureWriteRequest = 5, + SecureWriteSequentialRequest = 6, + SecureWriteResponseModel = 7, + GetDeviceSecurityCertificate = 8, + SetOperatorSecurityCertificate = 9, + CommunicationInterfaceHeader = 10, + EthernetLinkLayer = 11, + IPv4 = 12, + IPv6 = 13, + ProxyServer = 14, + InterfaceCountersModel = 15, + SimpleIpNetwork = 16, + SerialInterface = 17, + CellularLink = 18, + PPPLink = 19, + InverterSinglePhase = 101, + InverterSplitPhase = 102, + InverterThreePhase = 103, + InverterSinglePhaseFloat = 111, + InverterSplitPhaseFloat = 112, + InverterThreePhaseFloat = 113, + Nameplate = 120, + BasicSettings = 121, + MeasurementsStatus = 122, + ImmediateControls = 123, + Storage = 124, + Pricing = 125, + StaticVoltVAR = 126, + FreqWattParam = 127, + DynamicReactiveCurrent = 128, + LVRTD = 129, + HVRTD = 130, + WattPF = 131, + VoltWatt = 132, + BasicScheduling = 133, + FreqWattCrv = 134, + LFRT = 135, + HFRT = 136, + LVRTC = 137, + HVRTC = 138, + MultipleMPPTInverterExtensionModel = 160, + SinglePhaseMeter = 201, + SplitSinglePhaseMeter = 202, + WyeConnectThreePhaseMeter = 203, + DeltaConnectThreePhaseMeter = 204, + SinglePhaseMeterFloat = 211, + SplitSinglePhaseMeterFloat = 212, + WyeConnectThreePhaseMeterFloat = 213, + DeltaConnectThreePhaseMeterFloat = 214, + SecureACMeterSelectedReadings = 220, + IrradianceModel = 302, + BackOfModuleTemperatureModel = 303, + InclinometerModel = 304, + GPS = 305, + ReferencePointModel = 306, + BaseMet = 307, + MiniMetModel = 308, + StringCombiner = 401, + StringCombinerAdvanced = 402, + StringCombinerCurrent = 403, + StringCombinerCurrentAdvanced = 404, + SolarModuleFloat = 501, + SolarModule = 502, + TrackerController = 601, + EnergyStorageBaseModel = 801, + BatteryBaseModel = 802, + LithiumIonBatteryModel = 803, + VerisStatusConfiguration = 64001, + MersenGreenString = 64020, + EltekInverterExtension = 64101, + OutBackAXSDevice = 64110, + BasicChargeController = 64111, + OutBackFMChargeController = 64112, + End = 65535 + }; + Q_ENUM(BlockId) + + explicit SunSpec(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); + ~SunSpec(); + bool connectModbus(); + 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 = "unkown"; + QString m_deviceModel = "unknown"; + QString m_serialNumber = "unknown"; + + void findBaseRegister(); + void findModbusMap(const QList &mapIds, uint modbusAddressOffset = 69); + + void readMapHeader(uint modbusAddress); + void readMap(uint modbusAddress, uint modelLength); //modbusAddress = model start address, model length is without header + void readCommonMap(); + + float convertValueWithSSF(quint16 rawValue, 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 slaveAddress, uint registerAddress, quint16 value); + QUuid writeHoldingRegisters(uint slaveAddress, uint registerAddress, const QVector &values); + +signals: + void connectionStateChanged(bool status); + void requestExecuted(QUuid requetId, bool success); + + void foundModbusMap(BlockId mapId, int modbusStartRegister); + void modbusMapSearchFinished(const QList &mapIds, uint modbusStartRegister, const QString &error); + + void mapHeaderReceived(uint modbusAddress, BlockId mapId, uint mapLength); + void mapReceived(BlockId mapId, uint mapLength, QVector data); + +private slots: + void onModbusStateChanged(QModbusDevice::State state); + + void onRequestExecuted(QUuid requestId, bool success); + void onRequestError(QUuid requestId, const QString &error); + void onReceivedHoldingRegister(quint32 slaveAddress, quint32 modbusRegister, const QVector &values); +}; + +#endif // SUNSPEC_H diff --git a/sunspec/sunspec.pro b/sunspec/sunspec.pro index a9c3e6a..f005cb0 100644 --- a/sunspec/sunspec.pro +++ b/sunspec/sunspec.pro @@ -7,9 +7,17 @@ QT += \ SOURCES += \ integrationpluginsunspec.cpp \ sunspec.cpp \ - ../modbus/modbustcpmaster.cpp \ + sunspecinverter.cpp \ + sunspecmeter.cpp \ + sunspecstorage.cpp \ + sunspectracker.cpp \ + sunspecstringcombiner.cpp \ HEADERS += \ integrationpluginsunspec.h \ sunspec.h \ - ../modbus/modbustcpmaster.h \ + sunspecinverter.h \ + sunspecmeter.h \ + sunspecstorage.h \ + sunspectracker.h \ + sunspecstringcombiner.h \ diff --git a/sunspec/sunspecinverter.cpp b/sunspec/sunspecinverter.cpp new file mode 100644 index 0000000..69b9591 --- /dev/null +++ b/sunspec/sunspecinverter.cpp @@ -0,0 +1,129 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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" + +SunSpecInverter::SunSpecInverter(const QHostAddress &hostAddress, uint port, QObject *parent) : SunSpec(hostAddress, port, parent) +{ + connect(m_modbusTcpClient, &QModbusClient::stateChanged, this, [this] (QModbusDevice::State state) { + if (state == QModbusDevice::ConnectedState) { + qCDebug(dcSunSpec()) << "Inverter connected successfully"; + QList mapIds; + mapIds.append(BlockId::InverterSinglePhase); + mapIds.append(BlockId::InverterSplitPhase); + mapIds.append(BlockId::InverterThreePhase); + mapIds.append(BlockId::InverterSinglePhaseFloat); + mapIds.append(BlockId::InverterSplitPhaseFloat); + mapIds.append(BlockId::InverterThreePhaseFloat); + findModbusMap(mapIds); + } + }); + connect(this, &SunSpec::foundModbusMap, this, [this] (BlockId mapId, uint modbusRegisterAddress) { + qCDebug(dcSunSpec()) << "Read map header for mapId" << mapId << "and modbus register" << modbusRegisterAddress; + readMapHeader(modbusRegisterAddress); + }); + + connect(this, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, BlockId mapId, uint mapLength) { + m_id = mapId; + m_mapLength = mapLength; + m_mapModbusStartRegister = modbusAddress; + readMap(modbusAddress, mapLength); + }); + + connect(this, &SunSpec::mapReceived, this, &SunSpecInverter::onModbusMapReceived); +} + +void SunSpecInverter::getInverterMap() +{ + readMap(m_mapModbusStartRegister, m_mapLength); +} + +void SunSpecInverter::readInverterBlockHeader() +{ + readMapHeader(m_mapModbusStartRegister); +} + +void SunSpecInverter::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data) +{ + Q_UNUSED(mapLength) + switch (mapId) { + case BlockId::InverterSinglePhase: + case BlockId::InverterSplitPhase: + case BlockId::InverterThreePhase: { + InverterData inverterData; + inverterData.acCurrent= convertValueWithSSF(data[Model10X::Model10XAcCurrent], data[Model10X::Model10XAmpereScaleFactor]); + inverterData.acPower = convertValueWithSSF(data[Model10X::Model10XACPower], data[Model10X::Model10XWattScaleFactor]); + inverterData.lineFrequency = convertValueWithSSF(data[Model10X::Model10XLineFrequency], data[Model10X::Model10XHerzScaleFactor]); + + inverterData.phaseACurrent = convertValueWithSSF(data[Model10X::Model10XPhaseACurrent], data[Model10X::Model10XAmpereScaleFactor]); + inverterData.phaseBCurrent = convertValueWithSSF(data[Model10X::Model10XPhaseBCurrent], data[Model10X::Model10XAmpereScaleFactor]); + inverterData.phaseCCurrent = convertValueWithSSF(data[Model10X::Model10XPhaseCCurrent], data[Model10X::Model10XAmpereScaleFactor]); + + inverterData.phaseVoltageAN = convertValueWithSSF(data[Model10X::Model10XPhaseVoltageAN], data[Model10X::Model10XVoltageScaleFactor]); + inverterData.phaseVoltageBN = convertValueWithSSF(data[Model10X::Model10XPhaseVoltageBN], data[Model10X::Model10XVoltageScaleFactor]); + inverterData.phaseVoltageCN = convertValueWithSSF(data[Model10X::Model10XPhaseVoltageCN], data[Model10X::Model10XVoltageScaleFactor]); + + inverterData.acEnergy = convertValueWithSSF(data[Model10X::Model10XAcEnergy], data[Model10X::Model10XWattHoursScaleFactor]); + + inverterData.cabinetTemperature = convertValueWithSSF(data[Model10X::Model10XCabinetTemperature], data[Model10X::Model10XTemperatureScaleFactor]); + inverterData.event = SunSpecEvent1(data[Model10X::Model10XEvent1]); + inverterData.operatingState = SunSpecOperatingState(data[Model10X::Model10XOperatingState]); + emit inverterDataReceived(inverterData); + + } break; + case BlockId::InverterSinglePhaseFloat: + case BlockId::InverterSplitPhaseFloat: + case BlockId::InverterThreePhaseFloat: { + InverterData inverterData; + inverterData.acCurrent = convertFloatValues(data[Model11X::Model11XAcCurrent], data[Model11X::Model11XAcCurrent+1]); + + inverterData.phaseACurrent = convertFloatValues(data[Model11X::Model11XPhaseACurrent], data[Model11X::Model11XPhaseACurrent+1]); + inverterData.phaseBCurrent = convertFloatValues(data[Model11X::Model11XPhaseBCurrent], data[Model11X::Model11XPhaseBCurrent+1]); + inverterData.phaseCCurrent = convertFloatValues(data[Model11X::Model11XPhaseCCurrent], data[Model11X::Model11XPhaseCCurrent+1]); + + inverterData.phaseVoltageAN = convertFloatValues(data[Model11X::Model11XPhaseVoltageAN], data[Model11X::Model11XPhaseVoltageAN+1]); + inverterData.phaseVoltageBN = convertFloatValues(data[Model11X::Model11XPhaseVoltageBN], data[Model11X::Model11XPhaseVoltageBN+1]); + inverterData.phaseVoltageCN = convertFloatValues(data[Model11X::Model11XPhaseVoltageCN], data[Model11X::Model11XPhaseVoltageCN+1]); + + inverterData.acPower = convertFloatValues(data[Model11X::Model11XACPower], data[Model11X::Model11XACPower+1]); + inverterData.lineFrequency = convertFloatValues(data[Model11X::Model11XLineFrequency], data[Model11X::Model11XLineFrequency+1]); + + inverterData.acEnergy = convertFloatValues(data[Model11X::Model11XAcEnergy], data[Model11X::Model11XAcEnergy+1]); + inverterData.cabinetTemperature = convertFloatValues(data[Model11X::Model11XCabinetTemperature], data[Model11X::Model11XCabinetTemperature+1]); + inverterData.event = SunSpecEvent1(data[Model11X::Model11XEvent1]); + inverterData.operatingState = 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..7243c17 --- /dev/null +++ b/sunspec/sunspecinverter.h @@ -0,0 +1,118 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 SunSpec +{ + Q_OBJECT +public: + SunSpecInverter(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); + + enum Model10X { // Mandatory register + Model10XAcCurrent = 2, + Model10XPhaseACurrent = 3, + Model10XPhaseBCurrent = 4, + Model10XPhaseCCurrent = 5, + Model10XAmpereScaleFactor = 6, + Model10XPhaseVoltageAN = 10, + Model10XPhaseVoltageBN = 11, + Model10XPhaseVoltageCN = 12, + Model10XVoltageScaleFactor = 13, + Model10XACPower = 14, + Model10XWattScaleFactor = 15, + Model10XLineFrequency = 16, + Model10XHerzScaleFactor = 17, + Model10XAcEnergy = 24, + Model10XWattHoursScaleFactor = 25, + Model10XCabinetTemperature = 33, + Model10XTemperatureScaleFactor = 37, + Model10XOperatingState = 38, + Model10XEvent1 = 40 + }; + + 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 + }; + + 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 event; + SunSpecOperatingState operatingState; + }; + + void getInverterMap(); + +private: + BlockId m_id = BlockId::InverterThreePhase; //e.g. 103 for three phase inverter, 113 for three phase inverter with floating point representation + uint m_mapLength = 0; + uint m_mapModbusStartRegister = 40000; + + void readInverterBlockHeader(); + +private slots: + void onConnectionStateChanged(); + void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); + +signals: + void initFinished(); + void inverterDataReceived(InverterData data); +}; + +#endif // SUNSPECINVERTER_H diff --git a/sunspec/sunspecmeter.cpp b/sunspec/sunspecmeter.cpp new file mode 100644 index 0000000..d9b1763 --- /dev/null +++ b/sunspec/sunspecmeter.cpp @@ -0,0 +1,61 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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(const QHostAddress &hostAddress, uint port, QObject *parent) : SunSpec(hostAddress, port, parent) +{ + connect(m_modbusTcpClient, &QModbusClient::stateChanged, this, [this] (QModbusDevice::State state) { + if (state == QModbusDevice::ConnectedState) { + qCDebug(dcSunSpec()) << "Meter connected successfully"; + QList mapIds; + mapIds.append(BlockId::SinglePhaseMeter); + mapIds.append(BlockId::SplitSinglePhaseMeter); + mapIds.append(BlockId::WyeConnectThreePhaseMeter); + mapIds.append(BlockId::DeltaConnectThreePhaseMeter); + mapIds.append(BlockId::SinglePhaseMeterFloat); + mapIds.append(BlockId::SplitSinglePhaseMeterFloat); + mapIds.append(BlockId::WyeConnectThreePhaseMeterFloat); + mapIds.append(BlockId::DeltaConnectThreePhaseMeterFloat); + findModbusMap(mapIds); + } + }); +} + +void SunSpecMeter::geMeterMap() +{ + readMap(m_mapModbusStartRegister, m_mapLength); +} + +void SunSpecMeter::readMeterBlockHeader() +{ + +} diff --git a/sunspec/sunspecmeter.h b/sunspec/sunspecmeter.h new file mode 100644 index 0000000..a05967e --- /dev/null +++ b/sunspec/sunspecmeter.h @@ -0,0 +1,91 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef SUNSPECMETER_H +#define SUNSPECMETER_H + +#include +#include "sunspec.h" + +class SunSpecMeter : public SunSpec +{ + Q_OBJECT +public: + //Model 203 = Three phase meter + enum MandatoryRegistersModel20X { + TotalAcCurrent = 2, + PhaseACurrent = 3, + PhaseBCurrent = 4, + PhaseCCurrent = 5, + CurrentScaleFactor = 6, + VoltageLN = 7, + PhaseVoltageAN = 8, + PhaseVoltageBN = 9, + PhaseVoltageCN = 10, + VoltageLL = 11, + PhaseVoltageAB = 12, + PhaseVoltageBC = 13, + PhaseVoltageCA = 14, + VoltageScaleFactor = 15, + Frequency = 16, + TotalRealPower = 18, + RealPowerScaleFactor = 22, + TotalRealEnergyExported = 38, + TotalRealEnergyImported = 46, + RealEnergyScaleFactor = 54, + MeterEventFlags = 105 + }; + + struct MeterData { + SunSpecEvent1 event; + SunSpecOperatingState operatingState; + }; + + SunSpecMeter(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); + + void geMeterMap(); + +private: + BlockId m_id = BlockId::DeltaConnectThreePhaseMeter; + uint m_mapLength = 0; + uint m_mapModbusStartRegister = 40000; + + void readMeterBlockHeader(); + +private slots: + void onConnectionStateChanged(); + void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); + +signals: + void initFinished(); + void meterDataReceived(const MeterData &data); +}; + +#endif // SUNSPECMETER_H diff --git a/sunspec/sunspecstorage.cpp b/sunspec/sunspecstorage.cpp new file mode 100644 index 0000000..516cba9 --- /dev/null +++ b/sunspec/sunspecstorage.cpp @@ -0,0 +1,123 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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(const QHostAddress &hostAddress, uint port, QObject *parent) : SunSpec(hostAddress, port, parent) +{ + connect(m_modbusTcpClient, &QModbusClient::stateChanged, this, [this] (QModbusDevice::State state) { + if (state == QModbusDevice::ConnectedState) { + qCDebug(dcSunSpec()) << "Inverter connected successfully"; + QList mapIds; + mapIds.append(BlockId::Storage); + findModbusMap(mapIds); + } + }); + connect(this, &SunSpec::foundModbusMap, this, [this] (BlockId mapId, uint modbusRegisterAddress) { + qCDebug(dcSunSpec()) << "Read map header for mapId" << mapId << "and modbus register" << modbusRegisterAddress; + readMapHeader(modbusRegisterAddress); + }); + + connect(this, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, BlockId mapId, uint mapLength) { + m_id = mapId; + m_mapLength = mapLength; + m_mapModbusStartRegister = modbusAddress; + readMap(modbusAddress, mapLength); + }); + + connect(this, &SunSpec::mapReceived, this, &SunSpecStorage::onModbusMapReceived); +} + +void SunSpecStorage::getStorageMap() +{ + readMap(m_mapModbusStartRegister, m_mapLength); +} + +void SunSpecStorage::readStorageBlockHeader() +{ + readMapHeader(m_mapModbusStartRegister); +} + +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_mapModbusStartRegister + Model124::Model124ChaGriSet; + quint16 value = enabled; + return writeHoldingRegister(m_slaveId, 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) << StorageControlBitFieldCharge) | + (static_cast(dischargingEnabled) << StorageControlBitFieldDischarge)) ; + + uint modbusRegister = m_mapModbusStartRegister + Model124::Model124ActivateStorageControlMode; + return writeHoldingRegister(m_slaveId, modbusRegister, value); +} + +QUuid SunSpecStorage::setChargingRate(int rate) +{ + //Register Name InWRte + /* Defines the maximum charge rate (charge limit). Default is 100% */ + + uint modbusRegister = m_mapModbusStartRegister + Model124::Model124SetpointMaximumChargingRate; + int16_t value = rate * 100; + return writeHoldingRegister(m_slaveId, modbusRegister, value); +} + +QUuid SunSpecStorage::setDischargingRate(int charging) +{ + //Register Name OutWRte + /* Defines the maximum discharge rate (discharge limit). Default is 100% */ + uint modbusRegister = m_mapModbusStartRegister + Model124::Model124SetpointMaximumDischargeRate; + quint16 value = charging * 100; + return writeHoldingRegister(m_slaveId, modbusRegister, value); +} + +void SunSpecStorage::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, const QVector &data) +{ + Q_UNUSED(mapLength) + switch (mapId) { + case BlockId::Storage: { + StorageData storageData; + storageData.chargingState = ChargingState(data[Model124::Model124ChargeStatus]); + emit storageDataReceived(storageData); + } break; + default: + break; + } +} diff --git a/sunspec/sunspecstorage.h b/sunspec/sunspecstorage.h new file mode 100644 index 0000000..36cdbef --- /dev/null +++ b/sunspec/sunspecstorage.h @@ -0,0 +1,109 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 SunSpec +{ + Q_OBJECT +public: + SunSpecStorage(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); + + QUuid setGridCharging(bool enabled); + QUuid setDischargingRate(int rate); + QUuid setChargingRate(int rate); + QUuid setStorageControlMode(bool chargingEnabled, bool dischargingEnabled); + + + enum StorageControlBitField { + StorageControlBitFieldCharge = 0, + StorageControlBitFieldDischarge = 1 + }; + Q_ENUM(StorageControlBitField) + + enum GridCharge { + PV = 0, + Grid = 1 + }; + Q_ENUM(GridCharge) + + enum ChargingState { + ChargingStateOff, + ChargingStateEmpty, + ChargingStateDischarging, + ChargingStateCharging, + ChargingStateFull, + ChargingStateHolding, + ChargingStateTesting + }; + Q_ENUM(ChargingState) + + enum Model124 { // Mandatory register + Model124SetpointMaximumCharge = 0, + Model124SetpointMaximumChargingRate = 1, + Model124SetpointMaximumDischargeRate = 2, + Model124ActivateStorageControlMode = 3, + Model124CurrentlyAvailableEnergy = 6, + Model124ChargeStatus = 9, + Model124ChaGriSet = 15, + Model124ScaleFactorMaximumCharge = 16, + Model124ScaleFactorMaximumChargeDischargeRate = 17, + Model124ScaleFactorAvailableEnergyPercent = 20 + }; + Q_ENUM(Model124) + + struct StorageData { + ChargingState chargingState; + bool gridChargingEnabled; + }; + + void getStorageMap(); + +private: + BlockId m_id = BlockId::EnergyStorageBaseModel; + uint m_mapLength = 0; + uint m_mapModbusStartRegister = 40000; + + void readStorageBlockHeader(); + +private slots: + void onConnectionStateChanged(); + void onModbusMapReceived(BlockId mapId, uint mapLength, const QVector &data); + +signals: + void initFinished(); + void storageDataReceived(const StorageData &data); +}; + +#endif // SUNSPECSTORAGE_H diff --git a/sunspec/sunspecstringcombiner.cpp b/sunspec/sunspecstringcombiner.cpp new file mode 100644 index 0000000..dc36460 --- /dev/null +++ b/sunspec/sunspecstringcombiner.cpp @@ -0,0 +1,89 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "sunspecstringcombiner.h" +#include "extern-plugininfo.h" + +SunSpecStringCombiner::SunSpecStringCombiner(const QHostAddress &hostAddress, uint port, QObject *parent) : SunSpec(hostAddress, port, parent) +{ + connect(m_modbusTcpClient, &QModbusClient::stateChanged, this, [this] (QModbusDevice::State state) { + if (state == QModbusDevice::ConnectedState) { + qCDebug(dcSunSpec()) << "String combiner connected successfully"; + QList mapIds; + mapIds.append(BlockId::StringCombiner); + mapIds.append(BlockId::StringCombinerCurrent); + mapIds.append(BlockId::StringCombinerAdvanced); + mapIds.append(BlockId::StringCombinerCurrentAdvanced); + findModbusMap(mapIds); + } + }); + connect(this, &SunSpec::foundModbusMap, this, [this] (BlockId mapId, uint modbusRegisterAddress) { + qCDebug(dcSunSpec()) << "Read map header for mapId" << mapId << "and modbus register" << modbusRegisterAddress; + readMapHeader(modbusRegisterAddress); + }); + + connect(this, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, BlockId mapId, uint mapLength) { + m_id = mapId; + m_mapLength = mapLength; + m_mapModbusStartRegister = modbusAddress; + readMap(modbusAddress, mapLength); + }); + + connect(this, &SunSpec::mapReceived, this, &SunSpecStringCombiner::onModbusMapReceived); +} + +void SunSpecStringCombiner::getStringCombinerMap() +{ + +} + +void SunSpecStringCombiner::readStringCombinerMapHeader() +{ + readMap(m_mapModbusStartRegister, m_mapLength); +} + +void SunSpecStringCombiner::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data) +{ + + switch (mapId) { + case BlockId::StringCombiner: { + int rbCount = (mapLength-14)/8; + qCDebug(dcSunSpec()) << "Map" << mapId << "Repeating Block Count" << rbCount; + } break; + case BlockId::StringCombinerCurrent: + case BlockId::StringCombinerAdvanced: + case BlockId::StringCombinerCurrentAdvanced: { + //StringCombinerData stringCombinerData; + //stringCombinerData.acCurrent= convertValueWithSSF(data[Model10X::Model10XAcCurrent], data[Model10X::Model10XAmpereScaleFactor]); + } break; + default: + break; + } +} diff --git a/sunspec/sunspecstringcombiner.h b/sunspec/sunspecstringcombiner.h new file mode 100644 index 0000000..b4645fd --- /dev/null +++ b/sunspec/sunspecstringcombiner.h @@ -0,0 +1,129 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 SUNSPECSTRINGCOMBINER_H +#define SUNSPECSTRINGCOMBINER_H + +#include +#include "sunspec.h" + +class SunSpecStringCombiner : public SunSpec +{ + Q_OBJECT +public: + + //Map401 length: 14 + (RB Count * 8) + //Map403 length: 16 + (RB Count * 8) + enum Map401 { + CurrentScaleFactor = 0, + AmpHourScaleFactor = 1, + VoltageScaleFactor = 2, + MaximumDCCurrentRating = 3, + NumberOfInputs = 4, + Events = 5, + VendorDefniedEvents = 7, + TotalMeasuredCurrent = 9, + TotalMeteredAmpHours = 10, + OutputVoltage = 12, + InternalOperatingTemperature = 13 + }; + + enum Map402 { + CurrentScaleFactor, + AmpHourScaleFactor, + VoltageScaleFactor + PowerScale factor + EnergyScale factor + Maximum DC Current Rating + Number of Inputs + Bitmask value. Events + Bitmask value. Vendor defnied events + Total measured current + Total metered Amp-hours + OutputVoltage + }; + + enum Map401RB { //Repeating block + ID = 0, + Event = 1, + VendorEvent = 3, + Amps = 5, + AmpHours = 6 + }; + + enum StringCombinerEvent { + LowVoltage = 0, + LowPower, + LowEfficiency, + Current, + Voltage, + Power, + Pr, + Disconnected, + FuseFault, + CombinerFuseFault, + CombinerCabinetOpen, + Temp, + Groundfault, + ReversedPolarity, + Incompatible, + CommunicationError, + InternalError, + Theft, + ArcDetected + }; + Q_ENUM(StringCombinerEvent) + + struct StringCombinerData { + SunSpecEvent1 event; + SunSpecOperatingState operatingState; + }; + + SunSpecStringCombiner(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); + + void getStringCombinerMap(); + +private: + BlockId m_id = BlockId::StringCombiner; + uint m_mapLength = 0; + uint m_mapModbusStartRegister = 40000; + + void readStringCombinerMapHeader(); + +private slots: + void onConnectionStateChanged(); + void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); + +signals: + void initFinished(); + void stringCombinerDataReceived(const StringCombinerData &data); +}; + +#endif // SUNSPECSTRINGCOMBINER_H diff --git a/sunspec/sunspectracker.cpp b/sunspec/sunspectracker.cpp new file mode 100644 index 0000000..8d99fbf --- /dev/null +++ b/sunspec/sunspectracker.cpp @@ -0,0 +1,42 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "sunspectracker.h" +#include "extern-plugininfo.h" + +SunSpecTracker::SunSpecTracker(const QHostAddress &hostAddress, uint port, QObject *parent) : SunSpec(hostAddress, port, parent) +{ + +} + +void SunSpecTracker::readTrackerBlockHeader() +{ + readMap(m_mapModbusStartRegister, m_mapLength); +} diff --git a/sunspec/sunspectracker.h b/sunspec/sunspectracker.h new file mode 100644 index 0000000..8549e46 --- /dev/null +++ b/sunspec/sunspectracker.h @@ -0,0 +1,78 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef SUNSPECTRACKER_H +#define SUNSPECTRACKER_H + +#include +#include "sunspec.h" + +class SunSpecTracker : public SunSpec +{ + Q_OBJECT +public: + + enum TrackerType { + Unknown = 0, + Fixed = 1, + Horizontal = 2, + Tilted = 3, + Azimuth = 4, + Dual = 5, + Other = 99 + }; + Q_ENUM(TrackerType) + + struct TrackerData { + SunSpecEvent1 event; + SunSpecOperatingState operatingState; + }; + + SunSpecTracker(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); + + void getTrackerMap(); + +private: + BlockId m_id = BlockId::TrackerController; + uint m_mapLength = 0; + uint m_mapModbusStartRegister = 40000; + + void readTrackerBlockHeader(); + +private slots: + void onConnectionStateChanged(); + void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); + +signals: + void initFinished(); + void trackerDataReceived(const TrackerData &data); +}; + +#endif // SUNSPECTRACKER_H From 6a56838c8fe5749825c99c17a448c8b4472a3f90 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Tue, 1 Dec 2020 16:44:26 +0100 Subject: [PATCH 03/16] further implementeion of sunspec features --- sunspec/integrationpluginsunspec.cpp | 54 ++++++++- sunspec/integrationpluginsunspec.h | 2 +- sunspec/integrationpluginsunspec.json | 62 +++++++++- sunspec/sunspec.cpp | 18 ++- sunspec/sunspec.h | 165 +++++++++++++------------- sunspec/sunspecinverter.cpp | 24 ++-- sunspec/sunspecinverter.h | 3 +- sunspec/sunspecmeter.cpp | 16 +-- sunspec/sunspecmeter.h | 5 +- sunspec/sunspecstorage.cpp | 4 +- sunspec/sunspecstorage.h | 3 +- sunspec/sunspecstringcombiner.cpp | 17 +-- sunspec/sunspecstringcombiner.h | 49 ++++---- sunspec/sunspectracker.h | 5 +- 14 files changed, 264 insertions(+), 163 deletions(-) diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index 378dbd3..ff7dda4 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -40,7 +40,32 @@ IntegrationPluginSunSpec::IntegrationPluginSunSpec() void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) { - if (info->thing()->thingClassId() == sunspecInverterThingClassId) { + if (info->thing()->thingClassId() == sunspecConnectionThingClassId) { + QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecConnectionThingIpAddressParamTypeId).toString()); + SunSpec *sunSpec = new SunSpec(address, 502, this); + m_sunSpecConnections.insert(info->thing(), sunSpec); + if (!sunSpec->connectModbus()) { + QTimer::singleShot(2000, info, [this, info] { + setupThing(info); + }); + qCWarning(dcSunSpec()) << "Error connecting to SunSpec device"; + return; + } + connect(sunSpec, &SunSpec::connectionStateChanged, info, [info] (bool status) { + qCDebug(dcSunSpec()) << "Modbus Inverter init finished" << status; + if (status) { + info->finish(Thing::ThingErrorNoError); + } else { + info->finish(Thing::ThingErrorHardwareFailure); + } + }); + + connect(info, &ThingSetupInfo::aborted, sunSpec, [info, this] { + m_sunSpecConnections.take(info->thing())->deleteLater(); + }); + //connect(sunSpec, &SunSpecInverter::inverterDataReceived, this, &IntegrationPluginSunSpec::onInverterDataReceived); + + } else if (info->thing()->thingClassId() == sunspecInverterThingClassId) { QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecInverterThingIpAddressParamTypeId).toString()); SunSpecInverter *sunSpecInverter = new SunSpecInverter(address, 502, this); m_sunSpecInverters.insert(info->thing(), sunSpecInverter); @@ -229,14 +254,14 @@ void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info) connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); } } else if (action.actionTypeId() == sunspecStorageEnableChargingLimitActionTypeId) { - int value = (action.param(sunspecStorageEnableChargingLimitActionEnableChargingLimitParamTypeId).value().toBool() << 1) | thing->stateValue(sunspecStorageEnableDischargingLimitStateTypeId).toBool(); + /*int value = (action.param(sunspecStorageEnableChargingLimitActionEnableChargingLimitParamTypeId).value().toBool() << 1) | thing->stateValue(sunspecStorageEnableDischargingLimitStateTypeId).toBool(); QUuid requestId = sunSpecStorage->setStorageControlMode(value); 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()) { @@ -246,14 +271,14 @@ void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info) connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); } } else if (action.actionTypeId() == sunspecStorageEnableDischargingLimitActionTypeId) { - int value = (action.param(sunspecStorageEnableDischargingLimitActionEnableDischargingLimitParamTypeId).value().toBool() << 1) | thing->stateValue(sunspecStorageEnableChargingLimitStateTypeId).toBool(); + /*int value = (action.param(sunspecStorageEnableDischargingLimitActionEnableDischargingLimitParamTypeId).value().toBool() << 1) | thing->stateValue(sunspecStorageEnableChargingLimitStateTypeId).toBool(); QUuid requestId = sunSpecStorage->setStorageControlMode(value); 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()) { @@ -299,6 +324,23 @@ void IntegrationPluginSunSpec::onPluginConfigurationChanged(const ParamTypeId &p } } +void IntegrationPluginSunSpec::onConnectionStateChanged(bool status) +{ + Q_UNUSED(status) +} + +void IntegrationPluginSunSpec::onWriteRequestExecuted(QUuid requestId, bool success) +{ + Q_UNUSED(requestId) + Q_UNUSED(success) +} + +void IntegrationPluginSunSpec::onWriteRequestError(QUuid requestId, const QString &error) +{ + Q_UNUSED(requestId) + Q_UNUSED(error) +} + void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::InverterData &inverterData) { SunSpecInverter *inverter = static_cast(sender()); @@ -413,6 +455,7 @@ void IntegrationPluginSunSpec::onStorageDataReceived(const SunSpecStorage::Stora void IntegrationPluginSunSpec::onMeterDataReceived(const SunSpecMeter::MeterData &meterData) { + Q_UNUSED(meterData) SunSpecMeter *meter = static_cast(sender()); Thing *thing = m_sunSpecMeters.key(meter); @@ -424,6 +467,7 @@ void IntegrationPluginSunSpec::onMeterDataReceived(const SunSpecMeter::MeterData void IntegrationPluginSunSpec::onTrackerDataReceived(const SunSpecTracker::TrackerData &trackerData) { + Q_UNUSED(trackerData) SunSpecTracker *tracker = static_cast(sender()); Thing *thing = m_sunSpecTrackers.key(tracker); diff --git a/sunspec/integrationpluginsunspec.h b/sunspec/integrationpluginsunspec.h index 48c393a..8b46bdb 100644 --- a/sunspec/integrationpluginsunspec.h +++ b/sunspec/integrationpluginsunspec.h @@ -60,12 +60,12 @@ public: private: PluginTimer *m_refreshTimer = nullptr; QHash m_asyncActions; + QHash m_sunSpecConnections; QHash m_sunSpecInverters; QHash m_sunSpecStorages; QHash m_sunSpecMeters; QHash m_sunSpecTrackers; - void update(Thing *thing); private slots: void onRefreshTimer(); diff --git a/sunspec/integrationpluginsunspec.json b/sunspec/integrationpluginsunspec.json index f394fda..e07b66b 100644 --- a/sunspec/integrationpluginsunspec.json +++ b/sunspec/integrationpluginsunspec.json @@ -1,7 +1,7 @@ { "name": "SunSpec", "displayName": "sunspec", - "id": "73c7efcc-80d5-4166-ad97-2cbbeb129d91", + "id": "cb4bdec6-cf2c-4a0f-9709-42d951ca2d8b", "paramTypes":[ { "id": "52da5222-9a94-47a2-9adc-004541d2f5ed", @@ -18,11 +18,61 @@ "displayName": "SunSpec", "id": "c143a7b4-a16c-4fff-86a3-9ffab3d6841d", "thingClasses": [ + { + "name": "sunspecConnection", + "displayName": "SunSpec Device", + "id": "f51853f3-8815-4cf3-b337-45cda1f3e6d5", + "createMethods": [ "User" ], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "6be6abc4-e2b2-4687-9343-0e5164ed0ab2", + "name":"ipAddress", + "displayName": "IP address", + "type": "QString" + }, + { + "id": "04970315-ed3a-45ce-98fc-35ae3c4eb27b", + "name":"manufacturer", + "displayName": "Manufacturer", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "58146c26-17d3-458e-a13f-d7f306c20c44", + "name":"deviceModel", + "displayName": "Device model", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + }, + { + "id": "6ed498e1-37ca-4bb7-bac7-463509c7784e", + "name":"serialNumber", + "displayName": "Serial number", + "type": "QString", + "readOnly": true, + "defaultValue": "Unkown" + } + ], + "stateTypes":[ + { + "id": "3e767ad0-b4b3-4398-94c1-00579ea09ca8", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false, + "cached": false + } + ] + }, { "name": "sunspecInverter", "displayName": "SunSpec Inverter", "id": "2e4122ea-96a5-415c-b5e2-7d6012265a83", - "createMethods": [ "User" ], + "createMethods": [ "Auto" ], "interfaces": ["connectable"], "paramTypes": [ { @@ -226,7 +276,7 @@ "name": "sunspecMeter", "displayName": "SunSpec Meter", "id": "68f822f9-ff30-4275-b229-39a3674fead7", - "createMethods": [ "User" ], + "createMethods": [ "Auto" ], "interfaces": ["connectable"], "paramTypes": [ { @@ -385,7 +435,7 @@ "name": "sunspecTracker", "displayName": "SunSpec Tracker", "id": "9941da30-a6d6-475d-8244-3c2145b419e6", - "createMethods": [ "User" ], + "createMethods": [ "Auto" ], "interfaces": ["connectable"], "paramTypes": [ { @@ -445,7 +495,7 @@ "name": "sunspecStorage", "displayName": "SunSpec Storage", "id": "9a643ba8-346c-4127-a2f8-956a7133d75e", - "createMethods": [ "User" ], + "createMethods": [ "Auto" ], "interfaces": ["connectable"], "paramTypes": [ { @@ -587,7 +637,7 @@ "name": "sunspecStringCombiner", "displayName": "SunSpec String Combiner", "id": "7787f238-f5a3-4ba5-b3ec-7d513b87bf71", - "createMethods": [ "User" ], + "createMethods": [ "Auto" ], "interfaces": ["connectable"], "paramTypes": [ { diff --git a/sunspec/sunspec.cpp b/sunspec/sunspec.cpp index 37b22d0..6830f9e 100644 --- a/sunspec/sunspec.cpp +++ b/sunspec/sunspec.cpp @@ -48,7 +48,6 @@ SunSpec::SunSpec(const QHostAddress &hostAddress, uint port, QObject *parent) : SunSpec::~SunSpec() { - } bool SunSpec::connectModbus() @@ -56,6 +55,21 @@ bool SunSpec::connectModbus() return m_modbusTcpClient->connectDevice();; } +QString SunSpec::manufacturer() +{ + return m_manufacturer; +} + +QString SunSpec::deviceModel() +{ + return m_deviceModel; +} + +QString SunSpec::serialNumber() +{ + return m_serialNumber; +} + void SunSpec::findBaseRegister() { qCDebug(dcSunSpec()) << "find base register"; @@ -109,7 +123,7 @@ void SunSpec::findModbusMap(const QList &ids, uint modbusAddressOffset) uint modbusAddress = unit.startAddress(); BlockId blockId = BlockId(unit.value(0)); int blockLength = unit.value(1); - if (blockId > 800 || blockId == BlockId::End) { + if (blockId > 800 || blockId == BlockIdEnd) { qCDebug(dcSunSpec()) << "Block id not found, Id:" << ids; modbusMapSearchFinished(ids, 0, "Ids not found"); return; diff --git a/sunspec/sunspec.h b/sunspec/sunspec.h index 878003b..b576793 100644 --- a/sunspec/sunspec.h +++ b/sunspec/sunspec.h @@ -88,84 +88,84 @@ public: Q_ENUM(SunSpecEvent1) enum BlockId { - Common = 1, - BasicAggregator = 2, - SecureDatasetReadRequest = 3, - SecureDatasetReadResponse = 4, - SecureWriteRequest = 5, - SecureWriteSequentialRequest = 6, - SecureWriteResponseModel = 7, - GetDeviceSecurityCertificate = 8, - SetOperatorSecurityCertificate = 9, - CommunicationInterfaceHeader = 10, - EthernetLinkLayer = 11, - IPv4 = 12, - IPv6 = 13, - ProxyServer = 14, - InterfaceCountersModel = 15, - SimpleIpNetwork = 16, - SerialInterface = 17, - CellularLink = 18, - PPPLink = 19, - InverterSinglePhase = 101, - InverterSplitPhase = 102, - InverterThreePhase = 103, - InverterSinglePhaseFloat = 111, - InverterSplitPhaseFloat = 112, - InverterThreePhaseFloat = 113, - Nameplate = 120, - BasicSettings = 121, - MeasurementsStatus = 122, - ImmediateControls = 123, - Storage = 124, - Pricing = 125, - StaticVoltVAR = 126, - FreqWattParam = 127, - DynamicReactiveCurrent = 128, - LVRTD = 129, - HVRTD = 130, - WattPF = 131, - VoltWatt = 132, - BasicScheduling = 133, - FreqWattCrv = 134, - LFRT = 135, - HFRT = 136, - LVRTC = 137, - HVRTC = 138, - MultipleMPPTInverterExtensionModel = 160, - SinglePhaseMeter = 201, - SplitSinglePhaseMeter = 202, - WyeConnectThreePhaseMeter = 203, - DeltaConnectThreePhaseMeter = 204, - SinglePhaseMeterFloat = 211, - SplitSinglePhaseMeterFloat = 212, - WyeConnectThreePhaseMeterFloat = 213, - DeltaConnectThreePhaseMeterFloat = 214, - SecureACMeterSelectedReadings = 220, - IrradianceModel = 302, - BackOfModuleTemperatureModel = 303, - InclinometerModel = 304, - GPS = 305, - ReferencePointModel = 306, - BaseMet = 307, - MiniMetModel = 308, - StringCombiner = 401, - StringCombinerAdvanced = 402, - StringCombinerCurrent = 403, - StringCombinerCurrentAdvanced = 404, - SolarModuleFloat = 501, - SolarModule = 502, - TrackerController = 601, - EnergyStorageBaseModel = 801, - BatteryBaseModel = 802, - LithiumIonBatteryModel = 803, - VerisStatusConfiguration = 64001, - MersenGreenString = 64020, - EltekInverterExtension = 64101, - OutBackAXSDevice = 64110, - BasicChargeController = 64111, - OutBackFMChargeController = 64112, - End = 65535 + BlockIdCommon = 1, + BlockIdBasicAggregator = 2, + BlockIdSecureDatasetReadRequest = 3, + BlockIdSecureDatasetReadResponse = 4, + BlockIdSecureWriteRequest = 5, + BlockIdSecureWriteSequentialRequest = 6, + BlockIdSecureWriteResponseModel = 7, + BlockIdGetDeviceSecurityCertificate = 8, + BlockIdSetOperatorSecurityCertificate = 9, + BlockIdCommunicationInterfaceHeader = 10, + BlockIdEthernetLinkLayer = 11, + BlockIdIPv4 = 12, + BlockIdIPv6 = 13, + BlockIdProxyServer = 14, + BlockIdInterfaceCountersModel = 15, + BlockIdSimpleIpNetwork = 16, + BlockIdSerialInterface = 17, + BlockIdCellularLink = 18, + BlockIdPPPLink = 19, + BlockIdInverterSinglePhase = 101, + BlockIdInverterSplitPhase = 102, + BlockIdInverterThreePhase = 103, + BlockIdInverterSinglePhaseFloat = 111, + BlockIdInverterSplitPhaseFloat = 112, + BlockIdInverterThreePhaseFloat = 113, + BlockIdNameplate = 120, + BlockIdBasicSettings = 121, + BlockIdMeasurementsStatus = 122, + BlockIdImmediateControls = 123, + BlockIdStorage = 124, + BlockIdPricing = 125, + BlockIdStaticVoltVAR = 126, + BlockIdFreqWattParam = 127, + BlockIdDynamicReactiveCurrent = 128, + BlockIdLVRTD = 129, + BlockIdHVRTD = 130, + BlockIdWattPF = 131, + BlockIdVoltWatt = 132, + BlockIdBasicScheduling = 133, + BlockIdFreqWattCrv = 134, + BlockIdLFRT = 135, + BlockIdHFRT = 136, + BlockIdLVRTC = 137, + BlockIdHVRTC = 138, + BlockIdMultipleMPPTInverterExtensionModel = 160, + BlockIdSinglePhaseMeter = 201, + BlockIdSplitSinglePhaseMeter = 202, + BlockIdWyeConnectThreePhaseMeter = 203, + BlockIdDeltaConnectThreePhaseMeter = 204, + BlockIdSinglePhaseMeterFloat = 211, + BlockIdSplitSinglePhaseMeterFloat = 212, + BlockIdWyeConnectThreePhaseMeterFloat = 213, + BlockIdDeltaConnectThreePhaseMeterFloat = 214, + BlockIdSecureACMeterSelectedReadings = 220, + BlockIdIrradianceModel = 302, + BlockIdBackOfModuleTemperatureModel = 303, + BlockIdInclinometerModel = 304, + BlockIdGPS = 305, + BlockIdReferencePointModel = 306, + BlockIdBaseMet = 307, + BlockIdMiniMetModel = 308, + BlockIdStringCombiner = 401, + BlockIdStringCombinerAdvanced = 402, + BlockIdStringCombinerCurrent = 403, + BlockIdStringCombinerCurrentAdvanced = 404, + BlockIdSolarModuleFloat = 501, + BlockIdSolarModule = 502, + BlockIdTrackerController = 601, + BlockIdEnergyStorageBaseModel = 801, + BlockIdBatteryBaseModel = 802, + BlockIdLithiumIonBatteryModel = 803, + BlockIdVerisStatusConfiguration = 64001, + BlockIdMersenGreenString = 64020, + BlockIdEltekInverterExtension = 64101, + BlockIdOutBackAXSDevice = 64110, + BlockIdBasicChargeController = 64111, + BlockIdOutBackFMChargeController = 64112, + BlockIdEnd = 65535 }; Q_ENUM(BlockId) @@ -182,9 +182,9 @@ public: int m_slaveId = 1; int m_baseRegister = 40000; bool m_floatingPointRepresentation = false; - QString m_manufacturer = "unkown"; - QString m_deviceModel = "unknown"; - QString m_serialNumber = "unknown"; + QString m_manufacturer = "Unknown"; + QString m_deviceModel = "Unknown"; + QString m_serialNumber = "Unknown"; void findBaseRegister(); void findModbusMap(const QList &mapIds, uint modbusAddressOffset = 69); @@ -214,9 +214,6 @@ signals: private slots: void onModbusStateChanged(QModbusDevice::State state); - - void onRequestExecuted(QUuid requestId, bool success); - void onRequestError(QUuid requestId, const QString &error); void onReceivedHoldingRegister(quint32 slaveAddress, quint32 modbusRegister, const QVector &values); }; diff --git a/sunspec/sunspecinverter.cpp b/sunspec/sunspecinverter.cpp index 69b9591..295d636 100644 --- a/sunspec/sunspecinverter.cpp +++ b/sunspec/sunspecinverter.cpp @@ -37,12 +37,12 @@ SunSpecInverter::SunSpecInverter(const QHostAddress &hostAddress, uint port, QOb if (state == QModbusDevice::ConnectedState) { qCDebug(dcSunSpec()) << "Inverter connected successfully"; QList mapIds; - mapIds.append(BlockId::InverterSinglePhase); - mapIds.append(BlockId::InverterSplitPhase); - mapIds.append(BlockId::InverterThreePhase); - mapIds.append(BlockId::InverterSinglePhaseFloat); - mapIds.append(BlockId::InverterSplitPhaseFloat); - mapIds.append(BlockId::InverterThreePhaseFloat); + mapIds.append(BlockIdInverterSinglePhase); + mapIds.append(BlockIdInverterSplitPhase); + mapIds.append(BlockIdInverterThreePhase); + mapIds.append(BlockIdInverterSinglePhaseFloat); + mapIds.append(BlockIdInverterSplitPhaseFloat); + mapIds.append(BlockIdInverterThreePhaseFloat); findModbusMap(mapIds); } }); @@ -75,9 +75,9 @@ void SunSpecInverter::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength { Q_UNUSED(mapLength) switch (mapId) { - case BlockId::InverterSinglePhase: - case BlockId::InverterSplitPhase: - case BlockId::InverterThreePhase: { + case BlockIdInverterSinglePhase: + case BlockIdInverterSplitPhase: + case BlockIdInverterThreePhase: { InverterData inverterData; inverterData.acCurrent= convertValueWithSSF(data[Model10X::Model10XAcCurrent], data[Model10X::Model10XAmpereScaleFactor]); inverterData.acPower = convertValueWithSSF(data[Model10X::Model10XACPower], data[Model10X::Model10XWattScaleFactor]); @@ -99,9 +99,9 @@ void SunSpecInverter::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength emit inverterDataReceived(inverterData); } break; - case BlockId::InverterSinglePhaseFloat: - case BlockId::InverterSplitPhaseFloat: - case BlockId::InverterThreePhaseFloat: { + case BlockIdInverterSinglePhaseFloat: + case BlockIdInverterSplitPhaseFloat: + case BlockIdInverterThreePhaseFloat: { InverterData inverterData; inverterData.acCurrent = convertFloatValues(data[Model11X::Model11XAcCurrent], data[Model11X::Model11XAcCurrent+1]); diff --git a/sunspec/sunspecinverter.h b/sunspec/sunspecinverter.h index 7243c17..f68c68b 100644 --- a/sunspec/sunspecinverter.h +++ b/sunspec/sunspecinverter.h @@ -100,14 +100,13 @@ public: void getInverterMap(); private: - BlockId m_id = BlockId::InverterThreePhase; //e.g. 103 for three phase inverter, 113 for three phase inverter with floating point representation + BlockId m_id = BlockIdInverterThreePhase; //e.g. 103 for three phase inverter, 113 for three phase inverter with floating point representation uint m_mapLength = 0; uint m_mapModbusStartRegister = 40000; void readInverterBlockHeader(); private slots: - void onConnectionStateChanged(); void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); signals: diff --git a/sunspec/sunspecmeter.cpp b/sunspec/sunspecmeter.cpp index d9b1763..cfd3b88 100644 --- a/sunspec/sunspecmeter.cpp +++ b/sunspec/sunspecmeter.cpp @@ -37,14 +37,14 @@ SunSpecMeter::SunSpecMeter(const QHostAddress &hostAddress, uint port, QObject * if (state == QModbusDevice::ConnectedState) { qCDebug(dcSunSpec()) << "Meter connected successfully"; QList mapIds; - mapIds.append(BlockId::SinglePhaseMeter); - mapIds.append(BlockId::SplitSinglePhaseMeter); - mapIds.append(BlockId::WyeConnectThreePhaseMeter); - mapIds.append(BlockId::DeltaConnectThreePhaseMeter); - mapIds.append(BlockId::SinglePhaseMeterFloat); - mapIds.append(BlockId::SplitSinglePhaseMeterFloat); - mapIds.append(BlockId::WyeConnectThreePhaseMeterFloat); - mapIds.append(BlockId::DeltaConnectThreePhaseMeterFloat); + mapIds.append(BlockIdSinglePhaseMeter); + mapIds.append(BlockIdSplitSinglePhaseMeter); + mapIds.append(BlockIdWyeConnectThreePhaseMeter); + mapIds.append(BlockIdDeltaConnectThreePhaseMeter); + mapIds.append(BlockIdSinglePhaseMeterFloat); + mapIds.append(BlockIdSplitSinglePhaseMeterFloat); + mapIds.append(BlockIdWyeConnectThreePhaseMeterFloat); + mapIds.append(BlockIdDeltaConnectThreePhaseMeterFloat); findModbusMap(mapIds); } }); diff --git a/sunspec/sunspecmeter.h b/sunspec/sunspecmeter.h index a05967e..e3154f4 100644 --- a/sunspec/sunspecmeter.h +++ b/sunspec/sunspecmeter.h @@ -73,15 +73,14 @@ public: void geMeterMap(); private: - BlockId m_id = BlockId::DeltaConnectThreePhaseMeter; + BlockId m_id = BlockIdDeltaConnectThreePhaseMeter; uint m_mapLength = 0; uint m_mapModbusStartRegister = 40000; void readMeterBlockHeader(); private slots: - void onConnectionStateChanged(); - void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); + // void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); signals: void initFinished(); diff --git a/sunspec/sunspecstorage.cpp b/sunspec/sunspecstorage.cpp index 516cba9..8ff722d 100644 --- a/sunspec/sunspecstorage.cpp +++ b/sunspec/sunspecstorage.cpp @@ -37,7 +37,7 @@ SunSpecStorage::SunSpecStorage(const QHostAddress &hostAddress, uint port, QObje if (state == QModbusDevice::ConnectedState) { qCDebug(dcSunSpec()) << "Inverter connected successfully"; QList mapIds; - mapIds.append(BlockId::Storage); + mapIds.append(BlockIdStorage); findModbusMap(mapIds); } }); @@ -112,7 +112,7 @@ void SunSpecStorage::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, { Q_UNUSED(mapLength) switch (mapId) { - case BlockId::Storage: { + case BlockIdStorage: { StorageData storageData; storageData.chargingState = ChargingState(data[Model124::Model124ChargeStatus]); emit storageDataReceived(storageData); diff --git a/sunspec/sunspecstorage.h b/sunspec/sunspecstorage.h index 36cdbef..29ceea2 100644 --- a/sunspec/sunspecstorage.h +++ b/sunspec/sunspecstorage.h @@ -91,14 +91,13 @@ public: void getStorageMap(); private: - BlockId m_id = BlockId::EnergyStorageBaseModel; + BlockId m_id = BlockIdEnergyStorageBaseModel; uint m_mapLength = 0; uint m_mapModbusStartRegister = 40000; void readStorageBlockHeader(); private slots: - void onConnectionStateChanged(); void onModbusMapReceived(BlockId mapId, uint mapLength, const QVector &data); signals: diff --git a/sunspec/sunspecstringcombiner.cpp b/sunspec/sunspecstringcombiner.cpp index dc36460..9445085 100644 --- a/sunspec/sunspecstringcombiner.cpp +++ b/sunspec/sunspecstringcombiner.cpp @@ -37,10 +37,10 @@ SunSpecStringCombiner::SunSpecStringCombiner(const QHostAddress &hostAddress, ui if (state == QModbusDevice::ConnectedState) { qCDebug(dcSunSpec()) << "String combiner connected successfully"; QList mapIds; - mapIds.append(BlockId::StringCombiner); - mapIds.append(BlockId::StringCombinerCurrent); - mapIds.append(BlockId::StringCombinerAdvanced); - mapIds.append(BlockId::StringCombinerCurrentAdvanced); + mapIds.append(BlockIdStringCombiner); + mapIds.append(BlockIdStringCombinerCurrent); + mapIds.append(BlockIdStringCombinerAdvanced); + mapIds.append(BlockIdStringCombinerCurrentAdvanced); findModbusMap(mapIds); } }); @@ -71,15 +71,16 @@ void SunSpecStringCombiner::readStringCombinerMapHeader() void SunSpecStringCombiner::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data) { + Q_UNUSED(data); switch (mapId) { - case BlockId::StringCombiner: { + case BlockIdStringCombiner: { int rbCount = (mapLength-14)/8; qCDebug(dcSunSpec()) << "Map" << mapId << "Repeating Block Count" << rbCount; } break; - case BlockId::StringCombinerCurrent: - case BlockId::StringCombinerAdvanced: - case BlockId::StringCombinerCurrentAdvanced: { + case BlockIdStringCombinerCurrent: + case BlockIdStringCombinerAdvanced: + case BlockIdStringCombinerCurrentAdvanced: { //StringCombinerData stringCombinerData; //stringCombinerData.acCurrent= convertValueWithSSF(data[Model10X::Model10XAcCurrent], data[Model10X::Model10XAmpereScaleFactor]); } break; diff --git a/sunspec/sunspecstringcombiner.h b/sunspec/sunspecstringcombiner.h index b4645fd..7e7afe5 100644 --- a/sunspec/sunspecstringcombiner.h +++ b/sunspec/sunspecstringcombiner.h @@ -42,32 +42,32 @@ public: //Map401 length: 14 + (RB Count * 8) //Map403 length: 16 + (RB Count * 8) enum Map401 { - CurrentScaleFactor = 0, - AmpHourScaleFactor = 1, - VoltageScaleFactor = 2, - MaximumDCCurrentRating = 3, - NumberOfInputs = 4, - Events = 5, - VendorDefniedEvents = 7, - TotalMeasuredCurrent = 9, - TotalMeteredAmpHours = 10, - OutputVoltage = 12, - InternalOperatingTemperature = 13 + Map401CurrentScaleFactor = 0, + Map401AmpHourScaleFactor = 1, + Map401VoltageScaleFactor = 2, + Map401MaximumDCCurrentRating = 3, + Map401NumberOfInputs = 4, + Map401Events = 5, + Map401VendorDefniedEvents = 7, + Map401TotalMeasuredCurrent = 9, + Map401TotalMeteredAmpHours = 10, + Map401OutputVoltage = 12, + Map401InternalOperatingTemperature = 13 }; enum Map402 { - CurrentScaleFactor, - AmpHourScaleFactor, - VoltageScaleFactor - PowerScale factor - EnergyScale factor - Maximum DC Current Rating - Number of Inputs - Bitmask value. Events - Bitmask value. Vendor defnied events - Total measured current - Total metered Amp-hours - OutputVoltage + Map402CurrentScaleFactor, + Map402AmpHourScaleFactor, + Map402VoltageScaleFactor, + Map402PowerScaleFactor, + Map402EnergyScaleFactor, + Map402MaximumDCCurrentRating, + Map402NumberOfInputs, + Map402BitmaskValueEvents, + Map402BitmaskvalueVendorDefniedEvents, + Map402TotalMeasuredCurrent, + Map402TotalMeteredAmpHours, + Map402OutputVoltage }; enum Map401RB { //Repeating block @@ -111,14 +111,13 @@ public: void getStringCombinerMap(); private: - BlockId m_id = BlockId::StringCombiner; + BlockId m_id = BlockIdStringCombiner; uint m_mapLength = 0; uint m_mapModbusStartRegister = 40000; void readStringCombinerMapHeader(); private slots: - void onConnectionStateChanged(); void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); signals: diff --git a/sunspec/sunspectracker.h b/sunspec/sunspectracker.h index 8549e46..2be9c84 100644 --- a/sunspec/sunspectracker.h +++ b/sunspec/sunspectracker.h @@ -60,15 +60,14 @@ public: void getTrackerMap(); private: - BlockId m_id = BlockId::TrackerController; + BlockId m_id = BlockIdTrackerController; uint m_mapLength = 0; uint m_mapModbusStartRegister = 40000; void readTrackerBlockHeader(); private slots: - void onConnectionStateChanged(); - void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); + //void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); signals: void initFinished(); From 0e3abab22959bd4301b73d00159d10c1c8d7e721 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Mon, 18 Jan 2021 14:44:49 +0100 Subject: [PATCH 04/16] improved thing setup and added debug output --- debian/control | 2 +- sunspec/README.md | 2 + sunspec/integrationpluginsunspec.cpp | 91 +++++++++++++++------------- sunspec/sunspec.cpp | 8 +++ sunspec/sunspec.h | 1 + 5 files changed, 62 insertions(+), 42 deletions(-) diff --git a/debian/control b/debian/control index bfaa5ac..288dfe8 100644 --- a/debian/control +++ b/debian/control @@ -74,7 +74,7 @@ Description: nymea.io plugin for SunSpec Modbus devices. 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 manufactor independent sunspec devices. + This package will install the nymea.io plugin for SunSpec. Package: nymea-plugin-wallbe diff --git a/sunspec/README.md b/sunspec/README.md index 95286d7..0d3b491 100644 --- a/sunspec/README.md +++ b/sunspec/README.md @@ -1,5 +1,7 @@ # SunSpec +Connect to SunSpec devices. + ## Supported Things * SunSpec Inverter diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index ff7dda4..fdd3f2b 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -40,62 +40,66 @@ IntegrationPluginSunSpec::IntegrationPluginSunSpec() void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) { - if (info->thing()->thingClassId() == sunspecConnectionThingClassId) { + Thing *thing = info->thing(); + qCDebug(dcSunSpec()) << "Setup thing" << thing->name(); + if (thing->thingClassId() == sunspecConnectionThingClassId) { + QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecConnectionThingIpAddressParamTypeId).toString()); - SunSpec *sunSpec = new SunSpec(address, 502, this); - m_sunSpecConnections.insert(info->thing(), sunSpec); + SunSpec *sunSpec; + if (m_sunSpecConnections.contains(thing)) { + qCDebug(dcSunSpec()) << "Reconfigure SunSpec connection with new address" << address; + sunSpec = m_sunSpecConnections.value(thing); + sunSpec->setHostAddress(address); + } else { + sunSpec = new SunSpec(address, 502, this); + m_sunSpecConnections.insert(info->thing(), sunSpec); + } + if (!sunSpec->connectModbus()) { - QTimer::singleShot(2000, info, [this, info] { - setupThing(info); - }); qCWarning(dcSunSpec()) << "Error connecting to SunSpec device"; - return; + return info->finish(Thing::ThingErrorHardwareNotAvailable); } connect(sunSpec, &SunSpec::connectionStateChanged, info, [info] (bool status) { qCDebug(dcSunSpec()) << "Modbus Inverter init finished" << status; if (status) { info->finish(Thing::ThingErrorNoError); - } else { - info->finish(Thing::ThingErrorHardwareFailure); } }); - connect(info, &ThingSetupInfo::aborted, sunSpec, [info, this] { - m_sunSpecConnections.take(info->thing())->deleteLater(); + connect(info, &ThingSetupInfo::aborted, sunSpec, &SunSpec::deleteLater); + connect(sunSpec, &SunSpec::destroyed, [this, info] { + m_sunSpecConnections.remove(info->thing()); }); //connect(sunSpec, &SunSpecInverter::inverterDataReceived, this, &IntegrationPluginSunSpec::onInverterDataReceived); - } else if (info->thing()->thingClassId() == sunspecInverterThingClassId) { + } else if (thing->thingClassId() == sunspecInverterThingClassId) { QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecInverterThingIpAddressParamTypeId).toString()); SunSpecInverter *sunSpecInverter = new SunSpecInverter(address, 502, this); - m_sunSpecInverters.insert(info->thing(), sunSpecInverter); + if (!sunSpecInverter->connectModbus()) { - QTimer::singleShot(2000, info, [this, info] { - setupThing(info); - }); qCWarning(dcSunSpec()) << "Error connecting to SunSpec device"; - return; + return info->finish(Thing::ThingErrorHardwareNotAvailable); } - connect(sunSpecInverter, &SunSpecInverter::initFinished, info, [info] { + connect(sunSpecInverter, &SunSpecInverter::initFinished, info, [this, sunSpecInverter, info] { qCDebug(dcSunSpec()) << "Modbus Inverter init finished"; + m_sunSpecInverters.insert(info->thing(), sunSpecInverter); info->finish(Thing::ThingErrorNoError); }); - connect(info, &ThingSetupInfo::aborted, sunSpecInverter, [info, this] { - m_sunSpecInverters.take(info->thing())->deleteLater(); - }); + connect(info, &ThingSetupInfo::aborted, sunSpecInverter, &SunSpecInverter::deleteLater); + connect(sunSpecInverter, &SunSpecInverter::destroyed, [info, this] {m_sunSpecInverters.remove(info->thing());}); connect(sunSpecInverter, &SunSpecInverter::inverterDataReceived, this, &IntegrationPluginSunSpec::onInverterDataReceived); - } else if (info->thing()->thingClassId() == sunspecMeterThingClassId) { + } else if (thing->thingClassId() == sunspecMeterThingClassId) { QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecMeterThingIpAddressParamTypeId).toString()); SunSpecMeter *sunSpecMeter = new SunSpecMeter(address, 502, this); - m_sunSpecMeters.insert(info->thing(), sunSpecMeter); - if (!sunSpecMeter->connectModbus()) { - QTimer::singleShot(2000, info, [this, info] { - setupThing(info); - }); - } + if (!sunSpecMeter->connectModbus()) { + sunSpecMeter->deleteLater(); + qCDebug(dcSunSpec()) << "Could not connect"; + return info->finish(Thing::ThingErrorHardwareNotAvailable); + } + m_sunSpecMeters.insert(info->thing(), sunSpecMeter); connect(info, &ThingSetupInfo::aborted, sunSpecMeter, [info, this] { m_sunSpecMeters.take(info->thing())->deleteLater(); }); @@ -103,11 +107,10 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) } else if (info->thing()->thingClassId() == sunspecTrackerThingClassId) { QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecTrackerThingIpAddressParamTypeId).toString()); SunSpecTracker *sunSpecTracker = new SunSpecTracker(address, 502, this); - m_sunSpecTrackers.insert(info->thing(), sunSpecTracker); if (!sunSpecTracker->connectModbus()) { - QTimer::singleShot(2000, info, [this, info] { - setupThing(info); - }); + sunSpecTracker->deleteLater(); + qCDebug(dcSunSpec()) << "Could not connect"; + return info->finish(Thing::ThingErrorHardwareNotAvailable); } connect(info, &ThingSetupInfo::aborted, sunSpecTracker, [info, this] { m_sunSpecTrackers.take(info->thing())->deleteLater(); @@ -116,12 +119,12 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) } else if (info->thing()->thingClassId() == sunspecStorageThingClassId) { QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecStorageThingIpAddressParamTypeId).toString()); SunSpecStorage *sunSpecStorage = new SunSpecStorage(address, 502, this); - m_sunSpecStorages.insert(info->thing(), sunSpecStorage); if (!sunSpecStorage->connectModbus()) { - QTimer::singleShot(2000, info, [this, info] { - setupThing(info); - }); + sunSpecStorage->deleteLater(); + qCDebug(dcSunSpec()) << "Could not connect"; + return info->finish(Thing::ThingErrorHardwareNotAvailable); } + m_sunSpecStorages.insert(info->thing(), sunSpecStorage); connect(info, &ThingSetupInfo::aborted, sunSpecStorage, [info, this] { m_sunSpecStorages.take(info->thing())->deleteLater(); }); @@ -133,9 +136,10 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) void IntegrationPluginSunSpec::postSetupThing(Thing *thing) { - Q_UNUSED(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); @@ -185,7 +189,7 @@ void IntegrationPluginSunSpec::postSetupThing(Thing *thing) void IntegrationPluginSunSpec::thingRemoved(Thing *thing) { - Q_UNUSED(thing) + qCDebug(dcSunSpec()) << "Thing removed" << thing->name(); if (thing->thingClassId() == sunspecInverterThingClassId) { SunSpecInverter *sunSpecInverter = m_sunSpecInverters.take(thing); @@ -212,6 +216,7 @@ void IntegrationPluginSunSpec::thingRemoved(Thing *thing) } if (myThings().isEmpty()) { + qCDebug(dcSunSpec()) << "Stopping refresh timer"; hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer); m_refreshTimer = nullptr; } @@ -297,6 +302,7 @@ void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info) void IntegrationPluginSunSpec::onRefreshTimer() { + qCDebug(dcSunSpec()) << "On refresh timer"; //get data foreach (SunSpecInverter *inverter, m_sunSpecInverters) { inverter->getInverterMap(); @@ -316,29 +322,32 @@ void IntegrationPluginSunSpec::onPluginConfigurationChanged(const ParamTypeId &p { // 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 { + qCWarning(dcSunSpec()) << "Unknown plugin configuration" << paramTypeId << "Value" << value; } } void IntegrationPluginSunSpec::onConnectionStateChanged(bool status) { - Q_UNUSED(status) + qCDebug(dcSunSpec()) << "Connection state changed" << status; } void IntegrationPluginSunSpec::onWriteRequestExecuted(QUuid requestId, bool success) { - Q_UNUSED(requestId) - Q_UNUSED(success) + qCDebug(dcSunSpec()) << "Write request executed" << requestId << success; } void IntegrationPluginSunSpec::onWriteRequestError(QUuid requestId, const QString &error) { Q_UNUSED(requestId) Q_UNUSED(error) + qCDebug(dcSunSpec()) << "Write request error" << error; } void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::InverterData &inverterData) diff --git a/sunspec/sunspec.cpp b/sunspec/sunspec.cpp index 6830f9e..694e807 100644 --- a/sunspec/sunspec.cpp +++ b/sunspec/sunspec.cpp @@ -55,6 +55,14 @@ bool SunSpec::connectModbus() return m_modbusTcpClient->connectDevice();; } +void SunSpec::setHostAddress(const QHostAddress &hostAddress) +{ + if (m_hostAddress != hostAddress) { + m_hostAddress = hostAddress; + m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, m_hostAddress.toString()); + } +} + QString SunSpec::manufacturer() { return m_manufacturer; diff --git a/sunspec/sunspec.h b/sunspec/sunspec.h index b576793..5a84ebe 100644 --- a/sunspec/sunspec.h +++ b/sunspec/sunspec.h @@ -172,6 +172,7 @@ public: explicit SunSpec(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); ~SunSpec(); bool connectModbus(); + void setHostAddress(const QHostAddress &hostAddress); QString manufacturer(); QString deviceModel(); QString serialNumber(); From 6383b60ce3e03e3924e4c43b5219747ecd348870 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Tue, 19 Jan 2021 18:12:59 +0100 Subject: [PATCH 05/16] added sunspec test server script --- sunspec/integrationpluginsunspec.cpp | 57 ++++++++++++++++--- sunspec/integrationpluginsunspec.h | 4 ++ sunspec/integrationpluginsunspec.json | 71 ++++++++++++++++------- sunspec/sunspec.cpp | 81 ++++++++++++++++++--------- sunspec/sunspec.h | 6 ++ sunspec/test/sunspec_server.sh | 19 +++++++ 6 files changed, 183 insertions(+), 55 deletions(-) create mode 100755 sunspec/test/sunspec_server.sh diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index fdd3f2b..127adb5 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -45,13 +45,16 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) 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(); + SunSpec *sunSpec; if (m_sunSpecConnections.contains(thing)) { qCDebug(dcSunSpec()) << "Reconfigure SunSpec connection with new address" << address; sunSpec = m_sunSpecConnections.value(thing); sunSpec->setHostAddress(address); } else { - sunSpec = new SunSpec(address, 502, this); + sunSpec = new SunSpec(address, port, this); m_sunSpecConnections.insert(info->thing(), sunSpec); } @@ -59,18 +62,24 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) qCWarning(dcSunSpec()) << "Error connecting to SunSpec device"; return info->finish(Thing::ThingErrorHardwareNotAvailable); } - connect(sunSpec, &SunSpec::connectionStateChanged, info, [info] (bool status) { - qCDebug(dcSunSpec()) << "Modbus Inverter init finished" << status; - if (status) { + 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, info] { m_sunSpecConnections.remove(info->thing()); }); - //connect(sunSpec, &SunSpecInverter::inverterDataReceived, this, &IntegrationPluginSunSpec::onInverterDataReceived); + connect(sunSpec, &SunSpec::connectionStateChanged, this, [thing] (bool status) { + thing->setStateValue(sunspecConnectionThingClassId, status); + }); + connect(sunSpec, &SunSpec::mapHeaderReceived, this, &IntegrationPluginSunSpec::onMapHeaderReceived); + connect(sunSpec, &SunSpec::mapReceived, this, &IntegrationPluginSunSpec::onMapReceived); } else if (thing->thingClassId() == sunspecInverterThingClassId) { QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecInverterThingIpAddressParamTypeId).toString()); @@ -145,7 +154,17 @@ void IntegrationPluginSunSpec::postSetupThing(Thing *thing) connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSunSpec::onRefreshTimer); } - if (thing->thingClassId() == sunspecInverterThingClassId) { + if (thing->thingClassId() == sunspecConnectionThingClassId) { + SunSpec *connection = m_sunSpecConnections.take(thing); + if (!connection) + return; + connection->readCommonMap(); + thing->setParamValue(sunspecConnectionManufacturerStateTypeId, connection->manufacturer()); + thing->setParamValue(sunspecInverterThingSerialNumberParamTypeId, connection->serialNumber()); + thing->setParamValue(sunspecInverterThingDeviceModelParamTypeId, connection->deviceModel()); + + + } else if (thing->thingClassId() == sunspecInverterThingClassId) { SunSpecInverter *sunSpecInverter = m_sunSpecInverters.take(thing); if (!sunSpecInverter) return; @@ -304,6 +323,9 @@ void IntegrationPluginSunSpec::onRefreshTimer() { qCDebug(dcSunSpec()) << "On refresh timer"; //get data + foreach (SunSpec *connections, m_sunSpecConnections) { + connections->readCommonMap(); + } foreach (SunSpecInverter *inverter, m_sunSpecInverters) { inverter->getInverterMap(); } @@ -338,6 +360,27 @@ void IntegrationPluginSunSpec::onConnectionStateChanged(bool status) qCDebug(dcSunSpec()) << "Connection state changed" << status; } +void IntegrationPluginSunSpec::onMapHeaderReceived(uint modbusAddress, SunSpec::BlockId mapId, uint mapLength) +{ + qCDebug(dcSunSpec()) << "On map header received" << modbusAddress << mapId << mapLength; +} + +void IntegrationPluginSunSpec::onMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data) +{ + Q_UNUSED(data) + SunSpec *connection = static_cast(sender()); + Thing *thing = m_sunSpecConnections.key(connection); + if (!thing) + return; + + qCDebug(dcSunSpec()) << "On map received" << mapId << mapLength; + if (mapId == SunSpec::BlockIdCommon && thing->thingClassId() == sunspecConnectionThingClassId) { + thing->setStateValue(sunspecConnectionManufacturerStateTypeId, connection->manufacturer()); + thing->setStateValue(sunspecConnectionSerialNumberStateTypeId, connection->serialNumber()); + thing->setStateValue(sunspecConnectionDeviceModelStateTypeId, connection->deviceModel()); + } +} + void IntegrationPluginSunSpec::onWriteRequestExecuted(QUuid requestId, bool success) { qCDebug(dcSunSpec()) << "Write request executed" << requestId << success; diff --git a/sunspec/integrationpluginsunspec.h b/sunspec/integrationpluginsunspec.h index 8b46bdb..c4aad1f 100644 --- a/sunspec/integrationpluginsunspec.h +++ b/sunspec/integrationpluginsunspec.h @@ -73,6 +73,10 @@ private slots: void onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value); void onConnectionStateChanged(bool status); + + void onMapHeaderReceived(uint modbusAddress, SunSpec::BlockId mapId, uint mapLength); + void onMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data); + void onWriteRequestExecuted(QUuid requestId, bool success); void onWriteRequestError(QUuid requestId, const QString &error); diff --git a/sunspec/integrationpluginsunspec.json b/sunspec/integrationpluginsunspec.json index e07b66b..209da62 100644 --- a/sunspec/integrationpluginsunspec.json +++ b/sunspec/integrationpluginsunspec.json @@ -10,6 +10,23 @@ "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": [ @@ -32,28 +49,18 @@ "type": "QString" }, { - "id": "04970315-ed3a-45ce-98fc-35ae3c4eb27b", - "name":"manufacturer", - "displayName": "Manufacturer", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" + "id": "1fa4fc9c-f6be-47c7-928a-bcefc1142eec", + "name":"port", + "displayName": "Port", + "type": "int", + "defaultValue": 502 }, { - "id": "58146c26-17d3-458e-a13f-d7f306c20c44", - "name":"deviceModel", - "displayName": "Device model", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" - }, - { - "id": "6ed498e1-37ca-4bb7-bac7-463509c7784e", - "name":"serialNumber", - "displayName": "Serial number", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" + "id": "953064e0-4675-4538-a9a2-fa22ce2f347c", + "name":"slaveId", + "displayName": "Slave ID", + "type": "int", + "defaultValue": 1 } ], "stateTypes":[ @@ -65,6 +72,30 @@ "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" } ] }, diff --git a/sunspec/sunspec.cpp b/sunspec/sunspec.cpp index 694e807..1947a8a 100644 --- a/sunspec/sunspec.cpp +++ b/sunspec/sunspec.cpp @@ -52,7 +52,7 @@ SunSpec::~SunSpec() bool SunSpec::connectModbus() { - return m_modbusTcpClient->connectDevice();; + return m_modbusTcpClient->connectDevice(); } void SunSpec::setHostAddress(const QHostAddress &hostAddress) @@ -63,6 +63,27 @@ void SunSpec::setHostAddress(const QHostAddress &hostAddress) } } +void SunSpec::setPort(uint port) +{ + m_port = port; + m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, port); +} + +void SunSpec::setSlaveId(uint slaveId) +{ + m_slaveId = slaveId; +} + +void SunSpec::setTimeout(uint milliSeconds) +{ + m_modbusTcpClient->setTimeout(milliSeconds); +} + +void SunSpec::setNumberOfRetries(uint retries) +{ + m_modbusTcpClient->setNumberOfRetries(retries); +} + QString SunSpec::manufacturer() { return m_manufacturer; @@ -80,39 +101,43 @@ QString SunSpec::serialNumber() void SunSpec::findBaseRegister() { - qCDebug(dcSunSpec()) << "find base register"; + qCDebug(dcSunSpec()) << "Find base register"; + QList validBaseRegisters; + validBaseRegisters.append(0); + validBaseRegisters.append(40000); + validBaseRegisters.append(50000); - QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, 40000, 2); + Q_FOREACH (int baseRegister, validBaseRegisters) { + 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, this] { - 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(); TODO - if ((unit.value(0) << 16 | unit.value(1)) == 0x53756e53) { - //Well-known value. Uniquely identifies this as a SunSpec Modbus Map - qCDebug(dcSunSpec()) << "Found start of modbus map"; - //emit foundBaseRegister(modbusAddress); + if (reply->error() == QModbusDevice::NoError) { + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + if ((unit.value(0) << 16 | unit.value(1)) == 0x53756e53) { + //Well-known value. Uniquely identifies this as a SunSpec Modbus Map + qCDebug(dcSunSpec()) << "Found start of modbus map" << modbusAddress; + m_baseRegister = modbusAddress; + emit foundBaseRegister(modbusAddress); + } else { + qCWarning(dcSunSpec()) << "Got reply on base register, but value didn't mach 0x53756e53"; + } + } else { + qCWarning(dcSunSpec()) << "Find base register read response error:" << reply->error(); } - } else { - qCWarning(dcSunSpec()) << "Read response error:" << reply->error(); - } - }); - connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { - qCWarning(dcSunSpec()) << "Modbus reply error:" << error; - reply->finished(); // To make sure it will be deleted - }); + }); + } else { + qCWarning(dcSunSpec()) << "Find base register eead error: " << m_modbusTcpClient->errorString(); + delete reply; // broadcast replies return immediately + return; + } } else { - qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); - delete reply; // broadcast replies return immediately + qCWarning(dcSunSpec()) << "Find base register read error: " << m_modbusTcpClient->errorString(); return; } - } else { - qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); - return; } } diff --git a/sunspec/sunspec.h b/sunspec/sunspec.h index 5a84ebe..73d75de 100644 --- a/sunspec/sunspec.h +++ b/sunspec/sunspec.h @@ -173,6 +173,11 @@ public: ~SunSpec(); bool connectModbus(); void setHostAddress(const QHostAddress &hostAddress); + void setPort(uint port); + void setSlaveId(uint slaveId); + void setTimeout(uint milliSeconds); + void setNumberOfRetries(uint retries); + QString manufacturer(); QString deviceModel(); QString serialNumber(); @@ -207,6 +212,7 @@ signals: void connectionStateChanged(bool status); void requestExecuted(QUuid requetId, bool success); + void foundBaseRegister(int modbusAddress); void foundModbusMap(BlockId mapId, int modbusStartRegister); void modbusMapSearchFinished(const QList &mapIds, uint modbusStartRegister, const QString &error); 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 From 0d91606e7d0ab6583ede0aff74afc51950e3b722 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Thu, 21 Jan 2021 13:25:34 +0100 Subject: [PATCH 06/16] fixed sf value conversion --- sunspec/README.md | 4 +- sunspec/integrationpluginsunspec.cpp | 429 ++++++++++++++++---------- sunspec/integrationpluginsunspec.h | 14 +- sunspec/integrationpluginsunspec.json | 413 ++++++++++++------------- sunspec/sunspec.cpp | 130 ++++---- sunspec/sunspec.h | 16 +- sunspec/sunspec.pro | 8 +- sunspec/sunspecinverter.cpp | 148 +++++---- sunspec/sunspecinverter.h | 56 ++-- sunspec/sunspecmeter.cpp | 57 +++- sunspec/sunspecmeter.h | 21 +- sunspec/sunspecstorage.cpp | 60 ++-- sunspec/sunspecstorage.h | 19 +- 13 files changed, 776 insertions(+), 599 deletions(-) diff --git a/sunspec/README.md b/sunspec/README.md index 0d3b491..f9ad5a5 100644 --- a/sunspec/README.md +++ b/sunspec/README.md @@ -1,13 +1,11 @@ # SunSpec -Connect to SunSpec devices. +Connect to SunSpec devices over Modbus TCP ## Supported Things * SunSpec Inverter -* SunSpec PV Modules * SunSpec Meter -* SunSpec Tracker * SunSpec Storage ## Requirements diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index 127adb5..9481dbc 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -38,6 +38,34 @@ 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_mapIdParamTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterThingMapIdParamTypeId); + m_mapIdParamTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterThingMapIdParamTypeId); + m_mapIdParamTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterThingMapIdParamTypeId); + m_mapIdParamTypeIds.insert(sunspecStorageThingClassId, sunspecStorageThingMapIdParamTypeId); + m_mapIdParamTypeIds.insert(sunspecSinglePhaseMeterThingClassId, sunspecSinglePhaseMeterThingMapIdParamTypeId); + m_mapIdParamTypeIds.insert(sunspecSplitPhaseMeterThingClassId, sunspecSplitPhaseMeterThingMapIdParamTypeId); + m_mapIdParamTypeIds.insert(sunspecThreePhaseMeterThingClassId, sunspecThreePhaseMeterThingMapIdParamTypeId); + + 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); +} + void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) { Thing *thing = info->thing(); @@ -49,13 +77,13 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) //int slaveId = info->thing()->paramValue(sunspecConnectionThingSlaveIdParamTypeId).toInt(); SunSpec *sunSpec; - if (m_sunSpecConnections.contains(thing)) { + if (m_sunSpecConnections.contains(thing->id())) { qCDebug(dcSunSpec()) << "Reconfigure SunSpec connection with new address" << address; - sunSpec = m_sunSpecConnections.value(thing); + sunSpec = m_sunSpecConnections.value(thing->id()); sunSpec->setHostAddress(address); } else { sunSpec = new SunSpec(address, port, this); - m_sunSpecConnections.insert(info->thing(), sunSpec); + m_sunSpecConnections.insert(thing->id(), sunSpec); } if (!sunSpec->connectModbus()) { @@ -72,71 +100,74 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) }); connect(info, &ThingSetupInfo::aborted, sunSpec, &SunSpec::deleteLater); - connect(sunSpec, &SunSpec::destroyed, [this, info] { - m_sunSpecConnections.remove(info->thing()); + connect(sunSpec, &SunSpec::destroyed, [this, thing] { + m_sunSpecConnections.remove(thing->id()); }); - connect(sunSpec, &SunSpec::connectionStateChanged, this, [thing] (bool status) { - thing->setStateValue(sunspecConnectionThingClassId, status); + connect(sunSpec, &SunSpec::foundModbusMap, this, &IntegrationPluginSunSpec::onFoundModbusMap); + connect(sunSpec, &SunSpec::modbusMapSearchFinished, this, &IntegrationPluginSunSpec::onModbusMapSearchFinished); + connect(sunSpec, &SunSpec::commonMapReceived, 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); }); - connect(sunSpec, &SunSpec::mapHeaderReceived, this, &IntegrationPluginSunSpec::onMapHeaderReceived); - connect(sunSpec, &SunSpec::mapReceived, this, &IntegrationPluginSunSpec::onMapReceived); - } else if (thing->thingClassId() == sunspecInverterThingClassId) { - QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecInverterThingIpAddressParamTypeId).toString()); - SunSpecInverter *sunSpecInverter = new SunSpecInverter(address, 502, this); + } else if (thing->thingClassId() == sunspecThreePhaseInverterThingClassId || + thing->thingClassId() == sunspecSplitPhaseInverterThingClassId || + thing->thingClassId() == sunspecSinglePhaseInverterThingClassId ) { - if (!sunSpecInverter->connectModbus()) { - qCWarning(dcSunSpec()) << "Error connecting to SunSpec device"; + uint mapId = thing->paramValue(m_mapIdParamTypeIds.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); } - connect(sunSpecInverter, &SunSpecInverter::initFinished, info, [this, sunSpecInverter, info] { - qCDebug(dcSunSpec()) << "Modbus Inverter init finished"; - m_sunSpecInverters.insert(info->thing(), sunSpecInverter); - info->finish(Thing::ThingErrorNoError); + SunSpecInverter *sunSpecInverter = new SunSpecInverter(connection, SunSpec::BlockId(mapId), 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, [info, this] {m_sunSpecInverters.remove(info->thing());}); + connect(sunSpecInverter, &SunSpecInverter::destroyed, thing, [thing, this] {m_sunSpecInverters.remove(thing);}); connect(sunSpecInverter, &SunSpecInverter::inverterDataReceived, this, &IntegrationPluginSunSpec::onInverterDataReceived); - } else if (thing->thingClassId() == sunspecMeterThingClassId) { - QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecMeterThingIpAddressParamTypeId).toString()); - SunSpecMeter *sunSpecMeter = new SunSpecMeter(address, 502, this); + } else if (thing->thingClassId() == sunspecSinglePhaseMeterThingClassId || + thing->thingClassId() == sunspecSplitPhaseMeterThingClassId || + thing->thingClassId() == sunspecThreePhaseMeterThingClassId) { - if (!sunSpecMeter->connectModbus()) { - sunSpecMeter->deleteLater(); - qCDebug(dcSunSpec()) << "Could not connect"; + uint mapId = thing->paramValue(m_mapIdParamTypeIds.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); } - m_sunSpecMeters.insert(info->thing(), sunSpecMeter); - connect(info, &ThingSetupInfo::aborted, sunSpecMeter, [info, this] { - m_sunSpecMeters.take(info->thing())->deleteLater(); + SunSpecMeter *sunSpecMeter = new SunSpecMeter(connection, SunSpec::BlockId(mapId), 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); + } }); - } else if (info->thing()->thingClassId() == sunspecTrackerThingClassId) { - QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecTrackerThingIpAddressParamTypeId).toString()); - SunSpecTracker *sunSpecTracker = new SunSpecTracker(address, 502, this); - if (!sunSpecTracker->connectModbus()) { - sunSpecTracker->deleteLater(); - qCDebug(dcSunSpec()) << "Could not connect"; - return info->finish(Thing::ThingErrorHardwareNotAvailable); - } - connect(info, &ThingSetupInfo::aborted, sunSpecTracker, [info, this] { - m_sunSpecTrackers.take(info->thing())->deleteLater(); - }); + 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); } else if (info->thing()->thingClassId() == sunspecStorageThingClassId) { - QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecStorageThingIpAddressParamTypeId).toString()); - SunSpecStorage *sunSpecStorage = new SunSpecStorage(address, 502, this); - if (!sunSpecStorage->connectModbus()) { - sunSpecStorage->deleteLater(); - qCDebug(dcSunSpec()) << "Could not connect"; - return info->finish(Thing::ThingErrorHardwareNotAvailable); - } - m_sunSpecStorages.insert(info->thing(), sunSpecStorage); - connect(info, &ThingSetupInfo::aborted, sunSpecStorage, [info, this] { - m_sunSpecStorages.take(info->thing())->deleteLater(); - }); + } else { Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(info->thing()->thingClassId().toString()).toUtf8()); @@ -155,50 +186,43 @@ void IntegrationPluginSunSpec::postSetupThing(Thing *thing) } if (thing->thingClassId() == sunspecConnectionThingClassId) { - SunSpec *connection = m_sunSpecConnections.take(thing); - if (!connection) + SunSpec *connection = m_sunSpecConnections.value(thing->id()); + if (!connection) { + qCDebug(dcSunSpec()) << "SunSpecConnection not found"; return; + } connection->readCommonMap(); - thing->setParamValue(sunspecConnectionManufacturerStateTypeId, connection->manufacturer()); - thing->setParamValue(sunspecInverterThingSerialNumberParamTypeId, connection->serialNumber()); - thing->setParamValue(sunspecInverterThingDeviceModelParamTypeId, connection->deviceModel()); + connection->findModbusMap(QList()); // Discover all maps, without filter + } else if (thing->thingClassId() == sunspecSinglePhaseInverterThingClassId || + thing->thingClassId() == sunspecSplitPhaseInverterThingClassId || + thing->thingClassId() == sunspecThreePhaseInverterThingClassId) { - } else if (thing->thingClassId() == sunspecInverterThingClassId) { - SunSpecInverter *sunSpecInverter = m_sunSpecInverters.take(thing); - if (!sunSpecInverter) + SunSpecInverter *sunSpecInverter = m_sunSpecInverters.value(thing); + if (!sunSpecInverter) { + qCDebug(dcSunSpec()) << "SunSpecInverter not found"; return; + } sunSpecInverter->getInverterMap(); - thing->setParamValue(sunspecInverterThingManufacturerParamTypeId, sunSpecInverter->manufacturer()); - thing->setParamValue(sunspecInverterThingSerialNumberParamTypeId, sunSpecInverter->serialNumber()); - thing->setParamValue(sunspecInverterThingDeviceModelParamTypeId, sunSpecInverter->deviceModel()); - } else if (thing->thingClassId() == sunspecMeterThingClassId) { - SunSpecMeter *sunSpecMeter = m_sunSpecMeters.take(thing); - if (!sunSpecMeter) - return; - //TODO upate data - thing->setParamValue(sunspecMeterThingManufacturerParamTypeId, sunSpecMeter->manufacturer()); - thing->setParamValue(sunspecMeterThingSerialNumberParamTypeId, sunSpecMeter->serialNumber()); - thing->setParamValue(sunspecMeterThingDeviceModelParamTypeId, sunSpecMeter->deviceModel()); + } else if (thing->thingClassId() == sunspecSinglePhaseMeterThingClassId || + thing->thingClassId() == sunspecSplitPhaseMeterThingClassId || + thing->thingClassId() == sunspecThreePhaseMeterThingClassId) { - } else if (thing->thingClassId() == sunspecTrackerThingClassId) { - SunSpecTracker *sunSpecTracker = m_sunSpecTrackers.take(thing); - if (!sunSpecTracker) + SunSpecMeter *sunSpecMeter = m_sunSpecMeters.value(thing); + if (!sunSpecMeter) { + qCDebug(dcSunSpec()) << "SunSpecMeter not found"; return; - //TODO upate data - thing->setParamValue(sunspecTrackerThingManufacturerParamTypeId, sunSpecTracker->manufacturer()); - thing->setParamValue(sunspecTrackerThingSerialNumberParamTypeId, sunSpecTracker->serialNumber()); - thing->setParamValue(sunspecTrackerThingDeviceModelParamTypeId, sunSpecTracker->deviceModel()); + } + sunSpecMeter->getMeterMap(); } else if (thing->thingClassId() == sunspecStorageThingClassId) { - SunSpecStorage *sunSpecStorage = m_sunSpecStorages.take(thing); - if (!sunSpecStorage) + SunSpecStorage *sunSpecStorage = m_sunSpecStorages.value(thing); + if (!sunSpecStorage) { + qCDebug(dcSunSpec()) << "SunSpecStorage not found"; return; - //TODO upate data - thing->setParamValue(sunspecStorageThingManufacturerParamTypeId, sunSpecStorage->manufacturer()); - thing->setParamValue(sunspecStorageThingSerialNumberParamTypeId, sunSpecStorage->serialNumber()); - thing->setParamValue(sunspecStorageThingDeviceModelParamTypeId, sunSpecStorage->deviceModel()); + } + sunSpecStorage->getStorageMap(); } else { Q_ASSERT_X(false, "postSetupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); @@ -210,21 +234,25 @@ void IntegrationPluginSunSpec::thingRemoved(Thing *thing) { qCDebug(dcSunSpec()) << "Thing removed" << thing->name(); - if (thing->thingClassId() == sunspecInverterThingClassId) { + 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() == sunspecMeterThingClassId) { + } 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() == sunspecTrackerThingClassId) { - SunSpecTracker *sunSpecTracker = m_sunSpecTrackers.take(thing); - if (sunSpecTracker) - sunSpecTracker->deleteLater(); - } else if (thing->thingClassId() == sunspecStorageThingClassId) { SunSpecStorage *sunSpecStorage = m_sunSpecStorages.take(thing); if (sunSpecStorage) @@ -246,7 +274,10 @@ void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info) Thing *thing = info->thing(); Action action = info->action(); - if (thing->thingClassId() == sunspecInverterThingClassId) { + if (thing->thingClassId() == sunspecSinglePhaseInverterThingClassId || + thing->thingClassId() == sunspecSplitPhaseInverterThingClassId || + thing->thingClassId() == sunspecThreePhaseInverterThingClassId) { + SunSpecInverter *sunSpecInverter = m_sunSpecInverters.value(thing); if (!sunSpecInverter) { qWarning(dcSunSpec()) << "Could not find SunSpec inverter for thing" << thing->name(); @@ -255,10 +286,9 @@ void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info) } info->finish(Thing::ThingErrorActionTypeNotFound); - } else if (thing->thingClassId() == sunspecMeterThingClassId) { - info->finish(Thing::ThingErrorActionTypeNotFound); - - } else if (thing->thingClassId() == sunspecTrackerThingClassId) { + } else if (thing->thingClassId() == sunspecSinglePhaseMeterThingClassId || + thing->thingClassId() == sunspecSplitPhaseMeterThingClassId || + thing->thingClassId() == sunspecThreePhaseMeterThingClassId) { info->finish(Thing::ThingErrorActionTypeNotFound); } else if (thing->thingClassId() == sunspecStorageThingClassId) { @@ -319,24 +349,30 @@ void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info) } } +bool IntegrationPluginSunSpec::checkIfThingExists(uint mapId, uint modbusAddress) +{ + Q_FOREACH(Thing *thing, myThings()) { + if (thing->paramValue(m_mapIdParamTypeIds.value(thing->thingClassId())).toUInt() == mapId && + thing->paramValue(m_modbusAddressParamTypeIds.value(thing->thingClassId())).toUInt() == modbusAddress) { + return true; + } + } + return false; +} + void IntegrationPluginSunSpec::onRefreshTimer() { - qCDebug(dcSunSpec()) << "On refresh timer"; - //get data - foreach (SunSpec *connections, m_sunSpecConnections) { - connections->readCommonMap(); + foreach (SunSpec *connection, m_sunSpecConnections) { + connection->readCommonMap(); //check connection } foreach (SunSpecInverter *inverter, m_sunSpecInverters) { inverter->getInverterMap(); } foreach (SunSpecMeter *meter, m_sunSpecMeters) { - Q_UNUSED(meter) //TODO - } - foreach (SunSpecTracker *tracker, m_sunSpecTrackers) { - Q_UNUSED(tracker) //TODO + meter->getMeterMap(); } foreach (SunSpecStorage *storage, m_sunSpecStorages) { - Q_UNUSED(storage) //TODO + storage->getStorageMap(); } } @@ -357,7 +393,18 @@ void IntegrationPluginSunSpec::onPluginConfigurationChanged(const ParamTypeId &p void IntegrationPluginSunSpec::onConnectionStateChanged(bool status) { - qCDebug(dcSunSpec()) << "Connection state changed" << 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::onMapHeaderReceived(uint modbusAddress, SunSpec::BlockId mapId, uint mapLength) @@ -369,16 +416,93 @@ void IntegrationPluginSunSpec::onMapReceived(SunSpec::BlockId mapId, uint mapLen { Q_UNUSED(data) SunSpec *connection = static_cast(sender()); - Thing *thing = m_sunSpecConnections.key(connection); + Thing *thing = myThings().findById(m_sunSpecConnections.key(connection)); if (!thing) return; - qCDebug(dcSunSpec()) << "On map received" << mapId << mapLength; - if (mapId == SunSpec::BlockIdCommon && thing->thingClassId() == sunspecConnectionThingClassId) { - thing->setStateValue(sunspecConnectionManufacturerStateTypeId, connection->manufacturer()); - thing->setStateValue(sunspecConnectionSerialNumberStateTypeId, connection->serialNumber()); - thing->setStateValue(sunspecConnectionDeviceModelStateTypeId, connection->deviceModel()); - } + qCDebug(dcSunSpec()) << "On map received" << mapId << "Map length" << mapLength << "Thing:" << thing->name(); +} + +void IntegrationPluginSunSpec::onFoundModbusMap(SunSpec::BlockId mapId, 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 map received" << mapId << "Map length" << modbusStartRegister << "Thing:" << thing->name(); + if (checkIfThingExists(mapId, modbusStartRegister)) { + return; + } + + switch (mapId) { + case SunSpec::BlockId::BlockIdInverterSinglePhase: + case SunSpec::BlockId::BlockIdInverterSinglePhaseFloat: { + QString model = thing->stateValue(sunspecConnectionDeviceModelStateTypeId).toString(); + ThingDescriptor descriptor(sunspecSinglePhaseInverterThingClassId, model+tr(" single phase inverter"), "", thing->id()); + ParamList params; + params.append(Param(sunspecSinglePhaseInverterThingMapIdParamTypeId, mapId)); + params.append(Param(sunspecSinglePhaseInverterThingModbusAddressParamTypeId, modbusStartRegister)); + descriptor.setParams(params); + emit autoThingsAppeared({descriptor}); + } break; + case SunSpec::BlockId::BlockIdInverterSplitPhase: + case SunSpec::BlockId::BlockIdInverterSplitPhaseFloat: { + QString model = thing->stateValue(sunspecConnectionDeviceModelStateTypeId).toString(); + ThingDescriptor descriptor(sunspecSplitPhaseInverterThingClassId, model+tr(" split phase inverter"), "", thing->id()); + ParamList params; + params.append(Param(sunspecSplitPhaseInverterThingMapIdParamTypeId, mapId)); + params.append(Param(sunspecSplitPhaseInverterThingModbusAddressParamTypeId, modbusStartRegister)); + descriptor.setParams(params); + emit autoThingsAppeared({descriptor}); + } break; + case SunSpec::BlockId::BlockIdInverterThreePhase: + case SunSpec::BlockId::BlockIdInverterThreePhaseFloat: { + QString model = thing->stateValue(sunspecConnectionDeviceModelStateTypeId).toString(); + ThingDescriptor descriptor(sunspecThreePhaseInverterThingClassId, model+tr(" three phase inverter"), "", thing->id()); + ParamList params; + params.append(Param(sunspecThreePhaseInverterThingMapIdParamTypeId, mapId)); + params.append(Param(sunspecThreePhaseInverterThingModbusAddressParamTypeId, modbusStartRegister)); + descriptor.setParams(params); + emit autoThingsAppeared({descriptor}); + } break; + + case SunSpec::BlockIdSinglePhaseMeter: + case SunSpec::BlockIdSinglePhaseMeterFloat: { + } break; + case SunSpec::BlockIdSplitSinglePhaseMeter: + case SunSpec::BlockIdSplitSinglePhaseMeterFloat: { + } break; + case SunSpec::BlockIdWyeConnectThreePhaseMeterFloat: + case SunSpec::BlockIdDeltaConnectThreePhaseMeterFloat: { + } break; + case SunSpec::BlockIdStorage: { + QString model = thing->stateValue(sunspecConnectionDeviceModelStateTypeId).toString(); + ThingDescriptor descriptor(sunspecStorageThingClassId, model+" Storage", "Storage", thing->id()); + ParamList params; + params.append(Param(sunspecStorageThingMapIdParamTypeId, mapId)); + params.append(Param(sunspecThreePhaseInverterThingModbusAddressParamTypeId, modbusStartRegister)); + descriptor.setParams(params); + emit autoThingsAppeared({descriptor}); + } break; + default: + qCDebug(dcSunSpec()) << "Block Id not handled"; + } +} + +void IntegrationPluginSunSpec::onModbusMapSearchFinished(const QHash &mapIds) +{ + 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 modbus map search finished, maps:" << mapIds.count(); + } void IntegrationPluginSunSpec::onWriteRequestExecuted(QUuid requestId, bool success) @@ -401,95 +525,96 @@ void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::Inv if(!thing) { return; } - thing->setStateValue(sunspecInverterAcPowerStateTypeId, inverterData.acPower); - thing->setStateValue(sunspecInverterAcEnergyStateTypeId, inverterData.acEnergy/1000.00); - thing->setStateValue(sunspecInverterLineFrequencyStateTypeId, inverterData.lineFrequency); - thing->setStateValue(sunspecInverterTotalCurrentStateTypeId, inverterData.acCurrent); - thing->setStateValue(sunspecInverterCabinetTemperatureStateTypeId, inverterData.cabinetTemperature); + thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true); + thing->setStateValue(sunspecThreePhaseInverterAcPowerStateTypeId, inverterData.acPower/1000.00); + thing->setStateValue(sunspecThreePhaseInverterAcEnergyStateTypeId, inverterData.acEnergy/1000.00); + thing->setStateValue(sunspecThreePhaseInverterLineFrequencyStateTypeId, inverterData.lineFrequency); + thing->setStateValue(sunspecThreePhaseInverterTotalCurrentStateTypeId, inverterData.acCurrent); + thing->setStateValue(sunspecThreePhaseInverterCabinetTemperatureStateTypeId, inverterData.cabinetTemperature); - thing->setStateValue(sunspecInverterPhaseACurrentStateTypeId, inverterData.phaseACurrent); - thing->setStateValue(sunspecInverterPhaseBCurrentStateTypeId, inverterData.phaseBCurrent); - thing->setStateValue(sunspecInverterPhaseCCurrentStateTypeId, inverterData.phaseCCurrent); + thing->setStateValue(sunspecThreePhaseInverterPhaseACurrentStateTypeId, inverterData.phaseACurrent); + thing->setStateValue(sunspecThreePhaseInverterPhaseBCurrentStateTypeId, inverterData.phaseBCurrent); + thing->setStateValue(sunspecThreePhaseInverterPhaseCCurrentStateTypeId, inverterData.phaseCCurrent); - thing->setStateValue(sunspecInverterPhaseANVoltageStateTypeId, inverterData.phaseVoltageAN); - thing->setStateValue(sunspecInverterPhaseBNVoltageStateTypeId, inverterData.phaseVoltageBN); - thing->setStateValue(sunspecInverterPhaseCNVoltageStateTypeId, inverterData.phaseVoltageCN); + thing->setStateValue(sunspecThreePhaseInverterPhaseANVoltageStateTypeId, inverterData.phaseVoltageAN); + thing->setStateValue(sunspecThreePhaseInverterPhaseBNVoltageStateTypeId, inverterData.phaseVoltageBN); + thing->setStateValue(sunspecThreePhaseInverterPhaseCNVoltageStateTypeId, inverterData.phaseVoltageCN); switch(inverterData.operatingState) { case SunSpec::SunSpecOperatingState::Off: - thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Off"); + thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "Off"); break; case SunSpec::SunSpecOperatingState::MPPT: - thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "MPPT"); + thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "MPPT"); break; case SunSpec::SunSpecOperatingState::Fault: - thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Fault"); + thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "Fault"); break; case SunSpec::SunSpecOperatingState::Standby: - thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Standby"); + thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "Standby"); break; case SunSpec::SunSpecOperatingState::Sleeping: - thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Sleeping"); + thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "Sleeping"); break; case SunSpec::SunSpecOperatingState::Starting: - thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Starting"); + thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "Starting"); break; case SunSpec::SunSpecOperatingState::Throttled: - thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Throttled"); + thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "Throttled"); break; case SunSpec::SunSpecOperatingState::ShuttingDown: - thing->setStateValue(sunspecInverterOperatingStateStateTypeId, "Shutting down"); + thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "Shutting down"); break; } switch(inverterData.event) { case SunSpec::SunSpecEvent1::OVER_TEMP: - thing->setStateValue(sunspecInverterErrorStateTypeId, "Over temperature"); + thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Over temperature"); break; case SunSpec::SunSpecEvent1::UNDER_TEMP: - thing->setStateValue(sunspecInverterErrorStateTypeId, "Under temperature"); + thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Under temperature"); break; case SunSpec::SunSpecEvent1::GroundFault: - thing->setStateValue(sunspecInverterErrorStateTypeId, "Ground fault"); + thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Ground fault"); break; case SunSpec::SunSpecEvent1::MEMORY_LOSS: - thing->setStateValue(sunspecInverterErrorStateTypeId, "Memory loss"); + thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Memory loss"); break; case SunSpec::SunSpecEvent1::AC_OVER_VOLT: - thing->setStateValue(sunspecInverterErrorStateTypeId, "AC voltage above limit"); + thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "AC voltage above limit"); break; case SunSpec::SunSpecEvent1::CABINET_OPEN: - thing->setStateValue(sunspecInverterErrorStateTypeId, "Cabinet open"); + thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Cabinet open"); break; case SunSpec::SunSpecEvent1::AC_DISCONNECT: - thing->setStateValue(sunspecInverterErrorStateTypeId, "AC disconnect open"); + thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "AC disconnect open"); break; case SunSpec::SunSpecEvent1::AC_UNDER_VOLT: - thing->setStateValue(sunspecInverterErrorStateTypeId, "AC voltage under limit"); + thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "AC voltage under limit"); break; case SunSpec::SunSpecEvent1::DC_DISCONNECT: - thing->setStateValue(sunspecInverterErrorStateTypeId, "DC disconnect open"); + thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "DC disconnect open"); break; case SunSpec::SunSpecEvent1::DcOverVolatage: - thing->setStateValue(sunspecInverterErrorStateTypeId, "DC over voltage"); + thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "DC over voltage"); break; case SunSpec::SunSpecEvent1::OVER_FREQUENCY: - thing->setStateValue(sunspecInverterErrorStateTypeId, "Frequency above limit"); + thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Frequency above limit"); break; case SunSpec::SunSpecEvent1::GRID_DISCONNECT: - thing->setStateValue(sunspecInverterErrorStateTypeId, "Grid disconnect"); + thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Grid disconnect"); break; case SunSpec::SunSpecEvent1::HW_TEST_FAILURE: - thing->setStateValue(sunspecInverterErrorStateTypeId, "Hardware test failure"); + thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Hardware test failure"); break; case SunSpec::SunSpecEvent1::MANUAL_SHUTDOWN: - thing->setStateValue(sunspecInverterErrorStateTypeId, "Manual shutdown"); + thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Manual shutdown"); break; case SunSpec::SunSpecEvent1::UNDER_FREQUENCY: - thing->setStateValue(sunspecInverterErrorStateTypeId, "Frequency under limit"); + thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Frequency under limit"); break; case SunSpec::SunSpecEvent1::BLOWN_STRING_FUSE: - thing->setStateValue(sunspecInverterErrorStateTypeId, "Blown string fuse on input"); + thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Blown string fuse on input"); break; } } @@ -516,15 +641,3 @@ void IntegrationPluginSunSpec::onMeterDataReceived(const SunSpecMeter::MeterData } //thing->setStateValue(sunspecMeterStorageStateStateTypeId, meterData.event); } - -void IntegrationPluginSunSpec::onTrackerDataReceived(const SunSpecTracker::TrackerData &trackerData) -{ - Q_UNUSED(trackerData) - SunSpecTracker *tracker = static_cast(sender()); - Thing *thing = m_sunSpecTrackers.key(tracker); - - if(!thing) { - return; - } - //thing->setStateValue(sunspecStorageStorageStateStateTypeId, storageData.chargingState); -} diff --git a/sunspec/integrationpluginsunspec.h b/sunspec/integrationpluginsunspec.h index c4aad1f..a5d328a 100644 --- a/sunspec/integrationpluginsunspec.h +++ b/sunspec/integrationpluginsunspec.h @@ -52,20 +52,26 @@ class IntegrationPluginSunSpec: public 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_mapIdParamTypeIds; + QHash m_modbusAddressParamTypeIds; + + QHash m_connectedStateTypeIds; PluginTimer *m_refreshTimer = nullptr; QHash m_asyncActions; - QHash m_sunSpecConnections; + QHash m_sunSpecConnections; QHash m_sunSpecInverters; QHash m_sunSpecStorages; QHash m_sunSpecMeters; - QHash m_sunSpecTrackers; + + bool checkIfThingExists(uint mapId, uint modbusAddress); private slots: void onRefreshTimer(); @@ -77,13 +83,15 @@ private slots: void onMapHeaderReceived(uint modbusAddress, SunSpec::BlockId mapId, uint mapLength); void onMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data); + void onFoundModbusMap(SunSpec::BlockId mapId, int modbusStartRegister); + void onModbusMapSearchFinished(const QHash &mapIds); + void onWriteRequestExecuted(QUuid requestId, bool success); void onWriteRequestError(QUuid requestId, const QString &error); void onInverterDataReceived(const SunSpecInverter::InverterData &inverterData); void onStorageDataReceived(const SunSpecStorage::StorageData &storageData); void onMeterDataReceived(const SunSpecMeter::MeterData &meterData); - void onTrackerDataReceived(const SunSpecTracker::TrackerData &trackerData); }; #endif // INTEGRATIONPLUGINSUNSPEC_H diff --git a/sunspec/integrationpluginsunspec.json b/sunspec/integrationpluginsunspec.json index 209da62..a0c1a72 100644 --- a/sunspec/integrationpluginsunspec.json +++ b/sunspec/integrationpluginsunspec.json @@ -46,7 +46,8 @@ "id": "6be6abc4-e2b2-4687-9343-0e5164ed0ab2", "name":"ipAddress", "displayName": "IP address", - "type": "QString" + "type": "QString", + "defaultValue": "127.0.0.1" }, { "id": "1fa4fc9c-f6be-47c7-928a-bcefc1142eec", @@ -100,41 +101,96 @@ ] }, { - "name": "sunspecInverter", - "displayName": "SunSpec Inverter", + "name": "sunspecSinglePhaseInverter", + "displayName": "SunSpec single phase inverter", + "id": "c5d5204e-3375-4b92-8128-fab77a671fed", + "createMethods": [ "Auto" ], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "41715d00-a947-4f43-a475-cea05790e01d", + "name":"mapId", + "displayName": "Map id", + "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 + } + ] + }, + { + "name": "sunspecSplitPhaseInverter", + "displayName": "SunSpec split phase inverter", + "id": "61b38f93-d331-42bf-b1ef-d3fb16ad1230", + "createMethods": [ "Auto" ], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "c42fb50e-210f-4b53-88eb-fa216e15f88f", + "name":"mapId", + "displayName": "Map id", + "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 + } + ] + }, + { + "name": "sunspecThreePhaseInverter", + "displayName": "SunSpec three phase inverter", "id": "2e4122ea-96a5-415c-b5e2-7d6012265a83", "createMethods": [ "Auto" ], "interfaces": ["connectable"], "paramTypes": [ { - "id": "6ffaa694-a4f2-4936-b043-37679449a34b", - "name":"ipAddress", - "displayName": "IP address", - "type": "QString" + "id": "8d5b2b58-ce46-406d-844e-f53136afcf09", + "name":"mapId", + "displayName": "Map id", + "type": "int", + "readOnly": true }, { - "id": "418d90eb-16e9-47e4-83e3-a116a8fa4de7", - "name":"manufacturer", - "displayName": "Manufacturer", - "type": "QString", + "id": "e5465ede-9d3d-4558-b614-40dda743ddae", + "name":"modbusAddress", + "displayName": "Modbus address", + "type": "uint", "readOnly": true, - "defaultValue": "Unkown" - }, - { - "id": "edf1bc0f-d143-4b75-a11f-635339fb30bf", - "name":"deviceModel", - "displayName": "Device model", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" - }, - { - "id": "d5181377-c746-4352-8683-6d90a4a83e6e", - "name":"serialNumber", - "displayName": "Serial number", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" + "defaultValue": 0 } ], "stateTypes":[ @@ -162,54 +218,54 @@ "name": "totalCurrent", "displayName": "Total AC current", "displayNameEvent": "Total AC current changed", - "type": "int", + "type": "double", "unit": "Ampere", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "3140ccd3-40cf-46c8-8bb2-8c3ea4582f84", "name": "phaseACurrent", "displayName": "Phase A current", "displayNameEvent": "Phase A current changed", - "type": "int", + "type": "double", "unit": "Ampere", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "7ea1a53a-6fd9-4914-8283-b57aa1aaaebf", "name": "phaseBCurrent", "displayName": "Phase B current", "displayNameEvent": "Phase B current changed", - "type": "int", + "type": "double", "unit": "Ampere", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "aa4b4cf5-43d0-4be5-9505-403918b5371d", "name": "phaseCCurrent", "displayName": "Phase C current", "displayNameEvent": "Phase C current changed", - "type": "int", + "type": "double", "unit": "Ampere", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "f08521aa-9c38-4c31-95e1-acb616f6e9c6", "name": "phaseANVoltage", "displayName": "Phase AN voltage", "displayNameEvent": "Phase AN volatage changed", - "type": "int", + "type": "double", "unit": "Volt", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "739b8805-d522-4406-bede-d1e4200a3aa9", "name": "phaseBNVoltage", "displayName": "Phase BN voltage", "displayNameEvent": "Phase BN voltage changed", - "type": "int", + "type": "double", "unit": "Volt", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "949b797d-5566-4667-8982-e430d23548e2", @@ -225,36 +281,36 @@ "name": "acPower", "displayName": "AC power", "displayNameEvent": "AC power changed", - "type": "int", + "type": "double", "unit": "KiloWatt", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "faa45cae-ed28-4150-9036-fceddf9d6776", "name": "lineFrequency", "displayName": "Line frequency", "displayNameEvent": "Line frequency changed", - "type": "int", + "type": "double", "unit": "Hertz", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "d493880d-eb58-4530-8010-8ea4f6d63387", "name": "acEnergy", "displayName": "AC energy", "displayNameEvent": "AC energy changed", - "type": "int", + "type": "double", "unit": "KiloWattHour", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "44b0320f-89a7-4248-bad4-288ef898a5cc", "name": "cabinetTemperature", "displayName": "Cabinet temperature", "displayNameEvent": "Cabinet temperature changed", - "type": "int", + "type": "double", "unit": "DegreeCelsius", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "cebdce98-42d1-4a28-8834-8960efc0e83f", @@ -304,41 +360,99 @@ ] }, { - "name": "sunspecMeter", - "displayName": "SunSpec Meter", + "name": "sunspecSinglePhaseMeter", + "displayName": "SunSpec single phase meter", + "id": "7ffa43b8-b56f-4435-8509-980e9d81dfa8", + "createMethods": [ "Auto" ], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "7d6fcafb-c62e-4a21-aae2-f4041c487149", + "name":"mapId", + "displayName": "Map id", + "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 + } + ] + }, + { + "name": "sunspecSplitPhaseMeter", + "displayName": "SunSpec split phase meter", + "id": "b8a18e45-5ff5-4f43-915f-04ee216c809d", + "createMethods": [ "Auto" ], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "89aeec6d-abeb-48b5-9594-214ad5db2d03", + "name":"mapId", + "displayName": "Map id", + "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 + } + ] + }, + { + "name": "sunspecThreePhaseMeter", + "displayName": "SunSpec three phase meter", "id": "68f822f9-ff30-4275-b229-39a3674fead7", "createMethods": [ "Auto" ], "interfaces": ["connectable"], "paramTypes": [ { - "id": "66bdb89b-bbd4-4edf-96df-649014d94c42", - "name":"ipAddress", - "displayName": "IP address", - "type": "QString" + "id": "a1960821-155c-4176-86fa-974429039182", + "name":"mapId", + "displayName": "Map id", + "type": "int", + "readOnly": true, + "defaultValue": 0 }, { - "id": "aa1ca51d-5e9c-4a1a-a4ec-e479d4f702d5", - "name":"manufacturer", - "displayName": "Manufacturer", - "type": "QString", + "id": "6d5dbd35-1bf6-46db-bee9-90c679421b89", + "name":"modbusAddress", + "displayName": "Modbus address", + "type": "uint", "readOnly": true, - "defaultValue": "Unkown" - }, - { - "id": "52da5388-28d7-48a2-8ad6-4c1e852a4348", - "name":"deviceModel", - "displayName": "Device model", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" - }, - { - "id": "9fa7288c-e7a1-4163-baa1-6f484e1feb55", - "name":"serialNumber", - "displayName": "Serial number", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" + "defaultValue": 0 } ], "stateTypes":[ @@ -442,8 +556,7 @@ "defaultValue": 0 }, { - "id": "73ebf57f-1ad2-4d19-bfd9-9e0a514c1243 -", + "id": "73ebf57f-1ad2-4d19-bfd9-9e0a514c1243", "name": "energyExported", "displayName": "Total real energy exported", "displayNameEvent": "Total real energy exported changed", @@ -462,66 +575,6 @@ } ] }, - { - "name": "sunspecTracker", - "displayName": "SunSpec Tracker", - "id": "9941da30-a6d6-475d-8244-3c2145b419e6", - "createMethods": [ "Auto" ], - "interfaces": ["connectable"], - "paramTypes": [ - { - "id": "ccdcca78-7efa-4253-8fdb-8731e6880e9b", - "name":"ipAddress", - "displayName": "IP address", - "type": "QString" - }, - { - "id": "2795052c-5d63-423d-b44e-fdf02dfaac37", - "name":"manufacturer", - "displayName": "Manufacturer", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" - }, - { - "id": "7528c840-da8c-40ba-ab82-914cfc686a49", - "name":"deviceModel", - "displayName": "Device model", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" - }, - { - "id": "ce9cc598-947c-4efc-b04f-21e784c5d2df", - "name":"serialNumber", - "displayName": "Serial number", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" - } - ], - "stateTypes":[ - { - "id": "fe57e465-e49e-4d50-b880-6d9a243783ff", - "name": "connected", - "displayName": "Connected", - "displayNameEvent": "Connected changed", - "type": "bool", - "defaultValue": false, - "cached": false - }, - { - "id": "d2bbba4b-998f-444e-935f-f958927afddd", - "name": "power", - "displayName": "Power", - "displayNameEvent": "Power changed", - "displayNameAction": "Change power", - "type": "bool", - "defaultValue": 0, - "writable": true - } - ] - }, { "name": "sunspecStorage", "displayName": "SunSpec Storage", @@ -530,34 +583,20 @@ "interfaces": ["connectable"], "paramTypes": [ { - "id": "421cacb9-a94d-4ba9-923c-3bc30d8b94c8", - "name":"ipAddress", - "displayName": "IP address", - "type": "QString" + "id": "219beb96-b9fe-4dd2-a386-ecfbbab8786d", + "name":"mapId", + "displayName": "Map id", + "type": "int", + "readOnly": true, + "defaultValue": 0 }, { - "id": "1d8f1ef7-33ad-40ed-99dd-ec54f87c2f17", - "name":"manufacturer", - "displayName": "Manufacturer", - "type": "QString", + "id": "3f107844-00c5-4f39-86e5-485b3d1f5c1a", + "name":"modbusAddress", + "displayName": "Modbus address", + "type": "uint", "readOnly": true, - "defaultValue": "Unkown" - }, - { - "id": "2a7f8464-6f03-4113-ac1d-d8ebd8705695", - "name":"deviceModel", - "displayName": "Device model", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" - }, - { - "id": "2003acdb-38ad-4184-baf8-9ef8e67fc452", - "name":"serialNumber", - "displayName": "Serial number", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" + "defaultValue": 0 } ], "stateTypes":[ @@ -663,56 +702,6 @@ "displayNameAction": "Set discharging rate" } ] - }, - { - "name": "sunspecStringCombiner", - "displayName": "SunSpec String Combiner", - "id": "7787f238-f5a3-4ba5-b3ec-7d513b87bf71", - "createMethods": [ "Auto" ], - "interfaces": ["connectable"], - "paramTypes": [ - { - "id": "226697c3-37bd-49c7-8c0e-40537573c18e", - "name":"ipAddress", - "displayName": "IP address", - "type": "QString" - }, - { - "id": "3f126e65-05e2-46a3-aeeb-bfacc0bdd9b0", - "name":"manufacturer", - "displayName": "Manufacturer", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" - }, - { - "id": "40666030-3609-467f-b26c-3a42d1c6dca1", - "name":"deviceModel", - "displayName": "Device model", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" - }, - { - "id": "f5046138-9271-4a91-a3eb-c2ecfa755ef5", - "name":"serialNumber", - "displayName": "Serial number", - "type": "QString", - "readOnly": true, - "defaultValue": "Unkown" - } - ], - "stateTypes":[ - { - "id": "3f791612-50fd-4a3c-ac19-27e01dd8c63a", - "name": "connected", - "displayName": "Connected", - "displayNameEvent": "Connected changed", - "type": "bool", - "defaultValue": false, - "cached": false - } - ] } ] } diff --git a/sunspec/sunspec.cpp b/sunspec/sunspec.cpp index 1947a8a..40b5950 100644 --- a/sunspec/sunspec.cpp +++ b/sunspec/sunspec.cpp @@ -37,6 +37,7 @@ SunSpec::SunSpec(const QHostAddress &hostAddress, uint port, QObject *parent) : m_hostAddress(hostAddress), m_port(port) { + 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()); @@ -48,16 +49,19 @@ SunSpec::SunSpec(const QHostAddress &hostAddress, uint port, QObject *parent) : 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()); } @@ -65,25 +69,46 @@ void SunSpec::setHostAddress(const QHostAddress &hostAddress) void SunSpec::setPort(uint port) { - m_port = port; - m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, 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; @@ -101,49 +126,45 @@ QString SunSpec::serialNumber() void SunSpec::findBaseRegister() { - qCDebug(dcSunSpec()) << "Find base register"; + 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, this] { + connect(reply, &QModbusReply::finished, this, [reply, baseRegister, this] { if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); - uint modbusAddress = unit.startAddress(); if ((unit.value(0) << 16 | unit.value(1)) == 0x53756e53) { //Well-known value. Uniquely identifies this as a SunSpec Modbus Map - qCDebug(dcSunSpec()) << "Found start of modbus map" << modbusAddress; - m_baseRegister = modbusAddress; - emit foundBaseRegister(modbusAddress); + qCDebug(dcSunSpec()) << "SunSpec: Found start of modbus map" << baseRegister; + m_baseRegister = baseRegister; + emit foundBaseRegister(baseRegister); } else { - qCWarning(dcSunSpec()) << "Got reply on base register, but value didn't mach 0x53756e53"; + qCWarning(dcSunSpec()) << "SunSpec: Got reply on base register" << baseRegister << ", but value didn't mach 0x53756e53"; } } else { - qCWarning(dcSunSpec()) << "Find base register read response error:" << reply->error(); + qCDebug(dcSunSpec()) << "SunSpec: Find base register not found at:" << baseRegister; } }); } else { - qCWarning(dcSunSpec()) << "Find base register eead error: " << m_modbusTcpClient->errorString(); delete reply; // broadcast replies return immediately return; } - } else { - qCWarning(dcSunSpec()) << "Find base register read error: " << m_modbusTcpClient->errorString(); - return; } } } void SunSpec::findModbusMap(const QList &ids, uint modbusAddressOffset) { - qCDebug(dcSunSpec()) << "Find modbus map. Start register" << m_baseRegister+modbusAddressOffset; + qCDebug(dcSunSpec()) << "SunSpec: Find modbus map. Start register" << m_baseRegister+modbusAddressOffset; QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_baseRegister+modbusAddressOffset, 2); if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, m_slaveId)) { @@ -156,41 +177,37 @@ void SunSpec::findModbusMap(const QList &ids, uint modbusAddressOffset) uint modbusAddress = unit.startAddress(); BlockId blockId = BlockId(unit.value(0)); int blockLength = unit.value(1); - if (blockId > 800 || blockId == BlockIdEnd) { - qCDebug(dcSunSpec()) << "Block id not found, Id:" << ids; - modbusMapSearchFinished(ids, 0, "Ids not found"); + if (blockId == BlockIdEnd) { + qCDebug(dcSunSpec()) << "SunSpec: Block Id End"; + modbusMapSearchFinished(m_mapList); return; } - if (ids.contains(blockId)) { - qCDebug(dcSunSpec()) << "Found block" << BlockId(blockId); + + if (ids.isEmpty() || ids.contains(blockId)) { + // If ids is empty then emit all blocks + qCDebug(dcSunSpec()) << "SunSpec: Found block" << BlockId(blockId) << "with block length" << blockLength; + m_mapList.insert(BlockId(blockId), modbusAddress); foundModbusMap(BlockId(blockId), modbusAddress); - } else { - //read next block header - qCDebug(dcSunSpec()) << "Found Block" << blockId << "with block length" << blockLength; - findModbusMap(ids, modbusAddress+2+blockLength-m_baseRegister); //read next block } + findModbusMap(ids, modbusAddress+2+blockLength-m_baseRegister); //read next block } else { - qCWarning(dcSunSpec()) << "Read response error:" << reply->error(); + qCWarning(dcSunSpec()) << "SunSpec: Find modbus map, read response error:" << reply->error(); } }); - connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { - qCWarning(dcSunSpec()) << "Modbus replay error:" << error; - reply->finished(); // To make sure it will be deleted - }); } else { delete reply; // broadcast replies return immediately return; } } else { - qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString(); return; } } void SunSpec::readMapHeader(uint modbusAddress) { - qCDebug(dcSunSpec()) << "Read block header. Modbus Address:" << modbusAddress << "Slave ID" << m_slaveId; - if (modbusAddress == 40000) + qCDebug(dcSunSpec()) << "SunSpec: Read map 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); @@ -228,7 +245,7 @@ void SunSpec::readMapHeader(uint modbusAddress) void SunSpec::readMap(uint modbusAddress, uint modelLength) { - qCDebug(dcSunSpec()) << "Read map. Modbus Address" << modbusAddress << ", Slave ID" << m_slaveId; + qCDebug(dcSunSpec()) << "SunSpec: Read map, modbus address" << modbusAddress << "model length" << modelLength << ", Slave ID" << m_slaveId; QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, modbusAddress, modelLength+2); @@ -265,18 +282,7 @@ void SunSpec::readMap(uint modbusAddress, uint modelLength) void SunSpec::readCommonMap() { - qCDebug(dcSunSpec()) << "Read common block"; - - // Base and Alternate Base Register Addresses - // DeviceModbus maps begin at one of three well-­known Modbus base addresses. - // Preferred Base Register: 40000 - // Alternate Base Register: 50000 - // Alternate Base Register: 00000 - - // Common Model model-length is 66 - // First two registers are the SunSpec Modbus Map identifier - - qCDebug(dcSunSpec()) << "Read common block header. Modbus Address" << m_baseRegister+2 << ", Slave ID" << m_slaveId; + qCDebug(dcSunSpec()) << "SunSpec: Read common block header. Modbus Address" << m_baseRegister+2 << ", Slave ID" << m_slaveId; QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_baseRegister+2, 66); @@ -287,27 +293,23 @@ void SunSpec::readCommonMap() if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); - uint modbusAddress = unit.startAddress(); - BlockId mapId = BlockId(unit.value(0)); - int mapLength = unit.value(1); + //uint modbusAddress = unit.startAddress(); + //BlockId mapId = BlockId(unit.value(0)); + //int mapLength = unit.value(1); 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()) << "Received common block response. Manufacturer" << m_manufacturer << "Model" << m_deviceModel << "Serial number" << m_serialNumber; - mapHeaderReceived(modbusAddress, mapId, mapLength); + qCDebug(dcSunSpec()) << "SunSpec: Received common block response. Manufacturer" << m_manufacturer << "Model" << m_deviceModel << "Serial number" << m_serialNumber; + commonMapReceived(m_manufacturer, m_deviceModel, m_serialNumber); } else { - qCWarning(dcSunSpec()) << "Read response error:" << reply->error(); + qCWarning(dcSunSpec()) << "SunSpec: Read common map, read response error:" << reply->error(); } }); - connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { - qCWarning(dcSunSpec()) << "Modbus reply error:" << error; - reply->finished(); // To make sure it will be deleted - }); } else { - qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + qCWarning(dcSunSpec()) << "Sunspec: Read common map read error: " << m_modbusTcpClient->errorString(); delete reply; // broadcast replies return immediately return; } @@ -390,7 +392,7 @@ QByteArray SunSpec::convertModbusRegisters(const QVector &modbusData, i float SunSpec::convertValueWithSSF(quint16 rawValue, quint16 sunssf) { float value; - value = rawValue * (10^static_cast(sunssf)); + value = rawValue * pow(10, static_cast(sunssf)); return value; } @@ -404,26 +406,24 @@ float SunSpec::convertFloatValues(quint16 rawValue0, quint16 rawValue1) void SunSpec::onModbusStateChanged(QModbusDevice::State state) { - bool connected = (state != QModbusDevice::UnconnectedState); + 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()) << "Could not reconnect"; + qCDebug(dcSunSpec()) << "SunSpec: Could not reconnect"; } }); - } else { - readCommonMap(); } emit connectionStateChanged(connected); } -QUuid SunSpec::writeHoldingRegister(uint slaveAddress, uint registerAddress, quint16 value) +QUuid SunSpec::writeHoldingRegister(uint registerAddress, quint16 value) { - return writeHoldingRegisters(slaveAddress, registerAddress, QVector() << value); + return writeHoldingRegisters(registerAddress, QVector() << value); } -QUuid SunSpec::writeHoldingRegisters(uint slaveAddress, uint registerAddress, const QVector &values) +QUuid SunSpec::writeHoldingRegisters(uint registerAddress, const QVector &values) { if (!m_modbusTcpClient) { return ""; @@ -432,7 +432,7 @@ QUuid SunSpec::writeHoldingRegisters(uint slaveAddress, uint registerAddress, co QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, values.length()); request.setValues(values); - if (QModbusReply *reply = m_modbusTcpClient->sendWriteRequest(request, slaveAddress)) { + 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] { diff --git a/sunspec/sunspec.h b/sunspec/sunspec.h index 73d75de..56ebff3 100644 --- a/sunspec/sunspec.h +++ b/sunspec/sunspec.h @@ -178,6 +178,9 @@ public: void setTimeout(uint milliSeconds); void setNumberOfRetries(uint retries); + QHostAddress hostAddress() const; + uint port(); + uint slaveId(); QString manufacturer(); QString deviceModel(); QString serialNumber(); @@ -191,13 +194,14 @@ public: QString m_manufacturer = "Unknown"; QString m_deviceModel = "Unknown"; QString m_serialNumber = "Unknown"; + QHash m_mapList; void findBaseRegister(); - void findModbusMap(const QList &mapIds, uint modbusAddressOffset = 69); + void findModbusMap(const QList &mapIds, uint modbusAddressOffset = 2); + void readCommonMap(); void readMapHeader(uint modbusAddress); void readMap(uint modbusAddress, uint modelLength); //modbusAddress = model start address, model length is without header - void readCommonMap(); float convertValueWithSSF(quint16 rawValue, quint16 sunssf); float convertFloatValues(quint16 rawValue0, quint16 rawValue1); @@ -205,16 +209,18 @@ public: QBitArray convertModbusRegisterBits(const uint16_t &modbusData); QByteArray convertModbusRegisters(const QVector &modbusData, int offset, int size); - QUuid writeHoldingRegister(uint slaveAddress, uint registerAddress, quint16 value); - QUuid writeHoldingRegisters(uint slaveAddress, uint registerAddress, const QVector &values); + QUuid writeHoldingRegister(uint registerAddress, quint16 value); + QUuid writeHoldingRegisters(uint registerAddress, const QVector &values); signals: void connectionStateChanged(bool status); void requestExecuted(QUuid requetId, bool success); void foundBaseRegister(int modbusAddress); + void commonMapReceived(const QString &manufacturer, const QString &deviceModel, const QString &serialNumber); + void foundModbusMap(BlockId mapId, int modbusStartRegister); - void modbusMapSearchFinished(const QList &mapIds, uint modbusStartRegister, const QString &error); + void modbusMapSearchFinished(const QHash &mapIds); void mapHeaderReceived(uint modbusAddress, BlockId mapId, uint mapLength); void mapReceived(BlockId mapId, uint mapLength, QVector data); diff --git a/sunspec/sunspec.pro b/sunspec/sunspec.pro index f005cb0..060a701 100644 --- a/sunspec/sunspec.pro +++ b/sunspec/sunspec.pro @@ -9,15 +9,11 @@ SOURCES += \ sunspec.cpp \ sunspecinverter.cpp \ sunspecmeter.cpp \ - sunspecstorage.cpp \ - sunspectracker.cpp \ - sunspecstringcombiner.cpp \ + sunspecstorage.cpp HEADERS += \ integrationpluginsunspec.h \ sunspec.h \ sunspecinverter.h \ sunspecmeter.h \ - sunspecstorage.h \ - sunspectracker.h \ - sunspecstringcombiner.h \ + sunspecstorage.h diff --git a/sunspec/sunspecinverter.cpp b/sunspec/sunspecinverter.cpp index 295d636..9dc9adb 100644 --- a/sunspec/sunspecinverter.cpp +++ b/sunspec/sunspecinverter.cpp @@ -31,95 +31,121 @@ #include "sunspecinverter.h" #include "extern-plugininfo.h" -SunSpecInverter::SunSpecInverter(const QHostAddress &hostAddress, uint port, QObject *parent) : SunSpec(hostAddress, port, parent) +#include + +SunSpecInverter::SunSpecInverter(SunSpec *sunspec, SunSpec::BlockId mapId, int modbusAddress) : + QObject(sunspec), + m_connection(sunspec), + m_id(mapId), + m_mapModbusStartRegister(modbusAddress) { - connect(m_modbusTcpClient, &QModbusClient::stateChanged, this, [this] (QModbusDevice::State state) { - if (state == QModbusDevice::ConnectedState) { - qCDebug(dcSunSpec()) << "Inverter connected successfully"; - QList mapIds; - mapIds.append(BlockIdInverterSinglePhase); - mapIds.append(BlockIdInverterSplitPhase); - mapIds.append(BlockIdInverterThreePhase); - mapIds.append(BlockIdInverterSinglePhaseFloat); - mapIds.append(BlockIdInverterSplitPhaseFloat); - mapIds.append(BlockIdInverterThreePhaseFloat); - findModbusMap(mapIds); + qCDebug(dcSunSpec()) << "SunSpecInverter: Setting up inverter"; + connect(m_connection, &SunSpec::mapReceived, this, &SunSpecInverter::onModbusMapReceived); +} + +SunSpec::BlockId SunSpecInverter::blockId() +{ + return m_id; +} + +void SunSpecInverter::init() +{ + qCDebug(dcSunSpec()) << "SunSpecInverter: Init"; + m_connection->readMapHeader(m_mapModbusStartRegister); + connect(m_connection, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, SunSpec::BlockId mapId, uint mapLength) { + qCDebug(dcSunSpec()) << "SunSpecInverter: Map Header received, modbus address:" << modbusAddress << "map Id:" << mapId << "map length:" << mapLength; + m_mapLength = mapLength; + emit initFinished(true); + m_initFinishedSuccess = true; + }); + QTimer::singleShot(10000, this,[this] { + if (!m_initFinishedSuccess) { + emit initFinished(false); } }); - connect(this, &SunSpec::foundModbusMap, this, [this] (BlockId mapId, uint modbusRegisterAddress) { - qCDebug(dcSunSpec()) << "Read map header for mapId" << mapId << "and modbus register" << modbusRegisterAddress; - readMapHeader(modbusRegisterAddress); - }); - - connect(this, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, BlockId mapId, uint mapLength) { - m_id = mapId; - m_mapLength = mapLength; - m_mapModbusStartRegister = modbusAddress; - readMap(modbusAddress, mapLength); - }); - - connect(this, &SunSpec::mapReceived, this, &SunSpecInverter::onModbusMapReceived); } void SunSpecInverter::getInverterMap() { - readMap(m_mapModbusStartRegister, m_mapLength); + // TODO check map length to modbus max value + m_connection->readMap(m_mapModbusStartRegister, m_mapLength); } void SunSpecInverter::readInverterBlockHeader() { - readMapHeader(m_mapModbusStartRegister); + m_connection->readMapHeader(m_mapModbusStartRegister); } void SunSpecInverter::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data) { Q_UNUSED(mapLength) + if (mapId != m_id) { + return; + } + if (mapLength < m_mapLength) { + qCDebug(dcSunSpec()) << "SunSpecInverter: on modbus map received, map length ist too short" << mapLength; + //return; + } + InverterData inverterData; + switch (mapId) { - case BlockIdInverterSinglePhase: - case BlockIdInverterSplitPhase: - case BlockIdInverterThreePhase: { - InverterData inverterData; - inverterData.acCurrent= convertValueWithSSF(data[Model10X::Model10XAcCurrent], data[Model10X::Model10XAmpereScaleFactor]); - inverterData.acPower = convertValueWithSSF(data[Model10X::Model10XACPower], data[Model10X::Model10XWattScaleFactor]); - inverterData.lineFrequency = convertValueWithSSF(data[Model10X::Model10XLineFrequency], data[Model10X::Model10XHerzScaleFactor]); + case SunSpec::BlockIdInverterSinglePhase: + case SunSpec::BlockIdInverterSplitPhase: + case SunSpec::BlockIdInverterThreePhase: { - inverterData.phaseACurrent = convertValueWithSSF(data[Model10X::Model10XPhaseACurrent], data[Model10X::Model10XAmpereScaleFactor]); - inverterData.phaseBCurrent = convertValueWithSSF(data[Model10X::Model10XPhaseBCurrent], data[Model10X::Model10XAmpereScaleFactor]); - inverterData.phaseCCurrent = convertValueWithSSF(data[Model10X::Model10XPhaseCCurrent], data[Model10X::Model10XAmpereScaleFactor]); + qCDebug(dcSunSpec()) << "Inverter with SSF values:"; + qCDebug(dcSunSpec()) << " - AC Current:" << data[Model10X::Model10XAcCurrent]; + qCDebug(dcSunSpec()) << " - Phase A Current:" << data[Model10X::Model10XPhaseACurrent]; + qCDebug(dcSunSpec()) << " - Phase B Current:" << data[Model10X::Model10XPhaseBCurrent]; + qCDebug(dcSunSpec()) << " - Phase C Current:" << data[Model10X::Model10XPhaseCCurrent]; + qCDebug(dcSunSpec()) << " - Ampere scale factor:" << static_cast(data[Model10X::Model10XAmpereScaleFactor]); - inverterData.phaseVoltageAN = convertValueWithSSF(data[Model10X::Model10XPhaseVoltageAN], data[Model10X::Model10XVoltageScaleFactor]); - inverterData.phaseVoltageBN = convertValueWithSSF(data[Model10X::Model10XPhaseVoltageBN], data[Model10X::Model10XVoltageScaleFactor]); - inverterData.phaseVoltageCN = convertValueWithSSF(data[Model10X::Model10XPhaseVoltageCN], data[Model10X::Model10XVoltageScaleFactor]); + inverterData.acCurrent= m_connection->convertValueWithSSF(data[Model10X::Model10XAcCurrent], data[Model10X::Model10XAmpereScaleFactor]); + inverterData.acPower = m_connection->convertValueWithSSF(data[Model10X::Model10XACPower], data[Model10X::Model10XWattScaleFactor]); + inverterData.lineFrequency = m_connection->convertValueWithSSF(data[Model10X::Model10XLineFrequency], data[Model10X::Model10XHerzScaleFactor]); - inverterData.acEnergy = convertValueWithSSF(data[Model10X::Model10XAcEnergy], data[Model10X::Model10XWattHoursScaleFactor]); + inverterData.phaseACurrent = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseACurrent], data[Model10X::Model10XAmpereScaleFactor]); + inverterData.phaseBCurrent = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseBCurrent], data[Model10X::Model10XAmpereScaleFactor]); + inverterData.phaseCCurrent = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseCCurrent], data[Model10X::Model10XAmpereScaleFactor]); - inverterData.cabinetTemperature = convertValueWithSSF(data[Model10X::Model10XCabinetTemperature], data[Model10X::Model10XTemperatureScaleFactor]); - inverterData.event = SunSpecEvent1(data[Model10X::Model10XEvent1]); - inverterData.operatingState = SunSpecOperatingState(data[Model10X::Model10XOperatingState]); + inverterData.phaseVoltageAN = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageAN], data[Model10X::Model10XVoltageScaleFactor]); + inverterData.phaseVoltageBN = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageBN], data[Model10X::Model10XVoltageScaleFactor]); + inverterData.phaseVoltageCN = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageCN], data[Model10X::Model10XVoltageScaleFactor]); + + qCDebug(dcSunSpec()) << "AC energy converting:"; + qCDebug(dcSunSpec()) << " - Origin" << data[Model10X::Model10XAcEnergy]; + qCDebug(dcSunSpec()) << " - SSF" << static_cast(data[Model10X::Model10XWattHoursScaleFactor]); + qCDebug(dcSunSpec()) << " - Converted "<< m_connection->convertValueWithSSF(data[Model10X::Model10XAcEnergy], data[Model10X::Model10XWattHoursScaleFactor]); + inverterData.acEnergy = m_connection->convertValueWithSSF(data[Model10X::Model10XAcEnergy], data[Model10X::Model10XWattHoursScaleFactor]); + + inverterData.cabinetTemperature = m_connection->convertValueWithSSF(data[Model10X::Model10XCabinetTemperature], data[Model10X::Model10XTemperatureScaleFactor]); + inverterData.event = SunSpec::SunSpecEvent1(data[Model10X::Model10XEvent1]); + inverterData.operatingState = SunSpec::SunSpecOperatingState(data[Model10X::Model10XOperatingState]); emit inverterDataReceived(inverterData); } break; - case BlockIdInverterSinglePhaseFloat: - case BlockIdInverterSplitPhaseFloat: - case BlockIdInverterThreePhaseFloat: { - InverterData inverterData; - inverterData.acCurrent = convertFloatValues(data[Model11X::Model11XAcCurrent], data[Model11X::Model11XAcCurrent+1]); + case SunSpec::BlockIdInverterThreePhaseFloat: + case SunSpec::BlockIdInverterSplitPhaseFloat: + case SunSpec::BlockIdInverterSinglePhaseFloat: { - inverterData.phaseACurrent = convertFloatValues(data[Model11X::Model11XPhaseACurrent], data[Model11X::Model11XPhaseACurrent+1]); - inverterData.phaseBCurrent = convertFloatValues(data[Model11X::Model11XPhaseBCurrent], data[Model11X::Model11XPhaseBCurrent+1]); - inverterData.phaseCCurrent = convertFloatValues(data[Model11X::Model11XPhaseCCurrent], data[Model11X::Model11XPhaseCCurrent+1]); + inverterData.acCurrent = m_connection->convertFloatValues(data[Model11X::Model11XAcCurrent], data[Model11X::Model11XAcCurrent+1]); - inverterData.phaseVoltageAN = convertFloatValues(data[Model11X::Model11XPhaseVoltageAN], data[Model11X::Model11XPhaseVoltageAN+1]); - inverterData.phaseVoltageBN = convertFloatValues(data[Model11X::Model11XPhaseVoltageBN], data[Model11X::Model11XPhaseVoltageBN+1]); - inverterData.phaseVoltageCN = convertFloatValues(data[Model11X::Model11XPhaseVoltageCN], data[Model11X::Model11XPhaseVoltageCN+1]); + inverterData.phaseCCurrent = m_connection->convertFloatValues(data[Model11X::Model11XPhaseCCurrent], data[Model11X::Model11XPhaseCCurrent+1]); + inverterData.phaseVoltageCN = m_connection->convertFloatValues(data[Model11X::Model11XPhaseVoltageCN], data[Model11X::Model11XPhaseVoltageCN+1]); - inverterData.acPower = convertFloatValues(data[Model11X::Model11XACPower], data[Model11X::Model11XACPower+1]); - inverterData.lineFrequency = convertFloatValues(data[Model11X::Model11XLineFrequency], data[Model11X::Model11XLineFrequency+1]); + inverterData.phaseBCurrent = m_connection->convertFloatValues(data[Model11X::Model11XPhaseBCurrent], data[Model11X::Model11XPhaseBCurrent+1]); + inverterData.phaseVoltageBN = m_connection->convertFloatValues(data[Model11X::Model11XPhaseVoltageBN], data[Model11X::Model11XPhaseVoltageBN+1]); - inverterData.acEnergy = convertFloatValues(data[Model11X::Model11XAcEnergy], data[Model11X::Model11XAcEnergy+1]); - inverterData.cabinetTemperature = convertFloatValues(data[Model11X::Model11XCabinetTemperature], data[Model11X::Model11XCabinetTemperature+1]); - inverterData.event = SunSpecEvent1(data[Model11X::Model11XEvent1]); - inverterData.operatingState = SunSpecOperatingState(data[Model11X::Model11XOperatingState]); + inverterData.phaseACurrent = m_connection->convertFloatValues(data[Model11X::Model11XPhaseACurrent], data[Model11X::Model11XPhaseACurrent+1]); + inverterData.phaseVoltageAN = m_connection->convertFloatValues(data[Model11X::Model11XPhaseVoltageAN], data[Model11X::Model11XPhaseVoltageAN+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.event = SunSpec::SunSpecEvent1(data[Model11X::Model11XEvent1]); + inverterData.operatingState = SunSpec::SunSpecOperatingState(data[Model11X::Model11XOperatingState]); emit inverterDataReceived(inverterData); } break; default: diff --git a/sunspec/sunspecinverter.h b/sunspec/sunspecinverter.h index f68c68b..2c6ca04 100644 --- a/sunspec/sunspecinverter.h +++ b/sunspec/sunspecinverter.h @@ -34,32 +34,31 @@ #include #include "sunspec.h" -class SunSpecInverter : public SunSpec +class SunSpecInverter : public QObject { Q_OBJECT public: - SunSpecInverter(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); enum Model10X { // Mandatory register - Model10XAcCurrent = 2, - Model10XPhaseACurrent = 3, - Model10XPhaseBCurrent = 4, - Model10XPhaseCCurrent = 5, - Model10XAmpereScaleFactor = 6, - Model10XPhaseVoltageAN = 10, - Model10XPhaseVoltageBN = 11, - Model10XPhaseVoltageCN = 12, - Model10XVoltageScaleFactor = 13, - Model10XACPower = 14, - Model10XWattScaleFactor = 15, - Model10XLineFrequency = 16, - Model10XHerzScaleFactor = 17, - Model10XAcEnergy = 24, - Model10XWattHoursScaleFactor = 25, - Model10XCabinetTemperature = 33, - Model10XTemperatureScaleFactor = 37, - Model10XOperatingState = 38, - Model10XEvent1 = 40 + 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 @@ -93,24 +92,29 @@ public: float lineFrequency; float acEnergy; float cabinetTemperature; // in degree Celsius - SunSpecEvent1 event; - SunSpecOperatingState operatingState; + SunSpec::SunSpecEvent1 event; + SunSpec::SunSpecOperatingState operatingState; }; + SunSpecInverter(SunSpec *sunspec, SunSpec::BlockId blockId, int modbusAddress); + SunSpec::BlockId blockId(); + void init(); void getInverterMap(); private: - BlockId m_id = BlockIdInverterThreePhase; //e.g. 103 for three phase inverter, 113 for three phase inverter with floating point representation + SunSpec *m_connection = nullptr; + SunSpec::BlockId m_id; //e.g. 103 for three phase inverter, 113 for three phase inverter with floating point representation uint m_mapLength = 0; uint m_mapModbusStartRegister = 40000; + bool m_initFinishedSuccess = false; void readInverterBlockHeader(); private slots: - void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); + void onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data); signals: - void initFinished(); + void initFinished(bool success); void inverterDataReceived(InverterData data); }; diff --git a/sunspec/sunspecmeter.cpp b/sunspec/sunspecmeter.cpp index cfd3b88..d7d6481 100644 --- a/sunspec/sunspecmeter.cpp +++ b/sunspec/sunspecmeter.cpp @@ -31,31 +31,54 @@ #include "sunspecmeter.h" #include "extern-plugininfo.h" -SunSpecMeter::SunSpecMeter(const QHostAddress &hostAddress, uint port, QObject *parent) : SunSpec(hostAddress, port, parent) +SunSpecMeter::SunSpecMeter(SunSpec *sunspec, SunSpec::BlockId blockId, int modbusAddress) : + QObject(sunspec), + m_connection(sunspec), + m_id(blockId), + m_mapModbusStartRegister(modbusAddress) { - connect(m_modbusTcpClient, &QModbusClient::stateChanged, this, [this] (QModbusDevice::State state) { - if (state == QModbusDevice::ConnectedState) { - qCDebug(dcSunSpec()) << "Meter connected successfully"; - QList mapIds; - mapIds.append(BlockIdSinglePhaseMeter); - mapIds.append(BlockIdSplitSinglePhaseMeter); - mapIds.append(BlockIdWyeConnectThreePhaseMeter); - mapIds.append(BlockIdDeltaConnectThreePhaseMeter); - mapIds.append(BlockIdSinglePhaseMeterFloat); - mapIds.append(BlockIdSplitSinglePhaseMeterFloat); - mapIds.append(BlockIdWyeConnectThreePhaseMeterFloat); - mapIds.append(BlockIdDeltaConnectThreePhaseMeterFloat); - findModbusMap(mapIds); - } + qCDebug(dcSunSpec()) << "SunSpecMeter: Setting up meter"; + connect(m_connection, &SunSpec::mapReceived, this, &SunSpecMeter::onModbusMapReceived); +} + +SunSpec::BlockId SunSpecMeter::blockId() +{ + return m_id; +} + +void SunSpecMeter::init() +{ + qCDebug(dcSunSpec()) << "SunSpecInverter: Init"; + m_connection->readMapHeader(m_mapModbusStartRegister); + connect(m_connection, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, SunSpec::BlockId mapId, uint mapLength) { + qCDebug(dcSunSpec()) << "SunSpecInverter: Map Header received, modbus address:" << modbusAddress << "map Id:" << mapId << "map length:" << mapLength; + m_mapLength = mapLength; + emit initFinished(true); + m_initFinishedSuccess = true; + }); + QTimer::singleShot(10000, this,[this] { + if (!m_initFinishedSuccess) { + emit initFinished(false); + } }); } -void SunSpecMeter::geMeterMap() +void SunSpecMeter::getMeterMap() { - readMap(m_mapModbusStartRegister, m_mapLength); + m_connection->readMap(m_mapModbusStartRegister, m_mapLength); } void SunSpecMeter::readMeterBlockHeader() { } + +void SunSpecMeter::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data) +{ + Q_UNUSED(mapLength) + Q_UNUSED(data) + switch (mapId) { + default: + break; + } +} diff --git a/sunspec/sunspecmeter.h b/sunspec/sunspecmeter.h index e3154f4..f7f81b8 100644 --- a/sunspec/sunspecmeter.h +++ b/sunspec/sunspecmeter.h @@ -34,7 +34,7 @@ #include #include "sunspec.h" -class SunSpecMeter : public SunSpec +class SunSpecMeter : public QObject { Q_OBJECT public: @@ -64,26 +64,29 @@ public: }; struct MeterData { - SunSpecEvent1 event; - SunSpecOperatingState operatingState; + SunSpec::SunSpecEvent1 event; + SunSpec::SunSpecOperatingState operatingState; }; - SunSpecMeter(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); - - void geMeterMap(); + SunSpecMeter(SunSpec *sunspec, SunSpec::BlockId blockId, int modbusAddress); + SunSpec::BlockId blockId(); + void init(); + void getMeterMap(); private: - BlockId m_id = BlockIdDeltaConnectThreePhaseMeter; + SunSpec *m_connection = nullptr; + SunSpec::BlockId m_id = SunSpec::BlockIdDeltaConnectThreePhaseMeter; uint m_mapLength = 0; uint m_mapModbusStartRegister = 40000; + bool m_initFinishedSuccess = false; void readMeterBlockHeader(); private slots: - // void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); + void onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data); signals: - void initFinished(); + void initFinished(bool success); void meterDataReceived(const MeterData &data); }; diff --git a/sunspec/sunspecstorage.cpp b/sunspec/sunspecstorage.cpp index 8ff722d..426f63e 100644 --- a/sunspec/sunspecstorage.cpp +++ b/sunspec/sunspecstorage.cpp @@ -31,39 +31,47 @@ #include "sunspecstorage.h" #include "extern-plugininfo.h" -SunSpecStorage::SunSpecStorage(const QHostAddress &hostAddress, uint port, QObject *parent) : SunSpec(hostAddress, port, parent) +SunSpecStorage::SunSpecStorage(SunSpec *sunspec, SunSpec::BlockId blockId, int modbusAddress) : + QObject(sunspec), + m_connection(sunspec), + m_id(blockId), + m_mapModbusStartRegister(modbusAddress) { - connect(m_modbusTcpClient, &QModbusClient::stateChanged, this, [this] (QModbusDevice::State state) { - if (state == QModbusDevice::ConnectedState) { - qCDebug(dcSunSpec()) << "Inverter connected successfully"; - QList mapIds; - mapIds.append(BlockIdStorage); - findModbusMap(mapIds); - } - }); - connect(this, &SunSpec::foundModbusMap, this, [this] (BlockId mapId, uint modbusRegisterAddress) { - qCDebug(dcSunSpec()) << "Read map header for mapId" << mapId << "and modbus register" << modbusRegisterAddress; - readMapHeader(modbusRegisterAddress); - }); + qCDebug(dcSunSpec()) << "SunSpecStorage: Setting up storage"; + connect(m_connection, &SunSpec::mapReceived, this, &SunSpecStorage::onModbusMapReceived); +} - connect(this, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, BlockId mapId, uint mapLength) { - m_id = mapId; +SunSpec::BlockId SunSpecStorage::blockId() +{ + return m_id; + +} + +void SunSpecStorage::init() +{ + qCDebug(dcSunSpec()) << "SunSpecInverter: Init"; + m_connection->readMapHeader(m_mapModbusStartRegister); + connect(m_connection, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, SunSpec::BlockId mapId, uint mapLength) { + qCDebug(dcSunSpec()) << "SunSpecInverter: Map Header received, modbus address:" << modbusAddress << "map Id:" << mapId << "map length:" << mapLength; m_mapLength = mapLength; - m_mapModbusStartRegister = modbusAddress; - readMap(modbusAddress, mapLength); + emit initFinished(true); + m_initFinishedSuccess = true; + }); + QTimer::singleShot(10000, this,[this] { + if (!m_initFinishedSuccess) { + emit initFinished(false); + } }); - - connect(this, &SunSpec::mapReceived, this, &SunSpecStorage::onModbusMapReceived); } void SunSpecStorage::getStorageMap() { - readMap(m_mapModbusStartRegister, m_mapLength); + m_connection->readMap(m_mapModbusStartRegister, m_mapLength); } void SunSpecStorage::readStorageBlockHeader() { - readMapHeader(m_mapModbusStartRegister); + m_connection->readMapHeader(m_mapModbusStartRegister); } QUuid SunSpecStorage::setGridCharging(bool enabled) @@ -76,7 +84,7 @@ QUuid SunSpecStorage::setGridCharging(bool enabled) uint registerAddress = m_mapModbusStartRegister + Model124::Model124ChaGriSet; quint16 value = enabled; - return writeHoldingRegister(m_slaveId, registerAddress, value); + return m_connection->writeHoldingRegister(registerAddress, value); } QUuid SunSpecStorage::setStorageControlMode(bool chargingEnabled, bool dischargingEnabled) @@ -86,7 +94,7 @@ QUuid SunSpecStorage::setStorageControlMode(bool chargingEnabled, bool dischargi (static_cast(dischargingEnabled) << StorageControlBitFieldDischarge)) ; uint modbusRegister = m_mapModbusStartRegister + Model124::Model124ActivateStorageControlMode; - return writeHoldingRegister(m_slaveId, modbusRegister, value); + return m_connection->writeHoldingRegister(modbusRegister, value); } QUuid SunSpecStorage::setChargingRate(int rate) @@ -96,7 +104,7 @@ QUuid SunSpecStorage::setChargingRate(int rate) uint modbusRegister = m_mapModbusStartRegister + Model124::Model124SetpointMaximumChargingRate; int16_t value = rate * 100; - return writeHoldingRegister(m_slaveId, modbusRegister, value); + return m_connection->writeHoldingRegister(modbusRegister, value); } QUuid SunSpecStorage::setDischargingRate(int charging) @@ -105,14 +113,14 @@ QUuid SunSpecStorage::setDischargingRate(int charging) /* Defines the maximum discharge rate (discharge limit). Default is 100% */ uint modbusRegister = m_mapModbusStartRegister + Model124::Model124SetpointMaximumDischargeRate; quint16 value = charging * 100; - return writeHoldingRegister(m_slaveId, modbusRegister, value); + return m_connection->writeHoldingRegister(modbusRegister, value); } void SunSpecStorage::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, const QVector &data) { Q_UNUSED(mapLength) switch (mapId) { - case BlockIdStorage: { + case SunSpec::BlockIdStorage: { StorageData storageData; storageData.chargingState = ChargingState(data[Model124::Model124ChargeStatus]); emit storageDataReceived(storageData); diff --git a/sunspec/sunspecstorage.h b/sunspec/sunspecstorage.h index 29ceea2..3d2cd3e 100644 --- a/sunspec/sunspecstorage.h +++ b/sunspec/sunspecstorage.h @@ -34,18 +34,21 @@ #include #include "sunspec.h" -class SunSpecStorage : public SunSpec +class SunSpecStorage : public QObject { Q_OBJECT public: - SunSpecStorage(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); + SunSpecStorage(SunSpec *sunspec, SunSpec::BlockId blockId, int modbusAddress); + + SunSpec::BlockId blockId(); + void init(); + void getStorageMap(); QUuid setGridCharging(bool enabled); QUuid setDischargingRate(int rate); QUuid setChargingRate(int rate); QUuid setStorageControlMode(bool chargingEnabled, bool dischargingEnabled); - enum StorageControlBitField { StorageControlBitFieldCharge = 0, StorageControlBitFieldDischarge = 1 @@ -88,20 +91,20 @@ public: bool gridChargingEnabled; }; - void getStorageMap(); - private: - BlockId m_id = BlockIdEnergyStorageBaseModel; + SunSpec *m_connection = nullptr; + SunSpec::BlockId m_id = SunSpec::BlockIdEnergyStorageBaseModel; uint m_mapLength = 0; uint m_mapModbusStartRegister = 40000; + bool m_initFinishedSuccess = false; void readStorageBlockHeader(); private slots: - void onModbusMapReceived(BlockId mapId, uint mapLength, const QVector &data); + void onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, const QVector &data); signals: - void initFinished(); + void initFinished(bool success); void storageDataReceived(const StorageData &data); }; From 4a9064c6479bd13e39d9d1fa3eab4578c85b00a5 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Thu, 21 Jan 2021 17:35:09 +0100 Subject: [PATCH 07/16] fixed ssf inverter energy value --- sunspec/integrationpluginsunspec.cpp | 82 ++++- sunspec/integrationpluginsunspec.h | 6 + sunspec/integrationpluginsunspec.json | 448 +++++++++++++++++++++++--- sunspec/sunspec.cpp | 2 +- sunspec/sunspec.h | 2 +- sunspec/sunspecinverter.cpp | 21 +- 6 files changed, 490 insertions(+), 71 deletions(-) diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index 9481dbc..2da3264 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -64,6 +64,30 @@ void IntegrationPluginSunSpec::init() m_modbusAddressParamTypeIds.insert(sunspecSinglePhaseMeterThingClassId, sunspecSinglePhaseMeterThingModbusAddressParamTypeId); m_modbusAddressParamTypeIds.insert(sunspecSplitPhaseMeterThingClassId, sunspecSplitPhaseMeterThingModbusAddressParamTypeId); m_modbusAddressParamTypeIds.insert(sunspecThreePhaseMeterThingClassId, sunspecThreePhaseMeterThingModbusAddressParamTypeId); + + m_acPowerStateTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterAcPowerStateTypeId); + m_acPowerStateTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterAcPowerStateTypeId); + m_acPowerStateTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterAcPowerStateTypeId); + + m_acEnergyStateTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterAcEnergyStateTypeId); + m_acEnergyStateTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterAcEnergyStateTypeId); + m_acEnergyStateTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterAcEnergyStateTypeId); + + m_inverterOperatingStateTypeIds.insert(sunspecSinglePhaseMeterThingClassId, sunspecSinglePhaseInverterOperatingStateStateTypeId); + m_inverterOperatingStateTypeIds.insert(sunspecSplitPhaseMeterThingClassId, sunspecSplitPhaseInverterOperatingStateStateTypeId); + m_inverterOperatingStateTypeIds.insert(sunspecThreePhaseMeterThingClassId, sunspecThreePhaseInverterOperatingStateStateTypeId); + + m_inverterErrorStateTypeIds.insert(sunspecSinglePhaseMeterThingClassId, sunspecSinglePhaseInverterErrorStateTypeId); + m_inverterErrorStateTypeIds.insert(sunspecSplitPhaseMeterThingClassId, sunspecSplitPhaseInverterErrorStateTypeId); + m_inverterErrorStateTypeIds.insert(sunspecThreePhaseMeterThingClassId, sunspecThreePhaseInverterErrorStateTypeId); + + 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) @@ -436,11 +460,10 @@ void IntegrationPluginSunSpec::onFoundModbusMap(SunSpec::BlockId mapId, int modb if (checkIfThingExists(mapId, modbusStartRegister)) { return; } - + QString model = thing->stateValue(sunspecConnectionDeviceModelStateTypeId).toString(); switch (mapId) { case SunSpec::BlockId::BlockIdInverterSinglePhase: case SunSpec::BlockId::BlockIdInverterSinglePhaseFloat: { - QString model = thing->stateValue(sunspecConnectionDeviceModelStateTypeId).toString(); ThingDescriptor descriptor(sunspecSinglePhaseInverterThingClassId, model+tr(" single phase inverter"), "", thing->id()); ParamList params; params.append(Param(sunspecSinglePhaseInverterThingMapIdParamTypeId, mapId)); @@ -450,7 +473,6 @@ void IntegrationPluginSunSpec::onFoundModbusMap(SunSpec::BlockId mapId, int modb } break; case SunSpec::BlockId::BlockIdInverterSplitPhase: case SunSpec::BlockId::BlockIdInverterSplitPhaseFloat: { - QString model = thing->stateValue(sunspecConnectionDeviceModelStateTypeId).toString(); ThingDescriptor descriptor(sunspecSplitPhaseInverterThingClassId, model+tr(" split phase inverter"), "", thing->id()); ParamList params; params.append(Param(sunspecSplitPhaseInverterThingMapIdParamTypeId, mapId)); @@ -460,7 +482,6 @@ void IntegrationPluginSunSpec::onFoundModbusMap(SunSpec::BlockId mapId, int modb } break; case SunSpec::BlockId::BlockIdInverterThreePhase: case SunSpec::BlockId::BlockIdInverterThreePhaseFloat: { - QString model = thing->stateValue(sunspecConnectionDeviceModelStateTypeId).toString(); ThingDescriptor descriptor(sunspecThreePhaseInverterThingClassId, model+tr(" three phase inverter"), "", thing->id()); ParamList params; params.append(Param(sunspecThreePhaseInverterThingMapIdParamTypeId, mapId)); @@ -471,16 +492,33 @@ void IntegrationPluginSunSpec::onFoundModbusMap(SunSpec::BlockId mapId, int modb case SunSpec::BlockIdSinglePhaseMeter: case SunSpec::BlockIdSinglePhaseMeterFloat: { + ThingDescriptor descriptor(sunspecSinglePhaseMeterThingClassId, model+tr(" Meter"), "", thing->id()); + ParamList params; + params.append(Param(sunspecSinglePhaseMeterThingMapIdParamTypeId, mapId)); + params.append(Param(sunspecSinglePhaseMeterThingModbusAddressParamTypeId, modbusStartRegister)); + descriptor.setParams(params); + emit autoThingsAppeared({descriptor}); } break; case SunSpec::BlockIdSplitSinglePhaseMeter: case SunSpec::BlockIdSplitSinglePhaseMeterFloat: { + ThingDescriptor descriptor(sunspecSplitPhaseMeterThingClassId, model+tr(" Meter"), "", thing->id()); + ParamList params; + params.append(Param(sunspecSplitPhaseMeterThingMapIdParamTypeId, mapId)); + params.append(Param(sunspecSplitPhaseMeterThingModbusAddressParamTypeId, modbusStartRegister)); + descriptor.setParams(params); + emit autoThingsAppeared({descriptor}); } break; case SunSpec::BlockIdWyeConnectThreePhaseMeterFloat: case SunSpec::BlockIdDeltaConnectThreePhaseMeterFloat: { + ThingDescriptor descriptor(sunspecThreePhaseMeterThingClassId, model+" Meter", "", thing->id()); + ParamList params; + params.append(Param(sunspecThreePhaseMeterThingMapIdParamTypeId, mapId)); + params.append(Param(sunspecThreePhaseMeterThingModbusAddressParamTypeId, modbusStartRegister)); + descriptor.setParams(params); + emit autoThingsAppeared({descriptor}); } break; case SunSpec::BlockIdStorage: { - QString model = thing->stateValue(sunspecConnectionDeviceModelStateTypeId).toString(); - ThingDescriptor descriptor(sunspecStorageThingClassId, model+" Storage", "Storage", thing->id()); + ThingDescriptor descriptor(sunspecStorageThingClassId, model+" Storage", "", thing->id()); ParamList params; params.append(Param(sunspecStorageThingMapIdParamTypeId, mapId)); params.append(Param(sunspecThreePhaseInverterThingModbusAddressParamTypeId, modbusStartRegister)); @@ -526,19 +564,33 @@ void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::Inv return; } thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true); - thing->setStateValue(sunspecThreePhaseInverterAcPowerStateTypeId, inverterData.acPower/1000.00); - thing->setStateValue(sunspecThreePhaseInverterAcEnergyStateTypeId, inverterData.acEnergy/1000.00); - thing->setStateValue(sunspecThreePhaseInverterLineFrequencyStateTypeId, inverterData.lineFrequency); + thing->setStateValue(m_acPowerStateTypeIds.value(thing->thingClassId()), inverterData.acPower/1000.00); + thing->setStateValue(m_acEnergyStateTypeIds.value(thing->thingClassId()), inverterData.acEnergy/1000.00); + thing->setStateValue(m_frequencyStateTypeIds.value(thing->thingClassId()), inverterData.lineFrequency); + thing->setStateValue(sunspecThreePhaseInverterTotalCurrentStateTypeId, inverterData.acCurrent); thing->setStateValue(sunspecThreePhaseInverterCabinetTemperatureStateTypeId, inverterData.cabinetTemperature); - thing->setStateValue(sunspecThreePhaseInverterPhaseACurrentStateTypeId, inverterData.phaseACurrent); - thing->setStateValue(sunspecThreePhaseInverterPhaseBCurrentStateTypeId, inverterData.phaseBCurrent); - thing->setStateValue(sunspecThreePhaseInverterPhaseCCurrentStateTypeId, inverterData.phaseCCurrent); - thing->setStateValue(sunspecThreePhaseInverterPhaseANVoltageStateTypeId, inverterData.phaseVoltageAN); - thing->setStateValue(sunspecThreePhaseInverterPhaseBNVoltageStateTypeId, inverterData.phaseVoltageBN); - thing->setStateValue(sunspecThreePhaseInverterPhaseCNVoltageStateTypeId, inverterData.phaseVoltageCN); + if (thing->thingClassId() == sunspecSplitPhaseMeterThingClassId) { + + thing->setStateValue(sunspecSplitPhaseInverterPhaseANVoltageStateTypeId, inverterData.phaseVoltageAN); + thing->setStateValue(sunspecSplitPhaseInverterPhaseBNVoltageStateTypeId, inverterData.phaseVoltageBN); + + thing->setStateValue(sunspecSplitPhaseInverterPhaseACurrentStateTypeId, inverterData.phaseACurrent); + thing->setStateValue(sunspecSplitPhaseInverterPhaseBCurrentStateTypeId, inverterData.phaseBCurrent); + + } else if (thing->thingClassId() == sunspecThreePhaseMeterThingClassId) { + + 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: diff --git a/sunspec/integrationpluginsunspec.h b/sunspec/integrationpluginsunspec.h index a5d328a..41ff90b 100644 --- a/sunspec/integrationpluginsunspec.h +++ b/sunspec/integrationpluginsunspec.h @@ -63,6 +63,12 @@ private: QHash m_modbusAddressParamTypeIds; QHash m_connectedStateTypeIds; + QHash m_frequencyStateTypeIds; + QHash m_acPowerStateTypeIds; + QHash m_acEnergyStateTypeIds; + QHash m_inverterOperatingStateTypeIds; + QHash m_inverterErrorStateTypeIds; + PluginTimer *m_refreshTimer = nullptr; QHash m_asyncActions; QHash m_sunSpecConnections; diff --git a/sunspec/integrationpluginsunspec.json b/sunspec/integrationpluginsunspec.json index a0c1a72..d719318 100644 --- a/sunspec/integrationpluginsunspec.json +++ b/sunspec/integrationpluginsunspec.json @@ -132,6 +132,105 @@ "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": "acPower", + "displayName": "AC power", + "displayNameEvent": "AC power changed", + "type": "double", + "unit": "KiloWatt", + "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": "acEnergy", + "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" } ] }, @@ -167,6 +266,132 @@ "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": "acPower", + "displayName": "AC power", + "displayNameEvent": "AC power changed", + "type": "double", + "unit": "KiloWatt", + "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": "acEnergy", + "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" } ] }, @@ -203,16 +428,6 @@ "defaultValue": false, "cached": false }, - { - "id": "9bdcc785-4738-437e-88a0-fc231095c2dd", - "name": "power", - "displayName": "Power", - "displayNameEvent": "Power changed", - "displayNameAction": "Change power", - "type": "bool", - "defaultValue": 0, - "writable": true - }, { "id": "26560dd8-6de4-445e-ba55-391d7241c370", "name": "totalCurrent", @@ -287,9 +502,9 @@ }, { "id": "faa45cae-ed28-4150-9036-fceddf9d6776", - "name": "lineFrequency", - "displayName": "Line frequency", - "displayNameEvent": "Line frequency changed", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", "type": "double", "unit": "Hertz", "defaultValue": 0.00 @@ -392,6 +607,69 @@ "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": "totalRealPower", + "displayName": "Total real power", + "displayNameEvent": "Total real power changed", + "type": "double", + "unit": "KiloWatt", + "defaultValue": 0.00 + }, + { + "id": "ba275bdf-f418-4ef0-afbe-ac425c6f6783", + "name": "energyExported", + "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": "energyImported", + "displayName": "Total real energy imported", + "displayNameEvent": "Total real energy imported changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0.00 } ] }, @@ -428,6 +706,96 @@ "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": "totalRealPower", + "displayName": "Total real power", + "displayNameEvent": "Total real power changed", + "type": "double", + "unit": "KiloWatt", + "defaultValue": 0.00 + }, + { + "id": "8a63bd73-0546-4636-8da2-23238cc06fb2", + "name": "energyExported", + "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": "energyImported", + "displayName": "Total real energy imported", + "displayNameEvent": "Total real energy imported changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0.00 } ] }, @@ -470,108 +838,108 @@ "name": "totalCurrent", "displayName": "Total AC current", "displayNameEvent": "Total AC current changed", - "type": "int", + "type": "double", "unit": "Ampere", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "da494d99-5de3-4d03-b7dd-33a33db32164", "name": "phaseACurrent", "displayName": "Phase A current", "displayNameEvent": "Phase A current changed", - "type": "int", + "type": "double", "unit": "Ampere", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "023b6e5c-be3f-4d8c-adfd-e009c7bebffb", "name": "phaseBCurrent", "displayName": "Phase B current", "displayNameEvent": "Phase B current changed", - "type": "int", + "type": "double", "unit": "Ampere", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "2676ad36-3d97-4691-8334-e13934cc58d1", "name": "phaseCCurrent", "displayName": "Phase C current", "displayNameEvent": "Phase C current changed", - "type": "int", + "type": "double", "unit": "Ampere", - "defaultValue": 0 + "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": "int", + "type": "double", "unit": "Volt", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "4bd32d91-877a-4821-9db9-5981de20d21d", "name": "phaseANVoltage", "displayName": "Phase AN voltage", "displayNameEvent": "Phase AN volatage changed", - "type": "int", + "type": "double", "unit": "Volt", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "f090cb78-d7ed-44fd-a5ad-ea9016021c34", "name": "phaseBNVoltage", "displayName": "Phase BN voltage", "displayNameEvent": "Phase BN voltage changed", - "type": "int", + "type": "double", "unit": "Volt", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "fa5aa6f5-e67d-485a-835f-24e49298856c", "name": "phaseCNVoltage", "displayName": "Phase CN voltage", "displayNameEvent": "Phase CN voltage changed", - "type": "int", + "type": "double", "unit": "Volt", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "dfcf52f5-6279-4d25-b7c8-a93b92c39a0c", "name": "frequency", "displayName": "Frequency", "displayNameEvent": "Frequency changed", - "type": "int", + "type": "double", "unit": "Hertz", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "c28c642f-46da-44de-ba0d-c4cbfadbf2cd", "name": "totalRealPower", "displayName": "Total real power", "displayNameEvent": "Total real power changed", - "type": "int", + "type": "double", "unit": "KiloWatt", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "73ebf57f-1ad2-4d19-bfd9-9e0a514c1243", "name": "energyExported", "displayName": "Total real energy exported", "displayNameEvent": "Total real energy exported changed", - "type": "int", + "type": "double", "unit": "KiloWattHour", - "defaultValue": 0 + "defaultValue": 0.00 }, { "id": "63fa4721-1b0a-458c-b66c-17c161107f0d", "name": "energyImported", "displayName": "Total real energy imported", "displayNameEvent": "Total real energy imported changed", - "type": "int", + "type": "double", "unit": "KiloWattHour", - "defaultValue": 0 + "defaultValue": 0.00 } ] }, @@ -622,7 +990,7 @@ "name": "batteryLevel", "displayName": "Battery level", "displayNameEvent": "Battery level changed", - "type": "int", + "type": "double", "unit": "Percentage", "minValue": 0, "maxValue": 100, @@ -707,7 +1075,3 @@ } ] } - - - - diff --git a/sunspec/sunspec.cpp b/sunspec/sunspec.cpp index 40b5950..2ebf713 100644 --- a/sunspec/sunspec.cpp +++ b/sunspec/sunspec.cpp @@ -389,7 +389,7 @@ QByteArray SunSpec::convertModbusRegisters(const QVector &modbusData, i return bytes; } -float SunSpec::convertValueWithSSF(quint16 rawValue, quint16 sunssf) +float SunSpec::convertValueWithSSF(quint32 rawValue, quint16 sunssf) { float value; value = rawValue * pow(10, static_cast(sunssf)); diff --git a/sunspec/sunspec.h b/sunspec/sunspec.h index 56ebff3..bb5ec18 100644 --- a/sunspec/sunspec.h +++ b/sunspec/sunspec.h @@ -203,7 +203,7 @@ public: void readMapHeader(uint modbusAddress); void readMap(uint modbusAddress, uint modelLength); //modbusAddress = model start address, model length is without header - float convertValueWithSSF(quint16 rawValue, quint16 sunssf); + float convertValueWithSSF(quint32 rawValue, quint16 sunssf); float convertFloatValues(quint16 rawValue0, quint16 rawValue1); QByteArray convertModbusRegister(const uint16_t &modbusData); QBitArray convertModbusRegisterBits(const uint16_t &modbusData); diff --git a/sunspec/sunspecinverter.cpp b/sunspec/sunspecinverter.cpp index 9dc9adb..5b7cb9d 100644 --- a/sunspec/sunspecinverter.cpp +++ b/sunspec/sunspecinverter.cpp @@ -93,13 +93,6 @@ void SunSpecInverter::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength case SunSpec::BlockIdInverterSplitPhase: case SunSpec::BlockIdInverterThreePhase: { - qCDebug(dcSunSpec()) << "Inverter with SSF values:"; - qCDebug(dcSunSpec()) << " - AC Current:" << data[Model10X::Model10XAcCurrent]; - qCDebug(dcSunSpec()) << " - Phase A Current:" << data[Model10X::Model10XPhaseACurrent]; - qCDebug(dcSunSpec()) << " - Phase B Current:" << data[Model10X::Model10XPhaseBCurrent]; - qCDebug(dcSunSpec()) << " - Phase C Current:" << data[Model10X::Model10XPhaseCCurrent]; - qCDebug(dcSunSpec()) << " - Ampere scale factor:" << static_cast(data[Model10X::Model10XAmpereScaleFactor]); - inverterData.acCurrent= m_connection->convertValueWithSSF(data[Model10X::Model10XAcCurrent], data[Model10X::Model10XAmpereScaleFactor]); inverterData.acPower = m_connection->convertValueWithSSF(data[Model10X::Model10XACPower], data[Model10X::Model10XWattScaleFactor]); inverterData.lineFrequency = m_connection->convertValueWithSSF(data[Model10X::Model10XLineFrequency], data[Model10X::Model10XHerzScaleFactor]); @@ -112,11 +105,15 @@ void SunSpecInverter::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength inverterData.phaseVoltageBN = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageBN], data[Model10X::Model10XVoltageScaleFactor]); inverterData.phaseVoltageCN = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageCN], data[Model10X::Model10XVoltageScaleFactor]); - qCDebug(dcSunSpec()) << "AC energy converting:"; - qCDebug(dcSunSpec()) << " - Origin" << data[Model10X::Model10XAcEnergy]; - qCDebug(dcSunSpec()) << " - SSF" << static_cast(data[Model10X::Model10XWattHoursScaleFactor]); - qCDebug(dcSunSpec()) << " - Converted "<< m_connection->convertValueWithSSF(data[Model10X::Model10XAcEnergy], data[Model10X::Model10XWattHoursScaleFactor]); - inverterData.acEnergy = m_connection->convertValueWithSSF(data[Model10X::Model10XAcEnergy], data[Model10X::Model10XWattHoursScaleFactor]); + + qCDebug(dcSunSpec()) << "Energy with SSF values:"; + qCDebug(dcSunSpec()) << " - AC Energy 1:" << data[Model10X::Model10XAcEnergy]; + qCDebug(dcSunSpec()) << " - AC Energy 2" << data[Model10X::Model10XAcEnergy+1]; + quint32 acEnergy = ((static_cast(data.value(Model10X::Model10XAcEnergy))<<16)|static_cast(data.value(Model10X::Model10XAcEnergy+1))); + qCDebug(dcSunSpec()) << " - AC Energy combined" << acEnergy; + qCDebug(dcSunSpec()) << " - Scale factor:" << data[Model10X::Model10XWattHoursScaleFactor]; + + inverterData.acEnergy = m_connection->convertValueWithSSF(acEnergy, data[Model10X::Model10XWattHoursScaleFactor]); inverterData.cabinetTemperature = m_connection->convertValueWithSSF(data[Model10X::Model10XCabinetTemperature], data[Model10X::Model10XTemperatureScaleFactor]); inverterData.event = SunSpec::SunSpecEvent1(data[Model10X::Model10XEvent1]); From b536b1b16e330643a089f4e8ea9e01eb407fc05e Mon Sep 17 00:00:00 2001 From: Boernsman Date: Fri, 22 Jan 2021 09:10:29 +0100 Subject: [PATCH 08/16] renamed block or map to model --- sunspec/README.md | 9 +- sunspec/integrationpluginsunspec.cpp | 173 +++++++++++++----------- sunspec/integrationpluginsunspec.h | 12 +- sunspec/integrationpluginsunspec.json | 30 ++--- sunspec/sunspec.cpp | 146 +++++++------------- sunspec/sunspec.h | 185 +++++++++++++------------- sunspec/sunspecinverter.cpp | 49 +++---- sunspec/sunspecinverter.h | 16 +-- sunspec/sunspecmeter.cpp | 49 ++++--- sunspec/sunspecmeter.h | 17 ++- sunspec/sunspecstorage.cpp | 62 +++++---- sunspec/sunspecstorage.h | 17 ++- sunspec/sunspectracker.h | 2 +- 13 files changed, 384 insertions(+), 383 deletions(-) diff --git a/sunspec/README.md b/sunspec/README.md index f9ad5a5..11999ea 100644 --- a/sunspec/README.md +++ b/sunspec/README.md @@ -5,12 +5,19 @@ Connect to SunSpec devices over Modbus TCP ## Supported Things * SunSpec Inverter + * 101 & 111 Single phase inverter + * 102 & 112 Split phase inverter + * 103 & 113 Three phase inverter * SunSpec Meter -* SunSpec Storage + * Single phase meter + * Split phase meter + * 203 Three phase meter +* SunSpec Storage [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 index 2da3264..cabaa94 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -49,13 +49,13 @@ void IntegrationPluginSunSpec::init() m_connectedStateTypeIds.insert(sunspecSplitPhaseMeterThingClassId, sunspecSplitPhaseMeterConnectedStateTypeId); m_connectedStateTypeIds.insert(sunspecThreePhaseMeterThingClassId, sunspecThreePhaseMeterConnectedStateTypeId); - m_mapIdParamTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterThingMapIdParamTypeId); - m_mapIdParamTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterThingMapIdParamTypeId); - m_mapIdParamTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterThingMapIdParamTypeId); - m_mapIdParamTypeIds.insert(sunspecStorageThingClassId, sunspecStorageThingMapIdParamTypeId); - m_mapIdParamTypeIds.insert(sunspecSinglePhaseMeterThingClassId, sunspecSinglePhaseMeterThingMapIdParamTypeId); - m_mapIdParamTypeIds.insert(sunspecSplitPhaseMeterThingClassId, sunspecSplitPhaseMeterThingMapIdParamTypeId); - m_mapIdParamTypeIds.insert(sunspecThreePhaseMeterThingClassId, sunspecThreePhaseMeterThingMapIdParamTypeId); + 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); @@ -98,17 +98,17 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) QHostAddress address = QHostAddress(info->thing()->paramValue(sunspecConnectionThingIpAddressParamTypeId).toString()); int port = info->thing()->paramValue(sunspecConnectionThingPortParamTypeId).toInt(); - //int slaveId = info->thing()->paramValue(sunspecConnectionThingSlaveIdParamTypeId).toInt(); + int slaveId = info->thing()->paramValue(sunspecConnectionThingSlaveIdParamTypeId).toInt(); - SunSpec *sunSpec; if (m_sunSpecConnections.contains(thing->id())) { qCDebug(dcSunSpec()) << "Reconfigure SunSpec connection with new address" << address; - sunSpec = m_sunSpecConnections.value(thing->id()); - sunSpec->setHostAddress(address); - } else { - sunSpec = new SunSpec(address, port, this); - m_sunSpecConnections.insert(thing->id(), sunSpec); + 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"; @@ -127,9 +127,9 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) connect(sunSpec, &SunSpec::destroyed, [this, thing] { m_sunSpecConnections.remove(thing->id()); }); - connect(sunSpec, &SunSpec::foundModbusMap, this, &IntegrationPluginSunSpec::onFoundModbusMap); - connect(sunSpec, &SunSpec::modbusMapSearchFinished, this, &IntegrationPluginSunSpec::onModbusMapSearchFinished); - connect(sunSpec, &SunSpec::commonMapReceived, thing, [thing] (const QString &manufacturer, const QString &deviceModel, const QString &serielNumber) { + 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); @@ -140,14 +140,14 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) thing->thingClassId() == sunspecSplitPhaseInverterThingClassId || thing->thingClassId() == sunspecSinglePhaseInverterThingClassId ) { - uint mapId = thing->paramValue(m_mapIdParamTypeIds.value(thing->thingClassId())).toInt(); + 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::BlockId(mapId), modbusAddress); + 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; @@ -167,14 +167,14 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) thing->thingClassId() == sunspecSplitPhaseMeterThingClassId || thing->thingClassId() == sunspecThreePhaseMeterThingClassId) { - uint mapId = thing->paramValue(m_mapIdParamTypeIds.value(thing->thingClassId())).toInt(); + 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::BlockId(mapId), modbusAddress); + 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; @@ -192,6 +192,28 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) } else if (info->thing()->thingClassId() == sunspecStorageThingClassId) { + 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); } else { Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(info->thing()->thingClassId().toString()).toUtf8()); @@ -215,8 +237,8 @@ void IntegrationPluginSunSpec::postSetupThing(Thing *thing) qCDebug(dcSunSpec()) << "SunSpecConnection not found"; return; } - connection->readCommonMap(); - connection->findModbusMap(QList()); // Discover all maps, without filter + connection->readCommonModel(); + connection->findSunSpecModels(QList()); // Discover all models, without filter } else if (thing->thingClassId() == sunspecSinglePhaseInverterThingClassId || thing->thingClassId() == sunspecSplitPhaseInverterThingClassId || @@ -227,7 +249,7 @@ void IntegrationPluginSunSpec::postSetupThing(Thing *thing) qCDebug(dcSunSpec()) << "SunSpecInverter not found"; return; } - sunSpecInverter->getInverterMap(); + sunSpecInverter->getInverterModelDataBlock(); } else if (thing->thingClassId() == sunspecSinglePhaseMeterThingClassId || thing->thingClassId() == sunspecSplitPhaseMeterThingClassId || @@ -238,7 +260,7 @@ void IntegrationPluginSunSpec::postSetupThing(Thing *thing) qCDebug(dcSunSpec()) << "SunSpecMeter not found"; return; } - sunSpecMeter->getMeterMap(); + sunSpecMeter->getMeterModelDataBlock(); } else if (thing->thingClassId() == sunspecStorageThingClassId) { SunSpecStorage *sunSpecStorage = m_sunSpecStorages.value(thing); @@ -246,7 +268,7 @@ void IntegrationPluginSunSpec::postSetupThing(Thing *thing) qCDebug(dcSunSpec()) << "SunSpecStorage not found"; return; } - sunSpecStorage->getStorageMap(); + sunSpecStorage->getStorageModelDataBlock(); } else { Q_ASSERT_X(false, "postSetupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); @@ -373,11 +395,11 @@ void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info) } } -bool IntegrationPluginSunSpec::checkIfThingExists(uint mapId, uint modbusAddress) +bool IntegrationPluginSunSpec::checkIfThingExists(uint modelId, uint modbusAddress) { Q_FOREACH(Thing *thing, myThings()) { - if (thing->paramValue(m_mapIdParamTypeIds.value(thing->thingClassId())).toUInt() == mapId && - thing->paramValue(m_modbusAddressParamTypeIds.value(thing->thingClassId())).toUInt() == modbusAddress) { + if (thing->paramValue(m_modelIdParamTypeIds.value(thing->thingClassId())).toUInt() == modelId && + thing->paramValue(m_modbusAddressParamTypeIds.value(thing->thingClassId())).toUInt() == modbusAddress) { return true; } } @@ -387,16 +409,16 @@ bool IntegrationPluginSunSpec::checkIfThingExists(uint mapId, uint modbusAddress void IntegrationPluginSunSpec::onRefreshTimer() { foreach (SunSpec *connection, m_sunSpecConnections) { - connection->readCommonMap(); //check connection + connection->readCommonModel(); //check connection } foreach (SunSpecInverter *inverter, m_sunSpecInverters) { - inverter->getInverterMap(); + inverter->getInverterModelDataBlock(); } foreach (SunSpecMeter *meter, m_sunSpecMeters) { - meter->getMeterMap(); + meter->getMeterModelDataBlock(); } foreach (SunSpecStorage *storage, m_sunSpecStorages) { - storage->getStorageMap(); + storage->getStorageModelDataBlock(); } } @@ -410,6 +432,18 @@ void IntegrationPluginSunSpec::onPluginConfigurationChanged(const ParamTypeId &p m_refreshTimer->stop(); m_refreshTimer->startTimer(refreshTime); } + } else if (paramTypeId == sunSpecPluginNumberOfRetriesParamTypeId) { + qCDebug(dcSunSpec()) << "Updating number of retires" << 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; } @@ -431,23 +465,7 @@ void IntegrationPluginSunSpec::onConnectionStateChanged(bool status) } } -void IntegrationPluginSunSpec::onMapHeaderReceived(uint modbusAddress, SunSpec::BlockId mapId, uint mapLength) -{ - qCDebug(dcSunSpec()) << "On map header received" << modbusAddress << mapId << mapLength; -} - -void IntegrationPluginSunSpec::onMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data) -{ - Q_UNUSED(data) - SunSpec *connection = static_cast(sender()); - Thing *thing = myThings().findById(m_sunSpecConnections.key(connection)); - if (!thing) - return; - - qCDebug(dcSunSpec()) << "On map received" << mapId << "Map length" << mapLength << "Thing:" << thing->name(); -} - -void IntegrationPluginSunSpec::onFoundModbusMap(SunSpec::BlockId mapId, int modbusStartRegister) +void IntegrationPluginSunSpec::onFoundSunSpecModel(SunSpec::ModelId modelId, int modbusStartRegister) { SunSpec *connection = static_cast(sender()); Thing *thing = myThings().findById(m_sunSpecConnections.key(connection)); @@ -456,71 +474,71 @@ void IntegrationPluginSunSpec::onFoundModbusMap(SunSpec::BlockId mapId, int modb return; } - qCDebug(dcSunSpec()) << "On map received" << mapId << "Map length" << modbusStartRegister << "Thing:" << thing->name(); - if (checkIfThingExists(mapId, modbusStartRegister)) { + qCDebug(dcSunSpec()) << "On model received" << modelId << "length" << modbusStartRegister << "Thing:" << thing->name(); + if (checkIfThingExists(modelId, modbusStartRegister)) { return; } QString model = thing->stateValue(sunspecConnectionDeviceModelStateTypeId).toString(); - switch (mapId) { - case SunSpec::BlockId::BlockIdInverterSinglePhase: - case SunSpec::BlockId::BlockIdInverterSinglePhaseFloat: { + 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(sunspecSinglePhaseInverterThingMapIdParamTypeId, mapId)); + params.append(Param(sunspecSinglePhaseInverterThingModelIdParamTypeId, modelId)); params.append(Param(sunspecSinglePhaseInverterThingModbusAddressParamTypeId, modbusStartRegister)); descriptor.setParams(params); emit autoThingsAppeared({descriptor}); } break; - case SunSpec::BlockId::BlockIdInverterSplitPhase: - case SunSpec::BlockId::BlockIdInverterSplitPhaseFloat: { + case SunSpec::ModelId::ModelIdInverterSplitPhase: + case SunSpec::ModelId::ModelIdInverterSplitPhaseFloat: { ThingDescriptor descriptor(sunspecSplitPhaseInverterThingClassId, model+tr(" split phase inverter"), "", thing->id()); ParamList params; - params.append(Param(sunspecSplitPhaseInverterThingMapIdParamTypeId, mapId)); + params.append(Param(sunspecSplitPhaseInverterThingModelIdParamTypeId, modelId)); params.append(Param(sunspecSplitPhaseInverterThingModbusAddressParamTypeId, modbusStartRegister)); descriptor.setParams(params); emit autoThingsAppeared({descriptor}); } break; - case SunSpec::BlockId::BlockIdInverterThreePhase: - case SunSpec::BlockId::BlockIdInverterThreePhaseFloat: { + case SunSpec::ModelId::ModelIdInverterThreePhase: + case SunSpec::ModelId::ModelIdInverterThreePhaseFloat: { ThingDescriptor descriptor(sunspecThreePhaseInverterThingClassId, model+tr(" three phase inverter"), "", thing->id()); ParamList params; - params.append(Param(sunspecThreePhaseInverterThingMapIdParamTypeId, mapId)); + params.append(Param(sunspecThreePhaseInverterThingModelIdParamTypeId, modelId)); params.append(Param(sunspecThreePhaseInverterThingModbusAddressParamTypeId, modbusStartRegister)); descriptor.setParams(params); emit autoThingsAppeared({descriptor}); } break; - case SunSpec::BlockIdSinglePhaseMeter: - case SunSpec::BlockIdSinglePhaseMeterFloat: { + case SunSpec::ModelIdSinglePhaseMeter: + case SunSpec::ModelIdSinglePhaseMeterFloat: { ThingDescriptor descriptor(sunspecSinglePhaseMeterThingClassId, model+tr(" Meter"), "", thing->id()); ParamList params; - params.append(Param(sunspecSinglePhaseMeterThingMapIdParamTypeId, mapId)); + params.append(Param(sunspecSinglePhaseMeterThingModelIdParamTypeId, modelId)); params.append(Param(sunspecSinglePhaseMeterThingModbusAddressParamTypeId, modbusStartRegister)); descriptor.setParams(params); emit autoThingsAppeared({descriptor}); } break; - case SunSpec::BlockIdSplitSinglePhaseMeter: - case SunSpec::BlockIdSplitSinglePhaseMeterFloat: { + case SunSpec::ModelIdSplitSinglePhaseMeter: + case SunSpec::ModelIdSplitSinglePhaseMeterFloat: { ThingDescriptor descriptor(sunspecSplitPhaseMeterThingClassId, model+tr(" Meter"), "", thing->id()); ParamList params; - params.append(Param(sunspecSplitPhaseMeterThingMapIdParamTypeId, mapId)); + params.append(Param(sunspecSplitPhaseMeterThingModelIdParamTypeId, modelId)); params.append(Param(sunspecSplitPhaseMeterThingModbusAddressParamTypeId, modbusStartRegister)); descriptor.setParams(params); emit autoThingsAppeared({descriptor}); } break; - case SunSpec::BlockIdWyeConnectThreePhaseMeterFloat: - case SunSpec::BlockIdDeltaConnectThreePhaseMeterFloat: { + case SunSpec::ModelIdWyeConnectThreePhaseMeterFloat: + case SunSpec::ModelIdDeltaConnectThreePhaseMeterFloat: { ThingDescriptor descriptor(sunspecThreePhaseMeterThingClassId, model+" Meter", "", thing->id()); ParamList params; - params.append(Param(sunspecThreePhaseMeterThingMapIdParamTypeId, mapId)); + params.append(Param(sunspecThreePhaseMeterThingModelIdParamTypeId, modelId)); params.append(Param(sunspecThreePhaseMeterThingModbusAddressParamTypeId, modbusStartRegister)); descriptor.setParams(params); emit autoThingsAppeared({descriptor}); } break; - case SunSpec::BlockIdStorage: { + case SunSpec::ModelIdStorage: { ThingDescriptor descriptor(sunspecStorageThingClassId, model+" Storage", "", thing->id()); ParamList params; - params.append(Param(sunspecStorageThingMapIdParamTypeId, mapId)); + params.append(Param(sunspecStorageThingModelIdParamTypeId, modelId)); params.append(Param(sunspecThreePhaseInverterThingModbusAddressParamTypeId, modbusStartRegister)); descriptor.setParams(params); emit autoThingsAppeared({descriptor}); @@ -530,7 +548,7 @@ void IntegrationPluginSunSpec::onFoundModbusMap(SunSpec::BlockId mapId, int modb } } -void IntegrationPluginSunSpec::onModbusMapSearchFinished(const QHash &mapIds) +void IntegrationPluginSunSpec::onSunSpecModelSearchFinished(const QHash &modelIds) { SunSpec *connection = static_cast(sender()); Thing *thing = myThings().findById(m_sunSpecConnections.key(connection)); @@ -539,7 +557,7 @@ void IntegrationPluginSunSpec::onModbusMapSearchFinished(const QHashsetStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true); thing->setStateValue(sunspecStorageStorageStateStateTypeId, storageData.chargingState); } @@ -688,8 +707,8 @@ void IntegrationPluginSunSpec::onMeterDataReceived(const SunSpecMeter::MeterData SunSpecMeter *meter = static_cast(sender()); Thing *thing = m_sunSpecMeters.key(meter); - if(!thing) { + if (!thing) { return; } - //thing->setStateValue(sunspecMeterStorageStateStateTypeId, meterData.event); + thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true); } diff --git a/sunspec/integrationpluginsunspec.h b/sunspec/integrationpluginsunspec.h index 41ff90b..fedbb21 100644 --- a/sunspec/integrationpluginsunspec.h +++ b/sunspec/integrationpluginsunspec.h @@ -59,7 +59,7 @@ public: void executeAction(ThingActionInfo *info) override; private: - QHash m_mapIdParamTypeIds; + QHash m_modelIdParamTypeIds; QHash m_modbusAddressParamTypeIds; QHash m_connectedStateTypeIds; @@ -77,7 +77,7 @@ private: QHash m_sunSpecStorages; QHash m_sunSpecMeters; - bool checkIfThingExists(uint mapId, uint modbusAddress); + bool checkIfThingExists(uint modelId, uint modbusAddress); private slots: void onRefreshTimer(); @@ -86,11 +86,8 @@ private slots: void onConnectionStateChanged(bool status); - void onMapHeaderReceived(uint modbusAddress, SunSpec::BlockId mapId, uint mapLength); - void onMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data); - - void onFoundModbusMap(SunSpec::BlockId mapId, int modbusStartRegister); - void onModbusMapSearchFinished(const QHash &mapIds); + 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); @@ -99,6 +96,5 @@ private slots: void onStorageDataReceived(const SunSpecStorage::StorageData &storageData); void onMeterDataReceived(const SunSpecMeter::MeterData &meterData); }; - #endif // INTEGRATIONPLUGINSUNSPEC_H diff --git a/sunspec/integrationpluginsunspec.json b/sunspec/integrationpluginsunspec.json index d719318..bb081cc 100644 --- a/sunspec/integrationpluginsunspec.json +++ b/sunspec/integrationpluginsunspec.json @@ -37,7 +37,7 @@ "thingClasses": [ { "name": "sunspecConnection", - "displayName": "SunSpec Device", + "displayName": "SunSpec connection", "id": "f51853f3-8815-4cf3-b337-45cda1f3e6d5", "createMethods": [ "User" ], "interfaces": ["connectable"], @@ -109,8 +109,8 @@ "paramTypes": [ { "id": "41715d00-a947-4f43-a475-cea05790e01d", - "name":"mapId", - "displayName": "Map id", + "name":"modelId", + "displayName": "Model", "type": "int", "readOnly": true }, @@ -243,8 +243,8 @@ "paramTypes": [ { "id": "c42fb50e-210f-4b53-88eb-fa216e15f88f", - "name":"mapId", - "displayName": "Map id", + "name":"modelId", + "displayName": "Model", "type": "int", "readOnly": true }, @@ -404,8 +404,8 @@ "paramTypes": [ { "id": "8d5b2b58-ce46-406d-844e-f53136afcf09", - "name":"mapId", - "displayName": "Map id", + "name":"modelId", + "displayName": "Model", "type": "int", "readOnly": true }, @@ -583,8 +583,8 @@ "paramTypes": [ { "id": "7d6fcafb-c62e-4a21-aae2-f4041c487149", - "name":"mapId", - "displayName": "Map id", + "name":"modelId", + "displayName": "Model", "type": "int", "readOnly": true, "defaultValue": 0 @@ -682,8 +682,8 @@ "paramTypes": [ { "id": "89aeec6d-abeb-48b5-9594-214ad5db2d03", - "name":"mapId", - "displayName": "Map id", + "name":"modelId", + "displayName": "Model", "type": "int", "readOnly": true, "defaultValue": 0 @@ -808,8 +808,8 @@ "paramTypes": [ { "id": "a1960821-155c-4176-86fa-974429039182", - "name":"mapId", - "displayName": "Map id", + "name":"modelId", + "displayName": "Model", "type": "int", "readOnly": true, "defaultValue": 0 @@ -952,8 +952,8 @@ "paramTypes": [ { "id": "219beb96-b9fe-4dd2-a386-ecfbbab8786d", - "name":"mapId", - "displayName": "Map id", + "name":"modelId", + "displayName": "Model", "type": "int", "readOnly": true, "defaultValue": 0 diff --git a/sunspec/sunspec.cpp b/sunspec/sunspec.cpp index 2ebf713..0dd3401 100644 --- a/sunspec/sunspec.cpp +++ b/sunspec/sunspec.cpp @@ -32,10 +32,11 @@ #include "extern-plugininfo.h" #include -SunSpec::SunSpec(const QHostAddress &hostAddress, uint port, QObject *parent) : +SunSpec::SunSpec(const QHostAddress &hostAddress, uint port, uint slaveId, QObject *parent) : QObject(parent), m_hostAddress(hostAddress), - m_port(port) + m_port(port), + m_slaveId(slaveId) { qCDebug(dcSunSpec()) << "SunSpec: Creating SunSpec connection"; m_modbusTcpClient = new QModbusTcpClient(this); @@ -143,8 +144,8 @@ void SunSpec::findBaseRegister() 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 Map - qCDebug(dcSunSpec()) << "SunSpec: Found start of modbus map" << baseRegister; + //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 { @@ -162,9 +163,9 @@ void SunSpec::findBaseRegister() } } -void SunSpec::findModbusMap(const QList &ids, uint modbusAddressOffset) +void SunSpec::findSunSpecModels(const QList &ids, uint modbusAddressOffset) { - qCDebug(dcSunSpec()) << "SunSpec: Find modbus map. Start register" << m_baseRegister+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)) { @@ -175,23 +176,23 @@ void SunSpec::findModbusMap(const QList &ids, uint modbusAddressOffset) if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); uint modbusAddress = unit.startAddress(); - BlockId blockId = BlockId(unit.value(0)); - int blockLength = unit.value(1); - if (blockId == BlockIdEnd) { - qCDebug(dcSunSpec()) << "SunSpec: Block Id End"; - modbusMapSearchFinished(m_mapList); + 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(blockId)) { - // If ids is empty then emit all blocks - qCDebug(dcSunSpec()) << "SunSpec: Found block" << BlockId(blockId) << "with block length" << blockLength; - m_mapList.insert(BlockId(blockId), modbusAddress); - foundModbusMap(BlockId(blockId), modbusAddress); + 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); } - findModbusMap(ids, modbusAddress+2+blockLength-m_baseRegister); //read next block + findSunSpecModels(ids, modbusAddress+2+modelLength-m_baseRegister); //read next model } else { - qCWarning(dcSunSpec()) << "SunSpec: Find modbus map, read response error:" << reply->error(); + qCWarning(dcSunSpec()) << "SunSpec: Find modbus model, read response error:" << reply->error(); } }); } else { @@ -204,9 +205,9 @@ void SunSpec::findModbusMap(const QList &ids, uint modbusAddressOffset) } } -void SunSpec::readMapHeader(uint modbusAddress) +void SunSpec::readModelHeader(uint modbusAddress) { - qCDebug(dcSunSpec()) << "SunSpec: Read map header, modbus address:" << modbusAddress << "Slave ID" << m_slaveId; + qCDebug(dcSunSpec()) << "SunSpec: Read model header, modbus address:" << modbusAddress << "Slave ID" << m_slaveId; if (modbusAddress == 0 || modbusAddress == 40000 || modbusAddress == 50000) modbusAddress += 2; @@ -220,34 +221,34 @@ void SunSpec::readMapHeader(uint modbusAddress) if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); uint modbusAddress = unit.startAddress(); - BlockId mapId = BlockId(unit.value(0)); - int mapLength = unit.value(1); - qCDebug(dcSunSpec()) << "Received block header response. Map ID:" << mapId << "Map length" << mapLength; - mapHeaderReceived(modbusAddress, mapId, mapLength); + 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()) << "Read response error:" << reply->error(); + qCWarning(dcSunSpec()) << "SunSpec: Read response error:" << reply->error(); } }); connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { - qCWarning(dcSunSpec()) << "Modbus reply error:" << error; + qCWarning(dcSunSpec()) << "SunSpec: Modbus reply error:" << error; reply->finished(); // To make sure it will be deleted }); } else { - qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString(); delete reply; // broadcast replies return immediately return; } } else { - qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString(); return; } } -void SunSpec::readMap(uint modbusAddress, uint modelLength) +void SunSpec::readModelDataBlock(uint modbusAddress, uint length) { - qCDebug(dcSunSpec()) << "SunSpec: Read map, modbus address" << modbusAddress << "model length" << modelLength << ", Slave ID" << m_slaveId; + qCDebug(dcSunSpec()) << "SunSpec: Read model, modbus address" << modbusAddress << "length" << length << ", Slave ID" << m_slaveId; - QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, modbusAddress, modelLength+2); + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, modbusAddress, length+2); if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, m_slaveId)) { if (!reply->isFinished()) { @@ -257,32 +258,32 @@ void SunSpec::readMap(uint modbusAddress, uint modelLength) if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); uint modbusAddress = unit.startAddress(); - BlockId mapId = BlockId(unit.value(0)); - uint mapLength = unit.value(1); - qCDebug(dcSunSpec()) << "Received map. Modbus address" << modbusAddress << "Map ID" << mapId << "Map Length" << mapLength; - emit mapReceived(mapId, mapLength, unit.values().mid(2)); + 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()) << "Read response error:" << reply->error(); + qCWarning(dcSunSpec()) << "SunSpec: Read response error:" << reply->error(); } }); connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { - qCWarning(dcSunSpec()) << "Modbus reply error:" << error; + qCWarning(dcSunSpec()) << "SunSpec: Modbus reply error:" << error; reply->finished(); // To make sure it will be deleted }); } else { - qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString(); delete reply; // broadcast replies return immediately return; } } else { - qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString(); return; } } -void SunSpec::readCommonMap() +void SunSpec::readCommonModel() { - qCDebug(dcSunSpec()) << "SunSpec: Read common block header. Modbus Address" << m_baseRegister+2 << ", Slave ID" << m_slaveId; + 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); @@ -293,9 +294,6 @@ void SunSpec::readCommonMap() if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); - //uint modbusAddress = unit.startAddress(); - //BlockId mapId = BlockId(unit.value(0)); - //int mapLength = unit.value(1); m_manufacturer = convertModbusRegisters(unit.values(), MandatoryRegistersModel1::Manufacturer, 16); m_manufacturer.remove('\x00'); m_deviceModel = convertModbusRegisters(unit.values(), MandatoryRegistersModel1::Model, 16); @@ -303,60 +301,22 @@ void SunSpec::readCommonMap() 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; - commonMapReceived(m_manufacturer, m_deviceModel, m_serialNumber); + commonModelReceived(m_manufacturer, m_deviceModel, m_serialNumber); } else { - qCWarning(dcSunSpec()) << "SunSpec: Read common map, read response error:" << reply->error(); + qCWarning(dcSunSpec()) << "SunSpec: Read common model, read response error:" << reply->error(); } }); } else { - qCWarning(dcSunSpec()) << "Sunspec: Read common map read error: " << m_modbusTcpClient->errorString(); + qCWarning(dcSunSpec()) << "Sunspec: Read common model read error: " << m_modbusTcpClient->errorString(); delete reply; // broadcast replies return immediately return; } } else { - qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString(); return; } } -void SunSpec::onReceivedHoldingRegister(quint32 slaveAddress, quint32 modbusRegister, const QVector &data) -{ - if (modbusRegister == 00000 || modbusRegister == 40000 || modbusRegister == 50000) { - // Common block, 66 registers long + 2 header registers - - qCDebug(dcSunSpec()) << "Sunspec Identification:" << convertModbusRegisters(data, 0, 2); - - if (convertModbusRegisters(data, 0, 2) != "SunS") { - qCWarning(dcSunSpec()) << "Could not find SunS value at" << modbusRegister << "at Slave" << slaveAddress; - return; - } - m_baseRegister = modbusRegister; - - //Mandatory SunSpec Registers - // ID: 40003 - qCDebug(dcSunSpec()) << "Id:" << data[MandatoryRegistersModel1::Manufacturer]; - - // Manufacturer: 40005 | 16 - qCDebug(dcSunSpec()) << "Manufacturer:" << QString::fromLatin1(convertModbusRegisters(data, MandatoryRegistersModel1::Manufacturer, 16)); - - // Thing model: 40021 | 16 - qCDebug(dcSunSpec()) << "Thing model:" << QString::fromLatin1(convertModbusRegisters(data, MandatoryRegistersModel1::Model, 16)); - - // Data manager version: 40037 | 8 - qCDebug(dcSunSpec()) << "Data manager version:" << QString::fromLatin1(convertModbusRegisters(data, 36, 8)); - - // Inverter Version: 40045 | 8 - qCDebug(dcSunSpec()) << "Inverter version:" << QString::fromLatin1(convertModbusRegisters(data, 44, 8)); - - // Serial Number: 40053 | 16 - qCDebug(dcSunSpec()) << "Serial number:" << QString::fromLatin1(convertModbusRegisters(data, MandatoryRegistersModel1::SerialNumber, 16)); - - // Modbus thing address : 40069 | 1 - qCDebug(dcSunSpec()) << "Thing modbus address:" << data[67]; - }; -} - - QByteArray SunSpec::convertModbusRegister(const uint16_t &modbusData) { uint8_t data[2]; @@ -438,22 +398,18 @@ QUuid SunSpec::writeHoldingRegisters(uint registerAddress, const QVectorerror() != QModbusDevice::NoError) { - qCWarning(dcSunSpec()) << "Read response error:" << reply->error(); + qCWarning(dcSunSpec()) << "SunSpec: Read response error:" << reply->error(); + emit requestExecuted(requestId, false); + return; } - reply->deleteLater(); + emit requestExecuted(requestId, true); }); - connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ - - qCWarning(dcSunSpec()) << "Modbus reply error:" << error; - reply->finished(); // To make sure it will be deleted - }); - QTimer::singleShot(200, reply, &QModbusReply::deleteLater); } else { delete reply; // broadcast replies return immediately return ""; } } else { - qCWarning(dcSunSpec()) << "Read error: " << m_modbusTcpClient->errorString(); + qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString(); return ""; } return requestId; diff --git a/sunspec/sunspec.h b/sunspec/sunspec.h index bb5ec18..2c6a038 100644 --- a/sunspec/sunspec.h +++ b/sunspec/sunspec.h @@ -87,89 +87,89 @@ public: }; Q_ENUM(SunSpecEvent1) - enum BlockId { - BlockIdCommon = 1, - BlockIdBasicAggregator = 2, - BlockIdSecureDatasetReadRequest = 3, - BlockIdSecureDatasetReadResponse = 4, - BlockIdSecureWriteRequest = 5, - BlockIdSecureWriteSequentialRequest = 6, - BlockIdSecureWriteResponseModel = 7, - BlockIdGetDeviceSecurityCertificate = 8, - BlockIdSetOperatorSecurityCertificate = 9, - BlockIdCommunicationInterfaceHeader = 10, - BlockIdEthernetLinkLayer = 11, - BlockIdIPv4 = 12, - BlockIdIPv6 = 13, - BlockIdProxyServer = 14, - BlockIdInterfaceCountersModel = 15, - BlockIdSimpleIpNetwork = 16, - BlockIdSerialInterface = 17, - BlockIdCellularLink = 18, - BlockIdPPPLink = 19, - BlockIdInverterSinglePhase = 101, - BlockIdInverterSplitPhase = 102, - BlockIdInverterThreePhase = 103, - BlockIdInverterSinglePhaseFloat = 111, - BlockIdInverterSplitPhaseFloat = 112, - BlockIdInverterThreePhaseFloat = 113, - BlockIdNameplate = 120, - BlockIdBasicSettings = 121, - BlockIdMeasurementsStatus = 122, - BlockIdImmediateControls = 123, - BlockIdStorage = 124, - BlockIdPricing = 125, - BlockIdStaticVoltVAR = 126, - BlockIdFreqWattParam = 127, - BlockIdDynamicReactiveCurrent = 128, - BlockIdLVRTD = 129, - BlockIdHVRTD = 130, - BlockIdWattPF = 131, - BlockIdVoltWatt = 132, - BlockIdBasicScheduling = 133, - BlockIdFreqWattCrv = 134, - BlockIdLFRT = 135, - BlockIdHFRT = 136, - BlockIdLVRTC = 137, - BlockIdHVRTC = 138, - BlockIdMultipleMPPTInverterExtensionModel = 160, - BlockIdSinglePhaseMeter = 201, - BlockIdSplitSinglePhaseMeter = 202, - BlockIdWyeConnectThreePhaseMeter = 203, - BlockIdDeltaConnectThreePhaseMeter = 204, - BlockIdSinglePhaseMeterFloat = 211, - BlockIdSplitSinglePhaseMeterFloat = 212, - BlockIdWyeConnectThreePhaseMeterFloat = 213, - BlockIdDeltaConnectThreePhaseMeterFloat = 214, - BlockIdSecureACMeterSelectedReadings = 220, - BlockIdIrradianceModel = 302, - BlockIdBackOfModuleTemperatureModel = 303, - BlockIdInclinometerModel = 304, - BlockIdGPS = 305, - BlockIdReferencePointModel = 306, - BlockIdBaseMet = 307, - BlockIdMiniMetModel = 308, - BlockIdStringCombiner = 401, - BlockIdStringCombinerAdvanced = 402, - BlockIdStringCombinerCurrent = 403, - BlockIdStringCombinerCurrentAdvanced = 404, - BlockIdSolarModuleFloat = 501, - BlockIdSolarModule = 502, - BlockIdTrackerController = 601, - BlockIdEnergyStorageBaseModel = 801, - BlockIdBatteryBaseModel = 802, - BlockIdLithiumIonBatteryModel = 803, - BlockIdVerisStatusConfiguration = 64001, - BlockIdMersenGreenString = 64020, - BlockIdEltekInverterExtension = 64101, - BlockIdOutBackAXSDevice = 64110, - BlockIdBasicChargeController = 64111, - BlockIdOutBackFMChargeController = 64112, - BlockIdEnd = 65535 + 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(BlockId) + Q_ENUM(ModelId) - explicit SunSpec(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); + explicit SunSpec(const QHostAddress &hostAddress, uint port = 502, uint slaveId = 1, QObject *parent = 0); ~SunSpec(); bool connectModbus(); void setHostAddress(const QHostAddress &hostAddress); @@ -194,14 +194,14 @@ public: QString m_manufacturer = "Unknown"; QString m_deviceModel = "Unknown"; QString m_serialNumber = "Unknown"; - QHash m_mapList; + QHash m_modelList; void findBaseRegister(); - void findModbusMap(const QList &mapIds, uint modbusAddressOffset = 2); + void findSunSpecModels(const QList &modelIds, uint modbusAddressOffset = 2); - void readCommonMap(); - void readMapHeader(uint modbusAddress); - void readMap(uint modbusAddress, uint modelLength); //modbusAddress = model start address, model length is without header + void readCommonModel(); + void readModelHeader(uint modbusAddress); + void readModelDataBlock(uint modbusAddress, uint modelLength); //modbusAddress = model start address, model length is without header float convertValueWithSSF(quint32 rawValue, quint16 sunssf); float convertFloatValues(quint16 rawValue0, quint16 rawValue1); @@ -214,20 +214,19 @@ public: signals: void connectionStateChanged(bool status); - void requestExecuted(QUuid requetId, bool success); + void requestExecuted(const QUuid &requestId, bool success); void foundBaseRegister(int modbusAddress); - void commonMapReceived(const QString &manufacturer, const QString &deviceModel, const QString &serialNumber); + void commonModelReceived(const QString &manufacturer, const QString &deviceModel, const QString &serialNumber); - void foundModbusMap(BlockId mapId, int modbusStartRegister); - void modbusMapSearchFinished(const QHash &mapIds); + void foundSunSpecModel(ModelId modelId, int modbusStartRegister); + void sunspecModelSearchFinished(const QHash &mapIds); - void mapHeaderReceived(uint modbusAddress, BlockId mapId, uint mapLength); - void mapReceived(BlockId mapId, uint mapLength, QVector data); + void modelHeaderReceived(uint modbusAddress, ModelId mapId, uint mapLength); + void modelDataBlockReceived(ModelId id, uint ength, QVector data); private slots: void onModbusStateChanged(QModbusDevice::State state); - void onReceivedHoldingRegister(quint32 slaveAddress, quint32 modbusRegister, const QVector &values); }; #endif // SUNSPEC_H diff --git a/sunspec/sunspecinverter.cpp b/sunspec/sunspecinverter.cpp index 5b7cb9d..bee5c8d 100644 --- a/sunspec/sunspecinverter.cpp +++ b/sunspec/sunspecinverter.cpp @@ -33,17 +33,17 @@ #include -SunSpecInverter::SunSpecInverter(SunSpec *sunspec, SunSpec::BlockId mapId, int modbusAddress) : +SunSpecInverter::SunSpecInverter(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress) : QObject(sunspec), m_connection(sunspec), - m_id(mapId), - m_mapModbusStartRegister(modbusAddress) + m_id(modelId), + m_modelModbusStartRegister(modbusAddress) { qCDebug(dcSunSpec()) << "SunSpecInverter: Setting up inverter"; - connect(m_connection, &SunSpec::mapReceived, this, &SunSpecInverter::onModbusMapReceived); + connect(m_connection, &SunSpec::modelDataBlockReceived, this, &SunSpecInverter::onModelDataBlockReceived); } -SunSpec::BlockId SunSpecInverter::blockId() +SunSpec::ModelId SunSpecInverter::modelId() { return m_id; } @@ -51,10 +51,10 @@ SunSpec::BlockId SunSpecInverter::blockId() void SunSpecInverter::init() { qCDebug(dcSunSpec()) << "SunSpecInverter: Init"; - m_connection->readMapHeader(m_mapModbusStartRegister); - connect(m_connection, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, SunSpec::BlockId mapId, uint mapLength) { - qCDebug(dcSunSpec()) << "SunSpecInverter: Map Header received, modbus address:" << modbusAddress << "map Id:" << mapId << "map length:" << mapLength; - m_mapLength = mapLength; + m_connection->readModelHeader(m_modelModbusStartRegister); + connect(m_connection, &SunSpec::modelHeaderReceived, this, [this] (uint modbusAddress, SunSpec::ModelId modelId, uint length) { + qCDebug(dcSunSpec()) << "SunSpecInverter: Model Header received, modbus address:" << modbusAddress << "model Id:" << modelId << "length:" << length; + m_modelLength = length; emit initFinished(true); m_initFinishedSuccess = true; }); @@ -65,33 +65,33 @@ void SunSpecInverter::init() }); } -void SunSpecInverter::getInverterMap() +void SunSpecInverter::getInverterModelDataBlock() { // TODO check map length to modbus max value - m_connection->readMap(m_mapModbusStartRegister, m_mapLength); + m_connection->readModelDataBlock(m_modelModbusStartRegister, m_modelLength); } -void SunSpecInverter::readInverterBlockHeader() +void SunSpecInverter::getInverterModelHeader() { - m_connection->readMapHeader(m_mapModbusStartRegister); + m_connection->readModelHeader(m_modelModbusStartRegister); } -void SunSpecInverter::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data) +void SunSpecInverter::onModelDataBlockReceived(SunSpec::ModelId mapId, uint mapLength, QVector data) { Q_UNUSED(mapLength) if (mapId != m_id) { return; } - if (mapLength < m_mapLength) { + if (mapLength < m_modelLength) { qCDebug(dcSunSpec()) << "SunSpecInverter: on modbus map received, map length ist too short" << mapLength; //return; } InverterData inverterData; switch (mapId) { - case SunSpec::BlockIdInverterSinglePhase: - case SunSpec::BlockIdInverterSplitPhase: - case SunSpec::BlockIdInverterThreePhase: { + case SunSpec::ModelIdInverterSinglePhase: + case SunSpec::ModelIdInverterSplitPhase: + case SunSpec::ModelIdInverterThreePhase: { inverterData.acCurrent= m_connection->convertValueWithSSF(data[Model10X::Model10XAcCurrent], data[Model10X::Model10XAmpereScaleFactor]); inverterData.acPower = m_connection->convertValueWithSSF(data[Model10X::Model10XACPower], data[Model10X::Model10XWattScaleFactor]); @@ -105,14 +105,7 @@ void SunSpecInverter::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength inverterData.phaseVoltageBN = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageBN], data[Model10X::Model10XVoltageScaleFactor]); inverterData.phaseVoltageCN = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageCN], data[Model10X::Model10XVoltageScaleFactor]); - - qCDebug(dcSunSpec()) << "Energy with SSF values:"; - qCDebug(dcSunSpec()) << " - AC Energy 1:" << data[Model10X::Model10XAcEnergy]; - qCDebug(dcSunSpec()) << " - AC Energy 2" << data[Model10X::Model10XAcEnergy+1]; quint32 acEnergy = ((static_cast(data.value(Model10X::Model10XAcEnergy))<<16)|static_cast(data.value(Model10X::Model10XAcEnergy+1))); - qCDebug(dcSunSpec()) << " - AC Energy combined" << acEnergy; - qCDebug(dcSunSpec()) << " - Scale factor:" << data[Model10X::Model10XWattHoursScaleFactor]; - inverterData.acEnergy = m_connection->convertValueWithSSF(acEnergy, data[Model10X::Model10XWattHoursScaleFactor]); inverterData.cabinetTemperature = m_connection->convertValueWithSSF(data[Model10X::Model10XCabinetTemperature], data[Model10X::Model10XTemperatureScaleFactor]); @@ -121,9 +114,9 @@ void SunSpecInverter::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength emit inverterDataReceived(inverterData); } break; - case SunSpec::BlockIdInverterThreePhaseFloat: - case SunSpec::BlockIdInverterSplitPhaseFloat: - case SunSpec::BlockIdInverterSinglePhaseFloat: { + case SunSpec::ModelIdInverterThreePhaseFloat: + case SunSpec::ModelIdInverterSplitPhaseFloat: + case SunSpec::ModelIdInverterSinglePhaseFloat: { inverterData.acCurrent = m_connection->convertFloatValues(data[Model11X::Model11XAcCurrent], data[Model11X::Model11XAcCurrent+1]); diff --git a/sunspec/sunspecinverter.h b/sunspec/sunspecinverter.h index 2c6ca04..918cc75 100644 --- a/sunspec/sunspecinverter.h +++ b/sunspec/sunspecinverter.h @@ -96,22 +96,22 @@ public: SunSpec::SunSpecOperatingState operatingState; }; - SunSpecInverter(SunSpec *sunspec, SunSpec::BlockId blockId, int modbusAddress); - SunSpec::BlockId blockId(); + SunSpecInverter(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress); + SunSpec::ModelId modelId(); void init(); - void getInverterMap(); + void getInverterModelDataBlock(); private: SunSpec *m_connection = nullptr; - SunSpec::BlockId m_id; //e.g. 103 for three phase inverter, 113 for three phase inverter with floating point representation - uint m_mapLength = 0; - uint m_mapModbusStartRegister = 40000; + SunSpec::ModelId m_id; //e.g. 103 for three phase inverter, 113 for three phase inverter with floating point representation + uint m_modelLength = 0; + uint m_modelModbusStartRegister = 40000; bool m_initFinishedSuccess = false; - void readInverterBlockHeader(); + void getInverterModelHeader(); private slots: - void onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data); + void onModelDataBlockReceived(SunSpec::ModelId mapId, uint mapLength, QVector data); signals: void initFinished(bool success); diff --git a/sunspec/sunspecmeter.cpp b/sunspec/sunspecmeter.cpp index d7d6481..e154d1b 100644 --- a/sunspec/sunspecmeter.cpp +++ b/sunspec/sunspecmeter.cpp @@ -31,28 +31,29 @@ #include "sunspecmeter.h" #include "extern-plugininfo.h" -SunSpecMeter::SunSpecMeter(SunSpec *sunspec, SunSpec::BlockId blockId, int modbusAddress) : +SunSpecMeter::SunSpecMeter(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress) : QObject(sunspec), m_connection(sunspec), - m_id(blockId), - m_mapModbusStartRegister(modbusAddress) + m_id(modelId), + m_modelModbusStartRegister(modbusAddress) { qCDebug(dcSunSpec()) << "SunSpecMeter: Setting up meter"; - connect(m_connection, &SunSpec::mapReceived, this, &SunSpecMeter::onModbusMapReceived); + connect(m_connection, &SunSpec::modelDataBlockReceived, this, &SunSpecMeter::onModelDataBlockReceived); } -SunSpec::BlockId SunSpecMeter::blockId() +SunSpec::ModelId SunSpecMeter::modelId() { + return m_id; } void SunSpecMeter::init() { - qCDebug(dcSunSpec()) << "SunSpecInverter: Init"; - m_connection->readMapHeader(m_mapModbusStartRegister); - connect(m_connection, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, SunSpec::BlockId mapId, uint mapLength) { - qCDebug(dcSunSpec()) << "SunSpecInverter: Map Header received, modbus address:" << modbusAddress << "map Id:" << mapId << "map length:" << mapLength; - m_mapLength = mapLength; + qCDebug(dcSunSpec()) << "SunSpecMeter: Init"; + m_connection->readModelHeader(m_modelModbusStartRegister); + connect(m_connection, &SunSpec::modelHeaderReceived, this, [this] (uint modbusAddress, SunSpec::ModelId modelId, uint length) { + qCDebug(dcSunSpec()) << "SunSpecMeter: Model Header received, modbus address:" << modbusAddress << "model Id:" << modelId << "length:" << length; + m_modelLength = length; emit initFinished(true); m_initFinishedSuccess = true; }); @@ -63,21 +64,37 @@ void SunSpecMeter::init() }); } -void SunSpecMeter::getMeterMap() +void SunSpecMeter::getMeterModelDataBlock() { - m_connection->readMap(m_mapModbusStartRegister, m_mapLength); + m_connection->readModelDataBlock(m_modelModbusStartRegister, m_modelLength); } -void SunSpecMeter::readMeterBlockHeader() +void SunSpecMeter::getMeterModelHeader() { } -void SunSpecMeter::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data) +void SunSpecMeter::onModelDataBlockReceived(SunSpec::ModelId modelId, uint length, QVector data) { - Q_UNUSED(mapLength) + if (modelId != m_id) { + return; + } + Q_UNUSED(length) Q_UNUSED(data) - switch (mapId) { + switch (modelId) { + + case SunSpec::ModelIdSinglePhaseMeter: + case SunSpec::ModelIdSinglePhaseMeterFloat: { + + } break; + case SunSpec::ModelIdSplitSinglePhaseMeter: + case SunSpec::ModelIdSplitSinglePhaseMeterFloat: { + + } break; + case SunSpec::ModelIdWyeConnectThreePhaseMeterFloat: + case SunSpec::ModelIdDeltaConnectThreePhaseMeterFloat: { + + } break; default: break; } diff --git a/sunspec/sunspecmeter.h b/sunspec/sunspecmeter.h index f7f81b8..1c0f2df 100644 --- a/sunspec/sunspecmeter.h +++ b/sunspec/sunspecmeter.h @@ -68,22 +68,21 @@ public: SunSpec::SunSpecOperatingState operatingState; }; - SunSpecMeter(SunSpec *sunspec, SunSpec::BlockId blockId, int modbusAddress); - SunSpec::BlockId blockId(); + SunSpecMeter(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress); + SunSpec::ModelId modelId(); void init(); - void getMeterMap(); + void getMeterModelHeader(); + void getMeterModelDataBlock(); private: SunSpec *m_connection = nullptr; - SunSpec::BlockId m_id = SunSpec::BlockIdDeltaConnectThreePhaseMeter; - uint m_mapLength = 0; - uint m_mapModbusStartRegister = 40000; + SunSpec::ModelId m_id = SunSpec::ModelIdDeltaConnectThreePhaseMeter; + uint m_modelLength = 0; + uint m_modelModbusStartRegister = 40000; bool m_initFinishedSuccess = false; - void readMeterBlockHeader(); - private slots: - void onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data); + void onModelDataBlockReceived(SunSpec::ModelId modelId, uint length, QVector data); signals: void initFinished(bool success); diff --git a/sunspec/sunspecstorage.cpp b/sunspec/sunspecstorage.cpp index 426f63e..28600cc 100644 --- a/sunspec/sunspecstorage.cpp +++ b/sunspec/sunspecstorage.cpp @@ -31,29 +31,28 @@ #include "sunspecstorage.h" #include "extern-plugininfo.h" -SunSpecStorage::SunSpecStorage(SunSpec *sunspec, SunSpec::BlockId blockId, int modbusAddress) : +SunSpecStorage::SunSpecStorage(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress) : QObject(sunspec), m_connection(sunspec), - m_id(blockId), - m_mapModbusStartRegister(modbusAddress) + m_id(modelId), + m_modelModbusStartRegister(modbusAddress) { qCDebug(dcSunSpec()) << "SunSpecStorage: Setting up storage"; - connect(m_connection, &SunSpec::mapReceived, this, &SunSpecStorage::onModbusMapReceived); + connect(m_connection, &SunSpec::modelDataBlockReceived, this, &SunSpecStorage::onModelDataBlockReceived); } -SunSpec::BlockId SunSpecStorage::blockId() +SunSpec::ModelId SunSpecStorage::modelId() { return m_id; - } void SunSpecStorage::init() { - qCDebug(dcSunSpec()) << "SunSpecInverter: Init"; - m_connection->readMapHeader(m_mapModbusStartRegister); - connect(m_connection, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, SunSpec::BlockId mapId, uint mapLength) { - qCDebug(dcSunSpec()) << "SunSpecInverter: Map Header received, modbus address:" << modbusAddress << "map Id:" << mapId << "map length:" << mapLength; - m_mapLength = mapLength; + qCDebug(dcSunSpec()) << "SunSpecStorage: Init"; + m_connection->readModelHeader(m_modelModbusStartRegister); + connect(m_connection, &SunSpec::modelHeaderReceived, this, [this] (uint modbusAddress, SunSpec::ModelId modelId, uint length) { + qCDebug(dcSunSpec()) << "SunSpecStorager: Model header received, modbus address:" << modbusAddress << "model Id:" << modelId << "length:" << length; + m_modelLength = length; emit initFinished(true); m_initFinishedSuccess = true; }); @@ -64,14 +63,14 @@ void SunSpecStorage::init() }); } -void SunSpecStorage::getStorageMap() +void SunSpecStorage::getStorageModelDataBlock() { - m_connection->readMap(m_mapModbusStartRegister, m_mapLength); + m_connection->readModelDataBlock(m_modelModbusStartRegister, m_modelLength); } -void SunSpecStorage::readStorageBlockHeader() +void SunSpecStorage::getStorageModelHeader() { - m_connection->readMapHeader(m_mapModbusStartRegister); + m_connection->readModelHeader(m_modelModbusStartRegister); } QUuid SunSpecStorage::setGridCharging(bool enabled) @@ -82,7 +81,7 @@ QUuid SunSpecStorage::setGridCharging(bool enabled) PV (charging from grid 0 disabled) GRID (charging from 1 grid enabled*/ - uint registerAddress = m_mapModbusStartRegister + Model124::Model124ChaGriSet; + uint registerAddress = m_modelModbusStartRegister + Model124::Model124ChaGriSet; quint16 value = enabled; return m_connection->writeHoldingRegister(registerAddress, value); } @@ -93,7 +92,7 @@ QUuid SunSpecStorage::setStorageControlMode(bool chargingEnabled, bool dischargi quint16 value = ((static_cast(chargingEnabled) << StorageControlBitFieldCharge) | (static_cast(dischargingEnabled) << StorageControlBitFieldDischarge)) ; - uint modbusRegister = m_mapModbusStartRegister + Model124::Model124ActivateStorageControlMode; + uint modbusRegister = m_modelModbusStartRegister + Model124::Model124ActivateStorageControlMode; return m_connection->writeHoldingRegister(modbusRegister, value); } @@ -102,7 +101,7 @@ QUuid SunSpecStorage::setChargingRate(int rate) //Register Name InWRte /* Defines the maximum charge rate (charge limit). Default is 100% */ - uint modbusRegister = m_mapModbusStartRegister + Model124::Model124SetpointMaximumChargingRate; + uint modbusRegister = m_modelModbusStartRegister + Model124::Model124SetpointMaximumChargingRate; int16_t value = rate * 100; return m_connection->writeHoldingRegister(modbusRegister, value); } @@ -111,20 +110,37 @@ QUuid SunSpecStorage::setDischargingRate(int charging) { //Register Name OutWRte /* Defines the maximum discharge rate (discharge limit). Default is 100% */ - uint modbusRegister = m_mapModbusStartRegister + Model124::Model124SetpointMaximumDischargeRate; + uint modbusRegister = m_modelModbusStartRegister + Model124::Model124SetpointMaximumDischargeRate; quint16 value = charging * 100; return m_connection->writeHoldingRegister(modbusRegister, value); } -void SunSpecStorage::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, const QVector &data) +void SunSpecStorage::onModelDataBlockReceived(SunSpec::ModelId modelId, uint length, const QVector &data) { - Q_UNUSED(mapLength) - switch (mapId) { - case SunSpec::BlockIdStorage: { + Q_UNUSED(length) + if (modelId != m_id) { + return; + } + + switch (modelId) { + case SunSpec::ModelIdStorage: { StorageData storageData; + qCDebug(dcSunSpec()) << "SunSpecStorage: Storage model received:"; + qCDebug(dcSunSpec()) << " - Setpoint maximum charge" << data[Model124SetpointMaximumCharge]; + qCDebug(dcSunSpec()) << " - Setpoint maximum charging rate" << data[Model124SetpointMaximumChargingRate]; + qCDebug(dcSunSpec()) << " - Setpoint maximum discharge rate" << data[Model124SetpointMaximumDischargeRate]; + qCDebug(dcSunSpec()) << " - Active storage control mode" << data[Model124ActivateStorageControlMode]; + qCDebug(dcSunSpec()) << " - Currently available energy" << data[Model124CurrentlyAvailableEnergy]; storageData.chargingState = ChargingState(data[Model124::Model124ChargeStatus]); + qCDebug(dcSunSpec()) << " - Setpoint maximum charging rate" << data[Model124ChaGriSet]; + qCDebug(dcSunSpec()) << " - Setpoint maximum charging rate" << data[Model124ScaleFactorMaximumCharge]; + qCDebug(dcSunSpec()) << " - Setpoint maximum charging rate" << data[Model124ScaleFactorMaximumChargeDischargeRate]; + qCDebug(dcSunSpec()) << " - Setpoint maximum charging rate" << data[Model124ScaleFactorAvailableEnergyPercent]; emit storageDataReceived(storageData); } break; + case SunSpec::ModelIdBatteryBaseModel: + case SunSpec::ModelIdLithiumIonBatteryModel: { + } default: break; } diff --git a/sunspec/sunspecstorage.h b/sunspec/sunspecstorage.h index 3d2cd3e..1cb7f1c 100644 --- a/sunspec/sunspecstorage.h +++ b/sunspec/sunspecstorage.h @@ -38,11 +38,12 @@ class SunSpecStorage : public QObject { Q_OBJECT public: - SunSpecStorage(SunSpec *sunspec, SunSpec::BlockId blockId, int modbusAddress); + SunSpecStorage(SunSpec *sunspec, SunSpec::ModelId modelId, int modbusAddress); - SunSpec::BlockId blockId(); + SunSpec::ModelId modelId(); void init(); - void getStorageMap(); + void getStorageModelHeader(); + void getStorageModelDataBlock(); QUuid setGridCharging(bool enabled); QUuid setDischargingRate(int rate); @@ -93,15 +94,13 @@ public: private: SunSpec *m_connection = nullptr; - SunSpec::BlockId m_id = SunSpec::BlockIdEnergyStorageBaseModel; - uint m_mapLength = 0; - uint m_mapModbusStartRegister = 40000; + SunSpec::ModelId m_id = SunSpec::ModelIdEnergyStorageBaseModel; + uint m_modelLength = 0; + uint m_modelModbusStartRegister = 40000; bool m_initFinishedSuccess = false; - void readStorageBlockHeader(); - private slots: - void onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, const QVector &data); + void onModelDataBlockReceived(SunSpec::ModelId modelId, uint length, const QVector &data); signals: void initFinished(bool success); diff --git a/sunspec/sunspectracker.h b/sunspec/sunspectracker.h index 2be9c84..88b81a8 100644 --- a/sunspec/sunspectracker.h +++ b/sunspec/sunspectracker.h @@ -60,7 +60,7 @@ public: void getTrackerMap(); private: - BlockId m_id = BlockIdTrackerController; + ModelId m_id = ModelIdTrackerController; uint m_mapLength = 0; uint m_mapModbusStartRegister = 40000; From c112e1e6a01352c507094a3ae7f18140395aae5d Mon Sep 17 00:00:00 2001 From: Boernsman Date: Fri, 22 Jan 2021 14:21:23 +0100 Subject: [PATCH 09/16] improved sunspec storage --- sunspec/integrationpluginsunspec.cpp | 238 ++++++++++++++++---------- sunspec/integrationpluginsunspec.h | 6 +- sunspec/integrationpluginsunspec.json | 40 ++--- sunspec/sunspec.cpp | 13 +- sunspec/sunspecinverter.cpp | 15 +- sunspec/sunspecmeter.cpp | 22 ++- sunspec/sunspecstorage.cpp | 62 ++++--- sunspec/sunspecstorage.h | 77 ++++++--- 8 files changed, 305 insertions(+), 168 deletions(-) diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index cabaa94..4fbfa66 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -140,81 +140,37 @@ void IntegrationPluginSunSpec::setupThing(ThingSetupInfo *info) thing->thingClassId() == sunspecSplitPhaseInverterThingClassId || thing->thingClassId() == sunspecSinglePhaseInverterThingClassId ) { - 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); + Thing *parent = myThings().findById(thing->parentId()); + if (parent->setupStatus() == Thing::ThingSetupStatusComplete) { + setupInverter(info); + } else { + connect(parent, &Thing::setupStatusChanged, info, [this, info] { + setupInverter(info); + }); } - 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); - } else if (thing->thingClassId() == sunspecSinglePhaseMeterThingClassId || thing->thingClassId() == sunspecSplitPhaseMeterThingClassId || thing->thingClassId() == sunspecThreePhaseMeterThingClassId) { - 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); + Thing *parent = myThings().findById(thing->parentId()); + if (parent->setupStatus() == Thing::ThingSetupStatusComplete) { + setupMeter(info); + } else { + connect(parent, &Thing::setupStatusChanged, info, [this, info] { + setupMeter(info); + }); } - 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); } else if (info->thing()->thingClassId() == sunspecStorageThingClassId) { - 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); + Thing *parent = myThings().findById(thing->parentId()); + if (parent->setupStatus() == Thing::ThingSetupStatusComplete) { + setupStorage(info); + } else { + connect(parent, &Thing::setupStatusChanged, info, [this, info] { + setupStorage(info); + }); } - 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); - } else { Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(info->thing()->thingClassId().toString()).toUtf8()); } @@ -353,15 +309,16 @@ void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info) m_asyncActions.insert(requestId, info); connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); } - } else if (action.actionTypeId() == sunspecStorageEnableChargingLimitActionTypeId) { - /*int value = (action.param(sunspecStorageEnableChargingLimitActionEnableChargingLimitParamTypeId).value().toBool() << 1) | thing->stateValue(sunspecStorageEnableDischargingLimitStateTypeId).toBool(); - QUuid requestId = sunSpecStorage->setStorageControlMode(value); + } 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()) { @@ -370,15 +327,16 @@ void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info) m_asyncActions.insert(requestId, info); connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); } - } else if (action.actionTypeId() == sunspecStorageEnableDischargingLimitActionTypeId) { - /*int value = (action.param(sunspecStorageEnableDischargingLimitActionEnableDischargingLimitParamTypeId).value().toBool() << 1) | thing->stateValue(sunspecStorageEnableChargingLimitStateTypeId).toBool(); - QUuid requestId = sunSpecStorage->setStorageControlMode(value); + } 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()) { @@ -406,6 +364,88 @@ bool IntegrationPluginSunSpec::checkIfThingExists(uint modelId, uint modbusAddre 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) { @@ -437,13 +477,11 @@ void IntegrationPluginSunSpec::onPluginConfigurationChanged(const ParamTypeId &p 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; } @@ -474,7 +512,7 @@ void IntegrationPluginSunSpec::onFoundSunSpecModel(SunSpec::ModelId modelId, int return; } - qCDebug(dcSunSpec()) << "On model received" << modelId << "length" << modbusStartRegister << "Thing:" << thing->name(); + qCDebug(dcSunSpec()) << "On model received" << modelId << "start register" << modbusStartRegister << "Thing:" << thing->name(); if (checkIfThingExists(modelId, modbusStartRegister)) { return; } @@ -510,7 +548,7 @@ void IntegrationPluginSunSpec::onFoundSunSpecModel(SunSpec::ModelId modelId, int case SunSpec::ModelIdSinglePhaseMeter: case SunSpec::ModelIdSinglePhaseMeterFloat: { - ThingDescriptor descriptor(sunspecSinglePhaseMeterThingClassId, model+tr(" Meter"), "", thing->id()); + ThingDescriptor descriptor(sunspecSinglePhaseMeterThingClassId, model+tr(" meter"), "", thing->id()); ParamList params; params.append(Param(sunspecSinglePhaseMeterThingModelIdParamTypeId, modelId)); params.append(Param(sunspecSinglePhaseMeterThingModbusAddressParamTypeId, modbusStartRegister)); @@ -519,7 +557,7 @@ void IntegrationPluginSunSpec::onFoundSunSpecModel(SunSpec::ModelId modelId, int } break; case SunSpec::ModelIdSplitSinglePhaseMeter: case SunSpec::ModelIdSplitSinglePhaseMeterFloat: { - ThingDescriptor descriptor(sunspecSplitPhaseMeterThingClassId, model+tr(" Meter"), "", thing->id()); + ThingDescriptor descriptor(sunspecSplitPhaseMeterThingClassId, model+tr(" meter"), "", thing->id()); ParamList params; params.append(Param(sunspecSplitPhaseMeterThingModelIdParamTypeId, modelId)); params.append(Param(sunspecSplitPhaseMeterThingModbusAddressParamTypeId, modbusStartRegister)); @@ -528,7 +566,7 @@ void IntegrationPluginSunSpec::onFoundSunSpecModel(SunSpec::ModelId modelId, int } break; case SunSpec::ModelIdWyeConnectThreePhaseMeterFloat: case SunSpec::ModelIdDeltaConnectThreePhaseMeterFloat: { - ThingDescriptor descriptor(sunspecThreePhaseMeterThingClassId, model+" Meter", "", thing->id()); + ThingDescriptor descriptor(sunspecThreePhaseMeterThingClassId, model+" meter", "", thing->id()); ParamList params; params.append(Param(sunspecThreePhaseMeterThingModelIdParamTypeId, modelId)); params.append(Param(sunspecThreePhaseMeterThingModbusAddressParamTypeId, modbusStartRegister)); @@ -536,10 +574,10 @@ void IntegrationPluginSunSpec::onFoundSunSpecModel(SunSpec::ModelId modelId, int emit autoThingsAppeared({descriptor}); } break; case SunSpec::ModelIdStorage: { - ThingDescriptor descriptor(sunspecStorageThingClassId, model+" Storage", "", thing->id()); + ThingDescriptor descriptor(sunspecStorageThingClassId, model+" storage", "", thing->id()); ParamList params; params.append(Param(sunspecStorageThingModelIdParamTypeId, modelId)); - params.append(Param(sunspecThreePhaseInverterThingModbusAddressParamTypeId, modbusStartRegister)); + params.append(Param(sunspecStorageThingModbusAddressParamTypeId, modbusStartRegister)); descriptor.setParams(params); emit autoThingsAppeared({descriptor}); } break; @@ -556,9 +594,7 @@ void IntegrationPluginSunSpec::onSunSpecModelSearchFinished(const QHashdeviceModel() << connection->serialNumber(); return; } - qCDebug(dcSunSpec()) << "On sunspec model search finished, models:" << modelIds.count(); - } void IntegrationPluginSunSpec::onWriteRequestExecuted(QUuid requestId, bool success) @@ -589,7 +625,6 @@ void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::Inv thing->setStateValue(sunspecThreePhaseInverterTotalCurrentStateTypeId, inverterData.acCurrent); thing->setStateValue(sunspecThreePhaseInverterCabinetTemperatureStateTypeId, inverterData.cabinetTemperature); - if (thing->thingClassId() == sunspecSplitPhaseMeterThingClassId) { thing->setStateValue(sunspecSplitPhaseInverterPhaseANVoltageStateTypeId, inverterData.phaseVoltageAN); @@ -609,7 +644,6 @@ void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::Inv thing->setStateValue(sunspecThreePhaseInverterPhaseCCurrentStateTypeId, inverterData.phaseCCurrent); } - switch(inverterData.operatingState) { case SunSpec::SunSpecOperatingState::Off: thing->setStateValue(sunspecThreePhaseInverterOperatingStateStateTypeId, "Off"); @@ -689,7 +723,7 @@ void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::Inv } } -void IntegrationPluginSunSpec::onStorageDataReceived(const SunSpecStorage::StorageData &storageData) +void IntegrationPluginSunSpec::onStorageDataReceived(const SunSpecStorage::StorageData &mandatory, const SunSpecStorage::StorageDataOptional &optional) { SunSpecStorage *storage = static_cast(sender()); Thing *thing = m_sunSpecStorages.key(storage); @@ -698,7 +732,37 @@ void IntegrationPluginSunSpec::onStorageDataReceived(const SunSpecStorage::Stora return; } thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true); - thing->setStateValue(sunspecStorageStorageStateStateTypeId, storageData.chargingState); + + thing->setStateValue(sunspecStorageChargingRateStateTypeId, mandatory.maxChargeRate); + thing->setStateValue(sunspecStorageDischargingRateStateTypeId, mandatory.maxDischargeRate); + + bool charging = false; + switch (optional.chargeSatus) { + 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.currentlyAvailableEnergy; + thing->setStateValue(sunspecStorageBatteryLevelStateTypeId, batteryLevel); + thing->setStateValue(sunspecStorageBatteryCriticalStateTypeId, (batteryLevel < 5 && !charging)); } void IntegrationPluginSunSpec::onMeterDataReceived(const SunSpecMeter::MeterData &meterData) diff --git a/sunspec/integrationpluginsunspec.h b/sunspec/integrationpluginsunspec.h index fedbb21..5a1f9aa 100644 --- a/sunspec/integrationpluginsunspec.h +++ b/sunspec/integrationpluginsunspec.h @@ -79,6 +79,10 @@ private: bool checkIfThingExists(uint modelId, uint modbusAddress); + void setupInverter(ThingSetupInfo *info); + void setupMeter(ThingSetupInfo *info); + void setupStorage(ThingSetupInfo *info); + private slots: void onRefreshTimer(); @@ -93,7 +97,7 @@ private slots: void onWriteRequestError(QUuid requestId, const QString &error); void onInverterDataReceived(const SunSpecInverter::InverterData &inverterData); - void onStorageDataReceived(const SunSpecStorage::StorageData &storageData); + 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 index bb081cc..b28d92d 100644 --- a/sunspec/integrationpluginsunspec.json +++ b/sunspec/integrationpluginsunspec.json @@ -998,9 +998,9 @@ }, { "id": "da2b19c5-0f48-49d1-93f0-abdc0051407d", - "name": "storageState", - "displayName": "State", - "displayNameEvent": "State changed", + "name": "storageStatus", + "displayName": "Status", + "displayNameEvent": "Status changed", "type": "QString", "possibleValues": [ "Off", @@ -1025,13 +1025,23 @@ }, { "id": "1f530f79-c0d2-466b-90e1-79149e34d92f", - "name": "enableChargingLimit", - "displayName": "Charging limit", - "displayNameEvent": "Charging limit changed", + "name": "enableCharging", + "displayName": "Charging", + "displayNameEvent": "Charging changed", + "displayNameAction": "Enable charging", "type": "bool", "defaultValue": false, - "writable": true, - "displayNameAction": "Enable Charging Limit" + "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", @@ -1039,30 +1049,20 @@ "displayName": "Charging rate", "displayNameEvent": "Charging rate changed", "type": "int", - "minValue": -100, + "minValue": 0, "maxValue": 100, "unit": "Percentage", "defaultValue": false, "writable": true, "displayNameAction": "Set charging rate" }, - { - "id": "bc99a159-815a-40ab-a6e8-b46f315305f7", - "name": "enableDischargingLimit", - "displayName": "Discharging limit", - "displayNameEvent": "Discharging limit changed", - "displayNameAction": "Enable Discharging Limit", - "type": "bool", - "defaultValue": false, - "writable": true - }, { "id": "6068f030-acce-44a2-b95f-bd00dd5ca760", "name": "dischargingRate", "displayName": "Discharging rate", "displayNameEvent": "Discharging rate changed", "type": "int", - "minValue": -100, + "minValue": 0, "maxValue": 100, "unit": "Percentage", "defaultValue": false, diff --git a/sunspec/sunspec.cpp b/sunspec/sunspec.cpp index 0dd3401..071666c 100644 --- a/sunspec/sunspec.cpp +++ b/sunspec/sunspec.cpp @@ -226,20 +226,20 @@ void SunSpec::readModelHeader(uint modbusAddress) qCDebug(dcSunSpec()) << "SunSpec: Received model header response. Model ID:" << modelId << "length" << length; modelHeaderReceived(modbusAddress, modelId, length); } else { - qCWarning(dcSunSpec()) << "SunSpec: Read response error:" << reply->error(); + qCWarning(dcSunSpec()) << "SunSpec: Read model header response error:" << reply->error(); } }); connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error) { - qCWarning(dcSunSpec()) << "SunSpec: Modbus reply 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 error: " << m_modbusTcpClient->errorString(); + qCWarning(dcSunSpec()) << "SunSpec: Read model header error: " << m_modbusTcpClient->errorString(); delete reply; // broadcast replies return immediately return; } } else { - qCWarning(dcSunSpec()) << "SunSpec: Read error: " << m_modbusTcpClient->errorString(); + qCWarning(dcSunSpec()) << "SunSpec: Read model header error: " << m_modbusTcpClient->errorString(); return; } } @@ -248,6 +248,11 @@ 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)) { diff --git a/sunspec/sunspecinverter.cpp b/sunspec/sunspecinverter.cpp index bee5c8d..7cc97f1 100644 --- a/sunspec/sunspecinverter.cpp +++ b/sunspec/sunspecinverter.cpp @@ -53,10 +53,12 @@ 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) { - qCDebug(dcSunSpec()) << "SunSpecInverter: Model Header received, modbus address:" << modbusAddress << "model Id:" << modelId << "length:" << length; - m_modelLength = length; - emit initFinished(true); - m_initFinishedSuccess = true; + 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) { @@ -67,12 +69,13 @@ void SunSpecInverter::init() void SunSpecInverter::getInverterModelDataBlock() { - // TODO check map length to modbus max value + qCDebug(dcSunSpec()) << "SunSpecInverter: get inverter model data block, modbus register" << m_modelModbusStartRegister << "length" << m_modelLength; m_connection->readModelDataBlock(m_modelModbusStartRegister, m_modelLength); } void SunSpecInverter::getInverterModelHeader() { + qCDebug(dcSunSpec()) << "SunSpecInverter: get inverter model header, modbus register" << m_modelModbusStartRegister; m_connection->readModelHeader(m_modelModbusStartRegister); } @@ -83,7 +86,7 @@ void SunSpecInverter::onModelDataBlockReceived(SunSpec::ModelId mapId, uint mapL return; } if (mapLength < m_modelLength) { - qCDebug(dcSunSpec()) << "SunSpecInverter: on modbus map received, map length ist too short" << mapLength; + qCDebug(dcSunSpec()) << "SunSpecInverter: on modbus map received, map length is too short" << mapLength; //return; } InverterData inverterData; diff --git a/sunspec/sunspecmeter.cpp b/sunspec/sunspecmeter.cpp index e154d1b..9e7c488 100644 --- a/sunspec/sunspecmeter.cpp +++ b/sunspec/sunspecmeter.cpp @@ -52,26 +52,30 @@ 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) { - qCDebug(dcSunSpec()) << "SunSpecMeter: Model Header received, modbus address:" << modbusAddress << "model Id:" << modelId << "length:" << length; - m_modelLength = length; - emit initFinished(true); - m_initFinishedSuccess = true; + 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); - } + if (!m_initFinishedSuccess) { + emit initFinished(false); + } }); } void SunSpecMeter::getMeterModelDataBlock() { - m_connection->readModelDataBlock(m_modelModbusStartRegister, m_modelLength); + 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) diff --git a/sunspec/sunspecstorage.cpp b/sunspec/sunspecstorage.cpp index 28600cc..5648ca5 100644 --- a/sunspec/sunspecstorage.cpp +++ b/sunspec/sunspecstorage.cpp @@ -49,27 +49,31 @@ SunSpec::ModelId SunSpecStorage::modelId() void SunSpecStorage::init() { qCDebug(dcSunSpec()) << "SunSpecStorage: Init"; - m_connection->readModelHeader(m_modelModbusStartRegister); + getStorageModelHeader(); connect(m_connection, &SunSpec::modelHeaderReceived, this, [this] (uint modbusAddress, SunSpec::ModelId modelId, uint length) { - qCDebug(dcSunSpec()) << "SunSpecStorager: Model header received, modbus address:" << modbusAddress << "model Id:" << modelId << "length:" << length; - m_modelLength = length; - emit initFinished(true); - m_initFinishedSuccess = true; + 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); - } + 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); } @@ -81,7 +85,7 @@ QUuid SunSpecStorage::setGridCharging(bool enabled) PV (charging from grid 0 disabled) GRID (charging from 1 grid enabled*/ - uint registerAddress = m_modelModbusStartRegister + Model124::Model124ChaGriSet; + uint registerAddress = m_modelModbusStartRegister + Model124Optional::Model124ChargeGridSet; quint16 value = enabled; return m_connection->writeHoldingRegister(registerAddress, value); } @@ -89,8 +93,8 @@ QUuid SunSpecStorage::setGridCharging(bool enabled) 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) << StorageControlBitFieldCharge) | - (static_cast(dischargingEnabled) << StorageControlBitFieldDischarge)) ; + quint16 value = ((static_cast(chargingEnabled)) | + (static_cast(dischargingEnabled) << 1)) ; uint modbusRegister = m_modelModbusStartRegister + Model124::Model124ActivateStorageControlMode; return m_connection->writeHoldingRegister(modbusRegister, value); @@ -124,22 +128,38 @@ void SunSpecStorage::onModelDataBlockReceived(SunSpec::ModelId modelId, uint len switch (modelId) { case SunSpec::ModelIdStorage: { - StorageData storageData; + StorageData mandatory; qCDebug(dcSunSpec()) << "SunSpecStorage: Storage model received:"; qCDebug(dcSunSpec()) << " - Setpoint maximum charge" << data[Model124SetpointMaximumCharge]; qCDebug(dcSunSpec()) << " - Setpoint maximum charging rate" << data[Model124SetpointMaximumChargingRate]; qCDebug(dcSunSpec()) << " - Setpoint maximum discharge rate" << data[Model124SetpointMaximumDischargeRate]; qCDebug(dcSunSpec()) << " - Active storage control mode" << data[Model124ActivateStorageControlMode]; - qCDebug(dcSunSpec()) << " - Currently available energy" << data[Model124CurrentlyAvailableEnergy]; - storageData.chargingState = ChargingState(data[Model124::Model124ChargeStatus]); - qCDebug(dcSunSpec()) << " - Setpoint maximum charging rate" << data[Model124ChaGriSet]; - qCDebug(dcSunSpec()) << " - Setpoint maximum charging rate" << data[Model124ScaleFactorMaximumCharge]; - qCDebug(dcSunSpec()) << " - Setpoint maximum charging rate" << data[Model124ScaleFactorMaximumChargeDischargeRate]; - qCDebug(dcSunSpec()) << " - Setpoint maximum charging rate" << data[Model124ScaleFactorAvailableEnergyPercent]; - emit storageDataReceived(storageData); + qCDebug(dcSunSpec()) << " - ChaGriSet" << data[Model124ChargeGridSet]; + qCDebug(dcSunSpec()) << " - Scale factor max charge" << data[Model124ScaleFactorMaximumCharge]; + qCDebug(dcSunSpec()) << " - Scale factor max charge/discharge rate" << data[Model124ScaleFactorMaximumChargeDischargeRate]; + qCDebug(dcSunSpec()) << " - Scale factor" << data[Model124ScaleFactorAvailableEnergyPercent]; + mandatory.maxCharge = m_connection->convertValueWithSSF(data[Model124SetpointMaximumCharge], data[Model124ScaleFactorMaximumChargeDischargeRate]); + mandatory.maxChargeRate = m_connection->convertValueWithSSF(data[Model124SetpointMaximumChargingRate], data[Model124ScaleFactorPercentChargeDischargeRate]); + mandatory.chargingEnabled = data[Model124ActivateStorageControlMode]&0x01; + mandatory.dischargingEnabled = data[Model124ActivateStorageControlMode]&0x02; + mandatory.maxDischargeRate = m_connection->convertValueWithSSF(data[Model124SetpointMaximumDischargeRate], data[Model124ScaleFactorPercentChargeDischargeRate]); + + StorageDataOptional optional; + optional.chargeSatus = ChargingStatus(data[Model124ChargeStatus]); + optional.batteryVoltage = m_connection->convertValueWithSSF(data[Model124InternalBatteryVoltage], data[Model124ScaleFactorBatteryVoltage]); + optional.storageAvailable = m_connection->convertValueWithSSF(data[Model124StorageAvailableAH], data[Model124ScaleFactorMaximumChargingVA]); + optional.gridChargingEnabled = (data[Model124ChargeGridSet] == 1); + optional.currentlyAvailableEnergy = m_connection->convertValueWithSSF(data[Model124CurrentlyAvailableEnergyPercent], data[Model124ScaleFactorAvailableEnergyPercent]); + //qCDebug(dcSunSpec()) << " - Currently available energy" << data[Model124CurrentlyAvailableEnergy]; + //mandatory.chargingState = ChargingStatus(data[Model124ChargeStatus]); + //qCDebug(dcSunSpec()) << " - Charging state" << mandatory.chargingState; + + emit storageDataReceived(mandatory, optional); + } break; - case SunSpec::ModelIdBatteryBaseModel: + case SunSpec::ModelIdBatteryBaseModel: case SunSpec::ModelIdLithiumIonBatteryModel: { + qCDebug(dcSunSpec()) << "Model not yet supported"; } default: break; diff --git a/sunspec/sunspecstorage.h b/sunspec/sunspecstorage.h index 1cb7f1c..87e316c 100644 --- a/sunspec/sunspecstorage.h +++ b/sunspec/sunspecstorage.h @@ -50,11 +50,12 @@ public: QUuid setChargingRate(int rate); QUuid setStorageControlMode(bool chargingEnabled, bool dischargingEnabled); - enum StorageControlBitField { - StorageControlBitFieldCharge = 0, - StorageControlBitFieldDischarge = 1 + enum StorageControl { + StorageControlHold = 0, + StorageControlCharge = 1, + StorageControlDischarge = 2, }; - Q_ENUM(StorageControlBitField) + Q_ENUM(StorageControl) enum GridCharge { PV = 0, @@ -62,33 +63,69 @@ public: }; Q_ENUM(GridCharge) - enum ChargingState { - ChargingStateOff, - ChargingStateEmpty, - ChargingStateDischarging, - ChargingStateCharging, - ChargingStateFull, - ChargingStateHolding, - ChargingStateTesting + enum ChargingStatus { + ChargingStatusOff, + ChargingStatusEmpty, + ChargingStatusDischarging, + ChargingStatusCharging, + ChargingStatusFull, + ChargingStatusHolding, + ChargingStatusTesting }; - Q_ENUM(ChargingState) + Q_ENUM(ChargingStatus) - enum Model124 { // Mandatory register + enum Model124 { // Mandatory registers Model124SetpointMaximumCharge = 0, Model124SetpointMaximumChargingRate = 1, Model124SetpointMaximumDischargeRate = 2, Model124ActivateStorageControlMode = 3, - Model124CurrentlyAvailableEnergy = 6, - Model124ChargeStatus = 9, - Model124ChaGriSet = 15, Model124ScaleFactorMaximumCharge = 16, Model124ScaleFactorMaximumChargeDischargeRate = 17, - Model124ScaleFactorAvailableEnergyPercent = 20 }; Q_ENUM(Model124) + enum Model124Optional { // Optional registers + Model124MaximumChargingVA = 4, // VAChaMax + Model124MinimumReserveStoragePercent = 5, // MinRsvPct + Model124CurrentlyAvailableEnergyPercent = 6, // ChaState + Model124StorageAvailableAH = 7, // StorAval + Model124InternalBatteryVoltage = 8, // InBatV + Model124ChargeStatus = 9, // ChaSt + Model124MaxDischargingRatePercent = 10, // OutWRte + Model124MaxChargingRatePercent = 11, + Model124ChargeDischargeTimeWindow = 12, + Model124ChargeDischargeTimeout = 13, + Model124RampTime = 14, // InOutWRte_RmpTms + Model124ChargeGridSet = 15, // ChGriSet + Model124ScaleFactorMaximumChargingVA = 18, + Model124ScaleFactorMinimumReservePercentage = 19, + Model124ScaleFactorAvailableEnergyPercent = 20, + Model124ScaleFactorStateCharge = 21, + Model124ScaleFactorBatteryVoltage = 22, + Model124ScaleFactorPercentChargeDischargeRate = 23 + }; + Q_ENUM(Model124Optional) + struct StorageData { - ChargingState chargingState; + double maxCharge; // [W] Setpoint for maximum charge. + double maxChargeRate; // [%] Setpoint for maximum charging rate. Default is MaxChaRte. + double maxDischargeRate; // [%] Setpoint for maximum discharge rate. Default is MaxDisChaRte. + bool chargingEnabled; + bool dischargingEnabled; + }; + + struct StorageDataOptional { + // [VA] Setpoint for maximum charging VA. + // [& ]Setpoint for minimum reserve for storage as a percentage of the nominal maximum storage. + double currentlyAvailableEnergy; // [%] Currently available energy as a percent of the capacity rating. + double storageAvailable; // [Ah] State of charge (ChaState) minus storage reserve (MinRsvPct) times capacity rating (AhrRtg). + double batteryVoltage; // [V] Internal battery voltage. + ChargingStatus chargeSatus; // Charge status of storage device. Enumerated value. + // [%] Percent of max discharge rate. + // [%] Percent of max charging rate. + // [s] Time window for charge/discharge rate change. + // [s] Timeout period for charge/discharge rate. + // [s] Ramp time for moving from current setpoint to new setpoint. bool gridChargingEnabled; }; @@ -104,7 +141,7 @@ private slots: signals: void initFinished(bool success); - void storageDataReceived(const StorageData &data); + void storageDataReceived(const StorageData &mandatory, const StorageDataOptional &optional); }; #endif // SUNSPECSTORAGE_H From 1020dd90a4f11f9904dffdb9b534d5a46033424c Mon Sep 17 00:00:00 2001 From: Boernsman Date: Wed, 3 Feb 2021 16:34:19 +0100 Subject: [PATCH 10/16] fixed sunspec inverter event state --- sunspec/integrationpluginsunspec.cpp | 105 ++++++++++++++-------- sunspec/integrationpluginsunspec.h | 1 - sunspec/sunspec.h | 19 ---- sunspec/sunspecinverter.cpp | 77 ++++++++++------ sunspec/sunspecinverter.h | 68 +++++++++++++- sunspec/sunspecmeter.cpp | 65 ++++++++++++-- sunspec/sunspecmeter.h | 125 ++++++++++++++++++++------ sunspec/sunspecstorage.cpp | 11 +-- sunspec/sunspecstringcombiner.cpp | 90 ------------------- sunspec/sunspecstringcombiner.h | 128 --------------------------- sunspec/sunspectracker.cpp | 42 --------- sunspec/sunspectracker.h | 77 ---------------- 12 files changed, 349 insertions(+), 459 deletions(-) delete mode 100644 sunspec/sunspecstringcombiner.cpp delete mode 100644 sunspec/sunspecstringcombiner.h delete mode 100644 sunspec/sunspectracker.cpp delete mode 100644 sunspec/sunspectracker.h diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index 4fbfa66..a4ebf2f 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -564,6 +564,8 @@ void IntegrationPluginSunSpec::onFoundSunSpecModel(SunSpec::ModelId modelId, int 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()); @@ -617,6 +619,24 @@ void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::Inv 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_acPowerStateTypeIds.value(thing->thingClassId()), inverterData.acPower/1000.00); thing->setStateValue(m_acEnergyStateTypeIds.value(thing->thingClassId()), inverterData.acEnergy/1000.00); @@ -625,7 +645,7 @@ void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::Inv thing->setStateValue(sunspecThreePhaseInverterTotalCurrentStateTypeId, inverterData.acCurrent); thing->setStateValue(sunspecThreePhaseInverterCabinetTemperatureStateTypeId, inverterData.cabinetTemperature); - if (thing->thingClassId() == sunspecSplitPhaseMeterThingClassId) { + if (thing->thingClassId() == sunspecSplitPhaseInverterThingClassId) { thing->setStateValue(sunspecSplitPhaseInverterPhaseANVoltageStateTypeId, inverterData.phaseVoltageAN); thing->setStateValue(sunspecSplitPhaseInverterPhaseBNVoltageStateTypeId, inverterData.phaseVoltageBN); @@ -633,7 +653,7 @@ void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::Inv thing->setStateValue(sunspecSplitPhaseInverterPhaseACurrentStateTypeId, inverterData.phaseACurrent); thing->setStateValue(sunspecSplitPhaseInverterPhaseBCurrentStateTypeId, inverterData.phaseBCurrent); - } else if (thing->thingClassId() == sunspecThreePhaseMeterThingClassId) { + } else if (thing->thingClassId() == sunspecThreePhaseInverterThingClassId) { thing->setStateValue(sunspecThreePhaseInverterPhaseANVoltageStateTypeId, inverterData.phaseVoltageAN); thing->setStateValue(sunspecThreePhaseInverterPhaseBNVoltageStateTypeId, inverterData.phaseVoltageBN); @@ -671,55 +691,41 @@ void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::Inv break; } - switch(inverterData.event) { - case SunSpec::SunSpecEvent1::OVER_TEMP: + //FIXME: Event1 may have multiple states at once. Only one is stated in nymea + if (inverterData.event1.overTemperature) { thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Over temperature"); - break; - case SunSpec::SunSpecEvent1::UNDER_TEMP: + } else if (inverterData.event1.underTemperature) { thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Under temperature"); - break; - case SunSpec::SunSpecEvent1::GroundFault: + } else if (inverterData.event1.groundFault) { thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Ground fault"); - break; - case SunSpec::SunSpecEvent1::MEMORY_LOSS: + } else if (inverterData.event1.memoryLoss) { thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Memory loss"); - break; - case SunSpec::SunSpecEvent1::AC_OVER_VOLT: + } else if (inverterData.event1.acOverVolt) { thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "AC voltage above limit"); - break; - case SunSpec::SunSpecEvent1::CABINET_OPEN: + } else if (inverterData.event1.cabinetOpen) { thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Cabinet open"); - break; - case SunSpec::SunSpecEvent1::AC_DISCONNECT: + } else if (inverterData.event1.acDisconnect) { thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "AC disconnect open"); - break; - case SunSpec::SunSpecEvent1::AC_UNDER_VOLT: + } else if (inverterData.event1.acUnderVolt) { thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "AC voltage under limit"); - break; - case SunSpec::SunSpecEvent1::DC_DISCONNECT: + } else if (inverterData.event1.dcDicconnect) { thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "DC disconnect open"); - break; - case SunSpec::SunSpecEvent1::DcOverVolatage: + } else if (inverterData.event1.dcOverVoltage) { thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "DC over voltage"); - break; - case SunSpec::SunSpecEvent1::OVER_FREQUENCY: + } else if (inverterData.event1.overFrequency) { thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Frequency above limit"); - break; - case SunSpec::SunSpecEvent1::GRID_DISCONNECT: + } else if (inverterData.event1.gridDisconnect) { thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Grid disconnect"); - break; - case SunSpec::SunSpecEvent1::HW_TEST_FAILURE: + } else if (inverterData.event1.hwTestFailure) { thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Hardware test failure"); - break; - case SunSpec::SunSpecEvent1::MANUAL_SHUTDOWN: + } else if (inverterData.event1.manualShutdown) { thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Manual shutdown"); - break; - case SunSpec::SunSpecEvent1::UNDER_FREQUENCY: + } else if (inverterData.event1.underFrequency) { thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Frequency under limit"); - break; - case SunSpec::SunSpecEvent1::BLOWN_STRING_FUSE: + } else if (inverterData.event1.blownStringFuse) { thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Blown string fuse on input"); - break; + } else { + thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "None"); } } @@ -774,5 +780,34 @@ void IntegrationPluginSunSpec::onMeterDataReceived(const SunSpecMeter::MeterData if (!thing) { return; } + thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true); + + 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(sunspecThreePhaseMeterTotalCurrentStateTypeId, meterData.totalAcCurrent); + thing->setStateValue(sunspecThreePhaseMeterPhaseACurrentStateTypeId, meterData.phaseACurrent); + thing->setStateValue(sunspecThreePhaseMeterPhaseBCurrentStateTypeId, meterData.phaseBCurrent); + thing->setStateValue(sunspecThreePhaseMeterPhaseCCurrentStateTypeId, meterData.phaseCCurrent); + thing->setStateValue(sunspecThreePhaseMeterLnACVoltageStateTypeId, meterData.voltageLN); + thing->setStateValue(sunspecThreePhaseMeterTotalRealPowerEventTypeId, meterData.totalRealPower); + thing->setStateValue(sunspecThreePhaseMeterEnergyExportedStateTypeId, meterData.totalRealEnergyExported); + thing->setStateValue(sunspecThreePhaseMeterEnergyImportedStateTypeId, meterData.totalRealEnergyImported); + thing->setStateValue(m_frequencyStateTypeIds.value(thing->thingClassId()), meterData.frequency); } diff --git a/sunspec/integrationpluginsunspec.h b/sunspec/integrationpluginsunspec.h index 5a1f9aa..b860ddd 100644 --- a/sunspec/integrationpluginsunspec.h +++ b/sunspec/integrationpluginsunspec.h @@ -39,7 +39,6 @@ #include "sunspecinverter.h" #include "sunspecstorage.h" #include "sunspecmeter.h" -#include "sunspectracker.h" #include diff --git a/sunspec/sunspec.h b/sunspec/sunspec.h index 2c6a038..8021df4 100644 --- a/sunspec/sunspec.h +++ b/sunspec/sunspec.h @@ -67,25 +67,6 @@ public: }; Q_ENUM(SunSpecOperatingState) - enum SunSpecEvent1 { - GroundFault = 0, - DcOverVolatage, - AC_DISCONNECT, - DC_DISCONNECT, - GRID_DISCONNECT, - CABINET_OPEN, - MANUAL_SHUTDOWN, - OVER_TEMP, - OVER_FREQUENCY, - UNDER_FREQUENCY, - AC_OVER_VOLT, - AC_UNDER_VOLT, - BLOWN_STRING_FUSE, - UNDER_TEMP, - MEMORY_LOSS, - HW_TEST_FAILURE - }; - Q_ENUM(SunSpecEvent1) enum ModelId { ModelIdCommon = 1, diff --git a/sunspec/sunspecinverter.cpp b/sunspec/sunspecinverter.cpp index 7cc97f1..9811a98 100644 --- a/sunspec/sunspecinverter.cpp +++ b/sunspec/sunspecinverter.cpp @@ -73,46 +73,70 @@ void SunSpecInverter::getInverterModelDataBlock() m_connection->readModelDataBlock(m_modelModbusStartRegister, m_modelLength); } +SunSpecInverter::SunSpecEvent1 SunSpecInverter::bitfieldToSunSpecEvent1(quint16 register1, quint16 register2) +{ + SunSpecEvent1 event1; + Q_UNUSED(register2); + event1.groundFault = ((register1 & (0x01 << 0)) != 0); + event1.dcOverVoltage = ((register1 & (0x01 << 1)) != 0); + event1.acDisconnect = ((register1 & (0x01 << 2)) != 0); + event1.dcDicconnect = ((register1 & (0x01 << 3)) != 0); + event1.gridDisconnect = ((register1 & (0x01 << 4)) != 0); + event1.cabinetOpen = ((register1 & (0x01 << 5)) != 0); + event1.manualShutdown = ((register1 & (0x01 << 6)) != 0); + event1.overTemperature = ((register1 & (0x01 << 7)) != 0); + event1.overFrequency = ((register1 & (0x01 << 8)) != 0); + event1.underFrequency = ((register1 & (0x01 << 9)) != 0); + event1.acOverVolt = ((register1 & (0x01 << 10)) != 0); + event1.acUnderVolt = ((register1 & (0x01 << 11)) != 0); + event1.blownStringFuse = ((register1 & (0x01 << 12)) != 0); + event1.underTemperature = ((register1 & (0x01 << 13)) != 0); + event1.memoryLoss = ((register1 & (0x01 << 14)) != 0); + event1.hwTestFailure = ((register1 & (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 mapId, uint mapLength, QVector data) +void SunSpecInverter::onModelDataBlockReceived(SunSpec::ModelId modelId, uint length, QVector data) { - Q_UNUSED(mapLength) - if (mapId != m_id) { + Q_UNUSED(length) + if (modelId != m_id) { return; } - if (mapLength < m_modelLength) { - qCDebug(dcSunSpec()) << "SunSpecInverter: on modbus map received, map length is too short" << mapLength; - //return; + if (length < m_modelLength) { + qCDebug(dcSunSpec()) << "SunSpecInverter: on model data block received, model length is too short" << length; + return; } InverterData inverterData; - switch (mapId) { + qCDebug(dcSunSpec()) << "SunSpecInverter: Received" << modelId; + switch (modelId) { case SunSpec::ModelIdInverterSinglePhase: case SunSpec::ModelIdInverterSplitPhase: case SunSpec::ModelIdInverterThreePhase: { - inverterData.acCurrent= m_connection->convertValueWithSSF(data[Model10X::Model10XAcCurrent], data[Model10X::Model10XAmpereScaleFactor]); inverterData.acPower = m_connection->convertValueWithSSF(data[Model10X::Model10XACPower], data[Model10X::Model10XWattScaleFactor]); inverterData.lineFrequency = m_connection->convertValueWithSSF(data[Model10X::Model10XLineFrequency], data[Model10X::Model10XHerzScaleFactor]); - inverterData.phaseACurrent = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseACurrent], data[Model10X::Model10XAmpereScaleFactor]); - inverterData.phaseBCurrent = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseBCurrent], data[Model10X::Model10XAmpereScaleFactor]); - inverterData.phaseCCurrent = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseCCurrent], data[Model10X::Model10XAmpereScaleFactor]); - - inverterData.phaseVoltageAN = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageAN], data[Model10X::Model10XVoltageScaleFactor]); - inverterData.phaseVoltageBN = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageBN], data[Model10X::Model10XVoltageScaleFactor]); - inverterData.phaseVoltageCN = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageCN], data[Model10X::Model10XVoltageScaleFactor]); + quint16 ampereScaleFactor = data[Model10X::Model10XAmpereScaleFactor]; + inverterData.phaseACurrent = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseACurrent], ampereScaleFactor); + inverterData.phaseBCurrent = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseBCurrent], ampereScaleFactor); + inverterData.phaseCCurrent = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseCCurrent], ampereScaleFactor); + quint16 voltageScaleFactor = data[Model10X::Model10XVoltageScaleFactor]; + inverterData.phaseVoltageAN = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageAN], voltageScaleFactor); + inverterData.phaseVoltageBN = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageBN], voltageScaleFactor); + inverterData.phaseVoltageCN = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageCN], voltageScaleFactor); quint32 acEnergy = ((static_cast(data.value(Model10X::Model10XAcEnergy))<<16)|static_cast(data.value(Model10X::Model10XAcEnergy+1))); inverterData.acEnergy = m_connection->convertValueWithSSF(acEnergy, data[Model10X::Model10XWattHoursScaleFactor]); inverterData.cabinetTemperature = m_connection->convertValueWithSSF(data[Model10X::Model10XCabinetTemperature], data[Model10X::Model10XTemperatureScaleFactor]); - inverterData.event = SunSpec::SunSpecEvent1(data[Model10X::Model10XEvent1]); + inverterData.event1 = bitfieldToSunSpecEvent1(data[Model10X::Model10XEvent1], data[Model10X::Model10XEvent1+1]); inverterData.operatingState = SunSpec::SunSpecOperatingState(data[Model10X::Model10XOperatingState]); emit inverterDataReceived(inverterData); @@ -123,21 +147,20 @@ void SunSpecInverter::onModelDataBlockReceived(SunSpec::ModelId mapId, uint mapL inverterData.acCurrent = m_connection->convertFloatValues(data[Model11X::Model11XAcCurrent], data[Model11X::Model11XAcCurrent+1]); - inverterData.phaseCCurrent = m_connection->convertFloatValues(data[Model11X::Model11XPhaseCCurrent], data[Model11X::Model11XPhaseCCurrent+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.phaseBCurrent = m_connection->convertFloatValues(data[Model11X::Model11XPhaseBCurrent], data[Model11X::Model11XPhaseBCurrent+1]); - inverterData.phaseVoltageBN = m_connection->convertFloatValues(data[Model11X::Model11XPhaseVoltageBN], data[Model11X::Model11XPhaseVoltageBN+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.phaseACurrent = m_connection->convertFloatValues(data[Model11X::Model11XPhaseACurrent], data[Model11X::Model11XPhaseACurrent+1]); - inverterData.phaseVoltageAN = m_connection->convertFloatValues(data[Model11X::Model11XPhaseVoltageAN], data[Model11X::Model11XPhaseVoltageAN+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.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.event = SunSpec::SunSpecEvent1(data[Model11X::Model11XEvent1]); + 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; diff --git a/sunspec/sunspecinverter.h b/sunspec/sunspecinverter.h index 918cc75..712983b 100644 --- a/sunspec/sunspecinverter.h +++ b/sunspec/sunspecinverter.h @@ -77,6 +77,71 @@ public: 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; @@ -92,7 +157,7 @@ public: float lineFrequency; float acEnergy; float cabinetTemperature; // in degree Celsius - SunSpec::SunSpecEvent1 event; + SunSpecEvent1 event1; SunSpec::SunSpecOperatingState operatingState; }; @@ -108,6 +173,7 @@ private: uint m_modelModbusStartRegister = 40000; bool m_initFinishedSuccess = false; + SunSpecEvent1 bitfieldToSunSpecEvent1(quint16 register1, quint16 register2); void getInverterModelHeader(); private slots: diff --git a/sunspec/sunspecmeter.cpp b/sunspec/sunspecmeter.cpp index 9e7c488..093c81b 100644 --- a/sunspec/sunspecmeter.cpp +++ b/sunspec/sunspecmeter.cpp @@ -83,20 +83,67 @@ void SunSpecMeter::onModelDataBlockReceived(SunSpec::ModelId modelId, uint lengt if (modelId != m_id) { return; } - Q_UNUSED(length) - Q_UNUSED(data) + + 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::ModelIdSinglePhaseMeterFloat: { - - } break; case SunSpec::ModelIdSplitSinglePhaseMeter: - case SunSpec::ModelIdSplitSinglePhaseMeterFloat: { + case SunSpec::ModelIdDeltaConnectThreePhaseMeter: + case SunSpec::ModelIdWyeConnectThreePhaseMeter: { + + MeterData meterData; + quint16 currentScaleFactor = data[Model20XCurrentScaleFactor]; + meterData.totalAcCurrent = m_connection->convertValueWithSSF(data[Model20XTotalAcCurrent], currentScaleFactor); + meterData.phaseACurrent = m_connection->convertValueWithSSF(data[Model20XPhaseACurrent], currentScaleFactor); + meterData.phaseBCurrent = m_connection->convertValueWithSSF(data[Model20XPhaseBCurrent], currentScaleFactor); + meterData.phaseCCurrent = m_connection->convertValueWithSSF(data[Model20XPhaseCCurrent], currentScaleFactor); + quint16 voltageScaleFactor = data[Model20XVoltageScaleFactor]; + meterData.voltageLN = m_connection->convertValueWithSSF(data[Model20XVoltageLN], voltageScaleFactor); + meterData.phaseVoltageAN = m_connection->convertValueWithSSF(data[Model20XPhaseVoltageAN], voltageScaleFactor); + meterData.phaseVoltageBN = m_connection->convertValueWithSSF(data[Model20XPhaseVoltageBN], voltageScaleFactor); + meterData.phaseVoltageCN = m_connection->convertValueWithSSF(data[Model20XPhaseVoltageCN], voltageScaleFactor); + meterData.voltageLL = m_connection->convertValueWithSSF(data[Model20XVoltageLL], voltageScaleFactor); + meterData.phaseVoltageAB = m_connection->convertValueWithSSF(data[Model20XPhaseVoltageAB], voltageScaleFactor); + meterData.phaseVoltageBC = m_connection->convertValueWithSSF(data[Model20XPhaseVoltageBC], voltageScaleFactor); + meterData.phaseVoltageCA = m_connection->convertValueWithSSF(data[Model20XPhaseVoltageCA], voltageScaleFactor); + meterData.frequency = m_connection->convertValueWithSSF(data[Model20XFrequency], data[Model20XFrequencyScaleFactor]); + meterData.totalRealPower = m_connection->convertValueWithSSF(data[Model20XTotalRealPower], data[Model20XRealPowerScaleFactor]); + quint16 energyScaleFactor = data[Model20XRealEnergyScaleFactor]; + meterData.totalRealEnergyExported = m_connection->convertValueWithSSF(data[Model20XTotalRealEnergyExported], energyScaleFactor); + meterData.totalRealEnergyImported = m_connection->convertValueWithSSF(data[Model20XTotalRealEnergyImported], energyScaleFactor);; + meterData.meterEventFlags = (static_cast(data[Model20XMeterEventFlags]) << 16) | data[Model20XMeterEventFlags+1]; + emit meterDataReceived(meterData); } break; - case SunSpec::ModelIdWyeConnectThreePhaseMeterFloat: - case SunSpec::ModelIdDeltaConnectThreePhaseMeterFloat: { + 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: diff --git a/sunspec/sunspecmeter.h b/sunspec/sunspecmeter.h index 1c0f2df..cc664d3 100644 --- a/sunspec/sunspecmeter.h +++ b/sunspec/sunspecmeter.h @@ -38,34 +38,109 @@ class SunSpecMeter : public QObject { Q_OBJECT public: - //Model 203 = Three phase meter - enum MandatoryRegistersModel20X { - TotalAcCurrent = 2, - PhaseACurrent = 3, - PhaseBCurrent = 4, - PhaseCCurrent = 5, - CurrentScaleFactor = 6, - VoltageLN = 7, - PhaseVoltageAN = 8, - PhaseVoltageBN = 9, - PhaseVoltageCN = 10, - VoltageLL = 11, - PhaseVoltageAB = 12, - PhaseVoltageBC = 13, - PhaseVoltageCA = 14, - VoltageScaleFactor = 15, - Frequency = 16, - TotalRealPower = 18, - RealPowerScaleFactor = 22, - TotalRealEnergyExported = 38, - TotalRealEnergyImported = 46, - RealEnergyScaleFactor = 54, - MeterEventFlags = 105 + + 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 { - SunSpec::SunSpecEvent1 event; - SunSpec::SunSpecOperatingState operatingState; + 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); diff --git a/sunspec/sunspecstorage.cpp b/sunspec/sunspecstorage.cpp index 5648ca5..b40a098 100644 --- a/sunspec/sunspecstorage.cpp +++ b/sunspec/sunspecstorage.cpp @@ -121,15 +121,19 @@ QUuid SunSpecStorage::setDischargingRate(int charging) void SunSpecStorage::onModelDataBlockReceived(SunSpec::ModelId modelId, uint length, const QVector &data) { - Q_UNUSED(length) 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; - qCDebug(dcSunSpec()) << "SunSpecStorage: Storage model received:"; qCDebug(dcSunSpec()) << " - Setpoint maximum charge" << data[Model124SetpointMaximumCharge]; qCDebug(dcSunSpec()) << " - Setpoint maximum charging rate" << data[Model124SetpointMaximumChargingRate]; qCDebug(dcSunSpec()) << " - Setpoint maximum discharge rate" << data[Model124SetpointMaximumDischargeRate]; @@ -150,9 +154,6 @@ void SunSpecStorage::onModelDataBlockReceived(SunSpec::ModelId modelId, uint len optional.storageAvailable = m_connection->convertValueWithSSF(data[Model124StorageAvailableAH], data[Model124ScaleFactorMaximumChargingVA]); optional.gridChargingEnabled = (data[Model124ChargeGridSet] == 1); optional.currentlyAvailableEnergy = m_connection->convertValueWithSSF(data[Model124CurrentlyAvailableEnergyPercent], data[Model124ScaleFactorAvailableEnergyPercent]); - //qCDebug(dcSunSpec()) << " - Currently available energy" << data[Model124CurrentlyAvailableEnergy]; - //mandatory.chargingState = ChargingStatus(data[Model124ChargeStatus]); - //qCDebug(dcSunSpec()) << " - Charging state" << mandatory.chargingState; emit storageDataReceived(mandatory, optional); diff --git a/sunspec/sunspecstringcombiner.cpp b/sunspec/sunspecstringcombiner.cpp deleted file mode 100644 index 9445085..0000000 --- a/sunspec/sunspecstringcombiner.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* 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 "sunspecstringcombiner.h" -#include "extern-plugininfo.h" - -SunSpecStringCombiner::SunSpecStringCombiner(const QHostAddress &hostAddress, uint port, QObject *parent) : SunSpec(hostAddress, port, parent) -{ - connect(m_modbusTcpClient, &QModbusClient::stateChanged, this, [this] (QModbusDevice::State state) { - if (state == QModbusDevice::ConnectedState) { - qCDebug(dcSunSpec()) << "String combiner connected successfully"; - QList mapIds; - mapIds.append(BlockIdStringCombiner); - mapIds.append(BlockIdStringCombinerCurrent); - mapIds.append(BlockIdStringCombinerAdvanced); - mapIds.append(BlockIdStringCombinerCurrentAdvanced); - findModbusMap(mapIds); - } - }); - connect(this, &SunSpec::foundModbusMap, this, [this] (BlockId mapId, uint modbusRegisterAddress) { - qCDebug(dcSunSpec()) << "Read map header for mapId" << mapId << "and modbus register" << modbusRegisterAddress; - readMapHeader(modbusRegisterAddress); - }); - - connect(this, &SunSpec::mapHeaderReceived, this, [this] (uint modbusAddress, BlockId mapId, uint mapLength) { - m_id = mapId; - m_mapLength = mapLength; - m_mapModbusStartRegister = modbusAddress; - readMap(modbusAddress, mapLength); - }); - - connect(this, &SunSpec::mapReceived, this, &SunSpecStringCombiner::onModbusMapReceived); -} - -void SunSpecStringCombiner::getStringCombinerMap() -{ - -} - -void SunSpecStringCombiner::readStringCombinerMapHeader() -{ - readMap(m_mapModbusStartRegister, m_mapLength); -} - -void SunSpecStringCombiner::onModbusMapReceived(SunSpec::BlockId mapId, uint mapLength, QVector data) -{ - Q_UNUSED(data); - - switch (mapId) { - case BlockIdStringCombiner: { - int rbCount = (mapLength-14)/8; - qCDebug(dcSunSpec()) << "Map" << mapId << "Repeating Block Count" << rbCount; - } break; - case BlockIdStringCombinerCurrent: - case BlockIdStringCombinerAdvanced: - case BlockIdStringCombinerCurrentAdvanced: { - //StringCombinerData stringCombinerData; - //stringCombinerData.acCurrent= convertValueWithSSF(data[Model10X::Model10XAcCurrent], data[Model10X::Model10XAmpereScaleFactor]); - } break; - default: - break; - } -} diff --git a/sunspec/sunspecstringcombiner.h b/sunspec/sunspecstringcombiner.h deleted file mode 100644 index 7e7afe5..0000000 --- a/sunspec/sunspecstringcombiner.h +++ /dev/null @@ -1,128 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* 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 SUNSPECSTRINGCOMBINER_H -#define SUNSPECSTRINGCOMBINER_H - -#include -#include "sunspec.h" - -class SunSpecStringCombiner : public SunSpec -{ - Q_OBJECT -public: - - //Map401 length: 14 + (RB Count * 8) - //Map403 length: 16 + (RB Count * 8) - enum Map401 { - Map401CurrentScaleFactor = 0, - Map401AmpHourScaleFactor = 1, - Map401VoltageScaleFactor = 2, - Map401MaximumDCCurrentRating = 3, - Map401NumberOfInputs = 4, - Map401Events = 5, - Map401VendorDefniedEvents = 7, - Map401TotalMeasuredCurrent = 9, - Map401TotalMeteredAmpHours = 10, - Map401OutputVoltage = 12, - Map401InternalOperatingTemperature = 13 - }; - - enum Map402 { - Map402CurrentScaleFactor, - Map402AmpHourScaleFactor, - Map402VoltageScaleFactor, - Map402PowerScaleFactor, - Map402EnergyScaleFactor, - Map402MaximumDCCurrentRating, - Map402NumberOfInputs, - Map402BitmaskValueEvents, - Map402BitmaskvalueVendorDefniedEvents, - Map402TotalMeasuredCurrent, - Map402TotalMeteredAmpHours, - Map402OutputVoltage - }; - - enum Map401RB { //Repeating block - ID = 0, - Event = 1, - VendorEvent = 3, - Amps = 5, - AmpHours = 6 - }; - - enum StringCombinerEvent { - LowVoltage = 0, - LowPower, - LowEfficiency, - Current, - Voltage, - Power, - Pr, - Disconnected, - FuseFault, - CombinerFuseFault, - CombinerCabinetOpen, - Temp, - Groundfault, - ReversedPolarity, - Incompatible, - CommunicationError, - InternalError, - Theft, - ArcDetected - }; - Q_ENUM(StringCombinerEvent) - - struct StringCombinerData { - SunSpecEvent1 event; - SunSpecOperatingState operatingState; - }; - - SunSpecStringCombiner(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); - - void getStringCombinerMap(); - -private: - BlockId m_id = BlockIdStringCombiner; - uint m_mapLength = 0; - uint m_mapModbusStartRegister = 40000; - - void readStringCombinerMapHeader(); - -private slots: - void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); - -signals: - void initFinished(); - void stringCombinerDataReceived(const StringCombinerData &data); -}; - -#endif // SUNSPECSTRINGCOMBINER_H diff --git a/sunspec/sunspectracker.cpp b/sunspec/sunspectracker.cpp deleted file mode 100644 index 8d99fbf..0000000 --- a/sunspec/sunspectracker.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* 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 "sunspectracker.h" -#include "extern-plugininfo.h" - -SunSpecTracker::SunSpecTracker(const QHostAddress &hostAddress, uint port, QObject *parent) : SunSpec(hostAddress, port, parent) -{ - -} - -void SunSpecTracker::readTrackerBlockHeader() -{ - readMap(m_mapModbusStartRegister, m_mapLength); -} diff --git a/sunspec/sunspectracker.h b/sunspec/sunspectracker.h deleted file mode 100644 index 88b81a8..0000000 --- a/sunspec/sunspectracker.h +++ /dev/null @@ -1,77 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* 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 SUNSPECTRACKER_H -#define SUNSPECTRACKER_H - -#include -#include "sunspec.h" - -class SunSpecTracker : public SunSpec -{ - Q_OBJECT -public: - - enum TrackerType { - Unknown = 0, - Fixed = 1, - Horizontal = 2, - Tilted = 3, - Azimuth = 4, - Dual = 5, - Other = 99 - }; - Q_ENUM(TrackerType) - - struct TrackerData { - SunSpecEvent1 event; - SunSpecOperatingState operatingState; - }; - - SunSpecTracker(const QHostAddress &hostAddress, uint port = 502, QObject *parent = 0); - - void getTrackerMap(); - -private: - ModelId m_id = ModelIdTrackerController; - uint m_mapLength = 0; - uint m_mapModbusStartRegister = 40000; - - void readTrackerBlockHeader(); - -private slots: - //void onModbusMapReceived(BlockId mapId, uint mapLength, QVector data); - -signals: - void initFinished(); - void trackerDataReceived(const TrackerData &data); -}; - -#endif // SUNSPECTRACKER_H From 62dc0ab8251da8c76707d5ce1881ff18a2ef3499 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Wed, 3 Feb 2021 20:59:21 +0100 Subject: [PATCH 11/16] added interfaces and fixed inverter states --- sunspec/integrationpluginsunspec.cpp | 112 ++++++++++++++++---------- sunspec/integrationpluginsunspec.h | 7 +- sunspec/integrationpluginsunspec.json | 56 ++++++------- sunspec/sunspec.cpp | 7 +- sunspec/sunspec.h | 6 ++ sunspec/sunspecinverter.cpp | 35 ++++---- 6 files changed, 130 insertions(+), 93 deletions(-) diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index a4ebf2f..a2f4656 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -65,21 +65,29 @@ void IntegrationPluginSunSpec::init() m_modbusAddressParamTypeIds.insert(sunspecSplitPhaseMeterThingClassId, sunspecSplitPhaseMeterThingModbusAddressParamTypeId); m_modbusAddressParamTypeIds.insert(sunspecThreePhaseMeterThingClassId, sunspecThreePhaseMeterThingModbusAddressParamTypeId); - m_acPowerStateTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterAcPowerStateTypeId); - m_acPowerStateTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterAcPowerStateTypeId); - m_acPowerStateTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterAcPowerStateTypeId); + m_inverterCurrentPowerStateTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterCurrentPowerStateTypeId); + m_inverterCurrentPowerStateTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterCurrentPowerStateTypeId); + m_inverterCurrentPowerStateTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterCurrentPowerStateTypeId); - m_acEnergyStateTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterAcEnergyStateTypeId); - m_acEnergyStateTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterAcEnergyStateTypeId); - m_acEnergyStateTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterAcEnergyStateTypeId); + m_inverterTotalEnergyProducedStateTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterTotalEnergyProducedStateTypeId); + m_inverterTotalEnergyProducedStateTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterTotalEnergyProducedStateTypeId); + m_inverterTotalEnergyProducedStateTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterTotalEnergyProducedStateTypeId); - m_inverterOperatingStateTypeIds.insert(sunspecSinglePhaseMeterThingClassId, sunspecSinglePhaseInverterOperatingStateStateTypeId); - m_inverterOperatingStateTypeIds.insert(sunspecSplitPhaseMeterThingClassId, sunspecSplitPhaseInverterOperatingStateStateTypeId); - m_inverterOperatingStateTypeIds.insert(sunspecThreePhaseMeterThingClassId, sunspecThreePhaseInverterOperatingStateStateTypeId); + m_inverterOperatingStateTypeIds.insert(sunspecSinglePhaseInverterThingClassId, sunspecSinglePhaseInverterOperatingStateStateTypeId); + m_inverterOperatingStateTypeIds.insert(sunspecSplitPhaseInverterThingClassId, sunspecSplitPhaseInverterOperatingStateStateTypeId); + m_inverterOperatingStateTypeIds.insert(sunspecThreePhaseInverterThingClassId, sunspecThreePhaseInverterOperatingStateStateTypeId); - m_inverterErrorStateTypeIds.insert(sunspecSinglePhaseMeterThingClassId, sunspecSinglePhaseInverterErrorStateTypeId); - m_inverterErrorStateTypeIds.insert(sunspecSplitPhaseMeterThingClassId, sunspecSplitPhaseInverterErrorStateTypeId); - m_inverterErrorStateTypeIds.insert(sunspecThreePhaseMeterThingClassId, sunspecThreePhaseInverterErrorStateTypeId); + 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); @@ -638,14 +646,18 @@ void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::Inv qCDebug(dcSunSpec()) << " - Operating state" << inverterData.operatingState; thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true); - thing->setStateValue(m_acPowerStateTypeIds.value(thing->thingClassId()), inverterData.acPower/1000.00); - thing->setStateValue(m_acEnergyStateTypeIds.value(thing->thingClassId()), inverterData.acEnergy/1000.00); + thing->setStateValue(m_inverterCurrentPowerStateTypeIds.value(thing->thingClassId()), inverterData.acPower/1000.00); + thing->setStateValue(m_inverterTotalEnergyProducedStateTypeIds.value(thing->thingClassId()), inverterData.acEnergy/1000.00); thing->setStateValue(m_frequencyStateTypeIds.value(thing->thingClassId()), inverterData.lineFrequency); - thing->setStateValue(sunspecThreePhaseInverterTotalCurrentStateTypeId, inverterData.acCurrent); - thing->setStateValue(sunspecThreePhaseInverterCabinetTemperatureStateTypeId, inverterData.cabinetTemperature); + thing->setStateValue(m_inverterAcCurrentStateTypeIds.value(thing->thingClassId()), inverterData.acCurrent); + thing->setStateValue(m_inverterCabinetTemperatureStateTypeIds.value(thing->thingClassId()), inverterData.cabinetTemperature); - if (thing->thingClassId() == sunspecSplitPhaseInverterThingClassId) { + if (thing->thingClassId() == sunspecSinglePhaseInverterThingClassId) { + + thing->setStateValue(sunspecSinglePhaseInverterPhaseVoltageStateTypeId, inverterData.phaseVoltageAN); + + } else if (thing->thingClassId() == sunspecSplitPhaseInverterThingClassId) { thing->setStateValue(sunspecSplitPhaseInverterPhaseANVoltageStateTypeId, inverterData.phaseVoltageAN); thing->setStateValue(sunspecSplitPhaseInverterPhaseBNVoltageStateTypeId, inverterData.phaseVoltageBN); @@ -693,39 +705,39 @@ void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::Inv //FIXME: Event1 may have multiple states at once. Only one is stated in nymea if (inverterData.event1.overTemperature) { - thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Over temperature"); + thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Over temperature"); } else if (inverterData.event1.underTemperature) { - thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Under temperature"); + thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Under temperature"); } else if (inverterData.event1.groundFault) { - thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Ground fault"); + thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Ground fault"); } else if (inverterData.event1.memoryLoss) { - thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Memory loss"); + thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Memory loss"); } else if (inverterData.event1.acOverVolt) { - thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "AC voltage above limit"); + thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "AC voltage above limit"); } else if (inverterData.event1.cabinetOpen) { - thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Cabinet open"); + thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Cabinet open"); } else if (inverterData.event1.acDisconnect) { - thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "AC disconnect open"); + thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "AC disconnect open"); } else if (inverterData.event1.acUnderVolt) { - thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "AC voltage under limit"); + thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "AC voltage under limit"); } else if (inverterData.event1.dcDicconnect) { - thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "DC disconnect open"); + thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "DC disconnect open"); } else if (inverterData.event1.dcOverVoltage) { - thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "DC over voltage"); + thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "DC over voltage"); } else if (inverterData.event1.overFrequency) { - thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Frequency above limit"); + thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Frequency above limit"); } else if (inverterData.event1.gridDisconnect) { - thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Grid disconnect"); + thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Grid disconnect"); } else if (inverterData.event1.hwTestFailure) { - thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Hardware test failure"); + thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Hardware test failure"); } else if (inverterData.event1.manualShutdown) { - thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Manual shutdown"); + thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Manual shutdown"); } else if (inverterData.event1.underFrequency) { - thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Frequency under limit"); + thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Frequency under limit"); } else if (inverterData.event1.blownStringFuse) { - thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "Blown string fuse on input"); + thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "Blown string fuse on input"); } else { - thing->setStateValue(sunspecThreePhaseInverterErrorStateTypeId, "None"); + thing->setStateValue(m_inverterErrorStateTypeIds.value(thing->thingClassId()), "None"); } } @@ -801,13 +813,29 @@ void IntegrationPluginSunSpec::onMeterDataReceived(const SunSpecMeter::MeterData qCDebug(dcSunSpec()) << " - Total real energy exported" << meterData.totalRealEnergyExported<< "[kWH]"; qCDebug(dcSunSpec()) << " - Total real energy imported" << meterData.totalRealEnergyImported<< "[kWH]"; + thing->setStateValue(m_frequencyStateTypeIds.value(thing->thingClassId()), meterData.frequency); thing->setStateValue(sunspecThreePhaseMeterTotalCurrentStateTypeId, meterData.totalAcCurrent); - thing->setStateValue(sunspecThreePhaseMeterPhaseACurrentStateTypeId, meterData.phaseACurrent); - thing->setStateValue(sunspecThreePhaseMeterPhaseBCurrentStateTypeId, meterData.phaseBCurrent); - thing->setStateValue(sunspecThreePhaseMeterPhaseCCurrentStateTypeId, meterData.phaseCCurrent); - thing->setStateValue(sunspecThreePhaseMeterLnACVoltageStateTypeId, meterData.voltageLN); - thing->setStateValue(sunspecThreePhaseMeterTotalRealPowerEventTypeId, meterData.totalRealPower); - thing->setStateValue(sunspecThreePhaseMeterEnergyExportedStateTypeId, meterData.totalRealEnergyExported); - thing->setStateValue(sunspecThreePhaseMeterEnergyImportedStateTypeId, meterData.totalRealEnergyImported); - 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(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(sunspecThreePhaseMeterCurrentPowerEventTypeId, meterData.totalRealPower); + thing->setStateValue(sunspecThreePhaseMeterTotalEnergyProducedStateTypeId, meterData.totalRealEnergyExported); + thing->setStateValue(sunspecThreePhaseMeterTotalEnergyConsumedStateTypeId, meterData.totalRealEnergyImported); + } } diff --git a/sunspec/integrationpluginsunspec.h b/sunspec/integrationpluginsunspec.h index b860ddd..bdb8252 100644 --- a/sunspec/integrationpluginsunspec.h +++ b/sunspec/integrationpluginsunspec.h @@ -63,10 +63,13 @@ private: QHash m_connectedStateTypeIds; QHash m_frequencyStateTypeIds; - QHash m_acPowerStateTypeIds; - QHash m_acEnergyStateTypeIds; + + 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; diff --git a/sunspec/integrationpluginsunspec.json b/sunspec/integrationpluginsunspec.json index b28d92d..df83b8b 100644 --- a/sunspec/integrationpluginsunspec.json +++ b/sunspec/integrationpluginsunspec.json @@ -40,7 +40,7 @@ "displayName": "SunSpec connection", "id": "f51853f3-8815-4cf3-b337-45cda1f3e6d5", "createMethods": [ "User" ], - "interfaces": ["connectable"], + "interfaces": ["gateway"], "paramTypes": [ { "id": "6be6abc4-e2b2-4687-9343-0e5164ed0ab2", @@ -105,7 +105,7 @@ "displayName": "SunSpec single phase inverter", "id": "c5d5204e-3375-4b92-8128-fab77a671fed", "createMethods": [ "Auto" ], - "interfaces": ["connectable"], + "interfaces": ["extendedsmartmeterproducer", "connectable"], "paramTypes": [ { "id": "41715d00-a947-4f43-a475-cea05790e01d", @@ -153,11 +153,11 @@ }, { "id": "f49591d6-d759-4be3-bafc-b6a7a72cf023", - "name": "acPower", + "name": "currentPower", "displayName": "AC power", "displayNameEvent": "AC power changed", "type": "double", - "unit": "KiloWatt", + "unit": "Watt", "defaultValue": 0.00 }, { @@ -171,7 +171,7 @@ }, { "id": "4c0407b3-5cd5-438d-bfa8-9a8d6695b458", - "name": "acEnergy", + "name": "totalEnergyProduced", "displayName": "AC energy", "displayNameEvent": "AC energy changed", "type": "double", @@ -239,7 +239,7 @@ "displayName": "SunSpec split phase inverter", "id": "61b38f93-d331-42bf-b1ef-d3fb16ad1230", "createMethods": [ "Auto" ], - "interfaces": ["connectable"], + "interfaces": ["extendedsmartmeterproducer", "connectable"], "paramTypes": [ { "id": "c42fb50e-210f-4b53-88eb-fa216e15f88f", @@ -314,11 +314,11 @@ }, { "id": "9235eb4b-906c-4557-8e18-bca268a367cc", - "name": "acPower", + "name": "currentPower", "displayName": "AC power", "displayNameEvent": "AC power changed", "type": "double", - "unit": "KiloWatt", + "unit": "Watt", "defaultValue": 0.00 }, { @@ -332,7 +332,7 @@ }, { "id": "fe3f8a65-121a-4ae1-b22a-ae325dc3e7e6", - "name": "acEnergy", + "name": "totalEnergyProduced", "displayName": "AC energy", "displayNameEvent": "AC energy changed", "type": "double", @@ -400,7 +400,7 @@ "displayName": "SunSpec three phase inverter", "id": "2e4122ea-96a5-415c-b5e2-7d6012265a83", "createMethods": [ "Auto" ], - "interfaces": ["connectable"], + "interfaces": ["extendedsmartmeterproducer", "connectable"], "paramTypes": [ { "id": "8d5b2b58-ce46-406d-844e-f53136afcf09", @@ -493,11 +493,11 @@ }, { "id": "14036f44-25fd-4e93-8e8c-c677b06a2c34", - "name": "acPower", + "name": "currentPower", "displayName": "AC power", "displayNameEvent": "AC power changed", "type": "double", - "unit": "KiloWatt", + "unit": "Watt", "defaultValue": 0.00 }, { @@ -511,7 +511,7 @@ }, { "id": "d493880d-eb58-4530-8010-8ea4f6d63387", - "name": "acEnergy", + "name": "totalEnergyProduced", "displayName": "AC energy", "displayNameEvent": "AC energy changed", "type": "double", @@ -579,7 +579,7 @@ "displayName": "SunSpec single phase meter", "id": "7ffa43b8-b56f-4435-8509-980e9d81dfa8", "createMethods": [ "Auto" ], - "interfaces": ["connectable"], + "interfaces": ["extendedsmartmeterconsumer", "connectable"], "paramTypes": [ { "id": "7d6fcafb-c62e-4a21-aae2-f4041c487149", @@ -646,16 +646,16 @@ }, { "id": "93cf8c6a-2620-42ed-9070-e0726d7b1dbc", - "name": "totalRealPower", + "name": "currentPower", "displayName": "Total real power", "displayNameEvent": "Total real power changed", "type": "double", - "unit": "KiloWatt", + "unit": "Watt", "defaultValue": 0.00 }, { "id": "ba275bdf-f418-4ef0-afbe-ac425c6f6783", - "name": "energyExported", + "name": "totalEnergyProduced", "displayName": "Total real energy exported", "displayNameEvent": "Total real energy exported changed", "type": "double", @@ -664,7 +664,7 @@ }, { "id": "c51dc6cb-5c05-4078-b11a-26afb2f85541", - "name": "energyImported", + "name": "totalEnergyConsumed", "displayName": "Total real energy imported", "displayNameEvent": "Total real energy imported changed", "type": "double", @@ -678,7 +678,7 @@ "displayName": "SunSpec split phase meter", "id": "b8a18e45-5ff5-4f43-915f-04ee216c809d", "createMethods": [ "Auto" ], - "interfaces": ["connectable"], + "interfaces": ["extendedsmartmeterconsumer", "connectable"], "paramTypes": [ { "id": "89aeec6d-abeb-48b5-9594-214ad5db2d03", @@ -772,16 +772,16 @@ }, { "id": "ef4bc0f8-f516-49b7-aba8-d5f987485aca", - "name": "totalRealPower", + "name": "currentPower", "displayName": "Total real power", "displayNameEvent": "Total real power changed", "type": "double", - "unit": "KiloWatt", + "unit": "Watt", "defaultValue": 0.00 }, { "id": "8a63bd73-0546-4636-8da2-23238cc06fb2", - "name": "energyExported", + "name": "totalEnergyProduced", "displayName": "Total real energy exported", "displayNameEvent": "Total real energy exported changed", "type": "double", @@ -790,7 +790,7 @@ }, { "id": "51ffb2ae-3920-40df-8290-bbf5b6e1a68f", - "name": "energyImported", + "name": "totalEnergyConsumed", "displayName": "Total real energy imported", "displayNameEvent": "Total real energy imported changed", "type": "double", @@ -804,7 +804,7 @@ "displayName": "SunSpec three phase meter", "id": "68f822f9-ff30-4275-b229-39a3674fead7", "createMethods": [ "Auto" ], - "interfaces": ["connectable"], + "interfaces": ["extendedsmartmeterconsumer", "connectable"], "paramTypes": [ { "id": "a1960821-155c-4176-86fa-974429039182", @@ -916,16 +916,16 @@ }, { "id": "c28c642f-46da-44de-ba0d-c4cbfadbf2cd", - "name": "totalRealPower", + "name": "currentPower", "displayName": "Total real power", "displayNameEvent": "Total real power changed", "type": "double", - "unit": "KiloWatt", + "unit": "Watt", "defaultValue": 0.00 }, { "id": "73ebf57f-1ad2-4d19-bfd9-9e0a514c1243", - "name": "energyExported", + "name": "totalEnergyProduced", "displayName": "Total real energy exported", "displayNameEvent": "Total real energy exported changed", "type": "double", @@ -934,7 +934,7 @@ }, { "id": "63fa4721-1b0a-458c-b66c-17c161107f0d", - "name": "energyImported", + "name": "totalEnergyConsumed", "displayName": "Total real energy imported", "displayNameEvent": "Total real energy imported changed", "type": "double", diff --git a/sunspec/sunspec.cpp b/sunspec/sunspec.cpp index 071666c..5aa4794 100644 --- a/sunspec/sunspec.cpp +++ b/sunspec/sunspec.cpp @@ -363,10 +363,9 @@ float SunSpec::convertValueWithSSF(quint32 rawValue, quint16 sunssf) float SunSpec::convertFloatValues(quint16 rawValue0, quint16 rawValue1) { - float value; - uint32_t i = qFromLittleEndian(((uint32_t)rawValue0 << 16) + rawValue1); - memcpy(&value, &i, sizeof(float)); - return value; + suns_modbus_v32_t value; + value.u = (static_cast(rawValue0) << 16) + rawValue1; + return value.f; } void SunSpec::onModbusStateChanged(QModbusDevice::State state) diff --git a/sunspec/sunspec.h b/sunspec/sunspec.h index 8021df4..971b919 100644 --- a/sunspec/sunspec.h +++ b/sunspec/sunspec.h @@ -42,6 +42,12 @@ class SunSpec : public QObject public: + typedef union { + int32_t s; + uint32_t u; + float f; + } suns_modbus_v32_t; + enum MandatoryRegistersModel1 { Manufacturer = 2, Model = 18, diff --git a/sunspec/sunspecinverter.cpp b/sunspec/sunspecinverter.cpp index 9811a98..f791415 100644 --- a/sunspec/sunspecinverter.cpp +++ b/sunspec/sunspecinverter.cpp @@ -76,23 +76,24 @@ void SunSpecInverter::getInverterModelDataBlock() SunSpecInverter::SunSpecEvent1 SunSpecInverter::bitfieldToSunSpecEvent1(quint16 register1, quint16 register2) { SunSpecEvent1 event1; - Q_UNUSED(register2); - event1.groundFault = ((register1 & (0x01 << 0)) != 0); - event1.dcOverVoltage = ((register1 & (0x01 << 1)) != 0); - event1.acDisconnect = ((register1 & (0x01 << 2)) != 0); - event1.dcDicconnect = ((register1 & (0x01 << 3)) != 0); - event1.gridDisconnect = ((register1 & (0x01 << 4)) != 0); - event1.cabinetOpen = ((register1 & (0x01 << 5)) != 0); - event1.manualShutdown = ((register1 & (0x01 << 6)) != 0); - event1.overTemperature = ((register1 & (0x01 << 7)) != 0); - event1.overFrequency = ((register1 & (0x01 << 8)) != 0); - event1.underFrequency = ((register1 & (0x01 << 9)) != 0); - event1.acOverVolt = ((register1 & (0x01 << 10)) != 0); - event1.acUnderVolt = ((register1 & (0x01 << 11)) != 0); - event1.blownStringFuse = ((register1 & (0x01 << 12)) != 0); - event1.underTemperature = ((register1 & (0x01 << 13)) != 0); - event1.memoryLoss = ((register1 & (0x01 << 14)) != 0); - event1.hwTestFailure = ((register1 & (0x01 << 15)) != 0); + quint32 value = (static_cast(register1)<<16 | register2); + //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; } From 5efce86683598be6c2cd6f3713f35d13ca163fc6 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Thu, 4 Feb 2021 13:00:22 +0100 Subject: [PATCH 12/16] tested and fixed storage model --- sunspec/integrationpluginsunspec.cpp | 43 +++++++++++++++++++++------- sunspec/sunspecinverter.cpp | 4 +-- sunspec/sunspecinverter.h | 4 +-- sunspec/sunspecstorage.cpp | 11 +------ sunspec/sunspecstorage.h | 2 +- 5 files changed, 39 insertions(+), 25 deletions(-) diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index a2f4656..48eaf2c 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -610,13 +610,19 @@ void IntegrationPluginSunSpec::onSunSpecModelSearchFinished(const QHashfinish(Thing::ThingErrorNoError); + } else { + info->finish(Thing::ThingErrorHardwareFailure); + } + } } void IntegrationPluginSunSpec::onWriteRequestError(QUuid requestId, const QString &error) { - Q_UNUSED(requestId) - Q_UNUSED(error) - qCDebug(dcSunSpec()) << "Write request error" << error; + qCDebug(dcSunSpec()) << "Write request error" << requestId << error; } void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::InverterData &inverterData) @@ -646,7 +652,7 @@ void IntegrationPluginSunSpec::onInverterDataReceived(const SunSpecInverter::Inv qCDebug(dcSunSpec()) << " - Operating state" << inverterData.operatingState; thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true); - thing->setStateValue(m_inverterCurrentPowerStateTypeIds.value(thing->thingClassId()), inverterData.acPower/1000.00); + 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); @@ -749,10 +755,23 @@ void IntegrationPluginSunSpec::onStorageDataReceived(const SunSpecStorage::Stora if(!thing) { return; } - thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true); + qCDebug(dcSunSpec()) << "Storage data received"; + qCDebug(dcSunSpec()) << " - Setpoint for maximum charge" << mandatory.maxCharge << "[W]"; + qCDebug(dcSunSpec()) << " - Setpoint for maximum charging rate." << mandatory.maxChargeRate << "[%]"; + qCDebug(dcSunSpec()) << " - Setpoint for maximum discharging rate." << mandatory.maxDischargeRate << "[%]"; + qCDebug(dcSunSpec()) << " - Charging enabled" << mandatory.chargingEnabled; + qCDebug(dcSunSpec()) << " - Discharging enabled" << mandatory.dischargingEnabled; + qCDebug(dcSunSpec()) << " - Storage status" << optional.chargeSatus; + qCDebug(dcSunSpec()) << " - Currently available energy" << optional.currentlyAvailableEnergy << "[%]"; + qCDebug(dcSunSpec()) << " - Grid charging enabled" << optional.gridChargingEnabled; + + thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true); thing->setStateValue(sunspecStorageChargingRateStateTypeId, mandatory.maxChargeRate); thing->setStateValue(sunspecStorageDischargingRateStateTypeId, mandatory.maxDischargeRate); + thing->setStateValue(sunspecStorageEnableChargingStateTypeId, mandatory.chargingEnabled); + thing->setStateValue(sunspecStorageEnableDischargingStateTypeId, mandatory.dischargingEnabled); + thing->setStateValue(sunspecStorageGridChargingStateTypeId, optional.gridChargingEnabled); bool charging = false; switch (optional.chargeSatus) { @@ -785,7 +804,6 @@ void IntegrationPluginSunSpec::onStorageDataReceived(const SunSpecStorage::Stora void IntegrationPluginSunSpec::onMeterDataReceived(const SunSpecMeter::MeterData &meterData) { - Q_UNUSED(meterData) SunSpecMeter *meter = static_cast(sender()); Thing *thing = m_sunSpecMeters.key(meter); @@ -793,8 +811,6 @@ void IntegrationPluginSunSpec::onMeterDataReceived(const SunSpecMeter::MeterData return; } - thing->setStateValue(m_connectedStateTypeIds.value(thing->thingClassId()), true); - qCDebug(dcSunSpec()) << "Meter data received"; qCDebug(dcSunSpec()) << " - Total AC Current" << meterData.totalAcCurrent << "[A]"; qCDebug(dcSunSpec()) << " - Phase A current" << meterData.phaseACurrent << "[A]"; @@ -813,8 +829,8 @@ void IntegrationPluginSunSpec::onMeterDataReceived(const SunSpecMeter::MeterData qCDebug(dcSunSpec()) << " - Total real energy exported" << meterData.totalRealEnergyExported<< "[kWH]"; qCDebug(dcSunSpec()) << " - Total real energy imported" << meterData.totalRealEnergyImported<< "[kWH]"; - thing->setStateValue(m_frequencyStateTypeIds.value(thing->thingClassId()), meterData.frequency); - thing->setStateValue(sunspecThreePhaseMeterTotalCurrentStateTypeId, meterData.totalAcCurrent); + 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); @@ -822,18 +838,25 @@ void IntegrationPluginSunSpec::onMeterDataReceived(const SunSpecMeter::MeterData 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/sunspecinverter.cpp b/sunspec/sunspecinverter.cpp index f791415..0f1dbb8 100644 --- a/sunspec/sunspecinverter.cpp +++ b/sunspec/sunspecinverter.cpp @@ -73,10 +73,10 @@ void SunSpecInverter::getInverterModelDataBlock() m_connection->readModelDataBlock(m_modelModbusStartRegister, m_modelLength); } -SunSpecInverter::SunSpecEvent1 SunSpecInverter::bitfieldToSunSpecEvent1(quint16 register1, quint16 register2) +SunSpecInverter::SunSpecEvent1 SunSpecInverter::bitfieldToSunSpecEvent1(quint16 register0, quint16 register1) { SunSpecEvent1 event1; - quint32 value = (static_cast(register1)<<16 | register2); + 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); diff --git a/sunspec/sunspecinverter.h b/sunspec/sunspecinverter.h index 712983b..e668ca9 100644 --- a/sunspec/sunspecinverter.h +++ b/sunspec/sunspecinverter.h @@ -168,12 +168,12 @@ public: private: SunSpec *m_connection = nullptr; - SunSpec::ModelId m_id; //e.g. 103 for three phase inverter, 113 for three phase inverter with floating point representation + SunSpec::ModelId m_id; uint m_modelLength = 0; uint m_modelModbusStartRegister = 40000; bool m_initFinishedSuccess = false; - SunSpecEvent1 bitfieldToSunSpecEvent1(quint16 register1, quint16 register2); + SunSpecEvent1 bitfieldToSunSpecEvent1(quint16 register0, quint16 register1); void getInverterModelHeader(); private slots: diff --git a/sunspec/sunspecstorage.cpp b/sunspec/sunspecstorage.cpp index b40a098..534c6fa 100644 --- a/sunspec/sunspecstorage.cpp +++ b/sunspec/sunspecstorage.cpp @@ -127,21 +127,13 @@ void SunSpecStorage::onModelDataBlockReceived(SunSpec::ModelId modelId, uint len if (length < m_modelLength) { qCDebug(dcSunSpec()) << "SunSpecMeter: on model data block received, model length is too short" << length; - //return; + return; } qCDebug(dcSunSpec()) << "SunSpecStorage: Received" << modelId; switch (modelId) { case SunSpec::ModelIdStorage: { StorageData mandatory; - qCDebug(dcSunSpec()) << " - Setpoint maximum charge" << data[Model124SetpointMaximumCharge]; - qCDebug(dcSunSpec()) << " - Setpoint maximum charging rate" << data[Model124SetpointMaximumChargingRate]; - qCDebug(dcSunSpec()) << " - Setpoint maximum discharge rate" << data[Model124SetpointMaximumDischargeRate]; - qCDebug(dcSunSpec()) << " - Active storage control mode" << data[Model124ActivateStorageControlMode]; - qCDebug(dcSunSpec()) << " - ChaGriSet" << data[Model124ChargeGridSet]; - qCDebug(dcSunSpec()) << " - Scale factor max charge" << data[Model124ScaleFactorMaximumCharge]; - qCDebug(dcSunSpec()) << " - Scale factor max charge/discharge rate" << data[Model124ScaleFactorMaximumChargeDischargeRate]; - qCDebug(dcSunSpec()) << " - Scale factor" << data[Model124ScaleFactorAvailableEnergyPercent]; mandatory.maxCharge = m_connection->convertValueWithSSF(data[Model124SetpointMaximumCharge], data[Model124ScaleFactorMaximumChargeDischargeRate]); mandatory.maxChargeRate = m_connection->convertValueWithSSF(data[Model124SetpointMaximumChargingRate], data[Model124ScaleFactorPercentChargeDischargeRate]); mandatory.chargingEnabled = data[Model124ActivateStorageControlMode]&0x01; @@ -154,7 +146,6 @@ void SunSpecStorage::onModelDataBlockReceived(SunSpec::ModelId modelId, uint len optional.storageAvailable = m_connection->convertValueWithSSF(data[Model124StorageAvailableAH], data[Model124ScaleFactorMaximumChargingVA]); optional.gridChargingEnabled = (data[Model124ChargeGridSet] == 1); optional.currentlyAvailableEnergy = m_connection->convertValueWithSSF(data[Model124CurrentlyAvailableEnergyPercent], data[Model124ScaleFactorAvailableEnergyPercent]); - emit storageDataReceived(mandatory, optional); } break; diff --git a/sunspec/sunspecstorage.h b/sunspec/sunspecstorage.h index 87e316c..eada11e 100644 --- a/sunspec/sunspecstorage.h +++ b/sunspec/sunspecstorage.h @@ -64,7 +64,7 @@ public: Q_ENUM(GridCharge) enum ChargingStatus { - ChargingStatusOff, + ChargingStatusOff = 1, ChargingStatusEmpty, ChargingStatusDischarging, ChargingStatusCharging, From 73958b2e597ebf77502d44803dfca173718abfb7 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Thu, 4 Feb 2021 13:37:31 +0100 Subject: [PATCH 13/16] Remaved storage register to original sunspec name --- sunspec/integrationpluginsunspec.cpp | 30 +++++----- sunspec/sunspecstorage.cpp | 36 +++++++----- sunspec/sunspecstorage.h | 83 ++++++++++++++-------------- 3 files changed, 77 insertions(+), 72 deletions(-) diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index 48eaf2c..9ac48e8 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -757,24 +757,24 @@ void IntegrationPluginSunSpec::onStorageDataReceived(const SunSpecStorage::Stora } qCDebug(dcSunSpec()) << "Storage data received"; - qCDebug(dcSunSpec()) << " - Setpoint for maximum charge" << mandatory.maxCharge << "[W]"; - qCDebug(dcSunSpec()) << " - Setpoint for maximum charging rate." << mandatory.maxChargeRate << "[%]"; - qCDebug(dcSunSpec()) << " - Setpoint for maximum discharging rate." << mandatory.maxDischargeRate << "[%]"; - qCDebug(dcSunSpec()) << " - Charging enabled" << mandatory.chargingEnabled; - qCDebug(dcSunSpec()) << " - Discharging enabled" << mandatory.dischargingEnabled; - qCDebug(dcSunSpec()) << " - Storage status" << optional.chargeSatus; - qCDebug(dcSunSpec()) << " - Currently available energy" << optional.currentlyAvailableEnergy << "[%]"; - qCDebug(dcSunSpec()) << " - Grid charging enabled" << optional.gridChargingEnabled; + 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.maxChargeRate); - thing->setStateValue(sunspecStorageDischargingRateStateTypeId, mandatory.maxDischargeRate); - thing->setStateValue(sunspecStorageEnableChargingStateTypeId, mandatory.chargingEnabled); - thing->setStateValue(sunspecStorageEnableDischargingStateTypeId, mandatory.dischargingEnabled); - thing->setStateValue(sunspecStorageGridChargingStateTypeId, optional.gridChargingEnabled); + 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.chargeSatus) { + switch (optional.ChaSt) { case SunSpecStorage::ChargingStatusOff: thing->setStateValue(sunspecStorageStorageStatusStateTypeId, "Off"); break; @@ -797,7 +797,7 @@ void IntegrationPluginSunSpec::onStorageDataReceived(const SunSpecStorage::Stora thing->setStateValue(sunspecStorageStorageStatusStateTypeId, "Discharging"); break; }; - double batteryLevel = optional.currentlyAvailableEnergy; + double batteryLevel = optional.ChaState; thing->setStateValue(sunspecStorageBatteryLevelStateTypeId, batteryLevel); thing->setStateValue(sunspecStorageBatteryCriticalStateTypeId, (batteryLevel < 5 && !charging)); } diff --git a/sunspec/sunspecstorage.cpp b/sunspec/sunspecstorage.cpp index 534c6fa..dab9bc5 100644 --- a/sunspec/sunspecstorage.cpp +++ b/sunspec/sunspecstorage.cpp @@ -85,7 +85,7 @@ QUuid SunSpecStorage::setGridCharging(bool enabled) PV (charging from grid 0 disabled) GRID (charging from 1 grid enabled*/ - uint registerAddress = m_modelModbusStartRegister + Model124Optional::Model124ChargeGridSet; + uint registerAddress = m_modelModbusStartRegister + Model124Optional::Model124ChaGriSet; quint16 value = enabled; return m_connection->writeHoldingRegister(registerAddress, value); } @@ -96,7 +96,7 @@ QUuid SunSpecStorage::setStorageControlMode(bool chargingEnabled, bool dischargi quint16 value = ((static_cast(chargingEnabled)) | (static_cast(dischargingEnabled) << 1)) ; - uint modbusRegister = m_modelModbusStartRegister + Model124::Model124ActivateStorageControlMode; + uint modbusRegister = m_modelModbusStartRegister + Model124::Model124StorCtl_Mod; return m_connection->writeHoldingRegister(modbusRegister, value); } @@ -105,7 +105,7 @@ QUuid SunSpecStorage::setChargingRate(int rate) //Register Name InWRte /* Defines the maximum charge rate (charge limit). Default is 100% */ - uint modbusRegister = m_modelModbusStartRegister + Model124::Model124SetpointMaximumChargingRate; + uint modbusRegister = m_modelModbusStartRegister + Model124::Model124WChaGra; int16_t value = rate * 100; return m_connection->writeHoldingRegister(modbusRegister, value); } @@ -114,7 +114,7 @@ QUuid SunSpecStorage::setDischargingRate(int charging) { //Register Name OutWRte /* Defines the maximum discharge rate (discharge limit). Default is 100% */ - uint modbusRegister = m_modelModbusStartRegister + Model124::Model124SetpointMaximumDischargeRate; + uint modbusRegister = m_modelModbusStartRegister + Model124::Model124WDisChaGra; quint16 value = charging * 100; return m_connection->writeHoldingRegister(modbusRegister, value); } @@ -134,20 +134,26 @@ void SunSpecStorage::onModelDataBlockReceived(SunSpec::ModelId modelId, uint len switch (modelId) { case SunSpec::ModelIdStorage: { StorageData mandatory; - mandatory.maxCharge = m_connection->convertValueWithSSF(data[Model124SetpointMaximumCharge], data[Model124ScaleFactorMaximumChargeDischargeRate]); - mandatory.maxChargeRate = m_connection->convertValueWithSSF(data[Model124SetpointMaximumChargingRate], data[Model124ScaleFactorPercentChargeDischargeRate]); - mandatory.chargingEnabled = data[Model124ActivateStorageControlMode]&0x01; - mandatory.dischargingEnabled = data[Model124ActivateStorageControlMode]&0x02; - mandatory.maxDischargeRate = m_connection->convertValueWithSSF(data[Model124SetpointMaximumDischargeRate], data[Model124ScaleFactorPercentChargeDischargeRate]); + mandatory.WChaMax = m_connection->convertValueWithSSF(data[Model124WChaMax], data[Model124WChaMax_SF]); + mandatory.WChaGra = m_connection->convertValueWithSSF(data[Model124WChaGra], data[Model124WChaDisChaGra_SF]); + mandatory.WDisChaGra = m_connection->convertValueWithSSF(data[Model124WDisChaGra], data[Model124WChaDisChaGra_SF]); + mandatory.StorCtl_Mod_ChargingEnabled = data[Model124StorCtl_Mod]&0x01; + mandatory.StorCtl_Mod_DischargingEnabled = data[Model124StorCtl_Mod]&0x02; StorageDataOptional optional; - optional.chargeSatus = ChargingStatus(data[Model124ChargeStatus]); - optional.batteryVoltage = m_connection->convertValueWithSSF(data[Model124InternalBatteryVoltage], data[Model124ScaleFactorBatteryVoltage]); - optional.storageAvailable = m_connection->convertValueWithSSF(data[Model124StorageAvailableAH], data[Model124ScaleFactorMaximumChargingVA]); - optional.gridChargingEnabled = (data[Model124ChargeGridSet] == 1); - optional.currentlyAvailableEnergy = m_connection->convertValueWithSSF(data[Model124CurrentlyAvailableEnergyPercent], data[Model124ScaleFactorAvailableEnergyPercent]); + optional.VAChaMax = m_connection->convertValueWithSSF(data[Model124VAChaMax], data[Model124VAChaMax_SF]); + optional.MinRsvPct = m_connection->convertValueWithSSF(data[Model124MinRsvPct], data[Model124MinRsvPct_SF]); + optional.ChaState = m_connection->convertValueWithSSF(data[Model124ChaState], data[Model124ChaState_SF]); + optional.StorAval = m_connection->convertValueWithSSF(data[Model124StorAval], data[Model124StorAval_SF]); + optional.InBatV = m_connection->convertValueWithSSF(data[Model124InBatV], data[Model124InBatV_SF]); + optional.ChaSt = ChargingStatus(data[Model124ChaSt]); + optional.OutWRte = m_connection->convertValueWithSSF(data[Model124OutWRte], data[Model124InOutWRte_SF]); + optional.InWRte = m_connection->convertValueWithSSF(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: { diff --git a/sunspec/sunspecstorage.h b/sunspec/sunspecstorage.h index eada11e..49b6bdc 100644 --- a/sunspec/sunspecstorage.h +++ b/sunspec/sunspecstorage.h @@ -75,58 +75,57 @@ public: Q_ENUM(ChargingStatus) enum Model124 { // Mandatory registers - Model124SetpointMaximumCharge = 0, - Model124SetpointMaximumChargingRate = 1, - Model124SetpointMaximumDischargeRate = 2, - Model124ActivateStorageControlMode = 3, - Model124ScaleFactorMaximumCharge = 16, - Model124ScaleFactorMaximumChargeDischargeRate = 17, + Model124WChaMax = 0, + Model124WChaGra = 1, + Model124WDisChaGra = 2, + Model124StorCtl_Mod = 3, + Model124WChaMax_SF = 16, + Model124WChaDisChaGra_SF = 17, }; - Q_ENUM(Model124) enum Model124Optional { // Optional registers - Model124MaximumChargingVA = 4, // VAChaMax - Model124MinimumReserveStoragePercent = 5, // MinRsvPct - Model124CurrentlyAvailableEnergyPercent = 6, // ChaState - Model124StorageAvailableAH = 7, // StorAval - Model124InternalBatteryVoltage = 8, // InBatV - Model124ChargeStatus = 9, // ChaSt - Model124MaxDischargingRatePercent = 10, // OutWRte - Model124MaxChargingRatePercent = 11, - Model124ChargeDischargeTimeWindow = 12, - Model124ChargeDischargeTimeout = 13, - Model124RampTime = 14, // InOutWRte_RmpTms - Model124ChargeGridSet = 15, // ChGriSet - Model124ScaleFactorMaximumChargingVA = 18, - Model124ScaleFactorMinimumReservePercentage = 19, - Model124ScaleFactorAvailableEnergyPercent = 20, - Model124ScaleFactorStateCharge = 21, - Model124ScaleFactorBatteryVoltage = 22, - Model124ScaleFactorPercentChargeDischargeRate = 23 + 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 maxCharge; // [W] Setpoint for maximum charge. - double maxChargeRate; // [%] Setpoint for maximum charging rate. Default is MaxChaRte. - double maxDischargeRate; // [%] Setpoint for maximum discharge rate. Default is MaxDisChaRte. - bool chargingEnabled; - bool dischargingEnabled; + 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 { - // [VA] Setpoint for maximum charging VA. - // [& ]Setpoint for minimum reserve for storage as a percentage of the nominal maximum storage. - double currentlyAvailableEnergy; // [%] Currently available energy as a percent of the capacity rating. - double storageAvailable; // [Ah] State of charge (ChaState) minus storage reserve (MinRsvPct) times capacity rating (AhrRtg). - double batteryVoltage; // [V] Internal battery voltage. - ChargingStatus chargeSatus; // Charge status of storage device. Enumerated value. - // [%] Percent of max discharge rate. - // [%] Percent of max charging rate. - // [s] Time window for charge/discharge rate change. - // [s] Timeout period for charge/discharge rate. - // [s] Ramp time for moving from current setpoint to new setpoint. - bool gridChargingEnabled; + 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: From 15967809168f5ede063fca9d5e69d50c2c94b245 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Thu, 4 Feb 2021 14:02:11 +0100 Subject: [PATCH 14/16] fixed storage actions --- sunspec/integrationpluginsunspec.cpp | 25 +++------------ sunspec/sunspec.cpp | 9 +++++- sunspec/sunspec.h | 4 ++- sunspec/sunspecinverter.cpp | 22 ++++++------- sunspec/sunspecmeter.cpp | 32 +++++++++---------- sunspec/sunspecstorage.cpp | 48 +++++++++++++++++----------- sunspec/sunspecstorage.h | 14 ++++++-- 7 files changed, 82 insertions(+), 72 deletions(-) diff --git a/sunspec/integrationpluginsunspec.cpp b/sunspec/integrationpluginsunspec.cpp index 9ac48e8..c6a95ed 100644 --- a/sunspec/integrationpluginsunspec.cpp +++ b/sunspec/integrationpluginsunspec.cpp @@ -284,24 +284,7 @@ void IntegrationPluginSunSpec::executeAction(ThingActionInfo *info) Thing *thing = info->thing(); Action action = info->action(); - if (thing->thingClassId() == sunspecSinglePhaseInverterThingClassId || - thing->thingClassId() == sunspecSplitPhaseInverterThingClassId || - thing->thingClassId() == sunspecThreePhaseInverterThingClassId) { - - SunSpecInverter *sunSpecInverter = m_sunSpecInverters.value(thing); - if (!sunSpecInverter) { - qWarning(dcSunSpec()) << "Could not find SunSpec inverter for thing" << thing->name(); - info->finish(Thing::ThingErrorHardwareNotAvailable); - return; - } - info->finish(Thing::ThingErrorActionTypeNotFound); - - } else if (thing->thingClassId() == sunspecSinglePhaseMeterThingClassId || - thing->thingClassId() == sunspecSplitPhaseMeterThingClassId || - thing->thingClassId() == sunspecThreePhaseMeterThingClassId) { - info->finish(Thing::ThingErrorActionTypeNotFound); - - } else if (thing->thingClassId() == sunspecStorageThingClassId) { + if (thing->thingClassId() == sunspecStorageThingClassId) { SunSpecStorage *sunSpecStorage = m_sunSpecStorages.value(thing); if (!sunSpecStorage) { qWarning(dcSunSpec()) << "Could not find sunspec instance for thing"; @@ -481,7 +464,7 @@ void IntegrationPluginSunSpec::onPluginConfigurationChanged(const ParamTypeId &p m_refreshTimer->startTimer(refreshTime); } } else if (paramTypeId == sunSpecPluginNumberOfRetriesParamTypeId) { - qCDebug(dcSunSpec()) << "Updating number of retires" << value.toUInt(); + qCDebug(dcSunSpec()) << "Updating number of retries" << value.toUInt(); Q_FOREACH(SunSpec *connection, m_sunSpecConnections) { connection->setNumberOfRetries(value.toUInt()); } @@ -592,7 +575,7 @@ void IntegrationPluginSunSpec::onFoundSunSpecModel(SunSpec::ModelId modelId, int emit autoThingsAppeared({descriptor}); } break; default: - qCDebug(dcSunSpec()) << "Block Id not handled"; + qCDebug(dcSunSpec()) << "Model Id not handled"; } } @@ -823,7 +806,7 @@ void IntegrationPluginSunSpec::onMeterDataReceived(const SunSpecMeter::MeterData 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()) << " - 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]"; diff --git a/sunspec/sunspec.cpp b/sunspec/sunspec.cpp index 5aa4794..262cf5f 100644 --- a/sunspec/sunspec.cpp +++ b/sunspec/sunspec.cpp @@ -354,13 +354,20 @@ QByteArray SunSpec::convertModbusRegisters(const QVector &modbusData, i return bytes; } -float SunSpec::convertValueWithSSF(quint32 rawValue, quint16 sunssf) +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; diff --git a/sunspec/sunspec.h b/sunspec/sunspec.h index 971b919..45f58b4 100644 --- a/sunspec/sunspec.h +++ b/sunspec/sunspec.h @@ -190,7 +190,9 @@ public: void readModelHeader(uint modbusAddress); void readModelDataBlock(uint modbusAddress, uint modelLength); //modbusAddress = model start address, model length is without header - float convertValueWithSSF(quint32 rawValue, quint16 sunssf); + 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); diff --git a/sunspec/sunspecinverter.cpp b/sunspec/sunspecinverter.cpp index 0f1dbb8..de61418 100644 --- a/sunspec/sunspecinverter.cpp +++ b/sunspec/sunspecinverter.cpp @@ -120,23 +120,23 @@ void SunSpecInverter::onModelDataBlockReceived(SunSpec::ModelId modelId, uint le case SunSpec::ModelIdInverterSinglePhase: case SunSpec::ModelIdInverterSplitPhase: case SunSpec::ModelIdInverterThreePhase: { - inverterData.acCurrent= m_connection->convertValueWithSSF(data[Model10X::Model10XAcCurrent], data[Model10X::Model10XAmpereScaleFactor]); - inverterData.acPower = m_connection->convertValueWithSSF(data[Model10X::Model10XACPower], data[Model10X::Model10XWattScaleFactor]); - inverterData.lineFrequency = m_connection->convertValueWithSSF(data[Model10X::Model10XLineFrequency], data[Model10X::Model10XHerzScaleFactor]); + 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->convertValueWithSSF(data[Model10X::Model10XPhaseACurrent], ampereScaleFactor); - inverterData.phaseBCurrent = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseBCurrent], ampereScaleFactor); - inverterData.phaseCCurrent = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseCCurrent], ampereScaleFactor); + 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->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageAN], voltageScaleFactor); - inverterData.phaseVoltageBN = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageBN], voltageScaleFactor); - inverterData.phaseVoltageCN = m_connection->convertValueWithSSF(data[Model10X::Model10XPhaseVoltageCN], voltageScaleFactor); + 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->convertValueWithSSF(acEnergy, data[Model10X::Model10XWattHoursScaleFactor]); + inverterData.acEnergy = m_connection->convertToFloatWithSSF(acEnergy, data[Model10X::Model10XWattHoursScaleFactor]); - inverterData.cabinetTemperature = m_connection->convertValueWithSSF(data[Model10X::Model10XCabinetTemperature], data[Model10X::Model10XTemperatureScaleFactor]); + 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); diff --git a/sunspec/sunspecmeter.cpp b/sunspec/sunspecmeter.cpp index 093c81b..a9bb23e 100644 --- a/sunspec/sunspecmeter.cpp +++ b/sunspec/sunspecmeter.cpp @@ -98,24 +98,24 @@ void SunSpecMeter::onModelDataBlockReceived(SunSpec::ModelId modelId, uint lengt MeterData meterData; quint16 currentScaleFactor = data[Model20XCurrentScaleFactor]; - meterData.totalAcCurrent = m_connection->convertValueWithSSF(data[Model20XTotalAcCurrent], currentScaleFactor); - meterData.phaseACurrent = m_connection->convertValueWithSSF(data[Model20XPhaseACurrent], currentScaleFactor); - meterData.phaseBCurrent = m_connection->convertValueWithSSF(data[Model20XPhaseBCurrent], currentScaleFactor); - meterData.phaseCCurrent = m_connection->convertValueWithSSF(data[Model20XPhaseCCurrent], currentScaleFactor); + 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->convertValueWithSSF(data[Model20XVoltageLN], voltageScaleFactor); - meterData.phaseVoltageAN = m_connection->convertValueWithSSF(data[Model20XPhaseVoltageAN], voltageScaleFactor); - meterData.phaseVoltageBN = m_connection->convertValueWithSSF(data[Model20XPhaseVoltageBN], voltageScaleFactor); - meterData.phaseVoltageCN = m_connection->convertValueWithSSF(data[Model20XPhaseVoltageCN], voltageScaleFactor); - meterData.voltageLL = m_connection->convertValueWithSSF(data[Model20XVoltageLL], voltageScaleFactor); - meterData.phaseVoltageAB = m_connection->convertValueWithSSF(data[Model20XPhaseVoltageAB], voltageScaleFactor); - meterData.phaseVoltageBC = m_connection->convertValueWithSSF(data[Model20XPhaseVoltageBC], voltageScaleFactor); - meterData.phaseVoltageCA = m_connection->convertValueWithSSF(data[Model20XPhaseVoltageCA], voltageScaleFactor); - meterData.frequency = m_connection->convertValueWithSSF(data[Model20XFrequency], data[Model20XFrequencyScaleFactor]); - meterData.totalRealPower = m_connection->convertValueWithSSF(data[Model20XTotalRealPower], data[Model20XRealPowerScaleFactor]); + 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->convertValueWithSSF(data[Model20XTotalRealEnergyExported], energyScaleFactor); - meterData.totalRealEnergyImported = m_connection->convertValueWithSSF(data[Model20XTotalRealEnergyImported], energyScaleFactor);; + 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); diff --git a/sunspec/sunspecstorage.cpp b/sunspec/sunspecstorage.cpp index dab9bc5..d5eba36 100644 --- a/sunspec/sunspecstorage.cpp +++ b/sunspec/sunspecstorage.cpp @@ -100,22 +100,32 @@ QUuid SunSpecStorage::setStorageControlMode(bool chargingEnabled, bool dischargi return m_connection->writeHoldingRegister(modbusRegister, value); } -QUuid SunSpecStorage::setChargingRate(int rate) +QUuid SunSpecStorage::setChargingRate(float rate) { - //Register Name InWRte - /* Defines the maximum charge rate (charge limit). Default is 100% */ - + 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; - int16_t value = rate * 100; + quint16 value = m_connection->convertFromFloatWithSSF(rate, m_WChaDisChaGra_SF); return m_connection->writeHoldingRegister(modbusRegister, value); } -QUuid SunSpecStorage::setDischargingRate(int charging) +QUuid SunSpecStorage::setDischargingRate(float rate) { - //Register Name OutWRte - /* Defines the maximum discharge rate (discharge limit). Default is 100% */ + 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 = charging * 100; + quint16 value = m_connection->convertFromFloatWithSSF(rate, m_WChaDisChaGra_SF); return m_connection->writeHoldingRegister(modbusRegister, value); } @@ -134,21 +144,21 @@ void SunSpecStorage::onModelDataBlockReceived(SunSpec::ModelId modelId, uint len switch (modelId) { case SunSpec::ModelIdStorage: { StorageData mandatory; - mandatory.WChaMax = m_connection->convertValueWithSSF(data[Model124WChaMax], data[Model124WChaMax_SF]); - mandatory.WChaGra = m_connection->convertValueWithSSF(data[Model124WChaGra], data[Model124WChaDisChaGra_SF]); - mandatory.WDisChaGra = m_connection->convertValueWithSSF(data[Model124WDisChaGra], data[Model124WChaDisChaGra_SF]); + 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->convertValueWithSSF(data[Model124VAChaMax], data[Model124VAChaMax_SF]); - optional.MinRsvPct = m_connection->convertValueWithSSF(data[Model124MinRsvPct], data[Model124MinRsvPct_SF]); - optional.ChaState = m_connection->convertValueWithSSF(data[Model124ChaState], data[Model124ChaState_SF]); - optional.StorAval = m_connection->convertValueWithSSF(data[Model124StorAval], data[Model124StorAval_SF]); - optional.InBatV = m_connection->convertValueWithSSF(data[Model124InBatV], data[Model124InBatV_SF]); + 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->convertValueWithSSF(data[Model124OutWRte], data[Model124InOutWRte_SF]); - optional.InWRte = m_connection->convertValueWithSSF(data[Model124InWRte], data[Model124InOutWRte_SF]); + 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]; diff --git a/sunspec/sunspecstorage.h b/sunspec/sunspecstorage.h index 49b6bdc..4995753 100644 --- a/sunspec/sunspecstorage.h +++ b/sunspec/sunspecstorage.h @@ -46,8 +46,8 @@ public: void getStorageModelDataBlock(); QUuid setGridCharging(bool enabled); - QUuid setDischargingRate(int rate); - QUuid setChargingRate(int rate); + QUuid setDischargingRate(float rate); + QUuid setChargingRate(float rate); QUuid setStorageControlMode(bool chargingEnabled, bool dischargingEnabled); enum StorageControl { @@ -79,7 +79,7 @@ public: Model124WChaGra = 1, Model124WDisChaGra = 2, Model124StorCtl_Mod = 3, - Model124WChaMax_SF = 16, + Model124WChaMax_SF = 16, Model124WChaDisChaGra_SF = 17, }; @@ -135,6 +135,14 @@ private: 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); From 688d51ef2547b1526898874985ba27cbec6f5a93 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Thu, 4 Feb 2021 14:08:57 +0100 Subject: [PATCH 15/16] updated readme and added translation --- sunspec/README.md | 17 +- ...bdec6-cf2c-4a0f-9709-42d951ca2d8b-en_US.ts | 1126 +++++++++++++++++ 2 files changed, 1135 insertions(+), 8 deletions(-) create mode 100644 sunspec/translations/cb4bdec6-cf2c-4a0f-9709-42d951ca2d8b-en_US.ts diff --git a/sunspec/README.md b/sunspec/README.md index 11999ea..64ffc1a 100644 --- a/sunspec/README.md +++ b/sunspec/README.md @@ -1,18 +1,19 @@ # SunSpec -Connect to SunSpec devices over Modbus TCP +Connect nymea to SunSpec devices over Modbus TCP ## Supported Things * SunSpec Inverter - * 101 & 111 Single phase inverter - * 102 & 112 Split phase inverter - * 103 & 113 Three phase inverter + * Model ID 101 & 111 Single phase inverter + * Model ID 102 & 112 Split phase inverter + * Model ID 103 & 113 Three phase inverter * SunSpec Meter - * Single phase meter - * Split phase meter - * 203 Three phase meter -* SunSpec Storage [124] + * 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 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}) + + + + From f9cbc4b74aaddb4a33b80372e9815cbdc075e1cc Mon Sep 17 00:00:00 2001 From: Boernsman Date: Fri, 5 Feb 2021 11:51:21 +0100 Subject: [PATCH 16/16] fixed debian install --- debian/control | 1 - debian/nymea-plugin-sunspec.install.in | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/debian/control b/debian/control index 288dfe8..8dfe067 100644 --- a/debian/control +++ b/debian/control @@ -35,7 +35,6 @@ Section: libs Depends: ${shlibs:Depends}, ${misc:Depends}, nymea-plugins-modbus-translations - nymea-plugins-translations Description: nymea.io plugin to send and receive generic modbus commands The nymea daemon is a plugin based IoT (Internet of Things) server. The server works like a translator for devices, things and services and diff --git a/debian/nymea-plugin-sunspec.install.in b/debian/nymea-plugin-sunspec.install.in index 03cc550..aae234d 100644 --- a/debian/nymea-plugin-sunspec.install.in +++ b/debian/nymea-plugin-sunspec.install.in @@ -1 +1 @@ -usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_devicepluginsunspec.so +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginsunspec.so