From 510cf324e4d3a29d35afd2d41031f4af94411dba Mon Sep 17 00:00:00 2001 From: jan iversen Date: Thu, 28 Nov 2024 13:46:55 +0100 Subject: [PATCH 1/4] Add datatype bits to convert_to/from_registers. --- pymodbus/client/mixin.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pymodbus/client/mixin.py b/pymodbus/client/mixin.py index 21cbc3c45..b12f3cf75 100644 --- a/pymodbus/client/mixin.py +++ b/pymodbus/client/mixin.py @@ -15,6 +15,7 @@ from pymodbus.constants import ModbusStatus from pymodbus.exceptions import ModbusException from pymodbus.pdu import ModbusPDU +from pymodbus.utilities import pack_bitstring, unpack_bitstring T = TypeVar("T", covariant=False) @@ -690,16 +691,17 @@ class DATATYPE(Enum): FLOAT32 = ("f", 2) FLOAT64 = ("d", 4) STRING = ("s", 0) + BITS = "bits" @classmethod def convert_from_registers( cls, registers: list[int], data_type: DATATYPE - ) -> int | float | str: + ) -> int | float | str | list[bool]: """Convert registers to int/float/str. :param registers: list of registers received from e.g. read_holding_registers() :param data_type: data type to convert to - :returns: int, float or str depending on "data_type" + :returns: int, float, str or list[bool] depending on "data_type" :raises ModbusException: when size of registers is not 1, 2 or 4 """ byte_list = bytearray() @@ -709,6 +711,8 @@ def convert_from_registers( if byte_list[-1:] == b"\00": byte_list = byte_list[:-1] return byte_list.decode("utf-8") + if data_type == cls.DATATYPE.BITS: + return unpack_bitstring(byte_list) if len(registers) != data_type.value[1]: raise ModbusException( f"Illegal size ({len(registers)}) of register array, cannot convert!" @@ -717,7 +721,7 @@ def convert_from_registers( @classmethod def convert_to_registers( - cls, value: int | float | str, data_type: DATATYPE + cls, value: int | float | str | list[bool], data_type: DATATYPE ) -> list[int]: """Convert int/float/str to registers (16/32/64 bit). @@ -726,7 +730,11 @@ def convert_to_registers( :returns: List of registers, can be used directly in e.g. write_registers() :raises TypeError: when there is a mismatch between data_type and value """ - if data_type == cls.DATATYPE.STRING: + if data_type == cls.DATATYPE.BITS: + if not isinstance(value, list): + raise TypeError(f"Value should be string but is {type(value)}.") + byte_list = pack_bitstring(value) + elif data_type == cls.DATATYPE.STRING: if not isinstance(value, str): raise TypeError(f"Value should be string but is {type(value)}.") byte_list = value.encode() From 754cd76c070efc8e7120696015064acf5dc14438 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Thu, 28 Nov 2024 14:50:53 +0100 Subject: [PATCH 2/4] Temp. --- test/client/test_client.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/client/test_client.py b/test/client/test_client.py index 06c0c552a..197b007de 100755 --- a/test/client/test_client.py +++ b/test/client/test_client.py @@ -131,16 +131,21 @@ def fake_execute(_self, _no_response_expected, request): -3.14159265358979, [0xC009, 0x21FB, 0x5444, 0x2D11], ), + ( + ModbusClientMixin.DATATYPE.BITS, + [False] * 13 + [True, False, True], + [0x00A0], + ), ], ) def test_client_mixin_convert(self, datatype, registers, value): """Test converter methods.""" - result = ModbusClientMixin.convert_from_registers(registers, datatype) - if datatype == ModbusClientMixin.DATATYPE.FLOAT32: - result = round(result, 6) - assert result == value regs = ModbusClientMixin.convert_to_registers(value, datatype) assert regs == registers + # result = ModbusClientMixin.convert_from_registers(registers, datatype) + # if datatype == ModbusClientMixin.DATATYPE.FLOAT32: + # result = round(result, 6) + # assert result == value @pytest.mark.parametrize( ("datatype", "value", "registers"), From bfd4ac5b850e99a9c996194a0493cbfb6bbe6d6f Mon Sep 17 00:00:00 2001 From: jan iversen Date: Thu, 28 Nov 2024 16:01:37 +0100 Subject: [PATCH 3/4] Temp. --- pymodbus/utilities.py | 21 --------------------- test/not_updated/test_utilities.py | 23 ++++++++++++----------- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/pymodbus/utilities.py b/pymodbus/utilities.py index 1bbbc8f3d..6c56e69e4 100644 --- a/pymodbus/utilities.py +++ b/pymodbus/utilities.py @@ -5,13 +5,6 @@ """ from __future__ import annotations - -__all__ = [ - "pack_bitstring", - "unpack_bitstring", - "default", -] - # pylint: disable=missing-type-doc import struct @@ -45,20 +38,6 @@ def to_string(cls, state): return states.get(state, None) -# --------------------------------------------------------------------------- # -# Helpers -# --------------------------------------------------------------------------- # - - -def default(value): - """Return the default value of object. - - :param value: The value to get the default of - :returns: The default value - """ - return type(value)() - - def dict_property(store, index): """Create class properties from a dictionary. diff --git a/test/not_updated/test_utilities.py b/test/not_updated/test_utilities.py index 8dcb6a5f2..63d3789ed 100644 --- a/test/not_updated/test_utilities.py +++ b/test/not_updated/test_utilities.py @@ -1,5 +1,6 @@ """Test utilities.""" import struct +import pytest from pymodbus.utilities import ( default, @@ -76,17 +77,17 @@ def test_dict_property(self): assert result.s_2 == "x" assert result.g_1 == "x" - def test_default_value(self): - """Test all string <=> bit packing functions.""" - assert not default(1) - assert not default(1.1) - assert not default(1 + 1) - assert not default("string") - assert not default([1, 2, 3]) - assert not default({1: 1}) - assert not default(True) - - def test_bit_packing(self): + @pytest.mark.parametrize( + ["bytestream", "bitlist"], + [ + (b"\x55", [True, False, True, False, True, False, True, False]), + (b"\x80", [False] * 7 + [True]), + (b"\x01", [True] + [False] * 7), + # (b"\x00\x80", [False] * 7 + [True]), + # (b"\x00\x01", [True] + [False] * 15), + ] + ) + def test_bit_packing(self, bytestream, bitlist): """Test all string <=> bit packing functions.""" assert unpack_bitstring(b"\x55") == self.bits assert pack_bitstring(self.bits) == b"\x55" From 33f8c6520b4ab25de145211c6ae82836c2cc130e Mon Sep 17 00:00:00 2001 From: jan iversen Date: Thu, 28 Nov 2024 18:55:39 +0100 Subject: [PATCH 4/4] Final. --- pymodbus/client/mixin.py | 2 ++ test/client/test_client.py | 36 ++++++++++++++++++++++++------ test/not_updated/test_utilities.py | 24 +++++--------------- 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/pymodbus/client/mixin.py b/pymodbus/client/mixin.py index b12f3cf75..b40196eb3 100644 --- a/pymodbus/client/mixin.py +++ b/pymodbus/client/mixin.py @@ -733,6 +733,8 @@ def convert_to_registers( if data_type == cls.DATATYPE.BITS: if not isinstance(value, list): raise TypeError(f"Value should be string but is {type(value)}.") + if (missing := len(value) % 16): + value = value + [False] * (16 - missing) byte_list = pack_bitstring(value) elif data_type == cls.DATATYPE.STRING: if not isinstance(value, str): diff --git a/test/client/test_client.py b/test/client/test_client.py index 197b007de..e8578b6bf 100755 --- a/test/client/test_client.py +++ b/test/client/test_client.py @@ -96,7 +96,6 @@ def fake_execute(_self, _no_response_expected, request): getattr(ModbusClientMixin(), method)(**arglist[arg]) assert isinstance(pdu_to_call, pdu_request) - @pytest.mark.parametrize( ("datatype", "value", "registers"), [ @@ -133,8 +132,28 @@ def fake_execute(_self, _no_response_expected, request): ), ( ModbusClientMixin.DATATYPE.BITS, - [False] * 13 + [True, False, True], - [0x00A0], + [True], + [256], + ), + ( + ModbusClientMixin.DATATYPE.BITS, + [True, False, True], + [1280], + ), + ( + ModbusClientMixin.DATATYPE.BITS, + [True, False, True] + [False] * 5 + [True], + [1281], + ), + ( + ModbusClientMixin.DATATYPE.BITS, + [True, False, True] + [False] * 5 + [True] + [False] * 6 + [True], + [1409], + ), + ( + ModbusClientMixin.DATATYPE.BITS, + [True, False, True] + [False] * 5 + [True] + [False] * 6 + [True] * 2, + [1409, 256], ), ], ) @@ -142,10 +161,13 @@ def test_client_mixin_convert(self, datatype, registers, value): """Test converter methods.""" regs = ModbusClientMixin.convert_to_registers(value, datatype) assert regs == registers - # result = ModbusClientMixin.convert_from_registers(registers, datatype) - # if datatype == ModbusClientMixin.DATATYPE.FLOAT32: - # result = round(result, 6) - # assert result == value + result = ModbusClientMixin.convert_from_registers(registers, datatype) + if datatype == ModbusClientMixin.DATATYPE.FLOAT32: + result = round(result, 6) + if datatype == ModbusClientMixin.DATATYPE.BITS: + if (missing := len(value) % 16): + value = value + [False] * (16 - missing) + assert result == value @pytest.mark.parametrize( ("datatype", "value", "registers"), diff --git a/test/not_updated/test_utilities.py b/test/not_updated/test_utilities.py index 63d3789ed..b08dcc018 100644 --- a/test/not_updated/test_utilities.py +++ b/test/not_updated/test_utilities.py @@ -1,9 +1,9 @@ """Test utilities.""" import struct + import pytest from pymodbus.utilities import ( - default, dict_property, pack_bitstring, unpack_bitstring, @@ -41,21 +41,9 @@ def setup_method(self): self.string = ( # pylint: disable=attribute-defined-outside-init b"test the computation" ) - self.bits = [ # pylint: disable=attribute-defined-outside-init - True, - False, - True, - False, - True, - False, - True, - False, - ] def teardown_method(self): """Clean up the test environment.""" - del self.bits - del self.string def test_dict_property(self): """Test all string <=> bit packing functions.""" @@ -78,16 +66,16 @@ def test_dict_property(self): assert result.g_1 == "x" @pytest.mark.parametrize( - ["bytestream", "bitlist"], + ("bytestream", "bitlist"), [ (b"\x55", [True, False, True, False, True, False, True, False]), (b"\x80", [False] * 7 + [True]), (b"\x01", [True] + [False] * 7), - # (b"\x00\x80", [False] * 7 + [True]), - # (b"\x00\x01", [True] + [False] * 15), + (b"\x80\x00", [False] * 7 + [True] + [False] * 8), + (b"\x01\x00", [True] + [False] * 15), ] ) def test_bit_packing(self, bytestream, bitlist): """Test all string <=> bit packing functions.""" - assert unpack_bitstring(b"\x55") == self.bits - assert pack_bitstring(self.bits) == b"\x55" + assert pack_bitstring(bitlist) == bytestream + assert unpack_bitstring(bytestream) == bitlist