10

Is it possible for this code to be modified to include Bluetooth Low Energy devices as well? https://code.google.com/p/pybluez/source/browse/trunk/examples/advanced/inquiry-with-rssi.py?r=1

I can find devices like my phone and other bluetooth 4.0 devices, but not any BLE. If this cannot be modified, is it possible to run the hcitool lescan and pull the data from hci dump within python? I can use the tools to see the devices I am looking for and it gives an RSSI in hcidump, which is what my end goal is. To get a MAC address and RSSI from the BLE device.

Thanks!

user3582887
  • 403
  • 3
  • 6
  • 19

2 Answers2

29

As I said in the comment, that library won't work with BLE.

Here's some example code to do a simple BLE scan:

import sys
import os
import struct
from ctypes import (CDLL, get_errno)
from ctypes.util import find_library
from socket import (
    socket,
    AF_BLUETOOTH,
    SOCK_RAW,
    BTPROTO_HCI,
    SOL_HCI,
    HCI_FILTER,
)

if not os.geteuid() == 0:
    sys.exit("script only works as root")

btlib = find_library("bluetooth")
if not btlib:
    raise Exception(
        "Can't find required bluetooth libraries"
        " (need to install bluez)"
    )
bluez = CDLL(btlib, use_errno=True)

dev_id = bluez.hci_get_route(None)

sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)
sock.bind((dev_id,))

err = bluez.hci_le_set_scan_parameters(sock.fileno(), 0, 0x10, 0x10, 0, 0, 1000);
if err < 0:
    raise Exception("Set scan parameters failed")
    # occurs when scanning is still enabled from previous call

# allows LE advertising events
hci_filter = struct.pack(
    "<IQH", 
    0x00000010, 
    0x4000000000000000, 
    0
)
sock.setsockopt(SOL_HCI, HCI_FILTER, hci_filter)

err = bluez.hci_le_set_scan_enable(
    sock.fileno(),
    1,  # 1 - turn on;  0 - turn off
    0, # 0-filtering disabled, 1-filter out duplicates
    1000  # timeout
)
if err < 0:
    errnum = get_errno()
    raise Exception("{} {}".format(
        errno.errorcode[errnum],
        os.strerror(errnum)
    ))

while True:
    data = sock.recv(1024)
    # print bluetooth address from LE Advert. packet
    print(':'.join("{0:02x}".format(x) for x in data[12:6:-1]))

I had to piece all of that together by looking at the hcitool and gatttool source code that comes with Bluez. The code is completely dependent on libbluetooth-dev so you'll have to make sure you have that installed first.

A better way would be to use dbus to make calls to bluetoothd, but I haven't had a chance to research that yet. Also, the dbus interface is limited in what you can do with a BLE connection after you make one.

EDIT:

Martin Tramšak pointed out that in Python 2 you need to change the last line to print(':'.join("{0:02x}".format(ord(x)) for x in data[12:6:-1]))

Tim Tisdall
  • 9,914
  • 3
  • 52
  • 82
  • Also, I am not trying to make a connection, only receive LE devices around me and find the RSSI and MAC address of those LE devices. So hopefully I will be able to figure it out.... thanks again. – user3582887 May 27 '14 at 18:16
  • k.. the RSSI value is in that packet... don't recall which bytes, though. – Tim Tisdall May 27 '14 at 19:27
  • Tim, I tried your code, forgive me, I am pretty new at this, do I need to edit anything? File "scanner.py", line 61, in print(':'.join("{0:02x}".format(x) for x in data[12:6:-1])) File "scanner.py" line 61, in print(':'.join("{0:02x}".format(x) for x in data[12:6:-1])) Valueerror: Unknown format code 'x' for object of type 'str' Thanks for any help. – user3582887 May 27 '14 at 20:17
  • I didn't specifically test it... did you get some sort of error? The code will only work the first time and then require a reset on the BT device as the scanning is left on. – Tim Tisdall May 27 '14 at 20:20
  • what version of python are you using? I just remembered I tested it in 3.4 – Tim Tisdall May 27 '14 at 20:22
  • I am using version 2.7 – user3582887 May 27 '14 at 20:23
  • you may have to modify it to work in python 2... I think there's a `hexify` method somewhere to output it as hex instead of using the `.format` method like I'm doing – Tim Tisdall May 27 '14 at 20:25
  • Thanks, it works fantastic. I am receiving the address with no problem. I edited the final line in the code to see if I could find the RSSI. Would the RSSI be in the data packet? There is a value that changes at the end of data, but it doesn't seem to correlate to signal strength. Or would I find the RSSI outside of the data packet somehow? – user3582887 May 28 '14 at 20:59
  • You really need to get the Bluetooth 4.1 specs and research the packet structure yourself if you want to go much further at such a low level... – Tim Tisdall Jun 02 '14 at 14:47
  • I'll give you this one, though... right after the BT address is one byte giving the length of the EIR data. After that is the EIR data and I think the first byte is typically the RSSI value which is the signal strength. I'm not sure if that's always the case, though. – Tim Tisdall Jun 02 '14 at 14:49
  • Thanks again Tim, please believe that I have tried to find all of this on my own, but I am new to both Python and bluetooth, I understand you have already given me a lot, but lines in your code like 37 through 42, I don't have a clue what that means or how you would know to write something like that. If you could point me to the documentation that I can use to learn about this, I will certainly go there and keep trying, or documentation how you found where to writ the code in the first place. Anyways... I appreciate everything you have helped me with. – user3582887 Jun 02 '14 at 15:33
  • it's no problem, I just can't answer _too_many_ questions... You can find the specs here: https://www.bluetooth.org/en-us/specification/adopted-specifications – Tim Tisdall Jun 02 '14 at 15:41
  • Looking at the specs again, I see I'm wrong about the RSSI... The RSSI comes _after_ the EIR data. It's the last byte of the packet. You can see the details of the advertising packet at 7.7.65.2 of the 4.1 spec (pg 1220 of the pdf) – Tim Tisdall Jun 02 '14 at 15:44
  • Great ! But I fell into the trap where it doesn't work the second time (as per your comment "occurs when scanning is still enabled from previous call") : how do I recover from this ? – monojohnny Dec 02 '15 at 00:25
  • hci_le_set_scan_enable takes an argument as to whether it should turn on scanning or shut it off. Just be sure to shut off scanning before closing the program. – Tim Tisdall Dec 02 '15 at 00:38
  • 2
    For python 2.7 change the last line to: print(':'.join("{0:02x}".format(ord(x)) for x in data[12:6:-1])) – Martin Tramšak Feb 26 '16 at 16:28
  • @Tim Tisdall Don't you mean the Measured Power? Measured Power is not RSSI. – dr.doom Jun 28 '16 at 20:28
  • @dr.doom it's RSSI. Measured Power is an ibeacon thing and this is the RSSI field in the BLE packet. – Tim Tisdall Jun 28 '16 at 21:13
  • 1
    Since the RSSI is sent as the last byte of the data packet, it's accessible like this: `print ord(data[-1])` – Mike Redrobe Nov 17 '16 at 14:20
7

You could also try pygattlib. It can be used to discover devices, and (currently) there is a basic support for reading/writing characteristics. No RSSI for now.

You could discover using the following snippet:

from gattlib import DiscoveryService

service = DiscoveryService("hci0")
devices = service.discover(2)

DiscoveryService accepts the name of the device, and the method discover accepts a timeout (in seconds) for waiting responses. devices is a dictionary, with BL address as keys, and names as values.

pygattlib is packaged for Debian (or Ubuntu), and also available as a pip package.

oscarah
  • 113
  • 1
  • 6