下面是一个完整的 Ubuntu C++ 项目,使用 gattlib-0.7.2 库实现 BLE 设备的扫描、连接和通知功能,并使用 CMake 进行编译。
项目结构
text
ble_project/ ├── CMakeLists.txt ├── include/ │ └── ble_scanner.h ├── src/ │ ├── ble_scanner.cpp │ └── main.cpp └── third_party/ └── gattlib/ └── (gattlib 头文件和库文件)
1. 安装依赖
首先安装必要的依赖包:
bash
sudo apt-get updatesudo apt-get install -y \ cmake \ build-essential \ pkg-config \ libbluetooth-dev \ libglib2.0-dev \ libboost-system-dev \ libboost-thread-dev
2. 下载和编译 gattlib
bash
# 下载 gattlibcd third_partygit clone https://github.com/labapart/gattlib.gitcd gattlib# 编译和安装mkdir build && cd build cmake ..makesudo make install
3. CMakeLists.txt
cmake
cmake_minimum_required(VERSION 3.10)project(BLE_Scanner)set(CMAKE_CXX_STANDARD 11)set(CMAKE_CXX_STANDARD_REQUIRED ON)# 查找依赖包find_package(PkgConfig REQUIRED)pkg_check_modules(GATTLIB REQUIRED gattlib)pkg_check_modules(GLIB2 REQUIRED glib-2.0)pkg_check_modules(BLUEZ REQUIRED bluez)# 包含目录include_directories( ${CMAKE_SOURCE_DIR}/include ${GATTLIB_INCLUDE_DIRS} ${GLIB2_INCLUDE_DIRS})# 添加可执行文件add_executable(ble_scanner src/main.cpp src/ble_scanner.cpp)# 链接库target_link_libraries(ble_scanner ${GATTLIB_LIBRARIES} ${GLIB2_LIBRARIES} ${BLUEZ_LIBRARIES} pthread dl)# 安装规则install(TARGETS ble_scanner DESTINATION bin)
4. 头文件 (include/ble_scanner.h)
cpp
#ifndef BLE_SCANNER_H#define BLE_SCANNER_H#include <gattlib.h>#include <string>#include <vector>#include <functional>#include <memory>// BLE 设备信息结构struct BLEDevice { std::string address; std::string name; int rssi;};// 特征值通知回调类型using NotificationCallback = std::function<void(const uint8_t* data, size_t length)>;class BLEScanner {public: BLEScanner(); ~BLEScanner(); // 扫描设备 bool scanDevices(int timeout_sec, std::vector<BLEDevice>& devices); // 连接设备 bool connect(const std::string& device_address); // 断开连接 void disconnect(); // 发现服务 bool discoverServices(); // 读取特征值 std::vector<uint8_t> readCharacteristic(const std::string& uuid); // 写入特征值 bool writeCharacteristic(const std::string& uuid, const std::vector<uint8_t>& data); // 启用通知 bool enableNotification(const std::string& uuid, NotificationCallback callback); // 禁用通知 bool disableNotification(const std::string& uuid);private: gatt_connection_t* m_connection; std::string m_connected_device; NotificationCallback m_notification_callback; // 静态回调函数 static void onNotification(const uuid_t* uuid, const uint8_t* data, size_t data_length, void* user_data);};#endif // BLE_SCANNER_H
5. 实现文件 (src/ble_scanner.cpp)
cpp
#include "ble_scanner.h"#include <iostream>#include <chrono>#include <thread>#include <mutex>// 扫描回调函数static void adapter_cb(const char* addr, const char* name, void* user_data) { auto devices = static_cast<std::vector<BLEDevice>*>(user_data); BLEDevice device; device.address = addr ? addr : "Unknown"; device.name = name ? name : "Unknown"; devices->push_back(device);}BLEScanner::BLEScanner() : m_connection(nullptr) {}BLEScanner::~BLEScanner() { disconnect();}bool BLEScanner::scanDevices(int timeout_sec, std::vector<BLEDevice>& devices) { devices.clear(); // 获取默认适配器 const char* adapter_name = nullptr; // 使用默认适配器 int ret = gattlib_adapter_scan_enable(adapter_name, adapter_cb, timeout_sec * 1000, &devices); if (ret != 0) { std::cerr << "Failed to start scanning: " << ret << std::endl; return false; } // 等待扫描完成 std::this_thread::sleep_for(std::chrono::seconds(timeout_sec)); gattlib_adapter_scan_disable(adapter_name); return true;}bool BLEScanner::connect(const std::string& device_address) { if (m_connection) { disconnect(); } const char* adapter_name = nullptr; const char* addr = device_address.c_str(); m_connection = gattlib_connect(adapter_name, addr, GATTLIB_CONNECTION_OPTIONS_LEGACY_DEFAULT); if (!m_connection) { std::cerr << "Failed to connect to device: " << device_address << std::endl; return false; } m_connected_device = device_address; std::cout << "Connected to device: " << device_address << std::endl; return true;}void BLEScanner::disconnect() { if (m_connection) { gattlib_disconnect(m_connection); m_connection = nullptr; m_connected_device.clear(); std::cout << "Disconnected" << std::endl; }}bool BLEScanner::discoverServices() { if (!m_connection) { std::cerr << "Not connected to any device" << std::endl; return false; } gattlib_primary_service_t* services; int services_count; int ret = gattlib_discover_primary_services(m_connection, &services, &services_count); if (ret != 0) { std::cerr << "Failed to discover services: " << ret << std::endl; return false; } std::cout << "Discovered " << services_count << " services:" << std::endl; for (int i = 0; i < services_count; i++) { char uuid_str[MAX_LEN_UUID_STR + 1]; gattlib_uuid_to_string(&services[i].uuid, uuid_str, sizeof(uuid_str)); std::cout << "Service " << i << ": " << uuid_str << std::endl; } gattlib_primary_service_discover_free(services, services_count); return true;}std::vector<uint8_t> BLEScanner::readCharacteristic(const std::string& uuid) { std::vector<uint8_t> data; if (!m_connection) { std::cerr << "Not connected to any device" << std::endl; return data; } uuid_t gattlib_uuid; if (gattlib_string_to_uuid(uuid.c_str(), uuid.length(), &gattlib_uuid) != 0) { std::cerr << "Invalid UUID: " << uuid << std::endl; return data; } uint8_t* buffer; size_t buffer_len; int ret = gattlib_read_char_by_uuid(m_connection, &gattlib_uuid, (void**)&buffer, &buffer_len); if (ret != 0) { std::cerr << "Failed to read characteristic: " << ret << std::endl; return data; } data.assign(buffer, buffer + buffer_len); free(buffer); return data;}bool BLEScanner::writeCharacteristic(const std::string& uuid, const std::vector<uint8_t>& data) { if (!m_connection) { std::cerr << "Not connected to any device" << std::endl; return false; } uuid_t gattlib_uuid; if (gattlib_string_to_uuid(uuid.c_str(), uuid.length(), &gattlib_uuid) != 0) { std::cerr << "Invalid UUID: " << uuid << std::endl; return false; } int ret = gattlib_write_char_by_uuid(m_connection, &gattlib_uuid, (void*)data.data(), data.size()); if (ret != 0) { std::cerr << "Failed to write characteristic: " << ret << std::endl; return false; } return true;}void BLEScanner::onNotification(const uuid_t* uuid, const uint8_t* data, size_t data_length, void* user_data) { BLEScanner* scanner = static_cast<BLEScanner*>(user_data); if (scanner && scanner->m_notification_callback) { scanner->m_notification_callback(data, data_length); }}bool BLEScanner::enableNotification(const std::string& uuid, NotificationCallback callback) { if (!m_connection) { std::cerr << "Not connected to any device" << std::endl; return false; } uuid_t gattlib_uuid; if (gattlib_string_to_uuid(uuid.c_str(), uuid.length(), &gattlib_uuid) != 0) { std::cerr << "Invalid UUID: " << uuid << std::endl; return false; } m_notification_callback = callback; int ret = gattlib_notification_start(m_connection, &gattlib_uuid, onNotification, this); if (ret != 0) { std::cerr << "Failed to enable notification: " << ret << std::endl; return false; } return true;}bool BLEScanner::disableNotification(const std::string& uuid) { if (!m_connection) { std::cerr << "Not connected to any device" << std::endl; return false; } uuid_t gattlib_uuid; if (gattlib_string_to_uuid(uuid.c_str(), uuid.length(), &gattlib_uuid) != 0) { std::cerr << "Invalid UUID: " << uuid << std::endl; return false; } int ret = gattlib_notification_stop(m_connection, &gattlib_uuid); if (ret != 0) { std::cerr << "Failed to disable notification: " << ret << std::endl; return false; } m_notification_callback = nullptr; return true;}
6. 主程序 (src/main.cpp)
cpp
#include "ble_scanner.h"#include <iostream>#include <iomanip>#include <thread>#include <atomic>std::atomic<bool> running(true);// 信号处理函数void signalHandler(int signum) { std::cout << "Interrupt signal received. Exiting..." << std::endl; running = false;}// 通知回调函数示例void notificationCallback(const uint8_t* data, size_t length) { std::cout << "Notification received (" << length << " bytes): "; for (size_t i = 0; i < length; i++) { std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(data[i]) << " "; } std::cout << std::dec << std::endl;}int main() { // 设置信号处理 signal(SIGINT, signalHandler); BLEScanner scanner; // 扫描设备 std::cout << "Scanning for BLE devices..." << std::endl; std::vector<BLEDevice> devices; if (!scanner.scanDevices(5, devices)) { std::cerr << "Scan failed" << std::endl; return 1; } std::cout << "Found " << devices.size() << " devices:" << std::endl; for (size_t i = 0; i < devices.size(); i++) { std::cout << i << ": " << devices[i].address << " - " << devices[i].name << " (RSSI: " << devices[i].rssi << ")" << std::endl; } if (devices.empty()) { std::cout << "No devices found" << std::endl; return 0; } // 连接第一个设备(可以根据需要修改) std::string target_device = devices[0].address; std::cout << "Connecting to: " << target_device << std::endl; if (!scanner.connect(target_device)) { std::cerr << "Connection failed" << std::endl; return 1; } // 发现服务 if (!scanner.discoverServices()) { std::cerr << "Service discovery failed" << std::endl; scanner.disconnect(); return 1; } // 示例:启用通知(需要替换为实际的 UUID) std::string notify_uuid = "00002a37-0000-1000-8000-00805f9b34fb"; // 心率测量 if (scanner.enableNotification(notify_uuid, notificationCallback)) { std::cout << "Notification enabled for UUID: " << notify_uuid << std::endl; // 保持运行以接收通知 while (running) { std::this_thread::sleep_for(std::chrono::seconds(1)); } scanner.disableNotification(notify_uuid); } else { std::cerr << "Failed to enable notification" << std::endl; } scanner.disconnect(); return 0;}
7. 编译和运行
bash
# 创建构建目录mkdir buildcd build# 配置项目cmake ..# 编译make# 运行(需要 root 权限访问蓝牙)sudo ./ble_scanner
8. 注意事项
权限问题:运行程序可能需要 root 权限或蓝牙相关权限
UUID:示例中的 UUID 需要替换为实际设备的 UUID
错误处理:实际应用中需要更完善的错误处理
资源管理:确保正确释放所有资源
9. 常见问题解决
如果遇到链接错误,确保 gattlib 正确安装:
bash
# 检查 gattlib 是否安装pkg-config --exists gattlib && echo "gattlib found" || echo "gattlib not found"# 如果找不到,可以手动指定路径export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH
这个项目提供了完整的 BLE 扫描、连接和通知功能框架,你可以根据实际需求进行修改和扩展。
收藏的用户(0) X
正在加载信息~
推荐阅读
最新回复 (0)
站点信息
- 文章2312
- 用户1336
- 访客11619001
每日一句
Books are passports you never need to renew.
书籍是永不过期的护照。
书籍是永不过期的护照。
You Only Look Once:Unified, Real-Time Object Detection-CVPR-2016
Thinkpad x1 Extreme黑苹果10.14.5安装完成
C++ 11新语法获取系统盘符
cocos2d-x横版ARPG过关游戏
程序员应该使用Linux的7个理由
去除WPS2016个人版自带广告弹窗
x86 emulation currently requires hardware acceleration
数字证书及CA的通俗介绍
Android c++屏幕实时录制
快速入门-如何在Java上使用Redis
请启用虚拟机平台 windows 功能并确保在 bios 中启用虚拟化
diskgenius 保存分区表时出现错误 代码00000032方法解决
.a静态库创建与合并
新会员