The birth of GattLib - Solving BLE GATT access on Long Term supported Linux distributions by Olivier on 28/04/2016 14:03

Problematic

In one of our projects we had to communicate with BLE (Bluetooth Low Energy) devices. After some early investigations we highlighted two issues:

C GATT Library

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.

Bluez v4.x Support

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!

Investigation

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.

  1. 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).

  2. 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.

  3. 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

Create GattLib library

  1. 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
    
  2. 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.

  3. 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.

  4. 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
      
  5. And finally a couple of days after starting the project, I can make it alive on Github: https://github.com/labapart/gattlib!

mweal-ed on 17/05/2016 12:49
Nice job, I wounder how many people have done this on there own to work with gatt (I know I did basically the same thing)
Anonymous on 24/08/2016 18:23
Hi:
I am sorry to comment as anonimous, I couldn't register in your site.
I try your gatt-lib (great job!!!)
its working but when I modified the read/write example to have notification like this:
-------------------------------
void notification_cb(uint16_t handle, const uint8_t* data, size_t data_length, void* user_data) {
printf("Notification on handle 0x%02x : ", handle);
}
main()
.......
connection = gattlib_connect(NULL, argv[1], BDADDR_LE_PUBLIC, BT_IO_SEC_LOW, 0, 0);
if (connection == NULL) {
fprintf(stderr, "Fail to connect to the bluetooth device %s.\n", argv[1]); //, CONNECTION_TIMEOUT);
return 1;
}
printf("Conectado a %s\n", argv[1]);
if (g_operation == READ) {
......
} else {
gattlib_register_notification(connection, notification_cb, NULL);
uint16_t handle = 0x0F; //TODO: FIXME -------modify to may own handle it's working
ret = gattlib_write_char_by_handle(connection, handle, &value_data, 1); //handle, buffer, sizeof(buffer));
//assert(ret == 0); -------commented
}
while(1) // to wait for the notification
;

gattlib_disconnect(connection);
return 0;
}
--------------------------------
I have the connection ok, the write at handle 0x0F ok on the slave side and the slave send the notificacions but nothing happend in the read_write example.
I am using a Pi 3, with bluez 5.41 installed
Can you help with this?
Thanks in advance.
jcuriel@tecpro.mx
Anonymous on 19/01/2017 05:20
Thank you for writing the library, it's really helpful.

To the person who posted the question above or to whoever who face this problem,
I also encountered the same problem but I am able to solve it. Hope this will be helpful.

This is my code:
To enable the notification, I write 0100 to the notif handler and to enable sensor 0001 to the sensor handler.
uint8_t notif_handle,sensor_handle;

while(1){
gattlib_write_char_by_handle(connection, sensor_handle,&value_data,sizeof(value_data)); //enable sensor
bt_string_to_uuid(&g_uuid, "f000aa01-0451-4000-b000-000000000000"); //this depends on the UUID you are reading from
len = gattlib_read_char_by_uuid(connection, &g_uuid, buffer, sizeof(buffer));

printf("Read UUID completed: ");
for (i = 0; i < len; i++)
printf("%02x ", buffer[i]);
printf("\n");

gattlib_write_char_by_handle(connection, notif_handle, &enable_notification, sizeof(enable_notification));//enable notif
gattlib_register_notification(connection, notification_cb, NULL); //register notif
}

And the rest are the same as yours. Thank you.

Login to comment Comment as anonymous