Quick Start: Your First BLE Interaction
This guide provides a complete, runnable example to get you started with btleplug
. We will scan for a BLE peripheral, connect to it, discover its services, and perform a simple interaction.
The Goal: A BLE Dance Party
The following example discovers a BLE-enabled LED light, connects to it, and sends commands to change its color randomly.
Full Example Code
This code is adapted from the lights.rs
example in the btleplug
repository. You can use it as a template for your own projects.
use btleplug::api::{bleuuid::uuid_from_u16, Central, Manager as _, Peripheral as _, ScanFilter, WriteType};
use btleplug::platform::{Adapter, Manager, Peripheral};
use rand::{Rng, thread_rng};
use std::error::Error;
use std::time::Duration;
use tokio::time;
use uuid::Uuid;
// The characteristic UUID for the light control.
const LIGHT_CHARACTERISTIC_UUID: Uuid = uuid_from_u16(0xFFE9);
// Asynchronous function to find a peripheral with "LEDBlue" in its name.
async fn find_light(central: &Adapter) -> Option<Peripheral> {
for p in central.peripherals().await.unwrap() {
if p.properties()
.await
.unwrap()
.unwrap()
.local_name
.iter()
.any(|name| name.contains("LEDBlue"))
{
return Some(p);
}
}
None
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// 1. Initialize the Manager
let manager = Manager::new().await?;
// 2. Get the first available adapter
let adapters = manager.adapters().await?;
let central = adapters.into_iter().nth(0).expect("No Bluetooth adapters found.");
// 3. Start scanning for peripherals
central.start_scan(ScanFilter::default()).await?;
println!("Scanning for 2 seconds...");
time::sleep(Duration::from_secs(2)).await;
// 4. Find our specific peripheral
let light = find_light(¢ral).await.expect("Could not find a light peripheral.");
let properties = light.properties().await?.unwrap();
println!("Found peripheral: {}", properties.local_name.unwrap_or_else(|| "(unknown)".into()));
// 5. Connect to the peripheral
println!("Connecting to peripheral...");
light.connect().await?;
println!("Connected!");
// 6. Discover services and characteristics
light.discover_services().await?;
// 7. Find the characteristic we want to write to
let chars = light.characteristics();
let cmd_char = chars
.iter()
.find(|c| c.uuid == LIGHT_CHARACTERISTIC_UUID)
.expect("Unable to find the light control characteristic.");
// 8. Send commands to the light
println!("Starting dance party...");
let mut rng = thread_rng();
for _ in 0..20 {
let color_cmd = vec![
0x56,
rng.gen(), // Red
rng.gen(), // Green
rng.gen(), // Blue
0x00,
0xF0,
0xAA,
];
light.write(cmd_char, &color_cmd, WriteType::WithoutResponse).await?;
time::sleep(Duration::from_millis(200)).await;
}
println!("Dance party over. Disconnecting...");
light.disconnect().await?;
Ok(())
}
Breakdown of the Code
-
Initialize the Manager:
Manager::new().await?
is the entry point to the library. It finds and initializes the underlying platform-specific Bluetooth backend. -
Get an Adapter:
manager.adapters().await?
returns a list of available Bluetooth adapters on the system. We take the first one for simplicity. -
Start Scanning:
central.start_scan(ScanFilter::default()).await?
begins scanning for nearby BLE devices. We use an emptyScanFilter
to discover all devices. We then wait for a couple of seconds to allow time for advertisements to be received. -
Find the Peripheral: The
find_light
helper function iterates through the list of discovered peripherals (central.peripherals().await?
) and checks their properties for a specific local name. -
Connect:
light.connect().await?
establishes a GATT connection with the peripheral. -
Discover Services:
light.discover_services().await?
is a crucial step. After connecting, you must query the peripheral to discover its available services and their associated characteristics. -
Find the Characteristic: We iterate through the cached characteristics (
light.characteristics()
) to find the specific one that controls the light, identified by itsUuid
. -
Send Commands:
light.write(...)
sends a byte payload to the characteristic. We useWriteType::WithoutResponse
because this particular light doesn't send a confirmation for color changes, which is common for high-frequency updates. -
Disconnect:
light.disconnect().await?
cleanly terminates the connection to the peripheral.