"""ModbusProtocol implementation for all clients.""" from __future__ import annotations from collections.abc import Callable from typing import cast from pymodbus.factory import ClientDecoder from pymodbus.framer import FRAMER_NAME_TO_CLASS, FramerType, ModbusFramer from pymodbus.logging import Log from pymodbus.transaction import ModbusTransactionManager from pymodbus.transport import CommParams, ModbusProtocol class ModbusClientProtocol(ModbusProtocol): """**ModbusClientProtocol**. :mod:`ModbusClientProtocol` is normally not referenced outside :mod:`pymodbus`. """ def __init__( self, framer: FramerType, params: CommParams, on_connect_callback: Callable[[bool], None] | None = None, ) -> None: """Initialize a client instance.""" ModbusProtocol.__init__( self, params, False, ) self.on_connect_callback = on_connect_callback # Common variables. self.framer = FRAMER_NAME_TO_CLASS.get( framer, cast(type[ModbusFramer], framer) )(ClientDecoder(), self) self.transaction = ModbusTransactionManager() def _handle_response(self, reply): """Handle the processed response and link to correct deferred.""" if reply is not None: tid = reply.transaction_id if handler := self.transaction.getTransaction(tid): reply.request = handler if not handler.fut.done(): handler.fut.set_result(reply) else: Log.debug("Unrequested message: {}", reply, ":str") def callback_new_connection(self): """Call when listener receive new connection request.""" def callback_connected(self) -> None: """Call when connection is succcesfull.""" if self.on_connect_callback: self.loop.call_soon(self.on_connect_callback, True) self.framer.resetFrame() def callback_disconnected(self, exc: Exception | None) -> None: """Call when connection is lost.""" Log.debug("callback_disconnected called: {}", exc) if self.on_connect_callback: self.loop.call_soon(self.on_connect_callback, False) def callback_data(self, data: bytes, addr: tuple | None = None) -> int: """Handle received data. returns number of bytes consumed """ self.framer.processIncomingPacket(data, self._handle_response, 0) return len(data) def __str__(self): """Build a string representation of the connection. :returns: The string representation """ return ( f"{self.__class__.__name__} {self.comm_params.host}:{self.comm_params.port}" )