rust 如何使用bluez堆栈实现可扫描BLE信标?

o2rvlv0m  于 2023-02-16  发布在  其他
关注(0)|答案(2)|浏览(397)
    • bounty将在2天后过期**。回答此问题可获得+50声望奖励。Hristo Kamenov希望引起更多人关注此问题。

我正在尝试实现一个BLE信标,它允许请求额外的信息。
我目前的理解是,在BLE中,设备可以广播广告数据包,广告数据包可以指示设备是可扫描的,这意味着客户端可以向信标发送扫描请求,然后信标可以发送包含附加信息的扫描响应。
因此,交换的数据包将如下所示:高级扫描指示-〉扫描请求-〉扫描响应。
我试图理解beacon实现应该如何工作。这是由适配器实现的吗(我必须预先指定要在扫描响应中发送回的数据)?或者beacon应该侦听SCAN_REQ包并在看到时广播SCAN_RSP吗?
我一直在寻找在Rust或Go中使用的库,但是在这些语言中使用bluez时似乎缺乏对开发BLE外设的支持。
任何编程语言/库中的答案我都能接受,只要它能在Linux上工作
到目前为止,我得到的关闭是使用蓝色的 rust 。

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let session = bluer::Session::new().await?;
    let adapter = session.default_adapter().await?;
    adapter.set_powered(true).await?;

    println!(
        "Advertising on Bluetooth adapter {} with address {}",
        adapter.name(),
        adapter.address().await?
    );

    let mut data = BTreeMap::new();
    data.insert(0, vec![1, 2, 3]);

    let le_advertisement = Advertisement {
        advertisement_type: bluer::adv::Type::Broadcast,
        local_name: Some("le_advertise".to_string()),
        advertisting_data: data,
        ..Default::default()
    };

    println!("{:?}", &le_advertisement);
    let handle = adapter.advertise(le_advertisement).await?;

    std::thread::sleep(std::time::Duration::from_secs(30));

    println!("Removing advertisement");
    drop(handle);
    Ok(())
}

这适用于广播广告。我可以使用nrf连接在手机上看到它。
然而,我找不到响应扫描请求的方法,也找不到指示信标可以被扫描的方法。

sr4lhrrt

sr4lhrrt1#

从理论部分开始,您对扫描响应的理解是正确的。蓝牙SIG的官方定义是(Bluetooth Core Specification v5.4,Vol 1,Part A,section4.2.2.2):-
广告设备可以从监听设备接收扫描请求,以便从广告设备获得附加用户数据。扫描响应由广告设备发送到作出扫描请求的设备。
这里也提到了这一点(蓝牙核心规范v5.4,第1卷,A部分,第3.3.2.2.2节):
由通告设备发送的一些通告事件允许监听设备(扫描器)在接收到通告分组的同一通告PHY信道上并发地发送扫描请求或连接请求分组。通告设备可以在同一通告事件内在同一通告PHY信道上再次发送扫描响应分组。
扫描响应通常在设备开始通告之前设置,然而,允许在设备已经开始通告时设置或更改扫描响应。这在蓝牙规范v5.4,第4卷,E部分,7.8.8 LES设置扫描响应数据命令中提到:-
如果广告目前已启用,管制员应在后续广告活动中使用新数据。如果发布此命令时广告活动正在进行中,管制员可使用旧数据或新数据。如果广告目前已禁用,则数据应由管制员保存,并在广告启用后使用。
默认扫描响应数据长度应为零,默认扫描响应数据应为31个零八位字节。
至于实用部分,您可以使用btmgmt工具和Linux上的以下命令轻松发送扫描报告:-

sudo btmgmt add-adv -d 02010606084142434400 -s 080954657374204C45

其中-d选项用于设置广告数据,-s选项用于设置扫描响应数据。btmgmt add-adv选项的完整列表包括:-

Usage: add-adv [options] <instance_id>

Options:
     -u, --uuid <uuid>         Service UUID
     -d, --adv-data <data>     Advertising Data bytes
     -s, --scan-rsp <data>     Scan Response Data bytes
     -t, --timeout <timeout>   Timeout in seconds
     -D, --duration <duration> Duration in seconds
     -P, --phy <phy>           Phy type, Specify 1M/2M/CODED
     -c, --connectable         "connectable" flag
     -g, --general-discov      "general-discoverable" flag
     -l, --limited-discov      "limited-discoverable" flag
     -n, --scan-rsp-local-name "local-name" flag
     -a, --scan-rsp-appearance "appearance" flag
     -m, --managed-flags       "managed-flags" flag
     -p, --tx-power            "tx-power" flag
e.g.:
    add-adv -u 180d -u 180f -d 080954657374204C45 1

广告中的BLE数据解码如下(基于on the Assigned Numbers Document):-

  • 第1字节=长度(n字节)
  • 第2个字节=类型
  • n-1字节=实际数据

所以我添加的广告数据的含义:-

  • 02长度(2个字节)
  • 01类型(标志)
  • 06标志- 02 && 04 LE一般可发现&& BR/EDR不受支持
  • 06长度(6字节)
  • 08类型(缩写本地名称)
  • 小行星4142434400(ABCD)

扫描响应数据的含义是:

  • 08长度(8字节)
  • 09类型(完整的本地名称)
  • 54657374204 C45(供试品LE)

一些更有用的链接:-

mm9b1k5b

mm9b1k5b2#

正如你所说,你可以接受任何编程语言/库的答案--我想向你介绍一下Qt框架,特别是Qt连接模块及其蓝牙组件。下面是一个示例摘录,其中广告数据和扫描响应数据都是配置好的。

QLowEnergyAdvertisingData advertisingData;
QLowEnergyAdvertisingData scanResponseData; // <- added to original example
advertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral);
advertisingData.setIncludePowerLevel(true);
advertisingData.setLocalName("HeartRateServer");
advertisingData.setServices(QList<QBluetoothUuid>() << QBluetoothUuid::ServiceClassUuid::HeartRate);
scanResponseData.setManufacturerData(0x1234, QByteArray("Hello"));  // <- added to example
bool errorOccurred = false;
const QScopedPointer<QLowEnergyController> leController(QLowEnergyController::createPeripheral());
auto errorHandler = [&leController,&errorOccurred](QLowEnergyController::Error errorCode)
{
        qWarning().noquote().nospace() << errorCode << " occurred: "
            << leController->errorString();
        if (errorCode != QLowEnergyController::RemoteHostClosedError) {
            qWarning("Heartrate-server quitting due to the error.");
            errorOccurred = true;
            QCoreApplication::quit();
        }
};
QObject::connect(leController.data(), &QLowEnergyController::errorOccurred, errorHandler);

QScopedPointer<QLowEnergyService> service(leController->addService(serviceData));
leController->startAdvertising(QLowEnergyAdvertisingParameters(), advertisingData,
                               scanResponseData);  // <- modified from original example

上面的例子说明了蓝牙协议栈通常是如何实现扫描响应的(特别是注意例子中的第二行和最后一行)。这个例子的完整源代码在https://code.qt.io/cgit/qt/qtconnectivity.git/tree/examples/bluetooth/heartrate-server/main.cpp?h=6.4,并在BSD许可下提供(注意,上面的摘录被修改以突出如何设置扫描响应)。
我已经在RPi上验证了上面的代码,我可以看到生成的BTLE广告使用NRFConnect添加了mfr数据。

  1. sudo apt安装qt5-默认qtcreator qtconnectivity5-示例qtconnectivity5-dev
    1.将这些例子复制到你可以使用它们的地方(例如mkdir test && cd test && cp-R/usr/lib/arm-linux-gnueabihf/qt5/examples.)[我使用了一个我已经准备好的旧的rpi--你的可能是新的,因此这些例子将在默认情况下保存在不同的目录中]
    1.编辑示例代码(例如cd examples/bluetooth/heartrate-server && vim main.cpp)--进行我上面指出的3个编辑
  2. qmake网站 heartrate-server.pro
    1.制造
  3. sudo ./心率-服务器
    这个例子本身并没有实现信标,但是,无论设备是信标,ble设备的通告部分在很大程度上都是相同的(仅限广告)或如果它有一项服务(或服务)。如果您从未连接到示例中的玩具服务,则它在功能上是信标。在任何情况下,当实现严格意义上是信标的设备时,将采用上述方法。此外,您已经表示希望实现"信标",但没有提供关于信标类型的进一步信息。通常,您会实现iBeacon或EddyStone信标--当然BLE规范也足够开放,允许您发明自己的信标。
    要回答有关信标实施应如何运行的问题,请执行以下操作:扫描响应数据包与广告数据包具有相同的结构,并且通常在配置广告数据的同时配置它。例如,Nordic堆栈就是这样工作的。显然,您可以在上面看到QT也是这样做的。
    原始的linux HCI接口在这方面稍有不同,因为必须将扫描响应指定为一个单独的命令,但有效载荷的格式与广告数据设置相同。adv数据和扫描响应数据都是在启用广告的过程中设置的。
    一般来说,你的蓝牙包应该能让你设置扫描响应,就像你设置广告数据一样--这两者是紧密联系的。(软设备)实现,并且在该特定平台上,确实可以选择发送SCAN_REQ事件。通常的做法是提前设置扫描响应数据。2在Linux上,我怀疑你还能做些什么:BTLE的实现需要在收到SCAN_REQ后快速发送扫描响应,这样就没有时间往返用户区。这些数据需要已经在内核端的缓冲区中。
    如果您深入研究Qt实现,您可以(最终)了解一下QLeAdvertiserBluez的实现;感兴趣的方法是setScanResponseData,它又委托给setData。对setData的调用是在isScanResponseData设置为真的情况下进行的,导致OcfLeSetScanResponseData命令被发送到Linux HCI(连同作为扫描响应传递的对象中的数据)。https://code.qt.io/cgit/qt/qtconnectivity.git/tree/src/bluetooth/qleadvertiser_bluez.cpp?h=5.15第339行。OcfLeSetScanResponseData结果是操作码命令字段0x9,其被传递到Qt的HciManager,并由此(经由套接字)连接到Linux HCI驱动器。
    ocf 0x9对应于
#define HCI_OP_LE_SET_SCAN_RSP_DATA 0x2009
struct hci_cp_le_set_scan_rsp_data {
    __u8    length;
    __u8    data[HCI_MAX_AD_LENGTH];

第1651行
所以--这些都是可行的。Qt显然做到了。我不明白的是为什么你的Rust包没有公开这个相当基本的功能。BlueZ的"文档"仍然是一个无法穿透的泥潭。每次我想使用用户空间BlueZ的东西时,我都会放弃,只使用hci. h中的结构定义

[Edit] While following up some dangling leads from the above research I found an alternate C API that may also be of interest. This one is direct from the bluez folks, but has a couple of major downsides. https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/src/shared That's bluez.git:src/shared -- apparently it is an unofficial API with no stability guarantees. Also, documentation for it appears to be thin to the point of non-existence (no worries, though -- code is self documenting, right?). There is an example at https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/tools/eddystone.c (bluez.git:tools/eddystone.c) which could probably be altered to make a beacon with a scan response. Look at adv_tx_power_callback in eddystone.c -- one could construct a bt_hci_cmd_le_set_scan_rsp_data struct, build the data array to contain a valid manufacturer info block (or a local name, or any other valid adv block), and then send the new struct with bt_hci_send(..., BT_HCI_CMD_LE_SET_SCAN_RSP_DATA,...)

相关问题