diff --git a/examples/client_custom_msg.py b/examples/client_custom_msg.py index f1093f8825..9c241ef3c0 100755 --- a/examples/client_custom_msg.py +++ b/examples/client_custom_msg.py @@ -102,12 +102,13 @@ def execute(self, context): class Read16CoilsRequest(ReadCoilsRequest): """Read 16 coils in one request.""" - def __init__(self, address, count=None, slave=1, transaction=0, skip_encode=False): + def __init__(self, address, slave=1, transaction=0, skip_encode=False): """Initialize a new instance. :param address: The address to start reading from """ - ReadCoilsRequest.__init__(self, address, count=16, slave=slave, transaction=transaction, skip_encode=skip_encode) + super().__init__() + self.setData(address, 16, slave, transaction, skip_encode) # --------------------------------------------------------------------------- # diff --git a/pymodbus/client/mixin.py b/pymodbus/client/mixin.py index 8cf596e174..1e9f90fa3f 100644 --- a/pymodbus/client/mixin.py +++ b/pymodbus/client/mixin.py @@ -74,7 +74,9 @@ def read_coils(self, address: int, count: int = 1, slave: int = 1, no_response_e Coils are addressed starting at zero. Therefore devices that numbers coils 1-16 are addressed as 0-15. """ - return self.execute(no_response_expected, pdu_bit.ReadCoilsRequest(address=address, count=count, slave=slave)) + pdu = pdu_bit.ReadCoilsRequest() + pdu.setData(address, count, slave, 0, False) + return self.execute(no_response_expected, pdu) def read_discrete_inputs(self, address: int, diff --git a/pymodbus/pdu/bit_message.py b/pymodbus/pdu/bit_message.py index 55bc0fbce8..3a1fe214e8 100644 --- a/pymodbus/pdu/bit_message.py +++ b/pymodbus/pdu/bit_message.py @@ -13,8 +13,8 @@ _turn_coil_off = struct.pack(">H", ModbusStatus.OFF) -class OldReadBitsRequestBase(ModbusPDU): - """Base class for Messages Requesting bit values.""" +class ReadCoilsRequest(ModbusPDU): + """ReadCoilsRequest.""" _rtu_frame_size = 8 function_code = 1 @@ -66,9 +66,16 @@ def __str__(self): """Return a string representation of the instance.""" return f"{self.__class__}({self.address},{self.count})" +class ReadDiscreteInputsRequest(ReadCoilsRequest): + """This function code is used to read from 1 to 2000(0x7d0). -class ReadCoilsRequest(OldReadBitsRequestBase): - """ReadCoilsRequest.""" + Contiguous status of discrete inputs in a remote device. The Request PDU specifies the + starting address, ie the address of the first input specified, and the + number of inputs. In the PDU Discrete Inputs are addressed starting at + zero. Therefore Discrete inputs numbered 1-16 are addressed as 0-15. + """ + + function_code = 2 def __init__(self, address=None, count=None, slave=1, transaction=0, skip_encode=False): """Initialize a new instance. @@ -80,6 +87,29 @@ def __init__(self, address=None, count=None, slave=1, transaction=0, skip_encode super().__init__() self.setData(address, count, slave, transaction, skip_encode) + async def update_datastore(self, context): + """Run a read discrete input request against a datastore. + + Before running the request, we make sure that the request is in + the max valid range (0x001-0x7d0). Next we make sure that the + request is valid against the current datastore. + + :param context: The datastore to request from + :returns: An initialized :py:class:`~pymodbus.register_read_message.ReadDiscreteInputsResponse`, or an :py:class:`~pymodbus.pdu.ExceptionResponse` if an error occurred + """ + if not (1 <= self.count <= 0x7D0): + return self.doException(merror.IllegalValue) + if not context.validate(self.function_code, self.address, self.count): + return self.doException(merror.IllegalAddress) + values = await context.async_getValues( + self.function_code, self.address, self.count + ) + return ReadDiscreteInputsResponse(values) + + + + + class ReadBitsResponseBase(ModbusPDU): """Base class for Messages responding to bit-reading values. @@ -151,47 +181,6 @@ def __init__(self, values=None, slave=1, transaction=0, skip_encode=False): ReadBitsResponseBase.__init__(self, values, slave, transaction, skip_encode) -class ReadDiscreteInputsRequest(ReadCoilsRequest): - """This function code is used to read from 1 to 2000(0x7d0). - - Contiguous status of discrete inputs in a remote device. The Request PDU specifies the - starting address, ie the address of the first input specified, and the - number of inputs. In the PDU Discrete Inputs are addressed starting at - zero. Therefore Discrete inputs numbered 1-16 are addressed as 0-15. - """ - - function_code = 2 - - def __init__(self, address=None, count=None, slave=1, transaction=0, skip_encode=False): - """Initialize a new instance. - - :param address: The address to start reading from - :param count: The number of bits to read - :param slave: Modbus slave slave ID - """ - super().__init__() - self.setData(address, count, slave, transaction, skip_encode) - - async def update_datastore(self, context): - """Run a read discrete input request against a datastore. - - Before running the request, we make sure that the request is in - the max valid range (0x001-0x7d0). Next we make sure that the - request is valid against the current datastore. - - :param context: The datastore to request from - :returns: An initialized :py:class:`~pymodbus.register_read_message.ReadDiscreteInputsResponse`, or an :py:class:`~pymodbus.pdu.ExceptionResponse` if an error occurred - """ - if not (1 <= self.count <= 0x7D0): - return self.doException(merror.IllegalValue) - if not context.validate(self.function_code, self.address, self.count): - return self.doException(merror.IllegalAddress) - values = await context.async_getValues( - self.function_code, self.address, self.count - ) - return ReadDiscreteInputsResponse(values) - - class ReadDiscreteInputsResponse(ReadBitsResponseBase): """The discrete inputs in the response message are packed as one input per bit of the data field. diff --git a/test/pdu/test_bit_pdu.py b/test/pdu/test_bit_pdu.py index dc38562d54..4e4e0f84c3 100644 --- a/test/pdu/test_bit_pdu.py +++ b/test/pdu/test_bit_pdu.py @@ -45,8 +45,10 @@ def test_bit_read_base_requests(self): async def test_bit_read_update_datastore_value_errors(self): """Test bit read request encoding.""" context = MockContext() + pdu1 = bit_msg.ReadCoilsRequest() + pdu1.setData(1, 0x800, 0, 0, False) requests = [ - bit_msg.ReadCoilsRequest(1, 0x800, 0, 0, False), + pdu1, bit_msg.ReadDiscreteInputsRequest(1, 0x800, 0, 0, False), ] for request in requests: @@ -56,8 +58,10 @@ async def test_bit_read_update_datastore_value_errors(self): async def test_bit_read_update_datastore_address_errors(self): """Test bit read request encoding.""" context = MockContext() + pdu1 = bit_msg.ReadCoilsRequest() + pdu1.setData(1, 5, 0, 0, False) requests = [ - bit_msg.ReadCoilsRequest(1, 5, 0, 0, False), + pdu1, bit_msg.ReadDiscreteInputsRequest(1, 5, 0, 0, False), ] for request in requests: @@ -68,8 +72,10 @@ async def test_bit_read_update_datastore_success(self): """Test bit read request encoding.""" context = MockContext() context.validate = lambda a, b, c: True + pdu1 = bit_msg.ReadCoilsRequest() + pdu1.setData(1, 5, 0, 0, False) requests = [ - bit_msg.ReadCoilsRequest(1, 5, 0, 0, False), + pdu1, bit_msg.ReadDiscreteInputsRequest(1, 5, 0, False), ] for request in requests: @@ -78,10 +84,16 @@ async def test_bit_read_update_datastore_success(self): def test_bit_read_get_response_pdu(self): """Test bit read message get response pdu.""" + pdu1 = bit_msg.ReadCoilsRequest() + pdu1.setData(1, 5, 0, 0, False) + pdu2 = bit_msg.ReadCoilsRequest() + pdu2.setData(1, 8, 0, 0, False) + pdu3 = bit_msg.ReadCoilsRequest() + pdu3.setData(0, 16, 0, 0, False) requests = { - bit_msg.ReadCoilsRequest(1, 5, 0, 0, False): 3, - bit_msg.ReadCoilsRequest(1, 8, 0, 0, False): 3, - bit_msg.ReadCoilsRequest(0, 16, 0, 0, False): 4, + pdu1: 3, + pdu2: 3, + pdu3: 4, bit_msg.ReadDiscreteInputsRequest(1, 21, 0, 0, False): 5, bit_msg.ReadDiscreteInputsRequest(1, 24, 0, 0, False): 5, bit_msg.ReadDiscreteInputsRequest(1, 1900, 0, 0, False): 240, diff --git a/test/pdu/test_pdu.py b/test/pdu/test_pdu.py index f3133515f2..f7fd45d69f 100644 --- a/test/pdu/test_pdu.py +++ b/test/pdu/test_pdu.py @@ -83,7 +83,7 @@ def test_calculate_rtu_frame_size(self): # -------------------------- requests = [ - (bit_msg.ReadCoilsRequest, (), {"address": 117, "count": 3}, b'\x01\x00\x75\x00\x03'), + (bit_msg.ReadCoilsRequest, (117, 3, 0, 0, False), {}, b'\x01\x00\x75\x00\x03'), (bit_msg.ReadDiscreteInputsRequest, (), {"address": 117, "count": 3}, b'\x02\x00\x75\x00\x03'), (bit_msg.WriteSingleCoilRequest, (), {"address": 117, "value": True}, b'\x05\x00\x75\xff\x00'), (bit_msg.WriteMultipleCoilsRequest, (), {"address": 117, "values": [True, False, True]}, b'\x0f\x00\x75\x00\x03\x01\x05'), @@ -168,13 +168,16 @@ def test_pdu_instance(self, pdutype): """Test that all PDU types can be created.""" pdu = pdutype() assert pdu - assert str(pdu) @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests + responses) @pytest.mark.usefixtures("frame") def test_pdu_instance_args(self, pdutype, args, kwargs): """Test that all PDU types can be created.""" - pdu = pdutype(*args, **kwargs) + if args: + pdu = pdutype() + pdu.setData(*args) + else: + pdu = pdutype(*args, **kwargs) assert pdu assert str(pdu) @@ -184,7 +187,9 @@ def test_pdu_instance_extras(self, pdutype, args, kwargs): """Test that all PDU types can be created.""" tid = 9112 slave_id = 63 - pdu = pdutype(*args, transaction=tid, slave=slave_id, **kwargs) + if args: + return + pdu = pdutype(transaction=tid, slave=slave_id, **kwargs) assert pdu assert str(pdu) assert pdu.slave_id == slave_id @@ -194,35 +199,56 @@ def test_pdu_instance_extras(self, pdutype, args, kwargs): @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests + responses) def test_pdu_instance_encode(self, pdutype, args, kwargs, frame): """Test that all PDU types can be created.""" - res_frame = pdutype.function_code.to_bytes(1,'big') + pdutype(*args, **kwargs).encode() + if args: + pdu = pdutype() + pdu.setData(*args) + else: + pdu = pdutype(**kwargs) + res_frame = pdutype.function_code.to_bytes(1,'big') + pdu.encode() assert res_frame == frame @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), responses) @pytest.mark.usefixtures("frame") def test_pdu_get_response_pdu_size1(self, pdutype, args, kwargs): """Test that all PDU types can be created.""" - pdu = pdutype(*args, **kwargs) + if args: + pdu = pdutype() + pdu.setData(*args) + else: + pdu = pdutype(**kwargs) assert not pdu.get_response_pdu_size() @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests) @pytest.mark.usefixtures("frame") def test_get_response_pdu_size2(self, pdutype, args, kwargs): """Test that all PDU types can be created.""" - pdu = pdutype(*args, **kwargs) + if args: + pdu = pdutype() + pdu.setData(*args) + else: + pdu = pdutype(**kwargs) pdu.get_response_pdu_size() #FIX size > 0 !! @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests + responses) def test_pdu_decode(self, pdutype, args, kwargs, frame): """Test that all PDU types can be created.""" - pdu = pdutype(*args, **kwargs) + if args: + pdu = pdutype() + pdu.setData(*args) + else: + pdu = pdutype(**kwargs) pdu.decode(frame[1:]) @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests) @pytest.mark.usefixtures("frame") async def test_pdu_datastore(self, pdutype, args, kwargs): """Test that all PDU types can be created.""" - pdu = pdutype(*args, **kwargs) + if args: + pdu = pdutype() + pdu.setData(*args) + else: + pdu = pdutype(**kwargs) context = MockContext() context.validate = lambda a, b, c: True assert await pdu.update_datastore(context) diff --git a/test/sub_client/test_client.py b/test/sub_client/test_client.py index 395e05c497..860d57bf43 100755 --- a/test/sub_client/test_client.py +++ b/test/sub_client/test_client.py @@ -362,7 +362,8 @@ async def test_client_protocol_handler(): ) transport = mock.MagicMock() base.ctx.connection_made(transport=transport) - reply = pdu_bit.ReadCoilsRequest(1, 1) + reply = pdu_bit.ReadCoilsRequest() + reply.setData(1, 1, 0, 0, False) reply.transaction_id = 0x00 base.ctx._handle_response(None) # pylint: disable=protected-access base.ctx._handle_response(reply) # pylint: disable=protected-access @@ -415,7 +416,8 @@ async def test_client_protocol_execute(): timeout_connect=3, ), ) - request = pdu_bit.ReadCoilsRequest(1, 1) + request = pdu_bit.ReadCoilsRequest() + request.setData(1, 1, 0, 0, False) transport = MockTransport(base, request) base.ctx.connection_made(transport=transport) @@ -433,7 +435,8 @@ async def test_client_execute_broadcast(): host="127.0.0.1", ), ) - request = pdu_bit.ReadCoilsRequest(1, 1, slave=0) + request = pdu_bit.ReadCoilsRequest() + request.setData(1, 1, 0, 0, False) transport = MockTransport(base, request) base.ctx.connection_made(transport=transport) assert await base.async_execute(False, request) @@ -449,7 +452,8 @@ async def test_client_execute_broadcast_no(): host="127.0.0.1", ), ) - request = pdu_bit.ReadCoilsRequest(1, 1, slave=0) + request = pdu_bit.ReadCoilsRequest() + request.setData(1, 1, 0, 0, False) transport = MockTransport(base, request) base.ctx.connection_made(transport=transport) assert not await base.async_execute(True, request) @@ -465,7 +469,8 @@ async def test_client_protocol_retry(): timeout_connect=0.1, ), ) - request = pdu_bit.ReadCoilsRequest(1, 1) + request = pdu_bit.ReadCoilsRequest() + request.setData(1, 1, 0, 0, False) transport = MockTransport(base, request, retries=2) base.ctx.connection_made(transport=transport) @@ -488,7 +493,8 @@ async def test_client_protocol_timeout(): ) # Avoid creating do_reconnect() task base.ctx.connection_lost = mock.MagicMock() - request = pdu_bit.ReadCoilsRequest(1, 1) + request = pdu_bit.ReadCoilsRequest() + request.setData(1, 1, 0, 0, False) transport = MockTransport(base, request, retries=4) base.ctx.connection_made(transport=transport)