diff --git a/libnymea-modbus/modbustcpmaster.h b/libnymea-modbus/modbustcpmaster.h index 4b456ee..4629419 100644 --- a/libnymea-modbus/modbustcpmaster.h +++ b/libnymea-modbus/modbustcpmaster.h @@ -88,7 +88,7 @@ public slots: void disconnectDevice(); bool reconnectDevice(); -private: +protected: QTimer *m_reconnectTimer = nullptr; QModbusTcpClient *m_modbusTcpClient = nullptr; diff --git a/libnymea-modbus/tools/connectiontool/modbustcp.py b/libnymea-modbus/tools/connectiontool/modbustcp.py index 508bae5..35e117c 100644 --- a/libnymea-modbus/tools/connectiontool/modbustcp.py +++ b/libnymea-modbus/tools/connectiontool/modbustcp.py @@ -97,6 +97,7 @@ def writePropertyUpdateMethodImplementationsTcp(fileDescriptor, className, regis writeLine(fileDescriptor) writeLine(fileDescriptor, ' connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);') writeLine(fileDescriptor, ' connect(reply, &QModbusReply::finished, this, [this, reply](){') + writeLine(fileDescriptor, ' handleModbusError(reply->error());') writeLine(fileDescriptor, ' if (reply->error() == QModbusDevice::NoError) {') writeLine(fileDescriptor, ' const QModbusDataUnit unit = reply->result();') writeLine(fileDescriptor, ' qCDebug(dc%s()) << "<-- Response from \\"%s\\" register" << %s << "size:" << %s << unit.values();' % (className, registerDefinition['description'], registerDefinition['address'], registerDefinition['size'])) @@ -144,7 +145,9 @@ def writeBlockUpdateMethodImplementationsTcp(fileDescriptor, className, blockDef writeLine(fileDescriptor, ' return;') writeLine(fileDescriptor, ' }') writeLine(fileDescriptor) + writeLine(fileDescriptor, ' connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);') writeLine(fileDescriptor, ' connect(reply, &QModbusReply::finished, this, [this, reply](){') + writeLine(fileDescriptor, ' handleModbusError(reply->error());') writeLine(fileDescriptor, ' if (reply->error() == QModbusDevice::NoError) {') writeLine(fileDescriptor, ' const QModbusDataUnit unit = reply->result();') writeLine(fileDescriptor, ' const QVector blockValues = unit.values();') @@ -266,6 +269,39 @@ def writeInternalBlockReadMethodImplementationsTcp(fileDescriptor, className, bl ############################################################## +def writeVerifyReachabilityImplementationsTcp(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, ' QModbusReply *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, ' reply->deleteLater(); // Broadcast reply returns immediatly') + writeLine(fileDescriptor, ' return;') + writeLine(fileDescriptor, ' }') + writeLine(fileDescriptor) + writeLine(fileDescriptor, ' connect(reply, &QModbusReply::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, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::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 writeInitMethodImplementationTcp(fileDescriptor, className, registerDefinitions, blockDefinitions): writeLine(fileDescriptor, 'bool %s::initialize()' % (className)) writeLine(fileDescriptor, '{') @@ -317,6 +353,7 @@ def writeInitMethodImplementationTcp(fileDescriptor, className, registerDefiniti writeLine(fileDescriptor, ' m_pendingInitReplies.append(reply);') writeLine(fileDescriptor, ' connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);') writeLine(fileDescriptor, ' connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){') + writeLine(fileDescriptor, ' handleModbusError(reply->error());') writeLine(fileDescriptor, ' m_pendingInitReplies.removeAll(reply);') writeLine(fileDescriptor, ' if (reply->error() != QModbusDevice::NoError) {') writeLine(fileDescriptor, ' finishInitialization(false);') @@ -371,11 +408,12 @@ def writeInitMethodImplementationTcp(fileDescriptor, className, registerDefiniti writeLine(fileDescriptor, ' connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);') writeLine(fileDescriptor, ' connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){') writeLine(fileDescriptor, ' m_pendingInitReplies.removeAll(reply);') + writeLine(fileDescriptor, ' handleModbusError(reply->error());') writeLine(fileDescriptor, ' if (reply->error() != QModbusDevice::NoError) {') writeLine(fileDescriptor, ' finishInitialization(false);') writeLine(fileDescriptor, ' return;') writeLine(fileDescriptor, ' }') - + writeLine(fileDescriptor) writeLine(fileDescriptor, ' const QModbusDataUnit unit = reply->result();') writeLine(fileDescriptor, ' const QVector blockValues = unit.values();') writeLine(fileDescriptor, ' qCDebug(dc%s()) << "<-- Response from reading init block \\"%s\\" register" << %s << "size:" << %s << blockValues;' % (className, blockName, blockStartAddress, blockSize)) @@ -456,6 +494,7 @@ def writeUpdateMethodTcp(fileDescriptor, className, registerDefinitions, blockDe writeLine(fileDescriptor, ' connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);') writeLine(fileDescriptor, ' connect(reply, &QModbusReply::finished, this, [this, reply](){') writeLine(fileDescriptor, ' m_pendingUpdateReplies.removeAll(reply);') + writeLine(fileDescriptor, ' handleModbusError(reply->error());') writeLine(fileDescriptor, ' if (reply->error() != QModbusDevice::NoError) {') writeLine(fileDescriptor, ' verifyUpdateFinished();') writeLine(fileDescriptor, ' return;') @@ -508,6 +547,7 @@ def writeUpdateMethodTcp(fileDescriptor, className, registerDefinitions, blockDe writeLine(fileDescriptor, ' connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);') writeLine(fileDescriptor, ' connect(reply, &QModbusReply::finished, this, [this, reply](){') writeLine(fileDescriptor, ' m_pendingUpdateReplies.removeAll(reply);') + writeLine(fileDescriptor, ' handleModbusError(reply->error());') writeLine(fileDescriptor, ' if (reply->error() != QModbusDevice::NoError) {') writeLine(fileDescriptor, ' verifyUpdateFinished();') writeLine(fileDescriptor, ' return;') diff --git a/libnymea-modbus/tools/generate-connection.py b/libnymea-modbus/tools/generate-connection.py index 266830d..255032a 100644 --- a/libnymea-modbus/tools/generate-connection.py +++ b/libnymea-modbus/tools/generate-connection.py @@ -66,6 +66,8 @@ def writeTcpHeaderFile(): writeLine(headerFile, ' explicit %s(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent = nullptr);' % className) writeLine(headerFile, ' ~%s() = default;' % className) writeLine(headerFile) + writeLine(headerFile, ' bool reachable() const;') + writeLine(headerFile) writeLine(headerFile, ' ModbusDataUtils::ByteOrder endianness() const;') writeLine(headerFile, ' void setEndianness(ModbusDataUtils::ByteOrder endianness);') writeLine(headerFile) @@ -104,6 +106,8 @@ def writeTcpHeaderFile(): # Write registers value changed signals writeLine(headerFile, 'signals:') + writeLine(headerFile, ' void reachableChanged(bool reachable);') + writeLine(headerFile) writeLine(headerFile, ' void initializationFinished(bool success);') writeLine(headerFile, ' void updateFinished();') writeLine(headerFile) @@ -135,6 +139,10 @@ def writeTcpHeaderFile(): writeLine(headerFile, 'private:') writeLine(headerFile, ' ModbusDataUtils::ByteOrder m_endianness = ModbusDataUtils::ByteOrder%s;' % endianness) writeLine(headerFile, ' quint16 m_slaveId = 1;') + writeLine(headerFile, ' bool m_reachable = false;') + writeLine(headerFile, ' bool m_communicationWorking = false;') + writeLine(headerFile, ' quint8 m_communicationFailedMax = %s;' % (errorLimitUntilNotReachable)) + writeLine(headerFile, ' quint8 m_communicationFailedCounter = 0;') writeLine(headerFile) writeLine(headerFile, ' QVector m_pendingInitReplies;') writeLine(headerFile, ' QVector m_pendingUpdateReplies;') @@ -144,6 +152,10 @@ def writeTcpHeaderFile(): writeLine(headerFile, ' void finishInitialization(bool success);') writeLine(headerFile) writeLine(headerFile, ' void verifyUpdateFinished();') + writeLine(headerFile) + writeLine(headerFile, ' void handleModbusError(QModbusDevice::Error error);') + writeLine(headerFile, ' void evaluateReachableState();') + writeLine(headerFile, ' void verifyReachability();') # End of class writeLine(headerFile) @@ -174,14 +186,30 @@ def writeTcpSourceFile(): writeLine(sourceFile, '{') writeLine(sourceFile, ' connect(this, &ModbusTCPMaster::connectionStateChanged, this, [this](bool status){') writeLine(sourceFile, ' if (status) {') + writeLine(sourceFile, ' qCDebug(dc%s()) << "Modbus TCP connection" << m_hostAddress.toString() << "connected. Start testing if the connection is reachable...";' % (className)) writeLine(sourceFile, ' // Cleanup before starting to initialize') 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 TCP connection diconnected from" << m_hostAddress.toString() << ". The connection is not reachable any more.";' % (className)) + writeLine(sourceFile, ' m_communicationWorking = false;') + writeLine(sourceFile, ' m_communicationFailedCounter = 0;') writeLine(sourceFile, ' }') + writeLine(sourceFile) + writeLine(sourceFile, ' evaluateReachableState();') writeLine(sourceFile, ' });') writeLine(sourceFile, '}') writeLine(sourceFile) + writeLine(sourceFile, 'bool %s::reachable() const' % (className)) + writeLine(sourceFile, '{') + writeLine(sourceFile, ' return m_reachable;') + writeLine(sourceFile, '}') + writeLine(sourceFile) + writeLine(sourceFile, 'ModbusDataUtils::ByteOrder %s::endianness() const' % (className)) writeLine(sourceFile, '{') writeLine(sourceFile, ' return m_endianness;') @@ -268,6 +296,41 @@ def writeTcpSourceFile(): writeLine(sourceFile, '}') writeLine(sourceFile) + writeLine(sourceFile, 'void %s::handleModbusError(QModbusDevice::Error error)' % (className)) + writeLine(sourceFile, '{') + writeLine(sourceFile, ' if (error == QModbusDevice::NoError) {') + writeLine(sourceFile, ' // Reset the communication counter and we know we can reach the device') + writeLine(sourceFile, ' m_communicationFailedCounter = 0;') + writeLine(sourceFile, ' if (!m_communicationWorking)') + writeLine(sourceFile, ' qCDebug(dc%s()) << "Received a reply without any errors. The communication with the device seems to work now.";' % (className)) + writeLine(sourceFile) + writeLine(sourceFile, ' m_communicationWorking = true;') + writeLine(sourceFile, ' evaluateReachableState();') + writeLine(sourceFile, ' } else {') + 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, ' evaluateReachableState();') + writeLine(sourceFile, ' }') + writeLine(sourceFile, ' }') + writeLine(sourceFile, '}') + writeLine(sourceFile) + + writeLine(sourceFile, 'void %s::evaluateReachableState()' % (className)) + writeLine(sourceFile, '{') + writeLine(sourceFile, ' bool reachable = m_communicationWorking && connected();') + writeLine(sourceFile, ' if (m_reachable == reachable)') + writeLine(sourceFile, ' return;') + writeLine(sourceFile) + writeLine(sourceFile, ' m_reachable = reachable;') + writeLine(sourceFile, ' emit reachableChanged(m_reachable);') + writeLine(sourceFile, '}') + writeLine(sourceFile) + + writeVerifyReachabilityImplementationsTcp(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)) @@ -570,7 +633,7 @@ def writeRtuSourceFile(): writeLine(sourceFile, 'void %s::handleModbusError(ModbusRtuReply::Error error)' % (className)) writeLine(sourceFile, '{') writeLine(sourceFile, ' if (error == ModbusRtuReply::NoError) {') - writeLine(sourceFile, ' // Reset the com counter and we know we can reach the device') + writeLine(sourceFile, ' // Reset the communication counter and we know we can reach the device') writeLine(sourceFile, ' m_communicationFailedCounter = 0;') writeLine(sourceFile, ' if (!m_communicationWorking)') writeLine(sourceFile, ' qCDebug(dc%s()) << "Received a reply without any errors. The communication with the device seems to work now.";' % (className)) @@ -590,7 +653,7 @@ def writeRtuSourceFile(): writeLine(sourceFile, 'void %s::evaluateReachableState()' % (className)) writeLine(sourceFile, '{') - writeLine(sourceFile, ' bool reachable = m_communicationWorking & m_modbusRtuMaster->connected();') + writeLine(sourceFile, ' bool reachable = m_communicationWorking && m_modbusRtuMaster->connected();') writeLine(sourceFile, ' if (m_reachable == reachable)') writeLine(sourceFile, ' return;') writeLine(sourceFile) diff --git a/phoenixconnect/integrationpluginphoenixconnect.cpp b/phoenixconnect/integrationpluginphoenixconnect.cpp index ddd2a4f..bccf398 100644 --- a/phoenixconnect/integrationpluginphoenixconnect.cpp +++ b/phoenixconnect/integrationpluginphoenixconnect.cpp @@ -128,10 +128,7 @@ void IntegrationPluginPhoenixConnect::setupThing(ThingSetupInfo *info) } }); - connect(connection, &PhoenixModbusTcpConnection::updateFinished, thing, [connection, thing](){ - qCDebug(dcPhoenixContact()) << "Update finished:" << thing->name() << connection; - }); - + // Only during setup connect(connection, &PhoenixModbusTcpConnection::initializationFinished, info, [this, thing, connection, monitor, info](bool success){ if (success) { qCDebug(dcPhoenixContact()) << "Phoenix wallbox initialized. Firmware version:" << connection->firmwareVersion(); @@ -145,6 +142,10 @@ void IntegrationPluginPhoenixConnect::setupThing(ThingSetupInfo *info) } }); + connect(connection, &PhoenixModbusTcpConnection::updateFinished, thing, [connection, thing](){ + qCDebug(dcPhoenixContact()) << "Update finished:" << thing->name() << connection; + }); + connect(connection, &PhoenixModbusTcpConnection::initializationFinished, thing, [thing, connection](bool success){ if (success) { thing->setStateValue("connected", true); @@ -152,7 +153,6 @@ void IntegrationPluginPhoenixConnect::setupThing(ThingSetupInfo *info) } }); - // Handle property changed signals connect(connection, &PhoenixModbusTcpConnection::cpStatusChanged, thing, [thing, connection](quint16 cpStatus){ qCDebug(dcPhoenixContact()) << "CP Signal state changed:" << (char)cpStatus;