From 290aead5877cc0b428a5b2fa0bf09a981d46981a Mon Sep 17 00:00:00 2001 From: Auke Willem Oosterhoff Date: Sat, 7 Nov 2015 16:41:12 +0100 Subject: [PATCH] #4 Implement WriteMultipleRegisters. --- modbus/functions.py | 80 ++++++++++++++++++++++++++++++++++++++++-- scripts/data_server.py | 4 +++ scripts/log_server.py | 10 +++--- 3 files changed, 88 insertions(+), 6 deletions(-) diff --git a/modbus/functions.py b/modbus/functions.py index 71310c7..8dffc0c 100644 --- a/modbus/functions.py +++ b/modbus/functions.py @@ -20,7 +20,7 @@ WRITE_SINGLE_COIL = 5 WRITE_SINGLE_REGISTER = 6 WRITE_MULTIPLE_COILS = 15 -WRITE_SINGLE_REGISTERS = 16 +WRITE_MULTIPLE_REGISTERS = 16 READ_FILE_RECORD = 20 WRITE_FILE_RECORD = 21 @@ -649,7 +649,7 @@ def __init__(self, starting_address, quantity, byte_count, values): if not(expected_byte_count == byte_count): raise IllegalDataValueError('Byte count is not correct. It is {0},' - 'but is {1}.' + 'but should be {1}.' .format(byte_count, expected_byte_count)) @@ -673,6 +673,81 @@ def create_from_request_pdu(cls, pdu): return cls(starting_address, quantity, byte_count, values) + +class WriteMultipleRegisters(WriteMultipleValueFunction): + """ Implement Modbus function 16 (0x10) Write Multiple Registers. + + "This function code is used to write a block of contiguous registers (1 to + 123 registers) in a remote device. + + The requested written values are specified in the request data field. Data + is packed as two bytes per register. + + The normal response returns the function code, starting address, and + quantity of registers written." + + - MODBUS Application Protocol Specification V1.1b3, chapter 6.12 + + The request PDU with function code 16 must be at least 8 bytes: + + +------------------+----------------+ + | Field | Length (bytes) | + +------------------+----------------+ + | Function code | 1 | + | Starting Address | 2 | + | Quantity | 2 | + | Byte count | 1 | + | Value | Quantity * 2 | + +------------------+----------------+ + + The PDU can unpacked to this:: + + >>> struct.unpack('>BHHBB', b'\x0f\x00d\x00\x01\x02\x00\x05') + (16, 100, 1, 2, 5) + + The reponse PDU is 5 bytes and contains following structure: + + +------------------+----------------+ + | Field | Length (bytes) | + +------------------+----------------+ + | Function code | 1 | + | Starting address | 2 | + | Quantity | 2 | + +------------------+----------------+ + + """ + function_code = WRITE_MULTIPLE_REGISTERS + + def __init__(self, starting_address, quantity, byte_count, values): + if not(1 <= quantity <= 0x7B): + raise IllegalDataValueError('Quantify field of request must be a ' + 'value between 0 and ' + '{0}.'.format(0x7B0)) + + # Values are 16 bit, so each value takes up 2 bytes. + if byte_count != (len(values) * 2): + raise IllegalDataValueError('Byte count is not correct. It is {0},' + 'but should be {1}.' + .format(byte_count, len(values))) + + WriteMultipleValueFunction.__init__(self, starting_address, values) + + @classmethod + def create_from_request_pdu(cls, pdu): + """ Create instance from request PDU. + + :param pdu: A request PDU. + :return: Instance of this class. + """ + _, starting_address, quantity, byte_count = \ + struct.unpack('>BHHB', pdu[:6]) + + # Values are 16 bit, so each value takes up 2 bytes. + fmt = '>' + ('H' * int((byte_count / 2))) + + values = list(struct.unpack(fmt, pdu[6:])) + return cls(starting_address, quantity, byte_count, values) + function_code_to_function_map = { READ_COILS: ReadCoils, READ_DISCRETE_INPUTS: ReadDiscreteInputs, @@ -681,4 +756,5 @@ def create_from_request_pdu(cls, pdu): WRITE_SINGLE_COIL: WriteSingleCoil, WRITE_SINGLE_REGISTER: WriteSingleRegister, WRITE_MULTIPLE_COILS: WriteMultipleCoils, + WRITE_MULTIPLE_REGISTERS: WriteMultipleRegisters, } diff --git a/scripts/data_server.py b/scripts/data_server.py index 5dd237b..4cc4324 100755 --- a/scripts/data_server.py +++ b/scripts/data_server.py @@ -29,6 +29,10 @@ def single_coil(slave_id, address, value): print(value) +@server.route(slave_ids=[1], function_codes=[15, 16], addresses=set(range(100, 200))) +def multiple_coils(slave_id, address, value): + print(value) + if __name__ == '__main__': try: server.serve_forever() diff --git a/scripts/log_server.py b/scripts/log_server.py index 9cedd52..97479da 100755 --- a/scripts/log_server.py +++ b/scripts/log_server.py @@ -1,17 +1,19 @@ #!/usr/bin/env python -from logging import info -from SocketServer import BaseRequestHandler, TCPServer +try: + from socketserver import BaseRequestHandler, TCPServer +except ImportError: + from socketserver import BaseRequestHandler, TCPServer class LogHandler(BaseRequestHandler): def handle(self): self.data = self.request.recv(1024).strip() - info('{0} wrote: {1}'.format(self.client_address[0], self.data)) + print('{0} wrote: {1}'.format(self.client_address[0], self.data)) self.request.sendall(self.data) if __name__ == '__main__': - HOST, PORT = 'localhost', 1337 + HOST, PORT = 'localhost', 1338 server = TCPServer((HOST, PORT), LogHandler) try: