Skip to content

Commit

Permalink
#254 Ensure backward compatibility with async client for the time being
Browse files Browse the repository at this point in the history
  • Loading branch information
dhoomakethu committed Sep 7, 2018
1 parent c303e33 commit cb3d4a4
Show file tree
Hide file tree
Showing 8 changed files with 353 additions and 76 deletions.
73 changes: 1 addition & 72 deletions pymodbus/client/async/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
2 changes: 1 addition & 1 deletion pymodbus/client/async/asyncio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
47 changes: 47 additions & 0 deletions pymodbus/client/async/deprecated/__init__.py
Original file line number Diff line number Diff line change
@@ -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)
231 changes: 231 additions & 0 deletions pymodbus/client/async/deprecated/async.py
Original file line number Diff line number Diff line change
@@ -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"
]
Loading

0 comments on commit cb3d4a4

Please sign in to comment.