Jetson AGX Orin基于BlueZl蓝牙协议栈AOJ红外蓝牙体温计开发(低功耗蓝牙ble)

本文主要是介绍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;}

这部分用来设置套接字选项的,需要根据具体需要设置。

  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_BLUETOOTHBTPROTO_HCI 协议族来创建。SOL_HCI 就是指定在这一协议族中使用的选项层级。

3.2HCI_FILTER

HCI_FILTERsetsockopt 函数中用于设置 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)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1061418

相关文章

使用Python开发一个带EPUB转换功能的Markdown编辑器

《使用Python开发一个带EPUB转换功能的Markdown编辑器》Markdown因其简单易用和强大的格式支持,成为了写作者、开发者及内容创作者的首选格式,本文将通过Python开发一个Markd... 目录应用概览代码结构与核心组件1. 初始化与布局 (__init__)2. 工具栏 (setup_t

Spring Shell 命令行实现交互式Shell应用开发

《SpringShell命令行实现交互式Shell应用开发》本文主要介绍了SpringShell命令行实现交互式Shell应用开发,能够帮助开发者快速构建功能丰富的命令行应用程序,具有一定的参考价... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定义S

Python通过模块化开发优化代码的技巧分享

《Python通过模块化开发优化代码的技巧分享》模块化开发就是把代码拆成一个个“零件”,该封装封装,该拆分拆分,下面小编就来和大家简单聊聊python如何用模块化开发进行代码优化吧... 目录什么是模块化开发如何拆分代码改进版:拆分成模块让模块更强大:使用 __init__.py你一定会遇到的问题模www.

Nginx中配置HTTP/2协议的详细指南

《Nginx中配置HTTP/2协议的详细指南》HTTP/2是HTTP协议的下一代版本,旨在提高性能、减少延迟并优化现代网络环境中的通信效率,本文将为大家介绍Nginx配置HTTP/2协议想详细步骤,需... 目录一、HTTP/2 协议概述1.HTTP/22. HTTP/2 的核心特性3. HTTP/2 的优

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

使用Python开发一个简单的本地图片服务器

《使用Python开发一个简单的本地图片服务器》本文介绍了如何结合wxPython构建的图形用户界面GUI和Python内建的Web服务器功能,在本地网络中搭建一个私人的,即开即用的网页相册,文中的示... 目录项目目标核心技术栈代码深度解析完整代码工作流程主要功能与优势潜在改进与思考运行结果总结你是否曾经

关于WebSocket协议状态码解析

《关于WebSocket协议状态码解析》:本文主要介绍关于WebSocket协议状态码的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录WebSocket协议状态码解析1. 引言2. WebSocket协议状态码概述3. WebSocket协议状态码详解3

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

Python基于wxPython和FFmpeg开发一个视频标签工具

《Python基于wxPython和FFmpeg开发一个视频标签工具》在当今数字媒体时代,视频内容的管理和标记变得越来越重要,无论是研究人员需要对实验视频进行时间点标记,还是个人用户希望对家庭视频进行... 目录引言1. 应用概述2. 技术栈分析2.1 核心库和模块2.2 wxpython作为GUI选择的优

利用Python开发Markdown表格结构转换为Excel工具

《利用Python开发Markdown表格结构转换为Excel工具》在数据管理和文档编写过程中,我们经常使用Markdown来记录表格数据,但它没有Excel使用方便,所以本文将使用Python编写一... 目录1.完整代码2. 项目概述3. 代码解析3.1 依赖库3.2 GUI 设计3.3 解析 Mark