Introduce checkReachableRegister and update all related plugins

pull/75/head
Simon Stürz 2022-07-29 10:18:21 +02:00
parent 8a0399f7c3
commit 86c730a0da
13 changed files with 123 additions and 19 deletions

View File

@ -2,6 +2,8 @@
"className": "AlphaInnotec",
"protocol": "TCP",
"endianness": "BigEndian",
"errorLimitUntilNotReachable": 30,
"checkReachableRegister": "smartGrid",
"enums": [
{
"name": "SystemStatus",

View File

@ -3,6 +3,7 @@
"protocol": "RTU",
"endianness": "BigEndian",
"errorLimitUntilNotReachable": 15,
"checkReachableRegister": "totalCurrentPower",
"registers": [
{
"id": "totalCurrentPower",

View File

@ -2,6 +2,8 @@
"className": "Huawei",
"protocol": "TCP",
"endianness": "BigEndian",
"errorLimitUntilNotReachable": 15,
"checkReachableRegister": "inverterActivePower",
"enums": [
{
"name": "InverterDeviceStatus",

View File

@ -2,6 +2,8 @@
"className": "Pro380",
"protocol": "RTU",
"endianness": "BigEndian",
"errorLimitUntilNotReachable": 15,
"checkReachableRegister": "totalEnergyConsumed",
"blocks": [
{
"id": "phasesVoltage",

View File

@ -21,6 +21,7 @@ The basic structure of the modbus register JSON looks like following example:
"protocol": "BOTH",
"endianness": "BigEndian",
"errorLimitUntilNotReachable": 10,
"checkReachableRegister": "registerPropertyName",
"enums": [
{
"name": "NameOfEnum",
@ -124,6 +125,10 @@ For modbus RTU it can be the case that the hardware resource is connected, but t
Depending on your device the amount of errors in a row can vary and can be specified using the `errorLimitUntilNotReachable` property. If not specified, a default of `10` will be assumend.
In order to check if the device is reachable, a register can be defined by the developer as a test register.
For this purpose the `checkReachableRegister` property has been introduced. The property describes the `id` of the register which will be used for testing the communication. The register should be mandatory on the device
and only one register in size to speed up things. During the check the response data will be ignored, only the communication will be tested. The register must be *readable* and be defined in the `registers` section of your JSON file.
## Endianness
When converting multiple registers to one data type (i.e. 2 registers uint16 values to one uint32), the order of the registers are important to align with the endianness of the data receiving.

View File

@ -73,7 +73,6 @@ def writePropertyGetSetMethodImplementationsRtu(fileDescriptor, className, regis
##############################################################
def writePropertyUpdateMethodImplementationsRtu(fileDescriptor, className, registerDefinitions):
for registerDefinition in registerDefinitions:
if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'init':
@ -268,6 +267,38 @@ def writeInternalBlockReadMethodImplementationsRtu(fileDescriptor, className, bl
##############################################################
def writeVerifyReachabilityImplementationsRtu(fileDescriptor, className, registerDefinitions, checkReachableRegister):
propertyName = checkReachableRegister['id']
propertyTyp = getCppDataType(checkReachableRegister)
writeLine(fileDescriptor, 'void %s::verifyReachability()' % (className))
writeLine(fileDescriptor, '{')
writeLine(fileDescriptor, ' // Try to read the check reachability register %s in order to verify if the communication is working or not.' % checkReachableRegister['id'])
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "--> Verify reachability by reading \\"%s\\" register:" << %s << "size:" << %s;' % (className, checkReachableRegister['description'], checkReachableRegister['address'], checkReachableRegister['size']))
writeLine(fileDescriptor, ' ModbusRtuReply *reply = read%s();' % (propertyName[0].upper() + propertyName[1:]))
writeLine(fileDescriptor, ' if (!reply) {')
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "Error occurred verifying reachability by reading \\"%s\\" register";' % (className, checkReachableRegister['description']))
writeLine(fileDescriptor, ' return;')
writeLine(fileDescriptor, ' }')
writeLine(fileDescriptor)
writeLine(fileDescriptor, ' if (reply->isFinished()) {')
writeLine(fileDescriptor, ' return;')
writeLine(fileDescriptor, ' }')
writeLine(fileDescriptor)
writeLine(fileDescriptor, ' connect(reply, &ModbusRtuReply::finished, this, [this, reply](){')
writeLine(fileDescriptor, ' // Note: we don\'t care about the result here, only the error')
writeLine(fileDescriptor, ' handleModbusError(reply->error());')
writeLine(fileDescriptor, ' });')
writeLine(fileDescriptor)
writeLine(fileDescriptor, ' connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){')
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "ModbusRtu reply error occurred while verifying reachability by reading \\"%s\\" register" << error << reply->errorString();' % (className, checkReachableRegister['description']))
writeLine(fileDescriptor, ' });')
writeLine(fileDescriptor, '}')
writeLine(fileDescriptor)
##############################################################
def writeInitMethodImplementationRtu(fileDescriptor, className, registerDefinitions, blockDefinitions):
writeLine(fileDescriptor, 'bool %s::initialize()' % (className))
writeLine(fileDescriptor, '{')
@ -303,6 +334,7 @@ def writeInitMethodImplementationRtu(fileDescriptor, className, registerDefiniti
if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'init':
writeLine(fileDescriptor)
writeLine(fileDescriptor, ' // Read %s' % registerDefinition['description'])
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "--> Read init \\"%s\\" register:" << %s << "size:" << %s;' % (className, registerDefinition['description'], registerDefinition['address'], registerDefinition['size']))
writeLine(fileDescriptor, ' reply = read%s();' % (propertyName[0].upper() + propertyName[1:]))
writeLine(fileDescriptor, ' if (!reply) {')
writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Error occurred while reading \\"%s\\" registers";' % (className, registerDefinition['description']))
@ -326,7 +358,7 @@ def writeInitMethodImplementationRtu(fileDescriptor, className, registerDefiniti
writeLine(fileDescriptor, ' }')
writeLine(fileDescriptor)
writeLine(fileDescriptor, ' QVector<quint16> values = reply->result();')
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "<-- Response from \\"%s\\" register" << %s << "size:" << %s << values;' % (className, registerDefinition['description'], registerDefinition['address'], registerDefinition['size']))
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "<-- Response from \\"%s\\" init register" << %s << "size:" << %s << values;' % (className, registerDefinition['description'], registerDefinition['address'], registerDefinition['size']))
writeLine(fileDescriptor, ' process%sRegisterValues(values);' % (propertyName[0].upper() + propertyName[1:]))
writeLine(fileDescriptor, ' verifyInitFinished();')
writeLine(fileDescriptor, ' });')
@ -356,6 +388,7 @@ def writeInitMethodImplementationRtu(fileDescriptor, className, registerDefiniti
writeLine(fileDescriptor)
writeLine(fileDescriptor, ' // Read %s' % blockName)
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "--> Read init block \\"%s\\" registers from:" << %s << "size:" << %s;' % (className, blockName, blockStartAddress, blockSize))
writeLine(fileDescriptor, ' reply = readBlock%s();' % (blockName[0].upper() + blockName[1:]))
writeLine(fileDescriptor, ' if (!reply) {')
writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Error occurred while reading block \\"%s\\" registers";' % (className, blockName))
@ -379,7 +412,7 @@ def writeInitMethodImplementationRtu(fileDescriptor, className, registerDefiniti
writeLine(fileDescriptor, ' }')
writeLine(fileDescriptor)
writeLine(fileDescriptor, ' QVector<quint16> blockValues = reply->result();')
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "<-- Response from reading block \\"%s\\" register" << %s << "size:" << %s << blockValues;' % (className, blockName, blockStartAddress, blockSize))
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "<-- Response from reading init block \\"%s\\" register" << %s << "size:" << %s << blockValues;' % (className, blockName, blockStartAddress, blockSize))
# Start parsing the registers using offsets
offset = 0
@ -423,16 +456,25 @@ def writeUpdateMethodRtu(fileDescriptor, className, registerDefinitions, blockDe
break
if updateRequired:
writeLine(fileDescriptor, ' if (!m_reachable) {')
writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Tried to update but the connection is not reachable.";' % className)
writeLine(fileDescriptor, ' if (!m_modbusRtuMaster->connected()) {')
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "Tried to update the registers but the hardware resource seems not to be connected.";' % className)
writeLine(fileDescriptor, ' return false;')
writeLine(fileDescriptor, ' }')
writeLine(fileDescriptor)
writeLine(fileDescriptor, ' if (!m_pendingUpdateReplies.isEmpty()) {')
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "Tried to update but there are still some update replies pending. Waiting for them to be finished...";' % className)
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "Tried to update the registers but there are still some update replies pending. Waiting for them to be finished...";' % className)
writeLine(fileDescriptor, ' return true;')
writeLine(fileDescriptor, ' }')
writeLine(fileDescriptor)
writeLine(fileDescriptor, ' // Hardware resource available but communication not working. ')
writeLine(fileDescriptor, ' // Try to read the check reachability register to re-evaluatoe the communication... ')
writeLine(fileDescriptor, ' if (m_modbusRtuMaster->connected() && !m_communicationWorking) {')
writeLine(fileDescriptor, ' verifyReachability();')
writeLine(fileDescriptor, ' return false;')
writeLine(fileDescriptor, ' }')
writeLine(fileDescriptor)
writeLine(fileDescriptor, ' ModbusRtuReply *reply = nullptr;')
# Read individual registers
@ -443,6 +485,7 @@ def writeUpdateMethodRtu(fileDescriptor, className, registerDefinitions, blockDe
if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'update':
writeLine(fileDescriptor)
writeLine(fileDescriptor, ' // Read %s' % registerDefinition['description'])
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "--> Read \\"%s\\" register:" << %s << "size:" << %s;' % (className, registerDefinition['description'], registerDefinition['address'], registerDefinition['size']))
writeLine(fileDescriptor, ' reply = read%s();' % (propertyName[0].upper() + propertyName[1:]))
writeLine(fileDescriptor, ' if (!reply) {')
writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Error occurred while reading \\"%s\\" registers";' % (className, registerDefinition['description']))
@ -494,6 +537,7 @@ def writeUpdateMethodRtu(fileDescriptor, className, registerDefinitions, blockDe
writeLine(fileDescriptor)
writeLine(fileDescriptor, ' // Read %s' % blockName)
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "--> Read block \\"%s\\" registers from:" << %s << "size:" << %s;' % (className, blockName, blockStartAddress, blockSize))
writeLine(fileDescriptor, ' reply = readBlock%s();' % (blockName[0].upper() + blockName[1:]))
writeLine(fileDescriptor, ' if (!reply) {')
writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Error occurred while reading block \\"%s\\" registers";' % (className, blockName))

View File

@ -440,6 +440,7 @@ def writeUpdateMethodTcp(fileDescriptor, className, registerDefinitions, blockDe
if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'update':
writeLine(fileDescriptor)
writeLine(fileDescriptor, ' // Read %s' % registerDefinition['description'])
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "--> Read \\"%s\\" register:" << %s << "size:" << %s;' % (className, registerDefinition['description'], registerDefinition['address'], registerDefinition['size']))
writeLine(fileDescriptor, ' reply = read%s();' % (propertyName[0].upper() + propertyName[1:]))
writeLine(fileDescriptor, ' if (!reply) {')
writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Error occurred while reading \\"%s\\" registers from" << hostAddress().toString() << errorString();' % (className, registerDefinition['description']))
@ -461,7 +462,7 @@ def writeUpdateMethodTcp(fileDescriptor, className, registerDefinitions, blockDe
writeLine(fileDescriptor, ' }')
writeLine(fileDescriptor)
writeLine(fileDescriptor, ' const QModbusDataUnit unit = reply->result();')
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "<-- Response from init \\"%s\\" register" << %s << "size:" << %s << unit.values();' % (className, registerDefinition['description'], registerDefinition['address'], registerDefinition['size']))
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "<-- Response from \\"%s\\" register" << %s << "size:" << %s << unit.values();' % (className, registerDefinition['description'], registerDefinition['address'], registerDefinition['size']))
writeLine(fileDescriptor, ' process%sRegisterValues(unit.values());' % (propertyName[0].upper() + propertyName[1:]))
writeLine(fileDescriptor, ' verifyUpdateFinished();')
writeLine(fileDescriptor, ' });')
@ -492,6 +493,7 @@ def writeUpdateMethodTcp(fileDescriptor, className, registerDefinitions, blockDe
writeLine(fileDescriptor)
writeLine(fileDescriptor, ' // Read %s' % blockName)
writeLine(fileDescriptor, ' reply = readBlock%s();' % (blockName[0].upper() + blockName[1:]))
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "--> Read block \\"%s\\" registers from:" << %s << "size:" << %s;' % (className, blockName, blockStartAddress, blockSize))
writeLine(fileDescriptor, ' if (!reply) {')
writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Error occurred while reading block \\"%s\\" registers";' % (className, blockName))
writeLine(fileDescriptor, ' return false;')
@ -513,7 +515,7 @@ def writeUpdateMethodTcp(fileDescriptor, className, registerDefinitions, blockDe
writeLine(fileDescriptor)
writeLine(fileDescriptor, ' const QModbusDataUnit unit = reply->result();')
writeLine(fileDescriptor, ' const QVector<quint16> blockValues = unit.values();')
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "<-- Response from reading init block \\"%s\\" register" << %s << "size:" << %s << blockValues;' % (className, blockName, blockStartAddress, blockSize))
writeLine(fileDescriptor, ' qCDebug(dc%s()) << "<-- Response from reading block \\"%s\\" register" << %s << "size:" << %s << blockValues;' % (className, blockName, blockStartAddress, blockSize))
# Start parsing the registers using offsets
offset = 0

View File

@ -413,6 +413,7 @@ def writeRtuHeaderFile():
writeLine(headerFile)
writeLine(headerFile, ' void handleModbusError(ModbusRtuReply::Error error);')
writeLine(headerFile, ' void evaluateReachableState();')
writeLine(headerFile, ' void verifyReachability();')
# End of class
@ -446,11 +447,12 @@ def writeRtuSourceFile():
writeLine(sourceFile, '{')
writeLine(sourceFile, ' connect(m_modbusRtuMaster, &ModbusRtuMaster::connectedChanged, this, [=](bool connected){')
writeLine(sourceFile, ' if (connected) {')
writeLine(sourceFile, ' qCDebug(dc%s()) << "Modbus RTU resource" << m_modbusRtuMaster->serialPort() << "connected again. TODO: Test if the connection is reachable...";' % (className))
writeLine(sourceFile, ' qCDebug(dc%s()) << "Modbus RTU resource" << m_modbusRtuMaster->serialPort() << "connected again. Start testing if the connection is reachable...";' % (className))
writeLine(sourceFile, ' m_pendingInitReplies.clear();')
writeLine(sourceFile, ' m_pendingUpdateReplies.clear();')
writeLine(sourceFile, ' m_communicationWorking = false;')
writeLine(sourceFile, ' m_communicationFailedCounter = 0;')
writeLine(sourceFile, ' verifyReachability();')
writeLine(sourceFile, ' } else {')
writeLine(sourceFile, ' qCWarning(dc%s()) << "Modbus RTU resource" << m_modbusRtuMaster->serialPort() << "disconnected. The connection is not reachable any more.";' % (className))
writeLine(sourceFile, ' m_communicationWorking = false;')
@ -597,6 +599,8 @@ def writeRtuSourceFile():
writeLine(sourceFile, '}')
writeLine(sourceFile)
writeVerifyReachabilityImplementationsRtu(sourceFile, className, registerJson['registers'], checkReachableRegister)
# Write the debug print
debugObjectParamName = className[0].lower() + className[1:]
writeLine(sourceFile, 'QDebug operator<<(QDebug debug, %s *%s)' % (className, debugObjectParamName))
@ -648,7 +652,7 @@ if args.verboseOutput:
logger.debug("Verbose output enabled")
if not 'className' in registerJson:
logger.warning('Classname missing. Please specify the classname in the json file or pass it to the generatori using -c .')
logger.warning('Error: Class name property missing. Please specify the \"classname\" property in the json file.')
exit(1)
classNamePrefix = registerJson['className']
@ -661,11 +665,39 @@ errorLimitUntilNotReachable = 10
if 'errorLimitUntilNotReachable' in registerJson:
errorLimitUntilNotReachable = registerJson['errorLimitUntilNotReachable']
logger.debug('Scrip path: %s' % scriptPath)
# Check if the developer has specified an
checkReachableRegister = {}
if not 'checkReachableRegister' in registerJson:
logger.warning('Error: There is no checkReachableRegister specified. Please specify the \"checkReachableRegister\" property in the register JSON and set it to the \"id\" of a mandatory readable register which should be used for testing the communication.')
exit(1)
checkReachableRegister = registerJson['checkReachableRegister']
registerExists = False
# Make sure this is an existing and readable register
for registerDefinition in registerJson['registers']:
if registerDefinition['id'] == checkReachableRegister:
if not 'R' in registerDefinition['access']:
logger.warning('Error: The specified \"checkReachableRegister\" is not readable. Please select a manadtory readable register as checkReachableRegister.')
exit(1)
checkReachableRegister = registerDefinition
registerExists = True
break
if not registerExists:
logger.warning('Error: Could not find the given \"checkReachableRegister\". Please make sure the specified register matches the \"id\" of a defined register.')
exit(1)
else:
logger.debug('Verified successfully checkReachableRegister: %s' % checkReachableRegister['id'])
# 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('Error limit until not reachable: %s' % errorLimitUntilNotReachable)
logger.debug('Check reachable register: %s' % checkReachableRegister['id'])
protocol = 'TCP'
if 'protocol' in registerJson:
@ -678,7 +710,7 @@ if 'blocks' in registerJson:
writeTcp = protocol in ["TCP", "BOTH"]
writeRtu = protocol in ["RTU", "BOTH"]
if not writeTcp and not writeRtu:
logger.warning('Invalid protocol definition. Please use TCP, RTU or BOTH.')
logger.warning('Error: Invalid protocol definition. Please use TCP, RTU or BOTH in the register JSON file.')
exit(1)
headerFiles = []
@ -725,8 +757,8 @@ projectIncludeFilePath = os.path.join(outputDirectory, projectIncludeFileName)
# Note: we write the project file only if the registers
# file has been modified since the project has been modified the last time.
# This prevents qt-creator to retrigger qmake runs on it's own by changing the
# project file which retriggers a qmake run and so on...
# This prevents qt-creator to retrigger qmake runs on it's own if the pri file is open
# by changing the project file which retriggers a qmake run and so on...
if os.path.exists(projectIncludeFilePath):
timestampRegistersJson = os.path.getmtime(registerJsonFilePath)
timestampProjectInclude = os.path.getmtime(projectIncludeFilePath)

View File

@ -2,6 +2,8 @@
"className": "Test",
"protocol": "BOTH",
"endianness": "BigEndian",
"checkReachableRegister": "unsignedLongWord",
"errorLimitUntilNotReachable": 10,
"enums": [
{
"name": "TestEnum",

View File

@ -128,11 +128,17 @@ void IntegrationPluginPhoenixConnect::setupThing(ThingSetupInfo *info)
}
});
connect(connection, &PhoenixModbusTcpConnection::initializationFinished, info, [this, thing, connection, monitor, info]{
qCDebug(dcPhoenixContact()) << "Phoenix wallbox initialized. Firmware version:" << connection->firmwareVersion();
m_connections.insert(thing, connection);
m_monitors.insert(thing, monitor);
info->finish(Thing::ThingErrorNoError);
connect(connection, &PhoenixModbusTcpConnection::initializationFinished, info, [this, thing, connection, monitor, info](bool success){
if (success) {
qCDebug(dcPhoenixContact()) << "Phoenix wallbox initialized. Firmware version:" << connection->firmwareVersion();
m_connections.insert(thing, connection);
m_monitors.insert(thing, monitor);
info->finish(Thing::ThingErrorNoError);
} else {
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(monitor);
connection->deleteLater();
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Could not initialize the communication with the wallbox."));
}
});
connect(connection, &PhoenixModbusTcpConnection::initializationFinished, thing, [thing, connection]{

View File

@ -2,6 +2,8 @@
"className": "Phoenix",
"protocol": "TCP",
"endianness": "LittleEndian",
"errorLimitUntilNotReachable": 20,
"checkReachableRegister": "chargingCurrent",
"enums": [
{
"name": "ErrorCode",

View File

@ -2,6 +2,8 @@
"className": "Cion",
"protocol": "RTU",
"endianness": "LittleEndian",
"errorLimitUntilNotReachable": 10,
"checkReachableRegister": "chargingEnabled",
"blocks": [
{
"id": "e3",

View File

@ -2,6 +2,8 @@
"className": "StiebelEltron",
"protocol": "TCP",
"endianness": "BigEndian",
"errorLimitUntilNotReachable": 40,
"checkReachableRegister": "outdoorTemperature",
"enums": [
{
"name": "OperatingMode",