Created initial gattlib support

pull/5/head
Olivier Martin 2016-04-23 17:26:11 +01:00
parent cc27526fdc
commit fc58fc0307
10 changed files with 857 additions and 27 deletions

View File

@ -22,10 +22,13 @@
cmake_minimum_required(VERSION 2.6)
project(gattlib)
find_package(PkgConfig REQUIRED)
include_directories(bluez/attrib bluez/btio bluez/src)
include_directories(include bluez/attrib bluez/btio bluez/src)
# Bluez specific files
set(gattlib_SRCS bluez/attrib/att.c
bluez/attrib/gatt.c
bluez/attrib/gattrib.c
@ -38,14 +41,48 @@ set(gattlib_SRCS bluez/attrib/att.c
add_definitions(-DVERSION="4.101")
# Gattlib files
list(APPEND gattlib_SRCS src/gattlib_connect.c
src/gattlib_discover.c
src/gattlib_read_write.c)
# Added Glib support
pkg_search_module(GLIB REQUIRED glib-2.0)
include_directories(${GLIB_INCLUDE_DIRS})
list(APPEND gattlib_LIBS ${GLIB_LIBRARIES})
# gattlib
add_library(gattlib ${gattlib_SRCS})
add_library(gattlib SHARED ${gattlib_SRCS})
target_link_libraries(gattlib ${gattlib_LIBS})
# Examples
add_subdirectory(examples/gatttool)
#
# Packaging
#
set(CPACK_PACKAGE_INSTALL_DIRECTORY /usr CACHE STRING "Install directory (default: /usr).")
set(CPACK_PACKAGE_VERSION 0.1)
set(CPACK_PACKAGE_CONTACT "Olivier Martin <olivier@labapart.com>")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Library to access GATT information from a Bluetooth device.")
set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${CPACK_PACKAGE_VERSION}")
#
# Debian package
#
set(CPACK_GENERATOR "DEB;RPM;ZIP")
# Detect platform architecture to use it for the Debian package
execute_process(COMMAND dpkg --print-architecture OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE OUTPUT_QUIET)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libglib2.0-0")
#
# List of file to install
#
configure_file(gattlib.pc.in gattlib.pc @ONLY)
install(TARGETS gattlib LIBRARY DESTINATION lib)
install(FILES include/gattlib.h DESTINATION include)
install(FILES ${PROJECT_BINARY_DIR}/gattlib.pc DESTINATION lib/pkgconfig)
include(CPack)

View File

@ -35,6 +35,7 @@
#include "att.h"
#include "btio.h"
#include "gattrib.h"
#include "gattlib_internal.h"
#define GATT_TIMEOUT 30
@ -43,9 +44,9 @@ struct _GAttrib {
gint refs;
uint8_t *buf;
int buflen;
guint read_watch;
guint write_watch;
guint timeout_watch;
GSource* read_watch;
GSource* write_watch;
GSource* timeout_watch;
GQueue *requests;
GQueue *responses;
GSList *events;
@ -193,13 +194,13 @@ static void attrib_destroy(GAttrib *attrib)
attrib->events = NULL;
if (attrib->timeout_watch > 0)
g_source_remove(attrib->timeout_watch);
g_source_destroy(attrib->timeout_watch);
if (attrib->write_watch > 0)
g_source_remove(attrib->write_watch);
g_source_destroy(attrib->write_watch);
if (attrib->read_watch > 0)
g_source_remove(attrib->read_watch);
g_source_destroy(attrib->read_watch);
if (attrib->io)
g_io_channel_unref(attrib->io);
@ -325,9 +326,9 @@ static gboolean can_write_data(GIOChannel *io, GIOCondition cond,
cmd->sent = TRUE;
if (attrib->timeout_watch == 0)
attrib->timeout_watch = g_timeout_add_seconds(GATT_TIMEOUT,
disconnect_timeout, attrib);
if (attrib->timeout_watch == 0) {
attrib->timeout_watch = gattlib_timeout_add_seconds(GATT_TIMEOUT, disconnect_timeout, attrib);
}
return FALSE;
}
@ -346,8 +347,10 @@ static void wake_up_sender(struct _GAttrib *attrib)
return;
attrib = g_attrib_ref(attrib);
attrib->write_watch = g_io_add_watch_full(attrib->io,
G_PRIORITY_DEFAULT, G_IO_OUT,
// Tell the main loop to write the command with `can_write_data` when
// write can be done without blocking (G_IO_OUT).
attrib->write_watch = gattlib_watch_connection_full(attrib->io,
G_IO_OUT,
can_write_data, attrib, destroy_sender);
}
@ -392,7 +395,7 @@ static gboolean received_data(GIOChannel *io, GIOCondition cond, gpointer data)
return TRUE;
if (attrib->timeout_watch > 0) {
g_source_remove(attrib->timeout_watch);
g_source_destroy(attrib->timeout_watch);
attrib->timeout_watch = 0;
}
@ -468,9 +471,9 @@ GAttrib *g_attrib_new(GIOChannel *io)
attrib->requests = g_queue_new();
attrib->responses = g_queue_new();
attrib->read_watch = g_io_add_watch(attrib->io,
attrib->read_watch = gattlib_watch_connection_full(attrib->io,
G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
received_data, attrib);
received_data, attrib, NULL);
return g_attrib_ref(attrib);
}

View File

@ -31,11 +31,12 @@ extern "C" {
#define GATTRIB_ALL_EVENTS 0xFF
#define GATTRIB_ALL_REQS 0xFE
#include "gattlib.h"
struct _GAttrib;
typedef struct _GAttrib GAttrib;
typedef void (*GAttribResultFunc) (guint8 status, const guint8 *pdu,
guint16 len, gpointer user_data);
guint16 len, gpointer user_data);
typedef void (*GAttribDisconnectFunc)(gpointer user_data);
typedef void (*GAttribDebugFunc)(const char *str, gpointer user_data);
typedef void (*GAttribNotifyFunc)(const guint8 *pdu, guint16 len,

View File

@ -37,6 +37,7 @@
#include <glib.h>
#include "btio.h"
#include "gattlib_internal.h"
#ifndef BT_FLUSHABLE
#define BT_FLUSHABLE 8
@ -70,6 +71,7 @@ struct connect {
BtIOConnect connect;
gpointer user_data;
GDestroyNotify destroy;
GSource* source;
};
struct accept {
@ -94,8 +96,10 @@ static void server_remove(struct server *server)
static void connect_remove(struct connect *conn)
{
if (conn->destroy)
g_source_destroy(conn->source);
if (conn->destroy) {
conn->destroy(conn->user_data);
}
g_free(conn);
}
@ -238,7 +242,7 @@ static void connect_add(GIOChannel *io, BtIOConnect connect,
conn->destroy = destroy;
cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, connect_cb, conn,
conn->source = gattlib_watch_connection_full(io, cond, connect_cb, conn,
(GDestroyNotify) connect_remove);
}

View File

@ -25,6 +25,7 @@
#define BT_IO_H
#include <glib.h>
#include "gattlib.h"
typedef enum {
BT_IO_ERROR_DISCONNECTED,
@ -71,13 +72,6 @@ typedef enum {
BT_IO_OPT_PRIORITY,
} BtIOOption;
typedef enum {
BT_IO_SEC_SDP = 0,
BT_IO_SEC_LOW,
BT_IO_SEC_MEDIUM,
BT_IO_SEC_HIGH,
} BtIOSecLevel;
typedef enum {
BT_IO_MODE_BASIC = 0,
BT_IO_MODE_RETRANS,

97
include/gattlib.h Normal file
View File

@ -0,0 +1,97 @@
/*
*
* GattLib - GATT Library
*
* Copyright (C) 2016 Olivier Martin <olivier@labapart.org>
*
*
* 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
*
*/
#ifndef __GATTLIB_H__
#define __GATTLIB_H__
#include <glib.h>
#include <stdint.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/sdp.h>
#include <bluetooth/uuid.h>
typedef enum {
BT_IO_SEC_SDP = 0,
BT_IO_SEC_LOW,
BT_IO_SEC_MEDIUM,
BT_IO_SEC_HIGH,
} BtIOSecLevel;
typedef struct _GAttrib GAttrib;
typedef struct _gatt_connection_t {
GIOChannel *io;
GAttrib *attrib;
} gatt_connection_t;
typedef void (*gatt_connect_cb_t)(gatt_connection_t* connection);
typedef void (*gatt_cb_t) (GSList *l, guint8 status, gpointer user_data);
typedef void* (*gatt_read_cb_t)(void* buffer, size_t buffer_len);
/**
* @param src Local Adaptater interface
* @param dst Remote Bluetooth address
* @param dst_type Set LE address type (either BDADDR_LE_PUBLIC or BDADDR_LE_RANDOM)
* @param sec_level Set security level (either BT_IO_SEC_LOW, BT_IO_SEC_MEDIUM, BT_IO_SEC_HIGH)
* @param psm Specify the PSM for GATT/ATT over BR/EDR
* @param mtu Specify the MTU size
*/
gatt_connection_t *gattlib_connect(const gchar *src, const gchar *dst,
uint8_t dest_type, BtIOSecLevel sec_level, int psm, int mtu);
gatt_connection_t *gattlib_connect_async(const gchar *src, const gchar *dst,
uint8_t dest_type, BtIOSecLevel sec_level, int psm, int mtu,
gatt_connect_cb_t connect_cb);
int gattlib_disconnect(gatt_connection_t* connection);
typedef struct {
uint16_t attr_handle_start;
uint16_t attr_handle_end;
bt_uuid_t uuid;
} gattlib_primary_service_t;
typedef struct {
uint8_t properties;
uint16_t value_handle;
bt_uuid_t uuid;
} gattlib_characteristic_t;
int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_service_t** services, int* services_count);
int gattlib_discover_char(gatt_connection_t* connection, gattlib_characteristic_t** characteristics, int* characteristic_count);
int gattlib_read_char_by_uuid(gatt_connection_t* connection, bt_uuid_t* uuid, void* buffer, size_t buffer_len);
int gattlib_read_char_by_uuid_async(gatt_connection_t* connection, bt_uuid_t* uuid, gatt_read_cb_t gatt_read_cb);
int gattlib_write_char_by_handle(gatt_connection_t* connection, uint16_t handle, void* buffer, size_t buffer_len);
typedef void (*gattlib_event_handler_t)(uint16_t handle, const uint8_t* data, size_t data_length);
void gattlib_register_notification(gattlib_event_handler_t notification_handler);
void gattlib_register_indication(gattlib_event_handler_t indication_handler);
int gattlib_uuid_to_string(const bt_uuid_t *uuid, char *str, size_t n);
int gattlib_string_to_uuid(bt_uuid_t *uuid, const char *str);
#endif

View File

@ -0,0 +1,45 @@
/*
*
* GattLib - GATT Library
*
* Copyright (C) 2016 Olivier Martin <olivier@labapart.org>
*
*
* 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
*
*/
#ifndef __GATTLIB_INTERNAL_H__
#define __GATTLIB_INTERNAL_H__
#include "gattlib.h"
struct gattlib_thread_t {
int ref;
pthread_t thread;
GMainContext* loop_context;
GMainLoop* loop;
};
extern struct gattlib_thread_t g_gattlib_thread;
/**
* Watch the GATT connection for conditions
*/
GSource* gattlib_watch_connection_full(GIOChannel* io, GIOCondition condition,
GIOFunc func, gpointer user_data, GDestroyNotify notify);
GSource* gattlib_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data);
#endif

343
src/gattlib_connect.c Normal file
View File

@ -0,0 +1,343 @@
/*
*
* GattLib - GATT Library
*
* Copyright (C) 2016 Olivier Martin <olivier@labapart.org>
*
*
* 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
*
*/
#include <assert.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/uuid.h>
#include "gattlib_internal.h"
#include "att.h"
#include "btio.h"
#include "gattrib.h"
struct gattlib_thread_t g_gattlib_thread = { 0 };
static gattlib_event_handler_t g_notification_handler;
static gattlib_event_handler_t g_indication_handler;
typedef struct {
gatt_connection_t* conn;
gatt_connect_cb_t connect_cb;
int connected;
GError* error;
} io_connect_arg_t;
static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
{
GAttrib *attrib = user_data;
uint8_t opdu[ATT_MAX_MTU];
uint16_t handle, i, olen = 0;
handle = att_get_u16(&pdu[1]);
switch (pdu[0]) {
case ATT_OP_HANDLE_NOTIFY:
if (g_notification_handler) {
g_notification_handler(handle, &pdu[3], len);
}
break;
case ATT_OP_HANDLE_IND:
if (g_indication_handler) {
g_indication_handler(handle, &pdu[3], len);
}
break;
default:
g_print("Invalid opcode\n");
return;
}
if (pdu[0] == ATT_OP_HANDLE_NOTIFY)
return;
olen = enc_confirmation(opdu, sizeof(opdu));
if (olen > 0)
g_attrib_send(attrib, 0, opdu[0], opdu, olen, NULL, NULL, NULL);
}
static gboolean io_listen_cb(gpointer user_data) {
gatt_connection_t *conn = user_data;
g_attrib_register(conn->attrib, ATT_OP_HANDLE_NOTIFY, events_handler, conn->attrib, NULL);
g_attrib_register(conn->attrib, ATT_OP_HANDLE_IND, events_handler, conn->attrib, NULL);
return FALSE;
}
static void io_connect_cb(GIOChannel *io, GError *err, gpointer user_data) {
io_connect_arg_t* io_connect_arg = user_data;
if (err) {
io_connect_arg->error = err;
// Call callback if defined
if (io_connect_arg->connect_cb) {
io_connect_arg->connect_cb(NULL);
}
} else {
io_connect_arg->conn->attrib = g_attrib_new(io);
//
// Register the listener callback
//
GSource *source = g_idle_source_new ();
assert(source != NULL);
g_source_set_callback(source, io_listen_cb, io_connect_arg->conn, NULL);
// Attaches the listener to the main loop context
guint id = g_source_attach(source, g_gattlib_thread.loop_context);
g_source_unref (source);
assert(id != 0);
//
// Call callback if defined
//
if (io_connect_arg->connect_cb) {
io_connect_arg->connect_cb(io_connect_arg->conn);
}
}
if (io_connect_arg->connect_cb) {
free(io_connect_arg);
} else {
io_connect_arg->connected = TRUE;
}
}
static void *connection_thread(void* arg) {
struct gattlib_thread_t* loop_thread = arg;
loop_thread->loop_context = g_main_context_new();
loop_thread->loop = g_main_loop_new(loop_thread->loop_context, TRUE);
g_main_loop_run(loop_thread->loop);
g_main_loop_unref(loop_thread->loop);
assert(0);
}
static gatt_connection_t *initialize_gattlib_connection(const gchar *src, const gchar *dst,
uint8_t dest_type, BtIOSecLevel sec_level, int psm, int mtu,
gatt_connect_cb_t connect_cb,
io_connect_arg_t* io_connect_arg)
{
bdaddr_t sba, dba;
GError *err = NULL;
/* Check if the GattLib thread has been started */
if (g_gattlib_thread.ref == 0) {
/* Start it */
/* Create a thread that will handle Bluetooth events */
int error = pthread_create(&g_gattlib_thread.thread, NULL, &connection_thread, &g_gattlib_thread);
if (error != 0) {
fprintf(stderr, "Cannot create connection thread: %s", strerror(error));
return NULL;
}
/* Wait for the loop to be started */
while (!g_gattlib_thread.loop || !g_main_loop_is_running (g_gattlib_thread.loop)) {
usleep(1000);
}
} else {
/* Increase the reference to know how many GATT connection use the loop */
g_gattlib_thread.ref++;
}
/* Remote device */
if (dst == NULL) {
fprintf(stderr, "Remote Bluetooth address required\n");
return NULL;
}
str2ba(dst, &dba);
/* Local adapter */
if (src != NULL) {
if (!strncmp(src, "hci", 3))
hci_devba(atoi(src + 3), &sba);
else
str2ba(src, &sba);
} else
bacpy(&sba, BDADDR_ANY);
/* Not used for BR/EDR */
if ((dest_type != BDADDR_LE_PUBLIC) && (dest_type != BDADDR_LE_RANDOM)) {
return NULL;
}
if ((sec_level != BT_IO_SEC_LOW) && (sec_level != BT_IO_SEC_MEDIUM) && (sec_level != BT_IO_SEC_HIGH)) {
return NULL;
}
gatt_connection_t* conn = malloc(sizeof(gatt_connection_t));
if (conn == NULL) {
return NULL;
}
/* Intialize bt_io_connect argument */
io_connect_arg->conn = conn;
io_connect_arg->connect_cb = connect_cb;
io_connect_arg->connected = FALSE;
io_connect_arg->error = NULL;
if (psm == 0)
conn->io = bt_io_connect(BT_IO_L2CAP, io_connect_cb, io_connect_arg, NULL, &err,
BT_IO_OPT_SOURCE_BDADDR, &sba,
BT_IO_OPT_DEST_BDADDR, &dba,
BT_IO_OPT_DEST_TYPE, dest_type,
BT_IO_OPT_CID, ATT_CID,
BT_IO_OPT_SEC_LEVEL, sec_level,
BT_IO_OPT_INVALID);
else
conn->io = bt_io_connect(BT_IO_L2CAP, io_connect_cb, io_connect_arg, NULL, &err,
BT_IO_OPT_SOURCE_BDADDR, &sba,
BT_IO_OPT_DEST_BDADDR, &dba,
BT_IO_OPT_PSM, psm,
BT_IO_OPT_IMTU, mtu,
BT_IO_OPT_SEC_LEVEL, sec_level,
BT_IO_OPT_INVALID);
if (err) {
fprintf(stderr, "%s\n", err->message);
g_error_free(err);
return NULL;
} else {
return conn;
}
}
gatt_connection_t *gattlib_connect_async(const gchar *src, const gchar *dst,
uint8_t dest_type, BtIOSecLevel sec_level, int psm, int mtu,
gatt_connect_cb_t connect_cb)
{
io_connect_arg_t* io_connect_arg = malloc(sizeof(io_connect_arg_t));
return initialize_gattlib_connection(src, dst, dest_type, sec_level,
psm, mtu, connect_cb, io_connect_arg);
}
/**
* @param src Local Adaptater interface
* @param dst Remote Bluetooth address
* @param dst_type Set LE address type (either BDADDR_LE_PUBLIC or BDADDR_LE_RANDOM)
* @param sec_level Set security level (either BT_IO_SEC_LOW, BT_IO_SEC_MEDIUM, BT_IO_SEC_HIGH)
* @param psm Specify the PSM for GATT/ATT over BR/EDR
* @param mtu Specify the MTU size
*/
gatt_connection_t *gattlib_connect(const gchar *src, const gchar *dst,
uint8_t dest_type, BtIOSecLevel sec_level, int psm, int mtu)
{
io_connect_arg_t io_connect_arg;
gatt_connection_t *conn = initialize_gattlib_connection(src, dst, dest_type, sec_level,
psm, mtu, NULL, &io_connect_arg);
// Wait for the connection to be done
while(io_connect_arg.connected == FALSE) {
g_main_context_iteration(g_gattlib_thread.loop_context, FALSE);
}
if (io_connect_arg.error) {
fprintf(stderr, "gattlib_connect - error:%s\n", io_connect_arg.error->message);
return NULL;
} else {
return conn;
}
}
int gattlib_disconnect(gatt_connection_t* connection) {
// Stop the I/O Channel
GIOStatus status = g_io_channel_shutdown(connection->io, FALSE, NULL);
assert(status == G_IO_STATUS_NORMAL);
g_io_channel_unref(connection->io);
g_attrib_unref(connection->attrib);
free(connection);
//TODO: Add a mutex around this code to avoid a race condition
/* Decrease the reference counter of the loop */
g_gattlib_thread.ref--;
/* Check if we are the last one */
if (g_gattlib_thread.ref == 0) {
g_main_loop_quit(g_gattlib_thread.loop);
g_main_loop_unref(g_gattlib_thread.loop);
g_main_context_unref(g_gattlib_thread.loop_context);
// Detach the thread
pthread_detach(g_gattlib_thread.thread);
}
return 0;
}
GSource* gattlib_watch_connection_full(GIOChannel* io, GIOCondition condition,
GIOFunc func, gpointer user_data, GDestroyNotify notify)
{
// Create a main loop source
GSource *source = g_io_create_watch (io, condition);
assert(source != NULL);
g_source_set_callback (source, (GSourceFunc)func, user_data, notify);
// Attaches it to the main loop context
guint id = g_source_attach(source, g_gattlib_thread.loop_context);
g_source_unref (source);
assert(id != 0);
return source;
}
GSource* gattlib_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data) {
GSource *source = g_timeout_source_new_seconds(interval);
assert(source != NULL);
g_source_set_callback(source, function, data, NULL);
// Attaches it to the main loop context
guint id = g_source_attach(source, g_gattlib_thread.loop_context);
g_source_unref (source);
assert(id != 0);
return source;
}
void gattlib_register_notification(gattlib_event_handler_t notification_handler) {
g_notification_handler = notification_handler;
}
void gattlib_register_indication(gattlib_event_handler_t indication_handler) {
g_indication_handler = indication_handler;
}
int gattlib_uuid_to_string(const bt_uuid_t *uuid, char *str, size_t n) {
return bt_uuid_to_string(uuid, str, n);
}
int gattlib_string_to_uuid(bt_uuid_t *uuid, const char *str) {
return bt_string_to_uuid(uuid, str);
}

149
src/gattlib_discover.c Normal file
View File

@ -0,0 +1,149 @@
/*
*
* GattLib - GATT Library
*
* Copyright (C) 2016 Olivier Martin <olivier@labapart.org>
*
*
* 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
*
*/
#include <assert.h>
#include <stdlib.h>
#include "gattlib_internal.h"
#include "att.h"
#include "gattrib.h"
#include "gatt.h"
struct primary_all_cb_t {
gattlib_primary_service_t* services;
int services_count;
int discovered;
};
static void primary_all_cb(GSList *services, guint8 status, gpointer user_data) {
struct primary_all_cb_t* data = user_data;
GSList *l;
int i;
if (status) {
fprintf(stderr, "Discover all primary services failed: %s\n", att_ecode2str(status));
goto done;
}
// Allocate array
data->services_count = g_slist_length(services);
data->services = malloc(data->services_count * sizeof(gattlib_primary_service_t));
for (i = 0, l = services; l; l = l->next, i++) {
struct gatt_primary *prim = l->data;
data->services[i].attr_handle_start = prim->range.start;
data->services[i].attr_handle_end = prim->range.end;
bt_string_to_uuid(&data->services[i].uuid, prim->uuid);
assert(i < data->services_count);
}
done:
data->discovered = TRUE;
}
int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_service_t** services, int* services_count) {
struct primary_all_cb_t user_data;
guint ret;
bzero(&user_data, sizeof(user_data));
user_data.discovered = FALSE;
ret = gatt_discover_primary(connection->attrib, NULL, primary_all_cb, &user_data);
if (ret == 0) {
fprintf(stderr, "Fail to discover primary services.\n");
return 1;
}
// Wait for completion
while(user_data.discovered == FALSE) {
g_main_context_iteration(g_gattlib_thread.loop_context, FALSE);
}
*services = user_data.services;
*services_count = user_data.services_count;
return 0;
}
struct characteristic_cb_t {
gattlib_characteristic_t* characteristics;
int characteristics_count;
int discovered;
};
static void characteristic_cb(GSList *characteristics, guint8 status, gpointer user_data) {
struct characteristic_cb_t* data = user_data;
GSList *l;
int i;
if (status) {
fprintf(stderr, "Discover all characteristics failed: %s\n", att_ecode2str(status));
goto done;
}
// Allocate array
data->characteristics_count = g_slist_length(characteristics);
data->characteristics = malloc(data->characteristics_count * sizeof(gattlib_characteristic_t));
for (i = 0, l = characteristics; l; l = l->next, i++) {
struct gatt_char *chars = l->data;
data->characteristics[i].properties = chars->properties;
data->characteristics[i].value_handle = chars->value_handle;
bt_string_to_uuid(&data->characteristics[i].uuid, chars->uuid);
assert(i < data->characteristics_count);
}
done:
data->discovered = TRUE;
}
int gattlib_discover_char(gatt_connection_t* connection, gattlib_characteristic_t** characteristics, int* characteristics_count) {
struct characteristic_cb_t user_data;
const int start = 0x0001;
const int end = 0xffff;
guint ret;
bzero(&user_data, sizeof(user_data));
user_data.discovered = FALSE;
ret = gatt_discover_char(connection->attrib, start, end, NULL, characteristic_cb, &user_data);
if (ret == 0) {
fprintf(stderr, "Fail to discover characteristics.\n");
return 1;
}
// Wait for completion
while(user_data.discovered == FALSE) {
g_main_context_iteration(g_gattlib_thread.loop_context, FALSE);
}
*characteristics = user_data.characteristics;
*characteristics_count = user_data.characteristics_count;
return 0;
}

157
src/gattlib_read_write.c Normal file
View File

@ -0,0 +1,157 @@
/*
*
* GattLib - GATT Library
*
* Copyright (C) 2016 Olivier Martin <olivier@labapart.org>
*
*
* 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
*
*/
#include <stdlib.h>
#include "gattlib_internal.h"
#include "att.h"
struct gattlib_result_read_uuid_t {
void* buffer;
size_t buffer_max_len;
size_t buffer_len;
gatt_read_cb_t callback;
int completed;
};
static void gattlib_result_read_uuid_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data) {
struct gattlib_result_read_uuid_t* gattlib_result = user_data;
struct att_data_list *list;
int i;
if (status == ATT_ECODE_ATTR_NOT_FOUND) {
goto done;
}
if (status != 0) {
fprintf(stderr, "Read characteristics by UUID failed: %s\n", att_ecode2str(status));
goto done;
}
list = dec_read_by_type_resp(pdu, len);
if (list == NULL) {
goto done;
}
for (i = 0; i < list->num; i++) {
uint8_t *value = list->data[i];
int j;
value += 2;
gattlib_result->buffer_len = list->len - 2;
if (gattlib_result->callback) {
gattlib_result->callback(value, gattlib_result->buffer_len);
} else {
memcpy(gattlib_result->buffer, value, MIN(gattlib_result->buffer_len, gattlib_result->buffer_max_len));
}
}
att_data_list_free(list);
done:
if (gattlib_result->callback) {
free(gattlib_result);
} else {
gattlib_result->completed = TRUE;
}
}
int gattlib_read_char_by_uuid(gatt_connection_t* connection, bt_uuid_t* uuid,
void* buffer, size_t buffer_len)
{
struct gattlib_result_read_uuid_t* gattlib_result;
const int start = 0x0001;
const int end = 0xffff;
int len;
gattlib_result = malloc(sizeof(struct gattlib_result_read_uuid_t));
if (gattlib_result == NULL) {
return 0;
}
gattlib_result->buffer = buffer;
gattlib_result->buffer_max_len = buffer_len;
gattlib_result->buffer_len = 0;
gattlib_result->callback = NULL;
gattlib_result->completed = FALSE;
gatt_read_char_by_uuid(connection->attrib, start, end, uuid,
gattlib_result_read_uuid_cb, gattlib_result);
// Wait for completion of the event
while(gattlib_result->completed == FALSE) {
g_main_context_iteration(g_gattlib_thread.loop_context, FALSE);
}
len = gattlib_result->buffer_len;
free(gattlib_result);
return len;
}
int gattlib_read_char_by_uuid_async(gatt_connection_t* connection, bt_uuid_t* uuid,
gatt_read_cb_t gatt_read_cb)
{
struct gattlib_result_read_uuid_t* gattlib_result;
const int start = 0x0001;
const int end = 0xffff;
int i;
gattlib_result = malloc(sizeof(struct gattlib_result_read_uuid_t));
if (gattlib_result == NULL) {
return 0;
}
gattlib_result->buffer = NULL;
gattlib_result->buffer_max_len = 0;
gattlib_result->buffer_len = 0;
gattlib_result->callback = gatt_read_cb;
gattlib_result->completed = FALSE;
return gatt_read_char_by_uuid(connection->attrib, start, end, uuid,
gattlib_result_read_uuid_cb, gattlib_result);
}
void gattlib_write_result_cb(guint8 status, const guint8 *pdu, guint16 len, gpointer user_data) {
int* write_completed = user_data;
*write_completed = TRUE;
}
int gattlib_write_char_by_handle(gatt_connection_t* connection, uint16_t handle, void* buffer, size_t buffer_len) {
int write_completed = FALSE;
guint ret = gatt_write_char(connection->attrib, handle, buffer, buffer_len,
gattlib_write_result_cb, &write_completed);
if (ret == 0) {
return 1;
}
// Wait for completion of the event
while(write_completed == FALSE) {
g_main_context_iteration(g_gattlib_thread.loop_context, FALSE);
}
return 0;
}