一、准备工作
安装blueZ以及相关的蓝牙测试工具:
sudo apt update
sudo apt install bluez-hcidump
sudo apt-get install bluez bluez-tools
sudo apt-get install bluez-tools
apt install libbluetooth-dev
然后看下蓝牙设备是否识别到,已经是否处于开启状态:
root@test-desktop:~# hciconfig -a
hci0: Type: Primary Bus: USB
BD Address: A4:42:3B:2A:47:DD ACL MTU: 1021:4 SCO MTU: 96:6
UP RUNNING PSCAN ISCAN
RX bytes:437421 acl:0 sco:0 events:8518 errors:0
TX bytes:18880 acl:0 sco:0 commands:1564 errors:0
Features: 0xbf 0xfe 0x0f 0xfe 0xdb 0xff 0x7b 0x87
Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3
Link policy: RSWITCH SNIFF
Link mode: SLAVE ACCEPT
Name: 'test-desktop'
Class: 0x1c0000
Service Classes: Rendering, Capturing, Object Transfer
Device Class: Miscellaneous,
HCI Version: 5.1 (0xa) Revision: 0x100
LMP Version: 5.1 (0xa) Subversion: 0x100
Manufacturer: Intel Corp. (2)
UP RUNNING PSCAN ISCAN
说明已经打开。
此时可以通过指令bluetoothctl scan on
来扫描周围的蓝牙设备;
命令bluetoothctl pair 需要连接的蓝牙MAC
可以进行配对,然后再通过bluetoothctl connect 需要连接的蓝牙MAC
连接对应蓝牙。
二、经典蓝牙的编程方式
无论低功耗蓝牙BLE还是经典蓝牙,都可以通过以下方式进行编程处理。
2.1 扫描设备
例程:
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
int main(int argc, char **argv) {
int dev_id, sock, len, max_rsp, flags;
int i;
char addr[19] = {0};
char name[248] = {0};
inquiry_info *ii = NULL;
dev_id = hci_get_route(NULL);
sock = hci_open_dev(dev_id);
if (dev_id < 0 || sock < 0) {
perror("打开设备失败");
exit(1);
}
len = 8;
max_rsp = 255;
flags = IREQ_CACHE_FLUSH;
ii = (inquiry_info *)malloc(max_rsp * sizeof(inquiry_info));
memset(ii, 0, max_rsp * sizeof(inquiry_info));
int num_rsp = hci_inquiry(dev_id, len, max_rsp, NULL, &ii, flags);
if (num_rsp < 0) perror("hci_inquiry");
for (i = 0; i < num_rsp; i++) {
ba2str(&(ii + i)->bdaddr, addr);
memset(name, 0, sizeof(name));
if (hci_read_remote_name(sock, &(ii + i)->bdaddr, sizeof(name), name, 0) < 0)
strcpy(name, "[unknown]");
printf("%s %s", addr, name);
}
free(ii);
close(sock);
return 0;
}
2.2发送数据
//以下是一个简单的Linux蓝牙C++收发例程,用于在两个设备之间发送和接收数据:
//发送端(sender.cpp):
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
int main(int argc, char **argv) {
struct sockaddr_rc addr = {0};
int s, status;
char dest[18] = "01:23:45:67:89:AB"; // 目标设备的蓝牙地址
// 分配一个 socket
s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
if (s == -1) {
perror("socket");
exit(1);
}
// 连接到目标设备
addr.rc_family = AF_BLUETOOTH;
addr.rc_channel = (uint8_t)1;
str2ba(dest, &addr.rc_bdaddr);
status = connect(s, (struct sockaddr *)&addr, sizeof(addr));
if (status == -1) {
perror("connect");
exit(1);
}
// 发送数据
const char *msg = "Hello, receiver!";
status = write(s, msg, strlen(msg));
if (status == -1) {
perror("write");
exit(1);
}
// 关闭 socket
close(s);
return 0;
}
2.3 接收数据
//接收端(receiver.cpp):
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
int main(int argc, char **argv) {
struct sockaddr_rc loc_addr = {0}, rem_addr = {0};
socklen_t opt = sizeof(rem_addr);
int s, client, bytes_read;
char buf[1024] = {0};
// 分配一个 socket
s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
if (s == -1) {
perror("socket");
exit(1);
}
// 绑定本地地址和通道
loc_addr.rc_family = AF_BLUETOOTH;
loc_addr.rc_channel = (uint8_t)1;
bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr));
// 监听连接请求
listen(s, 1);
// 接受连接请求
client = accept(s, (struct sockaddr *)&rem_addr, &opt);
if (client == -1) {
perror("accept");
exit(1);
}
// 读取数据
bytes_read = read(client, buf, sizeof(buf));
if (bytes_read > 0) {
printf("Received: %s", buf);
}
// 关闭 socket
close(client);
close(s);
return 0;
}
因为我开发的是低功耗蓝牙,因此上面的例程不做过多解释。
三、低功耗蓝牙广播获取
3.1 命令行的方式获取
指令:
sudo hcidump --raw
–raw表示显示数据包的内容。
可能会收到很多的内容,对于AOJ来说,可以根据他们的协议来确定广播。
sudo hcidump --raw | grep "AA 01 C1"
AOJ数据包解析:
下面是我收到的一个广播包,尝试进行数据解析。
04 3E 26 02 01 04 00 04 21 ED 38 C1 A4 1A 08 09 41 4F 4A 2D 32 30 41 05 12 28 00 28 00 02 0A 00 07 FF AA 01 CE 64 E4 4F CE
字节结构解析
前六个字节
04:包类型。0x04 表示事件包。
3E:事件代码。0x3E 表示LE Meta事件。
26:事件参数长度。0x26 表示后续参数的长度为38个字节。
02:子事件代码。0x02 表示LE Advertising Report事件。
01:Number of Reports。0x01 表示包含一个报告。
04:事件类型。0x04 表示可连接的未定向广告(ADV_IND)。
接下来部分
00:Peer Address Type。0x00 表示公有地址。
04 21 ED 38 C1 A4:广告地址(Peer Address)。
1A:广告数据长度。0x1A 表示26个字节的广告数据长度。
广告数据部分(26字节)
广告数据由多个广告数据类型(AD Type)和数据内容组成:
08:AD Type - Complete Local Name
09:AD Length - 9 bytes
41 4F 4A 2D 32 30 41:广告名称 “AOJ-20A”
05:AD Length - 5 bytes
12:AD Type - Appearance
28 00 28 00:Appearance数据
02:AD Length - 2 bytes
0A:AD Type - Tx Power Level
00:Tx Power Level 数据
07:AD Length - 7 bytes
FF:AD Type - Manufacturer Specific Data
AA 01 CE 64 E4 4F:体温数据
最后一个字节
CE:数据校验和或结束符。
3.2 使用gatttool 的方式测试BLE广播
以AOJ为例。
在蓝牙低功耗(BLE)通信中,UUID(Universally Unique Identifier,全局唯一标识符)用于标识服务、特性和描述符。对于每一个BLE特性(Characteristic)
,有一些标准的属性,如Read、Write、Notify
等。Notify和Write UUID 是与BLE特性相关的两个重要概念。
Notify UUID:当一个特性具有Notify
属性时,意味着设备可以将特性值的改变通知到已连接的客户端(如手机或其他设备)。客户端可以订阅该特性,当特性值改变时,服务器(如BLE设备)会主动通知客户端。Notify UUID是用于标识具有Notify属性的特性UUID。
Write UUID:当一个特性具有Write
属性时,意味着客户端可以向该特性写入数据。Write UUID是用于标识具有Write属性的特性UUID
。
简而言之:
Notify UUID:标识那些可以向客户端发送通知的特性。
Write UUID:标识那些可以从客户端接收数据的特性。
3.2.1获取handle
服务UUID:0xFFE0
Notify UUID:0xFFE1(温度计通过 Notify Character 发送数据)
Write UUID:0xFFE2 (APP 通过 Write Character 发送指令给温度计)
也可以通过以下命令获取:
可以看到handle为0x000d的即是Notify UUID;
其他的类似。
3.2.2设备连接
指令如下:
可以看到具有Notification
属性的通知会被打印出来。依照AOJ提供的蓝牙广播协议即可进行解析。
另外,也可以看到该工具提供了其他的读写命令:
char-desc
char-read-hnd
char-read-uuid
char-write-cmd
char-write-req
characteristics
connect
除了connect
其他的读写都需要提供上面获取的handle
。
3.3写程序的方式获取全部广播
首先这种方式是获取全部的蓝牙广播,如果环境比较复杂,那么将会有非常多的数据被接收到!
另外,这种方式不需要和蓝牙设备进行配对即可扫描到广播数据,其行为和使用指令hcidump --raw
获取的一致。
AOJ的体温计属于低功耗蓝牙设备,并且不能通过经典的连接方式进行配对连接。我们可以通过监听蓝牙广播,获取体温数据。
例程:
bool monitor()
{
int device_id = hci_get_route(nullptr);
int sock = hci_open_dev(device_id);
if (device_id < 0 || sock < 0)
{
std::cerr << "Error opening socket." << std::endl;
return 1;
}
// 设置HCI套接字选项
struct hci_filter new_filter;
hci_filter_clear(&new_filter);
hci_filter_set_ptype(HCI_EVENT_PKT, &new_filter);
hci_filter_set_event(EVT_LE_META_EVENT, &new_filter);
if (setsockopt(sock, SOL_HCI, HCI_FILTER, &new_filter, sizeof(new_filter)) < 0)
{
std::cerr << "Error setting socket options." << std::endl;
close(sock);
return 1;
}
uint8_t buf[HCI_MAX_EVENT_SIZE];
while (true)
{
ssize_t len = read(sock, buf, sizeof(buf));
if (len < 0)
{
std::cerr << "Error reading from socket." << std::endl;
break;
}
evt_le_meta_event *meta = (evt_le_meta_event *)(buf + (1 + HCI_EVENT_HDR_SIZE));
if (meta->subevent != EVT_LE_ADVERTISING_REPORT)
{
// printf("Not BLE Advertising!\n");
continue;
}
le_advertising_info *info = (le_advertising_info *)(meta->data + 1);
char addr[19] = {0};
ba2str(&info->bdaddr, addr);
std::cout << "mac:" << addr << std::endl;
if (compare_mac(addr, target_mac))
{
std::cout << "Found target device: " << addr << std::endl;
std::cout << "Advertising data: ";
print_advertising_data(info->data, info->length);
// 处理设备广播消息
}
else
{
std::cout << "Found device: " << addr << std::endl;
}
sleep(0.5);
}
close(sock);
return 0;
}
代码分析:
注意,在nano的测试中,我发现上述的程序会一直卡在if (meta->subevent != EVT_LE_ADVERTISING_REPORT)
也就是说,识别不到具体的广播包类型。
这个稍后详细说明。
首先,int device_id = hci_get_route(nullptr); int sock = hci_open_dev(device_id);
都是固定代码。
struct hci_filter new_filter;
hci_filter_clear(&new_filter);
hci_filter_set_ptype(HCI_EVENT_PKT, &new_filter);
hci_filter_set_event(EVT_LE_META_EVENT, &new_filter);
if (setsockopt(sock, SOL_HCI, HCI_FILTER, &new_filter, sizeof(new_filter)) < 0)
{
std::cerr << "Error setting socket options." << std::endl;
close(sock);
return 1;
}
这部分用来设置套接字选项的,需要根据具体需要设置。
- 定义和清除过滤器
struct hci_filter new_filter;
hci_filter_clear(&new_filter);
new_filter
是一个结构体 hci_filter
,用于存储和管理HCI套接字的过滤规则。
hci_filter_clear(&new_filter)
函数用于清除 new_filter 结构体的所有过滤规则,确保其初始状态为空。
2. 设置数据包类型和事件类型过滤
hci_filter_set_ptype(HCI_EVENT_PKT, &new_filter);
hci_filter_set_ptype
函数用于设置过滤器的数据包类型。在这里,使用 HCI_EVENT_PKT
表示设置过滤器捕获所有HCI事件类型的数据包。在 C 语言中,特别是在蓝牙编程中,该函数通常用于设置套接字以过滤和捕获特定类型的蓝牙数据包。
功能和用法:
功能:
hci_filter_set_ptype
用于设置 HCI 过滤器以指定接收的数据包类型。在蓝牙编程中,常见的数据包类型包括命令包(Command Packet)、事件包(Event Packet)、ACL 数据包(ACL Data Packet)和 SCO 数据包(SCO Data Packet)等。
参数:
数据包类型(Packet Type):通常作为参数传递给 hci_filter_set_ptype
,用于指定要接收的数据包类型。
例如:
HCI_COMMAND_PKT:命令包类型。
HCI_EVENT_PKT:事件包类型。
HCI_ACLDATA_PKT:ACL 数据包类型。
HCI_SCODATA_PKT:SCO 数据包类型。
具体含义:
命令包(Command Packet):
类型码:HCI_COMMAND_PKT
- 含义:用于从主机(Host)向控制器(Controller)发送命令。这些命令可以请求控制器执行各种操作,例如启动扫描、建立连接、设置参数等。命令包通常包含一个命令标识符和相关的参数。
事件包(Event Packet):
类型码:HCI_EVENT_PKT
- 含义:控制器向主机发送的事件报告。这些事件可以包括设备状态改变、连接状态改变、错误报告等。事件包通常包含一个事件标识符和相关的事件参数。
ACL 数据包(ACL Data Packet):
类型码:HCI_ACLDATA_PKT
- 含义:用于传输应用数据的数据包类型。ACL(Asynchronous Connectionless Link)数据包用于在蓝牙连接上传输大部分数据,例如传输文件、音频流等。ACL 数据包通常包含数据通道标识符、数据长度和实际的应用数据。
SCO 数据包(SCO Data Packet):
类型码:HCI_SCODATA_PKT
- 含义:用于传输音频数据的数据包类型。SCO(Synchronous Connection-Oriented)数据包专门用于在蓝牙连接上传输实时音频数据,例如电话通话中的语音数据。SCO 数据包通常包含音频通道标识符、数据长度和音频数据。
设置过程:
在设置 HCI 套接字的过程中,首先需要创建一个 hci_filter 结构体,并使用 hci_filter_clear 函数初始化。
然后,使用 hci_filter_set_ptype 函数将特定的数据包类型设置到过滤器中。
最后,使用 setsockopt 函数将设置好的过滤器应用到 HCI 套接字上,以确保套接字只接收所需的数据包类型。
hci_filter_set_event(EVT_LE_META_EVENT, &new_filter);
hci_filter_set_event
函数用于设置过滤器捕获特定的HCI事件类型。在这里,使用 EVT_LE_META_EVENT
表示设置过滤器捕获蓝牙低功耗(BLE)元事件。
3. 应用和检查设置选项
if (setsockopt(sock, SOL_HCI, HCI_FILTER, &new_filter, sizeof(new_filter)) < 0)
{
std::cerr << "Error setting socket options." << std::endl;
close(sock);
return 1;
}
setsockopt
函数用于设置套接字选项。在这里,通过 SOL_HCI
和HCI_FILTER
参数设置HCI
套接字的过滤器选项。
如果 setsockopt
返回小于0的值,表示设置选项时出错,可能是因为权限不足或其他问题。
如果设置失败,输出错误信息,关闭套接字并返回1表示程序执行失败。
在理解 SOL_HCI
和 HCI_FILTER
参数的作用之前,我们需要明确以下几点:
3.1 SOL_HCI
SOL_HCI
是用于指定 socket
选项的协议层级,它用于与 Linux 下的 HCI(Host Controller Interface)
套接字通信。HCI 是用于与蓝牙设备通信的标准接口,它允许在应用程序和蓝牙适配器之间进行数据交换和控制。
在 Linux 系统中,与蓝牙通信的套接字(socket)可以通过AF_BLUETOOTH
和 BTPROTO_HCI
协议族来创建。SOL_HCI 就是指定在这一协议族中使用的选项层级。
3.2HCI_FILTER
HCI_FILTER
是 setsockopt
函数中用于设置 HCI 套接字过滤器选项的参数。通过设置 HCI_FILTER
,可以定义套接字如何处理接收到的数据包,以及哪些数据包应该被传递给应用程序。
具体来说,通过 HCI_FILTER 可以设置的内容包括:
数据包类型(Packet Type):例如指定只接收 HCI 命令包、事件包或者数据包。
事件类型(Event Type):指定只接收特定类型的事件,如 LE 元事件(Low Energy Meta Event)。
广播频道(Advertising Channel):指定只接收特定广播频道的数据。
连接句柄(Connection Handle):指定只接收特定连接的数据。
在实际应用中,设置正确的 HCI_FILTER 可以帮助应用程序过滤掉不需要的数据包,集中处理特定类型或特定条件下的数据,从而提高数据处理效率和精度。
3.3 AOJ的广播类型
其使用的是ADV_IND
广播数据包
3.3.1 ADV_IND 数据包格式
·ADV_IND(Advertising Indication·)数据包通常用于蓝牙设备定期广播自身的信息,以便周围设备发现和连接。它的格式可以分为两部分:广告数据(Advertising Data) 和 广告地址(Advertising Address)。
广告数据(Advertising Data):
包含设备广播的信息,例如设备名称、服务UUID、厂商自定义数据等。广告数据最多可以达到 31 字节。
广告地址(Advertising Address):
指示广播这些数据的设备的地址。广播地址通常是 6 字节的蓝牙设备地址。
具体字段解释:
广告数据(Advertising Data):
可能包括但不限于以下信息:
设备名称:设备的用户可见名称。
服务UUID:设备支持的服务的唯一标识符。
厂商自定义数据:设备制造商自定义的信息。
广告地址(Advertising Address):
是广播这些数据的蓝牙设备的物理地址,通常是设备的 MAC 地址。
在蓝牙(Bluetooth)
中,ADV_IND 是一种广告数据包(Advertising Data Packet)的类型。它是一种广告数据类型,用于在蓝牙低功耗(Bluetooth Low Energy, BLE)
中用于设备的广告和扫描响应过程中。
具体来说,ADV_IND
表示广告数据包中的广告指示器(Advertising Indicator)
,它包含了设备的标识信息和其他必要的数据。广告数据包(Advertising Data Packet)
是蓝牙设备周期性地广播的一种数据包,用于向周围设备传达自身的存在和基本信息,例如设备类型、服务支持、厂商信息等。
在 BLE 中,广告数据包通常用于以下几种情况:
广播(Advertising)
:设备定期广播自身的信息,例如在处于非连接状态下向周围设备宣传自身。
扫描响应(Scan Response)
:在接收到扫描请求后,设备可以选择性地发送扫描响应数据包,包含更详细的信息。
ADV_IND 数据包是广告和扫描响应的核心组成部分之一,通过解析ADV_IND
数据包,周围的设备可以识别和了解广播设备的特性和服务,进而采取适当的响应行动,例如连接设备或者采集设备信息。
开发过程中,我想要只接收这种广播,那么我可以设置过滤器只接收特定类型的广播数据包(例如 ADV_IND),可以结合使用 HCI_FILTER
和hci_filter_set_event
函数来实现。
3.5 通过gatt的方式获取特定广播
这种方式需要跟BLE设备进行连接,优点是可以只获取连接设备的广播,不需要在大量数据中找到自己想要的广播类型。
blueZ开源协议栈并没有提供低功耗蓝牙的C接口,但是github上面有人封装好了,我们可以直接拿过来用,gattlib
安装编译即可,里面提供了一些示例进程,我在notification的示例进程基础上,增加注册了断联回调以及重连机制,已经可以愉快的使用了
示例代码如下:
#include <iostream>
#include <thread>
#include <atomic>
#include <unistd.h>
#include <cstring>
#include <gattlib.h>
#include <glib.h>
#include <assert.h>
#include <ctype.h>
#include <glib.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define BLE_SCAN_TIMEOUT 10
struct
{
char *adapter_name;
const char *mac_address;
uuid_t gatt_notification_uuid;
uuid_t gatt_write_uuid;
long int gatt_write_data;
} m_argument;
// Declaration of thread condition variable
static pthread_cond_t m_connection_terminated = PTHREAD_COND_INITIALIZER;
// declaring mutex
static pthread_mutex_t m_connection_terminated_lock = PTHREAD_MUTEX_INITIALIZER;
class BluetoothNotifyReader
{
public:
BluetoothNotifyReader(const std::string &deviceAddress, const std::string &characteristicUUID)
: deviceAddress(deviceAddress), characteristicUUID(characteristicUUID), running(false) {}
~BluetoothNotifyReader()
{
stop();
}
void stop()
{
if (running)
{
running = false;
if (readerThread.joinable())
{
readerThread.join();
}
}
}
static void on_disconnect(gattlib_connection_t *connection, void *user_data)
{
std::cout << "Device disconnected" << std::endl;
gattlib_disconnect(connection, false /* wait_disconnection */);
pthread_mutex_lock(&m_connection_terminated_lock);
pthread_cond_signal(&m_connection_terminated);
pthread_mutex_unlock(&m_connection_terminated_lock);
}
static void on_device_connect(gattlib_adapter_t *adapter, const char *dst, gattlib_connection_t *connection, int error, void *user_data)
{
int ret;
ret = gattlib_register_on_disconnect(connection, on_disconnect, NULL);
if (ret != GATTLIB_SUCCESS)
{
}
if (m_argument.gatt_write_data != 0)
{
ret = gattlib_write_char_by_uuid(connection, &m_argument.gatt_write_uuid, &m_argument.gatt_write_data, 1);
if (ret != GATTLIB_SUCCESS)
{
}
}
ret = gattlib_register_notification(connection, notification_handler, NULL);
if (ret)
{
printf("Fail to register notification callback.\n");
gattlib_disconnect(connection, false /* wait_disconnection */);
}
ret = gattlib_notification_start(connection, &m_argument.gatt_notification_uuid);
if (ret)
{
printf("Fail to start notification.\n");
gattlib_disconnect(connection, false /* wait_disconnection */);
}
printf("Wait for notification for 20 seconds...\n");
usleep(2 * G_USEC_PER_SEC);
}
static void ble_discovered_device(gattlib_adapter_t *adapter, const char *addr, const char *name, void *user_data)
{
int ret;
int16_t rssi;
if (strcasecmp(addr, m_argument.mac_address) != 0)
{
printf("Got Wrong Addr : %s\n", addr);
return;
}
gattlib_adapter_scan_disable(adapter);
ret = gattlib_get_rssi_from_mac(adapter, addr, &rssi);
if (ret == 0)
{
printf("Found bluetooth device '%s' with RSSI:%d\n", addr, rssi);
}
else
{
printf("Found bluetooth device '%s'\n", m_argument.mac_address);
}
ret = gattlib_connect(adapter, addr, GATTLIB_CONNECTION_OPTIONS_NONE, on_device_connect, NULL);
if (ret != GATTLIB_SUCCESS)
{
printf("Failed to connect to the bluetooth device '%s'\n", addr);
pthread_mutex_lock(&m_connection_terminated_lock);
pthread_cond_signal(&m_connection_terminated);
pthread_mutex_unlock(&m_connection_terminated_lock);
}
}
static void *ble_task(void *arg)
{
char *addr = (char *)arg;
// printf("addr:%s\n", addr);
gattlib_adapter_t *adapter;
int ret;
while (1)
{
ret = gattlib_adapter_open(m_argument.adapter_name, &adapter);
if (ret)
{
printf("Failed to open adapter.\n");
}
ret = gattlib_adapter_scan_enable(adapter, ble_discovered_device, BLE_SCAN_TIMEOUT, NULL);
if (ret)
{
printf("Failed to scan.\n");
}
pthread_mutex_lock(&m_connection_terminated_lock);
pthread_cond_wait(&m_connection_terminated, &m_connection_terminated_lock);
pthread_mutex_unlock(&m_connection_terminated_lock);
printf("Got lock!\n");
gattlib_adapter_scan_disable(adapter);
gattlib_adapter_close(adapter);
}
return NULL;
}
void run()
{
uuid_t uuid;
gattlib_primary_service_t *services;
int services_count;
int ret;
m_argument.adapter_name = NULL;
m_argument.mac_address = deviceAddress.c_str();
printf("12312312312\n");
// Convert the characteristic UUID from string to uuid_t
ret = gattlib_string_to_uuid(characteristicUUID.c_str(), characteristicUUID.length() + 1, &m_argument.gatt_notification_uuid);
if (ret != GATTLIB_SUCCESS)
{
std::cerr << "Fail to convert UUID." << std::endl;
return;
}
ret = gattlib_mainloop(ble_task, NULL);
if (ret != GATTLIB_SUCCESS)
{
printf("Failed to create gattlib mainloop\n");
}
}
private:
static void notification_handler(const uuid_t *uuid, const uint8_t *data, size_t data_length, void *user_data)
{
std::cout << "Notification received: ";
for (size_t i = 0; i < data_length; i++)
{
std::cout << std::hex << (int)data[i] << " ";
}
std::cout << std::endl;
}
std::string deviceAddress;
std::string characteristicUUID;
std::atomic<bool> running;
std::thread readerThread;
};
int main(int argc, char *argv[])
{
BluetoothNotifyReader reader("A4:C1:38:ED:21:04", "0000ffe1-0000-1000-8000-00805f9b34fb");
reader.run();
return 0;
}
代码分为两个部分,一个用来读一个用来写。记得链接glib-2.0
和gattlib
还有pthread
。
可以看到,可以正确的重连以及接收广播数据。
四、遇到的问题以及解决方案
4.1 接收不到任何广播数据
ssize_t len = read(sock, buf, sizeof(buf));
程序当中,buf里面没有读到任何数据。程序运行之前,首先需要将蓝牙设备设置成扫描状态bluetoothctl scan no
4.2 接收不到对应的广播类型
if (meta->subevent != EVT_LE_ADVERTISING_REPORT)
程序中这个判断有可能会一直成立。
具体原因未知,目前的解决方案是直接解析buf中的数据:
void findAndPrint(uint8_t *array, size_t length)
{
bool found = false;
for (size_t i = 0; i < length - 2; ++i)
{
if (array[i] == 0xAA && array[i + 1] == 0x01 && array[i + 2] == 0xC1)
{
found = true;
if (i + 3 + 5 <= length)
{ // Check if there are at least 5 bytes after the pattern
printf("Temperature: %d ", (array[i + 4] << 8) + array[i + 5]);
printf("Test Mode : ");
switch (array[i + 6])
{
case 0x01:
printf("Adult Mode!\n");
break;
case 0x02:
printf("Children Mode!\n");
break;
case 0x03:
printf("Ear Mode!\n");
break;
case 0x04:
printf("Surface Mode!\n");
break;
default:
break;
}
}
else
{
std::cout << "Pattern found at index " << i << ", but not enough bytes after the pattern." << std::endl;
}
}
}
}
通过 if (array[i] == 0xAA && array[i + 1] == 0x01 && array[i + 2] == 0xC1)
查找特定广播协议的头,从而实现数据解析。