From 2538cdb0288c36f3b4b6a259cd854bdbddbc9289 Mon Sep 17 00:00:00 2001 From: Pat Deegan Date: Wed, 20 Dec 2017 11:03:49 -0500 Subject: [PATCH 01/12] Async: Implementation of *_async versions for long-blocking calls (scan/connect/read/write), that use callbacks to report completion or return data. A ble_scan_async sample is included and a C++ library using these will be made available shortly. --- CMakeLists.txt | 1 + dbus/CMakeLists.txt | 1 + dbus/gattlib.c | 235 +++++--- dbus/gattlib_async.c | 730 +++++++++++++++++++++++ dbus/gattlib_internal.h | 18 +- examples/ble_scan_async/CMakeLists.txt | 36 ++ examples/ble_scan_async/ble_scan_async.c | 291 +++++++++ include/gattlib.h | 95 ++- 8 files changed, 1323 insertions(+), 84 deletions(-) create mode 100644 dbus/gattlib_async.c create mode 100644 examples/ble_scan_async/CMakeLists.txt create mode 100644 examples/ble_scan_async/ble_scan_async.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 967c0660..f99d24dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,7 @@ set(ENV{PKG_CONFIG_PATH} "${PROJECT_BINARY_DIR}:$ENV{PKG_CONFIG_PATH}") # Examples add_subdirectory(examples/ble_scan) +add_subdirectory(examples/ble_scan_async) add_subdirectory(examples/discover) add_subdirectory(examples/read_write) add_subdirectory(examples/notification) diff --git a/dbus/CMakeLists.txt b/dbus/CMakeLists.txt index 0abb5f4a..624701e0 100644 --- a/dbus/CMakeLists.txt +++ b/dbus/CMakeLists.txt @@ -73,6 +73,7 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/org-bluez-gattdescriptor1. include_directories(. ${CMAKE_CURRENT_BINARY_DIR} ${GIO_UNIX_INCLUDE_DIRS} ${BLUEZ_INCLUDE_DIRS}) set(gattlib_SRCS gattlib.c + gattlib_async.c bluez5/lib/uuid.c ../gattlib_common.c ${CMAKE_CURRENT_BINARY_DIR}/org-bluez-adaptater1.c diff --git a/dbus/gattlib.c b/dbus/gattlib.c index 87d121df..916a02e0 100644 --- a/dbus/gattlib.c +++ b/dbus/gattlib.c @@ -22,6 +22,7 @@ */ #include + #include #include @@ -46,22 +47,41 @@ int gattlib_adapter_open(const char* adapter_name, void** adapter) { object_path, NULL, &error); if (adapter_proxy == NULL) { - printf("Failed to get adapter %s\n", object_path); + ERROR_GATTLIB("Failed to get adapter %s\n", object_path); return 1; } + + *adapter = adapter_proxy; + // Ensure the adapter is powered on org_bluez_adapter1_set_powered(adapter_proxy, TRUE); - *adapter = adapter_proxy; + return 0; } -static gboolean stop_scan_func(gpointer data) { +/* gattlib_adapter_powered -- returns gboolean TRUE (1) if powered. + * This is useful because you can open the adapter and still not + * have access to functionaliy, if the user has "turned bluetooth off" + * manually. + */ +int gattlib_adapter_powered(void* adapter) { + OrgBluezAdapter1 *adapter_proxy = adapter; + return org_bluez_adapter1_get_powered(adapter_proxy); +} + +static gboolean loop_timeout_func(gpointer data) { + DEBUG_GATTLIB("\nloop_timeout_func()!\n"); g_main_loop_quit(data); return FALSE; } +static gboolean stop_scan_func(gpointer data) { + DEBUG_GATTLIB("\nstop_scan_func()!\n"); + return loop_timeout_func(data); +} + void on_dbus_object_added(GDBusObjectManager *device_manager, GDBusObject *object, gpointer user_data) @@ -91,9 +111,18 @@ void on_dbus_object_added(GDBusObjectManager *device_manager, } } -int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t discovered_device_cb, int timeout) { - GDBusObjectManager *device_manager; + +/* + * gattlib_adapter_scan_enable_setup + * functionality brought out of scan_enable to allow + * re-use for async version + */ +int gattlib_adapter_scan_enable_setup(void* adapter, gattlib_discovered_device_t discovered_device_cb, + GDBusObjectManager **dev_manager) { + + GDBusObjectManager *device_manager ; GError *error = NULL; + *dev_manager = NULL; org_bluez_adapter1_call_start_discovery_sync((OrgBluezAdapter1*)adapter, NULL, &error); @@ -149,6 +178,23 @@ int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t disco G_CALLBACK (on_dbus_object_added), discovered_device_cb); + *dev_manager = device_manager; + + return 0; + +} + + + +int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t discovered_device_cb, int timeout) { + + GDBusObjectManager *device_manager; + int setupReturn = gattlib_adapter_scan_enable_setup(adapter, discovered_device_cb, &device_manager); + if (setupReturn) + { + return setupReturn; + } + // Run Glib loop for 'timeout' seconds GMainLoop *loop = g_main_loop_new(NULL, 0); g_timeout_add_seconds (timeout, stop_scan_func, loop); @@ -159,6 +205,8 @@ int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t disco return 0; } + + int gattlib_adapter_scan_disable(void* adapter) { GError *error = NULL; @@ -166,6 +214,10 @@ int gattlib_adapter_scan_disable(void* adapter) { return 0; } + + + + int gattlib_adapter_close(void* adapter) { g_object_unref(adapter); return 0; @@ -219,6 +271,9 @@ gatt_connection_t *gattlib_connect(const char *src, const char *dst, adapter_name = "hci0"; } + + DEBUG_GATTLIB("gattlib attempting connect through adapter %s\n", adapter_name); + // Transform string from 'DA:94:40:95:E0:87' to 'dev_DA_94_40_95_E0_87' strncpy(device_address_str, dst, sizeof(device_address_str)); for (i = 0; i < strlen(device_address_str); i++) { @@ -232,11 +287,13 @@ gatt_connection_t *gattlib_connect(const char *src, const char *dst, gattlib_context_t* conn_context = calloc(sizeof(gattlib_context_t), 1); if (conn_context == NULL) { + DEBUG_GATTLIB("connect() couldn't calloc context!\n"); return NULL; } gatt_connection_t* connection = calloc(sizeof(gatt_connection_t), 1); if (connection == NULL) { + DEBUG_GATTLIB("connect() couldn't calloc connection!\n"); return NULL; } else { connection->context = conn_context; @@ -250,30 +307,39 @@ gatt_connection_t *gattlib_connect(const char *src, const char *dst, NULL, &error); if (device == NULL) { + DEBUG_GATTLIB("connect() device not present!\n"); goto FREE_CONNECTION; } else { conn_context->device = device; conn_context->device_object_path = strdup(object_path); } + error = NULL; org_bluez_device1_call_connect_sync(device, NULL, &error); if (error) { - printf("Device connected error: %s\n", error->message); + ERROR_GATTLIB("Device connected error: %s\n", error->message); goto FREE_DEVICE; } // Wait for the property 'UUIDs' to be changed. We assume 'org.bluez.GattService1 // and 'org.bluez.GattCharacteristic1' to be advertised at that moment. - GMainLoop *loop = g_main_loop_new(NULL, 0); + + + + + + DEBUG_GATTLIB("gattlib_connect starting loop (timeout %i s.)\n", CONNECT_TIMEOUT); + + GMainLoop *loop = g_main_loop_new(NULL, 0); // Register a handle for notification g_signal_connect(device, "g-properties-changed", G_CALLBACK (on_handle_device_property_change), loop); - g_timeout_add_seconds (CONNECT_TIMEOUT, stop_scan_func, loop); + g_timeout_add_seconds (CONNECT_TIMEOUT, loop_timeout_func, loop); g_main_loop_run(loop); g_main_loop_unref(loop); @@ -288,17 +354,14 @@ gatt_connection_t *gattlib_connect(const char *src, const char *dst, return NULL; } -gatt_connection_t *gattlib_connect_async(const char *src, const char *dst, - uint8_t dest_type, gattlib_bt_sec_level_t sec_level, int psm, int mtu, - gatt_connect_cb_t connect_cb) -{ - return NULL; -} + + int gattlib_disconnect(gatt_connection_t* connection) { gattlib_context_t* conn_context = connection->context; GError *error = NULL; + DEBUG_GATTLIB("gattlib_disconnect\n"); org_bluez_device1_call_disconnect_sync(conn_context->device, NULL, &error); free(conn_context->device_object_path); @@ -309,6 +372,7 @@ int gattlib_disconnect(gatt_connection_t* connection) { return 0; } + // Bluez was using org.bluez.Device1.GattServices until 5.37 to expose the list of available GATT Services #if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 38) int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_service_t** services, int* services_count) { @@ -346,7 +410,7 @@ int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_serv NULL, &error); if (service_proxy == NULL) { - printf("Failed to open service '%s'.\n", *service_str); + ERROR_GATTLIB("Failed to open service '%s'.\n", *service_str); continue; } @@ -426,7 +490,7 @@ int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_serv NULL, &error); if (service_proxy == NULL) { - printf("Failed to open service '%s'.\n", object_path); + ERROR_GATTLIB("Failed to open service '%s'.\n", object_path); continue; } @@ -488,7 +552,7 @@ int gattlib_discover_char(gatt_connection_t* connection, gattlib_characteristic_ NULL, &error); if (service_proxy == NULL) { - printf("Failed to open services '%s'.\n", *service_str); + ERROR_GATTLIB("Failed to open services '%s'.\n", *service_str); continue; } @@ -519,7 +583,7 @@ int gattlib_discover_char(gatt_connection_t* connection, gattlib_characteristic_ NULL, &error); if (service_proxy == NULL) { - printf("Failed to open service '%s'.\n", *service_str); + ERROR_GATTLIB("Failed to open service '%s'.\n", *service_str); continue; } @@ -537,29 +601,38 @@ int gattlib_discover_char(gatt_connection_t* connection, gattlib_characteristic_ NULL, &error); if (characteristic_proxy == NULL) { - printf("Failed to open characteristic '%s'.\n", *characteristic_str); + ERROR_GATTLIB("Failed to open characteristic '%s'.\n", *characteristic_str); continue; } else { characteristic_list[count].handle = 0; characteristic_list[count].value_handle = 0; + DEBUG_GATTLIB("gattlib got char: "); const gchar *const * flags = org_bluez_gatt_characteristic1_get_flags(characteristic_proxy); for (; *flags != NULL; flags++) { if (strcmp(*flags,"broadcast") == 0) { + DEBUG_GATTLIB("bcast "); characteristic_list[count].properties |= GATTLIB_CHARACTERISTIC_BROADCAST; } else if (strcmp(*flags,"read") == 0) { + DEBUG_GATTLIB("read "); characteristic_list[count].properties |= GATTLIB_CHARACTERISTIC_READ; } else if (strcmp(*flags,"write") == 0) { + DEBUG_GATTLIB("write "); characteristic_list[count].properties |= GATTLIB_CHARACTERISTIC_WRITE; } else if (strcmp(*flags,"write-without-response") == 0) { + DEBUG_GATTLIB("write[no resp] "); characteristic_list[count].properties |= GATTLIB_CHARACTERISTIC_WRITE_WITHOUT_RESP; } else if (strcmp(*flags,"notify") == 0) { + DEBUG_GATTLIB("notify "); characteristic_list[count].properties |= GATTLIB_CHARACTERISTIC_NOTIFY; } else if (strcmp(*flags,"indicate") == 0) { + DEBUG_GATTLIB("indicate "); characteristic_list[count].properties |= GATTLIB_CHARACTERISTIC_INDICATE; } } + + DEBUG_GATTLIB("\n"); gattlib_string_to_uuid( org_bluez_gatt_characteristic1_get_uuid(characteristic_proxy), MAX_LEN_UUID_STR + 1, @@ -597,7 +670,7 @@ static void add_characteristics_from_service(GDBusObjectManager *device_manager, NULL, &error); if (characteristic == NULL) { - printf("Failed to open characteristic '%s'.\n", object_path); + ERROR_GATTLIB("Failed to open characteristic '%s'.\n", object_path); continue; } @@ -607,23 +680,34 @@ static void add_characteristics_from_service(GDBusObjectManager *device_manager, characteristic_list[*count].handle = 0; characteristic_list[*count].value_handle = 0; + + DEBUG_GATTLIB("gattlib got char: "); + const gchar *const * flags = org_bluez_gatt_characteristic1_get_flags(characteristic); for (; *flags != NULL; flags++) { if (strcmp(*flags,"broadcast") == 0) { + DEBUG_GATTLIB("bcast "); characteristic_list[*count].properties |= GATTLIB_CHARACTERISTIC_BROADCAST; } else if (strcmp(*flags,"read") == 0) { + DEBUG_GATTLIB("read "); characteristic_list[*count].properties |= GATTLIB_CHARACTERISTIC_READ; } else if (strcmp(*flags,"write") == 0) { + DEBUG_GATTLIB("write "); characteristic_list[*count].properties |= GATTLIB_CHARACTERISTIC_WRITE; } else if (strcmp(*flags,"write-without-response") == 0) { + DEBUG_GATTLIB("write[no resp] "); characteristic_list[*count].properties |= GATTLIB_CHARACTERISTIC_WRITE_WITHOUT_RESP; } else if (strcmp(*flags,"notify") == 0) { + DEBUG_GATTLIB("notify "); characteristic_list[*count].properties |= GATTLIB_CHARACTERISTIC_NOTIFY; } else if (strcmp(*flags,"indicate") == 0) { + DEBUG_GATTLIB("indicate "); characteristic_list[*count].properties |= GATTLIB_CHARACTERISTIC_INDICATE; } } + DEBUG_GATTLIB("\n"); + gattlib_string_to_uuid( org_bluez_gatt_characteristic1_get_uuid(characteristic), MAX_LEN_UUID_STR + 1, @@ -688,7 +772,7 @@ int gattlib_discover_char(gatt_connection_t* connection, gattlib_characteristic_ NULL, &error); if (service_proxy == NULL) { - printf("Failed to open service '%s'.\n", object_path); + ERROR_GATTLIB("Failed to open service '%s'.\n", object_path); continue; } @@ -711,7 +795,8 @@ int gattlib_discover_char(gatt_connection_t* connection, gattlib_characteristic_ } #endif -static OrgBluezGattCharacteristic1 *get_characteristic_from_uuid(const uuid_t* uuid) { + +OrgBluezGattCharacteristic1 *get_characteristic_from_uuid(const uuid_t* uuid) { OrgBluezGattCharacteristic1 *characteristic = NULL; GError *error = NULL; @@ -814,45 +899,11 @@ int gattlib_read_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, void* return 0; } -int gattlib_read_char_by_uuid_async(gatt_connection_t* connection, uuid_t* uuid, gatt_read_cb_t gatt_read_cb) { - OrgBluezGattCharacteristic1 *characteristic = get_characteristic_from_uuid(uuid); - if (characteristic == NULL) { - return -1; - } - - GVariant *out_value; - GError *error = NULL; - -#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 40) - org_bluez_gatt_characteristic1_call_read_value_sync( - characteristic, &out_value, NULL, &error); -#else - GVariantBuilder *options = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); - org_bluez_gatt_characteristic1_call_read_value_sync( - characteristic, g_variant_builder_end(options), &out_value, NULL, &error); - g_variant_builder_unref(options); -#endif - if (error != NULL) { - return -1; - } - - gsize n_elements; - gconstpointer const_buffer = g_variant_get_fixed_array(out_value, &n_elements, sizeof(guchar)); - if (const_buffer) { - gatt_read_cb(const_buffer, n_elements); - } - - g_object_unref(characteristic); - -#if BLUEZ_VERSION >= BLUEZ_VERSIONS(5, 40) - //g_variant_unref(in_params); See: https://github.com/labapart/gattlib/issues/28#issuecomment-311486629 -#endif - return 0; -} - int gattlib_write_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len) { OrgBluezGattCharacteristic1 *characteristic = get_characteristic_from_uuid(uuid); if (characteristic == NULL) { + + DEBUG_GATTLIB("\nwrite() can't find this characteristic!\n"); return -1; } @@ -867,6 +918,7 @@ int gattlib_write_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, cons g_variant_builder_unref(options); #endif if (error != NULL) { + DEBUG_GATTLIB("\nwrite() error returned from bluez write\n"); return -1; } @@ -889,37 +941,51 @@ gboolean on_handle_characteristic_property_change( { gatt_connection_t* connection = user_data; - if (connection->notification_handler) { - // Retrieve 'Value' from 'arg_changed_properties' - if (g_variant_n_children (arg_changed_properties) > 0) { - GVariantIter *iter; - const gchar *key; - GVariant *value; - - g_variant_get (arg_changed_properties, "a{sv}", &iter); - while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) { - if (strcmp(key, "Value") == 0) { - uuid_t uuid; - size_t data_length; - const uint8_t* data = g_variant_get_fixed_array(value, &data_length, sizeof(guchar)); - - gattlib_string_to_uuid( - org_bluez_gatt_characteristic1_get_uuid(object), - MAX_LEN_UUID_STR + 1, - &uuid); - - connection->notification_handler(&uuid, data, data_length, user_data); - break; - } - } + DEBUG_GATTLIB("\nchar prop changed called "); + if (!connection->notification_handler) { + DEBUG_GATTLIB("but we have NO notification_handler\n"); + return TRUE; + } + DEBUG_GATTLIB("and we have notification_handler "); + // Retrieve 'Value' from 'arg_changed_properties' + + if (g_variant_n_children(arg_changed_properties) <= 0) { + + DEBUG_GATTLIB(" but 0 changed properties...\n"); + } + DEBUG_GATTLIB(" and some changed properties...\n"); + GVariantIter *iter; + const gchar *key; + GVariant *value; + + g_variant_get(arg_changed_properties, "a{sv}", &iter); + while (g_variant_iter_loop(iter, "{&sv}", &key, &value)) { + if (strcmp(key, "Value") == 0) { + uuid_t uuid; + size_t data_length; + const uint8_t* data = g_variant_get_fixed_array(value, &data_length, + sizeof(guchar)); + + DEBUG_GATTLIB("Got Value of len %li, passing to notif handler\n", data_length); + gattlib_string_to_uuid( + org_bluez_gatt_characteristic1_get_uuid(object), + MAX_LEN_UUID_STR + 1, &uuid); + + connection->notification_handler(&uuid, data, data_length, + user_data); + break; } } + + return TRUE; } int gattlib_notification_start(gatt_connection_t* connection, const uuid_t* uuid) { + DEBUG_GATTLIB("\ngattlib_notification_start()... "); OrgBluezGattCharacteristic1 *characteristic = get_characteristic_from_uuid(uuid); if (characteristic == NULL) { + DEBUG_GATTLIB("can't find this char\n"); return -1; } @@ -933,15 +999,20 @@ int gattlib_notification_start(gatt_connection_t* connection, const uuid_t* uuid org_bluez_gatt_characteristic1_call_start_notify_sync(characteristic, NULL, &error); if (error) { + + DEBUG_GATTLIB("error returned by bluez start notify\n"); return 1; } else { + DEBUG_GATTLIB("OK\n"); return 0; } } int gattlib_notification_stop(gatt_connection_t* connection, const uuid_t* uuid) { + DEBUG_GATTLIB("\ngattlib_notification_stop()... "); OrgBluezGattCharacteristic1 *characteristic = get_characteristic_from_uuid(uuid); if (characteristic == NULL) { + DEBUG_GATTLIB("can't find this char\n"); return -1; } @@ -950,8 +1021,10 @@ int gattlib_notification_stop(gatt_connection_t* connection, const uuid_t* uuid) characteristic, NULL, &error); if (error) { + DEBUG_GATTLIB("error returned by bluez start notify\n"); return 1; } else { + DEBUG_GATTLIB("OK\n"); return 0; } } diff --git a/dbus/gattlib_async.c b/dbus/gattlib_async.c new file mode 100644 index 00000000..c53c841a --- /dev/null +++ b/dbus/gattlib_async.c @@ -0,0 +1,730 @@ +/* + * + * GattLib Async - GATT Library Asynchronous functions + * + * Copyright (C) 2017 Pat Deegan, psychogenic.com + * + * Async expansions to the GattLib library. + * + * 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 + * + */ + + + +/* + * *** Async Notes *** + * + * Async versions of the worst-offenders (slowest/longest blocking) + * are now available and implemented here. + * + * These operations hey need some time allocated + * in order to process events, trigger callbacks, etc. The simplest + * way to do this is to periodically give them a moment in your main + * loop, eg + * + * while (1) { + * // handle user events + * // do processing, etc. + * + * // process any async events + * gattlib_async_process(); + * + * } + * + * + * + * + */ + +#include + +#include +#include + +#include "gattlib_internal.h" + +/* decl -- implemented in gattlib.c, re-used here */ +int gattlib_adapter_scan_enable_setup(void* adapter, gattlib_discovered_device_t discovered_device_cb, + GDBusObjectManager **dev_manager); + + +#define GATTLIB_ASYNC_LOOP_CREATECONTEXT TRUE +/* + * ************* Async Structures and Utility Functions ************* + */ +typedef void (*async_cleanuptime_cb_t)(void); + +/* our state... single struct to hold anything our async + * stuff needs. + */ +typedef struct gattlib_async_statestruct { + + /* + * async functions will have a specific "main" loop + * associated with them, so they can run tasks/handle + * timeouts etc. + */ + GMainContext *current_context; + GMainLoop * current_loop; + GSource * timeout_source; + + + /* + * callbacks they may be assigned for specific _async() + * calls. + * done_callback is a generic "this method is done" call. + * + */ + gattlib_async_completed_cb_t done_callback; + gattlib_async_error_cb_t error_callback; + gatt_connect_cb_t connection_done_cb; + gboolean am_scanning; + + /* + * internals + */ + async_cleanuptime_cb_t cleanup; + void * async_proc_data; + +} gattlib_async_state_t; + +static volatile gattlib_async_state_t gattlib_async_global_state = { 0 }; + +/* + * gattlib_async_quit_currentloop() -- mark the current loop as done + */ +static void gattlib_async_quit_currentloop() { + DEBUG_GATTLIB("async_quit_currentloop()\n"); + if (gattlib_async_global_state.current_loop) { + g_main_loop_quit(gattlib_async_global_state.current_loop); + } else { + DEBUG_GATTLIB("booo, no loop?\n"); + } +} + + + +/* + * gattlib_timeout_async_loop() -- general callback for async loop + * timeouts. + */ +static gboolean gattlib_timeout_async_loop(gpointer data) { + DEBUG_GATTLIB("\nasync timeout called\n"); + if (data) { + // if (gattlib_async_global_state.current_loop) { + DEBUG_GATTLIB("sending timeout quit to loop\n"); + // g_main_loop_quit(gattlib_async_global_state.current_loop); + g_main_loop_quit(data); + } + + return FALSE; +} + +/* + * gattlib_async_setup_currentloop(TIMEOUT, CREATE_CONTEXT) + * Utility function to setup a "main loop" for async operations. + * Will fail if a loop is already alive and current. + * + * return 0 on success + */ +int gattlib_async_setup_currentloop(int timeout, gboolean useOwnContext) { + DEBUG_GATTLIB("setting up async loop..."); + if (gattlib_async_global_state.current_loop) { + DEBUG_GATTLIB("boo, already done\n"); + return 1; + } + if (useOwnContext == TRUE) { + + DEBUG_GATTLIB("with own context.\n"); + gattlib_async_global_state.current_context = g_main_context_new(); + gattlib_async_global_state.current_loop = g_main_loop_new( + gattlib_async_global_state.current_context, TRUE); + } else { + DEBUG_GATTLIB("with global context.\n"); + gattlib_async_global_state.current_loop = g_main_loop_new(NULL, TRUE); + } + + + if (! gattlib_async_global_state.current_loop) { + ERROR_GATTLIB("async loop setup: could not get new loop??\n"); + return 1; + } + if (timeout) { + DEBUG_GATTLIB("Adding a timeout to loop of %i seconds\n", timeout); + + gattlib_async_global_state.timeout_source = g_timeout_source_new_seconds(timeout); + g_source_set_callback (gattlib_async_global_state.timeout_source, + gattlib_timeout_async_loop, + gattlib_async_global_state.current_loop, + NULL); + + g_source_attach(gattlib_async_global_state.timeout_source, + g_main_loop_get_context(gattlib_async_global_state.current_loop)); + + } + + DEBUG_GATTLIB("setup curloop done\n"); + return 0; +} + +/* + * gattlib_async_triggerandclear_donecallback() + * Utility function to trigger any user-specified "async done" callback, + * and clear it from the state. + */ +static void gattlib_async_triggerandclear_donecallback() { + + gattlib_async_completed_cb_t dcb = gattlib_async_global_state.done_callback; + gattlib_async_global_state.done_callback = NULL; + + if (dcb) { + (dcb)(); + } else { + DEBUG_GATTLIB("no done cb to trigger\n"); + } +} + +/* + * gattlib_async_teardown_currentloop() + * Converse of gattlib_async_setup_currentloop() -- destroys and + * clears out the current main loop from async state. + * + * Side effect: will trigger done callback if it's set. + */ +static void gattlib_async_teardown_currentloop() { + + DEBUG_GATTLIB("teardown async loop..."); + if (!gattlib_async_global_state.current_loop) { + DEBUG_GATTLIB("boo, no loop\n"); + return; + } + + g_main_loop_quit(gattlib_async_global_state.current_loop); // TEST + + if (gattlib_async_global_state.timeout_source) { + g_source_unref(gattlib_async_global_state.timeout_source); + gattlib_async_global_state.timeout_source = NULL; + } + + g_main_loop_unref(gattlib_async_global_state.current_loop); + gattlib_async_global_state.current_loop = NULL; + + if (gattlib_async_global_state.current_context) { + g_main_context_unref(gattlib_async_global_state.current_context); + gattlib_async_global_state.current_context = NULL; + } + + if (gattlib_async_global_state.cleanup) { + (gattlib_async_global_state.cleanup)(); + gattlib_async_global_state.cleanup = NULL; + } + + + DEBUG_GATTLIB("teardown trigger done cb()\n"); + + gattlib_async_triggerandclear_donecallback(); + + DEBUG_GATTLIB("teardown done\n"); + +} + +/* + * gattlib_async_process() + * + * Allots a time slice to process events for currently setup + * async main loop. + */ +int gattlib_async_process() { + + // always tick the main context, so bluez signals can get through + g_main_context_iteration(NULL, FALSE); + + if (!gattlib_async_global_state.current_loop) { + + return 1; + } + + // DEBUG_GATTLIB("!"); + if (!g_main_loop_is_running(gattlib_async_global_state.current_loop)) { + DEBUG_GATTLIB("loop run done\n"); + gattlib_async_teardown_currentloop(); + return 1; + } + + g_main_context_iteration( + g_main_loop_get_context(gattlib_async_global_state.current_loop), + FALSE); + + return 0; + +} + +/* + * gattlib_async_process_all() + * Similar to gattlib_async_process() but will process events until + * no more are pending in the current loop. + */ +int gattlib_async_process_all() { + + // always tick the main context, so bluez signals can get through + g_main_context_iteration(NULL, FALSE); + if (!gattlib_async_global_state.current_loop) { + return 1 ; + } + + gboolean moreToProcess = TRUE; + + while ( gattlib_async_global_state.current_loop && (moreToProcess == TRUE)) { + gattlib_async_process(); + if (gattlib_async_global_state.current_loop) { + moreToProcess = g_main_context_pending(g_main_loop_get_context(gattlib_async_global_state.current_loop)); + } + } + + return 0; +} + + + + + +static void gattlib_async_scan_cleanup(void) { + DEBUG_GATTLIB("\nasync scan cleanup... "); + GDBusObjectManager *device_manager = gattlib_async_global_state.async_proc_data; + if (device_manager) { + + DEBUG_GATTLIB("freeing device manager\n"); + g_object_unref(device_manager); + gattlib_async_global_state.async_proc_data = NULL; + } else { + DEBUG_GATTLIB("no device manager to free\n"); + } + DEBUG_GATTLIB("am_scanning = FALSE\n"); + gattlib_async_global_state.am_scanning = FALSE; +} + +int gattlib_adapter_scan_enable_async(void* adapter, gattlib_discovered_device_t discovered_device_cb, int timeout, gattlib_async_completed_cb_t done_cb) { + + GDBusObjectManager *device_manager; + + DEBUG_GATTLIB("\nscan_enable_async called..."); + if (gattlib_async_setup_currentloop(timeout, GATTLIB_ASYNC_LOOP_CREATECONTEXT)) { + // gattlib_async_scan_cleanup(); + + DEBUG_GATTLIB("but there's a loop running already.\n"); + return 1; + } + + + DEBUG_GATTLIB("(am_scanning = TRUE)\n"); + int setupReturn = gattlib_adapter_scan_enable_setup(adapter, discovered_device_cb, &device_manager); + if (setupReturn) + { + DEBUG_GATTLIB("but scan setup failed.\n"); + return setupReturn; + } + + + gattlib_async_global_state.am_scanning = TRUE; + gattlib_async_global_state.cleanup = gattlib_async_scan_cleanup; + gattlib_async_global_state.done_callback = done_cb; + + gattlib_async_global_state.async_proc_data = device_manager; + + DEBUG_GATTLIB("and we're go!\n"); + + return 0; + + +} + + + +static +void gattlib_async_scandisable_ready_cb(GObject *source_object, + GAsyncResult *res, + gpointer user_data) { + + DEBUG_GATTLIB("\nhah! gattlib_async_scandisable_ready_cb called\n"); + gattlib_async_quit_currentloop(); + // gattlib_async_triggerandclear_donecallback(); +} + + +int gattlib_adapter_scan_disable_async(void* adapter, gattlib_async_completed_cb_t done_cb) { + + // likely we're scanning now + DEBUG_GATTLIB("\nscan disable async called..."); + + if (gattlib_async_global_state.current_loop && gattlib_async_global_state.am_scanning) { + DEBUG_GATTLIB("while scanning... quitting current loop\n"); + gattlib_async_global_state.done_callback = NULL; // cancel that + gattlib_async_quit_currentloop(); + gattlib_async_process(); // do one run to clean it out. + + } + + + DEBUG_GATTLIB("scan disable prep done, calling stop discovery.\n"); + gattlib_async_global_state.done_callback = done_cb; + gattlib_async_setup_currentloop(20, GATTLIB_ASYNC_LOOP_CREATECONTEXT); + org_bluez_adapter1_call_stop_discovery((OrgBluezAdapter1*)adapter, NULL, + gattlib_async_scandisable_ready_cb, NULL); + return 0; +} + + + + + + +/* gattlib_connect_params_t + * used to pack all our async connect() call params + * into a single spot, for easier management + */ +typedef struct { + char *src; + char *dst; + uint8_t dest_type; + gattlib_bt_sec_level_t sec_level; + int psm; + int mtu; +} gattlib_connect_params_t; + +/* + * Async connection functions... this may be more complex than required, + * but the connect_async() stuff was created using a glib task, so it involves + * a whole bunch-o-functions working together... bitofamess. + */ +static void gattlib_async_connect_trigger_conn_cb(gatt_connection_t * connptr) { + + if (gattlib_async_global_state.connection_done_cb) { + DEBUG_GATTLIB("gotta conn cb to call!\n"); + gatt_connect_cb_t thecb = gattlib_async_global_state.connection_done_cb; + gattlib_async_global_state.done_callback = NULL; + /* teardown the current loop immediately, in case the callback wants to + * set another async op up. + */ + gattlib_async_teardown_currentloop(); + gattlib_async_global_state.connection_done_cb = NULL; + thecb(connptr); + } + +} + +static void gattlib_async_connect_timeouttrigger_conn_cb() { + gattlib_async_global_state.done_callback = NULL; // clear it out. + gattlib_async_connect_trigger_conn_cb(NULL); +} + +static void gattlib_connect_thread_data_free(void * dptr) { + gattlib_connect_params_t *data = dptr; + if (data->src) { + free(data->src); + } + if (data->dst) { + free(data->dst); + } + g_free(data); +} +static void gattlib_connect_destroy_unclaimed_connection(gpointer data) { + + DEBUG_GATTLIB("gattlib_connect_destroy_unclaimed_connection\n"); + gatt_connection_t * conn = data; + if (conn) { + gattlib_disconnect(conn); + } + +} +static void gattlib_connect_thread_cb(GTask *task, gpointer source_object, + gpointer task_data, GCancellable *cancellable) { + DEBUG_GATTLIB("connect thread_cb launched\n"); + gattlib_connect_params_t *data = task_data; + gatt_connection_t * retval; + + /* Handle cancellation. */ + if (g_task_return_error_if_cancelled(task)) { + return; + } + + /* Run the blocking function. */ + retval = gattlib_connect(data->src, data->dst, data->dest_type, + data->sec_level, data->psm, data->mtu); + + if (!retval) { + DEBUG_GATTLIB("async conn returned nuffin' !!!\n"); + // gattlib_async_connect_trigger_conn_cb(NULL); + + } + g_task_return_pointer(task, retval, + gattlib_connect_destroy_unclaimed_connection); + +} + +void gattlib_connect_async_glib(const char *src, const char *dst, + uint8_t dest_type, gattlib_bt_sec_level_t sec_level, int psm, int mtu, + /* gatt_connect_cb_t connect_cb, */ + GCancellable *cancellable, GAsyncReadyCallback callback, + gpointer user_data) { + GTask *task = NULL; /* owned */ + gattlib_connect_params_t *data = NULL; /* owned */ + + DEBUG_GATTLIB("gattlib_connect_async_glib\n"); + + /* g_return_if_fail (src && strlen(src)); */ + /* g_return_if_fail (dst && strlen(dst)); */ + g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new(NULL, cancellable, callback, user_data); + g_task_set_source_tag(task, gattlib_connect_async_glib); + + /* Cancellation should be handled manually using mechanisms specific to + * some_blocking_function(). */ + g_task_set_return_on_cancel(task, FALSE); + + /* Set up a closure containing the call’s parameters. Copy them to avoid + * locking issues between the calling thread and the worker thread. */ + data = g_new0(gattlib_connect_params_t, 1); + if (src) { + data->src = malloc(strlen(src) + 1); + if (data->src) { + strcpy(data->src, src); + } + } + + if (dst) { + data->dst = malloc(strlen(dst) + 1); + if (data->dst) { + strcpy(data->dst, dst); + } + } + data->dest_type = dest_type; + data->sec_level = sec_level; + data->psm = psm; + data->mtu = mtu; + /* data->connect_cb = connect_cb; */ + + g_task_set_task_data(task, data, gattlib_connect_thread_data_free); + + /* Run the task in a worker thread and return immediately while that continues + * in the background. When it’s done it will call @callback in the current + * thread default main context. */ + g_task_run_in_thread(task, gattlib_connect_thread_cb); + + g_object_unref(task); +} + +gatt_connection_t * +gattlib_connect_async_finish(GAsyncResult *result, GError **error) { + DEBUG_GATTLIB("gattlib_connect_async_finish\n"); + g_return_val_if_fail(g_task_is_valid(result, gattlib_connect_async_glib), + NULL); + g_return_val_if_fail(error == NULL || *error == NULL, NULL); + + return g_task_propagate_pointer(G_TASK(result), error); +} + +static +void gattlib_async_connect_ready_cb(GObject *source_object, GAsyncResult *res, + gpointer user_data) { + GError *error = NULL; + gatt_connection_t * connptr; + DEBUG_GATTLIB("gattlib_async_connect_ready_cb\n"); + if (gattlib_async_global_state.connection_done_cb) { + connptr = g_task_propagate_pointer(G_TASK(res), &error); + gattlib_async_connect_trigger_conn_cb(connptr); + } + +} + +int gattlib_connect_async(const char *src, const char *dst, uint8_t dest_type, + gattlib_bt_sec_level_t sec_level, int psm, int mtu, + gatt_connect_cb_t connect_cb) { + + DEBUG_GATTLIB("gattlib_connect_async\n"); + gattlib_async_global_state.connection_done_cb = connect_cb; + gattlib_async_global_state.done_callback = gattlib_async_connect_timeouttrigger_conn_cb; + + gattlib_async_setup_currentloop(8, GATTLIB_ASYNC_LOOP_CREATECONTEXT); + gattlib_connect_async_glib(src, dst, dest_type, sec_level, psm, mtu, NULL, + gattlib_async_connect_ready_cb, NULL); + return 0; + +} + + +static void gattlib_async_disconnect_ready_cb(GObject *source_object, + GAsyncResult *res, gpointer user_data) { + + DEBUG_GATTLIB("\nhah! gattlib_async_disconnect_ready_cb called, freeing stuffz\n"); + gatt_connection_t* connection = user_data; + if (connection) { + gattlib_context_t* conn_context = connection->context; + + free(conn_context->device_object_path); + g_object_unref(conn_context->device); + + free(connection->context); + free(connection); + } + + gattlib_async_quit_currentloop(); +} + +int gattlib_disconnect_async(gatt_connection_t* connection, + gattlib_async_completed_cb_t done_cb) { + DEBUG_GATTLIB("disconn (async)!\n"); + + if (!connection) { + DEBUG_GATTLIB("boo, no connection!\n"); + return 1; + } + gattlib_async_global_state.done_callback = done_cb; + gattlib_context_t* conn_context = connection->context; + + if (gattlib_async_setup_currentloop(20, GATTLIB_ASYNC_LOOP_CREATECONTEXT)) { + DEBUG_GATTLIB("boo, can't setup mainloop!\n"); + return 1; + } + + org_bluez_device1_call_disconnect(conn_context->device, NULL, + gattlib_async_disconnect_ready_cb, connection); + + return 0; +} + + + + +int gattlib_read_char_by_uuid_async(gatt_connection_t* connection, uuid_t* uuid, gatt_read_cb_t gatt_read_cb) { + OrgBluezGattCharacteristic1 *characteristic = get_characteristic_from_uuid(uuid); + if (characteristic == NULL) { + return -1; + } + + GVariant *out_value; + GError *error = NULL; + +#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 40) + org_bluez_gatt_characteristic1_call_read_value_sync( + characteristic, &out_value, NULL, &error); +#else + GVariantBuilder *options = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + org_bluez_gatt_characteristic1_call_read_value_sync( + characteristic, g_variant_builder_end(options), &out_value, NULL, &error); + g_variant_builder_unref(options); +#endif + if (error != NULL) { + return -1; + } + + gsize n_elements; + gconstpointer const_buffer = g_variant_get_fixed_array(out_value, &n_elements, sizeof(guchar)); + if (const_buffer) { + gatt_read_cb(const_buffer, n_elements); + } + + g_object_unref(characteristic); + +#if BLUEZ_VERSION >= BLUEZ_VERSIONS(5, 40) + //g_variant_unref(in_params); See: https://github.com/labapart/gattlib/issues/28#issuecomment-311486629 +#endif + return 0; +} + + +static +void gattlib_async_write_ready_cb(GObject *source_object, + GAsyncResult *res, + gpointer user_data) { + + DEBUG_GATTLIB("\nhah! gattlib_async_write_ready_cb called "); + GError *error = NULL; + + if (org_bluez_gatt_characteristic1_call_write_value_finish( + gattlib_async_global_state.async_proc_data, res, &error) == FALSE) { + + DEBUG_GATTLIB("but had error. %s\n", error->message); + if (gattlib_async_global_state.error_callback) + { + gattlib_async_global_state.done_callback = gattlib_async_global_state.error_callback; + gattlib_async_global_state.error_callback = NULL; + } + } else { + + DEBUG_GATTLIB("and lookin good.\n"); + } + + if ( gattlib_async_global_state.async_proc_data) { + g_object_unref(gattlib_async_global_state.async_proc_data); + gattlib_async_global_state.async_proc_data = NULL; + } + + + gattlib_async_quit_currentloop(); + + + +} + + +int gattlib_write_char_by_uuid_async(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len, + gattlib_async_completed_cb_t done_cb, gattlib_async_error_cb_t error_cb) { + + DEBUG_GATTLIB("write_by_uuid (async)!\n"); + + if (!connection) { + DEBUG_GATTLIB("boo, no connection!\n"); + if (error_cb) { + error_cb(); + } + return 1; + } + + OrgBluezGattCharacteristic1 *characteristic = get_characteristic_from_uuid(uuid); + if (characteristic == NULL) { + + DEBUG_GATTLIB("\nwrite() can't find this characteristic!\n"); + if (error_cb) { + error_cb(); + } + return -1; + } + + GVariant *value = g_variant_new_from_data(G_VARIANT_TYPE ("ay"), buffer, buffer_len, TRUE, NULL, NULL); + + gattlib_async_global_state.error_callback = error_cb; + gattlib_async_global_state.done_callback = done_cb; + gattlib_async_global_state.async_proc_data = characteristic; + gattlib_async_setup_currentloop(10, GATTLIB_ASYNC_LOOP_CREATECONTEXT); +#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 40) + org_bluez_gatt_characteristic1_call_write_value(characteristic, value, NULL, NULL, gattlib_async_write_ready_cb, NULL); +#else + GVariantBuilder *options = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + org_bluez_gatt_characteristic1_call_write_value(characteristic, value, g_variant_builder_end(options), NULL, gattlib_async_write_ready_cb, NULL); + g_variant_builder_unref(options); +#endif + +#if BLUEZ_VERSION >= BLUEZ_VERSIONS(5, 40) + //g_variant_unref(in_params); See: https://github.com/labapart/gattlib/issues/28#issuecomment-311486629 +#endif + return 0; +} + + + + + diff --git a/dbus/gattlib_internal.h b/dbus/gattlib_internal.h index 6dc023cd..8437a7be 100644 --- a/dbus/gattlib_internal.h +++ b/dbus/gattlib_internal.h @@ -23,7 +23,7 @@ #ifndef __GATTLIB_INTERNAL_H__ #define __GATTLIB_INTERNAL_H__ - +#include #include "gattlib.h" #include "org-bluez-adaptater1.h" @@ -42,4 +42,20 @@ typedef struct { OrgBluezDevice1* device; } gattlib_context_t; + + + + +/* define GATTLIB_DEBUG_OUTPUT_ENABLE */ + +#ifdef GATTLIB_DEBUG_OUTPUT_ENABLE +#define DEBUG_GATTLIB(...) fprintf(stderr, __VA_ARGS__) +#else +#define DEBUG_GATTLIB(...) +#endif + +#define ERROR_GATTLIB(...) fprintf(stderr, __VA_ARGS__) + +OrgBluezGattCharacteristic1 *get_characteristic_from_uuid(const uuid_t* uuid); + #endif diff --git a/examples/ble_scan_async/CMakeLists.txt b/examples/ble_scan_async/CMakeLists.txt new file mode 100644 index 00000000..947196bc --- /dev/null +++ b/examples/ble_scan_async/CMakeLists.txt @@ -0,0 +1,36 @@ +# +# BLE scan async example +# +# Copyright (C) 2017 Pat Deegan, psychogenic.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 +# + +cmake_minimum_required(VERSION 2.6) + +find_package(PkgConfig REQUIRED) + +pkg_search_module(GATTLIB REQUIRED gattlib) + +# Added Glib support to ensure we have 'glib loops' +pkg_search_module(GLIB REQUIRED glib-2.0) + +include_directories(${GLIB_INCLUDE_DIRS}) + +set(ble_scan_async_SRCS ble_scan_async.c) + +add_executable(ble_scan_async ${ble_scan_async_SRCS}) +target_link_libraries(ble_scan_async ${GATTLIB_LDFLAGS} ${GLIB_LDFLAGS} pthread) diff --git a/examples/ble_scan_async/ble_scan_async.c b/examples/ble_scan_async/ble_scan_async.c new file mode 100644 index 00000000..4af74808 --- /dev/null +++ b/examples/ble_scan_async/ble_scan_async.c @@ -0,0 +1,291 @@ +/* + * ble_scan_async + * + * Copyright (C) 2017 Pat Deegan, psychogenic.com + * + * + * Look 'ma, no threads! + * + * This is an async version of the ble_scan example, using relevant + * async calls to scan/connect, and callbacks to implement the logic. + * + * The point of this sample is that threads could be used, + * for instance to drive the process_async() calls, but aren't + * required in order to get on with other business while the BLE + * stuff is happening in the background. + * + * + * 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.h" + + +#define BLE_SCAN_TIMEOUT 5 +#define MAINLOOP_SLEEP_US 10000 + +typedef void (*ble_discovered_device_t)(const char* addr, const char* name); + + +LIST_HEAD(listhead, connection_t) g_ble_connections; +struct connection_t { + pthread_t thread; + char* addr; + LIST_ENTRY(connection_t) entries; +}; + +/* + * ble_discovered_device -- callback used by scan to report devices. + * Simply adds the device to our g_ble_connections list. + */ +static void ble_discovered_device(const char* addr, const char* name) { + struct connection_t *connection; + + if (name) { + fprintf(stderr, "Discovered %s - '%s'\n", addr, name); + } else { + fprintf(stderr, "Discovered %s\n", addr); + } + + connection = malloc(sizeof(struct connection_t)); + if (connection == NULL) { + fprintf(stderr, "Failed to allocate connection.\n"); + return; + } + connection->addr = strdup(addr); + + LIST_INSERT_HEAD(&g_ble_connections, connection, entries); +} + + + +/* + * adapter -- global used to store pointer to the BLE adapter + */ +void* adapter = NULL; + +/* + * AllDone -- global flag to indicate + * program is finished. + */ +int AllDone = 0; + +/* forward decl */ +void connectAndDiscoverNext(); + +/* clear out connection from our list, once handled */ +void removeCurrentConnectionFromList() { + struct connection_t * connection = g_ble_connections.lh_first; + LIST_REMOVE(g_ble_connections.lh_first, entries); + free(connection->addr); + free(connection); +} + +/* process next scanned device */ +void connectionMoveToNext() { + + removeCurrentConnectionFromList(); + connectAndDiscoverNext(); +} + +/* async connection completed callback: discovers the services + * and chars of a device, after connection is established. + * + * This function actually calls blocking versions of the discovery + * functions, as + * a) I have yet to actually implement async versions; + * b) using async versions would make the string of callbacks/async + * calls pretty unweildy; and + * c) they seem pretty fast in any case. + * + * It does, however, use the async disconnect function, with it's callback + * triggering the next connection+discovery. + */ +void connectionEstablishedCb(gatt_connection_t* gatt_connection) { + gattlib_primary_service_t* services; + gattlib_characteristic_t* characteristics; + int services_count, characteristics_count; + char uuid_str[MAX_LEN_UUID_STR + 1]; + int ret, i; + + if (! gatt_connection) { + connectionMoveToNext(); + return; + } + + struct connection_t* connection = g_ble_connections.lh_first; + + char* addr = connection->addr; + + fprintf(stderr, "\n------------START %s ---------------\n", addr); + + ret = gattlib_discover_primary(gatt_connection, &services, &services_count); + if (ret != 0) { + fprintf(stderr, "Fail to discover primary services.\n"); + goto disconnect_exit; + } + + for (i = 0; i < services_count; i++) { + gattlib_uuid_to_string(&services[i].uuid, uuid_str, sizeof(uuid_str)); + + fprintf(stderr, "service[%d] start_handle:%02x end_handle:%02x uuid:%s\n", i, + services[i].attr_handle_start, services[i].attr_handle_end, + uuid_str); + } + free(services); + + ret = gattlib_discover_char(gatt_connection, &characteristics, &characteristics_count); + if (ret != 0) { + fprintf(stderr, "Fail to discover characteristics.\n"); + goto disconnect_exit; + } + for (i = 0; i < characteristics_count; i++) { + gattlib_uuid_to_string(&characteristics[i].uuid, uuid_str, sizeof(uuid_str)); + + fprintf(stderr, "characteristic[%d] properties:%02x value_handle:%04x uuid:%s\n", i, + characteristics[i].properties, characteristics[i].value_handle, + uuid_str); + } + free(characteristics); + +disconnect_exit: + gattlib_disconnect_async(gatt_connection, connectionMoveToNext); + fprintf(stderr, "------------DONE %s ---------------\n", addr); + + +} + +/* + * connectAndDiscoverNext connect to, and then discover chars, + * for next ble device in list. + * + */ +void connectAndDiscoverNext() { + + if (g_ble_connections.lh_first == NULL) { + AllDone = 1; + fprintf(stderr, "No more connections to scan"); + return; + } + + struct connection_t* connection = g_ble_connections.lh_first; + + char* addr = connection->addr; + + fprintf(stderr, "\nDoing async conn to next... @ %s\n" , addr); + /* call async connect with connectionEstablishedCb callback */ + gattlib_connect_async(NULL, addr, BDADDR_LE_PUBLIC, + BT_SEC_LOW, 0, 0, connectionEstablishedCb); + + +} + +/* + * scanDisabledCb -- called when scan is turned off, + * triggers the start of connection+discovery chain. + */ +void scanDisabledCb() { + fprintf(stderr, "\nscan now disabled!\n"); + connectAndDiscoverNext(); +} + +/* + * scanCompleteCb -- called when our + * async scanning interval times out, and calls + * async scan disable. + * + */ +void scanCompleteCb() { + fprintf(stderr, "\nscanCompleteCb! startup scan disable\n"); + gattlib_adapter_scan_disable_async(adapter, scanDisabledCb); +} + + +int main(int argc, const char *argv[]) { + const char* adapter_name; + int ret; + + if (argc == 1) { + adapter_name = NULL; + } else if (argc == 2) { + adapter_name = argv[1]; + } else { + fprintf(stderr, "%s []\n", argv[0]); + return 1; + } + + LIST_INIT(&g_ble_connections); + + /* open the adapter */ + ret = gattlib_adapter_open(adapter_name, &adapter); + if (ret) { + fprintf(stderr, "ERROR: Failed to open adapter.\n"); + return 1; + } + + /* + * Could use a state machine or whatever, but to keep things + * simple and demo the async stuff, we've setup a chain of + * callbacks, above. Each one will trigger the next step. + * + * To get the ball rolling, we start by scanning the environment, + * with an async call to scan_enable. It will keep scanning + * until BLE_SCAN_TIMEOUT expires, then trigger its "done + * callback", which will in turn call async scan_disable, etc. + */ + + ret = gattlib_adapter_scan_enable_async(adapter, + ble_discovered_device, BLE_SCAN_TIMEOUT, scanCompleteCb); + + /* + * Now comes our main loop. In this toy example, I don't have much to + * do, but this is where you'd process user events, UI stuff, whatever. + * + * In this case, all we do is call one of the async_process functions, + * output the occasional character just to show we're still running, and + * spend lots of time sleeping. + * + * The point is that this loop is simple, with all the heavy lifting + * happening in callbacks, and no threads needed in this code. + */ + int counter = 0; + while (! AllDone) { + /* could do gattlib_async_process_all(), but since we're looping 'often' we + * just call: + */ + + gattlib_async_process(); + + if (counter++ == 5) + { + fprintf(stderr, "o"); + } else if (counter == 10 ) { + counter = 0; + fprintf(stderr, "O"); + } + usleep(MAINLOOP_SLEEP_US); + } + + fprintf(stderr, "\n\nOk, we're all done!\nGoodbye\n"); + gattlib_adapter_close(adapter); + return 0; +} diff --git a/include/gattlib.h b/include/gattlib.h index 1740a719..530989da 100644 --- a/include/gattlib.h +++ b/include/gattlib.h @@ -28,6 +28,7 @@ extern "C" { #endif + #include #include @@ -67,6 +68,7 @@ typedef enum { typedef struct _GAttrib GAttrib; + typedef void (*gattlib_event_handler_t)(const uuid_t* uuid, const uint8_t* data, size_t data_length, void* user_data); typedef struct _gatt_connection_t { @@ -84,14 +86,96 @@ typedef void (*gatt_connect_cb_t)(gatt_connection_t* connection); typedef void* (*gatt_read_cb_t)(const void* buffer, size_t buffer_len); + +/* + * **** Asynchronous Operations **** + * + * Asynchronous operations support added by Pat Deegan, psychogenic.com + * Two NOTES: + * 1) they're async, but only use them "one at a time" -- meaning wait + * until operation A completes before starting operation B, and + * 2) you need to allocate time to handle events/trigger callbacks, + * using gattlib_async_process/gattlib_async_process_all, as described + * below. + * + * Now have async versions of the worst-offenders (slowest/longest blocking) + * are now available, including: + * + * - gattlib_adapter_scan_enable_async + * - gattlib_adapter_scan_disable_async + * - gattlib_connect_async + * - gattlib_disconnect_async + * - gattlib_read_char_by_uuid_async + * - gattlib_write_char_by_uuid_async + * + * These operations can be launched while you keep doing other things, + * e.g. handling user events. However, they need some time allocated + * in order to process events, trigger callbacks, etc. The simplest + * way to do this is to periodically give them a moment in your main + * loop, eg + * + * while (doKeepGoing) { + * // handle user events + * // do processing, etc. + * + * // process any async events + * gattlib_async_process(); + * + * } + * + * See ble_scan_async example and/or gattlib_async.c, here. + * + */ + + + + + +/* gattlib_async_completed_cb_t -- generic "async is done" callback + * signature: void X(void); + */ +typedef void (*gattlib_async_completed_cb_t)(void); +typedef gattlib_async_completed_cb_t gattlib_async_error_cb_t; +/* + * async process: the async calls (*_async()) need slots in which to process + * pending events and do their thing. This can be in a thread, + * as part of your main loop, or whatever... just need to be called + * periodically. + * + * gattlib_async_process() -- does one iteration of event processing + * + * gattlib_async_process_all() -- processes all pending events in one go. + */ +int gattlib_async_process(); +int gattlib_async_process_all(); + + + + + + /** * Open Bluetooth adapter * * @adapter_name With value NULL, the default adapter will be selected. */ int gattlib_adapter_open(const char* adapter_name, void** adapter); + +/* gattlib_adapter_powered return gboolean TRUE (1) if powered, 0 otherwise */ +int gattlib_adapter_powered(void* adapter); + + int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t discovered_device_cb, int timeout); +int gattlib_adapter_scan_enable_async(void* adapter, gattlib_discovered_device_t discovered_device_cb, + int timeout, gattlib_async_completed_cb_t done_cb); + + + int gattlib_adapter_scan_disable(void* adapter); +int gattlib_adapter_scan_disable_async(void* adapter, + gattlib_async_completed_cb_t done_cb); + + int gattlib_adapter_close(void* adapter); /** @@ -105,11 +189,15 @@ int gattlib_adapter_close(void* adapter); gatt_connection_t *gattlib_connect(const char *src, const char *dst, uint8_t dest_type, gattlib_bt_sec_level_t sec_level, int psm, int mtu); -gatt_connection_t *gattlib_connect_async(const char *src, const char *dst, +int gattlib_connect_async(const char *src, const char *dst, uint8_t dest_type, gattlib_bt_sec_level_t sec_level, int psm, int mtu, gatt_connect_cb_t connect_cb); + + int gattlib_disconnect(gatt_connection_t* connection); +int gattlib_disconnect_async(gatt_connection_t* connection, + gattlib_async_completed_cb_t done_cb); typedef struct { uint16_t attr_handle_start; @@ -142,13 +230,16 @@ int gattlib_read_char_by_uuid_async(gatt_connection_t* connection, uuid_t* uuid, int gattlib_write_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len); int gattlib_write_char_by_handle(gatt_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len); +int gattlib_write_char_by_uuid_async(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len, + gattlib_async_completed_cb_t done_cb, gattlib_async_error_cb_t error_cb); /* * @param uuid UUID of the characteristic that will trigger the notification */ int gattlib_notification_start(gatt_connection_t* connection, const uuid_t* uuid); int gattlib_notification_stop(gatt_connection_t* connection, const uuid_t* uuid); -void gattlib_register_notification(gatt_connection_t* connection, gattlib_event_handler_t notification_handler, void* user_data); +void gattlib_register_notification(gatt_connection_t* connection, + gattlib_event_handler_t notification_handler, void* user_data); void gattlib_register_indication(gatt_connection_t* connection, gattlib_event_handler_t indication_handler, void* user_data); int gattlib_uuid_to_string(const uuid_t *uuid, char *str, size_t n); From f72f62c7ddb0a2d5d5c80215b9095fbd089c649e Mon Sep 17 00:00:00 2001 From: Pat Deegan Date: Wed, 20 Dec 2017 13:21:48 -0500 Subject: [PATCH 02/12] Fix for failing build under older bluez installations, had to rename async connect function and break naming convention... --- dbus/gattlib_async.c | 2 +- examples/ble_scan_async/ble_scan_async.c | 2 +- include/gattlib.h | 14 ++++++++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/dbus/gattlib_async.c b/dbus/gattlib_async.c index c53c841a..84c58479 100644 --- a/dbus/gattlib_async.c +++ b/dbus/gattlib_async.c @@ -550,7 +550,7 @@ void gattlib_async_connect_ready_cb(GObject *source_object, GAsyncResult *res, } -int gattlib_connect_async(const char *src, const char *dst, uint8_t dest_type, +int gattlib_async_connect(const char *src, const char *dst, uint8_t dest_type, gattlib_bt_sec_level_t sec_level, int psm, int mtu, gatt_connect_cb_t connect_cb) { diff --git a/examples/ble_scan_async/ble_scan_async.c b/examples/ble_scan_async/ble_scan_async.c index 4af74808..028026ab 100644 --- a/examples/ble_scan_async/ble_scan_async.c +++ b/examples/ble_scan_async/ble_scan_async.c @@ -193,7 +193,7 @@ void connectAndDiscoverNext() { fprintf(stderr, "\nDoing async conn to next... @ %s\n" , addr); /* call async connect with connectionEstablishedCb callback */ - gattlib_connect_async(NULL, addr, BDADDR_LE_PUBLIC, + gattlib_async_connect(NULL, addr, BDADDR_LE_PUBLIC, BT_SEC_LOW, 0, 0, connectionEstablishedCb); diff --git a/include/gattlib.h b/include/gattlib.h index 530989da..ff58dd1f 100644 --- a/include/gattlib.h +++ b/include/gattlib.h @@ -103,7 +103,7 @@ typedef void* (*gatt_read_cb_t)(const void* buffer, size_t buffer_len); * * - gattlib_adapter_scan_enable_async * - gattlib_adapter_scan_disable_async - * - gattlib_connect_async + * - gattlib_async_connect (yes, I know the naming is weird, see below) * - gattlib_disconnect_async * - gattlib_read_char_by_uuid_async * - gattlib_write_char_by_uuid_async @@ -189,7 +189,17 @@ int gattlib_adapter_close(void* adapter); gatt_connection_t *gattlib_connect(const char *src, const char *dst, uint8_t dest_type, gattlib_bt_sec_level_t sec_level, int psm, int mtu); -int gattlib_connect_async(const char *src, const char *dst, +/* original version of this function name, used under older bluez implementation, + * not sure how it works or how it's async... + * + */ +gatt_connection_t *gattlib_connect_async(const char *src, const char *dst, + uint8_t dest_type, gattlib_bt_sec_level_t sec_level, int psm, int mtu, + gatt_connect_cb_t connect_cb); +/* + * oddly named async with callback connect(), see above + */ +int gattlib_async_connect(const char *src, const char *dst, uint8_t dest_type, gattlib_bt_sec_level_t sec_level, int psm, int mtu, gatt_connect_cb_t connect_cb); From c2006a06363bb27370cc9c6f205492e31ff786df Mon Sep 17 00:00:00 2001 From: Pat Deegan Date: Wed, 20 Dec 2017 14:35:38 -0500 Subject: [PATCH 03/12] async functions currently only supported on dbus-based systems--older bluez is a whole 'nother story. --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f99d24dc..589f1684 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,9 @@ set(ENV{PKG_CONFIG_PATH} "${PROJECT_BINARY_DIR}:$ENV{PKG_CONFIG_PATH}") # Examples add_subdirectory(examples/ble_scan) +if (GATTLIB_DBUS) add_subdirectory(examples/ble_scan_async) +endif() add_subdirectory(examples/discover) add_subdirectory(examples/read_write) add_subdirectory(examples/notification) From a290c60954592399a9a1557b8d0afeb1242364ca Mon Sep 17 00:00:00 2001 From: Pat Deegan Date: Tue, 2 Jan 2018 07:59:33 -0500 Subject: [PATCH 04/12] Fix to provide a context to the connect call's main loop, to avoid interfering with glib based apps. --- CMakeLists.txt | 4 ++-- dbus/gattlib.c | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 589f1684..7a6bb355 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,9 +99,9 @@ set(CPACK_PACKAGE_INSTALL_DIRECTORY /usr CACHE STRING "Install directory (defaul if (ENV{TRAVIS_TAG}) set(CPACK_PACKAGE_VERSION $ENV{TRAVIS_TAG}) else() - set(CPACK_PACKAGE_VERSION 0.2-dev) + set(CPACK_PACKAGE_VERSION 0.3.1) endif() -set(CPACK_PACKAGE_CONTACT "Olivier Martin ") +set(CPACK_PACKAGE_CONTACT "Pat Deegan ") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Library to access GATT information from Bluetooth Low Energy (BLE) devices") set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${CPACK_PACKAGE_VERSION}_${CPACK_PACKAGE_ARCHITECTURE}") diff --git a/dbus/gattlib.c b/dbus/gattlib.c index 916a02e0..55718f80 100644 --- a/dbus/gattlib.c +++ b/dbus/gattlib.c @@ -328,11 +328,10 @@ gatt_connection_t *gattlib_connect(const char *src, const char *dst, - - DEBUG_GATTLIB("gattlib_connect starting loop (timeout %i s.)\n", CONNECT_TIMEOUT); - GMainLoop *loop = g_main_loop_new(NULL, 0); + GMainContext * loopyContext = g_main_context_new(); + GMainLoop *loop = g_main_loop_new(loopyContext, 0); // Register a handle for notification g_signal_connect(device, "g-properties-changed", @@ -341,6 +340,7 @@ gatt_connection_t *gattlib_connect(const char *src, const char *dst, g_timeout_add_seconds (CONNECT_TIMEOUT, loop_timeout_func, loop); g_main_loop_run(loop); + g_main_context_unref(loopyContext); g_main_loop_unref(loop); return connection; From d6543e5d99101aa423d933cb08728a284944c0da Mon Sep 17 00:00:00 2001 From: Pat Deegan Date: Tue, 2 Jan 2018 18:47:09 -0500 Subject: [PATCH 05/12] Longer timeouts for sloooow devices --- dbus/gattlib_async.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dbus/gattlib_async.c b/dbus/gattlib_async.c index 84c58479..a6d44b87 100644 --- a/dbus/gattlib_async.c +++ b/dbus/gattlib_async.c @@ -380,7 +380,7 @@ int gattlib_adapter_scan_disable_async(void* adapter, gattlib_async_completed_cb DEBUG_GATTLIB("scan disable prep done, calling stop discovery.\n"); gattlib_async_global_state.done_callback = done_cb; - gattlib_async_setup_currentloop(20, GATTLIB_ASYNC_LOOP_CREATECONTEXT); + gattlib_async_setup_currentloop(25, GATTLIB_ASYNC_LOOP_CREATECONTEXT); org_bluez_adapter1_call_stop_discovery((OrgBluezAdapter1*)adapter, NULL, gattlib_async_scandisable_ready_cb, NULL); return 0; @@ -558,7 +558,7 @@ int gattlib_async_connect(const char *src, const char *dst, uint8_t dest_type, gattlib_async_global_state.connection_done_cb = connect_cb; gattlib_async_global_state.done_callback = gattlib_async_connect_timeouttrigger_conn_cb; - gattlib_async_setup_currentloop(8, GATTLIB_ASYNC_LOOP_CREATECONTEXT); + gattlib_async_setup_currentloop(25, GATTLIB_ASYNC_LOOP_CREATECONTEXT); gattlib_connect_async_glib(src, dst, dest_type, sec_level, psm, mtu, NULL, gattlib_async_connect_ready_cb, NULL); return 0; @@ -595,7 +595,7 @@ int gattlib_disconnect_async(gatt_connection_t* connection, gattlib_async_global_state.done_callback = done_cb; gattlib_context_t* conn_context = connection->context; - if (gattlib_async_setup_currentloop(20, GATTLIB_ASYNC_LOOP_CREATECONTEXT)) { + if (gattlib_async_setup_currentloop(25, GATTLIB_ASYNC_LOOP_CREATECONTEXT)) { DEBUG_GATTLIB("boo, can't setup mainloop!\n"); return 1; } @@ -709,7 +709,7 @@ int gattlib_write_char_by_uuid_async(gatt_connection_t* connection, uuid_t* uuid gattlib_async_global_state.error_callback = error_cb; gattlib_async_global_state.done_callback = done_cb; gattlib_async_global_state.async_proc_data = characteristic; - gattlib_async_setup_currentloop(10, GATTLIB_ASYNC_LOOP_CREATECONTEXT); + gattlib_async_setup_currentloop(12, GATTLIB_ASYNC_LOOP_CREATECONTEXT); #if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 40) org_bluez_gatt_characteristic1_call_write_value(characteristic, value, NULL, NULL, gattlib_async_write_ready_cb, NULL); #else From 2c15befd893c9b0a62da246c669b920c77179e6e Mon Sep 17 00:00:00 2001 From: Pat Deegan Date: Tue, 2 Jan 2018 18:47:44 -0500 Subject: [PATCH 06/12] Manual loop on connect, to both avoid interfering with glib-mainloop higher up while catching the dbus signal on connect. --- CMakeLists.txt | 2 +- dbus/gattlib.c | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a6bb355..226eb8a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,7 +99,7 @@ set(CPACK_PACKAGE_INSTALL_DIRECTORY /usr CACHE STRING "Install directory (defaul if (ENV{TRAVIS_TAG}) set(CPACK_PACKAGE_VERSION $ENV{TRAVIS_TAG}) else() - set(CPACK_PACKAGE_VERSION 0.3.1) + set(CPACK_PACKAGE_VERSION 0.3.2) endif() set(CPACK_PACKAGE_CONTACT "Pat Deegan ") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Library to access GATT information from Bluetooth Low Energy (BLE) devices") diff --git a/dbus/gattlib.c b/dbus/gattlib.c index 55718f80..96df1b7d 100644 --- a/dbus/gattlib.c +++ b/dbus/gattlib.c @@ -28,7 +28,7 @@ #include "gattlib_internal.h" -#define CONNECT_TIMEOUT 4 +#define CONNECT_TIMEOUT 20 int gattlib_adapter_open(const char* adapter_name, void** adapter) { char object_path[20]; @@ -330,6 +330,8 @@ gatt_connection_t *gattlib_connect(const char *src, const char *dst, DEBUG_GATTLIB("gattlib_connect starting loop (timeout %i s.)\n", CONNECT_TIMEOUT); + // run this in its own context to avoid freezing + // any glib-loop-based app GMainContext * loopyContext = g_main_context_new(); GMainLoop *loop = g_main_loop_new(loopyContext, 0); // Register a handle for notification @@ -339,7 +341,15 @@ gatt_connection_t *gattlib_connect(const char *src, const char *dst, loop); g_timeout_add_seconds (CONNECT_TIMEOUT, loop_timeout_func, loop); - g_main_loop_run(loop); + /* have to do the loop manually, as the signal will arrive + * on the main/NULL loop + */ + while (g_main_loop_is_running(loop)) + { + g_main_context_iteration(loopyContext, FALSE); + g_main_context_iteration(NULL, FALSE); + } + g_main_context_unref(loopyContext); g_main_loop_unref(loop); From 74661330b07407bec96d18f223d3b655ac686b75 Mon Sep 17 00:00:00 2001 From: Pat Deegan Date: Tue, 2 Jan 2018 19:03:04 -0500 Subject: [PATCH 07/12] Changes to cmake config to fix pc file prefix --- CMakeLists.txt | 56 +++++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 226eb8a8..77273610 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,30 +71,16 @@ else() link_directories(${PROJECT_BINARY_DIR}/bluez) endif() -# Generate pkg-config file before building the examples -configure_file(dbus/gattlib.pc.in ${PROJECT_BINARY_DIR}/gattlib.pc @ONLY) - -# Add the build directory to PKG_CONFIG_PATH -set(ENV{PKG_CONFIG_PATH} "${PROJECT_BINARY_DIR}:$ENV{PKG_CONFIG_PATH}") - -# Examples -add_subdirectory(examples/ble_scan) -if (GATTLIB_DBUS) -add_subdirectory(examples/ble_scan_async) -endif() -add_subdirectory(examples/discover) -add_subdirectory(examples/read_write) -add_subdirectory(examples/notification) -add_subdirectory(examples/nordic_uart) - -# Some examples require Bluez code and other DBus support -if (NOT GATTLIB_DBUS) - add_subdirectory(examples/gatttool) -endif() # -# Packaging +# Packaging (this needs to be before the pc file setup to take effect) # + +set(CPACK_SET_DESTDIR true) +set(CPACK_PACKAGE_INSTALL_DIRECTORY "/usr" CACHE STRING "Install directory (default: /usr).") +set(CPACK_INSTALL_PREFIX ${CPACK_PACKAGE_INSTALL_DIRECTORY} CACHE STRING "Install directory (default: /usr).") +set(CPACK_PACKAGING_PREFIX ${CPACK_PACKAGE_INSTALL_DIRECTORY} CACHE STRING "Install directory (default: /usr).") + set(CPACK_PACKAGE_INSTALL_DIRECTORY /usr CACHE STRING "Install directory (default: /usr).") if (ENV{TRAVIS_TAG}) set(CPACK_PACKAGE_VERSION $ENV{TRAVIS_TAG}) @@ -127,6 +113,34 @@ if (GATTLIB_DBUS AND (BLUEZ_VERSION_MAJOR EQUAL 5) AND (BLUEZ_VERSION_MINOR GREA set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}, bluez (>= 5.40)") endif() + + + + + +# Generate pkg-config file before building the examples +configure_file(dbus/gattlib.pc.in ${PROJECT_BINARY_DIR}/gattlib.pc @ONLY) + +# Add the build directory to PKG_CONFIG_PATH +set(ENV{PKG_CONFIG_PATH} "${PROJECT_BINARY_DIR}:$ENV{PKG_CONFIG_PATH}") + +# Examples +add_subdirectory(examples/ble_scan) +if (GATTLIB_DBUS) +add_subdirectory(examples/ble_scan_async) +endif() +add_subdirectory(examples/discover) +add_subdirectory(examples/read_write) +add_subdirectory(examples/notification) +add_subdirectory(examples/nordic_uart) + +# Some examples require Bluez code and other DBus support +if (NOT GATTLIB_DBUS) + add_subdirectory(examples/gatttool) +endif() + + + # # List of file to install # From eacee8c5f1f41d5c3544ffc0b75024a0ddeac8d1 Mon Sep 17 00:00:00 2001 From: Pat Deegan Date: Tue, 2 Jan 2018 19:15:26 -0500 Subject: [PATCH 08/12] Added binary release files for the async (psychogenic fork) --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e38293c0..6cb8359d 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,18 @@ cmake -DGATTLIB_FORCE_DBUS=TRUE .. make ``` -Latest GattLib Release packages -=============================== +Latest GattLib (Async support) Release packages +=============================================== -* For x86_64, with Bluez DBUS Support (Recommended): +* For x86_64, with Bluez DBUS Support (Recommended, and required for Gattlib++ and Coraline): - - ZIP: - - DEB: - - RPM: + + - ZIP: + - DEB: + - RPM: + +Latest GattLib (original) packages (no async, as of yet) +======================================================== * For x86_64, with Bluez Legacy Support: From 7462949e17c65fbefd93174f180a0d229c4855cb Mon Sep 17 00:00:00 2001 From: Pat Deegan Date: Tue, 2 Jan 2018 20:19:27 -0500 Subject: [PATCH 09/12] Bug fix for connect loop--manual iterations need to be flagged as running. --- CMakeLists.txt | 2 +- dbus/gattlib.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 77273610..06bf4e59 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,7 +85,7 @@ set(CPACK_PACKAGE_INSTALL_DIRECTORY /usr CACHE STRING "Install directory (defaul if (ENV{TRAVIS_TAG}) set(CPACK_PACKAGE_VERSION $ENV{TRAVIS_TAG}) else() - set(CPACK_PACKAGE_VERSION 0.3.2) + set(CPACK_PACKAGE_VERSION 0.3.3) endif() set(CPACK_PACKAGE_CONTACT "Pat Deegan ") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Library to access GATT information from Bluetooth Low Energy (BLE) devices") diff --git a/dbus/gattlib.c b/dbus/gattlib.c index 96df1b7d..fef302a3 100644 --- a/dbus/gattlib.c +++ b/dbus/gattlib.c @@ -333,7 +333,7 @@ gatt_connection_t *gattlib_connect(const char *src, const char *dst, // run this in its own context to avoid freezing // any glib-loop-based app GMainContext * loopyContext = g_main_context_new(); - GMainLoop *loop = g_main_loop_new(loopyContext, 0); + GMainLoop *loop = g_main_loop_new(loopyContext, TRUE); // Register a handle for notification g_signal_connect(device, "g-properties-changed", From 4ef727289f071dacf9c6132413ca7e549708b4d2 Mon Sep 17 00:00:00 2001 From: Pat Deegan Date: Tue, 2 Jan 2018 20:21:10 -0500 Subject: [PATCH 10/12] Updated links for release --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6cb8359d..0e130383 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ Latest GattLib (Async support) Release packages * For x86_64, with Bluez DBUS Support (Recommended, and required for Gattlib++ and Coraline): - - ZIP: - - DEB: - - RPM: + - ZIP: + - DEB: + - RPM: Latest GattLib (original) packages (no async, as of yet) ======================================================== From 838ae20eeec65e2749e122e614cc5e0bbc7e84b0 Mon Sep 17 00:00:00 2001 From: Pat Deegan Date: Wed, 3 Jan 2018 00:24:39 -0500 Subject: [PATCH 11/12] Fix (and mess-o-debugging) for the snafu introduced with connect's own context. --- CMakeLists.txt | 2 +- dbus/gattlib.c | 63 +++++++++++++++++++++++++++++++++++++---- dbus/gattlib_async.c | 37 +++++++++++++++++++----- dbus/gattlib_internal.h | 8 ++++++ 4 files changed, 97 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 06bf4e59..2b85f52b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,7 +85,7 @@ set(CPACK_PACKAGE_INSTALL_DIRECTORY /usr CACHE STRING "Install directory (defaul if (ENV{TRAVIS_TAG}) set(CPACK_PACKAGE_VERSION $ENV{TRAVIS_TAG}) else() - set(CPACK_PACKAGE_VERSION 0.3.3) + set(CPACK_PACKAGE_VERSION 0.3.4) endif() set(CPACK_PACKAGE_CONTACT "Pat Deegan ") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Library to access GATT information from Bluetooth Low Energy (BLE) devices") diff --git a/dbus/gattlib.c b/dbus/gattlib.c index fef302a3..ff240811 100644 --- a/dbus/gattlib.c +++ b/dbus/gattlib.c @@ -28,7 +28,11 @@ #include "gattlib_internal.h" -#define CONNECT_TIMEOUT 20 +#define CONNECT_TIMEOUT 4 + +#ifdef GATTLIB_DEBUG_OUTPUT_ENABLE +int debug_num_loops = 0 ; +#endif int gattlib_adapter_open(const char* adapter_name, void** adapter) { char object_path[20]; @@ -73,6 +77,8 @@ int gattlib_adapter_powered(void* adapter) { static gboolean loop_timeout_func(gpointer data) { DEBUG_GATTLIB("\nloop_timeout_func()!\n"); + DEBUG_GATTLIB("g_main_loop_quit\n"); + DEBUG_DEC_NUMLOOPS(); g_main_loop_quit(data); return FALSE; } @@ -196,6 +202,8 @@ int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t disco } // Run Glib loop for 'timeout' seconds + DEBUG_GATTLIB("g_main_loop_new (def context)\n"); + DEBUG_INC_NUMLOOPS(); GMainLoop *loop = g_main_loop_new(NULL, 0); g_timeout_add_seconds (timeout, stop_scan_func, loop); g_main_loop_run(loop); @@ -231,6 +239,7 @@ gboolean on_handle_device_property_change( { GMainLoop *loop = user_data; + DEBUG_GATTLIB("\non_handle_device_property_change\n"); // Retrieve 'Value' from 'arg_changed_properties' if (g_variant_n_children (arg_changed_properties) > 0) { GVariantIter *iter; @@ -239,9 +248,13 @@ gboolean on_handle_device_property_change( g_variant_get (arg_changed_properties, "a{sv}", &iter); while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) { + DEBUG_GATTLIB(" key: %s \n", key); if (strcmp(key, "UUIDs") == 0) { + //if (1) { + DEBUG_GATTLIB(" Quitting loop because of prop change\n"); + DEBUG_DEC_NUMLOOPS(); g_main_loop_quit(loop); - break; + return FALSE; } } } @@ -299,6 +312,7 @@ gatt_connection_t *gattlib_connect(const char *src, const char *dst, connection->context = conn_context; } + DEBUG_GATTLIB("getting bluez dev proxy..."); OrgBluezDevice1* device = org_bluez_device1_proxy_new_for_bus_sync( G_BUS_TYPE_SYSTEM, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, @@ -315,6 +329,7 @@ gatt_connection_t *gattlib_connect(const char *src, const char *dst, } + DEBUG_GATTLIB("got it, calling dev connect_sync\n"); error = NULL; org_bluez_device1_call_connect_sync(device, NULL, &error); if (error) { @@ -328,12 +343,19 @@ gatt_connection_t *gattlib_connect(const char *src, const char *dst, - DEBUG_GATTLIB("gattlib_connect starting loop (timeout %i s.)\n", CONNECT_TIMEOUT); + DEBUG_GATTLIB("gattlib_connect starting loop (timeout %i s.)", CONNECT_TIMEOUT); +#define CONNECT_USES_OWN_CONTEXT +#ifdef CONNECT_USES_OWN_CONTEXT // run this in its own context to avoid freezing // any glib-loop-based app + + + + DEBUG_GATTLIB("g_main_loop_new (own context)\n"); + DEBUG_INC_NUMLOOPS(); GMainContext * loopyContext = g_main_context_new(); - GMainLoop *loop = g_main_loop_new(loopyContext, TRUE); + GMainLoop *loop = g_main_loop_new(loopyContext, 0); // Register a handle for notification g_signal_connect(device, "g-properties-changed", @@ -341,16 +363,47 @@ gatt_connection_t *gattlib_connect(const char *src, const char *dst, loop); g_timeout_add_seconds (CONNECT_TIMEOUT, loop_timeout_func, loop); + g_main_loop_run(loop); + g_main_context_unref(loopyContext); + + +#if 0 + GMainContext * loopyContext = g_main_context_new(); + GMainLoop *loop = g_main_loop_new(loopyContext, TRUE); + // Register a handle for notification + g_signal_connect(device, + "g-properties-changed", + G_CALLBACK (on_handle_device_property_change), + loop); + + guint tout = g_timeout_add_seconds (6, loop_timeout_func, loop); + // guint tout = g_timeout_add_seconds (CONNECT_TIMEOUT, loop_timeout_func, loop); /* have to do the loop manually, as the signal will arrive * on the main/NULL loop */ while (g_main_loop_is_running(loop)) { + // DEBUG_GATTLIB("."); g_main_context_iteration(loopyContext, FALSE); g_main_context_iteration(NULL, FALSE); } - + g_source_remove(tout); g_main_context_unref(loopyContext); +#endif + +#else + DEBUG_GATTLIB("g_main_loop_new (def context)\n"); + DEBUG_INC_NUMLOOPS(); + GMainLoop *loop = g_main_loop_new(NULL, TRUE); + // Register a handle for notification + g_signal_connect(device, + "g-properties-changed", + G_CALLBACK (on_handle_device_property_change), + loop); + + guint tout = g_timeout_add_seconds (6, loop_timeout_func, loop); + g_main_loop_run(loop); +#endif g_main_loop_unref(loop); return connection; diff --git a/dbus/gattlib_async.c b/dbus/gattlib_async.c index a6d44b87..70a2ca29 100644 --- a/dbus/gattlib_async.c +++ b/dbus/gattlib_async.c @@ -109,7 +109,18 @@ static volatile gattlib_async_state_t gattlib_async_global_state = { 0 }; static void gattlib_async_quit_currentloop() { DEBUG_GATTLIB("async_quit_currentloop()\n"); if (gattlib_async_global_state.current_loop) { - g_main_loop_quit(gattlib_async_global_state.current_loop); + if (g_main_loop_is_running(gattlib_async_global_state.current_loop)) { + DEBUG_GATTLIB("g_main_loop_quit\n"); + DEBUG_DEC_NUMLOOPS(); + g_main_loop_quit(gattlib_async_global_state.current_loop); + + g_main_context_iteration( + g_main_loop_get_context(gattlib_async_global_state.current_loop), + FALSE); + g_main_context_iteration(NULL, FALSE); + + + } } else { DEBUG_GATTLIB("booo, no loop?\n"); } @@ -127,6 +138,8 @@ static gboolean gattlib_timeout_async_loop(gpointer data) { // if (gattlib_async_global_state.current_loop) { DEBUG_GATTLIB("sending timeout quit to loop\n"); // g_main_loop_quit(gattlib_async_global_state.current_loop); + DEBUG_GATTLIB("g_main_loop_quit\n"); + DEBUG_DEC_NUMLOOPS(); g_main_loop_quit(data); } @@ -142,18 +155,23 @@ static gboolean gattlib_timeout_async_loop(gpointer data) { */ int gattlib_async_setup_currentloop(int timeout, gboolean useOwnContext) { DEBUG_GATTLIB("setting up async loop..."); + if (gattlib_async_global_state.current_loop) { DEBUG_GATTLIB("boo, already done\n"); - return 1; + // return 1; } if (useOwnContext == TRUE) { DEBUG_GATTLIB("with own context.\n"); gattlib_async_global_state.current_context = g_main_context_new(); + DEBUG_GATTLIB("g_main_loop_new (own context)\n"); + DEBUG_INC_NUMLOOPS(); gattlib_async_global_state.current_loop = g_main_loop_new( gattlib_async_global_state.current_context, TRUE); } else { DEBUG_GATTLIB("with global context.\n"); + DEBUG_GATTLIB("g_main_loop_new (def context)\n"); + DEBUG_INC_NUMLOOPS(); gattlib_async_global_state.current_loop = g_main_loop_new(NULL, TRUE); } @@ -212,6 +230,8 @@ static void gattlib_async_teardown_currentloop() { return; } + DEBUG_GATTLIB("g_main_loop_quit\n"); + DEBUG_DEC_NUMLOOPS(); g_main_loop_quit(gattlib_async_global_state.current_loop); // TEST if (gattlib_async_global_state.timeout_source) { @@ -369,6 +389,7 @@ int gattlib_adapter_scan_disable_async(void* adapter, gattlib_async_completed_cb // likely we're scanning now DEBUG_GATTLIB("\nscan disable async called..."); + /* if (gattlib_async_global_state.current_loop && gattlib_async_global_state.am_scanning) { DEBUG_GATTLIB("while scanning... quitting current loop\n"); gattlib_async_global_state.done_callback = NULL; // cancel that @@ -376,6 +397,7 @@ int gattlib_adapter_scan_disable_async(void* adapter, gattlib_async_completed_cb gattlib_async_process(); // do one run to clean it out. } + */ DEBUG_GATTLIB("scan disable prep done, calling stop discovery.\n"); @@ -421,8 +443,11 @@ static void gattlib_async_connect_trigger_conn_cb(gatt_connection_t * connptr) { gattlib_async_teardown_currentloop(); gattlib_async_global_state.connection_done_cb = NULL; thecb(connptr); + } else { + gattlib_async_teardown_currentloop(); } + } static void gattlib_async_connect_timeouttrigger_conn_cb() { @@ -466,7 +491,7 @@ static void gattlib_connect_thread_cb(GTask *task, gpointer source_object, if (!retval) { DEBUG_GATTLIB("async conn returned nuffin' !!!\n"); - // gattlib_async_connect_trigger_conn_cb(NULL); + gattlib_async_connect_trigger_conn_cb(NULL); } g_task_return_pointer(task, retval, @@ -543,10 +568,8 @@ void gattlib_async_connect_ready_cb(GObject *source_object, GAsyncResult *res, GError *error = NULL; gatt_connection_t * connptr; DEBUG_GATTLIB("gattlib_async_connect_ready_cb\n"); - if (gattlib_async_global_state.connection_done_cb) { - connptr = g_task_propagate_pointer(G_TASK(res), &error); - gattlib_async_connect_trigger_conn_cb(connptr); - } + connptr = g_task_propagate_pointer(G_TASK(res), &error); + gattlib_async_connect_trigger_conn_cb(connptr); } diff --git a/dbus/gattlib_internal.h b/dbus/gattlib_internal.h index 8437a7be..b69232f4 100644 --- a/dbus/gattlib_internal.h +++ b/dbus/gattlib_internal.h @@ -48,10 +48,18 @@ typedef struct { /* define GATTLIB_DEBUG_OUTPUT_ENABLE */ +#ifdef GATTLIB_DEBUG_OUTPUT_ENABLE +extern int debug_num_loops; +#endif + #ifdef GATTLIB_DEBUG_OUTPUT_ENABLE #define DEBUG_GATTLIB(...) fprintf(stderr, __VA_ARGS__) +#define DEBUG_INC_NUMLOOPS() debug_num_loops++; DEBUG_GATTLIB("\n++ num loops now: %i\n", debug_num_loops) +#define DEBUG_DEC_NUMLOOPS() debug_num_loops--; DEBUG_GATTLIB("\n-- num loops now: %i\n", debug_num_loops) #else #define DEBUG_GATTLIB(...) +#define DEBUG_INC_NUMLOOPS() +#define DEBUG_DEC_NUMLOOPS() #endif #define ERROR_GATTLIB(...) fprintf(stderr, __VA_ARGS__) From 62735e47e89fe8079436a4b6e05fd2e1541de5fd Mon Sep 17 00:00:00 2001 From: Pat Deegan Date: Wed, 3 Jan 2018 00:28:31 -0500 Subject: [PATCH 12/12] Updated links --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0e130383..a51faf6e 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ Latest GattLib (Async support) Release packages * For x86_64, with Bluez DBUS Support (Recommended, and required for Gattlib++ and Coraline): - - ZIP: - - DEB: - - RPM: + - ZIP: + - DEB: + - RPM: Latest GattLib (original) packages (no async, as of yet) ========================================================