Bluest: Cross-platform BLE library for Rust

repository·main·Indexed 41 days ago

https://github.com/alexmoon/bluest

A cross-platform Bluetooth Low Energy (BLE) library for Rust providing a thin, safe abstraction for GAP Central and GATT Client roles on Windows, MacOS/iOS, and Linux. Version 0.6.10 supports device discovery, pairing, and GATT operations including service and characteristic discovery, reading, writing, and notifications. Requires the Tokio runtime on Linux.

Tokens
10.9K
Snippets
35
Records
45
Agent score
99%

What's inside Bluest

  1. Core BLE capabilities in Bluest

    main

    Bluest provides a thin abstraction for the following BLE operations:

    Device Discovery

    • Scanning: Use Adapter::scan to find devices and receive advertisements.
    • Connected Devices: Use Adapter::connected_devices to find currently connected devices.
    • Opening Devices: Use Adapter::open_device to access previously found devices.
    • Connecting: Use Adapter::connect_device to establish a connection.
    • Pairing: Use Device::pair to pair with a device.

    GATT Operations

    • Service Discovery: Use Device::discover_services to find services on a device.
    • Characteristic Discovery: Use Service::discover_characteristics to find characteristics within a service.
    • Descriptor Discovery: Use Characteristic::discover_descriptors to find descriptors for a characteristic.
    • Data Operations:
      • Read: Characteristic::read or Descriptor::read.
      • Write: Characteristic::write (with response) or Characteristic::write_without_response.
      • Notify/Indicate: Characteristic::notify.
      • Descriptor Write: Descriptor::write.
  2. Platform support and API limitations

    main

    Bluest provides a thin abstraction, meaning it only exposes the lowest common denominator of APIs across supported platforms.

    Important Note on Addresses: Because Apple's CoreBluetooth does not expose Bluetooth addresses to applications, Bluest does not provide a method to retrieve a device's Bluetooth address, nor does it include a Bluetooth address struct.

    Supported Platforms

    • Windows (version 10 and later)
    • MacOS/iOS
    • Linux
    • Android support is planned.

    API Availability Matrix

    MethodMacOS/iOSWindowsLinux
    Adapter::connect_device✅✨✅
    Adapter::disconnect_device✅✨✅
    Device::name✅✅⌛️
    Device::is_paired❌✅✅
    Device::pair✨✅✅
    Device::pair_with_agent✨✅✅
    Device::unpair❌✅✅
    Device::rssi✅❌❌
    Service::uuid✅✅⌛️
    Service::is_primary✅❌✅
    Characteristic::uuid✅✅⌛️
    Characteristic::max_write_len✅✅⌛️
    Descriptor::uuid✅✅-

    Legend:

    • ✅ = supported
    • ✨ = managed automatically by the OS (method is a no-op)
    • ⌛️ = underlying API is async; uses Tokio's block_in_place internally
    • ❌ = returns ErrorKind::NotSupported error
  3. Quickstart: Scanning for BLE devices

    main

    To start scanning for Bluetooth Low Energy devices, initialize an Adapter, wait for it to become available, and then call scan. You can iterate over the discovered devices using the scan object's stream.

    let adapter = Adapter::default().await.ok_or("Bluetooth adapter not found")?;
    adapter.wait_available().await?;
    
    println!("starting scan");
    let mut scan = adapter.scan(&[]).await?;
    println!("scan started");
    while let Some(discovered_device) = scan.next().await {
       println!(
           "{}{}: {:?}",
           discovered_device.device.name().as_deref().unwrap_or("(unknown)"),
           discovered_device
               .rssi
               .map(|x| format!(" ({}dBm)", x))
               .unwrap_or_default(),
           discovered_device.adv_data.services
       );
    }
  4. Asynchronous runtime requirements

    main

    Bluest's runtime requirements depend on the target platform:

    • Non-Linux platforms: Works with any asynchronous runtime.
    • Linux: Requires the Tokio runtime. Bluest uses Tokio's block_in_place API (which requires a multi-threaded runtime) to provide synchronous versions of certain methods. However, Linux-only asynchronous versions of these methods are also provided and should be preferred in platform-specific code.
  5. Enable Serde support via feature flags

    main

    To enable serialization and deserialization of device identifiers, enable the serde feature flag in your Cargo.toml.

    [dependencies]
    bluest = { version = "0.6.9", features = ["serde"] }
  6. Handle Bluetooth Attribute Protocol (ATT) errors

    main

    When an error occurs during an ATT operation, the ErrorKind::Protocol variant contains an AttError. AttError represents specific error codes defined in the Bluetooth Core Specification. You can convert between u8 and AttError using AttError::from_u8(val) or att_error.as_u8().

    // Example of handling a protocol error
    match error.kind() {
        ErrorKind::Protocol(att_err) => {
            if att_err == AttError::INSUFFICIENT_ENCRYPTION {
                // Trigger encryption process
            }
        }
        _ => {}
    }
  7. How Bluest's device discovery and GATT access work

    main

    Bluest provides a thin abstraction for the GAP Central and GATT Client roles. The workflow typically follows these steps:

    1. Discovery: Use Adapter::scan to find devices or Adapter::connected_devices to find existing connections.
    2. Connection: Use Adapter::open_device to access a known device or Adapter::connect_device to establish a new connection.
    3. Pairing: Use Device::pair or Device::pair_with_agent to pair with a device.
    4. GATT Exploration: Once connected, use Device::discover_services to find services, Service::discover_characteristics to find characteristics within a service, and Characteristic::discover_descriptors to find descriptors.
    5. Data Operations: Perform Characteristic::read, Characteristic::write, Characteristic::write_without_response, or Characteristic::notify on characteristics, and Descriptor::read/write on descriptors.
  8. Quickstart: Scanning for BLE devices with Bluest

    main

    To start using Bluest, initialize an Adapter, wait for it to become available, and then call scan. You can iterate over the discovered devices using a stream. Each AdvertisingDevice provides access to the Device object, advertisement data, and RSSI (signal strength).

    Note: On Linux, Bluest requires the tokio multi-threaded runtime because it uses block_in_place for certain synchronous operations.

    use bluest::Adapter;
    use futures_lite::StreamExt;
    
    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
        let adapter = Adapter::default().await?;
        adapter.wait_available().await?;
    
        println!("starting scan");
        let mut scan = adapter.scan(&[]).await?;
        println!("scan started");
    
        while let Some(discovered_device) = scan.next().await {
            println!(
                "{}{}: {:?}",
                discovered_device.device.name().as_deref().unwrap_or("(unknown)"),
                discovered_device
                    .rssi
                    .map(|x| format!(" ({}dBm)", x))
                    .unwrap_or_default(),
                discovered_device.adv_data.services
            );
        }
    
        Ok(())
    }
  9. Initialize the Bluetooth Adapter

    main

    To interact with Bluetooth hardware, you must first create an Adapter instance. You can use the default configuration or provide a custom AdapterConfig via with_config.

    Note: Adapter::default() is not available on Android.

    // Using default configuration (not available on Android)
    let adapter = Adapter::default().await?;
    
    // Using custom configuration
    let config = AdapterConfig::default(); // or your custom config
    let adapter = Adapter::with_config(config).await?;
  10. Panics in Service::uuid() on Linux

    main

    The synchronous uuid() method on Linux may panic in the following scenarios:

    1. There is a current Tokio runtime and it is single-threaded.
    2. There is no current Tokio runtime and attempting to create one fails.
    3. The underlying uuid_async() method fails.

    To avoid panics, prefer using the asynchronous uuid_async() method.

  11. Platform-specific behavior for Service::is_primary()

    main

    When calling is_primary(), be aware of platform limitations:

    • Windows: This method returns crate::error::ErrorKind::NotSupported because the underlying platform does not support this check.
  12. Use NoInputOutputPairingAgent for JustWorks pairing

    main

    The NoInputOutputPairingAgent is a built-in, zero-interaction agent. It sets the IoCapability to NoInputNoOutput and automatically confirms any pairing requests that do not require user input or output.

    Warning: This enables "JustWorks" pairing, which provides encryption but does not provide authentication or protection against Man-in-the-Middle (MITM) attacks.

    use bluest::pairing::NoInputOutputPairingAgent;
    
    // Use this agent when you want to skip all user prompts
    let agent = NoInputOutputPairingAgent;