[WIP] Refactoring/extending models and parsers for Bluetooth entities.

This commit is contained in:
Fabio Manganiello 2023-03-03 02:10:11 +01:00
parent a688e7102e
commit 72c55c03f2
Signed by: blacklight
GPG Key ID: D90FBA7F76362774
14 changed files with 764 additions and 0 deletions

View File

@ -0,0 +1,11 @@
from ._model import BluetoothDevice
from ._scanner import DeviceScanner
__all__ = [
"BluetoothDevice",
"DeviceScanner",
]
# vim:sw=4:ts=4:et:

View File

@ -0,0 +1,8 @@
from ._device import BluetoothDevice
from ._service import BluetoothService
__all__ = [
"BluetoothDevice",
"BluetoothService",
]

View File

@ -0,0 +1,9 @@
from ._device import MajorDeviceClass, MinorDeviceClass
from ._service import MajorServiceClass
__all__ = [
"MajorDeviceClass",
"MinorDeviceClass",
"MajorServiceClass",
]

View File

@ -0,0 +1,59 @@
from enum import Enum
from dataclasses import dataclass
from typing import Optional
class BaseBluetoothClass(Enum):
"""
Base enum to model Bluetooth device/service classes.
"""
def __repr__(self) -> str:
"""
:return: The enum class formatted as ``<name: <value>``.
"""
return f'<{self.__class__.__name__}.{self.name}: {str(self)}>'
def __str__(self) -> str:
"""
:return Only the readable string value of the class.
"""
return self.value.name
@dataclass
class ClassProperty:
"""
Models a Bluetooth class property.
Given a Bluetooth class as a 24-bit unsigned integer, this class models the
filter that should be applied to the class to tell if the device exposes the
property.
"""
name: str
""" The name of the property. """
bitmask: int
""" Bitmask used to select the property bits from the class. """
bit_shift: int = 0
""" Number of bits to shift the class value after applying the bitmask. """
match_value: int = 1
"""
This should be the result of the bitwise filter for the property to match.
"""
parent: Optional[BaseBluetoothClass] = None
"""
Parent class property, if this is a minor property. If this is the case,
then the advertised class value should also match the parent property.
"""
def matches(self, cls: int) -> bool:
"""
Match function.
:param cls: The Bluetooth composite class value.
:return: True if the class matches the property.
"""
if self.parent and not self.parent.value.matches(cls):
return False
return ((cls & self.bitmask) >> self.bit_shift) == self.match_value

View File

@ -0,0 +1,8 @@
from ._major import MajorDeviceClass
from ._minor import MinorDeviceClass
__all__ = [
"MajorDeviceClass",
"MinorDeviceClass",
]

View File

@ -0,0 +1,20 @@
from .._base import BaseBluetoothClass, ClassProperty
class MajorDeviceClass(BaseBluetoothClass):
"""
Models Bluetooth major device classes - see
https://btprodspecificationrefs.blob.core.windows.net/assigned-numbers/Assigned%20Number%20Types/Assigned%20Numbers.pdf,
Section 2.8.2
"""
UNKNOWN = ClassProperty('Unknown', 0x1F00, 8, 0b0000)
COMPUTER = ClassProperty('Computer', 0x1F00, 8, 0b0001)
PHONE = ClassProperty('Phone', 0x1F00, 8, 0b0010)
AP = ClassProperty('LAN / Network Access Point', 0x1F00, 8, 0b0011)
MULTIMEDIA = ClassProperty('Audio / Video', 0x1F00, 8, 0b0100)
PERIPHERAL = ClassProperty('Peripheral', 0x1F00, 8, 0b0101)
IMAGING = ClassProperty('Imaging', 0x1F00, 8, 0b0110)
WEARABLE = ClassProperty('Wearable', 0x1F00, 8, 0b0111)
TOY = ClassProperty('Toy', 0x1F00, 8, 0b1000)
HEALTH = ClassProperty('Health', 0x1F00, 8, 0b1001)

View File

@ -0,0 +1,255 @@
from .._base import BaseBluetoothClass, ClassProperty
from ._major import MajorDeviceClass
class MinorDeviceClass(BaseBluetoothClass):
"""
Models Bluetooth minor device classes - see
https://btprodspecificationrefs.blob.core.windows.net/assigned-numbers/Assigned%20Number%20Types/Assigned%20Numbers.pdf,
Sections 2.8.2.1 - 2.8.2.9
"""
# Computer classes
COMPUTER_UNKNOWN = ClassProperty(
'Unknown', 0xFC, 2, 0b000, MajorDeviceClass.COMPUTER
)
COMPUTER_DESKTOP = ClassProperty(
'Desktop Workstation', 0xFC, 2, 0b001, MajorDeviceClass.COMPUTER
)
COMPUTER_SERVER = ClassProperty('Server', 0xFC, 2, 0b010, MajorDeviceClass.COMPUTER)
COMPUTER_LAPTOP = ClassProperty('Laptop', 0xFC, 2, 0b011, MajorDeviceClass.COMPUTER)
COMPUTER_HANDHELD_PDA = ClassProperty(
'Handheld PDA', 0xFC, 2, 0b100, MajorDeviceClass.COMPUTER
)
COMPUTER_PALM_PDA = ClassProperty(
'Palm-sized PDA', 0xFC, 2, 0b101, MajorDeviceClass.COMPUTER
)
COMPUTER_WEARABLE = ClassProperty(
'Wearable Computer', 0xFC, 2, 0b110, MajorDeviceClass.COMPUTER
)
# Phone classes
PHONE_UNKNOWN = ClassProperty('Unknown', 0xFC, 2, 0b000, MajorDeviceClass.PHONE)
PHONE_CELLULAR = ClassProperty('Cellular', 0xFC, 2, 0b001, MajorDeviceClass.PHONE)
PHONE_CORDLESS = ClassProperty('Cordless', 0xFC, 2, 0b010, MajorDeviceClass.PHONE)
PHONE_SMARTPHONE = ClassProperty(
'Smartphone', 0xFC, 2, 0b011, MajorDeviceClass.PHONE
)
PHONE_WIRED_MODEM = ClassProperty(
'Wired Modem', 0xFC, 2, 0b100, MajorDeviceClass.PHONE
)
PHONE_ISDN_ACCESS = ClassProperty(
'ISDN Access Point', 0xFC, 2, 0b101, MajorDeviceClass.PHONE
)
# LAN / Access Point classes
AP_USAGE_0 = ClassProperty('Fully Available', 0xE0, 5, 0b000, MajorDeviceClass.AP)
AP_USAGE_1_17 = ClassProperty(
'1 - 17% Utilized', 0xE0, 5, 0b001, MajorDeviceClass.AP
)
AP_USAGE_17_33 = ClassProperty(
'17 - 33% Utilized', 0xE0, 5, 0b010, MajorDeviceClass.AP
)
AP_USAGE_33_50 = ClassProperty(
'33 - 50% Utilized', 0xE0, 5, 0b011, MajorDeviceClass.AP
)
AP_USAGE_50_67 = ClassProperty(
'50 - 67% Utilized', 0xE0, 5, 0b100, MajorDeviceClass.AP
)
AP_USAGE_67_83 = ClassProperty(
'67 - 83% Utilized', 0xE0, 5, 0b101, MajorDeviceClass.AP
)
AP_USAGE_83_99 = ClassProperty(
'83 - 99% Utilized', 0xE0, 5, 0b110, MajorDeviceClass.AP
)
AP_USAGE_100 = ClassProperty(
'No Service Available', 0xE0, 5, 0b111, MajorDeviceClass.AP
)
# Multimedia classes
MULTIMEDIA_HEADSET = ClassProperty(
'Headset', 0xFC, 2, 0b000001, MajorDeviceClass.MULTIMEDIA
)
MULTIMEDIA_HANDS_FREE = ClassProperty(
'Hands-free Device', 0xFC, 2, 0b000010, MajorDeviceClass.MULTIMEDIA
)
MULTIMEDIA_MICROPHONE = ClassProperty(
'Microphone', 0xFC, 2, 0b000100, MajorDeviceClass.MULTIMEDIA
)
MULTIMEDIA_LOUDSPEAKER = ClassProperty(
'Loudspeaker', 0xFC, 2, 0b000101, MajorDeviceClass.MULTIMEDIA
)
MULTIMEDIA_HEADPHONES = ClassProperty(
'Headphones', 0xFC, 2, 0b000110, MajorDeviceClass.MULTIMEDIA
)
MULTIMEDIA_PORTABLE_AUDIO = ClassProperty(
'Portable Audio', 0xFC, 2, 0b000111, MajorDeviceClass.MULTIMEDIA
)
MULTIMEDIA_CAR_AUDIO = ClassProperty(
'Car Audio', 0xFC, 2, 0b001000, MajorDeviceClass.MULTIMEDIA
)
MULTIMEDIA_SET_TOP_BOX = ClassProperty(
'Set-top Box', 0xFC, 2, 0b001001, MajorDeviceClass.MULTIMEDIA
)
MULTIMEDIA_HIFI_AUDIO = ClassProperty(
'HiFi Audio Device', 0xFC, 2, 0b001010, MajorDeviceClass.MULTIMEDIA
)
MULTIMEDIA_VCR = ClassProperty(
'VCR', 0xFC, 2, 0b001011, MajorDeviceClass.MULTIMEDIA
)
MULTIMEDIA_VIDEO_CAMERA = ClassProperty(
'Video Camera', 0xFC, 2, 0b001100, MajorDeviceClass.MULTIMEDIA
)
MULTIMEDIA_CAMCODER = ClassProperty(
'Camcoder', 0xFC, 2, 0b001101, MajorDeviceClass.MULTIMEDIA
)
MULTIMEDIA_VIDEO_MONITOR = ClassProperty(
'Video Monitor', 0xFC, 2, 0b001110, MajorDeviceClass.MULTIMEDIA
)
MULTIMEDIA_VIDEO_DISPLAY_AND_LOUDSPEAKER = ClassProperty(
'Video Display and Loudspeaker', 0xFC, 2, 0b001111, MajorDeviceClass.MULTIMEDIA
)
MULTIMEDIA_VIDEO_CONFERENCING = ClassProperty(
'Video Conferencing', 0xFC, 2, 0b010000, MajorDeviceClass.MULTIMEDIA
)
MULTIMEDIA_GAMING_TOY = ClassProperty(
'Gaming / Toy', 0xFC, 2, 0b010010, MajorDeviceClass.MULTIMEDIA
)
# Peripheral classes
PERIPHERAL_UNKNOWN = ClassProperty(
'Unknown', 0xFC, 2, 0b000000, MajorDeviceClass.PERIPHERAL
)
PERIPHERAL_KEYBOARD = ClassProperty(
'Keyboard', 0xC0, 6, 0b01, MajorDeviceClass.PERIPHERAL
)
PERIPHERAL_POINTER = ClassProperty(
'Pointing Device', 0xC0, 6, 0b10, MajorDeviceClass.PERIPHERAL
)
PERIPHERAL_KEYBOARD_POINTER = ClassProperty(
'Combo Keyboard/Pointing Device', 0xC0, 6, 0b11, MajorDeviceClass.PERIPHERAL
)
PERIPHERAL_JOYSTICK = ClassProperty(
'Joystick', 0x3C, 2, 0b0001, MajorDeviceClass.PERIPHERAL
)
PERIPHERAL_GAMEPAD = ClassProperty(
'Gamepad', 0x3C, 2, 0b0010, MajorDeviceClass.PERIPHERAL
)
PERIPHERAL_REMOTE_CONTROL = ClassProperty(
'Remote Control', 0x3C, 2, 0b0011, MajorDeviceClass.PERIPHERAL
)
PERIPHERAL_SENSOR = ClassProperty(
'Sensing Device', 0x3C, 2, 0b0100, MajorDeviceClass.PERIPHERAL
)
PERIPHERAL_DIGIT_TABLET = ClassProperty(
'Digitizer Tablet', 0x3C, 2, 0b0101, MajorDeviceClass.PERIPHERAL
)
PERIPHERAL_CARD_READER = ClassProperty(
'Card Reader', 0x3C, 2, 0b0110, MajorDeviceClass.PERIPHERAL
)
PERIPHERAL_DIGITAL_PEN = ClassProperty(
'Card Reader', 0x3C, 2, 0b0111, MajorDeviceClass.PERIPHERAL
)
PERIPHERAL_SCANNER = ClassProperty(
'Handheld Scanner', 0x3C, 2, 0b1000, MajorDeviceClass.PERIPHERAL
)
PERIPHERAL_GESTURES = ClassProperty(
'Handheld Gesture Input Device', 0x3C, 2, 0b1001, MajorDeviceClass.PERIPHERAL
)
# Imaging classes
IMAGING_UNKNOWN = ClassProperty(
'Unknown', 0xFC, 2, 0b000000, MajorDeviceClass.IMAGING
)
IMAGING_DISPLAY = ClassProperty(
'Display', 0xF0, 4, 0b0001, MajorDeviceClass.IMAGING
)
IMAGING_CAMERA = ClassProperty('Camera', 0xF0, 4, 0b0010, MajorDeviceClass.IMAGING)
IMAGING_SCANNER = ClassProperty(
'Scanner', 0xF0, 4, 0b0100, MajorDeviceClass.IMAGING
)
IMAGING_PRINTER = ClassProperty(
'Printer', 0xF0, 4, 0b1000, MajorDeviceClass.IMAGING
)
# Wearable classes
WEARABLE_UNKNOWN = ClassProperty(
'Unknown', 0xFC, 2, 0b000000, MajorDeviceClass.WEARABLE
)
WEARABLE_WRISTWATCH = ClassProperty(
'Wristwatch', 0xFC, 2, 0b000001, MajorDeviceClass.WEARABLE
)
WEARABLE_PAGER = ClassProperty(
'Pager', 0xFC, 2, 0b000010, MajorDeviceClass.WEARABLE
)
WEARABLE_JACKET = ClassProperty(
'Jacket', 0xFC, 2, 0b000011, MajorDeviceClass.WEARABLE
)
WEARABLE_HELMET = ClassProperty(
'Helmet', 0xFC, 2, 0b000100, MajorDeviceClass.WEARABLE
)
WEARABLE_GLASSES = ClassProperty(
'Glasses', 0xFC, 2, 0b000101, MajorDeviceClass.WEARABLE
)
# Toy classes
TOY_UNKNOWN = ClassProperty('Unknown', 0xFC, 2, 0b000000, MajorDeviceClass.TOY)
TOY_ROBOT = ClassProperty('Robot', 0xFC, 2, 0b000001, MajorDeviceClass.TOY)
TOY_VEHICLE = ClassProperty('Vehicle', 0xFC, 2, 0b000010, MajorDeviceClass.TOY)
TOY_DOLL = ClassProperty(
'Doll / Action Figure', 0xFC, 2, 0b000011, MajorDeviceClass.TOY
)
TOY_CONTROLLER = ClassProperty(
'Controller', 0xFC, 2, 0b000100, MajorDeviceClass.TOY
)
TOY_GAME = ClassProperty('Game', 0xFC, 2, 0b000101, MajorDeviceClass.TOY)
# Health classes
HEALTH_UNKNOWN = ClassProperty(
'Unknown', 0xFC, 2, 0b000000, MajorDeviceClass.HEALTH
)
HEALTH_BLOOD_PRESSURE = ClassProperty(
'Blood Pressure Monitor', 0xFC, 2, 0b000001, MajorDeviceClass.HEALTH
)
HEALTH_THERMOMETER = ClassProperty(
'Thermometer', 0xFC, 2, 0b000010, MajorDeviceClass.HEALTH
)
HEALTH_SCALE = ClassProperty(
'Weighing Scale', 0xFC, 2, 0b000011, MajorDeviceClass.HEALTH
)
HEALTH_GLUCOSE = ClassProperty(
'Glucose Meter', 0xFC, 2, 0b000100, MajorDeviceClass.HEALTH
)
HEALTH_OXIMETER = ClassProperty(
'Pulse Oximeter', 0xFC, 2, 0b000101, MajorDeviceClass.HEALTH
)
HEALTH_PULSE = ClassProperty(
'Heart Rate/Pulse Monitor', 0xFC, 2, 0b000110, MajorDeviceClass.HEALTH
)
HEALTH_DISPLAY = ClassProperty(
'Health Data Display', 0xFC, 2, 0b000111, MajorDeviceClass.HEALTH
)
HEALTH_STEPS = ClassProperty(
'Step Counter', 0xFC, 2, 0b001000, MajorDeviceClass.HEALTH
)
HEALTH_COMPOSITION = ClassProperty(
'Body Composition Analyzer', 0xFC, 2, 0b001001, MajorDeviceClass.HEALTH
)
HEALTH_PEAK_FLOW = ClassProperty(
'Peak Flow Monitor', 0xFC, 2, 0b001010, MajorDeviceClass.HEALTH
)
HEALTH_MEDICATION = ClassProperty(
'Medication Monitor', 0xFC, 2, 0b001011, MajorDeviceClass.HEALTH
)
HEALTH_KNEE_PROSTHESIS = ClassProperty(
'Knee Prosthesis', 0xFC, 2, 0b001100, MajorDeviceClass.HEALTH
)
HEALTH_ANKLE_PROSTHESIS = ClassProperty(
'Ankle Prosthesis', 0xFC, 2, 0b001101, MajorDeviceClass.HEALTH
)
HEALTH_GENERIC = ClassProperty(
'Generic Health Manager', 0xFC, 2, 0b001110, MajorDeviceClass.HEALTH
)
HEALTH_MOBILITY = ClassProperty(
'Personal Mobility Device', 0xFC, 2, 0b001111, MajorDeviceClass.HEALTH
)

View File

@ -0,0 +1,19 @@
from ._base import BaseBluetoothClass, ClassProperty
class MajorServiceClass(BaseBluetoothClass):
"""
Models Bluetooth major service classes - see
https://btprodspecificationrefs.blob.core.windows.net/assigned-numbers/Assigned%20Number%20Types/Assigned%20Numbers.pdf,
Section 2.8.1
"""
LE_AUDIO = ClassProperty('Low-energy Audio', 1 << 14, 14)
POSITIONING = ClassProperty('Positioning', 1 << 16, 16)
NETWORKING = ClassProperty('Networking', 1 << 17, 17)
RENDERING = ClassProperty('Rendering', 1 << 18, 18)
CAPTURING = ClassProperty('Capturing', 1 << 19, 19)
OBJECT_TRANSFER = ClassProperty('Object Transfer', 1 << 20, 20)
AUDIO = ClassProperty('Audio', 1 << 21, 21)
TELEPHONY = ClassProperty('Telephony', 1 << 22, 22)
INFORMATION = ClassProperty('Information', 1 << 23, 23)

View File

@ -0,0 +1,42 @@
from enum import Enum
class Protocol(Enum):
"""
Models a Bluetooth protocol.
"""
RFCOMM = 'RFCOMM'
L2CAP = 'L2CAP'
TCP = 'TCP'
UDP = 'UDP'
SDP = 'SDP'
BNEP = 'BNEP'
TCS_BIN = 'TCS-BIN'
TCS_AT = 'TCS-AT'
OBEX = 'OBEX'
IP = 'IP'
FTP = 'FTP'
HTTP = 'HTTP'
WSP = 'WSP'
UPNP = 'UPNP'
HIDP = 'HIDP'
AVCTP = 'AVCTP'
AVDTP = 'AVDTP'
CMTP = 'CMTP'
UDI_C_PLANE = 'UDI_C-Plane'
HardCopyControlChannel = 'HardCopyControlChannel'
HardCopyDataChannel = 'HardCopyDataChannel'
HardCopyNotification = 'HardCopyNotification'
def __str__(self) -> str:
"""
Only returns the value of the enum.
"""
return self.value
def __repr__(self) -> str:
"""
Only returns the value of the enum.
"""
return str(self)

View File

@ -0,0 +1,4 @@
from ._base import BluetoothService
__all__ = ['BluetoothService']

View File

@ -0,0 +1,127 @@
from dataclasses import dataclass
from typing import Any, Dict, Iterable, Optional, Set, Tuple
from uuid import UUID
from .._protocol import Protocol
from ._directory import ServiceClass
from ._types import RawServiceClass
VersionedServices = Dict[ServiceClass, Optional[int]]
""" Service -> Version mapping. """
@dataclass
class BluetoothService:
"""
Models a discovered Bluetooth service.
"""
address: str
""" The address of the service that exposes the service. """
port: int
""" The Bluetooth port associated to the service. """
protocol: Protocol
""" The service protocol. """
name: Optional[str]
""" The name of the service. """
description: Optional[str]
""" The description of the service. """
service_id: Optional[str]
""" The ID of the service. """
service_classes: VersionedServices
"""
The compatible classes exposed by the service - see
https://btprodspecificationrefs.blob.core.windows.net/assigned-numbers/Assigned%20Number%20Types/Assigned%20Numbers.pdf,
Section 5.
"""
unknown_service_classes: Iterable[RawServiceClass]
""" Service classes that are not supported. """
@classmethod
def build(cls, service: Dict[str, Any]) -> 'BluetoothService':
"""
Builds a :class:`BluetoothService` from a service dictionary returned by
pybluez.
"""
return cls(
address=service['host'],
port=service['port'],
protocol=Protocol(service['protocol']),
name=service['name'],
description=service['description'],
service_id=service['service-id'],
service_classes=cls._parse_services(
service['service-classes'], service['profiles']
),
unknown_service_classes=cls._parse_unknown_services(
service['service-classes'],
),
)
@classmethod
def _parse_services(
cls, service_classes: Iterable[str], profiles: Iterable[Tuple[str, int]]
) -> VersionedServices:
"""
Parses the services.
:param service_classes: The service classes returned by pybluez.
:param profiles: The profiles returned by pybluez as a list of
``[(service, version)]`` tuples.
:return: A list of parsed service classes.
"""
# Parse the service classes
parsed_services: Dict[RawServiceClass, ServiceClass] = {}
for srv in service_classes:
srv_class = cls._parse_service_class(srv)
parsed_services[srv_class.value] = srv_class
# Parse the service classes versions
versioned_classes: VersionedServices = {}
for srv, version in profiles:
value = cls._parse_service_class(srv).value
parsed_srv = parsed_services.get(value)
if parsed_srv:
versioned_classes[parsed_srv] = version
return {
srv: versioned_classes.get(srv)
for srv in parsed_services.values()
if srv != ServiceClass.UNKNOWN
}
@classmethod
def _parse_unknown_services(
cls, service_classes: Iterable[str]
) -> Set[RawServiceClass]:
return {
cls._uuid(srv)
for srv in service_classes
if cls._parse_service_class(srv) == ServiceClass.UNKNOWN
}
@classmethod
def _parse_service_class(cls, srv: str) -> ServiceClass:
"""
:param srv: The service class returned by pybluez as a string (either
hex-encoded or UUID).
:return: The parsed :class:`ServiceClass` object or ``ServiceClass.UNKNOWN``.
"""
srv_class: ServiceClass = ServiceClass.UNKNOWN
try:
srv_class = ServiceClass.get(cls._uuid(srv))
except (TypeError, ValueError):
pass
return srv_class
@staticmethod
def _uuid(s: str) -> RawServiceClass:
"""
:param s: The service class returned by pybluez as a string.
:return: The UUID of the service class as a 16-bit or 128-bit identifier.
"""
try:
return UUID(s)
except ValueError:
return int(s, 16)

View File

@ -0,0 +1,163 @@
import re
from enum import Enum
from typing import Dict
import bluetooth_numbers
from ._types import RawServiceClass
def _service_name_to_enum_name(service_name: str) -> str:
"""
Convert a service name to an enum-key compatible string.
"""
ret = service_name.title()
ret = re.sub(r"\(.+?\)", "", ret)
ret = re.sub(r"\s+", "_", ret)
ret = re.sub(r"[^a-zA-Z0-9_]", "", ret)
ret = re.sub(r"_+", "_", ret)
return ret.upper()
_service_classes: Dict[RawServiceClass, str] = {
0x0: "Unknown",
0x1000: "Service Discovery Server Service Class ID",
0x1001: "Browse Group Descriptor Service Class ID",
0x1101: "Serial Port",
0x1102: "LAN Access Using PPP",
0x1103: "Dialup Networking",
0x1104: "IR MC Sync",
0x1105: "OBEX Object Push",
0x1106: "OBEX File Transfer",
0x1107: "IR MC Sync Command",
0x1108: "Headset",
0x1109: "Cordless Telephony",
0x110A: "Audio Source",
0x110B: "Audio Sink",
0x110C: "A/V Remote Control Target",
0x110D: "Advanced Audio Distribution",
0x110E: "A/V Remote Control",
0x110F: "A/V Remote Control Controller",
0x1110: "Intercom",
0x1111: "Fax",
0x1112: "Headset Audio Gateway",
0x1113: "WAP",
0x1114: "WAP Client",
0x1115: "PANU",
0x1116: "NAP",
0x1117: "GN",
0x1118: "Direct Printing",
0x1119: "Reference Printing",
0x111A: "Basic Imaging Profile",
0x111B: "Imaging Responder",
0x111C: "Imaging Automatic Archive",
0x111D: "Imaging Referenced Objects",
0x111E: "Handsfree",
0x111F: "Handsfree Audio Gateway",
0x1120: "Direct Printing Reference Objects Service",
0x1121: "Reflected UI",
0x1122: "Basic Printing",
0x1123: "Printing Status",
0x1124: "Human Interface Device Service",
0x1125: "Hard Copy Cable Replacement",
0x1126: "HCR Print",
0x1127: "HCR Scan",
0x1128: "Common ISDN Access",
0x112D: "SIM Access",
0x112E: "Phone Book Access PCE",
0x112F: "Phone Book Access PSE",
0x1130: "Phone Book Access",
0x1131: "Headset NS",
0x1132: "Message Access Server",
0x1133: "Message Notification Server",
0x1134: "Message Notification Profile",
0x1135: "GNSS",
0x1136: "GNSS Server",
0x1137: "3D Display",
0x1138: "3D Glasses",
0x1139: "3D Synchronization",
0x113A: "MPS Profile",
0x113B: "MPS SC",
0x113C: "CTN Access Service",
0x113D: "CTN Notification Service",
0x113E: "CTN Profile",
0x1200: "PnP Information",
0x1201: "Generic Networking",
0x1202: "Generic File Transfer",
0x1203: "Generic Audio",
0x1204: "Generic Telephony",
0x1205: "UPNP Service",
0x1206: "UPNP IP Service",
0x1300: "ESDP UPNP IP PAN",
0x1301: "ESDP UPNP IP LAP",
0x1302: "ESDP UPNP L2CAP",
0x1303: "Video Source",
0x1304: "Video Sink",
0x1305: "Video Distribution",
0x1400: "HDP",
0x1401: "HDP Source",
0x1402: "HDP Sink",
}
"""
Directory of known Bluetooth service UUIDs.
See
https://btprodspecificationrefs.blob.core.windows.net/assigned-numbers/Assigned%20Number%20Types/Assigned%20Numbers.pdf,
Section 3.3.
"""
# Update the base services with the GATT service UUIDs defined in ``bluetooth_numbers``. See
# https://btprodspecificationrefs.blob.core.windows.net/assigned-numbers/Assigned%20Number%20Types/Assigned%20Numbers.pdf,
# Section 3.4
_service_classes.update(bluetooth_numbers.service)
_service_classes_by_name: Dict[str, RawServiceClass] = {
name: cls for cls, name in _service_classes.items()
}
class _ServiceClassMeta:
"""
Metaclass for :class:`ServiceClass`.
"""
value: RawServiceClass
""" The raw service class value. """
@classmethod
def get(cls, value: RawServiceClass) -> "ServiceClass":
"""
:param value: The raw service class UUID.
:return: The parsed :class:`ServiceClass` instance, or
``ServiceClass.UNKNOWN``.
"""
try:
return ServiceClass(value)
except ValueError:
return ServiceClass.UNKNOWN # type: ignore
@classmethod
def by_name(cls, name: str) -> "ServiceClass":
"""
:param name: The name of the service class.
:return: The :class:`ServiceClass` instance, or
``ServiceClass.UNKNOWN``.
"""
return (
ServiceClass(_service_classes_by_name.get(name))
or ServiceClass.UNKNOWN # type: ignore
)
def __str__(self) -> str:
return _service_classes.get(self.value, ServiceClass(0).value)
def __repr__(self) -> str:
return f"<{self.value}: {str(self)}>"
ServiceClass = Enum( # type: ignore
"ServiceClass",
{_service_name_to_enum_name(name): cls for cls, name in _service_classes.items()},
type=_ServiceClassMeta,
)
""" Enumeration of known Bluetooth services. """

View File

@ -0,0 +1,31 @@
# mypy stub for ServiceClass
from enum import Enum
from ._types import RawServiceClass
class ServiceClass(Enum):
"""
Enumeration of supported Bluetooth service classes.
"""
value: RawServiceClass
""" The raw service class value. """
UNKNOWN = ...
""" A class for unknown services. """
@classmethod
def get(cls, value: RawServiceClass) -> "ServiceClass":
"""
:param value: The raw service class UUID.
:return: The parsed :class:`ServiceClass` instance, or
``ServiceClass.UNKNOWN``.
"""
@classmethod
def by_name(cls, name: str) -> "ServiceClass":
"""
:param name: The name of the service class.
:return: The :class:`ServiceClass` instance, or
``ServiceClass.UNKNOWN``.
"""

View File

@ -0,0 +1,8 @@
from typing import Union
from uuid import UUID
RawServiceClass = Union[UUID, int]
"""
Raw type for service classes received by pybluez.
Can be either a 16-bit integer or a UUID.
"""