forked from platypush/platypush
[WIP] Refactoring/extending models and parsers for Bluetooth entities.
This commit is contained in:
parent
a688e7102e
commit
72c55c03f2
14 changed files with 764 additions and 0 deletions
11
platypush/plugins/bluetooth/_legacy/__init__.py
Normal file
11
platypush/plugins/bluetooth/_legacy/__init__.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from ._model import BluetoothDevice
|
||||
from ._scanner import DeviceScanner
|
||||
|
||||
|
||||
__all__ = [
|
||||
"BluetoothDevice",
|
||||
"DeviceScanner",
|
||||
]
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
8
platypush/plugins/bluetooth/_legacy/_model/__init__.py
Normal file
8
platypush/plugins/bluetooth/_legacy/_model/__init__.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from ._device import BluetoothDevice
|
||||
from ._service import BluetoothService
|
||||
|
||||
|
||||
__all__ = [
|
||||
"BluetoothDevice",
|
||||
"BluetoothService",
|
||||
]
|
|
@ -0,0 +1,9 @@
|
|||
from ._device import MajorDeviceClass, MinorDeviceClass
|
||||
from ._service import MajorServiceClass
|
||||
|
||||
|
||||
__all__ = [
|
||||
"MajorDeviceClass",
|
||||
"MinorDeviceClass",
|
||||
"MajorServiceClass",
|
||||
]
|
59
platypush/plugins/bluetooth/_legacy/_model/_classes/_base.py
Normal file
59
platypush/plugins/bluetooth/_legacy/_model/_classes/_base.py
Normal 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
|
|
@ -0,0 +1,8 @@
|
|||
from ._major import MajorDeviceClass
|
||||
from ._minor import MinorDeviceClass
|
||||
|
||||
|
||||
__all__ = [
|
||||
"MajorDeviceClass",
|
||||
"MinorDeviceClass",
|
||||
]
|
|
@ -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)
|
|
@ -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
|
||||
)
|
|
@ -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)
|
42
platypush/plugins/bluetooth/_legacy/_model/_protocol.py
Normal file
42
platypush/plugins/bluetooth/_legacy/_model/_protocol.py
Normal 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)
|
|
@ -0,0 +1,4 @@
|
|||
from ._base import BluetoothService
|
||||
|
||||
|
||||
__all__ = ['BluetoothService']
|
127
platypush/plugins/bluetooth/_legacy/_model/_service/_base.py
Normal file
127
platypush/plugins/bluetooth/_legacy/_model/_service/_base.py
Normal 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)
|
|
@ -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. """
|
|
@ -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``.
|
||||
"""
|
|
@ -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.
|
||||
"""
|
Loading…
Reference in a new issue