├── VERSION ├── .gitignore ├── toolchains ├── windows │ ├── tools │ │ └── vswhere.exe │ ├── windows_compile.bat │ └── scripts │ │ └── compile.ps1 ├── linux │ ├── linux_install.sh │ └── linux_compile.sh ├── utils │ └── tag_push_release.sh └── macos │ └── macos_compile.sh ├── .gitattributes ├── src-ble ├── linux │ ├── simpledbus │ │ ├── SimpleDBus.h │ │ ├── interfaces │ │ │ ├── PropertyHandler.h │ │ │ └── PropertyHandler.cpp │ │ ├── base │ │ │ ├── Connection.h │ │ │ ├── Logger.h │ │ │ ├── Message.h │ │ │ ├── Holder.h │ │ │ ├── Connection.cpp │ │ │ ├── Logger.cpp │ │ │ ├── Holder.cpp │ │ │ └── Message.cpp │ │ └── common │ │ │ ├── ObjectManager.h │ │ │ ├── Properties.h │ │ │ ├── ObjectManager.cpp │ │ │ └── Properties.cpp │ ├── bluezdbus │ │ ├── BluezAgent.cpp │ │ ├── BluezAgent.h │ │ ├── interfaces │ │ │ ├── GattService1.h │ │ │ ├── GattService1.cpp │ │ │ ├── Adapter1.h │ │ │ ├── Device1.h │ │ │ ├── GattCharacteristic1.h │ │ │ ├── Adapter1.cpp │ │ │ ├── Device1.cpp │ │ │ └── GattCharacteristic1.cpp │ │ ├── BluezGattCharacteristic.h │ │ ├── BluezService.h │ │ ├── BluezGattService.h │ │ ├── BluezDevice.h │ │ ├── BluezAdapter.h │ │ ├── BluezGattCharacteristic.cpp │ │ ├── BluezGattService.cpp │ │ ├── BluezDevice.cpp │ │ ├── BluezService.cpp │ │ └── BluezAdapter.cpp │ ├── NativeBleInternal.h │ └── NativeBleInternal.cpp ├── NativeBleControllerTypes.h ├── macos │ ├── NativeBleInternal.h │ ├── macable │ │ ├── macable.h │ │ └── macable.mm │ └── NativeBleInternal.mm ├── windows │ ├── NativeBleInternal.h │ └── NativeBleInternal.cpp ├── NativeBleController.cpp └── NativeBleController.h ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── ci_nightly.yml │ └── ci_release.yml ├── cmake ├── configure_dependencies.cmake ├── configure_cmake.cmake ├── configure_outputs.cmake └── configure_targets.cmake ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENCE.md ├── src-ble-c-api ├── ble_c_api.h └── ble_c_api.cpp ├── .clang-format ├── src-ble-test └── main.cpp ├── README.md └── TUTORIAL.md /VERSION: -------------------------------------------------------------------------------- 1 | 1.0.0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | output.binlog 2 | bin 3 | build 4 | .vscode 5 | .DS_Store -------------------------------------------------------------------------------- /toolchains/windows/tools/vswhere.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdewald/nativeble/HEAD/toolchains/windows/tools/vswhere.exe -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | *.sh text eol=lf 5 | -------------------------------------------------------------------------------- /toolchains/windows/windows_compile.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | powershell.exe -NoProfile -ExecutionPolicy Bypass "& {& '%~dp0scripts\compile.ps1' %*}" -------------------------------------------------------------------------------- /toolchains/linux/linux_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo apt-get update 4 | sudo apt-get install -y build-essential cmake git zip pkg-config libdbus-1-dev libsystemd-dev -------------------------------------------------------------------------------- /src-ble/linux/simpledbus/SimpleDBus.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "base/Connection.h" 4 | #include "base/Message.h" 5 | 6 | #include "interfaces/PropertyHandler.h" 7 | 8 | #include "common/ObjectManager.h" 9 | #include "common/Properties.h" -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/BluezAgent.cpp: -------------------------------------------------------------------------------- 1 | #include "BluezAgent.h" 2 | 3 | BluezAgent::BluezAgent(std::string path, SimpleDBus::Holder options) : _path(path) {} 4 | 5 | BluezAgent::~BluezAgent() {} 6 | 7 | bool BluezAgent::process_received_signal(SimpleDBus::Message& message) { 8 | if (message.get_path() == _path) { 9 | return true; 10 | } 11 | return false; 12 | } -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/BluezAgent.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "simpledbus/SimpleDBus.h" 4 | 5 | #include 6 | 7 | class BluezAgent { 8 | private: 9 | std::string _path; 10 | 11 | public: 12 | BluezAgent(std::string path, SimpleDBus::Holder options); 13 | ~BluezAgent(); 14 | 15 | bool process_received_signal(SimpleDBus::Message& message); 16 | }; 17 | -------------------------------------------------------------------------------- /src-ble/linux/simpledbus/interfaces/PropertyHandler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../base/Holder.h" 4 | 5 | namespace SimpleDBus { 6 | 7 | namespace Interfaces { 8 | 9 | class PropertyHandler { 10 | protected: 11 | virtual void add_option(std::string option_name, Holder value) = 0; 12 | virtual void remove_option(std::string option_name) = 0; 13 | 14 | public: 15 | void set_options(Holder changed_properties); 16 | void set_options(Holder changed_properties, Holder invalidated_properties); 17 | }; 18 | } // namespace Interfaces 19 | 20 | } // namespace SimpleDBus -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/interfaces/GattService1.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "simpledbus/SimpleDBus.h" 4 | 5 | #include 6 | 7 | class GattService1 : public SimpleDBus::Interfaces::PropertyHandler { 8 | private: 9 | SimpleDBus::Connection* _conn; 10 | std::string _path; 11 | 12 | std::string _uuid; 13 | 14 | void add_option(std::string option_name, SimpleDBus::Holder value); 15 | void remove_option(std::string option_name); 16 | 17 | public: 18 | GattService1(SimpleDBus::Connection* conn, std::string path); 19 | ~GattService1(); 20 | 21 | std::string get_uuid(); 22 | }; -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/interfaces/GattService1.cpp: -------------------------------------------------------------------------------- 1 | #include "GattService1.h" 2 | 3 | #include 4 | 5 | GattService1::GattService1(SimpleDBus::Connection* conn, std::string path) : _conn(conn), _path(path) { 6 | // std::cout << "Creating org.bluez.GattService1: " << path << std::endl; 7 | } 8 | 9 | GattService1::~GattService1() {} 10 | 11 | void GattService1::add_option(std::string option_name, SimpleDBus::Holder value) { 12 | if (option_name == "UUID") { 13 | _uuid = value.get_string(); 14 | } 15 | } 16 | 17 | void GattService1::remove_option(std::string option_name) {} 18 | 19 | std::string GattService1::get_uuid() { return _uuid; } 20 | -------------------------------------------------------------------------------- /toolchains/utils/tag_push_release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ${EUID} -eq 0 ]; then 4 | echo "This script must not be executed as root!" 5 | exit 1 6 | fi 7 | 8 | PROJECT_ROOT=$(realpath $(dirname `realpath $0`)/../..) 9 | 10 | VERSION_FILE="$PROJECT_ROOT/VERSION" 11 | if [[ ! -f "$VERSION_FILE" ]]; then 12 | echo "VERSION file not found, aborting." 13 | exit -1 14 | fi 15 | 16 | VERSION=`cat $VERSION_FILE` 17 | 18 | GIT_BRANCH=`git rev-parse --abbrev-ref HEAD` 19 | if [[ ! $GIT_BRANCH == "master" ]]; then 20 | echo "Formal release tags can only be appended to the master branch. Aborting." 21 | exit -1 22 | fi 23 | 24 | git tag v$VERSION 25 | git push origin v$VERSION -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src-ble/linux/simpledbus/base/Connection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Message.h" 5 | 6 | namespace SimpleDBus { 7 | 8 | class Message; 9 | 10 | class Connection { 11 | private: 12 | ::DBusBusType _dbus_bus_type; 13 | ::DBusConnection* _conn; 14 | ::DBusError _err; 15 | 16 | public: 17 | Connection(::DBusBusType dbus_bus_type); 18 | ~Connection(); 19 | 20 | void init(); 21 | 22 | void add_match(std::string rule); 23 | void remove_match(std::string rule); 24 | 25 | void read_write(); 26 | Message pop_message(); 27 | 28 | uint32_t send(Message& msg); 29 | Message send_with_reply_and_block(Message& msg); 30 | }; 31 | 32 | } // namespace SimpleDBus -------------------------------------------------------------------------------- /src-ble/linux/simpledbus/interfaces/PropertyHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "PropertyHandler.h" 2 | 3 | using namespace SimpleDBus::Interfaces; 4 | 5 | void PropertyHandler::set_options(SimpleDBus::Holder changed_properties) { 6 | this->set_options(changed_properties, SimpleDBus::Holder()); 7 | } 8 | 9 | void PropertyHandler::set_options(SimpleDBus::Holder changed_properties, SimpleDBus::Holder invalidated_properties) { 10 | auto changed_options = changed_properties.get_dict(); 11 | for (auto& [name, value] : changed_options) { 12 | this->add_option(name, value); 13 | } 14 | 15 | auto removed_options = invalidated_properties.get_array(); 16 | for (auto& removed_option : removed_options) { 17 | this->remove_option(removed_option.get_string()); 18 | } 19 | } -------------------------------------------------------------------------------- /cmake/configure_dependencies.cmake: -------------------------------------------------------------------------------- 1 | 2 | IF (CMAKE_SYSTEM_NAME STREQUAL "Windows") 3 | set(FILESYSTEM_LIBS ${FILESYSTEM_LIBS}) 4 | ELSEIF(CMAKE_SYSTEM_NAME STREQUAL "Darwin") 5 | set(FILESYSTEM_LIBS ${FILESYSTEM_LIBS}) 6 | set(BLE_LIBS ${BLE_LIBS} "-framework Foundation" "-framework CoreBluetooth" objc) 7 | ELSEIF(CMAKE_SYSTEM_NAME STREQUAL "Linux") 8 | set(FILESYSTEM_LIBS ${FILESYSTEM_LIBS} stdc++fs) 9 | find_package(PkgConfig REQUIRED) 10 | pkg_search_module(DBUS REQUIRED dbus-1) 11 | if(DBUS_FOUND) 12 | include_directories(${DBUS_INCLUDE_DIRS}) 13 | message(STATUS "Using DBUS from path: ${DBUS_INCLUDE_DIRS}") 14 | endif() 15 | 16 | set(BLE_LIBS ${BLE_LIBS} ${DBUS_LIBRARIES} pthread) 17 | ENDIF() 18 | 19 | # Combine with configure_outputs.cmake -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/BluezGattCharacteristic.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "simpledbus/SimpleDBus.h" 4 | 5 | #include "interfaces/GattCharacteristic1.h" 6 | 7 | #include 8 | 9 | class BluezGattCharacteristic : public GattCharacteristic1, public SimpleDBus::Properties { 10 | private: 11 | SimpleDBus::Connection* _conn; 12 | std::string _path; 13 | 14 | void add_interface(std::string interface_name, SimpleDBus::Holder options); 15 | 16 | public: 17 | BluezGattCharacteristic(SimpleDBus::Connection* conn, std::string path, SimpleDBus::Holder options); 18 | ~BluezGattCharacteristic(); 19 | 20 | bool add_path(std::string path, SimpleDBus::Holder options); 21 | bool remove_path(std::string path, SimpleDBus::Holder options); 22 | bool process_received_signal(SimpleDBus::Message& message); 23 | }; 24 | -------------------------------------------------------------------------------- /cmake/configure_cmake.cmake: -------------------------------------------------------------------------------- 1 | # Configure Library Directories for each Platform 2 | IF (CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows" AND NOT DEFINED CMAKE_SYSTEM_NAME) 3 | message("-- [INFO] Windows Host Detected") 4 | if(CMAKE_GENERATOR_PLATFORM MATCHES "^[Ww][Ii][Nn]32$") 5 | set(WINDOWS_TARGET_ARCH x86) 6 | elseif(CMAKE_GENERATOR_PLATFORM MATCHES "^[Xx]64$") 7 | set(WINDOWS_TARGET_ARCH x64) 8 | endif() 9 | set(OUTPUT_SUFFIX "windows-${WINDOWS_TARGET_ARCH}") 10 | 11 | ELSEIF(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") 12 | message("-- [INFO] Darwin Host Detected") 13 | set(OUTPUT_SUFFIX "darwin") 14 | ELSEIF(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") 15 | # Linux uses system libraries, so no weird stuff has to be done here. 16 | message("-- [INFO] Linux Host Detected") 17 | set(OUTPUT_SUFFIX "linux") 18 | ELSE() 19 | message("-- UNSUPPORTED SYSTEM: ${CMAKE_HOST_SYSTEM_NAME} ${CMAKE_SYSTEM_NAME}") 20 | ENDIF() -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/BluezService.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "simpledbus/SimpleDBus.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "BluezAdapter.h" 9 | #include "BluezAgent.h" 10 | 11 | class BluezService { 12 | private: 13 | SimpleDBus::Connection conn; 14 | SimpleDBus::ObjectManager object_manager; 15 | 16 | void add_path(std::string path, SimpleDBus::Holder options); 17 | void remove_path(std::string path, SimpleDBus::Holder options); 18 | 19 | std::shared_ptr agent; 20 | std::map> adapters; 21 | 22 | void process_received_signal(SimpleDBus::Message& message); 23 | 24 | public: 25 | BluezService(); 26 | ~BluezService(); 27 | 28 | void init(); 29 | void run_async(); 30 | 31 | std::shared_ptr get_first_adapter(); 32 | std::shared_ptr get_adapter(std::string adapter_name); 33 | }; 34 | -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/interfaces/Adapter1.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "simpledbus/SimpleDBus.h" 4 | 5 | #include 6 | 7 | class Adapter1 : public SimpleDBus::Interfaces::PropertyHandler { 8 | private: 9 | static const std::string _interface_name; 10 | 11 | SimpleDBus::Connection* _conn; 12 | std::string _path; 13 | 14 | bool _discovering; 15 | 16 | void add_option(std::string option_name, SimpleDBus::Holder value); 17 | void remove_option(std::string option_name); 18 | 19 | public: 20 | Adapter1(SimpleDBus::Connection* conn, std::string path); 21 | ~Adapter1(); 22 | 23 | // DBus Methods 24 | void StartDiscovery(); 25 | void StopDiscovery(); 26 | void SetDiscoveryFilter(SimpleDBus::Holder properties); 27 | SimpleDBus::Holder GetDiscoveryFilters(); 28 | 29 | bool is_discovering(); 30 | 31 | std::function OnDiscoveryStarted; 32 | std::function OnDiscoveryStopped; 33 | }; 34 | -------------------------------------------------------------------------------- /src-ble/linux/simpledbus/common/ObjectManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include "../base/Connection.h" 6 | #include "../base/Holder.h" 7 | #include "../base/Message.h" 8 | 9 | namespace SimpleDBus { 10 | 11 | class Holder; 12 | class Connection; 13 | 14 | class ObjectManager { 15 | private: 16 | const std::string _interface; 17 | 18 | std::string _path; 19 | std::string _service; 20 | Connection* _conn; 21 | 22 | public: 23 | ObjectManager(Connection* conn, std::string service, std::string path); 24 | ~ObjectManager(); 25 | 26 | // Names are made matching the ones from the DBus specification 27 | Holder GetManagedObjects(bool use_callbacks = false); 28 | std::function InterfacesAdded; 29 | std::function InterfacesRemoved; 30 | 31 | bool process_received_signal(Message& message); 32 | }; 33 | 34 | } // namespace SimpleDBus -------------------------------------------------------------------------------- /toolchains/linux/linux_compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PROJECT_ROOT=$(realpath $(dirname `realpath $0`)/../..) 4 | FLAG_DEBUG="-DDEFINE_DEBUG=OFF" 5 | CMAKE_BUILD_TYPE="Release" 6 | 7 | # Parse the received commands 8 | while :; do 9 | case $1 in 10 | -c|--clean) FLAG_CLEAN="SET" 11 | ;; 12 | -d|--debug) 13 | FLAG_DEBUG="-DDEFINE_DEBUG=ON" 14 | CMAKE_BUILD_TYPE="Debug" 15 | ;; 16 | *) break 17 | esac 18 | shift 19 | done 20 | 21 | # Cleanup the existing files 22 | if [[ ! -z "$FLAG_CLEAN" ]]; then 23 | rm -rf $PROJECT_ROOT/bin/linux $PROJECT_ROOT/build/linux 24 | fi 25 | 26 | # Compile! 27 | THREAD_COUNT=$(nproc --all) 28 | mkdir -p $PROJECT_ROOT/build/linux 29 | cd $PROJECT_ROOT/build/linux 30 | cmake -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE -DOUTPUT_DIR=$PROJECT_ROOT/bin/linux -B. -H$PROJECT_ROOT $FLAG_DEBUG 31 | make -j$THREAD_COUNT 32 | cd $PROJECT_ROOT 33 | 34 | zip -r -j $PROJECT_ROOT/bin/linux/linux-x64.zip $PROJECT_ROOT/bin/linux -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [1.0.1] - 2021-02-20 8 | 9 | ### Added 10 | - `OUTPUT_DIR` option to control where artifacts are built. 11 | - Implemented unsubscribe functionality. 12 | - Implemented installation targets for system-wide installation. (Thanks xloem!) 13 | 14 | ### Changed 15 | - Upgraded SimpleDBus to version 1.1.1 16 | - Increased the speed of the Linux async thread polling to every 50us. 17 | - Linux implementation uses OnServicesResolved to trigger `callback_on_device_connected`. 18 | 19 | ### Fixed 20 | - Added missing declaration for `NativeBleInternal::unsubscribe` in MacOS. (Thanks Mach1!) 21 | 22 | 23 | ## [1.0.0] - 2020-08-14 24 | 25 | ### Added 26 | - External facing API with specific implementations for Windows, Linux and macOS. 27 | 28 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.0) 2 | 3 | SET(PROJECT_DIR_PATH ${CMAKE_CURRENT_SOURCE_DIR}) 4 | INCLUDE(${CMAKE_CURRENT_SOURCE_DIR}/cmake/configure_cmake.cmake) 5 | 6 | option(ENABLE_LIB_BLE_C_API "Build C Wrapper" ON) 7 | option(ENABLE_APP_BLE_TEST "Build Test App" ON) 8 | option(OUTPUT_DIR "Output directory for generated artifacts" ${CMAKE_CURRENT_SOURCE_DIR}/bin/${OUTPUT_SUFFIX}) 9 | 10 | project(NATIVEBLE) 11 | 12 | if (OUTPUT_DIR STREQUAL "OFF") 13 | # Set OUTPUT_DIR to its default value if it hasn't been set externally. 14 | set(OUTPUT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/bin/${OUTPUT_SUFFIX}) 15 | endif() 16 | 17 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${OUTPUT_DIR}) 18 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${OUTPUT_DIR}) 19 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${OUTPUT_DIR}) 20 | 21 | INCLUDE(${CMAKE_CURRENT_SOURCE_DIR}/cmake/configure_targets.cmake) 22 | INCLUDE(${CMAKE_CURRENT_SOURCE_DIR}/cmake/configure_dependencies.cmake) 23 | INCLUDE(${CMAKE_CURRENT_SOURCE_DIR}/cmake/configure_outputs.cmake) 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /src-ble/linux/simpledbus/common/Properties.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include "../base/Connection.h" 6 | #include "../base/Holder.h" 7 | #include "../base/Message.h" 8 | 9 | namespace SimpleDBus { 10 | 11 | class Holder; 12 | class Connection; 13 | 14 | class Properties { 15 | private: 16 | const std::string _interface; 17 | 18 | std::string _path; 19 | std::string _service; 20 | Connection* _conn; 21 | 22 | public: 23 | Properties(Connection* conn, std::string service, std::string path); 24 | ~Properties(); 25 | 26 | // Names are made matching the ones from the DBus specification 27 | Holder Get(std::string interface, std::string name); 28 | Holder GetAll(std::string interface); 29 | void Set(std::string interface, std::string name, Holder value); 30 | 31 | std::function PropertiesChanged; 32 | 33 | bool process_received_signal(Message& message); 34 | }; 35 | 36 | } // namespace SimpleDBus -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/BluezGattService.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "simpledbus/SimpleDBus.h" 4 | 5 | #include "interfaces/GattService1.h" 6 | 7 | #include "BluezGattCharacteristic.h" 8 | 9 | #include 10 | #include 11 | 12 | class BluezGattService : public GattService1, public SimpleDBus::Properties { 13 | private: 14 | SimpleDBus::Connection* _conn; 15 | std::string _path; 16 | 17 | std::map> gatt_characteristics; 18 | void add_interface(std::string interface_name, SimpleDBus::Holder options); 19 | 20 | public: 21 | BluezGattService(SimpleDBus::Connection* conn, std::string path, SimpleDBus::Holder options); 22 | ~BluezGattService(); 23 | 24 | bool add_path(std::string path, SimpleDBus::Holder options); 25 | bool remove_path(std::string path, SimpleDBus::Holder options); 26 | bool process_received_signal(SimpleDBus::Message& message); 27 | 28 | std::shared_ptr get_characteristic(std::string char_uuid); 29 | }; 30 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020, Pison Technology Inc. 4 | Copyright (c) 2021, Kevin Dewald. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/interfaces/Device1.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "simpledbus/SimpleDBus.h" 4 | 5 | #include 6 | 7 | class Device1 : public SimpleDBus::Interfaces::PropertyHandler { 8 | private: 9 | static const std::string _interface_name; 10 | 11 | SimpleDBus::Connection* _conn; 12 | std::string _path; 13 | 14 | int16_t _rssi; 15 | std::string _name; 16 | std::string _alias; 17 | std::string _address; 18 | bool _connected; 19 | bool _services_resolved; 20 | 21 | void add_option(std::string option_name, SimpleDBus::Holder value); 22 | void remove_option(std::string option_name); 23 | 24 | public: 25 | Device1(SimpleDBus::Connection* conn, std::string path); 26 | ~Device1(); 27 | 28 | void Connect(); 29 | void Disconnect(); 30 | 31 | int16_t get_rssi(); 32 | std::string get_name(); 33 | std::string get_alias(); 34 | std::string get_address(); 35 | bool is_connected(); 36 | 37 | std::function OnConnected; 38 | std::function OnDisconnected; 39 | std::function OnServicesResolved; 40 | }; -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/BluezDevice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "simpledbus/SimpleDBus.h" 4 | 5 | #include "interfaces/Device1.h" 6 | 7 | #include "BluezGattService.h" 8 | 9 | #include 10 | #include 11 | 12 | class BluezDevice : public Device1, public SimpleDBus::Properties { 13 | private: 14 | SimpleDBus::Connection* _conn; 15 | std::string _path; 16 | 17 | std::map> gatt_services; 18 | void add_interface(std::string interface_name, SimpleDBus::Holder options); 19 | 20 | public: 21 | BluezDevice(SimpleDBus::Connection* conn, std::string path, SimpleDBus::Holder options); 22 | ~BluezDevice(); 23 | 24 | bool add_path(std::string path, SimpleDBus::Holder options); 25 | bool remove_path(std::string path, SimpleDBus::Holder options); 26 | bool process_received_signal(SimpleDBus::Message& message); 27 | 28 | void connect(); 29 | void disconnect(); 30 | 31 | std::shared_ptr get_service(std::string service_uuid); 32 | std::shared_ptr get_characteristic(std::string service_uuid, std::string char_uuid); 33 | }; 34 | -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/BluezAdapter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "simpledbus/SimpleDBus.h" 4 | 5 | #include "interfaces/Adapter1.h" 6 | 7 | #include "BluezDevice.h" 8 | 9 | #include 10 | #include 11 | 12 | class BluezAdapter : public Adapter1, public SimpleDBus::Properties { 13 | private: 14 | SimpleDBus::Connection* _conn; 15 | std::string _path; 16 | 17 | void add_interface(std::string interface_name, SimpleDBus::Holder options); 18 | 19 | public: 20 | BluezAdapter(SimpleDBus::Connection* conn, std::string path, SimpleDBus::Holder managed_interfaces); 21 | ~BluezAdapter(); 22 | 23 | std::map> devices; 24 | std::shared_ptr get_device(std::string mac_address); 25 | 26 | bool add_path(std::string path, SimpleDBus::Holder options); 27 | bool remove_path(std::string path, SimpleDBus::Holder options); 28 | bool process_received_signal(SimpleDBus::Message& message); 29 | 30 | // TODO: Add support for more complex filter types. 31 | void discovery_filter_transport_set(std::string value); 32 | 33 | std::function OnDeviceFound; 34 | }; 35 | -------------------------------------------------------------------------------- /toolchains/macos/macos_compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | realpath() { 4 | OURPWD=$PWD 5 | cd "$(dirname "$1")" 6 | LINK=$(readlink "$(basename "$1")") 7 | while [ "$LINK" ]; do 8 | cd "$(dirname "$LINK")" 9 | LINK=$(readlink "$(basename "$1")") 10 | done 11 | REALPATH="$PWD/$(basename "$1")" 12 | cd "$OURPWD" 13 | echo "$REALPATH" 14 | } 15 | 16 | PROJECT_ROOT=$(realpath $(dirname `realpath $0`)/../..) 17 | FLAG_DEBUG="-DDEFINE_DEBUG=OFF" 18 | CMAKE_BUILD_TYPE="Release" 19 | 20 | # Parse the received commands 21 | while :; do 22 | case $1 in 23 | -c|--clean) FLAG_CLEAN="SET" 24 | ;; 25 | -d|--debug) 26 | FLAG_DEBUG="-DDEFINE_DEBUG=ON" 27 | CMAKE_BUILD_TYPE="Debug" 28 | ;; 29 | *) break 30 | esac 31 | shift 32 | done 33 | 34 | # Cleanup the existing files 35 | if [[ ! -z "$FLAG_CLEAN" ]]; then 36 | rm -rf $PROJECT_ROOT/bin/darwin $PROJECT_ROOT/build/darwin 37 | fi 38 | 39 | # Compile! 40 | mkdir -p $PROJECT_ROOT/build/darwin 41 | cd $PROJECT_ROOT/build/darwin 42 | cmake -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE -B. -H$PROJECT_ROOT $FLAG_DEBUG 43 | make -j8 44 | cd $PROJECT_ROOT 45 | 46 | zip -r -j $PROJECT_ROOT/bin/darwin/darwin-x64.zip $PROJECT_ROOT/bin/darwin 47 | -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/interfaces/GattCharacteristic1.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "simpledbus/SimpleDBus.h" 4 | 5 | #include 6 | #include 7 | 8 | class GattCharacteristic1 : public SimpleDBus::Interfaces::PropertyHandler { 9 | private: 10 | static const std::string _interface_name; 11 | 12 | SimpleDBus::Connection* _conn; 13 | std::string _path; 14 | 15 | std::string _uuid; 16 | std::vector _value; 17 | bool _notifying; 18 | 19 | void add_option(std::string option_name, SimpleDBus::Holder value); 20 | void remove_option(std::string option_name); 21 | 22 | public: 23 | GattCharacteristic1(SimpleDBus::Connection* conn, std::string path); 24 | ~GattCharacteristic1(); 25 | 26 | void StartNotify(); 27 | void StopNotify(); 28 | 29 | void WriteValue(SimpleDBus::Holder value, SimpleDBus::Holder options); 30 | SimpleDBus::Holder ReadValue(SimpleDBus::Holder options); 31 | 32 | std::function new_value)> ValueChanged; 33 | 34 | void write_request(const uint8_t* data, uint16_t length); 35 | void write_command(const uint8_t* data, uint16_t length); 36 | 37 | std::string get_uuid(); 38 | std::vector get_value(); 39 | }; -------------------------------------------------------------------------------- /src-ble/NativeBleControllerTypes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace NativeBLE { 10 | 11 | typedef std::string BluetoothAddress; 12 | typedef std::string BluetoothUUID; 13 | typedef std::string DataChunk; 14 | 15 | typedef struct { 16 | BluetoothAddress address; 17 | std::string name; 18 | } DeviceDescriptor; 19 | 20 | typedef std::map> BluetoothServiceMap; 21 | 22 | class CallbackHolder { 23 | public: 24 | std::function callback_on_scan_start; 25 | std::function callback_on_scan_stop; 26 | std::function callback_on_scan_found; 27 | std::function callback_on_device_connected; 28 | std::function callback_on_device_disconnected; 29 | // TODO: callback_on_services_resolved; 30 | 31 | CallbackHolder() 32 | : callback_on_scan_start([]() {}), 33 | callback_on_scan_stop([]() {}), 34 | callback_on_scan_found([](DeviceDescriptor) {}), 35 | callback_on_device_connected([]() {}), 36 | callback_on_device_disconnected([](std::string) {}) {} 37 | }; 38 | 39 | } // namespace NativeBLE -------------------------------------------------------------------------------- /src-ble/macos/NativeBleInternal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "NativeBleControllerTypes.h" 3 | #include 4 | 5 | namespace NativeBLE { 6 | class NativeBleInternal { 7 | private: 8 | CallbackHolder callback_holder; 9 | std::set detected_addresses; 10 | public: 11 | NativeBleInternal(/* args */); 12 | ~NativeBleInternal(); 13 | 14 | void setup(CallbackHolder callback_holder); 15 | 16 | void scan_start(); 17 | void scan_stop(); 18 | bool scan_is_active(); 19 | void scan_timeout(int32_t timeout_ms); 20 | 21 | bool is_connected(); 22 | 23 | void connect(const BluetoothAddress &address); 24 | 25 | void write_request(BluetoothUUID service, BluetoothUUID characteristic, DataChunk &data); 26 | void write_command(BluetoothUUID service, BluetoothUUID characteristic, DataChunk &data); 27 | 28 | void read(BluetoothUUID service, BluetoothUUID characteristic, 29 | std::function callback_on_read); 30 | void notify(BluetoothUUID service, BluetoothUUID characteristic, 31 | std::function callback_on_notify); 32 | void indicate(BluetoothUUID service, BluetoothUUID characteristic, 33 | std::function callback_on_indicate); 34 | 35 | void unsubscribe(BluetoothUUID service, BluetoothUUID characteristic); 36 | 37 | void disconnect(); 38 | void dispose(); 39 | }; 40 | 41 | } // namespace NativeBLE -------------------------------------------------------------------------------- /src-ble/linux/simpledbus/base/Logger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * Note: Only use this class within the SimpleDBus, as some internal name choices might 5 | * generate conflicts with other libraries. 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | namespace SimpleDBus { 12 | 13 | class Logger { 14 | public: 15 | typedef enum { 16 | LOG_NONE = 0, 17 | LOG_FATAL, 18 | LOG_ERROR, 19 | LOG_WARN, 20 | LOG_INFO, 21 | LOG_DEBUG, 22 | LOG_VERBOSE_0, 23 | LOG_VERBOSE_1, 24 | LOG_VERBOSE_2, 25 | LOG_VERBOSE_3, // Used for tracking the creation/destruction of BlueZ abstractions. 26 | } LogLevel; 27 | 28 | static Logger* get(); 29 | 30 | void log(LogLevel level, const char* file, const char* function, unsigned int line, const char* format, ...); 31 | void set_level(LogLevel level); 32 | 33 | private: 34 | Logger(); 35 | ~Logger(); 36 | Logger(Logger& other) = delete; // Remove the copy constructor 37 | void operator=(const Logger&) = delete; // Remove the copy assignment 38 | 39 | void print_log(std::string message); 40 | 41 | LogLevel _log_level; 42 | std::mutex _mutex; 43 | 44 | static std::string string_format(const char* format, ...); 45 | static std::string string_format(const char* format, va_list vlist); 46 | static std::string parse_function_signature(const char* function); 47 | static std::string parse_file_path(const char* file); 48 | }; 49 | 50 | } // namespace SimpleDBus 51 | 52 | #define VLOG_F(level, ...) SimpleDBus::Logger::get()->log(level, __FILE__, __PRETTY_FUNCTION__, __LINE__, __VA_ARGS__) 53 | #define LOG_F(level, ...) VLOG_F(SimpleDBus::Logger::LogLevel::LOG_##level, __VA_ARGS__) 54 | #define LOG_LEVEL_SET(level) SimpleDBus::Logger::get()->set_level(level) -------------------------------------------------------------------------------- /src-ble/linux/NativeBleInternal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "NativeBleControllerTypes.h" 7 | #include "bluezdbus/BluezService.h" 8 | 9 | namespace NativeBLE { 10 | 11 | class NativeBleInternal { 12 | private: 13 | BluezService bluez_service; 14 | std::shared_ptr adapter; 15 | std::shared_ptr device; 16 | 17 | std::thread *async_thread; 18 | volatile bool async_thread_active = true; 19 | void async_thread_function(); 20 | 21 | CallbackHolder callback_holder; 22 | BluetoothAddress format_mac_address(std::string address); 23 | 24 | public: 25 | NativeBleInternal(); 26 | ~NativeBleInternal(); 27 | 28 | void setup(CallbackHolder callback_holder); 29 | 30 | void scan_start(); 31 | void scan_stop(); 32 | bool scan_is_active(); 33 | void scan_timeout(int32_t timeout_ms); 34 | 35 | bool is_connected(); 36 | 37 | void connect(const BluetoothAddress &address); 38 | 39 | void write_request(BluetoothUUID service, BluetoothUUID characteristic, DataChunk &data); 40 | void write_command(BluetoothUUID service, BluetoothUUID characteristic, DataChunk &data); 41 | 42 | void read(BluetoothUUID service, BluetoothUUID characteristic, 43 | std::function callback_on_read); 44 | void notify(BluetoothUUID service, BluetoothUUID characteristic, 45 | std::function callback_on_notify); 46 | void indicate(BluetoothUUID service, BluetoothUUID characteristic, 47 | std::function callback_on_indicate); 48 | 49 | void unsubscribe(BluetoothUUID service, BluetoothUUID characteristic); 50 | 51 | void disconnect(); 52 | void dispose(); 53 | }; 54 | 55 | } // namespace NativeBLE -------------------------------------------------------------------------------- /src-ble/linux/simpledbus/common/ObjectManager.cpp: -------------------------------------------------------------------------------- 1 | #include "ObjectManager.h" 2 | 3 | #include "../base/Message.h" 4 | 5 | using namespace SimpleDBus; 6 | 7 | ObjectManager::ObjectManager(Connection* conn, std::string service, std::string path) 8 | : _conn(conn), _service(service), _path(path), _interface("org.freedesktop.DBus.ObjectManager") {} 9 | 10 | ObjectManager::~ObjectManager() {} 11 | 12 | Holder ObjectManager::GetManagedObjects(bool use_callbacks) { 13 | Message query_msg = Message::create_method_call(_service, _path, _interface, "GetManagedObjects"); 14 | Message reply_msg = _conn->send_with_reply_and_block(query_msg); 15 | Holder managed_objects = reply_msg.extract(); 16 | if (use_callbacks) { 17 | auto managed_object = managed_objects.get_dict(); 18 | for (auto& [path, options] : managed_object) { 19 | if (InterfacesAdded) { 20 | InterfacesAdded(path, options); 21 | } 22 | } 23 | } 24 | return managed_objects; 25 | } 26 | 27 | bool ObjectManager::process_received_signal(Message& message) { 28 | if (message.get_path() == _path) { 29 | if (message.is_signal(_interface, "InterfacesAdded")) { 30 | std::string path = message.extract().get_string(); 31 | message.extract_next(); 32 | Holder options = message.extract(); 33 | if (InterfacesAdded) { 34 | InterfacesAdded(path, options); 35 | } 36 | return true; 37 | } else if (message.is_signal(_interface, "InterfacesRemoved")) { 38 | std::string path = message.extract().get_string(); 39 | message.extract_next(); 40 | Holder options = message.extract(); 41 | if (InterfacesRemoved) { 42 | InterfacesRemoved(path, options); 43 | } 44 | return true; 45 | } 46 | } 47 | return false; 48 | } -------------------------------------------------------------------------------- /cmake/configure_outputs.cmake: -------------------------------------------------------------------------------- 1 | message("-- [INFO] Building Native BLE") 2 | include_directories(${PROJECT_DIR_PATH}/src-ble) 3 | 4 | IF (CMAKE_SYSTEM_NAME STREQUAL "Windows") 5 | include_directories(${PROJECT_DIR_PATH}/src-ble/windows) 6 | file(GLOB_RECURSE SRC_BLE_FILES "src-ble/NativeBleController.cpp" "src-ble/windows/*.cpp" "src-ble/windows/*.cc") 7 | ELSEIF(CMAKE_SYSTEM_NAME STREQUAL "Darwin") 8 | include_directories(${PROJECT_DIR_PATH}/src-ble/macos) 9 | file(GLOB_RECURSE SRC_BLE_FILES "src-ble/NativeBleController.cpp" "src-ble/macos/*.cpp" "src-ble/macos/*.cc" "src-ble/macos/*.mm") 10 | ELSEIF(CMAKE_SYSTEM_NAME STREQUAL "Linux") 11 | include_directories(${PROJECT_DIR_PATH}/src-ble/linux) 12 | file(GLOB_RECURSE SRC_BLE_FILES "src-ble/NativeBleController.cpp" "src-ble/linux/*.cpp" "src-ble/linux/*.cc") 13 | ENDIF() 14 | 15 | add_library(nativeble-static STATIC ${SRC_BLE_FILES}) 16 | target_link_libraries(nativeble-static ${BLE_LIBS}) 17 | add_library(nativeble SHARED ${SRC_BLE_FILES}) 18 | target_link_libraries(nativeble ${BLE_LIBS}) 19 | 20 | file(COPY "src-ble/NativeBleController.h" "src-ble/NativeBleControllerTypes.h" DESTINATION ${OUTPUT_DIR}) 21 | 22 | install(FILES "src-ble/NativeBleController.h" "src-ble/NativeBleControllerTypes.h" DESTINATION include) 23 | install(TARGETS nativeble-static nativeble) 24 | 25 | IF(ENABLE_LIB_BLE_C_API) 26 | message("-- [INFO] Building Native BLE C Wrapper") 27 | file(GLOB_RECURSE SRC_BLE_C_API_FILES "src-ble-c-api/*.cpp" "src-ble-c-api/*.h") 28 | add_library(nativeble_c SHARED ${SRC_BLE_C_API_FILES}) 29 | target_link_libraries(nativeble_c nativeble-static) 30 | 31 | install(FILES "src-ble-c-api/ble_c_api.h" DESTINATION include) 32 | install(TARGETS nativeble_c) 33 | ENDIF() 34 | 35 | IF(ENABLE_APP_BLE_TEST) 36 | message("-- [INFO] Building Native BLE Test Application") 37 | file(GLOB_RECURSE SRC_BLE_TEST_FILES "src-ble-test/*.cpp" "src-ble-test/*.h" "src-ble-test/*.cc") 38 | add_executable(nativeble_tester ${SRC_BLE_TEST_FILES}) 39 | target_link_libraries(nativeble_tester nativeble-static) 40 | ENDIF() 41 | -------------------------------------------------------------------------------- /src-ble-c-api/ble_c_api.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef _WIN32 6 | #define EXPORT_SYMBOL __declspec(dllexport) 7 | #else 8 | #define EXPORT_SYMBOL 9 | #endif 10 | 11 | #ifdef __cplusplus 12 | extern "C" { 13 | #endif 14 | 15 | EXPORT_SYMBOL void* ble_construct(); 16 | EXPORT_SYMBOL void ble_destruct(void* ble_ptr); 17 | 18 | EXPORT_SYMBOL void ble_setup(void* ble_ptr, void (*callback_on_scan_start)(), void (*callback_on_scan_stop)(), 19 | void (*callback_on_scan_found)(const char*, const char*), 20 | void (*callback_on_device_connected)(), 21 | void (*callback_on_device_disconnected)(const char*)); 22 | 23 | EXPORT_SYMBOL void ble_scan_start(void* ble_ptr); 24 | EXPORT_SYMBOL void ble_scan_stop(void* ble_ptr); 25 | EXPORT_SYMBOL bool ble_scan_is_active(void* ble_ptr); 26 | EXPORT_SYMBOL void ble_scan_timeout(void* ble_ptr, int32_t timeout_ms); 27 | 28 | EXPORT_SYMBOL void ble_connect(void* ble_ptr, const char* address_ptr); 29 | 30 | EXPORT_SYMBOL void ble_write_request(void* ble_ptr, const char* service, const char* characteristic, const char* data, 31 | uint32_t data_len); 32 | EXPORT_SYMBOL void ble_write_command(void* ble_ptr, const char* service, const char* characteristic, const char* data, 33 | uint32_t data_len); 34 | 35 | EXPORT_SYMBOL void ble_read(void* ble_ptr, const char* service, const char* characteristic, 36 | void (*callback_on_read)(const uint8_t* data, uint32_t length)); 37 | EXPORT_SYMBOL void ble_notify(void* ble_ptr, const char* service, const char* characteristic, 38 | void (*callback_on_notify)(const uint8_t* data, uint32_t length)); 39 | EXPORT_SYMBOL void ble_indicate(void* ble_ptr, const char* service, const char* characteristic, 40 | void (*callback_on_indicate)(const uint8_t* data, uint32_t length)); 41 | 42 | EXPORT_SYMBOL void ble_disconnect(void* ble_ptr); 43 | EXPORT_SYMBOL void ble_dispose(void* ble_ptr); 44 | 45 | #ifdef __cplusplus 46 | } 47 | #endif -------------------------------------------------------------------------------- /.github/workflows/ci_nightly.yml: -------------------------------------------------------------------------------- 1 | name: Nightly Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 7 | # schedule: 8 | # - cron: '0 5 * * 1' 9 | 10 | jobs: 11 | 12 | delete-artifacts: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: kolpav/purge-artifacts-action@v1 16 | with: 17 | token: ${{ secrets.GITHUB_TOKEN }} 18 | expire-in: 14days # Setting this to 0 will delete all artifacts 19 | 20 | build-linux: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout Code 24 | uses: actions/checkout@v2 25 | with: 26 | ref: develop 27 | 28 | - name: Setup Toolchain Environment 29 | run: toolchains/linux/linux_install.sh 30 | 31 | - name: Build for Linux 32 | run: toolchains/linux/linux_compile.sh 33 | 34 | - name: Upload artifacts for Linux 35 | uses: actions/upload-artifact@v2 36 | with: 37 | name: linux-x64 38 | path: bin/linux/linux-x64.zip 39 | 40 | build-macos: 41 | runs-on: macos-latest 42 | steps: 43 | - name: Checkout Code 44 | uses: actions/checkout@v2 45 | with: 46 | ref: develop 47 | 48 | - name: Build for macOS 49 | run: toolchains/macos/macos_compile.sh 50 | 51 | - name: Upload artifacts for macOS 52 | uses: actions/upload-artifact@v2 53 | with: 54 | name: darwin-x64 55 | path: bin/darwin/darwin-x64.zip 56 | 57 | build-windows: 58 | runs-on: windows-latest 59 | steps: 60 | - name: Checkout Code 61 | uses: actions/checkout@v2 62 | with: 63 | ref: develop 64 | 65 | - name: Build for Windows x64 66 | shell: pwsh 67 | run: .\toolchains\windows\scripts\compile.ps1 -a x64 68 | 69 | - name: Build for Windows x86 70 | shell: pwsh 71 | run: .\toolchains\windows\scripts\compile.ps1 -a x86 72 | 73 | - name: Upload artifacts for Windows x64 74 | uses: actions/upload-artifact@v2 75 | with: 76 | name: windows-x64 77 | path: .\bin\windows-x64\windows-x64.zip 78 | 79 | - name: Upload artifacts for Windows x86 80 | uses: actions/upload-artifact@v2 81 | with: 82 | name: windows-x86 83 | path: .\bin\windows-x86\windows-x86.zip 84 | 85 | -------------------------------------------------------------------------------- /toolchains/windows/scripts/compile.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [Alias('c')] 3 | [switch]$clean = $false, 4 | [validateset('x86','x64')] 5 | [Alias('a')] 6 | [string]$arch = "x64" # x86, x64 7 | ) 8 | 9 | [string]$PROJECT_ROOT = Resolve-Path $($PSScriptRoot + "\..\..\..") 10 | [string]$VSWHERE_PATH = Resolve-Path $($PROJECT_ROOT + "\toolchains\windows\tools\vswhere.exe") 11 | # -products flag allows the system to also search for users who installed Visual Studio Build Tools 2019 (https://github.com/3F/hMSBuild/issues/12) 12 | [string]$MSBUILD_PATH = & $VSWHERE_PATH -latest -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe -products * | select-object -first 1 13 | [string]$TARGET = "Release" 14 | 15 | if ($null -eq $MSBUILD_PATH) { 16 | Write-Error "MSBuild path not found." 17 | exit -1 18 | } 19 | 20 | # Validate the received architecture 21 | switch -regex ($arch) { 22 | 'x86' { 23 | [string]$OUTPUT_PATH = "windows-x86" 24 | [string]$WINDOWS_ARCH = "Win32" 25 | } 26 | 'x64' { 27 | [string]$OUTPUT_PATH = "windows-x64" 28 | [string]$WINDOWS_ARCH = "x64" 29 | } 30 | } 31 | 32 | 33 | # Clean directories if needed and recreate the necessary folder structure. 34 | if ($clean) { 35 | Remove-Item -Path "$PROJECT_ROOT\bin\$OUTPUT_PATH" -Force -Recurse -ErrorAction SilentlyContinue 36 | Remove-Item -Path "$PROJECT_ROOT\build\$OUTPUT_PATH\*" -Force -Recurse -ErrorAction SilentlyContinue 37 | } 38 | New-Item -ItemType Directory -Force -Path "$PROJECT_ROOT\build\$OUTPUT_PATH" | Out-Null 39 | New-Item -ItemType Directory -Force -Path "$PROJECT_ROOT\bin\$OUTPUT_PATH" | Out-Null 40 | 41 | # Run CMake to create our build files. 42 | cmake -S "$PROJECT_ROOT" -B "$PROJECT_ROOT\build\$OUTPUT_PATH\" -A $WINDOWS_ARCH 43 | 44 | # Run the compiler 45 | & $MSBUILD_PATH "$PROJECT_ROOT\build\$OUTPUT_PATH\NATIVEBLE.sln" -nologo -m:8 /bl:output.binlog /verbosity:minimal /p:Configuration=$TARGET 46 | 47 | #Copy all generated files to the bin folder for consistency and remove the output folder. 48 | Copy-item -Force -Recurse "$PROJECT_ROOT\bin\$OUTPUT_PATH\$TARGET\*" -Destination "$PROJECT_ROOT\bin\$OUTPUT_PATH\" 49 | #Remove-Item -Path "$PROJECT_ROOT\bin\$TARGET" -Force -Recurse -ErrorAction SilentlyContinue 50 | 51 | Compress-Archive -Path "$PROJECT_ROOT\bin\$OUTPUT_PATH\$TARGET\*" -DestinationPath "$PROJECT_ROOT\bin\$OUTPUT_PATH\$OUTPUT_PATH" -Force -------------------------------------------------------------------------------- /src-ble/linux/simpledbus/base/Message.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "Connection.h" 10 | #include "Holder.h" 11 | 12 | namespace SimpleDBus { 13 | 14 | class Connection; 15 | class Interface; 16 | 17 | typedef enum { 18 | INVALID = DBUS_MESSAGE_TYPE_INVALID, 19 | METHOD_CALL = DBUS_MESSAGE_TYPE_METHOD_CALL, 20 | METHOD_RETURN = DBUS_MESSAGE_TYPE_METHOD_RETURN, 21 | ERROR = DBUS_MESSAGE_TYPE_ERROR, 22 | SIGNAL = DBUS_MESSAGE_TYPE_SIGNAL, 23 | } MessageType; 24 | 25 | class Message { 26 | private: 27 | friend class Connection; 28 | 29 | static int creation_counter; 30 | int indent; 31 | 32 | int _unique_id; 33 | DBusMessageIter _iter; 34 | bool _iter_initialized; 35 | bool _is_extracted; 36 | Holder _extracted; 37 | DBusMessage* _msg; 38 | 39 | Holder _extract_bytearray(DBusMessageIter* iter); 40 | Holder _extract_array(DBusMessageIter* iter); 41 | Holder _extract_dict(DBusMessageIter* iter); 42 | Holder _extract_generic(DBusMessageIter* iter); 43 | void _append_argument(DBusMessageIter* iter, Holder& argument, std::string signature); 44 | 45 | void _invalidate(); 46 | void _safe_delete(); 47 | 48 | public: 49 | Message(); 50 | Message(DBusMessage* msg); 51 | Message(Message&& other); // Custom move constructor 52 | Message(const Message& other); // Custom copy constructor 53 | Message& operator=(Message&& other); // Custom move assignment 54 | Message& operator=(const Message& other); // Custom copy assignment 55 | ~Message(); 56 | 57 | bool is_valid() const; 58 | void append_argument(Holder argument, std::string signature); 59 | 60 | Holder extract(); 61 | void extract_reset(); 62 | bool extract_has_next(); 63 | void extract_next(); 64 | std::string to_string() const; 65 | 66 | int32_t get_unique_id(); 67 | uint32_t get_serial(); 68 | std::string get_signature(); 69 | std::string get_interface(); 70 | std::string get_path(); 71 | MessageType get_type(); 72 | 73 | bool is_signal(std::string interface, std::string signal_name); 74 | 75 | static Message create_method_call(std::string bus_name, std::string path, std::string interface, 76 | std::string method); 77 | }; 78 | 79 | } // namespace SimpleDBus -------------------------------------------------------------------------------- /src-ble/windows/NativeBleInternal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "NativeBleControllerTypes.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "winrt/Windows.Devices.Bluetooth.h" 9 | #include "winrt/Windows.Devices.Bluetooth.Advertisement.h" 10 | using namespace winrt::Windows::Devices::Bluetooth; 11 | using namespace winrt::Windows::Devices::Bluetooth::GenericAttributeProfile; 12 | 13 | namespace NativeBLE { 14 | 15 | class NativeBleInternal { 16 | private: 17 | CallbackHolder callback_holder; 18 | BluetoothLEDevice device; 19 | bool scanning; 20 | std::set detected_addresses; 21 | struct Advertisement::BluetoothLEAdvertisementWatcher scanner; 22 | std::map> characteristics_map; 23 | 24 | winrt::guid uuid_to_guid(const std::string &uuid); 25 | std::string guid_to_uuid(const winrt::guid &guid); 26 | GattCharacteristic *fetch_characteristic(const std::string &service_uuid, const std::string &characteristic_uuid); 27 | 28 | void disconnect_execute(); 29 | 30 | BluetoothAddress format_mac_address(std::string); 31 | 32 | public: 33 | NativeBleInternal(/* args */); 34 | ~NativeBleInternal(); 35 | 36 | void setup(CallbackHolder callback_holder); 37 | 38 | void scan_start(); 39 | void scan_stop(); 40 | bool scan_is_active(); 41 | void scan_timeout(int32_t timeout_ms); 42 | 43 | bool is_connected(); 44 | 45 | void connect(const BluetoothAddress &address); 46 | 47 | void write_request(BluetoothUUID service, BluetoothUUID characteristic, DataChunk &data); 48 | void write_command(BluetoothUUID service, BluetoothUUID characteristic, DataChunk &data); 49 | 50 | void read(BluetoothUUID service, BluetoothUUID characteristic, 51 | std::function callback_on_read); 52 | void notify(BluetoothUUID service, BluetoothUUID characteristic, 53 | std::function callback_on_notify); 54 | void indicate(BluetoothUUID service, BluetoothUUID characteristic, 55 | std::function callback_on_indicate); 56 | 57 | void unsubscribe(BluetoothUUID service, BluetoothUUID characteristic); 58 | 59 | void disconnect(); 60 | void dispose(); 61 | }; 62 | 63 | } // namespace NativeBLE 64 | -------------------------------------------------------------------------------- /cmake/configure_targets.cmake: -------------------------------------------------------------------------------- 1 | 2 | # message("-- CMAKE_SYSTEM_INFO_FILE: ${CMAKE_SYSTEM_INFO_FILE}") 3 | # message("-- CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}") 4 | # message("-- CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}") 5 | # message("-- CMAKE_SYSTEM: ${CMAKE_SYSTEM}") 6 | # message(${CMAKE_SIZEOF_VOID_P}) # We'll keep this for later. 7 | 8 | # Configure Compiler Flags for each Platform 9 | IF (CMAKE_SYSTEM_NAME STREQUAL "Windows") 10 | set(WINVERSION_CODE 0x0A00) # Selected Windows 10 based on https://docs.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt 11 | 12 | # Add all the special definitions that need to be added for the program to properly compile on windows. 13 | set(SPECIAL_DEFINITIONS "/D_WIN32_WINNT=${WINVERSION_CODE} /D_USE_MATH_DEFINES") 14 | 15 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17 /W2 /wd4251 ${SPECIAL_DEFINITIONS}") 16 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /std:c11 /W2 /wd4251 ${SPECIAL_DEFINITIONS}") 17 | add_definitions(-DOS_WINDOWS) 18 | 19 | ELSEIF(CMAKE_SYSTEM_NAME STREQUAL "Darwin") 20 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -std=c++17 -Wfatal-errors") 21 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -std=c11 -Wfatal-errors") 22 | add_definitions(-DOS_DARWIN) 23 | 24 | ELSEIF(CMAKE_SYSTEM_NAME STREQUAL "Linux") 25 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -std=c++17 -Wfatal-errors -Wno-ignored-attributes -Wpedantic") 26 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -std=c11 -Wfatal-errors -Wno-ignored-attributes -Wpedantic") 27 | add_definitions(-DOS_LINUX) 28 | 29 | ENDIF() 30 | 31 | if(DEFINE_DEBUG) 32 | message(STATUS "DEBUG MODE ENABLED") 33 | add_definitions(-DDEBUG) 34 | 35 | IF (CMAKE_SYSTEM_NAME STREQUAL "Windows") 36 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 37 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") 38 | 39 | ELSEIF(CMAKE_SYSTEM_NAME STREQUAL "Darwin") 40 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 41 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") 42 | 43 | ELSEIF(CMAKE_SYSTEM_NAME STREQUAL "Linux") 44 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 45 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") 46 | 47 | ENDIF() 48 | 49 | endif() 50 | 51 | # ---------------------------------------------------------------------------------------- 52 | # ---------------------------------------------------------------------------------------- 53 | # ---------------------------------------------------------------------------------------- 54 | 55 | -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/BluezGattCharacteristic.cpp: -------------------------------------------------------------------------------- 1 | #include "BluezGattCharacteristic.h" 2 | 3 | #include "simpledbus/base/Logger.h" 4 | 5 | BluezGattCharacteristic::BluezGattCharacteristic(SimpleDBus::Connection* conn, std::string path, 6 | SimpleDBus::Holder managed_interfaces) 7 | : _conn(conn), _path(path), GattCharacteristic1{conn, path}, Properties{conn, "org.bluez", path} { 8 | Properties::PropertiesChanged = [&](std::string interface, SimpleDBus::Holder changed_properties, 9 | SimpleDBus::Holder invalidated_properties) { 10 | if (interface == "org.bluez.GattCharacteristic1") { 11 | GattCharacteristic1::set_options(changed_properties, invalidated_properties); 12 | } else { 13 | } 14 | }; 15 | 16 | auto managed_interface = managed_interfaces.get_dict(); 17 | for (auto& [iface, options] : managed_interface) { 18 | add_interface(iface, options); 19 | } 20 | } 21 | 22 | BluezGattCharacteristic::~BluezGattCharacteristic() { 23 | // std::cout << "Destroying BluezGattCharacteristic" << std::endl; 24 | } 25 | 26 | bool BluezGattCharacteristic::process_received_signal(SimpleDBus::Message& message) { 27 | if (message.get_path() == _path) { 28 | if (Properties::process_received_signal(message)) return true; 29 | // TODO: Add any remaining signal receivers. 30 | } 31 | return false; 32 | } 33 | 34 | void BluezGattCharacteristic::add_interface(std::string interface_name, SimpleDBus::Holder options) { 35 | // std::cout << interface_name << std::endl; 36 | if (interface_name == "org.bluez.GattCharacteristic1") { 37 | GattCharacteristic1::set_options(options); 38 | } else { 39 | } 40 | } 41 | 42 | bool BluezGattCharacteristic::add_path(std::string path, SimpleDBus::Holder options) { 43 | int path_elements = std::count(path.begin(), path.end(), '/'); 44 | if (path.rfind(_path, 0) == 0) { 45 | if (path_elements == 7) { 46 | // TODO: Characteristics also have Descriptors, although I'm not sure we need them. 47 | // std::cout << "New path: " << path << std::endl << options.represent() << std::endl; 48 | } else { 49 | } 50 | 51 | return true; 52 | } 53 | return false; 54 | } 55 | 56 | bool BluezGattCharacteristic::remove_path(std::string path, SimpleDBus::Holder options) { 57 | LOG_F(DEBUG, "remove_path not implemented (%s needed to remove %s)", _path.c_str(), _path.c_str()); 58 | return false; 59 | } -------------------------------------------------------------------------------- /src-ble/NativeBleController.cpp: -------------------------------------------------------------------------------- 1 | #include "NativeBleController.h" 2 | #include "NativeBleInternal.h" 3 | 4 | #include 5 | 6 | using namespace NativeBLE; 7 | 8 | NativeBleController::NativeBleController() : internal(nullptr) { internal = new NativeBleInternal(); } 9 | 10 | NativeBleController::~NativeBleController() { 11 | if (internal != nullptr) { 12 | delete internal; 13 | } 14 | } 15 | 16 | void NativeBleController::setup(CallbackHolder callback_holder) { internal->setup(callback_holder); } 17 | 18 | void NativeBleController::scan_start() { internal->scan_start(); } 19 | 20 | void NativeBleController::scan_stop() { internal->scan_stop(); } 21 | 22 | bool NativeBleController::scan_is_active() { return internal->scan_is_active(); } 23 | 24 | void NativeBleController::scan_timeout(int32_t timeout_ms) { internal->scan_timeout(timeout_ms); } 25 | 26 | bool NativeBleController::is_connected() { return internal->is_connected(); } 27 | 28 | void NativeBleController::connect(const BluetoothAddress& address) { internal->connect(address); } 29 | 30 | void NativeBleController::write_request(BluetoothUUID service, BluetoothUUID characteristic, DataChunk data) { 31 | internal->write_request(service, characteristic, data); 32 | } 33 | 34 | void NativeBleController::write_command(BluetoothUUID service, BluetoothUUID characteristic, DataChunk data) { 35 | internal->write_command(service, characteristic, data); 36 | } 37 | 38 | void NativeBleController::read(BluetoothUUID service, BluetoothUUID characteristic, 39 | std::function callback_on_read) { 40 | internal->read(service, characteristic, callback_on_read); 41 | } 42 | 43 | void NativeBleController::notify(BluetoothUUID service, BluetoothUUID characteristic, 44 | std::function callback_on_notify) { 45 | internal->notify(service, characteristic, callback_on_notify); 46 | } 47 | 48 | void NativeBleController::indicate(BluetoothUUID service, BluetoothUUID characteristic, 49 | std::function callback_on_indicate) { 50 | internal->indicate(service, characteristic, callback_on_indicate); 51 | } 52 | 53 | void NativeBleController::unsubscribe(BluetoothUUID service, BluetoothUUID characteristic) { 54 | internal->unsubscribe(service, characteristic); 55 | } 56 | 57 | void NativeBleController::disconnect() { internal->disconnect(); } 58 | 59 | void NativeBleController::dispose() { internal->dispose(); } -------------------------------------------------------------------------------- /src-ble/linux/simpledbus/base/Holder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace SimpleDBus { 9 | 10 | typedef enum { 11 | NONE, 12 | BYTE, 13 | BOOLEAN, 14 | INT16, 15 | UINT16, 16 | INT32, 17 | UINT32, 18 | INT64, 19 | UINT64, 20 | DOUBLE, 21 | STRING, 22 | OBJ_PATH, 23 | SIGNATURE, 24 | ARRAY, 25 | DICT, 26 | } HolderType; // TODO: Move into the Holder class. 27 | 28 | class Holder; 29 | 30 | class Holder { 31 | private: 32 | HolderType _type; 33 | 34 | union { 35 | bool holder_boolean; 36 | uint64_t holder_integer; 37 | double holder_double; 38 | }; 39 | std::string holder_string; 40 | std::vector holder_array; 41 | std::map holder_dict; 42 | 43 | std::vector _represent_container(); 44 | std::string _represent_simple(); 45 | std::string _signature_simple(); 46 | 47 | public: 48 | Holder(); 49 | ~Holder(); 50 | Holder(const Holder& other); 51 | Holder& operator=(const Holder& other); 52 | 53 | HolderType type(); 54 | std::string represent(); 55 | std::string signature(); 56 | 57 | static Holder create_boolean(bool value); 58 | static Holder create_byte(uint8_t value); 59 | static Holder create_int16(int16_t value); 60 | static Holder create_uint16(uint16_t value); 61 | static Holder create_int32(int32_t value); 62 | static Holder create_uint32(uint32_t value); 63 | static Holder create_int64(int64_t value); 64 | static Holder create_uint64(uint64_t value); 65 | static Holder create_double(double value); 66 | static Holder create_string(const char* str); 67 | static Holder create_object_path(const char* str); 68 | static Holder create_signature(const char* str); 69 | static Holder create_array(); 70 | static Holder create_dict(); // TODO: Add support for different key types. 71 | 72 | bool get_boolean(); 73 | uint8_t get_byte(); 74 | int16_t get_int16(); 75 | uint16_t get_uint16(); 76 | int32_t get_int32(); 77 | uint32_t get_uint32(); 78 | int64_t get_int64(); 79 | uint64_t get_uint64(); 80 | double get_double(); 81 | std::string get_string(); 82 | std::string get_object_path(); 83 | std::string get_signature(); 84 | std::vector get_array(); 85 | std::map get_dict(); 86 | 87 | void array_append(Holder holder); 88 | void dict_append(std::string key, Holder value); 89 | }; 90 | 91 | } // namespace SimpleDBus -------------------------------------------------------------------------------- /src-ble/linux/simpledbus/common/Properties.cpp: -------------------------------------------------------------------------------- 1 | #include "Properties.h" 2 | 3 | #include "../base/Message.h" 4 | 5 | #include 6 | 7 | using namespace SimpleDBus; 8 | 9 | Properties::Properties(Connection* conn, std::string service, std::string path) 10 | : _conn(conn), _service(service), _path(path), _interface("org.freedesktop.DBus.Properties") {} 11 | 12 | Properties::~Properties() {} 13 | 14 | // Names are made matching the ones from the DBus specification 15 | Holder Properties::Get(std::string interface, std::string name) { 16 | Message query_msg = Message::create_method_call(_service, _path, _interface, "Get"); 17 | 18 | Holder h_interface = Holder::create_string(interface.c_str()); 19 | query_msg.append_argument(h_interface, "s"); 20 | 21 | Holder h_name = Holder::create_string(name.c_str()); 22 | query_msg.append_argument(h_name, "s"); 23 | 24 | Message reply_msg = _conn->send_with_reply_and_block(query_msg); 25 | Holder result = reply_msg.extract(); 26 | return result; 27 | } 28 | 29 | Holder Properties::GetAll(std::string interface) { 30 | Message query_msg = Message::create_method_call(_service, _path, _interface, "GetAll"); 31 | 32 | Holder h_interface = Holder::create_string(interface.c_str()); 33 | query_msg.append_argument(h_interface, "s"); 34 | 35 | Message reply_msg = _conn->send_with_reply_and_block(query_msg); 36 | Holder result = reply_msg.extract(); 37 | return result; 38 | } 39 | 40 | void Properties::Set(std::string interface, std::string name, Holder value) { 41 | Message query_msg = Message::create_method_call(_service, _path, _interface, "Set"); 42 | 43 | Holder h_interface = Holder::create_string(interface.c_str()); 44 | query_msg.append_argument(h_interface, "s"); 45 | 46 | Holder h_name = Holder::create_string(name.c_str()); 47 | query_msg.append_argument(h_name, "s"); 48 | 49 | query_msg.append_argument(value, "v"); 50 | 51 | _conn->send_with_reply_and_block(query_msg); 52 | } 53 | 54 | bool Properties::process_received_signal(Message& message) { 55 | if (message.get_path() == _path && message.is_signal(_interface, "PropertiesChanged")) { 56 | Holder interface = message.extract(); 57 | message.extract_next(); 58 | Holder changed_properties = message.extract(); 59 | message.extract_next(); 60 | Holder invalidated_properties = message.extract(); 61 | if (PropertiesChanged) { 62 | PropertiesChanged(interface.get_string(), changed_properties, invalidated_properties); 63 | } 64 | return true; 65 | } 66 | return false; 67 | } -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/interfaces/Adapter1.cpp: -------------------------------------------------------------------------------- 1 | #include "Adapter1.h" 2 | 3 | #include "simpledbus/base/Logger.h" 4 | 5 | const std::string Adapter1::_interface_name = "org.bluez.Adapter1"; 6 | 7 | Adapter1::Adapter1(SimpleDBus::Connection* conn, std::string path) : _conn(conn), _path(path), _discovering(false) {} 8 | 9 | Adapter1::~Adapter1() {} 10 | 11 | void Adapter1::add_option(std::string option_name, SimpleDBus::Holder value) { 12 | if (option_name == "Discovering") { 13 | _discovering = value.get_boolean(); 14 | if (_discovering && OnDiscoveryStarted) { 15 | LOG_F(VERBOSE_0, "%s -> OnDiscoveryStarted", _path.c_str()); 16 | OnDiscoveryStarted(); 17 | } else if (!_discovering && OnDiscoveryStopped) { 18 | LOG_F(VERBOSE_0, "%s -> OnDiscoveryStopped", _path.c_str()); 19 | OnDiscoveryStopped(); 20 | } 21 | } 22 | } 23 | void Adapter1::remove_option(std::string option_name) {} 24 | 25 | void Adapter1::StartDiscovery() { 26 | if (!_discovering) { 27 | LOG_F(DEBUG, "%s -> StartDiscovery", _path.c_str()); 28 | auto msg = SimpleDBus::Message::create_method_call("org.bluez", _path, _interface_name, "StartDiscovery"); 29 | _conn->send_with_reply_and_block(msg); 30 | } else { 31 | LOG_F(WARN, "%s is already discoverying...", _path.c_str()); 32 | } 33 | } 34 | void Adapter1::StopDiscovery() { 35 | if (_discovering) { 36 | LOG_F(DEBUG, "%s -> StopDiscovery", _path.c_str()); 37 | auto msg = SimpleDBus::Message::create_method_call("org.bluez", _path, _interface_name, "StopDiscovery"); 38 | _conn->send_with_reply_and_block(msg); 39 | // NOTE: It might take a few seconds until the peripheral reports that is has actually stopped discovering. 40 | } else { 41 | LOG_F(WARN, "%s was not discoverying...", _path.c_str()); 42 | } 43 | } 44 | 45 | SimpleDBus::Holder Adapter1::GetDiscoveryFilters() { 46 | LOG_F(DEBUG, "%s -> GetDiscoveryFilters", _path.c_str()); 47 | auto msg = SimpleDBus::Message::create_method_call("org.bluez", _path, _interface_name, "GetDiscoveryFilters"); 48 | SimpleDBus::Message reply_msg = _conn->send_with_reply_and_block(msg); 49 | SimpleDBus::Holder discovery_filters = reply_msg.extract(); 50 | // std::cout << discovery_filters.represent() << std::endl; 51 | return discovery_filters; 52 | } 53 | 54 | void Adapter1::SetDiscoveryFilter(SimpleDBus::Holder properties) { 55 | LOG_F(DEBUG, "%s -> SetDiscoveryFilters", _path.c_str()); 56 | auto msg = SimpleDBus::Message::create_method_call("org.bluez", _path, _interface_name, "SetDiscoveryFilter"); 57 | msg.append_argument(properties, "a{sv}"); 58 | _conn->send_with_reply_and_block(msg); 59 | } 60 | 61 | bool Adapter1::is_discovering() { return _discovering; } 62 | -------------------------------------------------------------------------------- /src-ble/linux/simpledbus/base/Connection.cpp: -------------------------------------------------------------------------------- 1 | #include "Connection.h" 2 | #include "simpledbus/base/Logger.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace SimpleDBus; 9 | 10 | Connection::Connection(DBusBusType dbus_bus_type) : _dbus_bus_type(dbus_bus_type) {} 11 | 12 | Connection::~Connection() { 13 | // In order to prevent a crash on any third party environment 14 | // we need to flush the connection queue. 15 | SimpleDBus::Message message; 16 | do { 17 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 18 | read_write(); 19 | message = pop_message(); 20 | } while (message.is_valid()); 21 | // --------------------------------------------------------- 22 | 23 | dbus_error_free(&_err); 24 | dbus_connection_unref(_conn); 25 | } 26 | 27 | void Connection::init() { 28 | dbus_threads_init_default(); 29 | dbus_error_init(&_err); 30 | _conn = dbus_bus_get(_dbus_bus_type, &_err); 31 | if (dbus_error_is_set(&_err)) { 32 | LOG_F(ERROR, "Failed to get the DBus bus. (%s: %s)", _err.name, _err.message); 33 | dbus_error_free(&_err); 34 | } 35 | } 36 | 37 | void Connection::add_match(std::string rule) { 38 | dbus_bus_add_match(_conn, rule.c_str(), &_err); 39 | dbus_connection_flush(_conn); 40 | if (dbus_error_is_set(&_err)) { 41 | LOG_F(ERROR, "Failed to add match. (%s: %s)", _err.name, _err.message); 42 | dbus_error_free(&_err); 43 | } 44 | } 45 | 46 | void Connection::remove_match(std::string rule) { 47 | dbus_bus_remove_match(_conn, rule.c_str(), &_err); 48 | dbus_connection_flush(_conn); 49 | if (dbus_error_is_set(&_err)) { 50 | LOG_F(ERROR, "Failed to remove match. (%s: %s)", _err.name, _err.message); 51 | dbus_error_free(&_err); 52 | } 53 | } 54 | 55 | void Connection::read_write() { 56 | // Non blocking read of the next available message 57 | dbus_connection_read_write(_conn, 0); 58 | } 59 | 60 | Message Connection::pop_message() { 61 | DBusMessage* msg = dbus_connection_pop_message(_conn); 62 | if (msg == nullptr) { 63 | return Message(); 64 | } else { 65 | return Message(msg); 66 | } 67 | } 68 | 69 | uint32_t Connection::send(Message& msg) { 70 | uint32_t msg_serial = 0; 71 | bool success = dbus_connection_send(_conn, msg._msg, &msg_serial); 72 | 73 | if (!success) { 74 | LOG_F(ERROR, "Message send failed."); 75 | } else { 76 | dbus_connection_flush(_conn); 77 | } 78 | return msg_serial; 79 | } 80 | 81 | Message Connection::send_with_reply_and_block(Message& msg) { 82 | DBusMessage* msg_tmp = dbus_connection_send_with_reply_and_block(_conn, msg._msg, -1, &_err); 83 | 84 | if (dbus_error_is_set(&_err)) { 85 | LOG_F(WARN, "Message send failed. (%s: %s)", _err.name, _err.message); 86 | dbus_error_free(&_err); 87 | return Message(); 88 | } else { 89 | return Message(msg_tmp); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /.github/workflows/ci_release.yml: -------------------------------------------------------------------------------- 1 | name: Release Build 2 | 3 | on: 4 | # push: 5 | # branches: 6 | # - master 7 | release: 8 | types: [published] 9 | 10 | jobs: 11 | 12 | extract-version: 13 | runs-on: ubuntu-latest 14 | outputs: 15 | version: ${{ steps.get-version.outputs.version }} 16 | steps: 17 | - uses: actions/checkout@v2 18 | with: 19 | ref: master 20 | - id: get-version 21 | run: echo "::set-output name=version::v$(cat `pwd`/VERSION)" 22 | 23 | build-linux: 24 | runs-on: ubuntu-latest 25 | needs: extract-version 26 | steps: 27 | - name: Checkout Code 28 | uses: actions/checkout@v2 29 | with: 30 | ref: master 31 | 32 | - name: Setup Toolchain Environment 33 | run: toolchains/linux/linux_install.sh 34 | 35 | - name: Build for Linux 36 | run: toolchains/linux/linux_compile.sh 37 | 38 | - name: Upload release files for Linux 39 | uses: djnicholson/release-action@v2.4 40 | with: 41 | token: ${{ secrets.GITHUB_TOKEN }} 42 | tag-name: ${{needs.extract-version.outputs.version}} 43 | asset-name: linux-x64.zip 44 | file: bin/linux/linux-x64.zip 45 | 46 | build-macos: 47 | runs-on: macos-latest 48 | needs: extract-version 49 | steps: 50 | - name: Checkout Code 51 | uses: actions/checkout@v2 52 | with: 53 | ref: master 54 | 55 | - name: Build for macOS 56 | run: toolchains/macos/macos_compile.sh 57 | 58 | - name: Upload release files for macOS 59 | uses: djnicholson/release-action@v2.4 60 | with: 61 | token: ${{ secrets.GITHUB_TOKEN }} 62 | tag-name: ${{needs.extract-version.outputs.version}} 63 | asset-name: darwin-x64.zip 64 | file: bin/darwin/darwin-x64.zip 65 | 66 | build-windows: 67 | runs-on: windows-latest 68 | needs: extract-version 69 | steps: 70 | - name: Checkout Code 71 | uses: actions/checkout@v2 72 | with: 73 | ref: master 74 | 75 | - name: Build for Windows x64 76 | shell: pwsh 77 | run: .\toolchains\windows\scripts\compile.ps1 -a x64 78 | 79 | - name: Build for Windows x86 80 | shell: pwsh 81 | run: .\toolchains\windows\scripts\compile.ps1 -a x86 82 | 83 | - name: Upload release files for Windows x64 84 | uses: djnicholson/release-action@v2.4 85 | with: 86 | token: ${{ secrets.GITHUB_TOKEN }} 87 | tag-name: ${{needs.extract-version.outputs.version}} 88 | asset-name: windows-x64.zip 89 | file: .\bin\windows-x64\windows-x64.zip 90 | 91 | - name: Upload release files for Windows x86 92 | uses: djnicholson/release-action@v2.4 93 | with: 94 | token: ${{ secrets.GITHUB_TOKEN }} 95 | tag-name: ${{needs.extract-version.outputs.version}} 96 | asset-name: windows-x86.zip 97 | file: .\bin\windows-x86\windows-x86.zip -------------------------------------------------------------------------------- /src-ble/linux/simpledbus/base/Logger.cpp: -------------------------------------------------------------------------------- 1 | #include "Logger.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace SimpleDBus; 8 | 9 | static const char* log_level_strings[] = {"NONE", "FATAL", "ERROR", "WARN", "INFO", 10 | "DEBUG", "VERBOSE", "VERBOSE", "VERBOSE", "VERBOSE"}; 11 | 12 | Logger::Logger() : _log_level(LogLevel::LOG_INFO) {} 13 | 14 | Logger::~Logger() {} 15 | 16 | Logger* Logger::get() { 17 | static std::mutex get_mutex; // Static mutex to ensure thread safety when accessing the logger 18 | std::scoped_lock lock(get_mutex); // Unlock the mutex on function return 19 | static Logger instance; // Static instance of the logger to ensure proper lifecycle management 20 | return &instance; 21 | } 22 | 23 | void Logger::print_log(std::string message) { std::cerr << message << std::endl; } 24 | 25 | void Logger::log(LogLevel level, const char* file, const char* function, unsigned int line, const char* format, ...) { 26 | std::scoped_lock lock(_mutex); 27 | if (level > _log_level) return; 28 | 29 | va_list vlist; 30 | va_start(vlist, format); 31 | std::string user_message = Logger::string_format(format, vlist); 32 | va_end(vlist); 33 | 34 | std::string function_signature = parse_function_signature(function); 35 | std::string filename = parse_file_path(file); 36 | 37 | std::string log_message = Logger::string_format("[%7s] %s (%s:%u) %s", log_level_strings[level], filename.c_str(), 38 | function_signature.c_str(), line, user_message.c_str()); 39 | print_log(log_message); 40 | } 41 | 42 | void Logger::set_level(LogLevel level) { 43 | std::scoped_lock lock(_mutex); 44 | _log_level = level; 45 | } 46 | 47 | std::string Logger::string_format(const char* format, ...) { 48 | std::string text = ""; 49 | va_list vlist; 50 | va_start(vlist, format); 51 | text = Logger::string_format(format, vlist); 52 | va_end(vlist); 53 | return text; 54 | } 55 | 56 | std::string Logger::string_format(const char* format, va_list vlist) { 57 | std::string text = ""; 58 | char* text_buffer = nullptr; 59 | 60 | int result = vasprintf(&text_buffer, format, vlist); 61 | 62 | if (result >= 0) { 63 | text = std::string(text_buffer); 64 | free(text_buffer); 65 | } else { 66 | // An error has happened with vasprintf. 67 | printf("Error during message generation. Format was: '%s'", format); 68 | // Abort program execution as we might have entered some invalid state. 69 | abort(); 70 | } 71 | return text; 72 | } 73 | 74 | std::string Logger::parse_function_signature(const char* function) { 75 | // NOTE: This function will have trouble with more function signatures having types 76 | // that contain spaces or 77 | std::string function_signature(function); 78 | 79 | std::size_t name_start = function_signature.find_first_of(" ") + 1; 80 | std::size_t name_end = function_signature.find_first_of("("); 81 | 82 | return function_signature.substr(name_start, name_end - name_start); 83 | } 84 | 85 | std::string Logger::parse_file_path(const char* file) { 86 | std::string file_path(file); 87 | std::size_t name_start = file_path.find_last_of("/") + 1; 88 | return file_path.substr(name_start); 89 | } 90 | -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/interfaces/Device1.cpp: -------------------------------------------------------------------------------- 1 | #include "Device1.h" 2 | 3 | #include "simpledbus/base/Logger.h" 4 | 5 | const std::string Device1::_interface_name = "org.bluez.Device1"; 6 | 7 | Device1::Device1(SimpleDBus::Connection* conn, std::string path) 8 | : _conn(conn), _path(path), _address(""), _name(""), _connected(false), _services_resolved(false) {} 9 | 10 | Device1::~Device1() {} 11 | 12 | void Device1::add_option(std::string option_name, SimpleDBus::Holder value) { 13 | if (option_name == "Address") { 14 | _address = value.get_string(); 15 | } else if (option_name == "Name") { 16 | _name = value.get_string(); 17 | } else if (option_name == "Alias") { 18 | _alias = value.get_string(); 19 | } else if (option_name == "RSSI") { 20 | _rssi = value.get_int16(); 21 | } else if (option_name == "Connected") { 22 | _connected = value.get_boolean(); 23 | if (_connected && OnConnected) { 24 | LOG_F(VERBOSE_0, "%s -> OnConnected", _path.c_str()); 25 | OnConnected(); 26 | } else if (!_connected && OnDisconnected) { 27 | LOG_F(VERBOSE_0, "%s -> OnDisconnected", _path.c_str()); 28 | OnDisconnected(); 29 | } 30 | } else if (option_name == "ServicesResolved") { 31 | _services_resolved = value.get_boolean(); 32 | if (_services_resolved && OnServicesResolved) { 33 | LOG_F(VERBOSE_0, "%s -> OnServicesResolved", _path.c_str()); 34 | OnServicesResolved(); 35 | } 36 | } 37 | // TODO: Add ManufacturerData 38 | } 39 | 40 | void Device1::remove_option(std::string option_name) {} 41 | 42 | void Device1::Connect() { 43 | if (!_connected) { 44 | // Only attempt connection if disconnected. 45 | LOG_F(DEBUG, "%s -> Connect", _path.c_str()); 46 | auto msg = SimpleDBus::Message::create_method_call("org.bluez", _path, _interface_name, "Connect"); 47 | _conn->send_with_reply_and_block(msg); 48 | } else { 49 | LOG_F(WARN, "%s is already connected...", _path.c_str()); 50 | // If already connected, run callbacks to emulate a successful connection. 51 | if (OnConnected) { 52 | LOG_F(VERBOSE_0, "%s -> OnConnected (fake)", _path.c_str()); 53 | OnConnected(); 54 | } 55 | if (_services_resolved && OnServicesResolved) { 56 | LOG_F(VERBOSE_0, "%s -> OnServicesResolved (fake)", _path.c_str()); 57 | OnServicesResolved(); 58 | } 59 | } 60 | } 61 | 62 | void Device1::Disconnect() { 63 | if (_connected) { 64 | // Only attempt disconnection if connected. 65 | LOG_F(DEBUG, "%s -> Disconnect", _path.c_str()); 66 | auto msg = SimpleDBus::Message::create_method_call("org.bluez", _path, _interface_name, "Disconnect"); 67 | _conn->send_with_reply_and_block(msg); 68 | } else { 69 | LOG_F(WARN, "%s is already disconnected...", _path.c_str()); 70 | // If already disconnected, run callbacks to emulate a successful disconnection. 71 | if (OnDisconnected) { 72 | LOG_F(VERBOSE_0, "%s -> OnDisconnected (fake)", _path.c_str()); 73 | OnDisconnected(); 74 | } 75 | } 76 | } 77 | 78 | int16_t Device1::get_rssi() { return _rssi; } 79 | 80 | std::string Device1::get_name() { return _name; } 81 | 82 | std::string Device1::get_alias() { return _alias; } 83 | 84 | std::string Device1::get_address() { return _address; } 85 | 86 | bool Device1::is_connected() { return _connected; } -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/BluezGattService.cpp: -------------------------------------------------------------------------------- 1 | #include "BluezGattService.h" 2 | 3 | #include "simpledbus/base/Logger.h" 4 | 5 | BluezGattService::BluezGattService(SimpleDBus::Connection* conn, std::string path, 6 | SimpleDBus::Holder managed_interfaces) 7 | : _conn(conn), _path(path), GattService1{conn, path}, Properties{conn, "org.bluez", path} { 8 | Properties::PropertiesChanged = [&](std::string interface, SimpleDBus::Holder changed_properties, 9 | SimpleDBus::Holder invalidated_properties) { 10 | if (interface == "org.bluez.GattService1") { 11 | GattService1::set_options(changed_properties, invalidated_properties); 12 | } else { 13 | } 14 | }; 15 | 16 | auto managed_interface = managed_interfaces.get_dict(); 17 | for (auto& [iface, options] : managed_interface) { 18 | add_interface(iface, options); 19 | } 20 | } 21 | 22 | BluezGattService::~BluezGattService() {} 23 | 24 | bool BluezGattService::process_received_signal(SimpleDBus::Message& message) { 25 | if (message.get_path() == _path) { 26 | if (Properties::process_received_signal(message)) return true; 27 | } else { 28 | for (auto& [gatt_char_path, gatt_characteristic] : gatt_characteristics) { 29 | if (gatt_characteristic->process_received_signal(message)) return true; 30 | } 31 | } 32 | return false; 33 | } 34 | 35 | void BluezGattService::add_interface(std::string interface_name, SimpleDBus::Holder options) { 36 | // std::cout << interface_name << std::endl; 37 | if (interface_name == "org.bluez.GattService1") { 38 | GattService1::set_options(options); 39 | } else { 40 | } 41 | } 42 | 43 | bool BluezGattService::add_path(std::string path, SimpleDBus::Holder options) { 44 | int path_elements = std::count(path.begin(), path.end(), '/'); 45 | if (path.rfind(_path, 0) == 0) { 46 | if (path_elements == 6) { 47 | if (path.find("char") != std::string::npos) { 48 | // We're adding a service 49 | gatt_characteristics.emplace(std::make_pair(path, new BluezGattCharacteristic(_conn, path, options))); 50 | } 51 | } else { 52 | // Corresponds to a device component 53 | for (auto& [gatt_char_path, gatt_characteristic] : gatt_characteristics) { 54 | if (gatt_characteristic->add_path(path, options)) return true; 55 | } 56 | } 57 | return true; 58 | } 59 | return false; 60 | } 61 | 62 | bool BluezGattService::remove_path(std::string path, SimpleDBus::Holder options) { 63 | int path_elements = std::count(path.begin(), path.end(), '/'); 64 | if (path.rfind(_path, 0) == 0) { 65 | if (path_elements == 6) { 66 | gatt_characteristics.erase(path); 67 | return true; 68 | } else { 69 | // Propagate the paths downwards until someone claims it. 70 | for (auto& [gatt_char_path, gatt_characteristic] : gatt_characteristics) { 71 | if (gatt_characteristic->remove_path(path, options)) return true; 72 | } 73 | } 74 | } 75 | return false; 76 | } 77 | 78 | std::shared_ptr BluezGattService::get_characteristic(std::string char_uuid) { 79 | std::shared_ptr return_value = nullptr; 80 | 81 | for (auto& [gatt_char_path, gatt_characteristic] : gatt_characteristics) { 82 | if (gatt_characteristic->get_uuid() == char_uuid) { 83 | return_value = gatt_characteristic; 84 | break; 85 | } 86 | } 87 | 88 | return return_value; 89 | } -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | #https://clang.llvm.org/docs/ClangFormatStyleOptions.html 2 | Language: Cpp 3 | # BasedOnStyle: Google 4 | 5 | AccessModifierOffset: -2 6 | AlignAfterOpenBracket: Align 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveDeclarations: false 9 | AlignEscapedNewlines: Left 10 | AlignOperands: true 11 | AlignTrailingComments: true 12 | AllowAllParametersOfDeclarationOnNextLine: true 13 | AllowShortBlocksOnASingleLine: false 14 | AllowShortCaseLabelsOnASingleLine: false 15 | AllowShortFunctionsOnASingleLine: All 16 | AllowShortIfStatementsOnASingleLine: true 17 | AllowShortLoopsOnASingleLine: true 18 | AlwaysBreakAfterDefinitionReturnType: None 19 | AlwaysBreakAfterReturnType: None 20 | AlwaysBreakBeforeMultilineStrings: true 21 | AlwaysBreakTemplateDeclarations: true 22 | BinPackArguments: true 23 | BinPackParameters: true 24 | BraceWrapping: 25 | AfterClass: false 26 | AfterControlStatement: false 27 | AfterEnum: false 28 | AfterFunction: false 29 | AfterNamespace: false 30 | AfterObjCDeclaration: false 31 | AfterStruct: false 32 | AfterUnion: false 33 | AfterExternBlock: false 34 | BeforeCatch: false 35 | BeforeElse: false 36 | IndentBraces: false 37 | SplitEmptyFunction: true 38 | SplitEmptyRecord: true 39 | SplitEmptyNamespace: true 40 | BreakBeforeBinaryOperators: None 41 | BreakBeforeBraces: Attach 42 | BreakBeforeInheritanceComma: false 43 | BreakBeforeTernaryOperators: true 44 | BreakConstructorInitializersBeforeComma: false 45 | BreakConstructorInitializers: BeforeColon 46 | BreakAfterJavaFieldAnnotations: false 47 | BreakStringLiterals: true 48 | ColumnLimit: 120 49 | CommentPragmas: '^ IWYU pragma:' 50 | CompactNamespaces: false 51 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 52 | ConstructorInitializerIndentWidth: 4 53 | ContinuationIndentWidth: 4 54 | Cpp11BracedListStyle: true 55 | DerivePointerAlignment: true 56 | DisableFormat: false 57 | ExperimentalAutoDetectBinPacking: false 58 | FixNamespaceComments: true 59 | ForEachMacros: 60 | - foreach 61 | - Q_FOREACH 62 | - BOOST_FOREACH 63 | IncludeBlocks: Preserve 64 | IncludeCategories: 65 | - Regex: '^' 66 | Priority: 2 67 | - Regex: '^<.*\.h>' 68 | Priority: 1 69 | - Regex: '^<.*' 70 | Priority: 2 71 | - Regex: '.*' 72 | Priority: 3 73 | IncludeIsMainRegex: '([-_](test|unittest))?$' 74 | IndentCaseLabels: true 75 | IndentPPDirectives: None 76 | IndentWidth: 4 77 | IndentWrappedFunctionNames: false 78 | JavaScriptQuotes: Leave 79 | JavaScriptWrapImports: true 80 | KeepEmptyLinesAtTheStartOfBlocks: false 81 | MacroBlockBegin: '' 82 | MacroBlockEnd: '' 83 | MaxEmptyLinesToKeep: 1 84 | NamespaceIndentation: None 85 | ObjCBlockIndentWidth: 2 86 | ObjCSpaceAfterProperty: false 87 | ObjCSpaceBeforeProtocolList: false 88 | PenaltyBreakAssignment: 50 89 | PenaltyBreakBeforeFirstCallParameter: 1 90 | PenaltyBreakComment: 300 91 | PenaltyBreakFirstLessLess: 120 92 | PenaltyBreakString: 1000 93 | PenaltyExcessCharacter: 1000000 94 | PenaltyReturnTypeOnItsOwnLine: 200 95 | PointerAlignment: Left 96 | ReflowComments: true 97 | SortIncludes: true 98 | SortUsingDeclarations: true 99 | SpaceAfterCStyleCast: false 100 | SpaceAfterTemplateKeyword: true 101 | SpaceBeforeAssignmentOperators: true 102 | SpaceBeforeParens: ControlStatements 103 | SpaceInEmptyParentheses: false 104 | SpacesBeforeTrailingComments: 2 105 | SpacesInAngles: false 106 | SpacesInContainerLiterals: true 107 | SpacesInCStyleCastParentheses: false 108 | SpacesInParentheses: false 109 | SpacesInSquareBrackets: false 110 | Standard: Auto 111 | TabWidth: 8 112 | UseTab: Never -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/BluezDevice.cpp: -------------------------------------------------------------------------------- 1 | #include "BluezDevice.h" 2 | 3 | #include "simpledbus/base/Logger.h" 4 | 5 | #include 6 | 7 | BluezDevice::BluezDevice(SimpleDBus::Connection* conn, std::string path, SimpleDBus::Holder managed_interfaces) 8 | : _conn(conn), _path(path), Device1{conn, path}, Properties{conn, "org.bluez", path} { 9 | Properties::PropertiesChanged = [&](std::string interface, SimpleDBus::Holder changed_properties, 10 | SimpleDBus::Holder invalidated_properties) { 11 | if (interface == "org.bluez.Device1") { 12 | Device1::set_options(changed_properties, invalidated_properties); 13 | } else { 14 | } 15 | }; 16 | 17 | auto managed_interface = managed_interfaces.get_dict(); 18 | for (auto& [iface, options] : managed_interface) { 19 | add_interface(iface, options); 20 | } 21 | } 22 | 23 | BluezDevice::~BluezDevice() {} 24 | 25 | bool BluezDevice::process_received_signal(SimpleDBus::Message& message) { 26 | if (message.get_path() == _path) { 27 | if (Properties::process_received_signal(message)) return true; 28 | } else { 29 | for (auto& [gatt_service_path, gatt_service] : gatt_services) { 30 | if (gatt_service->process_received_signal(message)) return true; 31 | } 32 | } 33 | return false; 34 | } 35 | 36 | void BluezDevice::add_interface(std::string interface_name, SimpleDBus::Holder options) { 37 | if (interface_name == "org.bluez.Device1") { 38 | Device1::set_options(options); 39 | } else { 40 | } 41 | } 42 | 43 | bool BluezDevice::add_path(std::string path, SimpleDBus::Holder options) { 44 | int path_elements = std::count(path.begin(), path.end(), '/'); 45 | if (path.rfind(_path, 0) == 0) { 46 | if (path_elements == 5) { 47 | if (path.find("service") != std::string::npos) { 48 | // We're adding a service 49 | gatt_services.emplace(std::make_pair(path, new BluezGattService(_conn, path, options))); 50 | } 51 | } else { 52 | // Corresponds to a device component 53 | for (auto& [gatt_service_path, gatt_service] : gatt_services) { 54 | if (gatt_service->add_path(path, options)) return true; 55 | } 56 | } 57 | return true; 58 | } 59 | return false; 60 | } 61 | 62 | bool BluezDevice::remove_path(std::string path, SimpleDBus::Holder options) { 63 | int path_elements = std::count(path.begin(), path.end(), '/'); 64 | if (path.rfind(_path, 0) == 0) { 65 | if (path_elements == 5) { 66 | gatt_services.erase(path); 67 | return true; 68 | } else { 69 | // Propagate the paths downwards until someone claims it. 70 | for (auto& [gatt_service_path, gatt_service] : gatt_services) { 71 | if (gatt_service->remove_path(path, options)) return true; 72 | } 73 | } 74 | } 75 | return false; 76 | } 77 | 78 | std::shared_ptr BluezDevice::get_service(std::string service_uuid) { 79 | std::shared_ptr return_value = nullptr; 80 | 81 | for (auto& [gatt_service_path, gatt_service] : gatt_services) { 82 | if (gatt_service->get_uuid() == service_uuid) { 83 | return_value = gatt_service; 84 | break; 85 | } 86 | } 87 | 88 | return return_value; 89 | } 90 | 91 | std::shared_ptr BluezDevice::get_characteristic(std::string service_uuid, 92 | std::string char_uuid) { 93 | std::shared_ptr return_value = nullptr; 94 | std::shared_ptr service = get_service(service_uuid); 95 | 96 | if (service != nullptr) { 97 | return_value = service->get_characteristic(char_uuid); 98 | } 99 | return return_value; 100 | } -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/interfaces/GattCharacteristic1.cpp: -------------------------------------------------------------------------------- 1 | #include "GattCharacteristic1.h" 2 | 3 | #include "simpledbus/base/Logger.h" 4 | 5 | const std::string GattCharacteristic1::_interface_name = "org.bluez.GattCharacteristic1"; 6 | 7 | GattCharacteristic1::GattCharacteristic1(SimpleDBus::Connection* conn, std::string path) 8 | : _conn(conn), _path(path), _notifying(false) {} 9 | 10 | GattCharacteristic1::~GattCharacteristic1() {} 11 | 12 | void GattCharacteristic1::add_option(std::string option_name, SimpleDBus::Holder value) { 13 | if (option_name == "UUID") { 14 | _uuid = value.get_string(); 15 | } else if (option_name == "Value") { 16 | _value.clear(); 17 | auto value_array = value.get_array(); 18 | LOG_F(VERBOSE_2, "%s -> Value Length: %d", _path.c_str(), value_array.size()); 19 | for (auto& elem : value_array) { 20 | _value.push_back(elem.get_byte()); 21 | } 22 | if (ValueChanged) { 23 | LOG_F(VERBOSE_2, "%s -> ValueChanged\n%s", _path.c_str(), value.represent().c_str()); 24 | ValueChanged(_value); 25 | } 26 | } else if (option_name == "Notifying") { 27 | _notifying = value.get_boolean(); 28 | } 29 | } 30 | 31 | void GattCharacteristic1::remove_option(std::string option_name) {} 32 | 33 | std::string GattCharacteristic1::get_uuid() { return _uuid; } 34 | 35 | std::vector GattCharacteristic1::get_value() { return _value; } 36 | 37 | void GattCharacteristic1::StartNotify() { 38 | if (!_notifying) { 39 | LOG_F(DEBUG, "%s -> StartNotify", _path.c_str()); 40 | auto msg = SimpleDBus::Message::create_method_call("org.bluez", _path, _interface_name, "StartNotify"); 41 | _conn->send_with_reply_and_block(msg); 42 | } else { 43 | LOG_F(WARN, "%s is already notifying...", _path.c_str()); 44 | } 45 | } 46 | void GattCharacteristic1::StopNotify() { 47 | if (_notifying) { 48 | LOG_F(DEBUG, "%s -> StopNotify", _path.c_str()); 49 | auto msg = SimpleDBus::Message::create_method_call("org.bluez", _path, _interface_name, "StopNotify"); 50 | _conn->send_with_reply_and_block(msg); 51 | } else { 52 | LOG_F(WARN, "%s was not notifying...", _path.c_str()); 53 | } 54 | } 55 | 56 | void GattCharacteristic1::WriteValue(SimpleDBus::Holder value, SimpleDBus::Holder options) { 57 | LOG_F(DEBUG, "%s -> WriteValue", _path.c_str()); 58 | auto msg = SimpleDBus::Message::create_method_call("org.bluez", _path, _interface_name, "WriteValue"); 59 | msg.append_argument(value, "ay"); 60 | msg.append_argument(options, "a{sv}"); 61 | _conn->send_with_reply_and_block(msg); 62 | } 63 | 64 | SimpleDBus::Holder GattCharacteristic1::ReadValue(SimpleDBus::Holder options) { 65 | LOG_F(DEBUG, "%s -> ReadValue", _path.c_str()); 66 | auto msg = SimpleDBus::Message::create_method_call("org.bluez", _path, _interface_name, "ReadValue"); 67 | msg.append_argument(options, "a{sv}"); 68 | SimpleDBus::Message reply_msg = _conn->send_with_reply_and_block(msg); 69 | SimpleDBus::Holder value = reply_msg.extract(); 70 | return value; 71 | } 72 | 73 | void GattCharacteristic1::write_request(const uint8_t* data, uint16_t length) { 74 | SimpleDBus::Holder value = SimpleDBus::Holder::create_array(); 75 | for (uint16_t i = 0; i < length; i++) { 76 | value.array_append(SimpleDBus::Holder::create_byte(data[i])); 77 | } 78 | SimpleDBus::Holder options = SimpleDBus::Holder::create_dict(); 79 | options.dict_append("type", SimpleDBus::Holder::create_string("request")); 80 | WriteValue(value, options); 81 | } 82 | 83 | void GattCharacteristic1::write_command(const uint8_t* data, uint16_t length) { 84 | SimpleDBus::Holder value = SimpleDBus::Holder::create_array(); 85 | for (uint16_t i = 0; i < length; i++) { 86 | value.array_append(SimpleDBus::Holder::create_byte(data[i])); 87 | } 88 | SimpleDBus::Holder options = SimpleDBus::Holder::create_dict(); 89 | options.dict_append("type", SimpleDBus::Holder::create_string("command")); 90 | WriteValue(value, options); 91 | } 92 | -------------------------------------------------------------------------------- /src-ble-test/main.cpp: -------------------------------------------------------------------------------- 1 | #include "NativeBleController.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define SCAN_TIMEOUT_MS 5000 9 | 10 | #define NORDIC_UART_SERVICE_UUID "6e400001-b5a3-f393-e0a9-e50e24dcca9e" 11 | #define NORDIC_UART_CHAR_RX "6e400002-b5a3-f393-e0a9-e50e24dcca9e" 12 | #define NORDIC_UART_CHAR_TX "6e400003-b5a3-f393-e0a9-e50e24dcca9e" 13 | 14 | NativeBLE::NativeBleController ble; 15 | 16 | void signal_handler(int signal) { 17 | if (signal == SIGINT) { 18 | std::cout << std::endl << "User quit program." << std::endl; 19 | } 20 | 21 | ble.disconnect(); 22 | ble.dispose(); 23 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 24 | exit(signal); 25 | } 26 | 27 | /** 28 | * This program will scan for devices and ask you to choose a device to connect to. 29 | * After connecting, the output of the device will be printed to the terminal, and 30 | * messages can be sent to the device from the terminal. 31 | * 32 | * Known issues: 33 | * If while typing, a message is received from the device, your message will be erased 34 | * from the screen but not from memory, so if you type 'enter' after receiving a message, 35 | * whatever you were in the process of typing will get sent, even if you can't see it. 36 | */ 37 | int main() { 38 | //Setup signal to disconnect and dispose ble when program is quit. 39 | std::signal(SIGINT, signal_handler); 40 | 41 | NativeBLE::CallbackHolder ble_events; 42 | std::vector devices; 43 | 44 | // Setup callback functions 45 | ble_events.callback_on_device_disconnected = [](std::string msg) { 46 | std::cout << "Disconnected: " << msg << std::endl; 47 | return; 48 | }; 49 | ble_events.callback_on_device_connected = []() { 50 | std::cout << "Connected" << std::endl; 51 | }; 52 | ble_events.callback_on_scan_found = [&](NativeBLE::DeviceDescriptor device) { 53 | static int i = 1; 54 | std::cout << "\r" << i++ << " devices found."; 55 | fflush(stdout); 56 | devices.push_back(device); 57 | }; 58 | ble_events.callback_on_scan_start = []() { 59 | std::cout << "Scanning for " << SCAN_TIMEOUT_MS << " ms..." << std::endl; 60 | }; 61 | ble_events.callback_on_scan_stop = []() { 62 | std::cout << std::endl << "Scan complete." << std::endl; 63 | }; 64 | 65 | ble.setup(ble_events); 66 | ble.scan_timeout(SCAN_TIMEOUT_MS); 67 | 68 | std::cout << devices.size() << " devices found:" << std::endl; 69 | 70 | for (int i = 0; i < devices.size(); i++) { 71 | std::cout << " " << i << ": " << devices[i].name << " (" << devices[i].address << ")" << std::endl; 72 | } 73 | 74 | std::cout << "Type index of device to connect to: "; 75 | 76 | int device; 77 | std::cin >> device; 78 | 79 | if (device >= devices.size()) { 80 | std::cout << "Device index out of range." << std::endl; 81 | exit(-1); 82 | } 83 | 84 | ble.connect(devices[device].address); 85 | 86 | // Setup notify for when data is received 87 | ble.notify(NORDIC_UART_SERVICE_UUID, NORDIC_UART_CHAR_TX, [&](const uint8_t* data, uint32_t length) { 88 | std::cout << "\r<" << devices[device].name << "> " << "(" << length << ") "; 89 | for (int i = 0; i < length; i++) { std::cout << data[i]; } 90 | std::cout << std::endl << " > "; 91 | fflush(stdout); 92 | }); 93 | 94 | std::this_thread::sleep_for(std::chrono::milliseconds(5000)); 95 | ble.read(NORDIC_UART_SERVICE_UUID, NORDIC_UART_CHAR_TX, [&](const uint8_t* data, uint32_t length) { 96 | std::cout << "\r<" << devices[device].name << "> " << "(" << length << ") "; 97 | for (int i = 0; i < length; i++) { std::cout << data[i]; } 98 | std::cout << std::endl << " > "; 99 | fflush(stdout); 100 | }); 101 | 102 | // * Endless loop to send/receive BLE UART 103 | std::string message; 104 | while (true) { 105 | getline(std::cin, message); 106 | ble.write_request(NORDIC_UART_SERVICE_UUID, NORDIC_UART_CHAR_RX, message); 107 | std::cout << " > "; 108 | } 109 | 110 | ble.disconnect(); 111 | ble.dispose(); 112 | } 113 | -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/BluezService.cpp: -------------------------------------------------------------------------------- 1 | #include "BluezService.h" 2 | 3 | #include 4 | 5 | BluezService::BluezService() : conn(DBUS_BUS_SYSTEM), object_manager(&conn, "org.bluez", "/") { 6 | object_manager.InterfacesAdded = [&](std::string path, SimpleDBus::Holder options) { add_path(path, options); }; 7 | object_manager.InterfacesRemoved = [&](std::string path, SimpleDBus::Holder options) { 8 | remove_path(path, options); 9 | }; 10 | } 11 | 12 | BluezService::~BluezService() { conn.remove_match("type='signal',sender='org.bluez'"); } 13 | 14 | void BluezService::init() { 15 | conn.init(); 16 | object_manager.GetManagedObjects(true); // Feed the objects via callback. 17 | 18 | conn.add_match("type='signal',sender='org.bluez'"); 19 | } 20 | 21 | void BluezService::run_async() { 22 | conn.read_write(); 23 | SimpleDBus::Message message = conn.pop_message(); 24 | while (message.is_valid()) { 25 | switch (message.get_type()) { 26 | case SimpleDBus::MessageType::SIGNAL: 27 | process_received_signal(message); 28 | break; 29 | default: 30 | break; 31 | } 32 | message = conn.pop_message(); 33 | } 34 | } 35 | 36 | void BluezService::process_received_signal(SimpleDBus::Message& message) { 37 | std::string path = message.get_path(); 38 | 39 | if (path == "/org/freedesktop/DBus") return; 40 | 41 | if (object_manager.process_received_signal(message)) return; 42 | 43 | for (auto& [path, adapter] : adapters) { 44 | if (adapter->process_received_signal(message)) return; 45 | } 46 | } 47 | 48 | void BluezService::add_path(std::string path, SimpleDBus::Holder options) { 49 | int path_elements = std::count(path.begin(), path.end(), '/'); 50 | 51 | switch (path_elements) { 52 | case 2: 53 | agent.reset(new BluezAgent(path, options)); 54 | break; 55 | case 3: 56 | adapters.emplace(std::make_pair(path, new BluezAdapter(&conn, path, options))); 57 | break; 58 | default: 59 | // Propagate the paths downwards until someone claims it. 60 | for (auto& [adapter_path, adapter] : adapters) { 61 | if (adapter->add_path(path, options)) return; 62 | } 63 | break; 64 | } 65 | } 66 | 67 | void BluezService::remove_path(std::string path, SimpleDBus::Holder options) { 68 | int path_elements = std::count(path.begin(), path.end(), '/'); 69 | 70 | switch (path_elements) { 71 | case 2: 72 | break; 73 | case 3: { 74 | // HACK: Due to the library architecture, the only way to make sure if one 75 | // of the underlying objects has to be deleted is to check if the options 76 | // list contains `org.bluez.Adapter1`, `org.freedesktop.DBus.Introspectable`, 77 | // or `org.freedesktop.DBus.Properties`. 78 | // Eventually this structure will have to be revisited. 79 | std::vector interface_list; 80 | for (auto& option : options.get_array()) { 81 | interface_list.push_back(option.get_string()); 82 | } 83 | bool must_erase = std::any_of(interface_list.begin(), interface_list.end(), [](const std::string& str) { 84 | return str == "org.freedesktop.DBus.Properties" || str == "org.freedesktop.DBus.Introspectable" || 85 | str == "org.bluez.Adapter1"; 86 | }); 87 | if (must_erase) { 88 | adapters.erase(path); 89 | } 90 | break; 91 | } 92 | default: 93 | // Propagate the paths downwards until someone claims it. 94 | for (auto& [adapter_path, adapter] : adapters) { 95 | if (adapter->remove_path(path, options)) return; 96 | } 97 | break; 98 | } 99 | } 100 | 101 | std::shared_ptr BluezService::get_first_adapter() { 102 | std::shared_ptr return_value = nullptr; 103 | if (!adapters.empty()) { 104 | return_value = adapters.begin()->second; 105 | } 106 | 107 | return return_value; 108 | } 109 | 110 | std::shared_ptr BluezService::get_adapter(std::string adapter_name) { 111 | std::shared_ptr return_value = nullptr; 112 | std::string expected_path = "/org/bluez/" + adapter_name; 113 | 114 | for (auto& [adapter_path, adapter] : adapters) { 115 | if (adapter_path == expected_path) { 116 | return_value = adapter; 117 | break; 118 | } 119 | } 120 | return return_value; 121 | } 122 | -------------------------------------------------------------------------------- /src-ble/macos/macable/macable.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #include 5 | #include 6 | #include 7 | 8 | #define CENTRAL_MANAGER_TRIES 10 9 | #define CENTRAL_MANAGER_SLEEP 250 10 | 11 | @interface Macable:NSObject 12 | { 13 | CBCentralManager *centralManager; 14 | dispatch_queue_t dispatchQueue; 15 | } 16 | 17 | @property (readonly, getter=isPeripheralReady) BOOL peripheralReady; 18 | @property (readonly, getter=isCentralManagerPoweredOn) BOOL centralManagerPoweredOn; 19 | @property (strong) CBPeripheral *peripheral; 20 | @property std::function onScanFound; 21 | @property std::function onConnected; 22 | @property std::function onDisconnected; 23 | @property std::function onCharacteristicUpdated; 24 | 25 | /** 26 | * [Macable setup] 27 | * 28 | * Initialize the CoreBluetooth driver 29 | * 30 | */ 31 | -(void) setup; 32 | 33 | /** 34 | * [Macable startScan] 35 | * 36 | * Begin scanning indefinitely for BLE devices 37 | * Each time a peripheral is found onScanFound will be called with the peripheral's information 38 | * 39 | */ 40 | -(void) startScan; 41 | 42 | /** 43 | * [Macable stopScan] 44 | * 45 | * Stop scanning 46 | * 47 | */ 48 | -(void) stopScan; 49 | 50 | /** 51 | * [Macable connect] 52 | * 53 | * Will connect to the peripheral saved in @property peripheral and attaches Macable as delegate 54 | * for peripheral events 55 | * ! Must save to property by calling [Macable setPeripheral:] ! 56 | * onConnected will be called when the peripheral is connected and all services and characteristics 57 | * are populated 58 | * 59 | */ 60 | -(void) connect; 61 | 62 | /** 63 | * [Macable getPeripheralWithUUID:] 64 | * 65 | * Returns a CBPeripheral matching the provided UUID if it is found, nil otherwise 66 | * 67 | * @param uuid: the uuid of the desired peripheral 68 | * @return CBPeripheral object with provided UUID 69 | * 70 | */ 71 | -(CBPeripheral *) getPeripheralWithUUID:(NSUUID *)uuid; 72 | 73 | /** 74 | * [Macable disconnect] 75 | * 76 | * Disconnects from the peripheral saved in @property peripheral and detaches Macable as delegate 77 | * for peripheral events. 78 | * onDisconnected will be called when the peripheral disconnects 79 | * 80 | */ 81 | -(void) disconnect; 82 | 83 | /** 84 | * [Macable readValueOnCharacteristic:service:] 85 | * 86 | * Read the value on the provided characteristic and service. onCharacteristicUpdated will 87 | * be called with the value if read is successful 88 | * 89 | * @param characteristic_uuid: the uuid of the characteristic 90 | * @param service_uuid: the uuid of the service 91 | * 92 | */ 93 | -(void) readValueOnCharacteristic:(CBUUID*)characteristic_uuid service:(CBUUID*)service_uuid; 94 | 95 | /** 96 | * [Macable subscribeToNotificationsOnCharacteristic:service:] 97 | * 98 | * Subscribe to notifications on the provided characteristic and service. onCharacteristicUpdated will 99 | * be called with the value whenever the characteristic is updated 100 | * 101 | * @param characteristic_uuid: the uuid of the characteristic 102 | * @param service_uuid: the uuid of the service 103 | * 104 | */ 105 | -(void) subscribeToNotificationsOnCharacteristic:(CBUUID *)characteristic_uuid service:(CBUUID *)service_uuid; 106 | 107 | /** 108 | * [Macable subscribeToIndicationsOnCharacteristic:service:] 109 | * 110 | * Subscribe to indications on the provided characteristic and service. onCharacteristicUpdated will 111 | * be called with the value whenever the characteristic is updated 112 | * 113 | * @param characteristic_uuid: the uuid of the characteristic 114 | * @param service_uuid: the uuid of the service 115 | * 116 | */ 117 | -(void) subscribeToIndicationsOnCharacteristic:(CBUUID *)characteristic_uuid service:(CBUUID *)service_uuid; 118 | 119 | /** 120 | * [Macable writeData:toCharacteristic:service:] 121 | * 122 | * Writes data to provided characteristic and service using a WriteWithResponse command 123 | * 124 | * @param data: data to be written 125 | * @param characteristic_uuid: the uuid of the characteristic 126 | * @param service_uuid: the uuid of the service 127 | * 128 | */ 129 | -(void) writeData:(NSData*)data toCharacteristic:(CBUUID*)characteristic_uuid service:(CBUUID*)service_uuid; 130 | 131 | /** 132 | * [Macable writeNoResponseData:toCharacteristic:service] 133 | * 134 | * Writes data to provided characteristic using a WriteWithoutResponse command 135 | * 136 | * @param data: data to be written 137 | * @param characteristic_uuid: the uuid of the characteristic 138 | * @param service_uuid: the uuid of the service 139 | * 140 | */ 141 | -(void) writeNoResponseData:(NSData*)data toCharacteristic:(CBUUID*)characteristic_uuid service:(CBUUID*)service_uuid; 142 | 143 | @end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **IMPORTANT NOTICE** 2 | 3 | As of September 2021, NativeBLE will be deprecated and replaced by [SimpleBLE](https://github.com/OpenBluetoothToolbox/SimpleBLE). This is a new project that I've started 4 | that takes all the lessons learned from NativeBLE and makes it easier to use with far more functionality and stability. 5 | 6 | # NativeBLE 7 | 8 | The purpose of NativeBLE is to add full-fledged BLE capability without the need to worry about specific platform compatibility issues. The API is designed to work on Windows, Linux, and macOS and allows you to write the same code for all three platforms. 9 | 10 | The API is very simple and easy to use, giving developers the ability to interact with any BLE device. This library will allow you to: 11 | * Manage connections to BLE devices 12 | * Read and write to a characteristic 13 | * Subscribe to notifications or indications for characteristics 14 | 15 | The provided tester application shows how the same code can be compiled and run on any platform. 16 | 17 | For more information, read our [TUTORIAL](TUTORIAL.md). 18 | 19 | ## Code Structure 20 | The code is organized as follows: 21 | * `src-ble` 22 | * An abstracted API that is platform-less (`NativeBleController`) 23 | * Internal code for each platform (`NativeBleInternal`) 24 | * `src-ble-c-api` 25 | * Wrapper in standard C for the API 26 | * `src-ble-test` 27 | * A tester application that uses the compiled `src-ble` library 28 | 29 | ## Build Instructions 30 | We have specific scripts to compile the library on each platform. By default, the C wrapper and the tester application will also be built. The flags to enable each of these can be found in `CMakeLists.txt`. 31 | 32 | All binaries will be placed in the `bin` folder. 33 | 34 | ### Windows 35 | * Install Visual Studio 2019, selecting the C++ toolchain. 36 | * Install CMake from https://cmake.org/ 37 | * Run `.\toolchains\windows\windows_compile.bat` from Powershell. 38 | * Use `-c` or `-clean` to perform a full rebuild of the project. 39 | * Use `-a=` or `-arch=` to build the libraries for a specific architecture. (Supported values are `x86` and `x64`.) 40 | 41 | ### Linux 42 | * Run `./toolchains/linux/linux_install.sh` to install the OS dependencies. This should be required only once. 43 | * Run `./toolchains/linux/linux_compile.sh` to build the project. 44 | * Use `-c` or `-clean` to perform a full rebuild of the project. 45 | 46 | ### macOS 47 | * Install Homebrew from https://brew.sh/ 48 | * Run `brew install cmake` to install CMake. 49 | * Run `./toolchains/macos/macos_compile.sh` to build the project. 50 | * Use `-c` or `-clean` to perform a full rebuild of the project. 51 | The library will be compiled into a `.dylib` and saved into `bin/darwin`. 52 | 53 | ## License 54 | All components within this project that have not been bundled from external creators, are licensed under the terms of the [MIT Licence](LICENCE.md). 55 | 56 | ## Important things about the macOS platform 57 | Apple's Bluetooth API for its platforms macOS, iOS, and iPadOS is CoreBluetooth. NativeBLE uses CoreBluetooth for the macOS platform, and in turn, this library can be used for the other Apple platforms as well, without any major changes. 58 | 59 | ### Maximum Message Size 60 | In BLE, there is a term known as MTU (Maximum Transmission Unit). This defines the largest size a message can be that is transferred from the Peripheral to the Central. On most platforms, this number is set to 244 bytes, however this number differs on various Apple devices. Below is a list of some Apple devices and their MTU. 61 | 62 | - MacBook Pro 2016 and older without Touchbar: 104 bytes 63 | - MacBook Pro 2016 and newer with Touchbar: 244 bytes 64 | - iOS devices: 185 bytes 65 | 66 | It is important to respect these numbers when developing an application with Apple devices, because sending a message larger than the MTU size will result in those bytes being clipped off without any indication. Unfortunately, CoreBluetooth does not expose any methods for identifying what the MTU of the device it is running on is, so this information must be passed to it from the peripheral. 67 | 68 | CoreBluetooth will always negotiate the highest possible MTU at the beginning of a connection. For example, if a peripheral supports an MTU of 244 bytes and your MacBook supports an MTU of 104 bytes, CoreBluetooth on your Mac will choose 104 because it is the highest possible MTU supported by both parties. 69 | 70 | ### MAC Addresses and UUIDs 71 | In an effort to protect privacy, CoreBluetooth does not expose the MAC address of a device to a user. Instead, it randomizes the MAC address to a UUID (Universal Unique Identifier) that is exposed to the user. Because of this, you cannot manually input a Bluetooth MAC address into the `connect` method of `NativeBleController` on an Apple device. Instead, you will need to scan for devices and find the UUID of the desired device to connect to. This UUID will be passed into the `connect` method. 72 | 73 | This will only be a problem if your application has hard-coded a Bluetooth MAC address to connect to. If you are scanning for devices, on Apple devices, the returned devices from the scan will have their address field filled with the UUID of the device and not the Bluetooth MAC address. This UUID can be used for the `connect` method. 74 | -------------------------------------------------------------------------------- /src-ble/linux/bluezdbus/BluezAdapter.cpp: -------------------------------------------------------------------------------- 1 | #include "BluezAdapter.h" 2 | 3 | #include "simpledbus/base/Logger.h" 4 | 5 | #include 6 | 7 | BluezAdapter::BluezAdapter(SimpleDBus::Connection* conn, std::string path, SimpleDBus::Holder managed_interfaces) 8 | : _conn(conn), _path(path), Adapter1{conn, path}, Properties{conn, "org.bluez", path} { 9 | // std::cout << "Creating BluezAdapter" << std::endl; 10 | 11 | Properties::PropertiesChanged = [&](std::string interface, SimpleDBus::Holder changed_properties, 12 | SimpleDBus::Holder invalidated_properties) { 13 | if (interface == "org.bluez.Adapter1") { 14 | Adapter1::set_options(changed_properties, invalidated_properties); 15 | } else { 16 | } 17 | }; 18 | 19 | auto managed_interface = managed_interfaces.get_dict(); 20 | for (auto& [iface, options] : managed_interface) { 21 | add_interface(iface, options); 22 | } 23 | } 24 | 25 | BluezAdapter::~BluezAdapter() { 26 | // std::cout << "Destroying BluezAdapter" << std::endl; 27 | } 28 | 29 | bool BluezAdapter::process_received_signal(SimpleDBus::Message& message) { 30 | if (message.get_path() == _path) { 31 | if (Properties::process_received_signal(message)) return true; 32 | } else { 33 | for (auto& [device_path, device] : devices) { 34 | if (device->process_received_signal(message)) return true; 35 | } 36 | } 37 | return false; 38 | } 39 | 40 | void BluezAdapter::add_interface(std::string interface_name, SimpleDBus::Holder options) { 41 | // std::cout << interface_name << std::endl; 42 | 43 | if (interface_name == "org.bluez.Adapter1") { 44 | Adapter1::set_options(options); 45 | } else { 46 | } 47 | } 48 | 49 | bool BluezAdapter::add_path(std::string path, SimpleDBus::Holder options) { 50 | int path_elements = std::count(path.begin(), path.end(), '/'); 51 | if (path.rfind(_path, 0) == 0) { 52 | if (path_elements == 4) { 53 | // Corresponds to a device 54 | devices.emplace(std::make_pair(path, new BluezDevice(_conn, path, options))); 55 | if (OnDeviceFound) { 56 | OnDeviceFound(devices[path]->get_address(), devices[path]->get_name()); 57 | } 58 | return true; 59 | } else { 60 | // Corresponds to a device component 61 | for (auto& [device_path, device] : devices) { 62 | if (device->add_path(path, options)) return true; 63 | } 64 | } 65 | } 66 | return false; 67 | } 68 | 69 | bool BluezAdapter::remove_path(std::string path, SimpleDBus::Holder options) { 70 | int path_elements = std::count(path.begin(), path.end(), '/'); 71 | if (path.rfind(_path, 0) == 0) { 72 | if (path_elements == 4) { 73 | // HACK: Due to the library architecture, the only way to make sure if one 74 | // of the underlying objects has to be deleted is to check if the options 75 | // list contains `org.bluez.Device1`, `org.freedesktop.DBus.Introspectable`, 76 | // or `org.freedesktop.DBus.Properties`. 77 | // A BluezDevice might also extend from `org.bluez.Battery1`, for example. 78 | // Eventually this structure will have to be revisited. 79 | std::vector interface_list; 80 | for (auto& option : options.get_array()) { 81 | interface_list.push_back(option.get_string()); 82 | } 83 | bool must_erase = std::any_of(interface_list.begin(), interface_list.end(), [](const std::string& str) { 84 | return str == "org.freedesktop.DBus.Properties" || str == "org.freedesktop.DBus.Introspectable" || 85 | str == "org.bluez.Device1"; 86 | }); 87 | if (must_erase) { 88 | devices.erase(path); 89 | } 90 | return true; 91 | } else { 92 | // Propagate the paths downwards until someone claims it. 93 | for (auto& [device_path, device] : devices) { 94 | if (device->remove_path(path, options)) return true; 95 | } 96 | } 97 | } 98 | return false; 99 | } 100 | 101 | void BluezAdapter::discovery_filter_transport_set(std::string value) { 102 | /* 103 | Possible values: 104 | "auto" - interleaved scan 105 | "bredr" - BR/EDR inquiry 106 | "le" - LE scan only 107 | */ 108 | // TODO: Validate input 109 | 110 | SimpleDBus::Holder argument = SimpleDBus::Holder::create_dict(); 111 | argument.dict_append("Transport", SimpleDBus::Holder::create_string(value.c_str())); 112 | SetDiscoveryFilter(argument); 113 | } 114 | 115 | std::shared_ptr BluezAdapter::get_device(std::string mac_address) { 116 | // TODO: Apply proper mac address formatting 117 | // TODO: How do I know which adapter I should check? 118 | std::shared_ptr return_value = nullptr; 119 | 120 | for (auto& [path, device] : devices) { 121 | if (device->get_address() == mac_address) { 122 | return_value = device; 123 | } 124 | } 125 | return return_value; 126 | } -------------------------------------------------------------------------------- /src-ble-c-api/ble_c_api.cpp: -------------------------------------------------------------------------------- 1 | #include "ble_c_api.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "NativeBleController.h" 7 | 8 | void* ble_construct() { 9 | void* ble_ptr = new NativeBLE::NativeBleController(); 10 | return ble_ptr; 11 | } 12 | 13 | void ble_destruct(void* ble_ptr) { 14 | NativeBLE::NativeBleController* ble = (NativeBLE::NativeBleController*)ble_ptr; 15 | delete ble; 16 | } 17 | 18 | void ble_setup(void* ble_ptr, void (*callback_on_scan_start)(), void (*callback_on_scan_stop)(), 19 | void (*callback_on_scan_found)(const char*, const char*), void (*callback_on_device_connected)(), 20 | void (*callback_on_device_disconnected)(const char*)) { 21 | NativeBLE::NativeBleController* ble = (NativeBLE::NativeBleController*)ble_ptr; 22 | NativeBLE::CallbackHolder callback_holder; 23 | 24 | if (callback_on_scan_start != nullptr) { 25 | callback_holder.callback_on_scan_start = [=]() { callback_on_scan_start(); }; 26 | } 27 | 28 | if (callback_on_scan_stop != nullptr) { 29 | callback_holder.callback_on_scan_stop = [=]() { callback_on_scan_stop(); }; 30 | } 31 | 32 | if (callback_on_scan_found != nullptr) { 33 | callback_holder.callback_on_scan_found = [=](NativeBLE::DeviceDescriptor descriptor) { 34 | callback_on_scan_found(descriptor.name.c_str(), descriptor.address.c_str()); 35 | }; 36 | } 37 | 38 | if (callback_on_device_connected != nullptr) { 39 | callback_holder.callback_on_device_connected = [=]() { callback_on_device_connected(); }; 40 | } 41 | 42 | if (callback_on_device_disconnected != nullptr) { 43 | callback_holder.callback_on_device_disconnected = [=](std::string reason) { 44 | callback_on_device_disconnected(reason.c_str()); 45 | }; 46 | } 47 | 48 | ble->setup(callback_holder); 49 | } 50 | 51 | void ble_scan_start(void* ble_ptr) { 52 | NativeBLE::NativeBleController* ble = (NativeBLE::NativeBleController*)ble_ptr; 53 | ble->scan_start(); 54 | } 55 | 56 | void ble_scan_stop(void* ble_ptr) { 57 | NativeBLE::NativeBleController* ble = (NativeBLE::NativeBleController*)ble_ptr; 58 | ble->scan_stop(); 59 | } 60 | 61 | bool ble_scan_is_active(void* ble_ptr) { 62 | NativeBLE::NativeBleController* ble = (NativeBLE::NativeBleController*)ble_ptr; 63 | return ble->scan_is_active(); 64 | } 65 | 66 | void ble_scan_timeout(void* ble_ptr, int32_t timeout_ms) { 67 | NativeBLE::NativeBleController* ble = (NativeBLE::NativeBleController*)ble_ptr; 68 | ble->scan_timeout(timeout_ms); 69 | } 70 | 71 | void ble_connect(void* ble_ptr, const char* address_ptr) { 72 | NativeBLE::NativeBleController* ble = (NativeBLE::NativeBleController*)ble_ptr; 73 | std::string address(address_ptr); 74 | ble->connect(address); 75 | } 76 | 77 | void ble_write_request(void* ble_ptr, const char* service_ptr, const char* characteristic_ptr, const char* data_ptr, 78 | uint32_t data_len) { 79 | NativeBLE::NativeBleController* ble = (NativeBLE::NativeBleController*)ble_ptr; 80 | std::string service(service_ptr); 81 | std::string characteristic(characteristic_ptr); 82 | std::string data(data_ptr, data_len); 83 | 84 | ble->write_request(service, characteristic, data); 85 | } 86 | 87 | void ble_write_command(void* ble_ptr, const char* service_ptr, const char* characteristic_ptr, const char* data_ptr, 88 | uint32_t data_len) { 89 | NativeBLE::NativeBleController* ble = (NativeBLE::NativeBleController*)ble_ptr; 90 | std::string service(service_ptr); 91 | std::string characteristic(characteristic_ptr); 92 | std::string data(data_ptr, data_len); 93 | 94 | ble->write_command(service, characteristic, data); 95 | } 96 | 97 | void ble_read(void* ble_ptr, const char* service_ptr, const char* characteristic_ptr, 98 | void (*callback)(const uint8_t* data, uint32_t length)) { 99 | NativeBLE::NativeBleController* ble = (NativeBLE::NativeBleController*)ble_ptr; 100 | std::string service(service_ptr); 101 | std::string characteristic(characteristic_ptr); 102 | ble->read(service, characteristic, [=](const uint8_t* data, uint32_t length) { callback(data, length); }); 103 | } 104 | 105 | void ble_notify(void* ble_ptr, const char* service_ptr, const char* characteristic_ptr, 106 | void (*callback)(const uint8_t* data, uint32_t length)) { 107 | NativeBLE::NativeBleController* ble = (NativeBLE::NativeBleController*)ble_ptr; 108 | std::string service(service_ptr); 109 | std::string characteristic(characteristic_ptr); 110 | ble->notify(service, characteristic, [=](const uint8_t* data, uint32_t length) { callback(data, length); }); 111 | } 112 | 113 | void ble_indicate(void* ble_ptr, const char* service_ptr, const char* characteristic_ptr, 114 | void (*callback)(const uint8_t* data, uint32_t length)) { 115 | NativeBLE::NativeBleController* ble = (NativeBLE::NativeBleController*)ble_ptr; 116 | std::string service(service_ptr); 117 | std::string characteristic(characteristic_ptr); 118 | ble->indicate(service, characteristic, [=](const uint8_t* data, uint32_t length) { callback(data, length); }); 119 | } 120 | 121 | void ble_disconnect(void* ble_ptr) { 122 | NativeBLE::NativeBleController* ble = (NativeBLE::NativeBleController*)ble_ptr; 123 | ble->disconnect(); 124 | } 125 | void ble_dispose(void* ble_ptr) { 126 | NativeBLE::NativeBleController* ble = (NativeBLE::NativeBleController*)ble_ptr; 127 | ble->dispose(); 128 | } 129 | -------------------------------------------------------------------------------- /src-ble/NativeBleController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "NativeBleControllerTypes.h" 4 | 5 | using namespace std::string_literals; // enables s-suffix for std::string literals 6 | 7 | namespace NativeBLE { 8 | 9 | class NativeBleInternal; 10 | 11 | class NativeBleController { 12 | // TODO Add getServices, getCharacteristicsForService. 13 | // TODO Add a callback to notify the user of disconnections. 14 | 15 | private: 16 | // * Pointer to object specific to platform (Windows, macOS, Linux) 17 | NativeBleInternal *internal; 18 | 19 | public: 20 | /** 21 | * NativeBleController::NativeBleController 22 | * 23 | * Constructor to initialize the internal object 24 | * 25 | */ 26 | NativeBleController(); 27 | 28 | /** 29 | * NativeBleController::~NativeBleController 30 | * 31 | * Destructor 32 | * 33 | */ 34 | ~NativeBleController(); 35 | 36 | /** 37 | * NativeBleController::setup 38 | * 39 | * Sets up the callback holder so that all functions have pointers to callbacks 40 | * 41 | * @param callback_holder: object containing a set of callback functions 42 | */ 43 | void setup(CallbackHolder callback_holder); 44 | 45 | /** 46 | * NativeBleController::scan_start 47 | * 48 | * Starts an indefinite BLE scan (must be stopped by scan_stop()) 49 | * While scanning, when a device is found, the callback for a device found will 50 | * be called from callback_holder. 51 | * 52 | */ 53 | void scan_start(); 54 | 55 | /** 56 | * NativeBleController::scan_stop 57 | * 58 | * Stops a BLE scan (started by scan_start()) 59 | * 60 | */ 61 | void scan_stop(); 62 | 63 | /** 64 | * NativeBleController::scan_is_active 65 | * 66 | * Returns a boolean of whether there is a BLE scan active currently 67 | * 68 | * @return whether scan is active currently 69 | */ 70 | bool scan_is_active(); 71 | 72 | /** 73 | * NativeBleController::scan_timeout 74 | * 75 | * Starts a BLE scan for a prescribed amount of time and stops. 76 | * While scanning, when a device is found, the callback for a device found will 77 | * be called from callback_holder. 78 | * 79 | * @param timeout_ms: number of milliseconds to scan 80 | */ 81 | void scan_timeout(int32_t timeout_ms); 82 | 83 | /** 84 | * NativeBleController::is_connected 85 | * 86 | * Returns a boolean of whether BLE is connected to a device currently 87 | * 88 | * @return whether BLE device is connected currently 89 | */ 90 | bool is_connected(); 91 | 92 | /** 93 | * NativeBleController::connect 94 | * 95 | * Connects to the provided BLE MAC address 96 | * 97 | * @param address: the MAC address in a 12 character string format 98 | */ 99 | void connect(const BluetoothAddress &address); 100 | 101 | /** 102 | * NativeBleController::write_request 103 | * 104 | * Writes to the provided characteristic on the provided service using 105 | * a regular BLE WRITE command 106 | * 107 | * @param service: UUID of the BLE service 108 | * @param characteristic: UUID of the characteristic on the service 109 | * @param data: a string containing the data that should be written to the characteristic 110 | */ 111 | void write_request(BluetoothUUID service, BluetoothUUID characteristic, DataChunk data); 112 | 113 | /** 114 | * NativeBleController::write_command 115 | * 116 | * Writes to the provided characteristic on the provided service using 117 | * a BLE WRITE_NO_RESPONSE command 118 | * 119 | * @param service: UUID of the BLE service 120 | * @param characteristic: UUID of the characteristic on the service 121 | * @param data: a string containing the data that should be written to the characteristic 122 | */ 123 | void write_command(BluetoothUUID service, BluetoothUUID characteristic, DataChunk data); 124 | 125 | /** 126 | * NativeBleController::read 127 | * 128 | * Reads from the provided characteristic on the provided service 129 | * 130 | * @param service: UUID of the BLE service 131 | * @param characteristic: UUID of the characteristic on the service 132 | * @param callback_on_read: callback std::function for when the read is complete, providing 133 | * pointer to data and length of data 134 | */ 135 | void read(BluetoothUUID service, BluetoothUUID characteristic, 136 | std::function callback_on_read); 137 | 138 | /** 139 | * NativeBleController::notify 140 | * 141 | * Enables notification for when the BLE device notifies the provided characteristic 142 | * 143 | * @param service: UUID of the BLE service 144 | * @param characteristic: UUID of the characteristic on the service 145 | * @param callback_on_notify: callback std::function for when a notification happens, providing 146 | * pointer to data and length of data 147 | */ 148 | void notify(BluetoothUUID service, BluetoothUUID characteristic, 149 | std::function callback_on_notify); 150 | 151 | /** 152 | * NativeBleController::indicate 153 | * 154 | * Enables indication for when the BLE device notifies the provided characteristic 155 | * 156 | * @param service: UUID of the BLE service 157 | * @param characteristic: UUID of the characteristic on the service 158 | * @param callback_on_indicate: callback std::function for when an indication happens, providing 159 | * pointer to data and length of data 160 | */ 161 | void indicate(BluetoothUUID service, BluetoothUUID characteristic, 162 | std::function callback_on_indicate); 163 | 164 | /** 165 | * NativeBleController::unsubscribe 166 | * 167 | * Disables notifications/indications for the provided service/characteristic pair. 168 | * 169 | * @param service: UUID of the BLE service 170 | * @param characteristic: UUID of the characteristic on the service 171 | */ 172 | void unsubscribe(BluetoothUUID service, BluetoothUUID characteristic); 173 | 174 | /** 175 | * NativeBleController::disconnect 176 | * 177 | * Disconnects from BLE device 178 | * 179 | */ 180 | void disconnect(); 181 | 182 | /** 183 | * NativeBleController::dispose 184 | * 185 | * Dispose of any remaining pointers or information from NativeBleController 186 | * 187 | */ 188 | void dispose(); 189 | }; 190 | } // namespace NativeBLE -------------------------------------------------------------------------------- /src-ble/macos/NativeBleInternal.mm: -------------------------------------------------------------------------------- 1 | #import "NativeBleInternal.h" 2 | #import "macable/macable.h" 3 | #import 4 | 5 | using namespace NativeBLE; 6 | 7 | Macable *macable; 8 | 9 | NativeBleInternal::NativeBleInternal() {} 10 | 11 | NativeBleInternal::~NativeBleInternal() {} 12 | 13 | void NativeBleInternal::setup(CallbackHolder cb_holder) { 14 | this->callback_holder = cb_holder; 15 | 16 | macable = [[Macable alloc]init]; 17 | [macable setup]; 18 | [macable setOnScanFound:[&](CBPeripheral* peripheral, NSDictionary* advData, NSNumber* rssi) { 19 | if (peripheral.name != nil) { 20 | DeviceDescriptor device; 21 | device.name = [peripheral.name cStringUsingEncoding:NSUTF8StringEncoding]; 22 | device.address = [peripheral.identifier.UUIDString cStringUsingEncoding:NSUTF8StringEncoding]; 23 | 24 | uint8_t prevSize = detected_addresses.size(); 25 | detected_addresses.insert(device.address); 26 | 27 | if (prevSize != detected_addresses.size()) { 28 | callback_holder.callback_on_scan_found(device); 29 | } 30 | } 31 | }]; 32 | [macable setOnConnected:[&]() { 33 | callback_holder.callback_on_device_connected(); 34 | }]; 35 | [macable setOnDisconnected:[&](NSString* msg) { 36 | std::string message = [msg cStringUsingEncoding:NSUTF8StringEncoding]; 37 | callback_holder.callback_on_device_disconnected(message); 38 | }]; 39 | } 40 | 41 | void NativeBleInternal::scan_timeout(int32_t timeout_ms) { 42 | this->scan_start(); 43 | std::this_thread::sleep_for(std::chrono::milliseconds(timeout_ms)); 44 | this->scan_stop(); 45 | } 46 | 47 | void NativeBleInternal::scan_start() { 48 | [macable startScan]; 49 | callback_holder.callback_on_scan_start(); 50 | } 51 | 52 | void NativeBleInternal::scan_stop() { 53 | [macable stopScan]; 54 | callback_holder.callback_on_scan_stop(); 55 | } 56 | 57 | bool NativeBleInternal::scan_is_active() { 58 | //TODO: implement this 59 | return false; 60 | } 61 | 62 | bool NativeBleInternal::is_connected() { 63 | return [macable isPeripheralReady] == YES ? true : false; 64 | } 65 | 66 | 67 | void NativeBleInternal::connect(const BluetoothAddress &address) { 68 | //Ensure that the address is a valid UUID 69 | if (address.size() != 36) return; 70 | 71 | //Create a UUID object from std::string 72 | NSString *string = [[NSString alloc]initWithUTF8String:address.c_str()]; 73 | NSUUID *uuid = [[NSUUID alloc]initWithUUIDString:string]; 74 | 75 | CBPeripheral *peripheral = [macable getPeripheralWithUUID:uuid]; 76 | [macable setPeripheral:peripheral]; 77 | [macable connect]; 78 | 79 | while (!this->is_connected()); 80 | } 81 | 82 | void NativeBleInternal::write_request(BluetoothUUID service, BluetoothUUID characteristic, DataChunk &_data) { 83 | NSString *serviceStr = [[NSString alloc]initWithUTF8String:service.c_str()]; 84 | NSString *charStr = [[NSString alloc]initWithUTF8String:characteristic.c_str()]; 85 | 86 | CBUUID *service_uuid = [CBUUID UUIDWithString:serviceStr]; 87 | CBUUID *characteristic_uuid = [CBUUID UUIDWithString:charStr]; 88 | 89 | NSData* data = [NSData dataWithBytes:_data.c_str() length:_data.size()]; 90 | 91 | [macable writeData:data toCharacteristic:characteristic_uuid service:service_uuid]; 92 | } 93 | 94 | void NativeBleInternal::write_command(BluetoothUUID service, BluetoothUUID characteristic, DataChunk &_data) { 95 | NSString *serviceStr = [[NSString alloc]initWithUTF8String:service.c_str()]; 96 | NSString *charStr = [[NSString alloc]initWithUTF8String:characteristic.c_str()]; 97 | 98 | CBUUID *service_uuid = [CBUUID UUIDWithString:serviceStr]; 99 | CBUUID *characteristic_uuid = [CBUUID UUIDWithString:charStr]; 100 | 101 | NSData* data = [NSData dataWithBytes:_data.c_str() length:_data.size()]; 102 | 103 | [macable writeNoResponseData:data toCharacteristic:characteristic_uuid service:service_uuid]; 104 | } 105 | 106 | void NativeBleInternal::read(BluetoothUUID service, BluetoothUUID characteristic, 107 | std::function callback_on_read) { 108 | NSString *serviceStr = [[NSString alloc]initWithUTF8String:service.c_str()]; 109 | NSString *charStr = [[NSString alloc]initWithUTF8String:characteristic.c_str()]; 110 | 111 | CBUUID *service_uuid = [CBUUID UUIDWithString:serviceStr]; 112 | CBUUID *characteristic_uuid = [CBUUID UUIDWithString:charStr]; 113 | 114 | std::function onCharacteristicUpdated = macable.onCharacteristicUpdated; 115 | 116 | [macable setOnCharacteristicUpdated:[=](NSData* data) { 117 | [macable setOnCharacteristicUpdated:onCharacteristicUpdated]; 118 | callback_on_read((const uint8_t*) data.bytes, (uint32_t) data.length); 119 | }]; 120 | 121 | [macable readValueOnCharacteristic:characteristic_uuid service:service_uuid]; 122 | } 123 | 124 | void NativeBleInternal::notify(BluetoothUUID service, BluetoothUUID characteristic, 125 | std::function callback_on_notify) { 126 | NSString *serviceStr = [[NSString alloc]initWithUTF8String:service.c_str()]; 127 | NSString *charStr = [[NSString alloc]initWithUTF8String:characteristic.c_str()]; 128 | 129 | CBUUID *service_uuid = [CBUUID UUIDWithString:serviceStr]; 130 | CBUUID *characteristic_uuid = [CBUUID UUIDWithString:charStr]; 131 | 132 | [macable setOnCharacteristicUpdated:[=](NSData* data) { 133 | callback_on_notify((const uint8_t*) data.bytes, (uint32_t) data.length); 134 | }]; 135 | [macable subscribeToNotificationsOnCharacteristic:characteristic_uuid service:service_uuid]; 136 | } 137 | 138 | void NativeBleInternal::indicate(BluetoothUUID service, BluetoothUUID characteristic, 139 | std::function callback_on_indicate) { 140 | NSString *serviceStr = [[NSString alloc]initWithUTF8String:service.c_str()]; 141 | NSString *charStr = [[NSString alloc]initWithUTF8String:characteristic.c_str()]; 142 | 143 | CBUUID *service_uuid = [CBUUID UUIDWithString:serviceStr]; 144 | CBUUID *characteristic_uuid = [CBUUID UUIDWithString:charStr]; 145 | 146 | [macable setOnCharacteristicUpdated:[=](NSData* data) { 147 | callback_on_indicate((const uint8_t*) data.bytes, (uint32_t) data.length); 148 | }]; 149 | [macable subscribeToNotificationsOnCharacteristic:characteristic_uuid service:service_uuid]; 150 | } 151 | 152 | void NativeBleInternal::unsubscribe(BluetoothUUID service, BluetoothUUID characteristic) { 153 | // TODO: IMPLEMENT 154 | } 155 | 156 | void NativeBleInternal::disconnect() { 157 | [macable disconnect]; 158 | } 159 | void NativeBleInternal::dispose() {} 160 | 161 | -------------------------------------------------------------------------------- /src-ble/linux/NativeBleInternal.cpp: -------------------------------------------------------------------------------- 1 | #include "NativeBleInternal.h" 2 | #include 3 | 4 | using namespace NativeBLE; 5 | 6 | NativeBleInternal::NativeBleInternal() : async_thread(nullptr), async_thread_active(true) { 7 | bluez_service.init(); 8 | adapter = bluez_service.get_first_adapter(); 9 | if (adapter == nullptr) { 10 | // No adapter available. 11 | } else { 12 | async_thread = new std::thread(&NativeBleInternal::async_thread_function, this); 13 | } 14 | } 15 | 16 | NativeBleInternal::~NativeBleInternal() { 17 | async_thread_active = false; 18 | while (!async_thread->joinable()) { 19 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 20 | } 21 | async_thread->join(); 22 | delete async_thread; 23 | } 24 | 25 | void NativeBleInternal::setup(CallbackHolder callback_holder) { this->callback_holder = callback_holder; } 26 | 27 | void NativeBleInternal::scan_start() { 28 | if (adapter == nullptr) return; 29 | for (auto& [device_path, dev] : adapter->devices) { 30 | DeviceDescriptor descriptor; 31 | descriptor.address = dev->get_address(); 32 | descriptor.name = dev->get_name(); 33 | callback_holder.callback_on_scan_found(descriptor); 34 | } 35 | adapter->OnDeviceFound = [&](std::string address, std::string name) { 36 | DeviceDescriptor descriptor; 37 | descriptor.address = address; 38 | descriptor.name = name; 39 | callback_holder.callback_on_scan_found(descriptor); 40 | }; 41 | adapter->discovery_filter_transport_set("le"); 42 | adapter->StartDiscovery(); 43 | callback_holder.callback_on_scan_start(); 44 | } 45 | 46 | void NativeBleInternal::scan_stop() { 47 | if (adapter == nullptr) return; 48 | adapter->StopDiscovery(); 49 | while(adapter->is_discovering()){ 50 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); 51 | } 52 | callback_holder.callback_on_scan_stop(); 53 | } 54 | 55 | bool NativeBleInternal::scan_is_active() { 56 | if (adapter == nullptr) return false; 57 | return adapter->is_discovering(); 58 | } 59 | 60 | void NativeBleInternal::scan_timeout(int32_t timeout_ms) { 61 | this->scan_start(); 62 | std::this_thread::sleep_for(std::chrono::milliseconds(timeout_ms)); 63 | this->scan_stop(); 64 | } 65 | 66 | bool NativeBleInternal::is_connected() { return device != nullptr ? true : false; } 67 | 68 | void NativeBleInternal::connect(const BluetoothAddress& address) { 69 | BluetoothAddress addr = format_mac_address(address); 70 | 71 | if (adapter == nullptr) return; 72 | device = adapter->get_device(address); 73 | if (device != nullptr) { 74 | device->OnServicesResolved = [&]() { 75 | callback_holder.callback_on_device_connected(); 76 | }; 77 | device->Connect(); 78 | } 79 | } 80 | 81 | void NativeBleInternal::write_request(BluetoothUUID service_uuid, BluetoothUUID characteristic_uuid, DataChunk& data) { 82 | if (device == nullptr) return; 83 | auto characteristic = device->get_characteristic(service_uuid, characteristic_uuid); 84 | if (characteristic != nullptr) { 85 | characteristic->write_request((const uint8_t*)data.c_str(), data.length()); 86 | } 87 | } 88 | 89 | void NativeBleInternal::write_command(BluetoothUUID service_uuid, BluetoothUUID characteristic_uuid, DataChunk& data) { 90 | if (device == nullptr) return; 91 | auto characteristic = device->get_characteristic(service_uuid, characteristic_uuid); 92 | if (characteristic != nullptr) { 93 | characteristic->write_command((const uint8_t*)data.c_str(), data.length()); 94 | } 95 | } 96 | 97 | void NativeBleInternal::read(BluetoothUUID service_uuid, BluetoothUUID characteristic_uuid, 98 | std::function callback_on_read) { 99 | if (device == nullptr) return; 100 | auto characteristic = device->get_characteristic(service_uuid, characteristic_uuid); 101 | if (characteristic != nullptr) { 102 | SimpleDBus::Holder result = characteristic->ReadValue(SimpleDBus::Holder()); 103 | auto result_array = result.get_array(); 104 | 105 | uint8_t* result_data = new uint8_t[result_array.size()]; 106 | for (int i = 0; i < result_array.size(); i++) { 107 | result_data[i] = result_array[i].get_byte(); 108 | } 109 | delete[] result_data; 110 | callback_on_read(result_data, result_array.size()); 111 | } 112 | } 113 | 114 | void NativeBleInternal::notify(BluetoothUUID service_uuid, BluetoothUUID characteristic_uuid, 115 | std::function callback_on_notify) { 116 | if (device == nullptr) return; 117 | auto characteristic = device->get_characteristic(service_uuid, characteristic_uuid); 118 | if (characteristic != nullptr) { 119 | characteristic->ValueChanged = [callback_on_notify](std::vector new_value) { 120 | callback_on_notify(&new_value[0], new_value.size()); 121 | }; 122 | characteristic->StartNotify(); 123 | } 124 | } 125 | 126 | void NativeBleInternal::indicate(BluetoothUUID service_uuid, BluetoothUUID characteristic_uuid, 127 | std::function callback_on_indicate) { 128 | if (device == nullptr) return; 129 | auto characteristic = device->get_characteristic(service_uuid, characteristic_uuid); 130 | if (characteristic != nullptr) { 131 | characteristic->ValueChanged = [callback_on_indicate](std::vector new_value) { 132 | callback_on_indicate(&new_value[0], new_value.size()); 133 | }; 134 | characteristic->StartNotify(); 135 | } 136 | } 137 | 138 | void NativeBleInternal::unsubscribe(BluetoothUUID service_uuid, BluetoothUUID characteristic_uuid) { 139 | if (device == nullptr) return; 140 | auto characteristic = device->get_characteristic(service_uuid, characteristic_uuid); 141 | if (characteristic != nullptr) { 142 | characteristic->StopNotify(); 143 | } 144 | } 145 | 146 | void NativeBleInternal::disconnect() { 147 | if (device == nullptr) return; 148 | device->Disconnect(); 149 | } 150 | 151 | void NativeBleInternal::dispose() {} 152 | 153 | void NativeBleInternal::async_thread_function() { 154 | while (async_thread_active) { 155 | bluez_service.run_async(); 156 | std::this_thread::sleep_for(std::chrono::microseconds(50)); 157 | } 158 | } 159 | 160 | BluetoothAddress NativeBleInternal::format_mac_address(std::string address) { 161 | if ((int)address.find(' ') > 0) { 162 | address.replace(address.begin(), address.end(), ' ', ':'); 163 | } 164 | if ((int)address.find('-') > 0) { 165 | address.replace(address.begin(), address.end(), '-', ':'); 166 | } 167 | 168 | if ((int)address.find(':') < 0) { 169 | std::stringstream formatted_addr; 170 | formatted_addr << address[0] << address[1]; 171 | for (int i = 0; i < address.length(); i += 2) { 172 | formatted_addr << ":" << address[i] << address[i + 1]; 173 | } 174 | 175 | return (BluetoothAddress)formatted_addr.str(); 176 | } 177 | return (BluetoothAddress)address; 178 | } 179 | -------------------------------------------------------------------------------- /src-ble/macos/macable/macable.mm: -------------------------------------------------------------------------------- 1 | #import "macable.h" 2 | 3 | int servicesCount; 4 | 5 | @implementation Macable 6 | -(id) init { 7 | self = [super init]; 8 | 9 | if (self) { 10 | _peripheralReady = NO; 11 | _centralManagerPoweredOn = NO; 12 | _onScanFound = [](CBPeripheral*, NSDictionary*, NSNumber*) {}; 13 | _onConnected = []() {}; 14 | _onDisconnected = [](NSString*) {}; 15 | _onCharacteristicUpdated = [](NSData*) {}; 16 | } 17 | 18 | return self; 19 | } 20 | 21 | -(void) setup { 22 | dispatchQueue = dispatch_queue_create("CBqueue", DISPATCH_QUEUE_SERIAL); 23 | centralManager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatchQueue]; 24 | _peripheralReady = NO; 25 | int tries = 0; 26 | while (not _centralManagerPoweredOn){ 27 | tries++; 28 | if (tries > CENTRAL_MANAGER_TRIES){ 29 | std::cerr << "CentralManager not powered on after " << CENTRAL_MANAGER_TRIES << " tries.\n"; 30 | break; 31 | } 32 | std::this_thread::sleep_for(std::chrono::milliseconds(CENTRAL_MANAGER_SLEEP)); 33 | } 34 | } 35 | 36 | -(void) startScan { 37 | [centralManager scanForPeripheralsWithServices:nil options:nil]; 38 | } 39 | 40 | -(void) stopScan { 41 | [centralManager stopScan]; 42 | } 43 | 44 | -(void) connect { 45 | if (_peripheral != nil) { 46 | _peripheral.delegate = self; 47 | [centralManager connectPeripheral:_peripheral options:nil]; 48 | } 49 | } 50 | 51 | -(CBPeripheral*) getPeripheralWithUUID:(NSUUID*)uuid { 52 | NSArray* peripherals = [centralManager retrievePeripheralsWithIdentifiers:@[uuid]]; 53 | 54 | if ([peripherals count] > 0) { 55 | return [peripherals objectAtIndex:0]; 56 | } 57 | 58 | return nil; 59 | } 60 | 61 | -(void) disconnect { 62 | if (_peripheral != nil) { 63 | [centralManager cancelPeripheralConnection:_peripheral]; 64 | } 65 | } 66 | 67 | -(void) readValueOnCharacteristic:(CBUUID*)characteristic_uuid service:(CBUUID*)service_uuid { 68 | for (CBService* service in _peripheral.services) { 69 | if ([service.UUID isEqual:service_uuid]) { 70 | for (CBCharacteristic* characteristic in service.characteristics) { 71 | if ([characteristic.UUID isEqual:characteristic_uuid]) { 72 | if ((characteristic.properties & CBCharacteristicPropertyRead) == CBCharacteristicPropertyRead) { 73 | [_peripheral readValueForCharacteristic:characteristic]; 74 | break; 75 | } 76 | return; 77 | } 78 | } 79 | break; 80 | } 81 | } 82 | } 83 | 84 | -(void) subscribeToNotificationsOnCharacteristic:(CBUUID*)characteristic_uuid service:(CBUUID*)service_uuid { 85 | for (CBService* service in _peripheral.services) { 86 | if ([service.UUID isEqual:service_uuid]) { 87 | for (CBCharacteristic* characteristic in service.characteristics) { 88 | if ([characteristic.UUID isEqual:characteristic_uuid]) { 89 | if ((characteristic.properties & CBCharacteristicPropertyNotify) == CBCharacteristicPropertyNotify) { 90 | [_peripheral setNotifyValue:YES forCharacteristic:characteristic]; 91 | break; 92 | } 93 | return; 94 | } 95 | } 96 | break; 97 | } 98 | } 99 | } 100 | 101 | -(void) subscribeToIndicationsOnCharacteristic:(CBUUID*)characteristic_uuid service:(CBUUID*)service_uuid { 102 | for (CBService* service in _peripheral.services) { 103 | if ([service.UUID isEqual:service_uuid]) { 104 | for (CBCharacteristic* characteristic in service.characteristics) { 105 | if ([characteristic.UUID isEqual:characteristic_uuid]) { 106 | if ((characteristic.properties & CBCharacteristicPropertyIndicate) == CBCharacteristicPropertyIndicate) { 107 | [_peripheral setNotifyValue:YES forCharacteristic:characteristic]; 108 | break; 109 | } 110 | return; 111 | } 112 | } 113 | break; 114 | } 115 | } 116 | } 117 | 118 | -(void) writeData:(NSData*)data toCharacteristic:(CBUUID*)characteristic_uuid service:(CBUUID*)service_uuid { 119 | //TODO: Add breaks and returns to stop looping once found 120 | for (CBService* service in _peripheral.services) { 121 | if ([service.UUID isEqual:service_uuid]) { 122 | for (CBCharacteristic* characteristic in service.characteristics) { 123 | if ([characteristic.UUID isEqual:characteristic_uuid]) { 124 | if ((characteristic.properties & CBCharacteristicPropertyWrite) == CBCharacteristicPropertyWrite) { 125 | [_peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse]; 126 | } 127 | } 128 | } 129 | } 130 | } 131 | } 132 | 133 | -(void) writeNoResponseData:(NSData*)data toCharacteristic:(CBUUID*)characteristic_uuid service:(CBUUID*)service_uuid { 134 | //TODO: Add breaks and returns to stop looping once found 135 | for (CBService* service in _peripheral.services) { 136 | if ([service.UUID isEqual:service_uuid]) { 137 | for (CBCharacteristic* characteristic in service.characteristics) { 138 | if ([characteristic.UUID isEqual:characteristic_uuid]) { 139 | if ((characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse) == CBCharacteristicPropertyWriteWithoutResponse) { 140 | [_peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse]; 141 | } 142 | } 143 | } 144 | } 145 | } 146 | } 147 | 148 | -(void) centralManager:(CBCentralManager*)central didDiscoverPeripheral:(CBPeripheral*)device 149 | advertisementData:(NSDictionary*)advertisementData RSSI:(NSNumber*)RSSI { 150 | self.onScanFound(device, advertisementData, RSSI); 151 | } 152 | 153 | -(void) centralManagerDidUpdateState:(CBCentralManager*)central { 154 | switch (central.state) { 155 | case CBManagerStateUnknown:break; 156 | case CBManagerStateResetting:break; 157 | case CBManagerStateUnsupported:break; 158 | case CBManagerStateUnauthorized:break; 159 | case CBManagerStatePoweredOff:break; 160 | case CBManagerStatePoweredOn: _centralManagerPoweredOn = YES; break; 161 | } 162 | } 163 | 164 | -(void) centralManager:(CBCentralManager*)central didConnectPeripheral:(CBPeripheral*)device { 165 | [device discoverServices:nil]; 166 | } 167 | 168 | -(void) centralManager:(CBCentralManager*)central didDisconnectPeripheral:(CBPeripheral*)device 169 | error:(NSError*)error { 170 | _peripheral.delegate = nil; 171 | 172 | _peripheralReady = NO; 173 | if (error.localizedDescription == nil) { 174 | self.onDisconnected(@"Manually disconnected."); 175 | } else { 176 | self.onDisconnected(error.localizedDescription); 177 | } 178 | } 179 | 180 | -(void) peripheral:(CBPeripheral*)peripheral didUpdateValueForCharacteristic:(CBCharacteristic*)characteristic 181 | error:(NSError*)error { 182 | self.onCharacteristicUpdated(characteristic.value); 183 | } 184 | 185 | -(void) peripheral:(CBPeripheral*)peripheral didDiscoverServices:(NSError*)error { 186 | if (error == nil) { 187 | servicesCount = [peripheral.services count]; 188 | for (CBService* service in peripheral.services) { 189 | [peripheral discoverCharacteristics:nil forService:service]; 190 | } 191 | } 192 | } 193 | 194 | -(void) peripheral:(CBPeripheral*)peripheral didDiscoverCharacteristicsForService:(CBService*)service 195 | error:(NSError*)error { 196 | 197 | servicesCount--; 198 | 199 | if (error == nil && servicesCount == 0) { 200 | _peripheralReady = YES; 201 | self.onConnected(); 202 | } 203 | } 204 | 205 | 206 | @end -------------------------------------------------------------------------------- /TUTORIAL.md: -------------------------------------------------------------------------------- 1 | # NativeBLE 2 | 3 | Welcome to the NativeBLE tutorial! 4 | 5 | This page will explain in-depth the code for the NativeBLE test application so that you, as the developer, can understand how to incorporate the Native BLE library into your own projects. 6 | 7 | The source code test application can be found in `src-ble-test`, and after using CMake to build the library, the application will be compiled to the `bin` folder. 8 | 9 | ## An Overview 10 | The purpose of the tester application is to expose the developer to a basic use-case of the Native BLE library. Although the library can be used to interface with any BLE device, in this test application we will interface with a Nordic UART characteristic. This can be on a Nordic BLE microcontroller, or can be emulated using the nRF Connect app on the Android Play Store. 11 | 12 | In this tutorial we will use the nRF Connect app to interface with the test application. 13 | 14 | ## A Basic Explanation of BLE 15 | BLE is a protocol designed to exchange information with various devices and sensors using very low energy, hence the name Bluetooth Low Energy. In a basic sense, the protocol involves a Central device (in our case the PC) and any Peripheral device (any sensor, device, app) that the Central device wants to get information from. 16 | 17 | Each Peripheral has a set of services which each have a set of characteristics that have read and/or write properties. Characteristics that contain information that should be read by the Central will have read properties. When information needs to be sent to the Peripheral, the Central will write to a characteristic with a write property. Characteristics can also have both properties enabled. 18 | 19 | Within the read properties there are: 20 | - Read 21 | - allows the Central device to manually read the value of a characteristic at any time 22 | - Notify 23 | - issues a notification to the Central device that the value of a characteristic has been updated 24 | - Central device will issue a response to Peripheral indicating it received the notification 25 | - Indicate 26 | - issues a notification to the Central device that the value of a characteristic has been updated 27 | - no response is sent to Peripheral 28 | 29 | Within the write properties there are: 30 | * Write 31 | * Central will write to a characteristic of Peripheral and will expect a response from the Peripheral indicating the write was successful 32 | * Write With No Response 33 | * Central will write to a characteristic of Peripheral and will not expect a response 34 | 35 | ## Test Application Code 36 | In this section, we will go through the code of the Test Application to understand how to use the Native BLE library. 37 | 38 | ### Setup 39 | First, we need to declare a `NativeBleController` object from the `NativeBLE` namespace. We can declare this in the global namespace so that it's a static variable. 40 | ``` 41 | NativeBLE::NativeBleController ble; 42 | ``` 43 | 44 | 45 | We also want to define some constants. We will define a constant for how long we want to scan for devices, `SCAN_TIMEOUT_MS` and also constants for the UUIDs of the Nordic UART Service and its TX and RX characteristics. 46 | ``` 47 | #define SCAN_TIMEOUT_MS 5000 48 | 49 | #define NORDIC_UART_SERVICE_UUID "6e400001-b5a3-f393-e0a9-e50e24dcca9e" 50 | #define NORDIC_UART_CHAR_RX "6e400002-b5a3-f393-e0a9-e50e24dcca9e" 51 | #define NORDIC_UART_CHAR_TX "6e400003-b5a3-f393-e0a9-e50e24dcca9e" 52 | ``` 53 | 54 | Now, we want to start setting up our `NativeBleController` object, `ble`. We'll start with declaring the set of callback lambdas that `ble` will use. We'll also declare an array of `DeviceDescriptor`s that will contain a list of all the devices discovered after scanning. 55 | ``` 56 | NativeBLE::CallbackHolder ble_events; 57 | std::vector devices; 58 | ``` 59 | 60 | The `callback_on_device_disconnected` passes the reason for disconnect as a string. We will simply print this. 61 | > The exact message will differ on each platform because Windows, Linux, and macOS differ in their disconnection messages. 62 | ``` 63 | ble_events.callback_on_device_disconnected = [](std::string msg) { 64 | std::cout << "Disconnected: " << msg << std::endl; 65 | return; 66 | }; 67 | ``` 68 | 69 | The `callback_on_device_connected` is called when a Peripheral is connected and its services and characteristics are discovered. 70 | ``` 71 | ble_events.callback_on_device_connected = []() { 72 | std::cout << "Connected" << std::endl; 73 | }; 74 | ``` 75 | 76 | The `callback_on_scan_found` is called whenever a new device is discovered during a scan. We want to save these devices to an array that we can use to remember the MAC address of the device we want to connect to. We also print to the screen how many devices have been found so far. 77 | > CoreBluetooth, which runs on Apple devices (macOS, iOS, iPadOS) do not report Bluetooth MAC address. Instead they internally map the addresses to a UUID which is exposed to the developer. So, on Apple devices, `device.address` will provide this UUID and not the Bluetooth MAC address that other platforms show. 78 | ``` 79 | ble_events.callback_on_scan_found = [&](NativeBLE::DeviceDescriptor device) { 80 | devices.push_back(device); 81 | std::cout << "\r" << devices.size() << " devices found."; 82 | fflush(stdout); 83 | }; 84 | ``` 85 | 86 | The `callback_on_scan_start` and `callback_on_scan_stop` are called when scanning starts and stops, respectively. 87 | ``` 88 | ble_events.callback_on_scan_start = []() { 89 | std::cout << "Scanning for " << SCAN_TIMEOUT_MS << " ms..." << std::endl; 90 | }; 91 | ble_events.callback_on_scan_stop = []() { 92 | std::cout << std::endl << "Scan complete." << std::endl; 93 | }; 94 | ``` 95 | 96 | Now that we have initialized the callbacks, we can pass them into the `setup` method of `ble`. 97 | > Note that the callbacks can also be left empty when initializing `ble`. 98 | ``` 99 | ble.setup(ble_events); 100 | ``` 101 | 102 | This completes the setup portion of the `NativeBleController`. Now we can use use `ble` to scan, connect, write and read. 103 | 104 | ### Scanning and Connecting to Device 105 | To start scanning for devices, we call the `scan_timeout` method. 106 | > This method is blocking, which means that it will not complete until `SCAN_TIMEOUT_MS` milliseconds has passed. In this case that is 5000 milliseconds. You can use `scan_start` and `scan_stop` to implement a non-blocking scanning operation as well. 107 | ``` 108 | ble.scan_timeout(SCAN_TIMEOUT_MS); 109 | ``` 110 | 111 | Our `callback_on_scan_found` will populate the `devices` array we declared earlier, so when the scanning finishes, we can print out this list in a readable form for the user to choose which device to connect to. 112 | ``` 113 | for (int i = 0; i < devices.size(); i++) { 114 | std::cout << " " << i ": " devices[i].name << " (" << devices[i].address << ")" << std::endl; 115 | } 116 | std::cout << "Type index of device to connect to: "; 117 | int device; 118 | std::cin >> device; 119 | ``` 120 | 121 | Now to connect to the desired device, we pass its address into the `connect` method. 122 | > This method is blocking and will not return until the device is connected and the services and characteristics are discovered. 123 | ``` 124 | ble.connect(devices[device].address); 125 | ``` 126 | 127 | Now we are ready to enable communication with the BLE device. Remember, in this example we are writing and reading to the Nordic UART Service, but when working with other devices, like a BLE Heart Rate monitor, you would be reading/writing to its services. 128 | 129 | ### Enabling Communication 130 | In order to receive notifications on whether a characteristic's value has been updated on the device we are connected to, we need to call the `notify` method and provide a callback lambda for when the characteristic's value is updated. In our callback, we add some formatting to show the message from the device on the screen. 131 | > We can also call the `indicate` method with the same parameters to enable indications. 132 | ``` 133 | ble.notify(NORDIC_UART_SERVICE_UUID, NORDIC_UART_CHAR_TX, [7](const uint8_t* data, uint32_t length) { 134 | std::cout << "\r" << devices[device].name << "> "; 135 | for (int i = 0; i < length; i++) { std::cout << data[i]' } 136 | std::cout << std::endl; << " > "; 137 | fflush(stdout); 138 | }); 139 | ``` 140 | 141 | After enabling notifications, we want to create a loop that the application will stay in so that we can send messages to the device. Our loop will read in all characters that are typed until an ENTER, and send these characters to the peripheral device's RX Characteristic. 142 | > Here, `write_request` is used, which is a write with response operation, as described in A Basic Explanation of BLE. We can also use `write_command` with the same parameters to issue a write without response operation. 143 | ``` 144 | std::string message; 145 | while (true) { 146 | getline(std::cin, message); 147 | ble.write_request(NORDIC_UART_SERVICE_UUID, NORDIC_UART_CHAR_RX, message); 148 | std::cout << " > "; 149 | } 150 | ``` 151 | 152 | At this point, your app will be able to talk to a peripheral device that has the Nordic UART Service with the TX and RX characteristic. The last thing we will do is add some safe practices for disconnecting from the BLE device when your app is closed. 153 | 154 | ### Disconnecting 155 | To close this test application after starting, you can use the Ctrl-C command. However, we want to add some code that will properly disconnect and dispose of our `NativeBleController` object before closing the application, to ensure that the Bluetooth hardware on your device is released appropriately. 156 | 157 | We will add a signal handler in our code to handle this when the program closes, using the `std::signal` type. First, let's create our signal handler. 158 | ``` 159 | void signal_handler(int signal) { 160 | if (signal == SIGINT) { 161 | std::cout << std::endl << "User quit program." << std::endl; 162 | } 163 | ble.disconnect(); 164 | ble.dispose(); 165 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 166 | exit(signal); 167 | } 168 | ``` 169 | 170 | The `sleep_for` is to wait so that the `callback_on_device_disconnected` is called and the disconnect message is printed before the program closes. 171 | 172 | The full source code for this test application can be found in the `src-ble-test` folder in the repository. 173 | 174 | -------------------------------------------------------------------------------- /src-ble/linux/simpledbus/base/Holder.cpp: -------------------------------------------------------------------------------- 1 | #include "Holder.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #include "dbus/dbus-protocol.h" 7 | 8 | using namespace SimpleDBus; 9 | 10 | Holder::Holder() : _type(NONE) {} 11 | 12 | Holder::~Holder() {} 13 | 14 | Holder::Holder(const Holder& other) { *this = other; } 15 | 16 | Holder& Holder::operator=(const Holder& other) { 17 | if (this != &other) { 18 | _type = other._type; 19 | switch (_type) { 20 | case BOOLEAN: 21 | this->holder_boolean = other.holder_boolean; 22 | break; 23 | case BYTE: 24 | case INT16: 25 | case UINT16: 26 | case INT32: 27 | case UINT32: 28 | case INT64: 29 | case UINT64: 30 | this->holder_integer = other.holder_integer; 31 | break; 32 | case DOUBLE: 33 | this->holder_double = other.holder_double; 34 | break; 35 | case STRING: 36 | case OBJ_PATH: 37 | case SIGNATURE: 38 | this->holder_string = other.holder_string; 39 | break; 40 | case ARRAY: 41 | this->holder_array = other.holder_array; 42 | break; 43 | case DICT: 44 | this->holder_dict = other.holder_dict; 45 | break; 46 | } 47 | } 48 | return *this; 49 | } 50 | 51 | HolderType Holder::type() { return _type; } 52 | 53 | std::string Holder::_represent_simple() { 54 | std::ostringstream output; 55 | 56 | switch (_type) { 57 | case BOOLEAN: 58 | output << get_boolean(); 59 | break; 60 | case BYTE: 61 | output << (int)get_byte(); 62 | break; 63 | case INT16: 64 | output << (int)get_int16(); 65 | break; 66 | case UINT16: 67 | output << (int)get_uint16(); 68 | break; 69 | case INT32: 70 | output << get_int32(); 71 | break; 72 | case UINT32: 73 | output << get_uint32(); 74 | break; 75 | case INT64: 76 | output << get_int64(); 77 | break; 78 | case UINT64: 79 | output << get_uint64(); 80 | break; 81 | case DOUBLE: 82 | output << get_double(); 83 | break; 84 | case STRING: 85 | case OBJ_PATH: 86 | case SIGNATURE: 87 | output << get_string(); 88 | break; 89 | } 90 | return output.str(); 91 | } 92 | 93 | std::vector Holder::_represent_container() { 94 | std::vector output_lines; 95 | switch (_type) { 96 | case BOOLEAN: 97 | case BYTE: 98 | case INT16: 99 | case UINT16: 100 | case INT32: 101 | case UINT32: 102 | case INT64: 103 | case UINT64: 104 | case DOUBLE: 105 | case STRING: 106 | case OBJ_PATH: 107 | case SIGNATURE: 108 | output_lines.push_back(_represent_simple()); 109 | break; 110 | case ARRAY: { 111 | output_lines.push_back("Array:"); 112 | std::vector additional_lines; 113 | if (holder_array.size() > 0 && holder_array[0]._type == BYTE) { 114 | // Dealing with an array of bytes, use custom print functionality. 115 | std::string temp_line = ""; 116 | for (int i = 0; i < holder_array.size(); i++) { 117 | // Represent each byte as a hex string 118 | std::stringstream stream; 119 | stream << std::setfill('0') << std::setw(2) << std::hex << ((int)holder_array[i].get_byte()); 120 | temp_line += (stream.str() + " "); 121 | if ((i + 1) % 32 == 0) { 122 | additional_lines.push_back(temp_line); 123 | temp_line = ""; 124 | } 125 | } 126 | additional_lines.push_back(temp_line); 127 | } else { 128 | for (int i = 0; i < holder_array.size(); i++) { 129 | for (auto& line : holder_array[i]._represent_container()) { 130 | additional_lines.push_back(line); 131 | } 132 | } 133 | } 134 | for (auto& line : additional_lines) { 135 | output_lines.push_back(" " + line); 136 | } 137 | break; 138 | } 139 | case DICT: 140 | for (auto& [key, value] : holder_dict) { 141 | output_lines.push_back(key); 142 | auto additional_lines = value._represent_container(); 143 | for (auto& line : additional_lines) { 144 | output_lines.push_back(" " + line); 145 | } 146 | // output_lines.push_back(value.represent()); 147 | } 148 | break; 149 | } 150 | return output_lines; 151 | } 152 | 153 | std::string Holder::represent() { 154 | std::ostringstream output; 155 | auto output_lines = _represent_container(); 156 | for (auto& output_line : output_lines) { 157 | output << output_line << std::endl; 158 | } 159 | return output.str(); 160 | } 161 | 162 | std::string Holder::_signature_simple() { 163 | switch (_type) { 164 | case BOOLEAN: 165 | return DBUS_TYPE_BOOLEAN_AS_STRING; 166 | case BYTE: 167 | return DBUS_TYPE_BYTE_AS_STRING; 168 | case INT16: 169 | return DBUS_TYPE_INT16_AS_STRING; 170 | case UINT16: 171 | return DBUS_TYPE_UINT16_AS_STRING; 172 | case INT32: 173 | return DBUS_TYPE_INT32_AS_STRING; 174 | case UINT32: 175 | return DBUS_TYPE_UINT32_AS_STRING; 176 | case INT64: 177 | return DBUS_TYPE_INT64_AS_STRING; 178 | case UINT64: 179 | return DBUS_TYPE_UINT64_AS_STRING; 180 | case DOUBLE: 181 | return DBUS_TYPE_DOUBLE_AS_STRING; 182 | case STRING: 183 | return DBUS_TYPE_STRING_AS_STRING; 184 | case OBJ_PATH: 185 | return DBUS_TYPE_OBJECT_PATH_AS_STRING; 186 | case SIGNATURE: 187 | return DBUS_TYPE_SIGNATURE_AS_STRING; 188 | } 189 | return ""; 190 | } 191 | 192 | std::string Holder::signature() { 193 | std::string output; 194 | switch (_type) { 195 | case BOOLEAN: 196 | case BYTE: 197 | case INT16: 198 | case UINT16: 199 | case INT32: 200 | case UINT32: 201 | case INT64: 202 | case UINT64: 203 | case DOUBLE: 204 | case STRING: 205 | case OBJ_PATH: 206 | case SIGNATURE: 207 | output = _signature_simple(); 208 | break; 209 | case ARRAY: 210 | output = DBUS_TYPE_ARRAY_AS_STRING; 211 | if (holder_array.size() == 0) { 212 | output += DBUS_TYPE_VARIANT_AS_STRING; 213 | } else { 214 | // ! FIXME: This is not entirely correct, as not all elements might be equal. 215 | auto internal_holder = holder_array[0]; 216 | output += internal_holder.signature(); 217 | } 218 | break; 219 | case DICT: 220 | output = DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING; 221 | // ! FIXME: This is not entirely correct, we're assuming all key elements are strings. 222 | output += DBUS_TYPE_VARIANT_AS_STRING; 223 | output += holder_dict.begin()->second.signature(); 224 | output = DBUS_DICT_ENTRY_END_CHAR_AS_STRING; 225 | break; 226 | } 227 | return output; 228 | } 229 | 230 | Holder Holder::create_byte(uint8_t value) { 231 | Holder h; 232 | h._type = BYTE; 233 | h.holder_integer = value; 234 | return h; 235 | } 236 | Holder Holder::create_boolean(bool value) { 237 | Holder h; 238 | h._type = BOOLEAN; 239 | h.holder_boolean = value; 240 | return h; 241 | } 242 | Holder Holder::create_int16(int16_t value) { 243 | Holder h; 244 | h._type = INT16; 245 | h.holder_integer = value; 246 | return h; 247 | } 248 | Holder Holder::create_uint16(uint16_t value) { 249 | Holder h; 250 | h._type = UINT16; 251 | h.holder_integer = value; 252 | return h; 253 | } 254 | Holder Holder::create_int32(int32_t value) { 255 | Holder h; 256 | h._type = INT32; 257 | h.holder_integer = value; 258 | return h; 259 | } 260 | Holder Holder::create_uint32(uint32_t value) { 261 | Holder h; 262 | h._type = UINT32; 263 | h.holder_integer = value; 264 | return h; 265 | } 266 | Holder Holder::create_int64(int64_t value) { 267 | Holder h; 268 | h._type = INT64; 269 | h.holder_integer = value; 270 | return h; 271 | } 272 | Holder Holder::create_uint64(uint64_t value) { 273 | Holder h; 274 | h._type = UINT64; 275 | h.holder_integer = value; 276 | return h; 277 | } 278 | Holder Holder::create_double(double value) { 279 | Holder h; 280 | h._type = DOUBLE; 281 | h.holder_double = value; 282 | return h; 283 | } 284 | Holder Holder::create_string(const char* str) { 285 | Holder h; 286 | h._type = STRING; 287 | h.holder_string = std::string(str); 288 | return h; 289 | } 290 | Holder Holder::create_object_path(const char* str) { 291 | Holder h; 292 | h._type = OBJ_PATH; 293 | h.holder_string = std::string(str); 294 | return h; 295 | } 296 | Holder Holder::create_signature(const char* str) { 297 | Holder h; 298 | h._type = SIGNATURE; 299 | h.holder_string = std::string(str); 300 | return h; 301 | } 302 | Holder Holder::create_array() { 303 | Holder h; 304 | h._type = ARRAY; 305 | h.holder_array.clear(); 306 | return h; 307 | } 308 | Holder Holder::create_dict() { 309 | Holder h; 310 | h._type = DICT; 311 | h.holder_dict.clear(); 312 | return h; 313 | } 314 | 315 | bool Holder::get_boolean() { return holder_boolean; } 316 | uint8_t Holder::get_byte() { return (uint8_t)(holder_integer & 0x00000000000000FFL); } 317 | int16_t Holder::get_int16() { return (int16_t)(holder_integer & 0x000000000000FFFFL); } 318 | uint16_t Holder::get_uint16() { return (uint16_t)(holder_integer & 0x000000000000FFFFL); } 319 | int32_t Holder::get_int32() { return (int32_t)(holder_integer & 0x00000000FFFFFFFFL); } 320 | uint32_t Holder::get_uint32() { return (uint32_t)(holder_integer & 0x00000000FFFFFFFFL); } 321 | int64_t Holder::get_int64() { return (int64_t)holder_integer; } 322 | uint64_t Holder::get_uint64() { return holder_integer; } 323 | double Holder::get_double() { return holder_double; } 324 | std::string Holder::get_string() { return holder_string; } 325 | std::string Holder::get_object_path() { return holder_string; } 326 | std::string Holder::get_signature() { return holder_string; } 327 | std::vector Holder::get_array() { return holder_array; } 328 | std::map Holder::get_dict() { return holder_dict; } 329 | 330 | void Holder::array_append(Holder holder) { holder_array.push_back(holder); } 331 | void Holder::dict_append(std::string key, Holder value) { holder_dict[key] = value; } 332 | -------------------------------------------------------------------------------- /src-ble/windows/NativeBleInternal.cpp: -------------------------------------------------------------------------------- 1 | #pragma comment(lib, "windowsapp") 2 | #include "NativeBleInternal.h" 3 | 4 | #include "winrt/Windows.Devices.Bluetooth.Advertisement.h" 5 | #include "winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h" 6 | #include "winrt/Windows.Foundation.h" 7 | #include "winrt/Windows.Foundation.Collections.h" 8 | #include "winrt/Windows.Storage.Streams.h" 9 | #include "winrt/base.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | using namespace NativeBLE; 20 | 21 | NativeBleInternal::NativeBleInternal() : device(nullptr), scanning(false) {} 22 | 23 | NativeBleInternal::~NativeBleInternal() {} 24 | 25 | void NativeBleInternal::setup(CallbackHolder callback_holder) { 26 | this->callback_holder = callback_holder; 27 | winrt::init_apartment(); 28 | } 29 | 30 | void NativeBleInternal::scan_start() { 31 | detected_addresses.clear(); 32 | scanner = Advertisement::BluetoothLEAdvertisementWatcher(); 33 | scanner.ScanningMode(Advertisement::BluetoothLEScanningMode::Active); 34 | scanner.Received([&](const auto& w, const Advertisement::BluetoothLEAdvertisementReceivedEventArgs args) { 35 | DeviceDescriptor descriptor; 36 | std::stringstream helper; 37 | helper << std::hex << args.BluetoothAddress(); 38 | descriptor.name = winrt::to_string(args.Advertisement().LocalName()); 39 | descriptor.address = helper.str(); 40 | if (descriptor.name.length() > 0 && detected_addresses.find(descriptor.address) == detected_addresses.end()) { 41 | // If the detected address has not been seen before, add it to the list and notify the callback. 42 | detected_addresses.insert(descriptor.address); 43 | callback_holder.callback_on_scan_found(descriptor); 44 | } 45 | }); 46 | scanner.Start(); 47 | scanning = true; 48 | callback_holder.callback_on_scan_start(); 49 | } 50 | 51 | void NativeBleInternal::scan_stop() { 52 | if (scanning) { 53 | scanner.Stop(); 54 | callback_holder.callback_on_scan_stop(); 55 | scanning = false; 56 | } 57 | } 58 | 59 | bool NativeBleInternal::scan_is_active() { return scanning; } 60 | 61 | void NativeBleInternal::scan_timeout(int32_t timeout_ms) { 62 | this->scan_start(); 63 | std::this_thread::sleep_for(std::chrono::milliseconds(timeout_ms)); 64 | this->scan_stop(); 65 | } 66 | 67 | bool NativeBleInternal::is_connected() { 68 | if (device != nullptr && device.ConnectionStatus() == BluetoothConnectionStatus::Connected) { 69 | return true; 70 | } 71 | return false; 72 | } 73 | 74 | void NativeBleInternal::connect(const BluetoothAddress& address) { 75 | if (scanning) { 76 | this->scan_stop(); 77 | } 78 | 79 | std::string addr = format_mac_address(address); 80 | 81 | uint64_t ble_address = strtoull(addr.c_str(), nullptr, 16); 82 | device = BluetoothLEDevice::FromBluetoothAddressAsync(ble_address).get(); 83 | device.ConnectionStatusChanged([=](const BluetoothLEDevice device, const auto args) { 84 | if (device.ConnectionStatus() == BluetoothConnectionStatus::Connected) { 85 | } else { 86 | this->disconnect_execute(); 87 | callback_holder.callback_on_device_disconnected("Disconnected from device."); 88 | } 89 | }); 90 | 91 | // We'll now cache all services and characteristics, because if not the underlying objects will be garbage 92 | // collected. 93 | auto services_result = device.GetGattServicesAsync(BluetoothCacheMode::Uncached).get(); 94 | if (services_result.Status() == GenericAttributeProfile::GattCommunicationStatus::Success) { 95 | auto gatt_services = services_result.Services(); 96 | for (GattDeviceService&& service : gatt_services) { 97 | // For each service... 98 | auto characteristics_result = service.GetCharacteristicsAsync(BluetoothCacheMode::Uncached).get(); 99 | if (characteristics_result.Status() == GattCommunicationStatus::Success) { 100 | auto gatt_characteristics = characteristics_result.Characteristics(); 101 | for (GattCharacteristic&& characteristic : gatt_characteristics) { 102 | // For each characteristic... 103 | // Store the underlying object pointer in a map. 104 | std::string service_uuid = guid_to_uuid(service.Uuid()); 105 | std::string characteristic_uuid = guid_to_uuid(characteristic.Uuid()); 106 | characteristics_map[service_uuid].emplace(characteristic_uuid, characteristic); 107 | } 108 | } else { 109 | this->disconnect_execute(); 110 | callback_holder.callback_on_device_disconnected("Could not discover characteristics for service."); 111 | return; 112 | } 113 | } 114 | } else { 115 | this->disconnect_execute(); 116 | callback_holder.callback_on_device_disconnected("Could not discover services for device."); 117 | return; 118 | } 119 | 120 | callback_holder.callback_on_device_connected(); 121 | } 122 | 123 | void NativeBleInternal::write_request(BluetoothUUID service_uuid, BluetoothUUID characteristic_uuid, DataChunk& data) { 124 | winrt::guid service_guid = uuid_to_guid(service_uuid); 125 | winrt::guid characteristic_guid = uuid_to_guid(characteristic_uuid); 126 | 127 | GattCharacteristic* gatt_characteristic = fetch_characteristic(service_uuid, characteristic_uuid); 128 | uint32_t gatt_characteristic_prop = (uint32_t)gatt_characteristic->CharacteristicProperties(); 129 | 130 | if (gatt_characteristic != nullptr && 131 | (gatt_characteristic_prop & (uint32_t)GattCharacteristicProperties::Write) != 0) { 132 | auto writer = winrt::Windows::Storage::Streams::DataWriter(); 133 | std::vector data_buffer; 134 | for (int i = 0; i < data.length(); i++) { 135 | data_buffer.push_back(data[i]); 136 | } 137 | writer.WriteBytes(data_buffer); 138 | 139 | auto status = 140 | gatt_characteristic->WriteValueAsync(writer.DetachBuffer(), GattWriteOption::WriteWithResponse).get(); 141 | if (status == GenericAttributeProfile::GattCommunicationStatus::Success) { 142 | } 143 | } 144 | } 145 | 146 | void NativeBleInternal::write_command(BluetoothUUID service_uuid, BluetoothUUID characteristic_uuid, DataChunk& data) { 147 | winrt::guid service_guid = uuid_to_guid(service_uuid); 148 | winrt::guid characteristic_guid = uuid_to_guid(characteristic_uuid); 149 | 150 | GattCharacteristic* gatt_characteristic = fetch_characteristic(service_uuid, characteristic_uuid); 151 | uint32_t gatt_characteristic_prop = (uint32_t)gatt_characteristic->CharacteristicProperties(); 152 | 153 | if (gatt_characteristic != nullptr && 154 | (gatt_characteristic_prop & (uint32_t)GattCharacteristicProperties::WriteWithoutResponse) != 0) { 155 | auto writer = winrt::Windows::Storage::Streams::DataWriter(); 156 | std::vector data_buffer; 157 | for (int i = 0; i < data.length(); i++) { 158 | data_buffer.push_back(data[i]); 159 | } 160 | writer.WriteBytes(data_buffer); 161 | 162 | auto status = 163 | gatt_characteristic->WriteValueAsync(writer.DetachBuffer(), GattWriteOption::WriteWithoutResponse).get(); 164 | if (status == GenericAttributeProfile::GattCommunicationStatus::Success) { 165 | } 166 | } 167 | } 168 | 169 | void NativeBleInternal::read(BluetoothUUID service_uuid, BluetoothUUID characteristic_uuid, 170 | std::function callback_on_read) { 171 | winrt::guid service_guid = uuid_to_guid(service_uuid); 172 | winrt::guid characteristic_guid = uuid_to_guid(characteristic_uuid); 173 | 174 | GattCharacteristic* gatt_characteristic = fetch_characteristic(service_uuid, characteristic_uuid); 175 | uint32_t gatt_characteristic_prop = (uint32_t)gatt_characteristic->CharacteristicProperties(); 176 | 177 | if (gatt_characteristic != nullptr) { 178 | auto result = gatt_characteristic->ReadValueAsync().get(); 179 | 180 | if (result.Status() == GenericAttributeProfile::GattCommunicationStatus::Success) { 181 | auto reader = winrt::Windows::Storage::Streams::DataReader::FromBuffer(result.Value()); 182 | uint32_t recv_size = reader.UnconsumedBufferLength(); 183 | std::vector recv_buffer(recv_size); 184 | reader.ReadBytes(recv_buffer); 185 | callback_on_read(recv_buffer.data(), recv_size); 186 | } 187 | } 188 | } 189 | 190 | void NativeBleInternal::notify(BluetoothUUID service_uuid, BluetoothUUID characteristic_uuid, 191 | std::function callback_on_notify) { 192 | GattCharacteristic* gatt_characteristic = fetch_characteristic(service_uuid, characteristic_uuid); 193 | uint32_t gatt_characteristic_prop = (uint32_t)gatt_characteristic->CharacteristicProperties(); 194 | 195 | if (gatt_characteristic != nullptr && 196 | (gatt_characteristic_prop & (uint32_t)GattCharacteristicProperties::Notify) != 0) { 197 | auto status = gatt_characteristic 198 | ->WriteClientCharacteristicConfigurationDescriptorAsync( 199 | GattClientCharacteristicConfigurationDescriptorValue::Notify) 200 | .get(); 201 | if (status == GenericAttributeProfile::GattCommunicationStatus::Success) { 202 | gatt_characteristic->ValueChanged( 203 | [=](const GattCharacteristic sender, const GattValueChangedEventArgs args) { 204 | auto reader = winrt::Windows::Storage::Streams::DataReader::FromBuffer(args.CharacteristicValue()); 205 | uint32_t recv_size = reader.UnconsumedBufferLength(); 206 | std::vector recv_buffer(recv_size); 207 | reader.ReadBytes(recv_buffer); 208 | callback_on_notify(recv_buffer.data(), recv_size); 209 | }); 210 | } 211 | } 212 | } 213 | 214 | void NativeBleInternal::indicate(BluetoothUUID service_uuid, BluetoothUUID characteristic_uuid, 215 | std::function callback_on_indicate) { 216 | GattCharacteristic* gatt_characteristic = fetch_characteristic(service_uuid, characteristic_uuid); 217 | uint32_t gatt_characteristic_prop = (uint32_t)gatt_characteristic->CharacteristicProperties(); 218 | 219 | if (gatt_characteristic != nullptr && 220 | (gatt_characteristic_prop & (uint32_t)GattCharacteristicProperties::Indicate) != 0) { 221 | auto status = gatt_characteristic 222 | ->WriteClientCharacteristicConfigurationDescriptorAsync( 223 | GattClientCharacteristicConfigurationDescriptorValue::Indicate) 224 | .get(); 225 | if (status == GenericAttributeProfile::GattCommunicationStatus::Success) { 226 | gatt_characteristic->ValueChanged( 227 | [=](const GattCharacteristic sender, const GattValueChangedEventArgs args) { 228 | auto reader = winrt::Windows::Storage::Streams::DataReader::FromBuffer(args.CharacteristicValue()); 229 | uint32_t recv_size = reader.UnconsumedBufferLength(); 230 | std::vector recv_buffer(recv_size); 231 | reader.ReadBytes(recv_buffer); 232 | callback_on_indicate(recv_buffer.data(), recv_size); 233 | }); 234 | } 235 | } 236 | } 237 | 238 | void NativeBleInternal::unsubscribe(BluetoothUUID service_uuid, BluetoothUUID characteristic_uuid) { 239 | // TODO: IMPLEMENT 240 | } 241 | 242 | void NativeBleInternal::disconnect_execute() { 243 | if (device != nullptr) { 244 | auto services = device.GetGattServicesAsync().get().Services(); 245 | for (GattDeviceService s : services) { 246 | s.Close(); 247 | } 248 | device.Close(); 249 | characteristics_map.clear(); 250 | device = nullptr; 251 | } 252 | } 253 | 254 | void NativeBleInternal::disconnect() { 255 | if (is_connected()) { 256 | disconnect_execute(); 257 | callback_holder.callback_on_device_disconnected("Manually disconnected"); 258 | } 259 | } 260 | 261 | void NativeBleInternal::dispose() {} 262 | 263 | // ----- Start of auxiliary functions. ----- 264 | 265 | GattCharacteristic* NativeBleInternal::fetch_characteristic(const std::string& service_uuid, 266 | const std::string& characteristic_uuid) { 267 | if (characteristics_map.count(service_uuid) == 1) { 268 | if (characteristics_map[service_uuid].count(characteristic_uuid) == 1) { 269 | return &characteristics_map[service_uuid].at(characteristic_uuid); 270 | } 271 | } 272 | return nullptr; 273 | } 274 | 275 | winrt::guid NativeBleInternal::uuid_to_guid(const std::string& uuid) { 276 | // TODO: Add proper cleanup / validation 277 | std::stringstream helper; 278 | for (int i = 0; i < uuid.length(); i++) { 279 | if (uuid[i] != '-') { 280 | helper << uuid[i]; 281 | } 282 | } 283 | std::string clean_uuid = helper.str(); 284 | winrt::guid guid; 285 | uint64_t* data4_ptr = (uint64_t*)guid.Data4; 286 | 287 | guid.Data1 = std::strtoul(clean_uuid.substr(0, 8).c_str(), nullptr, 16); 288 | guid.Data2 = std::strtoul(clean_uuid.substr(8, 4).c_str(), nullptr, 16); 289 | guid.Data3 = std::strtoul(clean_uuid.substr(12, 4).c_str(), nullptr, 16); 290 | *data4_ptr = _byteswap_uint64(std::strtoull(clean_uuid.substr(16, 16).c_str(), nullptr, 16)); 291 | 292 | return guid; 293 | } 294 | 295 | std::string NativeBleInternal::guid_to_uuid(const winrt::guid& guid) { 296 | std::stringstream helper; 297 | 298 | for (uint32_t i = 0; i < 4; i++) { 299 | // * NOTE: We're performing a byte swap! 300 | helper << std::hex << std::setw(2) << std::setfill('0') << (int)((uint8_t*)&guid.Data1)[3 - i]; 301 | } 302 | helper << '-'; 303 | for (uint32_t i = 0; i < 2; i++) { 304 | // * NOTE: We're performing a byte swap! 305 | helper << std::hex << std::setw(2) << std::setfill('0') << (int)((uint8_t*)&guid.Data2)[1 - i]; 306 | } 307 | helper << '-'; 308 | for (uint32_t i = 0; i < 2; i++) { 309 | // * NOTE: We're performing a byte swap! 310 | helper << std::hex << std::setw(2) << std::setfill('0') << (int)((uint8_t*)&guid.Data3)[1 - i]; 311 | } 312 | helper << '-'; 313 | for (uint32_t i = 0; i < 2; i++) { 314 | helper << std::hex << std::setw(2) << std::setfill('0') << (int)guid.Data4[i]; 315 | } 316 | helper << '-'; 317 | for (uint32_t i = 0; i < 6; i++) { 318 | helper << std::hex << std::setw(2) << std::setfill('0') << (int)guid.Data4[2 + i]; 319 | } 320 | return helper.str(); 321 | } 322 | 323 | // Format all mac addresses to Windows format (e.g. abcdef123456) 324 | BluetoothAddress NativeBleInternal::format_mac_address(std::string address) { 325 | std::string new_addr; 326 | for (int i = 0; i < address.length(); i++) { 327 | if (address[i] >= 'A' && address[i] <= 'F') { 328 | new_addr.push_back(address[i] - 32); //cast to lower case 329 | } else if ((address[i] >= 'a' && address[i] <= 'f') || (address[i] >= '0' && address[i] <= '9')) { 330 | new_addr.push_back(address[i]); //add characters that are only valid to a MAC address 331 | } 332 | } 333 | 334 | return (BluetoothAddress) new_addr; 335 | } 336 | -------------------------------------------------------------------------------- /src-ble/linux/simpledbus/base/Message.cpp: -------------------------------------------------------------------------------- 1 | #include "Message.h" 2 | 3 | #include 4 | #include 5 | 6 | using namespace SimpleDBus; 7 | 8 | int Message::creation_counter = 0; 9 | 10 | Message::Message() : Message(nullptr) {} 11 | 12 | Message::Message(DBusMessage* msg) : _msg(msg), _iter_initialized(false), _is_extracted(false), indent(0) { 13 | if (is_valid()) { 14 | _unique_id = creation_counter++; 15 | } else { 16 | _unique_id = -1; 17 | } 18 | } 19 | 20 | Message::~Message() { 21 | if (is_valid()) { 22 | _safe_delete(); 23 | } 24 | } 25 | 26 | Message::Message(Message&& other) : Message() { 27 | // Move constructor: Other needs to be completely cleared. 28 | // Copy all fields over directly. 29 | indent = other.indent; 30 | 31 | this->_unique_id = other._unique_id; 32 | this->_iter_initialized = other._iter_initialized; 33 | this->_is_extracted = other._is_extracted; 34 | this->_extracted = other._extracted; 35 | this->_msg = other._msg; 36 | this->_iter = other._iter; 37 | 38 | // Invalidate the old message. 39 | other._invalidate(); 40 | } 41 | 42 | Message::Message(const Message& other) : Message() { 43 | // Copy assignment: We need a completely new message and preserve the old one. 44 | // After a safe deletion, a copy only needs to be made if the other message is valid. 45 | if (other.is_valid()) { 46 | // Copy all fields over directly 47 | indent = other.indent; 48 | 49 | this->_unique_id = creation_counter++; 50 | this->_is_extracted = other._is_extracted; 51 | this->_extracted = other._extracted; 52 | this->_msg = dbus_message_copy(other._msg); 53 | } 54 | } 55 | 56 | Message& Message::operator=(Message&& other) { 57 | // Move assignment: Other needs to be completely cleared. 58 | if (this != &other) { 59 | _safe_delete(); 60 | // Copy all fields over directly. 61 | indent = other.indent; 62 | 63 | this->_unique_id = other._unique_id; 64 | this->_iter_initialized = other._iter_initialized; 65 | this->_is_extracted = other._is_extracted; 66 | this->_extracted = other._extracted; 67 | this->_msg = other._msg; 68 | this->_iter = other._iter; 69 | 70 | // Invalidate the old message. 71 | other._invalidate(); 72 | } 73 | 74 | return *this; 75 | } 76 | 77 | Message& Message::operator=(const Message& other) { 78 | // Copy assignment: We need a completely new message and preserve the old one. 79 | if (this != &other) { 80 | _safe_delete(); 81 | // After a safe deletion, a copy only needs to be made if the other message is valid. 82 | if (other.is_valid()) { 83 | // Copy all fields over directly 84 | indent = other.indent; 85 | 86 | this->_unique_id = creation_counter++; 87 | this->_is_extracted = other._is_extracted; 88 | this->_extracted = other._extracted; 89 | this->_msg = dbus_message_copy(other._msg); 90 | } 91 | } 92 | 93 | return *this; 94 | } 95 | 96 | void Message::_invalidate() { 97 | this->_unique_id = -1; 98 | this->_msg = nullptr; 99 | this->_iter_initialized = false; 100 | this->_is_extracted = false; 101 | this->_extracted = Holder(); 102 | 103 | #ifdef DBUS_MESSAGE_ITER_INIT_CLOSED 104 | this->_iter = DBUS_MESSAGE_ITER_INIT_CLOSED; 105 | #else 106 | // For older versions of DBus, DBUS_MESSAGE_ITER_INIT_CLOSED is not defined. 107 | this->_iter = DBusMessageIter(); 108 | #endif 109 | } 110 | 111 | void Message::_safe_delete() { 112 | if (is_valid()) { 113 | dbus_message_unref(this->_msg); 114 | _invalidate(); 115 | } 116 | } 117 | 118 | bool Message::is_valid() const { return _msg != nullptr; } 119 | 120 | void Message::_append_argument(DBusMessageIter* iter, Holder& argument, std::string signature) { 121 | switch (signature[0]) { 122 | case DBUS_TYPE_BYTE: { 123 | uint8_t value = argument.get_byte(); 124 | dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &value); 125 | break; 126 | } 127 | case DBUS_TYPE_BOOLEAN: { 128 | bool value = argument.get_boolean(); 129 | dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); 130 | break; 131 | } 132 | case DBUS_TYPE_INT16: { 133 | int16_t value = argument.get_int16(); 134 | dbus_message_iter_append_basic(iter, DBUS_TYPE_INT16, &value); 135 | break; 136 | } 137 | case DBUS_TYPE_UINT16: { 138 | uint16_t value = argument.get_uint16(); 139 | dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &value); 140 | break; 141 | } 142 | case DBUS_TYPE_INT32: { 143 | int32_t value = argument.get_int32(); 144 | dbus_message_iter_append_basic(iter, DBUS_TYPE_INT32, &value); 145 | break; 146 | } 147 | case DBUS_TYPE_UINT32: { 148 | uint32_t value = argument.get_uint32(); 149 | dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &value); 150 | break; 151 | } 152 | case DBUS_TYPE_INT64: { 153 | int64_t value = argument.get_int64(); 154 | dbus_message_iter_append_basic(iter, DBUS_TYPE_INT64, &value); 155 | break; 156 | } 157 | case DBUS_TYPE_UINT64: { 158 | uint64_t value = argument.get_uint64(); 159 | dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &value); 160 | break; 161 | } 162 | case DBUS_TYPE_DOUBLE: { 163 | double value = argument.get_double(); 164 | dbus_message_iter_append_basic(iter, DBUS_TYPE_DOUBLE, &value); 165 | break; 166 | } 167 | case DBUS_TYPE_STRING: { 168 | std::string value = argument.get_string(); 169 | const char* p_value = value.c_str(); 170 | dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &p_value); 171 | break; 172 | } 173 | case DBUS_TYPE_OBJECT_PATH: { 174 | std::string value = argument.get_object_path(); 175 | const char* p_value = value.c_str(); 176 | dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &p_value); 177 | break; 178 | } 179 | case DBUS_TYPE_SIGNATURE: { 180 | std::string value = argument.get_signature(); 181 | const char* p_value = value.c_str(); 182 | dbus_message_iter_append_basic(iter, DBUS_TYPE_SIGNATURE, &p_value); 183 | break; 184 | } 185 | case DBUS_TYPE_VARIANT: { 186 | DBusMessageIter sub_iter; 187 | std::string signature = argument.signature(); 188 | dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, signature.c_str(), &sub_iter); 189 | _append_argument(&sub_iter, argument, signature); 190 | dbus_message_iter_close_container(iter, &sub_iter); 191 | break; 192 | } 193 | 194 | case DBUS_TYPE_ARRAY: { 195 | auto sig_next = signature.substr(1); 196 | DBusMessageIter sub_iter; 197 | dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, sig_next.c_str(), &sub_iter); 198 | if (sig_next[0] != DBUS_DICT_ENTRY_BEGIN_CHAR) { 199 | auto array_contents = argument.get_array(); 200 | for (auto elem : array_contents) { 201 | _append_argument(&sub_iter, elem, sig_next); 202 | } 203 | } else { 204 | sig_next = sig_next.substr(1, sig_next.length() - 2); 205 | auto key_sig = sig_next[0]; 206 | auto value_sig = sig_next.substr(1); 207 | auto dict_contents = argument.get_dict(); 208 | for (auto& [key, value] : dict_contents) { 209 | DBusMessageIter entry_iter; 210 | dbus_message_iter_open_container(&sub_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); 211 | switch (key_sig) { 212 | case DBUS_TYPE_STRING: { 213 | const char* p_value = key.c_str(); 214 | dbus_message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &p_value); 215 | break; 216 | } 217 | case DBUS_TYPE_OBJECT_PATH: { 218 | const char* p_value = key.c_str(); 219 | dbus_message_iter_append_basic(&entry_iter, DBUS_TYPE_OBJECT_PATH, &p_value); 220 | break; 221 | } 222 | case DBUS_TYPE_SIGNATURE: { 223 | const char* p_value = key.c_str(); 224 | dbus_message_iter_append_basic(&entry_iter, DBUS_TYPE_SIGNATURE, &p_value); 225 | break; 226 | } 227 | } 228 | _append_argument(&entry_iter, value, value_sig); 229 | dbus_message_iter_close_container(&sub_iter, &entry_iter); 230 | } 231 | } 232 | dbus_message_iter_close_container(iter, &sub_iter); 233 | } 234 | } 235 | } 236 | 237 | void Message::append_argument(Holder argument, std::string signature) { 238 | dbus_message_iter_init_append(_msg, &_iter); 239 | _append_argument(&_iter, argument, signature); 240 | } 241 | 242 | int32_t Message::get_unique_id() { return _unique_id; } 243 | 244 | uint32_t Message::get_serial() { 245 | if (is_valid()) { 246 | return dbus_message_get_serial(_msg); 247 | } else { 248 | return 0; 249 | } 250 | } 251 | 252 | std::string Message::get_signature() { 253 | if (is_valid() && _iter_initialized) { 254 | return dbus_message_iter_get_signature(&_iter); 255 | } else { 256 | return ""; 257 | } 258 | } 259 | 260 | MessageType Message::get_type() { 261 | if (is_valid()) { 262 | return (MessageType)dbus_message_get_type(_msg); 263 | } else { 264 | return MessageType::INVALID; 265 | } 266 | } 267 | 268 | std::string Message::get_path() { 269 | if (is_valid() && get_type() == MessageType::SIGNAL) { 270 | return dbus_message_get_path(_msg); 271 | } else { 272 | return ""; 273 | } 274 | } 275 | 276 | std::string Message::get_interface() { 277 | if (is_valid()) { 278 | return dbus_message_get_interface(_msg); 279 | } else { 280 | return ""; 281 | } 282 | } 283 | 284 | bool Message::is_signal(std::string interface, std::string signal_name) { 285 | return is_valid() && dbus_message_is_signal(_msg, interface.c_str(), signal_name.c_str()); 286 | } 287 | 288 | static const char* type_to_name(int message_type) { 289 | switch (message_type) { 290 | case DBUS_MESSAGE_TYPE_SIGNAL: 291 | return "signal"; 292 | case DBUS_MESSAGE_TYPE_METHOD_CALL: 293 | return "method call"; 294 | case DBUS_MESSAGE_TYPE_METHOD_RETURN: 295 | return "method return"; 296 | case DBUS_MESSAGE_TYPE_ERROR: 297 | return "error"; 298 | default: 299 | return "(unknown message type)"; 300 | } 301 | } 302 | 303 | std::string Message::to_string() const { 304 | if (!is_valid()) { 305 | return "INVALID"; 306 | } 307 | 308 | std::ostringstream oss; 309 | 310 | const char* sender; 311 | const char* destination; 312 | 313 | sender = dbus_message_get_sender(_msg); 314 | sender = sender ? sender : "(null)"; 315 | destination = dbus_message_get_destination(_msg); 316 | destination = destination ? destination : "(null)"; 317 | 318 | oss << "[" << _unique_id << "] " << type_to_name(dbus_message_get_type(_msg)); 319 | oss << "[" << sender << "->" << destination << "] "; 320 | oss << dbus_message_get_path(_msg) << " " << dbus_message_get_interface(_msg) << " " 321 | << dbus_message_get_member(_msg) << " "; 322 | 323 | return oss.str(); 324 | } 325 | 326 | Holder Message::extract() { 327 | if (!_is_extracted) { 328 | if (!_iter_initialized) { 329 | extract_reset(); 330 | } 331 | _extracted = _extract_generic(&_iter); 332 | _is_extracted = true; 333 | } 334 | return _extracted; 335 | } 336 | 337 | void Message::extract_reset() { 338 | if (is_valid()) { 339 | dbus_message_iter_init(_msg, &_iter); 340 | _iter_initialized = true; 341 | } 342 | } 343 | 344 | bool Message::extract_has_next() { return _iter_initialized && dbus_message_iter_has_next(&_iter); } 345 | 346 | void Message::extract_next() { 347 | if (extract_has_next()) { 348 | dbus_message_iter_next(&_iter); 349 | _is_extracted = false; 350 | } 351 | } 352 | 353 | Holder Message::_extract_bytearray(DBusMessageIter* iter) { 354 | const unsigned char* bytes; 355 | int len; 356 | dbus_message_iter_get_fixed_array(iter, &bytes, &len); 357 | Holder holder_array = Holder::create_array(); 358 | for (int i = 0; i < len; i++) { 359 | holder_array.array_append(Holder::create_byte(bytes[i])); 360 | } 361 | return holder_array; 362 | } 363 | 364 | Holder Message::_extract_array(DBusMessageIter* iter) { 365 | Holder holder_array = Holder::create_array(); 366 | indent += 1; 367 | int current_type = dbus_message_iter_get_arg_type(iter); 368 | if (current_type == DBUS_TYPE_BYTE) { 369 | holder_array = _extract_bytearray(iter); 370 | } else { 371 | while ((current_type = dbus_message_iter_get_arg_type(iter)) != DBUS_TYPE_INVALID) { 372 | Holder h = _extract_generic(iter); 373 | if (h.type() != NONE) { 374 | holder_array.array_append(h); 375 | } 376 | dbus_message_iter_next(iter); 377 | } 378 | } 379 | indent -= 1; 380 | return holder_array; 381 | } 382 | 383 | Holder Message::_extract_dict(DBusMessageIter* iter) { 384 | Holder holder_dict = Holder::create_dict(); 385 | indent += 1; 386 | int current_type; 387 | 388 | // Loop through all dictionary entries. 389 | while ((current_type = dbus_message_iter_get_arg_type(iter)) != DBUS_TYPE_INVALID) { 390 | // Access the dictionary entry 391 | DBusMessageIter sub; 392 | dbus_message_iter_recurse(iter, &sub); 393 | // Extract the data from the dictionary entry 394 | Holder key = _extract_generic(&sub); 395 | dbus_message_iter_next(&sub); 396 | Holder value = _extract_generic(&sub); 397 | bool key_is_text = key.type() == STRING || key.type() == OBJ_PATH || key.type() == SIGNATURE; 398 | if (key_is_text && value.type() != NONE) { 399 | holder_dict.dict_append(key.get_string(), value); 400 | } 401 | dbus_message_iter_next(iter); 402 | } 403 | indent -= 1; 404 | return holder_dict; 405 | } 406 | 407 | Holder Message::_extract_generic(DBusMessageIter* iter) { 408 | int current_type = dbus_message_iter_get_arg_type(iter); 409 | if (current_type != DBUS_TYPE_INVALID) { 410 | // for (int i = 0; i < indent; i++) { 411 | // std::cout << '\t'; 412 | // } 413 | // std::cout << "Type: " << (char)current_type << std::endl; 414 | switch (current_type) { 415 | case DBUS_TYPE_BYTE: { 416 | uint8_t contents; 417 | dbus_message_iter_get_basic(iter, &contents); 418 | return Holder::create_byte(contents); 419 | break; 420 | } 421 | case DBUS_TYPE_BOOLEAN: { 422 | bool contents; 423 | dbus_message_iter_get_basic(iter, &contents); 424 | return Holder::create_boolean(contents); 425 | break; 426 | } 427 | case DBUS_TYPE_INT16: { 428 | int16_t contents; 429 | dbus_message_iter_get_basic(iter, &contents); 430 | return Holder::create_int16(contents); 431 | break; 432 | } 433 | case DBUS_TYPE_UINT16: { 434 | uint16_t contents; 435 | dbus_message_iter_get_basic(iter, &contents); 436 | return Holder::create_uint16(contents); 437 | break; 438 | } 439 | case DBUS_TYPE_INT32: { 440 | int32_t contents; 441 | dbus_message_iter_get_basic(iter, &contents); 442 | return Holder::create_int32(contents); 443 | break; 444 | } 445 | case DBUS_TYPE_UINT32: { 446 | uint32_t contents; 447 | dbus_message_iter_get_basic(iter, &contents); 448 | return Holder::create_uint32(contents); 449 | break; 450 | } 451 | case DBUS_TYPE_INT64: { 452 | int64_t contents; 453 | dbus_message_iter_get_basic(iter, &contents); 454 | return Holder::create_int64(contents); 455 | break; 456 | } 457 | case DBUS_TYPE_UINT64: { 458 | uint64_t contents; 459 | dbus_message_iter_get_basic(iter, &contents); 460 | return Holder::create_uint64(contents); 461 | break; 462 | } 463 | case DBUS_TYPE_DOUBLE: { 464 | double contents; 465 | dbus_message_iter_get_basic(iter, &contents); 466 | return Holder::create_double(contents); 467 | break; 468 | } 469 | case DBUS_TYPE_STRING: { 470 | char* contents; 471 | dbus_message_iter_get_basic(iter, &contents); 472 | return Holder::create_string(contents); 473 | break; 474 | } 475 | case DBUS_TYPE_OBJECT_PATH: { 476 | char* contents; 477 | dbus_message_iter_get_basic(iter, &contents); 478 | return Holder::create_object_path(contents); 479 | break; 480 | } 481 | case DBUS_TYPE_SIGNATURE: { 482 | char* contents; 483 | dbus_message_iter_get_basic(iter, &contents); 484 | return Holder::create_signature(contents); 485 | break; 486 | } 487 | case DBUS_TYPE_ARRAY: { 488 | DBusMessageIter sub; 489 | dbus_message_iter_recurse(iter, &sub); 490 | int sub_type = dbus_message_iter_get_arg_type(&sub); 491 | if (sub_type == DBUS_TYPE_DICT_ENTRY) { 492 | return _extract_dict(&sub); 493 | } else { 494 | return _extract_array(&sub); 495 | } 496 | break; 497 | } 498 | case DBUS_TYPE_VARIANT: { 499 | DBusMessageIter sub; 500 | dbus_message_iter_recurse(iter, &sub); 501 | indent += 1; 502 | Holder h = _extract_generic(&sub); 503 | indent -= 1; 504 | return h; 505 | break; 506 | } 507 | } 508 | } 509 | return Holder(); 510 | } 511 | 512 | Message Message::create_method_call(std::string bus_name, std::string path, std::string interface, std::string method) { 513 | return Message(dbus_message_new_method_call(bus_name.c_str(), path.c_str(), interface.c_str(), method.c_str())); 514 | } 515 | --------------------------------------------------------------------------------