1# Copyright 2021-2022 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15# ----------------------------------------------------------------------------- 16# Imports 17# ----------------------------------------------------------------------------- 18from __future__ import annotations 19import dataclasses 20import enum 21import struct 22from typing import List, Optional, Tuple, Union, cast, Dict 23from typing_extensions import Self 24 25from bumble.company_ids import COMPANY_IDENTIFIERS 26from bumble.utils import OpenIntEnum 27 28 29# ----------------------------------------------------------------------------- 30# Constants 31# ----------------------------------------------------------------------------- 32# fmt: off 33 34BT_CENTRAL_ROLE = 0 35BT_PERIPHERAL_ROLE = 1 36 37BT_BR_EDR_TRANSPORT = 0 38BT_LE_TRANSPORT = 1 39 40 41# fmt: on 42 43 44# ----------------------------------------------------------------------------- 45# Utils 46# ----------------------------------------------------------------------------- 47def bit_flags_to_strings(bits, bit_flag_names): 48 names = [] 49 index = 0 50 while bits != 0: 51 if bits & 1: 52 name = bit_flag_names[index] if index < len(bit_flag_names) else f'#{index}' 53 names.append(name) 54 bits >>= 1 55 index += 1 56 57 return names 58 59 60def name_or_number(dictionary: Dict[int, str], number: int, width: int = 2) -> str: 61 name = dictionary.get(number) 62 if name is not None: 63 return name 64 return f'[0x{number:0{width}X}]' 65 66 67def padded_bytes(buffer, size): 68 padding_size = max(size - len(buffer), 0) 69 return buffer + bytes(padding_size) 70 71 72def get_dict_key_by_value(dictionary, value): 73 for key, val in dictionary.items(): 74 if val == value: 75 return key 76 return None 77 78 79# ----------------------------------------------------------------------------- 80# Exceptions 81# ----------------------------------------------------------------------------- 82 83 84class BaseBumbleError(Exception): 85 """Base Error raised by Bumble.""" 86 87 88class BaseError(BaseBumbleError): 89 """Base class for errors with an error code, error name and namespace""" 90 91 def __init__( 92 self, 93 error_code: Optional[int], 94 error_namespace: str = '', 95 error_name: str = '', 96 details: str = '', 97 ): 98 super().__init__() 99 self.error_code = error_code 100 self.error_namespace = error_namespace 101 self.error_name = error_name 102 self.details = details 103 104 def __str__(self): 105 if self.error_namespace: 106 namespace = f'{self.error_namespace}/' 107 else: 108 namespace = '' 109 have_name = self.error_name != '' 110 have_code = self.error_code is not None 111 if have_name and have_code: 112 error_text = f'{self.error_name} [0x{self.error_code:X}]' 113 elif have_name and not have_code: 114 error_text = self.error_name 115 elif not have_name and have_code: 116 error_text = f'0x{self.error_code:X}' 117 else: 118 error_text = '<unspecified>' 119 120 return f'{type(self).__name__}({namespace}{error_text})' 121 122 123class ProtocolError(BaseError): 124 """Protocol Error""" 125 126 127class TimeoutError(BaseBumbleError): # pylint: disable=redefined-builtin 128 """Timeout Error""" 129 130 131class CommandTimeoutError(BaseBumbleError): 132 """Command Timeout Error""" 133 134 135class InvalidStateError(BaseBumbleError): 136 """Invalid State Error""" 137 138 139class InvalidArgumentError(BaseBumbleError, ValueError): 140 """Invalid Argument Error""" 141 142 143class InvalidPacketError(BaseBumbleError, ValueError): 144 """Invalid Packet Error""" 145 146 147class InvalidOperationError(BaseBumbleError, RuntimeError): 148 """Invalid Operation Error""" 149 150 151class NotSupportedError(BaseBumbleError, RuntimeError): 152 """Not Supported""" 153 154 155class OutOfResourcesError(BaseBumbleError, RuntimeError): 156 """Out of Resources Error""" 157 158 159class UnreachableError(BaseBumbleError): 160 """The code path raising this error should be unreachable.""" 161 162 163class ConnectionError(BaseError): # pylint: disable=redefined-builtin 164 """Connection Error""" 165 166 FAILURE = 0x01 167 CONNECTION_REFUSED = 0x02 168 169 def __init__( 170 self, 171 error_code, 172 transport, 173 peer_address, 174 error_namespace='', 175 error_name='', 176 details='', 177 ): 178 super().__init__(error_code, error_namespace, error_name, details) 179 self.transport = transport 180 self.peer_address = peer_address 181 182 183class ConnectionParameterUpdateError(BaseError): 184 """Connection Parameter Update Error""" 185 186 187# ----------------------------------------------------------------------------- 188# UUID 189# 190# NOTE: the internal byte representation is in little-endian byte order 191# 192# Base UUID: 00000000-0000-1000-8000- 00805F9B34FB 193# ----------------------------------------------------------------------------- 194class UUID: 195 ''' 196 See Bluetooth spec Vol 3, Part B - 2.5.1 UUID 197 198 Note that this class expects and works in little-endian byte-order throughout. 199 The exception is when interacting with strings, which are in big-endian byte-order. 200 ''' 201 202 BASE_UUID = bytes.fromhex('00001000800000805F9B34FB')[::-1] # little-endian 203 UUIDS: List[UUID] = [] # Registry of all instances created 204 205 uuid_bytes: bytes 206 name: Optional[str] 207 208 def __init__( 209 self, uuid_str_or_int: Union[str, int], name: Optional[str] = None 210 ) -> None: 211 if isinstance(uuid_str_or_int, int): 212 self.uuid_bytes = struct.pack('<H', uuid_str_or_int) 213 else: 214 if len(uuid_str_or_int) == 36: 215 if ( 216 uuid_str_or_int[8] != '-' 217 or uuid_str_or_int[13] != '-' 218 or uuid_str_or_int[18] != '-' 219 or uuid_str_or_int[23] != '-' 220 ): 221 raise InvalidArgumentError('invalid UUID format') 222 uuid_str = uuid_str_or_int.replace('-', '') 223 else: 224 uuid_str = uuid_str_or_int 225 if len(uuid_str) != 32 and len(uuid_str) != 8 and len(uuid_str) != 4: 226 raise InvalidArgumentError(f"invalid UUID format: {uuid_str}") 227 self.uuid_bytes = bytes(reversed(bytes.fromhex(uuid_str))) 228 self.name = name 229 230 def register(self) -> UUID: 231 # Register this object in the class registry, and update the entry's name if 232 # it wasn't set already 233 for uuid in self.UUIDS: 234 if self == uuid: 235 if uuid.name is None: 236 uuid.name = self.name 237 return uuid 238 239 self.UUIDS.append(self) 240 return self 241 242 @classmethod 243 def from_bytes(cls, uuid_bytes: bytes, name: Optional[str] = None) -> UUID: 244 if len(uuid_bytes) in (2, 4, 16): 245 self = cls.__new__(cls) 246 self.uuid_bytes = uuid_bytes 247 self.name = name 248 249 return self.register() 250 251 raise InvalidArgumentError('only 2, 4 and 16 bytes are allowed') 252 253 @classmethod 254 def from_16_bits(cls, uuid_16: int, name: Optional[str] = None) -> UUID: 255 return cls.from_bytes(struct.pack('<H', uuid_16), name) 256 257 @classmethod 258 def from_32_bits(cls, uuid_32: int, name: Optional[str] = None) -> UUID: 259 return cls.from_bytes(struct.pack('<I', uuid_32), name) 260 261 @classmethod 262 def parse_uuid(cls, uuid_as_bytes: bytes, offset: int) -> Tuple[int, UUID]: 263 return len(uuid_as_bytes), cls.from_bytes(uuid_as_bytes[offset:]) 264 265 @classmethod 266 def parse_uuid_2(cls, uuid_as_bytes: bytes, offset: int) -> Tuple[int, UUID]: 267 return offset + 2, cls.from_bytes(uuid_as_bytes[offset : offset + 2]) 268 269 def to_bytes(self, force_128: bool = False) -> bytes: 270 ''' 271 Serialize UUID in little-endian byte-order 272 ''' 273 if not force_128: 274 return self.uuid_bytes 275 276 if len(self.uuid_bytes) == 2: 277 return self.BASE_UUID + self.uuid_bytes + bytes([0, 0]) 278 elif len(self.uuid_bytes) == 4: 279 return self.BASE_UUID + self.uuid_bytes 280 elif len(self.uuid_bytes) == 16: 281 return self.uuid_bytes 282 else: 283 assert False, "unreachable" 284 285 def to_pdu_bytes(self) -> bytes: 286 ''' 287 Convert to bytes for use in an ATT PDU. 288 According to Vol 3, Part F - 3.2.1 Attribute Type: 289 "All 32-bit Attribute UUIDs shall be converted to 128-bit UUIDs when the 290 Attribute UUID is contained in an ATT PDU." 291 ''' 292 return self.to_bytes(force_128=(len(self.uuid_bytes) == 4)) 293 294 def to_hex_str(self, separator: str = '') -> str: 295 if len(self.uuid_bytes) == 2 or len(self.uuid_bytes) == 4: 296 return bytes(reversed(self.uuid_bytes)).hex().upper() 297 298 return separator.join( 299 [ 300 bytes(reversed(self.uuid_bytes[12:16])).hex(), 301 bytes(reversed(self.uuid_bytes[10:12])).hex(), 302 bytes(reversed(self.uuid_bytes[8:10])).hex(), 303 bytes(reversed(self.uuid_bytes[6:8])).hex(), 304 bytes(reversed(self.uuid_bytes[0:6])).hex(), 305 ] 306 ).upper() 307 308 def __bytes__(self) -> bytes: 309 return self.to_bytes() 310 311 def __eq__(self, other: object) -> bool: 312 if isinstance(other, UUID): 313 return self.to_bytes(force_128=True) == other.to_bytes(force_128=True) 314 315 if isinstance(other, str): 316 return UUID(other) == self 317 318 return False 319 320 def __hash__(self) -> int: 321 return hash(self.uuid_bytes) 322 323 def __str__(self) -> str: 324 result = self.to_hex_str(separator='-') 325 if len(self.uuid_bytes) == 2: 326 result = 'UUID-16:' + result 327 elif len(self.uuid_bytes) == 4: 328 result = 'UUID-32:' + result 329 if self.name is not None: 330 result += f' ({self.name})' 331 return result 332 333 334# ----------------------------------------------------------------------------- 335# Common UUID constants 336# ----------------------------------------------------------------------------- 337# fmt: off 338# pylint: disable=line-too-long 339 340# Protocol Identifiers 341BT_SDP_PROTOCOL_ID = UUID.from_16_bits(0x0001, 'SDP') 342BT_UDP_PROTOCOL_ID = UUID.from_16_bits(0x0002, 'UDP') 343BT_RFCOMM_PROTOCOL_ID = UUID.from_16_bits(0x0003, 'RFCOMM') 344BT_TCP_PROTOCOL_ID = UUID.from_16_bits(0x0004, 'TCP') 345BT_TCS_BIN_PROTOCOL_ID = UUID.from_16_bits(0x0005, 'TCP-BIN') 346BT_TCS_AT_PROTOCOL_ID = UUID.from_16_bits(0x0006, 'TCS-AT') 347BT_ATT_PROTOCOL_ID = UUID.from_16_bits(0x0007, 'ATT') 348BT_OBEX_PROTOCOL_ID = UUID.from_16_bits(0x0008, 'OBEX') 349BT_IP_PROTOCOL_ID = UUID.from_16_bits(0x0009, 'IP') 350BT_FTP_PROTOCOL_ID = UUID.from_16_bits(0x000A, 'FTP') 351BT_HTTP_PROTOCOL_ID = UUID.from_16_bits(0x000C, 'HTTP') 352BT_WSP_PROTOCOL_ID = UUID.from_16_bits(0x000E, 'WSP') 353BT_BNEP_PROTOCOL_ID = UUID.from_16_bits(0x000F, 'BNEP') 354BT_UPNP_PROTOCOL_ID = UUID.from_16_bits(0x0010, 'UPNP') 355BT_HIDP_PROTOCOL_ID = UUID.from_16_bits(0x0011, 'HIDP') 356BT_HARDCOPY_CONTROL_CHANNEL_PROTOCOL_ID = UUID.from_16_bits(0x0012, 'HardcopyControlChannel') 357BT_HARDCOPY_DATA_CHANNEL_PROTOCOL_ID = UUID.from_16_bits(0x0014, 'HardcopyDataChannel') 358BT_HARDCOPY_NOTIFICATION_PROTOCOL_ID = UUID.from_16_bits(0x0016, 'HardcopyNotification') 359BT_AVCTP_PROTOCOL_ID = UUID.from_16_bits(0x0017, 'AVCTP') 360BT_AVDTP_PROTOCOL_ID = UUID.from_16_bits(0x0019, 'AVDTP') 361BT_CMTP_PROTOCOL_ID = UUID.from_16_bits(0x001B, 'CMTP') 362BT_MCAP_CONTROL_CHANNEL_PROTOCOL_ID = UUID.from_16_bits(0x001E, 'MCAPControlChannel') 363BT_MCAP_DATA_CHANNEL_PROTOCOL_ID = UUID.from_16_bits(0x001F, 'MCAPDataChannel') 364BT_L2CAP_PROTOCOL_ID = UUID.from_16_bits(0x0100, 'L2CAP') 365 366# Service Classes and Profiles 367BT_SERVICE_DISCOVERY_SERVER_SERVICE_CLASS_ID_SERVICE = UUID.from_16_bits(0x1000, 'ServiceDiscoveryServerServiceClassID') 368BT_BROWSE_GROUP_DESCRIPTOR_SERVICE_CLASS_ID_SERVICE = UUID.from_16_bits(0x1001, 'BrowseGroupDescriptorServiceClassID') 369BT_SERIAL_PORT_SERVICE = UUID.from_16_bits(0x1101, 'SerialPort') 370BT_LAN_ACCESS_USING_PPP_SERVICE = UUID.from_16_bits(0x1102, 'LANAccessUsingPPP') 371BT_DIALUP_NETWORKING_SERVICE = UUID.from_16_bits(0x1103, 'DialupNetworking') 372BT_IR_MCSYNC_SERVICE = UUID.from_16_bits(0x1104, 'IrMCSync') 373BT_OBEX_OBJECT_PUSH_SERVICE = UUID.from_16_bits(0x1105, 'OBEXObjectPush') 374BT_OBEX_FILE_TRANSFER_SERVICE = UUID.from_16_bits(0x1106, 'OBEXFileTransfer') 375BT_IR_MCSYNC_COMMAND_SERVICE = UUID.from_16_bits(0x1107, 'IrMCSyncCommand') 376BT_HEADSET_SERVICE = UUID.from_16_bits(0x1108, 'Headset') 377BT_CORDLESS_TELEPHONY_SERVICE = UUID.from_16_bits(0x1109, 'CordlessTelephony') 378BT_AUDIO_SOURCE_SERVICE = UUID.from_16_bits(0x110A, 'AudioSource') 379BT_AUDIO_SINK_SERVICE = UUID.from_16_bits(0x110B, 'AudioSink') 380BT_AV_REMOTE_CONTROL_TARGET_SERVICE = UUID.from_16_bits(0x110C, 'A/V_RemoteControlTarget') 381BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE = UUID.from_16_bits(0x110D, 'AdvancedAudioDistribution') 382BT_AV_REMOTE_CONTROL_SERVICE = UUID.from_16_bits(0x110E, 'A/V_RemoteControl') 383BT_AV_REMOTE_CONTROL_CONTROLLER_SERVICE = UUID.from_16_bits(0x110F, 'A/V_RemoteControlController') 384BT_INTERCOM_SERVICE = UUID.from_16_bits(0x1110, 'Intercom') 385BT_FAX_SERVICE = UUID.from_16_bits(0x1111, 'Fax') 386BT_HEADSET_AUDIO_GATEWAY_SERVICE = UUID.from_16_bits(0x1112, 'Headset - Audio Gateway') 387BT_WAP_SERVICE = UUID.from_16_bits(0x1113, 'WAP') 388BT_WAP_CLIENT_SERVICE = UUID.from_16_bits(0x1114, 'WAP_CLIENT') 389BT_PANU_SERVICE = UUID.from_16_bits(0x1115, 'PANU') 390BT_NAP_SERVICE = UUID.from_16_bits(0x1116, 'NAP') 391BT_GN_SERVICE = UUID.from_16_bits(0x1117, 'GN') 392BT_DIRECT_PRINTING_SERVICE = UUID.from_16_bits(0x1118, 'DirectPrinting') 393BT_REFERENCE_PRINTING_SERVICE = UUID.from_16_bits(0x1119, 'ReferencePrinting') 394BT_BASIC_IMAGING_PROFILE_SERVICE = UUID.from_16_bits(0x111A, 'Basic Imaging Profile') 395BT_IMAGING_RESPONDER_SERVICE = UUID.from_16_bits(0x111B, 'ImagingResponder') 396BT_IMAGING_AUTOMATIC_ARCHIVE_SERVICE = UUID.from_16_bits(0x111C, 'ImagingAutomaticArchive') 397BT_IMAGING_REFERENCED_OBJECTS_SERVICE = UUID.from_16_bits(0x111D, 'ImagingReferencedObjects') 398BT_HANDSFREE_SERVICE = UUID.from_16_bits(0x111E, 'Handsfree') 399BT_HANDSFREE_AUDIO_GATEWAY_SERVICE = UUID.from_16_bits(0x111F, 'HandsfreeAudioGateway') 400BT_DIRECT_PRINTING_REFERENCE_OBJECTS_SERVICE = UUID.from_16_bits(0x1120, 'DirectPrintingReferenceObjectsService') 401BT_REFLECTED_UI_SERVICE = UUID.from_16_bits(0x1121, 'ReflectedUI') 402BT_BASIC_PRINTING_SERVICE = UUID.from_16_bits(0x1122, 'BasicPrinting') 403BT_PRINTING_STATUS_SERVICE = UUID.from_16_bits(0x1123, 'PrintingStatus') 404BT_HUMAN_INTERFACE_DEVICE_SERVICE = UUID.from_16_bits(0x1124, 'HumanInterfaceDeviceService') 405BT_HARDCOPY_CABLE_REPLACEMENT_SERVICE = UUID.from_16_bits(0x1125, 'HardcopyCableReplacement') 406BT_HCR_PRINT_SERVICE = UUID.from_16_bits(0x1126, 'HCR_Print') 407BT_HCR_SCAN_SERVICE = UUID.from_16_bits(0x1127, 'HCR_Scan') 408BT_COMMON_ISDN_ACCESS_SERVICE = UUID.from_16_bits(0x1128, 'Common_ISDN_Access') 409BT_SIM_ACCESS_SERVICE = UUID.from_16_bits(0x112D, 'SIM_Access') 410BT_PHONEBOOK_ACCESS_PCE_SERVICE = UUID.from_16_bits(0x112E, 'Phonebook Access - PCE') 411BT_PHONEBOOK_ACCESS_PSE_SERVICE = UUID.from_16_bits(0x112F, 'Phonebook Access - PSE') 412BT_PHONEBOOK_ACCESS_SERVICE = UUID.from_16_bits(0x1130, 'Phonebook Access') 413BT_HEADSET_HS_SERVICE = UUID.from_16_bits(0x1131, 'Headset - HS') 414BT_MESSAGE_ACCESS_SERVER_SERVICE = UUID.from_16_bits(0x1132, 'Message Access Server') 415BT_MESSAGE_NOTIFICATION_SERVER_SERVICE = UUID.from_16_bits(0x1133, 'Message Notification Server') 416BT_MESSAGE_ACCESS_PROFILE_SERVICE = UUID.from_16_bits(0x1134, 'Message Access Profile') 417BT_GNSS_SERVICE = UUID.from_16_bits(0x1135, 'GNSS') 418BT_GNSS_SERVER_SERVICE = UUID.from_16_bits(0x1136, 'GNSS_Server') 419BT_3D_DISPLAY_SERVICE = UUID.from_16_bits(0x1137, '3D Display') 420BT_3D_GLASSES_SERVICE = UUID.from_16_bits(0x1138, '3D Glasses') 421BT_3D_SYNCHRONIZATION_SERVICE = UUID.from_16_bits(0x1139, '3D Synchronization') 422BT_MPS_PROFILE_SERVICE = UUID.from_16_bits(0x113A, 'MPS Profile') 423BT_MPS_SC_SERVICE = UUID.from_16_bits(0x113B, 'MPS SC') 424BT_ACCESS_SERVICE_SERVICE = UUID.from_16_bits(0x113C, 'CTN Access Service') 425BT_CTN_NOTIFICATION_SERVICE_SERVICE = UUID.from_16_bits(0x113D, 'CTN Notification Service') 426BT_CTN_PROFILE_SERVICE = UUID.from_16_bits(0x113E, 'CTN Profile') 427BT_PNP_INFORMATION_SERVICE = UUID.from_16_bits(0x1200, 'PnPInformation') 428BT_GENERIC_NETWORKING_SERVICE = UUID.from_16_bits(0x1201, 'GenericNetworking') 429BT_GENERIC_FILE_TRANSFER_SERVICE = UUID.from_16_bits(0x1202, 'GenericFileTransfer') 430BT_GENERIC_AUDIO_SERVICE = UUID.from_16_bits(0x1203, 'GenericAudio') 431BT_GENERIC_TELEPHONY_SERVICE = UUID.from_16_bits(0x1204, 'GenericTelephony') 432BT_UPNP_SERVICE = UUID.from_16_bits(0x1205, 'UPNP_Service') 433BT_UPNP_IP_SERVICE = UUID.from_16_bits(0x1206, 'UPNP_IP_Service') 434BT_ESDP_UPNP_IP_PAN_SERVICE = UUID.from_16_bits(0x1300, 'ESDP_UPNP_IP_PAN') 435BT_ESDP_UPNP_IP_LAP_SERVICE = UUID.from_16_bits(0x1301, 'ESDP_UPNP_IP_LAP') 436BT_ESDP_UPNP_L2CAP_SERVICE = UUID.from_16_bits(0x1302, 'ESDP_UPNP_L2CAP') 437BT_VIDEO_SOURCE_SERVICE = UUID.from_16_bits(0x1303, 'VideoSource') 438BT_VIDEO_SINK_SERVICE = UUID.from_16_bits(0x1304, 'VideoSink') 439BT_VIDEO_DISTRIBUTION_SERVICE = UUID.from_16_bits(0x1305, 'VideoDistribution') 440BT_HDP_SERVICE = UUID.from_16_bits(0x1400, 'HDP') 441BT_HDP_SOURCE_SERVICE = UUID.from_16_bits(0x1401, 'HDP Source') 442BT_HDP_SINK_SERVICE = UUID.from_16_bits(0x1402, 'HDP Sink') 443 444# fmt: on 445# pylint: enable=line-too-long 446 447 448# ----------------------------------------------------------------------------- 449# DeviceClass 450# ----------------------------------------------------------------------------- 451class DeviceClass: 452 # fmt: off 453 # pylint: disable=line-too-long 454 455 # Major Service Classes (flags combined with OR) 456 LIMITED_DISCOVERABLE_MODE_SERVICE_CLASS = (1 << 0) 457 LE_AUDIO_SERVICE_CLASS = (1 << 1) 458 RESERVED = (1 << 2) 459 POSITIONING_SERVICE_CLASS = (1 << 3) 460 NETWORKING_SERVICE_CLASS = (1 << 4) 461 RENDERING_SERVICE_CLASS = (1 << 5) 462 CAPTURING_SERVICE_CLASS = (1 << 6) 463 OBJECT_TRANSFER_SERVICE_CLASS = (1 << 7) 464 AUDIO_SERVICE_CLASS = (1 << 8) 465 TELEPHONY_SERVICE_CLASS = (1 << 9) 466 INFORMATION_SERVICE_CLASS = (1 << 10) 467 468 SERVICE_CLASS_LABELS = [ 469 'Limited Discoverable Mode', 470 'LE audio', 471 '(reserved)', 472 'Positioning', 473 'Networking', 474 'Rendering', 475 'Capturing', 476 'Object Transfer', 477 'Audio', 478 'Telephony', 479 'Information' 480 ] 481 482 # Major Device Classes 483 MISCELLANEOUS_MAJOR_DEVICE_CLASS = 0x00 484 COMPUTER_MAJOR_DEVICE_CLASS = 0x01 485 PHONE_MAJOR_DEVICE_CLASS = 0x02 486 LAN_NETWORK_ACCESS_POINT_MAJOR_DEVICE_CLASS = 0x03 487 AUDIO_VIDEO_MAJOR_DEVICE_CLASS = 0x04 488 PERIPHERAL_MAJOR_DEVICE_CLASS = 0x05 489 IMAGING_MAJOR_DEVICE_CLASS = 0x06 490 WEARABLE_MAJOR_DEVICE_CLASS = 0x07 491 TOY_MAJOR_DEVICE_CLASS = 0x08 492 HEALTH_MAJOR_DEVICE_CLASS = 0x09 493 UNCATEGORIZED_MAJOR_DEVICE_CLASS = 0x1F 494 495 MAJOR_DEVICE_CLASS_NAMES = { 496 MISCELLANEOUS_MAJOR_DEVICE_CLASS: 'Miscellaneous', 497 COMPUTER_MAJOR_DEVICE_CLASS: 'Computer', 498 PHONE_MAJOR_DEVICE_CLASS: 'Phone', 499 LAN_NETWORK_ACCESS_POINT_MAJOR_DEVICE_CLASS: 'LAN/Network Access Point', 500 AUDIO_VIDEO_MAJOR_DEVICE_CLASS: 'Audio/Video', 501 PERIPHERAL_MAJOR_DEVICE_CLASS: 'Peripheral', 502 IMAGING_MAJOR_DEVICE_CLASS: 'Imaging', 503 WEARABLE_MAJOR_DEVICE_CLASS: 'Wearable', 504 TOY_MAJOR_DEVICE_CLASS: 'Toy', 505 HEALTH_MAJOR_DEVICE_CLASS: 'Health', 506 UNCATEGORIZED_MAJOR_DEVICE_CLASS: 'Uncategorized' 507 } 508 509 COMPUTER_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00 510 COMPUTER_DESKTOP_WORKSTATION_MINOR_DEVICE_CLASS = 0x01 511 COMPUTER_SERVER_CLASS_COMPUTER_MINOR_DEVICE_CLASS = 0x02 512 COMPUTER_LAPTOP_COMPUTER_MINOR_DEVICE_CLASS = 0x03 513 COMPUTER_HANDHELD_PC_PDA_MINOR_DEVICE_CLASS = 0x04 514 COMPUTER_PALM_SIZE_PC_PDA_MINOR_DEVICE_CLASS = 0x05 515 COMPUTER_WEARABLE_COMPUTER_MINOR_DEVICE_CLASS = 0x06 516 COMPUTER_TABLET_MINOR_DEVICE_CLASS = 0x07 517 518 COMPUTER_MINOR_DEVICE_CLASS_NAMES = { 519 COMPUTER_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized', 520 COMPUTER_DESKTOP_WORKSTATION_MINOR_DEVICE_CLASS: 'Desktop workstation', 521 COMPUTER_SERVER_CLASS_COMPUTER_MINOR_DEVICE_CLASS: 'Server-class computer', 522 COMPUTER_LAPTOP_COMPUTER_MINOR_DEVICE_CLASS: 'Laptop', 523 COMPUTER_HANDHELD_PC_PDA_MINOR_DEVICE_CLASS: 'Handheld PC/PDA', 524 COMPUTER_PALM_SIZE_PC_PDA_MINOR_DEVICE_CLASS: 'Palm-size PC/PDA', 525 COMPUTER_WEARABLE_COMPUTER_MINOR_DEVICE_CLASS: 'Wearable computer', 526 COMPUTER_TABLET_MINOR_DEVICE_CLASS: 'Tablet' 527 } 528 529 PHONE_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00 530 PHONE_CELLULAR_MINOR_DEVICE_CLASS = 0x01 531 PHONE_CORDLESS_MINOR_DEVICE_CLASS = 0x02 532 PHONE_SMARTPHONE_MINOR_DEVICE_CLASS = 0x03 533 PHONE_WIRED_MODEM_OR_VOICE_GATEWAY_MINOR_DEVICE_CLASS = 0x04 534 PHONE_COMMON_ISDN_MINOR_DEVICE_CLASS = 0x05 535 536 PHONE_MINOR_DEVICE_CLASS_NAMES = { 537 PHONE_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized', 538 PHONE_CELLULAR_MINOR_DEVICE_CLASS: 'Cellular', 539 PHONE_CORDLESS_MINOR_DEVICE_CLASS: 'Cordless', 540 PHONE_SMARTPHONE_MINOR_DEVICE_CLASS: 'Smartphone', 541 PHONE_WIRED_MODEM_OR_VOICE_GATEWAY_MINOR_DEVICE_CLASS: 'Wired modem or voice gateway', 542 PHONE_COMMON_ISDN_MINOR_DEVICE_CLASS: 'Common ISDN access' 543 } 544 545 AUDIO_VIDEO_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00 546 AUDIO_VIDEO_WEARABLE_HEADSET_DEVICE_MINOR_DEVICE_CLASS = 0x01 547 AUDIO_VIDEO_HANDS_FREE_DEVICE_MINOR_DEVICE_CLASS = 0x02 548 # (RESERVED) = 0x03 549 AUDIO_VIDEO_MICROPHONE_MINOR_DEVICE_CLASS = 0x04 550 AUDIO_VIDEO_LOUDSPEAKER_MINOR_DEVICE_CLASS = 0x05 551 AUDIO_VIDEO_HEADPHONES_MINOR_DEVICE_CLASS = 0x06 552 AUDIO_VIDEO_PORTABLE_AUDIO_MINOR_DEVICE_CLASS = 0x07 553 AUDIO_VIDEO_CAR_AUDIO_MINOR_DEVICE_CLASS = 0x08 554 AUDIO_VIDEO_SET_TOP_BOX_MINOR_DEVICE_CLASS = 0x09 555 AUDIO_VIDEO_HIFI_AUDIO_DEVICE_MINOR_DEVICE_CLASS = 0x0A 556 AUDIO_VIDEO_VCR_MINOR_DEVICE_CLASS = 0x0B 557 AUDIO_VIDEO_VIDEO_CAMERA_MINOR_DEVICE_CLASS = 0x0C 558 AUDIO_VIDEO_CAMCORDER_MINOR_DEVICE_CLASS = 0x0D 559 AUDIO_VIDEO_VIDEO_MONITOR_MINOR_DEVICE_CLASS = 0x0E 560 AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER_MINOR_DEVICE_CLASS = 0x0F 561 AUDIO_VIDEO_VIDEO_CONFERENCING_MINOR_DEVICE_CLASS = 0x10 562 # (RESERVED) = 0x11 563 AUDIO_VIDEO_GAMING_OR_TOY_MINOR_DEVICE_CLASS = 0x12 564 565 AUDIO_VIDEO_MINOR_DEVICE_CLASS_NAMES = { 566 AUDIO_VIDEO_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized', 567 AUDIO_VIDEO_WEARABLE_HEADSET_DEVICE_MINOR_DEVICE_CLASS: 'Wearable Headset Device', 568 AUDIO_VIDEO_HANDS_FREE_DEVICE_MINOR_DEVICE_CLASS: 'Hands-free Device', 569 AUDIO_VIDEO_MICROPHONE_MINOR_DEVICE_CLASS: 'Microphone', 570 AUDIO_VIDEO_LOUDSPEAKER_MINOR_DEVICE_CLASS: 'Loudspeaker', 571 AUDIO_VIDEO_HEADPHONES_MINOR_DEVICE_CLASS: 'Headphones', 572 AUDIO_VIDEO_PORTABLE_AUDIO_MINOR_DEVICE_CLASS: 'Portable Audio', 573 AUDIO_VIDEO_CAR_AUDIO_MINOR_DEVICE_CLASS: 'Car audio', 574 AUDIO_VIDEO_SET_TOP_BOX_MINOR_DEVICE_CLASS: 'Set-top box', 575 AUDIO_VIDEO_HIFI_AUDIO_DEVICE_MINOR_DEVICE_CLASS: 'HiFi Audio Device', 576 AUDIO_VIDEO_VCR_MINOR_DEVICE_CLASS: 'VCR', 577 AUDIO_VIDEO_VIDEO_CAMERA_MINOR_DEVICE_CLASS: 'Video Camera', 578 AUDIO_VIDEO_CAMCORDER_MINOR_DEVICE_CLASS: 'Camcorder', 579 AUDIO_VIDEO_VIDEO_MONITOR_MINOR_DEVICE_CLASS: 'Video Monitor', 580 AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER_MINOR_DEVICE_CLASS: 'Video Display and Loudspeaker', 581 AUDIO_VIDEO_VIDEO_CONFERENCING_MINOR_DEVICE_CLASS: 'Video Conferencing', 582 AUDIO_VIDEO_GAMING_OR_TOY_MINOR_DEVICE_CLASS: 'Gaming/Toy' 583 } 584 585 PERIPHERAL_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00 586 PERIPHERAL_KEYBOARD_MINOR_DEVICE_CLASS = 0x10 587 PERIPHERAL_POINTING_DEVICE_MINOR_DEVICE_CLASS = 0x20 588 PERIPHERAL_COMBO_KEYBOARD_POINTING_DEVICE_MINOR_DEVICE_CLASS = 0x30 589 PERIPHERAL_JOYSTICK_MINOR_DEVICE_CLASS = 0x01 590 PERIPHERAL_GAMEPAD_MINOR_DEVICE_CLASS = 0x02 591 PERIPHERAL_REMOTE_CONTROL_MINOR_DEVICE_CLASS = 0x03 592 PERIPHERAL_SENSING_DEVICE_MINOR_DEVICE_CLASS = 0x04 593 PERIPHERAL_DIGITIZER_TABLET_MINOR_DEVICE_CLASS = 0x05 594 PERIPHERAL_CARD_READER_MINOR_DEVICE_CLASS = 0x06 595 PERIPHERAL_DIGITAL_PEN_MINOR_DEVICE_CLASS = 0x07 596 PERIPHERAL_HANDHELD_SCANNER_MINOR_DEVICE_CLASS = 0x08 597 PERIPHERAL_HANDHELD_GESTURAL_INPUT_DEVICE_MINOR_DEVICE_CLASS = 0x09 598 599 PERIPHERAL_MINOR_DEVICE_CLASS_NAMES = { 600 PERIPHERAL_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized', 601 PERIPHERAL_KEYBOARD_MINOR_DEVICE_CLASS: 'Keyboard', 602 PERIPHERAL_POINTING_DEVICE_MINOR_DEVICE_CLASS: 'Pointing device', 603 PERIPHERAL_COMBO_KEYBOARD_POINTING_DEVICE_MINOR_DEVICE_CLASS: 'Combo keyboard/pointing device', 604 PERIPHERAL_JOYSTICK_MINOR_DEVICE_CLASS: 'Joystick', 605 PERIPHERAL_GAMEPAD_MINOR_DEVICE_CLASS: 'Gamepad', 606 PERIPHERAL_REMOTE_CONTROL_MINOR_DEVICE_CLASS: 'Remote control', 607 PERIPHERAL_SENSING_DEVICE_MINOR_DEVICE_CLASS: 'Sensing device', 608 PERIPHERAL_DIGITIZER_TABLET_MINOR_DEVICE_CLASS: 'Digitizer tablet', 609 PERIPHERAL_CARD_READER_MINOR_DEVICE_CLASS: 'Card Reader', 610 PERIPHERAL_DIGITAL_PEN_MINOR_DEVICE_CLASS: 'Digital Pen', 611 PERIPHERAL_HANDHELD_SCANNER_MINOR_DEVICE_CLASS: 'Handheld scanner', 612 PERIPHERAL_HANDHELD_GESTURAL_INPUT_DEVICE_MINOR_DEVICE_CLASS: 'Handheld gestural input device' 613 } 614 615 WEARABLE_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00 616 WEARABLE_WRISTWATCH_MINOR_DEVICE_CLASS = 0x01 617 WEARABLE_PAGER_MINOR_DEVICE_CLASS = 0x02 618 WEARABLE_JACKET_MINOR_DEVICE_CLASS = 0x03 619 WEARABLE_HELMET_MINOR_DEVICE_CLASS = 0x04 620 WEARABLE_GLASSES_MINOR_DEVICE_CLASS = 0x05 621 622 WEARABLE_MINOR_DEVICE_CLASS_NAMES = { 623 WEARABLE_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized', 624 WEARABLE_WRISTWATCH_MINOR_DEVICE_CLASS: 'Wristwatch', 625 WEARABLE_PAGER_MINOR_DEVICE_CLASS: 'Pager', 626 WEARABLE_JACKET_MINOR_DEVICE_CLASS: 'Jacket', 627 WEARABLE_HELMET_MINOR_DEVICE_CLASS: 'Helmet', 628 WEARABLE_GLASSES_MINOR_DEVICE_CLASS: 'Glasses', 629 } 630 631 TOY_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00 632 TOY_ROBOT_MINOR_DEVICE_CLASS = 0x01 633 TOY_VEHICLE_MINOR_DEVICE_CLASS = 0x02 634 TOY_DOLL_ACTION_FIGURE_MINOR_DEVICE_CLASS = 0x03 635 TOY_CONTROLLER_MINOR_DEVICE_CLASS = 0x04 636 TOY_GAME_MINOR_DEVICE_CLASS = 0x05 637 638 TOY_MINOR_DEVICE_CLASS_NAMES = { 639 TOY_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized', 640 TOY_ROBOT_MINOR_DEVICE_CLASS: 'Robot', 641 TOY_VEHICLE_MINOR_DEVICE_CLASS: 'Vehicle', 642 TOY_DOLL_ACTION_FIGURE_MINOR_DEVICE_CLASS: 'Doll/Action figure', 643 TOY_CONTROLLER_MINOR_DEVICE_CLASS: 'Controller', 644 TOY_GAME_MINOR_DEVICE_CLASS: 'Game', 645 } 646 647 HEALTH_UNDEFINED_MINOR_DEVICE_CLASS = 0x00 648 HEALTH_BLOOD_PRESSURE_MONITOR_MINOR_DEVICE_CLASS = 0x01 649 HEALTH_THERMOMETER_MINOR_DEVICE_CLASS = 0x02 650 HEALTH_WEIGHING_SCALE_MINOR_DEVICE_CLASS = 0x03 651 HEALTH_GLUCOSE_METER_MINOR_DEVICE_CLASS = 0x04 652 HEALTH_PULSE_OXIMETER_MINOR_DEVICE_CLASS = 0x05 653 HEALTH_HEART_PULSE_RATE_MONITOR_MINOR_DEVICE_CLASS = 0x06 654 HEALTH_HEALTH_DATA_DISPLAY_MINOR_DEVICE_CLASS = 0x07 655 HEALTH_STEP_COUNTER_MINOR_DEVICE_CLASS = 0x08 656 HEALTH_BODY_COMPOSITION_ANALYZER_MINOR_DEVICE_CLASS = 0x09 657 HEALTH_PEAK_FLOW_MONITOR_MINOR_DEVICE_CLASS = 0x0A 658 HEALTH_MEDICATION_MONITOR_MINOR_DEVICE_CLASS = 0x0B 659 HEALTH_KNEE_PROSTHESIS_MINOR_DEVICE_CLASS = 0x0C 660 HEALTH_ANKLE_PROSTHESIS_MINOR_DEVICE_CLASS = 0x0D 661 HEALTH_GENERIC_HEALTH_MANAGER_MINOR_DEVICE_CLASS = 0x0E 662 HEALTH_PERSONAL_MOBILITY_DEVICE_MINOR_DEVICE_CLASS = 0x0F 663 664 HEALTH_MINOR_DEVICE_CLASS_NAMES = { 665 HEALTH_UNDEFINED_MINOR_DEVICE_CLASS: 'Undefined', 666 HEALTH_BLOOD_PRESSURE_MONITOR_MINOR_DEVICE_CLASS: 'Blood Pressure Monitor', 667 HEALTH_THERMOMETER_MINOR_DEVICE_CLASS: 'Thermometer', 668 HEALTH_WEIGHING_SCALE_MINOR_DEVICE_CLASS: 'Weighing Scale', 669 HEALTH_GLUCOSE_METER_MINOR_DEVICE_CLASS: 'Glucose Meter', 670 HEALTH_PULSE_OXIMETER_MINOR_DEVICE_CLASS: 'Pulse Oximeter', 671 HEALTH_HEART_PULSE_RATE_MONITOR_MINOR_DEVICE_CLASS: 'Heart/Pulse Rate Monitor', 672 HEALTH_HEALTH_DATA_DISPLAY_MINOR_DEVICE_CLASS: 'Health Data Display', 673 HEALTH_STEP_COUNTER_MINOR_DEVICE_CLASS: 'Step Counter', 674 HEALTH_BODY_COMPOSITION_ANALYZER_MINOR_DEVICE_CLASS: 'Body Composition Analyzer', 675 HEALTH_PEAK_FLOW_MONITOR_MINOR_DEVICE_CLASS: 'Peak Flow Monitor', 676 HEALTH_MEDICATION_MONITOR_MINOR_DEVICE_CLASS: 'Medication Monitor', 677 HEALTH_KNEE_PROSTHESIS_MINOR_DEVICE_CLASS: 'Knee Prosthesis', 678 HEALTH_ANKLE_PROSTHESIS_MINOR_DEVICE_CLASS: 'Ankle Prosthesis', 679 HEALTH_GENERIC_HEALTH_MANAGER_MINOR_DEVICE_CLASS: 'Generic Health Manager', 680 HEALTH_PERSONAL_MOBILITY_DEVICE_MINOR_DEVICE_CLASS: 'Personal Mobility Device', 681 } 682 683 MINOR_DEVICE_CLASS_NAMES = { 684 COMPUTER_MAJOR_DEVICE_CLASS: COMPUTER_MINOR_DEVICE_CLASS_NAMES, 685 PHONE_MAJOR_DEVICE_CLASS: PHONE_MINOR_DEVICE_CLASS_NAMES, 686 AUDIO_VIDEO_MAJOR_DEVICE_CLASS: AUDIO_VIDEO_MINOR_DEVICE_CLASS_NAMES, 687 PERIPHERAL_MAJOR_DEVICE_CLASS: PERIPHERAL_MINOR_DEVICE_CLASS_NAMES, 688 WEARABLE_MAJOR_DEVICE_CLASS: WEARABLE_MINOR_DEVICE_CLASS_NAMES, 689 TOY_MAJOR_DEVICE_CLASS: TOY_MINOR_DEVICE_CLASS_NAMES, 690 HEALTH_MAJOR_DEVICE_CLASS: HEALTH_MINOR_DEVICE_CLASS_NAMES, 691 } 692 693 # fmt: on 694 # pylint: enable=line-too-long 695 696 @staticmethod 697 def split_class_of_device(class_of_device): 698 # Split the bit fields of the composite class of device value into: 699 # (service_classes, major_device_class, minor_device_class) 700 return ( 701 (class_of_device >> 13 & 0x7FF), 702 (class_of_device >> 8 & 0x1F), 703 (class_of_device >> 2 & 0x3F), 704 ) 705 706 @staticmethod 707 def pack_class_of_device(service_classes, major_device_class, minor_device_class): 708 return service_classes << 13 | major_device_class << 8 | minor_device_class << 2 709 710 @staticmethod 711 def service_class_labels(service_class_flags): 712 return bit_flags_to_strings( 713 service_class_flags, DeviceClass.SERVICE_CLASS_LABELS 714 ) 715 716 @staticmethod 717 def major_device_class_name(device_class): 718 return name_or_number(DeviceClass.MAJOR_DEVICE_CLASS_NAMES, device_class) 719 720 @staticmethod 721 def minor_device_class_name(major_device_class, minor_device_class): 722 class_names = DeviceClass.MINOR_DEVICE_CLASS_NAMES.get(major_device_class) 723 if class_names is None: 724 return f'#{minor_device_class:02X}' 725 return name_or_number(class_names, minor_device_class) 726 727 728# ----------------------------------------------------------------------------- 729# Appearance 730# ----------------------------------------------------------------------------- 731class Appearance: 732 class Category(OpenIntEnum): 733 UNKNOWN = 0x0000 734 PHONE = 0x0001 735 COMPUTER = 0x0002 736 WATCH = 0x0003 737 CLOCK = 0x0004 738 DISPLAY = 0x0005 739 REMOTE_CONTROL = 0x0006 740 EYE_GLASSES = 0x0007 741 TAG = 0x0008 742 KEYRING = 0x0009 743 MEDIA_PLAYER = 0x000A 744 BARCODE_SCANNER = 0x000B 745 THERMOMETER = 0x000C 746 HEART_RATE_SENSOR = 0x000D 747 BLOOD_PRESSURE = 0x000E 748 HUMAN_INTERFACE_DEVICE = 0x000F 749 GLUCOSE_METER = 0x0010 750 RUNNING_WALKING_SENSOR = 0x0011 751 CYCLING = 0x0012 752 CONTROL_DEVICE = 0x0013 753 NETWORK_DEVICE = 0x0014 754 SENSOR = 0x0015 755 LIGHT_FIXTURES = 0x0016 756 FAN = 0x0017 757 HVAC = 0x0018 758 AIR_CONDITIONING = 0x0019 759 HUMIDIFIER = 0x001A 760 HEATING = 0x001B 761 ACCESS_CONTROL = 0x001C 762 MOTORIZED_DEVICE = 0x001D 763 POWER_DEVICE = 0x001E 764 LIGHT_SOURCE = 0x001F 765 WINDOW_COVERING = 0x0020 766 AUDIO_SINK = 0x0021 767 AUDIO_SOURCE = 0x0022 768 MOTORIZED_VEHICLE = 0x0023 769 DOMESTIC_APPLIANCE = 0x0024 770 WEARABLE_AUDIO_DEVICE = 0x0025 771 AIRCRAFT = 0x0026 772 AV_EQUIPMENT = 0x0027 773 DISPLAY_EQUIPMENT = 0x0028 774 HEARING_AID = 0x0029 775 GAMING = 0x002A 776 SIGNAGE = 0x002B 777 PULSE_OXIMETER = 0x0031 778 WEIGHT_SCALE = 0x0032 779 PERSONAL_MOBILITY_DEVICE = 0x0033 780 CONTINUOUS_GLUCOSE_MONITOR = 0x0034 781 INSULIN_PUMP = 0x0035 782 MEDICATION_DELIVERY = 0x0036 783 SPIROMETER = 0x0037 784 OUTDOOR_SPORTS_ACTIVITY = 0x0051 785 786 class UnknownSubcategory(OpenIntEnum): 787 GENERIC_UNKNOWN = 0x00 788 789 class PhoneSubcategory(OpenIntEnum): 790 GENERIC_PHONE = 0x00 791 792 class ComputerSubcategory(OpenIntEnum): 793 GENERIC_COMPUTER = 0x00 794 DESKTOP_WORKSTATION = 0x01 795 SERVER_CLASS_COMPUTER = 0x02 796 LAPTOP = 0x03 797 HANDHELD_PC_PDA = 0x04 798 PALM_SIZE_PC_PDA = 0x05 799 WEARABLE_COMPUTER = 0x06 800 TABLET = 0x07 801 DOCKING_STATION = 0x08 802 ALL_IN_ONE = 0x09 803 BLADE_SERVER = 0x0A 804 CONVERTIBLE = 0x0B 805 DETACHABLE = 0x0C 806 IOT_GATEWAY = 0x0D 807 MINI_PC = 0x0E 808 STICK_PC = 0x0F 809 810 class WatchSubcategory(OpenIntEnum): 811 GENENERIC_WATCH = 0x00 812 SPORTS_WATCH = 0x01 813 SMARTWATCH = 0x02 814 815 class ClockSubcategory(OpenIntEnum): 816 GENERIC_CLOCK = 0x00 817 818 class DisplaySubcategory(OpenIntEnum): 819 GENERIC_DISPLAY = 0x00 820 821 class RemoteControlSubcategory(OpenIntEnum): 822 GENERIC_REMOTE_CONTROL = 0x00 823 824 class EyeglassesSubcategory(OpenIntEnum): 825 GENERIC_EYEGLASSES = 0x00 826 827 class TagSubcategory(OpenIntEnum): 828 GENERIC_TAG = 0x00 829 830 class KeyringSubcategory(OpenIntEnum): 831 GENERIC_KEYRING = 0x00 832 833 class MediaPlayerSubcategory(OpenIntEnum): 834 GENERIC_MEDIA_PLAYER = 0x00 835 836 class BarcodeScannerSubcategory(OpenIntEnum): 837 GENERIC_BARCODE_SCANNER = 0x00 838 839 class ThermometerSubcategory(OpenIntEnum): 840 GENERIC_THERMOMETER = 0x00 841 EAR_THERMOMETER = 0x01 842 843 class HeartRateSensorSubcategory(OpenIntEnum): 844 GENERIC_HEART_RATE_SENSOR = 0x00 845 HEART_RATE_BELT = 0x01 846 847 class BloodPressureSubcategory(OpenIntEnum): 848 GENERIC_BLOOD_PRESSURE = 0x00 849 ARM_BLOOD_PRESSURE = 0x01 850 WRIST_BLOOD_PRESSURE = 0x02 851 852 class HumanInterfaceDeviceSubcategory(OpenIntEnum): 853 GENERIC_HUMAN_INTERFACE_DEVICE = 0x00 854 KEYBOARD = 0x01 855 MOUSE = 0x02 856 JOYSTICK = 0x03 857 GAMEPAD = 0x04 858 DIGITIZER_TABLET = 0x05 859 CARD_READER = 0x06 860 DIGITAL_PEN = 0x07 861 BARCODE_SCANNER = 0x08 862 TOUCHPAD = 0x09 863 PRESENTATION_REMOTE = 0x0A 864 865 class GlucoseMeterSubcategory(OpenIntEnum): 866 GENERIC_GLUCOSE_METER = 0x00 867 868 class RunningWalkingSensorSubcategory(OpenIntEnum): 869 GENERIC_RUNNING_WALKING_SENSOR = 0x00 870 IN_SHOE_RUNNING_WALKING_SENSOR = 0x01 871 ON_SHOW_RUNNING_WALKING_SENSOR = 0x02 872 ON_HIP_RUNNING_WALKING_SENSOR = 0x03 873 874 class CyclingSubcategory(OpenIntEnum): 875 GENERIC_CYCLING = 0x00 876 CYCLING_COMPUTER = 0x01 877 SPEED_SENSOR = 0x02 878 CADENCE_SENSOR = 0x03 879 POWER_SENSOR = 0x04 880 SPEED_AND_CADENCE_SENSOR = 0x05 881 882 class ControlDeviceSubcategory(OpenIntEnum): 883 GENERIC_CONTROL_DEVICE = 0x00 884 SWITCH = 0x01 885 MULTI_SWITCH = 0x02 886 BUTTON = 0x03 887 SLIDER = 0x04 888 ROTARY_SWITCH = 0x05 889 TOUCH_PANEL = 0x06 890 SINGLE_SWITCH = 0x07 891 DOUBLE_SWITCH = 0x08 892 TRIPLE_SWITCH = 0x09 893 BATTERY_SWITCH = 0x0A 894 ENERGY_HARVESTING_SWITCH = 0x0B 895 PUSH_BUTTON = 0x0C 896 897 class NetworkDeviceSubcategory(OpenIntEnum): 898 GENERIC_NETWORK_DEVICE = 0x00 899 ACCESS_POINT = 0x01 900 MESH_DEVICE = 0x02 901 MESH_NETWORK_PROXY = 0x03 902 903 class SensorSubcategory(OpenIntEnum): 904 GENERIC_SENSOR = 0x00 905 MOTION_SENSOR = 0x01 906 AIR_QUALITY_SENSOR = 0x02 907 TEMPERATURE_SENSOR = 0x03 908 HUMIDITY_SENSOR = 0x04 909 LEAK_SENSOR = 0x05 910 SMOKE_SENSOR = 0x06 911 OCCUPANCY_SENSOR = 0x07 912 CONTACT_SENSOR = 0x08 913 CARBON_MONOXIDE_SENSOR = 0x09 914 CARBON_DIOXIDE_SENSOR = 0x0A 915 AMBIENT_LIGHT_SENSOR = 0x0B 916 ENERGY_SENSOR = 0x0C 917 COLOR_LIGHT_SENSOR = 0x0D 918 RAIN_SENSOR = 0x0E 919 FIRE_SENSOR = 0x0F 920 WIND_SENSOR = 0x10 921 PROXIMITY_SENSOR = 0x11 922 MULTI_SENSOR = 0x12 923 FLUSH_MOUNTED_SENSOR = 0x13 924 CEILING_MOUNTED_SENSOR = 0x14 925 WALL_MOUNTED_SENSOR = 0x15 926 MULTISENSOR = 0x16 927 ENERGY_METER = 0x17 928 FLAME_DETECTOR = 0x18 929 VEHICLE_TIRE_PRESSURE_SENSOR = 0x19 930 931 class LightFixturesSubcategory(OpenIntEnum): 932 GENERIC_LIGHT_FIXTURES = 0x00 933 WALL_LIGHT = 0x01 934 CEILING_LIGHT = 0x02 935 FLOOR_LIGHT = 0x03 936 CABINET_LIGHT = 0x04 937 DESK_LIGHT = 0x05 938 TROFFER_LIGHT = 0x06 939 PENDANT_LIGHT = 0x07 940 IN_GROUND_LIGHT = 0x08 941 FLOOD_LIGHT = 0x09 942 UNDERWATER_LIGHT = 0x0A 943 BOLLARD_WITH_LIGHT = 0x0B 944 PATHWAY_LIGHT = 0x0C 945 GARDEN_LIGHT = 0x0D 946 POLE_TOP_LIGHT = 0x0E 947 SPOTLIGHT = 0x0F 948 LINEAR_LIGHT = 0x10 949 STREET_LIGHT = 0x11 950 SHELVES_LIGHT = 0x12 951 BAY_LIGHT = 0x013 952 EMERGENCY_EXIT_LIGHT = 0x14 953 LIGHT_CONTROLLER = 0x15 954 LIGHT_DRIVER = 0x16 955 BULB = 0x17 956 LOW_BAY_LIGHT = 0x18 957 HIGH_BAY_LIGHT = 0x19 958 959 class FanSubcategory(OpenIntEnum): 960 GENERIC_FAN = 0x00 961 CEILING_FAN = 0x01 962 AXIAL_FAN = 0x02 963 EXHAUST_FAN = 0x03 964 PEDESTAL_FAN = 0x04 965 DESK_FAN = 0x05 966 WALL_FAN = 0x06 967 968 class HvacSubcategory(OpenIntEnum): 969 GENERIC_HVAC = 0x00 970 THERMOSTAT = 0x01 971 HUMIDIFIER = 0x02 972 DEHUMIDIFIER = 0x03 973 HEATER = 0x04 974 RADIATOR = 0x05 975 BOILER = 0x06 976 HEAT_PUMP = 0x07 977 INFRARED_HEATER = 0x08 978 RADIANT_PANEL_HEATER = 0x09 979 FAN_HEATER = 0x0A 980 AIR_CURTAIN = 0x0B 981 982 class AirConditioningSubcategory(OpenIntEnum): 983 GENERIC_AIR_CONDITIONING = 0x00 984 985 class HumidifierSubcategory(OpenIntEnum): 986 GENERIC_HUMIDIFIER = 0x00 987 988 class HeatingSubcategory(OpenIntEnum): 989 GENERIC_HEATING = 0x00 990 RADIATOR = 0x01 991 BOILER = 0x02 992 HEAT_PUMP = 0x03 993 INFRARED_HEATER = 0x04 994 RADIANT_PANEL_HEATER = 0x05 995 FAN_HEATER = 0x06 996 AIR_CURTAIN = 0x07 997 998 class AccessControlSubcategory(OpenIntEnum): 999 GENERIC_ACCESS_CONTROL = 0x00 1000 ACCESS_DOOR = 0x01 1001 GARAGE_DOOR = 0x02 1002 EMERGENCY_EXIT_DOOR = 0x03 1003 ACCESS_LOCK = 0x04 1004 ELEVATOR = 0x05 1005 WINDOW = 0x06 1006 ENTRANCE_GATE = 0x07 1007 DOOR_LOCK = 0x08 1008 LOCKER = 0x09 1009 1010 class MotorizedDeviceSubcategory(OpenIntEnum): 1011 GENERIC_MOTORIZED_DEVICE = 0x00 1012 MOTORIZED_GATE = 0x01 1013 AWNING = 0x02 1014 BLINDS_OR_SHADES = 0x03 1015 CURTAINS = 0x04 1016 SCREEN = 0x05 1017 1018 class PowerDeviceSubcategory(OpenIntEnum): 1019 GENERIC_POWER_DEVICE = 0x00 1020 POWER_OUTLET = 0x01 1021 POWER_STRIP = 0x02 1022 PLUG = 0x03 1023 POWER_SUPPLY = 0x04 1024 LED_DRIVER = 0x05 1025 FLUORESCENT_LAMP_GEAR = 0x06 1026 HID_LAMP_GEAR = 0x07 1027 CHARGE_CASE = 0x08 1028 POWER_BANK = 0x09 1029 1030 class LightSourceSubcategory(OpenIntEnum): 1031 GENERIC_LIGHT_SOURCE = 0x00 1032 INCANDESCENT_LIGHT_BULB = 0x01 1033 LED_LAMP = 0x02 1034 HID_LAMP = 0x03 1035 FLUORESCENT_LAMP = 0x04 1036 LED_ARRAY = 0x05 1037 MULTI_COLOR_LED_ARRAY = 0x06 1038 LOW_VOLTAGE_HALOGEN = 0x07 1039 ORGANIC_LIGHT_EMITTING_DIODE = 0x08 1040 1041 class WindowCoveringSubcategory(OpenIntEnum): 1042 GENERIC_WINDOW_COVERING = 0x00 1043 WINDOW_SHADES = 0x01 1044 WINDOW_BLINDS = 0x02 1045 WINDOW_AWNING = 0x03 1046 WINDOW_CURTAIN = 0x04 1047 EXTERIOR_SHUTTER = 0x05 1048 EXTERIOR_SCREEN = 0x06 1049 1050 class AudioSinkSubcategory(OpenIntEnum): 1051 GENERIC_AUDIO_SINK = 0x00 1052 STANDALONE_SPEAKER = 0x01 1053 SOUNDBAR = 0x02 1054 BOOKSHELF_SPEAKER = 0x03 1055 STANDMOUNTED_SPEAKER = 0x04 1056 SPEAKERPHONE = 0x05 1057 1058 class AudioSourceSubcategory(OpenIntEnum): 1059 GENERIC_AUDIO_SOURCE = 0x00 1060 MICROPHONE = 0x01 1061 ALARM = 0x02 1062 BELL = 0x03 1063 HORN = 0x04 1064 BROADCASTING_DEVICE = 0x05 1065 SERVICE_DESK = 0x06 1066 KIOSK = 0x07 1067 BROADCASTING_ROOM = 0x08 1068 AUDITORIUM = 0x09 1069 1070 class MotorizedVehicleSubcategory(OpenIntEnum): 1071 GENERIC_MOTORIZED_VEHICLE = 0x00 1072 CAR = 0x01 1073 LARGE_GOODS_VEHICLE = 0x02 1074 TWO_WHEELED_VEHICLE = 0x03 1075 MOTORBIKE = 0x04 1076 SCOOTER = 0x05 1077 MOPED = 0x06 1078 THREE_WHEELED_VEHICLE = 0x07 1079 LIGHT_VEHICLE = 0x08 1080 QUAD_BIKE = 0x09 1081 MINIBUS = 0x0A 1082 BUS = 0x0B 1083 TROLLEY = 0x0C 1084 AGRICULTURAL_VEHICLE = 0x0D 1085 CAMPER_CARAVAN = 0x0E 1086 RECREATIONAL_VEHICLE_MOTOR_HOME = 0x0F 1087 1088 class DomesticApplianceSubcategory(OpenIntEnum): 1089 GENERIC_DOMESTIC_APPLIANCE = 0x00 1090 REFRIGERATOR = 0x01 1091 FREEZER = 0x02 1092 OVEN = 0x03 1093 MICROWAVE = 0x04 1094 TOASTER = 0x05 1095 WASHING_MACHINE = 0x06 1096 DRYER = 0x07 1097 COFFEE_MAKER = 0x08 1098 CLOTHES_IRON = 0x09 1099 CURLING_IRON = 0x0A 1100 HAIR_DRYER = 0x0B 1101 VACUUM_CLEANER = 0x0C 1102 ROBOTIC_VACUUM_CLEANER = 0x0D 1103 RICE_COOKER = 0x0E 1104 CLOTHES_STEAMER = 0x0F 1105 1106 class WearableAudioDeviceSubcategory(OpenIntEnum): 1107 GENERIC_WEARABLE_AUDIO_DEVICE = 0x00 1108 EARBUD = 0x01 1109 HEADSET = 0x02 1110 HEADPHONES = 0x03 1111 NECK_BAND = 0x04 1112 1113 class AircraftSubcategory(OpenIntEnum): 1114 GENERIC_AIRCRAFT = 0x00 1115 LIGHT_AIRCRAFT = 0x01 1116 MICROLIGHT = 0x02 1117 PARAGLIDER = 0x03 1118 LARGE_PASSENGER_AIRCRAFT = 0x04 1119 1120 class AvEquipmentSubcategory(OpenIntEnum): 1121 GENERIC_AV_EQUIPMENT = 0x00 1122 AMPLIFIER = 0x01 1123 RECEIVER = 0x02 1124 RADIO = 0x03 1125 TUNER = 0x04 1126 TURNTABLE = 0x05 1127 CD_PLAYER = 0x06 1128 DVD_PLAYER = 0x07 1129 BLUERAY_PLAYER = 0x08 1130 OPTICAL_DISC_PLAYER = 0x09 1131 SET_TOP_BOX = 0x0A 1132 1133 class DisplayEquipmentSubcategory(OpenIntEnum): 1134 GENERIC_DISPLAY_EQUIPMENT = 0x00 1135 TELEVISION = 0x01 1136 MONITOR = 0x02 1137 PROJECTOR = 0x03 1138 1139 class HearingAidSubcategory(OpenIntEnum): 1140 GENERIC_HEARING_AID = 0x00 1141 IN_EAR_HEARING_AID = 0x01 1142 BEHIND_EAR_HEARING_AID = 0x02 1143 COCHLEAR_IMPLANT = 0x03 1144 1145 class GamingSubcategory(OpenIntEnum): 1146 GENERIC_GAMING = 0x00 1147 HOME_VIDEO_GAME_CONSOLE = 0x01 1148 PORTABLE_HANDHELD_CONSOLE = 0x02 1149 1150 class SignageSubcategory(OpenIntEnum): 1151 GENERIC_SIGNAGE = 0x00 1152 DIGITAL_SIGNAGE = 0x01 1153 ELECTRONIC_LABEL = 0x02 1154 1155 class PulseOximeterSubcategory(OpenIntEnum): 1156 GENERIC_PULSE_OXIMETER = 0x00 1157 FINGERTIP_PULSE_OXIMETER = 0x01 1158 WRIST_WORN_PULSE_OXIMETER = 0x02 1159 1160 class WeightScaleSubcategory(OpenIntEnum): 1161 GENERIC_WEIGHT_SCALE = 0x00 1162 1163 class PersonalMobilityDeviceSubcategory(OpenIntEnum): 1164 GENERIC_PERSONAL_MOBILITY_DEVICE = 0x00 1165 POWERED_WHEELCHAIR = 0x01 1166 MOBILITY_SCOOTER = 0x02 1167 1168 class ContinuousGlucoseMonitorSubcategory(OpenIntEnum): 1169 GENERIC_CONTINUOUS_GLUCOSE_MONITOR = 0x00 1170 1171 class InsulinPumpSubcategory(OpenIntEnum): 1172 GENERIC_INSULIN_PUMP = 0x00 1173 INSULIN_PUMP_DURABLE_PUMP = 0x01 1174 INSULIN_PUMP_PATCH_PUMP = 0x02 1175 INSULIN_PEN = 0x03 1176 1177 class MedicationDeliverySubcategory(OpenIntEnum): 1178 GENERIC_MEDICATION_DELIVERY = 0x00 1179 1180 class SpirometerSubcategory(OpenIntEnum): 1181 GENERIC_SPIROMETER = 0x00 1182 HANDHELD_SPIROMETER = 0x01 1183 1184 class OutdoorSportsActivitySubcategory(OpenIntEnum): 1185 GENERIC_OUTDOOR_SPORTS_ACTIVITY = 0x00 1186 LOCATION_DISPLAY = 0x01 1187 LOCATION_AND_NAVIGATION_DISPLAY = 0x02 1188 LOCATION_POD = 0x03 1189 LOCATION_AND_NAVIGATION_POD = 0x04 1190 1191 class _OpenSubcategory(OpenIntEnum): 1192 GENERIC = 0x00 1193 1194 SUBCATEGORY_CLASSES = { 1195 Category.UNKNOWN: UnknownSubcategory, 1196 Category.PHONE: PhoneSubcategory, 1197 Category.COMPUTER: ComputerSubcategory, 1198 Category.WATCH: WatchSubcategory, 1199 Category.CLOCK: ClockSubcategory, 1200 Category.DISPLAY: DisplaySubcategory, 1201 Category.REMOTE_CONTROL: RemoteControlSubcategory, 1202 Category.EYE_GLASSES: EyeglassesSubcategory, 1203 Category.TAG: TagSubcategory, 1204 Category.KEYRING: KeyringSubcategory, 1205 Category.MEDIA_PLAYER: MediaPlayerSubcategory, 1206 Category.BARCODE_SCANNER: BarcodeScannerSubcategory, 1207 Category.THERMOMETER: ThermometerSubcategory, 1208 Category.HEART_RATE_SENSOR: HeartRateSensorSubcategory, 1209 Category.BLOOD_PRESSURE: BloodPressureSubcategory, 1210 Category.HUMAN_INTERFACE_DEVICE: HumanInterfaceDeviceSubcategory, 1211 Category.GLUCOSE_METER: GlucoseMeterSubcategory, 1212 Category.RUNNING_WALKING_SENSOR: RunningWalkingSensorSubcategory, 1213 Category.CYCLING: CyclingSubcategory, 1214 Category.CONTROL_DEVICE: ControlDeviceSubcategory, 1215 Category.NETWORK_DEVICE: NetworkDeviceSubcategory, 1216 Category.SENSOR: SensorSubcategory, 1217 Category.LIGHT_FIXTURES: LightFixturesSubcategory, 1218 Category.FAN: FanSubcategory, 1219 Category.HVAC: HvacSubcategory, 1220 Category.AIR_CONDITIONING: AirConditioningSubcategory, 1221 Category.HUMIDIFIER: HumidifierSubcategory, 1222 Category.HEATING: HeatingSubcategory, 1223 Category.ACCESS_CONTROL: AccessControlSubcategory, 1224 Category.MOTORIZED_DEVICE: MotorizedDeviceSubcategory, 1225 Category.POWER_DEVICE: PowerDeviceSubcategory, 1226 Category.LIGHT_SOURCE: LightSourceSubcategory, 1227 Category.WINDOW_COVERING: WindowCoveringSubcategory, 1228 Category.AUDIO_SINK: AudioSinkSubcategory, 1229 Category.AUDIO_SOURCE: AudioSourceSubcategory, 1230 Category.MOTORIZED_VEHICLE: MotorizedVehicleSubcategory, 1231 Category.DOMESTIC_APPLIANCE: DomesticApplianceSubcategory, 1232 Category.WEARABLE_AUDIO_DEVICE: WearableAudioDeviceSubcategory, 1233 Category.AIRCRAFT: AircraftSubcategory, 1234 Category.AV_EQUIPMENT: AvEquipmentSubcategory, 1235 Category.DISPLAY_EQUIPMENT: DisplayEquipmentSubcategory, 1236 Category.HEARING_AID: HearingAidSubcategory, 1237 Category.GAMING: GamingSubcategory, 1238 Category.SIGNAGE: SignageSubcategory, 1239 Category.PULSE_OXIMETER: PulseOximeterSubcategory, 1240 Category.WEIGHT_SCALE: WeightScaleSubcategory, 1241 Category.PERSONAL_MOBILITY_DEVICE: PersonalMobilityDeviceSubcategory, 1242 Category.CONTINUOUS_GLUCOSE_MONITOR: ContinuousGlucoseMonitorSubcategory, 1243 Category.INSULIN_PUMP: InsulinPumpSubcategory, 1244 Category.MEDICATION_DELIVERY: MedicationDeliverySubcategory, 1245 Category.SPIROMETER: SpirometerSubcategory, 1246 Category.OUTDOOR_SPORTS_ACTIVITY: OutdoorSportsActivitySubcategory, 1247 } 1248 1249 category: Category 1250 subcategory: enum.IntEnum 1251 1252 @classmethod 1253 def from_int(cls, appearance: int) -> Self: 1254 category = cls.Category(appearance >> 6) 1255 return cls(category, appearance & 0x3F) 1256 1257 def __init__(self, category: Category, subcategory: int) -> None: 1258 self.category = category 1259 if subcategory_class := self.SUBCATEGORY_CLASSES.get(category): 1260 self.subcategory = subcategory_class(subcategory) 1261 else: 1262 self.subcategory = self._OpenSubcategory(subcategory) 1263 1264 def __int__(self) -> int: 1265 return self.category << 6 | self.subcategory 1266 1267 def __repr__(self) -> str: 1268 return ( 1269 'Appearance(' 1270 f'category={self.category.name}, ' 1271 f'subcategory={self.subcategory.name}' 1272 ')' 1273 ) 1274 1275 def __str__(self) -> str: 1276 return f'{self.category.name}/{self.subcategory.name}' 1277 1278 1279# ----------------------------------------------------------------------------- 1280# Advertising Data 1281# ----------------------------------------------------------------------------- 1282AdvertisingDataObject = Union[ 1283 List[UUID], 1284 Tuple[UUID, bytes], 1285 bytes, 1286 str, 1287 int, 1288 Tuple[int, int], 1289 Tuple[int, bytes], 1290 Appearance, 1291] 1292 1293 1294class AdvertisingData: 1295 # fmt: off 1296 # pylint: disable=line-too-long 1297 1298 FLAGS = 0x01 1299 INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS = 0x02 1300 COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS = 0x03 1301 INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS = 0x04 1302 COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS = 0x05 1303 INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS = 0x06 1304 COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS = 0x07 1305 SHORTENED_LOCAL_NAME = 0x08 1306 COMPLETE_LOCAL_NAME = 0x09 1307 TX_POWER_LEVEL = 0x0A 1308 CLASS_OF_DEVICE = 0x0D 1309 SIMPLE_PAIRING_HASH_C = 0x0E 1310 SIMPLE_PAIRING_HASH_C_192 = 0x0E 1311 SIMPLE_PAIRING_RANDOMIZER_R = 0x0F 1312 SIMPLE_PAIRING_RANDOMIZER_R_192 = 0x0F 1313 DEVICE_ID = 0x10 1314 SECURITY_MANAGER_TK_VALUE = 0x10 1315 SECURITY_MANAGER_OUT_OF_BAND_FLAGS = 0x11 1316 PERIPHERAL_CONNECTION_INTERVAL_RANGE = 0x12 1317 LIST_OF_16_BIT_SERVICE_SOLICITATION_UUIDS = 0x14 1318 LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS = 0x15 1319 SERVICE_DATA = 0x16 1320 SERVICE_DATA_16_BIT_UUID = 0x16 1321 PUBLIC_TARGET_ADDRESS = 0x17 1322 RANDOM_TARGET_ADDRESS = 0x18 1323 APPEARANCE = 0x19 1324 ADVERTISING_INTERVAL = 0x1A 1325 LE_BLUETOOTH_DEVICE_ADDRESS = 0x1B 1326 LE_ROLE = 0x1C 1327 SIMPLE_PAIRING_HASH_C_256 = 0x1D 1328 SIMPLE_PAIRING_RANDOMIZER_R_256 = 0x1E 1329 LIST_OF_32_BIT_SERVICE_SOLICITATION_UUIDS = 0x1F 1330 SERVICE_DATA_32_BIT_UUID = 0x20 1331 SERVICE_DATA_128_BIT_UUID = 0x21 1332 LE_SECURE_CONNECTIONS_CONFIRMATION_VALUE = 0x22 1333 LE_SECURE_CONNECTIONS_RANDOM_VALUE = 0x23 1334 URI = 0x24 1335 INDOOR_POSITIONING = 0x25 1336 TRANSPORT_DISCOVERY_DATA = 0x26 1337 LE_SUPPORTED_FEATURES = 0x27 1338 CHANNEL_MAP_UPDATE_INDICATION = 0x28 1339 PB_ADV = 0x29 1340 MESH_MESSAGE = 0x2A 1341 MESH_BEACON = 0x2B 1342 BIGINFO = 0x2C 1343 BROADCAST_CODE = 0x2D 1344 RESOLVABLE_SET_IDENTIFIER = 0x2E 1345 ADVERTISING_INTERVAL_LONG = 0x2F 1346 BROADCAST_NAME = 0x30 1347 ENCRYPTED_ADVERTISING_DATA = 0X31 1348 PERIODIC_ADVERTISING_RESPONSE_TIMING_INFORMATION = 0X32 1349 ELECTRONIC_SHELF_LABEL = 0X34 1350 THREE_D_INFORMATION_DATA = 0x3D 1351 MANUFACTURER_SPECIFIC_DATA = 0xFF 1352 1353 AD_TYPE_NAMES = { 1354 FLAGS: 'FLAGS', 1355 INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS: 'INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS', 1356 COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS: 'COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS', 1357 INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS: 'INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS', 1358 COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS: 'COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS', 1359 INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS: 'INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS', 1360 COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS: 'COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS', 1361 SHORTENED_LOCAL_NAME: 'SHORTENED_LOCAL_NAME', 1362 COMPLETE_LOCAL_NAME: 'COMPLETE_LOCAL_NAME', 1363 TX_POWER_LEVEL: 'TX_POWER_LEVEL', 1364 CLASS_OF_DEVICE: 'CLASS_OF_DEVICE', 1365 SIMPLE_PAIRING_HASH_C: 'SIMPLE_PAIRING_HASH_C', 1366 SIMPLE_PAIRING_HASH_C_192: 'SIMPLE_PAIRING_HASH_C_192', 1367 SIMPLE_PAIRING_RANDOMIZER_R: 'SIMPLE_PAIRING_RANDOMIZER_R', 1368 SIMPLE_PAIRING_RANDOMIZER_R_192: 'SIMPLE_PAIRING_RANDOMIZER_R_192', 1369 DEVICE_ID: 'DEVICE_ID', 1370 SECURITY_MANAGER_TK_VALUE: 'SECURITY_MANAGER_TK_VALUE', 1371 SECURITY_MANAGER_OUT_OF_BAND_FLAGS: 'SECURITY_MANAGER_OUT_OF_BAND_FLAGS', 1372 PERIPHERAL_CONNECTION_INTERVAL_RANGE: 'PERIPHERAL_CONNECTION_INTERVAL_RANGE', 1373 LIST_OF_16_BIT_SERVICE_SOLICITATION_UUIDS: 'LIST_OF_16_BIT_SERVICE_SOLICITATION_UUIDS', 1374 LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS: 'LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS', 1375 SERVICE_DATA_16_BIT_UUID: 'SERVICE_DATA_16_BIT_UUID', 1376 PUBLIC_TARGET_ADDRESS: 'PUBLIC_TARGET_ADDRESS', 1377 RANDOM_TARGET_ADDRESS: 'RANDOM_TARGET_ADDRESS', 1378 APPEARANCE: 'APPEARANCE', 1379 ADVERTISING_INTERVAL: 'ADVERTISING_INTERVAL', 1380 LE_BLUETOOTH_DEVICE_ADDRESS: 'LE_BLUETOOTH_DEVICE_ADDRESS', 1381 LE_ROLE: 'LE_ROLE', 1382 SIMPLE_PAIRING_HASH_C_256: 'SIMPLE_PAIRING_HASH_C_256', 1383 SIMPLE_PAIRING_RANDOMIZER_R_256: 'SIMPLE_PAIRING_RANDOMIZER_R_256', 1384 LIST_OF_32_BIT_SERVICE_SOLICITATION_UUIDS: 'LIST_OF_32_BIT_SERVICE_SOLICITATION_UUIDS', 1385 SERVICE_DATA_32_BIT_UUID: 'SERVICE_DATA_32_BIT_UUID', 1386 SERVICE_DATA_128_BIT_UUID: 'SERVICE_DATA_128_BIT_UUID', 1387 LE_SECURE_CONNECTIONS_CONFIRMATION_VALUE: 'LE_SECURE_CONNECTIONS_CONFIRMATION_VALUE', 1388 LE_SECURE_CONNECTIONS_RANDOM_VALUE: 'LE_SECURE_CONNECTIONS_RANDOM_VALUE', 1389 URI: 'URI', 1390 INDOOR_POSITIONING: 'INDOOR_POSITIONING', 1391 TRANSPORT_DISCOVERY_DATA: 'TRANSPORT_DISCOVERY_DATA', 1392 LE_SUPPORTED_FEATURES: 'LE_SUPPORTED_FEATURES', 1393 CHANNEL_MAP_UPDATE_INDICATION: 'CHANNEL_MAP_UPDATE_INDICATION', 1394 PB_ADV: 'PB_ADV', 1395 MESH_MESSAGE: 'MESH_MESSAGE', 1396 MESH_BEACON: 'MESH_BEACON', 1397 BIGINFO: 'BIGINFO', 1398 BROADCAST_CODE: 'BROADCAST_CODE', 1399 RESOLVABLE_SET_IDENTIFIER: 'RESOLVABLE_SET_IDENTIFIER', 1400 ADVERTISING_INTERVAL_LONG: 'ADVERTISING_INTERVAL_LONG', 1401 BROADCAST_NAME: 'BROADCAST_NAME', 1402 ENCRYPTED_ADVERTISING_DATA: 'ENCRYPTED_ADVERTISING_DATA', 1403 PERIODIC_ADVERTISING_RESPONSE_TIMING_INFORMATION: 'PERIODIC_ADVERTISING_RESPONSE_TIMING_INFORMATION', 1404 ELECTRONIC_SHELF_LABEL: 'ELECTRONIC_SHELF_LABEL', 1405 THREE_D_INFORMATION_DATA: 'THREE_D_INFORMATION_DATA', 1406 MANUFACTURER_SPECIFIC_DATA: 'MANUFACTURER_SPECIFIC_DATA' 1407 } 1408 1409 LE_LIMITED_DISCOVERABLE_MODE_FLAG = 0x01 1410 LE_GENERAL_DISCOVERABLE_MODE_FLAG = 0x02 1411 BR_EDR_NOT_SUPPORTED_FLAG = 0x04 1412 BR_EDR_CONTROLLER_FLAG = 0x08 1413 BR_EDR_HOST_FLAG = 0x10 1414 1415 ad_structures: List[Tuple[int, bytes]] 1416 1417 # fmt: on 1418 # pylint: enable=line-too-long 1419 1420 def __init__(self, ad_structures: Optional[List[Tuple[int, bytes]]] = None) -> None: 1421 if ad_structures is None: 1422 ad_structures = [] 1423 self.ad_structures = ad_structures[:] 1424 1425 @classmethod 1426 def from_bytes(cls, data: bytes) -> AdvertisingData: 1427 instance = AdvertisingData() 1428 instance.append(data) 1429 return instance 1430 1431 @staticmethod 1432 def flags_to_string(flags, short=False): 1433 flag_names = ( 1434 ['LE Limited', 'LE General', 'No BR/EDR', 'BR/EDR C', 'BR/EDR H'] 1435 if short 1436 else [ 1437 'LE Limited Discoverable Mode', 1438 'LE General Discoverable Mode', 1439 'BR/EDR Not Supported', 1440 'Simultaneous LE and BR/EDR (Controller)', 1441 'Simultaneous LE and BR/EDR (Host)', 1442 ] 1443 ) 1444 return ','.join(bit_flags_to_strings(flags, flag_names)) 1445 1446 @staticmethod 1447 def uuid_list_to_objects(ad_data: bytes, uuid_size: int) -> List[UUID]: 1448 uuids = [] 1449 offset = 0 1450 while (offset + uuid_size) <= len(ad_data): 1451 uuids.append(UUID.from_bytes(ad_data[offset : offset + uuid_size])) 1452 offset += uuid_size 1453 return uuids 1454 1455 @staticmethod 1456 def uuid_list_to_string(ad_data, uuid_size): 1457 return ', '.join( 1458 [ 1459 str(uuid) 1460 for uuid in AdvertisingData.uuid_list_to_objects(ad_data, uuid_size) 1461 ] 1462 ) 1463 1464 @staticmethod 1465 def ad_data_to_string(ad_type, ad_data): 1466 if ad_type == AdvertisingData.FLAGS: 1467 ad_type_str = 'Flags' 1468 ad_data_str = AdvertisingData.flags_to_string(ad_data[0], short=True) 1469 elif ad_type == AdvertisingData.COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS: 1470 ad_type_str = 'Complete List of 16-bit Service Class UUIDs' 1471 ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 2) 1472 elif ad_type == AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS: 1473 ad_type_str = 'Incomplete List of 16-bit Service Class UUIDs' 1474 ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 2) 1475 elif ad_type == AdvertisingData.COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS: 1476 ad_type_str = 'Complete List of 32-bit Service Class UUIDs' 1477 ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 4) 1478 elif ad_type == AdvertisingData.INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS: 1479 ad_type_str = 'Incomplete List of 32-bit Service Class UUIDs' 1480 ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 4) 1481 elif ad_type == AdvertisingData.COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS: 1482 ad_type_str = 'Complete List of 128-bit Service Class UUIDs' 1483 ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 16) 1484 elif ad_type == AdvertisingData.INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS: 1485 ad_type_str = 'Incomplete List of 128-bit Service Class UUIDs' 1486 ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 16) 1487 elif ad_type == AdvertisingData.SERVICE_DATA_16_BIT_UUID: 1488 ad_type_str = 'Service Data' 1489 uuid = UUID.from_bytes(ad_data[:2]) 1490 ad_data_str = f'service={uuid}, data={ad_data[2:].hex()}' 1491 elif ad_type == AdvertisingData.SERVICE_DATA_32_BIT_UUID: 1492 ad_type_str = 'Service Data' 1493 uuid = UUID.from_bytes(ad_data[:4]) 1494 ad_data_str = f'service={uuid}, data={ad_data[4:].hex()}' 1495 elif ad_type == AdvertisingData.SERVICE_DATA_128_BIT_UUID: 1496 ad_type_str = 'Service Data' 1497 uuid = UUID.from_bytes(ad_data[:16]) 1498 ad_data_str = f'service={uuid}, data={ad_data[16:].hex()}' 1499 elif ad_type == AdvertisingData.SHORTENED_LOCAL_NAME: 1500 ad_type_str = 'Shortened Local Name' 1501 ad_data_str = f'"{ad_data.decode("utf-8")}"' 1502 elif ad_type == AdvertisingData.COMPLETE_LOCAL_NAME: 1503 ad_type_str = 'Complete Local Name' 1504 ad_data_str = f'"{ad_data.decode("utf-8")}"' 1505 elif ad_type == AdvertisingData.TX_POWER_LEVEL: 1506 ad_type_str = 'TX Power Level' 1507 ad_data_str = str(ad_data[0]) 1508 elif ad_type == AdvertisingData.MANUFACTURER_SPECIFIC_DATA: 1509 ad_type_str = 'Manufacturer Specific Data' 1510 company_id = struct.unpack_from('<H', ad_data, 0)[0] 1511 company_name = COMPANY_IDENTIFIERS.get(company_id, f'0x{company_id:04X}') 1512 ad_data_str = f'company={company_name}, data={ad_data[2:].hex()}' 1513 elif ad_type == AdvertisingData.APPEARANCE: 1514 ad_type_str = 'Appearance' 1515 appearance = Appearance.from_int(struct.unpack_from('<H', ad_data, 0)[0]) 1516 ad_data_str = str(appearance) 1517 elif ad_type == AdvertisingData.BROADCAST_NAME: 1518 ad_type_str = 'Broadcast Name' 1519 ad_data_str = ad_data.decode('utf-8') 1520 else: 1521 ad_type_str = AdvertisingData.AD_TYPE_NAMES.get(ad_type, f'0x{ad_type:02X}') 1522 ad_data_str = ad_data.hex() 1523 1524 return f'[{ad_type_str}]: {ad_data_str}' 1525 1526 # pylint: disable=too-many-return-statements 1527 @staticmethod 1528 def ad_data_to_object(ad_type: int, ad_data: bytes) -> AdvertisingDataObject: 1529 if ad_type in ( 1530 AdvertisingData.COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, 1531 AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, 1532 AdvertisingData.LIST_OF_16_BIT_SERVICE_SOLICITATION_UUIDS, 1533 ): 1534 return AdvertisingData.uuid_list_to_objects(ad_data, 2) 1535 1536 if ad_type in ( 1537 AdvertisingData.COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS, 1538 AdvertisingData.INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS, 1539 AdvertisingData.LIST_OF_32_BIT_SERVICE_SOLICITATION_UUIDS, 1540 ): 1541 return AdvertisingData.uuid_list_to_objects(ad_data, 4) 1542 1543 if ad_type in ( 1544 AdvertisingData.COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS, 1545 AdvertisingData.INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS, 1546 AdvertisingData.LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS, 1547 ): 1548 return AdvertisingData.uuid_list_to_objects(ad_data, 16) 1549 1550 if ad_type == AdvertisingData.SERVICE_DATA_16_BIT_UUID: 1551 return (UUID.from_bytes(ad_data[:2]), ad_data[2:]) 1552 1553 if ad_type == AdvertisingData.SERVICE_DATA_32_BIT_UUID: 1554 return (UUID.from_bytes(ad_data[:4]), ad_data[4:]) 1555 1556 if ad_type == AdvertisingData.SERVICE_DATA_128_BIT_UUID: 1557 return (UUID.from_bytes(ad_data[:16]), ad_data[16:]) 1558 1559 if ad_type in ( 1560 AdvertisingData.SHORTENED_LOCAL_NAME, 1561 AdvertisingData.COMPLETE_LOCAL_NAME, 1562 AdvertisingData.URI, 1563 AdvertisingData.BROADCAST_NAME, 1564 ): 1565 return ad_data.decode("utf-8") 1566 1567 if ad_type in (AdvertisingData.TX_POWER_LEVEL, AdvertisingData.FLAGS): 1568 return cast(int, struct.unpack('B', ad_data)[0]) 1569 1570 if ad_type in (AdvertisingData.ADVERTISING_INTERVAL,): 1571 return cast(int, struct.unpack('<H', ad_data)[0]) 1572 1573 if ad_type == AdvertisingData.CLASS_OF_DEVICE: 1574 return cast(int, struct.unpack('<I', bytes([*ad_data, 0]))[0]) 1575 1576 if ad_type == AdvertisingData.PERIPHERAL_CONNECTION_INTERVAL_RANGE: 1577 return cast(Tuple[int, int], struct.unpack('<HH', ad_data)) 1578 1579 if ad_type == AdvertisingData.MANUFACTURER_SPECIFIC_DATA: 1580 return (cast(int, struct.unpack_from('<H', ad_data, 0)[0]), ad_data[2:]) 1581 1582 if ad_type == AdvertisingData.APPEARANCE: 1583 return Appearance.from_int( 1584 cast(int, struct.unpack_from('<H', ad_data, 0)[0]) 1585 ) 1586 1587 return ad_data 1588 1589 def append(self, data: bytes) -> None: 1590 offset = 0 1591 while offset + 1 < len(data): 1592 length = data[offset] 1593 offset += 1 1594 if length > 0: 1595 ad_type = data[offset] 1596 ad_data = data[offset + 1 : offset + length] 1597 self.ad_structures.append((ad_type, ad_data)) 1598 offset += length 1599 1600 def get_all(self, type_id: int, raw: bool = False) -> List[AdvertisingDataObject]: 1601 ''' 1602 Get Advertising Data Structure(s) with a given type 1603 1604 Returns a (possibly empty) list of matches. 1605 ''' 1606 1607 def process_ad_data(ad_data: bytes) -> AdvertisingDataObject: 1608 return ad_data if raw else self.ad_data_to_object(type_id, ad_data) 1609 1610 return [process_ad_data(ad[1]) for ad in self.ad_structures if ad[0] == type_id] 1611 1612 def get(self, type_id: int, raw: bool = False) -> Optional[AdvertisingDataObject]: 1613 ''' 1614 Get Advertising Data Structure(s) with a given type 1615 1616 Returns the first entry, or None if no structure matches. 1617 ''' 1618 1619 all_objects = self.get_all(type_id, raw=raw) 1620 return all_objects[0] if all_objects else None 1621 1622 def __bytes__(self): 1623 return b''.join( 1624 [bytes([len(x[1]) + 1, x[0]]) + x[1] for x in self.ad_structures] 1625 ) 1626 1627 def to_string(self, separator=', '): 1628 return separator.join( 1629 [AdvertisingData.ad_data_to_string(x[0], x[1]) for x in self.ad_structures] 1630 ) 1631 1632 def __str__(self): 1633 return self.to_string() 1634 1635 1636# ----------------------------------------------------------------------------- 1637# Connection Parameters 1638# ----------------------------------------------------------------------------- 1639class ConnectionParameters: 1640 def __init__(self, connection_interval, peripheral_latency, supervision_timeout): 1641 self.connection_interval = connection_interval 1642 self.peripheral_latency = peripheral_latency 1643 self.supervision_timeout = supervision_timeout 1644 1645 def __str__(self): 1646 return ( 1647 f'ConnectionParameters(connection_interval={self.connection_interval}, ' 1648 f'peripheral_latency={self.peripheral_latency}, ' 1649 f'supervision_timeout={self.supervision_timeout}' 1650 ) 1651 1652 1653# ----------------------------------------------------------------------------- 1654# Connection PHY 1655# ----------------------------------------------------------------------------- 1656class ConnectionPHY: 1657 def __init__(self, tx_phy, rx_phy): 1658 self.tx_phy = tx_phy 1659 self.rx_phy = rx_phy 1660 1661 def __str__(self): 1662 return f'ConnectionPHY(tx_phy={self.tx_phy}, rx_phy={self.rx_phy})' 1663 1664 1665# ----------------------------------------------------------------------------- 1666# LE Role 1667# ----------------------------------------------------------------------------- 1668class LeRole(enum.IntEnum): 1669 PERIPHERAL_ONLY = 0x00 1670 CENTRAL_ONLY = 0x01 1671 BOTH_PERIPHERAL_PREFERRED = 0x02 1672 BOTH_CENTRAL_PREFERRED = 0x03 1673