Added bluetooth scanner backends [closes #112]
This commit is contained in:
parent
535b2ec083
commit
59e3f81202
9 changed files with 145 additions and 14 deletions
|
@ -14,6 +14,8 @@ Backends
|
|||
platypush/backend/bluetooth.rst
|
||||
platypush/backend/bluetooth.fileserver.rst
|
||||
platypush/backend/bluetooth.pushserver.rst
|
||||
platypush/backend/bluetooth.scanner.rst
|
||||
platypush/backend/bluetooth.scanner.ble.rst
|
||||
platypush/backend/button.flic.rst
|
||||
platypush/backend/camera.pi.rst
|
||||
platypush/backend/chat.telegram.rst
|
||||
|
|
5
docs/source/platypush/backend/bluetooth.scanner.ble.rst
Normal file
5
docs/source/platypush/backend/bluetooth.scanner.ble.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
``platypush.backend.bluetooth.scanner.ble``
|
||||
===========================================
|
||||
|
||||
.. automodule:: platypush.backend.bluetooth.scanner.ble
|
||||
:members:
|
5
docs/source/platypush/backend/bluetooth.scanner.rst
Normal file
5
docs/source/platypush/backend/bluetooth.scanner.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
``platypush.backend.bluetooth.scanner``
|
||||
=======================================
|
||||
|
||||
.. automodule:: platypush.backend.bluetooth.scanner
|
||||
:members:
|
47
platypush/backend/bluetooth/scanner/__init__.py
Normal file
47
platypush/backend/bluetooth/scanner/__init__.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
from typing import Dict, Optional
|
||||
|
||||
from platypush.backend.sensor import SensorBackend
|
||||
from platypush.message.event.bluetooth import BluetoothDeviceFoundEvent, BluetoothDeviceLostEvent
|
||||
|
||||
|
||||
class BluetoothScannerBackend(SensorBackend):
|
||||
"""
|
||||
This backend periodically scans for available bluetooth devices and returns events when a devices enter or exits
|
||||
the range.
|
||||
|
||||
Triggers:
|
||||
|
||||
* :class:`platypush.message.event.bluetooth.BluetoothDeviceFoundEvent` when a new bluetooth device is found.
|
||||
* :class:`platypush.message.event.bluetooth.BluetoothDeviceLostEvent` when a bluetooth device is lost.
|
||||
|
||||
Requires:
|
||||
|
||||
* The :class:`platypush.plugins.bluetooth.BluetoothPlugin` plugin working.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, device_id: Optional[int] = None, scan_interval: int = 10, **kwargs):
|
||||
"""
|
||||
:param device_id: Bluetooth adapter ID to use (default configured on the ``bluetooth`` plugin if None).
|
||||
:param scan_interval: How long the scan should run (default: 10 seconds).
|
||||
"""
|
||||
super().__init__(plugin='bluetooth', plugin_args={
|
||||
'device_id': device_id,
|
||||
'duration': scan_interval,
|
||||
}, **kwargs)
|
||||
|
||||
self._last_seen_devices = {}
|
||||
|
||||
def process_data(self, data: Dict[str, dict], new_data: Dict[str, dict]):
|
||||
for addr, dev in data.items():
|
||||
if addr not in self._last_seen_devices:
|
||||
self.bus.post(BluetoothDeviceFoundEvent(address=dev.pop('addr'), **dev))
|
||||
self._last_seen_devices[addr] = {'addr': addr, **dev}
|
||||
|
||||
for addr, dev in self._last_seen_devices.copy().items():
|
||||
if addr not in data:
|
||||
self.bus.post(BluetoothDeviceLostEvent(address=dev.pop('addr'), **dev))
|
||||
del self._last_seen_devices[addr]
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
33
platypush/backend/bluetooth/scanner/ble.py
Normal file
33
platypush/backend/bluetooth/scanner/ble.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from typing import Optional
|
||||
|
||||
from platypush.backend.bluetooth.scanner import BluetoothScannerBackend
|
||||
|
||||
|
||||
class BluetoothBleScannerBackend(BluetoothScannerBackend):
|
||||
"""
|
||||
This backend periodically scans for available bluetooth low-energy devices and returns events when a devices enter
|
||||
or exits the range.
|
||||
|
||||
Triggers:
|
||||
|
||||
* :class:`platypush.message.event.bluetooth.BluetoothDeviceFoundEvent` when a new bluetooth device is found.
|
||||
* :class:`platypush.message.event.bluetooth.BluetoothDeviceLostEvent` when a bluetooth device is lost.
|
||||
|
||||
Requires:
|
||||
|
||||
* The :class:`platypush.plugins.bluetooth.BluetoothBlePlugin` plugin working.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, interface: Optional[int] = None, scan_interval: int = 10, **kwargs):
|
||||
"""
|
||||
:param interface: Bluetooth adapter name to use (default configured on the ``bluetooth.ble`` plugin if None).
|
||||
:param scan_interval: How long the scan should run (default: 10 seconds).
|
||||
"""
|
||||
super().__init__(plugin='bluetooth.ble', plugin_args={
|
||||
'interface': interface,
|
||||
'duration': scan_interval,
|
||||
}, **kwargs)
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
|
@ -158,7 +158,9 @@ class SensorBackend(Backend):
|
|||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
if tolerance is None or is_nan or abs(v - old_v) >= tolerance:
|
||||
if tolerance is None or abs(v - old_v) >= tolerance:
|
||||
ret[k] = v
|
||||
elif k not in self.data or self.data[k] != v:
|
||||
ret[k] = v
|
||||
|
||||
return ret
|
||||
|
@ -171,6 +173,10 @@ class SensorBackend(Backend):
|
|||
if plugin and hasattr(plugin, 'close'):
|
||||
plugin.close()
|
||||
|
||||
def process_data(self, data, new_data):
|
||||
if new_data:
|
||||
self.bus.post(SensorDataChangeEvent(data=data, source=self.plugin or self.__class__.__name__))
|
||||
|
||||
def run(self):
|
||||
super().run()
|
||||
self.logger.info('Initialized {} sensor backend'.format(self.__class__.__name__))
|
||||
|
@ -179,9 +185,7 @@ class SensorBackend(Backend):
|
|||
try:
|
||||
data = self.get_measurement()
|
||||
new_data = self.get_new_data(data)
|
||||
|
||||
if new_data:
|
||||
self.bus.post(SensorDataChangeEvent(data=new_data))
|
||||
self.process_data(data, new_data)
|
||||
|
||||
data_below_threshold = {}
|
||||
data_above_threshold = {}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from typing import Optional
|
||||
|
||||
from platypush.message.event import Event
|
||||
|
||||
|
||||
|
@ -5,6 +7,22 @@ class BluetoothEvent(Event):
|
|||
pass
|
||||
|
||||
|
||||
class BluetoothDeviceFoundEvent(Event):
|
||||
"""
|
||||
Event triggered when a bluetooth device is found during a scan.
|
||||
"""
|
||||
def __init__(self, address: str, name: Optional[str] = None, *args, **kwargs):
|
||||
super().__init__(*args, address=address, name=name, **kwargs)
|
||||
|
||||
|
||||
class BluetoothDeviceLostEvent(Event):
|
||||
"""
|
||||
Event triggered when a bluetooth device previously scanned is lost.
|
||||
"""
|
||||
def __init__(self, address: str, name: Optional[str] = None, *args, **kwargs):
|
||||
super().__init__(*args, address=address, name=name, **kwargs)
|
||||
|
||||
|
||||
class BluetoothDeviceConnectedEvent(Event):
|
||||
"""
|
||||
Event triggered on bluetooth device connection
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from typing import Optional
|
||||
|
||||
from platypush.message.event import Event
|
||||
|
||||
|
||||
|
@ -6,14 +8,14 @@ class SensorDataChangeEvent(Event):
|
|||
Event triggered when a sensor has new data
|
||||
"""
|
||||
|
||||
def __init__(self, data, *args, **kwargs):
|
||||
def __init__(self, data, source: Optional[str] = None, *args, **kwargs):
|
||||
"""
|
||||
:param data: Sensor data
|
||||
:type data: object
|
||||
"""
|
||||
|
||||
super().__init__(data=data, *args, **kwargs)
|
||||
super().__init__(data=data, source=source, *args, **kwargs)
|
||||
self.data = data
|
||||
self.source = source
|
||||
|
||||
|
||||
class SensorDataAboveThresholdEvent(Event):
|
||||
|
@ -24,7 +26,6 @@ class SensorDataAboveThresholdEvent(Event):
|
|||
def __init__(self, data, *args, **kwargs):
|
||||
"""
|
||||
:param data: Sensor data
|
||||
:type data: object
|
||||
"""
|
||||
|
||||
super().__init__(data=data, *args, **kwargs)
|
||||
|
@ -39,7 +40,6 @@ class SensorDataBelowThresholdEvent(Event):
|
|||
def __init__(self, data, *args, **kwargs):
|
||||
"""
|
||||
:param data: Sensor data
|
||||
:type data: object
|
||||
"""
|
||||
|
||||
super().__init__(data=data, *args, **kwargs)
|
||||
|
|
|
@ -3,12 +3,16 @@ import os
|
|||
import re
|
||||
import select
|
||||
|
||||
from platypush.plugins import Plugin, action
|
||||
from typing import Dict, Optional
|
||||
|
||||
from platypush.plugins.sensor import SensorPlugin
|
||||
|
||||
from platypush.plugins import action
|
||||
from platypush.message.response.bluetooth import BluetoothScanResponse, \
|
||||
BluetoothLookupNameResponse, BluetoothLookupServiceResponse, BluetoothResponse
|
||||
|
||||
|
||||
class BluetoothPlugin(Plugin):
|
||||
class BluetoothPlugin(SensorPlugin):
|
||||
"""
|
||||
Bluetooth plugin
|
||||
|
||||
|
@ -67,7 +71,7 @@ class BluetoothPlugin(Plugin):
|
|||
return self.lookup_address(device).output['addr']
|
||||
|
||||
@action
|
||||
def scan(self, device_id: int = None, duration: int = 10) -> BluetoothScanResponse:
|
||||
def scan(self, device_id: Optional[int] = None, duration: int = 10) -> BluetoothScanResponse:
|
||||
"""
|
||||
Scan for nearby bluetooth devices
|
||||
|
||||
|
@ -79,7 +83,7 @@ class BluetoothPlugin(Plugin):
|
|||
if device_id is None:
|
||||
device_id = self.device_id
|
||||
|
||||
self.logger.info('Discovering devices on adapter {}, duration: {} seconds'.format(
|
||||
self.logger.debug('Discovering devices on adapter {}, duration: {} seconds'.format(
|
||||
device_id, duration))
|
||||
|
||||
devices = discover_devices(duration=duration, lookup_names=True, lookup_class=True, device_id=device_id,
|
||||
|
@ -91,6 +95,19 @@ class BluetoothPlugin(Plugin):
|
|||
self._devices_by_name = {dev['name']: dev for dev in self._devices if dev.get('name')}
|
||||
return response
|
||||
|
||||
@action
|
||||
def get_measurement(self, device_id: Optional[int] = None, duration: Optional[int] = 10, *args, **kwargs) \
|
||||
-> Dict[str, dict]:
|
||||
"""
|
||||
Wrapper for ``scan`` that returns bluetooth devices in a format usable by sensor backends.
|
||||
|
||||
:param device_id: Bluetooth adapter ID to use (default configured if None)
|
||||
:param duration: Scan duration in seconds
|
||||
:return: Device address -> info map.
|
||||
"""
|
||||
devices = self.scan(device_id=device_id, duration=duration).output
|
||||
return {device['addr']: device for device in devices}
|
||||
|
||||
@action
|
||||
def lookup_name(self, addr: str, timeout: int = 10) -> BluetoothLookupNameResponse:
|
||||
"""
|
||||
|
@ -280,7 +297,7 @@ class BluetoothPlugin(Plugin):
|
|||
sock = self._get_sock(device=device, port=port, service_uuid=service_uuid, service_name=service_name)
|
||||
|
||||
if not sock:
|
||||
self.logger.info('Close on device {}({}) that is not connected'.format(device, port))
|
||||
self.logger.debug('Close on device {}({}) that is not connected'.format(device, port))
|
||||
return
|
||||
|
||||
try:
|
||||
|
|
Loading…
Reference in a new issue