Compare commits

...

23 Commits

Author SHA1 Message Date
Olivier Martin f99558d9b8 gattlib-py: Make pylint pass (and fix issue) 2024-04-24 14:37:28 +02:00
Olivier Martin b3c5d2d1ed common/gattlib_common: Add support to compare short UUID with long one 2024-04-24 14:37:28 +02:00
Olivier Martin 9fb48aacb6 gattlib-py/examples/advertisement_data: Fix example 2024-04-24 14:37:28 +02:00
Olivier Martin dbe599dbfb examples/notification: Port example to new gattlib API 2024-04-24 14:37:28 +02:00
Olivier Martin f79e90ce02 gattlib-py: Added support for manufacturer data from GATT advertising 2024-04-11 22:01:28 +02:00
Olivier Martin 35566d198a gattlib-py: Fix setup.py when building source package 2024-04-11 22:01:28 +02:00
Olivier Martin f4ed88eb31 mainloop/gattlib_glib_mainloop: Fix non initialized variable 2024-04-11 22:01:28 +02:00
Olivier Martin aaab2dc74e gattlib_adapter_close: Do not block mutex while waiting for scan_loop_thread to complete 2024-04-11 00:28:24 +02:00
Olivier Martin 880f1d2cd0 Add support to retrieve all manufacturer data from GATT advertisement packets 2024-04-10 10:53:45 +02:00
Olivier Martin 8a108495a1 Change 'gattlib_devices_dump_state()' to 'gattlib_adapter_dump_state()' 2024-04-10 10:53:30 +02:00
Olivier Martin 880ff269e5 gattlib_char: Ensure there is no buffer overflow when we initialize list of GATT characteristic 2024-04-10 10:51:58 +02:00
Olivier Martin 76353f8659 Do not build Python support by default and build examples by default 2024-04-10 10:50:48 +02:00
Olivier Martin db04a0eb5c Introduce gattlib_connection_is_valid() to not access 'connection->device' 2024-04-08 23:10:47 +02:00
Olivier Martin 2d771d9390 Fix gattlib_connection_is_connected 2024-04-08 23:09:09 +02:00
Olivier Martin 5406a97e57 More logging 2024-04-08 23:07:57 +02:00
Olivier Martin 4acf4aa0ab connection: Ensure device_object_path is not freed multiple time 2024-04-08 23:07:57 +02:00
Olivier Martin f609f7d507 adapter: Added support to open multiple time the same adapter 2024-04-08 23:07:57 +02:00
Olivier Martin dc009029fa Log when the adapter or device has been released 2024-04-08 12:01:53 +02:00
Olivier Martin cdd62f6d35 Return specific error code when the adapter or device has been removed 2024-04-08 10:08:57 +02:00
Olivier Martin 0e34df58e5 Single lock for all gattlib library 2024-04-08 00:15:16 +02:00
Olivier Martin 014c2802ee Refactor code to better separate gattlib_adapter_t, gattlib_device_t, gattlib_connection_t and their backends 2024-04-05 13:20:40 +02:00
Olivier Martin 22dca4511c Require a lower version of cmake (same version as Ubuntu 22.04 LTS) 2024-04-05 10:25:54 +02:00
Olivier Martin 5049443704 Ensure the connection structure is not freed when used by the connection thread 2024-04-04 23:48:43 +02:00
61 changed files with 2770 additions and 1216 deletions

View File

@ -6,14 +6,14 @@ jobs:
steps:
- uses: actions/checkout@v4
- run: sudo apt install libbluetooth-dev
- run: mkdir build && pushd build && cmake -DCMAKE_BUILD_TYPE=Debug .. && make
- run: mkdir build && pushd build && cmake -DCMAKE_BUILD_TYPE=Debug -DGATTLIB_PYTHON_INTERFACE=ON .. && make
build-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt install libbluetooth-dev doxygen
- run: mkdir build && pushd build && cmake -DCMAKE_BUILD_TYPE=Release -DGATTLIB_BUILD_DOCS=ON .. && make
- run: mkdir build && pushd build && cmake -DCMAKE_BUILD_TYPE=Release -DGATTLIB_BUILD_DOCS=ON -DGATTLIB_PYTHON_INTERFACE=ON .. && make
- run: pushd build && cpack ..
if: startsWith(github.ref, 'refs/tags/')
env:
@ -41,7 +41,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- run: sudo apt install libbluetooth-dev
- run: mkdir build && pushd build && cmake -DGATTLIB_FORCE_DBUS=TRUE -DCMAKE_BUILD_TYPE=Release .. && make
- run: mkdir build && pushd build && cmake -DGATTLIB_FORCE_DBUS=TRUE -DCMAKE_BUILD_TYPE=Release -DGATTLIB_PYTHON_INTERFACE=ON .. && make
build-release-without-python-support:
runs-on: ubuntu-latest
@ -50,6 +50,14 @@ jobs:
- run: sudo apt install libbluetooth-dev
- run: mkdir build && pushd build && cmake -DCMAKE_BUILD_TYPE=Release -DGATTLIB_PYTHON_INTERFACE=OFF .. && make
test-pylint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: python3 -m pip install PyGObject>=3.44.0
- run: python3 -m pip install pylint
- run: python3 -m pylint gattlib-py/gattlib --rcfile gattlib-py/.pylintrc
generate-python-binary-packages:
runs-on: ubuntu-latest
steps:

View File

@ -4,7 +4,7 @@
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
cmake_minimum_required(VERSION 3.25.1)
cmake_minimum_required(VERSION 3.22.0)
# Add Cross-Compilation support when the environment variables
# CROSS_COMPILE and SYSROOT are defined
@ -13,10 +13,10 @@ include(CrossCompilation.cmake)
project(gattlib)
#TODO: Gattlib examples must be ported to new gattlib_connect()
option(GATTLIB_BUILD_EXAMPLES "Build GattLib examples" NO)
option(GATTLIB_BUILD_EXAMPLES "Build GattLib examples" YES)
option(GATTLIB_SHARED_LIB "Build GattLib as a shared library" YES)
option(GATTLIB_BUILD_DOCS "Build GattLib docs" NO)
option(GATTLIB_PYTHON_INTERFACE "Build GattLib Python Interface" YES)
option(GATTLIB_PYTHON_INTERFACE "Build GattLib Python Interface" NO)
option(GATTLIB_ENABLE_ADDRESS_SANITIZER "Enable address sanitizer" NO)
find_package(PkgConfig REQUIRED)
@ -119,7 +119,7 @@ if(GATTLIB_BUILD_EXAMPLES)
add_subdirectory(examples/find_eddystone)
add_subdirectory(examples/read_write)
#add_subdirectory(examples/read_write_memory)
#add_subdirectory(examples/notification)
add_subdirectory(examples/notification)
#add_subdirectory(examples/nordic_uart)
add_subdirectory(tests/test_continuous_connection)

View File

@ -19,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 3.25.1)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -107,7 +107,7 @@ static char* parse_name(uint8_t* data, size_t size) {
return NULL;
}
static int ble_scan(void *adapter, int device_desc, gattlib_discovered_device_t discovered_device_cb, int timeout, void *user_data) {
static int ble_scan(gattlib_adapter_t* adapter, int device_desc, gattlib_discovered_device_t discovered_device_cb, int timeout, void *user_data) {
struct hci_filter old_options;
socklen_t slen = sizeof(old_options);
struct hci_filter new_options;
@ -212,7 +212,7 @@ static int ble_scan(void *adapter, int device_desc, gattlib_discovered_device_t
return GATTLIB_SUCCESS;
}
int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t discovered_device_cb, size_t timeout, void *user_data) {
int gattlib_adapter_scan_enable(gattlib_adapter_t* adapter, gattlib_discovered_device_t discovered_device_cb, size_t timeout, void *user_data) {
int device_desc = *(int*)adapter;
uint16_t interval = htobs(DISCOV_LE_SCAN_INT);
@ -241,13 +241,13 @@ int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t disco
return GATTLIB_SUCCESS;
}
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(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)
{
return GATTLIB_NOT_SUPPORTED;
}
int gattlib_adapter_scan_disable(void* adapter) {
int gattlib_adapter_scan_disable(gattlib_adapter_t* adapter) {
int device_desc = *(int*)adapter;
if (device_desc == -1) {
@ -262,7 +262,7 @@ int gattlib_adapter_scan_disable(void* adapter) {
return result;
}
int gattlib_adapter_close(void* adapter) {
int gattlib_adapter_close(gattlib_adapter_t* adapter) {
hci_close_dev(*(int*)adapter);
free(adapter);
return GATTLIB_SUCCESS;

View File

@ -41,7 +41,7 @@
struct gattlib_thread_t g_gattlib_thread = { 0 };
typedef struct {
gatt_connection_t* conn;
gattlib_connection_t* conn;
gatt_connect_cb_t connect_cb;
int connected;
int timeout;
@ -50,7 +50,7 @@ typedef struct {
} io_connect_arg_t;
static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data) {
gatt_connection_t *conn = user_data;
gattlib_connection_t *conn = user_data;
uint8_t opdu[ATT_MAX_MTU];
uint16_t handle, olen = 0;
uuid_t uuid = {};
@ -98,7 +98,7 @@ static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
}
static gboolean io_listen_cb(gpointer user_data) {
gatt_connection_t *conn = user_data;
gattlib_connection_t *conn = user_data;
gattlib_context_t* conn_context = conn->context;
g_attrib_register(conn_context->attrib, ATT_OP_HANDLE_NOTIFY,
@ -178,7 +178,7 @@ static void *connection_thread(void* arg) {
return NULL;
}
static gatt_connection_t *initialize_gattlib_connection(const gchar *src, const gchar *dst,
static gattlib_connection_t *initialize_gattlib_connection(const gchar *src, const gchar *dst,
uint8_t dest_type, BtIOSecLevel sec_level, int psm, int mtu,
gatt_connect_cb_t connect_cb,
io_connect_arg_t* io_connect_arg)
@ -250,7 +250,7 @@ static gatt_connection_t *initialize_gattlib_connection(const gchar *src, const
return NULL;
}
gatt_connection_t* conn = calloc(sizeof(gatt_connection_t), 1);
gattlib_connection_t* conn = calloc(sizeof(gattlib_connection_t), 1);
if (conn == NULL) {
free(conn_context);
return NULL;
@ -325,13 +325,13 @@ static void get_connection_options(unsigned long options, BtIOSecLevel *bt_io_se
*mtu = GATTLIB_CONNECTION_OPTIONS_LEGACY_GET_MTU(options);
}
int gattlib_connect(void *adapter, const char *dst,
int gattlib_connect(gattlib_adapter_t* adapter, const char *dst,
unsigned long options,
gatt_connect_cb_t connect_cb,
void* user_data)
{
const char *adapter_mac_address;
gatt_connection_t *conn;
gattlib_connection_t *conn;
BtIOSecLevel bt_io_sec_level;
int psm, mtu;
@ -393,11 +393,11 @@ static gboolean connection_timeout(gpointer user_data) {
* @param psm Specify the PSM for GATT/ATT over BR/EDR
* @param mtu Specify the MTU size
*/
static gatt_connection_t *gattlib_connect_with_options(const char *src, const char *dst,
static gattlib_connection_t *gattlib_connect_with_options(const char *src, const char *dst,
uint8_t dest_type, BtIOSecLevel bt_io_sec_level, int psm, int mtu)
{
GSource* timeout;
gatt_connection_t *conn;
gattlib_connection_t *conn;
io_connect_arg_t io_connect_arg;
conn = initialize_gattlib_connection(src, dst, dest_type, bt_io_sec_level,
@ -444,10 +444,10 @@ static gatt_connection_t *gattlib_connect_with_options(const char *src, const ch
* @param dst Remote Bluetooth address
* @param options Options to connect to BLE device. See `GATTLIB_CONNECTION_OPTIONS_*`
*/
gatt_connection_t *gattlib_connect(void* adapter, const char *dst, unsigned long options)
gattlib_connection_t *gattlib_connect(gattlib_adapter_t* adapter, const char *dst, unsigned long options)
{
const char* adapter_mac_address;
gatt_connection_t *conn;
gattlib_connection_t *conn;
BtIOSecLevel bt_io_sec_level;
int psm, mtu;
@ -483,7 +483,7 @@ gatt_connection_t *gattlib_connect(void* adapter, const char *dst, unsigned long
return conn;
}
int gattlib_disconnect(gatt_connection_t* connection, bool wait_disconnection) {
int gattlib_disconnect(gattlib_connection_t* connection, bool wait_disconnection) {
gattlib_context_t* conn_context = connection->context;
#if BLUEZ_VERSION_MAJOR == 4
@ -546,7 +546,7 @@ GSource* gattlib_timeout_add_seconds(guint interval, GSourceFunc function, gpoin
return source;
}
int get_uuid_from_handle(gatt_connection_t* connection, uint16_t handle, uuid_t* uuid) {
int get_uuid_from_handle(gattlib_connection_t* connection, uint16_t handle, uuid_t* uuid) {
gattlib_context_t* conn_context = connection->context;
int i;
@ -559,7 +559,7 @@ int get_uuid_from_handle(gatt_connection_t* connection, uint16_t handle, uuid_t*
return GATTLIB_NOT_FOUND;
}
int get_handle_from_uuid(gatt_connection_t* connection, const uuid_t* uuid, uint16_t* handle) {
int get_handle_from_uuid(gattlib_connection_t* connection, const uuid_t* uuid, uint16_t* handle) {
gattlib_context_t* conn_context = connection->context;
int i;
@ -573,13 +573,13 @@ int get_handle_from_uuid(gatt_connection_t* connection, const uuid_t* uuid, uint
}
#if 0 // Disable until https://github.com/labapart/gattlib/issues/75 is resolved
int gattlib_get_rssi(gatt_connection_t *connection, int16_t *rssi)
int gattlib_get_rssi(gattlib_connection_t *connection, int16_t *rssi)
{
return GATTLIB_NOT_SUPPORTED;
}
#endif
int gattlib_get_rssi_from_mac(void *adapter, const char *mac_address, int16_t *rssi)
int gattlib_get_rssi_from_mac(gattlib_adapter_t* adapter, const char *mac_address, int16_t *rssi)
{
return GATTLIB_NOT_SUPPORTED;
}

View File

@ -74,7 +74,7 @@ done:
data->discovered = TRUE;
}
int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_service_t** services, int* services_count) {
int gattlib_discover_primary(gattlib_connection_t* connection, gattlib_primary_service_t** services, int* services_count) {
struct primary_all_cb_t user_data;
guint ret;
@ -146,7 +146,7 @@ done:
data->discovered = TRUE;
}
int gattlib_discover_char_range(gatt_connection_t* connection, uint16_t start, uint16_t end, gattlib_characteristic_t** characteristics, int* characteristics_count) {
int gattlib_discover_char_range(gattlib_connection_t* connection, uint16_t start, uint16_t end, gattlib_characteristic_t** characteristics, int* characteristics_count) {
struct characteristic_cb_t user_data;
guint ret;
@ -170,7 +170,7 @@ int gattlib_discover_char_range(gatt_connection_t* connection, uint16_t start, u
return GATTLIB_SUCCESS;
}
int gattlib_discover_char(gatt_connection_t* connection, gattlib_characteristic_t** characteristics, int* characteristics_count) {
int gattlib_discover_char(gattlib_connection_t* connection, gattlib_characteristic_t** characteristics, int* characteristics_count) {
return gattlib_discover_char_range(connection, 0x0001, 0xffff, characteristics, characteristics_count);
}
@ -264,7 +264,7 @@ done:
}
#endif
int gattlib_discover_desc_range(gatt_connection_t* connection, int start, int end, gattlib_descriptor_t** descriptors, int* descriptor_count) {
int gattlib_discover_desc_range(gattlib_connection_t* connection, int start, int end, gattlib_descriptor_t** descriptors, int* descriptor_count) {
gattlib_context_t* conn_context = connection->context;
struct descriptor_cb_t descriptor_data;
guint ret;
@ -292,7 +292,7 @@ int gattlib_discover_desc_range(gatt_connection_t* connection, int start, int en
return GATTLIB_SUCCESS;
}
int gattlib_discover_desc(gatt_connection_t* connection, gattlib_descriptor_t** descriptors, int* descriptor_count) {
int gattlib_discover_desc(gattlib_connection_t* connection, gattlib_descriptor_t** descriptors, int* descriptor_count) {
return gattlib_discover_desc_range(connection, 0x0001, 0xffff, descriptors, descriptor_count);
}
@ -303,15 +303,14 @@ int gattlib_discover_desc(gatt_connection_t* connection, gattlib_descriptor_t**
* @param mac_address is the MAC address of the device to get the RSSI
* @param advertisement_data is an array of Service UUID and their respective data
* @param advertisement_data_count is the number of elements in the advertisement_data array
* @param manufacturer_id is the ID of the Manufacturer ID
* @param manufacturer_data is the data following Manufacturer ID
* @param manufacturer_data_size is the size of manufacturer_data
* @param manufacturer_data is an array of `gattlib_manufacturer_data_t`
* @param manufacturer_data_count is the number of entry in `gattlib_manufacturer_data_t` array
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_get_advertisement_data(gatt_connection_t *connection,
int gattlib_get_advertisement_data(gattlib_connection_t *connection,
gattlib_advertisement_data_t **advertisement_data, size_t *advertisement_data_count,
uint16_t *manufacturer_id, uint8_t **manufacturer_data, size_t *manufacturer_data_size)
gattlib_manufacturer_data_t** manufacturer_data, size_t* manufacturer_data_count)
{
return GATTLIB_NOT_SUPPORTED;
}
@ -323,15 +322,14 @@ int gattlib_get_advertisement_data(gatt_connection_t *connection,
* @param mac_address is the MAC address of the device to get the RSSI
* @param advertisement_data is an array of Service UUID and their respective data
* @param advertisement_data_count is the number of elements in the advertisement_data array
* @param manufacturer_id is the ID of the Manufacturer ID
* @param manufacturer_data is the data following Manufacturer ID
* @param manufacturer_data_size is the size of manufacturer_data
* @param manufacturer_data is an array of `gattlib_manufacturer_data_t`
* @param manufacturer_data_count is the number of entry in `gattlib_manufacturer_data_t` array
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_get_advertisement_data_from_mac(void *adapter, const char *mac_address,
int gattlib_get_advertisement_data_from_mac(gattlib_adapter_t* adapter, const char *mac_address,
gattlib_advertisement_data_t **advertisement_data, size_t *advertisement_data_count,
uint16_t *manufacturer_id, uint8_t **manufacturer_data, size_t *manufacturer_data_size)
gattlib_manufacturer_data_t** manufacturer_data, size_t* manufacturer_data_count)
{
return GATTLIB_NOT_SUPPORTED;
}

View File

@ -69,7 +69,7 @@ GSource* gattlib_timeout_add_seconds(guint interval, GSourceFunc function, gpoin
void uuid_to_bt_uuid(uuid_t* uuid, bt_uuid_t* bt_uuid);
void bt_uuid_to_uuid(bt_uuid_t* bt_uuid, uuid_t* uuid);
int get_uuid_from_handle(gatt_connection_t* connection, uint16_t handle, uuid_t* uuid);
int get_handle_from_uuid(gatt_connection_t* connection, const uuid_t* uuid, uint16_t* handle);
int get_uuid_from_handle(gattlib_connection_t* connection, uint16_t handle, uuid_t* uuid);
int get_handle_from_uuid(gattlib_connection_t* connection, const uuid_t* uuid, uint16_t* handle);
#endif

View File

@ -102,7 +102,7 @@ void uuid_to_bt_uuid(uuid_t* uuid, bt_uuid_t* bt_uuid) {
}
}
int gattlib_read_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid,
int gattlib_read_char_by_uuid(gattlib_connection_t* connection, uuid_t* uuid,
void **buffer, size_t* buffer_len)
{
gattlib_context_t* conn_context = connection->context;
@ -134,7 +134,7 @@ int gattlib_read_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid,
return GATTLIB_SUCCESS;
}
int gattlib_read_char_by_uuid_async(gatt_connection_t* connection, uuid_t* uuid,
int gattlib_read_char_by_uuid_async(gattlib_connection_t* connection, uuid_t* uuid,
gatt_read_cb_t gatt_read_cb)
{
gattlib_context_t* conn_context = connection->context;
@ -170,7 +170,7 @@ void gattlib_write_result_cb(guint8 status, const guint8 *pdu, guint16 len, gpoi
*write_completed = TRUE;
}
int gattlib_write_char_by_handle(gatt_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len) {
int gattlib_write_char_by_handle(gattlib_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len) {
gattlib_context_t* conn_context = connection->context;
int write_completed = FALSE;
@ -187,7 +187,7 @@ int gattlib_write_char_by_handle(gatt_connection_t* connection, uint16_t handle,
return 0;
}
int gattlib_write_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len) {
int gattlib_write_char_by_uuid(gattlib_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len) {
uint16_t handle = 0;
int ret;
@ -200,19 +200,19 @@ int gattlib_write_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, cons
return gattlib_write_char_by_handle(connection, handle, buffer, buffer_len);
}
int gattlib_write_without_response_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len)
int gattlib_write_without_response_char_by_uuid(gattlib_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len)
{
// Only supported in the DBUS API (ie: Bluez > v5.40) at the moment
return GATTLIB_NOT_SUPPORTED;
}
int gattlib_write_without_response_char_by_handle(gatt_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len)
int gattlib_write_without_response_char_by_handle(gattlib_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len)
{
// Only supported in the DBUS API (ie: Bluez > v5.40) at the moment
return GATTLIB_NOT_SUPPORTED;
}
int gattlib_notification_start(gatt_connection_t* connection, const uuid_t* uuid) {
int gattlib_notification_start(gattlib_connection_t* connection, const uuid_t* uuid) {
uint16_t handle;
uint16_t enable_notification = 0x0001;
@ -225,7 +225,7 @@ int gattlib_notification_start(gatt_connection_t* connection, const uuid_t* uuid
return gattlib_write_char_by_handle(connection, handle + 1, &enable_notification, sizeof(enable_notification));
}
int gattlib_notification_stop(gatt_connection_t* connection, const uuid_t* uuid) {
int gattlib_notification_stop(gattlib_connection_t* connection, const uuid_t* uuid) {
uint16_t handle;
uint16_t enable_notification = 0x0000;

View File

@ -28,8 +28,8 @@ mkdir ${gattlib_py_package_dir}/ci/
cp -r ${ROOT_PATH}/ci/install-bluez.sh ${gattlib_py_package_dir}/ci/
# Create MANIFEST.in
rm -f MANIFEST.in
cat <<EOT >> MANIFEST.in
rm -f ${gattlib_py_package_dir}/MANIFEST.in
cat <<EOT >> ${gattlib_py_package_dir}/MANIFEST.in
graft common
graft bluez
graft dbus
@ -51,7 +51,7 @@ python3 -m cibuildwheel --output-dir dist
python setup.py sdist
# Move generated artifact to project root path
ls dist/*
rm -Rf ${ROOT_PATH}/dist
mv dist ${ROOT_PATH}
popd

View File

@ -7,7 +7,7 @@
#include "gattlib_internal.h"
#if defined(WITH_PYTHON)
void gattlib_connected_device_python_callback(void *adapter, const char *dst, gatt_connection_t* connection, int error, void* user_data) {
void gattlib_connected_device_python_callback(gattlib_adapter_t* adapter, const char *dst, gattlib_connection_t* connection, int error, void* user_data) {
struct gattlib_python_args* args = user_data;
PyObject *result;
@ -17,7 +17,7 @@ void gattlib_connected_device_python_callback(void *adapter, const char *dst, ga
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)
// arguments: (gattlib_adapter_t* adapter, const char *dst, gattlib_connection_t* connection, void* user_data)
if (sizeof(void*) == 8) {
argument_string = "(LsLIO)";
} else {
@ -47,31 +47,51 @@ ON_ERROR:
#endif
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);
gattlib_connection_t* connection = data;
const gchar *device_mac_address = org_bluez_device1_get_address(connection->backend.device);
g_rec_mutex_lock(&connection->on_connection.mutex);
// Mutex to ensure the handler is valid
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_has_valid_handler(&connection->on_connection)) {
goto EXIT;
if (!gattlib_connection_is_connected(connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "_gattlib_connected_device_thread: Device is not connected (state:%s)",
device_state_str[connection->device->state]);
g_rec_mutex_unlock(&m_gattlib_mutex);
return NULL;
}
if (!gattlib_has_valid_handler(&connection->on_connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "_gattlib_connected_device_thread: Handler is not valid");
g_rec_mutex_unlock(&m_gattlib_mutex);
return NULL;
}
// Ensure we increment device reference counter to prevent the device/connection is freed during the execution
gattlib_device_ref(connection->device);
// We need to release the lock here to ensure the connection callback that is actually
// doing the application sepcific work is not locking the BLE state.
g_rec_mutex_unlock(&m_gattlib_mutex);
connection->on_connection.callback.connection_handler(
conn_context->adapter, device_mac_address, connection, 0 /* no error */,
connection->device->adapter, device_mac_address, connection, 0 /* no error */,
connection->on_connection.user_data);
EXIT:
g_rec_mutex_unlock(&connection->on_connection.mutex);
gattlib_device_unref(connection->device);
return NULL;
}
static void* _connected_device_thread_args_allocator(va_list args) {
gatt_connection_t* connection = va_arg(args, gatt_connection_t*);
gattlib_connection_t* connection = va_arg(args, gattlib_connection_t*);
return connection;
}
void gattlib_on_connected_device(gatt_connection_t* connection) {
void gattlib_on_connected_device(gattlib_connection_t* connection) {
if (!gattlib_connection_is_valid(connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_on_connected_device: Device is not valid");
return;
}
gattlib_handler_dispatch_to_thread(
&connection->on_connection,
#if defined(WITH_PYTHON)

View File

@ -7,7 +7,7 @@
#include "gattlib_internal.h"
#if defined(WITH_PYTHON)
void gattlib_disconnected_device_python_callback(gatt_connection_t* connection, void *user_data) {
void gattlib_disconnected_device_python_callback(gattlib_connection_t* connection, void *user_data) {
struct gattlib_python_args* args = user_data;
PyObject *result;
PyGILState_STATE d_gstate;
@ -30,10 +30,16 @@ void gattlib_disconnected_device_python_callback(gatt_connection_t* connection,
}
#endif
void gattlib_on_disconnected_device(gatt_connection_t* connection) {
if (gattlib_has_valid_handler(&connection->on_disconnection)) {
g_rec_mutex_lock(&connection->on_disconnection.mutex);
void gattlib_on_disconnected_device(gattlib_connection_t* connection) {
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_valid(connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_on_disconnected_device: Device not valid");
g_rec_mutex_unlock(&m_gattlib_mutex);
return;
}
if (gattlib_has_valid_handler(&connection->on_disconnection)) {
#if defined(WITH_PYTHON)
// 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.
@ -44,16 +50,16 @@ void gattlib_on_disconnected_device(gatt_connection_t* connection) {
// For GATT disconnection we do not use thread to ensure the callback is synchronous.
connection->on_disconnection.callback.disconnection_handler(connection, connection->on_disconnection.user_data);
g_rec_mutex_unlock(&connection->on_disconnection.mutex);
}
// Signal the device is now disconnected
g_mutex_lock(&connection->device_mutex);
connection->disconnection_wait.value = true;
g_cond_broadcast(&connection->disconnection_wait.condition);
g_mutex_unlock(&connection->device_mutex);
// Clean GATTLIB connection on disconnection
gattlib_connection_free(connection);
g_rec_mutex_unlock(&m_gattlib_mutex);
// Signal the device is now disconnected
g_mutex_lock(&m_gattlib_signal.mutex);
m_gattlib_signal.signals |= GATTLIB_SIGNAL_DEVICE_DISCONNECTION;
g_cond_broadcast(&m_gattlib_signal.condition);
g_mutex_unlock(&m_gattlib_signal.mutex);
}

View File

@ -7,7 +7,7 @@
#include "gattlib_internal.h"
#if defined(WITH_PYTHON)
void gattlib_discovered_device_python_callback(void *adapter, const char* addr, const char* name, void *user_data) {
void gattlib_discovered_device_python_callback(gattlib_adapter_t* adapter, const char* addr, const char* name, void *user_data) {
struct gattlib_python_args* args = user_data;
PyObject *result;
@ -46,7 +46,7 @@ ON_ERROR:
#endif
struct gattlib_discovered_device_thread_args {
struct gattlib_adapter* gattlib_adapter;
struct _gattlib_adapter* gattlib_adapter;
char* mac_address;
char* name;
OrgBluezDevice1* device1;
@ -55,21 +55,33 @@ struct gattlib_discovered_device_thread_args {
static gpointer _gattlib_discovered_device_thread(gpointer data) {
struct gattlib_discovered_device_thread_args* args = data;
g_rec_mutex_lock(&args->gattlib_adapter->ble_scan.discovered_device_callback.mutex);
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_has_valid_handler(&args->gattlib_adapter->ble_scan.discovered_device_callback)) {
if (!gattlib_adapter_is_valid(args->gattlib_adapter)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
goto EXIT;
}
args->gattlib_adapter->ble_scan.discovered_device_callback.callback.discovered_device(
if (!gattlib_has_valid_handler(&args->gattlib_adapter->discovered_device_callback)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
goto EXIT;
}
// Increase adapter reference counter to ensure the adapter is not freed while
// the callback is in use.
gattlib_adapter_ref(args->gattlib_adapter);
g_rec_mutex_unlock(&m_gattlib_mutex);
args->gattlib_adapter->discovered_device_callback.callback.discovered_device(
args->gattlib_adapter,
args->mac_address, args->name,
args->gattlib_adapter->ble_scan.discovered_device_callback.user_data
args->gattlib_adapter->discovered_device_callback.user_data
);
EXIT:
g_rec_mutex_unlock(&args->gattlib_adapter->ble_scan.discovered_device_callback.mutex);
gattlib_adapter_unref(args->gattlib_adapter);
EXIT:
free(args->mac_address);
if (args->name != NULL) {
free(args->name);
@ -80,7 +92,7 @@ EXIT:
}
static void* _discovered_device_thread_args_allocator(va_list args) {
struct gattlib_adapter* gattlib_adapter = va_arg(args, struct gattlib_adapter*);
gattlib_adapter_t* gattlib_adapter = va_arg(args, gattlib_adapter_t*);
OrgBluezDevice1* device1 = va_arg(args, OrgBluezDevice1*);
struct gattlib_discovered_device_thread_args* thread_args = calloc(sizeof(struct gattlib_discovered_device_thread_args), 1);
@ -95,9 +107,13 @@ static void* _discovered_device_thread_args_allocator(va_list args) {
return thread_args;
}
void gattlib_on_discovered_device(struct gattlib_adapter* gattlib_adapter, OrgBluezDevice1* device1) {
void gattlib_on_discovered_device(gattlib_adapter_t* gattlib_adapter, OrgBluezDevice1* device1) {
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
return;
}
gattlib_handler_dispatch_to_thread(
&gattlib_adapter->ble_scan.discovered_device_callback,
&gattlib_adapter->discovered_device_callback,
#if defined(WITH_PYTHON)
gattlib_discovered_device_python_callback /* python_callback */,
#else

View File

@ -46,7 +46,7 @@ void gattlib_notification_device_python_callback(const uuid_t* uuid, const uint8
#endif
struct gattlib_notification_device_thread_args {
gatt_connection_t* connection;
gattlib_connection_t* connection;
uuid_t* uuid;
uint8_t* data;
size_t data_length;
@ -56,14 +56,19 @@ void gattlib_notification_device_thread(gpointer data, gpointer user_data) {
struct gattlib_notification_device_thread_args* args = data;
struct gattlib_handler* handler = user_data;
g_rec_mutex_lock(&handler->mutex);
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_connected(args->connection)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return;
}
handler->callback.notification_handler(
args->uuid, args->data, args->data_length,
handler->user_data
);
g_rec_mutex_unlock(&handler->mutex);
g_rec_mutex_unlock(&m_gattlib_mutex);
if (args->uuid != NULL) {
free(args->uuid);
@ -75,7 +80,7 @@ void gattlib_notification_device_thread(gpointer data, gpointer user_data) {
}
}
static void* _notification_device_thread_args_allocator(gatt_connection_t* connection, const uuid_t* uuid, const uint8_t* data, size_t data_length) {
static void* _notification_device_thread_args_allocator(gattlib_connection_t* connection, const uuid_t* uuid, const uint8_t* data, size_t data_length) {
struct gattlib_notification_device_thread_args* thread_args = calloc(sizeof(struct gattlib_notification_device_thread_args), 1);
thread_args->connection = connection;
thread_args->uuid = calloc(sizeof(uuid_t), 1);
@ -91,7 +96,7 @@ static void* _notification_device_thread_args_allocator(gatt_connection_t* conne
return thread_args;
}
void gattlib_on_gatt_notification(gatt_connection_t* connection, const uuid_t* uuid, const uint8_t* data, size_t data_length) {
void gattlib_on_gatt_notification(gattlib_connection_t* connection, const uuid_t* uuid, const uint8_t* data, size_t data_length) {
GError *error = NULL;
assert(connection->notification.thread_pool != NULL);

View File

@ -8,11 +8,22 @@
#include "gattlib_internal.h"
int gattlib_register_notification(gatt_connection_t* connection, gattlib_event_handler_t notification_handler, void* user_data) {
int gattlib_register_notification(gattlib_connection_t* connection, gattlib_event_handler_t notification_handler, void* user_data) {
GError *error = NULL;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (connection == NULL) {
return GATTLIB_INVALID_PARAMETER;
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
if (!gattlib_connection_is_valid(connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_register_notification: Device not valid");
ret = GATTLIB_DEVICE_DISCONNECTED;
goto EXIT;
}
connection->notification.callback.notification_handler = notification_handler;
@ -25,19 +36,34 @@ int gattlib_register_notification(gatt_connection_t* connection, gattlib_event_h
if (error != NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_register_notification: Failed to create thread pool: %s", error->message);
g_error_free(error);
return GATTLIB_ERROR_INTERNAL;
ret = GATTLIB_ERROR_INTERNAL;
goto EXIT;
} else {
assert(connection->notification.thread_pool != NULL);
return GATTLIB_SUCCESS;
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
int gattlib_register_indication(gatt_connection_t* connection, gattlib_event_handler_t indication_handler, void* user_data) {
int gattlib_register_indication(gattlib_connection_t* connection, gattlib_event_handler_t indication_handler, void* user_data) {
GError *error = NULL;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (connection == NULL) {
return GATTLIB_INVALID_PARAMETER;
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
if (!gattlib_connection_is_valid(connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_register_indication: Device not valid");
ret = GATTLIB_DEVICE_DISCONNECTED;
goto EXIT;
}
connection->indication.callback.notification_handler = indication_handler;
connection->indication.user_data = user_data;
@ -48,19 +74,37 @@ int gattlib_register_indication(gatt_connection_t* connection, gattlib_event_han
if (error != NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_register_indication: Failed to create thread pool: %s", error->message);
g_error_free(error);
return GATTLIB_ERROR_INTERNAL;
} else {
return GATTLIB_SUCCESS;
ret = GATTLIB_ERROR_INTERNAL;
goto EXIT;
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
int gattlib_register_on_disconnect(gatt_connection_t *connection, gattlib_disconnection_handler_t handler, void* user_data) {
int gattlib_register_on_disconnect(gattlib_connection_t *connection, gattlib_disconnection_handler_t handler, void* user_data) {
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (connection == NULL) {
return GATTLIB_INVALID_PARAMETER;
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
if (!gattlib_connection_is_valid(connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_register_on_disconnect: Device not valid");
ret = GATTLIB_DEVICE_DISCONNECTED;
goto EXIT;
}
connection->on_disconnection.callback.disconnection_handler = handler;
connection->on_disconnection.user_data = user_data;
return GATTLIB_SUCCESS;
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
void bt_uuid_to_uuid(bt_uuid_t* bt_uuid, uuid_t* uuid) {
@ -117,9 +161,54 @@ int gattlib_string_to_uuid(const char *str, size_t n, uuid_t *uuid) {
return ret;
}
int gattlib_uuid_to_uuid128(const uuid_t *uuid, uuid_t *long_uuid) {
if (uuid->type == SDP_UUID128) {
memcpy(long_uuid, uuid, sizeof(uuid_t));
return 0;
}
long_uuid->type = SDP_UUID128;
long_uuid->value.uuid128.data[0] = 0xEF;
long_uuid->value.uuid128.data[1] = 0x68;
long_uuid->value.uuid128.data[2] = 0x00;
long_uuid->value.uuid128.data[3] = 0x00;
long_uuid->value.uuid128.data[4] = 0x9B;
long_uuid->value.uuid128.data[5] = 0x35;
long_uuid->value.uuid128.data[6] = 0x49;
long_uuid->value.uuid128.data[7] = 0x33;
long_uuid->value.uuid128.data[8] = 0x9B;
long_uuid->value.uuid128.data[9] = 0x10;
long_uuid->value.uuid128.data[10] = 0x52;
long_uuid->value.uuid128.data[11] = 0xFF;
long_uuid->value.uuid128.data[12] = 0xA9;
long_uuid->value.uuid128.data[13] = 0x74;
long_uuid->value.uuid128.data[14] = 0x00;
long_uuid->value.uuid128.data[15] = 0x42;
if (uuid->type == SDP_UUID32) {
long_uuid->value.uuid128.data[0] = (uuid->value.uuid32 >> 24) & 0xFF;
long_uuid->value.uuid128.data[1] = (uuid->value.uuid32 >> 16) & 0xFF;
long_uuid->value.uuid128.data[2] = (uuid->value.uuid32 >> 8) & 0xFF;
long_uuid->value.uuid128.data[3] = uuid->value.uuid32 & 0xFF;
} else if (uuid->type == SDP_UUID16) {
long_uuid->value.uuid128.data[2] = uuid->value.uuid16 >> 8;
long_uuid->value.uuid128.data[3] = uuid->value.uuid16 & 0xFF;
}
return 0;
}
int gattlib_uuid_cmp(const uuid_t *uuid1, const uuid_t *uuid2) {
if (uuid1->type != uuid2->type) {
return 1;
// Convert all UUID to UUID128 format to be compared
uuid_t uuid128_1, uuid128_2;
gattlib_uuid_to_uuid128(uuid1, &uuid128_1);
gattlib_uuid_to_uuid128(uuid2, &uuid128_2);
if (memcmp(&uuid128_1.value.uuid128, &uuid128_2.value.uuid128, sizeof(uuid1->value.uuid128)) == 0) {
return 0;
} else {
return 2;
}
} else if (uuid1->type == SDP_UUID16) {
if (uuid1->value.uuid16 == uuid2->value.uuid16) {
return 0;
@ -144,10 +233,8 @@ int gattlib_uuid_cmp(const uuid_t *uuid1, const uuid_t *uuid2) {
}
void gattlib_handler_free(struct gattlib_handler* handler) {
g_rec_mutex_lock(&handler->mutex);
if (!gattlib_has_valid_handler(handler)) {
goto EXIT;
return;
}
// Reset callback to stop calling it after we stopped
@ -172,9 +259,6 @@ void gattlib_handler_free(struct gattlib_handler* handler) {
g_thread_pool_free(handler->thread_pool, FALSE /* immediate */, TRUE /* wait */);
handler->thread_pool = NULL;
}
EXIT:
g_rec_mutex_unlock(&handler->mutex);
}
bool gattlib_has_valid_handler(struct gattlib_handler* handler) {
@ -185,8 +269,11 @@ void gattlib_handler_dispatch_to_thread(struct gattlib_handler* handler, void (*
GThreadFunc thread_func, const char* thread_name, void* (*thread_args_allocator)(va_list args), ...) {
GError *error = NULL;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_has_valid_handler(handler)) {
// We do not have (anymore) a callback, nothing to do
g_rec_mutex_unlock(&m_gattlib_mutex);
return;
}
@ -198,6 +285,8 @@ void gattlib_handler_dispatch_to_thread(struct gattlib_handler* handler, void (*
}
#endif
g_rec_mutex_unlock(&m_gattlib_mutex);
// We create a thread to ensure the callback is not blocking the mainloop
va_list args;
va_start(args, thread_args_allocator);
@ -218,3 +307,10 @@ void gattlib_free_mem(void *ptr) {
free(ptr);
}
}
int gattlib_device_ref(gattlib_device_t* device) {
g_rec_mutex_lock(&m_gattlib_mutex);
device->reference_counter++;
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_SUCCESS;
}

View File

@ -0,0 +1,175 @@
/*
* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later
*
* Copyright (c) 2021-2024, Olivier Martin <olivier@labapart.org>
*/
#include "gattlib_internal.h"
// Keep track of the allocated adapters to avoid an adapter to be freed twice.
// It could happen when using Python wrapper.
GSList *m_adapter_list;
static int stricmp(char const *a, char const *b) {
for (;; a++, b++) {
int d = tolower((unsigned char)*a) - tolower((unsigned char)*b);
if (d != 0 || !*a)
return d;
}
}
static gint _is_adapter_id(gconstpointer a, gconstpointer b) {
const gattlib_adapter_t* adapter = a;
const char* adapter_id = b;
return stricmp(adapter->id, adapter_id);
}
gattlib_adapter_t* gattlib_adapter_from_id(const char* adapter_id) {
gattlib_adapter_t* adapter = NULL;
g_rec_mutex_lock(&m_gattlib_mutex);
GSList *adapter_entry = g_slist_find_custom(m_adapter_list, adapter_id, _is_adapter_id);
if (adapter_entry == NULL) {
goto EXIT;
}
adapter = adapter_entry->data;
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return adapter;
}
bool gattlib_adapter_is_valid(gattlib_adapter_t* adapter) {
bool is_valid;
g_rec_mutex_lock(&m_gattlib_mutex);
GSList *adapter_entry = g_slist_find(m_adapter_list, adapter);
if (adapter_entry == NULL) {
is_valid = false;
} else {
is_valid = true;
}
g_rec_mutex_unlock(&m_gattlib_mutex);
return is_valid;
}
bool gattlib_adapter_is_scanning(gattlib_adapter_t* adapter) {
bool is_scanning = false;
g_rec_mutex_lock(&m_gattlib_mutex);
GSList *adapter_entry = g_slist_find(m_adapter_list, adapter);
if (adapter_entry == NULL) {
goto EXIT;
}
is_scanning = adapter->backend.ble_scan.is_scanning;
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return is_scanning;
}
struct _device_is_valid {
gattlib_device_t* device;
bool found;
};
static void _gattlib_device_is_valid(gpointer data, gpointer user_data) {
gattlib_adapter_t* adapter = data;
struct _device_is_valid* device_is_valid = user_data;
GSList *device_entry = g_slist_find(adapter->devices, device_is_valid->device);
if (device_entry != NULL) {
device_is_valid->found = true;
}
}
bool gattlib_device_is_valid(gattlib_device_t* device) {
struct _device_is_valid device_is_valid = {
.device = device,
.found = false
};
g_rec_mutex_lock(&m_gattlib_mutex);
g_slist_foreach(m_adapter_list, _gattlib_device_is_valid, &device_is_valid);
g_rec_mutex_unlock(&m_gattlib_mutex);
return device_is_valid.found;
}
struct _connection_is_valid {
gattlib_connection_t* connection;
bool is_valid;
};
static gint _is_device_connection(gconstpointer a, gconstpointer b) {
const gattlib_device_t* device = a;
return (&device->connection == b) ? 0 : -1; // We need to return 0 when it matches
}
static void _gattlib_connection_is_valid(gpointer data, gpointer user_data) {
gattlib_adapter_t* adapter = data;
struct _connection_is_valid* connection_is_valid = user_data;
//printf("_gattlib_connection_is_connected: Check device in adapter:%s\n", adapter->id);
GSList *device_entry = g_slist_find_custom(adapter->devices, connection_is_valid->connection, _is_device_connection);
if (device_entry == NULL) {
//printf("_gattlib_connection_is_connected: Did not find device %s\n", connection_is_connected->connection->device->device_id);
return;
}
connection_is_valid->is_valid = true;
}
bool gattlib_connection_is_valid(gattlib_connection_t* connection) {
struct _connection_is_valid connection_is_valid = {
.connection = connection,
.is_valid = false
};
g_rec_mutex_lock(&m_gattlib_mutex);
//printf("gattlib_connection_is_connected A");
g_slist_foreach(m_adapter_list, _gattlib_connection_is_valid, &connection_is_valid);
//printf("gattlib_connection_is_connected B");
g_rec_mutex_unlock(&m_gattlib_mutex);
return connection_is_valid.is_valid;
}
struct _connection_is_connected {
gattlib_connection_t* connection;
bool is_connected;
};
static void _gattlib_connection_is_connected(gpointer data, gpointer user_data) {
gattlib_adapter_t* adapter = data;
struct _connection_is_connected* connection_is_connected = user_data;
GSList *device_entry = g_slist_find_custom(adapter->devices, connection_is_connected->connection, _is_device_connection);
if (device_entry == NULL) {
return;
}
gattlib_device_t* device = device_entry->data;
connection_is_connected->is_connected = (device->state == CONNECTED);
}
bool gattlib_connection_is_connected(gattlib_connection_t* connection) {
struct _connection_is_connected connection_is_connected = {
.connection = connection,
.is_connected = false
};
g_rec_mutex_lock(&m_gattlib_mutex);
g_slist_foreach(m_adapter_list, _gattlib_connection_is_connected, &connection_is_connected);
g_rec_mutex_unlock(&m_gattlib_mutex);
return connection_is_connected.is_connected;
}

View File

@ -8,62 +8,52 @@
const char* device_state_str[] = {
"NOT_FOUND",
"CONNECTING",
"CONNECTED",
"DISCONNECTING",
"DISCONNECTED"
"CONNECTING",
"CONNECTED",
"DISCONNECTING",
"DISCONNECTED"
};
static gint _compare_device_with_device_id(gconstpointer a, gconstpointer b) {
const struct _gattlib_device* device = a;
const gattlib_device_t* device = a;
const char* device_id = b;
return g_ascii_strcasecmp(device->device_id, device_id);
}
static GSList* _find_device_with_device_id(struct gattlib_adapter* adapter, const char* device_id) {
static GSList* _find_device_with_device_id(gattlib_adapter_t* adapter, const char* device_id) {
return g_slist_find_custom(adapter->devices, device_id, _compare_device_with_device_id);
}
struct _gattlib_device* gattlib_device_get_device(void* adapter, const char* device_id) {
struct gattlib_adapter* gattlib_adapter = adapter;
struct _gattlib_device* device = NULL;
g_rec_mutex_lock(&gattlib_adapter->mutex);
GSList *item = _find_device_with_device_id(gattlib_adapter, device_id);
gattlib_device_t* gattlib_device_get_device(gattlib_adapter_t* adapter, const char* device_id) {
GSList *item = _find_device_with_device_id(adapter, device_id);
if (item == NULL) {
goto EXIT;
return NULL;
}
device = (struct _gattlib_device*)item->data;
EXIT:
g_rec_mutex_unlock(&gattlib_adapter->mutex);
return device;
return (gattlib_device_t*)item->data;
}
enum _gattlib_device_state gattlib_device_get_state(void* adapter, const char* device_id) {
struct gattlib_adapter* gattlib_adapter = adapter;
enum _gattlib_device_state state = NOT_FOUND;
g_rec_mutex_lock(&gattlib_adapter->mutex);
struct _gattlib_device* device = gattlib_device_get_device(adapter, device_id);
if (device != NULL) {
state = device->state;
enum _gattlib_device_state gattlib_device_get_state(gattlib_adapter_t* adapter, const char* device_id) {
gattlib_device_t* device = gattlib_device_get_device(adapter, device_id);
if (device == NULL) {
return NOT_FOUND;
}
g_rec_mutex_unlock(&gattlib_adapter->mutex);
return state;
return device->state;
}
int gattlib_device_set_state(void* adapter, const char* device_id, enum _gattlib_device_state new_state) {
struct gattlib_adapter* gattlib_adapter = adapter;
int gattlib_device_set_state(gattlib_adapter_t* adapter, const char* device_id, enum _gattlib_device_state new_state) {
enum _gattlib_device_state old_state;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&gattlib_adapter->mutex);
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(adapter)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_device_set_state: Adapter not valid");
ret = GATTLIB_ADAPTER_CLOSE;
goto EXIT;
}
old_state = gattlib_device_get_state(adapter, device_id);
if (old_state == NOT_FOUND) {
@ -71,7 +61,7 @@ int gattlib_device_set_state(void* adapter, const char* device_id, enum _gattlib
// The device does not exist yet
//
if (new_state != NOT_FOUND) {
struct _gattlib_device* device = calloc(sizeof(struct _gattlib_device), 1);
gattlib_device_t* device = calloc(sizeof(gattlib_device_t), 1);
if (device == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_device_set_state: Cannot allocate device");
ret = GATTLIB_OUT_OF_MEMORY;
@ -80,11 +70,13 @@ int gattlib_device_set_state(void* adapter, const char* device_id, enum _gattlib
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state:%s: Set initial state %s", device_id, device_state_str[new_state]);
device->reference_counter = 1;
device->adapter = adapter;
device->device_id = g_strdup(device_id);
device->state = new_state;
device->connection.device = device;
gattlib_adapter->devices = g_slist_append(gattlib_adapter->devices, device);
adapter->devices = g_slist_append(adapter->devices, device);
} else {
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state:%s: No state to set", device_id);
}
@ -92,20 +84,20 @@ int gattlib_device_set_state(void* adapter, const char* device_id, enum _gattlib
//
// The device needs to be remove and free
//
GSList *item = _find_device_with_device_id(gattlib_adapter, device_id);
GSList *item = _find_device_with_device_id(adapter, device_id);
if (item == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_device_set_state: The device is not present. It is not expected");
ret = GATTLIB_UNEXPECTED;
goto EXIT;
}
struct _gattlib_device* device = item->data;
gattlib_device_t* device = item->data;
switch (device->state) {
case DISCONNECTED:
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state: Free device %p", device);
gattlib_adapter->devices = g_slist_remove(gattlib_adapter->devices, device);
free(device);
adapter->devices = g_slist_remove(adapter->devices, device);
gattlib_device_unref(device);
break;
case CONNECTING:
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state: Connecting device needs to be removed - ignore it");
@ -127,21 +119,21 @@ int gattlib_device_set_state(void* adapter, const char* device_id, enum _gattlib
} else {
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state:%s: Set state %s", device_id, device_state_str[new_state]);
struct _gattlib_device* device = gattlib_device_get_device(adapter, device_id);
gattlib_device_t* device = gattlib_device_get_device(adapter, device_id);
device->state = new_state;
}
EXIT:
g_rec_mutex_unlock(&gattlib_adapter->mutex);
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
static void _gattlib_device_free(gpointer data) {
struct _gattlib_device* device = data;
gattlib_device_t* device = data;
switch (device->state) {
case DISCONNECTED:
free(device);
gattlib_device_unref(device);
break;
default:
GATTLIB_LOG(GATTLIB_WARNING, "Memory of the BLE device '%s' has not been freed because in state %s",
@ -149,18 +141,27 @@ static void _gattlib_device_free(gpointer data) {
}
}
int gattlib_devices_free(void* adapter) {
struct gattlib_adapter* gattlib_adapter = adapter;
g_rec_mutex_lock(&gattlib_adapter->mutex);
g_slist_free_full(gattlib_adapter->devices, _gattlib_device_free);
g_rec_mutex_unlock(&gattlib_adapter->mutex);
int gattlib_devices_free(gattlib_adapter_t* adapter) {
g_slist_free_full(adapter->devices, _gattlib_device_free);
return 0;
}
int gattlib_device_unref(gattlib_device_t* device) {
g_rec_mutex_lock(&m_gattlib_mutex);
device->reference_counter--;
if (device->reference_counter > 0) {
goto EXIT;
}
free(device);
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_SUCCESS;
}
static void _gattlib_device_is_disconnected(gpointer data, gpointer user_data) {
struct _gattlib_device* device = data;
gattlib_device_t* device = data;
bool* devices_are_disconnected_ptr = user_data;
if (device->state != DISCONNECTED) {
@ -168,13 +169,10 @@ static void _gattlib_device_is_disconnected(gpointer data, gpointer user_data) {
}
}
int gattlib_devices_are_disconnected(void* adapter) {
struct gattlib_adapter* gattlib_adapter = adapter;
int gattlib_devices_are_disconnected(gattlib_adapter_t* adapter) {
bool devices_are_disconnected = true;
g_rec_mutex_lock(&gattlib_adapter->mutex);
g_slist_foreach(gattlib_adapter->devices, _gattlib_device_is_disconnected, &devices_are_disconnected);
g_rec_mutex_unlock(&gattlib_adapter->mutex);
g_slist_foreach(adapter->devices, _gattlib_device_is_disconnected, &devices_are_disconnected);
return devices_are_disconnected;
}
@ -182,17 +180,25 @@ int gattlib_devices_are_disconnected(void* adapter) {
#ifdef DEBUG
static void _gattlib_device_dump_state(gpointer data, gpointer user_data) {
struct _gattlib_device* device = data;
gattlib_device_t* device = data;
GATTLIB_LOG(GATTLIB_DEBUG, "\t%s: %s", device->device_id, device_state_str[device->state]);
}
void gattlib_devices_dump_state(void* adapter) {
struct gattlib_adapter* gattlib_adapter = adapter;
void gattlib_adapter_dump_state(gattlib_adapter_t* adapter) {
g_rec_mutex_lock(&m_gattlib_mutex);
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_devices_dump_state: Adapter is_scanning:%d", adapter->backend.ble_scan.is_scanning);
if (!gattlib_adapter_is_valid(adapter)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_devices_dump_state: Adapter not valid");
goto EXIT;
}
g_rec_mutex_lock(&gattlib_adapter->mutex);
GATTLIB_LOG(GATTLIB_DEBUG, "Device list:");
g_slist_foreach(gattlib_adapter->devices, _gattlib_device_dump_state, NULL);
g_rec_mutex_unlock(&gattlib_adapter->mutex);
g_slist_foreach(adapter->devices, _gattlib_device_dump_state, NULL);
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
}
#endif

View File

@ -25,37 +25,39 @@ struct on_eddystone_discovered_device_arg {
void *user_data;
};
static void on_eddystone_discovered_device(void *adapter, const char* addr, const char* name, void *user_data)
static void on_eddystone_discovered_device(gattlib_adapter_t* adapter, const char* addr, const char* name, void *user_data)
{
struct on_eddystone_discovered_device_arg *callback_data = user_data;
gattlib_advertisement_data_t *advertisement_data = NULL;
size_t advertisement_data_count;
uint16_t manufacturer_id;
uint8_t *manufacturer_data = NULL;
size_t manufacturer_data_size;
gattlib_manufacturer_data_t* manufacturer_data = NULL;
size_t manufacturer_data_count = 0;
int ret;
ret = gattlib_get_advertisement_data_from_mac(adapter, addr,
&advertisement_data, &advertisement_data_count,
&manufacturer_id, &manufacturer_data, &manufacturer_data_size);
&manufacturer_data, &manufacturer_data_count);
if (ret != 0) {
return;
}
callback_data->discovered_device_cb(adapter, addr, name,
advertisement_data, advertisement_data_count,
manufacturer_id, manufacturer_data, manufacturer_data_size,
manufacturer_data, manufacturer_data_count,
callback_data->user_data);
if (advertisement_data != NULL) {
free(advertisement_data);
}
if (manufacturer_data != NULL) {
for (uintptr_t i = 0; i < manufacturer_data_count; i++) {
free(manufacturer_data[i].data);
}
free(manufacturer_data);
}
}
int gattlib_adapter_scan_eddystone(void *adapter, int16_t rssi_threshold, uint32_t eddystone_types,
int gattlib_adapter_scan_eddystone(gattlib_adapter_t* adapter, int16_t rssi_threshold, uint32_t eddystone_types,
gattlib_discovered_device_with_data_t discovered_device_cb, size_t timeout, void *user_data)
{
uuid_t eddystone_uuid;

184
common/gattlib_internal.h Normal file
View File

@ -0,0 +1,184 @@
/*
* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later
*
* Copyright (c) 2021-2024, Olivier Martin <olivier@labapart.org>
*/
#ifndef __GATTLIB_INTERNAL_H__
#define __GATTLIB_INTERNAL_H__
#include <stdbool.h>
#include <glib.h>
#if defined(WITH_PYTHON)
#include <Python.h>
#endif
#include "gattlib.h"
#include "gattlib_backend.h"
#if defined(WITH_PYTHON)
struct gattlib_python_args {
PyObject* callback;
PyObject* args;
};
#endif
#define GATTLIB_SIGNAL_DEVICE_DISCONNECTION (1 << 0)
#define GATTLIB_SIGNAL_ADAPTER_STOP_SCANNING (1 << 1)
struct gattlib_signal {
// Used by gattlib_disconnection when we want to wait for the disconnection to be effective
GCond condition;
// Mutex for condition
GMutex mutex;
// Identify the gattlib signals
uint32_t signals;
};
struct gattlib_handler {
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 (*callback)(void);
} callback;
void* user_data;
// We create a thread to ensure the callback is not blocking the mainloop
GThread *thread;
// Thread pool
GThreadPool *thread_pool;
#if defined(WITH_PYTHON)
// In case of Python callback and argument, we keep track to free it when we stopped to discover BLE devices
void* python_args;
#endif
};
enum _gattlib_device_state {
NOT_FOUND = 0,
CONNECTING,
CONNECTED,
DISCONNECTING,
DISCONNECTED
};
struct _gattlib_adapter {
// Context specific to the backend implementation (eg: dbus backend)
struct _gattlib_adapter_backend backend;
// BLE adapter id (could be its DBUS device path on Linux)
char* id;
// BLE adapter name
char* name;
// reference counter is used to know whether the adapter is still use by callback
// When the reference counter is 0 then the adapter is freed
uintptr_t reference_counter;
// List of `gattlib_device_t`. This list allows to know weither a device is
// discovered/disconnected/connecting/connected/disconnecting.
GSList *devices;
// Handler calls on discovered device
struct gattlib_handler discovered_device_callback;
};
struct _gattlib_connection {
struct _gattlib_device* device;
// Context specific to the backend implementation (eg: dbus backend)
struct _gattlib_connection_backend backend;
struct gattlib_handler on_connection;
struct gattlib_handler notification;
struct gattlib_handler indication;
struct gattlib_handler on_disconnection;
};
typedef struct _gattlib_device {
struct _gattlib_adapter* adapter;
// On some platform, the name could be a UUID, on others its the DBUS device path
char* device_id;
// reference counter is used to know whether the device is still use by callback
// When the reference counter is 0 then the device is freed
uintptr_t reference_counter;
// We keep the state to prevent concurrent connecting/connected/disconnecting operation
enum _gattlib_device_state state;
struct _gattlib_connection connection;
} gattlib_device_t;
// 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.
extern GRecMutex m_gattlib_mutex;
// Keep track of the allocated adapters to avoid an adapter to be freed twice.
// It could happen when using Python wrapper.
extern GSList *m_adapter_list;
// This structure is used for inter-thread communication
extern struct gattlib_signal m_gattlib_signal;
gattlib_adapter_t* gattlib_adapter_from_id(const char* adapter_id);
bool gattlib_adapter_is_valid(gattlib_adapter_t* adapter);
bool gattlib_adapter_is_scanning(gattlib_adapter_t* adapter);
int gattlib_adapter_ref(gattlib_adapter_t* adapter);
int gattlib_adapter_unref(gattlib_adapter_t* adapter);
bool gattlib_device_is_valid(gattlib_device_t* device);
int gattlib_device_ref(gattlib_device_t* device);
int gattlib_device_unref(gattlib_device_t* device);
/**
* This function is similar to 'gattlib_device_is_valid()' except we check if
* the connection (connected or not) still belongs to a valid device.
*
* It is to avoid to use 'connection->device' when the device has been freed
*/
bool gattlib_connection_is_valid(gattlib_connection_t* connection);
bool gattlib_connection_is_connected(gattlib_connection_t* connection);
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);
void gattlib_notification_device_thread(gpointer data, gpointer user_data);
/**
* Clean GATTLIB connection on disconnection
*
* This function is called by the disconnection callback to always be called on explicit
* and implicit disconnection.
*/
void gattlib_connection_free(gattlib_connection_t* connection);
extern const char* device_state_str[];
gattlib_device_t* gattlib_device_get_device(gattlib_adapter_t* adapter, const char* device_id);
enum _gattlib_device_state gattlib_device_get_state(gattlib_adapter_t* adapter, const char* device_id);
int gattlib_device_set_state(gattlib_adapter_t* adapter, const char* device_id, enum _gattlib_device_state new_state);
int gattlib_devices_are_disconnected(gattlib_adapter_t* adapter);
int gattlib_devices_free(gattlib_adapter_t* adapter);
#ifdef DEBUG
void gattlib_adapter_dump_state(gattlib_adapter_t* adapter);
#endif
#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(gattlib_adapter_t* adapter, const char* addr, const char* name, void *user_data);
void gattlib_connected_device_python_callback(gattlib_adapter_t* adapter, const char *dst, gattlib_connection_t* connection, int error, void* user_data);
void gattlib_disconnected_device_python_callback(gattlib_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

@ -1,123 +0,0 @@
/*
* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later
*
* 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"
#if defined(WITH_PYTHON)
struct gattlib_python_args {
PyObject* callback;
PyObject* args;
};
#endif
struct gattlib_handler {
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 (*callback)(void);
} callback;
void* user_data;
// We create a thread to ensure the callback is not blocking the mainloop
GThread *thread;
// The mutex ensures the callbacks is not being freed while being called
// We use a recursive mutex to be able to disable BLE scan from 'on_discovered_device'
// when we want to connect to the discovered device.
// Note: The risk is that we are actually realising the handle from the one we are executing
GRecMutex mutex;
// Thread pool
GThreadPool *thread_pool;
#if defined(WITH_PYTHON)
// In case of Python callback and argument, we keep track to free it when we stopped to discover BLE devices
void* python_args;
#endif
};
enum _gattlib_device_state {
NOT_FOUND = 0,
CONNECTING,
CONNECTED,
DISCONNECTING,
DISCONNECTED
};
struct _gattlib_device {
// Context specific to the backend implementation (eg: dbus backend)
void* context;
void* adapter;
// On some platform, the name could be a UUID, on others its the DBUS device path
char* device_id;
GMutex device_mutex;
// We keep the state to prevent concurrent connecting/connected/disconnecting operation
enum _gattlib_device_state state;
struct {
// Used by gattlib_disconnection when we want to wait for the disconnection to be effective
GCond condition;
// Used to avoid spurious or stolen wakeup
bool value;
} disconnection_wait;
struct gattlib_handler on_connection;
struct gattlib_handler notification;
struct gattlib_handler indication;
struct gattlib_handler on_disconnection;
};
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);
void gattlib_notification_device_thread(gpointer data, gpointer user_data);
/**
* Clean GATTLIB connection on disconnection
*
* This function is called by the disconnection callback to always be called on explicit
* and implicit disconnection.
*/
void gattlib_connection_free(gatt_connection_t* connection);
extern const char* device_state_str[];
struct _gattlib_device* gattlib_device_get_device(void* adapter, const char* device_id);
enum _gattlib_device_state gattlib_device_get_state(void* adapter, const char* device_id);
int gattlib_device_set_state(void* adapter, const char* device_id, enum _gattlib_device_state new_state);
int gattlib_devices_are_disconnected(void* adapter);
int gattlib_devices_free(void* adapter);
#ifdef DEBUG
void gattlib_devices_dump_state(void* adapter);
#endif
#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

@ -34,14 +34,18 @@ int gattlib_mainloop(void* (*task)(void* arg), void *arg) {
GError* error;
if (m_main_loop != NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Main loop is already running: %s", error->message);
g_error_free(error);
GATTLIB_LOG(GATTLIB_ERROR, "Main loop is already running");
return GATTLIB_BUSY;
}
m_main_loop = g_main_loop_new(NULL, FALSE);
GThread *task_thread = g_thread_try_new("gattlib_task", _execute_task, &execute_task_arg, &error);
if (task_thread == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Could not create task for main loop: %s", error->message);
g_error_free(error);
return GATTLIB_UNEXPECTED;
}
g_main_loop_run(m_main_loop);
g_main_loop_unref(m_main_loop);

View File

@ -4,7 +4,7 @@
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
cmake_minimum_required(VERSION 3.25.1)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)
@ -73,6 +73,7 @@ set(gattlib_SRCS gattlib.c
gattlib_stream.c
bluez5/lib/uuid.c
${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_common.c
${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_common_adapter.c
${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_device_state_management.c
${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_eddystone.c
${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_callback_connected_device.c

View File

@ -15,19 +15,25 @@
static const char *m_dbus_error_unknown_object = "GDBus.Error:org.freedesktop.DBus.Error.UnknownObject";
static void _on_device_connect(gatt_connection_t* connection) {
gattlib_context_t* conn_context = connection->context;
static void _on_device_connect(gattlib_connection_t* connection) {
GDBusObjectManager *device_manager;
GError *error = NULL;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_valid(connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "_on_device_connect: Device not valid");
goto EXIT;
}
// Stop the timeout for connection
if (conn_context->connection_timeout_id) {
g_source_remove(conn_context->connection_timeout_id);
conn_context->connection_timeout_id = 0;
if (connection->backend.connection_timeout_id) {
g_source_remove(connection->backend.connection_timeout_id);
connection->backend.connection_timeout_id = 0;
}
// Get list of objects belonging to Device Manager
device_manager = get_device_manager_from_adapter(conn_context->adapter, &error);
device_manager = get_device_manager_from_adapter(connection->device->adapter, &error);
if (device_manager == NULL) {
if (error != NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_connect: Failed to get device manager from adapter (%d, %d).", error->domain, error->code);
@ -36,26 +42,29 @@ static void _on_device_connect(gatt_connection_t* connection) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_connect: Failed to get device manager from adapter");
}
//TODO: Free device
return;
goto EXIT;
}
conn_context->dbus_objects = g_dbus_object_manager_get_objects(device_manager);
connection->backend.dbus_objects = g_dbus_object_manager_get_objects(device_manager);
gattlib_device_set_state(conn_context->adapter, connection->device_id, CONNECTED);
gattlib_device_set_state(connection->device->adapter, connection->device->device_id, CONNECTED);
gattlib_on_connected_device(connection);
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
}
gboolean on_handle_device_property_change(
OrgBluezGattCharacteristic1 *object,
GDBusProxy *proxy,
GVariant *arg_changed_properties,
const gchar *const *arg_invalidated_properties,
gpointer user_data)
{
gatt_connection_t* connection = user_data;
gattlib_context_t* conn_context = connection->context;
gattlib_connection_t* connection = user_data;
// Retrieve 'Value' from 'arg_changed_properties'
if (g_variant_n_children (arg_changed_properties) > 0) {
const gchar* device_object_path = g_dbus_proxy_get_object_path(proxy);
GVariantIter *iter;
const gchar *key;
GVariant *value;
@ -64,17 +73,14 @@ gboolean on_handle_device_property_change(
while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) {
if (strcmp(key, "Connected") == 0) {
if (!g_variant_get_boolean(value)) {
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: device_property_change(%s): Disconnection",
conn_context->device_object_path);
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: device_property_change(%s): Disconnection", device_object_path);
gattlib_on_disconnected_device(connection);
} else {
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: device_property_change(%s): Connection",
conn_context->device_object_path);
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: device_property_change(%s): Connection", device_object_path);
}
} else if (strcmp(key, "ServicesResolved") == 0) {
if (g_variant_get_boolean(value)) {
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: device_property_change(%s): Service Resolved",
conn_context->device_object_path);
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: device_property_change(%s): Service Resolved", device_object_path);
_on_device_connect(connection);
}
}
@ -132,10 +138,20 @@ void get_device_path_from_mac(const char *adapter_name, const char *mac_address,
}
static gboolean _stop_connect_func(gpointer data) {
gattlib_context_t *conn_context = data;
gattlib_connection_t *connection = data;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_valid(connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "_stop_connect_func: Device not valid");
goto EXIT;
}
// Reset the connection timeout
conn_context->connection_timeout_id = 0;
connection->backend.connection_timeout_id = 0;
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
// We return FALSE when it is a one-off event
return FALSE;
@ -154,30 +170,27 @@ static gboolean _stop_connect_func(gpointer data) {
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_connect(void *adapter, const char *dst,
int gattlib_connect(gattlib_adapter_t* adapter, const char *dst,
unsigned long options,
gatt_connect_cb_t connect_cb,
void* user_data)
{
struct gattlib_adapter *gattlib_adapter = adapter;
const char* adapter_name = NULL;
GError *error = NULL;
char object_path[100];
char object_path[GATTLIB_DBUS_OBJECT_PATH_SIZE_MAX];
int ret = GATTLIB_SUCCESS;
// In case NULL is passed, we initialized default adapter
if (gattlib_adapter == NULL) {
gattlib_adapter = init_default_adapter();
if (adapter == NULL) {
adapter = init_default_adapter();
if (adapter == NULL) {
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_connect: No default adapter");
return GATTLIB_NOT_FOUND;
}
} else {
adapter_name = gattlib_adapter->adapter_name;
adapter_name = adapter->name;
}
// even after init_default_adapter() - the adapter can be NULL
if (gattlib_adapter == NULL) {
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_connect: No adapter");
return GATTLIB_NOT_FOUND;
}
if (connect_cb == NULL) {
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_connect: Missing connection callback");
return GATTLIB_INVALID_PARAMETER;
@ -185,40 +198,42 @@ int gattlib_connect(void *adapter, const char *dst,
get_device_path_from_mac(adapter_name, dst, object_path, sizeof(object_path));
gatt_connection_t* connection = gattlib_device_get_device(adapter, object_path);
if (connection == NULL) {
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(adapter)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_connect: Adapter not valid");
ret = GATTLIB_ADAPTER_CLOSE;
goto EXIT;
}
gattlib_device_t* device = gattlib_device_get_device(adapter, object_path);
if (device == NULL) {
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_connect: Cannot find connection %s", dst);
return GATTLIB_INVALID_PARAMETER;
} else if (connection->state != DISCONNECTED) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
} else if (device->state != DISCONNECTED) {
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_connect: Cannot connect to '%s'. Device is in state %s",
dst, device_state_str[connection->state]);
return GATTLIB_BUSY;
dst, device_state_str[device->state]);
ret = GATTLIB_BUSY;
goto EXIT;
}
gattlib_context_t* conn_context = calloc(sizeof(gattlib_context_t), 1);
if (conn_context == NULL) {
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_connect: Cannot allocate context");
return GATTLIB_OUT_OF_MEMORY;
}
conn_context->adapter = gattlib_adapter;
connection->context = conn_context;
connection->on_connection.callback.connection_handler = connect_cb;
connection->on_connection.user_data = user_data;
device->connection.on_connection.callback.connection_handler = connect_cb;
device->connection.on_connection.user_data = user_data;
GATTLIB_LOG(GATTLIB_DEBUG, "Connecting bluetooth device %s", dst);
// Mark the device has disconnected
gattlib_device_set_state(connection->adapter, connection->device_id, CONNECTING);
gattlib_device_set_state(device->adapter, device->device_id, CONNECTING);
OrgBluezDevice1* device = org_bluez_device1_proxy_new_for_bus_sync(
OrgBluezDevice1* bluez_device = org_bluez_device1_proxy_new_for_bus_sync(
G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_NONE,
"org.bluez",
object_path,
NULL,
&error);
if (device == NULL) {
if (bluez_device == NULL) {
ret = GATTLIB_ERROR_DBUS;
if (error) {
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
@ -227,20 +242,20 @@ int gattlib_connect(void *adapter, const char *dst,
} else {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_connect: Failed to connect to DBus Bluez Device");
}
goto FREE_CONNECTION_CONTEXT;
goto EXIT;
} else {
conn_context->device = device;
conn_context->device_object_path = strdup(object_path);
device->connection.backend.device = bluez_device;
device->connection.backend.device_object_path = strdup(object_path);
}
// Register a handle for notification
conn_context->on_handle_device_property_change_id = g_signal_connect(device,
device->connection.backend.on_handle_device_property_change_id = g_signal_connect(bluez_device,
"g-properties-changed",
G_CALLBACK (on_handle_device_property_change),
connection);
G_CALLBACK(on_handle_device_property_change),
&device->connection);
error = NULL;
org_bluez_device1_call_connect_sync(device, NULL, &error);
org_bluez_device1_call_connect_sync(bluez_device, NULL, &error);
if (error) {
if (strncmp(error->message, m_dbus_error_unknown_object, strlen(m_dbus_error_unknown_object)) == 0) {
// You might have this error if the computer has not scanned or has not already had
@ -252,7 +267,7 @@ int gattlib_connect(void *adapter, const char *dst,
ret = GATTLIB_TIMEOUT;
} else {
GATTLIB_LOG(GATTLIB_ERROR, "Device connected error (device:%s): %s",
conn_context->device_object_path,
device->connection.backend.device_object_path,
error->message);
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
}
@ -260,33 +275,35 @@ int gattlib_connect(void *adapter, const char *dst,
g_error_free(error);
// Fail to connect. Mark the device has disconnected to be able to reconnect
gattlib_device_set_state(connection->adapter, connection->device_id, DISCONNECTED);
gattlib_device_set_state(adapter, device->device_id, DISCONNECTED);
goto FREE_DEVICE;
}
// Wait for the property 'UUIDs' to be changed. We assume 'org.bluez.GattService1
// and 'org.bluez.GattCharacteristic1' to be advertised at that moment.
conn_context->connection_timeout_id = g_timeout_add_seconds(CONNECT_TIMEOUT_SEC, _stop_connect_func, conn_context);
device->connection.backend.connection_timeout_id = g_timeout_add_seconds(CONNECT_TIMEOUT_SEC, _stop_connect_func, &device->connection);
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_SUCCESS;
FREE_DEVICE:
free(conn_context->device_object_path);
g_object_unref(conn_context->device);
FREE_CONNECTION_CONTEXT:
free(conn_context);
g_rec_mutex_lock(&m_gattlib_mutex);
free(device->connection.backend.device_object_path);
device->connection.backend.device_object_path = NULL;
g_rec_mutex_unlock(&m_gattlib_mutex);
// destroy default adapter
if(adapter == NULL) {
gattlib_adapter_close(gattlib_adapter);
gattlib_adapter_close(adapter);
}
EXIT:
if (ret != GATTLIB_SUCCESS) {
connect_cb(adapter, dst, NULL, ret /* error */, user_data);
}
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
@ -296,36 +313,31 @@ FREE_CONNECTION_CONTEXT:
* This function is called by the disconnection callback to always be called on explicit
* and implicit disconnection.
*/
void gattlib_connection_free(gatt_connection_t* connection) {
gattlib_context_t* conn_context;
void* adapter;
void gattlib_connection_free(gattlib_connection_t* connection) {
char* device_id;
g_mutex_lock(&connection->device_mutex);
conn_context = connection->context;
adapter = conn_context->adapter;
device_id = connection->device_id;
device_id = connection->device->device_id;
// Remove signal
if (conn_context->on_handle_device_property_change_id != 0) {
g_signal_handler_disconnect(conn_context->device, conn_context->on_handle_device_property_change_id);
conn_context->on_handle_device_property_change_id = 0;
if (connection->backend.on_handle_device_property_change_id != 0) {
g_signal_handler_disconnect(connection->backend.device, connection->backend.on_handle_device_property_change_id);
connection->backend.on_handle_device_property_change_id = 0;
}
// Stop the timeout for connection
if (conn_context->connection_timeout_id) {
g_source_remove(conn_context->connection_timeout_id);
conn_context->connection_timeout_id = 0;
if (connection->backend.connection_timeout_id) {
g_source_remove(connection->backend.connection_timeout_id);
connection->backend.connection_timeout_id = 0;
}
free(conn_context->device_object_path);
if (conn_context->device != NULL) {
g_object_unref(conn_context->device);
conn_context->device = NULL;
if (connection->backend.device_object_path != NULL) {
free(connection->backend.device_object_path);
connection->backend.device_object_path = NULL;
}
g_list_free_full(conn_context->dbus_objects, g_object_unref);
disconnect_all_notifications(conn_context);
g_list_free_full(connection->backend.dbus_objects, g_object_unref);
disconnect_all_notifications(&connection->backend);
// Free all handler
//TODO: Fixme - there is a memory leak by not freeing the handlers
@ -336,85 +348,94 @@ void gattlib_connection_free(gatt_connection_t* connection) {
// Note: We do not free adapter as it might still be used by other devices
free(connection->context);
connection->context = NULL;
// Mark the device has disconnected
gattlib_device_set_state(adapter, device_id, DISCONNECTED);
g_mutex_unlock(&connection->device_mutex);
gattlib_device_set_state(connection->device->adapter, device_id, DISCONNECTED);
}
int gattlib_disconnect(gatt_connection_t* connection, bool wait_disconnection) {
gattlib_context_t* conn_context;
int ret = GATTLIB_SUCCESS;
int gattlib_disconnect(gattlib_connection_t* connection, bool wait_disconnection) {
GError *error = NULL;
int ret = GATTLIB_SUCCESS;
if (connection == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Cannot disconnect - connection parameter is not valid.");
return GATTLIB_INVALID_PARAMETER;
}
g_mutex_lock(&connection->device_mutex);
conn_context = connection->context;
g_rec_mutex_lock(&m_gattlib_mutex);
if (conn_context == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Cannot disconnect - connection context is not valid.");
ret = GATTLIB_NOT_SUPPORTED;
goto EXIT;
} else if (connection->state != CONNECTED) {
if (!gattlib_connection_is_connected(connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "Cannot disconnect - connection is not in connected state (state=%s).",
device_state_str[connection->state]);
ret = GATTLIB_BUSY;
goto EXIT;
device_state_str[connection->device->state]);
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_BUSY;
}
GATTLIB_LOG(GATTLIB_DEBUG, "Disconnecting bluetooth device %s", conn_context->device_object_path);
GATTLIB_LOG(GATTLIB_DEBUG, "Disconnecting bluetooth device %s", connection->backend.device_object_path);
org_bluez_device1_call_disconnect_sync(conn_context->device, NULL, &error);
org_bluez_device1_call_disconnect_sync(connection->backend.device, NULL, &error);
if (error) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to disconnect DBus Bluez Device: %s", error->message);
g_error_free(error);
// We continue, we still want to set the correct state
}
// Mark the device has disconnected
gattlib_device_set_state(connection->adapter, connection->device_id, DISCONNECTING);
gattlib_device_set_state(connection->device->adapter, connection->device->device_id, DISCONNECTING);
//Note: Signals and memory will be removed/clean on disconnction callback
// See _gattlib_clean_on_disconnection()
// We must release the mutex before the loop to leave other threads to signal the disconnection
g_rec_mutex_unlock(&m_gattlib_mutex);
if (wait_disconnection) {
gint64 end_time;
g_mutex_lock(&m_gattlib_signal.mutex);
end_time = g_get_monotonic_time() + GATTLIB_DISCONNECTION_WAIT_TIMEOUT_SEC * G_TIME_SPAN_SECOND;
while (!connection->disconnection_wait.value) {
if (!g_cond_wait_until(&connection->disconnection_wait.condition, &connection->device_mutex, end_time)) {
while (gattlib_connection_is_connected(connection)) {
if (!g_cond_wait_until(&m_gattlib_signal.condition, &m_gattlib_signal.mutex, end_time)) {
ret = GATTLIB_TIMEOUT;
break;
}
}
g_mutex_unlock(&m_gattlib_signal.mutex);
}
EXIT:
g_mutex_unlock(&connection->device_mutex);
return ret;
}
// Bluez was using org.bluez.Device1.GattServices until 5.37 to expose the list of available GATT Services
#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 38)
int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_service_t** services, int* services_count) {
int gattlib_discover_primary(gattlib_connection_t* connection, gattlib_primary_service_t** services, int* services_count) {
const gchar* const* service_str;
GError *error = NULL;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (connection == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib connection not initialized.");
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_INVALID_PARAMETER;
}
gattlib_context_t* conn_context = connection->context;
OrgBluezDevice1* device = conn_context->device;
const gchar* const* service_str;
GError *error = NULL;
if (!gattlib_connection_is_valid(connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_discover_primary: Device not valid");
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_DEVICE_DISCONNECTED;
}
const gchar* const* service_strs = org_bluez_device1_get_gatt_services(device);
// Increase 'bluez_device' reference counter to avoid to keep the lock longer
OrgBluezDevice1* bluez_device = connection->backend.device;
g_object_ref(bluez_device);
g_rec_mutex_unlock(&m_gattlib_mutex);
const gchar* const* service_strs = org_bluez_device1_get_gatt_services(bluez_device);
if (service_strs == NULL) {
if (services != NULL) {
@ -434,7 +455,8 @@ int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_serv
gattlib_primary_service_t* primary_services = calloc(count_max * sizeof(gattlib_primary_service_t), 1);
if (primary_services == NULL) {
return GATTLIB_OUT_OF_MEMORY;
ret = GATTLIB_OUT_OF_MEMORY;
goto EXIT;
}
for (service_str = service_strs; *service_str != NULL; service_str++) {
@ -476,22 +498,34 @@ int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_serv
if (services_count != NULL) {
*services_count = count;
}
return GATTLIB_SUCCESS;
EXIT:
g_object_unref(bluez_device);
return ret;
}
#else
int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_service_t** services, int* services_count) {
if (connection == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib connection not initialized.");
return GATTLIB_INVALID_PARAMETER;
}
gattlib_context_t* conn_context = connection->context;
int gattlib_discover_primary(gattlib_connection_t* connection, gattlib_primary_service_t** services, int* services_count) {
GError *error = NULL;
GDBusObjectManager *device_manager = get_device_manager_from_adapter(conn_context->adapter, &error);
OrgBluezDevice1* device = conn_context->device;
const gchar* const* service_str;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (connection == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib connection not initialized.");
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
if (!gattlib_connection_is_valid(connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_discover_primary: Device not valid");
ret = GATTLIB_DEVICE_DISCONNECTED;
goto EXIT;
}
GDBusObjectManager *device_manager = get_device_manager_from_adapter(connection->device->adapter, &error);
OrgBluezDevice1* device = connection->backend.device;
const gchar* const* service_strs = org_bluez_device1_get_uuids(device);
if (device_manager == NULL) {
@ -503,7 +537,7 @@ int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_serv
ret = GATTLIB_ERROR_DBUS;
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib Context not initialized.");
}
return ret;
goto EXIT;
}
if (service_strs == NULL) {
@ -513,7 +547,7 @@ int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_serv
if (services_count != NULL) {
*services_count = 0;
}
return GATTLIB_SUCCESS;
goto EXIT;
}
// Maximum number of primary services
@ -524,11 +558,12 @@ int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_serv
gattlib_primary_service_t* primary_services = calloc(count_max * sizeof(gattlib_primary_service_t), 1);
if (primary_services == NULL) {
return GATTLIB_OUT_OF_MEMORY;
ret = GATTLIB_OUT_OF_MEMORY;
goto EXIT;
}
GList *l;
for (l = conn_context->dbus_objects; l != NULL; l = l->next) {
for (l = connection->backend.dbus_objects; l != NULL; l = l->next) {
GDBusObject *object = l->data;
const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object));
@ -568,7 +603,7 @@ int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_serv
}
continue;
}
if (strcmp(conn_context->device_object_path, service_property)) {
if (strcmp(connection->backend.device_object_path, service_property)) {
g_object_unref(service_proxy);
continue;
}
@ -582,7 +617,7 @@ int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_serv
primary_services[count].attr_handle_end = service_handle;
// Loop through all objects, as ordering is not guaranteed.
for (GList *m = conn_context->dbus_objects; m != NULL; m = m->next) {
for (GList *m = connection->backend.dbus_objects; m != NULL; m = m->next) {
GDBusObject *characteristic_object = m->data;
const char* characteristic_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(characteristic_object));
interface = g_dbus_object_manager_get_interface(device_manager, characteristic_path, "org.bluez.GattCharacteristic1");
@ -627,25 +662,41 @@ int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_serv
if (ret != GATTLIB_SUCCESS) {
free(primary_services);
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
#endif
// Bluez was using org.bluez.Device1.GattServices until 5.37 to expose the list of available GATT Services
#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 38)
int gattlib_discover_char_range(gatt_connection_t* connection, uint16_t start, uint16_t end, gattlib_characteristic_t** characteristics, int* characteristics_count) {
gattlib_context_t* conn_context = connection->context;
OrgBluezDevice1* device = conn_context->device;
int gattlib_discover_char_range(gattlib_connection_t* connection, uint16_t start, uint16_t end, gattlib_characteristic_t** characteristics, int* characteristics_count) {
GError *error = NULL;
int handle;
int ret = GATTLIB_SUCCESS;
const gchar* const* service_strs = org_bluez_device1_get_gatt_services(device);
// Increase bluez_device object reference counter to avoid to keep locking the mutex
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_valid(connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_discover_char_range: Device not valid");
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_DEVICE_DISCONNECTED;
}
OrgBluezDevice1* bluez_device = connection->backend.bluez_device;
g_object_ref(bluez_device);
g_rec_mutex_unlock(&m_gattlib_mutex);
const gchar* const* service_strs = org_bluez_device1_get_gatt_services(bluez_device);
const gchar* const* service_str;
const gchar* const* characteristic_strs;
const gchar* characteristic_str;
if (service_strs == NULL) {
return GATTLIB_INVALID_PARAMETER;
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
// Maximum number of primary services
@ -694,7 +745,8 @@ int gattlib_discover_char_range(gatt_connection_t* connection, uint16_t start, u
gattlib_characteristic_t* characteristic_list = calloc(count_max * sizeof(gattlib_characteristic_t), 1);
if (characteristic_list == NULL) {
return GATTLIB_OUT_OF_MEMORY;
ret = GATTLIB_OUT_OF_MEMORY;
goto EXIT;
}
for (service_str = service_strs; *service_str != NULL; service_str++) {
@ -782,17 +834,20 @@ int gattlib_discover_char_range(gatt_connection_t* connection, uint16_t start, u
*characteristics = characteristic_list;
*characteristics_count = count;
return GATTLIB_SUCCESS;
EXIT:
g_object_unref(bluez_device);
return ret;
}
#else
static void add_characteristics_from_service(gattlib_context_t* conn_context, GDBusObjectManager *device_manager,
static void add_characteristics_from_service(struct _gattlib_connection_backend* backend, GDBusObjectManager *device_manager,
const char* service_object_path,
unsigned int start, unsigned int end,
gattlib_characteristic_t* characteristic_list, int* count)
gattlib_characteristic_t* characteristic_list, int count_max, int* count)
{
GError *error = NULL;
for (GList *l = conn_context->dbus_objects; l != NULL; l = l->next) {
for (GList *l = backend->dbus_objects; l != NULL; l = l->next) {
GDBusObject *object = l->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.GattCharacteristic1");
@ -830,6 +885,7 @@ static void add_characteristics_from_service(gattlib_context_t* conn_context, GD
continue;
}
if (strcmp(property_value, service_object_path)) {
// This GATT characteristic is not for the current GATT service. Ignore it
g_object_unref(characteristic);
continue;
} else {
@ -844,6 +900,12 @@ static void add_characteristics_from_service(gattlib_context_t* conn_context, GD
continue;
}
// Sanity check to avoid buffer overflow
if (*count >= count_max) {
GATTLIB_LOG(GATTLIB_WARNING, "Skip GATT characteristic %s. Not enough space in the GATT characteristic array.", object_path);
continue;
}
characteristic_list[*count].handle = handle;
characteristic_list[*count].value_handle = handle;
characteristic_list[*count].properties = 0;
@ -876,13 +938,21 @@ static void add_characteristics_from_service(gattlib_context_t* conn_context, GD
}
}
int gattlib_discover_char_range(gatt_connection_t* connection, uint16_t start, uint16_t end, gattlib_characteristic_t** characteristics, int* characteristics_count) {
gattlib_context_t* conn_context = connection->context;
int gattlib_discover_char_range(gattlib_connection_t* connection, uint16_t start, uint16_t end, gattlib_characteristic_t** characteristics, int* characteristics_count) {
GError *error = NULL;
GDBusObjectManager *device_manager = get_device_manager_from_adapter(conn_context->adapter, &error);
GDBusObjectManager *device_manager;
GList *l;
int ret;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_valid(connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_discover_char_range: Device not valid");
ret = GATTLIB_DEVICE_DISCONNECTED;
goto EXIT;
}
device_manager = get_device_manager_from_adapter(connection->device->adapter, &error);
if (device_manager == NULL) {
if (error != NULL) {
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
@ -892,12 +962,12 @@ int gattlib_discover_char_range(gatt_connection_t* connection, uint16_t start, u
ret = GATTLIB_ERROR_DBUS;
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib Context not initialized.");
}
return ret;
goto EXIT;
}
// Count the maximum number of characteristic to allocate the array (we count all the characterstic for all devices)
int count_max = 0, count = 0;
for (l = conn_context->dbus_objects; l != NULL; l = l->next) {
for (l = connection->backend.dbus_objects; l != NULL; l = l->next) {
GDBusObject *object = l->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.GattCharacteristic1");
@ -916,11 +986,12 @@ int gattlib_discover_char_range(gatt_connection_t* connection, uint16_t start, u
gattlib_characteristic_t* characteristic_list = calloc(count_max * sizeof(gattlib_characteristic_t), 1);
if (characteristic_list == NULL) {
return GATTLIB_OUT_OF_MEMORY;
ret = GATTLIB_OUT_OF_MEMORY;
goto EXIT;
}
// List all services for this device
for (l = conn_context->dbus_objects; l != NULL; l = l->next) {
for (l = connection->backend.dbus_objects; l != NULL; l = l->next) {
GDBusObject *object = l->data;
const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object));
@ -932,6 +1003,12 @@ int gattlib_discover_char_range(gatt_connection_t* connection, uint16_t start, u
if (interface) {
g_object_unref(interface);
// Sanity check to avoid buffer overflow
if (count >= count_max) {
GATTLIB_LOG(GATTLIB_WARNING, "Skip battery characteristic. Not enough space in the GATT characteristic array.");
continue;
}
characteristic_list[count].handle = 0;
characteristic_list[count].value_handle = 0;
characteristic_list[count].properties = GATTLIB_CHARACTERISTIC_READ | GATTLIB_CHARACTERISTIC_NOTIFY;
@ -968,51 +1045,65 @@ int gattlib_discover_char_range(gatt_connection_t* connection, uint16_t start, u
// Ensure the service is attached to this device
const char* service_object_path = org_bluez_gatt_service1_get_device(service_proxy);
if (strcmp(conn_context->device_object_path, service_object_path)) {
if (strcmp(connection->backend.device_object_path, service_object_path)) {
g_object_unref(service_proxy);
continue;
}
// Add all characteristics attached to this service
add_characteristics_from_service(conn_context, device_manager, object_path, start, end, characteristic_list, &count);
add_characteristics_from_service(&connection->backend, device_manager, object_path, start, end, characteristic_list,
count_max, &count);
g_object_unref(service_proxy);
}
*characteristics = characteristic_list;
*characteristics_count = count;
return GATTLIB_SUCCESS;
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
#endif
int gattlib_discover_char(gatt_connection_t* connection, gattlib_characteristic_t** characteristics, int* characteristics_count)
int gattlib_discover_char(gattlib_connection_t* connection, gattlib_characteristic_t** characteristics, int* characteristics_count)
{
return gattlib_discover_char_range(connection, 0x00, 0xFF, characteristics, characteristics_count);
}
int gattlib_discover_desc_range(gatt_connection_t* connection, int start, int end, gattlib_descriptor_t** descriptors, int* descriptor_count) {
int gattlib_discover_desc_range(gattlib_connection_t* connection, int start, int end, gattlib_descriptor_t** descriptors, int* descriptor_count) {
return GATTLIB_NOT_SUPPORTED;
}
int gattlib_discover_desc(gatt_connection_t* connection, gattlib_descriptor_t** descriptors, int* descriptor_count) {
int gattlib_discover_desc(gattlib_connection_t* connection, gattlib_descriptor_t** descriptors, int* descriptor_count) {
return GATTLIB_NOT_SUPPORTED;
}
int get_bluez_device_from_mac(struct gattlib_adapter *adapter, const char *mac_address, OrgBluezDevice1 **bluez_device1)
int get_bluez_device_from_mac(struct _gattlib_adapter *adapter, const char *mac_address, OrgBluezDevice1 **bluez_device1)
{
GError *error = NULL;
char object_path[100];
int ret;
char object_path[GATTLIB_DBUS_OBJECT_PATH_SIZE_MAX];
if (adapter->adapter_proxy == NULL) {
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(adapter)) {
GATTLIB_LOG(GATTLIB_ERROR, "get_bluez_device_from_mac: Adapter not valid");
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_ADAPTER_CLOSE;
}
if (adapter->backend.adapter_proxy == NULL) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_NO_ADAPTER;
}
if (adapter != NULL) {
get_device_path_from_mac_with_adapter(adapter->adapter_proxy, mac_address, object_path, sizeof(object_path));
get_device_path_from_mac_with_adapter(adapter->backend.adapter_proxy, mac_address, object_path, sizeof(object_path));
} else {
get_device_path_from_mac(NULL, mac_address, object_path, sizeof(object_path));
}
// No need to keep the mutex longer. After it is DBUS specific operations not depending on gattlib structure
g_rec_mutex_unlock(&m_gattlib_mutex);
*bluez_device1 = org_bluez_device1_proxy_new_for_bus_sync(
G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_NONE,
@ -1021,33 +1112,47 @@ int get_bluez_device_from_mac(struct gattlib_adapter *adapter, const char *mac_a
NULL,
&error);
if (error) {
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
GATTLIB_LOG(GATTLIB_ERROR, "Failed to connection to new DBus Bluez Device: %s", error->message);
g_error_free(error);
return ret;
return GATTLIB_ERROR_DBUS_WITH_ERROR(error);
}
return GATTLIB_SUCCESS;
}
int gattlib_get_rssi(gatt_connection_t *connection, int16_t *rssi)
int gattlib_get_rssi(gattlib_connection_t *connection, int16_t *rssi)
{
if (connection == NULL) {
return GATTLIB_INVALID_PARAMETER;
}
gattlib_context_t* conn_context = connection->context;
if (rssi == NULL) {
return GATTLIB_INVALID_PARAMETER;
}
*rssi = org_bluez_device1_get_rssi(conn_context->device);
g_rec_mutex_lock(&m_gattlib_mutex);
if (connection == NULL) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_INVALID_PARAMETER;
}
if (!gattlib_connection_is_valid(connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_get_rssi: Device not valid");
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_DEVICE_DISCONNECTED;
}
// device is actually a GObject. Increasing its reference counter prevents to
// be freed if the connection is released.
OrgBluezDevice1* dbus_device = connection->backend.device;
g_object_ref(dbus_device);
g_rec_mutex_unlock(&m_gattlib_mutex);
*rssi = org_bluez_device1_get_rssi(dbus_device);
g_object_unref(dbus_device);
return GATTLIB_SUCCESS;
}
int gattlib_get_rssi_from_mac(void *adapter, const char *mac_address, int16_t *rssi)
int gattlib_get_rssi_from_mac(gattlib_adapter_t* adapter, const char *mac_address, int16_t *rssi)
{
OrgBluezDevice1 *bluez_device1;
int ret;
@ -1056,9 +1161,12 @@ int gattlib_get_rssi_from_mac(void *adapter, const char *mac_address, int16_t *r
return GATTLIB_INVALID_PARAMETER;
}
//
// No need of locking the mutex in this function. get_bluez_device_from_mac() ensures the adapter is valid.
//
ret = get_bluez_device_from_mac(adapter, mac_address, &bluez_device1);
if (ret != GATTLIB_SUCCESS) {
g_object_unref(bluez_device1);
return ret;
}

View File

@ -6,16 +6,18 @@
#include "gattlib_internal.h"
// Keep track of the allocated adapters to avoid an adapter to be freed twice.
// It could happen when using Python wrapper.
static GSList *m_adapter_list;
static GMutex m_adapter_list_mutex;
// 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, void** adapter) {
int gattlib_adapter_open(const char* adapter_name, gattlib_adapter_t** adapter) {
char object_path[20];
gattlib_adapter_t* gattlib_adapter;
OrgBluezAdapter1 *adapter_proxy;
struct gattlib_adapter *gattlib_adapter;
GError *error = NULL;
if (adapter == NULL) {
@ -26,10 +28,21 @@ int gattlib_adapter_open(const char* adapter_name, void** adapter) {
adapter_name = GATTLIB_DEFAULT_ADAPTER;
}
GATTLIB_LOG(GATTLIB_DEBUG, "Open bluetooth adapter %s", adapter_name);
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",
@ -50,33 +63,39 @@ int gattlib_adapter_open(const char* adapter_name, void** adapter) {
// Ensure the adapter is powered on
org_bluez_adapter1_set_powered(adapter_proxy, TRUE);
gattlib_adapter = calloc(1, sizeof(struct gattlib_adapter));
gattlib_adapter = calloc(1, sizeof(struct _gattlib_adapter));
if (gattlib_adapter == NULL) {
return GATTLIB_OUT_OF_MEMORY;
}
// Initialize stucture
gattlib_adapter->adapter_name = strdup(adapter_name);
gattlib_adapter->adapter_proxy = adapter_proxy;
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_mutex_lock(&m_adapter_list_mutex);
g_rec_mutex_lock(&m_gattlib_mutex);
m_adapter_list = g_slist_append(m_adapter_list, gattlib_adapter);
g_mutex_unlock(&m_adapter_list_mutex);
*adapter = gattlib_adapter;
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_SUCCESS;
}
const char *gattlib_adapter_get_name(void* adapter) {
struct gattlib_adapter *gattlib_adapter = adapter;
return gattlib_adapter->adapter_name;
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;
}
struct gattlib_adapter *init_default_adapter(void) {
struct gattlib_adapter *gattlib_adapter;
gattlib_adapter_t* init_default_adapter(void) {
gattlib_adapter_t* gattlib_adapter;
int ret;
ret = gattlib_adapter_open(NULL, (void**)&gattlib_adapter);
ret = gattlib_adapter_open(NULL, &gattlib_adapter);
if (ret != GATTLIB_SUCCESS) {
return NULL;
} else {
@ -84,10 +103,8 @@ struct gattlib_adapter *init_default_adapter(void) {
}
}
GDBusObjectManager *get_device_manager_from_adapter(struct gattlib_adapter *gattlib_adapter, GError **error) {
g_mutex_lock(&m_adapter_list_mutex);
if (gattlib_adapter->device_manager) {
GDBusObjectManager *get_device_manager_from_adapter(gattlib_adapter_t* gattlib_adapter, GError **error) {
if (gattlib_adapter->backend.device_manager) {
goto EXIT;
}
@ -96,24 +113,22 @@ GDBusObjectManager *get_device_manager_from_adapter(struct gattlib_adapter *gatt
// We should get notified when the connection is lost with the target to allow
// us to advertise us again
//
gattlib_adapter->device_manager = g_dbus_object_manager_client_new_for_bus_sync(
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->device_manager == NULL) {
g_mutex_unlock(&m_adapter_list_mutex);
if (gattlib_adapter->backend.device_manager == NULL) {
return NULL;
}
EXIT:
g_mutex_unlock(&m_adapter_list_mutex);
return gattlib_adapter->device_manager;
return gattlib_adapter->backend.device_manager;
}
static void device_manager_on_added_device1_signal(const char* device1_path, struct gattlib_adapter* gattlib_adapter)
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(
@ -139,7 +154,14 @@ static void device_manager_on_added_device1_signal(const char* device1_path, str
return;
}
g_rec_mutex_lock(&gattlib_adapter->mutex);
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
@ -148,7 +170,7 @@ static void device_manager_on_added_device1_signal(const char* device1_path, str
gattlib_on_discovered_device(gattlib_adapter, device1);
}
g_rec_mutex_unlock(&gattlib_adapter->mutex);
g_rec_mutex_unlock(&m_gattlib_mutex);
g_object_unref(device1);
}
}
@ -178,7 +200,7 @@ static void on_dbus_object_removed(GDBusObjectManager *device_manager,
gpointer user_data)
{
const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object));
struct gattlib_adapter* gattlib_adapter = (struct gattlib_adapter*)user_data;
gattlib_adapter_t* gattlib_adapter = user_data;
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: on_object_removed: %s", object_path);
@ -195,11 +217,7 @@ on_interface_proxy_properties_changed (GDBusObjectManagerClient *device_manager,
gpointer user_data)
{
const char* proxy_object_path = g_dbus_proxy_get_object_path(interface_proxy);
struct gattlib_adapter* gattlib_adapter = user_data;
if (gattlib_adapter->device_manager == NULL) {
return;
}
gattlib_adapter_t* gattlib_adapter = user_data;
// Count number of invalidated properties
size_t invalidated_properties_count = 0;
@ -217,6 +235,17 @@ on_interface_proxy_properties_changed (GDBusObjectManagerClient *device_manager,
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'
@ -230,10 +259,10 @@ on_interface_proxy_properties_changed (GDBusObjectManagerClient *device_manager,
if (error) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to connection to new DBus Bluez Device: %s", error->message);
g_error_free(error);
return;
goto EXIT;
} else if (device1 == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Unexpected NULL device");
return;
goto EXIT;
}
// Check if the device has been disconnected
@ -242,8 +271,6 @@ on_interface_proxy_properties_changed (GDBusObjectManagerClient *device_manager,
GVariant* has_rssi = g_variant_dict_lookup_value(&dict, "RSSI", NULL);
GVariant* has_manufacturer_data = g_variant_dict_lookup_value(&dict, "ManufacturerData", NULL);
g_rec_mutex_lock(&gattlib_adapter->mutex);
enum _gattlib_device_state old_device_state = gattlib_device_get_state(gattlib_adapter, proxy_object_path);
if (old_device_state == NOT_FOUND) {
@ -255,11 +282,12 @@ on_interface_proxy_properties_changed (GDBusObjectManagerClient *device_manager,
}
}
g_rec_mutex_unlock(&gattlib_adapter->mutex);
g_variant_dict_end(&dict);
g_object_unref(device1);
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
}
/**
@ -267,29 +295,40 @@ on_interface_proxy_properties_changed (GDBusObjectManagerClient *device_manager,
*
* 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(struct gattlib_adapter *gattlib_adapter) {
g_mutex_lock(&gattlib_adapter->ble_scan.scan_loop.mutex);
while (gattlib_adapter->ble_scan.is_scanning) {
g_cond_wait(&gattlib_adapter->ble_scan.scan_loop.cond, &gattlib_adapter->ble_scan.scan_loop.mutex);
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(&gattlib_adapter->ble_scan.scan_loop.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) {
struct gattlib_adapter *gattlib_adapter = data;
gattlib_adapter_t* gattlib_adapter = data;
if (gattlib_adapter->ble_scan.is_scanning) {
g_mutex_lock(&gattlib_adapter->ble_scan.scan_loop.mutex);
gattlib_adapter->ble_scan.is_scanning = false;
g_cond_broadcast(&gattlib_adapter->ble_scan.scan_loop.cond);
g_mutex_unlock(&gattlib_adapter->ble_scan.scan_loop.mutex);
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->ble_scan.ble_scan_timeout_id = 0;
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;
@ -300,47 +339,65 @@ static gboolean _stop_scan_on_timeout(gpointer data) {
* or disabling the BLE scan
*/
static void* _ble_scan_loop_thread(void* args) {
struct gattlib_adapter *gattlib_adapter = args;
gattlib_adapter_t* gattlib_adapter = args;
if (gattlib_adapter->ble_scan.ble_scan_timeout_id > 0) {
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->ble_scan.is_scanning = true;
gattlib_adapter->backend.ble_scan.is_scanning = true;
if (gattlib_adapter->ble_scan.ble_scan_timeout > 0) {
GATTLIB_LOG(GATTLIB_DEBUG, "Scan for BLE devices for %ld seconds", gattlib_adapter->ble_scan.ble_scan_timeout);
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->ble_scan.ble_scan_timeout_id = g_timeout_add_seconds(gattlib_adapter->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_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.removed_signal_id);
g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(gattlib_adapter->device_manager), gattlib_adapter->ble_scan.changed_signal_id);
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);
return 0;
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return NULL;
}
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)
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)
{
struct gattlib_adapter *gattlib_adapter = adapter;
GDBusObjectManager *device_manager;
GError *error = NULL;
GVariantBuilder arg_properties_builder;
GVariant *rssi_variant = NULL;
int ret;
if ((gattlib_adapter == NULL) || (gattlib_adapter->adapter_proxy == NULL)) {
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;
}
@ -374,7 +431,7 @@ static int _gattlib_adapter_scan_enable_with_filter(void *adapter, uuid_t **uuid
g_variant_builder_add(&arg_properties_builder, "{sv}", "RSSI", rssi_variant);
}
org_bluez_adapter1_call_set_discovery_filter_sync(gattlib_adapter->adapter_proxy,
org_bluez_adapter1_call_set_discovery_filter_sync(adapter->backend.adapter_proxy,
g_variant_builder_end(&arg_properties_builder), NULL, &error);
if (rssi_variant) {
@ -394,7 +451,7 @@ static int _gattlib_adapter_scan_enable_with_filter(void *adapter, uuid_t **uuid
// 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(gattlib_adapter, &error);
device_manager = get_device_manager_from_adapter(adapter, &error);
if (device_manager == NULL) {
if (error != NULL) {
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
@ -406,30 +463,30 @@ static int _gattlib_adapter_scan_enable_with_filter(void *adapter, uuid_t **uuid
}
// 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.callback.discovered_device = discovered_device_cb;
gattlib_adapter->ble_scan.discovered_device_callback.user_data = user_data;
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;
gattlib_adapter->ble_scan.added_signal_id = g_signal_connect(G_DBUS_OBJECT_MANAGER(device_manager),
adapter->backend.ble_scan.added_signal_id = g_signal_connect(G_DBUS_OBJECT_MANAGER(device_manager),
"object-added",
G_CALLBACK(on_dbus_object_added),
gattlib_adapter);
adapter);
gattlib_adapter->ble_scan.removed_signal_id = g_signal_connect(G_DBUS_OBJECT_MANAGER(device_manager),
adapter->backend.ble_scan.removed_signal_id = g_signal_connect(G_DBUS_OBJECT_MANAGER(device_manager),
"object-removed",
G_CALLBACK(on_dbus_object_removed),
gattlib_adapter);
adapter);
// List for object changes to see if there are still devices around
gattlib_adapter->ble_scan.changed_signal_id = g_signal_connect(G_DBUS_OBJECT_MANAGER(device_manager),
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),
gattlib_adapter);
adapter);
// Now, start BLE discovery
org_bluez_adapter1_call_start_discovery_sync(gattlib_adapter->adapter_proxy, NULL, &error);
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);
@ -441,65 +498,98 @@ static int _gattlib_adapter_scan_enable_with_filter(void *adapter, uuid_t **uuid
return GATTLIB_SUCCESS;
}
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(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)
{
struct gattlib_adapter *gattlib_adapter = adapter;
GError *error = NULL;
int ret;
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) {
return ret;
goto EXIT;
}
gattlib_adapter->ble_scan.is_scanning = true;
adapter->backend.ble_scan.is_scanning = true;
gattlib_adapter->ble_scan.scan_loop_thread = g_thread_try_new("gattlib_ble_scan", _ble_scan_loop_thread, gattlib_adapter, &error);
if (gattlib_adapter->ble_scan.scan_loop_thread == NULL) {
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);
return GATTLIB_ERROR_INTERNAL;
ret = GATTLIB_ERROR_INTERNAL;
goto EXIT;
}
g_mutex_lock(&gattlib_adapter->ble_scan.scan_loop.mutex);
while (gattlib_adapter->ble_scan.is_scanning) {
g_cond_wait(&gattlib_adapter->ble_scan.scan_loop.cond, &gattlib_adapter->ble_scan.scan_loop.mutex);
// 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(gattlib_adapter->ble_scan.scan_loop_thread);
gattlib_adapter->ble_scan.scan_loop_thread = NULL;
g_mutex_unlock(&gattlib_adapter->ble_scan.scan_loop.mutex);
g_thread_unref(adapter->backend.ble_scan.scan_loop_thread);
adapter->backend.ble_scan.scan_loop_thread = NULL;
return 0;
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
int gattlib_adapter_scan_enable_with_filter_non_blocking(void *adapter, uuid_t **uuid_list, int16_t rssi_threshold, uint32_t enabled_filters,
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)
{
struct gattlib_adapter *gattlib_adapter = adapter;
GError *error = NULL;
int ret;
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) {
return ret;
goto EXIT;
}
gattlib_adapter->ble_scan.scan_loop_thread = g_thread_try_new("gattlib_ble_scan", _ble_scan_loop_thread, gattlib_adapter, &error);
if (gattlib_adapter->ble_scan.scan_loop_thread == NULL) {
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);
return GATTLIB_ERROR_INTERNAL;
ret = GATTLIB_ERROR_INTERNAL;
goto EXIT;
}
return 0;
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t discovered_device_cb, size_t timeout, void *user_data)
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 */,
@ -507,30 +597,38 @@ int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t disco
discovered_device_cb, timeout, user_data);
}
int gattlib_adapter_scan_disable(void* adapter) {
struct gattlib_adapter *gattlib_adapter = adapter;
int gattlib_adapter_scan_disable(gattlib_adapter_t* adapter) {
GError *error = NULL;
int ret = GATTLIB_SUCCESS;
if (gattlib_adapter->adapter_proxy == NULL) {
GATTLIB_LOG(GATTLIB_INFO, "Could not disable BLE scan. No BLE adapter setup.");
return GATTLIB_NO_ADAPTER;
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;
}
g_mutex_lock(&gattlib_adapter->ble_scan.scan_loop.mutex);
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(gattlib_adapter->adapter_proxy)) {
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 (!gattlib_adapter->ble_scan.is_scanning) {
} 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(gattlib_adapter->adapter_proxy, NULL, &error);
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 {
@ -539,82 +637,132 @@ int gattlib_adapter_scan_disable(void* adapter) {
}
// Free and reset callback to stop calling it after we stopped
gattlib_handler_free(&gattlib_adapter->ble_scan.discovered_device_callback);
gattlib_handler_free(&adapter->discovered_device_callback);
// Stop BLE scan loop thread
if (gattlib_adapter->ble_scan.is_scanning) {
gattlib_adapter->ble_scan.is_scanning = false;
g_cond_broadcast(&gattlib_adapter->ble_scan.scan_loop.cond);
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 (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;
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_mutex_unlock(&gattlib_adapter->ble_scan.scan_loop.mutex);
return GATTLIB_SUCCESS;
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
int gattlib_adapter_close(void* adapter) {
struct gattlib_adapter *gattlib_adapter = adapter;
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");
return GATTLIB_BUSY;
ret = GATTLIB_BUSY;
goto EXIT;
}
g_mutex_lock(&m_adapter_list_mutex);
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", gattlib_adapter->adapter_name);
GATTLIB_LOG(GATTLIB_DEBUG, "Close bluetooth adapter %s", adapter->name);
if (gattlib_adapter->ble_scan.is_scanning) {
gattlib_adapter_scan_disable(gattlib_adapter);
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);
_wait_scan_loop_stop_scanning(gattlib_adapter);
g_thread_join(gattlib_adapter->ble_scan.scan_loop_thread);
// 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 (gattlib_adapter->ble_scan.scan_loop_thread) {
g_thread_unref(gattlib_adapter->ble_scan.scan_loop_thread);
gattlib_adapter->ble_scan.scan_loop_thread = NULL;
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 (gattlib_adapter->device_manager) {
g_object_unref(gattlib_adapter->device_manager);
gattlib_adapter->device_manager = NULL;
if (adapter->backend.device_manager) {
g_object_unref(adapter->backend.device_manager);
adapter->backend.device_manager = NULL;
}
if (gattlib_adapter->adapter_proxy != NULL) {
g_object_unref(gattlib_adapter->adapter_proxy);
gattlib_adapter->adapter_proxy = NULL;
if (adapter->backend.adapter_proxy != NULL) {
g_object_unref(adapter->backend.adapter_proxy);
adapter->backend.adapter_proxy = NULL;
}
if (gattlib_adapter->adapter_name != NULL) {
free(gattlib_adapter->adapter_name);
gattlib_adapter->adapter_name = NULL;
if (adapter->id != NULL) {
free(adapter->id);
adapter->id = NULL;
}
gattlib_devices_free(gattlib_adapter);
if (adapter->name != NULL) {
free(adapter->name);
adapter->name = NULL;
}
free(gattlib_adapter);
gattlib_devices_free(adapter);
// Remove adapter from the global list
m_adapter_list = g_slist_remove(m_adapter_list, gattlib_adapter);
m_adapter_list = g_slist_remove(m_adapter_list, adapter);
gattlib_adapter = NULL;
free(adapter);
EXIT:
g_mutex_unlock(&m_adapter_list_mutex);
return GATTLIB_SUCCESS;
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}

View File

@ -8,16 +8,16 @@
#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 40)
int gattlib_get_advertisement_data(gatt_connection_t *connection,
int gattlib_get_advertisement_data(gattlib_connection_t *connection,
gattlib_advertisement_data_t **advertisement_data, size_t *advertisement_data_count,
uint16_t *manufacturer_id, uint8_t **manufacturer_data, size_t *manufacturer_data_size)
gattlib_manufacturer_data_t** manufacturer_data, size_t* manufacturer_data_count)
{
return GATTLIB_NOT_SUPPORTED;
}
int gattlib_get_advertisement_data_from_mac(void *adapter, const char *mac_address,
int gattlib_get_advertisement_data_from_mac(gattlib_adapter_t* adapter, const char *mac_address,
gattlib_advertisement_data_t **advertisement_data, size_t *advertisement_data_count,
uint16_t *manufacturer_id, uint8_t **manufacturer_data, size_t *manufacturer_data_size)
gattlib_manufacturer_data_t** manufacturer_data, size_t* manufacturer_data_count)
{
return GATTLIB_NOT_SUPPORTED;
}
@ -26,7 +26,7 @@ int gattlib_get_advertisement_data_from_mac(void *adapter, const char *mac_addre
int get_advertisement_data_from_device(OrgBluezDevice1 *bluez_device1,
gattlib_advertisement_data_t **advertisement_data, size_t *advertisement_data_count,
uint16_t *manufacturer_id, uint8_t **manufacturer_data, size_t *manufacturer_data_size)
gattlib_manufacturer_data_t** manufacturer_data, size_t* manufacturer_data_count)
{
GVariant *manufacturer_data_variant;
GVariant *service_data_variant;
@ -35,36 +35,41 @@ int get_advertisement_data_from_device(OrgBluezDevice1 *bluez_device1,
return GATTLIB_INVALID_PARAMETER;
}
*manufacturer_id = 0;
*manufacturer_data_size = 0;
*manufacturer_data_count = 0;
*manufacturer_data = NULL;
manufacturer_data_variant = org_bluez_device1_get_manufacturer_data(bluez_device1);
if (manufacturer_data_variant != NULL) {
if (g_variant_n_children(manufacturer_data_variant) != 1) {
GATTLIB_LOG(GATTLIB_WARNING, "Manufacturer Data with multiple children: %s",
g_variant_print(manufacturer_data_variant, TRUE));
return GATTLIB_NOT_SUPPORTED;
}
GVariant* manufacturer_data_dict = g_variant_get_child_value(manufacturer_data_variant, 0);
GVariantIter *iter;
GVariant* values;
g_variant_get(manufacturer_data_dict, "{qv}", manufacturer_id, &values);
*manufacturer_data_size = g_variant_n_children(values);
*manufacturer_data = calloc(*manufacturer_data_size, sizeof(guchar));
*manufacturer_data_count = g_variant_n_children(manufacturer_data_variant);
*manufacturer_data = malloc(sizeof(gattlib_manufacturer_data_t) * (*manufacturer_data_count));
if (*manufacturer_data == NULL) {
return GATTLIB_OUT_OF_MEMORY;
}
GVariant* value;
g_variant_get(values, "ay", &iter);
size_t index = 0;
for (uintptr_t i = 0; i < *manufacturer_data_count; i++) {
GVariant* manufacturer_data_dict = g_variant_get_child_value(manufacturer_data_variant, i);
GVariantIter *iter;
GVariant* values;
uint16_t manufacturer_id = 0;
while ((value = g_variant_iter_next_value(iter)) != NULL) {
g_variant_get(value, "y", &(*manufacturer_data)[index++]);
g_variant_unref(value);
g_variant_get(manufacturer_data_dict, "{qv}", &manufacturer_id, &values);
(*manufacturer_data)[i].manufacturer_id = manufacturer_id;
(*manufacturer_data)[i].data_size = g_variant_n_children(values);
(*manufacturer_data)[i].data = calloc((*manufacturer_data)[i].data_size, sizeof(guchar));
if ((*manufacturer_data)[i].data == NULL) {
return GATTLIB_OUT_OF_MEMORY;
}
// Copy manufacturer data to structure
GVariant* value;
g_variant_get(values, "ay", &iter);
size_t index = 0;
while ((value = g_variant_iter_next_value(iter)) != NULL) {
g_variant_get(value, "y", (*manufacturer_data)[i].data[index++]);
g_variant_unref(value);
}
g_variant_iter_free(iter);
}
g_variant_iter_free(iter);
}
service_data_variant = org_bluez_device1_get_service_data(bluez_device1);
@ -112,43 +117,64 @@ int get_advertisement_data_from_device(OrgBluezDevice1 *bluez_device1,
return GATTLIB_SUCCESS;
}
int gattlib_get_advertisement_data(gatt_connection_t *connection,
int gattlib_get_advertisement_data(gattlib_connection_t *connection,
gattlib_advertisement_data_t **advertisement_data, size_t *advertisement_data_count,
uint16_t *manufacturer_id, uint8_t **manufacturer_data, size_t *manufacturer_data_size)
gattlib_manufacturer_data_t** manufacturer_data, size_t* manufacturer_data_count)
{
gattlib_context_t* conn_context;
int ret;
g_rec_mutex_lock(&m_gattlib_mutex);
if (connection == NULL) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_INVALID_PARAMETER;
}
conn_context = connection->context;
return get_advertisement_data_from_device(conn_context->device,
advertisement_data, advertisement_data_count,
manufacturer_id, manufacturer_data, manufacturer_data_size);
}
int gattlib_get_advertisement_data_from_mac(void *adapter, const char *mac_address,
gattlib_advertisement_data_t **advertisement_data, size_t *advertisement_data_count,
uint16_t *manufacturer_id, uint8_t **manufacturer_data, size_t *manufacturer_data_size)
{
OrgBluezDevice1 *bluez_device1;
int ret;
ret = get_bluez_device_from_mac(adapter, mac_address, &bluez_device1);
if (ret != GATTLIB_SUCCESS) {
g_object_unref(bluez_device1);
return ret;
if (!gattlib_connection_is_valid(connection)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_DEVICE_DISCONNECTED;
}
ret = get_advertisement_data_from_device(bluez_device1,
advertisement_data, advertisement_data_count,
manufacturer_id, manufacturer_data, manufacturer_data_size);
// device is actually a GObject. Increasing its reference counter prevents to
// be freed if the connection is released.
OrgBluezDevice1* dbus_device = connection->backend.device;
g_object_ref(dbus_device);
g_rec_mutex_unlock(&m_gattlib_mutex);
g_object_unref(bluez_device1);
ret = get_advertisement_data_from_device(dbus_device,
advertisement_data, advertisement_data_count,
manufacturer_data, manufacturer_data_count);
g_object_unref(dbus_device);
return ret;
}
int gattlib_get_advertisement_data_from_mac(gattlib_adapter_t* adapter, const char *mac_address,
gattlib_advertisement_data_t **advertisement_data, size_t *advertisement_data_count,
gattlib_manufacturer_data_t** manufacturer_data, size_t* manufacturer_data_count)
{
OrgBluezDevice1 *bluez_device1;
int ret;
//
// No need of locking the mutex in this function. It is already done by get_bluez_device_from_mac()
// and get_advertisement_data_from_device() does not depend on gattlib objects
//
ret = get_bluez_device_from_mac(adapter, mac_address, &bluez_device1);
if (ret != GATTLIB_SUCCESS) {
goto EXIT;
}
ret = get_advertisement_data_from_device(bluez_device1,
advertisement_data, advertisement_data_count,
manufacturer_data, manufacturer_data_count);
g_object_unref(bluez_device1);
EXIT:
return ret;
}
#endif /* #if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 40) */

View File

@ -4,13 +4,12 @@
* Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
*/
#ifndef __GATTLIB_INTERNAL_H__
#define __GATTLIB_INTERNAL_H__
#ifndef __GATTLIB_BACKEND_H__
#define __GATTLIB_BACKEND_H__
#include <assert.h>
#include <pthread.h>
#include "gattlib_internal_defs.h"
#include "gattlib.h"
#include "org-bluez-adaptater1.h"
@ -37,9 +36,12 @@
#define GATTLIB_DEFAULT_ADAPTER "hci0"
typedef struct {
struct gattlib_adapter *adapter;
// Arbitrary size used to build DBUS object path from adapter name and mac address.
// Otherwise DBUs Object path are unlimited:
// See: https://dbus.freedesktop.org/doc/api/html/group__DBusProtocol.html#ga80186ac58d031d83127d1ad6644b0011
#define GATTLIB_DBUS_OBJECT_PATH_SIZE_MAX 200
struct _gattlib_connection_backend {
char* device_object_path;
OrgBluezDevice1* device;
@ -54,16 +56,12 @@ typedef struct {
// List of 'OrgBluezGattCharacteristic1*' which has an attached notification
GList *notified_characteristics;
} gattlib_context_t;
};
struct gattlib_adapter {
struct _gattlib_adapter_backend {
GDBusObjectManager *device_manager;
OrgBluezAdapter1 *adapter_proxy;
char* adapter_name;
// The recursive mutex allows to ensure sensible operations are always covered by a mutex in a same thread
GRecMutex mutex;
// Internal attributes only needed during BLE scanning
struct {
@ -76,19 +74,8 @@ struct gattlib_adapter {
GThread *scan_loop_thread; // Thread used to run the '_scan_loop()' when non-blocking
bool is_scanning;
struct {
GMutex mutex;
GCond cond;
} scan_loop;
uint32_t enabled_filters;
struct gattlib_handler discovered_device_callback;
} ble_scan;
// List of `struct _gattlib_device`. This list allows to know weither a device is
// discovered/disconnected/connecting/connected/disconnecting.
GSList *devices;
};
struct dbus_characteristic {
@ -107,24 +94,24 @@ struct dbus_characteristic {
extern const uuid_t m_battery_level_uuid;
struct gattlib_adapter *init_default_adapter(void);
GDBusObjectManager *get_device_manager_from_adapter(struct gattlib_adapter *gattlib_adapter, GError **error);
struct _gattlib_adapter *init_default_adapter(void);
GDBusObjectManager *get_device_manager_from_adapter(gattlib_adapter_t* gattlib_adapter, GError **error);
void get_device_path_from_mac_with_adapter(OrgBluezAdapter1* adapter, const char *mac_address, char *object_path, size_t object_path_len);
void get_device_path_from_mac(const char *adapter_name, const char *mac_address, char *object_path, size_t object_path_len);
int get_bluez_device_from_mac(struct gattlib_adapter *adapter, const char *mac_address, OrgBluezDevice1 **bluez_device1);
int get_bluez_device_from_mac(struct _gattlib_adapter *adapter, const char *mac_address, OrgBluezDevice1 **bluez_device1);
struct dbus_characteristic get_characteristic_from_uuid(gatt_connection_t* connection, const uuid_t* uuid);
struct dbus_characteristic get_characteristic_from_uuid(gattlib_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);
void gattlib_on_discovered_device(gattlib_adapter_t* gattlib_adapter, OrgBluezDevice1* device1);
// Invoke when a new device is being connected
void gattlib_on_connected_device(gatt_connection_t* connection);
void gattlib_on_connected_device(gattlib_connection_t* connection);
// Invoke when a new device is being disconnected
void gattlib_on_disconnected_device(gatt_connection_t* connection);
void gattlib_on_disconnected_device(gattlib_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 gattlib_on_gatt_notification(gattlib_connection_t* connection, const uuid_t* uuid, const uint8_t* data, size_t data_length);
void disconnect_all_notifications(gattlib_context_t* conn_context);
void disconnect_all_notifications(struct _gattlib_connection_backend* backend);
#endif

View File

@ -18,7 +18,7 @@ const uuid_t m_battery_level_uuid = CREATE_UUID16(0x2A19);
static const uuid_t m_ccc_uuid = CREATE_UUID16(0x2902);
static bool handle_dbus_gattcharacteristic_from_path(gattlib_context_t* conn_context, const uuid_t* uuid,
static bool handle_dbus_gattcharacteristic_from_path(struct _gattlib_connection_backend* backend, const uuid_t* uuid,
struct dbus_characteristic *dbus_characteristic, const char* object_path, GError **error)
{
OrgBluezGattCharacteristic1 *characteristic = NULL;
@ -61,7 +61,7 @@ static bool handle_dbus_gattcharacteristic_from_path(gattlib_context_t* conn_con
error);
if (service) {
const bool found = !strcmp(conn_context->device_object_path, org_bluez_gatt_service1_get_device(service));
const bool found = !strcmp(backend->device_object_path, org_bluez_gatt_service1_get_device(service));
g_object_unref(service);
@ -79,7 +79,7 @@ static bool handle_dbus_gattcharacteristic_from_path(gattlib_context_t* conn_con
}
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
static bool handle_dbus_battery_from_uuid(gattlib_context_t* conn_context, const uuid_t* uuid,
static bool handle_dbus_battery_from_uuid(struct _gattlib_connection_backend* backend, const uuid_t* uuid,
struct dbus_characteristic *dbus_characteristic, const char* object_path, GError **error)
{
OrgBluezBattery1 *battery = NULL;
@ -101,16 +101,22 @@ static bool handle_dbus_battery_from_uuid(gattlib_context_t* conn_context, const
}
#endif
struct dbus_characteristic get_characteristic_from_uuid(gatt_connection_t* connection, const uuid_t* uuid) {
gattlib_context_t* conn_context = connection->context;
struct dbus_characteristic get_characteristic_from_uuid(gattlib_connection_t* connection, const uuid_t* uuid) {
GError *error = NULL;
GDBusObjectManager *device_manager = get_device_manager_from_adapter(conn_context->adapter, &error);
GDBusObjectManager *device_manager;
bool is_battery_level_uuid = false;
struct dbus_characteristic dbus_characteristic = {
.type = TYPE_NONE
.type = TYPE_NONE
};
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_connected(connection)) {
goto EXIT;
}
device_manager = get_device_manager_from_adapter(connection->device->adapter, &error);
if (device_manager == NULL) {
if (error != NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib Context not initialized (%d, %d).", error->domain, error->code);
@ -118,7 +124,7 @@ struct dbus_characteristic get_characteristic_from_uuid(gatt_connection_t* conne
} else {
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib Context not initialized.");
}
return dbus_characteristic; // Return characteristic of type TYPE_NONE
goto EXIT; // Return characteristic of type TYPE_NONE
}
// Some GATT Characteristics are handled by D-BUS
@ -126,11 +132,11 @@ struct dbus_characteristic get_characteristic_from_uuid(gatt_connection_t* conne
is_battery_level_uuid = true;
} else if (gattlib_uuid_cmp(uuid, &m_ccc_uuid) == 0) {
GATTLIB_LOG(GATTLIB_ERROR, "Error: Bluez v5.42+ does not expose Client Characteristic Configuration Descriptor through DBUS interface");
return dbus_characteristic;
goto EXIT;
}
GList *l;
for (l = conn_context->dbus_objects; l != NULL; l = l->next) {
for (l = connection->backend.dbus_objects; l != NULL; l = l->next) {
GDBusInterface *interface;
bool found = false;
GDBusObject *object = l->data;
@ -140,7 +146,7 @@ struct dbus_characteristic get_characteristic_from_uuid(gatt_connection_t* conne
if (interface) {
g_object_unref(interface);
found = handle_dbus_gattcharacteristic_from_path(conn_context, uuid, &dbus_characteristic, object_path, &error);
found = handle_dbus_gattcharacteristic_from_path(&connection->backend, uuid, &dbus_characteristic, object_path, &error);
if (found) {
break;
}
@ -152,7 +158,7 @@ struct dbus_characteristic get_characteristic_from_uuid(gatt_connection_t* conne
if (interface) {
g_object_unref(interface);
found = handle_dbus_battery_from_uuid(conn_context, uuid, &dbus_characteristic, object_path, &error);
found = handle_dbus_battery_from_uuid(&connection->backend, uuid, &dbus_characteristic, object_path, &error);
if (found) {
break;
}
@ -163,19 +169,27 @@ struct dbus_characteristic get_characteristic_from_uuid(gatt_connection_t* conne
}
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return dbus_characteristic;
}
static struct dbus_characteristic get_characteristic_from_handle(gatt_connection_t* connection, unsigned int handle) {
gattlib_context_t* conn_context = connection->context;
static struct dbus_characteristic get_characteristic_from_handle(gattlib_connection_t* connection, unsigned int handle) {
GError *error = NULL;
GDBusObjectManager *device_manager = get_device_manager_from_adapter(conn_context->adapter, &error);
unsigned int char_handle;
GDBusObjectManager *device_manager;
struct dbus_characteristic dbus_characteristic = {
.type = TYPE_NONE
.type = TYPE_NONE
};
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_connected(connection)) {
goto EXIT;
}
device_manager = get_device_manager_from_adapter(connection->device->adapter, &error);
if (device_manager == NULL) {
if (error != NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib Context not initialized (%d, %d).", error->domain, error->code);
@ -183,10 +197,10 @@ static struct dbus_characteristic get_characteristic_from_handle(gatt_connection
} else {
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib Context not initialized.");
}
return dbus_characteristic;
goto EXIT;
}
for (GList *l = conn_context->dbus_objects; l != NULL; l = l->next) {
for (GList *l = connection->backend.dbus_objects; l != NULL; l = l->next) {
GDBusInterface *interface;
bool found;
GDBusObject *object = l->data;
@ -204,13 +218,15 @@ static struct dbus_characteristic get_characteristic_from_handle(gatt_connection
continue;
}
found = handle_dbus_gattcharacteristic_from_path(conn_context, NULL, &dbus_characteristic, object_path, &error);
found = handle_dbus_gattcharacteristic_from_path(&connection->backend, NULL, &dbus_characteristic, object_path, &error);
if (found) {
break;
}
}
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return dbus_characteristic;
}
@ -273,7 +289,12 @@ static int read_battery_level(struct dbus_characteristic *dbus_characteristic, v
}
#endif
int gattlib_read_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, void **buffer, size_t *buffer_len) {
int gattlib_read_char_by_uuid(gattlib_connection_t* connection, uuid_t* uuid, void **buffer, size_t *buffer_len) {
//
// No need of locking the gattlib mutex. get_characteristic_from_uuid() is taking care of the gattlib
// object coherency. And 'dbus_characteristic' is not linked to gattlib object
//
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
if (dbus_characteristic.type == TYPE_NONE) {
return GATTLIB_NOT_FOUND;
@ -296,9 +317,14 @@ int gattlib_read_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, void
}
}
int gattlib_read_char_by_uuid_async(gatt_connection_t* connection, uuid_t* uuid, gatt_read_cb_t gatt_read_cb) {
int gattlib_read_char_by_uuid_async(gattlib_connection_t* connection, uuid_t* uuid, gatt_read_cb_t gatt_read_cb) {
int ret = GATTLIB_SUCCESS;
//
// No need of locking the gattlib mutex. get_characteristic_from_uuid() is taking care of the gattlib
// object coherency. And 'dbus_characteristic' is not linked to gattlib object
//
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
if (dbus_characteristic.type == TYPE_NONE) {
return GATTLIB_NOT_FOUND;
@ -389,10 +415,15 @@ static int write_char(struct dbus_characteristic *dbus_characteristic, const voi
return ret;
}
int gattlib_write_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len)
int gattlib_write_char_by_uuid(gattlib_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len)
{
int ret;
//
// No need of locking the gattlib mutex. get_characteristic_from_uuid() is taking care of the gattlib
// object coherency. And 'dbus_characteristic' is not linked to gattlib object
//
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
if (dbus_characteristic.type == TYPE_NONE) {
return GATTLIB_NOT_FOUND;
@ -408,10 +439,15 @@ int gattlib_write_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, cons
return ret;
}
int gattlib_write_char_by_handle(gatt_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len)
int gattlib_write_char_by_handle(gattlib_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len)
{
int ret;
//
// No need of locking the gattlib mutex. get_characteristic_from_handle() is taking care of the gattlib
// object coherency. And 'dbus_characteristic' is not linked to gattlib object
//
struct dbus_characteristic dbus_characteristic = get_characteristic_from_handle(connection, handle);
if (dbus_characteristic.type == TYPE_NONE) {
return GATTLIB_NOT_FOUND;
@ -423,10 +459,15 @@ int gattlib_write_char_by_handle(gatt_connection_t* connection, uint16_t handle,
return ret;
}
int gattlib_write_without_response_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len)
int gattlib_write_without_response_char_by_uuid(gattlib_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len)
{
int ret;
//
// No need of locking the gattlib mutex. get_characteristic_from_uuid() is taking care of the gattlib
// object coherency. And 'dbus_characteristic' is not linked to gattlib object
//
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
if (dbus_characteristic.type == TYPE_NONE) {
return GATTLIB_NOT_FOUND;
@ -442,10 +483,15 @@ int gattlib_write_without_response_char_by_uuid(gatt_connection_t* connection, u
return ret;
}
int gattlib_write_without_response_char_by_handle(gatt_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len)
int gattlib_write_without_response_char_by_handle(gattlib_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len)
{
int ret;
//
// No need of locking the gattlib mutex. get_characteristic_from_handle() is taking care of the gattlib
// object coherency. And 'dbus_characteristic' is not linked to gattlib object
//
struct dbus_characteristic dbus_characteristic = get_characteristic_from_handle(connection, handle);
if (dbus_characteristic.type == TYPE_NONE) {
return GATTLIB_NOT_FOUND;

View File

@ -25,12 +25,19 @@ gboolean on_handle_battery_level_property_change(
gpointer user_data)
{
static guint8 percentage;
gatt_connection_t* connection = user_data;
gattlib_connection_t* connection = user_data;
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: on_handle_battery_level_property_change: changed_properties:%s invalidated_properties:%s",
g_variant_print(arg_changed_properties, TRUE),
arg_invalidated_properties);
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_connected(connection)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return FALSE;
}
if (gattlib_has_valid_handler(&connection->notification)) {
// Retrieve 'Value' from 'arg_changed_properties'
if (g_variant_n_children (arg_changed_properties) > 0) {
@ -54,6 +61,7 @@ gboolean on_handle_battery_level_property_change(
g_variant_iter_free(iter);
}
}
g_rec_mutex_unlock(&m_gattlib_mutex);
return TRUE;
}
#endif
@ -64,7 +72,14 @@ static gboolean on_handle_characteristic_property_change(
const gchar *const *arg_invalidated_properties,
gpointer user_data)
{
gatt_connection_t* connection = user_data;
gattlib_connection_t* connection = user_data;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_connected(connection)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
return FALSE;
}
if (gattlib_has_valid_handler(&connection->notification)) {
GVariantDict dict;
@ -96,6 +111,8 @@ static gboolean on_handle_characteristic_property_change(
} else {
GATTLIB_LOG(GATTLIB_DEBUG, "on_handle_characteristic_property_change: not a notification handler");
}
g_rec_mutex_unlock(&m_gattlib_mutex);
return TRUE;
}
@ -105,7 +122,7 @@ static gboolean on_handle_characteristic_indication(
const gchar *const *arg_invalidated_properties,
gpointer user_data)
{
gatt_connection_t* connection = user_data;
gattlib_connection_t* connection = user_data;
if (gattlib_has_valid_handler(&connection->indication)) {
// Retrieve 'Value' from 'arg_changed_properties'
@ -141,12 +158,18 @@ static gboolean on_handle_characteristic_indication(
return TRUE;
}
static int connect_signal_to_characteristic_uuid(gatt_connection_t* connection, const uuid_t* uuid, void *callback) {
gattlib_context_t* conn_context = connection->context;
int ret;
static int connect_signal_to_characteristic_uuid(gattlib_connection_t* connection, const uuid_t* uuid, void *callback) {
int ret = GATTLIB_SUCCESS;
assert(callback != NULL);
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_connected(connection)) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
if (dbus_characteristic.type == TYPE_NONE) {
char uuid_str[MAX_LEN_UUID_STR + 1];
@ -154,7 +177,8 @@ static int connect_signal_to_characteristic_uuid(gatt_connection_t* connection,
gattlib_uuid_to_string(uuid, uuid_str, sizeof(uuid_str));
GATTLIB_LOG(GATTLIB_ERROR, "GATT characteristic '%s' not found", uuid_str);
return GATTLIB_NOT_FOUND;
ret = GATTLIB_NOT_FOUND;
goto EXIT;
}
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
else if (dbus_characteristic.type == TYPE_BATTERY_LEVEL) {
@ -164,7 +188,8 @@ static int connect_signal_to_characteristic_uuid(gatt_connection_t* connection,
G_CALLBACK (on_handle_battery_level_property_change),
connection);
return GATTLIB_SUCCESS;
ret = GATTLIB_SUCCESS;
goto EXIT;
} else {
assert(dbus_characteristic.type == TYPE_GATT);
}
@ -177,18 +202,22 @@ static int connect_signal_to_characteristic_uuid(gatt_connection_t* connection,
connection);
if (signal_id == 0) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to connect signal to DBus GATT notification");
return GATTLIB_ERROR_DBUS;
ret = GATTLIB_ERROR_DBUS;
goto EXIT;
}
// Add signal to the list
struct gattlib_notification_handle *notification_handle = calloc(sizeof(struct gattlib_notification_handle), 1);
if (notification_handle == NULL) {
return GATTLIB_OUT_OF_MEMORY;
ret = GATTLIB_OUT_OF_MEMORY;
goto EXIT;
}
notification_handle->gatt = dbus_characteristic.gatt;
notification_handle->signal_id = signal_id;
memcpy(&notification_handle->uuid, uuid, sizeof(*uuid));
conn_context->notified_characteristics = g_list_append(conn_context->notified_characteristics, notification_handle);
connection->backend.notified_characteristics = g_list_append(connection->backend.notified_characteristics, notification_handle);
// Note: An optimisation could be to release mutex here after increasing reference counter of 'dbus_characteristic.gatt'
GError *error = NULL;
org_bluez_gatt_characteristic1_call_start_notify_sync(dbus_characteristic.gatt, NULL, &error);
@ -196,29 +225,39 @@ static int connect_signal_to_characteristic_uuid(gatt_connection_t* connection,
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
GATTLIB_LOG(GATTLIB_ERROR, "Failed to start DBus GATT notification: %s", error->message);
g_error_free(error);
return ret;
} else {
return GATTLIB_SUCCESS;
goto EXIT;
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
static int disconnect_signal_to_characteristic_uuid(gatt_connection_t* connection, const uuid_t* uuid, void *callback) {
gattlib_context_t* conn_context = connection->context;
static int disconnect_signal_to_characteristic_uuid(gattlib_connection_t* connection, const uuid_t* uuid, void *callback) {
struct gattlib_notification_handle *notification_handle = NULL;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_connected(connection)) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
// Find notification handle
for (GList *l = conn_context->notified_characteristics; l != NULL; l = l->next) {
for (GList *l = connection->backend.notified_characteristics; l != NULL; l = l->next) {
struct gattlib_notification_handle *notification_handle_ptr = l->data;
if (gattlib_uuid_cmp(&notification_handle_ptr->uuid, uuid) == GATTLIB_SUCCESS) {
notification_handle = notification_handle_ptr;
conn_context->notified_characteristics = g_list_delete_link(conn_context->notified_characteristics, l);
connection->backend.notified_characteristics = g_list_delete_link(connection->backend.notified_characteristics, l);
break;
}
}
if (notification_handle == NULL) {
return GATTLIB_NOT_FOUND;
ret = GATTLIB_NOT_FOUND;
goto EXIT;
}
g_signal_handler_disconnect(notification_handle->gatt, notification_handle->signal_id);
@ -232,25 +271,28 @@ static int disconnect_signal_to_characteristic_uuid(gatt_connection_t* connectio
if (error) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to stop DBus GATT notification: %s", error->message);
g_error_free(error);
return GATTLIB_NOT_FOUND;
} else {
return GATTLIB_SUCCESS;
ret = GATTLIB_NOT_FOUND;
goto EXIT;
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
int gattlib_notification_start(gatt_connection_t* connection, const uuid_t* uuid) {
int gattlib_notification_start(gattlib_connection_t* connection, const uuid_t* uuid) {
return connect_signal_to_characteristic_uuid(connection, uuid, on_handle_characteristic_property_change);
}
int gattlib_notification_stop(gatt_connection_t* connection, const uuid_t* uuid) {
int gattlib_notification_stop(gattlib_connection_t* connection, const uuid_t* uuid) {
return disconnect_signal_to_characteristic_uuid(connection, uuid, on_handle_characteristic_property_change);
}
int gattlib_indication_start(gatt_connection_t* connection, const uuid_t* uuid) {
int gattlib_indication_start(gattlib_connection_t* connection, const uuid_t* uuid) {
return connect_signal_to_characteristic_uuid(connection, uuid, on_handle_characteristic_indication);
}
int gattlib_indication_stop(gatt_connection_t* connection, const uuid_t* uuid) {
int gattlib_indication_stop(gattlib_connection_t* connection, const uuid_t* uuid) {
return disconnect_signal_to_characteristic_uuid(connection, uuid, on_handle_characteristic_indication);
}
@ -261,6 +303,6 @@ static void end_notification(void *notified_characteristic) {
free(notification_handle);
}
void disconnect_all_notifications(gattlib_context_t* conn_context) {
g_list_free_full(g_steal_pointer(&conn_context->notified_characteristics), end_notification);
void disconnect_all_notifications(struct _gattlib_connection_backend* backend) {
g_list_free_full(g_steal_pointer(&backend->notified_characteristics), end_notification);
}

View File

@ -12,32 +12,38 @@
#if BLUEZ_VERSION < BLUEZ_VERSIONS(5, 48)
int gattlib_write_char_by_uuid_stream_open(gatt_connection_t* connection, uuid_t* uuid, gatt_stream_t **stream, uint16_t *mtu)
int gattlib_write_char_by_uuid_stream_open(gattlib_connection_t* connection, uuid_t* uuid, gattlib_stream_t **stream, uint16_t *mtu)
{
return GATTLIB_NOT_SUPPORTED;
}
int gattlib_write_char_stream_write(gatt_stream_t *stream, const void *buffer, size_t buffer_len)
int gattlib_write_char_stream_write(gattlib_stream_t *stream, const void *buffer, size_t buffer_len)
{
return GATTLIB_NOT_SUPPORTED;
}
int gattlib_write_char_stream_close(gatt_stream_t *stream)
int gattlib_write_char_stream_close(gattlib_stream_t *stream)
{
return GATTLIB_NOT_SUPPORTED;
}
#else
int gattlib_write_char_by_uuid_stream_open(gatt_connection_t* connection, uuid_t* uuid, gatt_stream_t **stream, uint16_t *mtu)
int gattlib_write_char_by_uuid_stream_open(gattlib_connection_t* connection, uuid_t* uuid, gattlib_stream_t **stream, uint16_t *mtu)
{
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
GError *error = NULL;
GUnixFDList *fd_list;
GVariant *out_fd;
int ret;
int fd;
//
// No need of locking the gattlib mutex. get_characteristic_from_uuid() is taking care of the gattlib
// object coherency. And 'dbus_characteristic' is not linked to gattlib object
//
struct dbus_characteristic dbus_characteristic = get_characteristic_from_uuid(connection, uuid);
GVariantBuilder *variant_options = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
org_bluez_gatt_characteristic1_call_acquire_write_sync(
@ -67,12 +73,12 @@ int gattlib_write_char_by_uuid_stream_open(gatt_connection_t* connection, uuid_t
}
// We abuse the pointer 'stream' to pass the 'File Descriptor'
*stream = (gatt_stream_t*)(unsigned long)fd;
*stream = (gattlib_stream_t*)(unsigned long)fd;
return GATTLIB_SUCCESS;
}
int gattlib_write_char_stream_write(gatt_stream_t *stream, const void *buffer, size_t buffer_len)
int gattlib_write_char_stream_write(gattlib_stream_t *stream, const void *buffer, size_t buffer_len)
{
ssize_t ret = write((unsigned long)stream, buffer, buffer_len);
if (ret < 0) {
@ -82,7 +88,7 @@ int gattlib_write_char_stream_write(gatt_stream_t *stream, const void *buffer, s
}
}
int gattlib_write_char_stream_close(gatt_stream_t *stream)
int gattlib_write_char_stream_close(gattlib_stream_t *stream)
{
close((unsigned long)stream);
return GATTLIB_SUCCESS;

View File

@ -19,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 3.25.1)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -35,17 +35,16 @@
static const char* adapter_name;
static void ble_advertising_device(void *adapter, const char* addr, const char* name, void *user_data) {
static void ble_advertising_device(gattlib_adapter_t* adapter, const char* addr, const char* name, void *user_data) {
gattlib_advertisement_data_t *advertisement_data;
size_t advertisement_data_count;
uint16_t manufacturer_id;
uint8_t *manufacturer_data;
size_t manufacturer_data_size;
gattlib_manufacturer_data_t* manufacturer_data = NULL;
size_t manufacturer_data_count = 0;
int ret;
ret = gattlib_get_advertisement_data_from_mac(adapter, addr,
&advertisement_data, &advertisement_data_count,
&manufacturer_id, &manufacturer_data, &manufacturer_data_size);
&manufacturer_data, &manufacturer_data_count);
if (ret != 0) {
return;
}
@ -56,14 +55,17 @@ static void ble_advertising_device(void *adapter, const char* addr, const char*
printf("Device %s: ", addr);
}
for (size_t i = 0; i < manufacturer_data_size; i++) {
printf("%02x ", manufacturer_data[i]);
for (size_t i = 0; i < manufacturer_data_count; i++) {
printf("- Manufacturer data for id 0x%x: ", manufacturer_data[i].manufacturer_id);
for (size_t j = 0; j < manufacturer_data[i].data_size; j++) {
printf("%02x ", manufacturer_data[i].data[j]);
}
printf("\n");
}
printf("\n");
}
static void* ble_task(void *arg) {
void* adapter;
gattlib_adapter_t* adapter;
int ret;
ret = gattlib_adapter_open(adapter_name, &adapter);

View File

@ -19,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 3.25.1)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -44,12 +44,12 @@ static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
LIST_HEAD(listhead, connection_t) g_ble_connections;
struct connection_t {
pthread_t thread;
void *adapter;
gattlib_adapter_t* adapter;
char* addr;
LIST_ENTRY(connection_t) entries;
};
static void on_device_connect(void *adapter, const char *dst, gatt_connection_t* connection, int error, void* user_data) {
static void on_device_connect(gattlib_adapter_t* adapter, const char *dst, gattlib_connection_t* connection, int error, void* user_data) {
gattlib_primary_service_t* services;
gattlib_characteristic_t* characteristics;
int services_count, characteristics_count;
@ -107,7 +107,7 @@ static void *ble_connect_device(void *arg) {
return NULL;
}
static void ble_discovered_device(void *adapter, const char* addr, const char* name, void *user_data) {
static void ble_discovered_device(gattlib_adapter_t* adapter, const char* addr, const char* name, void *user_data) {
struct connection_t *connection;
int ret;
@ -135,7 +135,7 @@ static void ble_discovered_device(void *adapter, const char* addr, const char* n
}
static void* ble_task(void* arg) {
void* adapter;
gattlib_adapter_t* adapter;
int ret;
ret = gattlib_adapter_open(adapter_name, &adapter);

View File

@ -19,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 3.25.1)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -42,7 +42,7 @@ static pthread_cond_t m_connection_terminated = PTHREAD_COND_INITIALIZER;
// declaring mutex
static pthread_mutex_t m_connection_terminated_lock = PTHREAD_MUTEX_INITIALIZER;
static void on_device_connect(void *adapter, const char *dst, gatt_connection_t* connection, int error, void* user_data) {
static void on_device_connect(gattlib_adapter_t* adapter, const char *dst, gattlib_connection_t* connection, int error, void* user_data) {
gattlib_primary_service_t* services;
gattlib_characteristic_t* characteristics;
int services_count, characteristics_count;
@ -96,7 +96,7 @@ static int stricmp(char const *a, char const *b) {
}
}
static void ble_discovered_device(void *adapter, const char* addr, const char* name, void *user_data) {
static void ble_discovered_device(gattlib_adapter_t* adapter, const char* addr, const char* name, void *user_data) {
const char* reference_mac_address = user_data;
int ret;
@ -114,7 +114,7 @@ static void ble_discovered_device(void *adapter, const char* addr, const char* n
static void* ble_task(void* arg) {
char* addr = arg;
void* adapter;
gattlib_adapter_t* adapter;
int ret;
ret = gattlib_adapter_open(adapter_name, &adapter);

View File

@ -19,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 3.25.1)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -43,9 +43,9 @@ const char* m_adapter_name;
* @param manufacturer_data_size is the size of manufacturer_data
* @param user_data Data defined when calling `gattlib_register_on_disconnect()`
*/
void on_eddystone_found(void *adapter, const char* addr, const char* name,
void on_eddystone_found(gattlib_adapter_t* 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,
gattlib_manufacturer_data_t* manufacturer_data, size_t manufacturer_data_count,
void *user_data)
{
puts("Found Eddystone device");
@ -81,7 +81,7 @@ void on_eddystone_found(void *adapter, const char* addr, const char* name,
}
static void* ble_task(void* arg) {
void* adapter = NULL;
gattlib_adapter_t* adapter = NULL;
int ret;
ret = gattlib_adapter_open(m_adapter_name, &adapter);

View File

@ -19,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 3.25.1)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -106,7 +106,7 @@ void indication_handler(const uuid_t* uuid, const uint8_t* data, size_t data_len
rl_forced_update_display();
}
static void connect_cb(gatt_connection_t* connection, void* user_data)
static void connect_cb(gattlib_connection_t* connection, void* user_data)
{
if (connection == NULL) {
got_error = TRUE;
@ -148,9 +148,8 @@ done:
static gboolean primary(gpointer user_data)
{
struct _gattlib_device *connection = (struct _gattlib_device *)user_data;
gattlib_context_t* conn_context = connection->context;
GAttrib *attrib = conn_context->attrib;
gattlib_device_t* connection = (gattlib_device_t* )user_data;
GAttrib *attrib = connection->backend.attrib;
char uuid_str[MAX_LEN_UUID_STR + 1];
if (opt_uuid)
@ -176,7 +175,7 @@ static gboolean primary(gpointer user_data)
static gboolean characteristics(gpointer user_data)
{
gatt_connection_t* connection = (gatt_connection_t*)user_data;
gattlib_connection_t* connection = (gattlib_connection_t*)user_data;
gattlib_characteristic_t* characteristics;
int characteristic_count, i;
@ -244,7 +243,7 @@ static void bt_uuid_to_uuid(bt_uuid_t* bt_uuid, uuid_t* uuid) {
static gboolean characteristics_read(gpointer user_data)
{
gatt_connection_t* connection = (gatt_connection_t*)user_data;
gattlib_connection_t* connection = (gattlib_connection_t*)user_data;
gattlib_context_t* conn_context = connection->context;
GAttrib *attrib = conn_context->attrib;
@ -296,7 +295,7 @@ static void mainloop_quit(gpointer user_data)
static gboolean characteristics_write(gpointer user_data)
{
gattlib_context_t* conn_context = ((gatt_connection_t*)user_data)->context;
gattlib_context_t* conn_context = ((gattlib_connection_t*)user_data)->context;
GAttrib *attrib = conn_context->attrib;
uint8_t *value;
size_t len;
@ -354,7 +353,7 @@ done:
static gboolean characteristics_write_req(gpointer user_data)
{
gattlib_context_t* conn_context = ((gatt_connection_t*)user_data)->context;
gattlib_context_t* conn_context = ((gattlib_connection_t*)user_data)->context;
GAttrib *attrib = conn_context->attrib;
uint8_t *value;
size_t len;
@ -388,7 +387,7 @@ error:
static gboolean characteristics_desc(gpointer user_data)
{
gatt_connection_t* connection = (gatt_connection_t*)user_data;
gattlib_connection_t* connection = (gattlib_connection_t*)user_data;
gattlib_descriptor_t* descriptors;
int descriptor_count, i;
@ -486,7 +485,7 @@ int main(int argc, char *argv[])
GOptionContext *context;
GOptionGroup *gatt_group, *params_group, *char_rw_group;
GError *gerr = NULL;
gatt_connection_t *connection;
gattlib_connection_t *connection;
unsigned long conn_options = 0;
BtIOSecLevel sec_level;
uint8_t dest_type;

View File

@ -45,7 +45,7 @@
#include "gattlib_internal_defs.h"
static gatt_connection_t* g_connection = NULL;
static gattlib_connection_t* g_connection = NULL;
static GMainLoop *event_loop;
static GString *prompt;
@ -112,7 +112,7 @@ static void set_state(enum state st)
rl_redisplay();
}
static void connect_cb(gatt_connection_t* connection, void* user_data)
static void connect_cb(gattlib_connection_t* connection, void* user_data)
{
if (connection == NULL) {
set_state(STATE_DISCONNECTED);
@ -278,7 +278,7 @@ static gboolean channel_watcher(GIOChannel *chan, GIOCondition cond,
static void cmd_connect(int argcp, char **argvp)
{
gatt_connection_t *connection;
gattlib_connection_t *connection;
unsigned long conn_options = 0;
BtIOSecLevel sec_level;
uint8_t dst_type;
@ -327,9 +327,8 @@ static void cmd_connect(int argcp, char **argvp)
if (connection == NULL) {
set_state(STATE_DISCONNECTED);
} else {
struct _gattlib_device *gatt_connection = (struct _gattlib_device *)g_connection;
gattlib_context_t* conn_context = gatt_connection->context;
g_io_add_watch(conn_context->io, G_IO_HUP, channel_watcher, NULL);
gattlib_device_t* gatt_connection = (gattlib_device_t* )g_connection;
g_io_add_watch(gatt_connection->backend.io, G_IO_HUP, channel_watcher, NULL);
}
}

View File

@ -19,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 3.25.1)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -37,7 +37,7 @@
#define NUS_CHARACTERISTIC_TX_UUID "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
#define NUS_CHARACTERISTIC_RX_UUID "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
gatt_connection_t* m_connection;
gattlib_connection_t* m_connection;
void notification_cb(const uuid_t* uuid, const uint8_t* data, size_t data_length, void* user_data) {
int i;

View File

@ -19,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 3.25.1)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

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
@ -22,8 +22,9 @@
*/
#include <assert.h>
#include <ctype.h>
#include <glib.h>
#include <signal.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
@ -33,13 +34,28 @@
#include "gattlib.h"
static uuid_t g_notify_uuid;
static uuid_t g_write_uuid;
#define BLE_SCAN_TIMEOUT 10
static GMainLoop *m_main_loop;
static struct {
char *adapter_name;
char* mac_address;
uuid_t gatt_notification_uuid;
uuid_t gatt_write_uuid;
long int gatt_write_data;
} m_argument;
void notification_handler(const uuid_t* uuid, const uint8_t* data, size_t data_length, void* user_data) {
int i;
// Declaration of thread condition variable
static pthread_cond_t m_connection_terminated = PTHREAD_COND_INITIALIZER;
// declaring mutex
static pthread_mutex_t m_connection_terminated_lock = PTHREAD_MUTEX_INITIALIZER;
static void usage(char *argv[]) {
printf("%s <device_address> <notification_characteristic_uuid> [<write_characteristic_uuid> <write_characteristic_data>]\n", argv[0]);
}
static void notification_handler(const uuid_t* uuid, const uint8_t* data, size_t data_length, void* user_data) {
uintptr_t i;
printf("Notification Handler: ");
@ -49,94 +65,127 @@ void notification_handler(const uuid_t* uuid, const uint8_t* data, size_t data_l
printf("\n");
}
static void on_user_abort(int arg) {
g_main_loop_quit(m_main_loop);
static void on_device_connect(gattlib_adapter_t* adapter, const char *dst, gattlib_connection_t* connection, int error, void* user_data) {
int ret;
if (m_argument.gatt_write_data != 0) {
ret = gattlib_write_char_by_uuid(connection, &m_argument.gatt_write_uuid, &m_argument.gatt_write_data, 1);
if (ret != GATTLIB_SUCCESS) {
}
}
ret = gattlib_register_notification(connection, notification_handler, NULL);
if (ret) {
GATTLIB_LOG(GATTLIB_ERROR, "Fail to register notification callback.");
goto EXIT;
}
ret = gattlib_notification_start(connection, &m_argument.gatt_notification_uuid);
if (ret) {
GATTLIB_LOG(GATTLIB_ERROR, "Fail to start notification.");
goto EXIT;
}
GATTLIB_LOG(GATTLIB_INFO, "Wait for notification for 20 seconds...");
g_usleep(20 * G_USEC_PER_SEC);
EXIT:
gattlib_disconnect(connection, false /* wait_disconnection */);
pthread_mutex_lock(&m_connection_terminated_lock);
pthread_cond_signal(&m_connection_terminated);
pthread_mutex_unlock(&m_connection_terminated_lock);
}
static void usage(char *argv[]) {
printf("%s <device_address> <notification_characteristic_uuid> [<write_characteristic_uuid> <write_characteristic_hex_data> ...]\n", argv[0]);
static int stricmp(char const *a, char const *b) {
for (;; a++, b++) {
int d = tolower((unsigned char)*a) - tolower((unsigned char)*b);
if (d != 0 || !*a)
return d;
}
}
static void ble_discovered_device(gattlib_adapter_t* adapter, const char* addr, const char* name, void *user_data) {
int ret;
int16_t rssi;
if (stricmp(addr, m_argument.mac_address) != 0) {
return;
}
ret = gattlib_get_rssi_from_mac(adapter, addr, &rssi);
if (ret == 0) {
GATTLIB_LOG(GATTLIB_INFO, "Found bluetooth device '%s' with RSSI:%d", m_argument.mac_address, rssi);
} else {
GATTLIB_LOG(GATTLIB_INFO, "Found bluetooth device '%s'", m_argument.mac_address);
}
ret = gattlib_connect(adapter, addr, GATTLIB_CONNECTION_OPTIONS_NONE, on_device_connect, NULL);
if (ret != GATTLIB_SUCCESS) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to connect to the bluetooth device '%s'", addr);
}
}
static void* ble_task(void* arg) {
char* addr = arg;
gattlib_adapter_t* adapter;
int ret;
ret = gattlib_adapter_open(m_argument.adapter_name, &adapter);
if (ret) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to open adapter.");
return NULL;
}
ret = gattlib_adapter_scan_enable(adapter, ble_discovered_device, BLE_SCAN_TIMEOUT, addr);
if (ret) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to scan.");
return NULL;
}
// Wait for the device to be connected
pthread_mutex_lock(&m_connection_terminated_lock);
pthread_cond_wait(&m_connection_terminated, &m_connection_terminated_lock);
pthread_mutex_unlock(&m_connection_terminated_lock);
return NULL;
}
int main(int argc, char *argv[]) {
int ret;
int argid;
gatt_connection_t* connection;
if (argc < 3) {
usage(argv);
return 1;
}
if (gattlib_string_to_uuid(argv[2], strlen(argv[2]) + 1, &g_notify_uuid) < 0) {
if (gattlib_string_to_uuid(argv[2], strlen(argv[2]) + 1, &m_argument.gatt_notification_uuid) < 0) {
usage(argv);
return 1;
}
if (argc > 3) {
if (gattlib_string_to_uuid(argv[3], strlen(argv[3]) + 1, &g_write_uuid) < 0) {
if (argc == 5) {
if (gattlib_string_to_uuid(argv[3], strlen(argv[3]) + 1, &m_argument.gatt_write_uuid) < 0) {
usage(argv);
return 1;
}
sscanf(argv[4], "%ld", &m_argument.gatt_write_data);
}
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
openlog("gattlib_notification", LOG_CONS | LOG_NDELAY | LOG_PERROR | LOG_PID, LOG_USER);
setlogmask(LOG_UPTO(LOG_DEBUG));
#endif
connection = gattlib_connect(NULL, argv[1], GATTLIB_CONNECTION_OPTIONS_LEGACY_DEFAULT);
if (connection == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Fail to connect to the bluetooth device.");
return 1;
}
gattlib_register_notification(connection, notification_handler, NULL);
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
openlog("gattlib_notification", LOG_CONS | LOG_NDELAY | LOG_PERROR, LOG_USER);
setlogmask(LOG_UPTO(LOG_DEBUG));
setlogmask(LOG_UPTO(LOG_INFO));
#endif
ret = gattlib_notification_start(connection, &g_notify_uuid);
if (ret) {
GATTLIB_LOG(GATTLIB_ERROR, "Fail to start notification.");
goto DISCONNECT;
m_argument.adapter_name = NULL;
m_argument.mac_address = argv[1];
ret = gattlib_mainloop(ble_task, NULL);
if (ret != GATTLIB_SUCCESS) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to create gattlib mainloop");
}
// Optional byte writes to make to trigger notifications
for (argid = 4; argid < argc; argid ++) {
unsigned char data[256];
char * charp;
unsigned char * datap;
for (charp = argv[4], datap = data; charp[0] && charp[1]; charp += 2, datap ++) {
sscanf(charp, "%02hhx", datap);
}
ret = gattlib_write_char_by_uuid(connection, &g_write_uuid, data, datap - data);
if (ret != GATTLIB_SUCCESS) {
if (ret == GATTLIB_NOT_FOUND) {
GATTLIB_LOG(GATTLIB_ERROR, "Could not find GATT Characteristic with UUID %s.", argv[3]);
} else {
GATTLIB_LOG(GATTLIB_ERROR, "Error while writing GATT Characteristic with UUID %s (ret:%d)",
argv[3], ret);
}
goto DISCONNECT;
}
}
// Catch CTRL-C
signal(SIGINT, on_user_abort);
m_main_loop = g_main_loop_new(NULL, 0);
g_main_loop_run(m_main_loop);
// In case we quit the main loop, clean the connection
gattlib_notification_stop(connection, &g_notify_uuid);
g_main_loop_unref(m_main_loop);
DISCONNECT:
gattlib_disconnect(connection, false /* wait_disconnection */);
puts("Done");
return ret;
return 0;
}

View File

@ -19,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 3.25.1)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -53,7 +53,7 @@ static void usage(char *argv[]) {
printf("%s <device_address> <read|write> <uuid> [<hex-value-to-write>]\n", argv[0]);
}
static void on_device_connect(void *adapter, const char *dst, gatt_connection_t* connection, int error, void* user_data) {
static void on_device_connect(gattlib_adapter_t* adapter, const char *dst, gattlib_connection_t* connection, int error, void* user_data) {
int ret;
size_t len;
@ -116,7 +116,7 @@ static int stricmp(char const *a, char const *b) {
}
}
static void ble_discovered_device(void *adapter, const char* addr, const char* name, void *user_data) {
static void ble_discovered_device(gattlib_adapter_t* adapter, const char* addr, const char* name, void *user_data) {
int ret;
if (stricmp(addr, m_argument.mac_address) != 0) {
@ -133,7 +133,7 @@ static void ble_discovered_device(void *adapter, const char* addr, const char* n
static void* ble_task(void* arg) {
char* addr = arg;
void* adapter;
gattlib_adapter_t* adapter;
int ret;
ret = gattlib_adapter_open(m_argument.adapter_name, &adapter);

View File

@ -19,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 3.25.1)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -48,7 +48,7 @@ struct connect_ble_params {
void *connect_ble(void *arg) {
struct connect_ble_params *params = arg;
gatt_connection_t* connection;
gattlib_connection_t* connection;
int ret, i;
size_t len;

646
gattlib-py/.pylintrc Normal file
View File

@ -0,0 +1,646 @@
[MAIN]
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
# Clear in-memory caches upon conclusion of linting. Useful if running pylint
# in a server-like mode.
clear-cache-post-run=no
# Load and enable all available extensions. Use --list-extensions to see a list
# all available extensions.
#enable-all-extensions=
# In error mode, messages with a category besides ERROR or FATAL are
# suppressed, and no reports are done by default. Error mode is compatible with
# disabling specific errors.
#errors-only=
# Always return a 0 (non-error) status code, even if lint errors are found.
# This is primarily useful in continuous integration scripts.
#exit-zero=
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-allow-list=
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
# for backward compatibility.)
extension-pkg-whitelist=
# Return non-zero exit code if any of these messages/categories are detected,
# even if score is above --fail-under value. Syntax same as enable. Messages
# specified are enabled, while categories only check already-enabled messages.
fail-on=
# Specify a score threshold under which the program will exit with error.
fail-under=10
# Interpret the stdin as a python script, whose filename needs to be passed as
# the module_or_package argument.
#from-stdin=
# Files or directories to be skipped. They should be base names, not paths.
ignore=CVS
# Add files or directories matching the regular expressions patterns to the
# ignore-list. The regex matches against paths and can be in Posix or Windows
# format. Because '\\' represents the directory delimiter on Windows systems,
# it can't be used as an escape character.
ignore-paths=
# Files or directories matching the regular expression patterns are skipped.
# The regex matches against base names, not paths. The default value ignores
# Emacs file locks
ignore-patterns=^\.#
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis). It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use, and will cap the count on Windows to
# avoid hangs.
jobs=1
# Control the amount of potential inferred values when inferring a single
# object. This can help the performance when dealing with large functions or
# complex, nested conditions.
limit-inference-results=100
# List of plugins (as comma separated values of python module names) to load,
# usually to register additional checkers.
load-plugins=
# Pickle collected data for later comparisons.
persistent=yes
# Minimum Python version to use for version dependent checks. Will default to
# the version used to run pylint.
py-version=3.10
# Discover python modules and packages in the file system subtree.
recursive=no
# Add paths to the list of the source roots. Supports globbing patterns. The
# source root is an absolute path or a path relative to the current working
# directory used to determine a package namespace for modules located under the
# source root.
source-roots=
# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
# In verbose mode, extra non-checker-related info will be displayed.
#verbose=
[BASIC]
# Naming style matching correct argument names.
argument-naming-style=snake_case
# Regular expression matching correct argument names. Overrides argument-
# naming-style. If left empty, argument names will be checked with the set
# naming style.
#argument-rgx=
# Naming style matching correct attribute names.
attr-naming-style=snake_case
# Regular expression matching correct attribute names. Overrides attr-naming-
# style. If left empty, attribute names will be checked with the set naming
# style.
#attr-rgx=
# Bad variable names which should always be refused, separated by a comma.
bad-names=foo,
bar,
baz,
toto,
tutu,
tata
# Bad variable names regexes, separated by a comma. If names match any regex,
# they will always be refused
bad-names-rgxs=
# Naming style matching correct class attribute names.
class-attribute-naming-style=any
# Regular expression matching correct class attribute names. Overrides class-
# attribute-naming-style. If left empty, class attribute names will be checked
# with the set naming style.
#class-attribute-rgx=
# Naming style matching correct class constant names.
class-const-naming-style=UPPER_CASE
# Regular expression matching correct class constant names. Overrides class-
# const-naming-style. If left empty, class constant names will be checked with
# the set naming style.
#class-const-rgx=
# Naming style matching correct class names.
class-naming-style=PascalCase
# Regular expression matching correct class names. Overrides class-naming-
# style. If left empty, class names will be checked with the set naming style.
#class-rgx=
# Naming style matching correct constant names.
const-naming-style=UPPER_CASE
# Regular expression matching correct constant names. Overrides const-naming-
# style. If left empty, constant names will be checked with the set naming
# style.
#const-rgx=
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
# Naming style matching correct function names.
function-naming-style=snake_case
# Regular expression matching correct function names. Overrides function-
# naming-style. If left empty, function names will be checked with the set
# naming style.
#function-rgx=
# Good variable names which should always be accepted, separated by a comma.
good-names=i,
j,
k,
ex,
Run,
_
# Good variable names regexes, separated by a comma. If names match any regex,
# they will always be accepted
good-names-rgxs=
# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no
# Naming style matching correct inline iteration names.
inlinevar-naming-style=any
# Regular expression matching correct inline iteration names. Overrides
# inlinevar-naming-style. If left empty, inline iteration names will be checked
# with the set naming style.
#inlinevar-rgx=
# Naming style matching correct method names.
method-naming-style=snake_case
# Regular expression matching correct method names. Overrides method-naming-
# style. If left empty, method names will be checked with the set naming style.
#method-rgx=
# Naming style matching correct module names.
module-naming-style=snake_case
# Regular expression matching correct module names. Overrides module-naming-
# style. If left empty, module names will be checked with the set naming style.
#module-rgx=
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=^_
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty
# Regular expression matching correct type alias names. If left empty, type
# alias names will be checked with the set naming style.
#typealias-rgx=
# Regular expression matching correct type variable names. If left empty, type
# variable names will be checked with the set naming style.
#typevar-rgx=
# Naming style matching correct variable names.
variable-naming-style=snake_case
# Regular expression matching correct variable names. Overrides variable-
# naming-style. If left empty, variable names will be checked with the set
# naming style.
#variable-rgx=
[CLASSES]
# Warn about protected attribute access inside special methods
check-protected-access-in-special-methods=no
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
__new__,
setUp,
asyncSetUp,
__post_init__
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
[DESIGN]
# List of regular expressions of class ancestor names to ignore when counting
# public methods (see R0903)
exclude-too-few-public-methods=
# List of qualified class names to ignore when counting class parents (see
# R0901)
ignored-parents=
# Maximum number of arguments for function / method.
max-args=5
# Maximum number of attributes for a class (see R0902).
max-attributes=20
# Maximum number of boolean expressions in an if statement (see R0916).
max-bool-expr=5
# Maximum number of branch for function / method body.
max-branches=20
# Maximum number of locals for function / method body.
max-locals=20
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# Maximum number of return / yield for function / method body.
max-returns=6
# Maximum number of statements in function / method body.
max-statements=50
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
[EXCEPTIONS]
# Exceptions that will emit a warning when caught.
overgeneral-exceptions=builtins.BaseException,builtins.Exception
[FORMAT]
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Maximum number of characters on a single line.
max-line-length=140
# Maximum number of lines in a module.
max-module-lines=1000
# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
[IMPORTS]
# List of modules that can be imported at any level, not just the top level
# one.
allow-any-import-level=
# Allow explicit reexports by alias from a package __init__.
allow-reexport-from-package=no
# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no
# Deprecated modules which should not be used, separated by a comma.
deprecated-modules=
# Output a graph (.gv or any supported image format) of external dependencies
# to the given file (report RP0402 must not be disabled).
ext-import-graph=
# Output a graph (.gv or any supported image format) of all (i.e. internal and
# external) dependencies to the given file (report RP0402 must not be
# disabled).
import-graph=
# Output a graph (.gv or any supported image format) of internal dependencies
# to the given file (report RP0402 must not be disabled).
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
# Couples of modules and preferred modules, separated by a comma.
preferred-modules=
[LOGGING]
# The type of string formatting that logging methods do. `old` means using %
# formatting, `new` is for `{}` formatting.
logging-format-style=old
# Logging modules to check that the string format arguments are in logging
# function parameter format.
logging-modules=logging
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE,
# UNDEFINED.
confidence=HIGH,
CONTROL_FLOW,
INFERENCE,
INFERENCE_FAILURE,
UNDEFINED
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then re-enable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=raw-checker-failed,
bad-inline-option,
locally-disabled,
file-ignored,
suppressed-message,
useless-suppression,
deprecated-pragma,
use-implicit-booleaness-not-comparison-to-string,
use-implicit-booleaness-not-comparison-to-zero,
use-symbolic-message-instead,
superfluous-parens,
no-else-return,
unused-argument,
fixme,
too-few-public-methods,
too-many-arguments,
global-statement
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
enable=
[METHOD_ARGS]
# List of qualified names (i.e., library.method) which require a timeout
# parameter e.g. 'requests.api.get,requests.api.post'
timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,
XXX,
TODO
# Regular expression of note tags to take in consideration.
notes-rgx=
[REFACTORING]
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit,argparse.parse_error
# Let 'consider-using-join' be raised when the separator to join on would be
# non-empty (resulting in expected fixes of the type: ``"- " + " -
# ".join(items)``)
suggest-join-with-non-empty-separator=yes
[REPORTS]
# Python expression which should return a score less than or equal to 10. You
# have access to the variables 'fatal', 'error', 'warning', 'refactor',
# 'convention', and 'info' which contain the number of messages in each
# category, as well as 'statement' which is the total number of statements
# analyzed. This score is used by the global evaluation report (RP0004).
evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
msg-template=
# Set the output format. Available formats are: text, parseable, colorized,
# json2 (improved json format), json (old json format) and msvs (visual
# studio). You can also give a reporter class, e.g.
# mypackage.mymodule.MyReporterClass.
#output-format=
# Tells whether to display a full report or only the messages.
reports=no
# Activate the evaluation score.
score=yes
[SIMILARITIES]
# Comments are removed from the similarity computation
ignore-comments=yes
# Docstrings are removed from the similarity computation
ignore-docstrings=yes
# Imports are removed from the similarity computation
ignore-imports=yes
# Signatures are removed from the similarity computation
ignore-signatures=yes
# Minimum lines number of a similarity.
min-similarity-lines=4
[SPELLING]
# Limits count of emitted suggestions for spelling mistakes.
max-spelling-suggestions=4
# Spelling dictionary name. No available dictionaries : You need to install
# both the python package and the system dependency for enchant to work.
spelling-dict=
# List of comma separated words that should be considered directives if they
# appear at the beginning of a comment and should not be checked.
spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains the private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to the private dictionary (see the
# --spelling-private-dict-file option) instead of raising a message.
spelling-store-unknown-words=no
[STRING]
# This flag controls whether inconsistent-quotes generates a warning when the
# character used as a quote delimiter is used inconsistently within a module.
check-quote-consistency=no
# This flag controls whether the implicit-str-concat should generate a warning
# on implicit string concatenation in sequences defined over several lines.
check-str-concat-over-line-jumps=no
[TYPECHECK]
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
# Tells whether to warn about missing members when the owner of the attribute
# is inferred to be None.
ignore-none=yes
# This flag controls whether pylint should warn about no-member and similar
# checks whenever an opaque object is returned when inferring. The inference
# can return multiple potential results while evaluating a Python object, but
# some branches might not be evaluated, which results in partial inference. In
# that case, it might be useful to still emit no-member and other checks for
# the rest of the inferred objects.
ignore-on-opaque-inference=yes
# List of symbolic message names to ignore for Mixin members.
ignored-checks-for-mixins=no-member,
not-async-context-manager,
not-context-manager,
attribute-defined-outside-init
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace
# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
missing-member-hint=yes
# The minimum edit distance a name should have in order to be considered a
# similar match for a missing member name.
missing-member-hint-distance=1
# The total number of similar names that should be taken in consideration when
# showing a hint for a missing member.
missing-member-max-choices=1
# Regex pattern to define which classes are considered mixins.
mixin-class-rgx=.*[Mm]ixin
# List of decorators that change the signature of a decorated function.
signature-mutators=
[VARIABLES]
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid defining new builtins when possible.
additional-builtins=
# Tells whether unused global variables should be treated as a violation.
allow-global-unused-variables=yes
# List of names allowed to shadow builtins
allowed-redefined-builtins=
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,
_cb
# A regular expression matching the name of dummy variables (i.e. expected to
# not be used).
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
# Argument names that match this expression will be ignored.
ignored-argument-names=_.*|^ignored_|^unused_
# Tells whether we should check for unused import in __init__ files.
init-import=no
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io

View File

@ -15,7 +15,7 @@ args = parser.parse_args()
def on_discovered_ble_device(device, user_data):
advertisement_data, manufacturer_id, manufacturer_data = device.get_advertisement_data()
advertisement_data, manufacturer_data = device.get_advertisement_data()
print("Device Advertisement Data: %s" % manufacturer_data)

View File

@ -4,14 +4,16 @@
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
"""Gattlib C types and functions"""
from ctypes import *
import logging
import pathlib
try:
# '_version.py' is generated by 'setup.py'
from ._version import __version__
except:
from ._version import __version__ #pylint: disable=import-error
except: #pylint: disable=bare-except
pass
logger = logging.getLogger(__name__)
@ -24,6 +26,7 @@ except OSError:
gattlib = CDLL('libgattlib.so')
def native_logging(level: int, string: str):
"""Convert Gattlib logging to Python logging."""
if level == 3:
logger.debug(string)
elif level == 2:
@ -50,6 +53,7 @@ except AttributeError:
# uint8_t data[16];
# } uint128_t;
class GattlibUuid128(Structure):
"""Python class representing the C structure 'uint128_t'."""
_fields_ = [("data", c_byte * 16)]
@ -62,10 +66,12 @@ class GattlibUuid128(Structure):
# } value;
# } uuid_t;
class GattlibUuidValue(Union):
"""Python class representing the C structure of the value of 'uuid_t'."""
_fields_ = [("uuid16", c_ushort), ("uuid32", c_uint), ("uuid128", GattlibUuid128)]
class GattlibUuid(Structure):
"""Python class representing the C structure 'uuid_t'."""
_fields_ = [("type", c_byte), ("value", GattlibUuidValue)]
@ -75,6 +81,7 @@ class GattlibUuid(Structure):
# uuid_t uuid;
# } gattlib_primary_service_t;
class GattlibPrimaryService(Structure):
"""Python class representing the C structure 'gattlib_primary_service_t'."""
_fields_ = [("attr_handle_start", c_ushort),
("attr_handle_end", c_ushort),
("uuid", GattlibUuid)]
@ -87,6 +94,7 @@ class GattlibPrimaryService(Structure):
# uuid_t uuid;
# } gattlib_characteristic_t;
class GattlibCharacteristic(Structure):
"""Python class representing the C structure 'gattlib_characteristic_t'."""
_fields_ = [("handle", c_ushort),
("properties", c_byte),
("value_handle", c_ushort),
@ -99,26 +107,39 @@ class GattlibCharacteristic(Structure):
# size_t data_length;
# } gattlib_advertisement_data_t;
class GattlibAdvertisementData(Structure):
"""Python class representing the C structure 'gattlib_advertisement_data_t'."""
_fields_ = [("uuid", GattlibUuid),
("data", c_void_p),
("data_length", c_size_t)]
# typedef struct {
# uint16_t manufacturer_id;
# uint8_t* data;
# size_t data_size;
# } gattlib_manufacturer_data_t;
class GattlibManufacturerData(Structure):
"""Python class representing the C structure 'gattlib_manufacturer_data_t'."""
_fields_ = [("manufacturer_id", c_ushort),
("data", c_void_p),
("data_size", c_size_t)]
# int gattlib_adapter_open(const char* adapter_name, void** adapter);
# int gattlib_adapter_open(const char* adapter_name, gattlib_adapter_t** adapter);
gattlib_adapter_open = gattlib.gattlib_adapter_open
gattlib_adapter_open.argtypes = [c_char_p, POINTER(c_void_p)]
# const char *gattlib_adapter_get_name(void* adapter)
# const char *gattlib_adapter_get_name(gattlib_adapter_t* adapter)
gattlib_adapter_get_name = gattlib.gattlib_adapter_get_name
gattlib_adapter_get_name.argtypes = [c_void_p]
gattlib_adapter_get_name.restype = c_char_p
# void gattlib_discovered_device_python_callback(void *adapter, const char* addr, const char* name, void *user_data)
# void gattlib_discovered_device_python_callback(gattlib_adapter_t* 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);
# void gattlib_connected_device_python_callback(gattlib_adapter_t* adapter, const char *dst, gattlib_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
@ -138,33 +159,35 @@ 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,
# 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)
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, c_void_p, c_size_t, c_void_p]
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,
# int gattlib_adapter_scan_eddystone(gattlib_adapter_t* 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, py_object, c_size_t, py_object]
# int gattlib_connect(void *adapter, const char *dst, unsigned long options, gatt_connect_cb_t connect_cb, void* user_data)
# int gattlib_connect(gattlib_adapter_t* adapter, const char *dst, unsigned long options, gatt_connect_cb_t connect_cb, void* user_data)
gattlib_connect = gattlib.gattlib_connect
gattlib_connect.argtypes = [c_void_p, c_char_p, c_ulong, c_void_p, c_void_p]
# int gattlib_disconnect(gatt_connection_t* connection, bool wait_disconnection);
# int gattlib_disconnect(gattlib_connection_t* connection, bool wait_disconnection);
gattlib_disconnect = gattlib.gattlib_disconnect
gattlib_disconnect.argtypes = [c_void_p, c_bool]
# int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_service_t** services, int* services_count);
# int gattlib_discover_primary(gattlib_connection_t* connection, gattlib_primary_service_t** services, int* services_count);
gattlib_discover_primary = gattlib.gattlib_discover_primary
gattlib_discover_primary.argtypes = [c_void_p, POINTER(POINTER(GattlibPrimaryService)), POINTER(c_int)]
# int gattlib_discover_char(gatt_connection_t* connection, gattlib_characteristic_t** characteristics, int* characteristic_count);
# int gattlib_discover_char(gattlib_connection_t* connection, gattlib_characteristic_t** characteristics, int* characteristic_count);
gattlib_discover_char = gattlib.gattlib_discover_char
gattlib_discover_char.argtypes = [c_void_p, POINTER(POINTER(GattlibCharacteristic)), POINTER(c_int)]
# int gattlib_read_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, void** buffer, size_t* buffer_len);
# int gattlib_read_char_by_uuid(gattlib_connection_t* connection, uuid_t* uuid, void** buffer, size_t* buffer_len);
gattlib_read_char_by_uuid = gattlib.gattlib_read_char_by_uuid
gattlib_read_char_by_uuid.argtypes = [c_void_p, POINTER(GattlibUuid), POINTER(c_void_p), POINTER(c_size_t)]
@ -172,53 +195,57 @@ gattlib_read_char_by_uuid.argtypes = [c_void_p, POINTER(GattlibUuid), POINTER(c_
gattlib_characteristic_free_value = gattlib.gattlib_characteristic_free_value
gattlib_characteristic_free_value.argtypes = [c_void_p]
# int gattlib_write_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len)
# int gattlib_write_char_by_uuid(gattlib_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len)
gattlib_write_char_by_uuid = gattlib.gattlib_write_char_by_uuid
gattlib_write_char_by_uuid.argtypes = [c_void_p, POINTER(GattlibUuid), c_void_p, c_size_t]
# int gattlib_write_without_response_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len)
# int gattlib_write_without_response_char_by_uuid(gattlib_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len)
gattlib_write_without_response_char_by_uuid = gattlib.gattlib_write_without_response_char_by_uuid
gattlib_write_without_response_char_by_uuid.argtypes = [c_void_p, POINTER(GattlibUuid), c_void_p, c_size_t]
# int gattlib_write_char_by_uuid_stream_open(gatt_connection_t* connection, uuid_t* uuid, gatt_stream_t **stream, uint16_t *mtu)
# int gattlib_write_char_by_uuid_stream_open(gattlib_connection_t* connection, uuid_t* uuid, gattlib_stream_t **stream, uint16_t *mtu)
gattlib_write_char_by_uuid_stream_open = gattlib.gattlib_write_char_by_uuid_stream_open
gattlib_write_char_by_uuid_stream_open.argtypes = [c_void_p, POINTER(GattlibUuid), POINTER(c_void_p), POINTER(c_uint16)]
# int gattlib_notification_start(gatt_connection_t* connection, const uuid_t* uuid);
# int gattlib_notification_start(gattlib_connection_t* connection, const uuid_t* uuid);
gattlib_notification_start = gattlib.gattlib_notification_start
gattlib_notification_start.argtypes = [c_void_p, POINTER(GattlibUuid)]
# int gattlib_notification_stop(gatt_connection_t* connection, const uuid_t* uuid);
# int gattlib_notification_stop(gattlib_connection_t* connection, const uuid_t* uuid);
gattlib_notification_stop = gattlib.gattlib_notification_stop
gattlib_notification_stop.argtypes = [c_void_p, POINTER(GattlibUuid)]
# int gattlib_register_notification(gatt_connection_t* connection, gattlib_event_handler_t notification_handler, void* user_data);
# int gattlib_register_notification(gattlib_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]
# int gattlib_register_on_disconnect(gatt_connection_t *connection, PyObject *handler, PyObject *user_data)
# int gattlib_register_on_disconnect(gattlib_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)
# int gattlib_get_rssi(gattlib_connection_t *connection, int16_t *rssi)
gattlib_get_rssi = gattlib.gattlib_get_rssi
gattlib_get_rssi.argtypes = [c_void_p, POINTER(c_int16)]
# int gattlib_get_rssi_from_mac(void *adapter, const char *mac_address, int16_t *rssi)
# int gattlib_get_rssi_from_mac(gattlib_adapter_t* adapter, const char *mac_address, int16_t *rssi)
gattlib_get_rssi_from_mac = gattlib.gattlib_get_rssi_from_mac
gattlib_get_rssi_from_mac.argtypes = [c_void_p, c_char_p, POINTER(c_int16)]
# int gattlib_get_advertisement_data(gatt_connection_t *connection,
# int gattlib_get_advertisement_data(gattlib_connection_t *connection,
# gattlib_advertisement_data_t **advertisement_data, size_t *advertisement_data_count,
# uint16_t *manufacturer_id, uint8_t **manufacturer_data, size_t *manufacturer_data_size)
# gattlib_manufacturer_data_t** manufacturer_data, size_t* manufacturer_data_count)
gattlib_get_advertisement_data = gattlib.gattlib_get_advertisement_data
gattlib_get_advertisement_data.argtypes = [c_void_p, POINTER(POINTER(GattlibAdvertisementData)), POINTER(c_size_t), POINTER(c_uint16), POINTER(c_void_p), POINTER(c_size_t)]
gattlib_get_advertisement_data.argtypes = [c_void_p,
POINTER(POINTER(GattlibAdvertisementData)), POINTER(c_size_t),
POINTER(POINTER(GattlibManufacturerData)), POINTER(c_size_t)]
# int gattlib_get_advertisement_data_from_mac(void *adapter, const char *mac_address,
# int gattlib_get_advertisement_data_from_mac(gattlib_adapter_t* adapter, const char *mac_address,
# gattlib_advertisement_data_t **advertisement_data, size_t *advertisement_data_length,
# uint16_t *manufacturer_id, uint8_t **manufacturer_data, size_t *manufacturer_data_size)
# gattlib_manufacturer_data_t** manufacturer_data, size_t* manufacturer_data_count)
gattlib_get_advertisement_data_from_mac = gattlib.gattlib_get_advertisement_data_from_mac
gattlib_get_advertisement_data_from_mac.argtypes = [c_void_p, c_char_p, POINTER(POINTER(GattlibAdvertisementData)), POINTER(c_size_t), POINTER(c_uint16), POINTER(c_void_p), POINTER(c_size_t)]
gattlib_get_advertisement_data_from_mac.argtypes = [c_void_p, c_char_p,
POINTER(POINTER(GattlibAdvertisementData)), POINTER(c_size_t),
POINTER(POINTER(GattlibManufacturerData)), POINTER(c_size_t)]
# int gattlib_mainloop_python(PyObject *handler, PyObject *user_data)
gattlib_mainloop = gattlib.gattlib_mainloop_python

View File

@ -4,13 +4,15 @@
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
"""Gattlib Adapter API"""
import threading
from uuid import UUID
from gattlib import *
from gattlib import * #pylint: disable=wildcard-import,unused-wildcard-import
from .device import Device
from .exception import handle_return, AdapterNotOpened
from .uuid import gattlib_uuid_to_int
from .exception import handle_return
from .helpers import convert_gattlib_advertisement_c_data_to_dict
GATTLIB_DISCOVER_FILTER_USE_UUID = (1 << 0)
GATTLIB_DISCOVER_FILTER_USE_RSSI = (1 << 1)
@ -38,12 +40,14 @@ EDDYSTONE_URL_SCHEME_PREFIX = {
class Adapter:
"""Bluetooth adapter."""
def __init__(self, name=c_char_p(None)):
self._name = name
self._adapter = c_void_p(None)
self._is_opened = False # Note: 'self._adapter != c_void_p(None)' does not seem to return the expected result
self._lock = threading.Lock()
self._on_discovered_device_callback = None
self._on_discovered_device_user_callback = None
def __str__(self) -> str:
if self._name:
@ -53,20 +57,20 @@ class Adapter:
@property
def name(self):
"""Return adapter name."""
return self._name
@staticmethod
def list():
# TODO: Add support
return []
#@staticmethod
#def list():
# # TODO: Add support
# return []
def open(self):
self._lock.acquire()
if self._is_opened:
self._lock.release()
return
"""Open adapter."""
with self._lock:
if self._is_opened:
return
try:
self._adapter = c_void_p(None)
ret = gattlib_adapter_open(self._name, byref(self._adapter))
if ret == 0:
@ -75,27 +79,24 @@ class Adapter:
self._name = gattlib_adapter_get_name(self._adapter)
else:
handle_return(ret)
finally:
self._lock.release()
def close(self):
self._lock.acquire()
try:
"""Close adapter."""
with self._lock:
if self._adapter:
ret = gattlib.gattlib_adapter_close(self._adapter)
handle_return(ret)
self._is_opened = False
self._adapter = None
finally:
self._lock.release()
# 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):
"""Return a callback for newly discovered device."""
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:
self._on_discovered_device_user_callback(device, user_data)
except Exception as e: #pylint: disable=broad-exception-caught
logger.exception(e)
return on_discovered_device
@ -115,11 +116,11 @@ class Adapter:
@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_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()
assert on_discovered_device_callback is not None
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()
# Ensure BLE adapter it opened
if not self._is_opened:
@ -164,41 +165,23 @@ class Adapter:
uuid_list, rssi, enabled_filters,
gattlib_discovered_device_python_callback,
timeout,
gattlib_python_callback_args(self.on_discovered_device_callback, user_data))
gattlib_python_callback_args(self._on_discovered_device_callback, user_data))
handle_return(ret)
@staticmethod
def on_discovered_ble_device_with_details(adapter, mac_addr, name, advertisement_data_buffer, advertisement_data_count,
manufacturer_id, manufacturer_data_buffer, manufacturer_data_size,
manufacturer_data_buffer, manufacturer_data_count,
user_data):
advertisement_data = {}
manufacturer_data = None
for i in range(0, advertisement_data_count):
service_data = advertisement_data_buffer[i]
uuid = gattlib_uuid_to_int(service_data.uuid)
pointer_type = POINTER(c_byte * service_data.data_length)
c_bytearray = cast(service_data.data, pointer_type)
data = bytearray(service_data.data_length)
for i in range(service_data.data_length):
data[i] = c_bytearray.contents[i] & 0xFF
advertisement_data[uuid] = data
if manufacturer_data_size > 0:
pointer_type = POINTER(c_byte * manufacturer_data_size)
c_bytearray = cast(manufacturer_data_buffer, pointer_type)
manufacturer_data = bytearray(manufacturer_data_size)
for i in range(manufacturer_data_size):
manufacturer_data[i] = c_bytearray.contents[i] & 0xFF
"""Callback invoked when a new device has been discovered."""
advertisement_data, manufacturer_data = convert_gattlib_advertisement_c_data_to_dict(
advertisement_data_buffer, advertisement_data_count,
manufacturer_data_buffer, manufacturer_data_count)
device = Device(user_data["adapter"], mac_addr, name)
user_data["callback"](device, advertisement_data, manufacturer_id, manufacturer_data, user_data["user_data"])
user_data["callback"](device, advertisement_data, manufacturer_data, user_data["user_data"])
def scan_eddystone_enable(self, on_discovered_device_callback, eddystone_filters, timeout, rssi_threshold=None, user_data=None):
"""Enable BLE scan for Eddystone devices."""
# Ensure BLE adapter it opened
if not self._is_opened:
self.open()
@ -221,10 +204,16 @@ class Adapter:
handle_return(ret)
def scan_disable(self):
"""Disable BLE scan."""
ret = gattlib.gattlib_adapter_scan_disable(self._adapter)
handle_return(ret)
def get_rssi_from_mac(self, mac_address):
"""
Return RSSI of a device defined by its MAC address.
Note: The RSSI is 0 when the device is connected.
"""
if isinstance(mac_address, str):
mac_address = mac_address.encode("utf-8")
@ -233,46 +222,19 @@ class Adapter:
return rssi.value
def gattlib_get_advertisement_data_from_mac(self, mac_address):
"""Return advertisement and manufacturer data of the device."""
if isinstance(mac_address, str):
mac_address = mac_address.encode("utf-8")
_advertisement_data = POINTER(GattlibAdvertisementData)()
_advertisement_data_count = c_size_t(0)
_manufacturer_id = c_uint16(0)
_manufacturer_data = c_void_p(None)
_manufacturer_data_len = c_size_t(0)
_manufacturer_data = POINTER(GattlibManufacturerData)()
_manufacturer_data_count = c_size_t(0)
ret = gattlib_get_advertisement_data_from_mac(self._adapter, mac_address,
byref(_advertisement_data), byref(_advertisement_data_count),
byref(_manufacturer_id),
byref(_manufacturer_data), byref(_manufacturer_data_len))
byref(_manufacturer_data), byref(_manufacturer_data_count))
handle_return(ret)
advertisement_data = {}
manufacturer_data = None
for i in range(0, _advertisement_data_count.value):
service_data = _advertisement_data[i]
uuid = gattlib_uuid_to_int(service_data.uuid)
pointer_type = POINTER(c_byte * service_data.data_length)
c_bytearray = cast(service_data.data, pointer_type)
data = bytearray(service_data.data_length)
for i in range(service_data.data_length):
data[i] = c_bytearray.contents[i] & 0xFF
advertisement_data[uuid] = data
if _manufacturer_data_len.value > 0:
pointer_type = POINTER(c_byte * _manufacturer_data_len.value)
c_bytearray = cast(_manufacturer_data, pointer_type)
manufacturer_data = bytearray(_manufacturer_data_len.value)
for i in range(_manufacturer_data_len.value):
manufacturer_data[i] = c_bytearray.contents[i] & 0xFF
gattlib_free_mem(_advertisement_data)
gattlib_free_mem(_manufacturer_data)
return advertisement_data, _manufacturer_id.value, manufacturer_data
return convert_gattlib_advertisement_c_data_to_dict(
_advertisement_data, _advertisement_data_count, _manufacturer_data, _manufacturer_data_count)

View File

@ -4,16 +4,17 @@
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
"""Gattlib Device API"""
from __future__ import annotations
import logging
import uuid
import threading
from typing import TYPE_CHECKING
from gattlib import *
from .exception import handle_return, DeviceError, InvalidParameter
from gattlib import * #pylint: disable=wildcard-import,unused-wildcard-import
from .exception import handle_return, InvalidParameter
from .gatt import GattService, GattCharacteristic
from .uuid import gattlib_uuid_to_int
from .helpers import convert_gattlib_advertisement_c_data_to_dict
if TYPE_CHECKING:
from .adapter import Adapter
@ -31,10 +32,10 @@ CONNECTION_OPTIONS_LEGACY_DEFAULT = \
class Device:
"""GATT device"""
def __init__(self, adapter: Adapter, addr: str, name: str = None):
self._adapter = adapter
if type(addr) == str:
if isinstance(addr, str):
self._addr = addr.encode("utf-8")
else:
self._addr = addr
@ -45,8 +46,12 @@ class Device:
# We use a lock on disconnection to ensure the memory is safely freed
self._disconnection_lock = threading.Lock()
self._services: dict[int, GattService] = {}
self._characteristics: dict[int, GattCharacteristic] = {}
self.on_connection_callback = None
self.on_connection_error_callback = None
self.disconnection_callback = None
# Keep track if notification handler has been initialized
self._is_notification_init = False
@ -65,6 +70,7 @@ class Device:
@property
def connection(self):
"""Return Gattlib connection C handle."""
if self._connection:
return self._connection
else:
@ -72,9 +78,11 @@ class Device:
@property
def is_connected(self) -> bool:
"""Return True if the device is connected."""
return (self._connection is not None)
def connect(self, options=CONNECTION_OPTIONS_LEGACY_DEFAULT):
"""Connect the device."""
def _on_connection(adapter: c_void_p, mac_address: c_char_p, connection: c_void_p, error: c_int, user_data: py_object):
if error:
self._connection = None
@ -86,7 +94,7 @@ class Device:
if self._adapter is None:
adapter = None
else:
adapter = self._adapter._adapter
adapter = self._adapter._adapter #pylint: disable=protected-access
ret = gattlib_connect(adapter, self._addr, options,
gattlib_connected_device_python_callback,
@ -94,16 +102,19 @@ class Device:
handle_return(ret)
def on_connection(self, user_data: py_object):
if self.on_connection_callback:
self.on_connection_callback(self, user_data)
"""Method called on device connection."""
if callable(self.on_connection_callback):
self.on_connection_callback(self, user_data) #pylint: disable=not-callable
def on_connection_error(self, error: c_int, user_data: py_object):
"""Method called on device connection error."""
logger.error("Failed to connect due to error '0x%x'", error)
if self.on_connection_error_callback:
self.on_connection_error_callback(self, error, user_data)
if callable(self.on_connection_error_callback):
self.on_connection_error_callback(self, error, user_data) #pylint: disable=not-callable
@property
def rssi(self):
"""Return connection RSSI."""
_rssi = c_int16(0)
if self._connection:
ret = gattlib_get_rssi(self._connection, byref(_rssi))
@ -113,56 +124,50 @@ class Device:
return self._adapter.get_rssi_from_mac(self._addr)
def register_on_disconnect(self, callback, user_data=None):
"""Register disconnection callback."""
self.disconnection_callback = callback
def on_disconnection(user_data):
self._disconnection_lock.acquire()
with self._disconnection_lock:
if self.disconnection_callback:
self.disconnection_callback()
if self.disconnection_callback:
self.disconnection_callback()
# On disconnection, we do not need the list of GATT services and GATT characteristics
if self._services_ptr:
gattlib_free_mem(self._services_ptr)
self._services_ptr = None
if self._characteristics_ptr:
gattlib_free_mem(self._characteristics_ptr)
self._characteristics_ptr = None
# On disconnection, we do not need the list of GATT services and GATT characteristics
if self._services_ptr:
gattlib_free_mem(self._services_ptr)
self._services_ptr = None
if self._characteristics_ptr:
gattlib_free_mem(self._characteristics_ptr)
self._characteristics_ptr = None
# Reset the connection handler
self._connection = None
self._disconnection_lock.release()
# Reset the connection handler
self._connection = None
gattlib_register_on_disconnect(self.connection,
gattlib_disconnected_device_python_callback,
gattlib_python_callback_args(on_disconnection, user_data))
def disconnect(self, wait_disconnection: bool = False):
self._connection_lock.acquire()
try:
"""Disconnect connected device."""
with self._connection_lock:
if self._connection:
ret = gattlib_disconnect(self.connection, wait_disconnection)
handle_return(ret)
self._connection = None
finally:
self._connection_lock.release()
def discover(self):
#
# Discover GATT Services
#
"""Discover GATT Services."""
self._services_ptr = POINTER(GattlibPrimaryService)()
_services_count = c_int(0)
ret = gattlib_discover_primary(self.connection, byref(self._services_ptr), byref(_services_count))
services_count = c_int(0)
ret = gattlib_discover_primary(self.connection, byref(self._services_ptr), byref(services_count))
handle_return(ret)
self._services = {}
for i in range(0, _services_count.value):
for i in range(0, services_count.value):
service = GattService(self, self._services_ptr[i])
self._services[service.short_uuid] = service
logger.debug("Service UUID:0x%x" % service.short_uuid)
logger.debug("Service UUID:0x%x", service.short_uuid)
#
# Discover GATT Characteristics
@ -177,59 +182,33 @@ class Device:
characteristic = GattCharacteristic(self, self._characteristics_ptr[i])
self._characteristics[characteristic.short_uuid] = characteristic
logger.debug("Characteristic UUID:0x%x" % characteristic.short_uuid)
logger.debug("Characteristic UUID:0x%x", characteristic.short_uuid)
def get_advertisement_data(self):
_advertisement_data = POINTER(GattlibAdvertisementData)()
_advertisement_data_count = c_size_t(0)
_manufacturer_id = c_uint16(0)
_manufacturer_data = c_void_p(None)
_manufacturer_data_len = c_size_t(0)
"""Return advertisement and manufacturer data of the device."""
advertisement_data = POINTER(GattlibAdvertisementData)()
advertisement_data_count = c_size_t(0)
manufacturer_data = POINTER(GattlibManufacturerData)()
manufacturer_data_count = c_size_t(0)
if self._connection is None:
ret = gattlib_get_advertisement_data_from_mac(self._adapter._adapter, self._addr,
byref(_advertisement_data), byref(_advertisement_data_count),
byref(_manufacturer_id),
byref(_manufacturer_data), byref(_manufacturer_data_len))
ret = gattlib_get_advertisement_data_from_mac(self._adapter._adapter, self._addr, #pylint: disable=protected-access
byref(advertisement_data), byref(advertisement_data_count),
byref(manufacturer_data), byref(manufacturer_data_count))
else:
ret = gattlib_get_advertisement_data(self._connection,
byref(_advertisement_data), byref(_advertisement_data_count),
byref(_manufacturer_id),
byref(_manufacturer_data), byref(_manufacturer_data_len))
byref(advertisement_data), byref(advertisement_data_count),
byref(manufacturer_data), byref(manufacturer_data_count))
handle_return(ret)
advertisement_data = {}
manufacturer_data = None
for i in range(0, _advertisement_data_count.value):
service_data = _advertisement_data[i]
uuid = gattlib_uuid_to_int(service_data.uuid)
pointer_type = POINTER(c_byte * service_data.data_length)
c_bytearray = cast(service_data.data, pointer_type)
data = bytearray(service_data.data_length)
for i in range(service_data.data_length):
data[i] = c_bytearray.contents[i] & 0xFF
advertisement_data[uuid] = data
if _manufacturer_data_len.value > 0:
pointer_type = POINTER(c_byte * _manufacturer_data_len.value)
c_bytearray = cast(_manufacturer_data, pointer_type)
manufacturer_data = bytearray(_manufacturer_data_len.value)
for i in range(_manufacturer_data_len.value):
manufacturer_data[i] = c_bytearray.contents[i] & 0xFF
gattlib_free_mem(_advertisement_data)
gattlib_free_mem(_manufacturer_data)
return advertisement_data, _manufacturer_id.value, manufacturer_data
return convert_gattlib_advertisement_c_data_to_dict( #pylint: disable=protected-access
advertisement_data, advertisement_data_count,
manufacturer_data, manufacturer_data_count)
@property
def services(self):
def services(self) -> dict[int, GattService]:
"""Return a GATT Service dictionary - the GATT UUID being the key."""
if not hasattr(self, '_services'):
logger.warning("Start GATT discovery implicitly")
self.discover()
@ -237,7 +216,8 @@ class Device:
return self._services
@property
def characteristics(self):
def characteristics(self) -> dict[int, GattCharacteristic]:
"""Return a GATT Characteristic dictionary - the GATT UUID being the key."""
if not hasattr(self, '_characteristics'):
logger.warning("Start GATT discovery implicitly")
self.discover()
@ -245,16 +225,17 @@ class Device:
return self._characteristics
@staticmethod
def notification_callback(uuid_str, data, data_len, user_data):
def _notification_callback(uuid_str, data, data_len, user_data):
"""Helper method to call back characteristic callback."""
this = user_data
notification_uuid = uuid.UUID(uuid_str)
short_uuid = notification_uuid.int
if short_uuid not in this._gatt_characteristic_callbacks:
if short_uuid not in this._gatt_characteristic_callbacks: #pylint: disable=protected-access
raise RuntimeError("UUID '%s' is expected to be part of the notification list")
else:
characteristic_callback = this._gatt_characteristic_callbacks[short_uuid]
characteristic_callback = this._gatt_characteristic_callbacks[short_uuid] #pylint: disable=protected-access
# value = bytearray(data_len)
# for i in range(data_len):
@ -278,7 +259,7 @@ class Device:
gattlib_register_notification(self._connection,
gattlib_notification_device_python_callback,
gattlib_python_callback_args(Device.notification_callback, self))
gattlib_python_callback_args(Device._notification_callback, self))
def _notification_add_gatt_characteristic_callback(self, gatt_characteristic, callback, user_data):
if not callable(callback):

View File

@ -4,6 +4,8 @@
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
"""Gattlib Exceptions"""
GATTLIB_SUCCESS = 0
GATTLIB_INVALID_PARAMETER = 1
GATTLIB_NOT_FOUND = 2
@ -15,6 +17,8 @@ GATTLIB_DEVICE_NOT_CONNECTED = 7
GATTLIB_NO_ADAPTER = 8
GATTLIB_BUSY = 9
GATTLIB_UNEXPECTED = 10
GATTLIB_ADAPTER_CLOSE = 11
GATTLIB_DEVICE_DISCONNECTED = 12
GATTLIB_ERROR_MODULE_MASK = 0xF0000000
GATTLIB_ERROR_DBUS = 0x10000000
@ -23,36 +27,43 @@ GATTLIB_ERROR_INTERNAL = 0x80000000
class GattlibException(Exception):
pass
"""Generic Gattlib exception."""
class NoAdapter(GattlibException):
pass
"""Gattlib exception raised when no adapter is present."""
class Busy(GattlibException):
pass
"""Gattlib busy exception."""
class Unexpected(GattlibException):
pass
"""Gattlib unexpected exception."""
class AdapterNotOpened(GattlibException):
pass
"""Gattlib exception raised when adapter is not opened yet."""
class InvalidParameter(GattlibException):
pass
"""Gattlib invalid parameter exception."""
class NotFound(GattlibException):
pass
"""Gattlib not found exception."""
class OutOfMemory(GattlibException):
pass
"""Gattlib out of memory exception."""
class NotSupported(GattlibException):
pass
"""Gattlib not supported exception."""
class NotConnected(GattlibException):
pass
"""Gattlib exception raised when device is not connected."""
class AdapterClose(GattlibException):
"""Gattlib exception raised when the adapter is closed."""
class Disconnected(GattlibException):
"""Gattlib exception raised when the device is disconnected."""
class DeviceError(GattlibException):
"""Gattlib device exception."""
def __init__(self, adapter: str = None, mac_address: str = None) -> None:
self.adapter = adapter
self.mac_address = mac_address
@ -61,44 +72,50 @@ class DeviceError(GattlibException):
return f"Error with device {self.mac_address} on adapter {self.adapter}"
class DBusError(GattlibException):
"""Gattlib DBUS exception."""
def __init__(self, domain: int, code: int) -> None:
self.domain = domain
self.code = code
def __str__(self) -> str:
if self.domain == 238 and self.code == 60964:
return f"DBus Error: le-connection-abort-by-local"
return "DBus Error: le-connection-abort-by-local"
elif self.domain == 238 and self.code == 60952:
return f"DBus Error: Timeout was reached"
return "DBus Error: Timeout was reached"
elif self.domain == 238 and self.code == 60964:
return f"DBus Error: Timeout was reached"
return "DBus Error: Timeout was reached"
else:
return f"DBus Error domain={self.domain},code={self.code}"
def handle_return(ret):
"""Function to convert gattlib error to Python exception."""
if ret == GATTLIB_INVALID_PARAMETER:
raise InvalidParameter()
elif ret == GATTLIB_NOT_FOUND:
if ret == GATTLIB_NOT_FOUND:
raise NotFound()
elif ret == GATTLIB_OUT_OF_MEMORY:
if ret == GATTLIB_OUT_OF_MEMORY:
raise OutOfMemory()
elif ret == GATTLIB_TIMEOUT:
if ret == GATTLIB_TIMEOUT:
raise TimeoutError()
elif ret == GATTLIB_NOT_SUPPORTED:
if ret == GATTLIB_NOT_SUPPORTED:
raise NotSupported()
elif ret == GATTLIB_DEVICE_ERROR:
if ret == GATTLIB_DEVICE_ERROR:
raise DeviceError()
elif ret == GATTLIB_DEVICE_NOT_CONNECTED:
if ret == GATTLIB_DEVICE_NOT_CONNECTED:
raise NotConnected()
elif ret == GATTLIB_NO_ADAPTER:
if ret == GATTLIB_NO_ADAPTER:
raise NoAdapter()
elif ret == GATTLIB_BUSY:
if ret == GATTLIB_BUSY:
raise Busy()
elif ret == GATTLIB_UNEXPECTED:
if ret == GATTLIB_UNEXPECTED:
raise Unexpected()
elif (ret & GATTLIB_ERROR_MODULE_MASK) == GATTLIB_ERROR_DBUS:
if ret == GATTLIB_ADAPTER_CLOSE:
raise AdapterClose()
if ret == GATTLIB_DEVICE_DISCONNECTED:
raise Disconnected()
if (ret & GATTLIB_ERROR_MODULE_MASK) == GATTLIB_ERROR_DBUS:
raise DBusError((ret >> 8) & 0xFFF, ret & 0xFFFF)
elif ret == -22: # From '-EINVAL'
if ret == -22: # From '-EINVAL'
raise ValueError("Gattlib value error")
elif ret != 0:
raise RuntimeError("Gattlib exception %d" % ret)
if ret != 0:
raise RuntimeError(f"Gattlib exception {ret}")

View File

@ -4,23 +4,29 @@
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
from gattlib import *
"""Module for GATT Service, Characteristic and Stream."""
from uuid import UUID
from gattlib import * #pylint: disable=wildcard-import,unused-wildcard-import
from .uuid import gattlib_uuid_to_uuid, gattlib_uuid_to_int
from .exception import handle_return, InvalidParameter
class GattStream():
"""GATT Stream class."""
def __init__(self, fd, mtu):
self._fd = fd
self._mtu = mtu
@property
def mtu(self):
"""Return connection MTU."""
# Remove ATT Header (3 bytes)
return self._mtu - 3
def write(self, data, mtu=None):
"""Write data to GATT stream."""
if mtu is None:
mtu = self.mtu
@ -35,44 +41,51 @@ class GattStream():
gattlib.gattlib_write_char_stream_write(self._fd, buffer_type.from_buffer_copy(buffer), buffer_len)
def close(self):
"""Close GATT stream."""
gattlib.gattlib_write_char_stream_close(self._fd)
class GattService():
"""GATT Service class."""
def __init__(self, device, gattlib_primary_service):
self._device = device
self._gattlib_primary_service = gattlib_primary_service
@property
def uuid(self):
def uuid(self) -> UUID:
"""Return GATT service UUID"""
return gattlib_uuid_to_uuid(self._gattlib_primary_service.uuid)
@property
def short_uuid(self):
def short_uuid(self) -> int:
"""Return GATT service short UUID"""
return gattlib_uuid_to_int(self._gattlib_primary_service.uuid)
class GattCharacteristic():
"""GATT Characteristic class."""
def __init__(self, device, gattlib_characteristic):
self._device = device
self._gattlib_characteristic = gattlib_characteristic
@property
def uuid(self):
def uuid(self) -> UUID:
"""Read UUID characteristic."""
return gattlib_uuid_to_uuid(self._gattlib_characteristic.uuid)
@property
def short_uuid(self):
"""Return GATT characteristic short UUID"""
return gattlib_uuid_to_int(self._gattlib_characteristic.uuid)
@property
def connection(self):
"""Return Gattlib connection C handle."""
return self._device.connection
def read(self, callback=None):
if callback:
"""Read GATT characteristic."""
if callback: #pylint: disable=no-else-raise
raise NotImplementedError()
else:
_buffer = c_void_p(None)
@ -92,6 +105,7 @@ class GattCharacteristic():
return value
def write(self, data, without_response=False):
"""Write data to GATT characteristic."""
if not isinstance(data, bytes) and not isinstance(data, bytearray):
raise TypeError("Data must be of bytes type to know its size.")
@ -100,12 +114,17 @@ class GattCharacteristic():
buffer_len = len(data)
if without_response:
ret = gattlib_write_without_response_char_by_uuid(self.connection, self._gattlib_characteristic.uuid, buffer_type.from_buffer_copy(buffer), buffer_len)
ret = gattlib_write_without_response_char_by_uuid(self.connection,
self._gattlib_characteristic.uuid,
buffer_type.from_buffer_copy(buffer), buffer_len)
else:
ret = gattlib_write_char_by_uuid(self.connection, self._gattlib_characteristic.uuid, buffer_type.from_buffer_copy(buffer), buffer_len)
ret = gattlib_write_char_by_uuid(self.connection,
self._gattlib_characteristic.uuid,
buffer_type.from_buffer_copy(buffer), buffer_len)
handle_return(ret)
def stream_open(self):
"""Open GATT stream from GATT characteristic."""
_stream = c_void_p(None)
_mtu = c_uint16(0)
@ -115,20 +134,24 @@ class GattCharacteristic():
return GattStream(_stream, _mtu.value)
def register_notification(self, callback, user_data=None):
"""Register callback for notification on this GATT characteristic."""
if not callable(callback):
raise InvalidParameter("Notification callback is not callable.")
self._device._notification_add_gatt_characteristic_callback(self, callback, user_data)
self._device._notification_add_gatt_characteristic_callback(self, callback, user_data) #pylint: disable=protected-access
def unregister_notification(self):
self._device._notification_remove_gatt_characteristic_callback(self)
"""Unregister all notification callbacks."""
self._device._notification_remove_gatt_characteristic_callback(self) #pylint: disable=protected-access
def notification_start(self):
"""Start GATT notification."""
ret = gattlib_notification_start(self.connection, self._gattlib_characteristic.uuid)
handle_return(ret)
def notification_stop(self):
""" Could raise gattlib.exception.NotFound if notification has not been registered"""
"""Stop GATT notification."""
# Could raise gattlib.exception.NotFound if notification has not been registered
ret = gattlib_notification_stop(self.connection, self._gattlib_characteristic.uuid)
handle_return(ret)

View File

@ -0,0 +1,49 @@
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) 2024, Olivier Martin <olivier@labapart.org>
#
"""Module for helper functions for Gattlib module."""
from gattlib import * #pylint: disable=wildcard-import,unused-wildcard-import
from .uuid import gattlib_uuid_to_int
def convert_gattlib_advertisement_c_data_to_dict(advertisement_c_data, advertisement_c_data_count,
manufacturer_c_data, manufacturer_c_data_count):
"""Helper function to convert advertisement and manufacturer c-data to Python dictionary"""
advertisement_data = {}
manufacturer_data = {}
for i in range(0, advertisement_c_data_count.value):
service_data = advertisement_c_data[i]
uuid = gattlib_uuid_to_int(service_data.uuid)
pointer_type = POINTER(c_byte * service_data.data_length)
c_bytearray = cast(service_data.data, pointer_type)
data = bytearray(service_data.data_length)
for i in range(service_data.data_length):
data[i] = c_bytearray.contents[i] & 0xFF
advertisement_data[uuid] = data
gattlib_free_mem(service_data.data)
for i in range(0, manufacturer_c_data_count.value):
_manufacturer_c_data = manufacturer_c_data[i]
pointer_type = POINTER(c_byte * _manufacturer_c_data.data_size.value)
c_bytearray = cast(_manufacturer_c_data.data, pointer_type)
data = bytearray(_manufacturer_c_data.data_size.value)
for j in range(_manufacturer_c_data.data_size.value):
data[j] = c_bytearray.contents[j] & 0xFF
manufacturer_data[_manufacturer_c_data.manufacturer_id] = data
gattlib_free_mem(_manufacturer_c_data.data)
gattlib_free_mem(advertisement_c_data)
gattlib_free_mem(manufacturer_c_data)
return advertisement_data, manufacturer_data

View File

@ -4,6 +4,8 @@
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
"""Module for exposing main loop for Gattlib execution."""
import threading
import time
import traceback
@ -19,7 +21,7 @@ task_exception: Exception = None
def _user_thread_main(task):
"""Main entry point for the thread that will run user's code."""
global gobject_mainloop, task_returned_code, task_exception
global task_returned_code, task_exception
try:
# Wait for GLib main loop to start running before starting user code.
@ -32,7 +34,7 @@ def _user_thread_main(task):
# Run user's code.
task_returned_code = task()
except Exception as ex:
except Exception as ex: #pylint: disable=broad-except
logger.error("Exception in %s: %s: %s", task, type(ex), str(ex))
traceback.print_exception(type(ex), ex, ex.__traceback__)
task_exception = ex
@ -40,7 +42,12 @@ def _user_thread_main(task):
gobject_mainloop.quit()
def run_mainloop_with(task):
global gobject_mainloop, task_returned_code, task_exception
"""
Run main loop with the given task.
The main loop ends when the task has completed.
"""
global gobject_mainloop
if gobject_mainloop:
raise RuntimeError("A mainloop is already running")

View File

@ -4,19 +4,22 @@
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
"""Module to manipulate Gattlib UUID in Python environment."""
import re
from uuid import UUID
from gattlib import *
from gattlib import * #pylint: disable=wildcard-import,unused-wildcard-import
SDP_UUID16 = 0x19
SDP_UUID32 = 0x1A
SDP_UUID128 = 0x1C
GATT_STANDARD_UUID_FORMAT = re.compile("(\S+)-0000-1000-8000-00805f9b34fb", flags=re.IGNORECASE)
GATT_STANDARD_UUID_FORMAT = re.compile(r"(\S+)-0000-1000-8000-00805f9b34fb", flags=re.IGNORECASE)
def gattlib_uuid_to_uuid(gattlib_uuid):
def gattlib_uuid_to_uuid(gattlib_uuid) -> UUID:
"""Convert Gattlib UUID to Python UUID"""
if gattlib_uuid.type == SDP_UUID16:
return UUID(fields=(gattlib_uuid.value.uuid16, 0x0000, 0x1000, 0x80, 0x00, 0x00805f9b34fb))
elif gattlib_uuid.type == SDP_UUID32:
@ -25,10 +28,11 @@ def gattlib_uuid_to_uuid(gattlib_uuid):
data = bytes(gattlib_uuid.value.uuid128.data)
return UUID(bytes=data)
else:
return ValueError("Gattlib UUID not recognized (type:0x%x)" % gattlib_uuid.type)
return ValueError(f"Gattlib UUID not recognized (type:0x{gattlib_uuid.type:02x})")
def gattlib_uuid_to_int(gattlib_uuid):
def gattlib_uuid_to_int(gattlib_uuid) -> int:
"""Convert Gattlib UUID to integer."""
if gattlib_uuid.type == SDP_UUID16:
return gattlib_uuid.value.uuid16
elif gattlib_uuid.type == SDP_UUID32:
@ -37,10 +41,11 @@ def gattlib_uuid_to_int(gattlib_uuid):
data = bytes(gattlib_uuid.value.uuid128.data)
return int.from_bytes(data, byteorder='big')
else:
return ValueError("Gattlib UUID not recognized (type:0x%x)" % gattlib_uuid.type)
return ValueError(f"Gattlib UUID not recognized (type:0x{gattlib_uuid.type:02x})")
def gattlib_uuid_str_to_int(uuid_str: str) -> int:
"""Convert uuid string to integer"""
# Check if the string could already encode a UUID16 or UUID32
if len(uuid_str) <= 8:
return int(uuid_str, 16)

View File

@ -14,6 +14,8 @@ from setuptools import setup, find_packages, Extension
from setuptools.command.build_ext import build_ext
import subprocess
SETUP_DIR = os.path.dirname(os.path.realpath(__file__))
# Name of the directory containing the python sources
python_module_name = "gattlib"
# Specified where the CMakeLists.txt is located
@ -23,14 +25,28 @@ native_source_dir = os.environ.get("NATIVE_SOURCE_DIR", ".")
git_version_command = subprocess.Popen(['git', 'describe', '--abbrev=7', '--dirty', '--always', '--tags'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = git_version_command.communicate()
git_version = stdout.decode('utf-8').strip()
if git_version_command.returncode == 0:
git_version = stdout.decode('utf-8').strip()
else:
git_version = None
#
# Create '_version.py'
#
package_version = os.environ.get('GATTLIB_PY_VERSION', git_version)
with open(os.path.join("gattlib", "_version.py"), "w") as f:
f.write(f"__version__ = \"{package_version}\"\n")
GATTLIB_VERSION_FILE = os.path.join(SETUP_DIR, "gattlib", "_version.py")
# Case we are building from source package
if package_version is None:
with open(GATTLIB_VERSION_FILE, "r") as f:
gattlib_version_statement = f.read()
res = re.search(r'__version__ = "(.*)"', gattlib_version_statement)
package_version = res.group(1)
if package_version:
with open(GATTLIB_VERSION_FILE, "w") as f:
f.write(f"__version__ = \"{package_version}\"\n")
class CMakeExtension(Extension):
@ -77,6 +93,8 @@ class CMakeBuild(build_ext):
f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={cmake_library_output_dir}",
f"-DPYTHON_EXECUTABLE={sys.executable}",
f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm
"-DGATTLIB_PYTHON_INTERFACE=ON",
"-DGATTLIB_BUILD_EXAMPLES=OFF",
]
build_args = []
# Adding CMake arguments set as environment variable
@ -161,7 +179,7 @@ setup(
author_email="olivier@labapart.com",
description="Python wrapper for gattlib library",
url="https://github.com/labapart/gattlib/gattlib-py",
long_description=open('README.md').read(),
long_description=open(os.path.join(SETUP_DIR, 'README.md')).read(),
long_description_content_type='text/markdown',
packages=find_packages(),
install_requires=[

View File

@ -52,6 +52,8 @@ extern "C" {
#define GATTLIB_NO_ADAPTER 8
#define GATTLIB_BUSY 9
#define GATTLIB_UNEXPECTED 10
#define GATTLIB_ADAPTER_CLOSE 11
#define GATTLIB_DEVICE_DISCONNECTED 12
#define GATTLIB_ERROR_MODULE_MASK 0xF0000000
#define GATTLIB_ERROR_DBUS 0x10000000
#define GATTLIB_ERROR_BLUEZ 0x20000000
@ -149,8 +151,9 @@ extern "C" {
#define GATTLIB_LOG(level, args...) if (level <= GATTLIB_LOG_LEVEL) { gattlib_log(level, args); }
typedef struct _gattlib_device gatt_connection_t;
typedef struct _gatt_stream_t gatt_stream_t;
typedef struct _gattlib_adapter gattlib_adapter_t;
typedef struct _gattlib_connection gattlib_connection_t;
typedef struct _gattlib_stream_t gattlib_stream_t;
/**
* Structure to represent a GATT Service and its data in the BLE advertisement packet
@ -161,6 +164,15 @@ typedef struct {
size_t data_length; /**< Length of data attached to the GATT Service */
} gattlib_advertisement_data_t;
/**
* Structure to represent manufacturer data from GATT advertisement packet
*/
typedef struct {
uint16_t manufacturer_id;
uint8_t* data;
size_t data_size;
} gattlib_manufacturer_data_t;
typedef void (*gattlib_event_handler_t)(const uuid_t* uuid, const uint8_t* data, size_t data_length, void* user_data);
/**
@ -169,7 +181,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)(gatt_connection_t* connection, void* user_data);
typedef void (*gattlib_disconnection_handler_t)(gattlib_connection_t* connection, void* user_data);
/**
* @brief Handler called on new discovered BLE device
@ -179,7 +191,7 @@ typedef void (*gattlib_disconnection_handler_t)(gatt_connection_t* connection, v
* @param name is the name of BLE device if advertised
* @param user_data Data defined when calling `gattlib_register_on_disconnect()`
*/
typedef void (*gattlib_discovered_device_t)(void *adapter, const char* addr, const char* name, void *user_data);
typedef void (*gattlib_discovered_device_t)(gattlib_adapter_t* adapter, const char* addr, const char* name, void *user_data);
/**
* @brief Handler called on new discovered BLE device
@ -189,14 +201,13 @@ typedef void (*gattlib_discovered_device_t)(void *adapter, const char* addr, con
* @param name is the name of BLE device if advertised
* @param advertisement_data is an array of Service UUID and their respective data
* @param advertisement_data_count is the number of elements in the advertisement_data array
* @param manufacturer_id is the ID of the Manufacturer ID
* @param manufacturer_data is the data following Manufacturer ID
* @param manufacturer_data_size is the size of manufacturer_data
* @param manufacturer_data is an array of `gattlib_manufacturer_data_t`
* @param manufacturer_data_count is the number of entry in `gattlib_manufacturer_data_t` array
* @param user_data Data defined when calling `gattlib_register_on_disconnect()`
*/
typedef void (*gattlib_discovered_device_with_data_t)(void *adapter, const char* addr, const char* name,
typedef void (*gattlib_discovered_device_with_data_t)(gattlib_adapter_t* 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,
gattlib_manufacturer_data_t* manufacturer_data, size_t manufacturer_data_count,
void *user_data);
/**
@ -208,7 +219,7 @@ typedef void (*gattlib_discovered_device_with_data_t)(void *adapter, const char*
* @param error Connection error code
* @param user_data Data defined when calling `gattlib_register_on_disconnect()`
*/
typedef void (*gatt_connect_cb_t)(void *adapter, const char *dst, gatt_connection_t* connection, int error, void* user_data);
typedef void (*gatt_connect_cb_t)(gattlib_adapter_t* adapter, const char *dst, gattlib_connection_t* connection, int error, void* user_data);
/**
* @brief Callback called when GATT characteristic read value has been received
@ -239,7 +250,7 @@ extern const char *gattlib_eddystone_url_scheme_prefix[];
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_adapter_open(const char* adapter_name, void** adapter);
int gattlib_adapter_open(const char* adapter_name, gattlib_adapter_t** adapter);
/**
* @brief Get adapter name
@ -248,7 +259,7 @@ int gattlib_adapter_open(const char* adapter_name, void** adapter);
*
* @return Adapter name
*/
const char *gattlib_adapter_get_name(void* adapter);
const char *gattlib_adapter_get_name(gattlib_adapter_t* adapter);
/**
* @brief Enable Bluetooth scanning on a given adapter
@ -260,7 +271,7 @@ const char *gattlib_adapter_get_name(void* adapter);
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t discovered_device_cb, size_t timeout, void *user_data);
int gattlib_adapter_scan_enable(gattlib_adapter_t* adapter, gattlib_discovered_device_t discovered_device_cb, size_t timeout, void *user_data);
/**
* @brief Enable Bluetooth scanning on a given adapter
@ -279,7 +290,7 @@ int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t disco
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
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(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);
/**
@ -299,7 +310,7 @@ int gattlib_adapter_scan_enable_with_filter(void *adapter, uuid_t **uuid_list, i
*
* @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,
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);
/**
@ -318,7 +329,7 @@ int gattlib_adapter_scan_enable_with_filter_non_blocking(void *adapter, uuid_t *
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_adapter_scan_eddystone(void *adapter, int16_t rssi_threshold, uint32_t eddystone_types,
int gattlib_adapter_scan_eddystone(gattlib_adapter_t* adapter, int16_t rssi_threshold, uint32_t eddystone_types,
gattlib_discovered_device_with_data_t discovered_device_cb, size_t timeout, void *user_data);
/**
@ -328,7 +339,7 @@ int gattlib_adapter_scan_eddystone(void *adapter, int16_t rssi_threshold, uint32
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_adapter_scan_disable(void* adapter);
int gattlib_adapter_scan_disable(gattlib_adapter_t* adapter);
/**
* @brief Close Bluetooth adapter context
@ -337,7 +348,7 @@ int gattlib_adapter_scan_disable(void* adapter);
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_adapter_close(void* adapter);
int gattlib_adapter_close(gattlib_adapter_t* adapter);
/**
* @brief Function to asynchronously connect to a BLE device
@ -352,7 +363,7 @@ int gattlib_adapter_close(void* adapter);
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_connect(void *adapter, const char *dst,
int gattlib_connect(gattlib_adapter_t* adapter, const char *dst,
unsigned long options,
gatt_connect_cb_t connect_cb,
void* user_data);
@ -371,7 +382,7 @@ int gattlib_connect(void *adapter, const char *dst,
* @return GATTLIB_TIMEOUT when wait_disconnection is true and the device has not been disconnected for
* GATTLIB_DISCONNECTION_WAIT_TIMEOUT_SEC seconds
*/
int gattlib_disconnect(gatt_connection_t* connection, bool wait_disconnection);
int gattlib_disconnect(gattlib_connection_t* connection, bool wait_disconnection);
/**
* @brief Function to register a callback on GATT disconnection
@ -382,7 +393,7 @@ int gattlib_disconnect(gatt_connection_t* connection, bool wait_disconnection);
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_register_on_disconnect(gatt_connection_t *connection, gattlib_disconnection_handler_t handler, void* user_data);
int gattlib_register_on_disconnect(gattlib_connection_t *connection, gattlib_disconnection_handler_t handler, void* user_data);
/**
* Structure to represent GATT Primary Service
@ -423,7 +434,7 @@ typedef struct {
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_service_t** services, int* services_count);
int gattlib_discover_primary(gattlib_connection_t* connection, gattlib_primary_service_t** services, int* services_count);
/**
* @brief Function to discover GATT Characteristic
@ -438,7 +449,7 @@ int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_serv
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_discover_char_range(gatt_connection_t* connection, uint16_t start, uint16_t end, gattlib_characteristic_t** characteristics, int* characteristics_count);
int gattlib_discover_char_range(gattlib_connection_t* connection, uint16_t start, uint16_t end, gattlib_characteristic_t** characteristics, int* characteristics_count);
/**
* @brief Function to discover GATT Characteristic
@ -451,7 +462,7 @@ int gattlib_discover_char_range(gatt_connection_t* connection, uint16_t start, u
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_discover_char(gatt_connection_t* connection, gattlib_characteristic_t** characteristics, int* characteristics_count);
int gattlib_discover_char(gattlib_connection_t* connection, gattlib_characteristic_t** characteristics, int* characteristics_count);
/**
* @brief Function to discover GATT Descriptors in a range of handles
@ -464,7 +475,7 @@ int gattlib_discover_char(gatt_connection_t* connection, gattlib_characteristic_
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_discover_desc_range(gatt_connection_t* connection, int start, int end, gattlib_descriptor_t** descriptors, int* descriptors_count);
int gattlib_discover_desc_range(gattlib_connection_t* connection, int start, int end, gattlib_descriptor_t** descriptors, int* descriptors_count);
/**
* @brief Function to discover GATT Descriptor
@ -475,7 +486,7 @@ int gattlib_discover_desc_range(gatt_connection_t* connection, int start, int en
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_discover_desc(gatt_connection_t* connection, gattlib_descriptor_t** descriptors, int* descriptors_count);
int gattlib_discover_desc(gattlib_connection_t* connection, gattlib_descriptor_t** descriptors, int* descriptors_count);
/**
* @brief Function to read GATT characteristic
@ -489,7 +500,7 @@ int gattlib_discover_desc(gatt_connection_t* connection, gattlib_descriptor_t**
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_read_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, void** buffer, size_t* buffer_len);
int gattlib_read_char_by_uuid(gattlib_connection_t* connection, uuid_t* uuid, void** buffer, size_t* buffer_len);
/**
* @brief Function to asynchronously read GATT characteristic
@ -500,7 +511,7 @@ int gattlib_read_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, void*
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_read_char_by_uuid_async(gatt_connection_t* connection, uuid_t* uuid, gatt_read_cb_t gatt_read_cb);
int gattlib_read_char_by_uuid_async(gattlib_connection_t* connection, uuid_t* uuid, gatt_read_cb_t gatt_read_cb);
/**
* @brief Free buffer allocated by the characteristic reading to store the value
@ -519,7 +530,7 @@ void gattlib_characteristic_free_value(void *ptr);
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_write_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len);
int gattlib_write_char_by_uuid(gattlib_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len);
/**
* @brief Function to write to the GATT characteristic handle
@ -531,7 +542,7 @@ int gattlib_write_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, cons
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_write_char_by_handle(gatt_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len);
int gattlib_write_char_by_handle(gattlib_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len);
/**
* @brief Function to write without response to the GATT characteristic UUID
@ -543,7 +554,7 @@ int gattlib_write_char_by_handle(gatt_connection_t* connection, uint16_t handle,
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_write_without_response_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len);
int gattlib_write_without_response_char_by_uuid(gattlib_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len);
/**
* @brief Create a stream to a GATT characteristic to write data in continue
@ -557,7 +568,7 @@ int gattlib_write_without_response_char_by_uuid(gatt_connection_t* connection, u
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_write_char_by_uuid_stream_open(gatt_connection_t* connection, uuid_t* uuid, gatt_stream_t **stream, uint16_t *mtu);
int gattlib_write_char_by_uuid_stream_open(gattlib_connection_t* connection, uuid_t* uuid, gattlib_stream_t **stream, uint16_t *mtu);
/**
* @brief Write data to the stream previously created with `gattlib_write_char_by_uuid_stream_open()`
@ -568,7 +579,7 @@ int gattlib_write_char_by_uuid_stream_open(gatt_connection_t* connection, uuid_t
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_write_char_stream_write(gatt_stream_t *stream, const void *buffer, size_t buffer_len);
int gattlib_write_char_stream_write(gattlib_stream_t *stream, const void *buffer, size_t buffer_len);
/**
* @brief Close the stream previously created with `gattlib_write_char_by_uuid_stream_open()`
@ -577,7 +588,7 @@ int gattlib_write_char_stream_write(gatt_stream_t *stream, const void *buffer, s
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_write_char_stream_close(gatt_stream_t *stream);
int gattlib_write_char_stream_close(gattlib_stream_t *stream);
/**
* @brief Function to write without response to the GATT characteristic handle
@ -589,7 +600,7 @@ int gattlib_write_char_stream_close(gatt_stream_t *stream);
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_write_without_response_char_by_handle(gatt_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len);
int gattlib_write_without_response_char_by_handle(gattlib_connection_t* connection, uint16_t handle, const void* buffer, size_t buffer_len);
/*
* @brief Enable notification on GATT characteristic represented by its UUID
@ -599,7 +610,7 @@ int gattlib_write_without_response_char_by_handle(gatt_connection_t* connection,
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_notification_start(gatt_connection_t* connection, const uuid_t* uuid);
int gattlib_notification_start(gattlib_connection_t* connection, const uuid_t* uuid);
/*
* @brief Disable notification on GATT characteristic represented by its UUID
@ -609,7 +620,7 @@ int gattlib_notification_start(gatt_connection_t* connection, const uuid_t* uuid
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_notification_stop(gatt_connection_t* connection, const uuid_t* uuid);
int gattlib_notification_stop(gattlib_connection_t* connection, const uuid_t* uuid);
/*
* @brief Enable indication on GATT characteristic represented by its UUID
@ -619,7 +630,7 @@ int gattlib_notification_stop(gatt_connection_t* connection, const uuid_t* uuid)
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_indication_start(gatt_connection_t* connection, const uuid_t* uuid);
int gattlib_indication_start(gattlib_connection_t* connection, const uuid_t* uuid);
/*
* @brief Disable indication on GATT characteristic represented by its UUID
@ -629,7 +640,7 @@ int gattlib_indication_start(gatt_connection_t* connection, const uuid_t* uuid);
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_indication_stop(gatt_connection_t* connection, const uuid_t* uuid);
int gattlib_indication_stop(gattlib_connection_t* connection, const uuid_t* uuid);
/*
* @brief Register a handle for the GATT notifications
@ -640,7 +651,7 @@ int gattlib_indication_stop(gatt_connection_t* connection, const uuid_t* uuid);
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_register_notification(gatt_connection_t* connection, gattlib_event_handler_t notification_handler, void* user_data);
int gattlib_register_notification(gattlib_connection_t* connection, gattlib_event_handler_t notification_handler, void* user_data);
/*
* @brief Register a handle for the GATT indications
@ -651,7 +662,7 @@ int gattlib_register_notification(gatt_connection_t* connection, gattlib_event_h
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_register_indication(gatt_connection_t* connection, gattlib_event_handler_t indication_handler, void* user_data);
int gattlib_register_indication(gattlib_connection_t* connection, gattlib_event_handler_t indication_handler, void* user_data);
#if 0 // Disable until https://github.com/labapart/gattlib/issues/75 is resolved
/**
@ -662,14 +673,14 @@ int gattlib_register_indication(gatt_connection_t* connection, gattlib_event_han
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_get_rssi(gatt_connection_t *connection, int16_t *rssi);
int gattlib_get_rssi(gattlib_connection_t *connection, int16_t *rssi);
#endif
/**
* @brief Function to retrieve RSSI from a MAC Address
*
* @note: This function is mainly used before a connection is established. Once the connection
* established, the function `gattlib_get_rssi()` should be preferred.
* @note: This function must be used before a connection is established. Once the connection
* established, the function will return a null RSSI.
*
* @param adapter is the adapter the new device has been seen
* @param mac_address is the MAC address of the device to get the RSSI
@ -677,7 +688,7 @@ int gattlib_get_rssi(gatt_connection_t *connection, int16_t *rssi);
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_get_rssi_from_mac(void *adapter, const char *mac_address, int16_t *rssi);
int gattlib_get_rssi_from_mac(gattlib_adapter_t* adapter, const char *mac_address, int16_t *rssi);
/**
* @brief Function to retrieve Advertisement Data from a MAC Address
@ -685,15 +696,14 @@ int gattlib_get_rssi_from_mac(void *adapter, const char *mac_address, int16_t *r
* @param connection Active GATT connection
* @param advertisement_data is an array of Service UUID and their respective data
* @param advertisement_data_count is the number of elements in the advertisement_data array
* @param manufacturer_id is the ID of the Manufacturer ID
* @param manufacturer_data is the data following Manufacturer ID
* @param manufacturer_data_size is the size of manufacturer_data
* @param manufacturer_data is an array of `gattlib_manufacturer_data_t`
* @param manufacturer_data_count is the number of entry in `gattlib_manufacturer_data_t` array
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_get_advertisement_data(gatt_connection_t *connection,
int gattlib_get_advertisement_data(gattlib_connection_t *connection,
gattlib_advertisement_data_t **advertisement_data, size_t *advertisement_data_count,
uint16_t *manufacturer_id, uint8_t **manufacturer_data, size_t *manufacturer_data_size);
gattlib_manufacturer_data_t** manufacturer_data, size_t* manufacturer_data_count);
/**
* @brief Function to retrieve Advertisement Data from a MAC Address
@ -702,15 +712,14 @@ int gattlib_get_advertisement_data(gatt_connection_t *connection,
* @param mac_address is the MAC address of the device to get the RSSI
* @param advertisement_data is an array of Service UUID and their respective data
* @param advertisement_data_count is the number of elements in the advertisement_data array
* @param manufacturer_id is the ID of the Manufacturer ID
* @param manufacturer_data is the data following Manufacturer ID
* @param manufacturer_data_size is the size of manufacturer_data
* @param manufacturer_data is an array of `gattlib_manufacturer_data_t`
* @param manufacturer_data_count is the number of entry in `gattlib_manufacturer_data_t` array
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_get_advertisement_data_from_mac(void *adapter, const char *mac_address,
int gattlib_get_advertisement_data_from_mac(gattlib_adapter_t* adapter, const char *mac_address,
gattlib_advertisement_data_t **advertisement_data, size_t *advertisement_data_count,
uint16_t *manufacturer_id, uint8_t **manufacturer_data, size_t *manufacturer_data_size);
gattlib_manufacturer_data_t** manufacturer_data, size_t* manufacturer_data_count);
/**
* @brief Convert a UUID into a string

View File

@ -19,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 3.25.1)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -49,7 +49,7 @@ static struct {
bool value;
} m_connection_terminated;
static void on_device_connect(void *adapter, const char *dst, gatt_connection_t* connection, int error, void* user_data) {
static void on_device_connect(gattlib_adapter_t* adapter, const char *dst, gattlib_connection_t* connection, int error, void* user_data) {
int ret;
if (error != 0) {
@ -77,7 +77,7 @@ static int stricmp(char const *a, char const *b) {
}
}
static void ble_discovered_device(void *adapter, const char* addr, const char* name, void *user_data) {
static void ble_discovered_device(gattlib_adapter_t* adapter, const char* addr, const char* name, void *user_data) {
int ret;
if (stricmp(addr, reference_mac_address) != 0) {
@ -117,7 +117,7 @@ static void ble_discovered_device(void *adapter, const char* addr, const char* n
}
static void* ble_task(void* arg) {
void* adapter;
gattlib_adapter_t* adapter;
int ret;
ret = gattlib_adapter_open(adapter_name, &adapter);