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':
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'
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)
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:
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)