Example: Controlling a Smart Light
This example demonstrates a practical, real-world use case: controlling a generic BLE-enabled smart light. It showcases how to connect to a specific device and write data to a characteristic to change its color.
Full Code
This code is based on examples/lights.rs
.
use btleplug::api::{
bleuuid::uuid_from_u16, Central, Manager as _, Peripheral as _, ScanFilter, WriteType,
};
use btleplug::platform::{Adapter, Manager, Peripheral};
use rand::{thread_rng, Rng};
use std::time::Duration;
use tokio::time;
use uuid::Uuid;
// The standard short UUID for a generic light control characteristic
const LIGHT_CHARACTERISTIC_UUID: Uuid = uuid_from_u16(0xFFE9);
// Helper 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() -> anyhow::Result<()> {
pretty_env_logger::init();
let manager = Manager::new().await.unwrap();
let central = manager
.adapters()
.await?
.into_iter()
.nth(0)
.expect("Unable to find adapters.");
// Scan for devices and find our light
central.start_scan(ScanFilter::default()).await?;
time::sleep(Duration::from_secs(2)).await;
let light = find_light(¢ral).await.expect("No lights found");
// Connect and discover services
light.connect().await?;
light.discover_services().await?;
// Find the control characteristic
let chars = light.characteristics();
let cmd_char = chars
.iter()
.find(|c| c.uuid == LIGHT_CHARACTERISTIC_UUID)
.expect("Unable to find characterics");
// Create a "dance party" by sending random colors
let mut rng = thread_rng();
for _ in 0..20 {
let color_cmd = vec![
0x56, // Command prefix for this specific light protocol
rng.gen(), // Red component (0-255)
rng.gen(), // Green component (0-255)
rng.gen(), // Blue component (0-255)
0x00,
0xF0,
0xAA, // Command suffix
];
// Write the command to the characteristic
light
.write(&cmd_char, &color_cmd, WriteType::WithoutResponse)
.await?;
time::sleep(Duration::from_millis(200)).await;
}
light.disconnect().await?;
Ok(())
}
Explanation
-
Device Identification: The
find_light
function is similar to previous examples. It scans for peripherals and identifies the target light by looking for"LEDBlue"
in its advertised local name. -
Characteristic Identification: We use a constant
LIGHT_CHARACTERISTIC_UUID
(0xFFE9
) to identify the specific characteristic that accepts color commands. This UUID is common for a certain type of generic BLE light. -
The Command Protocol: The
color_cmd
vector is the most important part of this example. BLE communication is just an exchange of byte arrays. The meaning of these bytes is defined by the peripheral's manufacturer.For this particular light, the protocol is:
0x56
: A required prefix byte to indicate a color command.rng.gen()
: Three random bytes for the Red, Green, and Blue color components.0x00, 0xF0, 0xAA
: Required suffix bytes.
When developing for a new BLE device, you will need to find documentation or reverse-engineer the protocol to understand what byte commands it expects.
-
Writing the Command: We use
light.write()
withWriteType::WithoutResponse
. This is a "fire and forget" write, which is suitable for this light as it doesn't send a confirmation for each color change. This allows for rapid, successive commands to create the color-changing effect.