Once we setting bluetooth low energy(BLE) advertising, we would want to setting this beacon being connectable. The clients (like phone, computer) could read/upload data to/from that.
That applying is very wide: for example, the all-day heartbeat measurer in current, the sensors and recorder are connected physicallly as one device. The patient will feel it is not comfortable when he carry it (Just like carry urine bag but smaller) . If the manufacturer of heartbeat measurer introduce BLE technology, the recorder could be indenpendent device.
The connection between recorder and sensors is wireless. It is not necessary to carry recorder like shoulder bag all day, it could be put in desk.
Why not apply classical bluetooth for that goal ? For classical bluetooth spends too much energy, mercury battery could not afford such costing.
Ok, for our major issue: setting gatt service in linux.
In my environment, I use fedora 21 + bluez 5.
If dbus version of your linux distribution is lower than 1.6, you should use bluez 4.
DO NOT force upgrading your dbus version to 1.6, unless you know what you do.
The gatt service warm-up be the follow steps:
0. Download and untar the bluez package from bluez
(Use tar Jxvf XXX.tar.xz to uncompress file)
1. Build bluez on your computer.
For me, the configure parameters of bluez are:
./configure CFLAGS="$CFLAGS -Wno-sign-compare" --disable-udev --prefix=$PWD/built --enable-experimental --enable-maintainer-mode
The --enable-experimental and --enable-maintainer-mode set gatt-service example being included into compilation. and --disable-udev is for avoiding configuration error(and the udev module is useless for me). CFLAGS="$CFLAGS -Wno-sign-compare" set compiler to ignore sign/unsigned warning (the Makefile of Bluez set compiler to treat warning as error!).
2. Run the bluetoothd program, which is under src folder of bluez
The bluetoothd is a daemon program to manage all bluetooth device and (maybe all) service.
Once we have built this bluez, we could run the sbluetoothd as test.
But before the running, we should stop the one bluetoothd which has been run:
sudo killall -9 bluetoothd
we could read the help of bluetoothd.
[gaiger@localhost src]$ ./bluetoothd -h
Usage:
bluetoothd [OPTION...]
Help Options:
-h, --help Show help options
Application Options:
-d, --debug=DEBUG Specify debug options to enable
-p, --plugin=NAME,.., Specify plugins to load
-P, --noplugin=NAME,... Specify plugins not to load
-C, --compat Provide deprecated command line interfaces
-E, --experimental Enable experimental interfaces
-n, --nodetach Run with logging in foreground
-v, --version Show version information and exit
I note here : the -d argument would make the output be tedeious, and the -n argument would make the programming running on the front-end.
What is plugin? those are gatt service (example) which have been included in bluez.
Check the files under profiles and plugin, we could watch what service bluez have done:
[gaiger@localhost src]$ ls ../profiles/
alert cyclingspeed health input sap time
audio deviceinfo heartrate network scanparam
cups gap iap proximity thermometer
[gaiger@localhost src]$ ls ../plugins/*.c
../plugins/autopair.c ../plugins/hostname.c ../plugins/sixaxis.c
../plugins/external-dummy.c ../plugins/neard.c ../plugins/wiimote.c
../plugins/gatt-example.c ../plugins/policy.c
Now we try to enable the timer service in bluetoothd:
[gaiger@localhost src]$ sudo ./bluetoothd --plugin=time -n
bluetoothd[8511]: Bluetooth daemon 5.27
bluetoothd[8511]: Starting SDP server
bluetoothd[8511]: Ignoring (cli) hostname
bluetoothd[8511]: Ignoring (cli) wiimote
bluetoothd[8511]: Ignoring (cli) autopair
bluetoothd[8511]: Ignoring (cli) policy
bluetoothd[8511]: Ignoring (cli) gatt_example
bluetoothd[8511]: Ignoring (cli) neard
bluetoothd[8511]: Ignoring (cli) sap
bluetoothd[8511]: Ignoring (cli) a2dp
bluetoothd[8511]: Ignoring (cli) avrcp
bluetoothd[8511]: Ignoring (cli) network
bluetoothd[8511]: Ignoring (cli) input
bluetoothd[8511]: Ignoring (cli) hog
bluetoothd[8511]: Ignoring (cli) health
bluetoothd[8511]: Ignoring (cli) gap
bluetoothd[8511]: Ignoring (cli) scanparam
bluetoothd[8511]: Ignoring (cli) deviceinfo
bluetoothd[8511]: Ignoring (cli) alert
bluetoothd[8511]: Ignoring (cli) proximity
bluetoothd[8511]: Ignoring (cli) thermometer
bluetoothd[8511]: Ignoring (cli) heartrate
bluetoothd[8511]: Ignoring (cli) cyclingspeed
bluetoothd[8511]: Ignoring (cli) external_dummy
bluetoothd[8511]: Bluetooth management interface 1.7 initialized
Then run the script I have provided in
the last article, we could use BLE scanner (android's app) to detect this service.
Now we know that is a good example for us to set a gatt service.
Take look at the time service code, in profiles/time/server.c (buttom):
struct btd_profile time_profile = {
.name = "gatt-time-server",
.adapter_probe = time_server_init,
.adapter_remove = time_server_exit,
};
static int time_init(void)
{
return btd_profile_register(&time_profile);
}
static void time_exit(void)
{
btd_profile_unregister(&time_profile);
}
BLUETOOTH_PLUGIN_DEFINE(time, VERSION,
BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
time_init, time_exit)
The declaration of macro BLUETOOTH_PLUGIN_DEFINE be
#define BLUETOOTH_PLUGIN_DEFINE(name, version, priority, init, exit)
(in src/plugin.h). it is , to define what function is for initialization(entrance), what is for exit. As this example, the entrance and exit are just for setting/un-setting structure btd_profile, the structure are for customizing gatt service distinctly.
The structure btd_profile has 3 member, are name, service_init and service_exit, respectively.
Now look at the function time_server_init , time_server_exit and relative functions in the same file:
#define CURRENT_TIME_SVC_UUID 0x1805
#define REF_TIME_UPDATE_SVC_UUID 0x1806
#define LOCAL_TIME_INFO_CHR_UUID 0x2A0F
#define TIME_UPDATE_CTRL_CHR_UUID 0x2A16
#define TIME_UPDATE_STAT_CHR_UUID 0x2A17
#define CT_TIME_CHR_UUID 0x2A2B
static uint8_t current_time_read(struct attribute *a,
struct btd_device *device, gpointer user_data)
{
struct btd_adapter *adapter = user_data;
uint8_t value[10];
if (encode_current_time(value) < 0)
return ATT_ECODE_IO;
attrib_db_update(adapter, a->handle, NULL, value, sizeof(value), NULL);
return 0;
}
static uint8_t local_time_info_read(struct attribute *a,
struct btd_device *device, gpointer user_data)
{
struct btd_adapter *adapter = user_data;
uint8_t value[2];
DBG("a=%p", a);
tzset();
/* Convert POSIX "timezone" (seconds West of GMT) to Time Profile
* format (offset from UTC in number of 15 minutes increments). */
value[0] = (uint8_t) (-1 * timezone / (60 * 15));
/* FIXME: POSIX "daylight" variable only indicates whether there
* is DST for the local time or not. The offset is unknown. */
value[1] = daylight ? 0xff : 0x00;
attrib_db_update(adapter, a->handle, NULL, value, sizeof(value), NULL);
return 0;
}
static gboolean register_current_time_service(struct btd_adapter *adapter)
{
bt_uuid_t uuid;
bt_uuid16_create(&uuid, CURRENT_TIME_SVC_UUID);
/* Current Time service */
return gatt_service_add(adapter, GATT_PRIM_SVC_UUID, &uuid,
/* CT Time characteristic */
GATT_OPT_CHR_UUID16, CT_TIME_CHR_UUID,
GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ |
GATT_CHR_PROP_NOTIFY,
GATT_OPT_CHR_VALUE_CB, ATTRIB_READ,
current_time_read, adapter,
/* Local Time Information characteristic */
GATT_OPT_CHR_UUID16, LOCAL_TIME_INFO_CHR_UUID,
GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ,
GATT_OPT_CHR_VALUE_CB, ATTRIB_READ,
local_time_info_read, adapter,
GATT_OPT_INVALID);
}
static int time_server_init(struct btd_profile *p, struct btd_adapter *adapter)
{
const char *path = adapter_get_path(adapter);
DBG("path %s", path);
if (!register_current_time_service(adapter)) {
error("Current Time Service could not be registered");
return -EIO;
}
if (!register_ref_time_update_service(adapter)) {
error("Reference Time Update Service could not be registered");
return -EIO;
}
return 0;
}
static void time_server_exit(struct btd_profile *p,
struct btd_adapter *adapter)
{
const char *path = adapter_get_path(adapter);
DBG("path %s", path);
}
That is very obvious:
The function time_server_init register 2 service : register_current_time_service and register_ref_time_update_service. In the function register_current_time_service, the code call the function bt_uuid16_create with uuid REF_TIME_UPDATE_SVC_UUID to create a service, and add 2 characteristic CT_TIME_CHR_UUID and LOCAL_TIME_INFO_CHR_UUID by calling function gatt_service_add which be added function local_time_info_read and current_time_read. In these pointed function, the data be sent by calling function attrib_db_update.
bt_uuid16_create : create service.
gatt_service_add: set characteristic and set function pointer corresponding the characteristic.
attrib_db_update: sent data to client.
The functions are declared in src/plugin.h and attrib/gatt-service.h:
Note, gatt_service_add:
gboolean gatt_service_add(struct btd_adapter *adapter, uint16_t uuid,
bt_uuid_t *svc_uuid, gatt_option opt1, ...);
It is an uncertain parameter function, we could register several characteristic in formal of :
gatt_service_add(adapter, GATT_PRIM_SVC_UUID, &uuid,
/*first characteristic */
GATT_OPT_CHR_UUID16, FRIST_CHR_UUID,
GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ |
GATT_CHR_PROP_NOTIFY,
GATT_OPT_CHR_VALUE_CB, ATTRIB_READ,
first_pointer_read, adapter,
/* 2nd characteristic */
GATT_OPT_CHR_UUID16, SECOND_CHR_UUID,
GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ,
GATT_OPT_CHR_VALUE_CB, ATTRIB_READ,
second_pointer_read, adapter,
/* 3rd characteristic */
GATT_OPT_CHR_UUID16, THIRD_CHR_UUID,
GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ,
GATT_OPT_CHR_VALUE_CB, ATTRIB_READ,
third_pointer_read, adapter,
:
GATT_OPT_INVALID);
You could refer to the other gatt service example, I recommend plugins/gatt-example.c.
If you want to customize your gatt service, It is complicated to create a whole new one but without changing original example: it is hard to modify Makefile files! Just backup and modify the plugins/wiimote.c file, It maybe be the most useless one for being refered.
NOTE : If you want to know more detailedly about how to implement or modify a gatt-server ( peripheral) as yours, please read the
article written by me, which is the sequel of this one.