"""Modbus Utilities. A collection of utilities for packing data, unpacking data computing checksums, and decode checksums. """ from __future__ import annotations # pylint: disable=missing-type-doc import struct def dict_property(store, index): """Create class properties from a dictionary. Basically this allows you to remove a lot of possible boilerplate code. :param store: The store store to pull from :param index: The index into the store to close over :returns: An initialized property set """ if hasattr(store, "__call__"): getter = lambda self: store( # pylint: disable=unnecessary-lambda-assignment self )[index] setter = lambda self, value: store( # pylint: disable=unnecessary-lambda-assignment self ).__setitem__(index, value) elif isinstance(store, str): getter = lambda self: self.__getattribute__( # pylint: disable=unnecessary-dunder-call,unnecessary-lambda-assignment store )[index] setter = lambda self, value: self.__getattribute__( # pylint: disable=unnecessary-dunder-call,unnecessary-lambda-assignment store ).__setitem__(index, value) else: getter = ( lambda self: store[index] # pylint: disable=unnecessary-lambda-assignment ) setter = lambda self, value: store.__setitem__( # pylint: disable=unnecessary-lambda-assignment index, value ) return property(getter, setter) # --------------------------------------------------------------------------- # # Bit packing functions # --------------------------------------------------------------------------- # def pack_bitstring(bits: list[bool]) -> bytes: """Create a bytestring out of a list of bits. :param bits: A list of bits example:: bits = [False, True, False, True] result = pack_bitstring(bits) """ ret = b"" i = packed = 0 for bit in bits: if bit: packed += 128 i += 1 if i == 8: ret += struct.pack(">B", packed) i = packed = 0 else: packed >>= 1 if 0 < i < 8: packed >>= 7 - i ret += struct.pack(">B", packed) return ret def unpack_bitstring(data: bytes) -> list[bool]: """Create bit list out of a bytestring. :param data: The modbus data packet to decode example:: bytes = "bytes to decode" result = unpack_bitstring(bytes) """ byte_count = len(data) bits = [] for byte in range(byte_count): value = int(data[byte]) for _ in range(8): bits.append((value & 1) == 1) value >>= 1 return bits # --------------------------------------------------------------------------- # # Error Detection Functions # --------------------------------------------------------------------------- # def hexlify_packets(packet): """Return hex representation of bytestring received. :param packet: :return: """ if not packet: return "" return " ".join([hex(int(x)) for x in packet])