Skip to content

Commit

Permalink
Use MAC from payload for white/blacklist if MAC not in advertised dat…
Browse files Browse the repository at this point in the history
…a. (#107)

Minor fix for BLE advertised mfg_data.
  • Loading branch information
ttu committed Apr 19, 2020
1 parent 3dac6fc commit b7d2fc2
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 20 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions README.md
Expand Up @@ -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

Expand Down Expand Up @@ -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_.

Expand All @@ -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

Expand Down
19 changes: 13 additions & 6 deletions ruuvitag_sensor/adapters/bleson.py
@@ -1,4 +1,4 @@

import sys
import logging
from multiprocessing import Manager, Process
from queue import Queue
Expand All @@ -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)
Expand Down
11 changes: 11 additions & 0 deletions ruuvitag_sensor/decoder.py
Expand Up @@ -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
Expand Down
24 changes: 16 additions & 8 deletions ruuvitag_sensor/ruuvi.py
Expand Up @@ -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__)

Expand Down Expand Up @@ -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])
9 changes: 8 additions & 1 deletion 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

Expand Down Expand Up @@ -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)
7 changes: 4 additions & 3 deletions tests/test_ruuvitag_sensor.py
Expand Up @@ -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:
Expand All @@ -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)
Expand All @@ -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)
Expand Down

0 comments on commit b7d2fc2

Please sign in to comment.