Thursday, 18 August 2016

Experimenting BTLE with Bluez sucks


I spent 1 week to get a simple GATT service and one characteristic from a Linux box. I tried RPI on ARM, and regular Ubuntu on x86.  No success. Always ending up in following error.

Mar 28 00:29:43 minibian bluetoothd[1417]: Failed to obtain handles for "Service Changed" characteristic

__FILE__ = mysensors.c, __FUNCTION__ = _registerDeviceInfo, __LINE__ =261
__FILE__ = mysensors.c, __FUNCTION__ = _registerSENZService, __LINE__ =185
Mar 28 00:29:43 minibian bluetoothd[1417]: Not enough free handles to register service

If you can pass this error I would like to know how to. Searching for an answer, It claims that the kernel has a glitch, but Bleno  a Node JS BTLE implementation works on the same hardware with no problems.

If your BTLE on Bluez works, and you don't run into above error the following post might be useful.
If guides how to change the Bluez code so you can write real BTLE GATT plug-ins as dynamic libraries (*.so) and not compiled into bluez as currently Bluez does.

How to add BTLE  (*.so) dynamically loaded plugins to bluetooth.


I add extra initialisation code to bluez files plugin.c/h  located under ./src. I created a structure with pointers to functions for all required calls, the plug-in calls back into blue-tooth sources.

I came with pointers-to-functions approach after I start adding files dependencies to my-plug-in Makefile.  I almost end up adding the whole Bluez tree into  myplug-in to link it. The plug-in size was as big as the Bluetoothd binary.  The following approach passes to plug-in library pointers to all the functions the library would call. This would get rid of linking into the plugin tones of code from Bluez.

Here are the changes I did.  If I miss something I hope you'll figure it out



plugin.h   added code

//mco-mco {

typedef struct btd_adapter_driver btd_adapter_driver;
typedef struct attribute attribute;
typedef void (*fptr_btd_unregister_adapter_driver)(struct btd_adapter_driver *driver);
typedef int (*fptr_btd_register_adapter_driver)(struct btd_adapter_driver *driver);
typedef int (*fptr_attrib_db_update)(struct btd_adapter *adapter, uint16_t handle, bt_uuid_t *uuid, const 

             uint8_t *value, size_t len, struct attribute **attr);
typedef gboolean (*fptr_gatt_service_add)(struct btd_adapter *adapter, uint16_t uuid, bt_uuid_t *svc_uuid, 

                 gatt_option opt1, ...);
typedef int (*fptr_adapter_set_name)(struct btd_adapter *adapter, const char *name);
typedef int (*fptr_bt_uuid16_create)(bt_uuid_t *btuuid, uint16_t value);
typedef struct bluetooth_lib_funcs
{
    fptr_btd_unregister_adapter_driver  buad;
    fptr_btd_register_adapter_driver    brad;
    fptr_attrib_db_update   adu;
    fptr_gatt_service_add   gsa;
    fptr_adapter_set_name   asn;
    fptr_bt_uuid16_create   buc;

}bluetooth_lib_funcs;


//}


plugin.c added code



// mco-mco after includes {
struct bluetooth_lib_funcs  _Pfuncs;
typedef void (*pre_plug_init)(bluetooth_lib_funcs* pff);

// }



plugin init


gboolean plugin_init(const char *enable, const char *disable)
{
    char fullplug[256];
    GSList *list;
    GDir *dir;
    const char *file;
    char **cli_disabled, **cli_enabled;
    unsigned int i;
    char _plugindir[256]="/usr/lib/bluetooth/plugins";


    char* penv = getenv("LE_PLUGS");
    if(penv)
    {
        strcpy(_plugindir, penv);
    }

    // mco-mco
    _Pfuncs.buad = btd_unregister_adapter_driver;
    _Pfuncs.brad = btd_register_adapter_driver;
    _Pfuncs.adu = attrib_db_update;
    _Pfuncs.gsa = gatt_service_add;
    _Pfuncs.asn = adapter_set_name;
    _Pfuncs.buc = bt_uuid16_create;



    /* Make a call to BtIO API so its symbols got resolved before the
     * plugins are loaded. */
    bt_io_error_quark();

    if (enable)
    {
        printf("enable passed in = %s  %s \n", enable, __FUNCTION__);
        cli_enabled = g_strsplit_set(enable, ", ", -1);
    }
    else{
        printf("%s cli enabled NULL \n", __FUNCTION__);

        cli_enabled = NULL;
    }

    if (disable)
    {
        printf("OK disable passed in = %s \n", enable);

        cli_disabled = g_strsplit_set(disable, ", ", -1);
    }
    else
    {
        printf("%s cli disable NULL \n", __FUNCTION__);

    cli_disabled = NULL;
    }

    printf("Loading builtin plugins\n");


    for (i = 0; __bluetooth_builtin[i]; i++) {
        if (!enable_plugin(__bluetooth_builtin[i]->name, cli_enabled,
                                cli_disabled))
        {
            printf("-ignoring bultin plugin %s\n", __bluetooth_builtin[i]->name);
            continue;
        }
        printf("+adding bultin plugin %s\n", __bluetooth_builtin[i]->name);
        add_plugin(NULL,  __bluetooth_builtin[i]);
    }


    printf("%s Loading plugins from folder: %s\n", __FILE__, _plugindir);

    dir = g_dir_open(_plugindir, 0, NULL);
    if (!dir)
        goto start;

    while ((file = g_dir_read_name(dir)) != NULL)
    {
        struct bluetooth_plugin_desc *desc;
        void *handle;
        char *filename;

        if (!strstr(file,".so"))
            continue;

        filename = g_build_filename(_plugindir, file, NULL);
        strcpy(fullplug,filename);
        printf("trying to load %s\n", filename);

        handle = dlopen(filename, RTLD_NOW | RTLD_DEEPBIND);
        if (handle == NULL)
        {
            printf("Can't load plugin %s: %s\n", filename,    dlerror());
            g_free(filename);
            exit(1);
            continue;
        }

        g_free(filename);

        //mco-mco {
        pre_plug_init   pfoos = (pre_plug_init)dlsym(handle, "PlugPreInit");
        if(pfoos == NULL)
        {

            printf("\nCan't load plugin preinit: %s, err: %s \n", "PlugPreInit", dlerror());
            dlclose(handle);
            exit(3);
        }

         printf("    passing address to functions %x \r\n", &_Pfuncs);
        (pfoos)(&_Pfuncs);


        printf(" ... continuing to load plugin %s \r\n", fullplug);

        // }
        desc = dlsym(handle, "bluetooth_plugin_desc");
        if (desc == NULL)
        {
            printf("\n... Can't load plugin description: %s from %s \n", dlerror(), fullplug);


            desc = dlsym(handle, "bluetooth_plugin_DSC");
            if(desc==NULL)
            {
                printf("\nCan't load plugin description: %s from %s \n", dlerror(), fullplug);
                dlclose(handle);
                continue;
            }
        }

        printf(" ... description passed %s \r\n", fullplug);


/*
        if (!enable_plugin(desc->name, cli_enabled, cli_disabled))
        {
            printf("? cannot enable %s from %s \n", desc->name, fullplug);
            dlclose(handle);
            continue;
        }
*/

        if (add_plugin(handle, desc) == FALSE)
        {
            printf("cannot add plugin %s\n", fullplug);
            dlclose(handle);
        }

        printf("%s added successfuly \n", fullplug);
    }

    printf(" DONE adding so plugins \n");
    g_dir_close(dir);

start:
    for (list = plugins; list; list = list->next)
    {
        struct bluetooth_plugin *plugin = list->data;
        int err;

        err = plugin->desc->init();
        if (err < 0)
        {
            if (err == -ENOSYS)
                printf("System does not support %s plugin",
                       plugin->desc->name);
            else
                printf("Failed to init %s plugin",
                       plugin->desc->name);
            continue;
        }

        printf("plugin %s initalised \n", plugin->desc->name);
        plugin->active = TRUE;
    }

    g_strfreev(cli_enabled);
    g_strfreev(cli_disabled);

    return TRUE;
}


Then in, myplugin.c file under /plugins replace all function calls which call back into  bluetooth[d] process, to functors.


/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2011-2012 David Herrmann <dh.herrmann@googlemail.com>
 *
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <glib.h>
#include <errno.h>

#include "lib/bluetooth.h"
#include "lib/sdp.h"
#include "lib/uuid.h"

#include "src/plugin.h"
#include "src/plugin_init.h"
#include "src/adapter.h"
#include "src/shared/util.h"
#include "src/log.h"
#include "attrib/gattrib.h"
#include "attrib/gatt-service.h"
#include "attrib/att.h"
#include "attrib/gatt.h"
#include "attrib/att-database.h"
#include "src/attrib-server.h"


#define MAX_STR_LEN       (256)

#define SIMPLE_SVC_UUID             0xF0C1
#define SIMPLE_READ1_CHAR_UUID      0xF0C2
#define SIMPLE_READ2_CHAR_UUID      0xF0C3
#define SIMPLE_WRITE_CHAR_UUID      0xF0C4
#define SIMPLE_NOTIFY_CHAR_UUID     0xF0F5



static struct bluetooth_lib_funcs  _PFS = {0,0,0,0,0};

#define BLUETOOTH_PLUGIN_DEFINE_SO(name, version, priority, init, exit) \
        extern struct btd_debug_desc __start___debug[] \
                __attribute__ ((weak, visibility("hidden"))); \
        extern struct btd_debug_desc __stop___debug[] \
                __attribute__ ((weak, visibility("hidden"))); \
        extern struct bluetooth_plugin_desc bluetooth_plugin_desc \
                __attribute__ ((visibility("default"))); \
        struct bluetooth_plugin_desc bluetooth_plugin_desc = { \
            #name, version, priority, init, exit, \
            __start___debug, __stop___debug \
        };

//}


static char read1Data[MAX_STR_LEN];
static char read2Data[MAX_STR_LEN];

static int notifyData;

static uint8_t _SENZCharacteristic1Read(struct attribute *a,
        struct btd_device *device, gpointer user_data)
{
    struct btd_adapter *adapter;

    adapter = user_data;

    (_PFS.adu)(adapter, a->handle, NULL,
                            (uint8_t*)&read1Data[0], strlen(&read1Data[0]), NULL);

    return 0;
}/*Characteristic1Read*/



static uint8_t _SENZCharacteristic2Read(struct attribute *a,
        struct btd_device *device, gpointer user_data)
{
    struct btd_adapter *adapter;
    adapter = user_data;

    (_PFS.adu)(adapter, a->handle, NULL,
                            (uint8_t*)&read2Data[0], strlen(&read2Data[0]), NULL);

    return 0;
}/*Characteristic2Read*/


static uint8_t _SENZCharacteristicWrite(struct attribute *a,
        struct btd_device *device, gpointer user_data)
{

    unsigned char data[MAX_STR_LEN];
    int i;


    memset(&data[0], 0, MAX_STR_LEN);
    memcpy(&data[0], a->data, a->len);

    printf("written data : %s \n", &data[0]);

    for(i = 0; i< a->len; i++)
        printf("%0x2x ", (unsigned char)(data[i]));
    printf("\n");

    return 0;
}/*CharacteristicWrite*/


static uint8_t _SENZCharacteristicNotify(struct attribute *a,
        struct btd_device *device, gpointer user_data)
{
    struct btd_adapter *adapter;

    adapter = user_data;

    do
    {
        (_PFS.adu)(adapter, a->handle, NULL,
                                (uint8_t*)&notifyData, sizeof(notifyData), NULL);
        notifyData++; // test data
    }
    while(0);

    return 0;
}/*CharacteristicNotify*/


static void _registerSENZService(struct btd_adapter *adapter)
{
    bt_uuid_t uuid;

    (_PFS.buc)(&uuid, SIMPLE_SVC_UUID);


    (_PFS.gsa)(adapter, GATT_PRIM_SVC_UUID, &uuid,

                     /* characteristic register*/

                     /*read 1*/
                     GATT_OPT_CHR_UUID16, SIMPLE_READ1_CHAR_UUID,
                     GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ ,
                     GATT_OPT_CHR_VALUE_CB, ATTRIB_READ,
                     _SENZCharacteristic1Read, adapter,

                     /*read 2*/
                     GATT_OPT_CHR_UUID16, SIMPLE_READ1_CHAR_UUID,
                     GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ ,
                     GATT_OPT_CHR_VALUE_CB, ATTRIB_READ,
                     _SENZCharacteristic2Read, adapter,

                     /*write*/
                     GATT_OPT_CHR_UUID16, SIMPLE_WRITE_CHAR_UUID,
                     GATT_OPT_CHR_PROPS, GATT_CHR_PROP_WRITE_WITHOUT_RESP,
                     GATT_OPT_CHR_VALUE_CB, ATTRIB_WRITE,
                     _SENZCharacteristicWrite, adapter,

                     /*NOTIFY*/
                     GATT_OPT_CHR_UUID16, SIMPLE_NOTIFY_CHAR_UUID,
                     GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ|GATT_CHR_PROP_NOTIFY,
                     GATT_OPT_CHR_VALUE_CB, ATTRIB_READ,
                     _SENZCharacteristicNotify, adapter,
                     /*end*/
                     GATT_OPT_INVALID);


    return ;
}/*_registerSENZService*/

#define DEVICEINFO_SVC_UUID     0x180a

char versionStr[MAX_STR_LEN] = "0.0.1";
char manufacturerStr[MAX_STR_LEN] = "SomeName";


static uint8_t _softwareRevisionStringRead(struct attribute *a,
        struct btd_device *device, gpointer user_data)
{
    struct btd_adapter *adapter;

    adapter = user_data;

    (_PFS.adu)(adapter, a->handle, NULL,
                            (uint8_t*)&versionStr[0], strlen(&versionStr[0]), NULL);


    return 0;
}/*_softwareRevisionStringRead*/


static uint8_t _manufacturerStringRead(struct attribute *a,
                                      struct btd_device *device, gpointer user_data)
{
    struct btd_adapter *adapter;

    adapter = user_data;

    (_PFS.adu)(adapter, a->handle, NULL,
                            (uint8_t*)&manufacturerStr[0], strlen(&manufacturerStr[0]), NULL);


    return 0;
}/*_manufacturerStringRead*/


static void _registerDeviceInfo(struct btd_adapter *adapter)
{
    bt_uuid_t uuid;

    (_PFS.buc)(&uuid, DEVICEINFO_SVC_UUID);

    (_PFS.gsa)(adapter, GATT_PRIM_SVC_UUID, &uuid,

                     /* characteristic register*/

                     /*GATT_CHARAC_SOFTWARE_REVISION_STRING*/
                     GATT_OPT_CHR_UUID16, GATT_CHARAC_SOFTWARE_REVISION_STRING,
                     GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ ,
                     GATT_OPT_CHR_VALUE_CB, ATTRIB_READ,
                     _softwareRevisionStringRead, adapter,

                     /*GATT_CHARAC_MANUFACTURER_NAME_STRING*/
                     GATT_OPT_CHR_UUID16, GATT_CHARAC_MANUFACTURER_NAME_STRING,
                     GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ ,
                     GATT_OPT_CHR_VALUE_CB, ATTRIB_READ,
                     _manufacturerStringRead, adapter,
                     /*end*/
                     GATT_OPT_INVALID);


    return ;
}/*_registerSENZService*/

static void _update_name(struct btd_adapter *adapter, gpointer user_data)
{
    (_PFS.asn)(adapter, (char*)user_data);
}/*_update_name*/



static int _SENZ_probe(struct btd_adapter *adapter)
{
    printf("_SENZ_probe( \n");

    _update_name(adapter, "MYBTNAME");
    _registerDeviceInfo(adapter);
    _registerSENZService(adapter);

    printf("SENZ probed \r\n");

    return 0;
}/*_SENZ_probe*/


static void _SENZ_remove(struct btd_adapter *adapter)
{

}/*_SENZ_remove*/

/*function pointers*/
static struct btd_adapter_driver SENZ_driver =
{
    .name = "SENZFLEXCI",
    .probe = _SENZ_probe,
    .remove = _SENZ_remove,
};


static int SENZ_init(void)
{
    printf("__FUNCTION__ = %s\n", __FUNCTION__);


    memset(&read1Data[0], 0, MAX_STR_LEN);
    memset(&read2Data[0], 0, MAX_STR_LEN);
    notifyData = 0;

    snprintf(&read1Data[0], MAX_STR_LEN, "it is read 1");
    snprintf(&read2Data[0], MAX_STR_LEN, "it is read 2");

    return (_PFS.brad)(&SENZ_driver);
}

static void SENZ_exit(void)
{
    (_PFS.buad)(&SENZ_driver);
}

BLUETOOTH_PLUGIN_DEFINE_SO(SENZFLEXCI, VERSION, BLUETOOTH_PLUGIN_PRIORITY_LOW, SENZ_init, SENZ_exit)


/*
   pointer to bluez functions
*/
void PlugPreInit(bluetooth_lib_funcs* pff)
{
    printf("preiniting the plugin getting bt functions pointer = %0x \r\n", pff);
    if(pff)
    {
        memcpy(&_PFS, pff, sizeof(_PFS));
        printf("done: some adresses %08x %08x... \r\n", _PFS.adu, _PFS.asn);
    }
}



And finally; the compiler bash  makemyplug.sh commands to make your *.so
This is for R-PI. For other systems adjust the glib & dbus paths.

compile it:  makemyplug.sh  myname



#!/bin/bash
gcc -DHAVE_CONFIG_H \
-I. \
-I./lib -I/usr/include/dbus-1.0 \
-I ./.. \
-I/usr/lib/arm-linux-gnueabihf/dbus-1.0/include \
-I/usr/include/glib-2.0 \
-I/usr/lib/arm-linux-gnueabihf/glib-2.0/include   \
-Wl,-z,defs -shared -fPIC \
-o lib$1.so $1.c

[[ ! -f "lib$1.so" ]] && echo "some errors !" && exit





sudo cp -fv ./*.so /usr/lib/bluetooth/plugins




All I achieved is that I can write plugins and put them in the plugin dolder, and they being loaded, but non functional. no success with the mess.



Shell commands to lunch bluetoothd and to load the plugins.

#!/bin/bashsudo service bluetoothd stop
sudo pkill bluetoothd
sudo pkill tail

sudo echo "" > /var/log/syslog
sudo tail -f /var/log/syslog &
sudo ../src/bluetoothd -E --compat -p -n &

sleep 1
sudo hciconfig hci0 down
sleep 1
btmgmt -i hci0 power off
btmgmt -i hci0 le on
btmgmt -i hci0 connectable on
btmgmt -i hci0 advertising on
btmgmt -i hci0 power on
btmgmt -i hci0 power off
echo "status --------------------"sudo hciconfig -a
sudo ps ax | grep bluet
read -p  "preas  key to end" -n1 -s
sudo pkill tail
sudo pkill bluetoothd
sudo hciconfig hci0 down
echo "done"



After all this faulty experiment,I wrote BUNGET library for Linux




No comments:

Post a Comment