Compare commits

...

52 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
Olivier Martin a587aa9dfa gattlib_connect: Set the device has DISCONNECTED on error 2024-04-04 21:54:28 +02:00
Olivier Martin 67ff1de69b gattlib-py/adapter: Catch error on opening/closing BLE adapter 2024-04-04 12:03:29 +02:00
Olivier Martin 53e6c2c7ae tests: Added new test that try to connect/disconnect a same device 2024-04-04 12:03:29 +02:00
Olivier Martin fcedfb9e85 gattlib_adapter: Improve BLE scan code with comments and renaming 2024-04-04 12:03:29 +02:00
Olivier Martin b57d9546df gattlib_glib_mainloop: Ensure mainloop is initialized before using it 2024-04-04 12:03:29 +02:00
Olivier Martin 2e99c4f1b6 dbus: Handle when the device is removed from DBUS 2024-04-04 12:03:29 +02:00
Olivier Martin 709b76019e Manage device state during its life cycle 2024-04-04 12:03:29 +02:00
Olivier Martin 0c1334c5b4 Consolidate device list between discovered and connected devices 2024-04-04 12:03:29 +02:00
Olivier Martin aa6a7b79bb Consolidate device state to prevent concurrent accesses 2024-04-04 12:03:29 +02:00
Olivier Martin fab0e8fa67 Consolidate device mutex 2024-04-03 21:13:33 +02:00
Olivier Martin ce52533f39 Rename structure 'gattlib_connection_t' to '_gattlib_device' 2024-04-03 21:13:33 +02:00
Olivier Martin 2861549a80 ci/generate-python-package.sh: Fix script when no MANIFEST.in 2024-04-03 21:13:33 +02:00
Olivier Martin 7758bae7d4 ci: Automatically create a release on tag 2024-04-03 20:06:46 +02:00
Olivier Martin 42c97d4767 Added new Gattlib error types 2024-04-03 20:06:46 +02:00
Olivier Martin aac4e069c9 CMakeLists.txt: Add DEBUG flag when building DEBUG build 2024-04-03 20:06:46 +02:00
Olivier Martin 8f17232216 Update ".gitignore" 2024-04-03 15:03:48 +02:00
Olivier Martin 86f9a742f3 Ensure connection timeout is freed when the connection is free 2024-03-29 09:18:53 +01:00
Olivier Martin 50dca02e97 Update README 2024-03-29 00:20:41 +01:00
Olivier Martin 6cdbe58e7b FixMe: Disable freeing the handler - there is a pointer ref counter not valid when doing so 2024-03-29 00:20:41 +01:00
Olivier Martin db629448fd Use calloc() instead of malloc() when allocating 'struct' or array in memory 2024-03-29 00:20:30 +01:00
Olivier Martin 5f43addb8f gattlib_disconnection: Added support to wait for the disconnection to be effective 2024-03-29 00:20:30 +01:00
Olivier Martin a85dd83015 Consolidate handlers 2024-03-29 00:19:56 +01:00
Olivier Martin 5ca46ad208 examples: Port more examples to new API 2024-03-27 22:07:16 +01:00
Olivier Martin f3f6bb37bb Generate packages 2024-03-26 18:55:58 +01:00
Olivier Martin 5d9a36f1d1 logging_backend/python: Do not call 'GATTLIB_LOG(GATTLIB_ERROR, ...)' on error to avoid recursion 2024-03-26 16:49:19 +01:00
Olivier Martin 6e6436ceb3 gattlib_adapter: Check if 'is_scanning' before stopping BLE scan 2024-03-26 13:46:31 +01:00
Olivier Martin 5ba3eda6f5 dbus/gattlib_adapter: Use mutex to get device manager 2024-03-26 13:46:31 +01:00
Olivier Martin 8e351e746c python: Introduce __version__ 2024-03-26 13:43:42 +01:00
Olivier Martin 6cea2d37db Ensure gattlib can be built without Python support 2024-03-25 12:43:17 +01:00
65 changed files with 3593 additions and 1241 deletions

View File

@ -6,21 +6,57 @@ 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:
PACKAGE_VERSION: '${{github.ref_name}}'
- name: Archive Distribution packages
uses: actions/upload-artifact@v4
if: startsWith(github.ref, 'refs/tags/')
with:
name: distribution-packages
path: |
build/*.deb
build/*.rpm
build/*.zip
- name: Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
build/*.deb
build/*.rpm
build/*.zip
build-release-force-dbus:
runs-on: ubuntu-latest
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
steps:
- uses: actions/checkout@v4
- 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

7
.gitignore vendored
View File

@ -1,2 +1,9 @@
# IDE files
.vscode/
# Generated files
build
__pycache__
MANIFEST.in
dist
gattlib-py/gattlib_py.egg-info/

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)
@ -27,6 +27,10 @@ if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
endif()
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
add_definitions(-DDEBUG)
endif()
# Show all the warnings
if (MSVC)
# warning level 4
@ -114,9 +118,10 @@ if(GATTLIB_BUILD_EXAMPLES)
add_subdirectory(examples/discover)
add_subdirectory(examples/find_eddystone)
add_subdirectory(examples/read_write)
add_subdirectory(examples/read_write_memory)
#add_subdirectory(examples/read_write_memory)
add_subdirectory(examples/notification)
add_subdirectory(examples/nordic_uart)
#add_subdirectory(examples/nordic_uart)
add_subdirectory(tests/test_continuous_connection)
# Some examples require Bluez code and other DBus support
if (NOT GATTLIB_DBUS)
@ -128,13 +133,13 @@ endif()
# Packaging
#
set(CPACK_PACKAGE_INSTALL_DIRECTORY /usr CACHE STRING "Install directory (default: /usr).")
if (ENV{TRAVIS_TAG} AND (NOT "ENV{TRAVIS_TAG}" STREQUAL "dev"))
message("Package Gattlib for tagged version $ENV{TRAVIS_TAG}")
if (ENV{PACKAGE_VERSION} AND (NOT "ENV{PACKAGE_VERSION}" STREQUAL "dev"))
message("Package Gattlib for tagged version $ENV{PACKAGE_VERSION}")
# Transform 'v0.3' into '0.3' and 'v0.3-rc1' into '0.3-rc1'
string(REGEX REPLACE "v([0-9]+).([0-9]+)(.*)" "\\1.\\2\\3" CPACK_PACKAGE_VERSION $ENV{TRAVIS_TAG})
string(REGEX REPLACE "v([0-9]+).([0-9]+)(.*)" "\\1.\\2\\3" CPACK_PACKAGE_VERSION $ENV{PACKAGE_VERSION})
else()
set(CPACK_PACKAGE_VERSION 0.3-dev)
set(CPACK_PACKAGE_VERSION 0.4-dev)
message("Package Gattlib for development version $ENV{CPACK_PACKAGE_VERSION}")
endif()
set(CPACK_PACKAGE_CONTACT "Olivier Martin <olivier@labapart.com>")

View File

@ -1,5 +1,3 @@
[![Build Status](https://travis-ci.org/labapart/gattlib.svg?branch=master)](https://travis-ci.org/labapart/gattlib)
GattLib is a library used to access Generic Attribute Profile (GATT) protocol of BLE (Bluetooth Low Energy) devices.
It has been introduced to allow to build applications that could easily communicate with BLE devices.
@ -8,27 +6,12 @@ It supports Bluez v4 and v5.
Latest GattLib Release packages
===============================
* For x86_64, with Bluez DBUS Support (Recommended):
* The latest release can be found [here](https://github.com/labapart/gattlib/releases/latest). It contains:
- ZIP: <https://github.com/labapart/gattlib/releases/download/dev/gattlib_dbus_0.2-dev_x86_64.zip>
- DEB: <https://github.com/labapart/gattlib/releases/download/dev/gattlib_dbus_0.2-dev_x86_64.deb>
- RPM: <https://github.com/labapart/gattlib/releases/download/dev/gattlib_dbus_0.2-dev_x86_64.rpm>
- Prebuilt Debian, RPM and ZIP packages for x86_64 and Bluez v5.x
- Packages for ARM 32bit and 64bit would have to be built by the developer - see section [Package GattLib](#package-gattlib).
* For x86_64, with Bluez Legacy Support:
- ZIP: <https://github.com/labapart/gattlib/releases/download/dev/gattlib_0.2-dev_x86_64.zip>
- DEB: <https://github.com/labapart/gattlib/releases/download/dev/gattlib_0.2-dev_x86_64.deb>
- RPM: <https://github.com/labapart/gattlib/releases/download/dev/gattlib_0.2-dev_x86_64.rpm>
* For ARM 32-bit (for Bluez v5.40+ with DBus support):
- ZIP: <https://github.com/labapart/gattlib/releases/download/dev/gattlib_0.2-dev_armhf.zip>
- DEB: <https://github.com/labapart/gattlib/releases/download/dev/gattlib_0.2-dev_armhf.deb>
* For ARM 64-bit (for Bluez v5.40+ with DBus support):
- ZIP: <https://github.com/labapart/gattlib/releases/download/dev/gattlib_0.2-dev_arm64.zip>
- DEB: <https://github.com/labapart/gattlib/releases/download/dev/gattlib_0.2-dev_arm64.deb>
- Prebuilt Python packages are available on [Pypi repository](https://pypi.org/project/gattlib-py/).
Build GattLib
=============

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) {
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,7 +28,8 @@ mkdir ${gattlib_py_package_dir}/ci/
cp -r ${ROOT_PATH}/ci/install-bluez.sh ${gattlib_py_package_dir}/ci/
# Create 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
@ -50,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

@ -6,7 +6,8 @@
#include "gattlib_internal.h"
void gattlib_connected_device_python_callback(void *adapter, const char *dst, gatt_connection_t* connection, int error, void* user_data) {
#if defined(WITH_PYTHON)
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;
@ -16,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 {
@ -43,27 +44,61 @@ void gattlib_connected_device_python_callback(void *adapter, const char *dst, ga
ON_ERROR:
PyGILState_Release(d_gstate);
}
#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);
// Mutex to ensure the handler is valid
g_rec_mutex_lock(&m_gattlib_mutex);
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);
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)
gattlib_connected_device_python_callback /* python_callback */,
#else
NULL, // No Python support. So we do not need to check the callback against Python callback
#endif
_gattlib_connected_device_thread /* thread_func */,
"gattlib_connected_device" /* thread_name */,
_connected_device_thread_args_allocator /* thread_args_allocator */,

View File

@ -6,7 +6,8 @@
#include "gattlib_internal.h"
void gattlib_disconnected_device_python_callback(gatt_connection_t* connection, void *user_data) {
#if defined(WITH_PYTHON)
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;
@ -27,23 +28,38 @@ void gattlib_disconnected_device_python_callback(gatt_connection_t* connection,
PyGILState_Release(d_gstate);
}
#endif
void gattlib_on_disconnected_device(gatt_connection_t* connection) {
if (connection->on_disconnection.callback.callback == NULL) {
// We do not have (anymore) a callback, nothing to do
GATTLIB_LOG(GATTLIB_DEBUG, "No callback for GATT disconnection.");
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;
}
// Check if we are using the Python callback, in case of Python argument we keep track of the argument to free them
// once we are done with the handler.
if ((gattlib_disconnection_handler_t)connection->on_disconnection.callback.callback == gattlib_disconnected_device_python_callback) {
connection->on_disconnection.python_args = connection->on_disconnection.user_data;
}
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.
if ((gattlib_disconnection_handler_t)connection->on_disconnection.callback.callback == gattlib_disconnected_device_python_callback) {
connection->on_disconnection.python_args = connection->on_disconnection.user_data;
}
#endif
// 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);
// 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);
}
// 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

@ -6,7 +6,8 @@
#include "gattlib_internal.h"
void gattlib_discovered_device_python_callback(void *adapter, const char* addr, const char* name, void *user_data) {
#if defined(WITH_PYTHON)
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;
@ -42,9 +43,10 @@ void gattlib_discovered_device_python_callback(void *adapter, const char* addr,
ON_ERROR:
PyGILState_Release(d_gstate);
}
#endif
struct gattlib_discovered_device_thread_args {
struct gattlib_adapter* gattlib_adapter;
struct _gattlib_adapter* gattlib_adapter;
char* mac_address;
char* name;
OrgBluezDevice1* device1;
@ -53,12 +55,33 @@ struct gattlib_discovered_device_thread_args {
static gpointer _gattlib_discovered_device_thread(gpointer data) {
struct gattlib_discovered_device_thread_args* args = data;
args->gattlib_adapter->ble_scan.discovered_device_callback.callback.discovered_device(
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(args->gattlib_adapter)) {
g_rec_mutex_unlock(&m_gattlib_mutex);
goto EXIT;
}
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
);
gattlib_adapter_unref(args->gattlib_adapter);
EXIT:
free(args->mac_address);
if (args->name != NULL) {
free(args->name);
@ -69,10 +92,10 @@ static gpointer _gattlib_discovered_device_thread(gpointer data) {
}
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 = malloc(sizeof(struct gattlib_discovered_device_thread_args));
struct gattlib_discovered_device_thread_args* thread_args = calloc(sizeof(struct gattlib_discovered_device_thread_args), 1);
thread_args->gattlib_adapter = gattlib_adapter;
thread_args->mac_address = strdup(org_bluez_device1_get_address(device1));
const char* device_name = org_bluez_device1_get_name(device1);
@ -84,10 +107,18 @@ 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
NULL, // No Python support. So we do not need to check the callback against Python callback
#endif
_gattlib_discovered_device_thread /* thread_func */,
"gattlib_discovered_device" /* thread_name */,
_discovered_device_thread_args_allocator /* thread_args_allocator */,

View File

@ -6,6 +6,7 @@
#include "gattlib_internal.h"
#if defined(WITH_PYTHON)
void gattlib_notification_device_python_callback(const uuid_t* uuid, const uint8_t* data, size_t data_length, void* user_data) {
struct gattlib_python_args* args = user_data;
char uuid_str[MAX_LEN_UUID_STR + 1];
@ -42,9 +43,10 @@ void gattlib_notification_device_python_callback(const uuid_t* uuid, const uint8
PyGILState_Release(d_gstate);
}
#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;
@ -54,11 +56,20 @@ 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(&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(&m_gattlib_mutex);
if (args->uuid != NULL) {
free(args->uuid);
args->uuid = NULL;
@ -69,10 +80,10 @@ 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) {
struct gattlib_notification_device_thread_args* thread_args = malloc(sizeof(struct gattlib_notification_device_thread_args));
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 = malloc(sizeof(uuid_t));
thread_args->uuid = calloc(sizeof(uuid_t), 1);
if (thread_args->uuid != NULL) {
memcpy(thread_args->uuid, uuid, sizeof(uuid_t));
}
@ -85,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

@ -11,7 +11,7 @@ void* gattlib_python_callback_args(PyObject* python_callback, PyObject* python_a
assert(python_callback != NULL);
assert(python_args != NULL);
struct gattlib_python_args* args = malloc(sizeof(struct gattlib_python_args));
struct gattlib_python_args* args = calloc(sizeof(struct gattlib_python_args), 1);
if (args == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to allocate Python arguments for Python callback.");
return 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,17 +233,27 @@ int gattlib_uuid_cmp(const uuid_t *uuid1, const uuid_t *uuid2) {
}
void gattlib_handler_free(struct gattlib_handler* handler) {
if (!gattlib_has_valid_handler(handler)) {
return;
}
// Reset callback to stop calling it after we stopped
handler->callback.callback = NULL;
#if defined(WITH_PYTHON)
if (handler->python_args != NULL) {
struct gattlib_python_args* args = handler->python_args;
Py_DECREF(args->callback);
Py_DECREF(args->args);
if (args->callback != NULL) {
Py_DECREF(args->callback);
}
if (args->args != NULL) {
Py_DECREF(args->args);
}
handler->python_args = NULL;
free(handler->python_args);
handler->python_args = NULL;
}
#endif
if (handler->thread_pool != NULL) {
g_thread_pool_free(handler->thread_pool, FALSE /* immediate */, TRUE /* wait */);
@ -163,23 +262,30 @@ void gattlib_handler_free(struct gattlib_handler* handler) {
}
bool gattlib_has_valid_handler(struct gattlib_handler* handler) {
return (handler->callback.callback != NULL);
return (handler != NULL) && (handler->callback.callback != NULL);
}
void gattlib_handler_dispatch_to_thread(struct gattlib_handler* handler, void (*python_callback)(),
GThreadFunc thread_func, const char* thread_name, void* (*thread_args_allocator)(va_list args), ...) {
GError *error = NULL;
if (handler->callback.callback == NULL) {
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;
}
#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.
if (handler->callback.callback == python_callback) {
handler->python_args = handler->user_data;
}
#endif
g_rec_mutex_unlock(&m_gattlib_mutex);
// We create a thread to ensure the callback is not blocking the mainloop
va_list args;
@ -201,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

@ -0,0 +1,204 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2024, Olivier Martin <olivier@labapart.org>
*/
#include "gattlib_internal.h"
const char* device_state_str[] = {
"NOT_FOUND",
"CONNECTING",
"CONNECTED",
"DISCONNECTING",
"DISCONNECTED"
};
static gint _compare_device_with_device_id(gconstpointer a, gconstpointer b) {
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(gattlib_adapter_t* adapter, const char* device_id) {
return g_slist_find_custom(adapter->devices, device_id, _compare_device_with_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) {
return NULL;
}
return (gattlib_device_t*)item->data;
}
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;
}
return device->state;
}
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(&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) {
//
// The device does not exist yet
//
if (new_state != NOT_FOUND) {
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;
goto EXIT;
}
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;
adapter->devices = g_slist_append(adapter->devices, device);
} else {
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state:%s: No state to set", device_id);
}
} else if (new_state == NOT_FOUND) {
//
// The device needs to be remove and free
//
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;
}
gattlib_device_t* device = item->data;
switch (device->state) {
case DISCONNECTED:
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state: Free device %p", 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");
ret = GATTLIB_UNEXPECTED;
break;
case CONNECTED:
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state: Connecting device needs to be removed - ignore it");
ret = GATTLIB_UNEXPECTED;
break;
case DISCONNECTING:
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state: Connecting device needs to be removed - ignore it");
ret = GATTLIB_UNEXPECTED;
break;
case NOT_FOUND:
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state: Not found device needs to be removed - ignore it");
ret = GATTLIB_UNEXPECTED;
break;
}
} else {
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_device_set_state:%s: Set state %s", device_id, device_state_str[new_state]);
gattlib_device_t* device = gattlib_device_get_device(adapter, device_id);
device->state = new_state;
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
static void _gattlib_device_free(gpointer data) {
gattlib_device_t* device = data;
switch (device->state) {
case DISCONNECTED:
gattlib_device_unref(device);
break;
default:
GATTLIB_LOG(GATTLIB_WARNING, "Memory of the BLE device '%s' has not been freed because in state %s",
device->device_id, device_state_str[device->state]);
}
}
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) {
gattlib_device_t* device = data;
bool* devices_are_disconnected_ptr = user_data;
if (device->state != DISCONNECTED) {
*devices_are_disconnected_ptr = false;
}
}
int gattlib_devices_are_disconnected(gattlib_adapter_t* adapter) {
bool devices_are_disconnected = true;
g_slist_foreach(adapter->devices, _gattlib_device_is_disconnected, &devices_are_disconnected);
return devices_are_disconnected;
}
#ifdef DEBUG
static void _gattlib_device_dump_state(gpointer data, gpointer user_data) {
gattlib_device_t* device = data;
GATTLIB_LOG(GATTLIB_DEBUG, "\t%s: %s", device->device_id, device_state_str[device->state]);
}
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;
}
GATTLIB_LOG(GATTLIB_DEBUG, "Device list:");
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,82 +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"
struct gattlib_python_args {
PyObject* callback;
PyObject* args;
};
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;
// In case of Python callback and argument, we keep track to free it when we stopped to discover BLE devices
void* python_args;
};
struct _gatt_connection_t {
void* context;
GMutex connection_mutex;
struct gattlib_handler on_connection;
struct gattlib_handler on_connection_error;
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);
#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

@ -42,13 +42,14 @@ void gattlib_log(int level, const char *format, ...) {
#else
result = PyEval_CallObject(m_logging_func, arglist);
#endif
Py_DECREF(arglist);
if (result == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Python notification handler has raised an exception.");
// We do not call GATTLIB_LOG(GATTLIB_ERROR, "") in case of error to avoid recursion
fprintf(stderr, "Failed to call Python logging function for this logging: %s\n", format);
PyErr_Print();
}
Py_DECREF(arglist);
PyGILState_Release(d_gstate);
}

View File

@ -33,16 +33,20 @@ int gattlib_mainloop(void* (*task)(void* arg), void *arg) {
};
GError* error;
GThread *task_thread = g_thread_try_new("gattlib_task", _execute_task, &execute_task_arg, &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,12 +73,13 @@ 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
${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_callback_disconnected_device.c
${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_callback_discovered_device.c
${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_callback_notification_device.c
${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_callback_python.c
${CMAKE_CURRENT_LIST_DIR}/../common/logging_backend/${GATTLIB_LOG_BACKEND}/gattlib_logging.c
${CMAKE_CURRENT_LIST_DIR}/../common/mainloop/gattlib_glib_mainloop.c
${CMAKE_CURRENT_BINARY_DIR}/org-bluez-adaptater1.c
@ -87,6 +88,10 @@ set(gattlib_SRCS gattlib.c
${CMAKE_CURRENT_BINARY_DIR}/org-bluez-gattdescriptor1.c
${CMAKE_CURRENT_BINARY_DIR}/org-bluez-gattservice1.c)
if (GATTLIB_PYTHON_INTERFACE)
list(APPEND gattlib_SRCS ${CMAKE_CURRENT_LIST_DIR}/../common/gattlib_callback_python.c)
endif()
if (BLUEZ_VERSION_MINOR GREATER 40)
list(APPEND gattlib_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org-bluez-battery1.c)
endif()

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,24 +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(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;
@ -62,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);
}
}
@ -130,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;
@ -152,82 +170,92 @@ 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;
}
get_device_path_from_mac(adapter_name, dst, object_path, sizeof(object_path));
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;
g_rec_mutex_lock(&m_gattlib_mutex);
gatt_connection_t* connection = calloc(sizeof(gatt_connection_t), 1);
if (connection == NULL) {
GATTLIB_LOG(GATTLIB_DEBUG, "gattlib_connect: Cannot allocate connection");
ret = GATTLIB_OUT_OF_MEMORY;
goto FREE_CONN_CONTEXT;
if (!gattlib_adapter_is_valid(adapter)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_connect: Adapter not valid");
ret = GATTLIB_ADAPTER_CLOSE;
goto EXIT;
}
connection->context = conn_context;
connection->on_connection.callback.connection_handler = connect_cb;
connection->on_connection.user_data = user_data;
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);
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[device->state]);
ret = GATTLIB_BUSY;
goto EXIT;
}
GATTLIB_LOG(GATTLIB_DEBUG, "Connect bluetooth device %s", dst);
device->connection.on_connection.callback.connection_handler = connect_cb;
device->connection.on_connection.user_data = user_data;
OrgBluezDevice1* device = org_bluez_device1_proxy_new_for_bus_sync(
GATTLIB_LOG(GATTLIB_DEBUG, "Connecting bluetooth device %s", dst);
// Mark the device has disconnected
gattlib_device_set_state(device->adapter, device->device_id, CONNECTING);
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);;
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
GATTLIB_LOG(GATTLIB_ERROR, "Failed to connect to DBus Bluez Device: %s", error->message);
g_error_free(error);
} else {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_connect: Failed to connect to DBus Bluez Device");
}
goto FREE_CONNECTION;
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
@ -236,43 +264,46 @@ int gattlib_connect(void *adapter, const char *dst,
ret = GATTLIB_NOT_FOUND;
} else if ((error->domain == 238) && (error->code == 60952)) {
GATTLIB_LOG(GATTLIB_ERROR, "Device '%s': %s", dst, error->message);
ret = GATTLIB_ERROR_TIMEOUT;
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);
}
g_error_free(error);
// Fail to connect. Mark the device has disconnected to be able to reconnect
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:
free(connection);
FREE_CONN_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;
}
@ -282,87 +313,129 @@ FREE_CONN_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 gattlib_connection_free(gattlib_connection_t* connection) {
char* device_id;
g_mutex_lock(&connection->connection_mutex);
conn_context = connection->context;
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;
}
free(conn_context->device_object_path);
if (conn_context->device != NULL) {
g_object_unref(conn_context->device);
conn_context->device = NULL;
// Stop the timeout for connection
if (connection->backend.connection_timeout_id) {
g_source_remove(connection->backend.connection_timeout_id);
connection->backend.connection_timeout_id = 0;
}
g_list_free_full(conn_context->dbus_objects, g_object_unref);
disconnect_all_notifications(conn_context);
if (connection->backend.device_object_path != NULL) {
free(connection->backend.device_object_path);
connection->backend.device_object_path = NULL;
}
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
//gattlib_handler_free(&connection->on_connection);
//gattlib_handler_free(&connection->on_disconnection);
//gattlib_handler_free(&connection->indication);
//gattlib_handler_free(&connection->notification);
// Note: We do not free adapter as it might still be used by other devices
free(connection->context);
connection->context = NULL;
g_mutex_unlock(&connection->connection_mutex);
// And finally free the connection
free(connection);
// Mark the device has disconnected
gattlib_device_set_state(connection->device->adapter, device_id, DISCONNECTED);
}
int gattlib_disconnect(gatt_connection_t* connection) {
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->connection_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;
if (!gattlib_connection_is_connected(connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "Cannot disconnect - connection is not in connected state (state=%s).",
device_state_str[connection->device->state]);
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_BUSY;
}
GATTLIB_LOG(GATTLIB_DEBUG, "Disconnect 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->device->adapter, connection->device->device_id, DISCONNECTING);
//Note: Signals and memory will be removed/clean on disconnction callback
// See _gattlib_clean_on_disconnection()
EXIT:
g_mutex_unlock(&connection->connection_mutex);
// 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 (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);
}
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) {
@ -380,9 +453,10 @@ int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_serv
count_max++;
}
gattlib_primary_service_t* primary_services = malloc(count_max * sizeof(gattlib_primary_service_t));
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++) {
@ -424,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) {
@ -451,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) {
@ -461,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
@ -470,13 +556,14 @@ int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_serv
count_max++;
}
gattlib_primary_service_t* primary_services = malloc(count_max * sizeof(gattlib_primary_service_t));
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));
@ -516,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;
}
@ -530,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");
@ -575,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
@ -640,9 +743,10 @@ int gattlib_discover_char_range(gatt_connection_t* connection, uint16_t start, u
}
gattlib_characteristic_t* characteristic_list = malloc(count_max * sizeof(gattlib_characteristic_t));
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++) {
@ -730,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");
@ -778,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 {
@ -792,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;
@ -824,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);
@ -840,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");
@ -862,13 +984,14 @@ int gattlib_discover_char_range(gatt_connection_t* connection, uint16_t start, u
count_max++;
}
gattlib_characteristic_t* characteristic_list = malloc(count_max * sizeof(gattlib_characteristic_t));
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));
@ -880,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;
@ -916,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,
@ -969,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;
@ -1004,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,9 +103,9 @@ struct gattlib_adapter *init_default_adapter(void) {
}
}
GDBusObjectManager *get_device_manager_from_adapter(struct gattlib_adapter *gattlib_adapter, GError **error) {
if (gattlib_adapter->device_manager) {
return 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;
}
//
@ -94,21 +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) {
if (gattlib_adapter->backend.device_manager == NULL) {
return NULL;
}
return gattlib_adapter->device_manager;
EXIT:
return gattlib_adapter->backend.device_manager;
}
static void device_manager_on_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(
@ -126,6 +146,7 @@ static void device_manager_on_device1_signal(const char* device1_path, struct ga
if (device1) {
const gchar *address = org_bluez_device1_get_address(device1);
int ret;
// Sometimes org_bluez_device1_get_address returns null addresses. If that's the case, early return.
if (address == NULL) {
@ -133,19 +154,23 @@ static void device_manager_on_device1_signal(const char* device1_path, struct ga
return;
}
// Check if the device is already part of the list
g_mutex_lock(&gattlib_adapter->ble_scan.discovered_devices_mutex);
GSList *item = g_slist_find_custom(gattlib_adapter->ble_scan.discovered_devices, address, (GCompareFunc)g_ascii_strcasecmp);
// First time this device is in the list
if (item == NULL) {
// Add the device to the list
gattlib_adapter->ble_scan.discovered_devices = g_slist_append(gattlib_adapter->ble_scan.discovered_devices, g_strdup(address));
}
g_mutex_unlock(&gattlib_adapter->ble_scan.discovered_devices_mutex);
g_rec_mutex_lock(&m_gattlib_mutex);
if ((item == NULL) || (gattlib_adapter->ble_scan.enabled_filters & GATTLIB_DISCOVER_FILTER_NOTIFY_CHANGE)) {
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
GATTLIB_LOG(GATTLIB_ERROR, "device_manager_on_added_device1_signal: Adapter not valid");
g_rec_mutex_unlock(&m_gattlib_mutex);
g_object_unref(device1);
return;
}
//TODO: Add support for connected device with 'gboolean org_bluez_device1_get_connected (OrgBluezDevice1 *object);'
// When the device is connected, we potentially need to initialize some attributes
ret = gattlib_device_set_state(gattlib_adapter, device1_path, DISCONNECTED);
if (ret == GATTLIB_SUCCESS) {
gattlib_on_discovered_device(gattlib_adapter, device1);
}
g_rec_mutex_unlock(&m_gattlib_mutex);
g_object_unref(device1);
}
}
@ -165,11 +190,24 @@ static void on_dbus_object_added(GDBusObjectManager *device_manager,
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: on_object_added: %s (has 'org.bluez.Device1')", object_path);
// It is a 'org.bluez.Device1'
device_manager_on_device1_signal(object_path, user_data);
device_manager_on_added_device1_signal(object_path, user_data);
g_object_unref(interface);
}
static void on_dbus_object_removed(GDBusObjectManager *device_manager,
GDBusObject *object,
gpointer user_data)
{
const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object));
gattlib_adapter_t* gattlib_adapter = user_data;
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: on_object_removed: %s", object_path);
// Mark the device has not present
gattlib_device_set_state(gattlib_adapter, object_path, NOT_FOUND);
}
static void
on_interface_proxy_properties_changed (GDBusObjectManagerClient *device_manager,
GDBusObjectProxy *object_proxy,
@ -179,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;
@ -201,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'
@ -214,123 +259,145 @@ 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;
}
const char* device_mac_address = org_bluez_device1_get_address(device1);
// Check if the device has been disconnected
GVariantDict dict;
g_variant_dict_init(&dict, changed_properties);
GVariant* connected = g_variant_dict_lookup_value(&dict, "Connected", NULL);
GVariant* rssi = g_variant_dict_lookup_value(&dict, "RSSI", NULL);
GVariant* has_rssi = g_variant_dict_lookup_value(&dict, "RSSI", NULL);
GVariant* has_manufacturer_data = g_variant_dict_lookup_value(&dict, "ManufacturerData", NULL);
g_mutex_lock(&gattlib_adapter->ble_scan.discovered_devices_mutex);
enum _gattlib_device_state old_device_state = gattlib_device_get_state(gattlib_adapter, proxy_object_path);
// Check if the device is already part of the list
GSList *found_device = g_slist_find_custom(gattlib_adapter->ble_scan.discovered_devices, device_mac_address, (GCompareFunc)g_ascii_strcasecmp);
if (connected && !g_variant_get_boolean(connected)) {
// The device has been disconnected. We will remove it from the list of discovered device.
// In case the device has been found again, it will be seen as a new device
GATTLIB_LOG(GATTLIB_INFO, "Device %s has been disconnected", device_mac_address);
if (found_device) {
gattlib_adapter->ble_scan.discovered_devices = g_slist_remove(gattlib_adapter->ble_scan.discovered_devices, found_device);
}
} else if (rssi) {
// First time this device is in the list
if (found_device == NULL) {
// Add the device to the list
gattlib_adapter->ble_scan.discovered_devices = g_slist_append(gattlib_adapter->ble_scan.discovered_devices, g_strdup(device_mac_address));
gattlib_on_discovered_device(gattlib_adapter, device1);
if (old_device_state == NOT_FOUND) {
if (has_rssi || has_manufacturer_data) {
int ret = gattlib_device_set_state(gattlib_adapter, proxy_object_path, DISCONNECTED);
if (ret == GATTLIB_SUCCESS) {
gattlib_on_discovered_device(gattlib_adapter, device1);
}
}
}
g_mutex_unlock(&gattlib_adapter->ble_scan.discovered_devices_mutex);
g_variant_dict_end(&dict);
g_object_unref(device1);
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
}
static void _stop_scan_loop_thread(struct gattlib_adapter *gattlib_adapter) {
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);
/**
* Function that waits for the end of the BLE scan
*
* It either called when we wait for BLE scan to complete or when we close the BLE adapter
*/
static void _wait_scan_loop_stop_scanning(gattlib_adapter_t* gattlib_adapter) {
g_mutex_lock(&m_gattlib_signal.mutex);
while (gattlib_adapter_is_scanning(gattlib_adapter)) {
g_cond_wait(&m_gattlib_signal.condition, &m_gattlib_signal.mutex);
}
}
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);
}
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_func(gpointer data) {
struct gattlib_adapter *gattlib_adapter = data;
static gboolean _stop_scan_on_timeout(gpointer data) {
gattlib_adapter_t* gattlib_adapter = data;
_stop_scan_loop_thread(gattlib_adapter);
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;
}
static void* _ble_scan_loop(void* args) {
struct gattlib_adapter *gattlib_adapter = args;
/**
* Thread that waits for the end of BLE scan that is triggered either by a timeout of the BLE scan
* or disabling the BLE scan
*/
static void* _ble_scan_loop_thread(void* args) {
gattlib_adapter_t* gattlib_adapter = args;
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,
_stop_scan_func, gattlib_adapter);
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.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;
}
@ -364,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) {
@ -384,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);
@ -396,25 +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);
G_CALLBACK(on_dbus_object_added),
adapter);
adapter->backend.ble_scan.removed_signal_id = g_signal_connect(G_DBUS_OBJECT_MANAGER(device_manager),
"object-removed",
G_CALLBACK(on_dbus_object_removed),
adapter);
// List for object changes to see if there are still devices around
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);
@ -426,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, 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, 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 */,
@ -492,27 +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)) {
GATTLIB_LOG(GATTLIB_DEBUG, "No discovery in progress. We skip discovery stopping.");
if (!org_bluez_adapter1_get_discovering(adapter->backend.adapter_proxy)) {
GATTLIB_LOG(GATTLIB_DEBUG, "No discovery in progress. We skip discovery stopping (1).");
goto EXIT;
} else if (!adapter->backend.ble_scan.is_scanning) {
GATTLIB_LOG(GATTLIB_DEBUG, "No discovery in progress. We skip discovery stopping (2).");
goto EXIT;
}
GATTLIB_LOG(GATTLIB_DEBUG, "Stop bluetooth scan.");
org_bluez_adapter1_call_stop_discovery_sync(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 {
@ -521,81 +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;
}
// Free discovered device list
g_mutex_lock(&gattlib_adapter->ble_scan.discovered_devices_mutex);
g_slist_foreach(gattlib_adapter->ble_scan.discovered_devices, (GFunc)g_free, NULL);
g_slist_free(gattlib_adapter->ble_scan.discovered_devices);
gattlib_adapter->ble_scan.discovered_devices = NULL;
g_mutex_unlock(&gattlib_adapter->ble_scan.discovered_devices_mutex);
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");
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;
}
free(gattlib_adapter);
if (adapter->name != NULL) {
free(adapter->name);
adapter->name = NULL;
}
gattlib_devices_free(adapter);
// Remove adapter from the global list
m_adapter_list = g_slist_remove(m_adapter_list, 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,35 +56,25 @@ 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;
// Internal attributes only needed during BLE scanning
struct {
// This list is used to stored discovered devices during BLE scan.
// The list is freed when the BLE scanning is completed.
GSList *discovered_devices;
GMutex discovered_devices_mutex;
int added_signal_id;
int changed_signal_id;
int removed_signal_id;
size_t ble_scan_timeout;
guint ble_scan_timeout_id;
GThread *scan_loop_thread; // Thread used to run the '_scan_loop()' when non-blocking
bool is_scanning;
GMutex scan_loop_mutex;
GCond scan_loop_cond;
uint32_t enabled_filters;
struct gattlib_handler discovered_device_callback;
} ble_scan;
};
@ -102,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 = malloc(sizeof(struct gattlib_notification_handle));
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;
@ -86,7 +86,7 @@ static void on_device_connect(void *adapter, const char *dst, gatt_connection_t*
free(characteristics);
disconnect_exit:
gattlib_disconnect(connection);
gattlib_disconnect(connection, false /* wait_disconnection */);
}
static void *ble_connect_device(void *arg) {
@ -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;
@ -117,7 +117,7 @@ static void ble_discovered_device(void *adapter, const char* addr, const char* n
printf("Discovered %s\n", addr);
}
connection = malloc(sizeof(struct connection_t));
connection = calloc(sizeof(struct connection_t), 1);
if (connection == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Failt to allocate connection.");
return;
@ -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

@ -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
@ -21,6 +21,8 @@
*
*/
#include <ctype.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
@ -30,35 +32,29 @@
#include "gattlib.h"
int main(int argc, char *argv[])
{
gatt_connection_t* connection;
#define BLE_SCAN_TIMEOUT 10
static const char* adapter_name = NULL;
// 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 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;
char uuid_str[MAX_LEN_UUID_STR + 1];
int ret, i;
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
openlog("gattlib_discover", LOG_CONS | LOG_NDELAY | LOG_PERROR, LOG_USER);
setlogmask(LOG_UPTO(LOG_INFO));
#endif
if (argc != 2) {
printf("%s <device_address>\n", argv[0]);
return 1;
}
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_LOG(GATTLIB_INFO, "Connected to bluetooth device '%s'", dst);
ret = gattlib_discover_primary(connection, &services, &services_count);
if (ret != GATTLIB_SUCCESS) {
GATTLIB_LOG(GATTLIB_ERROR, "Fail to discover primary services.");
return 1;
goto EXIT;
}
for (i = 0; i < services_count; i++) {
@ -73,7 +69,7 @@ int main(int argc, char *argv[])
ret = gattlib_discover_char(connection, &characteristics, &characteristics_count);
if (ret != GATTLIB_SUCCESS) {
GATTLIB_LOG(GATTLIB_ERROR, "Fail to discover characteristics.");
return 1;
goto EXIT;
}
for (i = 0; i < characteristics_count; i++) {
gattlib_uuid_to_string(&characteristics[i].uuid, uuid_str, sizeof(uuid_str));
@ -84,6 +80,84 @@ int main(int argc, char *argv[])
}
free(characteristics);
gattlib_disconnect(connection);
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 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) {
const char* reference_mac_address = user_data;
int ret;
if (stricmp(addr, reference_mac_address) != 0) {
return;
}
GATTLIB_LOG(GATTLIB_INFO, "Found bluetooth device '%s'", reference_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(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[])
{
char* device_address;
int ret;
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
openlog("gattlib_discover", LOG_CONS | LOG_NDELAY | LOG_PERROR, LOG_USER);
setlogmask(LOG_UPTO(LOG_INFO));
#endif
if (argc != 2) {
printf("%s <device_address>\n", argv[0]);
return 1;
}
device_address = argv[1];
ret = gattlib_mainloop(ble_task, device_address);
if (ret != GATTLIB_SUCCESS) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to create gattlib mainloop");
}
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

@ -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 _gatt_connection_t *connection = (struct _gatt_connection_t *)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);
@ -129,7 +129,7 @@ static void disconnect_io()
if (conn_state == STATE_DISCONNECTED)
return;
gattlib_disconnect(g_connection);
gattlib_disconnect(g_connection, false /* wait_disconnection */);
opt_mtu = 0;
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 _gatt_connection_t *gatt_connection = (struct _gatt_connection_t *)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;
@ -52,7 +52,7 @@ static void usage(char *argv[]) {
}
void int_handler(int dummy) {
gattlib_disconnect(m_connection);
gattlib_disconnect(m_connection, false /* wait_disconnection */);
exit(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

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

@ -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,6 +22,8 @@
*/
#include <assert.h>
#include <ctype.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
@ -31,66 +33,38 @@
#include "gattlib.h"
typedef enum { READ, WRITE} operation_t;
operation_t g_operation;
#define BLE_SCAN_TIMEOUT 10
static uuid_t g_uuid;
long int value_data;
static struct {
char *adapter_name;
char* mac_address;
enum { READ, WRITE } operation;
uuid_t uuid;
long int value_data;
} m_argument;
// 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> <read|write> <uuid> [<hex-value-to-write>]\n", argv[0]);
}
int main(int argc, char *argv[]) {
int i, ret;
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;
gatt_connection_t* connection;
if ((argc != 4) && (argc != 5)) {
usage(argv);
return 1;
}
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
openlog("gattlib_read_write", LOG_CONS | LOG_NDELAY | LOG_PERROR, LOG_USER);
setlogmask(LOG_UPTO(LOG_INFO));
#endif
if (strcmp(argv[2], "read") == 0) {
g_operation = READ;
} else if ((strcmp(argv[2], "write") == 0) && (argc == 5)) {
g_operation = WRITE;
if ((strlen(argv[4]) >= 2) && (argv[4][0] == '0') && ((argv[4][1] == 'x') || (argv[4][1] == 'X'))) {
value_data = strtol(argv[4], NULL, 16);
} else {
value_data = strtol(argv[4], NULL, 0);
}
printf("Value to write: 0x%lx\n", value_data);
} else {
usage(argv);
return 1;
}
if (gattlib_string_to_uuid(argv[3], strlen(argv[3]) + 1, &g_uuid) < 0) {
usage(argv);
return 1;
}
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;
}
if (g_operation == READ) {
if (m_argument.operation == READ) {
uint8_t *buffer = NULL;
ret = gattlib_read_char_by_uuid(connection, &g_uuid, (void **)&buffer, &len);
ret = gattlib_read_char_by_uuid(connection, &m_argument.uuid, (void **)&buffer, &len);
if (ret != GATTLIB_SUCCESS) {
char uuid_str[MAX_LEN_UUID_STR + 1];
gattlib_uuid_to_string(&g_uuid, uuid_str, sizeof(uuid_str));
gattlib_uuid_to_string(&m_argument.uuid, uuid_str, sizeof(uuid_str));
if (ret == GATTLIB_NOT_FOUND) {
GATTLIB_LOG(GATTLIB_ERROR, "Could not find GATT Characteristic with UUID %s. "
@ -102,18 +76,18 @@ int main(int argc, char *argv[]) {
}
printf("Read UUID completed: ");
for (i = 0; i < len; i++) {
for (uintptr_t i = 0; i < len; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
gattlib_characteristic_free_value(buffer);
} else {
ret = gattlib_write_char_by_uuid(connection, &g_uuid, &value_data, sizeof(value_data));
ret = gattlib_write_char_by_uuid(connection, &m_argument.uuid, &m_argument.value_data, sizeof(m_argument.value_data));
if (ret != GATTLIB_SUCCESS) {
char uuid_str[MAX_LEN_UUID_STR + 1];
gattlib_uuid_to_string(&g_uuid, uuid_str, sizeof(uuid_str));
gattlib_uuid_to_string(&m_argument.uuid, uuid_str, sizeof(uuid_str));
if (ret == GATTLIB_NOT_FOUND) {
GATTLIB_LOG(GATTLIB_ERROR, "Could not find GATT Characteristic with UUID %s. "
@ -127,6 +101,102 @@ int main(int argc, char *argv[]) {
}
EXIT:
gattlib_disconnect(connection);
return ret;
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 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;
if (stricmp(addr, m_argument.mac_address) != 0) {
return;
}
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;
if ((argc != 4) && (argc != 5)) {
usage(argv);
return 1;
}
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
openlog("gattlib_read_write", LOG_CONS | LOG_NDELAY | LOG_PERROR, LOG_USER);
setlogmask(LOG_UPTO(LOG_INFO));
#endif
m_argument.adapter_name = NULL;
m_argument.mac_address = argv[1];
if (strcmp(argv[2], "read") == 0) {
m_argument.operation = READ;
} else if ((strcmp(argv[2], "write") == 0) && (argc == 5)) {
m_argument.operation = WRITE;
if ((strlen(argv[4]) >= 2) && (argv[4][0] == '0') && ((argv[4][1] == 'x') || (argv[4][1] == 'X'))) {
m_argument.value_data = strtol(argv[4], NULL, 16);
} else {
m_argument.value_data = strtol(argv[4], NULL, 0);
}
printf("Value to write: 0x%lx\n", m_argument.value_data);
} else {
usage(argv);
return 1;
}
if (gattlib_string_to_uuid(argv[3], strlen(argv[3]) + 1, &m_argument.uuid) < 0) {
usage(argv);
return 1;
}
ret = gattlib_mainloop(ble_task, NULL);
if (ret != GATTLIB_SUCCESS) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to create gattlib mainloop");
}
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

@ -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;
@ -104,7 +104,7 @@ void *connect_ble(void *arg) {
}
EXIT:
gattlib_disconnect(connection);
gattlib_disconnect(connection, false /* wait_disconnection */);
g_main_loop_quit(m_main_loop);
return NULL;

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,10 +4,18 @@
# 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__ #pylint: disable=import-error
except: #pylint: disable=bare-except
pass
logger = logging.getLogger(__name__)
try:
@ -18,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:
@ -44,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)]
@ -56,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)]
@ -69,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)]
@ -81,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),
@ -93,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
@ -132,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);
# int gattlib_disconnect(gattlib_connection_t* connection, bool wait_disconnection);
gattlib_disconnect = gattlib.gattlib_disconnect
gattlib_disconnect.argtypes = [c_void_p]
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)]
@ -166,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,45 +57,46 @@ 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 0
"""Open adapter."""
with self._lock:
if self._is_opened:
return
self._adapter = c_void_p(None)
ret = gattlib_adapter_open(self._name, byref(self._adapter))
if ret == 0:
self._is_opened = True
if self._name is None:
self._name = gattlib_adapter_get_name(self._adapter)
self._lock.release()
return ret
self._adapter = c_void_p(None)
ret = gattlib_adapter_open(self._name, byref(self._adapter))
if ret == 0:
self._is_opened = True
if self._name is None:
self._name = gattlib_adapter_get_name(self._adapter)
else:
handle_return(ret)
def close(self):
self._lock.acquire()
ret = 0
if self._adapter:
ret = gattlib.gattlib_adapter_close(self._adapter)
self._is_opened = False
self._adapter = None
self._lock.release()
return ret
"""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
# 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
@ -111,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:
@ -160,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()
@ -217,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")
@ -229,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):
self._connection_lock.acquire()
try:
def disconnect(self, wait_disconnection: bool = False):
"""Disconnect connected device."""
with self._connection_lock:
if self._connection:
ret = gattlib_disconnect(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,15 +4,21 @@
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
"""Gattlib Exceptions"""
GATTLIB_SUCCESS = 0
GATTLIB_INVALID_PARAMETER = 1
GATTLIB_NOT_FOUND = 2
GATTLIB_ERROR_TIMEOUT = 3
GATTLIB_TIMEOUT = 3
GATTLIB_OUT_OF_MEMORY = 4
GATTLIB_NOT_SUPPORTED = 5
GATTLIB_DEVICE_ERROR = 6
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
@ -21,30 +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):
"""Gattlib busy exception."""
class Unexpected(GattlibException):
"""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
@ -53,40 +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_ERROR_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_ERROR_MODULE_MASK) == GATTLIB_ERROR_DBUS:
if ret == GATTLIB_BUSY:
raise Busy()
if ret == GATTLIB_UNEXPECTED:
raise Unexpected()
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,7 +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)
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):
@ -70,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
@ -149,12 +174,12 @@ class CMakeBuild(build_ext):
setup(
name='gattlib-py',
version=os.environ.get('GATTLIB_PY_VERSION', git_version),
version=package_version,
author="Olivier Martin",
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

@ -11,6 +11,7 @@
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
#include <bluetooth/bluetooth.h>
@ -31,6 +32,11 @@ extern "C" {
#define ATT_MAX_MTU ATT_MAX_VALUE_LEN
#endif
/**
* Gattlib constants
*/
#define GATTLIB_DISCONNECTION_WAIT_TIMEOUT_SEC 5
/**
* @name Gattlib errors
*/
@ -38,13 +44,16 @@ extern "C" {
#define GATTLIB_SUCCESS 0
#define GATTLIB_INVALID_PARAMETER 1
#define GATTLIB_NOT_FOUND 2
#define GATTLIB_ERROR_TIMEOUT 3
#define GATTLIB_TIMEOUT 3
#define GATTLIB_OUT_OF_MEMORY 4
#define GATTLIB_NOT_SUPPORTED 5
#define GATTLIB_DEVICE_ERROR 6
#define GATTLIB_DEVICE_NOT_CONNECTED 7
#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
@ -132,7 +141,9 @@ extern "C" {
#define EDDYSTONE_TYPE_EID 0x30
//@}
/**
* Log level
*/
#define GATTLIB_ERROR 0
#define GATTLIB_WARNING 1
#define GATTLIB_INFO 2
@ -140,8 +151,9 @@ extern "C" {
#define GATTLIB_LOG(level, args...) if (level <= GATTLIB_LOG_LEVEL) { gattlib_log(level, args); }
typedef struct _gatt_connection_t 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
@ -152,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);
/**
@ -160,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
@ -170,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
@ -180,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);
/**
@ -199,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
@ -230,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
@ -239,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
@ -251,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
@ -270,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);
/**
@ -290,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);
/**
@ -309,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);
/**
@ -319,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
@ -328,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
@ -343,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);
@ -351,11 +371,18 @@ int gattlib_connect(void *adapter, const char *dst,
/**
* @brief Function to disconnect the GATT connection
*
* @param connection Active GATT connection
* @note: If a callback has been registered by gattlib_register_on_disconnect() then it will be called
* when the device will have signaled is disconnected.
*
* @param connection Active GATT connection
* @param wait_disconnection If false gattlib_disconnect does not wait for the device to confirm it has been
* disconnected and return immediately.
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
* @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);
int gattlib_disconnect(gattlib_connection_t* connection, bool wait_disconnection);
/**
* @brief Function to register a callback on GATT disconnection
@ -366,7 +393,7 @@ int gattlib_disconnect(gatt_connection_t* connection);
*
* @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
@ -407,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
@ -422,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
@ -435,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
@ -448,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
@ -459,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
@ -473,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
@ -484,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
@ -503,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
@ -515,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
@ -527,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
@ -541,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()`
@ -552,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()`
@ -561,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
@ -573,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
@ -583,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
@ -593,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
@ -603,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
@ -613,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
@ -624,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
@ -635,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
/**
@ -646,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
@ -661,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
@ -669,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
@ -686,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

@ -0,0 +1,32 @@
#
# GattLib - GATT Library
#
# Copyright (C) 2016-2024 Olivier Martin <olivier@labapart.org>
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)
pkg_search_module(GATTLIB REQUIRED gattlib)
pkg_search_module(PCRE REQUIRED libpcre)
pkg_search_module(GLIB REQUIRED glib-2.0)
add_executable(test_continuous_connection test_continuous_connection.c)
target_include_directories(test_continuous_connection PRIVATE ${GLIB_INCLUDE_DIRS})
target_link_libraries(test_continuous_connection ${GATTLIB_LIBRARIES} ${GATTLIB_LDFLAGS} ${PCRE_LIBRARIES} ${GLIB_LDFLAGS} pthread)

View File

@ -0,0 +1,170 @@
/*
*
* GattLib - GATT Library
*
* Copyright (C) 2021-2024 Olivier Martin <olivier@labapart.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <assert.h>
#include <ctype.h>
#include <pthread.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/queue.h>
#include <glib.h>
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
#include <syslog.h>
#endif
#include "gattlib.h"
#define BLE_CONNECT_LOOP_COUNT 20
#define BLE_SCAN_TIMEOUT 180
static const char* adapter_name;
static const char* reference_mac_address;
// Declaration of thread condition variable
static struct {
pthread_cond_t condition;
pthread_mutex_t lock;
bool value;
} m_connection_terminated;
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) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to connect to device '%s': Error %d", reference_mac_address, error);
goto EXIT;
}
ret = gattlib_disconnect(connection, true /* wait_disconnection */);
assert(ret == 0);
GATTLIB_LOG(GATTLIB_DEBUG, "Bluetooth device '%s' should be disconnected.", reference_mac_address);
EXIT:
pthread_mutex_lock(&m_connection_terminated.lock);
m_connection_terminated.value = true;
pthread_cond_signal(&m_connection_terminated.condition);
pthread_mutex_unlock(&m_connection_terminated.lock);
}
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;
if (stricmp(addr, reference_mac_address) != 0) {
return;
}
GATTLIB_LOG(GATTLIB_INFO, "Found bluetooth device '%s'", reference_mac_address);
for (uintptr_t i = 0; i < BLE_CONNECT_LOOP_COUNT; i++) {
GATTLIB_LOG(GATTLIB_INFO, "Connecting to the bluetooth device '%s' %d/%d", addr, i+1, BLE_CONNECT_LOOP_COUNT);
memset(&m_connection_terminated, 0, sizeof(m_connection_terminated));
// Try to connect while the connection is busy.
while (true) {
ret = gattlib_connect(adapter, addr, GATTLIB_CONNECTION_OPTIONS_NONE, on_device_connect, adapter);
if (ret != GATTLIB_BUSY) {
break;
}
GATTLIB_LOG(GATTLIB_DEBUG, "Failed to connect to the bluetooth device '%s' because busy. Try again", addr);
g_usleep(100);
}
if (ret != GATTLIB_SUCCESS) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to connect to the bluetooth device '%s': %d", addr, ret);
continue;
}
// Wait for the device to be connected
pthread_mutex_lock(&m_connection_terminated.lock);
while (!m_connection_terminated.value) {
pthread_cond_wait(&m_connection_terminated.condition, &m_connection_terminated.lock);
}
pthread_mutex_unlock(&m_connection_terminated.lock);
}
}
static void* ble_task(void* arg) {
gattlib_adapter_t* adapter;
int ret;
ret = gattlib_adapter_open(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, NULL /* user_data */);
if (ret) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to scan.");
goto EXIT;
}
gattlib_adapter_scan_disable(adapter);
GATTLIB_LOG(GATTLIB_INFO, "Scan completed");
EXIT:
gattlib_adapter_close(adapter);
return NULL;
}
int main(int argc, const char *argv[]) {
int ret;
if (argc == 1) {
adapter_name = NULL;
} else if (argc == 2) {
reference_mac_address = argv[1];
} else if (argc == 3) {
adapter_name = argv[1];
reference_mac_address = argv[2];
} else {
printf("%s [<bluetooth-adapter>] mac_address\n", argv[0]);
return 1;
}
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
openlog("gattlib_ble_scan", LOG_CONS | LOG_NDELAY | LOG_PERROR, LOG_USER);
setlogmask(LOG_UPTO(LOG_INFO));
#endif
ret = gattlib_mainloop(ble_task, NULL);
if (ret != GATTLIB_SUCCESS) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to create gattlib mainloop");
}
return ret;
}