Skip to content

Commit

Permalink
Reworking the transaction managers to be explicit
Browse files Browse the repository at this point in the history
- Serial framers use the FIFO manager (results in order)
- Socket framers use the Dict manager (tid -> result)
- Fixed tests and removed bad global managers
- Managers no longer use global state (now instance)
  • Loading branch information
bashwork committed Apr 2, 2013
1 parent c48b204 commit 996dff2
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 65 deletions.
6 changes: 6 additions & 0 deletions doc/sphinx/library/transaction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ API Documentation

.. automodule:: pymodbus.transaction

.. autoclass:: DictTransactionManager
:members:

.. autoclass:: FifoTransactionManager
:members:

.. autoclass:: ModbusSocketFramer
:members:

Expand Down
24 changes: 11 additions & 13 deletions pymodbus/client/async.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ def process():
from twisted.internet import defer, protocol
from pymodbus.factory import ClientDecoder
from pymodbus.exceptions import ConnectionException
from pymodbus.transaction import ModbusSocketFramer, ModbusTransactionManager
from pymodbus.transaction import ModbusSocketFramer
from pymodbus.transaction import FifoTransactionManager
from pymodbus.transaction import DictTransactionManager
from pymodbus.client.common import ModbusClientMixin
from twisted.python.failure import Failure

Expand All @@ -46,12 +48,6 @@ def process():
_logger = logging.getLogger(__name__)


#---------------------------------------------------------------------------#
# A manager for the transaction identifiers
#---------------------------------------------------------------------------#
_manager = ModbusTransactionManager()


#---------------------------------------------------------------------------#
# Connected Client Protocols
#---------------------------------------------------------------------------#
Expand All @@ -66,10 +62,11 @@ def __init__(self, framer=None):
:param framer: The framer to use for the protocol
'''
self.framer = framer or ModbusSocketFramer(ClientDecoder())
serial = not isinstance(framer, ModbusSocketFramer)
self.transaction = ModbusTransactionManager(self, serial)
self._connected = False
self.framer = framer or ModbusSocketFramer(ClientDecoder())
if isinstance(framer, ModbusSocketFramer):
self.transaction = DictTransactionManager(self)
else: self.transaction = FifoTransactionManager(self)

def connectionMade(self):
''' Called upon a successful client connection.
Expand Down Expand Up @@ -99,7 +96,7 @@ def execute(self, request):
''' Starts the producer to send the next request to
consumer.write(Frame(request))
'''
request.transaction_id = _manager.getNextTID()
request.transaction_id = self.transaction.getNextTID()
packet = self.framer.buildPacket(request)
self.transport.write(packet)
return self._buildResponse(request.transaction_id)
Expand Down Expand Up @@ -155,8 +152,9 @@ def __init__(self, framer=None):
:param framer: The framer to use for the protocol
'''
self.framer = framer or ModbusSocketFramer(ClientDecoder())
serial = not isinstance(framer, ModbusSocketFramer)
self.transaction = ModbusTransactionManager(self, serial)
if isinstance(framer, ModbusSocketFramer):
self.transaction = DictTransactionManager(self)
else: self.transaction = FifoTransactionManager(self)

def datagramReceived(self, data, (host, port)):
''' Get response, check for valid message, decode result
Expand Down
9 changes: 5 additions & 4 deletions pymodbus/client/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from pymodbus.factory import ClientDecoder
from pymodbus.exceptions import NotImplementedException, ParameterException
from pymodbus.exceptions import ConnectionException
from pymodbus.transaction import ModbusTransactionManager
from pymodbus.transaction import FifoTransactionManager
from pymodbus.transaction import DictTransactionManager
from pymodbus.transaction import ModbusSocketFramer, ModbusBinaryFramer
from pymodbus.transaction import ModbusAsciiFramer, ModbusRtuFramer
from pymodbus.client.common import ModbusClientMixin
Expand Down Expand Up @@ -33,9 +34,10 @@ def __init__(self, framer):
:param framer: The modbus framer implementation to use
'''
serial = not isinstance(framer, ModbusSocketFramer)
self.framer = framer
self.transaction = ModbusTransactionManager(self, serial)
if isinstance(framer, ModbusSocketFramer):
self.transaction = DictTransactionManager(self)
else: self.transaction = FifoTransactionManager(self)

#-----------------------------------------------------------------------#
# Client interface
Expand Down Expand Up @@ -133,7 +135,6 @@ def connect(self):
if self.socket: return True
try:
self.socket = socket.create_connection((self.host, self.port), Defaults.Timeout)
self.transaction = ModbusTransactionManager(self)
except socket.error, msg:
_logger.error('Connection to (%s, %s) failed: %s' % \
(self.host, self.port, msg))
Expand Down
150 changes: 117 additions & 33 deletions pymodbus/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,13 @@ class ModbusTransactionManager(object):
This module helps to abstract this away from the framer and protocol.
'''

__tid = Defaults.TransactionId
__transactions = {}

def __init__(self, client=None, fifo=False):
def __init__(self, client):
''' Initializes an instance of the ModbusTransactionManager
:param client: The client socket wrapper
:param fifo: Should this just return results in FIFO order
'''
self.tid = Defaults.TransactionId
self.client = client
self.fifo = fifo

def __iter__(self):
''' Iterater over the current managed transactions
:returns: An iterator of the managed transactions
'''
return iter(self.__transactions.keys())

def execute(self, request):
''' Starts the producer to send the next request to
Expand Down Expand Up @@ -88,12 +77,9 @@ def addTransaction(self, request, tid=None):
After being sent, the request is removed.
:param request: The request to hold on to
:param tid: The transaction id to attach this request with
:param tid: The overloaded transaction id to use
'''
if tid == None:
tid = request.transaction_id
_logger.debug("Adding transaction %d" % tid)
ModbusTransactionManager.__transactions[tid] = request
raise NotImplementedException("addTransaction")

def getTransaction(self, tid):
''' Returns a transaction matching the referenced tid
Expand All @@ -102,21 +88,14 @@ def getTransaction(self, tid):
:param tid: The transaction to retrieve
'''
if self.fifo:
if len(ModbusTransactionManager.__transactions):
return ModbusTransactionManager.__transactions.popitem()[1]
else: return None
return ModbusTransactionManager.__transactions.pop(tid, None)
raise NotImplementedException("getTransaction")

def delTransaction(self, tid):
''' Removes a transaction matching the referenced tid
:param tid: The transaction to remove
'''
if self.fifo:
if len(ModbusTransactionManager.__transactions):
ModbusTransactionManager.__transactions.popitem()
ModbusTransactionManager.__transactions.pop(tid, None)
raise NotImplementedException("delTransaction")

def getNextTID(self):
''' Retrieve the next unique transaction identifier
Expand All @@ -126,13 +105,117 @@ def getNextTID(self):
:returns: The next unique transaction identifier
'''
tid = (ModbusTransactionManager.__tid + 1) & 0xffff
ModbusTransactionManager.__tid = tid
return tid
self.tid = (self.tid + 1) & 0xffff
return self.tid

def resetTID(self):
def reset(self):
''' Resets the transaction identifier '''
ModbusTransactionManager.__tid = Defaults.TransactionId
self.tid = Defaults.TransactionId
self.transactions = type(self.transactions)()


class DictTransactionManager(ModbusTransactionManager):
''' Impelements a transaction for a manager where the
results are keyed based on the supplied transaction id.
'''

def __init__(self, client):
''' Initializes an instance of the ModbusTransactionManager
:param client: The client socket wrapper
'''
self.transactions = {}
super(DictTransactionManager, self).__init__(client)

def __iter__(self):
''' Iterater over the current managed transactions
:returns: An iterator of the managed transactions
'''
return iter(self.transactions.keys())

def addTransaction(self, request, tid=None):
''' Adds a transaction to the handler
This holds the requets in case it needs to be resent.
After being sent, the request is removed.
:param request: The request to hold on to
:param tid: The overloaded transaction id to use
'''
tid = tid if tid != None else request.transaction_id
_logger.debug("adding transaction %d" % tid)
self.transactions[tid] = request

def getTransaction(self, tid):
''' Returns a transaction matching the referenced tid
If the transaction does not exist, None is returned
:param tid: The transaction to retrieve
'''
_logger.debug("getting transaction %d" % tid)
return self.transactions.pop(tid, None)

def delTransaction(self, tid):
''' Removes a transaction matching the referenced tid
:param tid: The transaction to remove
'''
_logger.debug("deleting transaction %d" % tid)
self.transactions.pop(tid, None)


class FifoTransactionManager(ModbusTransactionManager):
''' Impelements a transaction for a manager where the
results are returned in a FIFO manner.
'''

def __init__(self, client):
''' Initializes an instance of the ModbusTransactionManager
:param client: The client socket wrapper
'''
super(FifoTransactionManager, self).__init__(client)
self.transactions = []

def __iter__(self):
''' Iterater over the current managed transactions
:returns: An iterator of the managed transactions
'''
return iter(self.transactions)

def addTransaction(self, request, tid=None):
''' Adds a transaction to the handler
This holds the requets in case it needs to be resent.
After being sent, the request is removed.
:param request: The request to hold on to
:param tid: The overloaded transaction id to use
'''
tid = tid if tid != None else request.transaction_id
_logger.debug("adding transaction %d" % tid)
self.transactions.append(request)

def getTransaction(self, tid):
''' Returns a transaction matching the referenced tid
If the transaction does not exist, None is returned
:param tid: The transaction to retrieve
'''
_logger.debug("getting transaction %s" % str(tid))
return self.transactions.pop(0) if self.transactions else None

def delTransaction(self, tid):
''' Removes a transaction matching the referenced tid
:param tid: The transaction to remove
'''
_logger.debug("deleting transaction %d" % tid)
if self.transactions: self.transactions.pop(0)


#---------------------------------------------------------------------------#
Expand Down Expand Up @@ -782,7 +865,8 @@ def _filter(a):
# Exported symbols
#---------------------------------------------------------------------------#
__all__ = [
"ModbusTransactionManager",
"FifoTransactionManager",
"DictTransactionManager",
"ModbusSocketFramer", "ModbusRtuFramer",
"ModbusAsciiFramer", "ModbusBinaryFramer",
]
Loading

0 comments on commit 996dff2

Please sign in to comment.