forked from platypush/platypush
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.rst
|
||||||
platypush/backend/bluetooth.fileserver.rst
|
platypush/backend/bluetooth.fileserver.rst
|
||||||
platypush/backend/bluetooth.pushserver.rst
|
platypush/backend/bluetooth.pushserver.rst
|
||||||
|
platypush/backend/bluetooth.scanner.rst
|
||||||
|
platypush/backend/bluetooth.scanner.ble.rst
|
||||||
platypush/backend/button.flic.rst
|
platypush/backend/button.flic.rst
|
||||||
platypush/backend/camera.pi.rst
|
platypush/backend/camera.pi.rst
|
||||||
platypush/backend/chat.telegram.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):
|
except (TypeError, ValueError):
|
||||||
pass
|
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
|
ret[k] = v
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
@ -171,6 +173,10 @@ class SensorBackend(Backend):
|
||||||
if plugin and hasattr(plugin, 'close'):
|
if plugin and hasattr(plugin, 'close'):
|
||||||
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):
|
def run(self):
|
||||||
super().run()
|
super().run()
|
||||||
self.logger.info('Initialized {} sensor backend'.format(self.__class__.__name__))
|
self.logger.info('Initialized {} sensor backend'.format(self.__class__.__name__))
|
||||||
|
@ -179,9 +185,7 @@ class SensorBackend(Backend):
|
||||||
try:
|
try:
|
||||||
data = self.get_measurement()
|
data = self.get_measurement()
|
||||||
new_data = self.get_new_data(data)
|
new_data = self.get_new_data(data)
|
||||||
|
self.process_data(data, new_data)
|
||||||
if new_data:
|
|
||||||
self.bus.post(SensorDataChangeEvent(data=new_data))
|
|
||||||
|
|
||||||
data_below_threshold = {}
|
data_below_threshold = {}
|
||||||
data_above_threshold = {}
|
data_above_threshold = {}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from platypush.message.event import Event
|
from platypush.message.event import Event
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,6 +7,22 @@ class BluetoothEvent(Event):
|
||||||
pass
|
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):
|
class BluetoothDeviceConnectedEvent(Event):
|
||||||
"""
|
"""
|
||||||
Event triggered on bluetooth device connection
|
Event triggered on bluetooth device connection
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from platypush.message.event import Event
|
from platypush.message.event import Event
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,14 +8,14 @@ class SensorDataChangeEvent(Event):
|
||||||
Event triggered when a sensor has new data
|
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
|
: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.data = data
|
||||||
|
self.source = source
|
||||||
|
|
||||||
|
|
||||||
class SensorDataAboveThresholdEvent(Event):
|
class SensorDataAboveThresholdEvent(Event):
|
||||||
|
@ -24,7 +26,6 @@ class SensorDataAboveThresholdEvent(Event):
|
||||||
def __init__(self, data, *args, **kwargs):
|
def __init__(self, data, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
:param data: Sensor data
|
:param data: Sensor data
|
||||||
:type data: object
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(data=data, *args, **kwargs)
|
super().__init__(data=data, *args, **kwargs)
|
||||||
|
@ -39,7 +40,6 @@ class SensorDataBelowThresholdEvent(Event):
|
||||||
def __init__(self, data, *args, **kwargs):
|
def __init__(self, data, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
:param data: Sensor data
|
:param data: Sensor data
|
||||||
:type data: object
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(data=data, *args, **kwargs)
|
super().__init__(data=data, *args, **kwargs)
|
||||||
|
|
|
@ -3,12 +3,16 @@ import os
|
||||||
import re
|
import re
|
||||||
import select
|
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, \
|
from platypush.message.response.bluetooth import BluetoothScanResponse, \
|
||||||
BluetoothLookupNameResponse, BluetoothLookupServiceResponse, BluetoothResponse
|
BluetoothLookupNameResponse, BluetoothLookupServiceResponse, BluetoothResponse
|
||||||
|
|
||||||
|
|
||||||
class BluetoothPlugin(Plugin):
|
class BluetoothPlugin(SensorPlugin):
|
||||||
"""
|
"""
|
||||||
Bluetooth plugin
|
Bluetooth plugin
|
||||||
|
|
||||||
|
@ -67,7 +71,7 @@ class BluetoothPlugin(Plugin):
|
||||||
return self.lookup_address(device).output['addr']
|
return self.lookup_address(device).output['addr']
|
||||||
|
|
||||||
@action
|
@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
|
Scan for nearby bluetooth devices
|
||||||
|
|
||||||
|
@ -79,7 +83,7 @@ class BluetoothPlugin(Plugin):
|
||||||
if device_id is None:
|
if device_id is None:
|
||||||
device_id = self.device_id
|
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))
|
device_id, duration))
|
||||||
|
|
||||||
devices = discover_devices(duration=duration, lookup_names=True, lookup_class=True, device_id=device_id,
|
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')}
|
self._devices_by_name = {dev['name']: dev for dev in self._devices if dev.get('name')}
|
||||||
return response
|
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
|
@action
|
||||||
def lookup_name(self, addr: str, timeout: int = 10) -> BluetoothLookupNameResponse:
|
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)
|
sock = self._get_sock(device=device, port=port, service_uuid=service_uuid, service_name=service_name)
|
||||||
|
|
||||||
if not sock:
|
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
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
Loading…
Reference in a new issue