-
Notifications
You must be signed in to change notification settings - Fork 45
/
Copy pathble_bas_peripheral.rs
198 lines (185 loc) · 7.24 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
use embassy_futures::join::join;
use embassy_futures::select::select;
use embassy_time::Timer;
use trouble_host::prelude::*;
/// Max number of connections
const CONNECTIONS_MAX: usize = 1;
/// Max number of L2CAP channels.
const L2CAP_CHANNELS_MAX: usize = 2; // Signal + att
// 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, name = "hello", 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, const L2CAP_MTU: usize>(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 = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);
info!("Our address = {:?}", address);
let mut resources: HostResources<CONNECTIONS_MAX, L2CAP_CHANNELS_MAX, L2CAP_MTU> = HostResources::new();
let stack = trouble_host::new(controller, &mut resources).set_random_address(address);
let Host {
mut peripheral, runner, ..
} = stack.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, &server).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: &GattConnection<'_, '_>) -> Result<(), Error> {
let level = server.battery_service.level;
loop {
match conn.next().await {
GattConnectionEvent::Disconnected { reason } => {
info!("[gatt] disconnected: {:?}", reason);
break;
}
GattConnectionEvent::Gatt { event } => match event {
Ok(event) => {
match &event {
GattEvent::Read(event) => {
if event.handle() == level.handle {
let value = server.get(&level);
info!("[gatt] Read Event to Level Characteristic: {:?}", value);
}
}
GattEvent::Write(event) => {
if event.handle() == level.handle {
info!("[gatt] Write Event to Level Characteristic: {:?}", event.data());
}
}
}
// This step is also performed at drop(), but writing it explicitly is necessary
// in order to ensure reply is sent.
match event.accept() {
Ok(reply) => {
reply.send().await;
}
Err(e) => warn!("[gatt] error sending response: {:?}", e),
}
}
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, 'b, C: Controller>(
name: &'a str,
peripheral: &mut Peripheral<'a, C>,
server: &'b Server<'_>,
) -> Result<GattConnection<'a, 'b>, BleHostError<C::Error>> {
let mut advertiser_data = [0; 31];
AdStructure::encode_slice(
&[
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
AdStructure::ServiceUuids16(&[[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?.with_attribute_server(server)?;
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: &GattConnection<'_, '_>, 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(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.raw().rssi(stack).await {
info!("[custom_task] RSSI: {:?}", rssi);
} else {
info!("[custom_task] error getting RSSI");
break;
};
Timer::after_secs(2).await;
}
}