### Setup and Run Virtual Peripheral for Integration Tests Source: https://github.com/deviceplug/btleplug/blob/master/README.md This snippet details the steps to set up and run a virtual BLE peripheral using Bumble, which is required for integration testing. Ensure you have a USB BLE dongle connected. The transport argument may vary based on your setup. ```bash cd test-peripheral/bumble pip install -r requirements.txt ./run.sh usb:0 # transport argument may vary; see Bumble docs ``` ```bash cargo test --test '*' -- --ignored ``` -------------------------------- ### Start Bumble Virtual Peripheral Source: https://github.com/deviceplug/btleplug/blob/master/docs/android-integration-test-runbook.md Use this command to start the Bumble virtual BLE peripheral. Ensure you have the necessary requirements installed. ```bash cd test-peripheral/bumble pip install -r requirements.txt python test_peripheral.py ``` -------------------------------- ### Setup Bumble Virtual Peripheral Source: https://github.com/deviceplug/btleplug/blob/master/test-peripheral/README.md Commands to set up the Bumble virtual BLE peripheral. This requires Python 3.10+ and a USB BLE dongle. ```bash cd bumble pip install -r requirements.txt ``` -------------------------------- ### Query Bluetooth adapter power state Source: https://context7.com/deviceplug/btleplug/llms.txt This example shows how to get the current power state of the Bluetooth adapter (PoweredOn, PoweredOff, Unknown). It iterates through available adapters and prints their status, which is useful for ensuring Bluetooth is enabled before starting scans. ```rust use btleplug::api::{Central, CentralState, Manager as _}; use btleplug::platform::Manager; #[tokio::main] async fn main() -> anyhow::Result<()> { let manager = Manager::new().await?; let adapters = manager.adapters().await?; for adapter in &adapters { let info = adapter.adapter_info().await?; let state = adapter.adapter_state().await?; match state { CentralState::PoweredOn => println!("{}: Ready", info), CentralState::PoweredOff => println!("{}: Powered off — enable Bluetooth", info), CentralState::Unknown => println!("{}: State unknown", info), } } Ok(()) } ``` -------------------------------- ### Build Integration Test Peripheral Source: https://github.com/deviceplug/btleplug/blob/master/README.md Flash the nRF52840 DK with the test peripheral firmware by building the Zephyr project. This is a one-time setup for running integration tests. ```bash # Flash the nRF52840 DK (one-time setup) cd test-peripheral/zephyr west build -b nrf52840dk/nrf52840 && west flash ``` -------------------------------- ### Check libusb Installation Source: https://github.com/deviceplug/btleplug/blob/master/test-peripheral/README.md Python command to verify if the `pyusb` library can detect the connected USB device. ```python import usb.core; print(list(usb.core.find(find_all=True))) ``` -------------------------------- ### Connect and Disconnect from BLE Devices with Timeouts Source: https://context7.com/deviceplug/btleplug/llms.txt Use `connect_with_timeout` and `discover_services_with_timeout` for controlled connections and service discovery. Ensure `Peripheral::disconnect()` is called to cleanly close the connection. This example requires `tokio` for async operations and `std::time::Duration` for timeouts. ```rust use btleplug::api::{Central, Manager as _, Peripheral, ScanFilter}; use btleplug::platform::Manager; use std::time::Duration; use tokio::time; #[tokio::main] async fn main() -> anyhow::Result<()> { let manager = Manager::new().await?; let adapter = manager.adapters().await?.into_iter().next().unwrap(); adapter.start_scan(ScanFilter::default()).await?; time::sleep(Duration::from_secs(3)).await; let peripherals = adapter.peripherals().await?; let target = peripherals.iter().find(|p| { // synchronous check won't work directly; in practice use properties() in async context true // just take the first one for this example }); if let Some(peripheral) = target { let props = peripheral.properties().await?; let name = props.and_then(|p| p.local_name).unwrap_or("(unknown)".into()); println!("Connecting to {}...", name); // Connect with a 10-second timeout peripheral.connect_with_timeout(Duration::from_secs(10)).await?; println!("Connected: {}", peripheral.is_connected().await?); // Always discover services before accessing characteristics peripheral.discover_services_with_timeout(Duration::from_secs(5)).await?; println!("Services discovered: {}", peripheral.services().len()); peripheral.disconnect().await?; println!("Disconnected."); } Ok(()) } ``` -------------------------------- ### Scan for BLE Peripherals Source: https://context7.com/deviceplug/btleplug/llms.txt Starts or stops BLE peripheral scanning using the host adapter. Optionally filters by service UUID. Discovered devices are accessible via `peripherals()`. ```rust use btleplug::api::{Central, Manager as _, ScanFilter}; use btleplug::platform::Manager; use std::time::Duration; use tokio::time; use uuid::Uuid; #[tokio::main] async fn main() -> anyhow::Result<()> { let manager = Manager::new().await?; let adapter = manager.adapters().await?.into_iter().next() .expect("No adapters found"); // Scan for all devices (no filter) adapter.start_scan(ScanFilter::default()).await?; time::sleep(Duration::from_secs(5)).await; // Or scan for devices advertising a specific service UUID let heart_rate_service = Uuid::parse_str("0000180d-0000-1000-8000-00805f9b34fb").unwrap(); adapter.stop_scan().await?; adapter.start_scan(ScanFilter { services: vec![heart_rate_service] }).await?; time::sleep(Duration::from_secs(5)).await; adapter.stop_scan().await?; let peripherals = adapter.peripherals().await?; println!("Found {} peripheral(s)", peripherals.len()); Ok(()) } ``` -------------------------------- ### Parse and format BDAddr Bluetooth MAC addresses Source: https://context7.com/deviceplug/btleplug/llms.txt Demonstrates parsing Bluetooth MAC addresses from various string formats, converting them to byte arrays and u64, and formatting them for display. Includes examples of error handling for invalid formats. ```rust use btleplug::api::BDAddr; use std::str::FromStr; fn main() { // Parse from colon-delimited string let addr: BDAddr = "AA:BB:CC:DD:EE:FF".parse().unwrap(); println!("{}", addr); // "AA:BB:CC:DD:EE:FF" println!("{:x}", addr); // "aa:bb:cc:dd:ee:ff" println!("{}", addr.to_string_no_delim()); // "aabbccddeeff" // Build from byte array (MSB first) let addr2: BDAddr = [0x2A, 0xCC, 0x00, 0x34, 0xFA, 0x00].into(); assert_eq!("2A:CC:00:34:FA:00", addr2.to_string()); // Parse without delimiters let addr3 = BDAddr::from_str("aabbccddeeff").unwrap(); println!("{}", addr3); // "AA:BB:CC:DD:EE:FF" (note: bytes differ from example) // Convert to/from u64 let addr4: BDAddr = [0x1F, 0x2A, 0x00, 0xCC, 0x22, 0xF1].into(); let as_u64: u64 = addr4.into(); let back: BDAddr = u64::try_from(0x00_00_1f_2a_00_cc_22_f1u64) .map(|v: u64| BDAddr::try_from(v).unwrap()).unwrap(); println!("{}", back); // "1F:2A:00:CC:22:F1" // Error cases assert!("AA:BB:CC".parse::().is_err()); // IncorrectByteCount assert!("AA:BB:CC:DD:EE:ZZ".parse::().is_err()); // InvalidDigit } ``` -------------------------------- ### Create Bluetooth Manager Entry Point Source: https://context7.com/deviceplug/btleplug/llms.txt Initializes the Bluetooth subsystem and retrieves available adapters. This is the first step in any btleplug application. ```rust use btleplug::api::Manager as _; use btleplug::platform::Manager; #[tokio::main] async fn main() -> anyhow::Result<()> { let manager = Manager::new().await?; let adapters = manager.adapters().await?; if adapters.is_empty() { eprintln!("No Bluetooth adapters found on this system."); return Ok(()) } for adapter in &adapters { println!("Adapter: {}", adapter.adapter_info().await?); // e.g. "Adapter: Intel Bluetooth Adapter (hci0)" } Ok(()) } ``` -------------------------------- ### Manager::new() Source: https://context7.com/deviceplug/btleplug/llms.txt Initializes the Bluetooth manager and retrieves available adapters. This is the entry point for using the btleplug library. ```APIDOC ## Manager::new() ### Description `Manager::new()` is the top-level constructor that initializes the platform's Bluetooth subsystem and returns a `Manager` instance. Once obtained, `manager.adapters()` retrieves all available Bluetooth adapters on the host system, each implementing the `Central` trait. This is always the first call in any btleplug application. ### Method `Manager::new()` ### Endpoint N/A (Rust library method) ### Parameters None ### Request Example ```rust use btleplug::api::Manager as _; use btleplug::platform::Manager; #[tokio::main] async fn main() -> anyhow::Result<()> { let manager = Manager::new().await?; let adapters = manager.adapters().await?; if adapters.is_empty() { eprintln!("No Bluetooth adapters found on this system."); return Ok(()) } for adapter in &adapters { println!("Adapter: {}", adapter.adapter_info().await?); // e.g. "Adapter: Intel Bluetooth Adapter (hci0)" } Ok(()) } ``` ### Response #### Success Response Returns a `Manager` instance which can be used to access Bluetooth adapters. #### Response Example (See Request Example for usage) ``` -------------------------------- ### Run Gradle with Increased Verbosity Source: https://github.com/deviceplug/btleplug/blob/master/docs/android-integration-test-runbook.md Navigate to the Android test directory and run Gradle with the `--info` flag for more detailed output during test execution. ```bash cd tests/android JAVA_HOME="/opt/homebrew/opt/openjdk@17" \ ANDROID_HOME="$HOME/Library/Android/sdk" \ ./gradlew connectedAndroidTest --info ``` -------------------------------- ### Run btleplug Integration Tests Source: https://github.com/deviceplug/btleplug/blob/master/test-peripheral/README.md Command to execute the integration tests for btleplug. Ensure you are in the btleplug repository root. ```bash cargo test --test '*' -- --ignored ``` -------------------------------- ### Central::adapter_state() — Query Bluetooth adapter power state Source: https://context7.com/deviceplug/btleplug/llms.txt Retrieves the current power state of the Bluetooth adapter, which can be `PoweredOn`, `PoweredOff`, or `Unknown`. This is essential for ensuring Bluetooth is active before starting operations like scanning. ```APIDOC ## `Central::adapter_state()` — Query Bluetooth adapter power state `adapter_state()` returns the current `CentralState` of the Bluetooth radio: `PoweredOn`, `PoweredOff`, or `Unknown`. This is useful for verifying that Bluetooth is available before initiating scans. On platforms that support it, `StateUpdate` events in the `CentralEvent` stream also signal transitions between these states. ```rust use btleplug::api::{Central, CentralState, Manager as _}; use btleplug::platform::Manager; #[tokio::main] async fn main() -> anyhow::Result<()> { let manager = Manager::new().await?; let adapters = manager.adapters().await?; for adapter in &adapters { let info = adapter.adapter_info().await?; let state = adapter.adapter_state().await?; match state { CentralState::PoweredOn => println!("{}: Ready", info), CentralState::PoweredOff => println!("{}: Powered off — enable Bluetooth", info), CentralState::Unknown => println!("{}: State unknown", info), } } Ok(()) } ``` ``` -------------------------------- ### Architecture Overview Source: https://github.com/deviceplug/btleplug/blob/master/docs/design-plans/2026-02-22-ble-integration-test-system.md Illustrates the two possible configurations for the BLE integration test system: using a physical nRF52840 DK with Zephyr or a virtual peripheral with Python Bumble. Both configurations are accessed by the same Rust integration tests. ```text ┌─────────────────────────┐ BLE Radio ┌─────────────────────────┐ │ Rust Integration Tests │ ◄──────────────► │ nRF52840 DK (Zephyr) │ │ (tests/*.rs) │ │ GATT Test Server │ │ using btleplug API │ │ (test-peripheral/ │ │ │ │ zephyr/) │ └─────────────────────────┘ └─────────────────────────┘ OR (no hardware) ┌─────────────────────────┐ Host BLE Stack ┌─────────────────────────┐ │ Rust Integration Tests │ ◄──────────────► │ Python Bumble │ │ (same tests) │ │ GATT Test Server │ │ │ │ (test-peripheral/ │ │ │ │ bumble/) │ └─────────────────────────┘ └─────────────────────────┘ ``` -------------------------------- ### Peripheral::discover_services() Source: https://context7.com/deviceplug/btleplug/llms.txt Enumerates GATT services and characteristics on a connected peripheral. Populates `services()` and `characteristics()` with discovered information. ```APIDOC ## `Peripheral::discover_services()` — Enumerate GATT services and characteristics `discover_services()` performs GATT service discovery on a connected peripheral, populating `services()` and `characteristics()`. Each `Service` contains its UUID, whether it is a primary service, and a `BTreeSet`. Each `Characteristic` exposes its UUID, service UUID, `CharPropFlags` (READ, WRITE, NOTIFY, INDICATE, etc.), and associated `Descriptor`s. ### Method Asynchronous Rust function call ### Endpoint N/A (SDK method) ### Parameters None ### Request Example ```rust p.discover_services().await?; ``` ### Response Populates internal state with services and characteristics. ``` -------------------------------- ### Running BLE Integration Tests Source: https://github.com/deviceplug/btleplug/blob/master/docs/design-plans/2026-02-22-ble-integration-test-system.md Commands to execute the BLE integration tests. Includes options for running specific tests, all tests, or skipping integration tests. ```bash # With hardware peripheral (nRF52 DK running, or Bumble started): cargo test --test test_read_write # All integration tests: cargo test --test '*' # Skip integration tests (unit tests only): cargo test --lib ``` ```bash cargo test --test '*' -- --ignored ``` -------------------------------- ### Discover GATT Services and Characteristics Source: https://context7.com/deviceplug/btleplug/llms.txt Use `discover_services()` to enumerate GATT services and characteristics on a connected peripheral. This populates the `services()` and `characteristics()` collections. ```rust use btleplug::api::{Central, CharPropFlags, Manager as _, Peripheral, ScanFilter}; use btleplug::platform::Manager; use std::time::Duration; use tokio::time; #[tokio::main] async fn main() -> anyhow::Result<()> { let manager = Manager::new().await?; let adapter = manager.adapters().await?.into_iter().next().unwrap(); adapter.start_scan(ScanFilter::default()).await?; time::sleep(Duration::from_secs(3)).await; let peripherals = adapter.peripherals().await?; if let Some(p) = peripherals.first() { p.connect().await?; p.discover_services().await?; for service in p.services() { println!("Service {} (primary={})", service.uuid, service.primary); for ch in &service.characteristics { let props = ch.properties; println!( " Characteristic {} | READ={} WRITE={} NOTIFY={} INDICATE={}", ch.uuid, props.contains(CharPropFlags::READ), props.contains(CharPropFlags::WRITE), props.contains(CharPropFlags::NOTIFY), props.contains(CharPropFlags::INDICATE), ); for desc in &ch.descriptors { println!(" Descriptor {}", desc.uuid); } } } p.disconnect().await?; } Ok(()) } ``` -------------------------------- ### Event-driven BLE Peripheral Discovery with `Central::events()` Source: https://context7.com/deviceplug/btleplug/llms.txt Use this snippet for reactive applications needing real-time notifications for device discovery, connection state changes, and advertisement data updates. Ensure the `btleplug` and `futures` crates are included. ```rust use btleplug::api::{Central, CentralEvent, Manager as _, ScanFilter, bleuuid::BleUuid}; use btleplug::platform::Manager; use futures::stream::StreamExt; #[tokio::main] async fn main() -> anyhow::Result<()> { let manager = Manager::new().await?; let adapter = manager.adapters().await?.into_iter().next().unwrap(); let mut events = adapter.events().await?; adapter.start_scan(ScanFilter::default()).await?; while let Some(event) = events.next().await { match event { CentralEvent::DeviceDiscovered(id) => { let p = adapter.peripheral(&id).await?; let props = p.properties().await?; let name = props.and_then(|p| p.local_name).unwrap_or_default(); println!("Discovered: {:?} name={}", id, name); } CentralEvent::DeviceConnected(id) => println!("Connected: {:?}", id), CentralEvent::DeviceDisconnected(id) => println!("Disconnected: {:?}", id), CentralEvent::ManufacturerDataAdvertisement { id, manufacturer_data } => { println!("Manufacturer data from {:?}: {:?}", id, manufacturer_data); } CentralEvent::ServicesAdvertisement { id, services } => { let svc_strs: Vec<_> = services.iter().map(|s| s.to_short_string()).collect(); println!("Services from {:?}: {:?}", id, svc_strs); } CentralEvent::RssiUpdate { id, rssi } => { println!("RSSI update {:?}: {} dBm", id, rssi); } CentralEvent::StateUpdate(state) => println!("Adapter state: {:?}", state), _ => {} } } Ok(()) } ``` -------------------------------- ### Build and Flash Zephyr Test Peripheral Source: https://github.com/deviceplug/btleplug/blob/master/docs/design-plans/2026-02-22-ble-integration-test-system.md Commands to build the Zephyr firmware for the nRF52840 DK and flash it to the device. Ensure you are in the `test-peripheral/zephyr` directory. ```bash cd test-peripheral/zephyr west build -b nrf52840dk/nrf52840 west flash ``` -------------------------------- ### Zephyr Peripheral Configuration Source: https://github.com/deviceplug/btleplug/blob/master/docs/zephyr-test-peripheral-debugging.md Essential `prj.conf` settings for enabling Bluetooth, configuring it as a peripheral, setting the device name, and enabling RTT logging. ```kconfig CONFIG_BT=y CONFIG_BT_PERIPHERAL=y CONFIG_BT_DEVICE_NAME="btleplug-test" CONFIG_BT_DEVICE_NAME_DYNAMIC=n CONFIG_BT_ATT_PREPARE_COUNT=5 CONFIG_BT_BUF_ACL_RX_SIZE=251 CONFIG_BT_BUF_ACL_TX_SIZE=251 CONFIG_BT_L2CAP_TX_MTU=247 CONFIG_LOG=y CONFIG_BT_LOG_LEVEL_INF=y CONFIG_USE_SEGGER_RTT=y CONFIG_LOG_BACKEND_RTT=y CONFIG_LOG_BACKEND_UART=n CONFIG_CONSOLE=y CONFIG_RTT_CONSOLE=y CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 CONFIG_HEAP_MEM_POOL_SIZE=4096 ``` -------------------------------- ### Build and Flash Zephyr Firmware Source: https://github.com/deviceplug/btleplug/blob/master/test-peripheral/README.md Commands to build and flash the Zephyr firmware for nRF52840 DK and ESP32-S3 DevKitC boards. ```bash cd zephyr # nRF52840 DK west build -b nrf52840dk/nrf52840 west flash # ESP32-S3 DevKitC west build -b esp32s3_devkitc/esp32s3/procpu west flash ``` -------------------------------- ### Run Android BLE Integration Tests Source: https://github.com/deviceplug/btleplug/blob/master/docs/plan-fix-android-test-failures.md Execute the Android BLE integration tests after setting up permissions and ensuring the screen stays on. Monitor logs for PASS indicators. ```bash adb shell svc power stayon true adb shell input keyevent KEYCODE_WAKEUP adb shell am instrument -w com.nonpolynomial.btleplug.test.test/androidx.test.runner.AndroidJUnitRunner ``` -------------------------------- ### Run Android Integration Tests Source: https://github.com/deviceplug/btleplug/blob/master/docs/android-integration-test-runbook.md Execute the Android integration tests using the provided script. You can customize environment variables for specific configurations. ```bash ANDROID_HOME="$HOME/Library/Android/sdk" ./scripts/run-integration-tests-android.sh ``` ```bash ANDROID_HOME="$HOME/Library/Android/sdk" \ BTLEPLUG_TEST_PERIPHERAL="btleplug-test" \ TARGET_ARCH=arm64-v8a \ ./scripts/run-integration-tests-android.sh ``` -------------------------------- ### Central::start_scan() / Central::stop_scan() Source: https://context7.com/deviceplug/btleplug/llms.txt Manages the scanning process for BLE peripherals. `start_scan` begins collecting advertising packets, optionally filtered by service UUID, and `stop_scan` halts the process. ```APIDOC ## Central::start_scan() / Central::stop_scan() ### Description `start_scan()` begins advertising packet collection using the host adapter. A `ScanFilter` can optionally restrict results to devices advertising specific service UUIDs. The scan continues until `stop_scan()` is called or the process exits. Discovered devices accumulate in the adapter's internal peripheral list and are accessible via `peripherals()`. ### Method `start_scan(ScanFilter)` `stop_scan()` ### Endpoint N/A (Rust library methods) ### Parameters #### `start_scan` Parameters - **ScanFilter** (struct) - Optional - Used to filter scan results by service UUIDs. - **services** (Vec) - Optional - A list of service UUIDs to filter by. #### `stop_scan` Parameters None ### Request Example ```rust use btleplug::api::{Central, Manager as _, ScanFilter}; use btleplug::platform::Manager; use std::time::Duration; use tokio::time; use uuid::Uuid; #[tokio::main] async fn main() -> anyhow::Result<()> { let manager = Manager::new().await?; let adapter = manager.adapters().await?.into_iter().next() .expect("No adapters found"); // Scan for all devices (no filter) adapter.start_scan(ScanFilter::default()).await?; time::sleep(Duration::from_secs(5)).await; // Or scan for devices advertising a specific service UUID let heart_rate_service = Uuid::parse_str("0000180d-0000-1000-8000-00805f9b34fb").unwrap(); adapter.stop_scan().await?; adapter.start_scan(ScanFilter { services: vec![heart_rate_service] }).await?; time::sleep(Duration::from_secs(5)).await; adapter.stop_scan().await?; let peripherals = adapter.peripherals().await?; println!("Found {} peripheral(s)", peripherals.len()); Ok(()) } ``` ### Response #### Success Response `start_scan` initiates scanning. `stop_scan` halts scanning. `peripherals()` returns a list of discovered `Peripheral` objects after scanning. #### Response Example (See Request Example for usage) ``` -------------------------------- ### Read and Write Peripheral Descriptors (Rust) Source: https://context7.com/deviceplug/btleplug/llms.txt Demonstrates reading and writing to a peripheral's descriptor, specifically the Client Characteristic Configuration Descriptor (CCCD) to control notifications. Use `subscribe`/`unsubscribe` for most CCCD operations. ```rust use btleplug::api::{Central, Manager as _, Peripheral, ScanFilter, bleuuid::uuid_from_u16}; use btleplug::platform::Manager; use std::time::Duration; use tokio::time; // Client Characteristic Configuration Descriptor (CCCD) const CCCD_UUID: uuid::Uuid = uuid_from_u16(0x2902); #[tokio::main] async fn main() -> anyhow::Result<()> { let manager = Manager::new().await?; let adapter = manager.adapters().await?.into_iter().next().unwrap(); adapter.start_scan(ScanFilter::default()).await?; time::sleep(Duration::from_secs(3)).await; let peripherals = adapter.peripherals().await?; if let Some(p) = peripherals.first() { p.connect().await?; p.discover_services().await?; // Find the first characteristic with a CCCD descriptor 'outer: for service in p.services() { for ch in &service.characteristics { for desc in &ch.descriptors { if desc.uuid == CCCD_UUID { // Read current CCCD value let value = p.read_descriptor(desc).await?; println!("CCCD value: {:?}", value); // e.g. [0x00, 0x00] = notifications off // Enable notifications: write 0x0100 (little-endian) p.write_descriptor(desc, &[0x01, 0x00]).await?; println!("Enabled notifications via CCCD write"); break 'outer; } } } } p.disconnect().await?; } Ok(()) } ``` -------------------------------- ### Run Bumble Virtual Peripheral Source: https://github.com/deviceplug/btleplug/blob/master/test-peripheral/README.md Commands to run the Bumble virtual peripheral using a USB dongle or Linux HCI socket. Requires `libusb`. ```bash ./run.sh usb:0 # USB dongle (most common) ./run.sh hci-socket:0 # Linux HCI socket (requires sudo) ``` -------------------------------- ### Enable Serde Feature for btleplug Source: https://github.com/deviceplug/btleplug/blob/master/README.md To enable the serialization and deserialization features for common types in the `api` module, add the `serde` feature to your `btleplug` dependency in `Cargo.toml`. ```toml [dependencies] btleplug = { version = "0.12", features = ["serde"] } ``` -------------------------------- ### Convert between BLE short and full UUIDs Source: https://context7.com/deviceplug/btleplug/llms.txt Shows how to create full 128-bit UUIDs from 16-bit and 32-bit short UUIDs using the standard Bluetooth Base UUID. Also demonstrates converting full UUIDs back to their short form and handling non-standard UUIDs. ```rust use btleplug::api::bleuuid::{uuid_from_u16, uuid_from_u32, BleUuid}; use uuid::Uuid; fn main() { // Create a full UUID from a 16-bit short UUID (standard GATT) let heart_rate_svc = uuid_from_u16(0x180D); println!("{}", heart_rate_svc); // "0000180d-0000-1000-8000-00805f9b34fb" println!("{}", heart_rate_svc.to_short_string()); // "0x180d" // Create from a 32-bit short UUID let uuid32 = uuid_from_u32(0x11223344); println!("{}", uuid32.to_short_string()); // "0x11223344" // Convert a full UUID back to its short form let battery_svc = Uuid::parse_str("0000180f-0000-1000-8000-00805f9b34fb").unwrap(); println!("{:?}", battery_svc.to_ble_u16()); // Some(0x180f) println!("{:?}", battery_svc.to_ble_u32()); // Some(0x0000180f) // Non-standard UUID returns None let custom = Uuid::parse_str("12345678-9000-1000-8000-00805f9b34fb").unwrap(); println!("{:?}", custom.to_ble_u16()); // None println!("{}", custom.to_short_string()); // "12345678-9000-1000-8000-00805f9b34fb" // Typical usage: filtering characteristics by standard UUID let target_uuid = uuid_from_u16(0x2A37); // Heart Rate Measurement characteristic println!("Looking for {}", target_uuid.to_short_string()); // "0x2a37" } ``` -------------------------------- ### Rust Test Suite Directory Structure Source: https://github.com/deviceplug/btleplug/blob/master/docs/design-plans/2026-02-22-ble-integration-test-system.md Overview of the directory structure for the Rust integration test suite. This helps in understanding the organization of different test modules. ```rust tests/ ├── common/ │ ├── mod.rs — Re-exports helpers │ ├── peripheral_finder.rs — Scan for "btleplug-test", connect, discover services │ └── gatt_uuids.rs — UUID constants matching the GATT profile above ├── test_discovery.rs — Scanning, advertisement data, CentralEvents ├── test_connection.rs — Connect, disconnect, reconnect, is_connected ├── test_read_write.rs — read(), write() with both WriteTypes, long values ├── test_notifications.rs — subscribe(), unsubscribe(), notifications stream ├── test_descriptors.rs — read_descriptor(), write_descriptor() └── test_device_info.rs — properties(), mtu(), read_rssi(), connection_parameters() ``` -------------------------------- ### Add Timeouts to Connect and Discover Services Source: https://github.com/deviceplug/btleplug/blob/master/docs/zephyr-test-peripheral-debugging.md Mitigates indefinite hangs during peripheral connection or service discovery by adding 10-second timeouts to `connect()` and `discover_services()` calls within the `find_and_connect()` function. ```rust peripheral.connect().await ``` -------------------------------- ### Peripheral::connect() / Peripheral::disconnect() Source: https://context7.com/deviceplug/btleplug/llms.txt Establishes and closes a GATT connection to a BLE peripheral. `connect_with_timeout` allows specifying a duration for the connection attempt. After connecting, `discover_services()` must be called before accessing characteristics. ```APIDOC ## Peripheral::connect() / Peripheral::disconnect() ### Description `connect()` establishes a GATT connection to a peripheral. The optional `connect_with_timeout()` variant returns `Error::TimedOut` if the connection does not complete within the specified `Duration`. Once connected, `discover_services()` must be called before accessing characteristics. `disconnect()` cleanly closes the connection. ### Method `connect()` `connect_with_timeout(timeout: Duration)` `disconnect()` ### Parameters - `connect_with_timeout`: Takes a `Duration` specifying the maximum time to wait for the connection. ### Returns - `connect()`: Returns `Ok(())` on successful connection or an `Error`. - `connect_with_timeout()`: Returns `Ok(())` on successful connection, `Error::TimedOut` if the timeout is reached, or another `Error`. - `disconnect()`: Returns `Ok(())` on successful disconnection or an `Error`. ``` -------------------------------- ### Serialize and Deserialize BDAddr with btleplug Source: https://context7.com/deviceplug/btleplug/llms.txt Demonstrates how to serialize and deserialize Bluetooth device addresses (BDAddr) using `serde` with `btleplug`. Shows default colon-delimited format and custom no-delimiter format. ```rust use btleplug::api::{BDAddr, PeripheralProperties}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] struct DeviceRecord { addr: BDAddr, name: Option, rssi: Option, } fn main() -> serde_json::Result<()> { // Serialize a BDAddr — colon-delimited format by default let addr: BDAddr = [0x00, 0xDE, 0xAD, 0xBE, 0xEF, 0x00].into(); let json = serde_json::to_string(&addr)?; println!("{}", json); // "\"00:DE:AD:BE:EF:00\"" // Deserialize from JSON let record: DeviceRecord = serde_json::from_str(r#" {"addr": "00:DE:AD:BE:EF:00", "name": "HeartMonitor", "rssi": -65} "#)?; println!("{:?}", record); // DeviceRecord { addr: 00:DE:AD:BE:EF:00, name: Some("HeartMonitor"), rssi: Some(-65) } // No-delimiter format via serde attribute #[derive(Debug, Serialize, Deserialize)] struct NoDelimRecord { #[serde(with = "btleplug::serde::bdaddr::no_delim")] addr: BDAddr, } let r: NoDelimRecord = serde_json::from_str(r#"{"addr": "00deadbeef00"}"#)?; println!("{:?}", r); // NoDelimRecord { addr: 00:DE:AD:BE:EF:00 } Ok(()) } ``` -------------------------------- ### Peripheral Connection Parameters Management Source: https://context7.com/deviceplug/btleplug/llms.txt This section covers how to retrieve current BLE connection parameters and request changes to them. The `connection_parameters()` method retrieves the current negotiated parameters (supported on Windows and Android), while `request_connection_parameters()` allows requesting specific presets like `Balanced`, `ThroughputOptimized`, or `PowerOptimized` from the remote device. ```APIDOC ## `Peripheral::connection_parameters()` / `request_connection_parameters()` — Connection parameter management `connection_parameters()` retrieves the current negotiated BLE connection interval, slave latency, and supervision timeout from the OS (supported on Windows and Android). `request_connection_parameters()` asks the remote device to update parameters using a `ConnectionParameterPreset`: `Balanced` (default), `ThroughputOptimized` (low latency bulk transfers), or `PowerOptimized` (reduced power). This is a request — the remote device may accept or reject it. ```rust use btleplug::api::{Central, ConnectionParameterPreset, Manager as _, Peripheral, ScanFilter}; use btleplug::platform::Manager; use std::time::Duration; use tokio::time; #[tokio::main] async fn main() -> anyhow::Result<()> { let manager = Manager::new().await?; let adapter = manager.adapters().await?.into_iter().next().unwrap(); adapter.start_scan(ScanFilter::default()).await?; time::sleep(Duration::from_secs(3)).await; let peripherals = adapter.peripherals().await?; if let Some(p) = peripherals.first() { p.connect().await?; p.discover_services().await?; // Read current parameters (Windows/Android only) match p.connection_parameters().await { Ok(Some(params)) => println!( "Interval: {}µs, Latency: {}, Supervision timeout: {}µs", params.interval_us, params.latency, params.supervision_timeout_us ), Ok(None) => println!("Connection parameters not supported on this platform"), Err(e) => println!("Error reading parameters: {}", e), } // Request throughput-optimized parameters for a bulk transfer match p.request_connection_parameters(ConnectionParameterPreset::ThroughputOptimized).await { Ok(()) => println!("Throughput-optimized parameters requested"), Err(e) => println!("Request not supported: {}", e), } // ... perform bulk data transfer ... // Return to balanced parameters when done let _ = p.request_connection_parameters(ConnectionParameterPreset::Balanced).await; p.disconnect().await?; } Ok(()) } ``` ``` -------------------------------- ### Central::events() Source: https://context7.com/deviceplug/btleplug/llms.txt Provides an asynchronous stream for real-time BLE events including device discovery, connection status changes, advertisement data, RSSI updates, and adapter state changes. This is ideal for reactive applications. ```APIDOC ## Central::events() ### Description Returns an async `Stream` for real-time notifications on device discovery, connection state changes, advertisement data updates, RSSI updates, and adapter state changes. This is the preferred approach for reactive applications. ### Method `events()` ### Parameters None ### Returns An asynchronous stream of `CentralEvent` items. ``` -------------------------------- ### Manage BLE Connection Parameters Source: https://context7.com/deviceplug/btleplug/llms.txt Retrieves current BLE connection parameters and requests new ones. Supported on Windows and Android for reading parameters. Requesting new parameters is a suggestion to the remote device. ```rust use btleplug::api::{Central, ConnectionParameterPreset, Manager as _, Peripheral, ScanFilter}; use btleplug::platform::Manager; use std::time::Duration; use tokio::time; #[tokio::main] async fn main() -> anyhow::Result<()> { let manager = Manager::new().await?; let adapter = manager.adapters().await?.into_iter().next().unwrap(); adapter.start_scan(ScanFilter::default()).await?; time::sleep(Duration::from_secs(3)).await; let peripherals = adapter.peripherals().await?; if let Some(p) = peripherals.first() { p.connect().await?; p.discover_services().await?; // Read current parameters (Windows/Android only) match p.connection_parameters().await { Ok(Some(params)) => println!( "Interval: {}µs, Latency: {}, Supervision timeout: {}µs", params.interval_us, params.latency, params.supervision_timeout_us ), Ok(None) => println!("Connection parameters not supported on this platform"), Err(e) => println!("Error reading parameters: {}", e), } // Request throughput-optimized parameters for a bulk transfer match p.request_connection_parameters(ConnectionParameterPreset::ThroughputOptimized).await { Ok(()) => println!("Throughput-optimized parameters requested"), Err(e) => println!("Request not supported: {}", e), } // ... perform bulk data transfer ... // Return to balanced parameters when done let _ = p.request_connection_parameters(ConnectionParameterPreset::Balanced).await; p.disconnect().await?; } Ok(()) } ``` -------------------------------- ### Android Proguard/R8 Configuration for btleplug Source: https://github.com/deviceplug/btleplug/blob/master/README.md If using Proguard/R8 with `minifyEnabled true` on Android, add these keep rules to your `proguard-rules.pro` file to prevent btleplug's Java classes from being stripped. ```groovy buildTypes { release { shrinkResources true minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } ``` ```proguard # btleplug Java classes (accessed only via JNI) -keep class com.nonpolynomial.** { *; } -keep class io.github.gedgygedgy.** { *; } ``` -------------------------------- ### Defer Advertising Restart with Workqueue Source: https://github.com/deviceplug/btleplug/blob/master/docs/zephyr-test-peripheral-debugging.md Fixes an issue where the peripheral fails to re-advertise after a disconnect. Deferring the advertising restart to a system workqueue prevents race conditions with the BLE controller's disconnect procedure. ```c static void restart_adv_work_handler(struct k_work *work) { bt_le_adv_stop(); int err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd)); if (err) { LOG_ERR("Advertising restart failed (err %d)", err); } } static K_WORK_DELAYABLE_DEFINE(restart_adv_work, restart_adv_work_handler); // In disconnected callback: k_work_reschedule(&restart_adv_work, K_MSEC(100)); ``` -------------------------------- ### Conditional Compilation Strategy for droidplug Shim Source: https://github.com/deviceplug/btleplug/blob/master/docs/design-plans/2026-03-08-jni-host-tests.md Use a minimal `droidplug` shim on non-Android targets to preserve the existing module hierarchy. This allows `jni_utils` to be compiled on host targets when the `jni-host-tests` feature is enabled. ```rust // src/lib.rs // Full droidplug on Android (existing, unchanged) #[cfg(target_os = "android")] mod droidplug; // Minimal shim on host — only jni_utils, no Android BLE code #[cfg(all(not(target_os = "android"), feature = "jni-host-tests"))] mod droidplug { pub mod jni_utils; } ``` -------------------------------- ### Subscribe to Characteristic Notifications Source: https://context7.com/deviceplug/btleplug/llms.txt Use `subscribe()` to enable notifications and `notifications()` to receive them as a stream. The stream handles reconnections automatically. Ensure the characteristic supports `CharPropFlags::NOTIFY`. ```rust use btleplug::api::{Central, CharPropFlags, Manager as _, Peripheral, ScanFilter}; use btleplug::platform::Manager; use futures::stream::StreamExt; use std::time::Duration; use tokio::time; use uuid::Uuid; // Nordic UART Service TX characteristic (example notify characteristic) const NOTIFY_UUID: Uuid = Uuid::from_u128(0x6e400003_b534_f393_67a9_e50e24dcca9e); #[tokio::main] async fn main() -> anyhow::Result<()> { let manager = Manager::new().await?; let adapter = manager.adapters().await?.into_iter().next().unwrap(); adapter.start_scan(ScanFilter::default()).await?; time::sleep(Duration::from_secs(3)).await; let peripherals = adapter.peripherals().await?; if let Some(p) = peripherals.first() { p.connect().await?; p.discover_services().await?; let chars = p.characteristics(); if let Some(notify_char) = chars.iter().find(|c| c.uuid == NOTIFY_UUID) { if notify_char.properties.contains(CharPropFlags::NOTIFY) { p.subscribe(notify_char).await?; println!("Subscribed to notifications on {}", notify_char.uuid); // Collect the first 10 notifications then unsubscribe let mut stream = p.notifications().await?.take(10); while let Some(notification) = stream.next().await { println!( "Notification from char={} service={} data={:?}", notification.uuid, notification.service_uuid, notification.value ); } p.unsubscribe(notify_char).await?; println!("Unsubscribed."); } } p.disconnect().await?; } Ok(()) } ```