Example: Discovering and Inspecting Devices

This example provides a complete walkthrough of the process of scanning for all nearby BLE peripherals, connecting to each one, discovering its services and characteristics, and then disconnecting.

This is a useful pattern for debugging or developing a general-purpose BLE scanner application.

Full Code

This code is based on examples/discover_adapters_peripherals.rs.

use std::time::Duration;
use tokio::time;

use btleplug::api::{Central, Manager as _, Peripheral, ScanFilter};
use btleplug::platform::Manager;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Initialize the logger
    pretty_env_logger::init();

    // Initialize the manager and get the first adapter
    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 on {}...", adapter.adapter_info().await?);
        adapter
            .start_scan(ScanFilter::default())
            .await
            .expect("Can't scan BLE adapter for connected devices...");

        // Scan for 10 seconds
        time::sleep(Duration::from_secs(10)).await;

        let peripherals = adapter.peripherals().await?;
        if peripherals.is_empty() {
            eprintln!("->>> No BLE peripheral devices were found.");
        } else {
            println!("Found {} peripherals:", peripherals.len());
            // Iterate through all discovered peripherals
            for peripheral in peripherals.iter() {
                let properties = peripheral.properties().await?;
                let local_name = properties
                    .unwrap()
                    .local_name
                    .unwrap_or_else(|| String::from("(peripheral name unknown)"));

                println!("\n--- Peripheral: {} ---", local_name);

                // Try to connect
                println!("Connecting to {}...", &local_name);
                if let Err(err) = peripheral.connect().await {
                    eprintln!("Error connecting to peripheral, skipping: {}", err);
                    continue;
                }

                let is_connected = peripheral.is_connected().await?;
                println!("Successfully connected: {}", is_connected);

                if is_connected {
                    // Discover services
                    println!("Discovering services...");
                    peripheral.discover_services().await?;

                    // Print all services and their characteristics
                    for service in peripheral.services() {
                        println!(
                            "  Service: UUID {}, Primary: {}",
                            service.uuid, service.primary
                        );
                        for characteristic in service.characteristics {
                            println!("    Characteristic: {:?}", characteristic);
                        }
                    }

                    // Disconnect
                    println!("Disconnecting from {}...", &local_name);
                    peripheral
                        .disconnect()
                        .await
                        .expect("Error disconnecting from BLE peripheral");
                }
            }
        }
    }
    Ok(())
}

Explanation

  1. Setup: We initialize a logger, the Manager, and get a list of all available Bluetooth Adapters.
  2. Scan: For each adapter, we call start_scan with a default (empty) filter to find all devices. We sleep for 10 seconds to allow ample time for devices to be discovered.
  3. Get Peripherals: After scanning, adapter.peripherals().await? returns a Vec<Peripheral> containing all devices found during the scan.
  4. Iterate and Connect: The code loops through each peripheral.
    • It retrieves and prints the peripheral's local_name.
    • It attempts to connect(). If the connection fails, it prints an error and moves to the next peripheral.
  5. Discover and Inspect: If the connection is successful:
    • It calls discover_services() to populate the peripheral's service and characteristic cache.
    • It then iterates through peripheral.services() and service.characteristics, printing detailed information about each.
  6. Disconnect: Finally, it calls disconnect() to cleanly close the connection before moving on to the next peripheral.