Skip to content
This repository has been archived by the owner on Oct 26, 2023. It is now read-only.

Commit

Permalink
blescanmulti - improve scanners options (#56)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
AdamStrojek authored and zewelor committed Mar 3, 2019
1 parent 716ea45 commit c6a068f
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 14 deletions.
6 changes: 6 additions & 0 deletions config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 14 additions & 0 deletions utils.py
Original file line number Diff line number Diff line change
@@ -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)
92 changes: 78 additions & 14 deletions workers/blescanmulti.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
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)
Expand All @@ -15,25 +17,87 @@ def handleDiscovery(self, dev, isNewDev, isNewData):
if isNewDev:
_LOGGER.debug("Discovered new device: %s" % dev.addr)


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):
def searchmac(self, devices, mac):
for dev in devices:
if dev.addr == mac.lower():
return dev
# 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

return None
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

0 comments on commit c6a068f

Please sign in to comment.