mirror of https://github.com/labapart/gattlib
Compare commits
166 Commits
Author | SHA1 | Date |
---|---|---|
Olivier Martin | f99558d9b8 | |
Olivier Martin | b3c5d2d1ed | |
Olivier Martin | 9fb48aacb6 | |
Olivier Martin | dbe599dbfb | |
Olivier Martin | f79e90ce02 | |
Olivier Martin | 35566d198a | |
Olivier Martin | f4ed88eb31 | |
Olivier Martin | aaab2dc74e | |
Olivier Martin | 880f1d2cd0 | |
Olivier Martin | 8a108495a1 | |
Olivier Martin | 880ff269e5 | |
Olivier Martin | 76353f8659 | |
Olivier Martin | db04a0eb5c | |
Olivier Martin | 2d771d9390 | |
Olivier Martin | 5406a97e57 | |
Olivier Martin | 4acf4aa0ab | |
Olivier Martin | f609f7d507 | |
Olivier Martin | dc009029fa | |
Olivier Martin | cdd62f6d35 | |
Olivier Martin | 0e34df58e5 | |
Olivier Martin | 014c2802ee | |
Olivier Martin | 22dca4511c | |
Olivier Martin | 5049443704 | |
Olivier Martin | a587aa9dfa | |
Olivier Martin | 67ff1de69b | |
Olivier Martin | 53e6c2c7ae | |
Olivier Martin | fcedfb9e85 | |
Olivier Martin | b57d9546df | |
Olivier Martin | 2e99c4f1b6 | |
Olivier Martin | 709b76019e | |
Olivier Martin | 0c1334c5b4 | |
Olivier Martin | aa6a7b79bb | |
Olivier Martin | fab0e8fa67 | |
Olivier Martin | ce52533f39 | |
Olivier Martin | 2861549a80 | |
Olivier Martin | 7758bae7d4 | |
Olivier Martin | 42c97d4767 | |
Olivier Martin | aac4e069c9 | |
Olivier Martin | 8f17232216 | |
Olivier Martin | 86f9a742f3 | |
Olivier Martin | 50dca02e97 | |
Olivier Martin | 6cdbe58e7b | |
Olivier Martin | db629448fd | |
Olivier Martin | 5f43addb8f | |
Olivier Martin | a85dd83015 | |
Olivier Martin | 5ca46ad208 | |
Olivier Martin | f3f6bb37bb | |
Olivier Martin | 5d9a36f1d1 | |
Olivier Martin | 6e6436ceb3 | |
Olivier Martin | 5ba3eda6f5 | |
Olivier Martin | 8e351e746c | |
Olivier Martin | 6cea2d37db | |
Olivier Martin | 2a46780e96 | |
Olivier Martin | b2c4094cb6 | |
Olivier Martin | d2fb01d85e | |
Olivier Martin | 98833bf7ce | |
Olivier Martin | 6823c02892 | |
Olivier Martin | 7925aa6a38 | |
Olivier Martin | 2edc8f2620 | |
Olivier Martin | 36e0cb4934 | |
Olivier Martin | 24ab0ea95f | |
Olivier Martin | e26e9f0b7a | |
Olivier Martin | 6e718253b0 | |
Olivier Martin | ea1fe15857 | |
Olivier Martin | 00cbc1ab87 | |
Olivier Martin | d2702050ec | |
Olivier Martin | f4cc321a90 | |
Olivier Martin | 6321d28d25 | |
Olivier Martin | f0426ae3e3 | |
Olivier Martin | ebd163f1dc | |
Olivier Martin | 01ea87aa16 | |
Olivier Martin | f38f73a9a4 | |
Olivier Martin | e554dec3dc | |
Olivier Martin | 100c5d5f69 | |
Olivier Martin | 860f7f5b61 | |
Olivier Martin | 13bd692bb4 | |
Olivier Martin | 18e1658d6f | |
Olivier Martin | d3d9600114 | |
Olivier Martin | 5f5cb5bd12 | |
Olivier Martin | 6751a17cee | |
Olivier Martin | 57c99c6038 | |
Olivier Martin | b8268c4cb5 | |
Olivier Martin | fbd65421d0 | |
Olivier Martin | 61043afd98 | |
Olivier Martin | aacc53c511 | |
Olivier Martin | 7d5748cb0d | |
Olivier Martin | 33a634a535 | |
Olivier Martin | 2f83e85eee | |
Olivier Martin | 1530184776 | |
Olivier Martin | b28a0422f0 | |
Olivier Martin | b193543fb0 | |
Olivier Martin | 7a8d53d817 | |
Olivier Martin | 7922810016 | |
Olivier Martin | 8e5412a1a9 | |
Olivier Martin | c4e4b8fa5a | |
Olivier Martin | 76fb44643f | |
Olivier Martin | 0a868a506b | |
Olivier Martin | 4c5f35f15f | |
Olivier Martin | ad7aa1899e | |
Olivier Martin | f894c8e23a | |
Olivier Martin | 7f6979c82b | |
Olivier Martin | db8aee543b | |
Olivier Martin | 8065d12dac | |
Olivier Martin | 011f4e4c4a | |
Olivier Martin | 0fde4ff82d | |
Olivier Martin | 9a9e49edcd | |
Olivier Martin | 959ee55b61 | |
Olivier Martin | 118dc961d6 | |
Olivier Martin | 2518348023 | |
Olivier Martin | 884904a3c0 | |
Olivier Martin | 1d80061bf2 | |
Olivier Martin | ec9e5cd38a | |
Olivier Martin | d5aa8d6468 | |
Olivier Martin | 306acf8483 | |
Olivier Martin | 3c9b0eaa1c | |
Olivier Martin | 028dfef5fc | |
Olivier Martin | a41061c1d4 | |
Olivier Martin | 642556f8b9 | |
Olivier Martin | 96407ad763 | |
Olivier Martin | 6d271f98b6 | |
Olivier Martin | 33a8a27592 | |
Olivier Martin | 0369342fd4 | |
Olivier Martin | 3ac5707c95 | |
Olivier Martin | c1a3c02154 | |
Olivier Martin | 20f2d5facf | |
Olivier Martin | 59820e2cad | |
Olivier Martin | ac1f3d44d5 | |
Olivier Martin | bae7df3ee0 | |
Olivier Martin | b5a785e4b6 | |
Olivier Martin | 75fda5df84 | |
Olivier Martin | 2570850046 | |
Olivier Martin | d9c18b93a3 | |
Olivier Martin | 0719dcc31d | |
Olivier Martin | 5c87eda925 | |
wurong | bdc273fe0a | |
tswaehn | b82be455b4 | |
tswaehn | 5badee94b9 | |
VL-80 | 2448da5410 | |
VL-80 | 85acbd8c88 | |
VL-80 | 5b8893c647 | |
tswaehn | c2c6b2f17b | |
tswaehn | deee3766e1 | |
Flaviu Tamas | bb90b55dde | |
Olivier Martin | 1afaa8b460 | |
Olivier Martin | 809a10a289 | |
Olivier Martin | e93504b91f | |
Olivier Martin | e2b189d226 | |
Peter Rosin | f90a95bcff | |
Olivier Martin | fa54ae42cc | |
Olivier Martin | c3abb7eb6c | |
Olivier Martin | f4ecc64d29 | |
Кирилл Зимников | 2c38df5f30 | |
Olivier Martin | db44d7a99d | |
vlefebvre | d66e268865 | |
vlefebvre | f3b5b295bc | |
0xloem | edb5862f96 | |
Kevin Dewald | 15210d1c35 | |
chenbin | 3f85abd606 | |
Kevin Dewald | b137160afc | |
Kevin Dewald | 9451e19426 | |
Kevin Dewald | 321c37dc57 | |
Kevin Dewald | 49790a92bf | |
Kevin Dewald | fee6603fa3 | |
Kevin Dewald | 3c917fe683 | |
Olivier Martin | d63b7ccb27 | |
Olivier Martin | b8f60de4e5 |
|
@ -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
|
|
@ -1,2 +1,9 @@
|
|||
# IDE files
|
||||
.vscode/
|
||||
|
||||
# Generated files
|
||||
build
|
||||
__pycache__
|
||||
MANIFEST.in
|
||||
dist
|
||||
gattlib-py/gattlib_py.egg-info/
|
||||
|
|
|
@ -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>")
|
||||
|
|
|
@ -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/
|
||||
|
|
32
README.md
32
README.md
|
@ -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
|
||||
=======
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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})
|
||||
|
||||
|
|
764
dbus/gattlib.c
764
dbus/gattlib.c
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) */
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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(¬ification_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(¬ification_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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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, ¶ms->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],
|
||||
};
|
||||
|
|
|
@ -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
|
|
@ -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.
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 00002A19–0000–1000–8000–00805f9b34fb
|
||||
#
|
||||
|
||||
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)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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},
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue