mirror of https://github.com/labapart/gattlib
769 lines
25 KiB
C
769 lines
25 KiB
C
/*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*
|
|
* Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
|
|
*/
|
|
|
|
#include "gattlib_internal.h"
|
|
|
|
// This recursive mutex ensures all gattlib objects can be accessed in a multi-threaded environment
|
|
// The recursive mutex allows a same thread to lock twice the mutex without being blocked by itself.
|
|
GRecMutex m_gattlib_mutex;
|
|
|
|
// This structure is used for inter-thread communication
|
|
struct gattlib_signal m_gattlib_signal;
|
|
|
|
|
|
int gattlib_adapter_open(const char* adapter_name, gattlib_adapter_t** adapter) {
|
|
char object_path[20];
|
|
gattlib_adapter_t* gattlib_adapter;
|
|
OrgBluezAdapter1 *adapter_proxy;
|
|
GError *error = NULL;
|
|
|
|
if (adapter == NULL) {
|
|
return GATTLIB_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (adapter_name == NULL) {
|
|
adapter_name = GATTLIB_DEFAULT_ADAPTER;
|
|
}
|
|
|
|
snprintf(object_path, sizeof(object_path), "/org/bluez/%s", adapter_name);
|
|
|
|
// Check if adapter has already be loaded
|
|
g_rec_mutex_lock(&m_gattlib_mutex);
|
|
*adapter = gattlib_adapter_from_id(object_path);
|
|
if (*adapter != NULL) {
|
|
GATTLIB_LOG(GATTLIB_DEBUG, "Bluetooth adapter %s has already been opened. Re-use it", adapter_name);
|
|
gattlib_adapter_ref(*adapter);
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
return GATTLIB_SUCCESS;
|
|
}
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
|
|
GATTLIB_LOG(GATTLIB_DEBUG, "Open bluetooth adapter %s", adapter_name);
|
|
|
|
adapter_proxy = org_bluez_adapter1_proxy_new_for_bus_sync(
|
|
G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE,
|
|
"org.bluez",
|
|
object_path,
|
|
NULL, &error);
|
|
if (adapter_proxy == NULL) {
|
|
int ret = GATTLIB_ERROR_DBUS;
|
|
if (error) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Failed to get adapter %s: %s", object_path, error->message);
|
|
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
|
|
g_error_free(error);
|
|
} else {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Failed to get adapter %s", object_path);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Ensure the adapter is powered on
|
|
org_bluez_adapter1_set_powered(adapter_proxy, TRUE);
|
|
|
|
gattlib_adapter = calloc(1, sizeof(struct _gattlib_adapter));
|
|
if (gattlib_adapter == NULL) {
|
|
return GATTLIB_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// Initialize stucture
|
|
gattlib_adapter->id = strdup(object_path);
|
|
gattlib_adapter->name = strdup(adapter_name);
|
|
gattlib_adapter->reference_counter = 1;
|
|
gattlib_adapter->backend.adapter_proxy = adapter_proxy;
|
|
|
|
g_rec_mutex_lock(&m_gattlib_mutex);
|
|
m_adapter_list = g_slist_append(m_adapter_list, gattlib_adapter);
|
|
*adapter = gattlib_adapter;
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
|
|
return GATTLIB_SUCCESS;
|
|
}
|
|
|
|
const char *gattlib_adapter_get_name(gattlib_adapter_t* adapter) {
|
|
//
|
|
// Note: There is a risk we access the memory when it has been freed
|
|
// What we should do is to take 'm_gattlib_mutex', then to check the adapter is valid
|
|
// then to duplicate the string
|
|
//
|
|
return adapter->name;
|
|
}
|
|
|
|
gattlib_adapter_t* init_default_adapter(void) {
|
|
gattlib_adapter_t* gattlib_adapter;
|
|
int ret;
|
|
|
|
ret = gattlib_adapter_open(NULL, &gattlib_adapter);
|
|
if (ret != GATTLIB_SUCCESS) {
|
|
return NULL;
|
|
} else {
|
|
return gattlib_adapter;
|
|
}
|
|
}
|
|
|
|
GDBusObjectManager *get_device_manager_from_adapter(gattlib_adapter_t* gattlib_adapter, GError **error) {
|
|
if (gattlib_adapter->backend.device_manager) {
|
|
goto EXIT;
|
|
}
|
|
|
|
//
|
|
// Get notification when objects are removed from the Bluez ObjectManager.
|
|
// We should get notified when the connection is lost with the target to allow
|
|
// us to advertise us again
|
|
//
|
|
gattlib_adapter->backend.device_manager = g_dbus_object_manager_client_new_for_bus_sync(
|
|
G_BUS_TYPE_SYSTEM,
|
|
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
|
"org.bluez",
|
|
"/",
|
|
NULL, NULL, NULL, NULL,
|
|
error);
|
|
if (gattlib_adapter->backend.device_manager == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
EXIT:
|
|
return gattlib_adapter->backend.device_manager;
|
|
}
|
|
|
|
static void device_manager_on_added_device1_signal(const char* device1_path, gattlib_adapter_t* gattlib_adapter)
|
|
{
|
|
GError *error = NULL;
|
|
OrgBluezDevice1* device1 = org_bluez_device1_proxy_new_for_bus_sync(
|
|
G_BUS_TYPE_SYSTEM,
|
|
G_DBUS_PROXY_FLAGS_NONE,
|
|
"org.bluez",
|
|
device1_path,
|
|
NULL,
|
|
&error);
|
|
if (error) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Failed to connection to new DBus Bluez Device: %s",
|
|
error->message);
|
|
g_error_free(error);
|
|
}
|
|
|
|
if (device1) {
|
|
const gchar *address = org_bluez_device1_get_address(device1);
|
|
int ret;
|
|
|
|
// Sometimes org_bluez_device1_get_address returns null addresses. If that's the case, early return.
|
|
if (address == NULL) {
|
|
g_object_unref(device1);
|
|
return;
|
|
}
|
|
|
|
g_rec_mutex_lock(&m_gattlib_mutex);
|
|
|
|
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "device_manager_on_added_device1_signal: Adapter not valid");
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
g_object_unref(device1);
|
|
return;
|
|
}
|
|
|
|
//TODO: Add support for connected device with 'gboolean org_bluez_device1_get_connected (OrgBluezDevice1 *object);'
|
|
// When the device is connected, we potentially need to initialize some attributes
|
|
ret = gattlib_device_set_state(gattlib_adapter, device1_path, DISCONNECTED);
|
|
if (ret == GATTLIB_SUCCESS) {
|
|
gattlib_on_discovered_device(gattlib_adapter, device1);
|
|
}
|
|
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
g_object_unref(device1);
|
|
}
|
|
}
|
|
|
|
static void on_dbus_object_added(GDBusObjectManager *device_manager,
|
|
GDBusObject *object,
|
|
gpointer user_data)
|
|
{
|
|
const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object));
|
|
|
|
GDBusInterface *interface = g_dbus_object_manager_get_interface(device_manager, object_path, "org.bluez.Device1");
|
|
if (!interface) {
|
|
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: on_object_added: %s (not 'org.bluez.Device1')", object_path);
|
|
return;
|
|
}
|
|
|
|
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: on_object_added: %s (has 'org.bluez.Device1')", object_path);
|
|
|
|
// It is a 'org.bluez.Device1'
|
|
device_manager_on_added_device1_signal(object_path, user_data);
|
|
|
|
g_object_unref(interface);
|
|
}
|
|
|
|
static void on_dbus_object_removed(GDBusObjectManager *device_manager,
|
|
GDBusObject *object,
|
|
gpointer user_data)
|
|
{
|
|
const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object));
|
|
gattlib_adapter_t* gattlib_adapter = user_data;
|
|
|
|
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: on_object_removed: %s", object_path);
|
|
|
|
// Mark the device has not present
|
|
gattlib_device_set_state(gattlib_adapter, object_path, NOT_FOUND);
|
|
}
|
|
|
|
static void
|
|
on_interface_proxy_properties_changed (GDBusObjectManagerClient *device_manager,
|
|
GDBusObjectProxy *object_proxy,
|
|
GDBusProxy *interface_proxy,
|
|
GVariant *changed_properties,
|
|
const gchar *const *invalidated_properties,
|
|
gpointer user_data)
|
|
{
|
|
const char* proxy_object_path = g_dbus_proxy_get_object_path(interface_proxy);
|
|
gattlib_adapter_t* gattlib_adapter = user_data;
|
|
|
|
// Count number of invalidated properties
|
|
size_t invalidated_properties_count = 0;
|
|
if (invalidated_properties != NULL) {
|
|
const gchar *const *invalidated_properties_ptr = invalidated_properties;
|
|
while (*invalidated_properties_ptr != NULL) {
|
|
invalidated_properties_count++;
|
|
invalidated_properties_ptr++;
|
|
}
|
|
}
|
|
|
|
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: on_interface_proxy_properties_changed(%s): interface:%s changed_properties:%s invalidated_properties:%d",
|
|
proxy_object_path,
|
|
g_dbus_proxy_get_interface_name(interface_proxy),
|
|
g_variant_print(changed_properties, TRUE),
|
|
invalidated_properties_count);
|
|
|
|
g_rec_mutex_lock(&m_gattlib_mutex);
|
|
|
|
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "on_interface_proxy_properties_changed: Adapter not valid");
|
|
goto EXIT;
|
|
}
|
|
|
|
if (gattlib_adapter->backend.device_manager == NULL) {
|
|
goto EXIT;
|
|
}
|
|
|
|
// Check if the object is a 'org.bluez.Device1'
|
|
if (strcmp(g_dbus_proxy_get_interface_name(interface_proxy), "org.bluez.Device1") == 0) {
|
|
// It is a 'org.bluez.Device1'
|
|
GError *error = NULL;
|
|
|
|
OrgBluezDevice1* device1 = org_bluez_device1_proxy_new_for_bus_sync(
|
|
G_BUS_TYPE_SYSTEM,
|
|
G_DBUS_PROXY_FLAGS_NONE,
|
|
"org.bluez",
|
|
proxy_object_path, NULL, &error);
|
|
if (error) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Failed to connection to new DBus Bluez Device: %s", error->message);
|
|
g_error_free(error);
|
|
goto EXIT;
|
|
} else if (device1 == NULL) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Unexpected NULL device");
|
|
goto EXIT;
|
|
}
|
|
|
|
// Check if the device has been disconnected
|
|
GVariantDict dict;
|
|
g_variant_dict_init(&dict, changed_properties);
|
|
GVariant* has_rssi = g_variant_dict_lookup_value(&dict, "RSSI", NULL);
|
|
GVariant* has_manufacturer_data = g_variant_dict_lookup_value(&dict, "ManufacturerData", NULL);
|
|
|
|
enum _gattlib_device_state old_device_state = gattlib_device_get_state(gattlib_adapter, proxy_object_path);
|
|
|
|
if (old_device_state == NOT_FOUND) {
|
|
if (has_rssi || has_manufacturer_data) {
|
|
int ret = gattlib_device_set_state(gattlib_adapter, proxy_object_path, DISCONNECTED);
|
|
if (ret == GATTLIB_SUCCESS) {
|
|
gattlib_on_discovered_device(gattlib_adapter, device1);
|
|
}
|
|
}
|
|
}
|
|
|
|
g_variant_dict_end(&dict);
|
|
g_object_unref(device1);
|
|
}
|
|
|
|
EXIT:
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
}
|
|
|
|
/**
|
|
* Function that waits for the end of the BLE scan
|
|
*
|
|
* It either called when we wait for BLE scan to complete or when we close the BLE adapter
|
|
*/
|
|
static void _wait_scan_loop_stop_scanning(gattlib_adapter_t* gattlib_adapter) {
|
|
g_mutex_lock(&m_gattlib_signal.mutex);
|
|
while (gattlib_adapter_is_scanning(gattlib_adapter)) {
|
|
g_cond_wait(&m_gattlib_signal.condition, &m_gattlib_signal.mutex);
|
|
}
|
|
g_mutex_unlock(&m_gattlib_signal.mutex);
|
|
}
|
|
|
|
/**
|
|
* Function called when the BLE scan duration has timeout
|
|
*/
|
|
static gboolean _stop_scan_on_timeout(gpointer data) {
|
|
gattlib_adapter_t* gattlib_adapter = data;
|
|
|
|
g_rec_mutex_lock(&m_gattlib_mutex);
|
|
|
|
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "_stop_scan_on_timeout: Adapter not valid");
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
return FALSE;
|
|
}
|
|
|
|
if (gattlib_adapter->backend.ble_scan.is_scanning) {
|
|
g_mutex_lock(&m_gattlib_signal.mutex);
|
|
gattlib_adapter->backend.ble_scan.is_scanning = false;
|
|
m_gattlib_signal.signals |= GATTLIB_SIGNAL_ADAPTER_STOP_SCANNING;
|
|
g_cond_broadcast(&m_gattlib_signal.condition);
|
|
g_mutex_unlock(&m_gattlib_signal.mutex);
|
|
}
|
|
|
|
// Unset timeout ID to not try removing it
|
|
gattlib_adapter->backend.ble_scan.ble_scan_timeout_id = 0;
|
|
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
|
|
GATTLIB_LOG(GATTLIB_DEBUG, "BLE scan is stopped after scanning time has expired.");
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Thread that waits for the end of BLE scan that is triggered either by a timeout of the BLE scan
|
|
* or disabling the BLE scan
|
|
*/
|
|
static void* _ble_scan_loop_thread(void* args) {
|
|
gattlib_adapter_t* gattlib_adapter = args;
|
|
|
|
g_rec_mutex_lock(&m_gattlib_mutex);
|
|
|
|
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "_ble_scan_loop_thread: Adapter not valid (1)");
|
|
goto EXIT;
|
|
}
|
|
|
|
if (gattlib_adapter->backend.ble_scan.ble_scan_timeout_id > 0) {
|
|
GATTLIB_LOG(GATTLIB_WARNING, "A BLE scan seems to already be in progress.");
|
|
}
|
|
|
|
gattlib_adapter->backend.ble_scan.is_scanning = true;
|
|
|
|
if (gattlib_adapter->backend.ble_scan.ble_scan_timeout > 0) {
|
|
GATTLIB_LOG(GATTLIB_DEBUG, "Scan for BLE devices for %ld seconds", gattlib_adapter->backend.ble_scan.ble_scan_timeout);
|
|
|
|
gattlib_adapter->backend.ble_scan.ble_scan_timeout_id = g_timeout_add_seconds(gattlib_adapter->backend.ble_scan.ble_scan_timeout,
|
|
_stop_scan_on_timeout, gattlib_adapter);
|
|
}
|
|
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
|
|
// Wait for the BLE scan to be explicitely stopped by 'gattlib_adapter_scan_disable()' or timeout.
|
|
_wait_scan_loop_stop_scanning(gattlib_adapter);
|
|
|
|
// Note: The function only resumes when loop timeout as expired or g_main_loop_quit has been called.
|
|
|
|
g_rec_mutex_lock(&m_gattlib_mutex);
|
|
|
|
// Confirm gattlib_adapter is still valid
|
|
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "_ble_scan_loop_thread: Adapter not valid (2)");
|
|
goto EXIT;
|
|
}
|
|
|
|
g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(gattlib_adapter->backend.device_manager), gattlib_adapter->backend.ble_scan.added_signal_id);
|
|
g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(gattlib_adapter->backend.device_manager), gattlib_adapter->backend.ble_scan.removed_signal_id);
|
|
g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(gattlib_adapter->backend.device_manager), gattlib_adapter->backend.ble_scan.changed_signal_id);
|
|
|
|
// Ensure BLE device discovery is stopped
|
|
gattlib_adapter_scan_disable(gattlib_adapter);
|
|
|
|
EXIT:
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
return NULL;
|
|
}
|
|
|
|
static int _gattlib_adapter_scan_enable_with_filter(gattlib_adapter_t* 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)
|
|
{
|
|
GDBusObjectManager *device_manager;
|
|
GError *error = NULL;
|
|
GVariantBuilder arg_properties_builder;
|
|
GVariant *rssi_variant = NULL;
|
|
int ret;
|
|
|
|
if ((adapter == NULL) || (adapter->backend.adapter_proxy == NULL)) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Could not start BLE scan. No opened bluetooth adapter");
|
|
return GATTLIB_NO_ADAPTER;
|
|
}
|
|
|
|
g_variant_builder_init(&arg_properties_builder, G_VARIANT_TYPE("a{sv}"));
|
|
|
|
if (enabled_filters & GATTLIB_DISCOVER_FILTER_USE_UUID) {
|
|
char uuid_str[MAX_LEN_UUID_STR + 1];
|
|
GVariantBuilder list_uuid_builder;
|
|
|
|
if (uuid_list == NULL) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Could not start BLE scan. Missing list of UUIDs");
|
|
return GATTLIB_INVALID_PARAMETER;
|
|
}
|
|
|
|
GATTLIB_LOG(GATTLIB_DEBUG, "Configure bluetooth scan with UUID");
|
|
|
|
g_variant_builder_init(&list_uuid_builder, G_VARIANT_TYPE ("as"));
|
|
|
|
for (uuid_t **uuid_ptr = uuid_list; *uuid_ptr != NULL; uuid_ptr++) {
|
|
gattlib_uuid_to_string(*uuid_ptr, uuid_str, sizeof(uuid_str));
|
|
g_variant_builder_add(&list_uuid_builder, "s", uuid_str);
|
|
}
|
|
|
|
g_variant_builder_add(&arg_properties_builder, "{sv}", "UUIDs", g_variant_builder_end(&list_uuid_builder));
|
|
}
|
|
|
|
if (enabled_filters & GATTLIB_DISCOVER_FILTER_USE_RSSI) {
|
|
GATTLIB_LOG(GATTLIB_DEBUG, "Configure bluetooth scan with RSSI");
|
|
GVariant *rssi_variant = g_variant_new_int16(rssi_threshold);
|
|
g_variant_builder_add(&arg_properties_builder, "{sv}", "RSSI", rssi_variant);
|
|
}
|
|
|
|
org_bluez_adapter1_call_set_discovery_filter_sync(adapter->backend.adapter_proxy,
|
|
g_variant_builder_end(&arg_properties_builder), NULL, &error);
|
|
|
|
if (rssi_variant) {
|
|
g_variant_unref(rssi_variant);
|
|
}
|
|
|
|
if (error) {
|
|
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Failed to set discovery filter: %s (%d.%d)",
|
|
error->message, error->domain, error->code);
|
|
g_error_free(error);
|
|
return ret;
|
|
}
|
|
|
|
//
|
|
// Get notification when objects are removed from the Bluez ObjectManager.
|
|
// We should get notified when the connection is lost with the target to allow
|
|
// us to advertise us again
|
|
//
|
|
device_manager = get_device_manager_from_adapter(adapter, &error);
|
|
if (device_manager == NULL) {
|
|
if (error != NULL) {
|
|
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
|
|
g_error_free(error);
|
|
} else {
|
|
ret = GATTLIB_ERROR_DBUS;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Clear BLE scan structure
|
|
memset(&adapter->backend.ble_scan, 0, sizeof(adapter->backend.ble_scan));
|
|
adapter->backend.ble_scan.enabled_filters = enabled_filters;
|
|
adapter->backend.ble_scan.ble_scan_timeout = timeout;
|
|
adapter->discovered_device_callback.callback.discovered_device = discovered_device_cb;
|
|
adapter->discovered_device_callback.user_data = user_data;
|
|
|
|
adapter->backend.ble_scan.added_signal_id = g_signal_connect(G_DBUS_OBJECT_MANAGER(device_manager),
|
|
"object-added",
|
|
G_CALLBACK(on_dbus_object_added),
|
|
adapter);
|
|
|
|
adapter->backend.ble_scan.removed_signal_id = g_signal_connect(G_DBUS_OBJECT_MANAGER(device_manager),
|
|
"object-removed",
|
|
G_CALLBACK(on_dbus_object_removed),
|
|
adapter);
|
|
|
|
// List for object changes to see if there are still devices around
|
|
adapter->backend.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),
|
|
adapter);
|
|
|
|
// Now, start BLE discovery
|
|
org_bluez_adapter1_call_start_discovery_sync(adapter->backend.adapter_proxy, NULL, &error);
|
|
if (error) {
|
|
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Failed to start discovery: %s", error->message);
|
|
g_error_free(error);
|
|
return ret;
|
|
}
|
|
|
|
GATTLIB_LOG(GATTLIB_DEBUG, "Bluetooth scan started");
|
|
return GATTLIB_SUCCESS;
|
|
}
|
|
|
|
int gattlib_adapter_scan_enable_with_filter(gattlib_adapter_t* 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)
|
|
{
|
|
GError *error = NULL;
|
|
int ret = GATTLIB_SUCCESS;
|
|
|
|
g_rec_mutex_lock(&m_gattlib_mutex);
|
|
|
|
if (!gattlib_adapter_is_valid(adapter)) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_adapter_scan_enable_with_filter: Adapter not valid (1)");
|
|
ret = GATTLIB_ADAPTER_CLOSE;
|
|
goto EXIT;
|
|
}
|
|
|
|
ret = _gattlib_adapter_scan_enable_with_filter(adapter, uuid_list, rssi_threshold, enabled_filters,
|
|
discovered_device_cb, timeout, user_data);
|
|
if (ret != GATTLIB_SUCCESS) {
|
|
goto EXIT;
|
|
}
|
|
|
|
adapter->backend.ble_scan.is_scanning = true;
|
|
|
|
adapter->backend.ble_scan.scan_loop_thread = g_thread_try_new("gattlib_ble_scan", _ble_scan_loop_thread, adapter, &error);
|
|
if (adapter->backend.ble_scan.scan_loop_thread == NULL) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Failed to create BLE scan thread: %s", error->message);
|
|
g_error_free(error);
|
|
ret = GATTLIB_ERROR_INTERNAL;
|
|
goto EXIT;
|
|
}
|
|
|
|
// We need to release the mutex to ensure we leave the other thread to signal us
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
|
|
g_mutex_lock(&m_gattlib_signal.mutex);
|
|
while (gattlib_adapter_is_scanning(adapter)) {
|
|
g_cond_wait(&m_gattlib_signal.condition, &m_gattlib_signal.mutex);
|
|
}
|
|
g_mutex_unlock(&m_gattlib_signal.mutex);
|
|
|
|
// Get the mutex again
|
|
g_rec_mutex_lock(&m_gattlib_mutex);
|
|
|
|
// Ensure the adapter is still valid when we get the mutex again
|
|
if (!gattlib_adapter_is_valid(adapter)) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_adapter_scan_enable_with_filter: Adapter not valid (2)");
|
|
ret = GATTLIB_ADAPTER_CLOSE;
|
|
goto EXIT;
|
|
}
|
|
|
|
// Free thread
|
|
g_thread_unref(adapter->backend.ble_scan.scan_loop_thread);
|
|
adapter->backend.ble_scan.scan_loop_thread = NULL;
|
|
|
|
EXIT:
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
return ret;
|
|
}
|
|
|
|
int gattlib_adapter_scan_enable_with_filter_non_blocking(gattlib_adapter_t* 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)
|
|
{
|
|
GError *error = NULL;
|
|
int ret = GATTLIB_SUCCESS;
|
|
|
|
g_rec_mutex_lock(&m_gattlib_mutex);
|
|
|
|
if (!gattlib_adapter_is_valid(adapter)) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_adapter_scan_enable_with_filter_non_blocking: Adapter not valid (2)");
|
|
ret = GATTLIB_ADAPTER_CLOSE;
|
|
goto EXIT;
|
|
}
|
|
|
|
ret = _gattlib_adapter_scan_enable_with_filter(adapter, uuid_list, rssi_threshold, enabled_filters,
|
|
discovered_device_cb, timeout, user_data);
|
|
if (ret != GATTLIB_SUCCESS) {
|
|
goto EXIT;
|
|
}
|
|
|
|
adapter->backend.ble_scan.scan_loop_thread = g_thread_try_new("gattlib_ble_scan", _ble_scan_loop_thread, adapter, &error);
|
|
if (adapter->backend.ble_scan.scan_loop_thread == NULL) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Failed to create BLE scan thread: %s", error->message);
|
|
g_error_free(error);
|
|
ret = GATTLIB_ERROR_INTERNAL;
|
|
goto EXIT;
|
|
}
|
|
|
|
EXIT:
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
return ret;
|
|
}
|
|
|
|
int gattlib_adapter_scan_enable(gattlib_adapter_t* adapter, gattlib_discovered_device_t discovered_device_cb, size_t timeout, void *user_data)
|
|
{
|
|
return gattlib_adapter_scan_enable_with_filter(adapter,
|
|
NULL, 0 /* RSSI Threshold */,
|
|
GATTLIB_DISCOVER_FILTER_USE_NONE,
|
|
discovered_device_cb, timeout, user_data);
|
|
}
|
|
|
|
int gattlib_adapter_scan_disable(gattlib_adapter_t* adapter) {
|
|
GError *error = NULL;
|
|
int ret = GATTLIB_SUCCESS;
|
|
|
|
g_rec_mutex_lock(&m_gattlib_mutex);
|
|
|
|
if (!gattlib_adapter_is_valid(adapter)) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_adapter_scan_disable: Adapter not valid");
|
|
ret = GATTLIB_ADAPTER_CLOSE;
|
|
goto EXIT;
|
|
}
|
|
|
|
if (adapter->backend.adapter_proxy == NULL) {
|
|
GATTLIB_LOG(GATTLIB_INFO, "Could not disable BLE scan. No BLE adapter setup.");
|
|
ret = GATTLIB_NO_ADAPTER;
|
|
goto EXIT;
|
|
}
|
|
|
|
if (!org_bluez_adapter1_get_discovering(adapter->backend.adapter_proxy)) {
|
|
GATTLIB_LOG(GATTLIB_DEBUG, "No discovery in progress. We skip discovery stopping (1).");
|
|
goto EXIT;
|
|
} else if (!adapter->backend.ble_scan.is_scanning) {
|
|
GATTLIB_LOG(GATTLIB_DEBUG, "No discovery in progress. We skip discovery stopping (2).");
|
|
goto EXIT;
|
|
}
|
|
|
|
GATTLIB_LOG(GATTLIB_DEBUG, "Stop bluetooth scan.");
|
|
|
|
org_bluez_adapter1_call_stop_discovery_sync(adapter->backend.adapter_proxy, NULL, &error);
|
|
if (error != NULL) {
|
|
if (((error->domain == 238) || (error->domain == 239)) && (error->code == 36)) {
|
|
GATTLIB_LOG(GATTLIB_WARNING, "No bluetooth scan has been started.");
|
|
// Correspond to error: GDBus.Error:org.bluez.Error.Failed: No discovery started
|
|
goto EXIT;
|
|
} else {
|
|
GATTLIB_LOG(GATTLIB_WARNING, "Error while stopping BLE discovery: %s (%d,%d)", error->message, error->domain, error->code);
|
|
}
|
|
}
|
|
|
|
// Free and reset callback to stop calling it after we stopped
|
|
gattlib_handler_free(&adapter->discovered_device_callback);
|
|
|
|
// Stop BLE scan loop thread
|
|
if (adapter->backend.ble_scan.is_scanning) {
|
|
adapter->backend.ble_scan.is_scanning = false;
|
|
g_mutex_lock(&m_gattlib_signal.mutex);
|
|
m_gattlib_signal.signals |= GATTLIB_SIGNAL_ADAPTER_STOP_SCANNING;
|
|
g_cond_broadcast(&m_gattlib_signal.condition);
|
|
g_mutex_unlock(&m_gattlib_signal.mutex);
|
|
}
|
|
|
|
// Remove timeout
|
|
if (adapter->backend.ble_scan.ble_scan_timeout_id) {
|
|
g_source_remove(adapter->backend.ble_scan.ble_scan_timeout_id);
|
|
adapter->backend.ble_scan.ble_scan_timeout_id = 0;
|
|
}
|
|
|
|
EXIT:
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
return ret;
|
|
}
|
|
|
|
int gattlib_adapter_close(gattlib_adapter_t* adapter) {
|
|
bool are_devices_disconnected;
|
|
int ret = GATTLIB_SUCCESS;
|
|
|
|
g_rec_mutex_lock(&m_gattlib_mutex);
|
|
|
|
if (!gattlib_adapter_is_valid(adapter)) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_adapter_close: Adapter not valid");
|
|
ret = GATTLIB_ADAPTER_CLOSE;
|
|
goto EXIT;
|
|
}
|
|
|
|
are_devices_disconnected = gattlib_devices_are_disconnected(adapter);
|
|
if (!are_devices_disconnected) {
|
|
GATTLIB_LOG(GATTLIB_ERROR, "Adapter cannot be closed as some devices are not disconnected");
|
|
ret = GATTLIB_BUSY;
|
|
goto EXIT;
|
|
}
|
|
|
|
GSList *adapter_entry = g_slist_find(m_adapter_list, adapter);
|
|
if (adapter_entry == NULL) {
|
|
GATTLIB_LOG(GATTLIB_WARNING, "Adapter has already been closed");
|
|
goto EXIT;
|
|
}
|
|
|
|
GATTLIB_LOG(GATTLIB_DEBUG, "Close bluetooth adapter %s", adapter->name);
|
|
|
|
if (adapter->backend.ble_scan.is_scanning) {
|
|
GATTLIB_LOG(GATTLIB_DEBUG, "Bluetooth adapter %s was scanning. We stop the scan", adapter->name);
|
|
gattlib_adapter_scan_disable(adapter);
|
|
|
|
// We must release gattlib mutex to not block the library
|
|
// We must also increase reference counter to not wait for a thread that has been freed
|
|
GThread *scan_loop_thread = adapter->backend.ble_scan.scan_loop_thread;
|
|
g_thread_ref(scan_loop_thread);
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
|
|
_wait_scan_loop_stop_scanning(adapter);
|
|
|
|
g_thread_join(adapter->backend.ble_scan.scan_loop_thread);
|
|
// At this stage scan_loop_thread should have completed
|
|
g_rec_mutex_lock(&m_gattlib_mutex);
|
|
g_thread_unref(scan_loop_thread);
|
|
}
|
|
|
|
// Unref/Free the adapter
|
|
gattlib_adapter_unref(adapter);
|
|
|
|
EXIT:
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
return ret;
|
|
}
|
|
|
|
int gattlib_adapter_ref(gattlib_adapter_t* adapter) {
|
|
g_rec_mutex_lock(&m_gattlib_mutex);
|
|
adapter->reference_counter++;
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
return GATTLIB_SUCCESS;
|
|
}
|
|
|
|
int gattlib_adapter_unref(gattlib_adapter_t* adapter) {
|
|
int ret = GATTLIB_SUCCESS;
|
|
|
|
g_rec_mutex_lock(&m_gattlib_mutex);
|
|
adapter->reference_counter--;
|
|
|
|
if (adapter->reference_counter > 0) {
|
|
goto EXIT;
|
|
}
|
|
|
|
// Ensure the thread is freed on adapter closing
|
|
if (adapter->backend.ble_scan.scan_loop_thread) {
|
|
g_thread_unref(adapter->backend.ble_scan.scan_loop_thread);
|
|
adapter->backend.ble_scan.scan_loop_thread = NULL;
|
|
}
|
|
|
|
if (adapter->backend.device_manager) {
|
|
g_object_unref(adapter->backend.device_manager);
|
|
adapter->backend.device_manager = NULL;
|
|
}
|
|
|
|
if (adapter->backend.adapter_proxy != NULL) {
|
|
g_object_unref(adapter->backend.adapter_proxy);
|
|
adapter->backend.adapter_proxy = NULL;
|
|
}
|
|
|
|
if (adapter->id != NULL) {
|
|
free(adapter->id);
|
|
adapter->id = NULL;
|
|
}
|
|
|
|
if (adapter->name != NULL) {
|
|
free(adapter->name);
|
|
adapter->name = NULL;
|
|
}
|
|
|
|
gattlib_devices_free(adapter);
|
|
|
|
// Remove adapter from the global list
|
|
m_adapter_list = g_slist_remove(m_adapter_list, adapter);
|
|
|
|
free(adapter);
|
|
|
|
EXIT:
|
|
g_rec_mutex_unlock(&m_gattlib_mutex);
|
|
return ret;
|
|
}
|