winki - 23 hours ago

Library: ModbusFB (CODESYS Modbus package)
IDE / Runtime: CODESYS V3.5 SP___ / Control runtime ___
Target: ___ (e.g. gateway, Linux ARM64 / Debian)
Slave: Modbus TCP inverter

Summary

I poll a Modbus TCP slave with a permanent connection: one ClientTCP
(kept connected, xConnect:=TRUE) and one
ClientRequestReadHoldingRegisters, triggered cyclically.

Everything works fine after a fresh application download. But as soon as
one single ReplyTimeout occurs (slave temporarily not answering), the
request never recovers. It stays stuck and no further messages are sent,
even though the TCP connection is still reported as alive. Only a full
application download brings it back β€” an online change or a runtime
reset is not enough / not what I want in production.

State observed when stuck

So the connection is alive, the request finished in ReplyTimeout, the FB
is back to _state = None, but udiNumMsgSent no longer increases β€” as if a
new rising edge on xExecute is no longer accepted / no longer produces a
new message.

My trigger logic (simplified)

Each PLC cycle, in this order:

If the request is fully at rest (NOT xExecute AND NOT xBusy AND NOT xDone AND NOT xError) and a read is pending, I set xExecute := TRUE.
I call clientTcp() then clientRequest(rClient := clientTcp) once
each, per cycle.
On xDone or xError, I set xExecute := FALSE.

I made sure to insert at least one full cycle with xExecute = FALSE
(seen by the FB call) before re-triggering, so the falling edge is processed.

Questions

After a ReplyTimeout (xError = TRUE), what is the exact, correct
sequence to re-trigger the same ClientRequest so a new message is
actually sent again? Is a full cycle with xExecute = FALSE between two
executions mandatory?
Is there a known condition where, after ReplyTimeout, the
ClientTCP keeps xConnected = TRUE but silently stops sending new
requests (e.g. an internal request queue / _udiRequestId that gets out
of sync)? udiNumMsgSent freezing at 23 while the FB shows _state = None is what puzzles me.
Is the recommended pattern to drop the connection (ClientTCP.xConnect := FALSE for one cycle, then TRUE) on a request error, rather than
keeping it alive? The docs state a request error does not close the
connection, so I kept it open β€” but maybe that is the issue here.
Could the large uiStartItem (50514) be relevant? It works right after
download, so addressing seems correct, but I want to rule it out.

Any guidance on the canonical recovery pattern after a request timeout would
be greatly appreciated.

Thanks!

 

Last edit: winki 23 hours ago