In one of our projects we had to communicate with BLE (Bluetooth Low Energy) devices. After some early investigations we highlighted two issues:
We know GATT libraries exist in other languages (eg: pygattlib for python, bluepy) but impossible to find one in C!
GATT protocol did not seem to be supported by libbluetooth
:
$ mkdir /tmp/libbluetooth-dev && cd /tmp/libbluetooth-dev
$ wget http://es.archive.ubuntu.com/ubuntu/pool/main/b/bluez/libbluetooth-dev_4.101-0ubuntu13_amd64.deb
$ sudo dpkg -x libbluetooth-dev_4.101-0ubuntu13_amd64.deb
$ grep -r gatt ./usr
$
Our project being mainly built in C we were not really considering to rewrite it into another language to only use an existing GATT library.
The 'new' Bluez D-BUS API that enables any user space application to communicate with Bluetooth devices through D-BUS is only supported in the latest Bluez v5.x releases. The Bluez D-BUS API is still receiving many bug fixes - we recently contributed a fix for it.
Unfortunately, few LTS (Long Term Support) Linux distributions still use Bluez v4.x - including Ubuntu 14.04 LTS that only support Bluez v4.x. Ubuntu 14.04 LTS is still supported until 2019! Either we do not support a widely available Linux distribution (including my own development machine!) or we find the solution to our issue...
Funnily enough I discovered the day we started our investigation that the day after would have been released the new Ubuntu 16.04 LTS. So I could have potentially go to the beach for a day and upgrade my Ubuntu machine the day after with the new LTS and Bluez v5.x and its D-BUS API.
But looking at different discussion threads on Internet I realized that there was a real gap. We had the mission to fix it!
During my initial investigation, I found that the only way to use Bluez GATT support was to manually pickup the files used by BlueZ gatttool
and to call the GATT functions in the way gatttool
is doing.
Obviously as many Open Source project no documentation - the documentation is the source code...
After losing few hairs while trying to understand the almost non commented source code and thinking few times I would lose my time with this new project. I decided to start slowly and see if I could isolate the source files and build gatttool
into its own project directory.
The initial step was to retrieve the latest stable version of Bluez v4.x (ie: v4.101 - actually the same version used by Ubuntu 14.04).
From the Bluez Makefile.tools
, I could already see the required files and libraries used by gatttool
:
attrib_gatttool_SOURCES = attrib/gatttool.c attrib/att.c attrib/gatt.c \
attrib/gattrib.c btio/btio.c \
attrib/gatttool.h attrib/interactive.c \
attrib/utils.c src/log.c
attrib_gatttool_LDADD = lib/libbluetooth-private.la @GLIB_LIBS@ @READLINE_LIBS@
I also wanted to see the Bluez build command lines to avoid missing any build arguments.
Build gatttool
The following dependencies are required: automake
, libtool
, libdbus-1-dev
, libreadline-dev
git clone git://git.kernel.org/pub/scm/bluetooth/bluez.git
cd bluez
git checkout 4.101
./bootstrap-configure
make V=1 attrib/gatttool
Finally, ensure the application runs ./attrib/gatttool
Isolate gatttool in its own project and create a CMake file to build the new project.
We should have this following tree:
├── bluez
│ ├── attrib
│ │ ├── att.c
│ │ ├── att.h
│ │ ├── gatt.c
│ │ ├── gatt.h
│ │ ├── gattrib.c
│ │ ├── gattrib.h
│ │ ├── gatttool.c
│ │ ├── gatttool.h
│ │ ├── interactive.c
│ │ └── utils.c
│ ├── btio
│ │ ├── btio.c
│ │ └── btio.h
│ └── src
│ ├── log.c
│ └── log.h
└── CMakeLists.txt
If we build this project:
mkdir build && cd build
cmake .. && make
We have some expected linking issues because gatttool
depends on lib/libbluetooth-private.la
:
Linking C executable gatttool
/usr/bin/cmake -E cmake_link_script CMakeFiles/gatttool.dir/link.txt --verbose=1
/usr/bin/cc CMakeFiles/gatttool.dir/bluez/attrib/att.c.o CMakeFiles/gatttool.dir/bluez/attrib/gatt.c.o CMakeFiles/gatttool.dir/bluez/attrib/gattrib.c.o CMakeFiles/gatttool.dir/bluez/attrib/gatttool.c.o CMakeFiles/gatttool.dir/bluez/attrib/interactive.c.o CMakeFiles/gatttool.dir/bluez/attrib/utils.c.o CMakeFiles/gatttool.dir/bluez/btio/btio.c.o CMakeFiles/gatttool.dir/bluez/src/log.c.o -o gatttool -rdynamic -lglib-2.0
CMakeFiles/gatttool.dir/bluez/attrib/att.c.o: In function `att_get_uuid16':
att.c:(.text+0x1e5): undefined reference to `bt_uuid16_create'
CMakeFiles/gatttool.dir/bluez/attrib/att.c.o: In function `att_get_uuid128':
att.c:(.text+0x263): undefined reference to `bt_uuid128_create'
CMakeFiles/gatttool.dir/bluez/attrib/gatt.c.o: In function `sdp_get_service_classes':
gatt.c:(.text+0x8d): undefined reference to `sdp_get_uuidseq_attr'
CMakeFiles/gatttool.dir/bluez/attrib/gatt.c.o: In function `att_get_uuid16':
gatt.c:(.text+0x140): undefined reference to `bt_uuid16_create'
CMakeFiles/gatttool.dir/bluez/attrib/gatt.c.o: In function `att_get_uuid128':
gatt.c:(.text+0x1be): undefined reference to `bt_uuid128_create'
CMakeFiles/gatttool.dir/bluez/attrib/gatt.c.o: In function `encode_discover_primary':
gatt.c:(.text+0x2bc): undefined reference to `bt_uuid16_create'
CMakeFiles/gatttool.dir/bluez/attrib/gatt.c.o: In function `primary_all_cb':
lib/libbluetooth-private.la
uses lib/bluetooth.c
, lib/hci.c
, lib/sdp.c
, lib/uuid.c
(see Bluez Makefile.am
). Let's add these files to the project.
After adding this file, I managed to build gatttool
. The next step was to separate gatttool
from the GATT code to isolate what was gatttool
specific and what could be used as the core of the GATT library.
The gatttool
specific files gatttool.c
, gatttool.h
, interactive.c
and utils.c
would live in their own directory examples/gatttool
.
Thanks to CMake, I quickly had my first GATT shared library and gatttool
application binary.
The next challenge was to provide a new API that would make the use of GattLib really easy.
The main issue I had during this phase was to understand how gatttool
was communicating with Bluez stack.
I finally found all the interactions were made through GLib IO Channel. GLib IO Channel is a wrapper on top of files, pipes and sockets - Unix Domain Socket are used by Bluez. The IO Channel only works when a GLib Main Event Loop is running.
As I did not want to force the GattLib users to have to add a GLib Main Event loop to their project I added a thread to GattLib where the GattLib specific GLib Main Loop would run from.
Bluez GATT API is all about asynchronous callbacks. I wanted my GattLib API to keep this asynchronous feature but also to support synchronous functions.
After spending a couple of days on this new library, I ended up with a satisfactory result that was good enough for my project.
I wrote one of my project BLE use cases with my new GattLib library and the result was a code clear to read.
I also wrote a couple of examples to help users to use this new library.
After doing a bit of CMake
and its magic CPack
, I could quickly package a first release of GattLib
as a Debian/Ubuntu/... DEB package, a RPM package and a ZIP package.
DEB Package:
$ dpkg-deb -I gattlib-0.1.deb
new debian package, version 2.0.
size 81510 bytes: control archive=440 bytes.
258 bytes, 10 lines control
175 bytes, 3 lines md5sums
Package: gattlib
Version: 0.1
Section: devel
Priority: optional
Architecture: amd64
Depends: libglib2.0-0
Installed-Size: 227
Maintainer: Olivier Martin <olivier@labapart.com>
Description: Library to access GATT information from Bluetooth Low Power devices
$ dpkg-deb -c gattlib-0.1.deb
drwxrwxr-x root/root 0 2016-04-28 13:05 ./usr/
drwxrwxr-x root/root 0 2016-04-28 13:05 ./usr/lib/
drwxrwxr-x root/root 0 2016-04-28 13:05 ./usr/lib/pkgconfig/
-rw-r--r-- root/root 269 2016-04-28 13:04 ./usr/lib/pkgconfig/gattlib.pc
-rw-r--r-- root/root 228334 2016-04-28 13:05 ./usr/lib/libgattlib.so
drwxrwxr-x root/root 0 2016-04-28 13:05 ./usr/include/
-rw-r--r-- root/root 3421 2016-04-28 00:47 ./usr/include/gattlib.h
RPM Package:
$ rpm -qpl gattlib-0.1.rpm
/usr/include/gattlib.h
/usr/lib/libgattlib.so
/usr/lib/pkgconfig
/usr/lib/pkgconfig/gattlib.pc
ZIP Package:
$ unzip -l gattlib-0.1.zip
Archive: gattlib-0.1.zip
Length Date Time Name
--------- ---------- ----- ----
269 2016-04-28 13:04 gattlib-0.1/lib/pkgconfig/gattlib.pc
228334 2016-04-28 13:05 gattlib-0.1/lib/libgattlib.so
3421 2016-04-28 00:47 gattlib-0.1/include/gattlib.h
--------- -------
232024 3 files
And finally a couple of days after starting the project, I can make it alive on Github: https://github.com/labapart/gattlib!