From 7369cb895075a9f177ae9412ca170ff0d64f49c0 Mon Sep 17 00:00:00 2001 From: Benjamin Braatz Date: Wed, 7 Apr 2021 13:19:36 +0200 Subject: [PATCH] Functions 01, 03, 05, 06 and test with pty. --- conf.json | 30 ++-- controlpi_plugins/modbus.py | 279 +++++++++++++++++++++++++++++++----- 2 files changed, 251 insertions(+), 58 deletions(-) diff --git a/conf.json b/conf.json index c3f5a18..4bcfeaa 100644 --- a/conf.json +++ b/conf.json @@ -7,27 +7,15 @@ "location": "Debug" } } }, - "Modbus": { + "Test Master": { + "plugin": "ModbusMaster", "device": "/dev/ttyS0", - "test device": "/dev/ttyUSB0", - "baudrate": 115200, - "slave types": { - "Hitachi SJ-P1": { - "slaves": { "FU-1": 1 }, - "coils": { - "Op": { - "address": 0, - "name": "Operation command" - } - }, - "holding registers": { - "Set frequency": { - "address": 10501, - "name": "RS485 Set frequency", - "count": 2 - } - } - } - } + "parity": "none" + }, + "Test Slave": { + "plugin": "ModbusSlave", + "device": "/dev/ttyUSB0", + "slave": 1, + "parity": "none" } } diff --git a/controlpi_plugins/modbus.py b/controlpi_plugins/modbus.py index 3c99654..dad43c7 100644 --- a/controlpi_plugins/modbus.py +++ b/controlpi_plugins/modbus.py @@ -81,32 +81,190 @@ for dict_key in range(256): class ModbusMaster(BasePlugin): """Modbus-RTU master plugin. - >>> import controlpi - >>> asyncio.run(controlpi.test( - ... {"Test Master": {"plugin": "ModbusMaster", - ... "device": "/dev/pts/4", - ... "parity": "none"}, - ... "Test Slave": {"plugin": "ModbusSlave", - ... "device": "/dev/pts/5", - ... "slave": 1, - ... "parity": "none"}}, - ... [{"target": "Test Master", "command": "read coils", - ... "slave": 1, "start": 1, "quantity": 2}, - ... {"target": "Test Master", "command": "write single coil", - ... "slave": 1, "address": 1, "value": False}, - ... {"target": "Test Master", "command": "write single coil", - ... "slave": 1, "address": 2, "value": True}, - ... {"target": "Test Master", "command": "read coils", - ... "slave": 1, "start": 1, "quantity": 2}, - ... {"target": "Test Master", "command": "read holding registers", - ... "slave": 1, "start": 7, "quantity": 2}, - ... {"target": "Test Master", "command": "write single register", - ... "slave": 1, "address": 7, "value": 42}, - ... {"target": "Test Master", "command": "write single register", - ... "slave": 1, "address": 8, "value": 42042}, - ... {"target": "Test Master", "command": "read holding registers", - ... "slave": 1, "start": 7, "quantity": 2}], 0.5)) + >>> import pty, os, controlpi + >>> master_controller, master = pty.openpty() + >>> slave_controller, slave = pty.openpty() + >>> def relay(source, target): + ... content = os.read(source, 2147483647) + ... os.write(target, content) + >>> async def test(): + ... loop = asyncio.get_event_loop() + ... loop.add_reader(master_controller, relay, + ... master_controller, slave_controller) + ... loop.add_reader(slave_controller, relay, + ... slave_controller, master_controller) + ... await controlpi.test( + ... {"Test Master": {"plugin": "ModbusMaster", + ... "device": os.ttyname(master), + ... "parity": "none"}, + ... "Test Slave": {"plugin": "ModbusSlave", + ... "device": os.ttyname(slave), + ... "slave": 1, + ... "parity": "none"}}, + ... [{"target": "Test Master", "command": "read coils", + ... "slave": 1, "start": 1, "quantity": 2}, + ... {"target": "Test Master", "command": "write single coil", + ... "slave": 1, "address": 1, "value": False}, + ... {"target": "Test Master", "command": "write single coil", + ... "slave": 1, "address": 2, "value": True}, + ... {"target": "Test Master", "command": "read coils", + ... "slave": 1, "start": 1, "quantity": 2}, + ... {"target": "Test Master", "command": "read holding registers", + ... "slave": 1, "start": 7, "quantity": 2}, + ... {"target": "Test Master", "command": "write single register", + ... "slave": 1, "address": 7, "value": 42}, + ... {"target": "Test Master", "command": "write single register", + ... "slave": 1, "address": 8, "value": 42042}, + ... {"target": "Test Master", "command": "read holding registers", + ... "slave": 1, "start": 7, "quantity": 2}], 0.1) + >>> asyncio.run(test()) ... # doctest: +NORMALIZE_WHITESPACE + test(): {'sender': '', 'event': 'registered', + 'client': 'Test Master', 'plugin': 'ModbusMaster', + 'sends': [{'event': {'const': 'error'}}, + {'event': {'const': 'response'}, + 'function': {'const': 'read coils'}, + 'slave': {'type': 'integer', + 'minimum': 1, 'maximum': 247}, + 'values': {'type': 'array', + 'items': {'type': 'boolean'}}}, + {'event': {'const': 'response'}, + 'function': {'const': 'read holding registers'}, + 'slave': {'type': 'integer', + 'minimum': 1, 'maximum': 247}, + 'values': {'type': 'array', + 'items': {'type': 'integer'}}}, + {'event': {'const': 'response'}, + 'function': {'const': 'write single coil'}, + 'slave': {'type': 'integer', + 'minimum': 1, 'maximum': 247}, + 'address': {'type': 'integer', + 'minimum': 1, 'maximum': 65536}, + 'value': {'type': 'boolean'}}, + {'event': {'const': 'response'}, + 'function': {'const': 'write single register'}, + 'slave': {'type': 'integer', + 'minimum': 1, 'maximum': 247}, + 'address': {'type': 'integer', + 'minimum': 1, 'maximum': 65536}, + 'value': {'type': 'integer', + 'minimum': 0, 'maximum': 65535}}], + 'receives': [{'target': {'const': 'Test Master'}, + 'command': {'const': 'read coils'}, + 'slave': {'type': 'integer', + 'minimum': 1, 'maximum': 247}, + 'start': {'type': 'integer', + 'minimum': 1, 'maximum': 65536}, + 'quantity': {'type': 'integer', + 'minimum': 1, 'maximum': 2000}}, + {'target': {'const': 'Test Master'}, + 'command': {'const': 'read holding registers'}, + 'slave': {'type': 'integer', + 'minimum': 1, 'maximum': 247}, + 'start': {'type': 'integer', + 'minimum': 1, 'maximum': 65536}, + 'quantity': {'type': 'integer', + 'minimum': 1, 'maximum': 125}}, + {'target': {'const': 'Test Master'}, + 'command': {'const': 'write single coil'}, + 'slave': {'type': 'integer', + 'minimum': 1, 'maximum': 247}, + 'address': {'type': 'integer', + 'minimum': 1, 'maximum': 65536}, + 'value': {'type': 'boolean'}}, + {'target': {'const': 'Test Master'}, + 'command': {'const': 'write single register'}, + 'slave': {'type': 'integer', + 'minimum': 1, 'maximum': 247}, + 'address': {'type': 'integer', + 'minimum': 1, 'maximum': 65536}, + 'value': {'type': 'integer', + 'minimum': 0, 'maximum': 65535}}]} + test(): {'sender': '', 'event': 'registered', + 'client': 'Test Slave', 'plugin': 'ModbusSlave', + 'sends': [{'event': {'const': 'received'}}, + {'event': {'const': 'sent'}}], + 'receives': []} + test(): {'sender': 'test()', 'target': 'Test Master', + 'command': 'read coils', 'slave': 1, + 'start': 1, 'quantity': 2} + test(): {'sender': 'test()', 'target': 'Test Master', + 'command': 'write single coil', 'slave': 1, + 'address': 1, 'value': False} + test(): {'sender': 'test()', 'target': 'Test Master', + 'command': 'write single coil', 'slave': 1, + 'address': 2, 'value': True} + test(): {'sender': 'test()', 'target': 'Test Master', + 'command': 'read coils', 'slave': 1, + 'start': 1, 'quantity': 2} + test(): {'sender': 'test()', 'target': 'Test Master', + 'command': 'read holding registers', 'slave': 1, + 'start': 7, 'quantity': 2} + test(): {'sender': 'test()', 'target': 'Test Master', + 'command': 'write single register', 'slave': 1, + 'address': 7, 'value': 42} + test(): {'sender': 'test()', 'target': 'Test Master', + 'command': 'write single register', 'slave': 1, + 'address': 8, 'value': 42042} + test(): {'sender': 'test()', 'target': 'Test Master', + 'command': 'read holding registers', 'slave': 1, + 'start': 7, 'quantity': 2} + test(): {'sender': 'Test Slave', 'event': 'received', + 'message': '010100000002bdcb'} + test(): {'sender': 'Test Slave', 'event': 'sent', + 'message': '018102c191'} + test(): {'sender': 'Test Master', 'event': 'error', + 'function': 'read coils', 'slave': 1, + 'description': 'Illegal data address'} + test(): {'sender': 'Test Slave', 'event': 'received', + 'message': '010500000000cdca'} + test(): {'sender': 'Test Slave', 'event': 'sent', + 'message': '010500000000cdca'} + test(): {'sender': 'Test Master', 'event': 'response', + 'function': 'write single coil', 'slave': 1, 'address': 1, + 'value': False} + test(): {'sender': 'Test Slave', 'event': 'received', + 'message': '01050001ff00ddfa'} + test(): {'sender': 'Test Slave', 'event': 'sent', + 'message': '01050001ff00ddfa'} + test(): {'sender': 'Test Master', 'event': 'response', + 'function': 'write single coil', 'slave': 1, 'address': 2, + 'value': True} + test(): {'sender': 'Test Slave', 'event': 'received', + 'message': '010100000002bdcb'} + test(): {'sender': 'Test Slave', 'event': 'sent', + 'message': '01010102d049'} + test(): {'sender': 'Test Master', 'event': 'response', + 'function': 'read coils', 'slave': 1, + 'values': [False, True, False, False, False, False, False, False]} + test(): {'sender': 'Test Slave', 'event': 'received', + 'message': '010300060002240a'} + test(): {'sender': 'Test Slave', 'event': 'sent', + 'message': '018302c0f1'} + test(): {'sender': 'Test Master', 'event': 'error', + 'function': 'read holding registers', 'slave': 1, + 'description': 'Illegal data address'} + test(): {'sender': 'Test Slave', 'event': 'received', + 'message': '01060006002ae814'} + test(): {'sender': 'Test Slave', 'event': 'sent', + 'message': '01060006002ae814'} + test(): {'sender': 'Test Master', 'event': 'response', + 'function': 'write single register', 'slave': 1, 'address': 7, + 'value': 42} + test(): {'sender': 'Test Slave', 'event': 'received', + 'message': '01060007a43ac2d8'} + test(): {'sender': 'Test Slave', 'event': 'sent', + 'message': '01060007a43ac2d8'} + test(): {'sender': 'Test Master', 'event': 'response', + 'function': 'write single register', 'slave': 1, 'address': 8, + 'value': 42042} + test(): {'sender': 'Test Slave', 'event': 'received', + 'message': '010300060002240a'} + test(): {'sender': 'Test Slave', 'event': 'sent', + 'message': '010304002aa43a2128'} + test(): {'sender': 'Test Master', 'event': 'response', + 'function': 'read holding registers', 'slave': 1, + 'values': [42, 42042]} """ CONF_SCHEMA = {'properties': @@ -340,13 +498,13 @@ class ModbusMaster(BasePlugin): message['message'] = modbus_message.hex() await self.bus.send(message) return - address_hi = modbus_message[3] - address_lo = modbus_message[4] + address_hi = modbus_message[2] + address_lo = modbus_message[3] message['address'] = address_hi * 256 + address_lo + 1 if function_code == 0x05: - if modbus_message[5] == 0xFF and modbus_message[6] == 0x00: + if modbus_message[4] == 0xFF and modbus_message[5] == 0x00: message['value'] = True - elif modbus_message[5] == 0x00 and modbus_message[6] == 0x00: + elif modbus_message[4] == 0x00 and modbus_message[5] == 0x00: message['value'] = False else: message['event'] = 'error' @@ -355,8 +513,8 @@ class ModbusMaster(BasePlugin): await self.bus.send(message) return if function_code == 0x06: - value_hi = modbus_message[5] - value_lo = modbus_message[6] + value_hi = modbus_message[4] + value_lo = modbus_message[5] message['value'] = value_hi * 256 + value_lo await self.bus.send(message) @@ -369,7 +527,7 @@ class ModbusMaster(BasePlugin): modbus_request = await self._queue.get() slave = modbus_request[0] tries = 0 - while tries < self.conf['retries']: + while tries <= self.conf['retries']: tries += 1 writer.write(modbus_request) if slave == 0: @@ -522,18 +680,65 @@ class ModbusSlave(BasePlugin): quantity = quantity_hi * 256 + quantity_lo if function_code == 0x01: okay = True + data = [] + byte = 0 + bit = 0 for i in range(quantity): if start + i not in self._coils: modbus_response += bytes([0x81, 0x02]) okay = False break - else: - if self._coils[start + i]: - pass + if self._coils[start + i]: + byte += 2**bit + bit += 1 + if bit == 8: + data.append(byte) + byte = 0 + bit = 0 + if okay: + if bit > 0: + data.append(byte) + modbus_response += bytes([0x01, len(data)]) + modbus_response += bytes(data) if function_code == 0x03: - pass + okay = True + data = [] + for i in range(quantity): + if start + i not in self._registers: + modbus_response += bytes([0x83, 0x02]) + okay = False + break + data.append(self._registers[start + i] >> 8) + data.append(self._registers[start + i] & 0xFF) + if okay: + modbus_response += bytes([0x03, len(data)]) + modbus_response += bytes(data) elif function_code == 0x05 or function_code == 0x06: - pass + if len(modbus_request) != 8: + modbus_response += bytes([function_code | 0x80, 0x03]) + else: + address_hi = modbus_request[2] + address_lo = modbus_request[3] + address = address_hi * 256 + address_lo + if function_code == 0x05: + okay = True + if (modbus_request[4] == 0xFF and + modbus_request[5] == 0x00): + self._coils[address] = True + elif (modbus_request[4] == 0x00 and + modbus_request[5] == 0x00): + self._coils[address] = False + else: + modbus_response += bytes([0x85, 0x03]) + okay = False + if okay: + modbus_response = modbus_request[:-2] + if function_code == 0x06: + value_hi = modbus_request[4] + value_lo = modbus_request[5] + value = value_hi * 256 + value_lo + self._registers[address] = value + modbus_response = modbus_request[:-2] else: modbus_response += bytes([function_code | 0x80, 0x01]) modbus_response += crc(modbus_response) -- 2.34.1