Source code for pymetawear.discover

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Performing BLE scans

:copyright: 2016-11-29 by hbldh <henrik.blidh@nedomkull.com>

"""

from __future__ import division
from __future__ import print_function
from __future__ import absolute_import

import os
import signal
import subprocess
import platform
import time

from pymetawear.exceptions import PyMetaWearException
from mbientlab.warble import BleScanner

try:
    input_fcn = raw_input
except NameError:
    input_fcn = input


[docs]def discover_devices(timeout=5): """Run a BLE scan to discover nearby devices. :param int timeout: Duration of scanning. :return: List of tuples with `(address, name)`. :rtype: list """ if platform.uname()[0] == 'Windows': return discover_devices_warble(timeout) else: return discover_devices_hcitool(timeout)
[docs]def discover_devices_hcitool(timeout=5): """Discover Bluetooth Low Energy Devices nearby on Linux Using ``hcitool`` from Bluez in subprocess, which requires root privileges. However, ``hcitool`` can be allowed to do scan without elevated permission. Install linux capabilities manipulation tools: .. code-block:: bash $ sudo apt-get install libcap2-bin Sets the missing capabilities on the executable quite like the setuid bit: .. code-block:: bash $ sudo setcap 'cap_net_raw,cap_net_admin+eip' `which hcitool` **References:** * `StackExchange, hcitool without sudo <https://unix.stackexchange.com/questions/96106/bluetooth-le-scan-as-non-root>`_ * `StackOverflow, hcitool lescan with timeout <https://stackoverflow.com/questions/26874829/hcitool-lescan-will-not-print-in-real-time-to-a-file>`_ :param int timeout: Duration of scanning. :return: List of tuples with `(address, name)`. :rtype: list """ p = subprocess.Popen(["hcitool", "lescan"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) time.sleep(timeout) os.kill(p.pid, signal.SIGINT) out, err = p.communicate() if len(out) == 0 and len(err) > 0: if err == b'Set scan parameters failed: Operation not permitted\n': raise PyMetaWearException("Missing capabilites for hcitool!") if err == b'Set scan parameters failed: Input/output error\n': raise PyMetaWearException("Could not perform scan.") ble_devices = list(set([tuple(x.split(' ')) for x in filter(None, out.decode('utf8').split('\n')[1:])])) filtered_devices = {} for d in ble_devices: if d[0] not in filtered_devices: filtered_devices[d[0]] = d[1] else: if filtered_devices.get(d[0]) == '(unknown)': filtered_devices[d[0]] = d[1] return [(k, v) for k, v in filtered_devices.items()]
[docs]def discover_devices_warble(timeout=5.0): """Use PyWarble's discovery method. Requires elevated access in Linux? :param int timeout: Duration of scanning. :return: List of tuples with `(address, name)`. :rtype: list """ devices = {} def handler(result): devices[result.mac] = result.name BleScanner.set_handler(handler) BleScanner.start() time.sleep(timeout) BleScanner.stop() return list(devices.items())
[docs]def select_device(timeout=3): """Run `discover_devices` and display a list to select from. :param int timeout: Duration of scanning. :return: The selected device's address. :rtype: str """ print("Discovering nearby Bluetooth Low Energy devices...") ble_devices = discover_devices(timeout=timeout) if len(ble_devices) > 1: for i, d in enumerate(ble_devices): print("[{0}] - {1}: {2}".format(i + 1, *d)) s = input("Which device do you want to connect to? ") if int(s) <= (i + 1): address = ble_devices[int(s) - 1][0] else: raise ValueError("Incorrect selection. Aborting...") elif len(ble_devices) == 1: address = ble_devices[0][0] print("Found only one device: {0}: {1}.".format(*ble_devices[0][::-1])) else: raise ValueError("Did not detect any BLE devices.") return address