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:
Adam Strojek
2019-03-03 09:12:00 +01:00
committed by zewelor
parent 716ea4578c
commit c6a068f811
3 changed files with 99 additions and 15 deletions
+6
View File
@@ -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:
+14
View File
@@ -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
View File
@@ -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