#!/usr/bin/env python
"""
Pymodbus Asynchronous Processor Example
--------------------------------------------------------------------------
The following is a full example of a continuous client processor. Feel
free to use it as a skeleton guide in implementing your own.
"""
# --------------------------------------------------------------------------- #
# import the neccessary modules
# --------------------------------------------------------------------------- #
from twisted.internet import serialport, reactor
from twisted.internet.protocol import ClientFactory
from pymodbus.factory import ClientDecoder
from pymodbus.client.async import ModbusClientProtocol
# --------------------------------------------------------------------------- #
# Choose the framer you want to use
# --------------------------------------------------------------------------- #
#from pymodbus.transaction import ModbusBinaryFramer as ModbusFramer
#from pymodbus.transaction import ModbusAsciiFramer as ModbusFramer
from pymodbus.transaction import ModbusRtuFramer as ModbusFramer
# from pymodbus.transaction import ModbusSocketFramer as ModbusFramer
# --------------------------------------------------------------------------- #
# configure the client logging
# --------------------------------------------------------------------------- #
import logging
FORMAT = ('%(asctime)-15s %(threadName)-15s'
' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)
# --------------------------------------------------------------------------- #
# state a few constants
# --------------------------------------------------------------------------- #
SERIAL_PORT = "/dev/ptyp0"
STATUS_REGS = (1, 2)
STATUS_COILS = (1, 3)
CLIENT_DELAY = 1
UNIT = 0x01
# --------------------------------------------------------------------------- #
# an example custom protocol
# --------------------------------------------------------------------------- #
# Here you can perform your main procesing loop utilizing defereds and timed
# callbacks.
# --------------------------------------------------------------------------- #
class ExampleProtocol(ModbusClientProtocol):
def __init__(self, framer, endpoint):
""" Initializes our custom protocol
:param framer: The decoder to use to process messages
:param endpoint: The endpoint to send results to
"""
ModbusClientProtocol.__init__(self, framer)
self.endpoint = endpoint
log.debug("Beginning the processing loop")
reactor.callLater(CLIENT_DELAY, self.fetch_holding_registers)
def fetch_holding_registers(self):
""" Defer fetching holding registers
"""
log.debug("Starting the next cycle")
d = self.read_holding_registers(*STATUS_REGS, unit=UNIT)
d.addCallbacks(self.send_holding_registers, self.error_handler)
def send_holding_registers(self, response):
""" Write values of holding registers, defer fetching coils
:param response: The response to process
"""
self.endpoint.write(response.getRegister(0))
self.endpoint.write(response.getRegister(1))
d = self.read_coils(*STATUS_COILS, unit=UNIT)
d.addCallbacks(self.start_next_cycle, self.error_handler)
def start_next_cycle(self, response):
""" Write values of coils, trigger next cycle
:param response: The response to process
"""
self.endpoint.write(response.getBit(0))
self.endpoint.write(response.getBit(1))
self.endpoint.write(response.getBit(2))
reactor.callLater(CLIENT_DELAY, self.fetch_holding_registers)
def error_handler(self, failure):
""" Handle any twisted errors
:param failure: The error to handle
"""
log.error(failure)
# --------------------------------------------------------------------------- #
# a factory for the example protocol
# --------------------------------------------------------------------------- #
# This is used to build client protocol's if you tie into twisted's method
# of processing. It basically produces client instances of the underlying
# protocol::
#
# Factory(Protocol) -> ProtocolInstance
#
# It also persists data between client instances (think protocol singelton).
# --------------------------------------------------------------------------- #
class ExampleFactory(ClientFactory):
protocol = ExampleProtocol
def __init__(self, framer, endpoint):
""" Remember things necessary for building a protocols """
self.framer = framer
self.endpoint = endpoint
def buildProtocol(self, _):
""" Create a protocol and start the reading cycle """
proto = self.protocol(self.framer, self.endpoint)
proto.factory = self
return proto
# --------------------------------------------------------------------------- #
# a custom client for our device
# --------------------------------------------------------------------------- #
# Twisted provides a number of helper methods for creating and starting
# clients:
# - protocol.ClientCreator
# - reactor.connectTCP
#
# How you start your client is really up to you.
# --------------------------------------------------------------------------- #
class SerialModbusClient(serialport.SerialPort):
def __init__(self, factory, *args, **kwargs):
""" Setup the client and start listening on the serial port
:param factory: The factory to build clients with
"""
protocol = factory.buildProtocol(None)
self.decoder = ClientDecoder()
serialport.SerialPort.__init__(self, protocol, *args, **kwargs)
# --------------------------------------------------------------------------- #
# a custom endpoint for our results
# --------------------------------------------------------------------------- #
# An example line reader, this can replace with:
# - the TCP protocol
# - a context recorder
# - a database or file recorder
# --------------------------------------------------------------------------- #
class LoggingLineReader(object):
def write(self, response):
""" Handle the next modbus response
:param response: The response to process
"""
log.info("Read Data: %d" % response)
# --------------------------------------------------------------------------- #
# start running the processor
# --------------------------------------------------------------------------- #
# This initializes the client, the framer, the factory, and starts the
# twisted event loop (the reactor). It should be noted that a number of
# things could be chanegd as one sees fit:
# - The ModbusRtuFramer could be replaced with a ModbusAsciiFramer
# - The SerialModbusClient could be replaced with reactor.connectTCP
# - The LineReader endpoint could be replaced with a database store
# --------------------------------------------------------------------------- #
def main():
log.debug("Initializing the client")
framer = ModbusFramer(ClientDecoder(), client=None)
reader = LoggingLineReader()
factory = ExampleFactory(framer, reader)
SerialModbusClient(factory, SERIAL_PORT, reactor)
# factory = reactor.connectTCP("localhost", 502, factory)
log.debug("Starting the client")
reactor.run()
if __name__ == "__main__":
main()