mirror of
https://github.com/google/bumble.git
synced 2026-04-16 00:25:31 +00:00
1669 lines
71 KiB
Python
1669 lines
71 KiB
Python
# Copyright 2021-2022 Google LLC
|
|
#
|
|
# 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
|
|
#
|
|
# https://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.
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Imports
|
|
# -----------------------------------------------------------------------------
|
|
from __future__ import annotations
|
|
import dataclasses
|
|
import enum
|
|
import struct
|
|
from typing import List, Optional, Tuple, Union, cast, Dict
|
|
from typing_extensions import Self
|
|
|
|
from bumble.company_ids import COMPANY_IDENTIFIERS
|
|
from bumble.utils import OpenIntEnum
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Constants
|
|
# -----------------------------------------------------------------------------
|
|
# fmt: off
|
|
|
|
BT_CENTRAL_ROLE = 0
|
|
BT_PERIPHERAL_ROLE = 1
|
|
|
|
BT_BR_EDR_TRANSPORT = 0
|
|
BT_LE_TRANSPORT = 1
|
|
|
|
|
|
# fmt: on
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Utils
|
|
# -----------------------------------------------------------------------------
|
|
def bit_flags_to_strings(bits, bit_flag_names):
|
|
names = []
|
|
index = 0
|
|
while bits != 0:
|
|
if bits & 1:
|
|
name = bit_flag_names[index] if index < len(bit_flag_names) else f'#{index}'
|
|
names.append(name)
|
|
bits >>= 1
|
|
index += 1
|
|
|
|
return names
|
|
|
|
|
|
def name_or_number(dictionary: Dict[int, str], number: int, width: int = 2) -> str:
|
|
name = dictionary.get(number)
|
|
if name is not None:
|
|
return name
|
|
return f'[0x{number:0{width}X}]'
|
|
|
|
|
|
def padded_bytes(buffer, size):
|
|
padding_size = max(size - len(buffer), 0)
|
|
return buffer + bytes(padding_size)
|
|
|
|
|
|
def get_dict_key_by_value(dictionary, value):
|
|
for key, val in dictionary.items():
|
|
if val == value:
|
|
return key
|
|
return None
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Exceptions
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
class BaseBumbleError(Exception):
|
|
"""Base Error raised by Bumble."""
|
|
|
|
|
|
class BaseError(BaseBumbleError):
|
|
"""Base class for errors with an error code, error name and namespace"""
|
|
|
|
def __init__(
|
|
self,
|
|
error_code: Optional[int],
|
|
error_namespace: str = '',
|
|
error_name: str = '',
|
|
details: str = '',
|
|
):
|
|
super().__init__()
|
|
self.error_code = error_code
|
|
self.error_namespace = error_namespace
|
|
self.error_name = error_name
|
|
self.details = details
|
|
|
|
def __str__(self):
|
|
if self.error_namespace:
|
|
namespace = f'{self.error_namespace}/'
|
|
else:
|
|
namespace = ''
|
|
have_name = self.error_name != ''
|
|
have_code = self.error_code is not None
|
|
if have_name and have_code:
|
|
error_text = f'{self.error_name} [0x{self.error_code:X}]'
|
|
elif have_name and not have_code:
|
|
error_text = self.error_name
|
|
elif not have_name and have_code:
|
|
error_text = f'0x{self.error_code:X}'
|
|
else:
|
|
error_text = '<unspecified>'
|
|
|
|
return f'{type(self).__name__}({namespace}{error_text})'
|
|
|
|
|
|
class ProtocolError(BaseError):
|
|
"""Protocol Error"""
|
|
|
|
|
|
class TimeoutError(BaseBumbleError): # pylint: disable=redefined-builtin
|
|
"""Timeout Error"""
|
|
|
|
|
|
class CommandTimeoutError(BaseBumbleError):
|
|
"""Command Timeout Error"""
|
|
|
|
|
|
class InvalidStateError(BaseBumbleError):
|
|
"""Invalid State Error"""
|
|
|
|
|
|
class InvalidArgumentError(BaseBumbleError, ValueError):
|
|
"""Invalid Argument Error"""
|
|
|
|
|
|
class InvalidPacketError(BaseBumbleError, ValueError):
|
|
"""Invalid Packet Error"""
|
|
|
|
|
|
class InvalidOperationError(BaseBumbleError, RuntimeError):
|
|
"""Invalid Operation Error"""
|
|
|
|
|
|
class OutOfResourcesError(BaseBumbleError, RuntimeError):
|
|
"""Out of Resources Error"""
|
|
|
|
|
|
class UnreachableError(BaseBumbleError):
|
|
"""The code path raising this error should be unreachable."""
|
|
|
|
|
|
class ConnectionError(BaseError): # pylint: disable=redefined-builtin
|
|
"""Connection Error"""
|
|
|
|
FAILURE = 0x01
|
|
CONNECTION_REFUSED = 0x02
|
|
|
|
def __init__(
|
|
self,
|
|
error_code,
|
|
transport,
|
|
peer_address,
|
|
error_namespace='',
|
|
error_name='',
|
|
details='',
|
|
):
|
|
super().__init__(error_code, error_namespace, error_name, details)
|
|
self.transport = transport
|
|
self.peer_address = peer_address
|
|
|
|
|
|
class ConnectionParameterUpdateError(BaseError):
|
|
"""Connection Parameter Update Error"""
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# UUID
|
|
#
|
|
# NOTE: the internal byte representation is in little-endian byte order
|
|
#
|
|
# Base UUID: 00000000-0000-1000-8000- 00805F9B34FB
|
|
# -----------------------------------------------------------------------------
|
|
class UUID:
|
|
'''
|
|
See Bluetooth spec Vol 3, Part B - 2.5.1 UUID
|
|
|
|
Note that this class expects and works in little-endian byte-order throughout.
|
|
The exception is when interacting with strings, which are in big-endian byte-order.
|
|
'''
|
|
|
|
BASE_UUID = bytes.fromhex('00001000800000805F9B34FB')[::-1] # little-endian
|
|
UUIDS: List[UUID] = [] # Registry of all instances created
|
|
|
|
uuid_bytes: bytes
|
|
name: Optional[str]
|
|
|
|
def __init__(
|
|
self, uuid_str_or_int: Union[str, int], name: Optional[str] = None
|
|
) -> None:
|
|
if isinstance(uuid_str_or_int, int):
|
|
self.uuid_bytes = struct.pack('<H', uuid_str_or_int)
|
|
else:
|
|
if len(uuid_str_or_int) == 36:
|
|
if (
|
|
uuid_str_or_int[8] != '-'
|
|
or uuid_str_or_int[13] != '-'
|
|
or uuid_str_or_int[18] != '-'
|
|
or uuid_str_or_int[23] != '-'
|
|
):
|
|
raise InvalidArgumentError('invalid UUID format')
|
|
uuid_str = uuid_str_or_int.replace('-', '')
|
|
else:
|
|
uuid_str = uuid_str_or_int
|
|
if len(uuid_str) != 32 and len(uuid_str) != 8 and len(uuid_str) != 4:
|
|
raise InvalidArgumentError(f"invalid UUID format: {uuid_str}")
|
|
self.uuid_bytes = bytes(reversed(bytes.fromhex(uuid_str)))
|
|
self.name = name
|
|
|
|
def register(self) -> UUID:
|
|
# Register this object in the class registry, and update the entry's name if
|
|
# it wasn't set already
|
|
for uuid in self.UUIDS:
|
|
if self == uuid:
|
|
if uuid.name is None:
|
|
uuid.name = self.name
|
|
return uuid
|
|
|
|
self.UUIDS.append(self)
|
|
return self
|
|
|
|
@classmethod
|
|
def from_bytes(cls, uuid_bytes: bytes, name: Optional[str] = None) -> UUID:
|
|
if len(uuid_bytes) in (2, 4, 16):
|
|
self = cls.__new__(cls)
|
|
self.uuid_bytes = uuid_bytes
|
|
self.name = name
|
|
|
|
return self.register()
|
|
|
|
raise InvalidArgumentError('only 2, 4 and 16 bytes are allowed')
|
|
|
|
@classmethod
|
|
def from_16_bits(cls, uuid_16: int, name: Optional[str] = None) -> UUID:
|
|
return cls.from_bytes(struct.pack('<H', uuid_16), name)
|
|
|
|
@classmethod
|
|
def from_32_bits(cls, uuid_32: int, name: Optional[str] = None) -> UUID:
|
|
return cls.from_bytes(struct.pack('<I', uuid_32), name)
|
|
|
|
@classmethod
|
|
def parse_uuid(cls, uuid_as_bytes: bytes, offset: int) -> Tuple[int, UUID]:
|
|
return len(uuid_as_bytes), cls.from_bytes(uuid_as_bytes[offset:])
|
|
|
|
@classmethod
|
|
def parse_uuid_2(cls, uuid_as_bytes: bytes, offset: int) -> Tuple[int, UUID]:
|
|
return offset + 2, cls.from_bytes(uuid_as_bytes[offset : offset + 2])
|
|
|
|
def to_bytes(self, force_128: bool = False) -> bytes:
|
|
'''
|
|
Serialize UUID in little-endian byte-order
|
|
'''
|
|
if not force_128:
|
|
return self.uuid_bytes
|
|
|
|
if len(self.uuid_bytes) == 2:
|
|
return self.BASE_UUID + self.uuid_bytes + bytes([0, 0])
|
|
elif len(self.uuid_bytes) == 4:
|
|
return self.BASE_UUID + self.uuid_bytes
|
|
elif len(self.uuid_bytes) == 16:
|
|
return self.uuid_bytes
|
|
else:
|
|
assert False, "unreachable"
|
|
|
|
def to_pdu_bytes(self) -> bytes:
|
|
'''
|
|
Convert to bytes for use in an ATT PDU.
|
|
According to Vol 3, Part F - 3.2.1 Attribute Type:
|
|
"All 32-bit Attribute UUIDs shall be converted to 128-bit UUIDs when the
|
|
Attribute UUID is contained in an ATT PDU."
|
|
'''
|
|
return self.to_bytes(force_128=(len(self.uuid_bytes) == 4))
|
|
|
|
def to_hex_str(self, separator: str = '') -> str:
|
|
if len(self.uuid_bytes) == 2 or len(self.uuid_bytes) == 4:
|
|
return bytes(reversed(self.uuid_bytes)).hex().upper()
|
|
|
|
return separator.join(
|
|
[
|
|
bytes(reversed(self.uuid_bytes[12:16])).hex(),
|
|
bytes(reversed(self.uuid_bytes[10:12])).hex(),
|
|
bytes(reversed(self.uuid_bytes[8:10])).hex(),
|
|
bytes(reversed(self.uuid_bytes[6:8])).hex(),
|
|
bytes(reversed(self.uuid_bytes[0:6])).hex(),
|
|
]
|
|
).upper()
|
|
|
|
def __bytes__(self) -> bytes:
|
|
return self.to_bytes()
|
|
|
|
def __eq__(self, other: object) -> bool:
|
|
if isinstance(other, UUID):
|
|
return self.to_bytes(force_128=True) == other.to_bytes(force_128=True)
|
|
|
|
if isinstance(other, str):
|
|
return UUID(other) == self
|
|
|
|
return False
|
|
|
|
def __hash__(self) -> int:
|
|
return hash(self.uuid_bytes)
|
|
|
|
def __str__(self) -> str:
|
|
result = self.to_hex_str(separator='-')
|
|
if len(self.uuid_bytes) == 2:
|
|
result = 'UUID-16:' + result
|
|
elif len(self.uuid_bytes) == 4:
|
|
result = 'UUID-32:' + result
|
|
if self.name is not None:
|
|
result += f' ({self.name})'
|
|
return result
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Common UUID constants
|
|
# -----------------------------------------------------------------------------
|
|
# fmt: off
|
|
# pylint: disable=line-too-long
|
|
|
|
# Protocol Identifiers
|
|
BT_SDP_PROTOCOL_ID = UUID.from_16_bits(0x0001, 'SDP')
|
|
BT_UDP_PROTOCOL_ID = UUID.from_16_bits(0x0002, 'UDP')
|
|
BT_RFCOMM_PROTOCOL_ID = UUID.from_16_bits(0x0003, 'RFCOMM')
|
|
BT_TCP_PROTOCOL_ID = UUID.from_16_bits(0x0004, 'TCP')
|
|
BT_TCS_BIN_PROTOCOL_ID = UUID.from_16_bits(0x0005, 'TCP-BIN')
|
|
BT_TCS_AT_PROTOCOL_ID = UUID.from_16_bits(0x0006, 'TCS-AT')
|
|
BT_ATT_PROTOCOL_ID = UUID.from_16_bits(0x0007, 'ATT')
|
|
BT_OBEX_PROTOCOL_ID = UUID.from_16_bits(0x0008, 'OBEX')
|
|
BT_IP_PROTOCOL_ID = UUID.from_16_bits(0x0009, 'IP')
|
|
BT_FTP_PROTOCOL_ID = UUID.from_16_bits(0x000A, 'FTP')
|
|
BT_HTTP_PROTOCOL_ID = UUID.from_16_bits(0x000C, 'HTTP')
|
|
BT_WSP_PROTOCOL_ID = UUID.from_16_bits(0x000E, 'WSP')
|
|
BT_BNEP_PROTOCOL_ID = UUID.from_16_bits(0x000F, 'BNEP')
|
|
BT_UPNP_PROTOCOL_ID = UUID.from_16_bits(0x0010, 'UPNP')
|
|
BT_HIDP_PROTOCOL_ID = UUID.from_16_bits(0x0011, 'HIDP')
|
|
BT_HARDCOPY_CONTROL_CHANNEL_PROTOCOL_ID = UUID.from_16_bits(0x0012, 'HardcopyControlChannel')
|
|
BT_HARDCOPY_DATA_CHANNEL_PROTOCOL_ID = UUID.from_16_bits(0x0014, 'HardcopyDataChannel')
|
|
BT_HARDCOPY_NOTIFICATION_PROTOCOL_ID = UUID.from_16_bits(0x0016, 'HardcopyNotification')
|
|
BT_AVCTP_PROTOCOL_ID = UUID.from_16_bits(0x0017, 'AVCTP')
|
|
BT_AVDTP_PROTOCOL_ID = UUID.from_16_bits(0x0019, 'AVDTP')
|
|
BT_CMTP_PROTOCOL_ID = UUID.from_16_bits(0x001B, 'CMTP')
|
|
BT_MCAP_CONTROL_CHANNEL_PROTOCOL_ID = UUID.from_16_bits(0x001E, 'MCAPControlChannel')
|
|
BT_MCAP_DATA_CHANNEL_PROTOCOL_ID = UUID.from_16_bits(0x001F, 'MCAPDataChannel')
|
|
BT_L2CAP_PROTOCOL_ID = UUID.from_16_bits(0x0100, 'L2CAP')
|
|
|
|
# Service Classes and Profiles
|
|
BT_SERVICE_DISCOVERY_SERVER_SERVICE_CLASS_ID_SERVICE = UUID.from_16_bits(0x1000, 'ServiceDiscoveryServerServiceClassID')
|
|
BT_BROWSE_GROUP_DESCRIPTOR_SERVICE_CLASS_ID_SERVICE = UUID.from_16_bits(0x1001, 'BrowseGroupDescriptorServiceClassID')
|
|
BT_SERIAL_PORT_SERVICE = UUID.from_16_bits(0x1101, 'SerialPort')
|
|
BT_LAN_ACCESS_USING_PPP_SERVICE = UUID.from_16_bits(0x1102, 'LANAccessUsingPPP')
|
|
BT_DIALUP_NETWORKING_SERVICE = UUID.from_16_bits(0x1103, 'DialupNetworking')
|
|
BT_IR_MCSYNC_SERVICE = UUID.from_16_bits(0x1104, 'IrMCSync')
|
|
BT_OBEX_OBJECT_PUSH_SERVICE = UUID.from_16_bits(0x1105, 'OBEXObjectPush')
|
|
BT_OBEX_FILE_TRANSFER_SERVICE = UUID.from_16_bits(0x1106, 'OBEXFileTransfer')
|
|
BT_IR_MCSYNC_COMMAND_SERVICE = UUID.from_16_bits(0x1107, 'IrMCSyncCommand')
|
|
BT_HEADSET_SERVICE = UUID.from_16_bits(0x1108, 'Headset')
|
|
BT_CORDLESS_TELEPHONY_SERVICE = UUID.from_16_bits(0x1109, 'CordlessTelephony')
|
|
BT_AUDIO_SOURCE_SERVICE = UUID.from_16_bits(0x110A, 'AudioSource')
|
|
BT_AUDIO_SINK_SERVICE = UUID.from_16_bits(0x110B, 'AudioSink')
|
|
BT_AV_REMOTE_CONTROL_TARGET_SERVICE = UUID.from_16_bits(0x110C, 'A/V_RemoteControlTarget')
|
|
BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE = UUID.from_16_bits(0x110D, 'AdvancedAudioDistribution')
|
|
BT_AV_REMOTE_CONTROL_SERVICE = UUID.from_16_bits(0x110E, 'A/V_RemoteControl')
|
|
BT_AV_REMOTE_CONTROL_CONTROLLER_SERVICE = UUID.from_16_bits(0x110F, 'A/V_RemoteControlController')
|
|
BT_INTERCOM_SERVICE = UUID.from_16_bits(0x1110, 'Intercom')
|
|
BT_FAX_SERVICE = UUID.from_16_bits(0x1111, 'Fax')
|
|
BT_HEADSET_AUDIO_GATEWAY_SERVICE = UUID.from_16_bits(0x1112, 'Headset - Audio Gateway')
|
|
BT_WAP_SERVICE = UUID.from_16_bits(0x1113, 'WAP')
|
|
BT_WAP_CLIENT_SERVICE = UUID.from_16_bits(0x1114, 'WAP_CLIENT')
|
|
BT_PANU_SERVICE = UUID.from_16_bits(0x1115, 'PANU')
|
|
BT_NAP_SERVICE = UUID.from_16_bits(0x1116, 'NAP')
|
|
BT_GN_SERVICE = UUID.from_16_bits(0x1117, 'GN')
|
|
BT_DIRECT_PRINTING_SERVICE = UUID.from_16_bits(0x1118, 'DirectPrinting')
|
|
BT_REFERENCE_PRINTING_SERVICE = UUID.from_16_bits(0x1119, 'ReferencePrinting')
|
|
BT_BASIC_IMAGING_PROFILE_SERVICE = UUID.from_16_bits(0x111A, 'Basic Imaging Profile')
|
|
BT_IMAGING_RESPONDER_SERVICE = UUID.from_16_bits(0x111B, 'ImagingResponder')
|
|
BT_IMAGING_AUTOMATIC_ARCHIVE_SERVICE = UUID.from_16_bits(0x111C, 'ImagingAutomaticArchive')
|
|
BT_IMAGING_REFERENCED_OBJECTS_SERVICE = UUID.from_16_bits(0x111D, 'ImagingReferencedObjects')
|
|
BT_HANDSFREE_SERVICE = UUID.from_16_bits(0x111E, 'Handsfree')
|
|
BT_HANDSFREE_AUDIO_GATEWAY_SERVICE = UUID.from_16_bits(0x111F, 'HandsfreeAudioGateway')
|
|
BT_DIRECT_PRINTING_REFERENCE_OBJECTS_SERVICE = UUID.from_16_bits(0x1120, 'DirectPrintingReferenceObjectsService')
|
|
BT_REFLECTED_UI_SERVICE = UUID.from_16_bits(0x1121, 'ReflectedUI')
|
|
BT_BASIC_PRINTING_SERVICE = UUID.from_16_bits(0x1122, 'BasicPrinting')
|
|
BT_PRINTING_STATUS_SERVICE = UUID.from_16_bits(0x1123, 'PrintingStatus')
|
|
BT_HUMAN_INTERFACE_DEVICE_SERVICE = UUID.from_16_bits(0x1124, 'HumanInterfaceDeviceService')
|
|
BT_HARDCOPY_CABLE_REPLACEMENT_SERVICE = UUID.from_16_bits(0x1125, 'HardcopyCableReplacement')
|
|
BT_HCR_PRINT_SERVICE = UUID.from_16_bits(0x1126, 'HCR_Print')
|
|
BT_HCR_SCAN_SERVICE = UUID.from_16_bits(0x1127, 'HCR_Scan')
|
|
BT_COMMON_ISDN_ACCESS_SERVICE = UUID.from_16_bits(0x1128, 'Common_ISDN_Access')
|
|
BT_SIM_ACCESS_SERVICE = UUID.from_16_bits(0x112D, 'SIM_Access')
|
|
BT_PHONEBOOK_ACCESS_PCE_SERVICE = UUID.from_16_bits(0x112E, 'Phonebook Access - PCE')
|
|
BT_PHONEBOOK_ACCESS_PSE_SERVICE = UUID.from_16_bits(0x112F, 'Phonebook Access - PSE')
|
|
BT_PHONEBOOK_ACCESS_SERVICE = UUID.from_16_bits(0x1130, 'Phonebook Access')
|
|
BT_HEADSET_HS_SERVICE = UUID.from_16_bits(0x1131, 'Headset - HS')
|
|
BT_MESSAGE_ACCESS_SERVER_SERVICE = UUID.from_16_bits(0x1132, 'Message Access Server')
|
|
BT_MESSAGE_NOTIFICATION_SERVER_SERVICE = UUID.from_16_bits(0x1133, 'Message Notification Server')
|
|
BT_MESSAGE_ACCESS_PROFILE_SERVICE = UUID.from_16_bits(0x1134, 'Message Access Profile')
|
|
BT_GNSS_SERVICE = UUID.from_16_bits(0x1135, 'GNSS')
|
|
BT_GNSS_SERVER_SERVICE = UUID.from_16_bits(0x1136, 'GNSS_Server')
|
|
BT_3D_DISPLAY_SERVICE = UUID.from_16_bits(0x1137, '3D Display')
|
|
BT_3D_GLASSES_SERVICE = UUID.from_16_bits(0x1138, '3D Glasses')
|
|
BT_3D_SYNCHRONIZATION_SERVICE = UUID.from_16_bits(0x1139, '3D Synchronization')
|
|
BT_MPS_PROFILE_SERVICE = UUID.from_16_bits(0x113A, 'MPS Profile')
|
|
BT_MPS_SC_SERVICE = UUID.from_16_bits(0x113B, 'MPS SC')
|
|
BT_ACCESS_SERVICE_SERVICE = UUID.from_16_bits(0x113C, 'CTN Access Service')
|
|
BT_CTN_NOTIFICATION_SERVICE_SERVICE = UUID.from_16_bits(0x113D, 'CTN Notification Service')
|
|
BT_CTN_PROFILE_SERVICE = UUID.from_16_bits(0x113E, 'CTN Profile')
|
|
BT_PNP_INFORMATION_SERVICE = UUID.from_16_bits(0x1200, 'PnPInformation')
|
|
BT_GENERIC_NETWORKING_SERVICE = UUID.from_16_bits(0x1201, 'GenericNetworking')
|
|
BT_GENERIC_FILE_TRANSFER_SERVICE = UUID.from_16_bits(0x1202, 'GenericFileTransfer')
|
|
BT_GENERIC_AUDIO_SERVICE = UUID.from_16_bits(0x1203, 'GenericAudio')
|
|
BT_GENERIC_TELEPHONY_SERVICE = UUID.from_16_bits(0x1204, 'GenericTelephony')
|
|
BT_UPNP_SERVICE = UUID.from_16_bits(0x1205, 'UPNP_Service')
|
|
BT_UPNP_IP_SERVICE = UUID.from_16_bits(0x1206, 'UPNP_IP_Service')
|
|
BT_ESDP_UPNP_IP_PAN_SERVICE = UUID.from_16_bits(0x1300, 'ESDP_UPNP_IP_PAN')
|
|
BT_ESDP_UPNP_IP_LAP_SERVICE = UUID.from_16_bits(0x1301, 'ESDP_UPNP_IP_LAP')
|
|
BT_ESDP_UPNP_L2CAP_SERVICE = UUID.from_16_bits(0x1302, 'ESDP_UPNP_L2CAP')
|
|
BT_VIDEO_SOURCE_SERVICE = UUID.from_16_bits(0x1303, 'VideoSource')
|
|
BT_VIDEO_SINK_SERVICE = UUID.from_16_bits(0x1304, 'VideoSink')
|
|
BT_VIDEO_DISTRIBUTION_SERVICE = UUID.from_16_bits(0x1305, 'VideoDistribution')
|
|
BT_HDP_SERVICE = UUID.from_16_bits(0x1400, 'HDP')
|
|
BT_HDP_SOURCE_SERVICE = UUID.from_16_bits(0x1401, 'HDP Source')
|
|
BT_HDP_SINK_SERVICE = UUID.from_16_bits(0x1402, 'HDP Sink')
|
|
|
|
# fmt: on
|
|
# pylint: enable=line-too-long
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# DeviceClass
|
|
# -----------------------------------------------------------------------------
|
|
class DeviceClass:
|
|
# fmt: off
|
|
# pylint: disable=line-too-long
|
|
|
|
# Major Service Classes (flags combined with OR)
|
|
LIMITED_DISCOVERABLE_MODE_SERVICE_CLASS = (1 << 0)
|
|
LE_AUDIO_SERVICE_CLASS = (1 << 1)
|
|
RESERVED = (1 << 2)
|
|
POSITIONING_SERVICE_CLASS = (1 << 3)
|
|
NETWORKING_SERVICE_CLASS = (1 << 4)
|
|
RENDERING_SERVICE_CLASS = (1 << 5)
|
|
CAPTURING_SERVICE_CLASS = (1 << 6)
|
|
OBJECT_TRANSFER_SERVICE_CLASS = (1 << 7)
|
|
AUDIO_SERVICE_CLASS = (1 << 8)
|
|
TELEPHONY_SERVICE_CLASS = (1 << 9)
|
|
INFORMATION_SERVICE_CLASS = (1 << 10)
|
|
|
|
SERVICE_CLASS_LABELS = [
|
|
'Limited Discoverable Mode',
|
|
'LE audio',
|
|
'(reserved)',
|
|
'Positioning',
|
|
'Networking',
|
|
'Rendering',
|
|
'Capturing',
|
|
'Object Transfer',
|
|
'Audio',
|
|
'Telephony',
|
|
'Information'
|
|
]
|
|
|
|
# Major Device Classes
|
|
MISCELLANEOUS_MAJOR_DEVICE_CLASS = 0x00
|
|
COMPUTER_MAJOR_DEVICE_CLASS = 0x01
|
|
PHONE_MAJOR_DEVICE_CLASS = 0x02
|
|
LAN_NETWORK_ACCESS_POINT_MAJOR_DEVICE_CLASS = 0x03
|
|
AUDIO_VIDEO_MAJOR_DEVICE_CLASS = 0x04
|
|
PERIPHERAL_MAJOR_DEVICE_CLASS = 0x05
|
|
IMAGING_MAJOR_DEVICE_CLASS = 0x06
|
|
WEARABLE_MAJOR_DEVICE_CLASS = 0x07
|
|
TOY_MAJOR_DEVICE_CLASS = 0x08
|
|
HEALTH_MAJOR_DEVICE_CLASS = 0x09
|
|
UNCATEGORIZED_MAJOR_DEVICE_CLASS = 0x1F
|
|
|
|
MAJOR_DEVICE_CLASS_NAMES = {
|
|
MISCELLANEOUS_MAJOR_DEVICE_CLASS: 'Miscellaneous',
|
|
COMPUTER_MAJOR_DEVICE_CLASS: 'Computer',
|
|
PHONE_MAJOR_DEVICE_CLASS: 'Phone',
|
|
LAN_NETWORK_ACCESS_POINT_MAJOR_DEVICE_CLASS: 'LAN/Network Access Point',
|
|
AUDIO_VIDEO_MAJOR_DEVICE_CLASS: 'Audio/Video',
|
|
PERIPHERAL_MAJOR_DEVICE_CLASS: 'Peripheral',
|
|
IMAGING_MAJOR_DEVICE_CLASS: 'Imaging',
|
|
WEARABLE_MAJOR_DEVICE_CLASS: 'Wearable',
|
|
TOY_MAJOR_DEVICE_CLASS: 'Toy',
|
|
HEALTH_MAJOR_DEVICE_CLASS: 'Health',
|
|
UNCATEGORIZED_MAJOR_DEVICE_CLASS: 'Uncategorized'
|
|
}
|
|
|
|
COMPUTER_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00
|
|
COMPUTER_DESKTOP_WORKSTATION_MINOR_DEVICE_CLASS = 0x01
|
|
COMPUTER_SERVER_CLASS_COMPUTER_MINOR_DEVICE_CLASS = 0x02
|
|
COMPUTER_LAPTOP_COMPUTER_MINOR_DEVICE_CLASS = 0x03
|
|
COMPUTER_HANDHELD_PC_PDA_MINOR_DEVICE_CLASS = 0x04
|
|
COMPUTER_PALM_SIZE_PC_PDA_MINOR_DEVICE_CLASS = 0x05
|
|
COMPUTER_WEARABLE_COMPUTER_MINOR_DEVICE_CLASS = 0x06
|
|
COMPUTER_TABLET_MINOR_DEVICE_CLASS = 0x07
|
|
|
|
COMPUTER_MINOR_DEVICE_CLASS_NAMES = {
|
|
COMPUTER_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized',
|
|
COMPUTER_DESKTOP_WORKSTATION_MINOR_DEVICE_CLASS: 'Desktop workstation',
|
|
COMPUTER_SERVER_CLASS_COMPUTER_MINOR_DEVICE_CLASS: 'Server-class computer',
|
|
COMPUTER_LAPTOP_COMPUTER_MINOR_DEVICE_CLASS: 'Laptop',
|
|
COMPUTER_HANDHELD_PC_PDA_MINOR_DEVICE_CLASS: 'Handheld PC/PDA',
|
|
COMPUTER_PALM_SIZE_PC_PDA_MINOR_DEVICE_CLASS: 'Palm-size PC/PDA',
|
|
COMPUTER_WEARABLE_COMPUTER_MINOR_DEVICE_CLASS: 'Wearable computer',
|
|
COMPUTER_TABLET_MINOR_DEVICE_CLASS: 'Tablet'
|
|
}
|
|
|
|
PHONE_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00
|
|
PHONE_CELLULAR_MINOR_DEVICE_CLASS = 0x01
|
|
PHONE_CORDLESS_MINOR_DEVICE_CLASS = 0x02
|
|
PHONE_SMARTPHONE_MINOR_DEVICE_CLASS = 0x03
|
|
PHONE_WIRED_MODEM_OR_VOICE_GATEWAY_MINOR_DEVICE_CLASS = 0x04
|
|
PHONE_COMMON_ISDN_MINOR_DEVICE_CLASS = 0x05
|
|
|
|
PHONE_MINOR_DEVICE_CLASS_NAMES = {
|
|
PHONE_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized',
|
|
PHONE_CELLULAR_MINOR_DEVICE_CLASS: 'Cellular',
|
|
PHONE_CORDLESS_MINOR_DEVICE_CLASS: 'Cordless',
|
|
PHONE_SMARTPHONE_MINOR_DEVICE_CLASS: 'Smartphone',
|
|
PHONE_WIRED_MODEM_OR_VOICE_GATEWAY_MINOR_DEVICE_CLASS: 'Wired modem or voice gateway',
|
|
PHONE_COMMON_ISDN_MINOR_DEVICE_CLASS: 'Common ISDN access'
|
|
}
|
|
|
|
AUDIO_VIDEO_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00
|
|
AUDIO_VIDEO_WEARABLE_HEADSET_DEVICE_MINOR_DEVICE_CLASS = 0x01
|
|
AUDIO_VIDEO_HANDS_FREE_DEVICE_MINOR_DEVICE_CLASS = 0x02
|
|
# (RESERVED) = 0x03
|
|
AUDIO_VIDEO_MICROPHONE_MINOR_DEVICE_CLASS = 0x04
|
|
AUDIO_VIDEO_LOUDSPEAKER_MINOR_DEVICE_CLASS = 0x05
|
|
AUDIO_VIDEO_HEADPHONES_MINOR_DEVICE_CLASS = 0x06
|
|
AUDIO_VIDEO_PORTABLE_AUDIO_MINOR_DEVICE_CLASS = 0x07
|
|
AUDIO_VIDEO_CAR_AUDIO_MINOR_DEVICE_CLASS = 0x08
|
|
AUDIO_VIDEO_SET_TOP_BOX_MINOR_DEVICE_CLASS = 0x09
|
|
AUDIO_VIDEO_HIFI_AUDIO_DEVICE_MINOR_DEVICE_CLASS = 0x0A
|
|
AUDIO_VIDEO_VCR_MINOR_DEVICE_CLASS = 0x0B
|
|
AUDIO_VIDEO_VIDEO_CAMERA_MINOR_DEVICE_CLASS = 0x0C
|
|
AUDIO_VIDEO_CAMCORDER_MINOR_DEVICE_CLASS = 0x0D
|
|
AUDIO_VIDEO_VIDEO_MONITOR_MINOR_DEVICE_CLASS = 0x0E
|
|
AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER_MINOR_DEVICE_CLASS = 0x0F
|
|
AUDIO_VIDEO_VIDEO_CONFERENCING_MINOR_DEVICE_CLASS = 0x10
|
|
# (RESERVED) = 0x11
|
|
AUDIO_VIDEO_GAMING_OR_TOY_MINOR_DEVICE_CLASS = 0x12
|
|
|
|
AUDIO_VIDEO_MINOR_DEVICE_CLASS_NAMES = {
|
|
AUDIO_VIDEO_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized',
|
|
AUDIO_VIDEO_WEARABLE_HEADSET_DEVICE_MINOR_DEVICE_CLASS: 'Wearable Headset Device',
|
|
AUDIO_VIDEO_HANDS_FREE_DEVICE_MINOR_DEVICE_CLASS: 'Hands-free Device',
|
|
AUDIO_VIDEO_MICROPHONE_MINOR_DEVICE_CLASS: 'Microphone',
|
|
AUDIO_VIDEO_LOUDSPEAKER_MINOR_DEVICE_CLASS: 'Loudspeaker',
|
|
AUDIO_VIDEO_HEADPHONES_MINOR_DEVICE_CLASS: 'Headphones',
|
|
AUDIO_VIDEO_PORTABLE_AUDIO_MINOR_DEVICE_CLASS: 'Portable Audio',
|
|
AUDIO_VIDEO_CAR_AUDIO_MINOR_DEVICE_CLASS: 'Car audio',
|
|
AUDIO_VIDEO_SET_TOP_BOX_MINOR_DEVICE_CLASS: 'Set-top box',
|
|
AUDIO_VIDEO_HIFI_AUDIO_DEVICE_MINOR_DEVICE_CLASS: 'HiFi Audio Device',
|
|
AUDIO_VIDEO_VCR_MINOR_DEVICE_CLASS: 'VCR',
|
|
AUDIO_VIDEO_VIDEO_CAMERA_MINOR_DEVICE_CLASS: 'Video Camera',
|
|
AUDIO_VIDEO_CAMCORDER_MINOR_DEVICE_CLASS: 'Camcorder',
|
|
AUDIO_VIDEO_VIDEO_MONITOR_MINOR_DEVICE_CLASS: 'Video Monitor',
|
|
AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER_MINOR_DEVICE_CLASS: 'Video Display and Loudspeaker',
|
|
AUDIO_VIDEO_VIDEO_CONFERENCING_MINOR_DEVICE_CLASS: 'Video Conferencing',
|
|
AUDIO_VIDEO_GAMING_OR_TOY_MINOR_DEVICE_CLASS: 'Gaming/Toy'
|
|
}
|
|
|
|
PERIPHERAL_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00
|
|
PERIPHERAL_KEYBOARD_MINOR_DEVICE_CLASS = 0x10
|
|
PERIPHERAL_POINTING_DEVICE_MINOR_DEVICE_CLASS = 0x20
|
|
PERIPHERAL_COMBO_KEYBOARD_POINTING_DEVICE_MINOR_DEVICE_CLASS = 0x30
|
|
PERIPHERAL_JOYSTICK_MINOR_DEVICE_CLASS = 0x01
|
|
PERIPHERAL_GAMEPAD_MINOR_DEVICE_CLASS = 0x02
|
|
PERIPHERAL_REMOTE_CONTROL_MINOR_DEVICE_CLASS = 0x03
|
|
PERIPHERAL_SENSING_DEVICE_MINOR_DEVICE_CLASS = 0x04
|
|
PERIPHERAL_DIGITIZER_TABLET_MINOR_DEVICE_CLASS = 0x05
|
|
PERIPHERAL_CARD_READER_MINOR_DEVICE_CLASS = 0x06
|
|
PERIPHERAL_DIGITAL_PEN_MINOR_DEVICE_CLASS = 0x07
|
|
PERIPHERAL_HANDHELD_SCANNER_MINOR_DEVICE_CLASS = 0x08
|
|
PERIPHERAL_HANDHELD_GESTURAL_INPUT_DEVICE_MINOR_DEVICE_CLASS = 0x09
|
|
|
|
PERIPHERAL_MINOR_DEVICE_CLASS_NAMES = {
|
|
PERIPHERAL_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized',
|
|
PERIPHERAL_KEYBOARD_MINOR_DEVICE_CLASS: 'Keyboard',
|
|
PERIPHERAL_POINTING_DEVICE_MINOR_DEVICE_CLASS: 'Pointing device',
|
|
PERIPHERAL_COMBO_KEYBOARD_POINTING_DEVICE_MINOR_DEVICE_CLASS: 'Combo keyboard/pointing device',
|
|
PERIPHERAL_JOYSTICK_MINOR_DEVICE_CLASS: 'Joystick',
|
|
PERIPHERAL_GAMEPAD_MINOR_DEVICE_CLASS: 'Gamepad',
|
|
PERIPHERAL_REMOTE_CONTROL_MINOR_DEVICE_CLASS: 'Remote control',
|
|
PERIPHERAL_SENSING_DEVICE_MINOR_DEVICE_CLASS: 'Sensing device',
|
|
PERIPHERAL_DIGITIZER_TABLET_MINOR_DEVICE_CLASS: 'Digitizer tablet',
|
|
PERIPHERAL_CARD_READER_MINOR_DEVICE_CLASS: 'Card Reader',
|
|
PERIPHERAL_DIGITAL_PEN_MINOR_DEVICE_CLASS: 'Digital Pen',
|
|
PERIPHERAL_HANDHELD_SCANNER_MINOR_DEVICE_CLASS: 'Handheld scanner',
|
|
PERIPHERAL_HANDHELD_GESTURAL_INPUT_DEVICE_MINOR_DEVICE_CLASS: 'Handheld gestural input device'
|
|
}
|
|
|
|
WEARABLE_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00
|
|
WEARABLE_WRISTWATCH_MINOR_DEVICE_CLASS = 0x01
|
|
WEARABLE_PAGER_MINOR_DEVICE_CLASS = 0x02
|
|
WEARABLE_JACKET_MINOR_DEVICE_CLASS = 0x03
|
|
WEARABLE_HELMET_MINOR_DEVICE_CLASS = 0x04
|
|
WEARABLE_GLASSES_MINOR_DEVICE_CLASS = 0x05
|
|
|
|
WEARABLE_MINOR_DEVICE_CLASS_NAMES = {
|
|
WEARABLE_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized',
|
|
WEARABLE_WRISTWATCH_MINOR_DEVICE_CLASS: 'Wristwatch',
|
|
WEARABLE_PAGER_MINOR_DEVICE_CLASS: 'Pager',
|
|
WEARABLE_JACKET_MINOR_DEVICE_CLASS: 'Jacket',
|
|
WEARABLE_HELMET_MINOR_DEVICE_CLASS: 'Helmet',
|
|
WEARABLE_GLASSES_MINOR_DEVICE_CLASS: 'Glasses',
|
|
}
|
|
|
|
TOY_UNCATEGORIZED_MINOR_DEVICE_CLASS = 0x00
|
|
TOY_ROBOT_MINOR_DEVICE_CLASS = 0x01
|
|
TOY_VEHICLE_MINOR_DEVICE_CLASS = 0x02
|
|
TOY_DOLL_ACTION_FIGURE_MINOR_DEVICE_CLASS = 0x03
|
|
TOY_CONTROLLER_MINOR_DEVICE_CLASS = 0x04
|
|
TOY_GAME_MINOR_DEVICE_CLASS = 0x05
|
|
|
|
TOY_MINOR_DEVICE_CLASS_NAMES = {
|
|
TOY_UNCATEGORIZED_MINOR_DEVICE_CLASS: 'Uncategorized',
|
|
TOY_ROBOT_MINOR_DEVICE_CLASS: 'Robot',
|
|
TOY_VEHICLE_MINOR_DEVICE_CLASS: 'Vehicle',
|
|
TOY_DOLL_ACTION_FIGURE_MINOR_DEVICE_CLASS: 'Doll/Action figure',
|
|
TOY_CONTROLLER_MINOR_DEVICE_CLASS: 'Controller',
|
|
TOY_GAME_MINOR_DEVICE_CLASS: 'Game',
|
|
}
|
|
|
|
HEALTH_UNDEFINED_MINOR_DEVICE_CLASS = 0x00
|
|
HEALTH_BLOOD_PRESSURE_MONITOR_MINOR_DEVICE_CLASS = 0x01
|
|
HEALTH_THERMOMETER_MINOR_DEVICE_CLASS = 0x02
|
|
HEALTH_WEIGHING_SCALE_MINOR_DEVICE_CLASS = 0x03
|
|
HEALTH_GLUCOSE_METER_MINOR_DEVICE_CLASS = 0x04
|
|
HEALTH_PULSE_OXIMETER_MINOR_DEVICE_CLASS = 0x05
|
|
HEALTH_HEART_PULSE_RATE_MONITOR_MINOR_DEVICE_CLASS = 0x06
|
|
HEALTH_HEALTH_DATA_DISPLAY_MINOR_DEVICE_CLASS = 0x07
|
|
HEALTH_STEP_COUNTER_MINOR_DEVICE_CLASS = 0x08
|
|
HEALTH_BODY_COMPOSITION_ANALYZER_MINOR_DEVICE_CLASS = 0x09
|
|
HEALTH_PEAK_FLOW_MONITOR_MINOR_DEVICE_CLASS = 0x0A
|
|
HEALTH_MEDICATION_MONITOR_MINOR_DEVICE_CLASS = 0x0B
|
|
HEALTH_KNEE_PROSTHESIS_MINOR_DEVICE_CLASS = 0x0C
|
|
HEALTH_ANKLE_PROSTHESIS_MINOR_DEVICE_CLASS = 0x0D
|
|
HEALTH_GENERIC_HEALTH_MANAGER_MINOR_DEVICE_CLASS = 0x0E
|
|
HEALTH_PERSONAL_MOBILITY_DEVICE_MINOR_DEVICE_CLASS = 0x0F
|
|
|
|
HEALTH_MINOR_DEVICE_CLASS_NAMES = {
|
|
HEALTH_UNDEFINED_MINOR_DEVICE_CLASS: 'Undefined',
|
|
HEALTH_BLOOD_PRESSURE_MONITOR_MINOR_DEVICE_CLASS: 'Blood Pressure Monitor',
|
|
HEALTH_THERMOMETER_MINOR_DEVICE_CLASS: 'Thermometer',
|
|
HEALTH_WEIGHING_SCALE_MINOR_DEVICE_CLASS: 'Weighing Scale',
|
|
HEALTH_GLUCOSE_METER_MINOR_DEVICE_CLASS: 'Glucose Meter',
|
|
HEALTH_PULSE_OXIMETER_MINOR_DEVICE_CLASS: 'Pulse Oximeter',
|
|
HEALTH_HEART_PULSE_RATE_MONITOR_MINOR_DEVICE_CLASS: 'Heart/Pulse Rate Monitor',
|
|
HEALTH_HEALTH_DATA_DISPLAY_MINOR_DEVICE_CLASS: 'Health Data Display',
|
|
HEALTH_STEP_COUNTER_MINOR_DEVICE_CLASS: 'Step Counter',
|
|
HEALTH_BODY_COMPOSITION_ANALYZER_MINOR_DEVICE_CLASS: 'Body Composition Analyzer',
|
|
HEALTH_PEAK_FLOW_MONITOR_MINOR_DEVICE_CLASS: 'Peak Flow Monitor',
|
|
HEALTH_MEDICATION_MONITOR_MINOR_DEVICE_CLASS: 'Medication Monitor',
|
|
HEALTH_KNEE_PROSTHESIS_MINOR_DEVICE_CLASS: 'Knee Prosthesis',
|
|
HEALTH_ANKLE_PROSTHESIS_MINOR_DEVICE_CLASS: 'Ankle Prosthesis',
|
|
HEALTH_GENERIC_HEALTH_MANAGER_MINOR_DEVICE_CLASS: 'Generic Health Manager',
|
|
HEALTH_PERSONAL_MOBILITY_DEVICE_MINOR_DEVICE_CLASS: 'Personal Mobility Device',
|
|
}
|
|
|
|
MINOR_DEVICE_CLASS_NAMES = {
|
|
COMPUTER_MAJOR_DEVICE_CLASS: COMPUTER_MINOR_DEVICE_CLASS_NAMES,
|
|
PHONE_MAJOR_DEVICE_CLASS: PHONE_MINOR_DEVICE_CLASS_NAMES,
|
|
AUDIO_VIDEO_MAJOR_DEVICE_CLASS: AUDIO_VIDEO_MINOR_DEVICE_CLASS_NAMES,
|
|
PERIPHERAL_MAJOR_DEVICE_CLASS: PERIPHERAL_MINOR_DEVICE_CLASS_NAMES,
|
|
WEARABLE_MAJOR_DEVICE_CLASS: WEARABLE_MINOR_DEVICE_CLASS_NAMES,
|
|
TOY_MAJOR_DEVICE_CLASS: TOY_MINOR_DEVICE_CLASS_NAMES,
|
|
HEALTH_MAJOR_DEVICE_CLASS: HEALTH_MINOR_DEVICE_CLASS_NAMES,
|
|
}
|
|
|
|
# fmt: on
|
|
# pylint: enable=line-too-long
|
|
|
|
@staticmethod
|
|
def split_class_of_device(class_of_device):
|
|
# Split the bit fields of the composite class of device value into:
|
|
# (service_classes, major_device_class, minor_device_class)
|
|
return (
|
|
(class_of_device >> 13 & 0x7FF),
|
|
(class_of_device >> 8 & 0x1F),
|
|
(class_of_device >> 2 & 0x3F),
|
|
)
|
|
|
|
@staticmethod
|
|
def pack_class_of_device(service_classes, major_device_class, minor_device_class):
|
|
return service_classes << 13 | major_device_class << 8 | minor_device_class << 2
|
|
|
|
@staticmethod
|
|
def service_class_labels(service_class_flags):
|
|
return bit_flags_to_strings(
|
|
service_class_flags, DeviceClass.SERVICE_CLASS_LABELS
|
|
)
|
|
|
|
@staticmethod
|
|
def major_device_class_name(device_class):
|
|
return name_or_number(DeviceClass.MAJOR_DEVICE_CLASS_NAMES, device_class)
|
|
|
|
@staticmethod
|
|
def minor_device_class_name(major_device_class, minor_device_class):
|
|
class_names = DeviceClass.MINOR_DEVICE_CLASS_NAMES.get(major_device_class)
|
|
if class_names is None:
|
|
return f'#{minor_device_class:02X}'
|
|
return name_or_number(class_names, minor_device_class)
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Appearance
|
|
# -----------------------------------------------------------------------------
|
|
class Appearance:
|
|
class Category(OpenIntEnum):
|
|
UNKNOWN = 0x0000
|
|
PHONE = 0x0001
|
|
COMPUTER = 0x0002
|
|
WATCH = 0x0003
|
|
CLOCK = 0x0004
|
|
DISPLAY = 0x0005
|
|
REMOTE_CONTROL = 0x0006
|
|
EYE_GLASSES = 0x0007
|
|
TAG = 0x0008
|
|
KEYRING = 0x0009
|
|
MEDIA_PLAYER = 0x000A
|
|
BARCODE_SCANNER = 0x000B
|
|
THERMOMETER = 0x000C
|
|
HEART_RATE_SENSOR = 0x000D
|
|
BLOOD_PRESSURE = 0x000E
|
|
HUMAN_INTERFACE_DEVICE = 0x000F
|
|
GLUCOSE_METER = 0x0010
|
|
RUNNING_WALKING_SENSOR = 0x0011
|
|
CYCLING = 0x0012
|
|
CONTROL_DEVICE = 0x0013
|
|
NETWORK_DEVICE = 0x0014
|
|
SENSOR = 0x0015
|
|
LIGHT_FIXTURES = 0x0016
|
|
FAN = 0x0017
|
|
HVAC = 0x0018
|
|
AIR_CONDITIONING = 0x0019
|
|
HUMIDIFIER = 0x001A
|
|
HEATING = 0x001B
|
|
ACCESS_CONTROL = 0x001C
|
|
MOTORIZED_DEVICE = 0x001D
|
|
POWER_DEVICE = 0x001E
|
|
LIGHT_SOURCE = 0x001F
|
|
WINDOW_COVERING = 0x0020
|
|
AUDIO_SINK = 0x0021
|
|
AUDIO_SOURCE = 0x0022
|
|
MOTORIZED_VEHICLE = 0x0023
|
|
DOMESTIC_APPLIANCE = 0x0024
|
|
WEARABLE_AUDIO_DEVICE = 0x0025
|
|
AIRCRAFT = 0x0026
|
|
AV_EQUIPMENT = 0x0027
|
|
DISPLAY_EQUIPMENT = 0x0028
|
|
HEARING_AID = 0x0029
|
|
GAMING = 0x002A
|
|
SIGNAGE = 0x002B
|
|
PULSE_OXIMETER = 0x0031
|
|
WEIGHT_SCALE = 0x0032
|
|
PERSONAL_MOBILITY_DEVICE = 0x0033
|
|
CONTINUOUS_GLUCOSE_MONITOR = 0x0034
|
|
INSULIN_PUMP = 0x0035
|
|
MEDICATION_DELIVERY = 0x0036
|
|
SPIROMETER = 0x0037
|
|
OUTDOOR_SPORTS_ACTIVITY = 0x0051
|
|
|
|
class UnknownSubcategory(OpenIntEnum):
|
|
GENERIC_UNKNOWN = 0x00
|
|
|
|
class PhoneSubcategory(OpenIntEnum):
|
|
GENERIC_PHONE = 0x00
|
|
|
|
class ComputerSubcategory(OpenIntEnum):
|
|
GENERIC_COMPUTER = 0x00
|
|
DESKTOP_WORKSTATION = 0x01
|
|
SERVER_CLASS_COMPUTER = 0x02
|
|
LAPTOP = 0x03
|
|
HANDHELD_PC_PDA = 0x04
|
|
PALM_SIZE_PC_PDA = 0x05
|
|
WEARABLE_COMPUTER = 0x06
|
|
TABLET = 0x07
|
|
DOCKING_STATION = 0x08
|
|
ALL_IN_ONE = 0x09
|
|
BLADE_SERVER = 0x0A
|
|
CONVERTIBLE = 0x0B
|
|
DETACHABLE = 0x0C
|
|
IOT_GATEWAY = 0x0D
|
|
MINI_PC = 0x0E
|
|
STICK_PC = 0x0F
|
|
|
|
class WatchSubcategory(OpenIntEnum):
|
|
GENENERIC_WATCH = 0x00
|
|
SPORTS_WATCH = 0x01
|
|
SMARTWATCH = 0x02
|
|
|
|
class ClockSubcategory(OpenIntEnum):
|
|
GENERIC_CLOCK = 0x00
|
|
|
|
class DisplaySubcategory(OpenIntEnum):
|
|
GENERIC_DISPLAY = 0x00
|
|
|
|
class RemoteControlSubcategory(OpenIntEnum):
|
|
GENERIC_REMOTE_CONTROL = 0x00
|
|
|
|
class EyeglassesSubcategory(OpenIntEnum):
|
|
GENERIC_EYEGLASSES = 0x00
|
|
|
|
class TagSubcategory(OpenIntEnum):
|
|
GENERIC_TAG = 0x00
|
|
|
|
class KeyringSubcategory(OpenIntEnum):
|
|
GENERIC_KEYRING = 0x00
|
|
|
|
class MediaPlayerSubcategory(OpenIntEnum):
|
|
GENERIC_MEDIA_PLAYER = 0x00
|
|
|
|
class BarcodeScannerSubcategory(OpenIntEnum):
|
|
GENERIC_BARCODE_SCANNER = 0x00
|
|
|
|
class ThermometerSubcategory(OpenIntEnum):
|
|
GENERIC_THERMOMETER = 0x00
|
|
EAR_THERMOMETER = 0x01
|
|
|
|
class HeartRateSensorSubcategory(OpenIntEnum):
|
|
GENERIC_HEART_RATE_SENSOR = 0x00
|
|
HEART_RATE_BELT = 0x01
|
|
|
|
class BloodPressureSubcategory(OpenIntEnum):
|
|
GENERIC_BLOOD_PRESSURE = 0x00
|
|
ARM_BLOOD_PRESSURE = 0x01
|
|
WRIST_BLOOD_PRESSURE = 0x02
|
|
|
|
class HumanInterfaceDeviceSubcategory(OpenIntEnum):
|
|
GENERIC_HUMAN_INTERFACE_DEVICE = 0x00
|
|
KEYBOARD = 0x01
|
|
MOUSE = 0x02
|
|
JOYSTICK = 0x03
|
|
GAMEPAD = 0x04
|
|
DIGITIZER_TABLET = 0x05
|
|
CARD_READER = 0x06
|
|
DIGITAL_PEN = 0x07
|
|
BARCODE_SCANNER = 0x08
|
|
TOUCHPAD = 0x09
|
|
PRESENTATION_REMOTE = 0x0A
|
|
|
|
class GlucoseMeterSubcategory(OpenIntEnum):
|
|
GENERIC_GLUCOSE_METER = 0x00
|
|
|
|
class RunningWalkingSensorSubcategory(OpenIntEnum):
|
|
GENERIC_RUNNING_WALKING_SENSOR = 0x00
|
|
IN_SHOE_RUNNING_WALKING_SENSOR = 0x01
|
|
ON_SHOW_RUNNING_WALKING_SENSOR = 0x02
|
|
ON_HIP_RUNNING_WALKING_SENSOR = 0x03
|
|
|
|
class CyclingSubcategory(OpenIntEnum):
|
|
GENERIC_CYCLING = 0x00
|
|
CYCLING_COMPUTER = 0x01
|
|
SPEED_SENSOR = 0x02
|
|
CADENCE_SENSOR = 0x03
|
|
POWER_SENSOR = 0x04
|
|
SPEED_AND_CADENCE_SENSOR = 0x05
|
|
|
|
class ControlDeviceSubcategory(OpenIntEnum):
|
|
GENERIC_CONTROL_DEVICE = 0x00
|
|
SWITCH = 0x01
|
|
MULTI_SWITCH = 0x02
|
|
BUTTON = 0x03
|
|
SLIDER = 0x04
|
|
ROTARY_SWITCH = 0x05
|
|
TOUCH_PANEL = 0x06
|
|
SINGLE_SWITCH = 0x07
|
|
DOUBLE_SWITCH = 0x08
|
|
TRIPLE_SWITCH = 0x09
|
|
BATTERY_SWITCH = 0x0A
|
|
ENERGY_HARVESTING_SWITCH = 0x0B
|
|
PUSH_BUTTON = 0x0C
|
|
|
|
class NetworkDeviceSubcategory(OpenIntEnum):
|
|
GENERIC_NETWORK_DEVICE = 0x00
|
|
ACCESS_POINT = 0x01
|
|
MESH_DEVICE = 0x02
|
|
MESH_NETWORK_PROXY = 0x03
|
|
|
|
class SensorSubcategory(OpenIntEnum):
|
|
GENERIC_SENSOR = 0x00
|
|
MOTION_SENSOR = 0x01
|
|
AIR_QUALITY_SENSOR = 0x02
|
|
TEMPERATURE_SENSOR = 0x03
|
|
HUMIDITY_SENSOR = 0x04
|
|
LEAK_SENSOR = 0x05
|
|
SMOKE_SENSOR = 0x06
|
|
OCCUPANCY_SENSOR = 0x07
|
|
CONTACT_SENSOR = 0x08
|
|
CARBON_MONOXIDE_SENSOR = 0x09
|
|
CARBON_DIOXIDE_SENSOR = 0x0A
|
|
AMBIENT_LIGHT_SENSOR = 0x0B
|
|
ENERGY_SENSOR = 0x0C
|
|
COLOR_LIGHT_SENSOR = 0x0D
|
|
RAIN_SENSOR = 0x0E
|
|
FIRE_SENSOR = 0x0F
|
|
WIND_SENSOR = 0x10
|
|
PROXIMITY_SENSOR = 0x11
|
|
MULTI_SENSOR = 0x12
|
|
FLUSH_MOUNTED_SENSOR = 0x13
|
|
CEILING_MOUNTED_SENSOR = 0x14
|
|
WALL_MOUNTED_SENSOR = 0x15
|
|
MULTISENSOR = 0x16
|
|
ENERGY_METER = 0x17
|
|
FLAME_DETECTOR = 0x18
|
|
VEHICLE_TIRE_PRESSURE_SENSOR = 0x19
|
|
|
|
class LightFixturesSubcategory(OpenIntEnum):
|
|
GENERIC_LIGHT_FIXTURES = 0x00
|
|
WALL_LIGHT = 0x01
|
|
CEILING_LIGHT = 0x02
|
|
FLOOR_LIGHT = 0x03
|
|
CABINET_LIGHT = 0x04
|
|
DESK_LIGHT = 0x05
|
|
TROFFER_LIGHT = 0x06
|
|
PENDANT_LIGHT = 0x07
|
|
IN_GROUND_LIGHT = 0x08
|
|
FLOOD_LIGHT = 0x09
|
|
UNDERWATER_LIGHT = 0x0A
|
|
BOLLARD_WITH_LIGHT = 0x0B
|
|
PATHWAY_LIGHT = 0x0C
|
|
GARDEN_LIGHT = 0x0D
|
|
POLE_TOP_LIGHT = 0x0E
|
|
SPOTLIGHT = 0x0F
|
|
LINEAR_LIGHT = 0x10
|
|
STREET_LIGHT = 0x11
|
|
SHELVES_LIGHT = 0x12
|
|
BAY_LIGHT = 0x013
|
|
EMERGENCY_EXIT_LIGHT = 0x14
|
|
LIGHT_CONTROLLER = 0x15
|
|
LIGHT_DRIVER = 0x16
|
|
BULB = 0x17
|
|
LOW_BAY_LIGHT = 0x18
|
|
HIGH_BAY_LIGHT = 0x19
|
|
|
|
class FanSubcategory(OpenIntEnum):
|
|
GENERIC_FAN = 0x00
|
|
CEILING_FAN = 0x01
|
|
AXIAL_FAN = 0x02
|
|
EXHAUST_FAN = 0x03
|
|
PEDESTAL_FAN = 0x04
|
|
DESK_FAN = 0x05
|
|
WALL_FAN = 0x06
|
|
|
|
class HvacSubcategory(OpenIntEnum):
|
|
GENERIC_HVAC = 0x00
|
|
THERMOSTAT = 0x01
|
|
HUMIDIFIER = 0x02
|
|
DEHUMIDIFIER = 0x03
|
|
HEATER = 0x04
|
|
RADIATOR = 0x05
|
|
BOILER = 0x06
|
|
HEAT_PUMP = 0x07
|
|
INFRARED_HEATER = 0x08
|
|
RADIANT_PANEL_HEATER = 0x09
|
|
FAN_HEATER = 0x0A
|
|
AIR_CURTAIN = 0x0B
|
|
|
|
class AirConditioningSubcategory(OpenIntEnum):
|
|
GENERIC_AIR_CONDITIONING = 0x00
|
|
|
|
class HumidifierSubcategory(OpenIntEnum):
|
|
GENERIC_HUMIDIFIER = 0x00
|
|
|
|
class HeatingSubcategory(OpenIntEnum):
|
|
GENERIC_HEATING = 0x00
|
|
RADIATOR = 0x01
|
|
BOILER = 0x02
|
|
HEAT_PUMP = 0x03
|
|
INFRARED_HEATER = 0x04
|
|
RADIANT_PANEL_HEATER = 0x05
|
|
FAN_HEATER = 0x06
|
|
AIR_CURTAIN = 0x07
|
|
|
|
class AccessControlSubcategory(OpenIntEnum):
|
|
GENERIC_ACCESS_CONTROL = 0x00
|
|
ACCESS_DOOR = 0x01
|
|
GARAGE_DOOR = 0x02
|
|
EMERGENCY_EXIT_DOOR = 0x03
|
|
ACCESS_LOCK = 0x04
|
|
ELEVATOR = 0x05
|
|
WINDOW = 0x06
|
|
ENTRANCE_GATE = 0x07
|
|
DOOR_LOCK = 0x08
|
|
LOCKER = 0x09
|
|
|
|
class MotorizedDeviceSubcategory(OpenIntEnum):
|
|
GENERIC_MOTORIZED_DEVICE = 0x00
|
|
MOTORIZED_GATE = 0x01
|
|
AWNING = 0x02
|
|
BLINDS_OR_SHADES = 0x03
|
|
CURTAINS = 0x04
|
|
SCREEN = 0x05
|
|
|
|
class PowerDeviceSubcategory(OpenIntEnum):
|
|
GENERIC_POWER_DEVICE = 0x00
|
|
POWER_OUTLET = 0x01
|
|
POWER_STRIP = 0x02
|
|
PLUG = 0x03
|
|
POWER_SUPPLY = 0x04
|
|
LED_DRIVER = 0x05
|
|
FLUORESCENT_LAMP_GEAR = 0x06
|
|
HID_LAMP_GEAR = 0x07
|
|
CHARGE_CASE = 0x08
|
|
POWER_BANK = 0x09
|
|
|
|
class LightSourceSubcategory(OpenIntEnum):
|
|
GENERIC_LIGHT_SOURCE = 0x00
|
|
INCANDESCENT_LIGHT_BULB = 0x01
|
|
LED_LAMP = 0x02
|
|
HID_LAMP = 0x03
|
|
FLUORESCENT_LAMP = 0x04
|
|
LED_ARRAY = 0x05
|
|
MULTI_COLOR_LED_ARRAY = 0x06
|
|
LOW_VOLTAGE_HALOGEN = 0x07
|
|
ORGANIC_LIGHT_EMITTING_DIODE = 0x08
|
|
|
|
class WindowCoveringSubcategory(OpenIntEnum):
|
|
GENERIC_WINDOW_COVERING = 0x00
|
|
WINDOW_SHADES = 0x01
|
|
WINDOW_BLINDS = 0x02
|
|
WINDOW_AWNING = 0x03
|
|
WINDOW_CURTAIN = 0x04
|
|
EXTERIOR_SHUTTER = 0x05
|
|
EXTERIOR_SCREEN = 0x06
|
|
|
|
class AudioSinkSubcategory(OpenIntEnum):
|
|
GENERIC_AUDIO_SINK = 0x00
|
|
STANDALONE_SPEAKER = 0x01
|
|
SOUNDBAR = 0x02
|
|
BOOKSHELF_SPEAKER = 0x03
|
|
STANDMOUNTED_SPEAKER = 0x04
|
|
SPEAKERPHONE = 0x05
|
|
|
|
class AudioSourceSubcategory(OpenIntEnum):
|
|
GENERIC_AUDIO_SOURCE = 0x00
|
|
MICROPHONE = 0x01
|
|
ALARM = 0x02
|
|
BELL = 0x03
|
|
HORN = 0x04
|
|
BROADCASTING_DEVICE = 0x05
|
|
SERVICE_DESK = 0x06
|
|
KIOSK = 0x07
|
|
BROADCASTING_ROOM = 0x08
|
|
AUDITORIUM = 0x09
|
|
|
|
class MotorizedVehicleSubcategory(OpenIntEnum):
|
|
GENERIC_MOTORIZED_VEHICLE = 0x00
|
|
CAR = 0x01
|
|
LARGE_GOODS_VEHICLE = 0x02
|
|
TWO_WHEELED_VEHICLE = 0x03
|
|
MOTORBIKE = 0x04
|
|
SCOOTER = 0x05
|
|
MOPED = 0x06
|
|
THREE_WHEELED_VEHICLE = 0x07
|
|
LIGHT_VEHICLE = 0x08
|
|
QUAD_BIKE = 0x09
|
|
MINIBUS = 0x0A
|
|
BUS = 0x0B
|
|
TROLLEY = 0x0C
|
|
AGRICULTURAL_VEHICLE = 0x0D
|
|
CAMPER_CARAVAN = 0x0E
|
|
RECREATIONAL_VEHICLE_MOTOR_HOME = 0x0F
|
|
|
|
class DomesticApplianceSubcategory(OpenIntEnum):
|
|
GENERIC_DOMESTIC_APPLIANCE = 0x00
|
|
REFRIGERATOR = 0x01
|
|
FREEZER = 0x02
|
|
OVEN = 0x03
|
|
MICROWAVE = 0x04
|
|
TOASTER = 0x05
|
|
WASHING_MACHINE = 0x06
|
|
DRYER = 0x07
|
|
COFFEE_MAKER = 0x08
|
|
CLOTHES_IRON = 0x09
|
|
CURLING_IRON = 0x0A
|
|
HAIR_DRYER = 0x0B
|
|
VACUUM_CLEANER = 0x0C
|
|
ROBOTIC_VACUUM_CLEANER = 0x0D
|
|
RICE_COOKER = 0x0E
|
|
CLOTHES_STEAMER = 0x0F
|
|
|
|
class WearableAudioDeviceSubcategory(OpenIntEnum):
|
|
GENERIC_WEARABLE_AUDIO_DEVICE = 0x00
|
|
EARBUD = 0x01
|
|
HEADSET = 0x02
|
|
HEADPHONES = 0x03
|
|
NECK_BAND = 0x04
|
|
|
|
class AircraftSubcategory(OpenIntEnum):
|
|
GENERIC_AIRCRAFT = 0x00
|
|
LIGHT_AIRCRAFT = 0x01
|
|
MICROLIGHT = 0x02
|
|
PARAGLIDER = 0x03
|
|
LARGE_PASSENGER_AIRCRAFT = 0x04
|
|
|
|
class AvEquipmentSubcategory(OpenIntEnum):
|
|
GENERIC_AV_EQUIPMENT = 0x00
|
|
AMPLIFIER = 0x01
|
|
RECEIVER = 0x02
|
|
RADIO = 0x03
|
|
TUNER = 0x04
|
|
TURNTABLE = 0x05
|
|
CD_PLAYER = 0x06
|
|
DVD_PLAYER = 0x07
|
|
BLUERAY_PLAYER = 0x08
|
|
OPTICAL_DISC_PLAYER = 0x09
|
|
SET_TOP_BOX = 0x0A
|
|
|
|
class DisplayEquipmentSubcategory(OpenIntEnum):
|
|
GENERIC_DISPLAY_EQUIPMENT = 0x00
|
|
TELEVISION = 0x01
|
|
MONITOR = 0x02
|
|
PROJECTOR = 0x03
|
|
|
|
class HearingAidSubcategory(OpenIntEnum):
|
|
GENERIC_HEARING_AID = 0x00
|
|
IN_EAR_HEARING_AID = 0x01
|
|
BEHIND_EAR_HEARING_AID = 0x02
|
|
COCHLEAR_IMPLANT = 0x03
|
|
|
|
class GamingSubcategory(OpenIntEnum):
|
|
GENERIC_GAMING = 0x00
|
|
HOME_VIDEO_GAME_CONSOLE = 0x01
|
|
PORTABLE_HANDHELD_CONSOLE = 0x02
|
|
|
|
class SignageSubcategory(OpenIntEnum):
|
|
GENERIC_SIGNAGE = 0x00
|
|
DIGITAL_SIGNAGE = 0x01
|
|
ELECTRONIC_LABEL = 0x02
|
|
|
|
class PulseOximeterSubcategory(OpenIntEnum):
|
|
GENERIC_PULSE_OXIMETER = 0x00
|
|
FINGERTIP_PULSE_OXIMETER = 0x01
|
|
WRIST_WORN_PULSE_OXIMETER = 0x02
|
|
|
|
class WeightScaleSubcategory(OpenIntEnum):
|
|
GENERIC_WEIGHT_SCALE = 0x00
|
|
|
|
class PersonalMobilityDeviceSubcategory(OpenIntEnum):
|
|
GENERIC_PERSONAL_MOBILITY_DEVICE = 0x00
|
|
POWERED_WHEELCHAIR = 0x01
|
|
MOBILITY_SCOOTER = 0x02
|
|
|
|
class ContinuousGlucoseMonitorSubcategory(OpenIntEnum):
|
|
GENERIC_CONTINUOUS_GLUCOSE_MONITOR = 0x00
|
|
|
|
class InsulinPumpSubcategory(OpenIntEnum):
|
|
GENERIC_INSULIN_PUMP = 0x00
|
|
INSULIN_PUMP_DURABLE_PUMP = 0x01
|
|
INSULIN_PUMP_PATCH_PUMP = 0x02
|
|
INSULIN_PEN = 0x03
|
|
|
|
class MedicationDeliverySubcategory(OpenIntEnum):
|
|
GENERIC_MEDICATION_DELIVERY = 0x00
|
|
|
|
class SpirometerSubcategory(OpenIntEnum):
|
|
GENERIC_SPIROMETER = 0x00
|
|
HANDHELD_SPIROMETER = 0x01
|
|
|
|
class OutdoorSportsActivitySubcategory(OpenIntEnum):
|
|
GENERIC_OUTDOOR_SPORTS_ACTIVITY = 0x00
|
|
LOCATION_DISPLAY = 0x01
|
|
LOCATION_AND_NAVIGATION_DISPLAY = 0x02
|
|
LOCATION_POD = 0x03
|
|
LOCATION_AND_NAVIGATION_POD = 0x04
|
|
|
|
class _OpenSubcategory(OpenIntEnum):
|
|
GENERIC = 0x00
|
|
|
|
SUBCATEGORY_CLASSES = {
|
|
Category.UNKNOWN: UnknownSubcategory,
|
|
Category.PHONE: PhoneSubcategory,
|
|
Category.COMPUTER: ComputerSubcategory,
|
|
Category.WATCH: WatchSubcategory,
|
|
Category.CLOCK: ClockSubcategory,
|
|
Category.DISPLAY: DisplaySubcategory,
|
|
Category.REMOTE_CONTROL: RemoteControlSubcategory,
|
|
Category.EYE_GLASSES: EyeglassesSubcategory,
|
|
Category.TAG: TagSubcategory,
|
|
Category.KEYRING: KeyringSubcategory,
|
|
Category.MEDIA_PLAYER: MediaPlayerSubcategory,
|
|
Category.BARCODE_SCANNER: BarcodeScannerSubcategory,
|
|
Category.THERMOMETER: ThermometerSubcategory,
|
|
Category.HEART_RATE_SENSOR: HeartRateSensorSubcategory,
|
|
Category.BLOOD_PRESSURE: BloodPressureSubcategory,
|
|
Category.HUMAN_INTERFACE_DEVICE: HumanInterfaceDeviceSubcategory,
|
|
Category.GLUCOSE_METER: GlucoseMeterSubcategory,
|
|
Category.RUNNING_WALKING_SENSOR: RunningWalkingSensorSubcategory,
|
|
Category.CYCLING: CyclingSubcategory,
|
|
Category.CONTROL_DEVICE: ControlDeviceSubcategory,
|
|
Category.NETWORK_DEVICE: NetworkDeviceSubcategory,
|
|
Category.SENSOR: SensorSubcategory,
|
|
Category.LIGHT_FIXTURES: LightFixturesSubcategory,
|
|
Category.FAN: FanSubcategory,
|
|
Category.HVAC: HvacSubcategory,
|
|
Category.AIR_CONDITIONING: AirConditioningSubcategory,
|
|
Category.HUMIDIFIER: HumidifierSubcategory,
|
|
Category.HEATING: HeatingSubcategory,
|
|
Category.ACCESS_CONTROL: AccessControlSubcategory,
|
|
Category.MOTORIZED_DEVICE: MotorizedDeviceSubcategory,
|
|
Category.POWER_DEVICE: PowerDeviceSubcategory,
|
|
Category.LIGHT_SOURCE: LightSourceSubcategory,
|
|
Category.WINDOW_COVERING: WindowCoveringSubcategory,
|
|
Category.AUDIO_SINK: AudioSinkSubcategory,
|
|
Category.AUDIO_SOURCE: AudioSourceSubcategory,
|
|
Category.MOTORIZED_VEHICLE: MotorizedVehicleSubcategory,
|
|
Category.DOMESTIC_APPLIANCE: DomesticApplianceSubcategory,
|
|
Category.WEARABLE_AUDIO_DEVICE: WearableAudioDeviceSubcategory,
|
|
Category.AIRCRAFT: AircraftSubcategory,
|
|
Category.AV_EQUIPMENT: AvEquipmentSubcategory,
|
|
Category.DISPLAY_EQUIPMENT: DisplayEquipmentSubcategory,
|
|
Category.HEARING_AID: HearingAidSubcategory,
|
|
Category.GAMING: GamingSubcategory,
|
|
Category.SIGNAGE: SignageSubcategory,
|
|
Category.PULSE_OXIMETER: PulseOximeterSubcategory,
|
|
Category.WEIGHT_SCALE: WeightScaleSubcategory,
|
|
Category.PERSONAL_MOBILITY_DEVICE: PersonalMobilityDeviceSubcategory,
|
|
Category.CONTINUOUS_GLUCOSE_MONITOR: ContinuousGlucoseMonitorSubcategory,
|
|
Category.INSULIN_PUMP: InsulinPumpSubcategory,
|
|
Category.MEDICATION_DELIVERY: MedicationDeliverySubcategory,
|
|
Category.SPIROMETER: SpirometerSubcategory,
|
|
Category.OUTDOOR_SPORTS_ACTIVITY: OutdoorSportsActivitySubcategory,
|
|
}
|
|
|
|
category: Category
|
|
subcategory: enum.IntEnum
|
|
|
|
@classmethod
|
|
def from_int(cls, appearance: int) -> Self:
|
|
category = cls.Category(appearance >> 6)
|
|
return cls(category, appearance & 0x3F)
|
|
|
|
def __init__(self, category: Category, subcategory: int) -> None:
|
|
self.category = category
|
|
if subcategory_class := self.SUBCATEGORY_CLASSES.get(category):
|
|
self.subcategory = subcategory_class(subcategory)
|
|
else:
|
|
self.subcategory = self._OpenSubcategory(subcategory)
|
|
|
|
def __int__(self) -> int:
|
|
return self.category << 6 | self.subcategory
|
|
|
|
def __repr__(self) -> str:
|
|
return (
|
|
'Appearance('
|
|
f'category={self.category.name}, '
|
|
f'subcategory={self.subcategory.name}'
|
|
')'
|
|
)
|
|
|
|
def __str__(self) -> str:
|
|
return f'{self.category.name}/{self.subcategory.name}'
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Advertising Data
|
|
# -----------------------------------------------------------------------------
|
|
AdvertisingDataObject = Union[
|
|
List[UUID],
|
|
Tuple[UUID, bytes],
|
|
bytes,
|
|
str,
|
|
int,
|
|
Tuple[int, int],
|
|
Tuple[int, bytes],
|
|
Appearance,
|
|
]
|
|
|
|
|
|
class AdvertisingData:
|
|
# fmt: off
|
|
# pylint: disable=line-too-long
|
|
|
|
FLAGS = 0x01
|
|
INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS = 0x02
|
|
COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS = 0x03
|
|
INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS = 0x04
|
|
COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS = 0x05
|
|
INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS = 0x06
|
|
COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS = 0x07
|
|
SHORTENED_LOCAL_NAME = 0x08
|
|
COMPLETE_LOCAL_NAME = 0x09
|
|
TX_POWER_LEVEL = 0x0A
|
|
CLASS_OF_DEVICE = 0x0D
|
|
SIMPLE_PAIRING_HASH_C = 0x0E
|
|
SIMPLE_PAIRING_HASH_C_192 = 0x0E
|
|
SIMPLE_PAIRING_RANDOMIZER_R = 0x0F
|
|
SIMPLE_PAIRING_RANDOMIZER_R_192 = 0x0F
|
|
DEVICE_ID = 0x10
|
|
SECURITY_MANAGER_TK_VALUE = 0x10
|
|
SECURITY_MANAGER_OUT_OF_BAND_FLAGS = 0x11
|
|
PERIPHERAL_CONNECTION_INTERVAL_RANGE = 0x12
|
|
LIST_OF_16_BIT_SERVICE_SOLICITATION_UUIDS = 0x14
|
|
LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS = 0x15
|
|
SERVICE_DATA = 0x16
|
|
SERVICE_DATA_16_BIT_UUID = 0x16
|
|
PUBLIC_TARGET_ADDRESS = 0x17
|
|
RANDOM_TARGET_ADDRESS = 0x18
|
|
APPEARANCE = 0x19
|
|
ADVERTISING_INTERVAL = 0x1A
|
|
LE_BLUETOOTH_DEVICE_ADDRESS = 0x1B
|
|
LE_ROLE = 0x1C
|
|
SIMPLE_PAIRING_HASH_C_256 = 0x1D
|
|
SIMPLE_PAIRING_RANDOMIZER_R_256 = 0x1E
|
|
LIST_OF_32_BIT_SERVICE_SOLICITATION_UUIDS = 0x1F
|
|
SERVICE_DATA_32_BIT_UUID = 0x20
|
|
SERVICE_DATA_128_BIT_UUID = 0x21
|
|
LE_SECURE_CONNECTIONS_CONFIRMATION_VALUE = 0x22
|
|
LE_SECURE_CONNECTIONS_RANDOM_VALUE = 0x23
|
|
URI = 0x24
|
|
INDOOR_POSITIONING = 0x25
|
|
TRANSPORT_DISCOVERY_DATA = 0x26
|
|
LE_SUPPORTED_FEATURES = 0x27
|
|
CHANNEL_MAP_UPDATE_INDICATION = 0x28
|
|
PB_ADV = 0x29
|
|
MESH_MESSAGE = 0x2A
|
|
MESH_BEACON = 0x2B
|
|
BIGINFO = 0x2C
|
|
BROADCAST_CODE = 0x2D
|
|
RESOLVABLE_SET_IDENTIFIER = 0x2E
|
|
ADVERTISING_INTERVAL_LONG = 0x2F
|
|
BROADCAST_NAME = 0x30
|
|
ENCRYPTED_ADVERTISING_DATA = 0X31
|
|
PERIODIC_ADVERTISING_RESPONSE_TIMING_INFORMATION = 0X32
|
|
ELECTRONIC_SHELF_LABEL = 0X34
|
|
THREE_D_INFORMATION_DATA = 0x3D
|
|
MANUFACTURER_SPECIFIC_DATA = 0xFF
|
|
|
|
AD_TYPE_NAMES = {
|
|
FLAGS: 'FLAGS',
|
|
INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS: 'INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS',
|
|
COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS: 'COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS',
|
|
INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS: 'INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS',
|
|
COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS: 'COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS',
|
|
INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS: 'INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS',
|
|
COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS: 'COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS',
|
|
SHORTENED_LOCAL_NAME: 'SHORTENED_LOCAL_NAME',
|
|
COMPLETE_LOCAL_NAME: 'COMPLETE_LOCAL_NAME',
|
|
TX_POWER_LEVEL: 'TX_POWER_LEVEL',
|
|
CLASS_OF_DEVICE: 'CLASS_OF_DEVICE',
|
|
SIMPLE_PAIRING_HASH_C: 'SIMPLE_PAIRING_HASH_C',
|
|
SIMPLE_PAIRING_HASH_C_192: 'SIMPLE_PAIRING_HASH_C_192',
|
|
SIMPLE_PAIRING_RANDOMIZER_R: 'SIMPLE_PAIRING_RANDOMIZER_R',
|
|
SIMPLE_PAIRING_RANDOMIZER_R_192: 'SIMPLE_PAIRING_RANDOMIZER_R_192',
|
|
DEVICE_ID: 'DEVICE_ID',
|
|
SECURITY_MANAGER_TK_VALUE: 'SECURITY_MANAGER_TK_VALUE',
|
|
SECURITY_MANAGER_OUT_OF_BAND_FLAGS: 'SECURITY_MANAGER_OUT_OF_BAND_FLAGS',
|
|
PERIPHERAL_CONNECTION_INTERVAL_RANGE: 'PERIPHERAL_CONNECTION_INTERVAL_RANGE',
|
|
LIST_OF_16_BIT_SERVICE_SOLICITATION_UUIDS: 'LIST_OF_16_BIT_SERVICE_SOLICITATION_UUIDS',
|
|
LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS: 'LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS',
|
|
SERVICE_DATA_16_BIT_UUID: 'SERVICE_DATA_16_BIT_UUID',
|
|
PUBLIC_TARGET_ADDRESS: 'PUBLIC_TARGET_ADDRESS',
|
|
RANDOM_TARGET_ADDRESS: 'RANDOM_TARGET_ADDRESS',
|
|
APPEARANCE: 'APPEARANCE',
|
|
ADVERTISING_INTERVAL: 'ADVERTISING_INTERVAL',
|
|
LE_BLUETOOTH_DEVICE_ADDRESS: 'LE_BLUETOOTH_DEVICE_ADDRESS',
|
|
LE_ROLE: 'LE_ROLE',
|
|
SIMPLE_PAIRING_HASH_C_256: 'SIMPLE_PAIRING_HASH_C_256',
|
|
SIMPLE_PAIRING_RANDOMIZER_R_256: 'SIMPLE_PAIRING_RANDOMIZER_R_256',
|
|
LIST_OF_32_BIT_SERVICE_SOLICITATION_UUIDS: 'LIST_OF_32_BIT_SERVICE_SOLICITATION_UUIDS',
|
|
SERVICE_DATA_32_BIT_UUID: 'SERVICE_DATA_32_BIT_UUID',
|
|
SERVICE_DATA_128_BIT_UUID: 'SERVICE_DATA_128_BIT_UUID',
|
|
LE_SECURE_CONNECTIONS_CONFIRMATION_VALUE: 'LE_SECURE_CONNECTIONS_CONFIRMATION_VALUE',
|
|
LE_SECURE_CONNECTIONS_RANDOM_VALUE: 'LE_SECURE_CONNECTIONS_RANDOM_VALUE',
|
|
URI: 'URI',
|
|
INDOOR_POSITIONING: 'INDOOR_POSITIONING',
|
|
TRANSPORT_DISCOVERY_DATA: 'TRANSPORT_DISCOVERY_DATA',
|
|
LE_SUPPORTED_FEATURES: 'LE_SUPPORTED_FEATURES',
|
|
CHANNEL_MAP_UPDATE_INDICATION: 'CHANNEL_MAP_UPDATE_INDICATION',
|
|
PB_ADV: 'PB_ADV',
|
|
MESH_MESSAGE: 'MESH_MESSAGE',
|
|
MESH_BEACON: 'MESH_BEACON',
|
|
BIGINFO: 'BIGINFO',
|
|
BROADCAST_CODE: 'BROADCAST_CODE',
|
|
RESOLVABLE_SET_IDENTIFIER: 'RESOLVABLE_SET_IDENTIFIER',
|
|
ADVERTISING_INTERVAL_LONG: 'ADVERTISING_INTERVAL_LONG',
|
|
BROADCAST_NAME: 'BROADCAST_NAME',
|
|
ENCRYPTED_ADVERTISING_DATA: 'ENCRYPTED_ADVERTISING_DATA',
|
|
PERIODIC_ADVERTISING_RESPONSE_TIMING_INFORMATION: 'PERIODIC_ADVERTISING_RESPONSE_TIMING_INFORMATION',
|
|
ELECTRONIC_SHELF_LABEL: 'ELECTRONIC_SHELF_LABEL',
|
|
THREE_D_INFORMATION_DATA: 'THREE_D_INFORMATION_DATA',
|
|
MANUFACTURER_SPECIFIC_DATA: 'MANUFACTURER_SPECIFIC_DATA'
|
|
}
|
|
|
|
LE_LIMITED_DISCOVERABLE_MODE_FLAG = 0x01
|
|
LE_GENERAL_DISCOVERABLE_MODE_FLAG = 0x02
|
|
BR_EDR_NOT_SUPPORTED_FLAG = 0x04
|
|
BR_EDR_CONTROLLER_FLAG = 0x08
|
|
BR_EDR_HOST_FLAG = 0x10
|
|
|
|
ad_structures: List[Tuple[int, bytes]]
|
|
|
|
# fmt: on
|
|
# pylint: enable=line-too-long
|
|
|
|
def __init__(self, ad_structures: Optional[List[Tuple[int, bytes]]] = None) -> None:
|
|
if ad_structures is None:
|
|
ad_structures = []
|
|
self.ad_structures = ad_structures[:]
|
|
|
|
@classmethod
|
|
def from_bytes(cls, data: bytes) -> AdvertisingData:
|
|
instance = AdvertisingData()
|
|
instance.append(data)
|
|
return instance
|
|
|
|
@staticmethod
|
|
def flags_to_string(flags, short=False):
|
|
flag_names = (
|
|
['LE Limited', 'LE General', 'No BR/EDR', 'BR/EDR C', 'BR/EDR H']
|
|
if short
|
|
else [
|
|
'LE Limited Discoverable Mode',
|
|
'LE General Discoverable Mode',
|
|
'BR/EDR Not Supported',
|
|
'Simultaneous LE and BR/EDR (Controller)',
|
|
'Simultaneous LE and BR/EDR (Host)',
|
|
]
|
|
)
|
|
return ','.join(bit_flags_to_strings(flags, flag_names))
|
|
|
|
@staticmethod
|
|
def uuid_list_to_objects(ad_data: bytes, uuid_size: int) -> List[UUID]:
|
|
uuids = []
|
|
offset = 0
|
|
while (offset + uuid_size) <= len(ad_data):
|
|
uuids.append(UUID.from_bytes(ad_data[offset : offset + uuid_size]))
|
|
offset += uuid_size
|
|
return uuids
|
|
|
|
@staticmethod
|
|
def uuid_list_to_string(ad_data, uuid_size):
|
|
return ', '.join(
|
|
[
|
|
str(uuid)
|
|
for uuid in AdvertisingData.uuid_list_to_objects(ad_data, uuid_size)
|
|
]
|
|
)
|
|
|
|
@staticmethod
|
|
def ad_data_to_string(ad_type, ad_data):
|
|
if ad_type == AdvertisingData.FLAGS:
|
|
ad_type_str = 'Flags'
|
|
ad_data_str = AdvertisingData.flags_to_string(ad_data[0], short=True)
|
|
elif ad_type == AdvertisingData.COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS:
|
|
ad_type_str = 'Complete List of 16-bit Service Class UUIDs'
|
|
ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 2)
|
|
elif ad_type == AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS:
|
|
ad_type_str = 'Incomplete List of 16-bit Service Class UUIDs'
|
|
ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 2)
|
|
elif ad_type == AdvertisingData.COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS:
|
|
ad_type_str = 'Complete List of 32-bit Service Class UUIDs'
|
|
ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 4)
|
|
elif ad_type == AdvertisingData.INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS:
|
|
ad_type_str = 'Incomplete List of 32-bit Service Class UUIDs'
|
|
ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 4)
|
|
elif ad_type == AdvertisingData.COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS:
|
|
ad_type_str = 'Complete List of 128-bit Service Class UUIDs'
|
|
ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 16)
|
|
elif ad_type == AdvertisingData.INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS:
|
|
ad_type_str = 'Incomplete List of 128-bit Service Class UUIDs'
|
|
ad_data_str = AdvertisingData.uuid_list_to_string(ad_data, 16)
|
|
elif ad_type == AdvertisingData.SERVICE_DATA_16_BIT_UUID:
|
|
ad_type_str = 'Service Data'
|
|
uuid = UUID.from_bytes(ad_data[:2])
|
|
ad_data_str = f'service={uuid}, data={ad_data[2:].hex()}'
|
|
elif ad_type == AdvertisingData.SERVICE_DATA_32_BIT_UUID:
|
|
ad_type_str = 'Service Data'
|
|
uuid = UUID.from_bytes(ad_data[:4])
|
|
ad_data_str = f'service={uuid}, data={ad_data[4:].hex()}'
|
|
elif ad_type == AdvertisingData.SERVICE_DATA_128_BIT_UUID:
|
|
ad_type_str = 'Service Data'
|
|
uuid = UUID.from_bytes(ad_data[:16])
|
|
ad_data_str = f'service={uuid}, data={ad_data[16:].hex()}'
|
|
elif ad_type == AdvertisingData.SHORTENED_LOCAL_NAME:
|
|
ad_type_str = 'Shortened Local Name'
|
|
ad_data_str = f'"{ad_data.decode("utf-8")}"'
|
|
elif ad_type == AdvertisingData.COMPLETE_LOCAL_NAME:
|
|
ad_type_str = 'Complete Local Name'
|
|
ad_data_str = f'"{ad_data.decode("utf-8")}"'
|
|
elif ad_type == AdvertisingData.TX_POWER_LEVEL:
|
|
ad_type_str = 'TX Power Level'
|
|
ad_data_str = str(ad_data[0])
|
|
elif ad_type == AdvertisingData.MANUFACTURER_SPECIFIC_DATA:
|
|
ad_type_str = 'Manufacturer Specific Data'
|
|
company_id = struct.unpack_from('<H', ad_data, 0)[0]
|
|
company_name = COMPANY_IDENTIFIERS.get(company_id, f'0x{company_id:04X}')
|
|
ad_data_str = f'company={company_name}, data={ad_data[2:].hex()}'
|
|
elif ad_type == AdvertisingData.APPEARANCE:
|
|
ad_type_str = 'Appearance'
|
|
appearance = Appearance.from_int(struct.unpack_from('<H', ad_data, 0)[0])
|
|
ad_data_str = str(appearance)
|
|
elif ad_type == AdvertisingData.BROADCAST_NAME:
|
|
ad_type_str = 'Broadcast Name'
|
|
ad_data_str = ad_data.decode('utf-8')
|
|
else:
|
|
ad_type_str = AdvertisingData.AD_TYPE_NAMES.get(ad_type, f'0x{ad_type:02X}')
|
|
ad_data_str = ad_data.hex()
|
|
|
|
return f'[{ad_type_str}]: {ad_data_str}'
|
|
|
|
# pylint: disable=too-many-return-statements
|
|
@staticmethod
|
|
def ad_data_to_object(ad_type: int, ad_data: bytes) -> AdvertisingDataObject:
|
|
if ad_type in (
|
|
AdvertisingData.COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS,
|
|
AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS,
|
|
AdvertisingData.LIST_OF_16_BIT_SERVICE_SOLICITATION_UUIDS,
|
|
):
|
|
return AdvertisingData.uuid_list_to_objects(ad_data, 2)
|
|
|
|
if ad_type in (
|
|
AdvertisingData.COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS,
|
|
AdvertisingData.INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS,
|
|
AdvertisingData.LIST_OF_32_BIT_SERVICE_SOLICITATION_UUIDS,
|
|
):
|
|
return AdvertisingData.uuid_list_to_objects(ad_data, 4)
|
|
|
|
if ad_type in (
|
|
AdvertisingData.COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS,
|
|
AdvertisingData.INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS,
|
|
AdvertisingData.LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS,
|
|
):
|
|
return AdvertisingData.uuid_list_to_objects(ad_data, 16)
|
|
|
|
if ad_type == AdvertisingData.SERVICE_DATA_16_BIT_UUID:
|
|
return (UUID.from_bytes(ad_data[:2]), ad_data[2:])
|
|
|
|
if ad_type == AdvertisingData.SERVICE_DATA_32_BIT_UUID:
|
|
return (UUID.from_bytes(ad_data[:4]), ad_data[4:])
|
|
|
|
if ad_type == AdvertisingData.SERVICE_DATA_128_BIT_UUID:
|
|
return (UUID.from_bytes(ad_data[:16]), ad_data[16:])
|
|
|
|
if ad_type in (
|
|
AdvertisingData.SHORTENED_LOCAL_NAME,
|
|
AdvertisingData.COMPLETE_LOCAL_NAME,
|
|
AdvertisingData.URI,
|
|
AdvertisingData.BROADCAST_NAME,
|
|
):
|
|
return ad_data.decode("utf-8")
|
|
|
|
if ad_type in (AdvertisingData.TX_POWER_LEVEL, AdvertisingData.FLAGS):
|
|
return cast(int, struct.unpack('B', ad_data)[0])
|
|
|
|
if ad_type in (AdvertisingData.ADVERTISING_INTERVAL,):
|
|
return cast(int, struct.unpack('<H', ad_data)[0])
|
|
|
|
if ad_type == AdvertisingData.CLASS_OF_DEVICE:
|
|
return cast(int, struct.unpack('<I', bytes([*ad_data, 0]))[0])
|
|
|
|
if ad_type == AdvertisingData.PERIPHERAL_CONNECTION_INTERVAL_RANGE:
|
|
return cast(Tuple[int, int], struct.unpack('<HH', ad_data))
|
|
|
|
if ad_type == AdvertisingData.MANUFACTURER_SPECIFIC_DATA:
|
|
return (cast(int, struct.unpack_from('<H', ad_data, 0)[0]), ad_data[2:])
|
|
|
|
if ad_type == AdvertisingData.APPEARANCE:
|
|
return Appearance.from_int(
|
|
cast(int, struct.unpack_from('<H', ad_data, 0)[0])
|
|
)
|
|
|
|
return ad_data
|
|
|
|
def append(self, data: bytes) -> None:
|
|
offset = 0
|
|
while offset + 1 < len(data):
|
|
length = data[offset]
|
|
offset += 1
|
|
if length > 0:
|
|
ad_type = data[offset]
|
|
ad_data = data[offset + 1 : offset + length]
|
|
self.ad_structures.append((ad_type, ad_data))
|
|
offset += length
|
|
|
|
def get_all(self, type_id: int, raw: bool = False) -> List[AdvertisingDataObject]:
|
|
'''
|
|
Get Advertising Data Structure(s) with a given type
|
|
|
|
Returns a (possibly empty) list of matches.
|
|
'''
|
|
|
|
def process_ad_data(ad_data: bytes) -> AdvertisingDataObject:
|
|
return ad_data if raw else self.ad_data_to_object(type_id, ad_data)
|
|
|
|
return [process_ad_data(ad[1]) for ad in self.ad_structures if ad[0] == type_id]
|
|
|
|
def get(self, type_id: int, raw: bool = False) -> Optional[AdvertisingDataObject]:
|
|
'''
|
|
Get Advertising Data Structure(s) with a given type
|
|
|
|
Returns the first entry, or None if no structure matches.
|
|
'''
|
|
|
|
all_objects = self.get_all(type_id, raw=raw)
|
|
return all_objects[0] if all_objects else None
|
|
|
|
def __bytes__(self):
|
|
return b''.join(
|
|
[bytes([len(x[1]) + 1, x[0]]) + x[1] for x in self.ad_structures]
|
|
)
|
|
|
|
def to_string(self, separator=', '):
|
|
return separator.join(
|
|
[AdvertisingData.ad_data_to_string(x[0], x[1]) for x in self.ad_structures]
|
|
)
|
|
|
|
def __str__(self):
|
|
return self.to_string()
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Connection Parameters
|
|
# -----------------------------------------------------------------------------
|
|
class ConnectionParameters:
|
|
def __init__(self, connection_interval, peripheral_latency, supervision_timeout):
|
|
self.connection_interval = connection_interval
|
|
self.peripheral_latency = peripheral_latency
|
|
self.supervision_timeout = supervision_timeout
|
|
|
|
def __str__(self):
|
|
return (
|
|
f'ConnectionParameters(connection_interval={self.connection_interval}, '
|
|
f'peripheral_latency={self.peripheral_latency}, '
|
|
f'supervision_timeout={self.supervision_timeout}'
|
|
)
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Connection PHY
|
|
# -----------------------------------------------------------------------------
|
|
class ConnectionPHY:
|
|
def __init__(self, tx_phy, rx_phy):
|
|
self.tx_phy = tx_phy
|
|
self.rx_phy = rx_phy
|
|
|
|
def __str__(self):
|
|
return f'ConnectionPHY(tx_phy={self.tx_phy}, rx_phy={self.rx_phy})'
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# LE Role
|
|
# -----------------------------------------------------------------------------
|
|
class LeRole(enum.IntEnum):
|
|
PERIPHERAL_ONLY = 0x00
|
|
CENTRAL_ONLY = 0x01
|
|
BOTH_PERIPHERAL_PREFERRED = 0x02
|
|
BOTH_CENTRAL_PREFERRED = 0x03
|