> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lagerdata.com/llms.txt
> Use this file to discover all available pages before exploring further.

# BLE

> 从 Box 扫描并连接低功耗蓝牙设备

用 Box 自带的蓝牙适配器找到正在广播的被测设备，连接它，并枚举它的 GATT 服务。

## 句柄

```rust theme={null}
use lager::LagerBox;

let lager = LagerBox::from_env()?;
let ble = lager.ble();
```

BLE 是 **Box 级**能力，不是一个 Net：它驱动的是 Box 自己的适配器。它没有 Net 名称，也没有 `name()`。

## 方法

| 方法             | 说明                |
| -------------- | ----------------- |
| `scan()`       | 扫描正在广播的设备         |
| `scan_named()` | 扫描，并按名称子串过滤       |
| `info()`       | 短暂连接并枚举 GATT 服务   |
| `connect()`    | 连接，并通过枚举服务来确认     |
| `disconnect()` | 确保某个设备已与 Box 断开连接 |

## 类型

```rust theme={null}
pub struct BleDevice {
    pub name: String,          // falls back to the address when unnamed
    pub address: String,       // XX:XX:XX:XX:XX:XX
    pub rssi: Option<i64>,     // dBm
    pub uuids: Vec<String>,
}

pub struct BleDeviceInfo {
    pub address: String,
    pub connected: bool,
    pub services: Vec<BleService>,
}

pub struct BleService {
    pub uuid: String,
    pub description: Option<String>,
    pub characteristics: Vec<BleCharacteristic>,
}

pub struct BleCharacteristic {
    pub uuid: String,
    pub description: Option<String>,
    pub properties: Vec<String>,   // e.g. ["read", "notify"]
}
```

## 方法参考

### `scan(timeout: f64) -> Result<Vec<BleDevice>>`

扫描正在广播的设备 `timeout` 秒，该值必须介于 0.1 和 300 之间。有名称的设备排在前面。

```rust theme={null}
for d in ble.scan(5.0)? {
    println!("{} ({}) {:?} dBm", d.name, d.address, d.rssi);
}
```

### `scan_named(timeout: f64, name_contains: &str) -> Result<Vec<BleDevice>>`

同样的扫描，但按不区分大小写的名称子串过滤。

### `info(address: &str) -> Result<BleDeviceInfo>`

短暂连接并枚举该设备的 GATT 服务。

### `connect(address: &str) -> Result<BleDeviceInfo>` 和 `disconnect(address: &str) -> Result<()>`

按地址连接某个设备，或者确保与它断开连接。

## 示例

### 断言被测设备带着正确的服务在广播

```rust theme={null}
use lager::LagerBox;

let lager = LagerBox::from_env()?;
let ble = lager.ble();
let supply = lager.supply("supply1");

supply.set_voltage(3.3)?;
supply.enable()?;
std::thread::sleep(std::time::Duration::from_secs(3));

let found = ble.scan_named(10.0, "my-dut")?;
let dut = found.first().expect("DUT is not advertising");
println!("found {} at {:?} dBm", dut.name, dut.rssi);

let info = ble.info(&dut.address)?;
assert!(info.services.iter().any(|s| s.uuid.starts_with("0000180f")),
        "battery service missing");

ble.disconnect(&dut.address)?;
supply.disable()?;
```

## 说明

* **每台 Box 只有一个适配器。** Box 会把它上面所有的 BLE **和** BluFi 工作串行化，因此并发调用会排队而不是失败。一次 BluFi 配网和一次 BLE 扫描不能重叠。
* `capabilities.ble_command` 为 `true` 只表示 Box **提供这条路由**，而不表示它能完成这项工作。容器里没有运行 BlueZ 的 Box 会用 `Error::Box` 和 HTTP 502 作答，并携带 `The name org.bluez was not provided by any .service files`。即使 USB 总线上确实有蓝牙控制器也会这样。请检查错误，而不是只看能力标志。
* `info`、`connect` 和 `disconnect` 在 Box 侧的连接超时是 10 秒；客户端在此之上再加 30 秒。
* `rssi` 是恰好收到的那条广播的一次快照。请把它当作排序提示，而不是测量值。
