-
Notifications
You must be signed in to change notification settings - Fork 44
/
Copy pathble_bas_peripheral.rs
206 lines (190 loc) · 7.31 KB
/
ble_bas_peripheral.rs
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
use embassy_futures::{join::join, select::select};
use embassy_time::Timer;
use trouble_host::prelude::*;
/// Size of L2CAP packets
#[cfg(not(feature = "esp"))]
pub const L2CAP_MTU: usize = 251;
#[cfg(feature = "esp")]
// Some esp chips only accept an MTU >= 1017
pub const L2CAP_MTU: usize = 1017;
/// Max number of connections
const CONNECTIONS_MAX: usize = 1;
/// Max number of L2CAP channels.
const L2CAP_CHANNELS_MAX: usize = 2; // Signal + att
const MAX_ATTRIBUTES: usize = 10;
type Resources<C> = HostResources<C, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX, L2CAP_MTU>;
// GATT Server definition
#[gatt_server]
struct Server {
battery_service: BatteryService,
}
/// Battery service
#[gatt_service(uuid = service::BATTERY)]
struct BatteryService {
/// Battery Level
#[descriptor(uuid = descriptors::VALID_RANGE, read, value = [0, 100])]
#[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, read, value = "Battery Level")]
#[characteristic(uuid = characteristic::BATTERY_LEVEL, read, notify, value = 10)]
level: u8,
#[characteristic(uuid = "408813df-5dd4-1f87-ec11-cdb001100000", write, read, notify)]
status: bool,
}
/// Run the BLE stack.
pub async fn run<C>(controller: C)
where
C: Controller,
{
// Using a fixed "random" address can be useful for testing. In real scenarios, one would
// use e.g. the MAC 6 byte array as the address (how to get that varies by the platform).
let address = Address::random([0x41, 0x5A, 0xE3, 0x1E, 0x83, 0xE7]);
info!("Our address = {:?}", address);
let mut resources = Resources::new(PacketQos::None);
let (stack, mut peripheral, _, runner) = trouble_host::new(controller, &mut resources)
.set_random_address(address)
.build();
info!("Starting advertising and GATT service");
let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig {
name: "TrouBLE",
appearance: &appearance::power_device::GENERIC_POWER_DEVICE,
}))
.unwrap();
let _ = join(ble_task(runner), async {
loop {
match advertise("Trouble Example", &mut peripheral).await {
Ok(conn) => {
// set up tasks when the connection is established to a central, so they don't run when no one is connected.
let a = gatt_events_task(&server, &conn);
let b = custom_task(&server, &conn, stack);
// run until any task ends (usually because the connection has been closed),
// then return to advertising state.
select(a, b).await;
}
Err(e) => {
#[cfg(feature = "defmt")]
let e = defmt::Debug2Format(&e);
panic!("[adv] error: {:?}", e);
}
}
}
})
.await;
}
/// This is a background task that is required to run forever alongside any other BLE tasks.
///
/// ## Alternative
///
/// If you didn't require this to be generic for your application, you could statically spawn this with i.e.
///
/// ```rust [ignore]
///
/// #[embassy_executor::task]
/// async fn ble_task(mut runner: Runner<'static, SoftdeviceController<'static>>) {
/// runner.run().await;
/// }
///
/// spawner.must_spawn(ble_task(runner));
/// ```
async fn ble_task<C: Controller>(mut runner: Runner<'_, C>) {
loop {
if let Err(e) = runner.run().await {
#[cfg(feature = "defmt")]
let e = defmt::Debug2Format(&e);
panic!("[ble_task] error: {:?}", e);
}
}
}
/// Stream Events until the connection closes.
///
/// This function will handle the GATT events and process them.
/// This is how we interact with read and write requests.
async fn gatt_events_task(server: &Server<'_>, conn: &Connection<'_>) -> Result<(), Error> {
let level = server.battery_service.level;
loop {
match conn.next().await {
ConnectionEvent::Disconnected { reason } => {
info!("[gatt] disconnected: {:?}", reason);
break;
}
ConnectionEvent::Gatt { data } => {
// We can choose to handle event directly without an attribute table
// let req = data.request();
// ..
// data.reply(conn, Ok(AttRsp::Error { .. }))
// But to simplify things, process it in the GATT server that handles
// the protocol details
match data.process(server).await {
// Server processing emits
Ok(Some(GattEvent::Read(event))) => {
if event.handle() == level.handle {
let value = server.get(&level);
info!("[gatt] Read Event to Level Characteristic: {:?}", value);
}
}
Ok(Some(GattEvent::Write(event))) => {
if event.handle() == level.handle {
info!("[gatt] Write Event to Level Characteristic: {:?}", event.data());
}
}
Ok(_) => {}
Err(e) => {
warn!("[gatt] error processing event: {:?}", e);
}
}
}
}
}
info!("[gatt] task finished");
Ok(())
}
/// Create an advertiser to use to connect to a BLE Central, and wait for it to connect.
async fn advertise<'a, C: Controller>(
name: &'a str,
peripheral: &mut Peripheral<'a, C>,
) -> Result<Connection<'a>, BleHostError<C::Error>> {
let mut advertiser_data = [0; 31];
AdStructure::encode_slice(
&[
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
AdStructure::ServiceUuids16(&[Uuid::Uuid16([0x0f, 0x18])]),
AdStructure::CompleteLocalName(name.as_bytes()),
],
&mut advertiser_data[..],
)?;
let advertiser = peripheral
.advertise(
&Default::default(),
Advertisement::ConnectableScannableUndirected {
adv_data: &advertiser_data[..],
scan_data: &[],
},
)
.await?;
info!("[adv] advertising");
let conn = advertiser.accept().await?;
info!("[adv] connection established");
Ok(conn)
}
/// Example task to use the BLE notifier interface.
/// This task will notify the connected central of a counter value every 2 seconds.
/// It will also read the RSSI value every 2 seconds.
/// and will stop when the connection is closed by the central or an error occurs.
async fn custom_task<C: Controller>(server: &Server<'_>, conn: &Connection<'_>, stack: Stack<'_, C>) {
let mut tick: u8 = 0;
let level = server.battery_service.level;
loop {
tick = tick.wrapping_add(1);
info!("[custom_task] notifying connection of tick {}", tick);
if level.notify(server, conn, &tick).await.is_err() {
info!("[custom_task] error notifying connection");
break;
};
// read RSSI (Received Signal Strength Indicator) of the connection.
if let Ok(rssi) = conn.rssi(stack).await {
info!("[custom_task] RSSI: {:?}", rssi);
} else {
info!("[custom_task] error getting RSSI");
break;
};
Timer::after_secs(2).await;
}
}