From cb3d4a428bb055c4aa0fccae444e5396cd8619c3 Mon Sep 17 00:00:00 2001 From: dhoomakethu Date: Fri, 7 Sep 2018 09:30:01 +0530 Subject: [PATCH] #254 Ensure backward compatibility with async client for the time being --- pymodbus/client/async/__init__.py | 73 +----- pymodbus/client/async/asyncio/__init__.py | 2 +- pymodbus/client/async/deprecated/__init__.py | 47 ++++ pymodbus/client/async/deprecated/async.py | 231 +++++++++++++++++++ pymodbus/client/async/mixins.py | 69 ++++++ pymodbus/client/async/tornado/__init__.py | 4 +- pymodbus/client/async/twisted/__init__.py | 2 +- setup.cfg | 1 + 8 files changed, 353 insertions(+), 76 deletions(-) create mode 100644 pymodbus/client/async/deprecated/__init__.py create mode 100644 pymodbus/client/async/deprecated/async.py create mode 100644 pymodbus/client/async/mixins.py diff --git a/pymodbus/client/async/__init__.py b/pymodbus/client/async/__init__.py index 9d050f90f..d14d0dd32 100644 --- a/pymodbus/client/async/__init__.py +++ b/pymodbus/client/async/__init__.py @@ -31,75 +31,4 @@ # For asyncio the actual client is returned and event loop is asyncio loop """ -from __future__ import unicode_literals -from __future__ import absolute_import - -import logging - -from pymodbus.client.sync import BaseModbusClient - -from pymodbus.constants import Defaults - -from pymodbus.factory import ClientDecoder -from pymodbus.transaction import ModbusSocketFramer - - -LOGGER = logging.getLogger(__name__) - - -class BaseAsyncModbusClient(BaseModbusClient): - """ - This represents the base ModbusAsyncClient. - """ - - def __init__(self, framer=None, **kwargs): - """ Initializes the framer module - - :param framer: The framer to use for the protocol. Default: - ModbusSocketFramer - :type framer: pymodbus.transaction.ModbusSocketFramer - """ - self._connected = False - - super(BaseAsyncModbusClient, self).__init__( - framer or ModbusSocketFramer(ClientDecoder()), **kwargs - ) - - -class AsyncModbusClientMixin(BaseAsyncModbusClient): - """ - Async Modbus client mixing for UDP and TCP clients - """ - def __init__(self, host="127.0.0.1", port=Defaults.Port, framer=None, - source_address=None, timeout=None, **kwargs): - """ - Initializes a Modbus TCP/UDP async client - :param host: Host IP address - :param port: Port - :param framer: Framer to use - :param source_address: Specific to underlying client being used - :param timeout: Timeout in seconds - :param kwargs: Extra arguments - """ - super(AsyncModbusClientMixin, self).__init__(framer=framer, **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 AsyncModbusSerialClientMixin(BaseAsyncModbusClient): - """ - Async Modbus Serial Client Mixing - """ - def __init__(self, framer=None, port=None, **kwargs): - """ - Initializes a Async Modbus Serial Client - :param framer: Modbus Framer - :param port: Serial port to use - :param kwargs: Extra arguments if any - """ - super(AsyncModbusSerialClientMixin, self).__init__(framer=framer) - self.port = port - self.serial_settings = kwargs - +from pymodbus.client.async.deprecated.async import * diff --git a/pymodbus/client/async/asyncio/__init__.py b/pymodbus/client/async/asyncio/__init__.py index cfdc5fd88..c7bde65c1 100644 --- a/pymodbus/client/async/asyncio/__init__.py +++ b/pymodbus/client/async/asyncio/__init__.py @@ -5,7 +5,7 @@ import asyncio import functools from pymodbus.exceptions import ConnectionException -from pymodbus.client.async import AsyncModbusClientMixin +from pymodbus.client.async.mixins import AsyncModbusClientMixin from pymodbus.compat import byte2int import logging diff --git a/pymodbus/client/async/deprecated/__init__.py b/pymodbus/client/async/deprecated/__init__.py new file mode 100644 index 000000000..196bcff0f --- /dev/null +++ b/pymodbus/client/async/deprecated/__init__.py @@ -0,0 +1,47 @@ +import warnings +warnings.simplefilter('always', DeprecationWarning) + +WARNING = """ +Usage of '{}' is deprecated from 2.0.0 and will be removed in future releases. +Use the new Async Modbus Client implementation based on Twisted, tornado +and asyncio +------------------------------------------------------------------------ + +Example run:: + + from pymodbus.client.async import schedulers + + # Import The clients + + from pymodbus.client.async.tcp import AsyncModbusTCPClient as Client + from pymodbus.client.async.serial import AsyncModbusSerialClient as Client + from pymodbus.client.async.udp import AsyncModbusUDPClient as Client + + # For tornado based async client use + event_loop, future = Client(schedulers.IO_LOOP, port=5020) + + # For twisted based async client use + event_loop, future = Client(schedulers.REACTOR, port=5020) + + # For asyncio based async client use + event_loop, client = Client(schedulers.ASYNC_IO, port=5020) + + # Here event_loop is a thread which would control the backend and future is + # a Future/deffered object which would be used to + # add call backs to run asynchronously. + + # The Actual client could be accessed with future.result() with Tornado + # and future.result when using twisted + + # For asyncio the actual client is returned and event loop is asyncio loop + +Refer: +https://pymodbus.readthedocs.io/en/dev/source/example/async_twisted_client.html +https://pymodbus.readthedocs.io/en/dev/source/example/async_tornado_client.html +https://pymodbus.readthedocs.io/en/dev/source/example/async_asyncio_client.html + +""" + + +def deprecated(name): # pragma: no cover + warnings.warn(WARNING.format(name), DeprecationWarning) \ No newline at end of file diff --git a/pymodbus/client/async/deprecated/async.py b/pymodbus/client/async/deprecated/async.py new file mode 100644 index 000000000..5d163a107 --- /dev/null +++ b/pymodbus/client/async/deprecated/async.py @@ -0,0 +1,231 @@ +""" +Implementation of a Modbus Client Using Twisted +-------------------------------------------------- + +Example run:: + + from twisted.internet import reactor, protocol + from pymodbus.client.async import ModbusClientProtocol + + def printResult(result): + print "Result: %d" % result.bits[0] + + def process(client): + result = client.write_coil(1, True) + result.addCallback(printResult) + reactor.callLater(1, reactor.stop) + + defer = protocol.ClientCreator(reactor, ModbusClientProtocol + ).connectTCP("localhost", 502) + defer.addCallback(process) + +Another example:: + + from twisted.internet import reactor + from pymodbus.client.async import ModbusClientFactory + + def process(): + factory = reactor.connectTCP("localhost", 502, ModbusClientFactory()) + reactor.stop() + + if __name__ == "__main__": + reactor.callLater(1, process) + reactor.run() +""" +import logging +from pymodbus.factory import ClientDecoder +from pymodbus.exceptions import ConnectionException +from pymodbus.transaction import ModbusSocketFramer +from pymodbus.transaction import FifoTransactionManager +from pymodbus.transaction import DictTransactionManager +from pymodbus.client.common import ModbusClientMixin +from pymodbus.client.async.deprecated import deprecated +from twisted.internet import defer, protocol +from twisted.python.failure import Failure + + +# --------------------------------------------------------------------------- # +# Logging +# --------------------------------------------------------------------------- # +_logger = logging.getLogger(__name__) + + +# --------------------------------------------------------------------------- # +# Connected Client Protocols +# --------------------------------------------------------------------------- # +class ModbusClientProtocol(protocol.Protocol, ModbusClientMixin): # pragma: no cover + """ + This represents the base modbus client protocol. All the application + layer code is deferred to a higher level wrapper. + """ + + def __init__(self, framer=None, **kwargs): + """ Initializes the framer module + + :param framer: The framer to use for the protocol + """ + deprecated(self.__class__.__name__) + self._connected = False + self.framer = framer or ModbusSocketFramer(ClientDecoder()) + if isinstance(self.framer, type): + # Framer class not instance + self.framer = self.framer(ClientDecoder(), client=None) + if isinstance(self.framer, ModbusSocketFramer): + self.transaction = DictTransactionManager(self, **kwargs) + else: + self.transaction = FifoTransactionManager(self, **kwargs) + + def connectionMade(self): + """ Called upon a successful client connection. + """ + _logger.debug("Client connected to modbus server") + self._connected = True + + def connectionLost(self, reason): + """ Called upon a client disconnect + + :param reason: The reason for the disconnect + """ + _logger.debug("Client disconnected from modbus server: %s" % reason) + self._connected = False + for tid in list(self.transaction): + self.transaction.getTransaction(tid).errback(Failure( + ConnectionException('Connection lost during request'))) + + def dataReceived(self, data): + """ Get response, check for valid message, decode result + + :param data: The data returned from the server + """ + unit = self.framer.decode_data(data).get("uid", 0) + self.framer.processIncomingPacket(data, self._handleResponse, unit=unit) + + def execute(self, request): + """ Starts the producer to send the next request to + consumer.write(Frame(request)) + """ + request.transaction_id = self.transaction.getNextTID() + packet = self.framer.buildPacket(request) + self.transport.write(packet) + return self._buildResponse(request.transaction_id) + + def _handleResponse(self, reply): + """ Handle the processed response and link to correct deferred + + :param reply: The reply to process + """ + if reply is not None: + tid = reply.transaction_id + handler = self.transaction.getTransaction(tid) + if handler: + handler.callback(reply) + else: + _logger.debug("Unrequested message: " + str(reply)) + + def _buildResponse(self, tid): + """ Helper method to return a deferred response + for the current request. + + :param tid: The transaction identifier for this response + :returns: A defer linked to the latest request + """ + if not self._connected: + return defer.fail(Failure( + ConnectionException('Client is not connected'))) + + d = defer.Deferred() + self.transaction.addTransaction(d, tid) + return d + + # ---------------------------------------------------------------------- # + # Extra Functions + # ---------------------------------------------------------------------- # + # if send_failed: + # if self.retry > 0: + # deferLater(clock, self.delay, send, message) + # self.retry -= 1 + + +# --------------------------------------------------------------------------- # +# Not Connected Client Protocol +# --------------------------------------------------------------------------- # +class ModbusUdpClientProtocol(protocol.DatagramProtocol, ModbusClientMixin): # pragma: no cover + """ + This represents the base modbus client protocol. All the application + layer code is deferred to a higher level wrapper. + """ + + def __init__(self, framer=None, **kwargs): + """ Initializes the framer module + + :param framer: The framer to use for the protocol + """ + deprecated(self.__class__.__name__) + self.framer = framer or ModbusSocketFramer(ClientDecoder()) + if isinstance(self.framer, ModbusSocketFramer): + self.transaction = DictTransactionManager(self, **kwargs) + else: self.transaction = FifoTransactionManager(self, **kwargs) + + def datagramReceived(self, data, params): + """ Get response, check for valid message, decode result + + :param data: The data returned from the server + :param params: The host parameters sending the datagram + """ + _logger.debug("Datagram from: %s:%d" % params) + unit = self.framer.decode_data(data).get("uid", 0) + self.framer.processIncomingPacket(data, self._handleResponse, unit=unit) + + def execute(self, request): + """ Starts the producer to send the next request to + consumer.write(Frame(request)) + """ + request.transaction_id = self.transaction.getNextTID() + packet = self.framer.buildPacket(request) + self.transport.write(packet) + return self._buildResponse(request.transaction_id) + + def _handleResponse(self, reply): + """ Handle the processed response and link to correct deferred + + :param reply: The reply to process + """ + if reply is not None: + tid = reply.transaction_id + handler = self.transaction.getTransaction(tid) + if handler: + handler.callback(reply) + else: _logger.debug("Unrequested message: " + str(reply)) + + def _buildResponse(self, tid): + """ Helper method to return a deferred response + for the current request. + + :param tid: The transaction identifier for this response + :returns: A defer linked to the latest request + """ + d = defer.Deferred() + self.transaction.addTransaction(d, tid) + return d + + +# --------------------------------------------------------------------------- # +# Client Factories +# --------------------------------------------------------------------------- # +class ModbusClientFactory(protocol.ReconnectingClientFactory): # pragma: no cover + """ Simple client protocol factory """ + + protocol = ModbusClientProtocol + + def __init__(self): + deprecated(self.__class__.__name__) + protocol.ReconnectingClientFactory.__init__(self) + +# --------------------------------------------------------------------------- # +# Exported symbols +# --------------------------------------------------------------------------- # + + +__all__ = [ + "ModbusClientProtocol", "ModbusUdpClientProtocol", "ModbusClientFactory" +] diff --git a/pymodbus/client/async/mixins.py b/pymodbus/client/async/mixins.py new file mode 100644 index 000000000..008cf49bf --- /dev/null +++ b/pymodbus/client/async/mixins.py @@ -0,0 +1,69 @@ +import logging + +from pymodbus.client.sync import BaseModbusClient + +from pymodbus.constants import Defaults + +from pymodbus.factory import ClientDecoder +from pymodbus.transaction import ModbusSocketFramer + + +LOGGER = logging.getLogger(__name__) + + +class BaseAsyncModbusClient(BaseModbusClient): + """ + This represents the base ModbusAsyncClient. + """ + + def __init__(self, framer=None, **kwargs): + """ Initializes the framer module + + :param framer: The framer to use for the protocol. Default: + ModbusSocketFramer + :type framer: pymodbus.transaction.ModbusSocketFramer + """ + self._connected = False + + super(BaseAsyncModbusClient, self).__init__( + framer or ModbusSocketFramer(ClientDecoder()), **kwargs + ) + + +class AsyncModbusClientMixin(BaseAsyncModbusClient): + """ + Async Modbus client mixing for UDP and TCP clients + """ + def __init__(self, host="127.0.0.1", port=Defaults.Port, framer=None, + source_address=None, timeout=None, **kwargs): + """ + Initializes a Modbus TCP/UDP async client + :param host: Host IP address + :param port: Port + :param framer: Framer to use + :param source_address: Specific to underlying client being used + :param timeout: Timeout in seconds + :param kwargs: Extra arguments + """ + super(AsyncModbusClientMixin, self).__init__(framer=framer, **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 AsyncModbusSerialClientMixin(BaseAsyncModbusClient): + """ + Async Modbus Serial Client Mixing + """ + def __init__(self, framer=None, port=None, **kwargs): + """ + Initializes a Async Modbus Serial Client + :param framer: Modbus Framer + :param port: Serial port to use + :param kwargs: Extra arguments if any + """ + super(AsyncModbusSerialClientMixin, self).__init__(framer=framer) + self.port = port + self.serial_settings = kwargs + diff --git a/pymodbus/client/async/tornado/__init__.py b/pymodbus/client/async/tornado/__init__.py index 3a959999f..5a9a67cf5 100644 --- a/pymodbus/client/async/tornado/__init__.py +++ b/pymodbus/client/async/tornado/__init__.py @@ -15,8 +15,8 @@ from tornado.iostream import IOStream from tornado.iostream import BaseIOStream -from pymodbus.client.async import (AsyncModbusClientMixin, - AsyncModbusSerialClientMixin) +from pymodbus.client.async.mixins import (AsyncModbusClientMixin, + AsyncModbusSerialClientMixin) from pymodbus.exceptions import ConnectionException from pymodbus.compat import byte2int diff --git a/pymodbus/client/async/twisted/__init__.py b/pymodbus/client/async/twisted/__init__.py index 3db72e615..268afefb6 100644 --- a/pymodbus/client/async/twisted/__init__.py +++ b/pymodbus/client/async/twisted/__init__.py @@ -37,7 +37,7 @@ def process(): from pymodbus.exceptions import ConnectionException from pymodbus.factory import ClientDecoder -from pymodbus.client.async import AsyncModbusClientMixin +from pymodbus.client.async.mixins import AsyncModbusClientMixin from pymodbus.transaction import FifoTransactionManager, DictTransactionManager from pymodbus.transaction import ModbusSocketFramer, ModbusRtuFramer from pymodbus.compat import byte2int diff --git a/setup.cfg b/setup.cfg index 179a6e4d7..0de377f65 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,4 +21,5 @@ universal=1 [tool:pytest] testpaths = test +addopts = -p no:warnings