Compare commits

...

166 Commits
dev ... master

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
Olivier Martin 2a46780e96 Fix gattlib and examples 2024-03-18 00:19:44 +01:00
Olivier Martin b2c4094cb6 gattlib-py: Fix some memory leaks 2024-03-13 13:52:57 +01:00
Olivier Martin d2fb01d85e Fix gattlib connection release on disconnection 2024-03-13 13:52:57 +01:00
Olivier Martin 98833bf7ce gattlib-py/gattlib/mainloop: Ensure we are not overwriting the mainloop 2024-03-13 13:52:57 +01:00
Olivier Martin 6823c02892 dbus: Improve logging 2024-03-13 13:52:57 +01:00
Olivier Martin 7925aa6a38 Fix compilation warnings about enum type 2024-03-12 20:38:31 +01:00
Olivier Martin 2edc8f2620 Add support to enable Address Sanitizer 2024-03-12 20:37:53 +01:00
Olivier Martin 36e0cb4934 Add more debug logs 2024-03-07 23:58:35 +01:00
Olivier Martin 24ab0ea95f Reduce compilation warnings 2024-03-07 23:57:58 +01:00
Olivier Martin e26e9f0b7a Fix some compiler warnings 2024-03-07 08:53:42 +01:00
Olivier Martin 6e718253b0 adapter: Add support to avoid double free of BLE adapter 2024-03-06 15:03:56 +01:00
Olivier Martin ea1fe15857 gattlib: Catch connectio timeout 2024-03-05 22:46:38 +01:00
Olivier Martin 00cbc1ab87 common: Set pointer to NULL after freeing them 2024-02-27 11:28:49 +01:00
Olivier Martin d2702050ec gattlib-py: Add support for loading 'libgattlib.so' from different location 2024-02-26 23:19:10 +01:00
Olivier Martin f4cc321a90 dbus/gattlib_adapter: Stop BLE scan before waiting for the loop to stop 2024-02-26 23:16:35 +01:00
Olivier Martin 6321d28d25 gattlib-py/setup.py: Add gattlib-py dependency 2024-02-23 10:20:21 +01:00
Olivier Martin f0426ae3e3 gattlib-py/gattlib: Fix loading native library 2024-02-22 22:26:43 +01:00
Olivier Martin ebd163f1dc ci: Push tagged gattlib to Pypi repo 2024-02-22 21:33:19 +01:00
Olivier Martin 01ea87aa16 Disable C examples as they are not ported yet to new gattlib_connect() 2024-02-22 19:54:43 +01:00
Olivier Martin f38f73a9a4 gattlib: Change gattlib_connect to better use event loop 2024-02-22 19:54:43 +01:00
Olivier Martin e554dec3dc gattlib_adapter: Improve/Fix BLE scan 2024-02-22 19:54:43 +01:00
Olivier Martin 100c5d5f69 gattlib_adapter: Introduce wait_scan_loop_stop_scanning 2024-02-22 19:54:43 +01:00
Olivier Martin 860f7f5b61 ci: Fix tagged release 2024-02-22 19:30:58 +01:00
Olivier Martin 13bd692bb4 ci: Generate Python package with cibuildwheel 2024-02-22 13:28:22 +01:00
Olivier Martin 18e1658d6f ci: Fix build 2024-02-22 09:30:22 +01:00
Olivier Martin d3d9600114 cmake: Specify cmake version + Fix finding Python development module 2024-02-22 09:30:22 +01:00
Olivier Martin 5f5cb5bd12 common/logging_backend: Introduce Python backend 2024-02-22 09:30:22 +01:00
Olivier Martin 6751a17cee .github/workflows/github-actions.yml: Fix Pypi publishing 2024-02-21 18:01:19 +01:00
Olivier Martin 57c99c6038 .github/workflows/github-actions.yml: Update actions/checkout 2024-02-21 17:53:59 +01:00
Olivier Martin b8268c4cb5 ci: Generate Python package 2024-02-21 17:45:09 +01:00
Olivier Martin fbd65421d0 Ensure adapter is present 2024-02-21 00:28:08 +01:00
Olivier Martin 61043afd98 gattlib-py: Use logger (instead of logging) 2024-02-21 00:28:08 +01:00
Olivier Martin aacc53c511 Fix warnings from release build 2024-02-21 00:28:08 +01:00
Olivier Martin 7d5748cb0d ci: Script to generate Python package 2024-02-21 00:28:08 +01:00
Olivier Martin 33a634a535 gattlib-py/setup.py: Build C files along Python module 2024-02-21 00:28:08 +01:00
Olivier Martin 2f83e85eee dbus: Remove event_thread 2024-02-19 18:00:52 +01:00
Olivier Martin 1530184776 gattlib_adapter: Add support to reconnect disconnected device 2024-02-19 18:00:52 +01:00
Olivier Martin b28a0422f0 dbus: Add support to remove device property signal 2024-02-19 18:00:52 +01:00
Olivier Martin b193543fb0 gattlib/mainloop: Move mainloop to Python to ease support 2024-02-19 18:00:52 +01:00
Olivier Martin 7a8d53d817 gattlib-py/gattlib/exception: Print 'le-connection-abort-by-local' on related exception 2024-02-19 18:00:52 +01:00
Olivier Martin 7922810016 gattlib-py/gattlib/device: Fix logging 2024-02-19 18:00:52 +01:00
Olivier Martin 8e5412a1a9 gattlib-py: Ensure BLE adapter is opened 2024-02-19 18:00:52 +01:00
Olivier Martin c4e4b8fa5a dbus/gattlib_notification: Use lookup instead of looping through variant keys 2024-02-19 18:00:52 +01:00
Olivier Martin 76fb44643f gattlib-py/setup.py: Remove Travis CI support 2024-02-19 18:00:52 +01:00
Olivier Martin 0a868a506b Added a public function to ease the use of GLib mainloop 2024-02-19 18:00:52 +01:00
Olivier Martin 4c5f35f15f error: Introduce error module to catch DBUS error details 2024-02-15 22:00:06 +01:00
Olivier Martin ad7aa1899e on_handle_device_property_change: Logging changes 2024-02-15 22:00:06 +01:00
Olivier Martin f894c8e23a gattlib_disconnect: Add mutex to avoid double free 2024-02-15 22:00:06 +01:00
Olivier Martin 7f6979c82b gatt_notification: Use thread pool of one thread to ensure serial processing of notification 2024-02-15 22:00:06 +01:00
Olivier Martin db8aee543b dbus/on_handle_characteristic_indication: Fix logging 2024-02-15 22:00:06 +01:00
Olivier Martin 8065d12dac python: Various fixes 2024-02-15 22:00:06 +01:00
Olivier Martin 011f4e4c4a gattlib_disconnect: Call on_disconnect callback 2024-02-15 22:00:06 +01:00
Olivier Martin 0fde4ff82d gattlib_adapter_close: Ensure BLE scan is disabled 2024-02-15 22:00:06 +01:00
Olivier Martin 9a9e49edcd dbus/on_interface_proxy_properties_changed: Check device_manager is not NULL (could happen on disconnection) 2024-02-15 22:00:06 +01:00
Olivier Martin 959ee55b61 gattlib: Change gattlib_register_(on_disconnect|notification|indication) 2024-02-15 22:00:06 +01:00
Olivier Martin 118dc961d6 gattlib_on_disconnected_device: Make callback synchronous 2024-02-15 22:00:06 +01:00
Olivier Martin 2518348023 gattlib: Introduce 'gattlib_adapter_get_name()' 2024-02-12 22:35:46 +01:00
Olivier Martin 884904a3c0 dbus/gattlib_adapter: Fix 'gattlib_adapter_close()' 2024-02-12 22:35:46 +01:00
Olivier Martin 1d80061bf2 python: Fix callback back to native gattlib 2024-02-12 22:04:57 +01:00
Olivier Martin ec9e5cd38a dbus: Fix build 2024-02-12 09:52:07 +01:00
Olivier Martin d5aa8d6468 Fix logging for on_interface_proxy_properties_changed() 2024-02-12 00:13:07 +01:00
Olivier Martin 306acf8483 Fix 'discovered_devices' list (set NULL after free + mutex) 2024-02-12 00:13:07 +01:00
Olivier Martin 3c9b0eaa1c Introduce 'GATTLIB_ERROR_TIMEOUT' error 2024-02-12 00:13:07 +01:00
Olivier Martin 028dfef5fc python: Update deprecated calls 2024-02-12 00:13:07 +01:00
Olivier Martin a41061c1d4 Various fixes/logging/features 2024-02-12 00:11:45 +01:00
Olivier Martin 642556f8b9 gattlib-py/gattlib/adapter: Update comment 2024-02-12 00:11:45 +01:00
Olivier Martin 96407ad763 gattlib-py/examples/ble_scan: Fix example 2024-02-12 00:11:45 +01:00
Olivier Martin 6d271f98b6 gattlib-py: Document how to develop 'gattlib-py' 2024-02-12 00:11:45 +01:00
Olivier Martin 33a8a27592 gattlib-py: Fix/Handle returned error code 2022-05-16 21:44:31 +02:00
Olivier Martin 0369342fd4 Introduce 'gattlib_characteristic_free_value()' to release memory allocated after reading GATT characteristic 2022-05-16 21:44:31 +02:00
Olivier Martin 3ac5707c95 dbus: Fix battery value reading 2022-05-16 21:44:31 +02:00
Olivier Martin c1a3c02154 dbus: Add battery characteristics to the list of GATT characteristic 2022-05-16 21:44:31 +02:00
Olivier Martin 20f2d5facf gattlib-py/device: Add support to unregister GATT notification 2022-05-16 21:44:31 +02:00
Olivier Martin 59820e2cad gattlib-py/device: Re-enable RSSI 2022-05-16 21:44:31 +02:00
Olivier Martin ac1f3d44d5 gattlib-py/exception: Handle -EINVAL 2022-05-16 21:44:31 +02:00
Olivier Martin bae7df3ee0 gattlib-py/adapter: Add support to pass UUID for the Advertisement filter 2022-05-16 21:44:31 +02:00
Olivier Martin b5a785e4b6 dbus: Refactore gattlib_adapter_scan_enable_with_filter() to also introduce the non-blocking version 2022-05-16 21:44:31 +02:00
Olivier Martin 75fda5df84 gattlib-py: Rename 'id' attribute into 'mac_address' 2022-05-11 20:29:07 +02:00
Olivier Martin 2570850046 common/gattlib_common: Release Python PyObject 2022-05-11 20:29:07 +02:00
Olivier Martin d9c18b93a3 dbus/gattlib_discover_primary: Ensure a valid connection pointer is given 2022-05-11 20:29:07 +02:00
Olivier Martin 0719dcc31d gattlib-py/examples/ble_scan: Explicit action (logging) 2022-05-11 20:29:07 +02:00
Olivier Martin 5c87eda925 build: Disable doc generation by default 2022-05-11 20:29:07 +02:00
wurong bdc273fe0a fix mem leak. 2022-05-11 15:53:48 +02:00
tswaehn b82be455b4 fix: sigsev when service is null 2022-03-10 22:00:34 +01:00
tswaehn 5badee94b9 fix: sigsev when service property returns null 2022-03-10 22:00:34 +01:00
VL-80 2448da5410 bluez5: Fix potential null pointer dereference
d->attrId was called before d was checked for being a valid pointer.
2022-01-30 21:43:05 +01:00
VL-80 85acbd8c88 bluez4: Fix potential null pointer dereference
d->attrId was called before d was checked for being a valid pointer.
2022-01-30 21:43:05 +01:00
VL-80 5b8893c647 bluez/gattlib_adapter.c: Fix bug
The checking of the result of the hci_open_dev() function needs to be done on the derefenced value, not the pointer (which can not be negative).
2022-01-30 21:43:05 +01:00
tswaehn c2c6b2f17b fix: device_manager not found => SIGSEV 2022-01-30 21:41:17 +01:00
tswaehn deee3766e1 fix: gattlib_adapter not found => SIGSEV 2022-01-30 21:41:17 +01:00
Flaviu Tamas bb90b55dde Flush uart after getting message
The internal stdout buffering causes problems when we're trying to get
data, but the data doesn't have any newlines or anything like that.
We're forced to wait for forever until the buffer gets filled up and
output to the console.

This solves that by flushing immediately after each message.
2021-12-03 09:39:07 +01:00
Olivier Martin 1afaa8b460 Introduce Gattlib logging backend 2021-10-18 16:46:45 +02:00
Olivier Martin 809a10a289 dbus/gattlib_notification: Fix warning 2021-10-17 22:28:22 +02:00
Olivier Martin e93504b91f cmake: Update 'cmake_minimum_required' as 2.6 is getting deprecated 2021-10-17 22:22:05 +02:00
Olivier Martin e2b189d226 handle_dbus_gattcharacteristic_from_path: Ensure 'characteristic' is unref 2021-10-17 22:15:27 +02:00
Peter Rosin f90a95bcff avoid calling g_object_unref on the pointers that have just been free()d
"Steal" the notified_characteristics pointer while at it so that it
does not remain and point to a stale list.
2021-10-17 22:07:06 +02:00
Olivier Martin fa54ae42cc handle_dbus_gattcharacteristic_from_path: Ensure 'characteristic_uuid_str' is not NULL 2021-09-15 22:44:28 +02:00
Olivier Martin c3abb7eb6c dbus/gattlib.c: Fix copyright 2021-09-01 10:42:26 +02:00
Olivier Martin f4ecc64d29 Clarify and update license 2021-09-01 00:04:00 +02:00
Кирилл Зимников 2c38df5f30 Fix memory problems 2021-08-31 23:19:37 +02:00
Olivier Martin db44d7a99d ci: Add support for github actions 2021-06-09 17:53:08 +02:00
vlefebvre d66e268865 fix glib CRITICAL warning
In the case when we deal with the timeout/error result, the resource
has lready been unref

Signed-off-by: vlefebvre <valentin.lefebvre@iot.bzh>
2021-05-28 22:29:40 +02:00
vlefebvre f3b5b295bc fix hard path during installation of gattlib
Using CMAKE macro to install correctly the library gattlib. In
aarch64 it will install into /usr/lib64 dir insteed of /usr/lib.

Signed-off-by: vlefebvre <valentin.lefebvre@iot.bzh>
2021-05-28 22:29:08 +02:00
0xloem edb5862f96 make notification example a little more general 2021-05-23 13:46:21 +02:00
Kevin Dewald 15210d1c35 Fixed recently introduced double-free error. 2021-04-15 22:39:10 +02:00
chenbin 3f85abd606 fix memory leak. 2021-04-13 22:49:25 +02:00
Kevin Dewald b137160afc Fixed handling of null MAC addresses during scanning. 2021-04-05 21:45:27 +02:00
Kevin Dewald 9451e19426 Add verification for null mac address. 2021-03-19 12:46:32 +01:00
Kevin Dewald 321c37dc57 Removed extra print statement. 2021-03-04 00:48:06 +01:00
Kevin Dewald 49790a92bf Fixed bug where notifications will be enabled but no events will be received. 2021-03-03 13:04:50 +01:00
Kevin Dewald fee6603fa3 Fixed uninitialized memory error. 2021-03-03 13:01:30 +01:00
Kevin Dewald 3c917fe683 Fixed missing characteristic detection when discovering services. 2021-03-02 22:23:45 +01:00
Olivier Martin d63b7ccb27 examples/nordic_uart: Use 'gattlib_notification_start()' 2021-02-01 16:48:02 +01:00
Olivier Martin b8f60de4e5 dbus/gattlib_notification: Print which UUID cannot be found 2021-02-01 16:46:25 +01:00
79 changed files with 6153 additions and 1713 deletions

118
.github/workflows/github-actions.yml vendored Normal file
View File

@ -0,0 +1,118 @@
name: gattlib
on: [push]
jobs:
build-debug:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt install libbluetooth-dev
- 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 -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 -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
steps:
- uses: actions/checkout@v4
- run: sudo apt install -y libbluetooth-dev
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
- run: ./ci/generate-python-package.sh
if: startsWith(github.ref, 'refs/tags/')
env:
CIBW_BEFORE_BUILD_LINUX: "sh ci/install-bluez.sh"
GATTLIB_PY_VERSION: '${{github.ref_name}}'
CIBW_ENVIRONMENT_PASS_LINUX: "GATTLIB_PY_VERSION"
#TODO: To support 'musllinux', we need to replace 'yum install' by 'apk install' - and detect which platform we are
CIBW_SKIP: "*-musllinux_*"
- run: ./ci/generate-python-package.sh
if: ${{ ! startsWith(github.ref, 'refs/tags/') }}
env:
CIBW_BEFORE_BUILD_LINUX: "sh ci/install-bluez.sh"
GATTLIB_PY_VERSION: '0.0.1'
CIBW_ENVIRONMENT_PASS_LINUX: "GATTLIB_PY_VERSION"
#TODO: To support 'musllinux', we need to replace 'yum install' by 'apk install' - and detect which platform we are
CIBW_SKIP: "*-musllinux_*"
- name: Archive Python packages
uses: actions/upload-artifact@v4
with:
name: python-binary-packages
path: dist/*
# publish-python-packages:
# needs:
# - generate-python-binary-packages
# runs-on: ubuntu-latest
# steps:
# - uses: actions/download-artifact@master
# - run: ls *
publish-to-pypi:
name: >-
Publish Python 🐍 distribution 📦 to PyPI
if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
needs:
- generate-python-binary-packages
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/gattlib-py # Replace <package-name> with your PyPI project name
permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing
steps:
- uses: actions/download-artifact@master
- run: |
mkdir dist/
mv python-binary-packages/*.tar.gz dist/
mv python-binary-packages/*.whl dist/
ls dist/*
- name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

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

@ -1,25 +1,10 @@
#
# GattLib - GATT Library
# SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later
#
# Copyright (C) 2016-2020 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
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
cmake_minimum_required(VERSION 3.4)
cmake_minimum_required(VERSION 3.22.0)
# Add Cross-Compilation support when the environment variables
# CROSS_COMPILE and SYSROOT are defined
@ -27,10 +12,12 @@ include(CrossCompilation.cmake)
project(gattlib)
#TODO: Gattlib examples must be ported to new gattlib_connect()
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" YES)
option(GATTLIB_PYTHON_INTERFACE "Build GattLib Python Interface" YES)
option(GATTLIB_BUILD_DOCS "Build GattLib docs" NO)
option(GATTLIB_PYTHON_INTERFACE "Build GattLib Python Interface" NO)
option(GATTLIB_ENABLE_ADDRESS_SANITIZER "Enable address sanitizer" NO)
find_package(PkgConfig REQUIRED)
find_package(Doxygen)
@ -40,8 +27,26 @@ 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
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
if (MSVC)
# warning level 4
add_compile_options(/W4)
else()
# additional warnings
add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-variadic-macros)
endif()
# Add stack protector
add_compile_options(-fstack-protector-strong)
if (GATTLIB_ENABLE_ADDRESS_SANITIZER)
add_compile_options(-fsanitize=address -fsanitize=bounds -fsanitize=undefined -fsanitize-recover=address)
add_link_options(-fsanitize=address -fsanitize=bounds -fsanitize=undefined -fsanitize-recover=address -static-libasan)
endif()
# Expose 'gattlib.h' to all sub-directories
include_directories(include)
@ -59,7 +64,7 @@ list(GET BLUEZ_VERSIONS 0 BLUEZ_VERSION_MAJOR)
list(GET BLUEZ_VERSIONS 1 BLUEZ_VERSION_MINOR)
add_definitions(-DBLUEZ_VERSION_MAJOR=${BLUEZ_VERSION_MAJOR} -DBLUEZ_VERSION_MINOR=${BLUEZ_VERSION_MINOR})
set(GATTLIB_FORCE_DBUS OFF CACHE BOOLEAN "Build gattlib with D-Bus support on Bluez version < v5.42")
set(GATTLIB_FORCE_DBUS OFF CACHE BOOL "Build gattlib with D-Bus support on Bluez version < v5.42")
if (BLUEZ_VERSION_MAJOR LESS 5)
set(GATTLIB_DBUS FALSE)
@ -73,6 +78,11 @@ else()
set(GATTLIB_DBUS TRUE)
endif()
# With 'syslog' backend, we enable all logs (ie: up to level debug) and we leave the
# application to set the level using 'setlogmask()'
set(GATTLIB_LOG_LEVEL 3 CACHE STRING "Define the minimum logging level for Gattlib (0=error, 1=warning, 2=info, 3=debug)")
set(GATTLIB_LOG_BACKEND syslog CACHE STRING "Define logging backend: syslog, printf, python (default: syslog)")
if (GATTLIB_DBUS)
# Build dbus-based gattlib
add_subdirectory(dbus)
@ -108,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)
@ -122,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,20 +1,8 @@
#
# Copyright (C) 2016 Olivier Martin <olivier@labapart.org>
# SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later
#
# Copyright (c) 2016-2021, 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
#
# Version: 1.0
# Repository: https://gist.github.com/oliviermartin/

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
=============
@ -154,6 +137,13 @@ TODO List
- Complete `examples/gatttool` port to GattLib to demonstrate the completeness of GattLib.
- Remove GLib dependencies to GattLib (mainly replacing GLib IO Channels by Unix Domain Socket).
License
=======
Gattlib with Bluez Legacy support (for Bluez v4) has a GPL v2.0 or later license.
While Gattlib for recent version of Bluez (v5.40+) has a BSD-3-Clause license - except `dbus/bluez5/lib/uuid.c`
and `dbus/bluez5/lib/uuid.h` that have a GPL v2.0 or later license.
Support
=======

View File

@ -1,7 +1,7 @@
#
# GattLib - GATT Library
#
# Copyright (C) 2016-2019 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
@ -19,14 +19,14 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 2.6)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)
message("Build gattlib for Bluez v${BLUEZ_VERSION_MAJOR}.${BLUEZ_VERSION_MINOR}")
set(bluez4_DIR bluez4)
set(bluez5_DIR bluez5)
set(bluez5_DIR bluez5)
# Bluez specific files
set(bluez4_SRCS ${bluez4_DIR}/attrib/att.c
@ -77,7 +77,8 @@ set(gattlib_SRCS gattlib_adapter.c
gattlib_discover.c
gattlib_read_write.c
${CMAKE_SOURCE_DIR}/common/gattlib_common.c
${CMAKE_SOURCE_DIR}/common/gattlib_eddystone.c)
${CMAKE_SOURCE_DIR}/common/gattlib_eddystone.c
${CMAKE_SOURCE_DIR}/common/logging_backend/${GATTLIB_LOG_BACKEND}/gattlib_logging.c)
# Added Glib support
pkg_search_module(GLIB REQUIRED glib-2.0)
@ -99,13 +100,21 @@ else()
endif()
# gattlib
target_compile_definitions(gattlib PUBLIC -DGATTLIB_LOG_LEVEL=${GATTLIB_LOG_LEVEL})
if (GATTLIB_LOG_BACKEND STREQUAL "syslog")
target_compile_definitions(gattlib PUBLIC -DGATTLIB_LOG_BACKEND_SYSLOG)
endif()
include(GNUInstallDirs)
if(GATTLIB_SHARED_LIB)
add_library(gattlib SHARED ${gattlib_SRCS})
target_link_libraries(gattlib ${gattlib_LIBS})
install(TARGETS gattlib LIBRARY DESTINATION lib)
install(TARGETS gattlib LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
else()
add_library(gattlib ${gattlib_SRCS})
target_include_directories(gattlib INTERFACE ../include)
target_link_libraries(gattlib ${gattlib_LIBS})
install(TARGETS gattlib LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif()

View File

@ -1383,14 +1383,16 @@ static void attr_print_func(void *value, void *userData)
{
sdp_data_t *d = (sdp_data_t *)value;
SDPDBG("=====================================\n");
SDPDBG("ATTRIBUTE IDENTIFIER : 0x%x\n", d->attrId);
SDPDBG("ATTRIBUTE VALUE PTR : %p\n", value);
if (d)
if (d) {
SDPDBG("=====================================\n");
SDPDBG("ATTRIBUTE IDENTIFIER : 0x%x\n", d->attrId);
SDPDBG("ATTRIBUTE VALUE PTR : %p\n", value);
sdp_data_print(d);
else
}
else {
SDPDBG("NULL value\n");
SDPDBG("=====================================\n");
SDPDBG("=====================================\n");
}
}
void sdp_print_service_attr(sdp_list_t *svcAttrList)

View File

@ -1381,14 +1381,15 @@ static void attr_print_func(void *value, void *userData)
{
sdp_data_t *d = (sdp_data_t *)value;
SDPDBG("=====================================");
SDPDBG("ATTRIBUTE IDENTIFIER : 0x%x", d->attrId);
SDPDBG("ATTRIBUTE VALUE PTR : %p", value);
if (d)
if (d) {
SDPDBG("=====================================");
SDPDBG("ATTRIBUTE IDENTIFIER : 0x%x", d->attrId);
SDPDBG("ATTRIBUTE VALUE PTR : %p", value);
sdp_data_print(d);
else
} else {
SDPDBG("NULL value");
SDPDBG("=====================================");
SDPDBG("=====================================");
}
}
void sdp_print_service_attr(sdp_list_t *svcAttrList)

View File

@ -1,3 +1,26 @@
/*
*
* GattLib - GATT Library
*
* Copyright (C) 2016-2021 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 "gattlib_internal.h"
#include <poll.h>
@ -49,7 +72,7 @@ int gattlib_adapter_open(const char* adapter_name, void** adapter) {
}
*device_desc = hci_open_dev(dev_id);
if (device_desc < 0) {
if (*device_desc < 0) {
fprintf(stderr, "ERROR: Could not open device.\n");
return GATTLIB_DEVICE_ERROR;
}
@ -84,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;
@ -189,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);
@ -218,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) {
@ -239,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

@ -2,7 +2,7 @@
*
* GattLib - GATT Library
*
* Copyright (C) 2016-2019 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
@ -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 = {};
@ -69,12 +69,12 @@ static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data)
switch (pdu[0]) {
case ATT_OP_HANDLE_NOTIFY:
if (gattlib_has_valid_handler(&conn->notification)) {
gattlib_call_notification_handler(&conn->notification, &uuid, &pdu[3], len - 3);
gattlib_on_gatt_notification(&conn, &uuid, &pdu[3], len - 3);
}
break;
case ATT_OP_HANDLE_IND:
if (gattlib_has_valid_handler(&conn->indication)) {
gattlib_call_notification_handler(&conn->notification, &uuid, &pdu[3], len - 3);
gattlib_on_gatt_notification(&conn, &uuid, &pdu[3], len - 3);
}
break;
default:
@ -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,12 +325,13 @@ static void get_connection_options(unsigned long options, BtIOSecLevel *bt_io_se
*mtu = GATTLIB_CONNECTION_OPTIONS_LEGACY_GET_MTU(options);
}
gatt_connection_t *gattlib_connect_async(void *adapter, const char *dst,
unsigned long options,
gatt_connect_cb_t connect_cb, void* data)
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;
@ -346,7 +347,7 @@ gatt_connection_t *gattlib_connect_async(void *adapter, const char *dst,
if ((options & (GATTLIB_CONNECTION_OPTIONS_LEGACY_BDADDR_LE_PUBLIC | GATTLIB_CONNECTION_OPTIONS_LEGACY_BDADDR_LE_RANDOM)) == 0) {
// Please, set GATTLIB_CONNECTION_OPTIONS_LEGACY_BDADDR_LE_PUBLIC or
// GATTLIB_CONNECTION_OPTIONS_LEGACY_BDADDR_LE_RANDMON
fprintf(stderr, "gattlib_connect_async() expects address type.\n");
fprintf(stderr, "gattlib_connect() expects address type.\n");
return NULL;
}
@ -392,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,
@ -404,6 +405,7 @@ static gatt_connection_t *gattlib_connect_with_options(const char *src, const ch
if (conn == NULL) {
if (io_connect_arg.error) {
fprintf(stderr, "Error: gattlib_connect - initialization error:%s\n", io_connect_arg.error->message);
g_error_free(io_connect_arg.error);
} else {
fprintf(stderr, "Error: gattlib_connect - initialization\n");
}
@ -417,8 +419,9 @@ static gatt_connection_t *gattlib_connect_with_options(const char *src, const ch
while ((io_connect_arg.connected == FALSE) && (io_connect_arg.timeout == FALSE)) {
g_main_context_iteration(g_gattlib_thread.loop_context, FALSE);
}
// Disconnect the timeout source
g_source_destroy(timeout);
// Disconnect the timeout source if connection success
if (io_connect_arg.connected) g_source_destroy(timeout);
if (io_connect_arg.timeout) {
return NULL;
@ -426,6 +429,7 @@ static gatt_connection_t *gattlib_connect_with_options(const char *src, const ch
if (io_connect_arg.error) {
fprintf(stderr, "gattlib_connect - connection error:%s\n", io_connect_arg.error->message);
g_error_free(io_connect_arg.error);
return NULL;
} else {
return conn;
@ -440,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;
@ -479,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
@ -542,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;
@ -555,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;
@ -569,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

@ -2,7 +2,7 @@
*
* GattLib - GATT Library
*
* Copyright (C) 2016-2019 Olivier Martin <olivier@labapart.org>
* Copyright (C) 2016-2021 Olivier Martin <olivier@labapart.org>
*
*
* This program is free software; you can redistribute it and/or modify
@ -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;
@ -84,8 +84,8 @@ int gattlib_discover_primary(gatt_connection_t* connection, gattlib_primary_serv
gattlib_context_t* conn_context = connection->context;
ret = gatt_discover_primary(conn_context->attrib, NULL, primary_all_cb, &user_data);
if (ret == 0) {
fprintf(stderr, "Fail to discover primary services.\n");
return GATTLIB_ERROR_BLUEZ;
GATTLIB_LOG(GATTLIB_ERROR, "Fail to discover primary services.");
return GATTLIB_ERROR_BLUEZ_WITH_ERROR(ret);
}
// Wait for completion
@ -146,7 +146,7 @@ done:
data->discovered = TRUE;
}
int gattlib_discover_char_range(gatt_connection_t* connection, int start, int 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;
@ -156,12 +156,12 @@ int gattlib_discover_char_range(gatt_connection_t* connection, int start, int en
gattlib_context_t* conn_context = connection->context;
ret = gatt_discover_char(conn_context->attrib, start, end, NULL, characteristic_cb, &user_data);
if (ret == 0) {
fprintf(stderr, "Fail to discover characteristics.\n");
return GATTLIB_ERROR_BLUEZ;
GATTLIB_LOG(GATTLIB_ERROR, "Fail to discover characteristics.");
return GATTLIB_ERROR_BLUEZ_WITH_ERROR(ret);
}
// Wait for completion
while(user_data.discovered == FALSE) {
while(user_data.discovered == FALSE) {
g_main_context_iteration(g_gattlib_thread.loop_context, FALSE);
}
*characteristics = user_data.characteristics;
@ -170,7 +170,7 @@ int gattlib_discover_char_range(gatt_connection_t* connection, int start, int en
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;
@ -278,7 +278,7 @@ int gattlib_discover_desc_range(gatt_connection_t* connection, int start, int en
#endif
if (ret == 0) {
fprintf(stderr, "Fail to discover descriptors.\n");
return GATTLIB_ERROR_BLUEZ;
return GATTLIB_ERROR_BLUEZ_WITH_ERROR(ret);
}
// Wait for completion
@ -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

@ -2,7 +2,7 @@
*
* GattLib - GATT Library
*
* Copyright (C) 2016-2019 Olivier Martin <olivier@labapart.org>
* Copyright (C) 2016-2021 Olivier Martin <olivier@labapart.org>
*
*
* This program is free software; you can redistribute it and/or modify
@ -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

@ -2,7 +2,7 @@
*
* GattLib - GATT Library
*
* Copyright (C) 2016-2017 Olivier Martin <olivier@labapart.org>
* Copyright (C) 2016-2021 Olivier Martin <olivier@labapart.org>
*
*
* This program is free software; you can redistribute it and/or modify
@ -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;

59
ci/generate-python-package.sh Executable file
View File

@ -0,0 +1,59 @@
#!/bin/bash
# Exit immediately if a command exits with a non-zero status.
set -e
# Retrieve path of current script
SCRIPT_PATH=`dirname "$0"`
SCRIPT_PATH=`( cd "$SCRIPT_PATH" && pwd )`
ROOT_PATH=`( cd "$SCRIPT_PATH/.." && pwd )`
gattlib_py_package_dir=$(mktemp -d -p $PWD -t gattlib-py-package-XXXXXXXXXX)
# Python code
cp -r ${ROOT_PATH}/gattlib-py/gattlib ${gattlib_py_package_dir}/
cp -r ${ROOT_PATH}/gattlib-py/setup.py ${gattlib_py_package_dir}/
cp -r ${ROOT_PATH}/gattlib-py/README.md ${gattlib_py_package_dir}/
# Native code
cp -r ${ROOT_PATH}/common ${gattlib_py_package_dir}/
cp -r ${ROOT_PATH}/bluez ${gattlib_py_package_dir}/
cp -r ${ROOT_PATH}/dbus ${gattlib_py_package_dir}/
cp -r ${ROOT_PATH}/include ${gattlib_py_package_dir}/
cp -r ${ROOT_PATH}/CMakeLists.txt ${gattlib_py_package_dir}/
cp -r ${ROOT_PATH}/CrossCompilation.cmake ${gattlib_py_package_dir}/
# Build script
mkdir ${gattlib_py_package_dir}/ci/
cp -r ${ROOT_PATH}/ci/install-bluez.sh ${gattlib_py_package_dir}/ci/
# Create MANIFEST.in
rm -f ${gattlib_py_package_dir}/MANIFEST.in
cat <<EOT >> ${gattlib_py_package_dir}/MANIFEST.in
graft common
graft bluez
graft dbus
graft include
include CMakeLists.txt
include CrossCompilation.cmake
EOT
# Install requirements
python3 -m pip --disable-pip-version-check install cibuildwheel==2.16.5
# Generate packages
pushd ${gattlib_py_package_dir}
# Binary package
python3 -m cibuildwheel --output-dir dist
# Source package
python setup.py sdist
# Move generated artifact to project root path
rm -Rf ${ROOT_PATH}/dist
mv dist ${ROOT_PATH}
popd
rm -Rf ${gattlib_py_package_dir}

21
ci/install-bluez.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash
# Exit immediately if a command exits with a non-zero status.
set -ex
# Install dependencies
yum -y install wget dbus-devel
#
#
#
wget http://www.kernel.org/pub/linux/bluetooth/bluez-5.66.tar.xz
tar -xf bluez-5.66.tar.xz
pushd bluez-5.66
./configure --prefix=/usr/local --disable-obex --disable-udev --disable-cups --disable-client --disable-manpages --disable-tools \
--disable-obex --disable-monitor --disable-hog --disable-hid --disable-network --disable-a2dp --disable-avrcp --disable-bap \
--disable-mcp --disable-vcp --enable-library
make
make install
popd
rm -Rf bluez-5.66

View File

@ -0,0 +1,106 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2024, Olivier Martin <olivier@labapart.org>
*/
#include "gattlib_internal.h"
#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;
// In case of Python support, we ensure we acquire the GIL (Global Intepreter Lock) to have
// a thread-safe Python execution.
PyGILState_STATE d_gstate = PyGILState_Ensure();
const char* argument_string;
// We pass pointer into integer/long parameter. We need to check the address size of the platform
// arguments: (gattlib_adapter_t* adapter, const char *dst, gattlib_connection_t* connection, void* user_data)
if (sizeof(void*) == 8) {
argument_string = "(LsLIO)";
} else {
argument_string = "(IsIIO)";
}
PyObject *arglist = Py_BuildValue(argument_string, adapter, dst, connection, error, args->args);
if (arglist == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Could not convert argument list to Python arguments");
PyErr_Print();
goto ON_ERROR;
}
#if PYTHON_VERSION >= PYTHON_VERSIONS(3, 9)
result = PyObject_Call(args->callback, arglist, NULL);
#else
result = PyEval_CallObject(args->callback, arglist);
#endif
Py_DECREF(arglist);
if (result == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Python connection device handler has raised an exception.");
PyErr_Print();
}
ON_ERROR:
PyGILState_Release(d_gstate);
}
#endif
static gpointer _gattlib_connected_device_thread(gpointer data) {
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(
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) {
gattlib_connection_t* connection = va_arg(args, gattlib_connection_t*);
return 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 */,
connection);
}

View File

@ -0,0 +1,65 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2024, Olivier Martin <olivier@labapart.org>
*/
#include "gattlib_internal.h"
#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;
d_gstate = PyGILState_Ensure();
PyObject *arglist = Py_BuildValue("(O)", args->args);
#if PYTHON_VERSION >= PYTHON_VERSIONS(3, 9)
result = PyObject_Call(args->callback, arglist, NULL);
#else
result = PyEval_CallObject(args->callback, arglist);
#endif
Py_DECREF(arglist);
if (result == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Python disconnection handler has raised an exception.");
PyErr_Print();
}
PyGILState_Release(d_gstate);
}
#endif
void gattlib_on_disconnected_device(gattlib_connection_t* connection) {
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_valid(connection)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_on_disconnected_device: Device not valid");
g_rec_mutex_unlock(&m_gattlib_mutex);
return;
}
if (gattlib_has_valid_handler(&connection->on_disconnection)) {
#if defined(WITH_PYTHON)
// Check if we are using the Python callback, in case of Python argument we keep track of the argument to free them
// once we are done with the handler.
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);
}
// 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

@ -0,0 +1,126 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2024, Olivier Martin <olivier@labapart.org>
*/
#include "gattlib_internal.h"
#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;
// In case of Python support, we ensure we acquire the GIL (Global Intepreter Lock) to have
// a thread-safe Python execution.
PyGILState_STATE d_gstate = PyGILState_Ensure();
const char* argument_string;
// We pass pointer into integer/long parameter. We need to check the address size of the platform
if (sizeof(void*) == 8) {
argument_string = "(LssO)";
} else {
argument_string = "(IssO)";
}
PyObject *arglist = Py_BuildValue(argument_string, adapter, addr, name, args->args);
if (arglist == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Could not convert argument list to Python arguments");
PyErr_Print();
goto ON_ERROR;
}
#if PYTHON_VERSION >= PYTHON_VERSIONS(3, 9)
result = PyObject_Call(args->callback, arglist, NULL);
#else
result = PyEval_CallObject(args->callback, arglist);
#endif
Py_DECREF(arglist);
if (result == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Python discovered device handler has raised an exception.");
PyErr_Print();
}
ON_ERROR:
PyGILState_Release(d_gstate);
}
#endif
struct gattlib_discovered_device_thread_args {
struct _gattlib_adapter* gattlib_adapter;
char* mac_address;
char* name;
OrgBluezDevice1* device1;
};
static gpointer _gattlib_discovered_device_thread(gpointer data) {
struct gattlib_discovered_device_thread_args* args = data;
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->discovered_device_callback.user_data
);
gattlib_adapter_unref(args->gattlib_adapter);
EXIT:
free(args->mac_address);
if (args->name != NULL) {
free(args->name);
args->name = NULL;
}
free(args);
return NULL;
}
static void* _discovered_device_thread_args_allocator(va_list args) {
gattlib_adapter_t* gattlib_adapter = va_arg(args, gattlib_adapter_t*);
OrgBluezDevice1* device1 = va_arg(args, OrgBluezDevice1*);
struct gattlib_discovered_device_thread_args* thread_args = calloc(sizeof(struct gattlib_discovered_device_thread_args), 1);
thread_args->gattlib_adapter = gattlib_adapter;
thread_args->mac_address = strdup(org_bluez_device1_get_address(device1));
const char* device_name = org_bluez_device1_get_name(device1);
if (device_name != NULL) {
thread_args->name = strdup(device_name);
} else {
thread_args->name = NULL;
}
return thread_args;
}
void gattlib_on_discovered_device(gattlib_adapter_t* gattlib_adapter, OrgBluezDevice1* device1) {
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
return;
}
gattlib_handler_dispatch_to_thread(
&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 */,
gattlib_adapter, device1);
}

View File

@ -0,0 +1,114 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2024, Olivier Martin <olivier@labapart.org>
*/
#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];
PyGILState_STATE d_gstate;
PyObject *result;
int ret;
ret = gattlib_uuid_to_string(uuid, uuid_str, sizeof(uuid_str));
if (ret != 0) {
return;
}
d_gstate = PyGILState_Ensure();
const char* argument_string;
// We pass pointer into integer/long parameter. We need to check the address size of the platform
if (sizeof(void*) == 8) {
argument_string = "(sLIO)";
} else {
argument_string = "(sIIO)";
}
PyObject *arglist = Py_BuildValue(argument_string, uuid_str, data, data_length, args->args);
#if PYTHON_VERSION >= PYTHON_VERSIONS(3, 9)
result = PyObject_Call(args->callback, arglist, NULL);
#else
result = PyEval_CallObject(args->callback, arglist);
#endif
Py_DECREF(arglist);
if (result == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Python notification handler has raised an exception.");
PyErr_Print();
}
PyGILState_Release(d_gstate);
}
#endif
struct gattlib_notification_device_thread_args {
gattlib_connection_t* connection;
uuid_t* uuid;
uint8_t* data;
size_t data_length;
};
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;
}
if (args->data != NULL) {
free(args->data);
args->data = NULL;
}
}
static void* _notification_device_thread_args_allocator(gattlib_connection_t* connection, const uuid_t* uuid, const uint8_t* data, size_t data_length) {
struct gattlib_notification_device_thread_args* thread_args = calloc(sizeof(struct gattlib_notification_device_thread_args), 1);
thread_args->connection = connection;
thread_args->uuid = calloc(sizeof(uuid_t), 1);
if (thread_args->uuid != NULL) {
memcpy(thread_args->uuid, uuid, sizeof(uuid_t));
}
thread_args->data = malloc(data_length);
if (thread_args->data != NULL) {
memcpy(thread_args->data, data, data_length);
}
thread_args->data_length = data_length;
return thread_args;
}
void gattlib_on_gatt_notification(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);
void* arg = _notification_device_thread_args_allocator(connection, uuid, data, data_length);
if (arg == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_on_gatt_notification: Failed to allocate arguments for thread");
return;
}
g_thread_pool_push(connection->notification.thread_pool, arg, &error);
if (error != NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_on_gatt_notification: Failed to push thread in pool: %s", error->message);
g_error_free(error);
}
}

View File

@ -0,0 +1,26 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2024, Olivier Martin <olivier@labapart.org>
*/
#include "gattlib_internal.h"
void* gattlib_python_callback_args(PyObject* python_callback, PyObject* python_args) {
assert(python_callback != NULL);
assert(python_args != NULL);
struct gattlib_python_args* args = calloc(sizeof(struct gattlib_python_args), 1);
if (args == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to allocate Python arguments for Python callback.");
return NULL;
}
Py_INCREF(python_callback);
Py_INCREF(python_args);
args->callback = python_callback;
args->args = python_args;
return args;
}

View File

@ -1,101 +1,110 @@
#if defined(WITH_PYTHON)
#include <Python.h>
#endif
/*
* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later
*
* Copyright (c) 2021-2024, Olivier Martin <olivier@labapart.org>
*/
#include <stdio.h>
#include "gattlib_internal.h"
void gattlib_register_notification(gatt_connection_t* connection, gattlib_event_handler_t notification_handler, void* user_data) {
connection->notification.type = NATIVE_NOTIFICATION;
connection->notification.notification_handler = notification_handler;
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) {
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;
connection->notification.user_data = user_data;
connection->notification.thread_pool = g_thread_pool_new(
gattlib_notification_device_thread,
&connection->notification,
1 /* max_threads */, FALSE /* exclusive */, &error);
if (error != NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_register_notification: Failed to create thread pool: %s", error->message);
g_error_free(error);
ret = GATTLIB_ERROR_INTERNAL;
goto EXIT;
} else {
assert(connection->notification.thread_pool != NULL);
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
void gattlib_register_indication(gatt_connection_t* connection, gattlib_event_handler_t indication_handler, void* user_data) {
connection->indication.type = NATIVE_NOTIFICATION;
connection->indication.notification_handler = indication_handler;
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) {
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;
}
void gattlib_register_on_disconnect(gatt_connection_t *connection, gattlib_disconnection_handler_t handler, void* user_data) {
connection->disconnection.type = NATIVE_DISCONNECTION;
connection->disconnection.disconnection_handler = handler;
connection->disconnection.user_data = user_data;
}
#if defined(WITH_PYTHON)
void gattlib_register_notification_python(gatt_connection_t* connection, PyObject *notification_handler, PyObject *user_data) {
connection->notification.type = PYTHON;
connection->notification.python_handler = notification_handler;
connection->notification.user_data = user_data;
}
void gattlib_register_indication_python(gatt_connection_t* connection, PyObject *indication_handler, PyObject *user_data) {
connection->indication.type = PYTHON;
connection->indication.python_handler = indication_handler;
connection->indication.user_data = user_data;
}
void gattlib_register_on_disconnect_python(gatt_connection_t *connection, PyObject *handler, PyObject *user_data) {
connection->disconnection.type = PYTHON;
connection->disconnection.python_handler = handler;
connection->disconnection.user_data = user_data;
}
#endif
bool gattlib_has_valid_handler(struct gattlib_handler *handler) {
return ((handler->type != UNKNOWN) && (handler->notification_handler != NULL));
}
void gattlib_call_notification_handler(struct gattlib_handler *handler, const uuid_t* uuid, const uint8_t* data, size_t data_length) {
if (handler->type == NATIVE_NOTIFICATION) {
handler->notification_handler(uuid, data, data_length, handler->user_data);
connection->indication.thread_pool = g_thread_pool_new(
gattlib_notification_device_thread,
&connection->indication,
1 /* max_threads */, FALSE /* exclusive */, &error);
if (error != NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_register_indication: Failed to create thread pool: %s", error->message);
g_error_free(error);
ret = GATTLIB_ERROR_INTERNAL;
goto EXIT;
}
#if defined(WITH_PYTHON)
else if (handler->type == PYTHON) {
char uuid_str[MAX_LEN_UUID_STR + 1];
PyGILState_STATE d_gstate;
gattlib_uuid_to_string(uuid, uuid_str, sizeof(uuid_str));
d_gstate = PyGILState_Ensure();
const char* argument_string;
if (sizeof(void*) == 8) {
argument_string = "(sLIO)";
} else {
argument_string = "(sIIO)";
}
PyObject *arglist = Py_BuildValue(argument_string, uuid_str, data, data_length, handler->user_data);
PyEval_CallObject((PyObject *)handler->notification_handler, arglist);
PyGILState_Release(d_gstate);
}
#endif
else {
fprintf(stderr, "Invalid notification handler.\n");
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
void gattlib_call_disconnection_handler(struct gattlib_handler *handler) {
if (handler->type == NATIVE_DISCONNECTION) {
handler->disconnection_handler(handler->user_data);
}
#if defined(WITH_PYTHON)
else if (handler->type == PYTHON) {
PyGILState_STATE d_gstate;
d_gstate = PyGILState_Ensure();
int gattlib_register_on_disconnect(gattlib_connection_t *connection, gattlib_disconnection_handler_t handler, void* user_data) {
int ret = GATTLIB_SUCCESS;
PyObject *arglist = Py_BuildValue("(O)", handler->user_data);
PyEval_CallObject((PyObject *)handler->disconnection_handler, arglist);
g_rec_mutex_lock(&m_gattlib_mutex);
PyGILState_Release(d_gstate);
if (connection == NULL) {
ret = GATTLIB_INVALID_PARAMETER;
goto EXIT;
}
#endif
else {
fprintf(stderr, "Invalid disconnection handler.\n");
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;
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
void bt_uuid_to_uuid(bt_uuid_t* bt_uuid, uuid_t* uuid) {
@ -152,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;
@ -177,3 +231,86 @@ int gattlib_uuid_cmp(const uuid_t *uuid1, const uuid_t *uuid2) {
return 3;
}
}
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;
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 */);
handler->thread_pool = NULL;
}
}
bool gattlib_has_valid_handler(struct gattlib_handler* handler) {
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;
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;
va_start(args, thread_args_allocator);
void* thread_args = thread_args_allocator(args);
va_end(args);
handler->thread = g_thread_try_new(thread_name, thread_func, thread_args, &error);
if (handler->thread == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to create thread '%s': %s", thread_name, error->message);
g_error_free(error);
return;
}
}
// Helper function to free memory from Python frontend
void gattlib_free_mem(void *ptr) {
if (ptr != NULL) {
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

@ -1,3 +1,9 @@
/*
* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later
*
* Copyright (c) 2021, Olivier Martin <olivier@labapart.org>
*/
#include <string.h>
#include "gattlib_internal.h"
@ -19,30 +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;
gattlib_advertisement_data_t *advertisement_data = NULL;
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;
}
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;
@ -51,7 +66,7 @@ int gattlib_adapter_scan_eddystone(void *adapter, int16_t rssi_threshold, uint32
ret = gattlib_string_to_uuid(EDDYSTONE_SERVICE_UUID, strlen(EDDYSTONE_SERVICE_UUID) + 1, &eddystone_uuid);
if (ret != 0) {
fprintf(stderr, "Fail to convert characteristic TX to UUID.\n");
GATTLIB_LOG(GATTLIB_ERROR, "Fail to convert characteristic TX to UUID.");
return GATTLIB_ERROR_INTERNAL;
}

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,32 +0,0 @@
#ifndef __GATTLIB_INTERNAL_DEFS_H__
#define __GATTLIB_INTERNAL_DEFS_H__
#include <stdbool.h>
#include "gattlib.h"
enum handler_type { UNKNOWN = 0, NATIVE_NOTIFICATION, NATIVE_DISCONNECTION, PYTHON };
struct gattlib_handler {
enum handler_type type;
union {
gattlib_event_handler_t notification_handler;
gattlib_disconnection_handler_t disconnection_handler;
void* python_handler;
};
void* user_data;
};
struct _gatt_connection_t {
void* context;
struct gattlib_handler notification;
struct gattlib_handler indication;
struct gattlib_handler disconnection;
};
bool gattlib_has_valid_handler(struct gattlib_handler *handler);
void gattlib_call_disconnection_handler(struct gattlib_handler *handler);
void gattlib_call_notification_handler(struct gattlib_handler *handler, const uuid_t* uuid, const uint8_t* data, size_t data_length);
#endif

View File

@ -0,0 +1,21 @@
/*
* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later
*
* Copyright (c) 2021, Olivier Martin <olivier@labapart.org>
*/
#include "gattlib_internal.h"
void gattlib_log(int level, const char *format, ...) {
va_list args;
FILE *stream = stdout;
if (level == GATTLIB_ERROR) {
stream = stderr;
}
va_start(args, format);
vfprintf(stream, format, args);
fprintf(stream, "\n");
va_end(args);
}

View File

@ -0,0 +1,57 @@
/*
* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later
*
* Copyright (c) 2024, Olivier Martin <olivier@labapart.org>
*/
#include <syslog.h>
#include "gattlib_internal.h"
static PyObject* m_logging_func;
void gattlib_log_init(PyObject* logging_func) {
m_logging_func = logging_func;
}
void gattlib_log(int level, const char *format, ...) {
va_list args;
va_start(args, format);
if (m_logging_func == NULL) {
FILE *stream = stdout;
if (level == GATTLIB_ERROR) {
stream = stderr;
}
vfprintf(stream, format, args);
fprintf(stream, "\n");
} else {
PyGILState_STATE d_gstate;
PyObject *result;
char string[400];
vsnprintf(string, sizeof(string), format, args);
d_gstate = PyGILState_Ensure();
PyObject *arglist = Py_BuildValue("Is", level, string);
#if PYTHON_VERSION >= PYTHON_VERSIONS(3, 9)
result = PyObject_Call(m_logging_func, arglist, NULL);
#else
result = PyEval_CallObject(m_logging_func, arglist);
#endif
if (result == NULL) {
// 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);
}
va_end(args);
}

View File

@ -0,0 +1,21 @@
/*
* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later
*
* Copyright (c) 2021, Olivier Martin <olivier@labapart.org>
*/
#include <syslog.h>
#include "gattlib_internal.h"
static const int m_gattlib_log_level_to_syslog[] = {
LOG_ERR, LOG_WARNING, LOG_INFO, LOG_DEBUG
};
void gattlib_log(int level, const char *format, ...) {
va_list args;
va_start(args, format);
vsyslog(m_gattlib_log_level_to_syslog[level], format, args);
va_end(args);
}

View File

@ -0,0 +1,94 @@
/*
* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later
*
* Copyright (c) 2021-2024, Olivier Martin <olivier@labapart.org>
*/
/**
* See some Glib mainloop API function usage in this example:
* https://github.com/ImageMagick/glib/blob/main/tests/mainloop-test.c
*/
#include "gattlib_internal.h"
// We make this variable global to be able to exit the main loop
static GMainLoop *m_main_loop;
struct _execute_task_arg {
void* (*task)(void* arg);
void* arg;
};
static void* _execute_task(void* arg) {
struct _execute_task_arg *execute_task_arg = arg;
execute_task_arg->task(execute_task_arg->arg);
g_main_loop_quit(m_main_loop);
return NULL;
}
int gattlib_mainloop(void* (*task)(void* arg), void *arg) {
struct _execute_task_arg execute_task_arg = {
.task = task,
.arg = arg
};
GError* error;
if (m_main_loop != NULL) {
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);
g_thread_join(task_thread);
g_thread_unref(task_thread);
m_main_loop = NULL;
return GATTLIB_SUCCESS;
}
#if defined(WITH_PYTHON)
struct gattlib_mainloop_handler_python_args {
PyObject *handler;
PyObject *user_data;
};
static void* _gattlib_mainloop_handler_python(void* args) {
struct gattlib_mainloop_handler_python_args* python_args = (struct gattlib_mainloop_handler_python_args*)args;
PyGILState_STATE d_gstate;
PyObject *result;
d_gstate = PyGILState_Ensure();
#if PYTHON_VERSION >= PYTHON_VERSIONS(3, 9)
result = PyObject_Call(python_args->handler, python_args->user_data, NULL);
#else
result = PyEval_CallObject(python_args->handler, python_args->user_data);
#endif
if (result == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Python task handler has raised an exception.");
}
PyGILState_Release(d_gstate);
return result;
}
int gattlib_mainloop_python(PyObject *handler, PyObject *user_data) {
struct gattlib_mainloop_handler_python_args python_args = {
.handler = handler,
.user_data = user_data
};
return gattlib_mainloop(_gattlib_mainloop_handler_python, &python_args);
}
#endif

View File

@ -1,25 +1,10 @@
#
# GattLib - GATT Library
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (C) 2017-2020 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
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
cmake_minimum_required(VERSION 3.0)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)
@ -88,13 +73,25 @@ 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/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
${CMAKE_CURRENT_BINARY_DIR}/org-bluez-device1.c
${CMAKE_CURRENT_BINARY_DIR}/org-bluez-gattcharacteristic1.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()
@ -110,13 +107,24 @@ if(GATTLIB_PYTHON_INTERFACE)
else()
set(Python_USE_STATIC_LIBS TRUE)
endif()
find_package(Python COMPONENTS Interpreter Development)
if (Python_Development_FOUND)
include_directories(${Python_INCLUDE_DIRS})
list(APPEND gattlib_LIBS ${Python_LIBRARIES})
add_definitions(-DWITH_PYTHON)
find_package(Python3 COMPONENTS Interpreter Development)
if (NOT Python3_Development_FOUND)
find_package(Python3 COMPONENTS Development.Module)
if (NOT Python3_Development.Module_FOUND)
message(FATAL_ERROR "Could not find Python developer package")
endif()
# Case of mainlinux container to build Wheel Python package
if (NOT Python3_LIBRARIES)
set(Python3_LIBRARIES "")
endif()
endif()
include_directories(${Python3_INCLUDE_DIRS})
list(APPEND gattlib_LIBS ${Python3_LIBRARIES})
add_definitions(-DWITH_PYTHON -DPYTHON_VERSION_MAJOR=${Python3_VERSION_MAJOR} -DPYTHON_VERSION_MINOR=${Python3_VERSION_MINOR})
endif()
# Gattlib
@ -126,6 +134,11 @@ else()
add_library(gattlib ${gattlib_SRCS})
endif()
target_compile_definitions(gattlib PUBLIC -DGATTLIB_LOG_LEVEL=${GATTLIB_LOG_LEVEL})
if (GATTLIB_LOG_BACKEND STREQUAL "syslog")
target_compile_definitions(gattlib PUBLIC -DGATTLIB_LOG_BACKEND_SYSLOG)
endif()
target_include_directories(gattlib PUBLIC ../include)
target_link_libraries(gattlib ${gattlib_LIBS})

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +1,23 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* GattLib - GATT Library
*
* Copyright (C) 2016-2020 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
*
* Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
*/
#include "gattlib_internal.h"
// This recursive mutex ensures all gattlib objects can be accessed in a multi-threaded environment
// The recursive mutex allows a same thread to lock twice the mutex without being blocked by itself.
GRecMutex m_gattlib_mutex;
int gattlib_adapter_open(const char* adapter_name, void** adapter) {
// This structure is used for inter-thread communication
struct gattlib_signal m_gattlib_signal;
int gattlib_adapter_open(const char* adapter_name, gattlib_adapter_t** adapter) {
char object_path[20];
gattlib_adapter_t* gattlib_adapter;
OrgBluezAdapter1 *adapter_proxy;
struct gattlib_adapter *gattlib_adapter;
GError *error = NULL;
if (adapter == NULL) {
@ -40,42 +30,72 @@ int gattlib_adapter_open(const char* adapter_name, void** adapter) {
snprintf(object_path, sizeof(object_path), "/org/bluez/%s", adapter_name);
// Check if adapter has already be loaded
g_rec_mutex_lock(&m_gattlib_mutex);
*adapter = gattlib_adapter_from_id(object_path);
if (*adapter != NULL) {
GATTLIB_LOG(GATTLIB_DEBUG, "Bluetooth adapter %s has already been opened. Re-use it", adapter_name);
gattlib_adapter_ref(*adapter);
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_SUCCESS;
}
g_rec_mutex_unlock(&m_gattlib_mutex);
GATTLIB_LOG(GATTLIB_DEBUG, "Open bluetooth adapter %s", adapter_name);
adapter_proxy = org_bluez_adapter1_proxy_new_for_bus_sync(
G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE,
"org.bluez",
object_path,
NULL, &error);
if (adapter_proxy == NULL) {
int ret = GATTLIB_ERROR_DBUS;
if (error) {
fprintf(stderr, "Failed to get adapter %s: %s\n", object_path, error->message);
GATTLIB_LOG(GATTLIB_ERROR, "Failed to get adapter %s: %s", object_path, error->message);
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
g_error_free(error);
} else {
fprintf(stderr, "Failed to get adapter %s\n", object_path);
GATTLIB_LOG(GATTLIB_ERROR, "Failed to get adapter %s", object_path);
}
return GATTLIB_ERROR_DBUS;
return ret;
}
// 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_rec_mutex_lock(&m_gattlib_mutex);
m_adapter_list = g_slist_append(m_adapter_list, gattlib_adapter);
*adapter = gattlib_adapter;
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_SUCCESS;
}
struct gattlib_adapter *init_default_adapter(void) {
struct gattlib_adapter *gattlib_adapter;
const char *gattlib_adapter_get_name(gattlib_adapter_t* adapter) {
//
// Note: There is a risk we access the memory when it has been freed
// What we should do is to take 'm_gattlib_mutex', then to check the adapter is valid
// then to duplicate the string
//
return adapter->name;
}
gattlib_adapter_t* init_default_adapter(void) {
gattlib_adapter_t* gattlib_adapter;
int ret;
ret = gattlib_adapter_open(NULL, (void**)&gattlib_adapter);
ret = gattlib_adapter_open(NULL, &gattlib_adapter);
if (ret != GATTLIB_SUCCESS) {
return NULL;
} else {
@ -83,11 +103,9 @@ struct gattlib_adapter *init_default_adapter(void) {
}
}
GDBusObjectManager *get_device_manager_from_adapter(struct gattlib_adapter *gattlib_adapter) {
GError *error = NULL;
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;
}
//
@ -95,72 +113,64 @@ 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 (error) {
fprintf(stderr, "Failed to get Bluez Device Manager: %s\n", error->message);
g_error_free(error);
} else {
fprintf(stderr, "Failed to get Bluez Device Manager.\n");
}
error);
if (gattlib_adapter->backend.device_manager == NULL) {
return NULL;
}
return gattlib_adapter->device_manager;
EXIT:
return gattlib_adapter->backend.device_manager;
}
/*
* Internal structure to pass to Device Manager signal handlers
*/
struct discovered_device_arg {
void *adapter;
uint32_t enabled_filters;
gattlib_discovered_device_t callback;
void *user_data;
GSList** discovered_devices_ptr;
};
static void device_manager_on_device1_signal(const char* device1_path, struct discovered_device_arg *arg)
static void device_manager_on_added_device1_signal(const char* device1_path, gattlib_adapter_t* gattlib_adapter)
{
GError *error = NULL;
OrgBluezDevice1* device1 = org_bluez_device1_proxy_new_for_bus_sync(
G_BUS_TYPE_SYSTEM,
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
G_DBUS_PROXY_FLAGS_NONE,
"org.bluez",
device1_path,
NULL,
&error);
if (error) {
fprintf(stderr, "Failed to connection to new DBus Bluez Device: %s\n",
GATTLIB_LOG(GATTLIB_ERROR, "Failed to connection to new DBus Bluez Device: %s",
error->message);
g_error_free(error);
}
if (device1) {
const gchar *address = org_bluez_device1_get_address(device1);
int ret;
// Check if the device is already part of the list
GSList *item = g_slist_find_custom(*arg->discovered_devices_ptr, address, (GCompareFunc)g_ascii_strcasecmp);
// First time this device is in the list
if (item == NULL) {
// Add the device to the list
*arg->discovered_devices_ptr = g_slist_append(*arg->discovered_devices_ptr, g_strdup(address));
// Sometimes org_bluez_device1_get_address returns null addresses. If that's the case, early return.
if (address == NULL) {
g_object_unref(device1);
return;
}
if ((item == NULL) || (arg->enabled_filters & GATTLIB_DISCOVER_FILTER_NOTIFY_CHANGE)) {
arg->callback(
arg->adapter,
org_bluez_device1_get_address(device1),
org_bluez_device1_get_name(device1),
arg->user_data);
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
GATTLIB_LOG(GATTLIB_ERROR, "device_manager_on_added_device1_signal: Adapter not valid");
g_rec_mutex_unlock(&m_gattlib_mutex);
g_object_unref(device1);
return;
}
//TODO: Add support for connected device with 'gboolean org_bluez_device1_get_connected (OrgBluezDevice1 *object);'
// When the device is connected, we potentially need to initialize some attributes
ret = gattlib_device_set_state(gattlib_adapter, device1_path, DISCONNECTED);
if (ret == GATTLIB_SUCCESS) {
gattlib_on_discovered_device(gattlib_adapter, device1);
}
g_rec_mutex_unlock(&m_gattlib_mutex);
g_object_unref(device1);
}
}
@ -170,17 +180,34 @@ static void on_dbus_object_added(GDBusObjectManager *device_manager,
gpointer user_data)
{
const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object));
GDBusInterface *interface = g_dbus_object_manager_get_interface(device_manager, object_path, "org.bluez.Device1");
if (!interface) {
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: on_object_added: %s (not 'org.bluez.Device1')", object_path);
return;
}
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: on_object_added: %s (has 'org.bluez.Device1')", object_path);
// It is a 'org.bluez.Device1'
device_manager_on_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,
@ -189,26 +216,191 @@ on_interface_proxy_properties_changed (GDBusObjectManagerClient *device_manager,
const gchar *const *invalidated_properties,
gpointer user_data)
{
// Check if the object is a 'org.bluez.Device1'
if (strcmp(g_dbus_proxy_get_interface_name(interface_proxy), "org.bluez.Device1") != 0) {
return;
const char* proxy_object_path = g_dbus_proxy_get_object_path(interface_proxy);
gattlib_adapter_t* gattlib_adapter = user_data;
// Count number of invalidated properties
size_t invalidated_properties_count = 0;
if (invalidated_properties != NULL) {
const gchar *const *invalidated_properties_ptr = invalidated_properties;
while (*invalidated_properties_ptr != NULL) {
invalidated_properties_count++;
invalidated_properties_ptr++;
}
}
// It is a 'org.bluez.Device1'
device_manager_on_device1_signal(g_dbus_proxy_get_object_path(interface_proxy), user_data);
GATTLIB_LOG(GATTLIB_DEBUG, "DBUS: on_interface_proxy_properties_changed(%s): interface:%s changed_properties:%s invalidated_properties:%d",
proxy_object_path,
g_dbus_proxy_get_interface_name(interface_proxy),
g_variant_print(changed_properties, TRUE),
invalidated_properties_count);
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
GATTLIB_LOG(GATTLIB_ERROR, "on_interface_proxy_properties_changed: Adapter not valid");
goto EXIT;
}
if (gattlib_adapter->backend.device_manager == NULL) {
goto EXIT;
}
// Check if the object is a 'org.bluez.Device1'
if (strcmp(g_dbus_proxy_get_interface_name(interface_proxy), "org.bluez.Device1") == 0) {
// It is a 'org.bluez.Device1'
GError *error = NULL;
OrgBluezDevice1* device1 = org_bluez_device1_proxy_new_for_bus_sync(
G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_NONE,
"org.bluez",
proxy_object_path, NULL, &error);
if (error) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to connection to new DBus Bluez Device: %s", error->message);
g_error_free(error);
goto EXIT;
} else if (device1 == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Unexpected NULL device");
goto EXIT;
}
// Check if the device has been disconnected
GVariantDict dict;
g_variant_dict_init(&dict, changed_properties);
GVariant* has_rssi = g_variant_dict_lookup_value(&dict, "RSSI", NULL);
GVariant* has_manufacturer_data = g_variant_dict_lookup_value(&dict, "ManufacturerData", NULL);
enum _gattlib_device_state old_device_state = gattlib_device_get_state(gattlib_adapter, proxy_object_path);
if (old_device_state == NOT_FOUND) {
if (has_rssi || has_manufacturer_data) {
int ret = gattlib_device_set_state(gattlib_adapter, proxy_object_path, DISCONNECTED);
if (ret == GATTLIB_SUCCESS) {
gattlib_on_discovered_device(gattlib_adapter, device1);
}
}
}
g_variant_dict_end(&dict);
g_object_unref(device1);
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
}
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)
/**
* Function that waits for the end of the BLE scan
*
* It either called when we wait for BLE scan to complete or when we close the BLE adapter
*/
static void _wait_scan_loop_stop_scanning(gattlib_adapter_t* gattlib_adapter) {
g_mutex_lock(&m_gattlib_signal.mutex);
while (gattlib_adapter_is_scanning(gattlib_adapter)) {
g_cond_wait(&m_gattlib_signal.condition, &m_gattlib_signal.mutex);
}
g_mutex_unlock(&m_gattlib_signal.mutex);
}
/**
* Function called when the BLE scan duration has timeout
*/
static gboolean _stop_scan_on_timeout(gpointer data) {
gattlib_adapter_t* gattlib_adapter = data;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
GATTLIB_LOG(GATTLIB_ERROR, "_stop_scan_on_timeout: Adapter not valid");
g_rec_mutex_unlock(&m_gattlib_mutex);
return FALSE;
}
if (gattlib_adapter->backend.ble_scan.is_scanning) {
g_mutex_lock(&m_gattlib_signal.mutex);
gattlib_adapter->backend.ble_scan.is_scanning = false;
m_gattlib_signal.signals |= GATTLIB_SIGNAL_ADAPTER_STOP_SCANNING;
g_cond_broadcast(&m_gattlib_signal.condition);
g_mutex_unlock(&m_gattlib_signal.mutex);
}
// Unset timeout ID to not try removing it
gattlib_adapter->backend.ble_scan.ble_scan_timeout_id = 0;
g_rec_mutex_unlock(&m_gattlib_mutex);
GATTLIB_LOG(GATTLIB_DEBUG, "BLE scan is stopped after scanning time has expired.");
return FALSE;
}
/**
* Thread that waits for the end of BLE scan that is triggered either by a timeout of the BLE scan
* or disabling the BLE scan
*/
static void* _ble_scan_loop_thread(void* args) {
gattlib_adapter_t* gattlib_adapter = args;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
GATTLIB_LOG(GATTLIB_ERROR, "_ble_scan_loop_thread: Adapter not valid (1)");
goto EXIT;
}
if (gattlib_adapter->backend.ble_scan.ble_scan_timeout_id > 0) {
GATTLIB_LOG(GATTLIB_WARNING, "A BLE scan seems to already be in progress.");
}
gattlib_adapter->backend.ble_scan.is_scanning = true;
if (gattlib_adapter->backend.ble_scan.ble_scan_timeout > 0) {
GATTLIB_LOG(GATTLIB_DEBUG, "Scan for BLE devices for %ld seconds", gattlib_adapter->backend.ble_scan.ble_scan_timeout);
gattlib_adapter->backend.ble_scan.ble_scan_timeout_id = g_timeout_add_seconds(gattlib_adapter->backend.ble_scan.ble_scan_timeout,
_stop_scan_on_timeout, gattlib_adapter);
}
g_rec_mutex_unlock(&m_gattlib_mutex);
// Wait for the BLE scan to be explicitely stopped by 'gattlib_adapter_scan_disable()' or timeout.
_wait_scan_loop_stop_scanning(gattlib_adapter);
// Note: The function only resumes when loop timeout as expired or g_main_loop_quit has been called.
g_rec_mutex_lock(&m_gattlib_mutex);
// Confirm gattlib_adapter is still valid
if (!gattlib_adapter_is_valid(gattlib_adapter)) {
GATTLIB_LOG(GATTLIB_ERROR, "_ble_scan_loop_thread: Adapter not valid (2)");
goto EXIT;
}
g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(gattlib_adapter->backend.device_manager), gattlib_adapter->backend.ble_scan.added_signal_id);
g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(gattlib_adapter->backend.device_manager), gattlib_adapter->backend.ble_scan.removed_signal_id);
g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(gattlib_adapter->backend.device_manager), gattlib_adapter->backend.ble_scan.changed_signal_id);
// Ensure BLE device discovery is stopped
gattlib_adapter_scan_disable(gattlib_adapter);
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return NULL;
}
static int _gattlib_adapter_scan_enable_with_filter(gattlib_adapter_t* adapter, uuid_t **uuid_list, int16_t rssi_threshold, uint32_t enabled_filters,
gattlib_discovered_device_t discovered_device_cb, size_t timeout, void *user_data)
{
struct gattlib_adapter *gattlib_adapter = adapter;
GDBusObjectManager *device_manager;
GError *error = NULL;
int ret = GATTLIB_SUCCESS;
int added_signal_id, changed_signal_id;
GSList *discovered_devices = NULL;
GVariantBuilder arg_properties_builder;
GVariant *rssi_variant = NULL;
int ret;
if ((adapter == NULL) || (adapter->backend.adapter_proxy == NULL)) {
GATTLIB_LOG(GATTLIB_ERROR, "Could not start BLE scan. No opened bluetooth adapter");
return GATTLIB_NO_ADAPTER;
}
g_variant_builder_init(&arg_properties_builder, G_VARIANT_TYPE("a{sv}"));
@ -216,6 +408,13 @@ int gattlib_adapter_scan_enable_with_filter(void *adapter, uuid_t **uuid_list, i
char uuid_str[MAX_LEN_UUID_STR + 1];
GVariantBuilder list_uuid_builder;
if (uuid_list == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Could not start BLE scan. Missing list of UUIDs");
return GATTLIB_INVALID_PARAMETER;
}
GATTLIB_LOG(GATTLIB_DEBUG, "Configure bluetooth scan with UUID");
g_variant_builder_init(&list_uuid_builder, G_VARIANT_TYPE ("as"));
for (uuid_t **uuid_ptr = uuid_list; *uuid_ptr != NULL; uuid_ptr++) {
@ -227,11 +426,12 @@ int gattlib_adapter_scan_enable_with_filter(void *adapter, uuid_t **uuid_list, i
}
if (enabled_filters & GATTLIB_DISCOVER_FILTER_USE_RSSI) {
GATTLIB_LOG(GATTLIB_DEBUG, "Configure bluetooth scan with RSSI");
GVariant *rssi_variant = g_variant_new_int16(rssi_threshold);
g_variant_builder_add(&arg_properties_builder, "{sv}", "RSSI", rssi_variant);
}
org_bluez_adapter1_call_set_discovery_filter_sync(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) {
@ -239,10 +439,11 @@ int gattlib_adapter_scan_enable_with_filter(void *adapter, uuid_t **uuid_list, i
}
if (error) {
printf("error: %d.%d\n", error->domain, error->code);
fprintf(stderr, "Failed to set discovery filter: %s\n", error->message);
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
GATTLIB_LOG(GATTLIB_ERROR, "Failed to set discovery filter: %s (%d.%d)",
error->message, error->domain, error->code);
g_error_free(error);
return GATTLIB_ERROR_DBUS;
return ret;
}
//
@ -250,64 +451,145 @@ int gattlib_adapter_scan_enable_with_filter(void *adapter, uuid_t **uuid_list, i
// 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);
device_manager = get_device_manager_from_adapter(adapter, &error);
if (device_manager == NULL) {
goto DISABLE_SCAN;
if (error != NULL) {
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
g_error_free(error);
} else {
ret = GATTLIB_ERROR_DBUS;
}
return ret;
}
// Pass the user callback and the discovered device list pointer to the signal handlers
struct discovered_device_arg discovered_device_arg = {
.adapter = adapter,
.enabled_filters = enabled_filters,
.callback = discovered_device_cb,
.user_data = user_data,
.discovered_devices_ptr = &discovered_devices,
};
// Clear BLE scan structure
memset(&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;
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),
&discovered_device_arg);
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
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),
&discovered_device_arg);
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) {
fprintf(stderr, "Failed to start discovery: %s\n", error->message);
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
GATTLIB_LOG(GATTLIB_ERROR, "Failed to start discovery: %s", error->message);
g_error_free(error);
return GATTLIB_ERROR_DBUS;
return ret;
}
// Run Glib loop for 'timeout' seconds
gattlib_adapter->scan_loop = g_main_loop_new(NULL, 0);
if (timeout > 0) {
gattlib_adapter->timeout_id = g_timeout_add_seconds(timeout, stop_scan_func, gattlib_adapter->scan_loop);
GATTLIB_LOG(GATTLIB_DEBUG, "Bluetooth scan started");
return GATTLIB_SUCCESS;
}
int gattlib_adapter_scan_enable_with_filter(gattlib_adapter_t* adapter, uuid_t **uuid_list, int16_t rssi_threshold, uint32_t enabled_filters,
gattlib_discovered_device_t discovered_device_cb, size_t timeout, void *user_data)
{
GError *error = NULL;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(adapter)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_adapter_scan_enable_with_filter: Adapter not valid (1)");
ret = GATTLIB_ADAPTER_CLOSE;
goto EXIT;
}
g_main_loop_run(gattlib_adapter->scan_loop);
// At this point, either the timeout expired (and automatically was removed) or scan_disable was called, removing the timer.
gattlib_adapter->timeout_id = 0;
// Note: The function only resumes when loop timeout as expired or g_main_loop_quit has been called.
ret = _gattlib_adapter_scan_enable_with_filter(adapter, uuid_list, rssi_threshold, enabled_filters,
discovered_device_cb, timeout, user_data);
if (ret != GATTLIB_SUCCESS) {
goto EXIT;
}
g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(device_manager), added_signal_id);
g_signal_handler_disconnect(G_DBUS_OBJECT_MANAGER(device_manager), changed_signal_id);
adapter->backend.ble_scan.is_scanning = true;
DISABLE_SCAN:
// Stop BLE device discovery
gattlib_adapter_scan_disable(adapter);
adapter->backend.ble_scan.scan_loop_thread = g_thread_try_new("gattlib_ble_scan", _ble_scan_loop_thread, adapter, &error);
if (adapter->backend.ble_scan.scan_loop_thread == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to create BLE scan thread: %s", error->message);
g_error_free(error);
ret = GATTLIB_ERROR_INTERNAL;
goto EXIT;
}
// Free discovered device list
g_slist_foreach(discovered_devices, (GFunc)g_free, NULL);
g_slist_free(discovered_devices);
// We need to release the mutex to ensure we leave the other thread to signal us
g_rec_mutex_unlock(&m_gattlib_mutex);
g_mutex_lock(&m_gattlib_signal.mutex);
while (gattlib_adapter_is_scanning(adapter)) {
g_cond_wait(&m_gattlib_signal.condition, &m_gattlib_signal.mutex);
}
g_mutex_unlock(&m_gattlib_signal.mutex);
// Get the mutex again
g_rec_mutex_lock(&m_gattlib_mutex);
// Ensure the adapter is still valid when we get the mutex again
if (!gattlib_adapter_is_valid(adapter)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_adapter_scan_enable_with_filter: Adapter not valid (2)");
ret = GATTLIB_ADAPTER_CLOSE;
goto EXIT;
}
// Free thread
g_thread_unref(adapter->backend.ble_scan.scan_loop_thread);
adapter->backend.ble_scan.scan_loop_thread = NULL;
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t discovered_device_cb, size_t timeout, void *user_data)
int gattlib_adapter_scan_enable_with_filter_non_blocking(gattlib_adapter_t* adapter, uuid_t **uuid_list, int16_t rssi_threshold, uint32_t enabled_filters,
gattlib_discovered_device_t discovered_device_cb, size_t timeout, void *user_data)
{
GError *error = NULL;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(adapter)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_adapter_scan_enable_with_filter_non_blocking: Adapter not valid (2)");
ret = GATTLIB_ADAPTER_CLOSE;
goto EXIT;
}
ret = _gattlib_adapter_scan_enable_with_filter(adapter, uuid_list, rssi_threshold, enabled_filters,
discovered_device_cb, timeout, user_data);
if (ret != GATTLIB_SUCCESS) {
goto EXIT;
}
adapter->backend.ble_scan.scan_loop_thread = g_thread_try_new("gattlib_ble_scan", _ble_scan_loop_thread, adapter, &error);
if (adapter->backend.ble_scan.scan_loop_thread == NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to create BLE scan thread: %s", error->message);
g_error_free(error);
ret = GATTLIB_ERROR_INTERNAL;
goto EXIT;
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
int gattlib_adapter_scan_enable(gattlib_adapter_t* adapter, gattlib_discovered_device_t discovered_device_cb, size_t timeout, void *user_data)
{
return gattlib_adapter_scan_enable_with_filter(adapter,
NULL, 0 /* RSSI Threshold */,
@ -315,45 +597,172 @@ 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->scan_loop) {
GError *error = NULL;
g_rec_mutex_lock(&m_gattlib_mutex);
org_bluez_adapter1_call_stop_discovery_sync(gattlib_adapter->adapter_proxy, NULL, &error);
// Ignore the error
// Remove timeout
if (gattlib_adapter->timeout_id) {
g_source_remove(gattlib_adapter->timeout_id);
gattlib_adapter->timeout_id = 0;
}
// Ensure the scan loop is quit
if (g_main_loop_is_running(gattlib_adapter->scan_loop)) {
g_main_loop_quit(gattlib_adapter->scan_loop);
}
g_main_loop_unref(gattlib_adapter->scan_loop);
gattlib_adapter->scan_loop = NULL;
if (!gattlib_adapter_is_valid(adapter)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_adapter_scan_disable: Adapter not valid");
ret = GATTLIB_ADAPTER_CLOSE;
goto EXIT;
}
if (adapter->backend.adapter_proxy == NULL) {
GATTLIB_LOG(GATTLIB_INFO, "Could not disable BLE scan. No BLE adapter setup.");
ret = GATTLIB_NO_ADAPTER;
goto EXIT;
}
if (!org_bluez_adapter1_get_discovering(adapter->backend.adapter_proxy)) {
GATTLIB_LOG(GATTLIB_DEBUG, "No discovery in progress. We skip discovery stopping (1).");
goto EXIT;
} else if (!adapter->backend.ble_scan.is_scanning) {
GATTLIB_LOG(GATTLIB_DEBUG, "No discovery in progress. We skip discovery stopping (2).");
goto EXIT;
}
GATTLIB_LOG(GATTLIB_DEBUG, "Stop bluetooth scan.");
org_bluez_adapter1_call_stop_discovery_sync(adapter->backend.adapter_proxy, NULL, &error);
if (error != NULL) {
if (((error->domain == 238) || (error->domain == 239)) && (error->code == 36)) {
GATTLIB_LOG(GATTLIB_WARNING, "No bluetooth scan has been started.");
// Correspond to error: GDBus.Error:org.bluez.Error.Failed: No discovery started
goto EXIT;
} else {
GATTLIB_LOG(GATTLIB_WARNING, "Error while stopping BLE discovery: %s (%d,%d)", error->message, error->domain, error->code);
}
}
// Free and reset callback to stop calling it after we stopped
gattlib_handler_free(&adapter->discovered_device_callback);
// Stop BLE scan loop thread
if (adapter->backend.ble_scan.is_scanning) {
adapter->backend.ble_scan.is_scanning = false;
g_mutex_lock(&m_gattlib_signal.mutex);
m_gattlib_signal.signals |= GATTLIB_SIGNAL_ADAPTER_STOP_SCANNING;
g_cond_broadcast(&m_gattlib_signal.condition);
g_mutex_unlock(&m_gattlib_signal.mutex);
}
// Remove timeout
if (adapter->backend.ble_scan.ble_scan_timeout_id) {
g_source_remove(adapter->backend.ble_scan.ble_scan_timeout_id);
adapter->backend.ble_scan.ble_scan_timeout_id = 0;
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
int gattlib_adapter_close(gattlib_adapter_t* adapter) {
bool are_devices_disconnected;
int ret = GATTLIB_SUCCESS;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_adapter_is_valid(adapter)) {
GATTLIB_LOG(GATTLIB_ERROR, "gattlib_adapter_close: Adapter not valid");
ret = GATTLIB_ADAPTER_CLOSE;
goto EXIT;
}
are_devices_disconnected = gattlib_devices_are_disconnected(adapter);
if (!are_devices_disconnected) {
GATTLIB_LOG(GATTLIB_ERROR, "Adapter cannot be closed as some devices are not disconnected");
ret = GATTLIB_BUSY;
goto EXIT;
}
GSList *adapter_entry = g_slist_find(m_adapter_list, adapter);
if (adapter_entry == NULL) {
GATTLIB_LOG(GATTLIB_WARNING, "Adapter has already been closed");
goto EXIT;
}
GATTLIB_LOG(GATTLIB_DEBUG, "Close bluetooth adapter %s", adapter->name);
if (adapter->backend.ble_scan.is_scanning) {
GATTLIB_LOG(GATTLIB_DEBUG, "Bluetooth adapter %s was scanning. We stop the scan", adapter->name);
gattlib_adapter_scan_disable(adapter);
// We must release gattlib mutex to not block the library
// We must also increase reference counter to not wait for a thread that has been freed
GThread *scan_loop_thread = adapter->backend.ble_scan.scan_loop_thread;
g_thread_ref(scan_loop_thread);
g_rec_mutex_unlock(&m_gattlib_mutex);
_wait_scan_loop_stop_scanning(adapter);
g_thread_join(adapter->backend.ble_scan.scan_loop_thread);
// At this stage scan_loop_thread should have completed
g_rec_mutex_lock(&m_gattlib_mutex);
g_thread_unref(scan_loop_thread);
}
// Unref/Free the adapter
gattlib_adapter_unref(adapter);
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}
int gattlib_adapter_ref(gattlib_adapter_t* adapter) {
g_rec_mutex_lock(&m_gattlib_mutex);
adapter->reference_counter++;
g_rec_mutex_unlock(&m_gattlib_mutex);
return GATTLIB_SUCCESS;
}
int gattlib_adapter_close(void* adapter)
{
struct gattlib_adapter *gattlib_adapter = adapter;
int gattlib_adapter_unref(gattlib_adapter_t* adapter) {
int ret = GATTLIB_SUCCESS;
g_object_unref(gattlib_adapter->device_manager);
g_object_unref(gattlib_adapter->adapter_proxy);
free(gattlib_adapter);
g_rec_mutex_lock(&m_gattlib_mutex);
adapter->reference_counter--;
return GATTLIB_SUCCESS;
}
gboolean stop_scan_func(gpointer data)
{
g_main_loop_quit(data);
return FALSE;
if (adapter->reference_counter > 0) {
goto EXIT;
}
// Ensure the thread is freed on adapter closing
if (adapter->backend.ble_scan.scan_loop_thread) {
g_thread_unref(adapter->backend.ble_scan.scan_loop_thread);
adapter->backend.ble_scan.scan_loop_thread = NULL;
}
if (adapter->backend.device_manager) {
g_object_unref(adapter->backend.device_manager);
adapter->backend.device_manager = NULL;
}
if (adapter->backend.adapter_proxy != NULL) {
g_object_unref(adapter->backend.adapter_proxy);
adapter->backend.adapter_proxy = NULL;
}
if (adapter->id != NULL) {
free(adapter->id);
adapter->id = NULL;
}
if (adapter->name != NULL) {
free(adapter->name);
adapter->name = NULL;
}
gattlib_devices_free(adapter);
// Remove adapter from the global list
m_adapter_list = g_slist_remove(m_adapter_list, adapter);
free(adapter);
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return ret;
}

View File

@ -1,40 +1,23 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* GattLib - GATT Library
*
* Copyright (C) 2016-2020 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
*
* Copyright (c) 2016-2021, Olivier Martin <olivier@labapart.org>
*/
#include "gattlib_internal.h"
#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;
}
@ -43,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;
@ -52,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) {
fprintf(stderr, "Warning: Manufacturer Data with multiple children: %s\n",
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);
@ -129,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) */

117
dbus/gattlib_backend.h Normal file
View File

@ -0,0 +1,117 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
*/
#ifndef __GATTLIB_BACKEND_H__
#define __GATTLIB_BACKEND_H__
#include <assert.h>
#include <pthread.h>
#include "gattlib.h"
#include "org-bluez-adaptater1.h"
#include "org-bluez-device1.h"
#include "org-bluez-gattcharacteristic1.h"
#include "org-bluez-gattdescriptor1.h"
#include "org-bluez-gattservice1.h"
#if defined(WITH_PYTHON)
#include <Python.h>
#define PYTHON_VERSIONS(major, minor) (((major) << 8) | (minor))
#define PYTHON_VERSION PYTHON_VERSIONS(PYTHON_VERSION_MAJOR, PYTHON_VERSION_MINOR)
#endif
#include "bluez5/lib/uuid.h"
#define BLUEZ_VERSIONS(major, minor) (((major) << 8) | (minor))
#define BLUEZ_VERSION BLUEZ_VERSIONS(BLUEZ_VERSION_MAJOR, BLUEZ_VERSION_MINOR)
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
#include "org-bluez-battery1.h"
#endif
#define GATTLIB_DEFAULT_ADAPTER "hci0"
// 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;
// ID of the timeout to know if we managed to connect to the device
guint connection_timeout_id;
// ID of the device property change signal
guint on_handle_device_property_change_id;
// List of DBUS Object managed by 'adapter->device_manager'
GList *dbus_objects;
// List of 'OrgBluezGattCharacteristic1*' which has an attached notification
GList *notified_characteristics;
};
struct _gattlib_adapter_backend {
GDBusObjectManager *device_manager;
OrgBluezAdapter1 *adapter_proxy;
// Internal attributes only needed during BLE scanning
struct {
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;
uint32_t enabled_filters;
} ble_scan;
};
struct dbus_characteristic {
union {
OrgBluezGattCharacteristic1 *gatt;
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
OrgBluezBattery1 *battery;
#endif
};
enum {
TYPE_NONE = 0,
TYPE_GATT,
TYPE_BATTERY_LEVEL
} type;
};
extern const uuid_t m_battery_level_uuid;
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);
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(gattlib_adapter_t* gattlib_adapter, OrgBluezDevice1* device1);
// Invoke when a new device is being connected
void gattlib_on_connected_device(gattlib_connection_t* connection);
// Invoke when a new device is being disconnected
void gattlib_on_disconnected_device(gattlib_connection_t* connection);
// Invoke when a new device receive a GATT notification
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(struct _gattlib_connection_backend* backend);
#endif

View File

@ -1,24 +1,7 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* GattLib - GATT Library
*
* Copyright (C) 2016-2019 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
*
* Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
*/
#include <stdlib.h>
@ -35,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;
@ -43,7 +26,7 @@ static bool handle_dbus_gattcharacteristic_from_path(gattlib_context_t* conn_con
*error = NULL;
characteristic = org_bluez_gatt_characteristic1_proxy_new_for_bus_sync (
G_BUS_TYPE_SYSTEM,
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
G_DBUS_PROXY_FLAGS_NONE,
"org.bluez",
object_path,
NULL,
@ -52,6 +35,12 @@ static bool handle_dbus_gattcharacteristic_from_path(gattlib_context_t* conn_con
if (uuid != NULL) {
uuid_t characteristic_uuid;
const gchar *characteristic_uuid_str = org_bluez_gatt_characteristic1_get_uuid(characteristic);
if (characteristic_uuid_str == NULL) {
// It should not be expected to get NULL from GATT characteristic UUID but we still test it
GATTLIB_LOG(GATTLIB_ERROR, "Error: %s path unexpectly returns a NULL UUID.", object_path);
g_object_unref(characteristic);
return false;
}
gattlib_string_to_uuid(characteristic_uuid_str, strlen(characteristic_uuid_str) + 1, &characteristic_uuid);
@ -65,14 +54,14 @@ static bool handle_dbus_gattcharacteristic_from_path(gattlib_context_t* conn_con
*error = NULL;
OrgBluezGattService1* service = org_bluez_gatt_service1_proxy_new_for_bus_sync (
G_BUS_TYPE_SYSTEM,
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
G_DBUS_PROXY_FLAGS_NONE,
"org.bluez",
org_bluez_gatt_characteristic1_get_service(characteristic),
NULL,
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);
@ -90,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;
@ -98,7 +87,7 @@ static bool handle_dbus_battery_from_uuid(gattlib_context_t* conn_context, const
*error = NULL;
battery = org_bluez_battery1_proxy_new_for_bus_sync (
G_BUS_TYPE_SYSTEM,
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
G_DBUS_PROXY_FLAGS_NONE,
"org.bluez",
object_path,
NULL,
@ -112,33 +101,44 @@ 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;
GDBusObjectManager *device_manager = get_device_manager_from_adapter(conn_context->adapter);
struct dbus_characteristic get_characteristic_from_uuid(gattlib_connection_t* connection, const uuid_t* uuid) {
GError *error = NULL;
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) {
fprintf(stderr, "Gattlib Context not initialized.\n");
return dbus_characteristic; // Return characteristic of type TYPE_NONE
if (error != NULL) {
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib Context not initialized (%d, %d).", error->domain, error->code);
g_error_free(error);
} else {
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib Context not initialized.");
}
goto EXIT; // Return characteristic of type TYPE_NONE
}
// Some GATT Characteristics are handled by D-BUS
if (gattlib_uuid_cmp(uuid, &m_battery_level_uuid) == 0) {
is_battery_level_uuid = true;
} else if (gattlib_uuid_cmp(uuid, &m_ccc_uuid) == 0) {
fprintf(stderr, "Error: Bluez v5.42+ does not expose Client Characteristic Configuration Descriptor through DBUS interface\n");
return dbus_characteristic;
GATTLIB_LOG(GATTLIB_ERROR, "Error: Bluez v5.42+ does not expose Client Characteristic Configuration Descriptor through DBUS interface");
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;
bool found = false;
GDBusObject *object = l->data;
const char* object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object));
@ -146,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;
}
@ -158,36 +158,49 @@ 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;
}
}
#else
fprintf(stderr, "You might use Bluez v5.48 with gattlib built for pre-v5.40\n");
GATTLIB_LOG(GATTLIB_ERROR, "You might use Bluez v5.48 with gattlib built for pre-v5.40");
#endif
}
}
EXIT:
g_rec_mutex_unlock(&m_gattlib_mutex);
return dbus_characteristic;
}
static struct dbus_characteristic get_characteristic_from_handle(gatt_connection_t* connection, int handle) {
gattlib_context_t* conn_context = connection->context;
GDBusObjectManager *device_manager = get_device_manager_from_adapter(conn_context->adapter);
static struct dbus_characteristic get_characteristic_from_handle(gattlib_connection_t* connection, unsigned int handle) {
GError *error = NULL;
int char_handle;
unsigned int char_handle;
GDBusObjectManager *device_manager;
struct dbus_characteristic dbus_characteristic = {
.type = TYPE_NONE
.type = TYPE_NONE
};
if (device_manager == NULL) {
fprintf(stderr, "Gattlib context not initialized.\n");
return dbus_characteristic;
g_rec_mutex_lock(&m_gattlib_mutex);
if (!gattlib_connection_is_connected(connection)) {
goto EXIT;
}
for (GList *l = conn_context->dbus_objects; l != NULL; l = l->next) {
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);
g_error_free(error);
} else {
GATTLIB_LOG(GATTLIB_ERROR, "Gattlib Context not initialized.");
}
goto EXIT;
}
for (GList *l = connection->backend.dbus_objects; l != NULL; l = l->next) {
GDBusInterface *interface;
bool found;
GDBusObject *object = l->data;
@ -205,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;
}
@ -230,9 +245,10 @@ static int read_gatt_characteristic(struct dbus_characteristic *dbus_characteris
g_variant_builder_unref(options);
#endif
if (error != NULL) {
fprintf(stderr, "Failed to read DBus GATT characteristic: %s\n", error->message);
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
GATTLIB_LOG(GATTLIB_ERROR, "Failed to read DBus GATT characteristic: %s", error->message);
g_error_free(error);
return GATTLIB_ERROR_DBUS;
return ret;
}
gsize n_elements = 0;
@ -256,10 +272,16 @@ EXIT:
}
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
static int read_battery_level(struct dbus_characteristic *dbus_characteristic, void* buffer, size_t* buffer_len) {
static int read_battery_level(struct dbus_characteristic *dbus_characteristic, void** buffer, size_t* buffer_len) {
guchar percentage = org_bluez_battery1_get_percentage(dbus_characteristic->battery);
memcpy(buffer, &percentage, sizeof(uint8_t));
*buffer = malloc(sizeof(uint8_t));
if (buffer == NULL) {
*buffer_len = 0;
return GATTLIB_OUT_OF_MEMORY;
}
memcpy(*buffer, &percentage, sizeof(uint8_t));
*buffer_len = sizeof(uint8_t);
g_object_unref(dbus_characteristic->battery);
@ -267,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;
@ -290,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;
@ -325,9 +357,9 @@ int gattlib_read_char_by_uuid_async(gatt_connection_t* connection, uuid_t* uuid,
g_variant_builder_unref(options);
#endif
if (error != NULL) {
fprintf(stderr, "Failed to read DBus GATT characteristic: %s\n", error->message);
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
GATTLIB_LOG(GATTLIB_ERROR, "Failed to read DBus GATT characteristic: %s", error->message);
g_error_free(error);
ret = GATTLIB_ERROR_DBUS;
goto EXIT;
}
@ -364,9 +396,15 @@ static int write_char(struct dbus_characteristic *dbus_characteristic, const voi
#endif
if (error != NULL) {
fprintf(stderr, "Failed to write DBus GATT characteristic: %s\n", error->message);
if ((error->domain == 238) && (error->code == 36)) {
ret = GATTLIB_DEVICE_NOT_CONNECTED;
} else {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to write DBus GATT characteristic: %s (%d,%d)",
error->message, error->domain, error->code);
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
}
g_error_free(error);
return GATTLIB_ERROR_DBUS;
return ret;
}
//
@ -377,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;
@ -396,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;
@ -411,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;
@ -430,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;
@ -444,3 +502,7 @@ int gattlib_write_without_response_char_by_handle(gatt_connection_t* connection,
g_object_unref(dbus_characteristic.gatt);
return ret;
}
void gattlib_characteristic_free_value(void *ptr) {
free(ptr);
}

View File

@ -1,107 +0,0 @@
/*
*
* GattLib - GATT Library
*
* Copyright (C) 2016-2020 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
*
*/
#ifndef __GATTLIB_INTERNAL_H__
#define __GATTLIB_INTERNAL_H__
#include <assert.h>
#include "gattlib_internal_defs.h"
#include "gattlib.h"
#include "org-bluez-adaptater1.h"
#include "org-bluez-device1.h"
#include "org-bluez-gattcharacteristic1.h"
#include "org-bluez-gattdescriptor1.h"
#include "org-bluez-gattservice1.h"
#include "bluez5/lib/uuid.h"
#define BLUEZ_VERSIONS(major, minor) (((major) << 8) | (minor))
#define BLUEZ_VERSION BLUEZ_VERSIONS(BLUEZ_VERSION_MAJOR, BLUEZ_VERSION_MINOR)
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
#include "org-bluez-battery1.h"
#endif
#define GATTLIB_DEFAULT_ADAPTER "hci0"
typedef struct {
struct gattlib_adapter *adapter;
char* device_object_path;
OrgBluezDevice1* device;
// This attribute is only used during the connection stage. By placing the attribute here, we can pass
// `gatt_connection_t` to
GMainLoop *connection_loop;
// ID of the timeout to know if we managed to connect to the device
guint connection_timeout;
// List of DBUS Object managed by 'adapter->device_manager'
GList *dbus_objects;
// List of 'OrgBluezGattCharacteristic1*' which has an attached notification
GList *notified_characteristics;
} gattlib_context_t;
struct gattlib_adapter {
GDBusObjectManager *device_manager;
OrgBluezAdapter1 *adapter_proxy;
char* adapter_name;
GMainLoop *scan_loop;
guint timeout_id;
};
struct dbus_characteristic {
union {
OrgBluezGattCharacteristic1 *gatt;
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
OrgBluezBattery1 *battery;
#endif
};
enum {
TYPE_NONE = 0,
TYPE_GATT,
TYPE_BATTERY_LEVEL
} type;
};
extern const uuid_t m_battery_level_uuid;
gboolean stop_scan_func(gpointer data);
struct gattlib_adapter *init_default_adapter(void);
GDBusObjectManager *get_device_manager_from_adapter(struct gattlib_adapter *gattlib_adapter);
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);
struct dbus_characteristic get_characteristic_from_uuid(gatt_connection_t* connection, const uuid_t* uuid);
void disconnect_all_notifications(gattlib_context_t* conn_context);
#endif

View File

@ -1,24 +1,7 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* GattLib - GATT Library
*
* Copyright (C) 2016-2020 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
*
* Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
*/
#include <glib.h>
@ -42,7 +25,18 @@ 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'
@ -58,7 +52,7 @@ gboolean on_handle_battery_level_property_change(
// GATT connection notifiying to Battery level
percentage = g_variant_get_byte(value);
gattlib_call_notification_handler(&connection->notification,
gattlib_on_gatt_notification(connection,
&m_battery_level_uuid,
(const uint8_t*)&percentage, sizeof(percentage));
break;
@ -67,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
@ -77,35 +72,47 @@ 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;
g_variant_dict_init(&dict, arg_changed_properties);
// Retrieve 'Value' from 'arg_changed_properties'
if (g_variant_n_children (arg_changed_properties) > 0) {
GVariantIter *iter;
const gchar *key;
GVariant *value;
GVariant* value = g_variant_dict_lookup_value(&dict, "Value", NULL);
if (value != NULL) {
uuid_t uuid;
size_t data_length;
const uint8_t* data = g_variant_get_fixed_array(value, &data_length, sizeof(guchar));
g_variant_get (arg_changed_properties, "a{sv}", &iter);
while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) {
if (strcmp(key, "Value") == 0) {
uuid_t uuid;
size_t data_length;
const uint8_t* data = g_variant_get_fixed_array(value, &data_length, sizeof(guchar));
// Dump the content of the notification
//GATTLIB_LOG(GATTLIB_DEBUG, "on_handle_characteristic_property_change: %s: %s", key, g_variant_print(value, TRUE));
GATTLIB_LOG(GATTLIB_DEBUG, "on_handle_characteristic_property_change: Value: Received %d bytes", data_length);
gattlib_string_to_uuid(
org_bluez_gatt_characteristic1_get_uuid(object),
MAX_LEN_UUID_STR + 1,
&uuid);
gattlib_string_to_uuid(
org_bluez_gatt_characteristic1_get_uuid(object),
MAX_LEN_UUID_STR + 1,
&uuid);
gattlib_call_notification_handler(&connection->notification,
&uuid, data, data_length);
break;
}
}
g_variant_iter_free(iter);
gattlib_on_gatt_notification(connection, &uuid, data, data_length);
// As per https://developer.gnome.org/glib/stable/glib-GVariant.html#g-variant-iter-loop, clean up `key` and `value`.
g_variant_unref(value);
}
g_variant_dict_end(&dict);
} else {
GATTLIB_LOG(GATTLIB_DEBUG, "on_handle_characteristic_property_change: not a notification handler");
}
g_rec_mutex_unlock(&m_gattlib_mutex);
return TRUE;
}
@ -115,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'
@ -126,6 +133,9 @@ static gboolean on_handle_characteristic_indication(
g_variant_get (arg_changed_properties, "a{sv}", &iter);
while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) {
GATTLIB_LOG(GATTLIB_DEBUG, "on_handle_characteristic_indication: %s:%s",
key, g_variant_print(value, TRUE));
if (strcmp(key, "Value") == 0) {
uuid_t uuid;
size_t data_length;
@ -136,24 +146,39 @@ static gboolean on_handle_characteristic_indication(
MAX_LEN_UUID_STR + 1,
&uuid);
gattlib_call_notification_handler(&connection->indication,
&uuid, data, data_length);
gattlib_on_gatt_notification(connection, &uuid, data, data_length);
break;
}
}
g_variant_iter_free(iter);
}
} else {
GATTLIB_LOG(GATTLIB_DEBUG, "on_handle_characteristic_indication: Not a valid indication handler");
}
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;
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) {
puts("Not found");
return GATTLIB_NOT_FOUND;
char uuid_str[MAX_LEN_UUID_STR + 1];
gattlib_uuid_to_string(uuid, uuid_str, sizeof(uuid_str));
GATTLIB_LOG(GATTLIB_ERROR, "GATT characteristic '%s' not found", uuid_str);
ret = GATTLIB_NOT_FOUND;
goto EXIT;
}
#if BLUEZ_VERSION > BLUEZ_VERSIONS(5, 40)
else if (dbus_characteristic.type == TYPE_BATTERY_LEVEL) {
@ -163,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);
}
@ -175,49 +201,63 @@ static int connect_signal_to_characteristic_uuid(gatt_connection_t* connection,
G_CALLBACK(callback),
connection);
if (signal_id == 0) {
fprintf(stderr, "Failed to connect signal to DBus GATT notification\n");
return GATTLIB_ERROR_DBUS;
GATTLIB_LOG(GATTLIB_ERROR, "Failed to connect signal to DBus GATT notification");
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);
if (error) {
fprintf(stderr, "Failed to start DBus GATT notification: %s\n", error->message);
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 GATTLIB_ERROR_DBUS;
} 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);
@ -229,38 +269,40 @@ static int disconnect_signal_to_characteristic_uuid(gatt_connection_t* connectio
free(notification_handle);
if (error) {
fprintf(stderr, "Failed to stop DBus GATT notification: %s\n", error->message);
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);
}
void disconnect_all_notifications(gattlib_context_t* conn_context) {
// Find notification handle
for (GList *l = conn_context->notified_characteristics; l != NULL; l = l->next) {
struct gattlib_notification_handle *notification_handle = l->data;
static void end_notification(void *notified_characteristic) {
struct gattlib_notification_handle *notification_handle = notified_characteristic;
g_signal_handler_disconnect(notification_handle->gatt, notification_handle->signal_id);
free(notification_handle);
}
g_list_free_full(conn_context->notified_characteristics, g_object_unref);
g_signal_handler_disconnect(notification_handle->gatt, notification_handle->signal_id);
free(notification_handle);
}
void disconnect_all_notifications(struct _gattlib_connection_backend* backend) {
g_list_free_full(g_steal_pointer(&backend->notified_characteristics), end_notification);
}

View File

@ -1,57 +1,49 @@
/*
* SPDX-License-Identifier: BSD-3-Clause
*
* GattLib - GATT Library
*
* Copyright (C) 2016-2019 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
*
* Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
*/
#include <errno.h>
#include <gio/gunixfdlist.h>
#include "gattlib_internal.h"
#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(
@ -65,32 +57,38 @@ int gattlib_write_char_by_uuid_stream_open(gatt_connection_t* connection, uuid_t
g_variant_builder_unref(variant_options);
if (error != NULL) {
fprintf(stderr, "Failed to acquired write DBus GATT characteristic: %s\n", error->message);
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
GATTLIB_LOG(GATTLIB_ERROR, "Failed to acquired write DBus GATT characteristic: %s", error->message);
g_error_free(error);
return GATTLIB_ERROR_DBUS;
return ret;
}
error = NULL;
fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(out_fd), &error);
if (error != NULL) {
fprintf(stderr, "Failed to retrieve Unix File Descriptor: %s\n", error->message);
ret = GATTLIB_ERROR_DBUS_WITH_ERROR(error);
GATTLIB_LOG(GATTLIB_ERROR, "Failed to retrieve Unix File Descriptor: %s", error->message);
g_error_free(error);
return GATTLIB_ERROR_DBUS;
return ret;
}
// 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)
{
write((unsigned long)stream, buffer, buffer_len);
return GATTLIB_SUCCESS;
ssize_t ret = write((unsigned long)stream, buffer, buffer_len);
if (ret < 0) {
return GATTLIB_ERROR_UNIX_WITH_ERROR(errno);
} else {
return GATTLIB_SUCCESS;
}
}
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

@ -1,7 +1,7 @@
#
# GattLib - GATT Library
#
# Copyright (C) 2016-2020 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
@ -19,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 2.6)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -1,22 +1,50 @@
/*
*
* GattLib - GATT Library
*
* Copyright (C) 2021 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 <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
#include <syslog.h>
#endif
#include "gattlib.h"
#define BLE_SCAN_TIMEOUT 60
static void ble_advertising_device(void *adapter, const char* addr, const char* name, void *user_data) {
static const char* adapter_name;
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;
}
@ -27,30 +55,23 @@ 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");
}
int main(int argc, const char *argv[]) {
const char* adapter_name;
void* adapter;
static void* ble_task(void *arg) {
gattlib_adapter_t* adapter;
int ret;
if (argc == 1) {
adapter_name = NULL;
} else if (argc == 2) {
adapter_name = argv[1];
} else {
fprintf(stderr, "%s [<bluetooth-adapter>]\n", argv[0]);
return 1;
}
ret = gattlib_adapter_open(adapter_name, &adapter);
if (ret) {
fprintf(stderr, "ERROR: Failed to open adapter.\n");
return 1;
GATTLIB_LOG(GATTLIB_ERROR, "Failed to open adapter.");
return NULL;
}
ret = gattlib_adapter_scan_enable_with_filter(adapter,
@ -61,7 +82,7 @@ int main(int argc, const char *argv[]) {
0, /* timeout=0 means infinite loop */
NULL /* user_data */);
if (ret) {
fprintf(stderr, "ERROR: Failed to scan.\n");
GATTLIB_LOG(GATTLIB_ERROR, "Failed to scan.");
goto EXIT;
}
@ -71,5 +92,30 @@ int main(int argc, const char *argv[]) {
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) {
adapter_name = argv[1];
} else {
GATTLIB_LOG(GATTLIB_ERROR, "%s [<bluetooth-adapter>]", argv[0]);
return 1;
}
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
openlog("gattlib_advertisement_dat", 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;
}

View File

@ -1,7 +1,7 @@
#
# GattLib - GATT Library
#
# Copyright (C) 2016-2017 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
@ -19,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 2.6)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -1,12 +1,40 @@
/*
*
* 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 <pthread.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/queue.h>
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
#include <syslog.h>
#endif
#include "gattlib.h"
#define BLE_SCAN_TIMEOUT 4
#define BLE_SCAN_TIMEOUT 10
static const char* adapter_name;
typedef void (*ble_discovered_device_t)(const char* addr, const char* name);
@ -16,35 +44,21 @@ static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
LIST_HEAD(listhead, connection_t) g_ble_connections;
struct connection_t {
pthread_t thread;
gattlib_adapter_t* adapter;
char* addr;
LIST_ENTRY(connection_t) entries;
};
static void *ble_connect_device(void *arg) {
struct connection_t *connection = arg;
char* addr = connection->addr;
gatt_connection_t* gatt_connection;
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;
pthread_mutex_lock(&g_mutex);
printf("------------START %s ---------------\n", addr);
gatt_connection = gattlib_connect(NULL, addr, GATTLIB_CONNECTION_OPTIONS_LEGACY_DEFAULT);
if (gatt_connection == NULL) {
fprintf(stderr, "Fail to connect to the bluetooth device.\n");
goto connection_exit;
} else {
puts("Succeeded to connect to the bluetooth device.");
}
ret = gattlib_discover_primary(gatt_connection, &services, &services_count);
ret = gattlib_discover_primary(connection, &services, &services_count);
if (ret != 0) {
fprintf(stderr, "Fail to discover primary services.\n");
GATTLIB_LOG(GATTLIB_ERROR, "Fail to discover primary services.");
goto disconnect_exit;
}
@ -57,9 +71,9 @@ static void *ble_connect_device(void *arg) {
}
free(services);
ret = gattlib_discover_char(gatt_connection, &characteristics, &characteristics_count);
ret = gattlib_discover_char(connection, &characteristics, &characteristics_count);
if (ret != 0) {
fprintf(stderr, "Fail to discover characteristics.\n");
GATTLIB_LOG(GATTLIB_ERROR, "Fail to discover characteristics.");
goto disconnect_exit;
}
for (i = 0; i < characteristics_count; i++) {
@ -72,15 +86,28 @@ static void *ble_connect_device(void *arg) {
free(characteristics);
disconnect_exit:
gattlib_disconnect(gatt_connection);
gattlib_disconnect(connection, false /* wait_disconnection */);
}
static void *ble_connect_device(void *arg) {
struct connection_t *connection = arg;
char* addr = connection->addr;
int ret;
pthread_mutex_lock(&g_mutex);
printf("------------START %s ---------------\n", addr);
ret = gattlib_connect(connection->adapter, connection->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'", connection->addr);
}
connection_exit:
printf("------------DONE %s ---------------\n", addr);
pthread_mutex_unlock(&g_mutex);
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;
@ -90,48 +117,37 @@ 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) {
fprintf(stderr, "Failt to allocate connection.\n");
GATTLIB_LOG(GATTLIB_ERROR, "Failt to allocate connection.");
return;
}
connection->addr = strdup(addr);
connection->adapter = adapter;
ret = pthread_create(&connection->thread, NULL, ble_connect_device, connection);
if (ret != 0) {
fprintf(stderr, "Failt to create BLE connection thread.\n");
GATTLIB_LOG(GATTLIB_ERROR, "Failt to create BLE connection thread.");
free(connection);
return;
}
LIST_INSERT_HEAD(&g_ble_connections, connection, entries);
}
int main(int argc, const char *argv[]) {
const char* adapter_name;
void* adapter;
static void* ble_task(void* arg) {
gattlib_adapter_t* adapter;
int ret;
if (argc == 1) {
adapter_name = NULL;
} else if (argc == 2) {
adapter_name = argv[1];
} else {
fprintf(stderr, "%s [<bluetooth-adapter>]\n", argv[0]);
return 1;
}
LIST_INIT(&g_ble_connections);
ret = gattlib_adapter_open(adapter_name, &adapter);
if (ret) {
fprintf(stderr, "ERROR: Failed to open adapter.\n");
return 1;
GATTLIB_LOG(GATTLIB_ERROR, "Failed to open adapter.");
return NULL;
}
pthread_mutex_lock(&g_mutex);
ret = gattlib_adapter_scan_enable(adapter, ble_discovered_device, BLE_SCAN_TIMEOUT, NULL /* user_data */);
if (ret) {
fprintf(stderr, "ERROR: Failed to scan.\n");
GATTLIB_LOG(GATTLIB_ERROR, "Failed to scan.");
goto EXIT;
}
@ -151,5 +167,32 @@ int main(int argc, const char *argv[]) {
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) {
adapter_name = argv[1];
} else {
printf("%s [<bluetooth-adapter>]\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
LIST_INIT(&g_ble_connections);
ret = gattlib_mainloop(ble_task, NULL);
if (ret != GATTLIB_SUCCESS) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to create gattlib mainloop");
}
return ret;
}

View File

@ -1,7 +1,7 @@
#
# GattLib - GATT Library
#
# Copyright (C) 2016-2017 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
@ -19,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 2.6)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -2,7 +2,7 @@
*
* GattLib - GATT Library
*
* Copyright (C) 2016 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,41 +21,46 @@
*
*/
#include <ctype.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
#include <syslog.h>
#endif
#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;
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) {
fprintf(stderr, "Fail to connect to the bluetooth device.\n");
return 1;
}
GATTLIB_LOG(GATTLIB_INFO, "Connected to bluetooth device '%s'", dst);
ret = gattlib_discover_primary(connection, &services, &services_count);
if (ret != GATTLIB_SUCCESS) {
fprintf(stderr, "Fail to discover primary services.\n");
return 1;
GATTLIB_LOG(GATTLIB_ERROR, "Fail to discover primary services.");
goto EXIT;
}
for (i = 0; i < services_count; i++) {
gattlib_uuid_to_string(&services[i].uuid, uuid_str, sizeof(uuid_str));
printf("service[%d] start_handle:%02x end_handle:%02x uuid:%s\n", i,
GATTLIB_LOG(GATTLIB_INFO, "service[%d] start_handle:%02x end_handle:%02x uuid:%s", i,
services[i].attr_handle_start, services[i].attr_handle_end,
uuid_str);
}
@ -63,18 +68,96 @@ int main(int argc, char *argv[])
ret = gattlib_discover_char(connection, &characteristics, &characteristics_count);
if (ret != GATTLIB_SUCCESS) {
fprintf(stderr, "Fail to discover characteristics.\n");
return 1;
GATTLIB_LOG(GATTLIB_ERROR, "Fail to discover characteristics.");
goto EXIT;
}
for (i = 0; i < characteristics_count; i++) {
gattlib_uuid_to_string(&characteristics[i].uuid, uuid_str, sizeof(uuid_str));
printf("characteristic[%d] properties:%02x value_handle:%04x uuid:%s\n", i,
GATTLIB_LOG(GATTLIB_INFO, "characteristic[%d] properties:%02x value_handle:%04x uuid:%s", i,
characteristics[i].properties, characteristics[i].value_handle,
uuid_str);
}
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

@ -1,7 +1,7 @@
#
# GattLib - GATT Library
#
# Copyright (C) 2016-2020 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
@ -19,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 2.6)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -1,13 +1,35 @@
//#include <pthread.h>
//#include <stdio.h>
//#include <stdint.h>
//#include <stdlib.h>
//#include <sys/queue.h>
/*
*
* 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
*
*/
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
#include <syslog.h>
#endif
#include "gattlib.h"
#define BLE_SCAN_EDDYSTONE_TIMEOUT 20
const char* m_adapter_name;
/**
* @brief Handler called on new discovered BLE device
*
@ -21,9 +43,9 @@
* @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");
@ -49,7 +71,7 @@ void on_eddystone_found(void *adapter, const char* addr, const char* name,
puts("\tEddystone EID");
break;
default:
fprintf(stderr, "\tEddystone ID %d not supported\n", advertisement_data_ptr->data[0]);
printf("\tEddystone ID %d not supported\n", advertisement_data_ptr->data[0]);
}
}
}
@ -58,32 +80,22 @@ void on_eddystone_found(void *adapter, const char* addr, const char* name,
gattlib_adapter_scan_disable(adapter);
}
int main(int argc, const char *argv[]) {
const char* adapter_name;
void* adapter;
static void* ble_task(void* arg) {
gattlib_adapter_t* adapter = NULL;
int ret;
if (argc == 1) {
adapter_name = NULL;
} else if (argc == 2) {
adapter_name = argv[1];
} else {
fprintf(stderr, "%s [<bluetooth-adapter>]\n", argv[0]);
return 1;
}
ret = gattlib_adapter_open(adapter_name, &adapter);
if (ret) {
fprintf(stderr, "ERROR: Failed to open adapter.\n");
return 1;
ret = gattlib_adapter_open(m_adapter_name, &adapter);
if (ret != GATTLIB_SUCCESS) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to open adapter.");
return NULL;
}
ret = gattlib_adapter_scan_eddystone(adapter,
0, /* rssi_threshold. The value is not relevant as we do not pass GATTLIB_EDDYSTONE_LIMIT_RSSI */
GATTLIB_EDDYSTONE_TYPE_URL,
on_eddystone_found, BLE_SCAN_EDDYSTONE_TIMEOUT, NULL);
if (ret) {
fprintf(stderr, "ERROR: Failed to scan.\n");
if (ret != GATTLIB_SUCCESS) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to scan.");
goto EXIT;
}
@ -91,5 +103,30 @@ int main(int argc, const char *argv[]) {
EXIT:
gattlib_adapter_close(adapter);
return NULL;
}
int main(int argc, const char *argv[]) {
int ret;
if (argc == 1) {
m_adapter_name = NULL;
} else if (argc == 2) {
m_adapter_name = argv[1];
} else {
printf("%s [<bluetooth-adapter>]\n", argv[0]);
return 1;
}
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
openlog("gattlib_find_eddystone", 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;
}

View File

@ -1,7 +1,7 @@
#
# GattLib - GATT Library
#
# Copyright (C) 2016-2019 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
@ -19,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 2.6)
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;
@ -266,7 +265,7 @@ static gboolean characteristics_read(gpointer user_data)
}
g_print("\n");
free(buffer);
gattlib_characteristic_free_value(buffer);
return TRUE;
}
}
@ -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

@ -1,8 +1,7 @@
#
#
# GattLib - GATT Library
#
# Copyright (C) 2017 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
@ -20,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 2.6)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -2,7 +2,7 @@
*
* GattLib - GATT Library
*
* Copyright (C) 2016-2019 Olivier Martin <olivier@labapart.org>
* Copyright (C) 2016-2021 Olivier Martin <olivier@labapart.org>
*
*
* This program is free software; you can redistribute it and/or modify
@ -26,6 +26,10 @@
#include <stdlib.h>
#include <signal.h>
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
#include <syslog.h>
#endif
#include "gattlib.h"
#define MIN(a,b) ((a)<(b)?(a):(b))
@ -33,13 +37,14 @@
#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;
for(i = 0; i < data_length; i++) {
printf("%c", data[i]);
}
fflush(stdout);
}
static void usage(char *argv[]) {
@ -47,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);
}
@ -63,23 +68,28 @@ int main(int argc, char *argv[]) {
return 1;
}
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
openlog("gattlib_nordic_uart", LOG_CONS | LOG_NDELAY | LOG_PERROR, LOG_USER);
setlogmask(LOG_UPTO(LOG_INFO));
#endif
m_connection = gattlib_connect(NULL, argv[1],
GATTLIB_CONNECTION_OPTIONS_LEGACY_BDADDR_LE_RANDOM |
GATTLIB_CONNECTION_OPTIONS_LEGACY_BT_SEC_LOW);
if (m_connection == NULL) {
fprintf(stderr, "Fail to connect to the bluetooth device.\n");
GATTLIB_LOG(GATTLIB_ERROR, "Fail to connect to the bluetooth device.");
return 1;
}
// Convert characteristics to their respective UUIDs
ret = gattlib_string_to_uuid(NUS_CHARACTERISTIC_TX_UUID, strlen(NUS_CHARACTERISTIC_TX_UUID) + 1, &nus_characteristic_tx_uuid);
if (ret) {
fprintf(stderr, "Fail to convert characteristic TX to UUID.\n");
GATTLIB_LOG(GATTLIB_ERROR, "Fail to convert characteristic TX to UUID.");
return 1;
}
ret = gattlib_string_to_uuid(NUS_CHARACTERISTIC_RX_UUID, strlen(NUS_CHARACTERISTIC_RX_UUID) + 1, &nus_characteristic_rx_uuid);
if (ret) {
fprintf(stderr, "Fail to convert characteristic RX to UUID.\n");
GATTLIB_LOG(GATTLIB_ERROR, "Fail to convert characteristic RX to UUID.");
return 1;
}
@ -88,7 +98,7 @@ int main(int argc, char *argv[]) {
int characteristic_count;
ret = gattlib_discover_char(m_connection, &characteristics, &characteristic_count);
if (ret) {
fprintf(stderr, "Fail to discover characteristic.\n");
GATTLIB_LOG(GATTLIB_ERROR, "Fail to discover characteristic.");
return 1;
}
@ -101,21 +111,23 @@ int main(int argc, char *argv[]) {
}
}
if (tx_handle == 0) {
fprintf(stderr, "Fail to find NUS TX characteristic.\n");
GATTLIB_LOG(GATTLIB_ERROR, "Fail to find NUS TX characteristic.");
return 1;
} else if (rx_handle == 0) {
fprintf(stderr, "Fail to find NUS RX characteristic.\n");
GATTLIB_LOG(GATTLIB_ERROR, "Fail to find NUS RX characteristic.");
return 1;
}
free(characteristics);
// Enable Status Notification
uint16_t enable_notification = 0x0001;
gattlib_write_char_by_handle(m_connection, rx_handle + 1, &enable_notification, sizeof(enable_notification));
// Register notification handler
gattlib_register_notification(m_connection, notification_cb, NULL);
ret = gattlib_notification_start(m_connection, &nus_characteristic_rx_uuid);
if (ret) {
GATTLIB_LOG(GATTLIB_ERROR, "Fail to start notification.");
return 2;
}
// Register handler to catch Ctrl+C
signal(SIGINT, int_handler);
@ -128,7 +140,7 @@ int main(int argc, char *argv[]) {
length = MIN(total_length, 20);
ret = gattlib_write_without_response_char_by_handle(m_connection, tx_handle, input_ptr, length);
if (ret) {
fprintf(stderr, "Fail to send data to NUS TX characteristic.\n");
GATTLIB_LOG(GATTLIB_ERROR, "Fail to send data to NUS TX characteristic.");
return 1;
}
input_ptr += length;

View File

@ -1,8 +1,7 @@
#
#
# GattLib - GATT Library
#
# Copyright (C) 2016-2017 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
@ -20,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 2.6)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -2,7 +2,7 @@
*
* GattLib - GATT Library
*
* Copyright (C) 2016-2017 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,20 +22,40 @@
*/
#include <assert.h>
#include <ctype.h>
#include <glib.h>
#include <signal.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
#include <syslog.h>
#endif
#include "gattlib.h"
// Battery Level UUID
const uuid_t g_battery_level_uuid = CREATE_UUID16(0x2A19);
#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: ");
@ -45,49 +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>\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;
gatt_connection_t* connection;
if (argc != 2) {
if (argc < 3) {
usage(argv);
return 1;
}
connection = gattlib_connect(NULL, argv[1], GATTLIB_CONNECTION_OPTIONS_LEGACY_DEFAULT);
if (connection == NULL) {
fprintf(stderr, "Fail to connect to the bluetooth device.\n");
if (gattlib_string_to_uuid(argv[2], strlen(argv[2]) + 1, &m_argument.gatt_notification_uuid) < 0) {
usage(argv);
return 1;
}
gattlib_register_notification(connection, notification_handler, NULL);
if (argc == 5) {
if (gattlib_string_to_uuid(argv[3], strlen(argv[3]) + 1, &m_argument.gatt_write_uuid) < 0) {
usage(argv);
return 1;
}
ret = gattlib_notification_start(connection, &g_battery_level_uuid);
if (ret) {
fprintf(stderr, "Fail to start notification.\n");
goto DISCONNECT;
sscanf(argv[4], "%ld", &m_argument.gatt_write_data);
}
// Catch CTRL-C
signal(SIGINT, on_user_abort);
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
openlog("gattlib_notification", LOG_CONS | LOG_NDELAY | LOG_PERROR, LOG_USER);
setlogmask(LOG_UPTO(LOG_INFO));
#endif
m_main_loop = g_main_loop_new(NULL, 0);
g_main_loop_run(m_main_loop);
m_argument.adapter_name = NULL;
m_argument.mac_address = argv[1];
// In case we quit the main loop, clean the connection
gattlib_notification_stop(connection, &g_battery_level_uuid);
g_main_loop_unref(m_main_loop);
ret = gattlib_mainloop(ble_task, NULL);
if (ret != GATTLIB_SUCCESS) {
GATTLIB_LOG(GATTLIB_ERROR, "Failed to create gattlib mainloop");
}
DISCONNECT:
gattlib_disconnect(connection);
puts("Done");
return ret;
return 0;
}

View File

@ -1,8 +1,7 @@
#
#
# GattLib - GATT Library
#
# Copyright (C) 2016-2017 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
@ -20,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 2.6)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -2,7 +2,7 @@
*
* GattLib - GATT Library
*
* Copyright (C) 2016-2019 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,95 +22,78 @@
*/
#include <assert.h>
#include <ctype.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
#include <syslog.h>
#endif
#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;
}
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) {
fprintf(stderr, "Fail to connect to the bluetooth device.\n");
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) {
fprintf(stderr, "Could not find GATT Characteristic with UUID %s. "
"You might call the program with '--gatt-discovery'.\n", uuid_str);
GATTLIB_LOG(GATTLIB_ERROR, "Could not find GATT Characteristic with UUID %s. "
"You might call the program with '--gatt-discovery'.", uuid_str);
} else {
fprintf(stderr, "Error while reading GATT Characteristic with UUID %s (ret:%d)\n", uuid_str, ret);
GATTLIB_LOG(GATTLIB_ERROR, "Error while reading GATT Characteristic with UUID %s (ret:%d)", uuid_str, ret);
}
goto EXIT;
}
printf("Read UUID completed: ");
for (i = 0; i < len; i++) {
for (uintptr_t i = 0; i < len; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
free(buffer);
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) {
fprintf(stderr, "Could not find GATT Characteristic with UUID %s. "
"You might call the program with '--gatt-discovery'.\n", uuid_str);
GATTLIB_LOG(GATTLIB_ERROR, "Could not find GATT Characteristic with UUID %s. "
"You might call the program with '--gatt-discovery'.", uuid_str);
} else {
fprintf(stderr, "Error while writing GATT Characteristic with UUID %s (ret:%d)\n",
GATTLIB_LOG(GATTLIB_ERROR, "Error while writing GATT Characteristic with UUID %s (ret:%d)",
uuid_str, ret);
}
goto EXIT;
@ -118,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

@ -1,8 +1,7 @@
#
#
# GattLib - GATT Library
#
# Copyright (C) 2016-2017 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
@ -20,7 +19,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
cmake_minimum_required(VERSION 2.6)
cmake_minimum_required(VERSION 3.22.0)
find_package(PkgConfig REQUIRED)

View File

@ -2,7 +2,7 @@
*
* GattLib - GATT Library
*
* Copyright (C) 2016-2019 Olivier Martin <olivier@labapart.org>
* Copyright (C) 2016-2021 Olivier Martin <olivier@labapart.org>
*
*
* This program is free software; you can redistribute it and/or modify
@ -27,6 +27,10 @@
#include <stdio.h>
#include <stdlib.h>
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
#include <syslog.h>
#endif
#include "gattlib.h"
static uuid_t m_uuid;
@ -44,13 +48,13 @@ 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;
connection = gattlib_connect(NULL, params->mac_address, GATTLIB_CONNECTION_OPTIONS_LEGACY_DEFAULT);
if (connection == NULL) {
fprintf(stderr, "Fail to connect to the bluetooth device.\n");
GATTLIB_LOG(GATTLIB_ERROR, "Fail to connect to the bluetooth device.");
return NULL;
}
@ -65,10 +69,10 @@ void *connect_ble(void *arg) {
gattlib_uuid_to_string(&m_uuid, uuid_str, sizeof(uuid_str));
if (ret == GATTLIB_NOT_FOUND) {
fprintf(stderr, "Could not find GATT Characteristic with UUID %s. "
"You might call the program with '--gatt-discovery'.\n", uuid_str);
GATTLIB_LOG(GATTLIB_ERROR, "Could not find GATT Characteristic with UUID %s. "
"You might call the program with '--gatt-discovery'.", uuid_str);
} else {
fprintf(stderr, "Error while reading GATT Characteristic with UUID %s (ret:%d)\n", uuid_str, ret);
GATTLIB_LOG(GATTLIB_ERROR, "Error while reading GATT Characteristic with UUID %s (ret:%d)", uuid_str, ret);
}
goto EXIT;
}
@ -79,7 +83,7 @@ void *connect_ble(void *arg) {
}
printf("\n");
free(buffer);
gattlib_characteristic_free_value(buffer);
}
} else {
ret = gattlib_write_char_by_uuid(connection, &m_uuid, &params->value_data, sizeof(params->value_data));
@ -89,10 +93,10 @@ void *connect_ble(void *arg) {
gattlib_uuid_to_string(&m_uuid, uuid_str, sizeof(uuid_str));
if (ret == GATTLIB_NOT_FOUND) {
fprintf(stderr, "Could not find GATT Characteristic with UUID %s. "
"You might call the program with '--gatt-discovery'.\n", uuid_str);
GATTLIB_LOG(GATTLIB_ERROR, "Could not find GATT Characteristic with UUID %s. "
"You might call the program with '--gatt-discovery'.", uuid_str);
} else {
fprintf(stderr, "Error while writing GATT Characteristic with UUID %s (ret:%d)\n",
GATTLIB_LOG(GATTLIB_ERROR, "Error while writing GATT Characteristic with UUID %s (ret:%d)",
uuid_str, ret);
}
goto EXIT;
@ -100,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;
@ -114,6 +118,11 @@ int main(int argc, char *argv[]) {
return 1;
}
#ifdef GATTLIB_LOG_BACKEND_SYSLOG
openlog("gattlib_read_write_memory", LOG_CONS | LOG_NDELAY | LOG_PERROR, LOG_USER);
setlogmask(LOG_UPTO(LOG_INFO));
#endif
struct connect_ble_params params = {
.mac_address = argv[1],
};

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

@ -1 +1,16 @@
`gattlib-py` is a wrapper for `gattlib` library.
`gattlib-py` is a wrapper for `gattlib` library.
Development
-----------
1. Set `PYTHONPATH`: `export PYTHONPATH=$PWD/gattlib-py:$PYTHONPATH`
2. Build native library
```
mkdir -p build && cd build
cmake ..
make
```
3. Set `export LD_LIBRARY_PATH=$PWD/dbus` for Python module to find native library.

View File

@ -1,5 +1,11 @@
#!/usr/bin/env python3
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
import argparse
from gattlib import adapter
@ -9,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

@ -1,41 +1,54 @@
#!/usr/bin/env python3
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
import argparse
import threading
import time
from gattlib import adapter
from gattlib import adapter, mainloop
SCAN_TIMEOUT_SEC = 60
parser = argparse.ArgumentParser(description='Gattlib BLE scan example')
parser.add_argument('--duration', default=SCAN_TIMEOUT_SEC, type=int, help='Duration of the BLE scanning')
args = parser.parse_args()
# We only use a lock to not mixed printing statements of various devices
lock = threading.Lock()
def connect_ble_device(device):
device.connect()
lock.acquire()
try:
print("---------------------------------")
print(f"Found BLE Device {device.mac_address}. Connection tentative...")
device.connect()
print("---------------------------------")
print("Found BLE Device %s" % device.id)
device.discover()
for key, val in device.characteristics.items():
print("- GATTCharacteristic: 0x%x" % key)
device.discover()
for key, val in device.characteristics.items():
print("- GATTCharacteristic: 0x%x" % key)
device.disconnect()
except Exception as e:
print(f"EXCEPTION: {type(e)}: {str(e)}")
lock.release()
device.disconnect()
def on_discovered_ble_device(device, user_data):
threading.Thread(target=connect_ble_device, args=(device,)).start()
# Use default adapter
default_adapter = adapter.Adapter()
# Scan for 30 seconds
default_adapter.open()
default_adapter.scan_enable(on_discovered_ble_device, 30)
def scan_ble_devices():
default_adapter.open()
# Scan for 'args.duration' seconds
default_adapter.scan_enable(on_discovered_ble_device, timeout=args.duration)
# Because scan_enable() is not blocking, we need to wait for the same duration
time.sleep(args.duration)
mainloop.run_mainloop_with(scan_ble_devices)

View File

@ -1,20 +1,26 @@
#!/usr/bin/env python3
import argparse
import threading
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
from gattlib import adapter
import argparse
import time
from gattlib import adapter, mainloop
SCAN_TIMEOUT_SEC = 60
parser = argparse.ArgumentParser(description='Gattlib Find Eddystone device example')
parser.add_argument('--duration', default=SCAN_TIMEOUT_SEC, type=int, help='Duration of the BLE scanning')
args = parser.parse_args()
# Use default adapter
default_adapter = adapter.Adapter()
def on_eddystone_device_found(device, advertisement_data, manufacturer_id, manufacturer_data, user_data):
rssi = default_adapter.get_rssi_from_mac(device.id)
print("Find Eddystone device %s (RSSI:%d)" % (device.id, rssi))
rssi = default_adapter.get_rssi_from_mac(device.mac_address)
print("Find Eddystone device %s (RSSI:%d)" % (device.mac_address, rssi))
# Service Data
eddystone_data = advertisement_data[adapter.EDDYSTONE_COMMON_DATA_UUID]
@ -29,9 +35,17 @@ def on_eddystone_device_found(device, advertisement_data, manufacturer_id, manuf
else:
print("Eddystone frame not supported: 0x%x" % eddystone_data[0])
# Use default adapter
default_adapter = adapter.Adapter()
# Scan for 30 seconds
default_adapter.open()
default_adapter.scan_eddystone_enable(on_eddystone_device_found,
adapter.GATTLIB_EDDYSTONE_TYPE_UID | adapter.GATTLIB_EDDYSTONE_TYPE_URL | adapter.GATTLIB_EDDYSTONE_TYPE_TLM | adapter.GATTLIB_EDDYSTONE_TYPE_EID,
30) # Look for 30 seconds
def scan_ble_devices():
default_adapter.open()
# Scan for 'args.duration' seconds
default_adapter.scan_eddystone_enable(on_eddystone_device_found,
adapter.GATTLIB_EDDYSTONE_TYPE_UID | adapter.GATTLIB_EDDYSTONE_TYPE_URL | adapter.GATTLIB_EDDYSTONE_TYPE_TLM | adapter.GATTLIB_EDDYSTONE_TYPE_EID,
args.duration)
# Because scan_enable() is not blocking, we need to wait for the same duration
time.sleep(args.duration)
mainloop.run_mainloop_with(scan_ble_devices)

View File

@ -1,3 +1,9 @@
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
import struct
import sys
import threading
@ -59,7 +65,7 @@ def graph_init():
global ax_temp, ax_hum
global simulation
font = {'size' : 9}
font = {'size': 9}
matplotlib.rc('font', **font)
# Setup figure and subplots

View File

@ -1,5 +1,11 @@
#!/usr/bin/env python3
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
import argparse
from gattlib import adapter, device

View File

@ -1,3 +1,9 @@
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
import time
import threading
import wave

View File

@ -1,8 +1,22 @@
#!/usr/bin/env python3
import argparse
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
from gattlib import device, uuid
#
# Example: Read battery level: ./gattlib-py/examples/read_write/read_write.py D2:9A:97:1E:86:C8 read 00002A1900001000800000805f9b34fb
#
import argparse
import logging
import time
from gattlib import adapter, device, mainloop, uuid
SCAN_TIMEOUT_SEC = 30
parser = argparse.ArgumentParser(description='Gattlib read_write example')
parser.add_argument('mac', type=str, help='Mac Address of the GATT device to connect')
@ -11,19 +25,41 @@ parser.add_argument('uuid', type=str, help='UUID of the GATT Characteristic')
parser.add_argument('value', type=str, nargs='?', help='Value to write to the GATT characteristic')
args = parser.parse_args()
gatt_device = device.Device(adapter=None, addr=args.mac)
gatt_device.connect()
default_adapter = adapter.Adapter()
uuid = uuid.gattlib_uuid_str_to_int(args.uuid)
if uuid not in gatt_device.characteristics:
raise RuntimeError("Failed to find GATT characteristic '%s'" % args.uuid)
def on_connected_ble_device(device: device.Device, user_data):
uuid_value = uuid.gattlib_uuid_str_to_int(args.uuid)
if uuid_value not in device.characteristics:
exception_str = f"Failed to find GATT characteristic '{args.uuid}' in {device.characteristics}"
device.disconnect()
raise RuntimeError(exception_str)
characteristic = gatt_device.characteristics[uuid]
characteristic = device.characteristics[uuid_value]
if args.action == "read":
value = characteristic.read()
print(value)
elif args.action == "write":
characteristic.write(value)
if args.action == "read":
value = characteristic.read()
print(value)
elif args.action == "write":
characteristic.write(args.value)
gatt_device.disconnect()
device.disconnect()
def on_connection_error_callback(device: device.Device, error: int, user_data):
logging.error("Device '%s' connection error: %d", device, error)
def on_discovered_ble_device(device: device.Device, user_data):
if device.mac_address != args.mac:
return
default_adapter.scan_disable()
device.on_connection_callback = on_connected_ble_device
device.on_connection_error_callback = on_connection_error_callback
device.connect()
def scan_ble_devices():
default_adapter.open()
default_adapter.scan_enable(on_discovered_ble_device, timeout=SCAN_TIMEOUT_SEC)
time.sleep(SCAN_TIMEOUT_SEC)
mainloop.run_mainloop_with(scan_ble_devices)

View File

@ -1,12 +1,59 @@
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
"""Gattlib C types and functions"""
from ctypes import *
import logging
import pathlib
gattlib = CDLL("libgattlib.so")
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:
gattlib = cdll.LoadLibrary(str(pathlib.Path(__file__).with_name('libgattlib.so')))
except OSError:
# While in development, we might not have 'libgattlib.so' into the python directory
# We can define 'libgattlib.so' location using LD_LIBRARY_PATH
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:
logger.info(string)
elif level == 1:
logger.warning(string)
elif level == 0:
logger.error(string)
else:
logger.debug(string)
try:
# void gattlib_log_init(PyObject* logging_func)
gattlib_log_init = gattlib.gattlib_log_init
gattlib_log_init.argtypes = [py_object]
# Declare Python function for logging native string
gattlib_log_init(native_logging)
except AttributeError:
# Excepted when using a Gattlib logging backend without 'gattlib_log_init'
pass
# typedef struct {
# uint8_t data[16];
# } uint128_t;
class GattlibUuid128(Structure):
"""Python class representing the C structure 'uint128_t'."""
_fields_ = [("data", c_byte * 16)]
@ -19,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)]
@ -32,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)]
@ -44,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),
@ -56,102 +107,150 @@ 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)]
# typedef void (*gattlib_discovered_device_t)(void *adapter, const char* addr, const char* name, void *user_data)
gattlib_discovered_device_type = CFUNCTYPE(None, c_void_p, c_char_p, c_char_p, py_object)
# 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
# typedef void (*gattlib_discovered_device_with_data_t)(void *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,
# void *user_data);
gattlib_discovered_device_with_data_type = CFUNCTYPE(None, c_void_p, c_char_p, c_char_p,
POINTER(GattlibAdvertisementData), c_size_t, c_uint16, c_void_p, c_size_t,
py_object)
# 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
# int gattlib_adapter_scan_enable_with_filter(void *adapter, uuid_t **uuid_list, int16_t rssi_threshold, uint32_t enabled_filters,
# 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
# void gattlib_disconnected_device_python_callback(void *user_data)
gattlib_disconnected_device_python_callback = gattlib.gattlib_disconnected_device_python_callback
gattlib_disconnected_device_python_callback.argtypes = [py_object]
gattlib_disconnected_device_python_callback.restype = c_void_p
# void gattlib_notification_device_python_callback(const uuid_t* uuid, const uint8_t* data, size_t data_length, void* user_data);
gattlib_notification_device_python_callback = gattlib.gattlib_notification_device_python_callback
gattlib_notification_device_python_callback.argtypes = [c_void_p, c_void_p, c_int, c_void_p]
gattlib_notification_device_python_callback.restype = c_void_p
# void* gattlib_python_callback_args(PyObject* python_callback, PyObject* python_args) {
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(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 = gattlib.gattlib_adapter_scan_enable_with_filter
gattlib_adapter_scan_enable_with_filter.argtypes = [c_void_p, POINTER(POINTER(GattlibUuid)), c_int16, c_uint32, gattlib_discovered_device_type, c_size_t, py_object]
gattlib_adapter_scan_enable_with_filter_non_blocking = gattlib.gattlib_adapter_scan_enable_with_filter_non_blocking
gattlib_adapter_scan_enable_with_filter_non_blocking.argtypes = [c_void_p, POINTER(POINTER(GattlibUuid)),
c_int16, c_uint32, 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, gattlib_discovered_device_with_data_type, c_size_t, py_object]
gattlib_adapter_scan_eddystone.argtypes = [c_void_p, c_int16, c_uint32, py_object, c_size_t, py_object]
# gatt_connection_t *gattlib_connect(const char *src, const char *dst, unsigned long options);
# 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.restype = c_void_p
gattlib_connect.argtypes = [c_char_p, c_char_p, c_ulong]
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)]
# int gattlib_write_char_by_uuid(gatt_connection_t* connection, uuid_t* uuid, const void* buffer, size_t buffer_len)
# void gattlib_characteristic_free_value(void* buffer);
gattlib_characteristic_free_value = gattlib.gattlib_characteristic_free_value
gattlib_characteristic_free_value.argtypes = [c_void_p]
# 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)]
# void gattlib_register_notification_python(gatt_connection_t* connection, PyObject *notification_handler, PyObject *user_data)
gattlib_register_notification = gattlib.gattlib_register_notification_python
gattlib_register_notification.argtypes = [c_void_p, py_object, py_object]
# 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]
# void gattlib_register_on_disconnect_python(gatt_connection_t *connection, PyObject *handler, PyObject *user_data)
gattlib_register_on_disconnect = gattlib.gattlib_register_on_disconnect_python
gattlib_register_on_disconnect.argtypes = [c_void_p, py_object, py_object]
# 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]
# Disable until https://github.com/labapart/gattlib/issues/75 is resolved
# int gattlib_get_rssi(gatt_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(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
gattlib_mainloop.argtypes = [py_object, py_object]
# void gattlib_free_mem(void *ptr])
gattlib_free_mem = gattlib.gattlib_free_mem
gattlib_free_mem.argtypes = [c_void_p]

View File

@ -1,7 +1,18 @@
from gattlib import *
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
"""Gattlib Adapter API"""
import threading
from uuid import UUID
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)
@ -29,42 +40,91 @@ 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:
return self._name
else:
return f"adapter@{self._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):
ret = gattlib_adapter_open(self._name, byref(self._adapter))
if ret == 0:
self._is_opened = True
return ret
"""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)
else:
handle_return(ret)
def close(self):
ret = gattlib.gattlib_adapter_close(self._adapter)
self._is_opened = False
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
def on_discovered_device(self, adapter, addr, name, user_data):
device = Device(self, addr, name)
self.on_discovered_device_callback(device, user_data)
# Use a closure to return a method that can be called by the C-library (see: https://stackoverflow.com/a/7261524/6267288)
def get_on_discovered_device_callback(self):
"""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: #pylint: disable=broad-exception-caught
logger.exception(e)
return on_discovered_device
def scan_enable(self, on_discovered_device_callback, timeout, notify_change=False, uuids=None, rssi_threshold=None, user_data=None):
assert on_discovered_device_callback != None
self.on_discovered_device_callback = on_discovered_device_callback
"""
Scan for BLE devices
Note: This function is not blocking
@param adapter: is the context of the newly opened adapter
@param uuid_list: is a NULL-terminated list of UUIDs to filter. The rule only applies to advertised UUID.
Returned devices would match any of the UUIDs of the list.
@param rssi_threshold: is the imposed RSSI threshold for the returned devices.
@param enabled_filters: defines the parameters to use for filtering. There are selected by using the macros
GATTLIB_DISCOVER_FILTER_USE_UUID and GATTLIB_DISCOVER_FILTER_USE_RSSI.
@param discovered_device_cb: is the function callback called for each new Bluetooth device discovered
@param timeout: defines the duration of the Bluetooth scanning. When timeout=None or 0, we scan indefinitely.
@param user_data: is the data passed to the callback `discovered_device_cb()`
"""
assert on_discovered_device_callback 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:
raise AdapterNotOpened()
self.open()
enabled_filters = 0
uuid_list = None
@ -79,7 +139,11 @@ class Adapter:
for uuid in uuids:
gattlib_uuid = GattlibUuid()
uuid_ascii = uuid.encode("utf-8")
if isinstance(uuid, UUID):
uuid_ascii = str(uuid).encode("utf-8")
else:
uuid_ascii = uuid.encode("utf-8")
ret = gattlib.gattlib_string_to_uuid(uuid_ascii, len(uuid_ascii), byref(gattlib_uuid))
handle_return(ret)
@ -93,46 +157,34 @@ class Adapter:
if notify_change:
enabled_filters |= GATTLIB_DISCOVER_FILTER_NOTIFY_CHANGE
ret = gattlib_adapter_scan_enable_with_filter(self._adapter,
# gattlib_adapter_scan_enable_with_filter_non_blocking() assumes a 0-timeout means scanning indefintely
if timeout is None:
timeout = 0
ret = gattlib_adapter_scan_enable_with_filter_non_blocking(self._adapter,
uuid_list, rssi, enabled_filters,
gattlib_discovered_device_type(self.on_discovered_device),
timeout, user_data)
gattlib_discovered_device_python_callback,
timeout,
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:
raise AdapterNotOpened()
self.open()
rssi = 0
@ -147,15 +199,21 @@ class Adapter:
}
ret = gattlib_adapter_scan_eddystone(self._adapter, rssi, eddystone_filters,
gattlib_discovered_device_with_data_type(Adapter.on_discovered_ble_device_with_details),
Adapter.on_discovered_ble_device_with_details,
timeout, args)
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")
@ -164,43 +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
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

@ -1,10 +1,23 @@
import logging
import uuid
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
from gattlib import *
from .exception import handle_return, DeviceError
"""Gattlib Device API"""
from __future__ import annotations
import uuid
import threading
from typing import TYPE_CHECKING
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
CONNECTION_OPTIONS_LEGACY_BDADDR_LE_PUBLIC = (1 << 0)
CONNECTION_OPTIONS_LEGACY_BDADDR_LE_RANDOM = (1 << 1)
@ -19,15 +32,26 @@ CONNECTION_OPTIONS_LEGACY_DEFAULT = \
class Device:
def __init__(self, adapter, addr, name=None):
"""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
self._name = name
self._connection = None
# We use a lock because on disconnection, we will set self._connection to None
self._connection_lock = threading.Lock()
# 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
@ -35,155 +59,183 @@ class Device:
# Dictionnary for GATT characteristic callback
self._gatt_characteristic_callbacks = {}
# Memory that could be allocated by native gattlib
self._services_ptr = None
self._characteristics_ptr = None
@property
def id(self):
def mac_address(self) -> str:
"""Return Device MAC Address"""
return self._addr.decode("utf-8")
@property
def connection(self):
"""Return Gattlib connection C handle."""
if self._connection:
return self._connection
else:
return c_void_p(None)
@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):
if self._adapter:
adapter_name = self._adapter.name
"""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
self.on_connection_error(error, user_data)
else:
self._connection = connection
self.on_connection(user_data)
if self._adapter is None:
adapter = None
else:
adapter_name = None
adapter = self._adapter._adapter #pylint: disable=protected-access
self._connection = gattlib_connect(adapter_name, self._addr, options)
if self._connection == 0:
raise DeviceError()
# Disable until https://github.com/labapart/gattlib/issues/75 is resolved
# @property
# def rssi(self):
# _rssi = c_int16(0)
# ret = gattlib_get_rssi(self._connection, byref(_rssi))
# handle_return(ret)
# return _rssi.value
@staticmethod
def on_disconnection(user_data):
this = user_data
this.disconnection_callback(this.disconnection_user_data)
def register_on_disconnect(self, callback, user_data):
self.disconnection_callback = callback
self.disconnection_user_data = user_data
gattlib_register_on_disconnect(self.connection, Device.on_disconnection, self)
def disconnect(self):
ret = gattlib_disconnect(self.connection)
ret = gattlib_connect(adapter, self._addr, options,
gattlib_connected_device_python_callback,
gattlib_python_callback_args(_on_connection, self))
handle_return(ret)
def on_connection(self, user_data: py_object):
"""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 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))
handle_return(ret)
return _rssi.value
else:
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):
with self._disconnection_lock:
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
# Reset the connection handler
self._connection = None
gattlib_register_on_disconnect(self.connection,
gattlib_disconnected_device_python_callback,
gattlib_python_callback_args(on_disconnection, user_data))
def disconnect(self, wait_disconnection: bool = False):
"""Disconnect connected device."""
with self._connection_lock:
if self._connection:
ret = gattlib_disconnect(self.connection, wait_disconnection)
handle_return(ret)
self._connection = None
def discover(self):
#
# Discover GATT Services
#
_services = POINTER(GattlibPrimaryService)()
_services_count = c_int(0)
ret = gattlib_discover_primary(self.connection, byref(_services), byref(_services_count))
"""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))
handle_return(ret)
self._services = {}
for i in range(0, _services_count.value):
service = GattService(self, _services[i])
for i in range(0, services_count.value):
service = GattService(self, self._services_ptr[i])
self._services[service.short_uuid] = service
logging.debug("Service UUID:0x%x" % service.short_uuid)
logger.debug("Service UUID:0x%x", service.short_uuid)
#
# Discover GATT Characteristics
#
_characteristics = POINTER(GattlibCharacteristic)()
self._characteristics_ptr = POINTER(GattlibCharacteristic)()
_characteristics_count = c_int(0)
ret = gattlib_discover_char(self.connection, byref(_characteristics), byref(_characteristics_count))
ret = gattlib_discover_char(self.connection, byref(self._characteristics_ptr), byref(_characteristics_count))
handle_return(ret)
self._characteristics = {}
for i in range(0, _characteristics_count.value):
characteristic = GattCharacteristic(self, _characteristics[i])
characteristic = GattCharacteristic(self, self._characteristics_ptr[i])
self._characteristics[characteristic.short_uuid] = characteristic
logging.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
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'):
logging.warning("Start GATT discovery implicitly")
logger.warning("Start GATT discovery implicitly")
self.discover()
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'):
logging.warning("Start GATT discovery implicitly")
logger.warning("Start GATT discovery implicitly")
self.discover()
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):
@ -205,14 +257,22 @@ class Device:
self._is_notification_init = True
gattlib_register_notification(self._connection, Device.notification_callback, self)
gattlib_register_notification(self._connection,
gattlib_notification_device_python_callback,
gattlib_python_callback_args(Device._notification_callback, self))
def _notification_add_gatt_characteristic_callback(self, gatt_characteristic, callback, user_data):
if not callable(callback):
raise InvalidParameter("Notification callback is not callable.")
if not self._is_notification_init:
self._notification_init()
self._gatt_characteristic_callbacks[gatt_characteristic.short_uuid] = { 'callback': callback, 'user_data': user_data }
def _notification_remove_gatt_characteristic_callback(self, gatt_characteristic):
self._gatt_characteristic_callbacks[gatt_characteristic.short_uuid] = None
def __str__(self):
name = self._name
if name:

View File

@ -1,56 +1,121 @@
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
"""Gattlib Exceptions"""
GATTLIB_SUCCESS = 0
GATTLIB_INVALID_PARAMETER = 1
GATTLIB_NOT_FOUND = 2
GATTLIB_OUT_OF_MEMORY = 3
GATTLIB_NOT_SUPPORTED = 4
GATTLIB_DEVICE_ERROR = 5
GATTLIB_ERROR_DBUS = 6
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
GATTLIB_ERROR_BLUEZ = 0x20000000
GATTLIB_ERROR_INTERNAL = 0x80000000
class GattlibException(Exception):
pass
"""Generic Gattlib exception."""
class NoAdapter(GattlibException):
"""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):
"""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):
pass
"""Gattlib device exception."""
def __init__(self, adapter: str = None, mac_address: str = None) -> None:
self.adapter = adapter
self.mac_address = mac_address
def __str__(self) -> str:
return f"Error with device {self.mac_address} on adapter {self.adapter}"
class DBusError(GattlibException):
pass
"""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 "DBus Error: le-connection-abort-by-local"
elif self.domain == 238 and self.code == 60952:
return "DBus Error: Timeout was reached"
elif self.domain == 238 and self.code == 60964:
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_NOT_SUPPORTED:
if ret == GATTLIB_TIMEOUT:
raise TimeoutError()
if ret == GATTLIB_NOT_SUPPORTED:
raise NotSupported()
elif ret == GATTLIB_DEVICE_ERROR:
if ret == GATTLIB_DEVICE_ERROR:
raise DeviceError()
elif ret == GATTLIB_ERROR_DBUS:
raise DBusError()
elif ret != 0:
raise RuntimeError("Gattlib exception %d" % ret)
if ret == GATTLIB_DEVICE_NOT_CONNECTED:
raise NotConnected()
if ret == GATTLIB_NO_ADAPTER:
raise NoAdapter()
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)
if ret == -22: # From '-EINVAL'
raise ValueError("Gattlib value error")
if ret != 0:
raise RuntimeError(f"Gattlib exception {ret}")

View File

@ -1,20 +1,32 @@
from gattlib import *
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
"""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
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
@ -29,50 +41,58 @@ 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:
raise RuntimeError("Not supported yet")
"""Read GATT characteristic."""
if callback: #pylint: disable=no-else-raise
raise NotImplementedError()
else:
_buffer = c_void_p(None)
_buffer_len = c_size_t(0)
ret = gattlib_read_char_by_uuid(self.connection, self._gattlib_characteristic.uuid, byref(_buffer), byref(_buffer_len))
handle_return(ret)
pointer_type = POINTER(c_ubyte * _buffer_len.value)
c_bytearray = cast(_buffer, pointer_type)
@ -81,9 +101,11 @@ class GattCharacteristic():
for i in range(_buffer_len.value):
value[i] = c_bytearray.contents[i]
gattlib_characteristic_free_value(_buffer)
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.")
@ -92,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)
@ -107,14 +134,24 @@ class GattCharacteristic():
return GattStream(_stream, _mtu.value)
def register_notification(self, callback, user_data=None):
self._device._notification_add_gatt_characteristic_callback(self, callback, user_data)
"""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) #pylint: disable=protected-access
def unregister_notification(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

@ -0,0 +1,84 @@
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
"""Module for exposing main loop for Gattlib execution."""
import threading
import time
import traceback
#import dbus.mainloop.glib
from gi.repository import GObject
from . import logger
gobject_mainloop: GObject.MainLoop = None
task_returned_code: int = -1
task_exception: Exception = None
def _user_thread_main(task):
"""Main entry point for the thread that will run user's code."""
global task_returned_code, task_exception
try:
# Wait for GLib main loop to start running before starting user code.
while True:
if gobject_mainloop is not None and gobject_mainloop.is_running():
# Main loop is running, we should be ready to make bluez DBus calls.
break
# Main loop isn't running yet, give time back to other threads.
time.sleep(0)
# Run user's code.
task_returned_code = task()
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
finally:
gobject_mainloop.quit()
def run_mainloop_with(task):
"""
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")
# Ensure GLib's threading is initialized to support python threads, and
# make a default mainloop that all DBus objects will inherit. These
# commands MUST execute before any other DBus commands!
#GObject.threads_init()
#dbus.mainloop.glib.threads_init()
# Set the default main loop, this also MUST happen before other DBus calls.
#mainloop = dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
# Create a background thread to run the user task
user_thread = threading.Thread(target=_user_thread_main, name="gattlib-task", args=(task,))
user_thread.start()
# Spin up a GLib main loop in the main thread to process async BLE events.
gobject_mainloop = GObject.MainLoop()
try:
gobject_mainloop.run() # Doesn't return until the mainloop ends.
user_thread.join()
if task_exception:
raise task_exception
return task_returned_code
except KeyboardInterrupt:
gobject_mainloop.quit()
# We assume that if we exit with keyboard interrupt than it is not the expected
# behaviour and we return -1
return -2
finally:
gobject_mainloop = None

View File

@ -1,16 +1,25 @@
#
# SPDX-License-Identifier: BSD-3-Clause
#
# 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:
@ -19,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:
@ -31,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):
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)
@ -44,4 +55,8 @@ def gattlib_uuid_str_to_int(uuid_str):
if match:
return int(match.group(1), 16)
else:
return UUID(uuid_str).int
try:
return UUID(uuid_str).int
except ValueError:
logger.error("Could not convert %s to a UUID", uuid_str)
raise

View File

@ -1,31 +1,191 @@
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
#
import os
from setuptools import setup, find_packages
import re
import subprocess
import sys
from pathlib import Path
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
native_source_dir = os.environ.get("NATIVE_SOURCE_DIR", ".")
# Retrieve version from GIT
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
# Value from travis-ci
if 'TRAVIS_TAG' in os.environ:
git_version = os.environ['TRAVIS_TAG']
elif 'TRAVIS_BUILD_ID' in os.environ:
git_version = os.environ['TRAVIS_BUILD_ID'] + '-' + git_version
#
# Create '_version.py'
#
package_version = os.environ.get('GATTLIB_PY_VERSION', git_version)
with open("README.md", "r") as fh:
long_description = fh.read()
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):
"""Custom extension class that allows to specify the root folder of the CMake project."""
def __init__(self, name, sourcedir='.', **kwa):
Extension.__init__(self, name, sources=[], **kwa)
self.sourcedir = os.path.abspath(sourcedir)
# Convert distutils Windows platform specifiers to CMake -A arguments
PLAT_TO_CMAKE = {
"win32": "Win32",
"win-amd64": "x64",
"win-arm32": "ARM",
"win-arm64": "ARM64",
}
class CMakeBuild(build_ext):
def build_extension(self, ext: CMakeExtension) -> None:
try:
_ = subprocess.check_output(['cmake', '--version'])
except OSError:
raise RuntimeError('Cannot find CMake executable')
# Must be in this form due to bug in .resolve() only fixed in Python 3.10+
ext_fullpath = Path.cwd() / self.get_ext_fullpath(ext.name)
extdir = ext_fullpath.parent.resolve()
# Using this requires trailing slash for auto-detection & inclusion of
# auxiliary "native" libs
debug = int(os.environ.get("DEBUG", 0)) if self.debug is None else self.debug
cfg = "Debug" if debug else "Release"
# CMake lets you override the generator - we need to check this.
# Can be set with Conda-Build, for example.
cmake_generator = os.environ.get("CMAKE_GENERATOR", "")
cmake_library_output_dir = Path(str(extdir), ext.name)
# Set Python_EXECUTABLE instead if you use PYBIND11_FINDPYTHON
# EXAMPLE_VERSION_INFO shows you how to pass a value into the C++ code
# from Python.
cmake_args = [
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
# (needed e.g. to build for ARM OSx on conda-forge)
if "CMAKE_ARGS" in os.environ:
cmake_args += [item for item in os.environ["CMAKE_ARGS"].split(" ") if item]
# In this example, we pass in the version to C++. You might not need to.
cmake_args += [
f"-DEXAMPLE_VERSION_INFO={self.distribution.get_version()}",
"-DGATTLIB_BUILD_EXAMPLES=NO",
"-DGATTLIB_LOG_BACKEND=python"]
if self.compiler.compiler_type != "msvc":
# Using Ninja-build since it a) is available as a wheel and b)
# multithreads automatically. MSVC would require all variables be
# exported for Ninja to pick it up, which is a little tricky to do.
# Users can override the generator with CMAKE_GENERATOR in CMake
# 3.15+.
if not cmake_generator or cmake_generator == "Ninja":
try:
import ninja
ninja_executable_path = Path(ninja.BIN_DIR) / "ninja"
cmake_args += [
"-GNinja",
f"-DCMAKE_MAKE_PROGRAM:FILEPATH={ninja_executable_path}",
]
except ImportError:
pass
else:
# Single config generators are handled "normally"
single_config = any(x in cmake_generator for x in {"NMake", "Ninja"})
# CMake allows an arch-in-generator style for backward compatibility
contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"})
# Specify the arch if using MSVC generator, but only if it doesn't
# contain a backward-compatibility arch spec already in the
# generator name.
if not single_config and not contains_arch:
cmake_args += ["-A", PLAT_TO_CMAKE[self.plat_name]]
# Multi-config generators have a different way to specify configs
if not single_config:
cmake_args += [
f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{cfg.upper()}={extdir}"
]
build_args += ["--config", cfg]
if sys.platform.startswith("darwin"):
# Cross-compile support for macOS - respect ARCHFLAGS if set
archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", ""))
if archs:
cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))]
# Set CMAKE_BUILD_PARALLEL_LEVEL to control the parallel build level
# across all generators.
if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ:
# self.parallel is a Python 3 only way to set parallel jobs by hand
# using -j in the build_ext call, not supported by pip or PyPA-build.
if hasattr(self, "parallel") and self.parallel:
# CMake 3.12+ only.
build_args += [f"-j{self.parallel}"]
build_temp = Path(self.build_temp) / ext.name
if not build_temp.exists():
build_temp.mkdir(parents=True)
subprocess.run(
["cmake", ext.sourcedir, *cmake_args], cwd=build_temp, check=True
)
subprocess.run(
["cmake", "--build", ".", "--target", "gattlib", *build_args], cwd=build_temp, check=True
)
setup(
name='gattlib-py',
version=git_version,
version=package_version,
author="Olivier Martin",
author_email="olivier@labapart.com",
description="Python wrapper for gattlib library",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/labapart/gattlib/gattlib-py",
long_description=open(os.path.join(SETUP_DIR, 'README.md')).read(),
long_description_content_type='text/markdown',
packages=find_packages(),
install_requires=['setuptools'],
install_requires=[
'setuptools',
'PyGObject>=3.44.0'
],
ext_modules=[CMakeExtension(python_module_name, sourcedir=native_source_dir)],
cmdclass={'build_ext': CMakeBuild},
)

View File

@ -1,24 +1,7 @@
/*
* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later
*
* GattLib - GATT Library
*
* Copyright (C) 2016-2020 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
*
* Copyright (c) 2016-2024, Olivier Martin <olivier@labapart.org>
*/
#ifndef __GATTLIB_H__
@ -28,6 +11,7 @@
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
#include <bluetooth/bluetooth.h>
@ -48,19 +32,40 @@ extern "C" {
#define ATT_MAX_MTU ATT_MAX_VALUE_LEN
#endif
/**
* Gattlib constants
*/
#define GATTLIB_DISCONNECTION_WAIT_TIMEOUT_SEC 5
/**
* @name Gattlib errors
*/
//@{
#define GATTLIB_SUCCESS 0
#define GATTLIB_INVALID_PARAMETER 1
#define GATTLIB_NOT_FOUND 2
#define GATTLIB_OUT_OF_MEMORY 3
#define GATTLIB_NOT_SUPPORTED 4
#define GATTLIB_DEVICE_ERROR 5
#define GATTLIB_ERROR_DBUS 6
#define GATTLIB_ERROR_BLUEZ 7
#define GATTLIB_ERROR_INTERNAL 8
#define GATTLIB_SUCCESS 0
#define GATTLIB_INVALID_PARAMETER 1
#define GATTLIB_NOT_FOUND 2
#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
#define GATTLIB_ERROR_UNIX 0x30000000
#define GATTLIB_ERROR_INTERNAL 0x80000000
#define GATTLIB_ERROR_DBUS_WITH_ERROR(error) \
(GATTLIB_ERROR_DBUS | (error->domain << 8) | (error->code))
#define GATTLIB_ERROR_BLUEZ_WITH_ERROR(ret) \
(GATTLIB_ERROR_BLUEZ | (ret))
#define GATTLIB_ERROR_UNIX_WITH_ERROR(ret) \
(GATTLIB_ERROR_UNIX | (ret))
//@}
/**
@ -87,6 +92,7 @@ extern "C" {
* is for Bluez prior to v5.42 (before Bluez) support
*/
//@{
#define GATTLIB_CONNECTION_OPTIONS_NONE 0
#define GATTLIB_CONNECTION_OPTIONS_LEGACY_BDADDR_LE_PUBLIC (1 << 0)
#define GATTLIB_CONNECTION_OPTIONS_LEGACY_BDADDR_LE_RANDOM (1 << 1)
#define GATTLIB_CONNECTION_OPTIONS_LEGACY_BT_SEC_LOW (1 << 2)
@ -135,9 +141,19 @@ extern "C" {
#define EDDYSTONE_TYPE_EID 0x30
//@}
/**
* Log level
*/
#define GATTLIB_ERROR 0
#define GATTLIB_WARNING 1
#define GATTLIB_INFO 2
#define GATTLIB_DEBUG 3
typedef struct _gatt_connection_t gatt_connection_t;
typedef struct _gatt_stream_t gatt_stream_t;
#define GATTLIB_LOG(level, args...) if (level <= GATTLIB_LOG_LEVEL) { gattlib_log(level, args); }
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
@ -148,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);
/**
@ -156,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)(void* user_data);
typedef void (*gattlib_disconnection_handler_t)(gattlib_connection_t* connection, void* user_data);
/**
* @brief Handler called on new discovered BLE device
@ -166,7 +191,7 @@ typedef void (*gattlib_disconnection_handler_t)(void* user_data);
* @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
@ -176,23 +201,25 @@ 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);
/**
* @brief Handler called on asynchronous connection when connection is ready
* @brief Handler called on asynchronous connection when connection is ready or on connection error
*
* @param adapter Local Adaptater interface. When passing NULL, we use default adapter.
* @param dst Remote Bluetooth address
* @param connection Connection that is disconnecting
* @param error Connection error code
* @param user_data Data defined when calling `gattlib_register_on_disconnect()`
*/
typedef void (*gatt_connect_cb_t)(gatt_connection_t* connection, 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
@ -223,7 +250,16 @@ 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
*
* @param adapter is the context of the newly opened adapter
*
* @return Adapter name
*/
const char *gattlib_adapter_get_name(gattlib_adapter_t* adapter);
/**
* @brief Enable Bluetooth scanning on a given adapter
@ -235,11 +271,13 @@ int gattlib_adapter_open(const char* adapter_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
*
* This function will block until either the timeout has expired or gattlib_adapter_scan_disable() has been called.
*
* @param adapter is the context of the newly opened adapter
* @param uuid_list is a NULL-terminated list of UUIDs to filter. The rule only applies to advertised UUID.
* Returned devices would match any of the UUIDs of the list.
@ -252,12 +290,34 @@ 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);
/**
* @brief Enable Bluetooth scanning on a given adapter (non-blocking)
*
* This function will return as soon as the BLE scan has been started.
*
* @param adapter is the context of the newly opened adapter
* @param uuid_list is a NULL-terminated list of UUIDs to filter. The rule only applies to advertised UUID.
* Returned devices would match any of the UUIDs of the list.
* @param rssi_threshold is the imposed RSSI threshold for the returned devices.
* @param enabled_filters defines the parameters to use for filtering. There are selected by using the macros
* GATTLIB_DISCOVER_FILTER_USE_UUID and GATTLIB_DISCOVER_FILTER_USE_RSSI.
* @param discovered_device_cb is the function callback called for each new Bluetooth device discovered
* @param timeout defines the duration of the Bluetooth scanning. When timeout=0, we scan indefinitely.
* @param user_data is the data passed to the callback `discovered_device_cb()`
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_adapter_scan_enable_with_filter_non_blocking(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);
/**
* @brief Enable Eddystone Bluetooth Device scanning on a given adapter
*
* This function will block until either the timeout has expired or gattlib_adapter_scan_disable() has been called.
*
* @param adapter is the context of the newly opened adapter
* @param rssi_threshold is the imposed RSSI threshold for the returned devices.
* @param eddystone_types defines the type(s) of Eddystone advertisement data type to select.
@ -269,7 +329,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_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);
/**
@ -279,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
@ -288,16 +348,7 @@ int gattlib_adapter_scan_disable(void* adapter);
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
int gattlib_adapter_close(void* adapter);
/**
* @brief Function to connect to a BLE device
*
* @param adapter Local Adaptater interface. When passing NULL, we use default adapter.
* @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);
int gattlib_adapter_close(gattlib_adapter_t* adapter);
/**
* @brief Function to asynchronously connect to a BLE device
@ -309,19 +360,29 @@ gatt_connection_t *gattlib_connect(void *adapter, const char *dst, unsigned long
* @param options Options to connect to BLE device. See `GATTLIB_CONNECTION_OPTIONS_*`
* @param connect_cb is the callback to call when the connection is established
* @param user_data is the user specific data to pass to the callback
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
gatt_connection_t *gattlib_connect_async(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);
gatt_connect_cb_t connect_cb,
void* user_data);
/**
* @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
@ -332,7 +393,7 @@ int gattlib_disconnect(gatt_connection_t* connection);
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
void 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
@ -373,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
@ -388,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, int start, int 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
@ -401,7 +462,7 @@ int gattlib_discover_char_range(gatt_connection_t* connection, int start, int en
*
* @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
@ -414,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
@ -425,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
@ -439,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
@ -450,7 +511,14 @@ 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
*
* @param buffer Buffer to free
*/
void gattlib_characteristic_free_value(void *ptr);
/**
* @brief Function to write to the GATT characteristic UUID
@ -462,7 +530,7 @@ int gattlib_read_char_by_uuid_async(gatt_connection_t* connection, uuid_t* uuid,
*
* @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
@ -474,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
@ -486,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
@ -500,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()`
@ -511,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()`
@ -520,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
@ -532,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
@ -542,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
@ -552,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
@ -562,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
@ -572,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
@ -583,7 +651,7 @@ int gattlib_indication_stop(gatt_connection_t* connection, const uuid_t* uuid);
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
void 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
@ -594,7 +662,7 @@ void gattlib_register_notification(gatt_connection_t* connection, gattlib_event_
*
* @return GATTLIB_SUCCESS on success or GATTLIB_* error code
*/
void 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
/**
@ -605,14 +673,14 @@ void gattlib_register_indication(gatt_connection_t* connection, gattlib_event_ha
*
* @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
@ -620,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
@ -628,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
@ -645,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
@ -687,6 +753,17 @@ int gattlib_string_to_uuid(const char *str, size_t size, uuid_t *uuid);
*/
int gattlib_uuid_cmp(const uuid_t *uuid1, const uuid_t *uuid2);
/**
* @brief Logging function used by Gattlib
*
* @param level is the logging level of the message
* @param format is the message format
*
*/
void gattlib_log(int level, const char *format, ...);
int gattlib_mainloop(void* (*task)(void* arg), void *arg);
#ifdef __cplusplus
}
#endif

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;
}