Repository: pymodbus-dev/pymodbus

Issue:
Client on_reconnect_callback is never executed
<!--
Before opening a new issue, make sure you do the following:

- Check that your issue isn't already filed: https://github.com/pymodbus-dev/pymodbus/issues
- Check the discussions forum https://github.com/pymodbus-dev/pymodbus/discussions
- Prepare a short, runnable example that reproduce the issue with the latest development version of Pymodbus
-->

### Versions

- Python: 3.11.4
- OS: Windows 10
- Pymodbus: 3.6.3
- Modbus Hardware: Phoenix Contact AXL F BK ETH via UDP/TCP

### Pymodbus Specific

- Client: tcp/udp - async (reconnect not applicable to sync)

### Description

<!-- What were you trying, what has happened, what went wrong, and what did you expect? -->
The client parameter/method on_reconnect_callback suggested in #1354 and implemented in #1425 is never executed in the current version of pymodbus. I believe this client parameter is unimplemented in the current version and failed to find any execution of the *client*.on_reconnect_callback method in the current pymodbus codebase or test suite. I attempted to use this callback to notify another async application of the failed connection, however, I could not trigger the callback in simulated/physical testing.

### Code

```python
from pymodbus.client import AsyncModbusTcpClient
from pymodbus import pymodbus_apply_logging_config
import asyncio


COMMAND_REGISTER = 2006
RESET_COMMAND = 0x8F00

HOST = "w.x.y.z"
PORT = 502
CALLBACK_USED = False


def reconnect_callback():
    CALLBACK_USED = True
    print("Reconnect callback called!")


async def call_device_restart(client: AsyncModbusTcpClient):
    """ Executes a reset command for a Phoenix-contact AXL-F device.
    Simulates disconnection, on_reconnect_callback is never called.
    """
    await client.write_register(COMMAND_REGISTER, value=RESET_COMMAND)


async def run_client():
    client = AsyncModbusTcpClient(HOST, PORT, on_reconnect_callback=reconnect_callback)
    await client.connect()
    assert client.connected
    print(f"Client Connected to {HOST}:{PORT}")
    print("Restarting Client")
    await call_device_restart(client)
    await asyncio.sleep(10)
    if not CALLBACK_USED:
        print("ERROR: Reconnect callback never called.")


if __name__ == "__main__":
    pymodbus_apply_logging_config("DEBUG")
    print("Starting Client.")
    asyncio.run(run_client(), debug=True)
    print("Script Done.")
```
### Logs

```
Starting Client.
2024-01-30 20:54:41,492 DEBUG logging:103 Connecting to w.x.y.z:502.
2024-01-30 20:54:41,493 DEBUG logging:103 Connecting comm
2024-01-30 20:54:41,500 DEBUG logging:103 Connected to comm
2024-01-30 20:54:41,500 DEBUG logging:103 callback_connected called
Client Connected to 169.254.0.13:502
Restarting Client
2024-01-30 20:54:41,503 DEBUG logging:103 send: 0x0 0x1 0x0 0x0 0x0 0x6 0x0 0x6 0x7 0xd6 0x8f 0x0
2024-01-30 20:54:41,506 DEBUG logging:103 Adding transaction 1
2024-01-30 20:54:41,508 DEBUG logging:103 recv: 0x0 0x1 0x0 0x0 0x0 0x6 0x0 0x6 0x7 0xd6 0x8f 0x0 old_data:  addr=None
2024-01-30 20:54:41,508 DEBUG logging:103 Processing: 0x0 0x1 0x0 0x0 0x0 0x6 0x0 0x6 0x7 0xd6 0x8f 0x0
2024-01-30 20:54:41,509 DEBUG logging:103 Factory Response[WriteSingleRegisterResponse': 6]
2024-01-30 20:54:41,509 DEBUG logging:103 Getting transaction 1
2024-01-30 20:54:46,788 DEBUG logging:103 Connection lost comm due to [WinError 1236] The network connection was aborted by the local system
2024-01-30 20:54:46,790 DEBUG logging:103 Wait comm 100.0 ms before reconnecting.
2024-01-30 20:54:46,892 DEBUG logging:103 Connecting comm
2024-01-30 20:54:46,896 WARNING logging:109 Failed to connect [WinError 1232] The network location cannot be reached. For information about network troubleshooting, see Windows Help
2024-01-30 20:54:46,897 DEBUG logging:103 Wait comm 200.0 ms before reconnecting.  
2024-01-30 20:54:47,100 DEBUG logging:103 Connecting comm
2024-01-30 20:54:47,104 WARNING logging:109 Failed to connect [WinError 1232] The network location cannot be reached. For information about network troubleshooting, see Windows Help
2024-01-30 20:54:47,105 DEBUG logging:103 Wait comm 400.0 ms before reconnecting.  
2024-01-30 20:54:47,508 DEBUG logging:103 Connecting comm
2024-01-30 20:54:47,512 WARNING logging:109 Failed to connect [WinError 1232] The network location cannot be reached. For information about network troubleshooting, see Windows Help
2024-01-30 20:54:47,513 DEBUG logging:103 Wait comm 800.0 ms before reconnecting.
2024-01-30 20:54:48,314 DEBUG logging:103 Connecting comm
2024-01-30 20:54:48,318 WARNING logging:109 Failed to connect [WinError 1232] The network location cannot be reached. For information about network troubleshooting, see Windows Help
2024-01-30 20:54:48,319 DEBUG logging:103 Wait comm 1600.0 ms before reconnecting.
2024-01-30 20:54:49,920 DEBUG logging:103 Connecting comm
2024-01-30 20:54:49,924 WARNING logging:109 Failed to connect [WinError 1232] The network location cannot be reached. For information about network troubleshooting, see Windows Help
2024-01-30 20:54:49,925 DEBUG logging:103 Wait comm 3200.0 ms before reconnecting.
ERROR: Reconnect callback never called.
Script Done.
```



Discussion:
Seems like a bug, let me have a look.

Resolving patch:
diff --git a/pymodbus/client/base.py b/pymodbus/client/base.py
--- a/pymodbus/client/base.py
+++ b/pymodbus/client/base.py
@@ -346,6 +346,7 @@ def __init__(  # pylint: disable=too-many-arguments
                 parity=kwargs.get("parity", None),
                 stopbits=kwargs.get("stopbits", None),
                 handle_local_echo=kwargs.get("handle_local_echo", False),
+                on_reconnect_callback = on_reconnect_callback,
             ),
             False,
         )
@@ -355,7 +356,6 @@ def __init__(  # pylint: disable=too-many-arguments
         self.params.close_comm_on_error = bool(close_comm_on_error)
         self.params.strict = bool(strict)
         self.params.broadcast_enable = bool(broadcast_enable)
-        self.on_reconnect_callback = on_reconnect_callback
         self.retry_on_empty: int = 0
         self.no_resend_on_retry = no_resend_on_retry
         self.slaves: list[int] = []
diff --git a/pymodbus/transport/transport.py b/pymodbus/transport/transport.py
--- a/pymodbus/transport/transport.py
+++ b/pymodbus/transport/transport.py
@@ -85,6 +85,7 @@ class CommParams:
     port: int = 0
     source_address: tuple[str, int] | None = None
     handle_local_echo: bool = False
+    on_reconnect_callback: Callable[[], None] | None = None
 
     # tls
     sslctx: ssl.SSLContext | None = None
@@ -467,6 +468,8 @@ async def do_reconnect(self) -> None:
                     self.reconnect_delay_current * 1000,
                 )
                 await asyncio.sleep(self.reconnect_delay_current)
+                if self.comm_params.on_reconnect_callback:
+                    self.comm_params.on_reconnect_callback()
                 if await self.transport_connect():
                     break
                 self.reconnect_delay_current = min(


Test patch: