From fc58fc03070d7205ed3bc1f8f76f424cad140e32 Mon Sep 17 00:00:00 2001 From: Olivier Martin Date: Sat, 23 Apr 2016 17:26:11 +0100 Subject: [PATCH] Created initial gattlib support --- CMakeLists.txt | 41 ++++- bluez/attrib/gattrib.c | 31 ++-- bluez/attrib/gattrib.h | 5 +- bluez/btio/btio.c | 8 +- bluez/btio/btio.h | 8 +- include/gattlib.h | 97 +++++++++++ include/gattlib_internal.h | 45 +++++ src/gattlib_connect.c | 343 +++++++++++++++++++++++++++++++++++++ src/gattlib_discover.c | 149 ++++++++++++++++ src/gattlib_read_write.c | 157 +++++++++++++++++ 10 files changed, 857 insertions(+), 27 deletions(-) create mode 100644 include/gattlib.h create mode 100644 include/gattlib_internal.h create mode 100644 src/gattlib_connect.c create mode 100644 src/gattlib_discover.c create mode 100644 src/gattlib_read_write.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 8454b83..5749b71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 ") +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) diff --git a/bluez/attrib/gattrib.c b/bluez/attrib/gattrib.c index 00f59d7..875d3a2 100644 --- a/bluez/attrib/gattrib.c +++ b/bluez/attrib/gattrib.c @@ -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); } diff --git a/bluez/attrib/gattrib.h b/bluez/attrib/gattrib.h index f73b741..78c5500 100644 --- a/bluez/attrib/gattrib.h +++ b/bluez/attrib/gattrib.h @@ -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, diff --git a/bluez/btio/btio.c b/bluez/btio/btio.c index e81fb75..6ed5948 100644 --- a/bluez/btio/btio.c +++ b/bluez/btio/btio.c @@ -37,6 +37,7 @@ #include #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); } diff --git a/bluez/btio/btio.h b/bluez/btio/btio.h index cf0e070..bdd1b22 100644 --- a/bluez/btio/btio.h +++ b/bluez/btio/btio.h @@ -25,6 +25,7 @@ #define BT_IO_H #include +#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, diff --git a/include/gattlib.h b/include/gattlib.h new file mode 100644 index 0000000..25f153a --- /dev/null +++ b/include/gattlib.h @@ -0,0 +1,97 @@ +/* + * + * GattLib - GATT Library + * + * Copyright (C) 2016 Olivier Martin + * + * + * 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 +#include + +#include +#include +#include + +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 diff --git a/include/gattlib_internal.h b/include/gattlib_internal.h new file mode 100644 index 0000000..f9cbcd2 --- /dev/null +++ b/include/gattlib_internal.h @@ -0,0 +1,45 @@ +/* + * + * GattLib - GATT Library + * + * Copyright (C) 2016 Olivier Martin + * + * + * 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 diff --git a/src/gattlib_connect.c b/src/gattlib_connect.c new file mode 100644 index 0000000..012268e --- /dev/null +++ b/src/gattlib_connect.c @@ -0,0 +1,343 @@ +/* + * + * GattLib - GATT Library + * + * Copyright (C) 2016 Olivier Martin + * + * + * 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 +#include +#include +#include + +#include +#include + +#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); +} diff --git a/src/gattlib_discover.c b/src/gattlib_discover.c new file mode 100644 index 0000000..f972326 --- /dev/null +++ b/src/gattlib_discover.c @@ -0,0 +1,149 @@ +/* + * + * GattLib - GATT Library + * + * Copyright (C) 2016 Olivier Martin + * + * + * 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 +#include + +#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; +} diff --git a/src/gattlib_read_write.c b/src/gattlib_read_write.c new file mode 100644 index 0000000..cf46375 --- /dev/null +++ b/src/gattlib_read_write.c @@ -0,0 +1,157 @@ +/* + * + * GattLib - GATT Library + * + * Copyright (C) 2016 Olivier Martin + * + * + * 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 + +#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; +}