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