Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor communicators + work of mac os (based on #72) #90

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
sudo: false
language: python
python:
- "3.5"
- "3.6"
- "3.7"
- "3.8"
install: pip install tox-travis
script: tox
2 changes: 1 addition & 1 deletion examples/get_ble_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Get all BLE device broadcasts
"""

from ruuvitag_sensor.ble_communication import BleCommunicationNix
from ruuvitag_sensor.adaptors.nix_hci import BleCommunicationNix
import ruuvitag_sensor.log

ruuvitag_sensor.log.enable_console()
Expand Down
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
testpaths = tests/
2 changes: 1 addition & 1 deletion ruuvitag_sensor/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.13.0'
__version__ = '0.13.0'
16 changes: 16 additions & 0 deletions ruuvitag_sensor/adaptors/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

import abc

class BleCommunication(object):
"""Bluetooth LE communication"""
__metaclass__ = abc.ABCMeta

@staticmethod
@abc.abstractmethod
def get_data(mac, bt_device=''):
pass

@staticmethod
@abc.abstractmethod
def get_datas(blacklist=[], bt_device=''):
pass
120 changes: 120 additions & 0 deletions ruuvitag_sensor/adaptors/bleson.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import time
import logging
from queue import Queue
from bleson import get_provider, Observer
from multiprocessing import Manager, Process
from ruuvitag_sensor.adaptors import BleCommunication

log = logging.getLogger(__name__)

class BleCommunicationBleson(BleCommunication):
'''Bluetooth LE communication with Bleson'''

@staticmethod
def _run_get_data_background(queue, shared_data, bt_device):
(observer, q) = BleCommunicationBleson.start(bt_device)

for line in BleCommunicationBleson.get_lines(q):
if shared_data['stop']:
break
try:
mac = line.address.address
if mac in shared_data['blacklist']:
continue
data = line.service_data or line.mfg_data
if data is None:
continue
queue.put((mac, data))
except GeneratorExit:
break
except:
continue

BleCommunicationBleson.stop(observer)

@staticmethod
def start(bt_device=''):
'''
Attributes:
device (string): BLE device (default 0)
'''

if not bt_device:
bt_device = 0
else:
# Old communication used hci0 etc.
bt_device = bt_device.replace('hci', '')

log.info('Start receiving broadcasts (device %s)', bt_device)

q = Queue()

adapter = get_provider().get_adapter(int(bt_device))
observer = Observer(adapter)
observer.on_advertising_data = q.put
observer.start()

return (observer, q)

@staticmethod
def stop(observer):
observer.stop()

@staticmethod
def get_lines(queue):
try:
while True:
next_item = queue.get(True, None)
yield next_item
except KeyboardInterrupt:
return
except Exception as ex:
log.info(ex)
return

@staticmethod
def get_datas(blacklist=[], bt_device=''):
m = Manager()
q = m.Queue()

# Use Manager dict to share data between processes
shared_data = m.dict()
shared_data['blacklist'] = blacklist
shared_data['stop'] = False

# Start background process
proc = Process(
target=BleCommunicationBleson._run_get_data_background,
args=[q, shared_data, bt_device]
)
proc.start()

try:
while True:
while not q.empty():
data = q.get()
yield data
log.info("Sleep")
time.sleep(1)
except GeneratorExit:
pass
except KeyboardInterrupt:
pass

shared_data['stop'] = True
proc.join()
return

@staticmethod
def get_data(mac, bt_device=''):
data = None
data_iter = BleCommunicationBleson.get_datas(bt_device)

for data in data_iter:
if mac == data[0]:
log.info('Data found')
data_iter.send(StopIteration)
data = data[1]
break

return data
18 changes: 18 additions & 0 deletions ruuvitag_sensor/adaptors/dummy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from ruuvitag_sensor.adaptors import BleCommunication

class BleCommunicationDummy(BleCommunication):
"""TODO: Find some working BLE implementation for Windows and OSX"""

@staticmethod
def get_data(mac, bt_device=''):
return '1E0201060303AAFE1616AAFE10EE037275752E76692F23416A7759414D4663CD'

@staticmethod
def get_datas(blacklist=[], bt_device=''):
datas = [
('75:B6:68:32:18:49', '1E0201060303AAFE1616AAFE10EE037275752E76692F23416A7759414D4663CD'),
('36:9B:7E:16:18:9B', '1E0201060303AAFE1616AAFE10EE037275752E76692F23416A7759414D4663CD')
]

for data in datas:
yield data
Original file line number Diff line number Diff line change
@@ -1,45 +1,12 @@
import abc
import logging
import os
import subprocess
import sys
import time

log = logging.getLogger(__name__)


class BleCommunication(object):
"""Bluetooth LE communication"""
__metaclass__ = abc.ABCMeta

@staticmethod
@abc.abstractmethod
def get_data(mac, bt_device=''):
pass

@staticmethod
@abc.abstractmethod
def get_datas(blacklist=[], bt_device=''):
pass


class BleCommunicationDummy(BleCommunication):
"""TODO: Find some working BLE implementation for Windows and OSX"""

@staticmethod
def get_data(mac, bt_device=''):
return '1E0201060303AAFE1616AAFE10EE037275752E76692F23416A7759414D4663CD'

@staticmethod
def get_datas(blacklist=[], bt_device=''):
datas = [
('DU:MM:YD:AT:A9:3D', '1E0201060303AAFE1616AAFE10EE037275752E76692F23416A7759414D4663CD'),
('NO:TS:UP:PO:RT:ED', '1E0201060303AAFE1616AAFE10EE037275752E76692F23416A7759414D4663CD')
]

for data in datas:
yield data
from ruuvitag_sensor.adaptors import BleCommunication

log = logging.getLogger(__name__)

class BleCommunicationNix(BleCommunication):
"""Bluetooth LE communication for Linux"""
Expand All @@ -50,33 +17,53 @@ def start(bt_device=''):
Attributes:
device (string): BLE device (default hci0)
"""
# import ptyprocess here so as long as all implementations are in the same file, all will work
# import ptyprocess here so as long as all implementations are in
# the same file, all will work
import ptyprocess

if not bt_device:
bt_device = 'hci0'

log.info('Start receiving broadcasts (device %s)', bt_device)
DEVNULL = subprocess.DEVNULL if sys.version_info >= (3, 3) else open(os.devnull, 'wb')
if sys.version_info >= (3, 3):
DEVNULL = subprocess.DEVNULL
else:
open(os.devnull, 'wb')

def reset_ble_adapter():
return subprocess.call('sudo hciconfig %s reset' % bt_device, shell=True, stdout=DEVNULL)
log.info("FYI: Calling a process with sudo!")
return subprocess.call(
'sudo hciconfig %s reset' % bt_device,
shell=True,
stdout=DEVNULL
)

def start_with_retry(func, try_count, interval, msg):
retcode = func()
if retcode != 0 and try_count > 0:
log.info(msg)
time.sleep(interval)
return start_with_retry(func, try_count - 1, interval + interval, msg)
return start_with_retry(
func, try_count - 1, interval + interval, msg)
return retcode

retcode = start_with_retry(reset_ble_adapter, 3, 1, 'Problem with hciconfig reset. Retry reset.')
retcode = start_with_retry(
reset_ble_adapter,
3, 1,
'Problem with hciconfig reset. Retry reset.'
)

if retcode != 0:
log.info('Problem with hciconfig reset. Exit.')
exit(1)

hcitool = ptyprocess.PtyProcess.spawn(['sudo', '-n', 'hcitool', '-i', bt_device, 'lescan2', '--duplicates'])
hcidump = ptyprocess.PtyProcess.spawn(['sudo', '-n', 'hcidump', '-i', bt_device, '--raw'])
log.info("FYI: Spawning 2 processes with sudo!")
hcitool = ptyprocess.PtyProcess.spawn(
['sudo', '-n', 'hcitool', '-i', bt_device, 'lescan2', '--duplicates']
)
hcidump = ptyprocess.PtyProcess.spawn(
['sudo', '-n', 'hcidump', '-i', bt_device, '--raw']
)
return (hcitool, hcidump)

@staticmethod
Expand All @@ -99,7 +86,7 @@ def get_lines(hcidump):
else:
if data:
data += line.strip().replace(' ', '')
except KeyboardInterrupt as ex:
except KeyboardInterrupt:
return
except Exception as ex:
log.info(ex)
Expand Down
15 changes: 12 additions & 3 deletions ruuvitag_sensor/data_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,22 @@ def convert_data(raw):
Returns:
tuple (int, string): Data Format type and Sensor data
"""
data = DataFormats._get_data_format_3(raw)

#Python duck typing
try:
string_data = raw.hex()
if string_data[0:2] != 'FF':
string_data = 'FF' + string_data

except (AttributeError):
string_data = raw

data = DataFormats._get_data_format_3(string_data)

if data is not None:
return (3, data)

data = DataFormats._get_data_format_5(raw)
data = DataFormats._get_data_format_5(string_data)

if data is not None:
return (5, data)
Expand Down Expand Up @@ -49,7 +59,6 @@ def _get_data_format_2and4(raw):
index = data.find('ruu.vi/#')
if index > -1:
return data[(index + 8):]

return None
except:
return None
Expand Down
18 changes: 10 additions & 8 deletions ruuvitag_sensor/ruuvi.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@
import os
import time
import logging
from multiprocessing import Manager

from ruuvitag_sensor.data_formats import DataFormats
from ruuvitag_sensor.decoder import get_decoder

log = logging.getLogger(__name__)


if not sys.platform.startswith('linux') or os.environ.get('CI') == 'True':
if os.environ.get('CI') == 'True':
# Use BleCommunicationDummy also for CI as it can't use bluez
from ruuvitag_sensor.ble_communication import BleCommunicationDummy
from ruuvitag_sensor.adaptors.dummy import BleCommunicationDummy
ble = BleCommunicationDummy()
else:
from ruuvitag_sensor.ble_communication import BleCommunicationNix
ble = BleCommunicationNix()
from ruuvitag_sensor.adaptors.bleson import BleCommunicationBleson
ble = BleCommunicationBleson()

# from ruuvitag_sensor.adaptors.bleson import BleCommunicationBleson
# ble = BleCommunicationBleson()

class RunFlag(object):
"""
Expand Down Expand Up @@ -127,18 +129,18 @@ def _get_ruuvitag_datas(macs=[], search_duratio_sec=None, run_flag=RunFlag(), bt
tuple: MAC and State of RuuviTag sensor data
"""

mac_blacklist = []
mac_blacklist = Manager().list()
start_time = time.time()
data_iter = ble.get_datas(mac_blacklist, bt_device)

for ble_data in data_iter:
# Check duration
if search_duratio_sec and time.time() - start_time > search_duratio_sec:
data_iter.send(StopIteration)
data_iter.send(StopIteration("Timeout"))
break
# Check running flag
if not run_flag.running:
data_iter.send(StopIteration)
data_iter.send(StopIteration("Not running"))
break
# Check MAC whitelist
if macs and not ble_data[0] in macs:
Expand Down
Loading