# # Copyright 2022 aiohomekit team # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import struct from collections.abc import Sequence from dataclasses import dataclass, field from typing import Any, Optional, Union from aiohomekit.protocol.tlv import HAP_TLV from aiohomekit.tlv8 import TLVStruct, tlv_entry, u16, u128 @dataclass class Pdu09Characteristic(TLVStruct): # raw value _value: bytes = field(init=False, default=None) type: u128 = tlv_entry(HAP_TLV.kTLVHAPParamCharacteristicType) instance_id: u16 = tlv_entry(HAP_TLV.kTLVHAPParamCharacteristicInstanceId) # permission bits properties: u16 = tlv_entry(HAP_TLV.kTLVHAPParamHAPCharacteristicPropertiesDescriptor) # 7 bytes, contains type & unit presentation_format: bytes = tlv_entry(HAP_TLV.kTLVHAPParamGATTPresentationFormatDescriptor) # min/max, int or float, same type as value valid_range: bytes = tlv_entry(HAP_TLV.kTLVHAPParamGATTValidRange) # int or float, same type as value step_value: bytes = tlv_entry(HAP_TLV.kTLVHAPParamHAPStepValueDescriptor) # list of valid uint8 values valid_values: bytes = tlv_entry(HAP_TLV.kTLVHAPParamHAPValidValuesDescriptor) # list of valid (start, end) uint8 range values valid_values_range: bytes = tlv_entry(HAP_TLV.kTLVHAPParamHAPValidValuesRangeDescriptor) user_descriptor: bytes = tlv_entry(HAP_TLV.kTLVHAPParamGATTUserDescriptionDescriptor) @property def supports_read(self) -> bool: return self.properties & 0x0001 @property def supports_write(self) -> bool: return self.properties & 0x0002 @property def supports_additional_authorization_data(self) -> bool: return self.properties & 0x0004 @property def requires_hap_characteristic_timed_write_procedure(self) -> bool: return self.properties & 0x0008 @property def supports_secure_reads(self) -> bool: return self.properties & 0x0010 @property def supports_secure_writes(self) -> bool: return self.properties & 0x0020 @property def hidden_from_user(self) -> bool: return self.properties & 0x0040 @property def notifies_events_in_connected_state(self) -> bool: return self.properties & 0x0080 @property def notifies_events_in_disconnected_state(self) -> bool: return self.properties & 0x0100 @property def supports_broadcast_notify(self) -> bool: return self.properties & 0x0200 @property def pf_format(self): if self.presentation_format is None: return None return struct.unpack(" Any: if not self.pf_format: return value if self.pf_format == 0x01: val = struct.unpack(" bytes: # if data type is unknown, copy value without modification if not self.pf_format: return value if self.pf_format == 0x01: return b"\x01" if value else b"\x00" if self.pf_format == 0x04: return struct.pack(" Any: if not self.step_value: return None return self._unpack_value(self.step_value) @property def min_max_value(self) -> Optional[tuple[Union[int, float], Union[int, float]]]: if not self.valid_range: return None if self.pf_format == 0x04: return struct.unpack(" list[Pdu09Characteristic]: return [container.characteristic for container in self._characteristics] def find_characteristic_by_iid(self, iid): for characteristic in self.characteristics: if characteristic.instance_id == iid: return characteristic return None def find_characteristic_by_type(self, characteristic_type): for characteristic in self.characteristics: if characteristic.type == characteristic_type: return characteristic return None def to_dict(self): res = { "type": f"{self.type:X}", "iid": self.instance_id, "characteristics": [characteristic.to_dict() for characteristic in self.characteristics], } if self.linked_services: res["linked"] = self.linked_services return res @dataclass class Pdu09ServiceContainer(TLVStruct): service: Pdu09Service = tlv_entry(HAP_TLV.kTLVHAPParamUnknown_15_Service) @dataclass class Pdu09Accessory(TLVStruct): instance_id: u16 = tlv_entry(HAP_TLV.kTLVHAPParamUnknown_1A_AccessoryInstanceId) _services: Sequence[Pdu09ServiceContainer] = tlv_entry(HAP_TLV.kTLVHAPParamUnknown_16_Services) @property def services(self) -> list[Pdu09Service]: return [container.service for container in self._services] def find_characteristic_by_iid(self, iid): for service in self.services: characteristic = service.find_characteristic_by_iid(iid) if characteristic: return characteristic return None def find_service_by_type(self, service_type): for service in self.services: if service.type == service_type: return service return None def find_service_characteristic_by_type(self, service_type, characteristic_type): service = self.find_service_by_type(service_type) if service: return service.find_characteristic_by_type(characteristic_type) return None def to_dict(self): return { "aid": self.instance_id, "services": [service.to_dict() for service in self.services], } @dataclass class Pdu09AccessoryContainer(TLVStruct): accessory: Pdu09Accessory = tlv_entry(HAP_TLV.kTLVHAPParamUnknown_19) @dataclass class Pdu09Database(TLVStruct): _accessories: Sequence[Pdu09AccessoryContainer] = tlv_entry(HAP_TLV.kTLVHAPParamUnknown_18) @property def accessories(self) -> list[Pdu09Accessory]: return [container.accessory for container in self._accessories] def find_characteristic_by_aid_iid(self, aid, iid): for accessory in self.accessories: if accessory.instance_id == aid: return accessory.find_characteristic_by_iid(iid) return None # return first matching iid def find_characteristic_by_iid(self, iid): for accessory in self.accessories: characteristic = accessory.find_characteristic_by_iid(iid) if characteristic: return characteristic return None def to_dict(self): return [accessory.to_dict() for accessory in self.accessories]