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

一、准备工作

安装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;
    }

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

  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 函数来实现。

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.0gattlib还有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)查找特定广播协议的头,从而实现数据解析。

  • 14
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Jetson AGX Orin是一款强大的嵌入式计算平台,可以轻松安装ROS 2以实现机器人及其他自动化系统的开发和运行。以下是使用300字中文回答Jetson AGX Orin安装ROS 2的步骤和注意事项。 首先,确保Jetson AGX Orin已经正常运行,并且您已经完成了基本的设置和配置。 接下来,从ROS 2官方网站(https://index.ros.org/doc/ros2/Installation/Foxy/Linux-Install-Debians/)下载适用于您的Jetson AGX Orin的ROS 2发行版,建议选择最新版本Foxy Fitzroy。下载完成后,将.deb文件保存到您的Jetson AGX Orin的本地存储中。 然后,在Jetson AGX Orin的终端中打开一个新的命令行窗口,以便在系统中进行安装。使用以下命令导航到.deb文件的目录: cd /path/to/deb/file 然后,使用以下命令安装ROS 2: sudo apt install ./ros-foxy-*.deb ROS 2的安装过程可能会花费一些时间,具体取决于您的Jetson AGX Orin的性能和网络连接速度。在安装过程中,请耐心等待,直到安装完成。 安装完成后,您需要设置ROS 2的工作环境。使用以下命令执行此操作: source /opt/ros/foxy/setup.bash 现在,您可以开始使用ROS 2在Jetson AGX Orin上进行开发和运行机器人应用程序了。请查阅ROS 2的文档和教程,以了解更多关于ROS 2在Jetson AGX Orin上的使用方法和功能。 总结一下,安装ROS 2到Jetson AGX Orin的过程包括下载适用于该平台的ROS 2发行版,并使用apt命令进行安装。安装完成后,使用source命令设置ROS 2的工作环境。最后,您就可以开始使用ROS 2在Jetson AGX Orin上进行开发和运行机器人应用程序了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蒙蒂锅巴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值