diff --git a/bluez/gattlib_connect.c b/bluez/gattlib_connect.c index 969f3fbc..5d68ecb4 100644 --- a/bluez/gattlib_connect.c +++ b/bluez/gattlib_connect.c @@ -2,7 +2,7 @@ * * GattLib - GATT Library * - * Copyright (C) 2016-2021 Olivier Martin + * Copyright (C) 2016-2024 Olivier Martin * * * This program is free software; you can redistribute it and/or modify @@ -69,12 +69,12 @@ static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data) switch (pdu[0]) { case ATT_OP_HANDLE_NOTIFY: if (gattlib_has_valid_handler(&conn->notification)) { - gattlib_call_notification_handler(&conn->notification, &uuid, &pdu[3], len - 3); + gattlib_on_gatt_notification(&conn, &uuid, &pdu[3], len - 3); } break; case ATT_OP_HANDLE_IND: if (gattlib_has_valid_handler(&conn->indication)) { - gattlib_call_notification_handler(&conn->notification, &uuid, &pdu[3], len - 3); + gattlib_on_gatt_notification(&conn, &uuid, &pdu[3], len - 3); } break; default: @@ -417,7 +417,7 @@ static gatt_connection_t *gattlib_connect_with_options(const char *src, const ch while ((io_connect_arg.connected == FALSE) && (io_connect_arg.timeout == FALSE)) { g_main_context_iteration(g_gattlib_thread.loop_context, FALSE); } - + // Disconnect the timeout source if connection success if (io_connect_arg.connected) g_source_destroy(timeout); diff --git a/common/gattlib_callback_connected_device.c b/common/gattlib_callback_connected_device.c new file mode 100644 index 00000000..282c9fe4 --- /dev/null +++ b/common/gattlib_callback_connected_device.c @@ -0,0 +1,71 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2024, Olivier Martin + */ + +#include "gattlib_internal.h" + +void gattlib_connected_device_python_callback(void *adapter, const char *dst, gatt_connection_t* connection, int error, void* user_data) { + struct gattlib_python_args* args = user_data; + PyObject *result; + + // In case of Python support, we ensure we acquire the GIL (Global Intepreter Lock) to have + // a thread-safe Python execution. + PyGILState_STATE d_gstate = PyGILState_Ensure(); + + const char* argument_string; + // We pass pointer into integer/long parameter. We need to check the address size of the platform + // arguments: (void *adapter, const char *dst, gatt_connection_t* connection, void* user_data) + if (sizeof(void*) == 8) { + argument_string = "(LsLIO)"; + } else { + argument_string = "(IsIIO)"; + } + PyObject *arglist = Py_BuildValue(argument_string, adapter, dst, connection, error, args->args); + if (arglist == NULL) { + GATTLIB_LOG(GATTLIB_ERROR, "Could not convert argument list to Python arguments"); + PyErr_Print(); + goto ON_ERROR; + } +#if PYTHON_VERSION >= PYTHON_VERSIONS(3, 9) + result = PyObject_Call(args->callback, arglist, NULL); +#else + result = PyEval_CallObject(args->callback, arglist); +#endif + Py_DECREF(arglist); + + if (result == NULL) { + GATTLIB_LOG(GATTLIB_ERROR, "Python connection device handler has raised an exception."); + PyErr_Print(); + } + +ON_ERROR: + PyGILState_Release(d_gstate); +} + +static gpointer _gattlib_connected_device_thread(gpointer data) { + gatt_connection_t* connection = data; + gattlib_context_t* conn_context = connection->context; + const gchar *device_mac_address = org_bluez_device1_get_address(conn_context->device); + + connection->on_connection.callback.connection_handler( + conn_context->adapter, device_mac_address, connection, 0 /* no error */, + connection->on_connection.user_data); + return NULL; +} + +static void* _connected_device_thread_args_allocator(va_list args) { + gatt_connection_t* connection = va_arg(args, gatt_connection_t*); + return connection; +} + +void gattlib_on_connected_device(gatt_connection_t* connection) { + gattlib_handler_dispatch_to_thread( + &connection->on_connection, + gattlib_connected_device_python_callback /* python_callback */, + _gattlib_connected_device_thread /* thread_func */, + "gattlib_connected_device" /* thread_name */, + _connected_device_thread_args_allocator /* thread_args_allocator */, + connection); +} diff --git a/common/gattlib_callback_disconnected_device.c b/common/gattlib_callback_disconnected_device.c new file mode 100644 index 00000000..e27de989 --- /dev/null +++ b/common/gattlib_callback_disconnected_device.c @@ -0,0 +1,51 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2024, Olivier Martin + */ + +#include "gattlib_internal.h" + +void gattlib_disconnected_device_python_callback(gatt_connection_t* connection, void *user_data) { + struct gattlib_python_args* args = user_data; + PyObject *result; + PyGILState_STATE d_gstate; + d_gstate = PyGILState_Ensure(); + + PyObject *arglist = Py_BuildValue("(O)", args->args); +#if PYTHON_VERSION >= PYTHON_VERSIONS(3, 9) + result = PyObject_Call(args->callback, arglist, NULL); +#else + result = PyEval_CallObject(args->callback, arglist); +#endif + Py_DECREF(arglist); + + if (result == NULL) { + GATTLIB_LOG(GATTLIB_ERROR, "Python disconnection handler has raised an exception."); + PyErr_Print(); + } + + PyGILState_Release(d_gstate); +} + +static gpointer _gattlib_disconnected_device_thread(gpointer data) { + gatt_connection_t* connection = data; + + connection->on_disconnection.callback.disconnection_handler(connection, connection->on_disconnection.user_data); + return NULL; +} + +static void* _disconnected_device_thread_args_allocator(va_list args) { + gatt_connection_t* connection = va_arg(args, gatt_connection_t*); + return connection; +} + +void gattlib_on_disconnected_device(gatt_connection_t* connection) { + gattlib_handler_dispatch_to_thread( + &connection->on_disconnection, + gattlib_disconnected_device_python_callback /* python_callback */, + _gattlib_disconnected_device_thread /* thread_func */, + "gattlib_disconnected_device" /* thread_name */, + _disconnected_device_thread_args_allocator /* thread_args_allocator */, + connection); +} diff --git a/common/gattlib_callback_discovered_device.c b/common/gattlib_callback_discovered_device.c new file mode 100644 index 00000000..b32f20ea --- /dev/null +++ b/common/gattlib_callback_discovered_device.c @@ -0,0 +1,94 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2024, Olivier Martin + */ + +#include "gattlib_internal.h" + +void gattlib_discovered_device_python_callback(void *adapter, const char* addr, const char* name, void *user_data) { + struct gattlib_python_args* args = user_data; + PyObject *result; + + // In case of Python support, we ensure we acquire the GIL (Global Intepreter Lock) to have + // a thread-safe Python execution. + PyGILState_STATE d_gstate = PyGILState_Ensure(); + + const char* argument_string; + // We pass pointer into integer/long parameter. We need to check the address size of the platform + if (sizeof(void*) == 8) { + argument_string = "(LssO)"; + } else { + argument_string = "(IssO)"; + } + PyObject *arglist = Py_BuildValue(argument_string, adapter, addr, name, args->args); + if (arglist == NULL) { + GATTLIB_LOG(GATTLIB_ERROR, "Could not convert argument list to Python arguments"); + PyErr_Print(); + goto ON_ERROR; + } +#if PYTHON_VERSION >= PYTHON_VERSIONS(3, 9) + result = PyObject_Call(args->callback, arglist, NULL); +#else + result = PyEval_CallObject(args->callback, arglist); +#endif + Py_DECREF(arglist); + + if (result == NULL) { + GATTLIB_LOG(GATTLIB_ERROR, "Python discovered device handler has raised an exception."); + PyErr_Print(); + } + +ON_ERROR: + PyGILState_Release(d_gstate); +} + +struct gattlib_discovered_device_thread_args { + struct gattlib_adapter* gattlib_adapter; + char* mac_address; + char* name; + OrgBluezDevice1* device1; +}; + +static gpointer _gattlib_discovered_device_thread(gpointer data) { + struct gattlib_discovered_device_thread_args* args = data; + + args->gattlib_adapter->ble_scan.discovered_device_callback.callback.discovered_device( + args->gattlib_adapter, + args->mac_address, args->name, + args->gattlib_adapter->ble_scan.discovered_device_callback.user_data + ); + + free(args->mac_address); + if (args->name != NULL) { + free(args->name); + } + free(args); + return NULL; +} + +static void* _discovered_device_thread_args_allocator(va_list args) { + struct gattlib_adapter* gattlib_adapter = va_arg(args, struct gattlib_adapter*); + OrgBluezDevice1* device1 = va_arg(args, OrgBluezDevice1*); + + struct gattlib_discovered_device_thread_args* thread_args = malloc(sizeof(struct gattlib_discovered_device_thread_args)); + thread_args->gattlib_adapter = gattlib_adapter; + thread_args->mac_address = strdup(org_bluez_device1_get_address(device1)); + const char* device_name = org_bluez_device1_get_name(device1); + if (device_name != NULL) { + thread_args->name = strdup(device_name); + } else { + thread_args->name = NULL; + } + return thread_args; +} + +void gattlib_on_discovered_device(struct gattlib_adapter* gattlib_adapter, OrgBluezDevice1* device1) { + gattlib_handler_dispatch_to_thread( + &gattlib_adapter->ble_scan.discovered_device_callback, + gattlib_discovered_device_python_callback /* python_callback */, + _gattlib_discovered_device_thread /* thread_func */, + "gattlib_discovered_device" /* thread_name */, + _discovered_device_thread_args_allocator /* thread_args_allocator */, + gattlib_adapter, device1); +} diff --git a/common/gattlib_callback_notification_device.c b/common/gattlib_callback_notification_device.c new file mode 100644 index 00000000..dba470a9 --- /dev/null +++ b/common/gattlib_callback_notification_device.c @@ -0,0 +1,97 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2024, Olivier Martin + */ + +#include "gattlib_internal.h" + +void gattlib_notification_device_python_callback(const uuid_t* uuid, const uint8_t* data, size_t data_length, void* user_data) { + struct gattlib_python_args* args = user_data; + char uuid_str[MAX_LEN_UUID_STR + 1]; + PyGILState_STATE d_gstate; + PyObject *result; + int ret; + + ret = gattlib_uuid_to_string(uuid, uuid_str, sizeof(uuid_str)); + assert(ret == 0); + + d_gstate = PyGILState_Ensure(); + + const char* argument_string; + // We pass pointer into integer/long parameter. We need to check the address size of the platform + if (sizeof(void*) == 8) { + argument_string = "(sLIO)"; + } else { + argument_string = "(sIIO)"; + } + PyObject *arglist = Py_BuildValue(argument_string, uuid_str, data, data_length, args->args); +#if PYTHON_VERSION >= PYTHON_VERSIONS(3, 9) + result = PyObject_Call(args->callback, arglist, NULL); +#else + result = PyEval_CallObject(args->callback, arglist); +#endif + Py_DECREF(arglist); + + if (result == NULL) { + GATTLIB_LOG(GATTLIB_ERROR, "Python notification handler has raised an exception."); + PyErr_Print(); + } + + PyGILState_Release(d_gstate); +} + +struct gattlib_notification_device_thread_args { + gatt_connection_t* connection; + uuid_t* uuid; + uint8_t* data; + size_t data_length; +}; + +static gpointer _gattlib_notification_device_thread(gpointer data) { + struct gattlib_notification_device_thread_args* args = data; + + args->connection->notification.callback.notification_handler( + args->uuid, args->data, args->data_length, + args->connection->notification.user_data + ); + + if (args->uuid != NULL) { + free(args->uuid); + } + if (args->data != NULL) { + free(args->data); + } + return NULL; +} + +static void* _notification_device_thread_args_allocator(va_list args) { + gatt_connection_t* connection = va_arg(args, gatt_connection_t*); + const uuid_t* uuid = va_arg(args, const uuid_t*); + const uint8_t* data = va_arg(args, const uint8_t*); + size_t data_length = va_arg(args, size_t); + + struct gattlib_notification_device_thread_args* thread_args = malloc(sizeof(struct gattlib_notification_device_thread_args)); + thread_args->connection = connection; + thread_args->uuid = malloc(sizeof(uuid_t)); + if (thread_args->uuid != NULL) { + memcpy(thread_args->uuid, uuid, sizeof(uuid_t)); + } + thread_args->data = malloc(data_length); + if (thread_args->data != NULL) { + memcpy(thread_args->data, data, data_length); + } + thread_args->data_length = data_length; + + return thread_args; +} + +void gattlib_on_gatt_notification(gatt_connection_t* connection, const uuid_t* uuid, const uint8_t* data, size_t data_length) { + gattlib_handler_dispatch_to_thread( + &connection->on_connection, + gattlib_notification_device_python_callback /* python_callback */, + _gattlib_notification_device_thread /* thread_func */, + "gattlib_notification_device" /* thread_name */, + _notification_device_thread_args_allocator /* thread_args_allocator */, + connection, uuid, data, data_length); +} diff --git a/common/gattlib_callback_python.c b/common/gattlib_callback_python.c new file mode 100644 index 00000000..dc9193ea --- /dev/null +++ b/common/gattlib_callback_python.c @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2024, Olivier Martin + */ + +#include "gattlib_internal.h" + + +void* gattlib_python_callback_args(PyObject* python_callback, PyObject* python_args) { + assert(python_callback != NULL); + assert(python_args != NULL); + + struct gattlib_python_args* args = malloc(sizeof(struct gattlib_python_args)); + if (args == NULL) { + GATTLIB_LOG(GATTLIB_ERROR, "Failed to allocate Python arguments for Python callback."); + return NULL; + } + Py_INCREF(python_callback); + Py_INCREF(python_args); + + args->callback = python_callback; + args->args = python_args; + return args; +} + diff --git a/common/gattlib_common.c b/common/gattlib_common.c index f0e40e6b..4e525865 100644 --- a/common/gattlib_common.c +++ b/common/gattlib_common.c @@ -9,115 +9,18 @@ #include "gattlib_internal.h" void gattlib_register_notification(gatt_connection_t* connection, gattlib_event_handler_t notification_handler, void* user_data) { - connection->notification.type = NATIVE_NOTIFICATION; - connection->notification.notification_handler = notification_handler; + connection->notification.callback.notification_handler = notification_handler; connection->notification.user_data = user_data; } void gattlib_register_indication(gatt_connection_t* connection, gattlib_event_handler_t indication_handler, void* user_data) { - connection->indication.type = NATIVE_NOTIFICATION; - connection->indication.notification_handler = indication_handler; + connection->indication.callback.notification_handler = indication_handler; connection->indication.user_data = user_data; } void gattlib_register_on_disconnect(gatt_connection_t *connection, gattlib_disconnection_handler_t handler, void* user_data) { - connection->disconnection.type = NATIVE_DISCONNECTION; - connection->disconnection.disconnection_handler = handler; - connection->disconnection.user_data = user_data; -} - -#if defined(WITH_PYTHON) -void gattlib_register_notification_python(gatt_connection_t* connection, PyObject *notification_handler, PyObject *user_data) { - connection->notification.type = PYTHON; - connection->notification.python_handler = notification_handler; - connection->notification.user_data = user_data; -} - -void gattlib_register_indication_python(gatt_connection_t* connection, PyObject *indication_handler, PyObject *user_data) { - connection->indication.type = PYTHON; - connection->indication.python_handler = indication_handler; - connection->indication.user_data = user_data; -} - -void gattlib_register_on_disconnect_python(gatt_connection_t *connection, PyObject *handler, PyObject *user_data) { - connection->disconnection.type = PYTHON; - connection->disconnection.python_handler = handler; - connection->disconnection.user_data = user_data; -} -#endif - -bool gattlib_has_valid_handler(struct gattlib_handler *handler) { - return ((handler->type != UNKNOWN) && (handler->notification_handler != NULL)); -} - -void gattlib_call_notification_handler(struct gattlib_handler *handler, const uuid_t* uuid, const uint8_t* data, size_t data_length) { - if (handler->type == NATIVE_NOTIFICATION) { - handler->notification_handler(uuid, data, data_length, handler->user_data); - } -#if defined(WITH_PYTHON) - else if (handler->type == PYTHON) { - char uuid_str[MAX_LEN_UUID_STR + 1]; - PyGILState_STATE d_gstate; - PyObject *result; - - gattlib_uuid_to_string(uuid, uuid_str, sizeof(uuid_str)); - - d_gstate = PyGILState_Ensure(); - - const char* argument_string; - if (sizeof(void*) == 8) { - argument_string = "(sLIO)"; - } else { - argument_string = "(sIIO)"; - } - PyObject *arglist = Py_BuildValue(argument_string, uuid_str, data, data_length, handler->user_data); -#if PYTHON_VERSION >= PYTHON_VERSIONS(3, 9) - result = PyObject_Call((PyObject *)handler->notification_handler, arglist, NULL); -#else - result = PyEval_CallObject((PyObject *)handler->notification_handler, arglist); -#endif - Py_DECREF(arglist); - - if (result == NULL) { - GATTLIB_LOG(GATTLIB_ERROR, "Python notification handler has raised an exception."); - } - - PyGILState_Release(d_gstate); - } -#endif - else { - GATTLIB_LOG(GATTLIB_ERROR, "Invalid notification handler."); - } -} - -void gattlib_call_disconnection_handler(struct gattlib_handler *handler) { - if (handler->type == NATIVE_DISCONNECTION) { - handler->disconnection_handler(handler->user_data); - } -#if defined(WITH_PYTHON) - else if (handler->type == PYTHON) { - PyObject *result; - PyGILState_STATE d_gstate; - d_gstate = PyGILState_Ensure(); - - PyObject *arglist = Py_BuildValue("(O)", handler->user_data); -#if PYTHON_VERSION >= PYTHON_VERSIONS(3, 9) - result = PyObject_Call((PyObject *)handler->disconnection_handler, arglist, NULL); -#else - result = PyEval_CallObject((PyObject *)handler->disconnection_handler, arglist); -#endif - Py_DECREF(arglist); - - if (result == NULL) { - GATTLIB_LOG(GATTLIB_ERROR, "Python handler has raised an exception."); - } - - PyGILState_Release(d_gstate); - } -#endif - else { - GATTLIB_LOG(GATTLIB_ERROR, "Invalid disconnection handler."); - } + connection->on_disconnection.callback.disconnection_handler = handler; + connection->on_disconnection.user_data = user_data; } void bt_uuid_to_uuid(bt_uuid_t* bt_uuid, uuid_t* uuid) { @@ -199,3 +102,51 @@ int gattlib_uuid_cmp(const uuid_t *uuid1, const uuid_t *uuid2) { return 3; } } + +void gattlib_handler_free(struct gattlib_handler* handler) { + // Reset callback to stop calling it after we stopped + handler->callback.callback = NULL; + + if (handler->python_args == NULL) { + return; + } + + struct gattlib_python_args* args = handler->python_args; + Py_DECREF(args->callback); + Py_DECREF(args->args); + free(args); + + handler->python_args = NULL; +} + +bool gattlib_has_valid_handler(struct gattlib_handler* handler) { + return (handler->callback.callback != NULL); +} + +void gattlib_handler_dispatch_to_thread(struct gattlib_handler* handler, void (*python_callback)(), + GThreadFunc thread_func, const char* thread_name, void* (*thread_args_allocator)(va_list args), ...) { + GError *error = NULL; + + if (handler->callback.callback == NULL) { + // We do not have (anymore) a callback, nothing to do + return; + } + + // Check if we are using the Python callback, in case of Python argument we keep track of the argument to free them + // once we are done with the handler. + if (handler->callback.callback == python_callback) { + handler->python_args = handler->user_data; + } + + // We create a thread to ensure the callback is not blocking the mainloop + va_list args; + va_start(args, thread_args_allocator); + void* thread_args = thread_args_allocator(args); + va_end(args); + + handler->thread = g_thread_try_new(thread_name, thread_func, thread_args, &error); + if (handler->thread == NULL) { + GATTLIB_LOG(GATTLIB_ERROR, "Failed to create thread '%s': %s", thread_name, error->message); + return; + } +} diff --git a/common/gattlib_internal_defs.h b/common/gattlib_internal_defs.h index ac5d363f..3a8e825e 100644 --- a/common/gattlib_internal_defs.h +++ b/common/gattlib_internal_defs.h @@ -1,38 +1,68 @@ /* * SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later * - * Copyright (c) 2021, Olivier Martin + * Copyright (c) 2021-2024, Olivier Martin */ #ifndef __GATTLIB_INTERNAL_DEFS_H__ #define __GATTLIB_INTERNAL_DEFS_H__ #include +#include + +#if defined(WITH_PYTHON) + #include +#endif #include "gattlib.h" -enum handler_type { UNKNOWN = 0, NATIVE_NOTIFICATION, NATIVE_DISCONNECTION, PYTHON }; +struct gattlib_python_args { + PyObject* callback; + PyObject* args; +}; struct gattlib_handler { - enum handler_type type; union { + gattlib_discovered_device_t discovered_device; + gatt_connect_cb_t connection_handler; gattlib_event_handler_t notification_handler; gattlib_disconnection_handler_t disconnection_handler; - void* python_handler; - }; + void (*callback)(void); + } callback; + void* user_data; + // We create a thread to ensure the callback is not blocking the mainloop + GThread *thread; + // In case of Python callback and argument, we keep track to free it when we stopped to discover BLE devices + void* python_args; }; struct _gatt_connection_t { void* context; + struct gattlib_handler on_connection; + struct gattlib_handler on_connection_error; struct gattlib_handler notification; struct gattlib_handler indication; - struct gattlib_handler disconnection; + struct gattlib_handler on_disconnection; }; -bool gattlib_has_valid_handler(struct gattlib_handler *handler); -void gattlib_call_disconnection_handler(struct gattlib_handler *handler); -void gattlib_call_notification_handler(struct gattlib_handler *handler, const uuid_t* uuid, const uint8_t* data, size_t data_length); +void gattlib_handler_dispatch_to_thread(struct gattlib_handler* handler, void (*python_callback)(), + GThreadFunc thread_func, const char* thread_name, void* (*thread_args_allocator)(va_list args), ...); +void gattlib_handler_free(struct gattlib_handler* handler); +bool gattlib_has_valid_handler(struct gattlib_handler* handler); + +#if defined(WITH_PYTHON) +// Callback used by Python to create arguments used by native callback +void* gattlib_python_callback_args(PyObject* python_callback, PyObject* python_args); + +/** + * These functions are called by Python wrapper + */ +void gattlib_discovered_device_python_callback(void *adapter, const char* addr, const char* name, void *user_data); +void gattlib_connected_device_python_callback(void *adapter, const char *dst, gatt_connection_t* connection, int error, void* user_data); +void gattlib_disconnected_device_python_callback(gatt_connection_t* connection, void *user_data); +void gattlib_notification_device_python_callback(const uuid_t* uuid, const uint8_t* data, size_t data_length, void* user_data); +#endif #endif diff --git a/dbus/CMakeLists.txt b/dbus/CMakeLists.txt index 2f43b2d2..2d65968e 100644 --- a/dbus/CMakeLists.txt +++ b/dbus/CMakeLists.txt @@ -74,6 +74,11 @@ set(gattlib_SRCS gattlib.c bluez5/lib/uuid.c ${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_common.c ${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_eddystone.c + ${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_callback_connected_device.c + ${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_callback_disconnected_device.c + ${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_callback_discovered_device.c + ${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_callback_notification_device.c + ${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_callback_python.c ${CMAKE_CURRENT_LIST_DIR}/../common/logging_backend/${GATTLIB_LOG_BACKEND}/gattlib_logging.c ${CMAKE_CURRENT_BINARY_DIR}/org-bluez-adaptater1.c ${CMAKE_CURRENT_BINARY_DIR}/org-bluez-device1.c diff --git a/dbus/gattlib.c b/dbus/gattlib.c index e469a344..59db4216 100644 --- a/dbus/gattlib.c +++ b/dbus/gattlib.c @@ -1,7 +1,7 @@ /* * SPDX-License-Identifier: BSD-3-Clause * - * Copyright (c) 2016-2021, Olivier Martin + * Copyright (c) 2016-2024, Olivier Martin */ #include @@ -21,6 +21,29 @@ static void* glib_event_thread(void* main_loop_p) { return NULL; } +static void _on_device_connect(gatt_connection_t* connection) { + gattlib_context_t* conn_context = connection->context; + GDBusObjectManager *device_manager; + + // Stop the timeout for connection + if (conn_context->connection_timeout) { + g_source_remove(conn_context->connection_timeout); + conn_context->connection_timeout = 0; + } + + // Get list of objects belonging to Device Manager + device_manager = get_device_manager_from_adapter(conn_context->adapter); + if (device_manager == NULL) { + GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_connect: Failed to get device manager from adapter"); + + //TODO: Free device + return; + } + conn_context->dbus_objects = g_dbus_object_manager_get_objects(device_manager); + + gattlib_on_connected_device(connection); +} + gboolean on_handle_device_property_change( OrgBluezGattCharacteristic1 *object, GVariant *arg_changed_properties, @@ -28,7 +51,6 @@ gboolean on_handle_device_property_change( gpointer user_data) { gatt_connection_t* connection = user_data; - gattlib_context_t* conn_context = connection->context; // Retrieve 'Value' from 'arg_changed_properties' if (g_variant_n_children (arg_changed_properties) > 0) { @@ -41,18 +63,11 @@ gboolean on_handle_device_property_change( GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: device_property_change: %s: %s", key, g_variant_print(value, TRUE)); if (strcmp(key, "Connected") == 0) { if (!g_variant_get_boolean(value)) { - // Disconnection case - if (gattlib_has_valid_handler(&connection->disconnection)) { - gattlib_call_disconnection_handler(&connection->disconnection); - } + gattlib_on_disconnected_device(connection); } } else if (strcmp(key, "ServicesResolved") == 0) { if (g_variant_get_boolean(value)) { - // Stop the timeout for connection - g_source_remove(conn_context->connection_timeout); - - // Tell we are now connected - g_main_loop_quit(conn_context->connection_loop); + _on_device_connect(connection); } } } @@ -160,10 +175,10 @@ gatt_connection_t *gattlib_connect(void* adapter, const char *dst, unsigned long if (connection == NULL) { GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_connect: Cannot allocate connection"); goto FREE_CONN_CONTEXT; - } else { - connection->context = conn_context; } + connection->context = conn_context; + OrgBluezDevice1* device = org_bluez_device1_proxy_new_for_bus_sync( G_BUS_TYPE_SYSTEM, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, @@ -258,7 +273,7 @@ gatt_connection_t *gattlib_connect_async(void *adapter, const char *dst, connection = gattlib_connect(adapter, dst, options); if ((connection != NULL) && (connect_cb != NULL)) { - connect_cb(connection, data); + connect_cb(adapter, dst, connection, 0 /* error */, data); } return connection; diff --git a/dbus/gattlib_adapter.c b/dbus/gattlib_adapter.c index 746693ef..d25a6d65 100644 --- a/dbus/gattlib_adapter.c +++ b/dbus/gattlib_adapter.c @@ -134,22 +134,7 @@ static void device_manager_on_device1_signal(const char* device1_path, struct ga g_mutex_unlock(&gattlib_adapter->ble_scan.discovered_devices_mutex); if ((item == NULL) || (gattlib_adapter->ble_scan.enabled_filters & GATTLIB_DISCOVER_FILTER_NOTIFY_CHANGE)) { -#if defined(WITH_PYTHON) - // In case of Python support, we ensure we acquire the GIL (Global Intepreter Lock) to have - // a thread-safe Python execution. - PyGILState_STATE d_gstate; - d_gstate = PyGILState_Ensure(); -#endif - - gattlib_adapter->ble_scan.discovered_device_callback( - gattlib_adapter, - org_bluez_device1_get_address(device1), - org_bluez_device1_get_name(device1), - gattlib_adapter->ble_scan.discovered_device_user_data); - -#if defined(WITH_PYTHON) - PyGILState_Release(d_gstate); -#endif + gattlib_on_discovered_device(gattlib_adapter, device1); } g_object_unref(device1); } @@ -217,7 +202,7 @@ static gboolean _stop_scan_func(gpointer data) { g_mutex_unlock(&gattlib_adapter->ble_scan.scan_loop_mutex); - GATTLIB_LOG(GATTLIB_DEBUG, "BLE scan is stopped after sacnning time has expired."); + GATTLIB_LOG(GATTLIB_DEBUG, "BLE scan is stopped after scanning time has expired."); return FALSE; } @@ -314,8 +299,8 @@ static int _gattlib_adapter_scan_enable_with_filter(void *adapter, uuid_t **uuid memset(&gattlib_adapter->ble_scan, 0, sizeof(gattlib_adapter->ble_scan)); gattlib_adapter->ble_scan.enabled_filters = enabled_filters; gattlib_adapter->ble_scan.ble_scan_timeout = timeout; - gattlib_adapter->ble_scan.discovered_device_callback = discovered_device_cb; - gattlib_adapter->ble_scan.discovered_device_user_data = user_data; + gattlib_adapter->ble_scan.discovered_device_callback.callback.discovered_device = discovered_device_cb; + gattlib_adapter->ble_scan.discovered_device_callback.user_data = user_data; gattlib_adapter->ble_scan.added_signal_id = g_signal_connect(G_DBUS_OBJECT_MANAGER(device_manager), "object-added", @@ -388,6 +373,12 @@ int gattlib_adapter_scan_disable(void* adapter) { struct gattlib_adapter *gattlib_adapter = adapter; GError *error = NULL; + org_bluez_adapter1_call_stop_discovery_sync(gattlib_adapter->adapter_proxy, NULL, &error); + // Ignore the error + + // Free and reset callback to stop calling it after we stopped + gattlib_handler_free(&gattlib_adapter->ble_scan.discovered_device_callback); + if (gattlib_adapter->ble_scan.is_scanning) { g_mutex_lock(&gattlib_adapter->ble_scan.scan_loop_mutex); gattlib_adapter->ble_scan.is_scanning = false; @@ -395,9 +386,6 @@ int gattlib_adapter_scan_disable(void* adapter) { g_mutex_unlock(&gattlib_adapter->ble_scan.scan_loop_mutex); } - org_bluez_adapter1_call_stop_discovery_sync(gattlib_adapter->adapter_proxy, NULL, &error); - // Ignore the error - // Remove timeout if (gattlib_adapter->ble_scan.ble_scan_timeout_id) { g_source_remove(gattlib_adapter->ble_scan.ble_scan_timeout_id); diff --git a/dbus/gattlib_internal.h b/dbus/gattlib_internal.h index 4b80e74e..2bca3ff0 100644 --- a/dbus/gattlib_internal.h +++ b/dbus/gattlib_internal.h @@ -81,8 +81,8 @@ struct gattlib_adapter { GCond scan_loop_cond; uint32_t enabled_filters; - gattlib_discovered_device_t discovered_device_callback; - void *discovered_device_user_data; + + struct gattlib_handler discovered_device_callback; } ble_scan; }; @@ -111,6 +111,15 @@ int get_bluez_device_from_mac(struct gattlib_adapter *adapter, const char *mac_a struct dbus_characteristic get_characteristic_from_uuid(gatt_connection_t* connection, const uuid_t* uuid); +// Invoke when a new device has been discovered +void gattlib_on_discovered_device(struct gattlib_adapter* gattlib_adapter, OrgBluezDevice1* device1); +// Invoke when a new device is being connected +void gattlib_on_connected_device(gatt_connection_t* connection); +// Invoke when a new device is being disconnected +void gattlib_on_disconnected_device(gatt_connection_t* connection); +// Invoke when a new device receive a GATT notification +void gattlib_on_gatt_notification(gatt_connection_t* connection, const uuid_t* uuid, const uint8_t* data, size_t data_length); + void disconnect_all_notifications(gattlib_context_t* conn_context); #endif diff --git a/dbus/gattlib_notification.c b/dbus/gattlib_notification.c index 2211147c..0fca80cc 100644 --- a/dbus/gattlib_notification.c +++ b/dbus/gattlib_notification.c @@ -45,7 +45,7 @@ gboolean on_handle_battery_level_property_change( // GATT connection notifiying to Battery level percentage = g_variant_get_byte(value); - gattlib_call_notification_handler(&connection->notification, + gattlib_on_gatt_notification(connection, &m_battery_level_uuid, (const uint8_t*)&percentage, sizeof(percentage)); break; @@ -88,8 +88,7 @@ static gboolean on_handle_characteristic_property_change( MAX_LEN_UUID_STR + 1, &uuid); - gattlib_call_notification_handler(&connection->notification, - &uuid, data, data_length); + gattlib_on_gatt_notification(connection, &uuid, data, data_length); // As per https://developer.gnome.org/glib/stable/glib-GVariant.html#g-variant-iter-loop, clean up `key` and `value`. g_variant_unref(value); @@ -135,8 +134,7 @@ static gboolean on_handle_characteristic_indication( MAX_LEN_UUID_STR + 1, &uuid); - gattlib_call_notification_handler(&connection->indication, - &uuid, data, data_length); + gattlib_on_gatt_notification(connection, &uuid, data, data_length); break; } } diff --git a/gattlib-py/gattlib/__init__.py b/gattlib-py/gattlib/__init__.py index c6d9e5a6..8adf2fb0 100644 --- a/gattlib-py/gattlib/__init__.py +++ b/gattlib-py/gattlib/__init__.py @@ -74,26 +74,45 @@ class GattlibAdvertisementData(Structure): gattlib_adapter_open = gattlib.gattlib_adapter_open gattlib_adapter_open.argtypes = [c_char_p, POINTER(c_void_p)] -# typedef void (*gattlib_discovered_device_t)(void *adapter, const char* addr, const char* name, void *user_data) -gattlib_discovered_device_type = CFUNCTYPE(None, c_void_p, c_char_p, c_char_p, py_object) - -# typedef void (*gattlib_discovered_device_with_data_t)(void *adapter, const char* addr, const char* name, -# gattlib_advertisement_data_t *advertisement_data, size_t advertisement_data_count, -# uint16_t manufacturer_id, uint8_t *manufacturer_data, size_t manufacturer_data_size, -# void *user_data); -gattlib_discovered_device_with_data_type = CFUNCTYPE(None, c_void_p, c_char_p, c_char_p, - POINTER(GattlibAdvertisementData), c_size_t, c_uint16, c_void_p, c_size_t, - py_object) +# const char *gattlib_adapter_get_name(void* adapter) +gattlib_adapter_get_name = gattlib.gattlib_adapter_get_name +gattlib_adapter_get_name.argtypes = [c_void_p] +gattlib_adapter_get_name.restype = c_char_p + +# void gattlib_discovered_device_python_callback(void *adapter, const char* addr, const char* name, void *user_data) +gattlib_discovered_device_python_callback = gattlib.gattlib_discovered_device_python_callback +gattlib_discovered_device_python_callback.argtypes = [c_void_p, c_char_p, c_char_p, py_object] +gattlib_discovered_device_python_callback.restype = c_void_p + +# void gattlib_connected_device_python_callback(void *adapter, const char *dst, gatt_connection_t* connection, int error, void* user_data); +gattlib_connected_device_python_callback = gattlib.gattlib_connected_device_python_callback +gattlib_connected_device_python_callback.argtypes = [c_void_p, c_char_p, c_void_p, c_int, py_object] +gattlib_connected_device_python_callback.restype = c_void_p + +# void gattlib_disconnected_device_python_callback(void *user_data) +gattlib_disconnected_device_python_callback = gattlib.gattlib_disconnected_device_python_callback +gattlib_disconnected_device_python_callback.argtypes = [py_object] +gattlib_disconnected_device_python_callback.restype = c_void_p + +# void gattlib_notification_device_python_callback(const uuid_t* uuid, const uint8_t* data, size_t data_length, void* user_data); +gattlib_notification_device_python_callback = gattlib.gattlib_notification_device_python_callback +gattlib_notification_device_python_callback.argtypes = [c_void_p, c_void_p, c_int, c_void_p] +gattlib_notification_device_python_callback.restype = c_void_p + +# void* gattlib_python_callback_args(PyObject* python_callback, PyObject* python_args) { +gattlib_python_callback_args = gattlib.gattlib_python_callback_args +gattlib_python_callback_args.argtypes = [py_object, py_object] +gattlib_python_callback_args.restype = c_void_p # int gattlib_adapter_scan_enable_with_filter_non_blocking(void *adapter, uuid_t **uuid_list, int16_t rssi_threshold, uint32_t enabled_filters, # gattlib_discovered_device_t discovered_device_cb, size_t timeout, void *user_data) gattlib_adapter_scan_enable_with_filter_non_blocking = gattlib.gattlib_adapter_scan_enable_with_filter_non_blocking -gattlib_adapter_scan_enable_with_filter_non_blocking.argtypes = [c_void_p, POINTER(POINTER(GattlibUuid)), c_int16, c_uint32, gattlib_discovered_device_type, c_size_t, py_object] +gattlib_adapter_scan_enable_with_filter_non_blocking.argtypes = [c_void_p, POINTER(POINTER(GattlibUuid)), c_int16, c_uint32, c_void_p, c_size_t, c_void_p] # int gattlib_adapter_scan_eddystone(void *adapter, int16_t rssi_threshold, uint32_t eddsytone_types, # gattlib_discovered_device_with_data_t discovered_device_cb, size_t timeout, void *user_data) gattlib_adapter_scan_eddystone = gattlib.gattlib_adapter_scan_eddystone -gattlib_adapter_scan_eddystone.argtypes = [c_void_p, c_int16, c_uint32, gattlib_discovered_device_with_data_type, c_size_t, py_object] +gattlib_adapter_scan_eddystone.argtypes = [c_void_p, c_int16, c_uint32, c_void_p, c_size_t, c_void_p] # gatt_connection_t *gattlib_connect(const char *src, const char *dst, unsigned long options); gattlib_connect = gattlib.gattlib_connect @@ -140,13 +159,13 @@ class GattlibAdvertisementData(Structure): gattlib_notification_stop = gattlib.gattlib_notification_stop gattlib_notification_stop.argtypes = [c_void_p, POINTER(GattlibUuid)] -# void gattlib_register_notification_python(gatt_connection_t* connection, PyObject *notification_handler, PyObject *user_data) -gattlib_register_notification = gattlib.gattlib_register_notification_python -gattlib_register_notification.argtypes = [c_void_p, py_object, py_object] +# void gattlib_register_notification(gatt_connection_t* connection, gattlib_event_handler_t notification_handler, void* user_data); +gattlib_register_notification = gattlib.gattlib_register_notification +gattlib_register_notification.argtypes = [c_void_p, c_void_p, c_void_p] -# void gattlib_register_on_disconnect_python(gatt_connection_t *connection, PyObject *handler, PyObject *user_data) -gattlib_register_on_disconnect = gattlib.gattlib_register_on_disconnect_python -gattlib_register_on_disconnect.argtypes = [c_void_p, py_object, py_object] +# void gattlib_register_on_disconnect(gatt_connection_t *connection, PyObject *handler, PyObject *user_data) +gattlib_register_on_disconnect = gattlib.gattlib_register_on_disconnect +gattlib_register_on_disconnect.argtypes = [c_void_p, c_void_p, c_void_p] # int gattlib_get_rssi(gatt_connection_t *connection, int16_t *rssi) gattlib_get_rssi = gattlib.gattlib_get_rssi diff --git a/gattlib-py/gattlib/adapter.py b/gattlib-py/gattlib/adapter.py index 03437aa2..ea386012 100644 --- a/gattlib-py/gattlib/adapter.py +++ b/gattlib-py/gattlib/adapter.py @@ -135,8 +135,9 @@ def scan_enable(self, on_discovered_device_callback, timeout, notify_change=Fals ret = gattlib_adapter_scan_enable_with_filter_non_blocking(self._adapter, uuid_list, rssi, enabled_filters, - self.on_discovered_device_callback, - timeout, user_data) + gattlib_discovered_device_python_callback, + timeout, + gattlib_python_callback_args(self.on_discovered_device_callback, user_data)) handle_return(ret) @staticmethod diff --git a/gattlib-py/gattlib/device.py b/gattlib-py/gattlib/device.py index 5b0f677b..0caead6b 100644 --- a/gattlib-py/gattlib/device.py +++ b/gattlib-py/gattlib/device.py @@ -4,14 +4,19 @@ # Copyright (c) 2016-2024, Olivier Martin # +from __future__ import annotations import logging import uuid +from typing import TYPE_CHECKING from gattlib import * -from .exception import handle_return, DeviceError +from .exception import handle_return, DeviceError, InvalidParameter from .gatt import GattService, GattCharacteristic from .uuid import gattlib_uuid_to_int +if TYPE_CHECKING: + from .adapter import Adapter + CONNECTION_OPTIONS_LEGACY_BDADDR_LE_PUBLIC = (1 << 0) CONNECTION_OPTIONS_LEGACY_BDADDR_LE_RANDOM = (1 << 1) CONNECTION_OPTIONS_LEGACY_BT_SEC_LOW = (1 << 2) @@ -26,7 +31,7 @@ class Device: - def __init__(self, adapter, addr, name=None): + def __init__(self, adapter: Adapter, addr: str, name: str = None): self._adapter = adapter if type(addr) == str: self._addr = addr.encode("utf-8") @@ -34,6 +39,7 @@ def __init__(self, adapter, addr, name=None): self._addr = addr self._name = name self._connection = None + self.on_connection_callback = None # Keep track if notification handler has been initialized self._is_notification_init = False @@ -58,14 +64,23 @@ def is_connected(self) -> bool: return (self._connection is not None) def connect(self, options=CONNECTION_OPTIONS_LEGACY_DEFAULT): - if self._adapter: - adapter_name = self._adapter.name + def _on_connection(adapter: c_void_p, mac_address: c_char_p, connection: c_void_p, error: c_int, user_data: py_object): + self._connection = connection + self.on_connection(user_data) + + if self._adapter is None: + adapter = None else: - adapter_name = None + adapter = self._adapter._adapter - self._connection = gattlib_connect(adapter_name, self._addr, options) - if self._connection is None: - raise DeviceError(adapter=adapter_name, mac_address=self._addr) + ret = gattlib_connect(adapter, self._addr, options, + gattlib_connected_device_python_callback, + gattlib_python_callback_args(_on_connection, self)) + handle_return(ret) + + def on_connection(self, user_data: py_object): + if self.on_connection_callback: + self.on_connection_callback(self, user_data) @property def rssi(self): @@ -87,7 +102,9 @@ def register_on_disconnect(self, callback, user_data): self.disconnection_callback = callback self.disconnection_user_data = user_data - gattlib_register_on_disconnect(self.connection, Device.on_disconnection, self) + gattlib_register_on_disconnect(self.connection, + gattlib_disconnected_device_python_callback, + gattlib_python_callback_args(Device.on_disconnection, user_data)) def disconnect(self): if self._connection: @@ -220,9 +237,14 @@ def _notification_init(self): self._is_notification_init = True - gattlib_register_notification(self._connection, Device.notification_callback, self) + gattlib_register_notification(self._connection, + gattlib_notification_device_python_callback, + gattlib_python_callback_args(Device.notification_callback, self)) def _notification_add_gatt_characteristic_callback(self, gatt_characteristic, callback, user_data): + if not callable(callback): + raise InvalidParameter("Notification callback is not callable.") + if not self._is_notification_init: self._notification_init() diff --git a/include/gattlib.h b/include/gattlib.h index f4ff3a2b..8fbb1172 100644 --- a/include/gattlib.h +++ b/include/gattlib.h @@ -1,7 +1,7 @@ /* * SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later * - * Copyright (c) 2016-2021, Olivier Martin + * Copyright (c) 2016-2024, Olivier Martin */ #ifndef __GATTLIB_H__ @@ -147,7 +147,7 @@ typedef void (*gattlib_event_handler_t)(const uuid_t* uuid, const uint8_t* data, * @param connection Connection that is disconnecting * @param user_data Data defined when calling `gattlib_register_on_disconnect()` */ -typedef void (*gattlib_disconnection_handler_t)(void* user_data); +typedef void (*gattlib_disconnection_handler_t)(gatt_connection_t* connection, void* user_data); /** * @brief Handler called on new discovered BLE device @@ -178,12 +178,15 @@ typedef void (*gattlib_discovered_device_with_data_t)(void *adapter, const char* void *user_data); /** - * @brief Handler called on asynchronous connection when connection is ready + * @brief Handler called on asynchronous connection when connection is ready or on connection error * + * @param adapter Local Adaptater interface. When passing NULL, we use default adapter. + * @param dst Remote Bluetooth address * @param connection Connection that is disconnecting + * @param error Connection error code * @param user_data Data defined when calling `gattlib_register_on_disconnect()` */ -typedef void (*gatt_connect_cb_t)(gatt_connection_t* connection, void* user_data); +typedef void (*gatt_connect_cb_t)(void *adapter, const char *dst, gatt_connection_t* connection, int error, void* user_data); /** * @brief Callback called when GATT characteristic read value has been received @@ -707,6 +710,13 @@ int gattlib_string_to_uuid(const char *str, size_t size, uuid_t *uuid); */ int gattlib_uuid_cmp(const uuid_t *uuid1, const uuid_t *uuid2); +/** + * @brief Logging function used by Gattlib + * + * @param level is the logging level of the message + * @param format is the message format + * + */ void gattlib_log(int level, const char *format, ...); #ifdef __cplusplus