Skip to content

Commit

Permalink
Simplify client parent classes. (#1018)
Browse files Browse the repository at this point in the history
  • Loading branch information
janiversen authored Aug 7, 2022
1 parent 0923f4a commit 9bca66d
Show file tree
Hide file tree
Showing 10 changed files with 70 additions and 147 deletions.
26 changes: 22 additions & 4 deletions doc/source/library/pymodbus.client.rst
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
pymodbus\.client
================

Pymodbus offers a :mod:`synchronous client <pymodbus.client>` and a :mod:`client based on asyncio <pymodbus.client.asynchronous>`.
Pymodbus offers

The documentation is divided in 2 parts , to keep it simple.
a :mod:`synchronous client <pymodbus.client>` and

First part is centered around the connection (establishing/terminating)
a :mod:`client based on asyncio <pymodbus.client.asynchronous>`.

Second part is centered around the calls to get/set information in the connected device.
Using a client to set/get information from a device (server) is simple as seen in this
example (more details in the chapters below)::

# create client object
client = ModbusSerial("/dev/tty")

# connect to device
client.start()

# set/get information
client.read_coils(0x01)
...

# disconnect device
client.stop()

The documentation is in 2 parts:

- connect/disconnect to device(s) with different transport protocols.
- set/get information independent of the chosen transport protocol.

.. toctree::

Expand Down
4 changes: 3 additions & 1 deletion doc/source/library/pymodbus.client.setup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ Pymodbus offers different transport methods Serial/TCP/TLS/UDP, which are implem
as separate classes. Each class defines exactly on transport type.

Applications can add custom transport types as long as the new class inherits
from class BaseModbusClient.
from class BaseOldModbusClient.

Applications can also custom decoders and customer framers.

All transport types are supplied in 2 versions:

a :mod:`synchronous client <pymodbus.client>` and

a :mod:`client based on asyncio <pymodbus.client.asynchronous>`.

Care have been made to ensure that large parts of the actual implementation or shared
Expand Down
73 changes: 2 additions & 71 deletions pymodbus/client/async_udp.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,79 +32,10 @@ async def run():
from pymodbus.factory import ClientDecoder
from pymodbus.transaction import ModbusSocketFramer
from pymodbus.client.helper_async import ModbusClientProtocol
from pymodbus.client.helper_async import BaseModbusAsyncClientProtocol

_logger = logging.getLogger(__name__)


class ModbusUdpClientProtocol( # pylint: disable=too-many-instance-attributes
BaseModbusAsyncClientProtocol,
asyncio.DatagramProtocol
):
r"""Modbus UDP client, asyncio based.
:param host: (positional) Host IP address
:param port: (optional default 502) The serial port used for communication.
:param protocol_class: (optional, default ModbusClientProtocol) Protocol communication class.
:param modbus_decoder: (optional, default ClientDecoder) Message decoder class.
:param framer: (optional, default ModbusSocketFramer) Framer class.
:param timeout: (optional, default 3s) Timeout for a request.
:param retries: (optional, default 3) Max number of retries pr request.
:param retry_on_empty: (optional, default false) Retry on empty response.
:param close_comm_on_error: (optional, default true) Close connection on error.
:param strict: (optional, default true) Strict timing, 1.5 character between requests.
:param source_address: (optional, default none) source address of client,
:param \*\*kwargs: (optional) Extra experimental parameters for transport
:return: client object
"""

#: Factory that created this instance.
factory = None

def __init__( # pylint: disable=too-many-arguments
# Fixed parameters
self,
host,
port=502,
# Common optional paramers:
protocol_class=ModbusClientProtocol,
modbus_decoder=ClientDecoder,
framer=ModbusSocketFramer,
timeout=10,
retries=3,
retry_on_empty=False,
close_comm_on_error=False,
strict=True,

# UDP setup parameters
source_address=None,

# Extra parameters for transport (experimental)
**kwargs,
):
"""Initialize Asyncio Modbus TCP Client."""
self.host = host
self.port = port
self.protocol_class = protocol_class
self.framer = framer(modbus_decoder())
self.timeout = timeout
self.retries = retries
self.retry_on_empty = retry_on_empty
self.close_comm_on_error = close_comm_on_error
self.strict = strict
self.source_address = source_address
self.kwargs = kwargs
super().__init__(**kwargs)

def datagram_received(self, data, addr):
"""Receive datagram."""
self._data_received(data)

def write_transport(self, packet):
"""Write transport."""
return self.transport.sendto(packet)


class AsyncModbusUdpClient:
"""Actual Async UDP Client to be used.
Expand All @@ -121,7 +52,7 @@ def __init__(
self,
host,
port=502,
protocol_class=ModbusUdpClientProtocol,
protocol_class=ModbusClientProtocol,
modbus_decoder=ClientDecoder,
framer=ModbusSocketFramer,
timeout=10,
Expand Down Expand Up @@ -193,7 +124,7 @@ def stop(self):

def _create_protocol(self, host=None, port=0):
"""Create initialized protocol instance with factory function."""
protocol = self.protocol_class(**self.kwargs)
protocol = self.protocol_class(use_udp=True, **self.kwargs)
protocol.host = host
protocol.port = port
protocol.factory = self
Expand Down
66 changes: 22 additions & 44 deletions pymodbus/client/helper_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,24 @@
from pymodbus.utilities import hexlify_packets
from pymodbus.exceptions import ConnectionException
from pymodbus.factory import ClientDecoder
from pymodbus.client.sync_tcp import BaseModbusClient
from pymodbus.client.sync_tcp import BaseOldModbusClient
from pymodbus.transaction import ModbusSocketFramer
from pymodbus.constants import Defaults

_logger = logging.getLogger(__name__)


class BaseAsyncModbusClient(BaseModbusClient):
"""This represents the base ModbusAsyncClient."""

def __init__(self, framer=None, timeout=2, **kwargs):
"""Initialize framer module
:param framer: The framer to use for the protocol. Default:
ModbusSocketFramer
:type framer: pymodbus.transaction.ModbusSocketFramer
"""
self._connected = False
self._timeout = timeout

super().__init__(framer or ModbusSocketFramer(ClientDecoder()), **kwargs)

class ModbusClientProtocol(
BaseOldModbusClient,
asyncio.Protocol,
asyncio.DatagramProtocol
):
"""Asyncio specific implementation of asynchronous modbus client protocol."""

class AsyncModbusClientMixin(BaseAsyncModbusClient):
"""Async Modbus client mixing for UDP and TCP clients."""
#: Factory that created this instance.
factory = None
transport = None
use_udp = False

def __init__(
self,
Expand All @@ -44,6 +37,7 @@ def __init__(
framer=None,
source_address=None,
timeout=None,
use_udp=False,
**kwargs
):
"""Initialize a Modbus TCP/UDP asynchronous client
Expand All @@ -55,20 +49,15 @@ def __init__(
:param timeout: Timeout in seconds
:param kwargs: Extra arguments
"""
super().__init__(framer=framer, **kwargs)
self.use_udp = use_udp
self._connected = False
super().__init__(framer or ModbusSocketFramer(ClientDecoder()), **kwargs)

self.host = host
self.port = port
self.source_address = source_address or ("", 0)
self._timeout = timeout if timeout is not None else Defaults.Timeout


class BaseModbusAsyncClientProtocol(AsyncModbusClientMixin):
"""Asyncio specific implementation of asynchronous modbus client protocol."""

#: Factory that created this instance.
factory = None
transport = None

async def execute(self, request=None): # pylint: disable=invalid-overridden-method
"""Execute requests asynchronously.
Expand All @@ -93,7 +82,7 @@ def connection_made(self, transport):
self._connection_made()

if self.factory:
self.factory.protocol_made_connection(self)
self.factory.protocol_made_connection(self) # pylint: disable=no-member,useless-suppression

def connection_lost(self, reason):
"""Call when the connection is lost or closed.
Expand All @@ -106,7 +95,7 @@ def connection_lost(self, reason):
self._connection_lost(reason)

if self.factory:
self.factory.protocol_lost_connection(self)
self.factory.protocol_lost_connection(self) # pylint: disable=no-member,useless-suppression

def data_received(self, data):
"""Call when some data is received.
Expand Down Expand Up @@ -165,6 +154,8 @@ def connected(self):

def write_transport(self, packet):
"""Write transport."""
if self.use_udp:
return self.transport.sendto(packet)
return self.transport.write(packet)

def _execute(self, request, **kwargs): # pylint: disable=unused-argument
Expand Down Expand Up @@ -218,19 +209,6 @@ def close(self):
self.transport.close()
self._connected = False


class ModbusClientProtocol(BaseModbusAsyncClientProtocol, asyncio.Protocol):
"""Asyncio specific implementation of asynchronous modbus client protocol."""

#: Factory that created this instance.
factory = None
transport = None

def data_received(self, data):
"""Call when some data is received.
data is a non-empty bytes object containing the incoming data.
:param data:
"""
def datagram_received(self, data, addr):
"""Receive datagram."""
self._data_received(data)
2 changes: 1 addition & 1 deletion pymodbus/client/helper_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def mask_write_register(self, *args, **kwargs):
return self.execute(request) # pylint: disable=no-member


class BaseModbusClient(ModbusClientMixin):
class BaseOldModbusClient(ModbusClientMixin):
"""Interface for a modbus synchronous client.
Defined here are all the methods for performing the related
Expand Down
6 changes: 3 additions & 3 deletions pymodbus/client/sync_serial.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def run():

import serial

from pymodbus.client.helper_sync import BaseModbusClient
from pymodbus.client.helper_sync import BaseOldModbusClient
from pymodbus.exceptions import ConnectionException
from pymodbus.factory import ClientDecoder
from pymodbus.transaction import ModbusRtuFramer
Expand All @@ -47,7 +47,7 @@ def run():


class ModbusSerialClient(
BaseModbusClient
BaseOldModbusClient
): # pylint: disable=too-many-instance-attributes
r"""Modbus client for serial (RS-485) communication.
Expand Down Expand Up @@ -117,7 +117,7 @@ def __init__( # pylint: disable=too-many-arguments
self.kwargs = kwargs

self.socket = None
BaseModbusClient.__init__(self, framer(ClientDecoder(), self), **kwargs)
BaseOldModbusClient.__init__(self, framer(ClientDecoder(), self), **kwargs)

self.last_frame_end = None
if isinstance(self.framer, ModbusRtuFramer):
Expand Down
6 changes: 3 additions & 3 deletions pymodbus/client/sync_tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def run():
import socket
import time

from pymodbus.client.helper_sync import BaseModbusClient
from pymodbus.client.helper_sync import BaseOldModbusClient
from pymodbus.exceptions import ConnectionException
from pymodbus.factory import ClientDecoder
from pymodbus.transaction import ModbusSocketFramer
Expand All @@ -39,7 +39,7 @@ def run():
_logger = logging.getLogger(__name__)


class ModbusTcpClient(BaseModbusClient): # pylint: disable=too-many-instance-attributes
class ModbusTcpClient(BaseOldModbusClient): # pylint: disable=too-many-instance-attributes
r"""Modbus client for TCP communication.
:param host: (positional) Host IP address
Expand Down Expand Up @@ -92,7 +92,7 @@ def __init__( # pylint: disable=too-many-arguments
self.kwargs = kwargs

self.socket = None
BaseModbusClient.__init__(self, framer(ClientDecoder(), self), **kwargs)
BaseOldModbusClient.__init__(self, framer(ClientDecoder(), self), **kwargs)

def start(self):
"""Connect to the modbus tcp server.
Expand Down
6 changes: 3 additions & 3 deletions pymodbus/client/sync_udp.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def run():
import logging
import socket

from pymodbus.client.helper_sync import BaseModbusClient
from pymodbus.client.helper_sync import BaseOldModbusClient
from pymodbus.exceptions import ConnectionException
from pymodbus.factory import ClientDecoder
from pymodbus.transaction import ModbusSocketFramer
Expand All @@ -44,7 +44,7 @@ def run():
# --------------------------------------------------------------------------- #


class ModbusUdpClient(BaseModbusClient): # pylint: disable=too-many-instance-attributes
class ModbusUdpClient(BaseOldModbusClient): # pylint: disable=too-many-instance-attributes
r"""Modbus client for UDP communication.
:param host: (positional) Host IP address
Expand Down Expand Up @@ -97,7 +97,7 @@ def __init__( # pylint: disable=too-many-arguments
self.kwargs = kwargs

self.socket = None
BaseModbusClient.__init__(self, framer(ClientDecoder(), self), **kwargs)
BaseOldModbusClient.__init__(self, framer(ClientDecoder(), self), **kwargs)

@classmethod
def _get_address_family(cls, address):
Expand Down
Loading

0 comments on commit 9bca66d

Please sign in to comment.