forked from platypush/platypush
Added support for custom Bluetooth device plugins.
This commit is contained in:
parent
af125347d6
commit
d6805a8b18
7 changed files with 103 additions and 3 deletions
|
@ -19,6 +19,7 @@ from platypush.message.event.bluetooth import (
|
|||
|
||||
from .._cache import EntityCache
|
||||
from .._model import ServiceClass
|
||||
from .._plugins import BaseBluetoothPlugin
|
||||
from ._cache import DeviceCache
|
||||
from ._mappers import device_to_entity
|
||||
|
||||
|
@ -89,7 +90,7 @@ event_matchers: Dict[
|
|||
or (old.reachable is False and new.reachable is True),
|
||||
BluetoothDeviceSignalUpdateEvent: lambda old, new: (
|
||||
(new.rssi is not None or new.tx_power is not None)
|
||||
and (old and old.rssi)
|
||||
and bool(old and old.rssi)
|
||||
and (
|
||||
_has_changed(old, new, 'rssi', tolerance=5)
|
||||
or _has_changed(old, new, 'tx_power', tolerance=5)
|
||||
|
@ -115,6 +116,7 @@ class EventHandler:
|
|||
device_queue: Queue,
|
||||
device_cache: DeviceCache,
|
||||
entity_cache: EntityCache,
|
||||
plugins: Collection[BaseBluetoothPlugin],
|
||||
exclude_known_noisy_beacons: bool,
|
||||
):
|
||||
"""
|
||||
|
@ -126,6 +128,7 @@ class EventHandler:
|
|||
self._device_queue = device_queue
|
||||
self._device_cache = device_cache
|
||||
self._entity_cache = entity_cache
|
||||
self._plugins = plugins
|
||||
self._exclude_known_noisy_beacons = exclude_known_noisy_beacons
|
||||
|
||||
def __call__(self, device: BLEDevice, data: AdvertisementData):
|
||||
|
@ -155,6 +158,10 @@ class EventHandler:
|
|||
)
|
||||
return
|
||||
|
||||
# Extend the new entity with children entities added by the plugins
|
||||
for plugin in self._plugins:
|
||||
plugin.extend_device(new_entity)
|
||||
|
||||
events: List[BluetoothDeviceEvent] = []
|
||||
existing_entity = self._entity_cache.get(device.address)
|
||||
events += [
|
||||
|
@ -195,7 +202,7 @@ class EventHandler:
|
|||
return False
|
||||
|
||||
mapped_uuids = [
|
||||
int(str(srv.uuid).split('-')[0], 16) & 0xFFFF
|
||||
int(str(srv.uuid).split('-', maxsplit=1)[0], 16) & 0xFFFF
|
||||
if isinstance(srv.uuid, UUID)
|
||||
else srv.uuid
|
||||
for srv in device.services
|
||||
|
|
|
@ -210,7 +210,7 @@ def _parse_services(
|
|||
srv_cls = ServiceClass.get(uuid)
|
||||
services.append(
|
||||
BluetoothService(
|
||||
id=f'{device.address}:{uuid}',
|
||||
id=f'{device.address}::{uuid}',
|
||||
uuid=uuid,
|
||||
name=f'[{uuid}]' if srv_cls == ServiceClass.UNKNOWN else str(srv_cls),
|
||||
protocol=Protocol.L2CAP,
|
||||
|
|
0
platypush/plugins/bluetooth/_ble/_plugins/__init__.py
Normal file
0
platypush/plugins/bluetooth/_ble/_plugins/__init__.py
Normal file
|
@ -43,6 +43,8 @@ class BaseBluetoothManager(ABC, threading.Thread):
|
|||
:param device_cache: Cache used to keep track of discovered devices.
|
||||
:param exclude_known_noisy_beacons: Exclude known noisy beacons.
|
||||
"""
|
||||
from ._plugins import scan_plugins
|
||||
|
||||
kwargs['name'] = f'Bluetooth:{self.__class__.__name__}'
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
@ -59,6 +61,8 @@ class BaseBluetoothManager(ABC, threading.Thread):
|
|||
|
||||
self._cache = device_cache or EntityCache()
|
||||
""" Cache of discovered devices. """
|
||||
self._plugins = scan_plugins(self)
|
||||
""" Plugins compatible with this manager. """
|
||||
|
||||
def notify(
|
||||
self, event_type: Type[BluetoothDeviceEvent], device: BluetoothDevice, **kwargs
|
||||
|
@ -71,6 +75,10 @@ class BaseBluetoothManager(ABC, threading.Thread):
|
|||
get_bus().post(event_type.from_device(device=device, **kwargs))
|
||||
self._device_queue.put_nowait(device)
|
||||
|
||||
@property
|
||||
def plugins(self):
|
||||
return self._plugins
|
||||
|
||||
def should_stop(self) -> bool:
|
||||
return self._stop_event.is_set()
|
||||
|
||||
|
|
4
platypush/plugins/bluetooth/_plugins/__init__.py
Normal file
4
platypush/plugins/bluetooth/_plugins/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
from ._base import BaseBluetoothPlugin
|
||||
from ._scanner import scan_plugins
|
||||
|
||||
__all__ = ['BaseBluetoothPlugin', 'scan_plugins']
|
39
platypush/plugins/bluetooth/_plugins/_base.py
Normal file
39
platypush/plugins/bluetooth/_plugins/_base.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import Iterable
|
||||
|
||||
from platypush.entities import Entity
|
||||
from platypush.entities.bluetooth import BluetoothDevice
|
||||
from platypush.plugins.bluetooth._manager import BaseBluetoothManager
|
||||
|
||||
|
||||
class BaseBluetoothPlugin(ABC):
|
||||
"""
|
||||
Base class for Bluetooth plugins, like Switchbot or HID integrations.
|
||||
"""
|
||||
|
||||
def __init__(self, manager: BaseBluetoothManager):
|
||||
self._manager = manager
|
||||
|
||||
@abstractmethod
|
||||
def supports_device(self, device: BluetoothDevice) -> bool:
|
||||
"""
|
||||
Returns True if the given device matches this plugin.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def _extract_entities(self, device: BluetoothDevice) -> Iterable[Entity]:
|
||||
"""
|
||||
If :meth:`_matches_device` returns True, this method should return an
|
||||
iterable of entities that will be used to extend the existing device -
|
||||
like sensors, switches etc.
|
||||
"""
|
||||
|
||||
def extend_device(self, device: BluetoothDevice):
|
||||
"""
|
||||
Extends the given device with entities extracted through
|
||||
:meth:`_extract_entities`, if :meth:`_matches_device` returns True.
|
||||
"""
|
||||
if not self.supports_device(device):
|
||||
return
|
||||
|
||||
device.children.extend(self._extract_entities(device))
|
42
platypush/plugins/bluetooth/_plugins/_scanner.py
Normal file
42
platypush/plugins/bluetooth/_plugins/_scanner.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
import importlib
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import pkgutil
|
||||
from typing import List
|
||||
|
||||
from platypush.plugins.bluetooth._plugins import BaseBluetoothPlugin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def scan_plugins(manager) -> List[BaseBluetoothPlugin]:
|
||||
"""
|
||||
Initializes all the plugins associated to the given BluetoothManager by
|
||||
scanning all the modules under the ``_plugins`` folder in the manager's
|
||||
package.
|
||||
"""
|
||||
plugins = {}
|
||||
base_dir = os.path.dirname(inspect.getfile(manager.__class__))
|
||||
module = inspect.getmodule(manager.__class__)
|
||||
assert module is not None
|
||||
package = module.__package__
|
||||
assert package is not None
|
||||
|
||||
for _, mod_name, _ in pkgutil.walk_packages([base_dir], prefix=package + '.'):
|
||||
try:
|
||||
module = importlib.import_module(mod_name)
|
||||
except Exception as e:
|
||||
logger.warning('Could not import module %s', mod_name)
|
||||
logger.exception(e)
|
||||
continue
|
||||
|
||||
for _, obj in inspect.getmembers(module):
|
||||
if (
|
||||
inspect.isclass(obj)
|
||||
and not inspect.isabstract(obj)
|
||||
and issubclass(obj, BaseBluetoothPlugin)
|
||||
):
|
||||
plugins[obj] = obj(manager)
|
||||
|
||||
return list(plugins.values())
|
Loading…
Reference in a new issue