本文主要是介绍Jetson AGX Orin基于BlueZl蓝牙协议栈AOJ红外蓝牙体温计开发(低功耗蓝牙ble),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
一、准备工作
安装blueZ以及相关的蓝牙测试工具:
sudo apt update
sudo apt install bluez
sudo apt install bluez-hcidump
然后看下蓝牙设备是否识别到,已经是否处于开启状态:
root@test-desktop:~# hciconfig -a
hci0: Type: Primary Bus: USBBD Address: A4:42:3B:2A:47:DD ACL MTU: 1021:4 SCO MTU: 96:6UP RUNNING PSCAN ISCANRX bytes:437421 acl:0 sco:0 events:8518 errors:0TX bytes:18880 acl:0 sco:0 commands:1564 errors:0Features: 0xbf 0xfe 0x0f 0xfe 0xdb 0xff 0x7b 0x87Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3Link policy: RSWITCH SNIFFLink mode: SLAVE ACCEPTName: 'test-desktop'Class: 0x1c0000Service Classes: Rendering, Capturing, Object TransferDevice Class: Miscellaneous,HCI Version: 5.1 (0xa) Revision: 0x100LMP Version: 5.1 (0xa) Subversion: 0x100Manufacturer: 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"; // 目标设备的蓝牙地址// 分配一个 sockets = 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);}// 关闭 socketclose(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};// 分配一个 sockets = 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);}// 关闭 socketclose(client);close(s);return 0;
}
因为我开发的是低功耗蓝牙,因此上面的例程不做过多解释。
三、低功耗蓝牙广播获取
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
函数来实现。
四、遇到的问题以及解决方案
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 patternprintf("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)
查找特定广播协议的头,从而实现数据解析。
这篇关于Jetson AGX Orin基于BlueZl蓝牙协议栈AOJ红外蓝牙体温计开发(低功耗蓝牙ble)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!