Example: Subscribing to Notifications
Many BLE devices use notifications (or indications) to send data to a central device without being polled. This is common for sensors that report data periodically or when a value changes. This example shows how to connect to a specific device, find a characteristic that supports notifications, and listen for incoming data.
Full Code
This code is based on examples/subscribe_notify_characteristic.rs
.
use btleplug::api::{Central, CharPropFlags, Manager as _, Peripheral, ScanFilter};
use btleplug::platform::Manager;
use futures::stream::StreamExt;
use std::time::Duration;
use tokio::time;
use uuid::Uuid;
/// Only devices whose name contains this string will be tried.
const PERIPHERAL_NAME_MATCH_FILTER: &str = "Neuro";
/// UUID of the characteristic for which we should subscribe to notifications.
const NOTIFY_CHARACTERISTIC_UUID: Uuid = Uuid::from_u128(0x6e400002_b534_f393_67a9_e50e24dccA9e);
#[tokio::main]
async fn main() -> anyhow::Result<()> {
pretty_env_logger::init();
let manager = Manager::new().await?;
let adapter_list = manager.adapters().await?;
if adapter_list.is_empty() {
eprintln!("No Bluetooth adapters found");
return Ok(());
}
for adapter in adapter_list.iter() {
println!("Starting scan...");
adapter.start_scan(ScanFilter::default()).await?;
time::sleep(Duration::from_secs(2)).await;
let peripherals = adapter.peripherals().await?;
for peripheral in peripherals.iter() {
let properties = peripheral.properties().await?;
if let Some(local_name) = properties.unwrap().local_name {
if local_name.contains(PERIPHERAL_NAME_MATCH_FILTER) {
println!("Found matching peripheral: {}", &local_name);
// Connect to the peripheral
peripheral.connect().await?;
println!("Connected to {}", &local_name);
// Discover services
peripheral.discover_services().await?;
// Find the notification characteristic
for characteristic in peripheral.characteristics() {
if characteristic.uuid == NOTIFY_CHARACTERISTIC_UUID
&& characteristic.properties.contains(CharPropFlags::NOTIFY)
{
println!("Subscribing to characteristic {}", characteristic.uuid);
peripheral.subscribe(&characteristic).await?;
// Get the notification stream
let mut notification_stream = peripheral.notifications().await?.take(4);
// Process the first 4 notifications
while let Some(data) = notification_stream.next().await {
println!(
"Received data from {} [{}]: {:?}",
local_name, data.uuid, data.value
);
}
}
}
// Disconnect
println!("Disconnecting from {}...", local_name);
peripheral.disconnect().await?;
return Ok(()); // Exit after handling the first matching device
}
}
}
}
Ok(())
}
Explanation
-
Constants: We define constants for the target peripheral's name and the specific characteristic UUID we are interested in. This makes the code clearer and easier to modify.
-
Scan and Filter: The code scans for all devices but then iterates through the results, looking for a peripheral whose
local_name
contains our target string ("Neuro"
). -
Connect and Discover: Once a matching peripheral is found, it connects and discovers its services, just like in the previous example.
-
Find the Target Characteristic: The code iterates through the discovered characteristics. It checks for two conditions:
- The UUID matches
NOTIFY_CHARACTERISTIC_UUID
. - The characteristic's
properties
include theNOTIFY
flag.
- The UUID matches
-
Subscribe:
peripheral.subscribe(&characteristic).await?
tells the peripheral that we want to receive notifications for this characteristic. -
Get Notification Stream:
peripheral.notifications().await?
returns aStream
that will yieldValueNotification
structs. We use.take(4)
to limit our example to processing just the first four notifications. -
Process Notifications: The
while let Some(data) = ...
loop asynchronously waits for items from the stream. Eachdata
item is aValueNotification
struct containing the UUID of the characteristic and thevalue
(payload) of the notification. -
Disconnect: After receiving the desired number of notifications, the program disconnects from the peripheral.