python: Fix callback back to native gattlib

pull/264/head
Olivier Martin 2024-02-12 13:29:40 +01:00 committed by Olivier Martin
parent ec9e5cd38a
commit 1d80061bf2
17 changed files with 578 additions and 191 deletions

View File

@ -2,7 +2,7 @@
*
* GattLib - GATT Library
*
* Copyright (C) 2016-2021 Olivier Martin <olivier@labapart.org>
* Copyright (C) 2016-2024 Olivier Martin <olivier@labapart.org>
*
*
* 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);

View File

@ -0,0 +1,71 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2024, Olivier Martin <olivier@labapart.org>
*/
#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);
}

View File

@ -0,0 +1,51 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2024, Olivier Martin <olivier@labapart.org>
*/
#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);
}

View File

@ -0,0 +1,94 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2024, Olivier Martin <olivier@labapart.org>
*/
#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);
}

View File

@ -0,0 +1,97 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2024, Olivier Martin <olivier@labapart.org>
*/
#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);
}

View File

@ -0,0 +1,26 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2024, Olivier Martin <olivier@labapart.org>
*/
#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;
}

View File

@ -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;
}
}

View File

@ -1,38 +1,68 @@
/*
* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later
*
* Copyright (c) 2021, Olivier Martin <olivier@labapart.org>
* Copyright (c) 2021-2024, Olivier Martin <olivier@labapart.org>
*/
#ifndef __GATTLIB_INTERNAL_DEFS_H__
#define __GATTLIB_INTERNAL_DEFS_H__
#include <stdbool.h>
#include <glib.h>
#if defined(WITH_PYTHON)
#include <Python.h>
#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

View File

@ -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

View File

@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2016-2021, Olivier Martin <olivier@labapart.org>
* Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
*/
#include <glib.h>
@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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;
}
}

View File

@ -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)
# 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
# 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)
# 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 @@ gattlib_notification_start.argtypes = [c_void_p, POINTER(GattlibUuid)]
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

View File

@ -135,8 +135,9 @@ class Adapter:
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

View File

@ -4,14 +4,19 @@
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
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 @@ CONNECTION_OPTIONS_LEGACY_DEFAULT = \
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 @@ class Device:
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 @@ class Device:
return (self._connection is not None)
def connect(self, options=CONNECTION_OPTIONS_LEGACY_DEFAULT):
if self._adapter:
adapter_name = self._adapter.name
else:
adapter_name = None
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)
self._connection = gattlib_connect(adapter_name, self._addr, options)
if self._connection is None:
raise DeviceError(adapter=adapter_name, mac_address=self._addr)
if self._adapter is None:
adapter = None
else:
adapter = self._adapter._adapter
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 @@ class Device:
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 @@ class Device:
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()

View File

@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later
*
* Copyright (c) 2016-2021, Olivier Martin <olivier@labapart.org>
* Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
*/
#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