From 9160b933817a425c7bb5e23b6a17c0d166fdbcec Mon Sep 17 00:00:00 2001 From: denpamusic Date: Fri, 10 Nov 2023 21:45:48 +0300 Subject: [PATCH] More data types improvements. - Byte datatype is now handled by struct module as an UnsignedChar. - Rename PascalString to VarString to avoid confusion, as in EM's these are true variable length. - Rename ByteString to VarBytes to follow new naming convention. - Rename Boolean to BitArray to better reflect it's use case. --- pyplumio/helpers/data_types.py | 58 ++++++++++------------- pyplumio/structures/network_info.py | 6 +-- pyplumio/structures/product_info.py | 6 +-- pyplumio/structures/regulator_data.py | 18 +++---- tests/helpers/test_data_types.py | 32 ++++++------- tests/testdata/responses/data_schema.json | 56 +++++++++++----------- 6 files changed, 84 insertions(+), 92 deletions(-) diff --git a/pyplumio/helpers/data_types.py b/pyplumio/helpers/data_types.py index fb8ebaed..b39941c4 100644 --- a/pyplumio/helpers/data_types.py +++ b/pyplumio/helpers/data_types.py @@ -30,7 +30,7 @@ def __eq__(self, other) -> bool: def _slice_data(self, data: bytes) -> bytes: """Slice the data to data type size.""" - return data[0 : self.size] if self.size is not None else data + return data[: self.size] if self.size is not None else data @classmethod def from_bytes(cls, data: bytes, offset: int = 0): @@ -74,27 +74,13 @@ def unpack(self, _: bytes) -> None: self._value = None -class Byte(DataType): - """Represents a byte.""" - - _size: int = 1 - - def pack(self) -> bytes: - """Pack the data.""" - return self.value.to_bytes(length=self.size, byteorder="big") - - def unpack(self, data: bytes) -> None: - """Unpack the data.""" - self._value = ord(self._slice_data(data)) - - -class Boolean(DataType): - """Represents a boolean.""" +class BitArray(DataType): + """Represents a bit array.""" _index: int = 0 def __init__(self, value: Any = None): - """Initialize a new boolean.""" + """Initialize a new bit array.""" self._index = 0 super().__init__(value) @@ -107,11 +93,11 @@ def next(self, index: int = 0) -> int: def pack(self) -> bytes: """Pack the data.""" - return self._value.to_bytes(length=1, byteorder="big") + return UnsignedChar(self._value).to_bytes() def unpack(self, data: bytes) -> None: """Unpack the data.""" - self._value = ord(data[0:1]) + self._value = UnsignedChar.from_bytes(data[:1]).value @property def value(self) -> bool | None: @@ -157,7 +143,7 @@ class String(DataType): def pack(self) -> bytes: """Pack the data.""" - return self.value.encode() + b"\x00" + return self.value.encode() + UnsignedChar(0).to_bytes() def unpack(self, data: bytes) -> None: """Unpack the data.""" @@ -175,12 +161,12 @@ def size(self) -> int: return len(self.value) + 1 if self.value is not None else 0 -class ByteString(DataType): +class VarBytes(DataType): """Represents a byte string.""" def pack(self) -> bytes: """Pack the data.""" - return bytearray([self.size - 1]) + self.value + return UnsignedChar(self.size - 1).to_bytes() + self.value def unpack(self, data: bytes) -> None: """Unpack the data.""" @@ -196,12 +182,12 @@ def size(self) -> int: return len(self.value) + 1 -class PascalString(ByteString): +class VarString(VarBytes): """Represents a Pascal string.""" def pack(self) -> bytes: """Pack the data.""" - return bytearray([self.size - 1]) + self.value.encode() + return UnsignedChar(self.size - 1).to_bytes() + self.value.encode() def unpack(self, data: bytes) -> None: """Unpack the data.""" @@ -236,16 +222,16 @@ class SignedChar(BuiltInDataType): _struct: ClassVar[struct.Struct] = struct.Struct(" bytearray: message.append(network_info.wlan.signal_quality) message.append(network_info.wlan.status) message += b"\x00" * 4 - message += PascalString(network_info.wlan.ssid).to_bytes() + message += VarString(network_info.wlan.ssid).to_bytes() return message @@ -92,7 +92,7 @@ def decode( encryption=EncryptionType(int(message[offset + 26])), signal_quality=int(message[offset + 27]), status=bool(message[offset + 28]), - ssid=PascalString.from_bytes(message, offset + 33).value, + ssid=VarString.from_bytes(message, offset + 33).value, ), server_status=bool(message[offset + 25]), ) diff --git a/pyplumio/structures/product_info.py b/pyplumio/structures/product_info.py index 7912fb3c..12f89fe9 100644 --- a/pyplumio/structures/product_info.py +++ b/pyplumio/structures/product_info.py @@ -7,7 +7,7 @@ from typing import Final from pyplumio.const import ProductType -from pyplumio.helpers.data_types import ByteString, PascalString, UnsignedShort +from pyplumio.helpers.data_types import UnsignedShort, VarBytes, VarString from pyplumio.helpers.typing import EventDataType from pyplumio.helpers.uid import decode_uid from pyplumio.structures import StructureDecoder @@ -48,7 +48,7 @@ def decode( product_type, product_id = struct.unpack_from(" 0: - # Current data type is not boolean, but previous was, thus - # we skip single byte that was left from the boolean - # and reset boolean index. + if not isinstance(data_type, BitArray) and self._bitarray_index > 0: + # Current data type is not bitarray, but previous was, thus + # we skip a single byte that was left from the bitarray + # and reset bitarray index. self._offset += 1 - self._boolean_index = 0 + self._bitarray_index = 0 data_type.unpack(message[self._offset :]) - if isinstance(data_type, Boolean): - self._boolean_index = data_type.next(self._boolean_index) + if isinstance(data_type, BitArray): + self._bitarray_index = data_type.next(self._bitarray_index) try: return data_type.value diff --git a/tests/helpers/test_data_types.py b/tests/helpers/test_data_types.py index 4efaa920..cbd9e020 100644 --- a/tests/helpers/test_data_types.py +++ b/tests/helpers/test_data_types.py @@ -70,10 +70,10 @@ def test_int() -> None: assert data_type.to_bytes() == buffer -def test_byte() -> None: - """Test a byte data type.""" +def test_unsigned_char() -> None: + """Test an signed char data type.""" buffer = bytearray([0x3]) - data_type = data_types.Byte.from_bytes(buffer) + data_type = data_types.UnsignedChar.from_bytes(buffer) assert data_type.value == 3 assert data_type.size == 1 assert data_type.to_bytes() == buffer @@ -115,10 +115,10 @@ def test_double() -> None: assert data_type.to_bytes() == buffer -def test_boolean() -> None: - """Test a boolean data type.""" +def test_bitarray() -> None: + """Test a bit array data type.""" buffer = bytearray([0x55]) - data_type = data_types.Boolean.from_bytes(buffer) + data_type = data_types.BitArray.from_bytes(buffer) for index, value in enumerate([1, 0, 1, 0, 1, 0, 1, 0]): next_bit = data_type.next(index) assert data_type.value == bool(value) @@ -197,19 +197,19 @@ def test_string() -> None: assert data_type.to_bytes() == buffer -def test_pascal_string() -> None: - """Test a pascal string data type.""" - buffer = b"\x04test" - data_type = data_types.PascalString.from_bytes(buffer) - assert data_type.value == "test" +def test_var_bytes() -> None: + """Test a variable bytes data type.""" + buffer = b"\x04\xDE\xAD\xBE\xEF" + data_type = data_types.VarBytes.from_bytes(buffer) + assert data_type.value == bytearray([0xDE, 0xAD, 0xBE, 0xEF]) assert data_type.size == 5 assert data_type.to_bytes() == buffer -def test_byte_string() -> None: - """Test a byte string data type.""" - buffer = b"\x04\xDE\xAD\xBE\xEF" - data_type = data_types.ByteString.from_bytes(buffer) - assert data_type.value == bytearray([0xDE, 0xAD, 0xBE, 0xEF]) +def test_var_string() -> None: + """Test a variable string data type.""" + buffer = b"\x04test" + data_type = data_types.VarString.from_bytes(buffer) + assert data_type.value == "test" assert data_type.size == 5 assert data_type.to_bytes() == buffer diff --git a/tests/testdata/responses/data_schema.json b/tests/testdata/responses/data_schema.json index fe5deb64..c3401ed6 100644 --- a/tests/testdata/responses/data_schema.json +++ b/tests/testdata/responses/data_schema.json @@ -17,7 +17,7 @@ 1792, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Byte" + "__class__": "UnsignedChar" } ], "__tuple__": true @@ -27,7 +27,7 @@ 1536, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -37,7 +37,7 @@ 1538, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -47,7 +47,7 @@ 1542, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -57,7 +57,7 @@ 1541, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -67,7 +67,7 @@ 5, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -77,7 +77,7 @@ 3, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -87,7 +87,7 @@ 1543, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -97,7 +97,7 @@ 1544, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -107,7 +107,7 @@ 1545, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -117,7 +117,7 @@ 1546, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -127,7 +127,7 @@ 1547, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -137,7 +137,7 @@ 1548, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -147,7 +147,7 @@ 1549, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -157,7 +157,7 @@ 2, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -167,7 +167,7 @@ 6, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -177,7 +177,7 @@ 6, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -347,7 +347,7 @@ 1280, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Byte" + "__class__": "UnsignedChar" } ], "__tuple__": true @@ -357,7 +357,7 @@ 1283, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Byte" + "__class__": "UnsignedChar" } ], "__tuple__": true @@ -367,7 +367,7 @@ 1282, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Byte" + "__class__": "UnsignedChar" } ], "__tuple__": true @@ -377,7 +377,7 @@ 1287, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Byte" + "__class__": "UnsignedChar" } ], "__tuple__": true @@ -387,7 +387,7 @@ 1281, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Byte" + "__class__": "UnsignedChar" } ], "__tuple__": true @@ -397,7 +397,7 @@ 1288, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Byte" + "__class__": "UnsignedChar" } ], "__tuple__": true @@ -407,7 +407,7 @@ 2048, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Byte" + "__class__": "UnsignedChar" } ], "__tuple__": true @@ -440,7 +440,7 @@ 1538, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -450,7 +450,7 @@ 1536, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -460,7 +460,7 @@ 1537, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Boolean" + "__class__": "BitArray" } ], "__tuple__": true @@ -470,7 +470,7 @@ 1792, { "__module__": "pyplumio.helpers.data_types", - "__class__": "Byte" + "__class__": "UnsignedChar" } ], "__tuple__": true