diff --git a/CHANGELOG.md b/CHANGELOG.md index 6268828..7f72727 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### [Unreleased] * Add Bleson BLE adapter +* Use MAC from payload for white/blacklist if MAC not in advertised data ## [1.0.1] - 2020-03-21 * Fix missing module from released package diff --git a/README.md b/README.md index f9037ef..4a5b12f 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,8 @@ __NOTE:__ This method shouldn't be used for a long duration with short timeout. ##### Get data from sensor +__NOTE:__ For a single sensor it is recommended to use `RuuviTagSensor.get_datas` or `RuuviTagSensor.get_data_for_sensors` functions instead of `RuuviTag`-class. + ```python from ruuvitag_sensor.ruuvitag import RuuviTag @@ -275,7 +277,7 @@ In case of errors, application tries to exit immediately, so it can be automatic Current state and known bugs in [issue #78](https://github.com/ttu/ruuvitag-sensor/issues/78). -Bleson works with Linux, macOS and Windows. +Bleson works with Linux, macOS and partially with Windows. Requires _Python 3_. @@ -291,7 +293,9 @@ Add environment variable `RUUVI_BLE_ADAPTER` with value `Bleson`. E.g. $ export RUUVI_BLE_ADAPTER="Bleson" ``` -__NOTE:__ On Windows Bleson works only with _Python 3.6_. +__NOTE:__ On macOS only Data Format 5 works as macOS doesn't advertise MAC-address and only DF5 has MAC in sensor payload. `RuuviTag`-class doesn't work with macOS. + +__NOTE:__ On Windows Bleson requires _Python 3.6_. Unfortunately on Windows, Bleson doesn't send any payload for advertised package, so it is still unusable. ## Examples diff --git a/ruuvitag_sensor/adapters/bleson.py b/ruuvitag_sensor/adapters/bleson.py index 0e04af7..45ec0b4 100644 --- a/ruuvitag_sensor/adapters/bleson.py +++ b/ruuvitag_sensor/adapters/bleson.py @@ -1,4 +1,4 @@ - +import sys import logging from multiprocessing import Manager, Process from queue import Queue @@ -18,20 +18,27 @@ class BleCommunicationBleson(BleCommunication): def _run_get_data_background(queue, shared_data, bt_device): (observer, q) = BleCommunicationBleson.start(bt_device) - for line in BleCommunicationBleson.get_lines(q): + for advertisement in BleCommunicationBleson.get_lines(q): + log.debug('Data: %s', advertisement) if shared_data['stop']: break try: - mac = line.address.address - if mac in shared_data['blacklist']: + # macOS doesn't return address on advertised package + mac = advertisement.address.address if advertisement.address is not None else None + if mac and mac in shared_data['blacklist']: + log.debug('MAC blacklised: %s', mac) continue - data = line.service_data or line.mfg_data - if data is None: + if advertisement.mfg_data is None: continue + # Linux returns bytearray for mfg_data, but macOS returns _NSInlineData + # which casts to byte array. We need to explicitly cast it to use hex + data = bytearray(advertisement.mfg_data) if sys.platform.startswith('darwin') \ + else advertisement.mfg_data queue.put((mac, data.hex())) except GeneratorExit: break except: + log.exception('Error in advertisement handling') continue BleCommunicationBleson.stop(observer) diff --git a/ruuvitag_sensor/decoder.py b/ruuvitag_sensor/decoder.py index e930f64..5b1ee29 100644 --- a/ruuvitag_sensor/decoder.py +++ b/ruuvitag_sensor/decoder.py @@ -45,6 +45,17 @@ def rshift(val, n): return (val % 0x100000000) >> n +def parse_mac(data_format, payload_mac): + """ + Data format 5 payload contains MAC-address in format e.g. e62eb92e73e5 + + Returns: + string: MAC separated and in upper case e.g. E6:2E:B9:2E:73:E5 + """ + if data_format == 5: + return ':'.join(payload_mac[i:i+2] for i in range(0, 12, 2)).upper() + return payload_mac + class UrlDecoder(object): """ Decodes data from RuuviTag url diff --git a/ruuvitag_sensor/ruuvi.py b/ruuvitag_sensor/ruuvi.py index af8abbc..0463ebb 100644 --- a/ruuvitag_sensor/ruuvi.py +++ b/ruuvitag_sensor/ruuvi.py @@ -5,7 +5,7 @@ from multiprocessing import Manager from ruuvitag_sensor.data_formats import DataFormats -from ruuvitag_sensor.decoder import get_decoder +from ruuvitag_sensor.decoder import get_decoder, parse_mac log = logging.getLogger(__name__) @@ -156,17 +156,25 @@ def _get_ruuvitag_datas(macs=[], search_duratio_sec=None, run_flag=RunFlag(), bt if not run_flag.running: data_iter.send(StopIteration) break - # Check MAC whitelist - if macs and not ble_data[0] in macs: + # Check MAC whitelist if advertised MAC available + if ble_data[0] and macs and not ble_data[0] in macs: continue + (data_format, data) = DataFormats.convert_data(ble_data[1]) # Check that encoded data is valid RuuviTag data and it is sensor data - # If data is not valid RuuviTag data add MAC to blacklist + # If data is not valid RuuviTag data add MAC to blacklist if MAC is available if data is not None: - state = get_decoder(data_format).decode_data(data) - if state is not None: - yield (ble_data[0], state) + decoded = get_decoder(data_format).decode_data(data) + if decoded is not None: + # If advertised MAC is missing, try to parse it from the payload + mac = ble_data[0] if ble_data[0] else \ + parse_mac(data_format, decoded['mac']) if decoded['mac'] else None + # Check whitelist using MAC from decoded data if advertised MAC is not available + if mac and macs and mac not in macs: + continue + yield (mac, decoded) else: log.error('Decoded data is null. MAC: %s - Raw: %s', ble_data[0], ble_data[1]) else: - mac_blacklist.append(ble_data[0]) + if ble_data[0]: + mac_blacklist.append(ble_data[0]) diff --git a/tests/test_decoder.py b/tests/test_decoder.py index db1382f..8c9cc7e 100644 --- a/tests/test_decoder.py +++ b/tests/test_decoder.py @@ -1,6 +1,6 @@ from unittest import TestCase -from ruuvitag_sensor.decoder import get_decoder, UrlDecoder, Df3Decoder, Df5Decoder +from ruuvitag_sensor.decoder import get_decoder, parse_mac, UrlDecoder, Df3Decoder, Df5Decoder # pylint: disable=line-too-long @@ -171,3 +171,10 @@ def test_df5decode_is_valid(self): self.assertEqual(data['movement_counter'], 66) self.assertEqual(data['measurement_sequence_number'], 205) self.assertEqual(data['mac'], 'cbb8334c884f') + + def test_parse_df5_mac(self): + mac_payload = 'e62eb92e73e5' + mac = 'E6:2E:B9:2E:73:E5' + + parsed = parse_mac(5, mac_payload) + self.assertEqual(parsed, mac) diff --git a/tests/test_ruuvitag_sensor.py b/tests/test_ruuvitag_sensor.py index 1e920e3..671f495 100644 --- a/tests/test_ruuvitag_sensor.py +++ b/tests/test_ruuvitag_sensor.py @@ -49,7 +49,8 @@ def get_datas(self, blacklist=[], bt_device=''): ('EE:2C:6A:1E:59:3D', '1F0201060303AAFE1716AAFE10F9037275752E76692F23416A5558314D417730C3'), ('FF:2C:6A:1E:59:3D', '1902010415FF990403291A1ECE1E02DEF94202CA0B5300000000BB'), ('00:2C:6A:1E:59:3D', '1902010415FF990403291A1ECE1E02DEF94202CA0B53BB'), - ('11:2C:6A:1E:59:3D', '043E2B020100014F884C33B8CB1F0201061BFF99040512FC5394C37C0004FFFC040CAC364200CDCBB8334C884FC4') + ('11:2C:6A:1E:59:3D', '043E2B020100014F884C33B8CB1F0201061BFF99040512FC5394C37C0004FFFC040CAC364200CDCBB8334C884FC4'), + (None, '043E2B020100014F884C33B8CB1F0201061BFF99040512FC5394C37C0004FFFC040CAC364200CDCBB8334C884FC4') ] for data in datas: @@ -61,7 +62,7 @@ def get_datas(self, blacklist=[], bt_device=''): get_datas) def test_find_tags(self): tags = RuuviTagSensor.find_ruuvitags() - self.assertEqual(7, len(tags)) + self.assertEqual(8, len(tags)) @patch('ruuvitag_sensor.adapters.dummy.BleCommunicationDummy.get_datas', get_datas) @@ -84,7 +85,7 @@ def test_get_data_for_sensors(self): def test_get_datas(self): datas = [] RuuviTagSensor.get_datas(datas.append) - self.assertEqual(7, len(datas)) + self.assertEqual(8, len(datas)) @patch('ruuvitag_sensor.adapters.dummy.BleCommunicationDummy.get_datas', get_datas)