Pythonic interface to Bluetooth Low Energy on Linux+bluez.
I was not content with the API offered by the existing projects, so I added this layer on top.
$ python
>>> import ble
>>> dev = ble.discover_device(lambda d: ble.uuids.heart_rate in d.uuids)
>>> dev.connect()
>>> dev.battery_service.battery_level.value
[100]
>>> dev.heart_rate_service.heart_rate_measurement.notifying=True
>>> print dev.heart_rate_service.heart_rate_measurement.value
{'hr': 97}
There are two interfaces to bluez that can be used. They are selected by editing the "ble.py" file.
-
bluepy
https://github.com/IanHarvey/bluepy
bluepy uses the hci socket interface to bluez. This works well but it is deprecated and requires root access. bluepy uses a compiled helper application for this. To run bluepy as a non-root user, you can set that binary to be suid-root. I've included the script
setup_helper.sh
to accomplish this. Note that this is bad security practice. Don't make an installer that does this. -
dbus
The other backend is shiny-new and crashes occasionally. Recent versions of bluez contain "GattService1" interfaces on the d-bus. So Python can directly control bluetooth without any shims or helper applications. To use this backend, download and compile bluez from the git repository. Then run bluetoothd with the experimental flag and plugins disabled.
My Ubuntu uses an ancient version of bluez, so I just build a git pull.
For Ubuntu 14.04, something like this works:
$ git clone https://git.kernel.org/pub/scm/bluetooth/bluez.git $ cd bluez $ ./bootstrap $ ./configure --disable-systemd --enable-experimental $ make $ sudo -s # "sudo service bluetooth stop" doesn't work # service bluetooth stop # src/bluetoothd -p x -n -E # no plugins, no detach, experimental enabled
Enable the dbus backend by editing
ble.py
. Maybe this will be runtime configurable in the future.
Services and characteristics are presented generically, but can be
extended by adding a profile definition in profiles/
. Any Python
file in there is assumed to be profile definitions and will be loaded
automatically when the associated UUID is encountered. Each service
should be a class derived from ble.Service and each characteristic
should be a class derived from ble.Characteristic. Any service or
characteristic should have either a uuid
class variable (to use an
existing UUID) or a uuid_def
class variable (to name a new
UUID). See cps_service.py
and dfu_service.py
respectively for
examples of the two declarations.
Bluetooth SIG defined UUID values are already named using the
generated file uuids.json
.
A characteristic is read and written using the property .value
for
reading and writing profile-interpreted data or .raw
for reading and
writing raw bytestrings. To receive notifications, set .notify
to
true. With notifications active, each read of .raw.
or .value
will present a new notificaion value. (The property .notify_timeout
can be used to adjust the length of time for waiting for new values.)
There are two examples included in the distribution.
hr_client.py
connects to a heart rate monitor, prints ten heart rate readings, then attemps to reset the energy integrator.
cps_client.py
connects to a power meter, prints a single power
notification, then does a calibration.
example_dfu.py
connects to a Nordic Semiconductor nRF5x device with
the Nordic DFU installed, then updates the application from the zipped
firmware image prepared with nrfutil
. This is a more complicated
example, but the protocol is well-documented on the Nordic devzone
website.
uuids.json is generated by the script https://github.com/IanHarvey/bluepy/blob/master/bluepy/get_services.py
example_dfu.py
reliably crashes git bluetoothd on my computer.
Using properties for non-idempotent things like reading successive notification feels like a weird API, but it's the closest mapping to the GATT API. But suggestions for a cleaner API are welcome.