blescanmulti - improve scanners options (#56)
* Helpful utils that will allow to reduce common code * Update config with new options for blescanmulti * Default values for blescanmulti if not provided by config file * Allow to configure scan behavior * Allow to configure returned payload * Initial version of saving last state * Support class that will remember last status of device * Add docs for available options * Move scanner to instance, this will prevent recreating it each time it is required to scan * Use items instead of values. We require here to have pair of key and value, not just value * Make sure that mac address is in lower case * Optimize searching for devices, use hash maps * Remove unused method * Because YAML is already doing some conversions this change will allow to always get proper value * Fix issue. Mac address and name was provided in wrong order * Use worker to format topic * Send also RSSI info
This commit is contained in:
@@ -54,6 +54,12 @@ manager:
|
||||
beacon: 00:11:22:33:44:55
|
||||
smartwath: 00:11:22:33:44:55
|
||||
topic_prefix: blescan
|
||||
available_payload: home
|
||||
unavailable_payload: not_home
|
||||
available_timeout: 0
|
||||
unavailable_timeout: 60
|
||||
scan_timeout: 10
|
||||
scan_passive: true
|
||||
update_interval: 60
|
||||
toothbrush:
|
||||
args:
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
true_statement = ('y', 'yes', 'on', '1', 'true', 't', )
|
||||
|
||||
|
||||
def booleanize(value) -> bool:
|
||||
"""
|
||||
This function will try to assume that provided string value is boolean in some way. It will accept a wide range
|
||||
of values for strings like ('y', 'yes', 'on', '1', 'true' and 't'. Any other value will be treated as false
|
||||
:param value: any value
|
||||
:return: boolean statement
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
return value.lower() in true_statement
|
||||
return bool(value)
|
||||
+79
-15
@@ -2,11 +2,13 @@ import time
|
||||
from interruptingcow import timeout
|
||||
from bluepy.btle import Scanner, DefaultDelegate
|
||||
from mqtt import MqttMessage
|
||||
from utils import booleanize
|
||||
from workers.base import BaseWorker
|
||||
from logger import _LOGGER
|
||||
|
||||
REQUIREMENTS = ['bluepy']
|
||||
|
||||
|
||||
class ScanDelegate(DefaultDelegate):
|
||||
def __init__(self):
|
||||
DefaultDelegate.__init__(self)
|
||||
@@ -15,25 +17,87 @@ class ScanDelegate(DefaultDelegate):
|
||||
if isNewDev:
|
||||
_LOGGER.debug("Discovered new device: %s" % dev.addr)
|
||||
|
||||
class BlescanmultiWorker(BaseWorker):
|
||||
def searchmac(self, devices, mac):
|
||||
for dev in devices:
|
||||
if dev.addr == mac.lower():
|
||||
return dev
|
||||
|
||||
return None
|
||||
class BleDeviceStatus:
|
||||
def __init__(self, worker, mac: str, name: str, available: bool = False, last_status_time: float = None,
|
||||
message_sent: bool = True):
|
||||
if last_status_time is None:
|
||||
last_status_time = time.time()
|
||||
|
||||
self.worker = worker # type: BlescanmultiWorker
|
||||
self.mac = mac.lower()
|
||||
self.name = name
|
||||
self.available = available
|
||||
self.last_status_time = last_status_time
|
||||
self.message_sent = message_sent
|
||||
|
||||
def set_status(self, available):
|
||||
if available != self.available:
|
||||
self.available = available
|
||||
self.last_status_time = time.time()
|
||||
self.message_sent = False
|
||||
|
||||
def _timeout(self):
|
||||
if self.available:
|
||||
return self.worker.available_timeout
|
||||
else:
|
||||
return self.worker.unavailable_timeout
|
||||
|
||||
def has_time_elapsed(self):
|
||||
elapsed = time.time() - self.last_status_time
|
||||
return elapsed > self._timeout()
|
||||
|
||||
def payload(self):
|
||||
if self.available:
|
||||
return self.worker.available_payload
|
||||
else:
|
||||
return self.worker.unavailable_payload
|
||||
|
||||
def generate_messages(self, device):
|
||||
messages = []
|
||||
if not self.message_sent and self.has_time_elapsed():
|
||||
self.message_sent = True
|
||||
messages.append(
|
||||
MqttMessage(topic=self.worker.format_topic('presence/{}'.format(self.name)), payload=self.payload())
|
||||
)
|
||||
if self.available:
|
||||
messages.append(
|
||||
MqttMessage(topic=self.worker.format_topic('presence/{}/rssi'.format(self.name)), payload=device.rssi)
|
||||
)
|
||||
return messages
|
||||
|
||||
|
||||
class BlescanmultiWorker(BaseWorker):
|
||||
# Default values
|
||||
devices = {}
|
||||
# Payload that should be send when device is available
|
||||
available_payload = 'home' # type: str
|
||||
# Payload that should be send when device is unavailable
|
||||
unavailable_payload = 'not_home' # type: str
|
||||
# After what time (in seconds) we should inform that device is available (default: 0 seconds)
|
||||
available_timeout = 0 # type: float
|
||||
# After what time (in seconds) we should inform that device is unavailable (default: 60 seconds)
|
||||
unavailable_timeout = 60 # type: float
|
||||
scan_timeout = 10. # type: float
|
||||
scan_passive = True # type: str or bool
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(BlescanmultiWorker, self).__init__(**kwargs)
|
||||
self.scanner = Scanner().withDelegate(ScanDelegate())
|
||||
self.last_status = [
|
||||
BleDeviceStatus(self, mac, name) for name, mac in self.devices.items()
|
||||
]
|
||||
|
||||
def status_update(self):
|
||||
scanner = Scanner().withDelegate(ScanDelegate())
|
||||
devices = scanner.scan(10.0)
|
||||
devices = self.scanner.scan(float(self.scan_timeout), passive=booleanize(self.scan_passive))
|
||||
mac_addresses = {
|
||||
device.addr: device for device in devices
|
||||
}
|
||||
ret = []
|
||||
|
||||
for name, mac in self.devices.items():
|
||||
device = self.searchmac(devices, mac)
|
||||
if device is None:
|
||||
ret.append(MqttMessage(topic=self.format_topic('presence/'+name), payload="0"))
|
||||
else:
|
||||
ret.append(MqttMessage(topic=self.format_topic('presence/'+name+'/rssi'), payload=device.rssi))
|
||||
ret.append(MqttMessage(topic=self.format_topic('presence/'+name), payload="1"))
|
||||
for status in self.last_status:
|
||||
device = mac_addresses.get(status.mac, None)
|
||||
status.set_status(device is not None)
|
||||
ret += status.generate_messages(device)
|
||||
|
||||
return ret
|
||||
|
||||
Reference in New Issue
Block a user