platypush/platypush/plugins/bluetooth/_model/_service/_directory.py

178 lines
5.3 KiB
Python

import re
from enum import Enum
from typing import Dict
from uuid import UUID
import bluetooth_numbers
from bleak.uuids import uuid16_dict, uuid128_dict
from platypush.plugins.bluetooth._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)
# Extend the service classes with the GATT service UUIDs defined in Bleak
_service_classes.update(uuid16_dict) # type: ignore
_service_classes.update({UUID(uuid): name for uuid, name in uuid128_dict.items()})
_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:
try:
if isinstance(value, UUID):
return ServiceClass(int(str(value).upper()[4:8], 16))
except ValueError:
pass
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.UNKNOWN.value # type: ignore
)
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. """