1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//! BLE (box-level): scan for and inspect Bluetooth Low Energy devices using
//! the box's own Bluetooth adapter.
//!
//! There is one adapter per box and the box serializes all BLE and BluFi
//! operations on it, so concurrent calls queue rather than fail.
use super::box_handle;
pub use crate::wire::{BleCharacteristic, BleDevice, BleDeviceInfo, BleService};
pub(crate) mod ops {
use std::time::Duration;
use serde_json::json;
use crate::error::Result;
use crate::wire::{
box_command, unit, value_as, value_list_field, BleDevice, BleDeviceInfo, CommandResponse,
Op, Timeout,
};
const PATH: &str = "/ble/command";
/// Box-side connect timeout (s) for info/connect, matching the CLI.
const CONNECT_TIMEOUT_S: f64 = 10.0;
fn parse_devices(resp: CommandResponse) -> Result<Vec<BleDevice>> {
value_list_field(resp, "devices")
}
/// Budget: the box blocks for the scan/connect duration plus bleak
/// overhead, and may queue behind another BLE/BluFi operation on the
/// adapter lock.
fn budget(box_side: f64) -> Timeout {
Timeout::After(Duration::from_secs_f64(box_side + 30.0))
}
pub(crate) fn scan(timeout: f64) -> Op<Vec<BleDevice>> {
Op {
req: box_command(PATH, "scan", json!({ "timeout": timeout }), budget(timeout)),
parse: parse_devices,
}
}
pub(crate) fn scan_named(timeout: f64, name_contains: &str) -> Op<Vec<BleDevice>> {
Op {
req: box_command(
PATH,
"scan",
json!({ "timeout": timeout, "name_contains": name_contains }),
budget(timeout),
),
parse: parse_devices,
}
}
pub(crate) fn info(address: &str) -> Op<BleDeviceInfo> {
Op {
req: box_command(
PATH,
"info",
json!({ "address": address, "timeout": CONNECT_TIMEOUT_S }),
budget(CONNECT_TIMEOUT_S),
),
parse: value_as::<BleDeviceInfo>,
}
}
pub(crate) fn connect(address: &str) -> Op<BleDeviceInfo> {
Op {
req: box_command(
PATH,
"connect",
json!({ "address": address, "timeout": CONNECT_TIMEOUT_S }),
budget(CONNECT_TIMEOUT_S),
),
parse: value_as::<BleDeviceInfo>,
}
}
pub(crate) fn disconnect(address: &str) -> Op<()> {
Op {
req: box_command(
PATH,
"disconnect",
json!({ "address": address }),
budget(CONNECT_TIMEOUT_S),
),
parse: unit,
}
}
}
box_handle! {
/// Handle for the box's BLE adapter (from [`crate::LagerBox::ble`]).
sync: Ble,
async: AsyncBle,
methods: {
/// Scan for advertising devices for `timeout` seconds
/// (0.1–300, sorted named-first).
fn scan(timeout: f64) -> Vec<BleDevice> = ops::scan;
/// Scan, keeping only devices whose name contains `name_contains`
/// (case-insensitive).
fn scan_named(timeout: f64, name_contains: &str) -> Vec<BleDevice> = ops::scan_named;
/// Connect briefly and enumerate the device's GATT services.
fn info(address: &str) -> BleDeviceInfo = ops::info;
/// Connect to a device (and enumerate its services to verify).
fn connect(address: &str) -> BleDeviceInfo = ops::connect;
/// Ensure a device is disconnected from the box.
fn disconnect(address: &str) -> () = ops::disconnect;
}
}