def main(): args = docopt(__doc__) server = TcpServer(port=int(args['--port'])) slave = server.add_slave(1) slave.add_block(0, cst.ANALOG_INPUTS, 0, 100) log.info('Add analog inputs data block from register 0 to 99 on slave 1') slave.add_block(1, cst.DISCRETE_INPUTS, 0, 100) log.info('Add discrete inputs data block from register 0 to 99 on slave 1') slave.add_block(2, cst.COILS, 100, 100) log.info('Add coils data block from register 100 to 199 on slave 1') slave.add_block(3, cst.HOLDING_REGISTERS, 100, 100) log.info('Add holding registers block from register 100 to 199 on slave 1') server.start() log.info('TcpServer started listening at port {0}.'.format(args['--port'])) try: while True: time.sleep(1) except KeyboardInterrupt: log.info('Received SIGINT. Exiting') finally: server.stop() log.info('TcpServer has stopped.')
def modbus_server(): """ Yield an instance of modbus_tk.TcpServer. """ modbus_server = TcpServer(port=0) slave = modbus_server.add_slave(1) slave.add_block(0, ANALOG_INPUTS, 0, 100) slave.set_values(0, 0, [1337, 2890]) slave.add_block(1, DISCRETE_INPUTS, 0, 100) slave.set_values(1, 0, [1, 0]) slave.add_block(2, COILS, 100, 100) slave.add_block(3, HOLDING_REGISTERS, 100, 100) modbus_server.start() yield modbus_server modbus_server.stop()
class Server (object): """Base Modbus Server. Basic Modbus Server that implements both RTU and TCP. For setting up an RTU server, make sure 'port' is configured to 0. Basic hooks are set up here, so that when subclassing this class, only minimal functions (namely update) need to be overwritten. A Server can have multiple slaves, although TCP protocol only allows for one (RTU allows for multiple slaves). In this instance, the slave id should be set to 1. This class is mostly used for testing to simulate a modbus server that we would see in the field. """ def __init__(self, address='127.0.0.1', port=502, baud=19200, timeout_in_sec=1, databank=None): """Initialize Base Server and install modbus hooks Has two important instance variables: _server - an instance of a Modbus RTU or TCP server. _slaves - dictionary of ModbusClient slaves keyed by slave ID. (For TCP, this should be 1) :param address: Address of the Server (Not used in RTU). Defaults to 127.0.0.1 :param port: Port of the Server (0 for RTU, defaults to 502 for TCP). For RTU, this MUST be set to 0. :param baud: Baudrate (Not used in TCP). Defaults to 19200 :param timeout_in_sec: Timeout (Not used in RTU). Defaults to 1 :param databank: (Not used in RTU) """ if port == 0: server_serial = serial.Serial( address, baudrate=baud, bytesize=8, parity=serial.PARITY_EVEN, stopbits=1, xonxoff=0) self._server = RtuServer(server_serial) else: self._server = TcpServer(address=address, port=port, timeout_in_sec=timeout_in_sec, databank=databank) # Begin Monkey Patch self._server._do_init = types.MethodType(_do_init, self._server) self._server._do_exit = types.MethodType(_do_exit, self._server) # End Monkey Patch self._slaves = dict() # Set up basic modbus hooks. These by default just log when they are getting called. To handle requests, # subclasses of Server should overwrite the 'update' method, which is called by 'after'. self.install_hook('modbus.Server.before_handle_request', 'before') self.install_hook('modbus.Server.after_handle_request', 'after') self.install_hook('modbus.Slave.handle_write_multiple_registers_request', 'write_register') self.install_hook('modbus.Slave.handle_write_single_register_request', 'write_register') self.install_hook('modbus.Slave.handle_read_holding_registers_request', 'read_register') self.install_hook('modbus.Slave.handle_write_multiple_coils_request', 'write_coils') self.install_hook('modbus.Slave.handle_write_single_coil_request', 'write_coils') def define_slave(self, slave_id, client_class, unsigned=True): """Add a Modbus Client Slave. :param slave_id: Slave ID. This must be 1 for Modbus TCP. :param client_class: Subclass of modbus.Client. This class holds all the register definitions. :param unsigned: Boolean indicating whether or not values are signed or unsigned. :return: Slave instance. """ self._slaves[slave_id] = client_class slave = self._server.add_slave(slave_id, unsigned=unsigned) slave.klass = client_class if not issubclass(client_class, Client): raise TypeError("client_class must be subclass of {0}".format(Client)) for request in client_class().requests(): # logger.info("Adding [%s], %s, %s, %s", request.name, request.table, request.address, request.count) slave.add_block(request.name, request.table, request.address, request.count) return slave @classmethod def install_hook(cls, hook_name, method): """Helper method for installing hooks. This is necessary to ensure child methods are called if implemented.""" install_hook(hook_name, getattr(cls, method)) def set_values(self, slave_id, field, values): slave = self._server.get_slave(slave_id) slave_class = self._slaves[slave_id] request = slave_class().get_request(field) slave.set_values(request.name, field.address, values) ################## # Server Methods # ################## def start(self): self._server.start() def stop(self): self._server.stop() def is_alive(self): return self._server._thread.isAlive() def set_verbose(self, on=True): self._server.set_verbose(on) ###################### # Basic Modbus Hooks # ###################### @staticmethod def before(args): server, request = args # logger.debug("Before: {0}-{1}".format(server, request)) @classmethod def after(cls, args): server, response = args # logger.debug("After: {0}-{1}".format(server, response)) cls.update(args) @staticmethod def update(args): pass @staticmethod def write_register(args): slave, request_pdu = args # logger.debug("Writing: {0}-{1}".format(slave, request_pdu)) @staticmethod def read_register(args): slave, request_pdu = args # logger.debug("Reading: {0}-{1}".format(slave, request_pdu)) @staticmethod def write_coils(args): slave, request_pdu = args