# # 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 enum import struct from collections.abc import Sequence from dataclasses import dataclass, field from typing import Any, Optional, Union from aiohomekit.tlv8 import TLVStruct, tlv_entry, u8, u16, u128 from .const import AdditionalParameterTypes @dataclass class ProtocolParams: state_number: int config_number: int advertising_id: bytes broadcast_key: Optional[bytes] class ProtocolParamsTLV(enum.IntEnum): """Protocol params.""" GlobalStateNumber = 0x01 ConfigurationNumber = 0x02 AdvertisingId = 0x03 BroadcastKey = 0x04 class HAP_BLE_PROTOCOL_CONFIGURATION_REQUEST_TLV(enum.IntEnum): GenerateBroadcastEncryptionKey = 0x01 GetAllParams = 0x02 class HAP_BLE_CHARACTERISTIC_CONFIGURATION_REQUEST_TLV(enum.IntEnum): kTLVHAPParamCharacteristicConfigurationProperties = 0x01 kTLVHAPParamCharacteristicConfigurationBroadcastInterval = 0x02 class HAP_TLV(enum.IntEnum): # Additional Parameter Types for BLE (Table 6-9 page 98) kTLVHAPSeparator = 0x00 kTLVHAPParamValue = 0x01 kTLVHAPParamAdditionalAuthorizationData = 0x02 kTLVHAPParamOrigin = 0x03 kTLVHAPParamCharacteristicType = 0x04 kTLVHAPParamCharacteristicInstanceId = 0x05 kTLVHAPParamServiceType = 0x06 kTLVHAPParamServiceInstanceId = 0x07 kTLVHAPParamTTL = 0x08 kTLVHAPParamParamReturnResponse = 0x09 kTLVHAPParamHAPCharacteristicPropertiesDescriptor = 0x0A kTLVHAPParamGATTUserDescriptionDescriptor = 0x0B kTLVHAPParamGATTPresentationFormatDescriptor = 0x0C kTLVHAPParamGATTValidRange = 0x0D kTLVHAPParamHAPStepValueDescriptor = 0x0E kTLVHAPParamHAPServiceProperties = 0x0F kTLVHAPParamHAPLinkedServices = 0x10 kTLVHAPParamHAPValidValuesDescriptor = 0x11 kTLVHAPParamHAPValidValuesRangeDescriptor = 0x12 kTLVHAPParamUnknown_13_Characteristic = 0x13 kTLVHAPParamUnknown_14_Characteristics = 0x14 kTLVHAPParamUnknown_15_Service = 0x15 kTLVHAPParamUnknown_16_Services = 0x16 kTLVHAPParamUnknown_17 = 0x17 kTLVHAPParamUnknown_18 = 0x18 kTLVHAPParamUnknown_19 = 0x19 kTLVHAPParamUnknown_1A_AccessoryInstanceId = 0x1A @dataclass class BleRequest(TLVStruct): expect_response: u8 = tlv_entry(AdditionalParameterTypes.ParamReturnResponse) value: bytes = tlv_entry(AdditionalParameterTypes.Value) DATA_TYPE_STR = { 0x01: "bool", 0x04: "uint8", 0x06: "uint16", 0x08: "uint32", 0x0A: "uint64", 0x10: "int", 0x14: "float", 0x19: "string", 0x1B: "data", } @dataclass class Characteristic(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) service_instance_id: bytes = tlv_entry(HAP_TLV.kTLVHAPParamServiceInstanceId) service_type: bytes = tlv_entry(HAP_TLV.kTLVHAPParamServiceType) user_description: 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(" bool: return self.service_properties and self.service_properties & 0x0001 @property def hidden_service(self) -> bool: return self.service_properties and self.service_properties & 0x0002 @property def supports_configuration(self) -> bool: return self.service_properties and self.service_properties & 0x0004 def to_dict(self): perms = [] if self.hidden_service: perms.append("hd") result = { "perms": perms, } if self.linked_services: result["linked"] = self.linked_services return result