From 586f50a2dc29381a1c4951c830615a70c64c8ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 2 Jun 2023 16:56:46 +0200 Subject: [PATCH] modbus-tool: Improve reachability check error handling --- libnymea-modbus/modbusdatautils.cpp | 42 +++++++++++++++ libnymea-modbus/modbusdatautils.h | 3 ++ libnymea-modbus/modbustcpmaster.cpp | 4 +- .../tools/connectiontool/modbustcp.py | 52 ++++++++++++++++--- libnymea-modbus/tools/generate-connection.py | 4 +- 5 files changed, 95 insertions(+), 10 deletions(-) diff --git a/libnymea-modbus/modbusdatautils.cpp b/libnymea-modbus/modbusdatautils.cpp index be6d61c..1729e58 100644 --- a/libnymea-modbus/modbusdatautils.cpp +++ b/libnymea-modbus/modbusdatautils.cpp @@ -270,3 +270,45 @@ QVector ModbusDataUtils::convertFromFloat64(double value, ByteOrder byt memcpy(&rawValue, &value, sizeof(double)); return ModbusDataUtils::convertFromUInt64(rawValue, byteOrder); } + +QString ModbusDataUtils::exceptionCodeToString(QModbusPdu::ExceptionCode exception) +{ + QString exceptionString; + switch (exception) { + case QModbusPdu::IllegalFunction: + exceptionString = "Illegal function"; + break; + case QModbusPdu::IllegalDataAddress: + exceptionString = "Illegal data address"; + break; + case QModbusPdu::IllegalDataValue: + exceptionString = "Illegal data value"; + break; + case QModbusPdu::ServerDeviceFailure: + exceptionString = "Server device failure"; + break; + case QModbusPdu::Acknowledge: + exceptionString = "Acknowledge"; + break; + case QModbusPdu::ServerDeviceBusy: + exceptionString = "Server device busy"; + break; + case QModbusPdu::NegativeAcknowledge: + exceptionString = "Negative acknowledge"; + break; + case QModbusPdu::MemoryParityError: + exceptionString = "Memory parity error"; + break; + case QModbusPdu::GatewayPathUnavailable: + exceptionString = "Gateway path unavailable"; + break; + case QModbusPdu::GatewayTargetDeviceFailedToRespond: + exceptionString = "Gateway target device failed to respond"; + break; + case QModbusPdu::ExtendedException: + exceptionString = "Extended exception"; + break; + } + + return exceptionString; +} diff --git a/libnymea-modbus/modbusdatautils.h b/libnymea-modbus/modbusdatautils.h index 97ded87..1a587e3 100644 --- a/libnymea-modbus/modbusdatautils.h +++ b/libnymea-modbus/modbusdatautils.h @@ -33,6 +33,7 @@ #include #include +#include class ModbusDataUtils { @@ -102,6 +103,8 @@ public: static QVector convertFromString(const QString &value, quint16 stringLength, ByteOrder characterByteOrder = ByteOrderLittleEndian); static QVector convertFromFloat32(float value, ByteOrder byteOrder = ByteOrderLittleEndian); static QVector convertFromFloat64(double value, ByteOrder byteOrder = ByteOrderLittleEndian); + + static QString exceptionCodeToString(QModbusPdu::ExceptionCode exception); }; #endif // MODBUSDATAUTILS_H diff --git a/libnymea-modbus/modbustcpmaster.cpp b/libnymea-modbus/modbustcpmaster.cpp index fa66e24..56a4871 100644 --- a/libnymea-modbus/modbustcpmaster.cpp +++ b/libnymea-modbus/modbustcpmaster.cpp @@ -100,9 +100,9 @@ bool ModbusTcpMaster::connectDevice() m_modbusTcpClient->setTimeout(m_timeout); m_modbusTcpClient->setNumberOfRetries(m_numberOfRetries); return m_modbusTcpClient->connectDevice(); - } else if (m_modbusTcpClient->state() != QModbusDevice::ConnectedState) { + } else if (m_modbusTcpClient->state() != QModbusDevice::ConnectedState && m_modbusTcpClient->state() != QModbusDevice::ConnectingState) { // Restart the timer in case of connecting not finished yet or closing - qCDebug(dcModbusTcpMaster()) << "Starting the reconnect mechanism timer"; + qCDebug(dcModbusTcpMaster()) << "Starting the re-connect mechanism timer"; m_reconnectTimer->start(); } else { qCWarning(dcModbusTcpMaster()) << "Connect modbus TCP device" << connectionUrl() << "called, but the socket is currently in the" << m_modbusTcpClient->state(); diff --git a/libnymea-modbus/tools/connectiontool/modbustcp.py b/libnymea-modbus/tools/connectiontool/modbustcp.py index d01756a..a5ff0fb 100644 --- a/libnymea-modbus/tools/connectiontool/modbustcp.py +++ b/libnymea-modbus/tools/connectiontool/modbustcp.py @@ -114,7 +114,12 @@ def writePropertyUpdateMethodImplementationsTcp(fileDescriptor, className, regis writeLine(fileDescriptor, ' });') writeLine(fileDescriptor) writeLine(fileDescriptor, ' connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){') - writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while updating \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();' % (className, registerDefinition['description'])) + writeLine(fileDescriptor, ' QModbusResponse response = reply->rawResult();') + writeLine(fileDescriptor, ' if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {') + writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while updating \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, registerDefinition['description'])) + writeLine(fileDescriptor, ' } else {') + writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while updating \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();' % (className, registerDefinition['description'])) + writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' });') writeLine(fileDescriptor, '}') writeLine(fileDescriptor) @@ -177,7 +182,12 @@ def writeBlockUpdateMethodImplementationsTcp(fileDescriptor, className, blockDef writeLine(fileDescriptor, ' });') writeLine(fileDescriptor) writeLine(fileDescriptor, ' connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error){') - writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString();' % (className, blockName)) + writeLine(fileDescriptor, ' QModbusResponse response = reply->rawResult();') + writeLine(fileDescriptor, ' if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {') + writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, blockName)) + writeLine(fileDescriptor, ' } else {') + writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString();' % (className, blockName)) + writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' });') writeLine(fileDescriptor, '}') writeLine(fileDescriptor) @@ -317,7 +327,15 @@ def writeTestReachabilityImplementationsTcp(fileDescriptor, className, registerD writeLine(fileDescriptor, ' });') writeLine(fileDescriptor) writeLine(fileDescriptor, ' connect(m_checkRechableReply, &QModbusReply::errorOccurred, this, [this] (QModbusDevice::Error error){') - writeLine(fileDescriptor, ' qCDebug(dc%s()) << "Modbus reply error occurred while verifying reachability by reading \\"%s\\" register" << error << m_checkRechableReply->errorString();' % (className, checkReachableRegister['description'])) + writeLine(fileDescriptor, ' QModbusResponse response = m_checkRechableReply->rawResult();') + writeLine(fileDescriptor, ' if (m_checkRechableReply->error() == QModbusDevice::ProtocolError && response.isException()) {') + writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while verifying reachability by reading \\"%s\\" register" << error << m_checkRechableReply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, checkReachableRegister['description'])) + writeLine(fileDescriptor, ' // Note: if we get an exception on the reachability register, the modbus server is probably not ready') + writeLine(fileDescriptor, ' // For some reasons on some devices the reply will never be finished on exception response. A reconnect might fix it.') + writeLine(fileDescriptor, ' QTimer::singleShot(2000, m_modbusTcpMaster, &ModbusTcpMaster::reconnectDevice);') + writeLine(fileDescriptor, ' } else {') + writeLine(fileDescriptor, ' qCDebug(dc%s()) << "Modbus reply error occurred while verifying reachability by reading \\"%s\\" register" << error << m_checkRechableReply->errorString();' % (className, checkReachableRegister['description'])) + writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' });') writeLine(fileDescriptor, '}') writeLine(fileDescriptor) @@ -398,7 +416,12 @@ def writeInitMethodImplementationTcp(fileDescriptor, className, registerDefiniti writeLine(fileDescriptor, ' });') writeLine(fileDescriptor) writeLine(fileDescriptor, ' connect(reply, &QModbusReply::errorOccurred, m_initObject, [this, reply] (QModbusDevice::Error error){') - writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while reading \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();' % (className, registerDefinition['description'])) + writeLine(fileDescriptor, ' QModbusResponse response = reply->rawResult();') + writeLine(fileDescriptor, ' if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {') + writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while reading \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, registerDefinition['description'])) + writeLine(fileDescriptor, ' } else {') + writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while reading \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();' % (className, registerDefinition['description'])) + writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' });') # Read init blocks @@ -465,7 +488,12 @@ def writeInitMethodImplementationTcp(fileDescriptor, className, registerDefiniti writeLine(fileDescriptor, ' });') writeLine(fileDescriptor) writeLine(fileDescriptor, ' connect(reply, &QModbusReply::errorOccurred, m_initObject, [reply] (QModbusDevice::Error error){') - writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString();' % (className, blockName)) + writeLine(fileDescriptor, ' QModbusResponse response = reply->rawResult();') + writeLine(fileDescriptor, ' if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {') + writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, blockName)) + writeLine(fileDescriptor, ' } else {') + writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString();' % (className, blockName)) + writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' });') writeLine(fileDescriptor) @@ -546,7 +574,12 @@ def writeUpdateMethodTcp(fileDescriptor, className, registerDefinitions, blockDe writeLine(fileDescriptor, ' });') writeLine(fileDescriptor) writeLine(fileDescriptor, ' connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){') - writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while reading \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();' % (className, registerDefinition['description'])) + writeLine(fileDescriptor, ' QModbusResponse response = reply->rawResult();') + writeLine(fileDescriptor, ' if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {') + writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while reading \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, registerDefinition['description'])) + writeLine(fileDescriptor, ' } else {') + writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while reading \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();' % (className, registerDefinition['description'])) + writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' });') # Read init blocks @@ -613,7 +646,12 @@ def writeUpdateMethodTcp(fileDescriptor, className, registerDefinitions, blockDe writeLine(fileDescriptor, ' });') writeLine(fileDescriptor) writeLine(fileDescriptor, ' connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error){') - writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString();' % (className, blockName)) + writeLine(fileDescriptor, ' QModbusResponse response = reply->rawResult();') + writeLine(fileDescriptor, ' if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {') + writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, blockName)) + writeLine(fileDescriptor, ' } else {') + writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString();' % (className, blockName)) + writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' });') writeLine(fileDescriptor) diff --git a/libnymea-modbus/tools/generate-connection.py b/libnymea-modbus/tools/generate-connection.py index 0d40dfd..1b9541f 100644 --- a/libnymea-modbus/tools/generate-connection.py +++ b/libnymea-modbus/tools/generate-connection.py @@ -208,6 +208,8 @@ def writeTcpSourceFile(): writeLine(sourceFile, '#include ') writeLine(sourceFile, '#include ') writeLine(sourceFile, '#include ') + writeLine(sourceFile, '#include ') + writeLine(sourceFile, '#include ') writeLine(sourceFile) writeLine(sourceFile, 'NYMEA_LOGGING_CATEGORY(dc%s, "%s")' % (className, className)) writeLine(sourceFile) @@ -366,7 +368,7 @@ def writeTcpSourceFile(): writeLine(sourceFile, ' m_communicationFailedCounter++;') writeLine(sourceFile, ' if (m_communicationWorking && m_communicationFailedCounter >= m_communicationFailedMax) {') writeLine(sourceFile, ' m_communicationWorking = false;') - writeLine(sourceFile, ' qCWarning(dc%s()) << "Received" << m_communicationFailedCounter << "errors while communicating with the RTU master. Mark as not reachable until the communication works again.";' % (className)) + writeLine(sourceFile, ' qCWarning(dc%s()) << "Received" << m_communicationFailedCounter << "errors while communicating with the TCP master. Mark as not reachable until the communication works again.";' % (className)) writeLine(sourceFile, ' evaluateReachableState();') writeLine(sourceFile, ' }') writeLine(sourceFile, ' }')