Skip to content

Commit

Permalink
python: Fix callback back to native gattlib
Browse files Browse the repository at this point in the history
  • Loading branch information
oliviermartin committed Feb 12, 2024
1 parent ec9e5cd commit 9fd8030
Show file tree
Hide file tree
Showing 17 changed files with 578 additions and 191 deletions.
8 changes: 4 additions & 4 deletions bluez/gattlib_connect.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);

Expand Down
71 changes: 71 additions & 0 deletions common/gattlib_callback_connected_device.c
Original file line number Diff line number Diff line change
@@ -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);
}
51 changes: 51 additions & 0 deletions common/gattlib_callback_disconnected_device.c
Original file line number Diff line number Diff line change
@@ -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);
}
94 changes: 94 additions & 0 deletions common/gattlib_callback_discovered_device.c
Original file line number Diff line number Diff line change
@@ -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);
}
97 changes: 97 additions & 0 deletions common/gattlib_callback_notification_device.c
Original file line number Diff line number Diff line change
@@ -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);
}
26 changes: 26 additions & 0 deletions common/gattlib_callback_python.c
Original file line number Diff line number Diff line change
@@ -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;
}

Loading

0 comments on commit 9fd8030

Please sign in to comment.