dbus: Refactore gattlib_adapter_scan_enable_with_filter() to also introduce the non-blocking version

fix-build
Olivier Martin 2022-05-13 16:10:55 +02:00 committed by Olivier Martin
parent 75fda5df84
commit b5a785e4b6
5 changed files with 184 additions and 73 deletions

View File

@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2016-2021, Olivier Martin <olivier@labapart.org>
* Copyright (c) 2016-2022, Olivier Martin <olivier@labapart.org>
*/
#include "gattlib_internal.h"
@ -98,18 +98,7 @@ GDBusObjectManager *get_device_manager_from_adapter(struct gattlib_adapter *gatt
return gattlib_adapter->device_manager;
}
/*
* Internal structure to pass to Device Manager signal handlers
*/
struct discovered_device_arg {
void *adapter;
uint32_t enabled_filters;
gattlib_discovered_device_t callback;
void *user_data;
GSList** discovered_devices_ptr;
};
static void device_manager_on_device1_signal(const char* device1_path, struct discovered_device_arg *arg)
static void device_manager_on_device1_signal(const char* device1_path, struct gattlib_adapter* gattlib_adapter)
{
GError *error = NULL;
OrgBluezDevice1* device1 = org_bluez_device1_proxy_new_for_bus_sync(
@ -135,20 +124,31 @@ static void device_manager_on_device1_signal(const char* device1_path, struct di
}
// Check if the device is already part of the list
GSList *item = g_slist_find_custom(*arg->discovered_devices_ptr, address, (GCompareFunc)g_ascii_strcasecmp);
GSList *item = g_slist_find_custom(gattlib_adapter->ble_scan.discovered_devices, address, (GCompareFunc)g_ascii_strcasecmp);
// First time this device is in the list
if (item == NULL) {
// Add the device to the list
*arg->discovered_devices_ptr = g_slist_append(*arg->discovered_devices_ptr, g_strdup(address));
gattlib_adapter->ble_scan.discovered_devices = g_slist_append(gattlib_adapter->ble_scan.discovered_devices, g_strdup(address));
}
if ((item == NULL) || (arg->enabled_filters & GATTLIB_DISCOVER_FILTER_NOTIFY_CHANGE)) {
arg->callback(
arg->adapter,
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),
arg->user_data);
gattlib_adapter->ble_scan.discovered_device_user_data);
#if defined(WITH_PYTHON)
PyGILState_Release(d_gstate);
#endif
}
g_object_unref(device1);
}
@ -196,15 +196,41 @@ on_interface_proxy_properties_changed (GDBusObjectManagerClient *device_manager,
device_manager_on_device1_signal(g_dbus_proxy_get_object_path(interface_proxy), user_data);
}
int gattlib_adapter_scan_enable_with_filter(void *adapter, uuid_t **uuid_list, int16_t rssi_threshold, uint32_t enabled_filters,
static void* _ble_scan_loop(void* args) {
struct gattlib_adapter *gattlib_adapter = args;
// Run Glib loop for 'timeout' seconds
gattlib_adapter->ble_scan.scan_loop = g_main_loop_new(NULL, 0);
if (gattlib_adapter->ble_scan.ble_scan_timeout > 0) {
gattlib_adapter->ble_scan.ble_scan_timeout_id = g_timeout_add_seconds(gattlib_adapter->ble_scan.ble_scan_timeout,
stop_scan_func, gattlib_adapter->ble_scan.scan_loop);
}
// And start the loop...
g_main_loop_run(gattlib_adapter->ble_scan.scan_loop);
// At this point, either the timeout expired (and automatically was removed) or scan_disable was called, removing the timer.
gattlib_adapter->ble_scan.ble_scan_timeout_id = 0;
// Note: The function only resumes when loop timeout as expired or g_main_loop_quit has been called.
g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(gattlib_adapter->device_manager), gattlib_adapter->ble_scan.added_signal_id);
g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(gattlib_adapter->device_manager), gattlib_adapter->ble_scan.changed_signal_id);
// Ensure BLE device discovery is stopped
gattlib_adapter_scan_disable(gattlib_adapter);
// Free discovered device list
g_slist_foreach(gattlib_adapter->ble_scan.discovered_devices, (GFunc)g_free, NULL);
g_slist_free(gattlib_adapter->ble_scan.discovered_devices);
return 0;
}
static int _gattlib_adapter_scan_enable_with_filter(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)
{
struct gattlib_adapter *gattlib_adapter = adapter;
GDBusObjectManager *device_manager;
GError *error = NULL;
int ret = GATTLIB_SUCCESS;
int added_signal_id, changed_signal_id;
GSList *discovered_devices = NULL;
GVariantBuilder arg_properties_builder;
GVariant *rssi_variant = NULL;
@ -250,28 +276,26 @@ int gattlib_adapter_scan_enable_with_filter(void *adapter, uuid_t **uuid_list, i
//
device_manager = get_device_manager_from_adapter(gattlib_adapter);
if (device_manager == NULL) {
goto DISABLE_SCAN;
return GATTLIB_ERROR_DBUS;
}
// Pass the user callback and the discovered device list pointer to the signal handlers
struct discovered_device_arg discovered_device_arg = {
.adapter = adapter,
.enabled_filters = enabled_filters,
.callback = discovered_device_cb,
.user_data = user_data,
.discovered_devices_ptr = &discovered_devices,
};
// Clear BLE scan structure
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;
added_signal_id = g_signal_connect(G_DBUS_OBJECT_MANAGER(device_manager),
gattlib_adapter->ble_scan.added_signal_id = g_signal_connect(G_DBUS_OBJECT_MANAGER(device_manager),
"object-added",
G_CALLBACK (on_dbus_object_added),
&discovered_device_arg);
gattlib_adapter);
// List for object changes to see if there are still devices around
changed_signal_id = g_signal_connect(G_DBUS_OBJECT_MANAGER(device_manager),
gattlib_adapter->ble_scan.changed_signal_id = g_signal_connect(G_DBUS_OBJECT_MANAGER(device_manager),
"interface-proxy-properties-changed",
G_CALLBACK(on_interface_proxy_properties_changed),
&discovered_device_arg);
gattlib_adapter);
// Now, start BLE discovery
org_bluez_adapter1_call_start_discovery_sync(gattlib_adapter->adapter_proxy, NULL, &error);
@ -281,28 +305,43 @@ int gattlib_adapter_scan_enable_with_filter(void *adapter, uuid_t **uuid_list, i
return GATTLIB_ERROR_DBUS;
}
// Run Glib loop for 'timeout' seconds
gattlib_adapter->scan_loop = g_main_loop_new(NULL, 0);
if (timeout > 0) {
gattlib_adapter->timeout_id = g_timeout_add_seconds(timeout, stop_scan_func, gattlib_adapter->scan_loop);
return GATTLIB_SUCCESS;
}
int gattlib_adapter_scan_enable_with_filter(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)
{
int ret;
ret = _gattlib_adapter_scan_enable_with_filter(adapter, uuid_list, rssi_threshold, enabled_filters,
discovered_device_cb, timeout, user_data);
if (ret != GATTLIB_SUCCESS) {
return ret;
}
g_main_loop_run(gattlib_adapter->scan_loop);
// At this point, either the timeout expired (and automatically was removed) or scan_disable was called, removing the timer.
gattlib_adapter->timeout_id = 0;
// Note: The function only resumes when loop timeout as expired or g_main_loop_quit has been called.
_ble_scan_loop(adapter);
return 0;
}
g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(device_manager), added_signal_id);
g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(device_manager), changed_signal_id);
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)
{
struct gattlib_adapter *gattlib_adapter = adapter;
int ret;
DISABLE_SCAN:
// Stop BLE device discovery
gattlib_adapter_scan_disable(adapter);
ret = _gattlib_adapter_scan_enable_with_filter(adapter, uuid_list, rssi_threshold, enabled_filters,
discovered_device_cb, timeout, user_data);
if (ret != GATTLIB_SUCCESS) {
return ret;
}
// Free discovered device list
g_slist_foreach(discovered_devices, (GFunc)g_free, NULL);
g_slist_free(discovered_devices);
return ret;
ret = pthread_create(&gattlib_adapter->ble_scan.thread, NULL, _ble_scan_loop, gattlib_adapter);
if (ret != 0) {
GATTLIB_LOG(GATTLIB_ERROR, "Failt to create BLE scan thread.");
return ret;
}
return 0;
}
int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t discovered_device_cb, size_t timeout, void *user_data)
@ -316,24 +355,24 @@ int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t disco
int gattlib_adapter_scan_disable(void* adapter) {
struct gattlib_adapter *gattlib_adapter = adapter;
if (gattlib_adapter->scan_loop) {
if (gattlib_adapter->ble_scan.scan_loop) {
GError *error = NULL;
org_bluez_adapter1_call_stop_discovery_sync(gattlib_adapter->adapter_proxy, NULL, &error);
// Ignore the error
// Remove timeout
if (gattlib_adapter->timeout_id) {
g_source_remove(gattlib_adapter->timeout_id);
gattlib_adapter->timeout_id = 0;
if (gattlib_adapter->ble_scan.ble_scan_timeout_id) {
g_source_remove(gattlib_adapter->ble_scan.ble_scan_timeout_id);
gattlib_adapter->ble_scan.ble_scan_timeout_id = 0;
}
// Ensure the scan loop is quit
if (g_main_loop_is_running(gattlib_adapter->scan_loop)) {
g_main_loop_quit(gattlib_adapter->scan_loop);
if (g_main_loop_is_running(gattlib_adapter->ble_scan.scan_loop)) {
g_main_loop_quit(gattlib_adapter->ble_scan.scan_loop);
}
g_main_loop_unref(gattlib_adapter->scan_loop);
gattlib_adapter->scan_loop = NULL;
g_main_loop_unref(gattlib_adapter->ble_scan.scan_loop);
gattlib_adapter->ble_scan.scan_loop = NULL;
}
return GATTLIB_SUCCESS;

View File

@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2016-2021, Olivier Martin <olivier@labapart.org>
* Copyright (c) 2016-2022, Olivier Martin <olivier@labapart.org>
*/
#ifndef __GATTLIB_INTERNAL_H__
@ -19,6 +19,10 @@
#include "org-bluez-gattdescriptor1.h"
#include "org-bluez-gattservice1.h"
#if defined(WITH_PYTHON)
#include <Python.h>
#endif
#include "bluez5/lib/uuid.h"
#define BLUEZ_VERSIONS(major, minor) (((major) << 8) | (minor))
@ -55,8 +59,25 @@ struct gattlib_adapter {
OrgBluezAdapter1 *adapter_proxy;
char* adapter_name;
GMainLoop *scan_loop;
guint timeout_id;
// Internal attributes only needed during BLE scanning
struct {
// This list is used to stored discovered devices during BLE scan.
// The list is freed when the BLE scanning is completed.
GSList *discovered_devices;
int added_signal_id;
int changed_signal_id;
size_t ble_scan_timeout;
guint ble_scan_timeout_id;
pthread_t thread; // Thread used to run the scan_loop
GMainLoop *scan_loop;
uint32_t enabled_filters;
gattlib_discovered_device_t discovered_device_callback;
void *discovered_device_user_data;
} ble_scan;
};
struct dbus_characteristic {

View File

@ -5,6 +5,9 @@
#
from ctypes import *
import logging
logger = logging.getLogger(__name__)
gattlib = CDLL("libgattlib.so")
@ -82,10 +85,10 @@ gattlib_discovered_device_with_data_type = CFUNCTYPE(None, c_void_p, c_char_p, c
POINTER(GattlibAdvertisementData), c_size_t, c_uint16, c_void_p, c_size_t,
py_object)
# int gattlib_adapter_scan_enable_with_filter(void *adapter, uuid_t **uuid_list, int16_t rssi_threshold, uint32_t enabled_filters,
# 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 = gattlib.gattlib_adapter_scan_enable_with_filter
gattlib_adapter_scan_enable_with_filter.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 = 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]
# 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)

View File

@ -61,13 +61,35 @@ class Adapter:
self._is_opened = False
return ret
def on_discovered_device(self, adapter, addr, name, user_data):
device = Device(self, addr, name)
self.on_discovered_device_callback(device, user_data)
# Use a closure to return a method that can be called by the C-library (see: https://stackoverflow.com/a/7261524/6267288)
def get_on_discovered_device_callback(self):
def on_discovered_device(adapter, addr, name, user_data):
try:
device = Device(self, addr, name)
self.on_discovered_device_user_callback(device, user_data)
except Exception as e:
logger.exception(e)
return gattlib_discovered_device_type(on_discovered_device)
def scan_enable(self, on_discovered_device_callback, timeout, notify_change=False, uuids=None, rssi_threshold=None, user_data=None):
"""
Scan for BLE devices
@param adapter: is the context of the newly opened adapter
@param uuid_list: is a NULL-terminated list of UUIDs to filter. The rule only applies to advertised UUID.
Returned devices would match any of the UUIDs of the list.
@param rssi_threshold: is the imposed RSSI threshold for the returned devices.
@param enabled_filters: defines the parameters to use for filtering. There are selected by using the macros
GATTLIB_DISCOVER_FILTER_USE_UUID and GATTLIB_DISCOVER_FILTER_USE_RSSI.
@param discovered_device_cb: is the function callback called for each new Bluetooth device discovered
@param timeout: defines the duration of the Bluetooth scanning. When timeout=None or 0, we scan indefinitely.
@param user_data: is the data passed to the callback `discovered_device_cb()`
"""
assert on_discovered_device_callback != None
self.on_discovered_device_callback = on_discovered_device_callback
self.on_discovered_device_user_callback = on_discovered_device_callback
# Save callback to prevent it to be cleaned by garbage collector see
# comment: https://stackoverflow.com/questions/7259794/how-can-i-get-methods-to-work-as-callbacks-with-python-ctypes#comment38658391_7261524
self.on_discovered_device_callback = self.get_on_discovered_device_callback()
if not self._is_opened:
raise AdapterNotOpened()
@ -99,9 +121,13 @@ class Adapter:
if notify_change:
enabled_filters |= GATTLIB_DISCOVER_FILTER_NOTIFY_CHANGE
ret = gattlib_adapter_scan_enable_with_filter(self._adapter,
# gattlib_adapter_scan_enable_with_filter_non_blocking() assumes a 0-timeout means scanning indefintely
if timeout is None:
timeout = 0
ret = gattlib_adapter_scan_enable_with_filter_non_blocking(self._adapter,
uuid_list, rssi, enabled_filters,
gattlib_discovered_device_type(self.on_discovered_device),
self.on_discovered_device_callback,
timeout, user_data)
handle_return(ret)

View File

@ -230,6 +230,8 @@ int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t disco
/**
* @brief Enable Bluetooth scanning on a given adapter
*
* This function will block until either the timeout has expired or gattlib_adapter_scan_disable() has been called.
*
* @param adapter is the context of the newly opened adapter
* @param uuid_list is a NULL-terminated list of UUIDs to filter. The rule only applies to advertised UUID.
* Returned devices would match any of the UUIDs of the list.
@ -245,6 +247,26 @@ int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t disco
int gattlib_adapter_scan_enable_with_filter(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);
/**
* @brief Enable Bluetooth scanning on a given adapter (non-blocking)
*
* This function will return as soon as the BLE scan has been started.
*
* @param adapter is the context of the newly opened adapter
* @param uuid_list is a NULL-terminated list of UUIDs to filter. The rule only applies to advertised UUID.
* Returned devices would match any of the UUIDs of the list.
* @param rssi_threshold is the imposed RSSI threshold for the returned devices.
* @param enabled_filters defines the parameters to use for filtering. There are selected by using the macros
* GATTLIB_DISCOVER_FILTER_USE_UUID and GATTLIB_DISCOVER_FILTER_USE_RSSI.
* @param discovered_device_cb is the function callback called for each new Bluetooth device discovered
* @param timeout defines the duration of the Bluetooth scanning. When timeout=0, we scan indefinitely.
* @param user_data is the data passed to the callback `discovered_device_cb()`
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
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);
/**
* @brief Enable Eddystone Bluetooth Device scanning on a given adapter
*