Event-Driven Usage

While polling for peripherals with adapter.peripherals() is useful for simple applications, a more robust and efficient approach for long-running applications is to use btleplug's event-driven API. This allows your application to react to Bluetooth events in real-time as they occur.

The Central Event Stream

The Central trait (implemented by Adapter) provides an events() method that returns a stream of CentralEvent enums.

By listening to this stream, you can be notified of:

  • Device discovery
  • Device property updates (e.g., name or RSSI changes)
  • Connections and disconnections
  • Changes in advertisement data

Example: Event-Driven Discovery

The following example demonstrates how to set up an event listener and handle different types of central events.

use btleplug::api::{Central, CentralEvent, Manager as _, Peripheral, ScanFilter};
use btleplug::platform::{Adapter, Manager};
use futures::stream::StreamExt;

async fn get_central(manager: &Manager) -> Adapter {
    let adapters = manager.adapters().await.unwrap();
    adapters.into_iter().nth(0).unwrap()
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let manager = Manager::new().await?;
    let central = get_central(&manager).await;

    // Get the stream of events
    let mut events = central.events().await?;

    // Start scanning
    central.start_scan(ScanFilter::default()).await?;

    println!("Listening for events...");

    // Process events from the stream
    while let Some(event) = events.next().await {
        match event {
            CentralEvent::DeviceDiscovered(id) => {
                println!("Device Discovered: {:?}", id);
            }
            CentralEvent::DeviceUpdated(id) => {
                println!("Device Updated: {:?}", id);
            }
            CentralEvent::DeviceConnected(id) => {
                println!("Device Connected: {:?}", id);
            }
            CentralEvent::DeviceDisconnected(id) => {
                println!("Device Disconnected: {:?}", id);
            }
            CentralEvent::ManufacturerDataAdvertisement { id, manufacturer_data } => {
                println!(
                    "Manufacturer Data Advertisement: {:?}, {:?}",
                    id, manufacturer_data
                );
            }
            CentralEvent::ServiceDataAdvertisement { id, service_data } => {
                println!("Service Data Advertisement: {:?}, {:?}", id, service_data);
            }
            CentralEvent::ServicesAdvertisement { id, services } => {
                println!("Services Advertisement: {:?}, {:?}", id, services);
            }
            _ => {}
        }
    }
    Ok(())
}

Understanding Central Events

Here's a breakdown of the most common events and what they mean:

  • CentralEvent::DeviceDiscovered(id): Fired the first time a peripheral is seen during a scan. The id is a PeripheralId, which you can use to get the full Peripheral object using central.peripheral(&id).await?.

  • CentralEvent::DeviceUpdated(id): Fired when an already-discovered peripheral sends a new advertisement packet, which might contain updated information like its name or RSSI value.

  • CentralEvent::DeviceConnected(id): Fired when a connection to a peripheral is successfully established.

  • CentralEvent::DeviceDisconnected(id): Fired when a peripheral disconnects, either intentionally or due to signal loss.

  • CentralEvent::ManufacturerDataAdvertisement { ... }: Fired when a peripheral's advertisement includes manufacturer-specific data. This is often used for custom protocols or device identification.

  • CentralEvent::ServiceDataAdvertisement { ... }: Fired when an advertisement contains data associated with a specific service UUID.

  • CentralEvent::ServicesAdvertisement { ... }: Fired when an advertisement includes a list of service UUIDs the peripheral supports.

  • CentralEvent::StateUpdate(state): Fired when the Bluetooth adapter's state changes (e.g., powered on or off). The state is a CentralState enum.

Why Use the Event-Driven Approach?

  • Efficiency: Your application doesn't need to repeatedly poll adapter.peripherals(), which can be resource-intensive.
  • Responsiveness: You can react to new devices and state changes instantly.
  • Correctness: It's the most reliable way to track the lifecycle of peripherals, especially their connection status.