New plugin: Solax inverter, meter and battery
modbus-tool: Introduce queued update request mechanism for TCP connections
This commit is contained in:
parent
63387535cb
commit
4d54c662cb
8
debian/control
vendored
8
debian/control
vendored
@ -210,6 +210,14 @@ Description: nymea integration plugin for SMA solar inverters and meters
|
||||
This package contains the nymea integration plugin for SMA solar inverters and meters.
|
||||
|
||||
|
||||
Package: nymea-plugin-solax
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends},
|
||||
Description: nymea integration plugin for Solax modbus devices
|
||||
This package contains the nymea integration plugin for solax compatible solar inverters, meters and batteries.
|
||||
|
||||
|
||||
Package: nymea-plugin-stiebeleltron
|
||||
Architecture: any
|
||||
Section: libs
|
||||
|
||||
2
debian/nymea-plugin-solax.install.in
vendored
Normal file
2
debian/nymea-plugin-solax.install.in
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginsolax.so
|
||||
solax/translations/*qm usr/share/nymea/translations/
|
||||
Binary file not shown.
@ -23,6 +23,8 @@ The basic structure of the modbus register JSON looks like following example:
|
||||
"stringEndianness": "BigEndian",
|
||||
"errorLimitUntilNotReachable": 10,
|
||||
"checkReachableRegister": "registerPropertyName",
|
||||
"queuedRequests": false,
|
||||
"queuedRequestsDelay": 0,
|
||||
"enums": [
|
||||
{
|
||||
"name": "NameOfEnum",
|
||||
@ -159,6 +161,19 @@ Many modbus devices provide inforation using `Enums`, indicating a special state
|
||||
If a register represets an enum, you simply add the property `"enum": "NameOfEnum"` in the register map and the property will be defined using the resulting enum type. All convertion between enum and resulting modbus register value will be done automatically.
|
||||
|
||||
|
||||
## Queued requests
|
||||
|
||||
Some modbus devices can process only one request at the time, and sometimes even require a delay between requests. For this purpose the boolean property `queuedRequests` and integer property `queuedRequestsDelay` (milliseconds) property hase been introdiced. By default, requests are not queued and the delay 0 ms.
|
||||
|
||||
```
|
||||
{
|
||||
...
|
||||
"queuedRequests": false,
|
||||
"queuedRequestsDelay": 0,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Read schedules
|
||||
|
||||
### init
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -352,11 +352,11 @@ def writeBlockGetMethodDeclarations(fileDescriptor, registerDefinitions):
|
||||
|
||||
def writePropertyUpdateMethodDeclarations(fileDescriptor, registerDefinitions):
|
||||
for registerDefinition in registerDefinitions:
|
||||
if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'init':
|
||||
continue
|
||||
if 'access' in registerDefinition:
|
||||
if not 'R' in registerDefinition['access']:
|
||||
continue
|
||||
|
||||
propertyName = registerDefinition['id']
|
||||
propertyTyp = getCppDataType(registerDefinition)
|
||||
writeLine(fileDescriptor, ' void update%s();' % (propertyName[0].upper() + propertyName[1:]))
|
||||
|
||||
|
||||
@ -427,12 +427,23 @@ def writeBlocksUpdateMethodDeclarations(fileDescriptor, blockDefinitions):
|
||||
|
||||
def writeRegistersDebugLine(fileDescriptor, debugObjectParamName, registerDefinitions):
|
||||
for registerDefinition in registerDefinitions:
|
||||
if not 'R' in registerDefinition['access']:
|
||||
continue
|
||||
if 'access' in registerDefinition:
|
||||
if not 'R' in registerDefinition['access']:
|
||||
continue
|
||||
|
||||
propertyName = registerDefinition['id']
|
||||
propertyTyp = getCppDataType(registerDefinition)
|
||||
line = ('" - %s - %s: " << %s->%s()' % (registerDefinition['address'], registerDefinition['description'], debugObjectParamName, propertyName))
|
||||
registerType = registerDefinition['registerType']
|
||||
typeString = ''
|
||||
if registerType == 'holdingRegister':
|
||||
typeString = 'holding '
|
||||
elif registerType == 'inputRegister':
|
||||
typeString = 'input '
|
||||
elif registerType == 'coils':
|
||||
typeString = 'coils '
|
||||
elif registerType == 'discreteInputs':
|
||||
typeString = 'discrete'
|
||||
|
||||
line = ('" - %s %s | %s: " << %s->%s()' % (typeString, registerDefinition['address'], registerDefinition['description'], debugObjectParamName, propertyName))
|
||||
if 'unit' in registerDefinition and registerDefinition['unit'] != '':
|
||||
line += (' << " [%s]"' % registerDefinition['unit'])
|
||||
writeLine(fileDescriptor, ' debug.nospace().noquote() << %s << "\\n";' % (line))
|
||||
@ -440,8 +451,9 @@ def writeRegistersDebugLine(fileDescriptor, debugObjectParamName, registerDefini
|
||||
|
||||
def writePropertyChangedSignals(fileDescriptor, registerDefinitions):
|
||||
for registerDefinition in registerDefinitions:
|
||||
if not 'R' in registerDefinition['access']:
|
||||
continue
|
||||
if 'access' in registerDefinition:
|
||||
if not 'R' in registerDefinition['access']:
|
||||
continue
|
||||
|
||||
propertyName = registerDefinition['id']
|
||||
propertyTyp = getCppDataType(registerDefinition)
|
||||
@ -455,8 +467,9 @@ def writePropertyChangedSignals(fileDescriptor, registerDefinitions):
|
||||
|
||||
def writeProtectedPropertyMembers(fileDescriptor, registerDefinitions):
|
||||
for registerDefinition in registerDefinitions:
|
||||
if not 'R' in registerDefinition['access']:
|
||||
continue
|
||||
if 'access' in registerDefinition:
|
||||
if not 'R' in registerDefinition['access']:
|
||||
continue
|
||||
|
||||
propertyName = registerDefinition['id']
|
||||
propertyTyp = getCppDataType(registerDefinition)
|
||||
@ -467,10 +480,10 @@ def writeProtectedPropertyMembers(fileDescriptor, registerDefinitions):
|
||||
|
||||
|
||||
def writePropertyProcessMethodDeclaration(fileDescriptor, registerDefinitions):
|
||||
propertyVariables = []
|
||||
for registerDefinition in registerDefinitions:
|
||||
if not 'R' in registerDefinition['access']:
|
||||
continue
|
||||
if 'access' in registerDefinition:
|
||||
if not 'R' in registerDefinition['access']:
|
||||
continue
|
||||
|
||||
propertyName = registerDefinition['id']
|
||||
writeLine(fileDescriptor, ' void process%sRegisterValues(const QVector<quint16> &values);' % (propertyName[0].upper() + propertyName[1:]))
|
||||
@ -479,10 +492,10 @@ def writePropertyProcessMethodDeclaration(fileDescriptor, registerDefinitions):
|
||||
|
||||
|
||||
def writePropertyProcessMethodImplementations(fileDescriptor, className, registerDefinitions):
|
||||
propertyVariables = []
|
||||
for registerDefinition in registerDefinitions:
|
||||
if not 'R' in registerDefinition['access']:
|
||||
continue
|
||||
if 'access' in registerDefinition:
|
||||
if not 'R' in registerDefinition['access']:
|
||||
continue
|
||||
|
||||
propertyTyp = getCppDataType(registerDefinition)
|
||||
propertyName = registerDefinition['id']
|
||||
@ -498,3 +511,55 @@ def writePropertyProcessMethodImplementations(fileDescriptor, className, registe
|
||||
writeLine(fileDescriptor, ' }')
|
||||
writeLine(fileDescriptor, '}')
|
||||
writeLine(fileDescriptor)
|
||||
|
||||
|
||||
def writeSendNextQueuedInitRequestMethodImplementation(fileDescriptor, className):
|
||||
writeLine(fileDescriptor, 'void %s::sendNextQueuedInitRequest()' % (className))
|
||||
writeLine(fileDescriptor, '{')
|
||||
writeLine(fileDescriptor, ' if (m_initRequestQueue.isEmpty())')
|
||||
writeLine(fileDescriptor, ' return;')
|
||||
writeLine(fileDescriptor)
|
||||
writeLine(fileDescriptor, ' if (m_currentInitReply)')
|
||||
writeLine(fileDescriptor, ' return;')
|
||||
writeLine(fileDescriptor)
|
||||
writeLine(fileDescriptor, ' %s::Function function = m_initRequestQueue.dequeue();' % (className))
|
||||
writeLine(fileDescriptor, ' (this->*function)();')
|
||||
writeLine(fileDescriptor, '}')
|
||||
writeLine(fileDescriptor)
|
||||
|
||||
|
||||
def writeEnqueueInitRequestMethodImplementation(fileDescriptor, className):
|
||||
writeLine(fileDescriptor, 'void %s::enqueueInitRequest(%s::Function function)' % (className, className))
|
||||
writeLine(fileDescriptor, '{')
|
||||
writeLine(fileDescriptor, ' if (m_initRequestQueue.contains(function))')
|
||||
writeLine(fileDescriptor, ' return;')
|
||||
writeLine(fileDescriptor)
|
||||
writeLine(fileDescriptor, ' m_initRequestQueue.enqueue(function);')
|
||||
writeLine(fileDescriptor, '}')
|
||||
writeLine(fileDescriptor)
|
||||
|
||||
|
||||
def writeSendNextQueuedRequestMethodImplementation(fileDescriptor, className):
|
||||
writeLine(fileDescriptor, 'void %s::sendNextQueuedRequest()' % (className))
|
||||
writeLine(fileDescriptor, '{')
|
||||
writeLine(fileDescriptor, ' if (m_updateRequestQueue.isEmpty())')
|
||||
writeLine(fileDescriptor, ' return;')
|
||||
writeLine(fileDescriptor)
|
||||
writeLine(fileDescriptor, ' if (m_currentUpdateReply)')
|
||||
writeLine(fileDescriptor, ' return;')
|
||||
writeLine(fileDescriptor)
|
||||
writeLine(fileDescriptor, ' %s::Function function = m_updateRequestQueue.dequeue();' % (className))
|
||||
writeLine(fileDescriptor, ' (this->*function)();')
|
||||
writeLine(fileDescriptor, '}')
|
||||
writeLine(fileDescriptor)
|
||||
|
||||
|
||||
def writeEnqueueRequestMethodImplementation(fileDescriptor, className):
|
||||
writeLine(fileDescriptor, 'void %s::enqueueRequest(%s::Function function)' % (className, className))
|
||||
writeLine(fileDescriptor, '{')
|
||||
writeLine(fileDescriptor, ' if (m_updateRequestQueue.contains(function))')
|
||||
writeLine(fileDescriptor, ' return;')
|
||||
writeLine(fileDescriptor)
|
||||
writeLine(fileDescriptor, ' m_updateRequestQueue.enqueue(function);')
|
||||
writeLine(fileDescriptor, '}')
|
||||
writeLine(fileDescriptor)
|
||||
@ -62,6 +62,10 @@ def writeTcpHeaderFile():
|
||||
for enumDefinition in registerJson['enums']:
|
||||
writeEnumDefinition(headerFile, enumDefinition)
|
||||
|
||||
if queuedRequests:
|
||||
writeLine(headerFile, ' typedef void(%s::*Function)(void);' % className)
|
||||
writeLine(headerFile)
|
||||
|
||||
# Constructor
|
||||
writeLine(headerFile, ' explicit %s(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent = nullptr);' % className)
|
||||
writeLine(headerFile, ' explicit %s(ModbusTcpMaster *modbusTcpMaster, quint16 slaveId, QObject *parent = nullptr);' % className)
|
||||
@ -72,6 +76,12 @@ def writeTcpHeaderFile():
|
||||
writeLine(headerFile)
|
||||
writeLine(headerFile, ' bool reachable() const;')
|
||||
writeLine(headerFile)
|
||||
|
||||
# Write init and update method declarations
|
||||
writeLine(headerFile, ' virtual bool initialize();')
|
||||
writeLine(headerFile, ' virtual bool update();')
|
||||
writeLine(headerFile)
|
||||
|
||||
writeLine(headerFile, ' ModbusDataUtils::ByteOrder endianness() const;')
|
||||
writeLine(headerFile, ' void setEndianness(ModbusDataUtils::ByteOrder endianness);')
|
||||
writeLine(headerFile)
|
||||
@ -109,11 +119,6 @@ def writeTcpHeaderFile():
|
||||
|
||||
writeLine(headerFile)
|
||||
|
||||
# Write init and update method declarations
|
||||
writeLine(headerFile, ' virtual bool initialize();')
|
||||
writeLine(headerFile, ' virtual bool update();')
|
||||
writeLine(headerFile)
|
||||
|
||||
writeLine(headerFile, 'public slots:')
|
||||
writeLine(headerFile, ' bool connectDevice();')
|
||||
writeLine(headerFile, ' void disconnectDevice();')
|
||||
@ -166,6 +171,14 @@ def writeTcpHeaderFile():
|
||||
writeLine(headerFile, ' ModbusDataUtils::ByteOrder m_stringEndianness = ModbusDataUtils::ByteOrder%s;' % stringEndianness)
|
||||
writeLine(headerFile, ' quint16 m_slaveId = 1;')
|
||||
writeLine(headerFile)
|
||||
|
||||
if queuedRequests:
|
||||
writeLine(headerFile, ' QModbusReply *m_currentInitReply = nullptr;')
|
||||
writeLine(headerFile, ' QQueue<%s::Function> m_initRequestQueue;' % className)
|
||||
writeLine(headerFile, ' QModbusReply *m_currentUpdateReply = nullptr;')
|
||||
writeLine(headerFile, ' QQueue<%s::Function> m_updateRequestQueue;' % className)
|
||||
writeLine(headerFile)
|
||||
|
||||
writeLine(headerFile, ' bool m_reachable = false;')
|
||||
writeLine(headerFile, ' QModbusReply *m_checkRechableReply = nullptr;')
|
||||
writeLine(headerFile, ' uint m_checkReachableRetries = 0;')
|
||||
@ -178,16 +191,24 @@ def writeTcpHeaderFile():
|
||||
writeLine(headerFile, ' QVector<QModbusReply *> m_pendingUpdateReplies;')
|
||||
writeLine(headerFile)
|
||||
writeLine(headerFile, ' QObject *m_initObject = nullptr;')
|
||||
writeLine(headerFile, ' void verifyInitFinished();')
|
||||
writeLine(headerFile, ' bool verifyInitFinished();')
|
||||
writeLine(headerFile, ' void finishInitialization(bool success);')
|
||||
writeLine(headerFile)
|
||||
|
||||
writeLine(headerFile, ' void setupConnection();')
|
||||
writeLine(headerFile)
|
||||
writeLine(headerFile, ' void verifyUpdateFinished();')
|
||||
writeLine(headerFile, ' bool verifyUpdateFinished();')
|
||||
writeLine(headerFile)
|
||||
writeLine(headerFile, ' void onReachabilityCheckFailed();')
|
||||
writeLine(headerFile, ' void evaluateReachableState();')
|
||||
|
||||
if queuedRequests:
|
||||
writeLine(headerFile)
|
||||
writeLine(headerFile, ' void sendNextQueuedInitRequest();')
|
||||
writeLine(headerFile, ' void enqueueInitRequest(%s::Function function);' % (className))
|
||||
writeLine(headerFile, ' void sendNextQueuedRequest();')
|
||||
writeLine(headerFile, ' void enqueueRequest(%s::Function function);' % (className))
|
||||
|
||||
# End of class
|
||||
writeLine(headerFile)
|
||||
writeLine(headerFile, '};')
|
||||
@ -205,6 +226,7 @@ def writeTcpSourceFile():
|
||||
writeLicenseHeader(sourceFile)
|
||||
writeLine(sourceFile)
|
||||
writeLine(sourceFile, '#include "%s"' % headerFileName)
|
||||
writeLine(sourceFile)
|
||||
writeLine(sourceFile, '#include <loggingcategories.h>')
|
||||
writeLine(sourceFile, '#include <math.h>')
|
||||
writeLine(sourceFile, '#include <QTimer>')
|
||||
@ -310,8 +332,8 @@ def writeTcpSourceFile():
|
||||
if 'blocks' in registerJson:
|
||||
blocks = registerJson['blocks']
|
||||
|
||||
writeInitMethodImplementationTcp(sourceFile, className, registerJson['registers'], blocks)
|
||||
writeUpdateMethodTcp(sourceFile, className, registerJson['registers'], blocks)
|
||||
writeInitMethodImplementationTcp(sourceFile, className, registerJson['registers'], blocks, queuedRequests)
|
||||
writeUpdateMethodTcp(sourceFile, className, registerJson['registers'], blocks, queuedRequests)
|
||||
|
||||
writeLine(sourceFile, 'bool %s::connectDevice()' % (className))
|
||||
writeLine(sourceFile, '{')
|
||||
@ -332,13 +354,13 @@ def writeTcpSourceFile():
|
||||
writeLine(sourceFile)
|
||||
|
||||
# Write update methods
|
||||
writePropertyUpdateMethodImplementationsTcp(sourceFile, className, registerJson['registers'])
|
||||
writePropertyUpdateMethodImplementationsTcp(sourceFile, className, registerJson['registers'], queuedRequests, queuedRequestsDelay)
|
||||
if 'blocks' in registerJson:
|
||||
for blockDefinition in registerJson['blocks']:
|
||||
writePropertyUpdateMethodImplementationsTcp(sourceFile, className, blockDefinition['registers'])
|
||||
writePropertyUpdateMethodImplementationsTcp(sourceFile, className, blockDefinition['registers'], queuedRequests, queuedRequestsDelay)
|
||||
|
||||
# Write block update method
|
||||
writeBlockUpdateMethodImplementationsTcp(sourceFile, className, registerJson['blocks'])
|
||||
writeBlockUpdateMethodImplementationsTcp(sourceFile, className, registerJson['blocks'], queuedRequests, queuedRequestsDelay)
|
||||
|
||||
# Write internal protected property read method implementations
|
||||
writeInternalPropertyReadMethodImplementationsTcp(sourceFile, className, registerJson['registers'])
|
||||
@ -377,11 +399,19 @@ def writeTcpSourceFile():
|
||||
|
||||
writeTestReachabilityImplementationsTcp(sourceFile, className, registerJson['registers'], checkReachableRegister)
|
||||
|
||||
writeLine(sourceFile, 'void %s::verifyInitFinished()' % (className))
|
||||
writeLine(sourceFile, 'bool %s::verifyInitFinished()' % (className))
|
||||
writeLine(sourceFile, '{')
|
||||
writeLine(sourceFile, ' if (m_pendingInitReplies.isEmpty()) {')
|
||||
writeLine(sourceFile, ' finishInitialization(true);')
|
||||
writeLine(sourceFile, ' }')
|
||||
if queuedRequests:
|
||||
writeLine(sourceFile, ' if (m_initRequestQueue.isEmpty() && !m_currentInitReply) {')
|
||||
writeLine(sourceFile, ' finishInitialization(true);')
|
||||
writeLine(sourceFile, ' return true;')
|
||||
writeLine(sourceFile, ' }')
|
||||
else:
|
||||
writeLine(sourceFile, ' if (m_pendingInitReplies.isEmpty()) {')
|
||||
writeLine(sourceFile, ' finishInitialization(true);')
|
||||
writeLine(sourceFile, ' return true;')
|
||||
writeLine(sourceFile, ' }')
|
||||
writeLine(sourceFile, ' return false;')
|
||||
writeLine(sourceFile, '}')
|
||||
writeLine(sourceFile)
|
||||
|
||||
@ -393,12 +423,19 @@ def writeTcpSourceFile():
|
||||
writeLine(sourceFile, ' qCWarning(dc%s()) << "Initialization finished of %s" << m_modbusTcpMaster->hostAddress().toString() << "failed.";' % (className, className))
|
||||
writeLine(sourceFile, ' }')
|
||||
writeLine(sourceFile)
|
||||
writeLine(sourceFile, ' // Cleanup init')
|
||||
writeLine(sourceFile, ' delete m_initObject;')
|
||||
writeLine(sourceFile, ' m_initObject = nullptr;')
|
||||
writeLine(sourceFile, ' m_pendingInitReplies.clear();')
|
||||
|
||||
if queuedRequests:
|
||||
writeLine(sourceFile, ' m_initRequestQueue.clear();')
|
||||
else:
|
||||
writeLine(sourceFile, ' // Cleanup init')
|
||||
writeLine(sourceFile, ' delete m_initObject;')
|
||||
writeLine(sourceFile, ' m_initObject = nullptr;')
|
||||
writeLine(sourceFile, ' m_pendingInitReplies.clear();')
|
||||
|
||||
writeLine(sourceFile)
|
||||
writeLine(sourceFile, ' emit initializationFinished(success);')
|
||||
writeLine(sourceFile, ' QTimer::singleShot(0, this, [this, success](){')
|
||||
writeLine(sourceFile, ' emit initializationFinished(success);')
|
||||
writeLine(sourceFile, ' });')
|
||||
writeLine(sourceFile, '}')
|
||||
writeLine(sourceFile)
|
||||
|
||||
@ -410,6 +447,10 @@ def writeTcpSourceFile():
|
||||
writeLine(sourceFile, ' // Cleanup before starting to initialize')
|
||||
writeLine(sourceFile, ' m_pendingInitReplies.clear();')
|
||||
writeLine(sourceFile, ' m_pendingUpdateReplies.clear();')
|
||||
if queuedRequests:
|
||||
writeLine(sourceFile, ' m_updateRequestQueue.clear();')
|
||||
writeLine(sourceFile, ' m_initRequestQueue.clear();')
|
||||
|
||||
writeLine(sourceFile, ' m_communicationWorking = false;')
|
||||
writeLine(sourceFile, ' m_communicationFailedCounter = 0;')
|
||||
writeLine(sourceFile, ' m_checkReachableRetriesCount = 0;')
|
||||
@ -419,6 +460,11 @@ def writeTcpSourceFile():
|
||||
writeLine(sourceFile, ' m_communicationWorking = false;')
|
||||
writeLine(sourceFile, ' m_communicationFailedCounter = 0;')
|
||||
writeLine(sourceFile, ' m_checkReachableRetriesCount = 0;')
|
||||
|
||||
if queuedRequests:
|
||||
writeLine(sourceFile, ' m_updateRequestQueue.clear();')
|
||||
writeLine(sourceFile, ' m_initRequestQueue.clear();')
|
||||
|
||||
writeLine(sourceFile, ' }')
|
||||
writeLine(sourceFile)
|
||||
writeLine(sourceFile, ' evaluateReachableState();')
|
||||
@ -426,11 +472,18 @@ def writeTcpSourceFile():
|
||||
writeLine(sourceFile, '}')
|
||||
writeLine(sourceFile)
|
||||
|
||||
writeLine(sourceFile, 'void %s::verifyUpdateFinished()' % (className))
|
||||
writeLine(sourceFile, 'bool %s::verifyUpdateFinished()' % (className))
|
||||
writeLine(sourceFile, '{')
|
||||
writeLine(sourceFile, ' if (m_pendingUpdateReplies.isEmpty()) {')
|
||||
writeLine(sourceFile, ' emit updateFinished();')
|
||||
if queuedRequests:
|
||||
writeLine(sourceFile, ' if (m_updateRequestQueue.isEmpty() && !m_currentUpdateReply) {')
|
||||
writeLine(sourceFile, ' emit updateFinished();')
|
||||
writeLine(sourceFile, ' return true;')
|
||||
else:
|
||||
writeLine(sourceFile, ' if (m_pendingUpdateReplies.isEmpty()) {')
|
||||
writeLine(sourceFile, ' emit updateFinished();')
|
||||
writeLine(sourceFile, ' return true;')
|
||||
writeLine(sourceFile, ' }')
|
||||
writeLine(sourceFile, ' return false;')
|
||||
writeLine(sourceFile, '}')
|
||||
writeLine(sourceFile)
|
||||
|
||||
@ -461,7 +514,11 @@ def writeTcpSourceFile():
|
||||
writeLine(sourceFile, '}')
|
||||
writeLine(sourceFile)
|
||||
|
||||
|
||||
if queuedRequests:
|
||||
writeSendNextQueuedInitRequestMethodImplementation(sourceFile, className)
|
||||
writeEnqueueInitRequestMethodImplementation(sourceFile, className)
|
||||
writeSendNextQueuedRequestMethodImplementation(sourceFile, className)
|
||||
writeEnqueueRequestMethodImplementation(sourceFile, className)
|
||||
|
||||
# Write the debug print
|
||||
debugObjectParamName = className[0].lower() + className[1:]
|
||||
@ -622,7 +679,7 @@ def writeRtuHeaderFile():
|
||||
writeLine(headerFile, ' void verifyInitFinished();')
|
||||
writeLine(headerFile, ' void finishInitialization(bool success);')
|
||||
writeLine(headerFile)
|
||||
writeLine(headerFile, ' void verifyUpdateFinished();')
|
||||
writeLine(headerFile, ' bool verifyUpdateFinished();')
|
||||
writeLine(headerFile)
|
||||
writeLine(headerFile, ' void onReachabilityCheckFailed();')
|
||||
writeLine(headerFile, ' void evaluateReachableState();')
|
||||
@ -645,6 +702,7 @@ def writeRtuSourceFile():
|
||||
writeLicenseHeader(sourceFile)
|
||||
|
||||
writeLine(sourceFile, '#include "%s"' % headerFileName)
|
||||
writeLine(sourceFile)
|
||||
writeLine(sourceFile, '#include <loggingcategories.h>')
|
||||
writeLine(sourceFile, '#include <math.h>')
|
||||
writeLine(sourceFile, '#include <QTimer>')
|
||||
@ -831,15 +889,26 @@ def writeRtuSourceFile():
|
||||
writeLine(sourceFile, ' m_initObject = nullptr;')
|
||||
writeLine(sourceFile, ' m_pendingInitReplies.clear();')
|
||||
writeLine(sourceFile)
|
||||
writeLine(sourceFile, ' emit initializationFinished(success);')
|
||||
writeLine(sourceFile, ' QTimer::singleShot(0, this, [this, success](){')
|
||||
writeLine(sourceFile, ' emit initializationFinished(success);')
|
||||
writeLine(sourceFile, ' });')
|
||||
|
||||
if queuedRequests:
|
||||
writeLine(sourceFile)
|
||||
writeLine(sourceFile, ' m_pendingInitReplies.clear();')
|
||||
writeLine(sourceFile, ' m_pendingUpdateReplies.clear();')
|
||||
writeLine(sourceFile, ' update();')
|
||||
|
||||
writeLine(sourceFile, '}')
|
||||
writeLine(sourceFile)
|
||||
|
||||
writeLine(sourceFile, 'void %s::verifyUpdateFinished()' % (className))
|
||||
writeLine(sourceFile, 'bool %s::verifyUpdateFinished()' % (className))
|
||||
writeLine(sourceFile, '{')
|
||||
writeLine(sourceFile, ' if (m_pendingUpdateReplies.isEmpty()) {')
|
||||
writeLine(sourceFile, ' emit updateFinished();')
|
||||
writeLine(sourceFile, ' return true;')
|
||||
writeLine(sourceFile, ' }')
|
||||
writeLine(sourceFile, ' return false;')
|
||||
writeLine(sourceFile, '}')
|
||||
writeLine(sourceFile)
|
||||
|
||||
@ -977,15 +1046,28 @@ else:
|
||||
logger.debug('Verified successfully checkReachableRegister: %s' % checkReachableRegister['id'])
|
||||
|
||||
|
||||
queuedRequests = False
|
||||
queuedRequestsDelay = 0
|
||||
|
||||
if 'queuedRequests' in registerJson:
|
||||
queuedRequests = registerJson['queuedRequests']
|
||||
|
||||
if 'queuedRequestsDelay' in registerJson:
|
||||
queuedRequestsDelay = registerJson['queuedRequestsDelay']
|
||||
|
||||
# Inform about parsed and validated configs if debugging enabled
|
||||
logger.debug('Script path: %s' % scriptPath)
|
||||
logger.debug('Output directory: %s' % outputDirectory)
|
||||
logger.debug('Class name prefix: %s' % classNamePrefix)
|
||||
logger.debug('Endianness: %s' % endianness)
|
||||
logger.debug('String endianness: %s' % stringEndianness)
|
||||
logger.debug('Queued requests: %s' % queuedRequests)
|
||||
logger.debug('Queued requests delay: %s ms' % queuedRequestsDelay)
|
||||
|
||||
logger.debug('Error limit until not reachable: %s' % errorLimitUntilNotReachable)
|
||||
logger.debug('Check reachable register: %s' % checkReachableRegister['id'])
|
||||
|
||||
|
||||
protocol = 'TCP'
|
||||
if 'protocol' in registerJson:
|
||||
protocol = registerJson['protocol']
|
||||
@ -1063,7 +1145,7 @@ projectIncludeFile = open(projectIncludeFilePath, 'w')
|
||||
writeLine(projectIncludeFile, '# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #')
|
||||
writeLine(projectIncludeFile, '#')
|
||||
writeLine(projectIncludeFile, '# This file has been autogenerated.')
|
||||
writeLine(projectIncludeFile, '# Any changes in this file may be overwritten from qmake.')
|
||||
writeLine(projectIncludeFile, '# Any changes in this file may be overwritten on a rebuild.')
|
||||
writeLine(projectIncludeFile, '#')
|
||||
writeLine(projectIncludeFile, '# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #')
|
||||
writeLine(projectIncludeFile)
|
||||
|
||||
@ -21,6 +21,7 @@ PLUGIN_DIRS = \
|
||||
schrack \
|
||||
senseair \
|
||||
sma \
|
||||
solax \
|
||||
stiebeleltron \
|
||||
sunspec \
|
||||
unipi \
|
||||
|
||||
@ -122,10 +122,10 @@ void SmaModbusSolarInverterDiscovery::checkNetworkDevice(const NetworkDeviceInfo
|
||||
m_discoveryResults.append(result);
|
||||
|
||||
qCDebug(dcSma()) << "Discovery: --> Found" << result.productName;
|
||||
qCDebug(dcSma()) << " Device name:" << result.deviceName;
|
||||
qCDebug(dcSma()) << " Serial number:" << result.serialNumber;
|
||||
qCDebug(dcSma()) << " Software version:" << result.softwareVersion;
|
||||
qCDebug(dcSma()) << " " << result.networkDeviceInfo;
|
||||
qCDebug(dcSma()) << "Discovery: Device name:" << result.deviceName;
|
||||
qCDebug(dcSma()) << "Discovery: Serial number:" << result.serialNumber;
|
||||
qCDebug(dcSma()) << "Discovery: Software version:" << result.softwareVersion;
|
||||
qCDebug(dcSma()) << "Discovery: " << result.networkDeviceInfo;
|
||||
|
||||
// Done with this connection
|
||||
cleanupConnection(connection);
|
||||
|
||||
19
solax/README.md
Normal file
19
solax/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Solax
|
||||
|
||||
Connects Solax inverters to nymea.
|
||||
|
||||
Currently supported models:
|
||||
|
||||
* X1 models
|
||||
* X3 models
|
||||
* G4 inverters
|
||||
* Meters
|
||||
* Batteries
|
||||
|
||||
## Connecting a second inverter
|
||||
|
||||
There is the possibility to connect an other solar inverter to the meter 2 interface of your Solax installation. In order to have both possibilities you can switch between a second meter or a second inverter in the settings of the main inverter within nymea.
|
||||
|
||||
# Requirements
|
||||
|
||||
nymea uses the modbus TCP connection in order to connect to the Solax inverter. Therefore the inverter must be reachable using the local network. The inverter allows only to have **one TCP connection**, please make sure there is no other service or device using modbus TCP with your inverter besides nymea.
|
||||
472
solax/integrationpluginsolax.cpp
Normal file
472
solax/integrationpluginsolax.cpp
Normal file
@ -0,0 +1,472 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2023, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "integrationpluginsolax.h"
|
||||
#include "plugininfo.h"
|
||||
#include "solaxdiscovery.h"
|
||||
|
||||
#include <network/networkdevicediscovery.h>
|
||||
#include <hardwaremanager.h>
|
||||
|
||||
IntegrationPluginSolax::IntegrationPluginSolax()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void IntegrationPluginSolax::discoverThings(ThingDiscoveryInfo *info)
|
||||
{
|
||||
if (!hardwareManager()->networkDeviceDiscovery()->available()) {
|
||||
qCWarning(dcSolax()) << "The network discovery is not available on this platform.";
|
||||
info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a discovery with the info as parent for auto deleting the object once the discovery info is done
|
||||
SolaxDiscovery *discovery = new SolaxDiscovery(hardwareManager()->networkDeviceDiscovery(), 502, 1, info);
|
||||
connect(discovery, &SolaxDiscovery::discoveryFinished, info, [=](){
|
||||
foreach (const SolaxDiscovery::SolaxDiscoveryResult &result, discovery->discoveryResults()) {
|
||||
|
||||
QString title;
|
||||
if (result.productName.isEmpty()) {
|
||||
title = "SolaX Inverter";
|
||||
} else {
|
||||
title = "SolaX " + result.productName;
|
||||
}
|
||||
|
||||
if (!result.serialNumber.isEmpty())
|
||||
title.append(" " + result.serialNumber);
|
||||
|
||||
ThingDescriptor descriptor(solaxInverterTcpThingClassId, title, result.networkDeviceInfo.address().toString() + " " + result.networkDeviceInfo.macAddress());
|
||||
qCInfo(dcSolax()) << "Discovered:" << descriptor.title() << descriptor.description();
|
||||
|
||||
// Check if we already have set up this device
|
||||
Things existingThings = myThings().filterByParam(solaxInverterTcpThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
|
||||
if (existingThings.count() == 1) {
|
||||
qCDebug(dcSolax()) << "This solax inverter already exists in the system:" << result.networkDeviceInfo;
|
||||
descriptor.setThingId(existingThings.first()->id());
|
||||
}
|
||||
|
||||
ParamList params;
|
||||
params << Param(solaxInverterTcpThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
|
||||
// Note: if we discover also the port and modbusaddress, we must fill them in from the discovery here, for now everywhere the defaults...
|
||||
descriptor.setParams(params);
|
||||
info->addThingDescriptor(descriptor);
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
|
||||
// Start the discovery process
|
||||
discovery->startDiscovery();
|
||||
}
|
||||
|
||||
void IntegrationPluginSolax::setupThing(ThingSetupInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
qCInfo(dcSolax()) << "Setup" << thing << thing->params();
|
||||
|
||||
// Inverter (connection)
|
||||
if (thing->thingClassId() == solaxInverterTcpThingClassId) {
|
||||
|
||||
// Handle reconfigure
|
||||
if (m_tcpConnections.contains(thing)) {
|
||||
qCDebug(dcSolax()) << "Reconfiguring existing thing" << thing->name();
|
||||
m_tcpConnections.take(thing)->deleteLater();
|
||||
if (m_monitors.contains(thing)) {
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
}
|
||||
}
|
||||
|
||||
MacAddress macAddress = MacAddress(thing->paramValue(solaxInverterTcpThingMacAddressParamTypeId).toString());
|
||||
if (!macAddress.isValid()) {
|
||||
qCWarning(dcSolax()) << "The configured mac address is not valid" << thing->params();
|
||||
info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The MAC address is not known. Please reconfigure the thing."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the monitor
|
||||
NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress);
|
||||
m_monitors.insert(thing, monitor);
|
||||
connect(info, &ThingSetupInfo::aborted, monitor, [=](){
|
||||
// Clean up in case the setup gets aborted
|
||||
if (m_monitors.contains(thing)) {
|
||||
qCDebug(dcSolax()) << "Unregister monitor because the setup has been aborted.";
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
}
|
||||
});
|
||||
|
||||
QHostAddress address = m_monitors.value(thing)->networkDeviceInfo().address();
|
||||
uint port = thing->paramValue(solaxInverterTcpThingPortParamTypeId).toUInt();
|
||||
quint16 slaveId = thing->paramValue(solaxInverterTcpThingSlaveIdParamTypeId).toUInt();
|
||||
|
||||
qCInfo(dcSolax()) << "Setting up solax on" << address.toString() << port << "unit ID:" << slaveId;
|
||||
SolaxModbusTcpConnection *solaxConnection = new SolaxModbusTcpConnection(address, port, slaveId, this);
|
||||
connect(info, &ThingSetupInfo::aborted, solaxConnection, &SolaxModbusTcpConnection::deleteLater);
|
||||
|
||||
// Reconnect on monitor reachable changed
|
||||
connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){
|
||||
qCDebug(dcSolax()) << "Network device monitor reachable changed for" << thing->name() << reachable;
|
||||
if (!thing->setupComplete())
|
||||
return;
|
||||
|
||||
if (reachable && !thing->stateValue("connected").toBool()) {
|
||||
solaxConnection->modbusTcpMaster()->setHostAddress(monitor->networkDeviceInfo().address());
|
||||
solaxConnection->reconnectDevice();
|
||||
} else if (!reachable) {
|
||||
// Note: We disable autoreconnect explicitly and we will
|
||||
// connect the device once the monitor says it is reachable again
|
||||
solaxConnection->disconnectDevice();
|
||||
}
|
||||
});
|
||||
|
||||
connect(solaxConnection, &SolaxModbusTcpConnection::reachableChanged, thing, [this, thing, solaxConnection](bool reachable){
|
||||
qCDebug(dcSolax()) << "Reachable changed to" << reachable << "for" << thing;
|
||||
if (reachable) {
|
||||
// Connected true will be set after successfull init
|
||||
solaxConnection->initialize();
|
||||
} else {
|
||||
thing->setStateValue("connected", false);
|
||||
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
|
||||
childThing->setStateValue("connected", false);
|
||||
}
|
||||
|
||||
// Reset any energy data due to connection loss
|
||||
Thing *child = getMeterThing(thing, 1);
|
||||
if (child) {
|
||||
child->setStateValue(solaxMeterCurrentPowerStateTypeId, 0);
|
||||
}
|
||||
|
||||
child = getMeterThing(thing, 2);
|
||||
if (child) {
|
||||
child->setStateValue(solaxMeterCurrentPowerStateTypeId, 0);
|
||||
child->setStateValue(solaxMeterCurrentPowerPhaseAStateTypeId, 0);
|
||||
child->setStateValue(solaxMeterCurrentPowerPhaseBStateTypeId, 0);
|
||||
child->setStateValue(solaxMeterCurrentPowerPhaseCStateTypeId, 0);
|
||||
}
|
||||
|
||||
Things inverterThings = myThings().filterByParentId(thing->id()).filterByThingClassId(solaxInverterChildThingClassId);
|
||||
if (!inverterThings.isEmpty()) {
|
||||
child = inverterThings.first();
|
||||
child->setSettingValue(solaxInverterChildCurrentPowerStateTypeId, 0);
|
||||
}
|
||||
|
||||
child = getBatteryThing(thing);
|
||||
if (child) {
|
||||
child->setStateValue(solaxBatteryVoltageStateTypeId, solaxConnection->batteryVoltage());
|
||||
child->setStateValue(solaxBatteryCurrentPowerStateTypeId, 0);
|
||||
child->setStateValue(solaxBatteryChargingStateStateTypeId, "idle");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connect(solaxConnection, &SolaxModbusTcpConnection::initializationFinished, thing, [=](bool success){
|
||||
thing->setStateValue("connected", success);
|
||||
|
||||
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
|
||||
childThing->setStateValue("connected", success);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
// Try once to reconnect the device
|
||||
solaxConnection->reconnectDevice();
|
||||
} else {
|
||||
// Start update cycle
|
||||
solaxConnection->update();
|
||||
}
|
||||
});
|
||||
|
||||
connect(solaxConnection, &SolaxModbusTcpConnection::updateFinished, thing, [=](){
|
||||
qCDebug(dcSolax()) << "Updated" << solaxConnection;
|
||||
|
||||
ThingClass meterThingClass = thingClass(solaxMeterThingClassId);
|
||||
|
||||
// Check if we have to create the meter for this solax inverter, or remove it due to communication errors
|
||||
if (myThings().filterByParentId(thing->id()).filterByThingClassId(solaxMeterThingClassId).isEmpty()) {
|
||||
if (solaxConnection->meter1ComState() == 1 && myThings().filterByParentId(thing->id()).filterByThingClassId(solaxMeterThingClassId).filterByParam(solaxMeterThingIdParamTypeId, 1).isEmpty()) {
|
||||
qCDebug(dcSolax()) << "There is no meter set up for this inverter. Creating a meter 1 for" << thing << solaxConnection->modbusTcpMaster();
|
||||
ThingDescriptor descriptor(solaxMeterThingClassId, meterThingClass.displayName(), QString(), thing->id());
|
||||
ParamList params;
|
||||
params.append(Param(solaxMeterThingIdParamTypeId, 1));
|
||||
descriptor.setParams(params);
|
||||
emit autoThingsAppeared(ThingDescriptors() << descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: it is possible to connect an additional inverter to the solax inverter, which will be measured by the meter 2.
|
||||
// if so, we create a child inverter instead of the second meter thing.
|
||||
if (solaxConnection->meter2ComState() == 1) {
|
||||
if (thing->setting(solaxInverterTcpSettingsMeter2InverterParamTypeId).toBool()) {
|
||||
if (myThings().filterByParentId(thing->id()).filterByThingClassId(solaxInverterChildThingClassId).isEmpty()) {
|
||||
// Add the meter 2 as child inverter
|
||||
emit autoThingsAppeared(ThingDescriptors() << ThingDescriptor(solaxInverterChildThingClassId, "SolaX inverter", QString(), thing->id()));
|
||||
}
|
||||
} else {
|
||||
if (myThings().filterByParentId(thing->id()).filterByThingClassId(solaxMeterThingClassId).filterByParam(solaxMeterThingIdParamTypeId, 2).isEmpty()) {
|
||||
ThingDescriptor descriptor(solaxMeterThingClassId, meterThingClass.displayName() + " 2", QString(), thing->id());
|
||||
ParamList params;
|
||||
params.append(Param(solaxMeterThingIdParamTypeId, 2));
|
||||
descriptor.setParams(params);
|
||||
emit autoThingsAppeared(ThingDescriptors() << descriptor);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Communication error with meter 2, remove any child devices for meter 2 registers
|
||||
cleanupMeter2(thing);
|
||||
cleanupChildInverters(thing);
|
||||
}
|
||||
|
||||
// Check if we have to create the battery for the solax inverter
|
||||
if (solaxConnection->batteryConnected() != 0 && myThings().filterByParentId(thing->id()).filterByThingClassId(solaxBatteryThingClassId).isEmpty()) {
|
||||
qCDebug(dcSolax()) << "There is a battery connected but not set up yet. Creating a battery...";
|
||||
ThingClass batteryThingClass = thingClass(solaxBatteryThingClassId);
|
||||
ThingDescriptor descriptor(solaxBatteryThingClassId, batteryThingClass.displayName(), QString(), thing->id());
|
||||
emit autoThingsAppeared(ThingDescriptors() << descriptor);
|
||||
}
|
||||
|
||||
// Update inverter states
|
||||
thing->setStateValue(solaxInverterTcpCurrentPowerStateTypeId, -solaxConnection->inverterPower());
|
||||
thing->setStateValue(solaxInverterTcpCurrentStateTypeId, -solaxConnection->inverterCurrent());
|
||||
thing->setStateValue(solaxInverterTcpCurrentVoltageStateTypeId, solaxConnection->inverterVoltage());
|
||||
thing->setStateValue(solaxInverterTcpTemperatureStateTypeId, solaxConnection->temperature());
|
||||
thing->setStateValue(solaxInverterTcpFrequencyStateTypeId, solaxConnection->inverterFrequency());
|
||||
thing->setStateValue(solaxInverterTcpTotalEnergyProducedStateTypeId, solaxConnection->totalEnergyProduced());
|
||||
|
||||
// Update the meter 1 if available
|
||||
Thing *meterThing = getMeterThing(thing, 1);
|
||||
if (meterThing) {
|
||||
meterThing->setStateValue(solaxMeterTotalEnergyConsumedStateTypeId, solaxConnection->meterTotalEnergyConsumend());
|
||||
meterThing->setStateValue(solaxMeterTotalEnergyProducedStateTypeId, solaxConnection->meterTotalEnergyProduced());
|
||||
|
||||
// Power
|
||||
meterThing->setStateValue(solaxMeterCurrentPowerStateTypeId, -solaxConnection->meterPower());
|
||||
}
|
||||
|
||||
// Update inverter 2 states if available
|
||||
if (thing->setting(solaxInverterTcpSettingsMeter2InverterParamTypeId).toBool()) {
|
||||
Things childInverters = myThings().filterByParentId(thing->id()).filterByThingClassId(solaxInverterChildThingClassId);
|
||||
if (!childInverters.isEmpty()) {
|
||||
Thing *childInverter = childInverters.first();
|
||||
childInverter->setStateValue(solaxInverterChildCurrentPowerStateTypeId, -solaxConnection->meter2Power());
|
||||
childInverter->setStateValue(solaxInverterChildTotalEnergyProducedStateTypeId, solaxConnection->meter2EnergyProduced());
|
||||
}
|
||||
} else {
|
||||
// Update the meter 2 if available
|
||||
meterThing = getMeterThing(thing, 2);
|
||||
if (meterThing) {
|
||||
meterThing->setStateValue(solaxMeterTotalEnergyConsumedStateTypeId, solaxConnection->meter2EnergyConsumed());
|
||||
meterThing->setStateValue(solaxMeterTotalEnergyProducedStateTypeId, solaxConnection->meter2EnergyProduced());
|
||||
|
||||
// Power
|
||||
meterThing->setStateValue(solaxMeterCurrentPowerStateTypeId, solaxConnection->meter2Power());
|
||||
meterThing->setStateValue(solaxMeterCurrentPowerPhaseAStateTypeId, solaxConnection->meter2PowerR());
|
||||
meterThing->setStateValue(solaxMeterCurrentPowerPhaseBStateTypeId, solaxConnection->meter2PowerS());
|
||||
meterThing->setStateValue(solaxMeterCurrentPowerPhaseCStateTypeId, solaxConnection->meter2PowerT());
|
||||
}
|
||||
}
|
||||
|
||||
// Update the battery if available
|
||||
Thing *batteryThing = getBatteryThing(thing);
|
||||
if (batteryThing) {
|
||||
batteryThing->setStateValue(solaxBatteryVoltageStateTypeId, solaxConnection->batteryVoltage());
|
||||
batteryThing->setStateValue(solaxBatteryTemperatureStateTypeId, solaxConnection->batteryTemperature());
|
||||
batteryThing->setStateValue(solaxBatteryBatteryLevelStateTypeId, solaxConnection->batteryCapacity());
|
||||
batteryThing->setStateValue(solaxBatteryBatteryCriticalStateTypeId, solaxConnection->batteryCapacity() < 5);
|
||||
|
||||
double batteryPower = solaxConnection->batteryPower();
|
||||
batteryThing->setStateValue(solaxBatteryCurrentPowerStateTypeId, solaxConnection->batteryPower());
|
||||
if (batteryPower == 0) {
|
||||
batteryThing->setStateValue(solaxBatteryChargingStateStateTypeId, "idle");
|
||||
} else if (batteryPower < 0) {
|
||||
batteryThing->setStateValue(solaxBatteryChargingStateStateTypeId, "discharging");
|
||||
} else if (batteryPower > 0) {
|
||||
batteryThing->setStateValue(solaxBatteryChargingStateStateTypeId, "charging");
|
||||
}
|
||||
}
|
||||
|
||||
// Run the next update cycle
|
||||
solaxConnection->update();
|
||||
});
|
||||
|
||||
connect(thing, &Thing::settingChanged, solaxConnection, [this, thing](const ParamTypeId ¶mTypeId, const QVariant &value){
|
||||
if (paramTypeId == solaxInverterTcpSettingsMeter2InverterParamTypeId) {
|
||||
// Note: we just need to cleanup here, if there is any device connected, it will be created in the update method.
|
||||
if (value.toBool()) {
|
||||
// The meter will be used as inverter. Clean up any meters for meter 2 registers...
|
||||
cleanupMeter2(thing);
|
||||
} else {
|
||||
// The meter will be used as meter. Clean up any child inverters for meter 2 registers...
|
||||
cleanupChildInverters(thing);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
m_tcpConnections.insert(thing, solaxConnection);
|
||||
|
||||
if (monitor->reachable())
|
||||
solaxConnection->connectDevice();
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
return;
|
||||
}
|
||||
|
||||
// Meter
|
||||
if (thing->thingClassId() == solaxMeterThingClassId) {
|
||||
|
||||
// Get the parent thing and the associated connection
|
||||
Thing *connectionThing = myThings().findById(thing->parentId());
|
||||
if (!connectionThing) {
|
||||
qCWarning(dcSolax()) << "Failed to set up solax energy meter because the parent thing with ID" << thing->parentId().toString() << "could not be found.";
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
return;
|
||||
}
|
||||
|
||||
SolaxModbusTcpConnection *solaxConnection = m_tcpConnections.value(connectionThing);
|
||||
if (!solaxConnection) {
|
||||
qCWarning(dcSolax()) << "Failed to set up solax energy meter because the connection for" << connectionThing << "does not exist.";
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: The states will be handled in the parent inverter thing on updated
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
return;
|
||||
}
|
||||
|
||||
// Child inverter
|
||||
if (thing->thingClassId() == solaxInverterChildThingClassId) {
|
||||
|
||||
// Get the parent thing and the associated connection
|
||||
Thing *connectionThing = myThings().findById(thing->parentId());
|
||||
if (!connectionThing) {
|
||||
qCWarning(dcSolax()) << "Failed to set up solax child inverter because the parent thing with ID" << thing->parentId().toString() << "could not be found.";
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
return;
|
||||
}
|
||||
|
||||
SolaxModbusTcpConnection *solaxConnection = m_tcpConnections.value(connectionThing);
|
||||
if (!solaxConnection) {
|
||||
qCWarning(dcSolax()) << "Failed to set up solax child inverter because the connection for" << connectionThing << "does not exist.";
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: The states will be handled in the parent inverter thing on updated
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
return;
|
||||
}
|
||||
|
||||
// Battery
|
||||
if (thing->thingClassId() == solaxBatteryThingClassId) {
|
||||
// Get the parent thing and the associated connection
|
||||
Thing *connectionThing = myThings().findById(thing->parentId());
|
||||
if (!connectionThing) {
|
||||
qCWarning(dcSolax()) << "Failed to set up solax battery because the parent thing with ID" << thing->parentId().toString() << "could not be found.";
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
return;
|
||||
}
|
||||
|
||||
SolaxModbusTcpConnection *solaxConnection = m_tcpConnections.value(connectionThing);
|
||||
if (!solaxConnection) {
|
||||
qCWarning(dcSolax()) << "Failed to set up solax battery because the connection for" << connectionThing << "does not exist.";
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: The states will be handled in the parent inverter thing on updated
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSolax::postSetupThing(Thing *thing)
|
||||
{
|
||||
if (thing->thingClassId() == solaxMeterThingClassId || thing->thingClassId() == solaxBatteryThingClassId || thing->thingClassId() == solaxInverterChildThingClassId) {
|
||||
Thing *connectionThing = myThings().findById(thing->parentId());
|
||||
if (connectionThing) {
|
||||
thing->setStateValue("connected", connectionThing->stateValue("connected"));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSolax::thingRemoved(Thing *thing)
|
||||
{
|
||||
if (thing->thingClassId() == solaxInverterTcpThingClassId && m_tcpConnections.contains(thing)) {
|
||||
SolaxModbusTcpConnection *connection = m_tcpConnections.take(thing);
|
||||
connection->modbusTcpMaster()->disconnectDevice();
|
||||
delete connection;
|
||||
}
|
||||
|
||||
// Unregister related hardware resources
|
||||
if (m_monitors.contains(thing))
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
|
||||
}
|
||||
|
||||
Thing *IntegrationPluginSolax::getMeterThing(Thing *parentThing, uint meterId)
|
||||
{
|
||||
Things meterThings = myThings().filterByParentId(parentThing->id()).filterByThingClassId(solaxMeterThingClassId).filterByParam(solaxMeterThingIdParamTypeId, meterId);
|
||||
if (meterThings.isEmpty())
|
||||
return nullptr;
|
||||
|
||||
return meterThings.first();
|
||||
}
|
||||
|
||||
Thing *IntegrationPluginSolax::getBatteryThing(Thing *parentThing)
|
||||
{
|
||||
Things batteryThings = myThings().filterByParentId(parentThing->id()).filterByThingClassId(solaxBatteryThingClassId);
|
||||
if (batteryThings.isEmpty())
|
||||
return nullptr;
|
||||
|
||||
return batteryThings.first();
|
||||
}
|
||||
|
||||
void IntegrationPluginSolax::cleanupMeter2(Thing *parentThing)
|
||||
{
|
||||
Things meters = myThings().filterByParentId(parentThing->id()).filterByThingClassId(solaxMeterThingClassId).filterByParam(solaxMeterThingIdParamTypeId, 2);
|
||||
if (!meters.isEmpty()) {
|
||||
emit autoThingDisappeared(meters.first()->id());
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSolax::cleanupChildInverters(Thing *parentThing)
|
||||
{
|
||||
Things inverterThings = myThings().filterByParentId(parentThing->id()).filterByThingClassId(solaxInverterChildThingClassId);
|
||||
if (!inverterThings.isEmpty()) {
|
||||
foreach (Thing *inverterThing, inverterThings) {
|
||||
emit autoThingDisappeared(inverterThing->id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
74
solax/integrationpluginsolax.h
Normal file
74
solax/integrationpluginsolax.h
Normal file
@ -0,0 +1,74 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2023, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef INTEGRATIONPLUGINSOLAX_H
|
||||
#define INTEGRATIONPLUGINSOLAX_H
|
||||
|
||||
#include <plugintimer.h>
|
||||
#include <integrations/integrationplugin.h>
|
||||
#include <network/networkdevicemonitor.h>
|
||||
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include "solaxmodbustcpconnection.h"
|
||||
#include "solaxmodbusrtuconnection.h"
|
||||
|
||||
class IntegrationPluginSolax: public IntegrationPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginsolax.json")
|
||||
Q_INTERFACES(IntegrationPlugin)
|
||||
|
||||
public:
|
||||
explicit IntegrationPluginSolax();
|
||||
|
||||
void discoverThings(ThingDiscoveryInfo *info) override;
|
||||
void setupThing(ThingSetupInfo *info) override;
|
||||
void postSetupThing(Thing *thing) override;
|
||||
void thingRemoved(Thing *thing) override;
|
||||
|
||||
private:
|
||||
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
|
||||
QHash<Thing *, SolaxModbusTcpConnection *> m_tcpConnections;
|
||||
QHash<Thing *, SolaxModbusRtuConnection *> m_rtuConnections;
|
||||
|
||||
void setupSolaxTcpConnection(ThingSetupInfo *info);
|
||||
|
||||
Thing *getMeterThing(Thing *parentThing, uint meterId);
|
||||
Thing *getBatteryThing(Thing *parentThing);
|
||||
|
||||
void cleanupMeter2(Thing *parentThing);
|
||||
void cleanupChildInverters(Thing *parentThing);
|
||||
};
|
||||
|
||||
#endif // INTEGRATIONPLUGINSOLAX_H
|
||||
|
||||
|
||||
384
solax/integrationpluginsolax.json
Normal file
384
solax/integrationpluginsolax.json
Normal file
@ -0,0 +1,384 @@
|
||||
{
|
||||
"name": "Solax",
|
||||
"displayName": "SolaX Power",
|
||||
"id": "c316666c-7070-42e2-8d37-1145715dc986",
|
||||
"vendors": [
|
||||
{
|
||||
"name": "solax",
|
||||
"displayName": "SolaX Power",
|
||||
"id": "a672201c-6b11-4e79-bef9-60a23e08ff8f",
|
||||
"thingClasses": [
|
||||
{
|
||||
"name": "solaxInverterTcp",
|
||||
"displayName": "Solax Inverter",
|
||||
"id": "fa1a559a-12a6-416f-ab77-a431a38bc3c2",
|
||||
"createMethods": ["discovery"],
|
||||
"discoveryType": "weak",
|
||||
"interfaces": ["solarinverter", "connectable"],
|
||||
"providedInterfaces": [ "energymeter", "energystorage"],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "acdee28d-4c73-4ed9-ad1b-d5d1440164c0",
|
||||
"name":"macAddress",
|
||||
"displayName": "MAC address",
|
||||
"type": "QString",
|
||||
"inputType": "MacAddress",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "c5324c59-39e6-439c-a9e0-bbe8055c9db0",
|
||||
"name":"port",
|
||||
"displayName": "Port",
|
||||
"type": "int",
|
||||
"defaultValue": 502
|
||||
},
|
||||
{
|
||||
"id": "154f8f71-1d84-4653-94a0-31337af55359",
|
||||
"name":"slaveId",
|
||||
"displayName": "Slave ID",
|
||||
"type": "int",
|
||||
"defaultValue": 1
|
||||
}
|
||||
],
|
||||
"settingsTypes": [
|
||||
{
|
||||
"id": "d065c829-6431-4a87-a30e-91d2dd864598",
|
||||
"name": "meter2Inverter",
|
||||
"displayName": "Inverter on Meter 2",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "948d0f5c-4547-4894-be13-8b7ea2af50df",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "7cc0df36-7ec8-499d-ba6b-8b62520a0d61",
|
||||
"name": "currentPower",
|
||||
"displayName": "Active power",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "85b505d0-363c-4608-8b26-1e9d4427d7ce",
|
||||
"name": "currentVoltage",
|
||||
"displayName": "Voltage",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
}
|
||||
,
|
||||
{
|
||||
"id": "38e333be-86e8-42d8-a753-4e8102d5c2be",
|
||||
"name": "current",
|
||||
"displayName": "Current",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "cbf8cd14-1661-4063-be78-a7151dfc24d4",
|
||||
"name": "totalEnergyProduced",
|
||||
"displayName": "Total energy produced",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.0,
|
||||
"cached": true
|
||||
},
|
||||
{
|
||||
"id": "e0bafe29-2eba-450b-9a0b-df65d0cbac7f",
|
||||
"name": "temperature",
|
||||
"displayName": "Temperature",
|
||||
"type": "double",
|
||||
"unit": "DegreeCelsius",
|
||||
"defaultValue": 0.00,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "9badd000-74b7-4293-8892-864a185d5073",
|
||||
"name": "frequency",
|
||||
"displayName": "Frequency",
|
||||
"type": "double",
|
||||
"unit": "Hertz",
|
||||
"defaultValue": 0.00,
|
||||
"cached": false
|
||||
}
|
||||
],
|
||||
"actionTypes": [ ]
|
||||
},
|
||||
{
|
||||
"name": "solaxInverterChild",
|
||||
"displayName": "Solax Inverter",
|
||||
"id": "84774ef9-5c4b-4f3f-95e7-846ba8380e22",
|
||||
"createMethods": ["auto"],
|
||||
"interfaces": ["solarinverter", "connectable"],
|
||||
"paramTypes": [ ],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "6b3e98c0-a562-4579-8e53-c4d7ac532057",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "f2644a0f-b16d-442b-add3-4458180c635c",
|
||||
"name": "currentPower",
|
||||
"displayName": "Active power",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "da1788cb-eb3e-43c6-8815-a60f983c7fe8",
|
||||
"name": "totalEnergyProduced",
|
||||
"displayName": "Total energy produced",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.0,
|
||||
"cached": true
|
||||
}
|
||||
],
|
||||
"actionTypes": [ ]
|
||||
},
|
||||
{
|
||||
"name": "solaxMeter",
|
||||
"displayName": "SolaX Meter",
|
||||
"id": "293d7cef-7bfb-4830-8958-b4b77ccb9786",
|
||||
"createMethods": ["auto"],
|
||||
"interfaces": [ "energymeter", "connectable"],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "2c50e082-9fba-4859-a8f4-18957518b359",
|
||||
"name": "id",
|
||||
"displayName": "Meter ID",
|
||||
"type": "uint",
|
||||
"defaultValue": 1
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "a9db94a3-64b4-4472-b5f9-89aded4f907c",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"displayNameEvent": "Connected changed",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "077234cc-87b1-40f2-a06b-532219e35948",
|
||||
"name": "currentPower",
|
||||
"displayName": "Current power",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0.00,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "d6a76445-e552-44bc-9d49-a64ac9f3263e",
|
||||
"name": "currentPowerPhaseA",
|
||||
"displayName": "Current power phase A",
|
||||
"displayNameEvent": "Current power phase A changed",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "74d4fa43-10d8-4c85-a2a0-1c318bf4b44d",
|
||||
"name": "currentPowerPhaseB",
|
||||
"displayName": "Current power phase B",
|
||||
"displayNameEvent": "Current power phase B changed",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "1be70078-7144-4325-b1fc-f73b23a33848",
|
||||
"name": "currentPowerPhaseC",
|
||||
"displayName": "Current power phase C",
|
||||
"displayNameEvent": "Current power phase C changed",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "1da7318a-9b2f-4abd-a30b-df0da04e8d9b",
|
||||
"name": "voltagePhaseA",
|
||||
"displayName": "Voltage phase A",
|
||||
"displayNameEvent": "Voltage phase A changed",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "89cebad3-8985-4f5c-bd69-cd041a436d48",
|
||||
"name": "voltagePhaseB",
|
||||
"displayName": "Voltage phase B",
|
||||
"displayNameEvent": "Voltage phase B changed",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "d80a0934-5a83-4bac-aeac-2360144b3f93",
|
||||
"name": "voltagePhaseC",
|
||||
"displayName": "Voltage phase C",
|
||||
"displayNameEvent": "Voltage phase C changed",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "d64f0d70-34a9-4426-a3c9-3689bf806f45",
|
||||
"name": "currentPhaseA",
|
||||
"displayName": "Current phase A",
|
||||
"displayNameEvent": "Current phase A changed",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "4007afc5-83d9-4427-bb3d-fe0197c33172",
|
||||
"name": "currentPhaseB",
|
||||
"displayName": "Current phase B",
|
||||
"displayNameEvent": "Current phase B changed",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "37a57511-dad5-490c-aa82-88f8e7ebbe1f",
|
||||
"name": "currentPhaseC",
|
||||
"displayName": "Current phase C",
|
||||
"displayNameEvent": "Current phase C changed",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "59397bac-a4d9-4e50-99a3-f329e3806b25",
|
||||
"name": "totalEnergyProduced",
|
||||
"displayName": "Total returned energy",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.00,
|
||||
"cached": true
|
||||
},
|
||||
{
|
||||
"id": "44f30880-cba9-4ce7-995d-8cbad4ff31a9",
|
||||
"name": "totalEnergyConsumed",
|
||||
"displayName": "Total imported energy",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.00,
|
||||
"cached": true
|
||||
},
|
||||
{
|
||||
"id": "09932aaa-5754-4fd9-a634-965902352de5",
|
||||
"name": "frequency",
|
||||
"displayName": "Frequency",
|
||||
"type": "double",
|
||||
"unit": "Hertz",
|
||||
"defaultValue": 0.00,
|
||||
"cached": false
|
||||
}
|
||||
],
|
||||
"actionTypes": [ ]
|
||||
},
|
||||
{
|
||||
"name": "solaxBattery",
|
||||
"displayName": "SolaX Battery",
|
||||
"id": "f9a03f59-7e2f-4794-98de-bd026d0052ce",
|
||||
"createMethods": ["auto"],
|
||||
"interfaces": [ "energystorage", "connectable"],
|
||||
"paramTypes": [
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "456f091a-e12f-4b1a-82b3-0a2467f79ee3",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "5344d1dc-a109-4b44-8d50-24f69a6f6993",
|
||||
"name": "batteryCritical",
|
||||
"displayName": "Battery critical",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "2d601edb-31e8-4c00-8567-b9f81121a33c",
|
||||
"name": "batteryLevel",
|
||||
"displayName": "Battery level",
|
||||
"type": "int",
|
||||
"unit": "Percentage",
|
||||
"minValue": 0,
|
||||
"maxValue": 100,
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "edc3c2fd-382d-41ac-b894-50881fb92bea",
|
||||
"name": "currentPower",
|
||||
"displayName": "Total real power",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0.00,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "e09c87be-ed6b-49f8-9693-ff15ff512db6",
|
||||
"name": "voltage",
|
||||
"displayName": "Voltage",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0.00,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "c21af13f-3ace-4f86-9d77-579b2a5e202c",
|
||||
"name": "temperature",
|
||||
"displayName": "Temperature",
|
||||
"type": "double",
|
||||
"unit": "DegreeCelsius",
|
||||
"defaultValue": 0.00,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "98099dbd-3f66-43b3-8192-f2e3fdcd5d62",
|
||||
"name": "capacity",
|
||||
"displayName": "Capacity",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "829173e8-7535-4aba-b403-d498ff68250e",
|
||||
"name": "chargingState",
|
||||
"displayName": "Charging state",
|
||||
"type": "QString",
|
||||
"possibleValues": ["idle", "charging", "discharging"],
|
||||
"defaultValue": "idle"
|
||||
}
|
||||
],
|
||||
"actionTypes": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
13
solax/meta.json
Normal file
13
solax/meta.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"title": "SolaX Power Inverter",
|
||||
"tagline": "Connect to SolaX Power X1 and X3 G4 inverters.",
|
||||
"icon": "solax.png",
|
||||
"stability": "consumer",
|
||||
"offline": true,
|
||||
"technologies": [
|
||||
"network"
|
||||
],
|
||||
"categories": [
|
||||
"energy"
|
||||
]
|
||||
}
|
||||
944
solax/solax-registers.json
Normal file
944
solax/solax-registers.json
Normal file
@ -0,0 +1,944 @@
|
||||
{
|
||||
"className": "Solax",
|
||||
"protocol": "BOTH",
|
||||
"endianness": "LittleEndian",
|
||||
"errorLimitUntilNotReachable": 5,
|
||||
"queuedRequests": true,
|
||||
"queuedRequestsDelay": 200,
|
||||
"checkReachableRegister": "inverterPower",
|
||||
"enums": [
|
||||
{
|
||||
"name": "RunMode",
|
||||
"values": [
|
||||
{
|
||||
"key": "WaitMode",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"key": "CheckMode",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"key": "NormalMode",
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"key": "FaultMode",
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"key": "PermanentFaultMode",
|
||||
"value": 4
|
||||
},
|
||||
{
|
||||
"key": "UpdateMode",
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"key": "EpsCheckMode",
|
||||
"value": 6
|
||||
},
|
||||
{
|
||||
"key": "EpsMode",
|
||||
"value": 7
|
||||
},
|
||||
{
|
||||
"key": "SelfTest",
|
||||
"value": 8
|
||||
},
|
||||
{
|
||||
"key": "IdleMode",
|
||||
"value": 9
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"blocks": [
|
||||
{
|
||||
"id": "identification",
|
||||
"readSchedule": "init",
|
||||
"registers": [
|
||||
{
|
||||
"id": "serialNumber",
|
||||
"address": 0,
|
||||
"size": 7,
|
||||
"type": "string",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Serial number",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "factoryName",
|
||||
"address": 7,
|
||||
"size": 7,
|
||||
"type": "string",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Factory name",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "moduleName",
|
||||
"address": 14,
|
||||
"size": 7,
|
||||
"type": "string",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Module name",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "versions",
|
||||
"readSchedule": "init",
|
||||
"registers": [
|
||||
{
|
||||
"id": "firmwareVersion",
|
||||
"address": 125,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Firmware version",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "hardwareVerrsion",
|
||||
"address": 126,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Hardware version",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "inverterEnergyValues",
|
||||
"readSchedule": "update",
|
||||
"registers": [
|
||||
{
|
||||
"id": "inverterVoltage",
|
||||
"address": 0,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Inverter voltage (X1)",
|
||||
"unit": "V",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "inverterCurrent",
|
||||
"address": 1,
|
||||
"size": 1,
|
||||
"type": "int16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Inverter current (X1)",
|
||||
"unit": "A",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "inverterPower",
|
||||
"address": 2,
|
||||
"size": 1,
|
||||
"type": "int16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Inverter power (X1)",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "hybridVoltageCurrentValues",
|
||||
"readSchedule": "update",
|
||||
"registers": [
|
||||
{
|
||||
"id": "pvVoltage1",
|
||||
"address": 3,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "PV voltage 1 (Hybrid)",
|
||||
"unit": "V",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "pvVoltage2",
|
||||
"address": 4,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "PV voltage 2 (Hybrid)",
|
||||
"unit": "V",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "pvCurrent1",
|
||||
"address": 5,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "PV current 1 (Hybrid)",
|
||||
"unit": "A",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "pvCurrent2",
|
||||
"address": 6,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "PV current 2 (Hybrid)",
|
||||
"unit": "A",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "inverterInformation",
|
||||
"readSchedule": "update",
|
||||
"registers": [
|
||||
{
|
||||
"id": "inverterFrequency",
|
||||
"address": 7,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Inverter frequency (X1)",
|
||||
"unit": "Hz",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -2,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "temperature",
|
||||
"address": 8,
|
||||
"size": 1,
|
||||
"type": "int16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Radiator temperature",
|
||||
"unit": "°C",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "runMode",
|
||||
"address": 9,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"enum": "RunMode",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Run mode",
|
||||
"defaultValue": "RunModeIdleMode",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "powerDc1",
|
||||
"address": 10,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Power DC 1 (Hybrid)",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "powerDc2",
|
||||
"address": 11,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Power DC 2 (Hybrid)",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "batteryValues",
|
||||
"readSchedule": "update",
|
||||
"registers": [
|
||||
{
|
||||
"id": "batteryVoltage",
|
||||
"address": 20,
|
||||
"size": 1,
|
||||
"type": "int16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Battery voltage (Charge 1)",
|
||||
"unit": "V",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "batteryCurrent",
|
||||
"address": 21,
|
||||
"size": 1,
|
||||
"type": "int16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Battery current (Charge 1)",
|
||||
"unit": "A",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "batteryPower",
|
||||
"address": 22,
|
||||
"size": 1,
|
||||
"type": "int16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Battery power (Charge 1)",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "batteryConnected",
|
||||
"address": 23,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Battery connected (0 disconnected, 1 connected)",
|
||||
"defaultValue": "false",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "batteryTemperature",
|
||||
"address": 24,
|
||||
"size": 1,
|
||||
"type": "int16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Battery temperature",
|
||||
"unit": "°C",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "batteryEnergyValues",
|
||||
"readSchedule": "update",
|
||||
"registers": [
|
||||
{
|
||||
"id": "batteryCapacity",
|
||||
"address": 28,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Battery capacity",
|
||||
"unit": "%",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "batteryEnergyOut",
|
||||
"address": 29,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Battery output energy",
|
||||
"unit": "kWh",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "bmsWarning",
|
||||
"address": 31,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "BMS warning",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "batteryEnergyOutToday",
|
||||
"address": 32,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Battery output energy today",
|
||||
"unit": "kWh",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "batteryEnergyIn",
|
||||
"address": 33,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Battery input energy",
|
||||
"unit": "kWh",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "batteryEnergyInToday",
|
||||
"address": 35,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Battery input energy today",
|
||||
"unit": "kWh",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "meterValues",
|
||||
"readSchedule": "update",
|
||||
"registers": [
|
||||
{
|
||||
"id": "meterPower",
|
||||
"address": 70,
|
||||
"size": 2,
|
||||
"type": "int32",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Meter power (+ returned, - aquired)",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "meterTotalEnergyProduced",
|
||||
"address": 72,
|
||||
"size": 2,
|
||||
"type": "int32",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Meter total energy returned",
|
||||
"unit": "kWh",
|
||||
"staticScaleFactor": -2,
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "meterTotalEnergyConsumend",
|
||||
"address": 74,
|
||||
"size": 2,
|
||||
"type": "int32",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Meter total energy consumed",
|
||||
"unit": "kWh",
|
||||
"staticScaleFactor": -2,
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gridAndEpsPhaseValues",
|
||||
"readSchedule": "update",
|
||||
"registers": [
|
||||
{
|
||||
"id": "gridVoltageR",
|
||||
"address": 106,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Grid voltage R L1",
|
||||
"unit": "V",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "gridCurrentR",
|
||||
"address": 107,
|
||||
"size": 1,
|
||||
"type": "int16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Grid current R L1",
|
||||
"unit": "A",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "gridPowerR",
|
||||
"address": 108,
|
||||
"size": 1,
|
||||
"type": "int16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Grid power R L1",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "gridFrequencyR",
|
||||
"address": 109,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Grid frequency R L1",
|
||||
"unit": "Hz",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -2,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "gridVoltageS",
|
||||
"address": 110,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Grid voltage S L2",
|
||||
"unit": "V",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "gridCurrentS",
|
||||
"address": 111,
|
||||
"size": 1,
|
||||
"type": "int16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Grid current S L2",
|
||||
"unit": "A",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "gridPowerS",
|
||||
"address": 112,
|
||||
"size": 1,
|
||||
"type": "int16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Grid power S L2",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "gridFrequencyS",
|
||||
"address": 113,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Grid frequency S L2",
|
||||
"unit": "Hz",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -2,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "gridVoltageT",
|
||||
"address": 114,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Grid voltage T L3",
|
||||
"unit": "V",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "gridCurrentT",
|
||||
"address": 115,
|
||||
"size": 1,
|
||||
"type": "int16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Grid current T L3",
|
||||
"unit": "A",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "gridPowerT",
|
||||
"address": 116,
|
||||
"size": 1,
|
||||
"type": "int16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Grid power T L3",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "gridFrequencyT",
|
||||
"address": 117,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Grid frequency T L3",
|
||||
"unit": "Hz",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -2,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "epsVoltageR",
|
||||
"address": 118,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "EPS voltage R L1",
|
||||
"unit": "V",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "epsCurrentR",
|
||||
"address": 119,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "EPS current R L1",
|
||||
"unit": "A",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "epsPowerActiveR",
|
||||
"address": 120,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "EPS power R L1",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "epsPowerSR",
|
||||
"address": 121,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "EPS power S R L1",
|
||||
"unit": "VA",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "epsVoltageS",
|
||||
"address": 122,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "EPS voltage S L2",
|
||||
"unit": "V",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "epsCurrentS",
|
||||
"address": 123,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "EPS current S L2",
|
||||
"unit": "A",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "epsPowerS",
|
||||
"address": 124,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "EPS power S L2",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "epsPowerSS",
|
||||
"address": 125,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "EPS power S S L1",
|
||||
"unit": "VA",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "epsVoltageT",
|
||||
"address": 126,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "EPS voltage T L3",
|
||||
"unit": "V",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "epsCurrentT",
|
||||
"address": 127,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "EPS current T L3",
|
||||
"unit": "A",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "epsPowerT",
|
||||
"address": 128,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "EPS power T L3",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "epsPowerST",
|
||||
"address": 129,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "EPS power S T L1",
|
||||
"unit": "VA",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "meter2Values",
|
||||
"readSchedule": "update",
|
||||
"registers": [
|
||||
{
|
||||
"id": "meter2Power",
|
||||
"address": 168,
|
||||
"size": 2,
|
||||
"type": "int32",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Meter 2 power",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "meter2EnergyProduced",
|
||||
"address": 170,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Meter 2 energy produced",
|
||||
"unit": "kWh",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -2,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "meter2EnergyConsumed",
|
||||
"address": 172,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Meter 2 energy consumed",
|
||||
"unit": "kWh",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -2,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "meter2EnergyProducedToday",
|
||||
"address": 174,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Meter 2 energy produced today",
|
||||
"unit": "kWh",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -2,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "meter2EnergyConsumedToday",
|
||||
"address": 176,
|
||||
"size": 2,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Meter 2 energy consumed today",
|
||||
"unit": "kWh",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -2,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "meter2PowerR",
|
||||
"address": 178,
|
||||
"size": 2,
|
||||
"type": "int32",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Meter 2 power R L1",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "meter2PowerS",
|
||||
"address": 180,
|
||||
"size": 2,
|
||||
"type": "int32",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Meter 2 power S L2",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "meter2PowerT",
|
||||
"address": 182,
|
||||
"size": 2,
|
||||
"type": "int32",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Meter 2 power T L3",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "meter1ComState",
|
||||
"address": 184,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Meter 1 communication state (0 error, 1 normal)",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "meter2ComState",
|
||||
"address": 185,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Meter 2 communication state (0 error, 1 normal)",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "batteryValues2",
|
||||
"readSchedule": "update",
|
||||
"registers": [
|
||||
{
|
||||
"id": "batteryVoltage2",
|
||||
"address": 194,
|
||||
"size": 1,
|
||||
"type": "int16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Battery voltage 2 (Charge 1)",
|
||||
"unit": "V",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "batteryCurrent2",
|
||||
"address": 195,
|
||||
"size": 1,
|
||||
"type": "int16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Battery current 2 (Charge 1)",
|
||||
"unit": "A",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "batteryPower2",
|
||||
"address": 196,
|
||||
"size": 1,
|
||||
"type": "int16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Battery power 2 (Charge 1)",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"staticScaleFactor": -1,
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "batteryConnected2",
|
||||
"address": 197,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Battery connected 2 (0 disconnected, 1 connected)",
|
||||
"defaultValue": "false",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "batteryTemperature2",
|
||||
"address": 198,
|
||||
"size": 1,
|
||||
"type": "int16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Battery temperature 2",
|
||||
"unit": "°C",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "batteryCapacity2",
|
||||
"address": 199,
|
||||
"size": 1,
|
||||
"type": "uint16",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Battery capacity 2",
|
||||
"unit": "%",
|
||||
"staticScaleFactor": -2,
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"registers": [
|
||||
{
|
||||
"id": "totalEnergyProduced",
|
||||
"address": 82,
|
||||
"size": 2,
|
||||
"type": "int32",
|
||||
"readSchedule": "update",
|
||||
"registerType": "inputRegister",
|
||||
"description": "Inverter total energy AC port",
|
||||
"unit": "kWh",
|
||||
"staticScaleFactor": -2,
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
solax/solax.png
Normal file
BIN
solax/solax.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
14
solax/solax.pro
Normal file
14
solax/solax.pro
Normal file
@ -0,0 +1,14 @@
|
||||
include(../plugins.pri)
|
||||
|
||||
# Generate modbus connection
|
||||
MODBUS_CONNECTIONS += solax-registers.json
|
||||
#MODBUS_TOOLS_CONFIG += VERBOSE
|
||||
include(../modbus.pri)
|
||||
|
||||
HEADERS += \
|
||||
integrationpluginsolax.h \
|
||||
solaxdiscovery.h
|
||||
|
||||
SOURCES += \
|
||||
integrationpluginsolax.cpp \
|
||||
solaxdiscovery.cpp
|
||||
163
solax/solaxdiscovery.cpp
Normal file
163
solax/solaxdiscovery.cpp
Normal file
@ -0,0 +1,163 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2023, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "solaxdiscovery.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
SolaxDiscovery::SolaxDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port, quint16 modbusAddress, QObject *parent) :
|
||||
QObject{parent},
|
||||
m_networkDeviceDiscovery{networkDeviceDiscovery},
|
||||
m_port{port},
|
||||
m_modbusAddress{modbusAddress}
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SolaxDiscovery::startDiscovery()
|
||||
{
|
||||
qCInfo(dcSolax()) << "Discovery: Start searching for Solax inverters in the network...";
|
||||
m_startDateTime = QDateTime::currentDateTime();
|
||||
|
||||
NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &SolaxDiscovery::checkNetworkDevice);
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, discoveryReply, &NetworkDeviceDiscoveryReply::deleteLater);
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
|
||||
qCDebug(dcSolax()) << "Discovery: Network discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices";
|
||||
|
||||
// Give the last connections added right before the network discovery finished a chance to check the device...
|
||||
QTimer::singleShot(3000, this, [this](){
|
||||
qCDebug(dcSolax()) << "Discovery: Grace period timer triggered.";
|
||||
finishDiscovery();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
QList<SolaxDiscovery::SolaxDiscoveryResult> SolaxDiscovery::discoveryResults() const
|
||||
{
|
||||
return m_discoveryResults;
|
||||
}
|
||||
|
||||
void SolaxDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
|
||||
{
|
||||
// Create a Solax connection and try to initialize it.
|
||||
// Only if initialized successfully and all information have been fetched correctly from
|
||||
// the device we can assume this is what we are locking for (ip, port, modbus address, correct registers).
|
||||
// We cloud tough also filter the result only for certain software versions, manufactueres or whatever...
|
||||
|
||||
SolaxModbusTcpConnection *connection = new SolaxModbusTcpConnection(networkDeviceInfo.address(), m_port, m_modbusAddress, this);
|
||||
connection->modbusTcpMaster()->setTimeout(500);
|
||||
connection->modbusTcpMaster()->setNumberOfRetries(0);
|
||||
m_connections.append(connection);
|
||||
|
||||
connect(connection, &SolaxModbusTcpConnection::reachableChanged, this, [=](bool reachable){
|
||||
if (!reachable) {
|
||||
// Disconnected ... done with this connection
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
// Modbus TCP connected and reachable call successed, let's try to initialize it!
|
||||
connect(connection, &SolaxModbusTcpConnection::initializationFinished, this, [=](bool success){
|
||||
if (!success) {
|
||||
qCDebug(dcSolax()) << "Discovery: Initialization failed on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
qCInfo(dcSolax()) << "Discovery: Initialized successfully" << networkDeviceInfo << connection->factoryName() << connection->serialNumber();
|
||||
|
||||
// Let's make sure the information are correct
|
||||
if (connection->factoryName().toLower().contains("solax")) {
|
||||
SolaxDiscoveryResult result;
|
||||
result.productName = connection->moduleName();
|
||||
result.manufacturerName = connection->factoryName();
|
||||
result.serialNumber = connection->serialNumber();
|
||||
result.networkDeviceInfo = networkDeviceInfo;
|
||||
m_discoveryResults.append(result);
|
||||
|
||||
qCInfo(dcSolax()) << "Discovery: --> Found" << result.manufacturerName << result.productName
|
||||
<< "Serial number:" << result.serialNumber
|
||||
<< result.networkDeviceInfo;
|
||||
}
|
||||
|
||||
connection->disconnectDevice();
|
||||
});
|
||||
|
||||
qCDebug(dcSolax()) << "Discovery: The host" << networkDeviceInfo << "is reachable, trying to initialize...";
|
||||
if (!connection->initialize()) {
|
||||
qCDebug(dcSolax()) << "Discovery: Unable to initialize connection on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
cleanupConnection(connection);
|
||||
}
|
||||
});
|
||||
|
||||
// If we get any error...skip this host...
|
||||
connect(connection->modbusTcpMaster(), &ModbusTcpMaster::connectionStateChanged, this, [=](bool connected){
|
||||
if (connected) {
|
||||
qCDebug(dcSolax()) << "Discovery: Connected with" << networkDeviceInfo.address().toString() << m_port;
|
||||
}
|
||||
});
|
||||
|
||||
// If we get any error...skip this host...
|
||||
connect(connection->modbusTcpMaster(), &ModbusTcpMaster::connectionErrorOccurred, this, [=](QModbusDevice::Error error){
|
||||
if (error != QModbusDevice::NoError) {
|
||||
qCDebug(dcSolax()) << "Discovery: Connection error on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
cleanupConnection(connection);
|
||||
}
|
||||
});
|
||||
|
||||
// If check reachability failed...skip this host...
|
||||
connect(connection, &SolaxModbusTcpConnection::checkReachabilityFailed, this, [=](){
|
||||
qCDebug(dcSolax()) << "Discovery: Check reachability failed on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
cleanupConnection(connection);
|
||||
});
|
||||
|
||||
// Try to connect, maybe it works, maybe not...
|
||||
connection->connectDevice();
|
||||
}
|
||||
|
||||
void SolaxDiscovery::cleanupConnection(SolaxModbusTcpConnection *connection)
|
||||
{
|
||||
qCDebug(dcSolax()) << "Discovery: Cleanup connection" << connection->modbusTcpMaster();
|
||||
m_connections.removeAll(connection);
|
||||
connection->disconnectDevice();
|
||||
connection->deleteLater();
|
||||
}
|
||||
|
||||
void SolaxDiscovery::finishDiscovery()
|
||||
{
|
||||
qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
|
||||
|
||||
// Cleanup any leftovers...we don't care any more
|
||||
foreach (SolaxModbusTcpConnection *connection, m_connections)
|
||||
cleanupConnection(connection);
|
||||
|
||||
qCInfo(dcSolax()) << "Discovery: Finished the discovery process. Found" << m_discoveryResults.count() << "Solax Inverters in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
|
||||
emit discoveryFinished();
|
||||
}
|
||||
76
solax/solaxdiscovery.h
Normal file
76
solax/solaxdiscovery.h
Normal file
@ -0,0 +1,76 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2023, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef SOLAXDISCOVERY_H
|
||||
#define SOLAXDISCOVERY_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include <network/networkdevicediscovery.h>
|
||||
|
||||
#include "solaxmodbustcpconnection.h"
|
||||
|
||||
class SolaxDiscovery : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SolaxDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port = 502, quint16 modbusAddress = 1, QObject *parent = nullptr);
|
||||
typedef struct SolaxDiscoveryResult {
|
||||
QString productName;
|
||||
QString manufacturerName;
|
||||
QString serialNumber;
|
||||
NetworkDeviceInfo networkDeviceInfo;
|
||||
} SolaxDiscoveryResult;
|
||||
|
||||
void startDiscovery();
|
||||
|
||||
QList<SolaxDiscoveryResult> discoveryResults() const;
|
||||
|
||||
signals:
|
||||
void discoveryFinished();
|
||||
|
||||
private:
|
||||
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
|
||||
quint16 m_port;
|
||||
quint16 m_modbusAddress;
|
||||
|
||||
QDateTime m_startDateTime;
|
||||
|
||||
QList<SolaxModbusTcpConnection *> m_connections;
|
||||
QList<SolaxDiscoveryResult> m_discoveryResults;
|
||||
|
||||
void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo);
|
||||
void cleanupConnection(SolaxModbusTcpConnection *connection);
|
||||
|
||||
void finishDiscovery();
|
||||
};
|
||||
|
||||
#endif // SOLAXDISCOVERY_H
|
||||
239
solax/translations/c316666c-7070-42e2-8d37-1145715dc986-en_US.ts
Normal file
239
solax/translations/c316666c-7070-42e2-8d37-1145715dc986-en_US.ts
Normal file
@ -0,0 +1,239 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1">
|
||||
<context>
|
||||
<name>IntegrationPluginSolax</name>
|
||||
<message>
|
||||
<location filename="../integrationpluginsolax.cpp" line="47"/>
|
||||
<source>The network device discovery is not available.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginsolax.cpp" line="100"/>
|
||||
<source>The MAC address is not known. Please reconfigure the thing.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Solax</name>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="61"/>
|
||||
<source>Active power</source>
|
||||
<extracomment>The name of the StateType ({7cc0df36-7ec8-499d-ba6b-8b62520a0d61}) of ThingClass solaxInverterTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="64"/>
|
||||
<source>Battery critical</source>
|
||||
<extracomment>The name of the StateType ({5344d1dc-a109-4b44-8d50-24f69a6f6993}) of ThingClass solaxBattery</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="67"/>
|
||||
<source>Battery level</source>
|
||||
<extracomment>The name of the StateType ({2d601edb-31e8-4c00-8567-b9f81121a33c}) of ThingClass solaxBattery</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="70"/>
|
||||
<source>Capacity</source>
|
||||
<extracomment>The name of the StateType ({98099dbd-3f66-43b3-8192-f2e3fdcd5d62}) of ThingClass solaxBattery</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="73"/>
|
||||
<source>Charging state</source>
|
||||
<extracomment>The name of the StateType ({829173e8-7535-4aba-b403-d498ff68250e}) of ThingClass solaxBattery</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="76"/>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="79"/>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="82"/>
|
||||
<source>Connected</source>
|
||||
<extracomment>The name of the StateType ({456f091a-e12f-4b1a-82b3-0a2467f79ee3}) of ThingClass solaxBattery
|
||||
----------
|
||||
The name of the StateType ({a9db94a3-64b4-4472-b5f9-89aded4f907c}) of ThingClass solaxMeter
|
||||
----------
|
||||
The name of the StateType ({948d0f5c-4547-4894-be13-8b7ea2af50df}) of ThingClass solaxInverterTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="85"/>
|
||||
<source>Current</source>
|
||||
<extracomment>The name of the StateType ({38e333be-86e8-42d8-a753-4e8102d5c2be}) of ThingClass solaxInverterTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="88"/>
|
||||
<source>Current phase A</source>
|
||||
<extracomment>The name of the StateType ({d64f0d70-34a9-4426-a3c9-3689bf806f45}) of ThingClass solaxMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="91"/>
|
||||
<source>Current phase B</source>
|
||||
<extracomment>The name of the StateType ({4007afc5-83d9-4427-bb3d-fe0197c33172}) of ThingClass solaxMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="94"/>
|
||||
<source>Current phase C</source>
|
||||
<extracomment>The name of the StateType ({37a57511-dad5-490c-aa82-88f8e7ebbe1f}) of ThingClass solaxMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="97"/>
|
||||
<source>Current power</source>
|
||||
<extracomment>The name of the StateType ({077234cc-87b1-40f2-a06b-532219e35948}) of ThingClass solaxMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="100"/>
|
||||
<source>Current power phase A</source>
|
||||
<extracomment>The name of the StateType ({d6a76445-e552-44bc-9d49-a64ac9f3263e}) of ThingClass solaxMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="103"/>
|
||||
<source>Current power phase B</source>
|
||||
<extracomment>The name of the StateType ({74d4fa43-10d8-4c85-a2a0-1c318bf4b44d}) of ThingClass solaxMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="106"/>
|
||||
<source>Current power phase C</source>
|
||||
<extracomment>The name of the StateType ({1be70078-7144-4325-b1fc-f73b23a33848}) of ThingClass solaxMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="109"/>
|
||||
<source>Frequency</source>
|
||||
<extracomment>The name of the StateType ({09932aaa-5754-4fd9-a634-965902352de5}) of ThingClass solaxMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="112"/>
|
||||
<source>MAC address</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: solaxInverterTcp, Type: thing, ID: {acdee28d-4c73-4ed9-ad1b-d5d1440164c0})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="115"/>
|
||||
<source>Port</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: solaxInverterTcp, Type: thing, ID: {c5324c59-39e6-439c-a9e0-bbe8055c9db0})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="118"/>
|
||||
<source>Slave ID</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: solaxInverterTcp, Type: thing, ID: {154f8f71-1d84-4653-94a0-31337af55359})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="121"/>
|
||||
<source>SolaX Battery</source>
|
||||
<extracomment>The name of the ThingClass ({f9a03f59-7e2f-4794-98de-bd026d0052ce})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="124"/>
|
||||
<source>SolaX Meter</source>
|
||||
<extracomment>The name of the ThingClass ({293d7cef-7bfb-4830-8958-b4b77ccb9786})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="127"/>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="130"/>
|
||||
<source>SolaX Power</source>
|
||||
<extracomment>The name of the vendor ({a672201c-6b11-4e79-bef9-60a23e08ff8f})
|
||||
----------
|
||||
The name of the plugin Solax ({c316666c-7070-42e2-8d37-1145715dc986})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="133"/>
|
||||
<source>Solax Inverter</source>
|
||||
<extracomment>The name of the ThingClass ({fa1a559a-12a6-416f-ab77-a431a38bc3c2})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="136"/>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="139"/>
|
||||
<source>Temperature</source>
|
||||
<extracomment>The name of the StateType ({c21af13f-3ace-4f86-9d77-579b2a5e202c}) of ThingClass solaxBattery
|
||||
----------
|
||||
The name of the StateType ({e0bafe29-2eba-450b-9a0b-df65d0cbac7f}) of ThingClass solaxInverterTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="142"/>
|
||||
<source>Total energy produced</source>
|
||||
<extracomment>The name of the StateType ({cbf8cd14-1661-4063-be78-a7151dfc24d4}) of ThingClass solaxInverterTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="145"/>
|
||||
<source>Total imported energy</source>
|
||||
<extracomment>The name of the StateType ({44f30880-cba9-4ce7-995d-8cbad4ff31a9}) of ThingClass solaxMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="148"/>
|
||||
<source>Total real power</source>
|
||||
<extracomment>The name of the StateType ({edc3c2fd-382d-41ac-b894-50881fb92bea}) of ThingClass solaxBattery</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="151"/>
|
||||
<source>Total returned energy</source>
|
||||
<extracomment>The name of the StateType ({59397bac-a4d9-4e50-99a3-f329e3806b25}) of ThingClass solaxMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="154"/>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="157"/>
|
||||
<source>Voltage</source>
|
||||
<extracomment>The name of the StateType ({e09c87be-ed6b-49f8-9693-ff15ff512db6}) of ThingClass solaxBattery
|
||||
----------
|
||||
The name of the StateType ({85b505d0-363c-4608-8b26-1e9d4427d7ce}) of ThingClass solaxInverterTcp</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="160"/>
|
||||
<source>Voltage phase A</source>
|
||||
<extracomment>The name of the StateType ({1da7318a-9b2f-4abd-a30b-df0da04e8d9b}) of ThingClass solaxMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="163"/>
|
||||
<source>Voltage phase B</source>
|
||||
<extracomment>The name of the StateType ({89cebad3-8985-4f5c-bd69-cd041a436d48}) of ThingClass solaxMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="166"/>
|
||||
<source>Voltage phase C</source>
|
||||
<extracomment>The name of the StateType ({d80a0934-5a83-4bac-aeac-2360144b3f93}) of ThingClass solaxMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="169"/>
|
||||
<source>charging</source>
|
||||
<extracomment>The name of a possible value of StateType {829173e8-7535-4aba-b403-d498ff68250e} of ThingClass solaxBattery</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="172"/>
|
||||
<source>discharging</source>
|
||||
<extracomment>The name of a possible value of StateType {829173e8-7535-4aba-b403-d498ff68250e} of ThingClass solaxBattery</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-modbus-Desktop-Debug/solax/plugininfo.h" line="175"/>
|
||||
<source>idle</source>
|
||||
<extracomment>The name of a possible value of StateType {829173e8-7535-4aba-b403-d498ff68250e} of ThingClass solaxBattery</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
Reference in New Issue
Block a user