def __init__(self, value: int | tuple[int]) -> None: """Initialize DPTBinary class.""" if isinstance(value, tuple): value = value[0] if not isinstance(value, int): raise TypeError() if value > DPTBinary.APCI_BITMASK or value < 0: raise ConversionError("Could not init DPTBinary", value=str(value)) self.value = value
def to_knx(self): """Serialize to KNX/IP raw data.""" if not isinstance(self.src_addr, Address): raise ConversionError("src_add not set") if not isinstance(self.dst_addr, Address): raise ConversionError("dst_add not set") data = [] data.append(self.code.value) data.append(0x00) data.append((self.flags >> 8) & 255) data.append(self.flags & 255) data.extend(self.src_addr.to_knx()) data.extend(self.dst_addr.to_knx()) def encode_cmd_and_payload(cmd, encoded_payload=0, appended_payload=None): """Encode cmd and payload.""" if appended_payload is None: appended_payload = [] data = [ 1 + len(appended_payload), (cmd.value >> 8) & 0xff, (cmd.value & 0xff) | (encoded_payload & DPTBinary.APCI_BITMASK) ] data.extend(appended_payload) return data if self.payload is None: data.extend(encode_cmd_and_payload(self.cmd)) elif isinstance(self.payload, DPTBinary): data.extend( encode_cmd_and_payload(self.cmd, encoded_payload=self.payload.value)) elif isinstance(self.payload, DPTArray): data.extend( encode_cmd_and_payload(self.cmd, appended_payload=self.payload.value)) else: raise TypeError() return data
def resolve_telegram_type(cmd): """Return telegram type from APCI Command.""" if cmd == APCICommand.GROUP_WRITE: return TelegramType.GROUP_WRITE elif cmd == APCICommand.GROUP_READ: return TelegramType.GROUP_READ elif cmd == APCICommand.GROUP_RESPONSE: return TelegramType.GROUP_RESPONSE else: raise ConversionError( "Telegram not implemented for {0}".format(self.cmd))
def from_knx(cls, raw, invert: bool = False): """Parse/deserialize from KNX/IP raw data.""" if not cls._test_boundaries(raw): raise ConversionError("Cant parse %s" % cls.__name__, raw=raw) control, step_code = cls._decode(raw) if invert: control = 0 if control > 0 else 1 return {"control": control, "step_code": step_code}
def test_bytesarray(cls, raw): """Test if array of raw bytes has the correct length and values of correct type.""" if cls.payload_length is None: raise NotImplementedError( "payload_length has to be defined for: %s" % cls) if (not isinstance(raw, (tuple, list)) or len(raw) != cls.payload_length or any(not isinstance(byte, int) for byte in raw) or any(byte < 0 for byte in raw) or any(byte > 255 for byte in raw)): raise ConversionError("Invalid raw bytes", raw=raw)
def _parse_pattern(self, pattern: str) -> None: if pattern.startswith("i"): self.internal_group_address_pattern = InternalGroupAddress( pattern).address return for part in pattern.split("/"): self.level_filters.append(AddressFilter.LevelFilter(part)) if len(self.level_filters) > 3: raise ConversionError("Too many parts within pattern.", pattern=pattern)
def to_knx(cls, value: int | float) -> tuple[int]: """Serialize to KNX/IP raw data.""" try: knx_value = int(value) if not cls._test_boundaries(knx_value): raise ValueError if knx_value < 0: knx_value += 0x100 return (knx_value & 0xFF,) except ValueError: raise ConversionError(f"Could not serialize {cls.__name__}", value=value)
def to_knx(self, value: Any) -> DPTBinary: """Convert value to payload.""" if isinstance(value, HVACControllerMode): # foreign operation modes will set the RemoteValue to False return DPTBinary(value == self.controller_mode) raise ConversionError( "value invalid", value=value, device_name=self.device_name, feature_name=self.feature_name, )
def resolve(raw_tpci: int, dst_is_group_address: bool) -> TPCI: """ Return TPCI instance from TPCI command. See KNX Specifications 03_03_04 Transport Layer v01.02.02 AS §2 TPDU """ control = raw_tpci & CONTROL_BIT_MASK numbered = raw_tpci & NUMBERED_BIT_MASK sequence_number = (raw_tpci >> 2) & 0xF if dst_is_group_address: if control or numbered: raise ConversionError("Invalid TPCI flags in group addressed frame.") if not sequence_number: return TDataGroup() if sequence_number == 1: # TDataTagGroup uses sequence number field as flag return TDataTagGroup() if not numbered and sequence_number: raise ConversionError("Sequence number not allowed for unnumbered TPCI") if not control: # data - last 2 bits are part of APCI if numbered: return TDataConnected(sequence_number=sequence_number) return TDataIndividual() # unnumbered control control_flags = raw_tpci & 0b11 if not numbered: if control_flags == 0: return TConnect() if control_flags == 1: return TDisconnect() # numbered control if control_flags == 0b10: return TAck(sequence_number=sequence_number) if control_flags == 0b11: return TNak(sequence_number=sequence_number) raise ConversionError(f"Unknown TPCI {raw_tpci:#10b}.")
def from_knx(cls, raw: tuple[int, ...]) -> HVACOperationMode: """Parse/deserialize from KNX/IP raw data.""" cls.test_bytesarray(raw) if raw[0] & 8 > 0: return HVACOperationMode.FROST_PROTECTION if raw[0] & 4 > 0: return HVACOperationMode.NIGHT if raw[0] & 2 > 0: return HVACOperationMode.STANDBY if raw[0] & 1 > 0: return HVACOperationMode.COMFORT raise ConversionError(f"Payload not supported for {cls.__name__}", raw=raw)
def to_knx(cls, value): """Serialize to KNX/IP raw data.""" try: percent_value = float(value) if not cls._test_boundaries(percent_value): raise ValueError delta = cls.value_max - cls.value_min knx_value = round((percent_value-cls.value_min)/delta*255) return (knx_value,) except ValueError: raise ConversionError("Cant serialize %s" % cls.__name__, value=value)
def from_knx(cls, raw): """Parse/deserialize from KNX/IP raw data.""" cls.test_bytesarray(raw, 1) knx_value = raw[0] delta = cls.value_max - cls.value_min value = round((knx_value/255)*delta) + cls.value_min if not cls._test_boundaries(value): raise ConversionError("Cant parse %s" % cls.__name__, value=value, raw=raw) return value
def to_knx(self, value: "RemoteValueUpDown.Direction") -> DPTBinary: """Convert value to payload.""" if value == self.Direction.UP: return DPTBinary(1) if self.invert else DPTBinary(0) if value == self.Direction.DOWN: return DPTBinary(0) if self.invert else DPTBinary(1) raise ConversionError( "value invalid", value=value, device_name=self.device_name, feature_name=self.feature_name, )
def to_knx(self) -> list[int]: """Serialize to KNX/IP raw data.""" if not isinstance(self.payload, APCI): raise TypeError() if not isinstance(self.src_addr, (GroupAddress, IndividualAddress)): raise ConversionError("src_addr not set") if not isinstance(self.dst_addr, (GroupAddress, IndividualAddress)): raise ConversionError("dst_addr not set") data = [] data.append(self.code.value) data.append(0x00) data.append((self.flags >> 8) & 255) data.append(self.flags & 255) data.extend(self.src_addr.to_knx()) data.extend(self.dst_addr.to_knx()) data.append(self.payload.calculated_length()) data.extend(self.payload.to_knx()) return data
def from_knx(cls, raw): """Parse/deserialize from KNX/IP raw data.""" cls.test_bytesarray(raw, 1) value = raw[0] if not cls._test_boundaries(value): raise ConversionError("Cant parse DPTSceneNumber", value=value, raw=raw) return value
def from_knx(cls, raw): """Parse/deserialize from KNX/IP raw data.""" cls.test_bytesarray(raw) value = raw[0] if not cls._test_boundaries(value): raise ConversionError( "Could not parse %s" % cls.__name__, value=value, raw=raw ) return value
def to_knx(self, value): """Convert value to payload.""" if value == self.Direction.INCREASE: return DPTBinary(0) if self.invert else DPTBinary(1) if value == self.Direction.DECREASE: return DPTBinary(1) if self.invert else DPTBinary(0) raise ConversionError( "value invalid", value=value, device_name=self.device_name, feature_name=self.feature_name, )
def to_knx(self, value): """Convert value to payload.""" if not isinstance(value, (list, tuple)): raise ConversionError( "Could not serialize RemoteValueColorRGB (wrong type)", value=value, type=type(value), ) if len(value) != 3: raise ConversionError( "Could not serialize DPT 232.600 (wrong length)", value=value, type=type(value), ) if (any(not isinstance(color, int) for color in value) or any(color < 0 for color in value) or any(color > 255 for color in value)): raise ConversionError( "Could not serialize DPT 232.600 (wrong bytes)", value=value) return DPTArray(list(value))
def from_knx(cls, raw: tuple[int, ...]) -> int: """Parse/deserialize from KNX/IP raw data.""" cls.test_bytesarray(raw) value = raw[0] if not cls._test_boundaries(value): raise ConversionError(f"Could not parse {cls.__name__}", value=value, raw=raw) return value
def to_knx(cls, value): """Serialize to KNX/IP raw data.""" try: knx_value = int(value) if not cls._test_boundaries(knx_value): raise ValueError if knx_value < 0: knx_value += 0x100 return (knx_value & 0xff, ) except ValueError: raise ConversionError("Cant serialize %s" % cls.__name__, value=value)
def from_knx(cls, raw: Tuple[int, ...]) -> time.struct_time: """Parse/deserialize from KNX/IP raw data.""" cls.test_bytesarray(raw) day = raw[0] & 0x1F month = raw[1] & 0x0F year = raw[2] & 0x7F if not DPTDate._test_range(day, month, year): raise ConversionError("Could not parse DPTDate", raw=raw) if year >= 90: year += 1900 else: year += 2000 try: # strptime conversion used for catching exceptions; filled with default values return time.strptime(f"{year} {month} {day}", "%Y %m %d") except ValueError: raise ConversionError("Could not parse DPTDate", raw=raw)
def to_knx(self, value): """ Convert value (4-6 bytes) to payload (6 bytes). * Structure of DPT 251.600 ** Byte 0: R value ** Byte 1: G value ** Byte 2: B value ** Byte 3: W value ** Byte 4: 0x00 (reserved) ** Byte 5: *** Bit 0: W value valid? *** Bit 1: B value valid? *** Bit 2: G value valid? *** Bit 3: R value valid? *** Bit 4-7: 0 In case we receive * > 6 bytes: error * 6 bytes: all bytes are passed through * 5 bytes: 0x00?? fill up to 6 bytes * 4 bytes: 0x000f right padding to 6 bytes * < 4 bytes: error """ if not isinstance(value, (list, tuple)): raise ConversionError("Cannot serialize RemoteValueColorRGBW (wrong type, expecting list of 4-6 bytes))", value=value, type=type(value)) if len(value) < 4 or len(value) > 6: raise ConversionError("Cannot serialize value to DPT 251.600 (wrong length, expecting list of 4-6 bytes)", value=value, type=type(value)) rgbw = value[:4] if any(not isinstance(color, int) for color in rgbw) \ or any(color < 0 for color in rgbw) \ or any(color > 255 for color in rgbw): raise ConversionError("Cannot serialize DPT 251.600 (wrong RGBW values)", value=value) if len(value) < 5: return DPTArray(list(rgbw) + [0x00, 0x0f]) if len(value) < 6: return DPTArray(list(rgbw) + [0x00] + list(value[4:])) return DPTArray(value)
def to_knx(cls, value: float) -> tuple[int]: """Serialize to KNX/IP raw data.""" try: percent_value = float(value) if not cls._test_boundaries(percent_value): raise ValueError delta = cls.value_max - cls.value_min knx_value = round((percent_value - cls.value_min) / delta * 255) return (knx_value, ) except ValueError: raise ConversionError(f"Could not serialize {cls.__name__}", value=value)
def to_knx(cls, value): """Serialize to KNX/IP raw data.""" try: knx_value = str(value) if not cls._test_boundaries(knx_value): raise ValueError raw = [] for character in knx_value: raw.append(ord(character)) raw.extend([0] * (cls.payload_length - len(raw))) return raw except ValueError: raise ConversionError("Cant serialize %s" % cls.__name__, value=value)
def to_knx(cls, value: Any) -> tuple[int]: """Serialize to KNX/IP raw data.""" # TODO: use Tuple or Named Tuple instead of Dict[str, int] to account for bool control if not isinstance(value, dict): raise ConversionError( "Cant serialize %s; invalid value type" % cls.__name__, value=value ) try: control = bool(value["control"]) step_code = value["step_code"] except KeyError: raise ConversionError( "Cant serialize %s; invalid keys" % cls.__name__, value=value ) if not cls._test_values(step_code): raise ConversionError( "Cant serialize %s; invalid values" % cls.__name__, value=value ) return (cls._encode(control, step_code),)
def to_knx(cls, value): """Serialize to KNX/IP raw data.""" if value == HVACOperationMode.AUTO: return (0, ) if value == HVACOperationMode.COMFORT: return (1, ) if value == HVACOperationMode.STANDBY: return (2, ) if value == HVACOperationMode.NIGHT: return (3, ) if value == HVACOperationMode.FROST_PROTECTION: return (4, ) raise ConversionError("Could not parse HVACOperationMode", value=value)
def to_knx(cls, value: str) -> tuple[int, ...]: """Serialize to KNX/IP raw data.""" try: knx_value = str(value) if not cls._test_boundaries(knx_value): raise ValueError except ValueError: raise ConversionError(f"Could not serialize {cls.__name__}", value=value) # replace invalid characters with question marks raw_bytes = knx_value.encode(cls._encoding, errors="replace") padding = bytes(cls.payload_length - len(raw_bytes)) return tuple(raw_bytes + padding)
def to_knx(cls, value: time.struct_time) -> tuple[int, int, int]: """Serialize to KNX/IP raw data from time.struct_time.""" def _knx_year(year: int) -> int: if 2000 <= year < 2090: return year - 2000 if 1990 <= year < 2000: return year - 1900 raise ConversionError("Could not serialize DPTDate", year=year) if not isinstance(value, time.struct_time): raise ConversionError("Could not serialize DPTDate", value=value) return (value.tm_mday, value.tm_mon, _knx_year(value.tm_year))
def to_knx(cls, value): """Serialize to KNX/IP raw data.""" if value == HVACOperationMode.AUTO: from xknx.exceptions import ConversionError raise ConversionError(value) elif value == HVACOperationMode.COMFORT: return (0x21, ) elif value == HVACOperationMode.STANDBY: return (0x22, ) elif value == HVACOperationMode.NIGHT: return (0x24, ) elif value == HVACOperationMode.FROST_PROTECTION: return (0x28, )
def to_knx(self) -> bytes: """Serialize to KNX/IP raw data.""" if self.count < 0 or self.count > 2**4: raise ConversionError("Count out of range.") payload = struct.pack( "!BBBB", self.object_index, self.property_id, self.count << 4, self.start_index, ) return encode_cmd_and_payload(self.CODE, appended_payload=payload)