### Build and Run ESP32 Example Source: https://github.com/embassy-rs/trouble/blob/main/examples/esp32/README.md Build and run an example on your ESP32 device. Ensure you are in the correct example directory and specify the chip features and target architecture. ```bash cd examples/esp32 # make sure you are in the right directory cargo run --release --no-default-features --features=esp32c6 --target=riscv32imac-unknown-none-elf --bin ble_bas_peripheral ``` -------------------------------- ### Install espflash Source: https://github.com/embassy-rs/trouble/blob/main/examples/esp32/README.md Install `espflash` and `cargo-espflash` using `cargo install` or `cargo binstall`. `cargo-binstall` is recommended for faster installation. ```bash cargo install cargo-binstall # binary installer tool (optional but recommended) cargo binstall cargo-espflash espflash # or cargo install if you don't use binstall ``` -------------------------------- ### Build and Run nRF Example Source: https://github.com/embassy-rs/trouble/blob/main/examples/nrf52/README.md Builds and runs an nRF example using cargo, specifying the target chip features and architecture. ```bash cd examples/nrf5 # make sure you are in the right directory cargo run --release --features nrf52833 --target thumbv7em-none-eabihf --bin ble_bas_peripheral ``` -------------------------------- ### Install probe-rs-tools Source: https://github.com/embassy-rs/trouble/blob/main/examples/nrf52/README.md Installs the probe-rs tools for flashing and debugging. Use cargo-binstall for a faster installation. ```bash cargo install cargo-binstall cargo binstall probe-rs-tools ``` -------------------------------- ### Build and Run nRF Example Source: https://github.com/embassy-rs/trouble/blob/main/examples/nrf54/README.md Builds and runs a specific nRF example (ble_bas_peripheral) for the nrf54l15 chip using the specified target architecture and release profile. ```bash cd examples/nrf54 cargo run --release --features nrf54l15 --target thumbv8m.main-none-eabihf --bin ble_bas_peripheral ``` -------------------------------- ### Run auto-pts Client Source: https://github.com/embassy-rs/trouble/blob/main/tester/nrf52/README.md Example command to run the auto-pts client for testing. Adjust the serial port, J-Link serial number, and IP addresses to match your specific setup. ```bash python ./autoptsclient-zephyr.py trouble \ -t /dev/cu.usbmodem0010502573281 \ -b nrf52 \ --pylink_reset \ -j 1050257328 \ -i 10.111.109.121 \ -l 10.111.109.158 \ --rtscts ``` -------------------------------- ### Full GATT Server Peripheral Example Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/gatt.adoc A complete example of a GATT peripheral server with a battery service that notifies a counter value. This includes setup, advertising, connection handling, and notification logic. ```rust use embassy_futures::join::join; use embassy_futures::select::select; use trouble_host::prelude::*; const CONNECTIONS_MAX: usize = 1; const L2CAP_CHANNELS_MAX: usize = 2; // Signal + ATT #[gatt_server] struct Server { battery_service: BatteryService, } #[gatt_service(uuid = service::BATTERY)] struct BatteryService { #[characteristic(uuid = characteristic::BATTERY_LEVEL, read, notify, value = 10)] level: u8, } pub async fn run(controller: C) { let address = Address::random([0xff, 0xff, 0x1a, 0x05, 0xe4, 0xff]); let mut resources: HostResources = HostResources::new(); let stack = trouble_host::new(controller, &mut resources) .set_random_address(address) .build(); let runner = stack.runner(); let mut peripheral = stack.peripheral(); let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig { name: "TrouBLE", appearance: &appearance::power_device::GENERIC_POWER_DEVICE, })).unwrap(); let _ = join( async { loop { runner.run().await.ok(); } }, async { loop { // Advertise and wait for connection let mut adv_data = [0; 31]; let len = AdStructure::encode_slice( &[ AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), AdStructure::CompleteLocalName(b"TrouBLE"), ], &mut adv_data[..], ).unwrap(); let advertiser = peripheral.advertise( &Default::default(), Advertisement::ConnectableScannableUndirected { adv_data: &adv_data[..len], scan_data: &[], }, ).await.unwrap(); let conn = advertiser.accept().await.unwrap().with_attribute_server(&server).unwrap(); // Run GATT event handler and notification task concurrently let level = server.battery_service.level; select( async { loop { match conn.next().await { GattConnectionEvent::Disconnected { .. } => break, GattConnectionEvent::Gatt { event } => { event.accept().ok(); } _ => {} } } }, async { let mut tick: u8 = 0; loop { tick = tick.wrapping_add(1); let _ = level.notify(&conn, &tick, true).await; Timer::after_secs(2).await; } }, ).await; } }, ).await; } ``` -------------------------------- ### Run ESP32 Example with Cargo Alias Source: https://github.com/embassy-rs/trouble/blob/main/examples/esp32/README.md Use a simplified `cargo` command with custom aliases for building and running examples on ESP32 devices. Refer to `./.cargo/config.toml` for alias configuration. ```bash # cargo --bin cargo esp32c6 --bin ble_bas_peripheral ``` -------------------------------- ### Install LLVM for Build Dependencies Source: https://github.com/embassy-rs/trouble/blob/main/examples/nrf52/README.md Installs LLVM, a build dependency for nRF-SDC. Installation methods vary by operating system. ```bash # Linux sudo apt install llvm # Windows (multiple ways to install) choco install llvm # Mac brew install llvm ``` -------------------------------- ### Initialize ESP32 BLE Controller Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/platforms.adoc Sets up the ESP32's built-in BLE radio using the `esp-radio` crate. This example demonstrates initializing peripherals and the BLE connector. ```rust #![no_std] #![no_main] use embassy_executor::Spawner; use esp_hal::clock::CpuClock; use esp_hal::timer::timg::TimerGroup; use esp_radio::ble::controller::BleConnector; use trouble_host::prelude::ExternalController; #[esp_rtos::main] async fn main(_s: Spawner) { let peripherals = esp_hal::init( esp_hal::Config::default().with_cpu_clock(CpuClock::max()) ); esp_alloc::heap_allocator!(size: 72 * 1024); let timg0 = TimerGroup::new(peripherals.TIMG0); esp_rtos::start(timg0.timer0, /* ... */); let connector = BleConnector::new(peripherals.BT, Default::default()).unwrap(); let controller: ExternalController<_, 20> = ExternalController::new(connector); my_app::run(controller).await; } ``` -------------------------------- ### Full L2CAP Echo Server Example Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/l2cap.adoc A peripheral that accepts an L2CAP channel and echoes received data. Requires `Controller` and `DefaultPacketPool` implementations. ```rust use embassy_futures::join::join; use trouble_host::prelude::*; const CONNECTIONS_MAX: usize = 1; const L2CAP_CHANNELS_MAX: usize = 3; const PAYLOAD_LEN: usize = 27; pub async fn run(controller: C) { let address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); let mut resources: HostResources = HostResources::new(); let stack = trouble_host::new(controller, &mut resources) .set_random_address(address) .register_l2cap_spsm(0x0081) .build(); let mut runner = stack.runner(); let mut peripheral = stack.peripheral(); let _ = join(runner.run(), async { loop { // Advertise and accept connection let mut adv_data = [0; 31]; let len = AdStructure::encode_slice( &[AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED)], &mut adv_data[..], ).unwrap(); let advertiser = peripheral.advertise( &Default::default(), Advertisement::ConnectableScannableUndirected { adv_data: &adv_data[..len], scan_data: &[], }, ).await.unwrap(); let conn = advertiser.accept().await.unwrap(); // Accept L2CAP channel let config = L2capChannelConfig { mtu: Some(PAYLOAD_LEN as u16), ..Default::default() }; let mut ch = L2capChannel::listen(&stack, &conn).accept(&config).await.unwrap(); // Echo received data let mut buf = [0; PAYLOAD_LEN]; loop { match ch.receive(&stack, &mut buf).await { Ok(len) => { ch.send(&stack, &buf[..len]).await.unwrap(); } Err(_) => break, } } } }).await; } ``` -------------------------------- ### Initialize CYW43 with Bluetooth on Raspberry Pi Pico W Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/platforms.adoc Initializes the CYW43 chip with firmware and Bluetooth support. Requires specific firmware files and SPI setup. ```rust let fw = include_bytes!("../../cyw43-firmware/43439A0.bin"); let clm = include_bytes!("../../cyw43-firmware/43439A0_clm.bin"); let btfw = include_bytes!("../../cyw43-firmware/43439A0_btfw.bin"); // Set up PIO SPI let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); let mut pio = Pio::new(p.PIO0, Irqs); let spi = PioSpi::new(/* ... */); // Initialize cyw43 static STATE: StaticCell = StaticCell::new(); let state = STATE.init(cyw43::State::new()); let (_net, bt_device, mut control, runner) = cyw43::new_with_bluetooth(state, pwr, spi, fw, btfw, nvram).await; spawner.must_spawn(cyw43_task(runner)); control.init(clm).await; let controller: ExternalController<_, 10> = ExternalController::new(bt_device); my_app::run(controller).await; } ``` -------------------------------- ### Initialize Raspberry Pi Pico W CYW43 Controller Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/platforms.adoc Configures the CYW43 WiFi/BLE chip on the Raspberry Pi Pico W using PIO SPI via the `cyw43` crate. This snippet shows the initial setup for the BLE controller. ```rust #![no_std] #![no_main] use cyw43_pio::PioSpi; use embassy_executor::Spawner; use embassy_rp::gpio::{Level, Output}; use embassy_rp::pio::Pio; use static_cell::StaticCell; use trouble_host::prelude::ExternalController; #[embassy_executor::task] async fn cyw43_task(runner: cyw43::Runner<'static, /* ... */>) -> ! { runner.run().await } #[embassy_executor::main] async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); // Load firmware blobs ``` -------------------------------- ### Windows WinUSB Driver Installation with Zadig Source: https://github.com/embassy-rs/trouble/blob/main/examples/usb-hci/README.md Instructions for installing the WinUSB driver for USB Bluetooth dongles on Windows using Zadig. This allows the device to be accessed by the WinUSB interface. ```text Download Zadig from https://zadig.akeo.ie/, run it as administrator, select your Bluetooth dongle from the device list, choose "WinUSB" as the driver, and click "Install Driver". ``` -------------------------------- ### Inspect and Accept L2CAP Channel Request (Peripheral) Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/l2cap.adoc Use `listen` to get a listener, then `next` to inspect incoming L2CAP CoC requests before accepting or rejecting them. ```rust let listener = L2capChannel::listen(&stack, &conn); let pending = listener.next().await.unwrap(); // Inspect the incoming request let spsm = pending.spsm(); let peer_mtu = pending.mtu(); // Accept or reject let ch = pending.accept(&stack, &config).await.unwrap(); // OR: pending.reject(&stack, LeCreditConnResultCode::UnacceptableParameters).await.unwrap(); ``` -------------------------------- ### Run BLE Host Stack with Join Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/getting_started.adoc The `runner` is responsible for executing the BLE host stack. Pass it to an async task that runs indefinitely. This example shows how to integrate the runner into an embassy application using `join` to manage the BLE task alongside other application logic. ```rust use embassy_futures::join::join; let _ = join( async { loop { if let Err(e) = runner.run().await { panic!("BLE runner error: {:?}", e); } } }, async { // Your application logic here (advertise, connect, etc.) }, ).await; ``` -------------------------------- ### Define a GATT Server Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/gatt.adoc Combines multiple GATT services into a single server. This example shows how to define a server struct that includes the previously defined BatteryService. ```rust #[gatt_server] struct Server { battery_service: BatteryService, } ``` -------------------------------- ### Configure HCI UART for High Throughput Source: https://github.com/embassy-rs/trouble/blob/main/examples/serial-hci/README.md Modify the HCI UART example's configuration to enable data length extension and increase buffer counts for high throughput. Add these settings to the `proj.conf` file. ```conf # Enable data length extension CONFIG_BT_CTLR_DATA_LENGTH_MAX=251 CONFIG_BT_BUF_ACL_TX_SIZE=251 # Increase buffer count CONFIG_BT_CTLR_SDC_TX_PACKET_COUNT=20 CONFIG_BT_CTLR_SDC_RX_PACKET_COUNT=20 ``` -------------------------------- ### Obtain BLE Stack Handles Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/getting_started.adoc After configuring the BLE stack properties, call `.build()` to get the `Stack` object. Use the stack to retrieve handles for the desired roles like `runner`, `central`, and `peripheral`. The availability of `central()` and `peripheral()` depends on enabled cargo features. ```rust let runner = stack.runner(); let central = stack.central(); let peripheral = stack.peripheral(); ``` -------------------------------- ### Specify HCI Device for BLE Scanner Source: https://github.com/embassy-rs/trouble/blob/main/examples/linux/README.md To use a different HCI device (e.g., `hci1`), pass the device number as an argument to the `cargo run` command. This example shows how to specify device `1`. ```bash cargo run --bin ble_scanner -- 1 ``` -------------------------------- ### Configure nRF52/nRF54 SoftDevice Controller Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/platforms.adoc Sets up the SoftDevice Controller for nRF52/nRF54 using the `nrf-sdc` crate. This involves initializing the Multiprotocol Service Layer (MPSL) and configuring the SoftDevice. ```rust #![no_std] #![no_main] use embassy_executor::Spawner; use embassy_nrf::rng; use nrf_sdc::{self as sdc, mpsl}; use static_cell::StaticCell; #[embassy_executor::task] async fn mpsl_task(mpsl: &'static mpsl::MultiprotocolServiceLayer<'static>) -> ! { mpsl.run().await } #[embassy_executor::main] async fn main(spawner: Spawner) { let p = embassy_nrf::init(Default::default()); // 1. Initialize MPSL let mpsl_p = mpsl::Peripherals::new( p.RTC0, p.TIMER0, p.TEMP, p.PPI_CH19, p.PPI_CH30, p.PPI_CH31, ); let lfclk_cfg = mpsl::raw::mpsl_clock_lfclk_cfg_t { source: mpsl::raw::MPSL_CLOCK_LF_SRC_RC as u8, rc_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_CTIV as u8, rc_temp_ctiv: mpsl::raw::MPSL_RECOMMENDED_RC_TEMP_CTIV as u8, accuracy_ppm: mpsl::raw::MPSL_DEFAULT_CLOCK_ACCURACY_PPM as u16, skip_wait_lfclk_started: mpsl::raw::MPSL_DEFAULT_SKIP_WAIT_LFCLK_STARTED != 0, }; static MPSL: StaticCell = StaticCell::new(); let mpsl = MPSL.init(mpsl::MultiprotocolServiceLayer::new(mpsl_p, Irqs, lfclk_cfg)); spawner.must_spawn(mpsl_task(mpsl)); // 2. Configure SoftDevice Controller let sdc_p = sdc::Peripherals::new(/* PPI channels */); let mut rng = rng::Rng::new(p.RNG, Irqs); let mut sdc_mem = sdc::Mem::<4864>::new(); let sdc = sdc::Builder::new().unwrap() .support_peripheral().unwrap() .peripheral_count(1).unwrap() .build(sdc_p, &mut rng, mpsl, &mut sdc_mem) .unwrap(); // 3. Run application my_app::run(sdc).await; } ``` -------------------------------- ### Initialize Linux HCI Controller Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/platforms.adoc Initializes the Bluetooth adapter on Linux using HCI sockets via Bluez. This is suitable for development and testing. ```rust use bt_hci::controller::ExternalController; use bt_hci_linux::Transport; #[tokio::main(flavor = "current_thread")] async fn main() { let dev = 0; // HCI device number (hci0) let transport = Transport::new(dev).unwrap(); let controller = ExternalController::<_, 8>::new(transport); // Pass controller to your application my_app::run(controller).await; } ``` -------------------------------- ### Build and Flash nRF Firmware Source: https://github.com/embassy-rs/trouble/blob/main/tester/nrf52/README.md Commands to build the release version of the firmware and then flash and run it on the nRF52840 DK. Ensure you are in the 'tester/nrf52' directory. ```bash cargo build --release ``` ```bash cargo run --release ``` -------------------------------- ### Initialize HostResources Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/getting_started.adoc Instantiate HostResources with specific generic parameters for connections, L2CAP channels, and advertising sets. The MTU is configured by a Cargo feature. ```rust let mut resources: HostResources = HostResources::new(); ``` -------------------------------- ### Initialize STM32WB with BLE Coprocessor Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/platforms.adoc Sets up the IPCC mailbox to communicate with the wireless coprocessor on STM32WB series. Requires ST's wireless firmware to be flashed. ```rust #![no_std] #![no_main] use embassy_executor::Spawner; use embassy_stm32::bind_interrupts; use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; use embassy_stm32::rcc; use embassy_stm32_wpan::TlMbox; use embassy_stm32_wpan::sub::{ble::ControllerAdapter, mm}; bind_interrupts!(struct Irqs { IPCC_C1_RX => ReceiveInterruptHandler; IPCC_C1_TX => TransmitInterruptHandler; }); #[embassy_executor::task] async fn run_mm_queue(mut memory_manager: mm::MemoryManager<'static>) { memory_manager.run_queue().await; } #[embassy_executor::main(executor = "embassy_stm32::executor::Executor", entry = "cortex_m_rt::entry")] async fn main(spawner: Spawner) { let mut config = embassy_stm32::Config::default(); config.rcc = rcc::Config::new_wpan(); let p = embassy_stm32::init(config); // 1. Bring up the IPCC mailbox to talk to the wireless coprocessor. let (ble, mm) = TlMbox::wait_ready(p.IPCC, Irqs, Config::default()) .await .unwrap() .init_ble(Default::default()) .await .unwrap(); spawner.spawn(run_mm_queue(mm).unwrap()); // 2. The ControllerAdapter implements the bt-hci Controller trait. let controller = ControllerAdapter::new(ble); // 3. Run application my_app::run(controller).await; } ``` -------------------------------- ### Create GATT Server with GAP Configuration Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/gatt.adoc Initializes a GATT server with a specific GAP configuration for peripheral role. This includes setting the device name and appearance. ```rust let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig { name: "TrouBLE", appearance: &appearance::power_device::GENERIC_POWER_DEVICE, })) .unwrap(); ``` -------------------------------- ### Add ESP32 Toolchain Source: https://github.com/embassy-rs/trouble/blob/main/examples/esp32/README.md Add the necessary RISC-V toolchain for ESP32 development using `rustup`. ```bash rustup target add riscv32imac-unknown-none-elf ``` -------------------------------- ### Build Zephyr HCI-UART Sample Source: https://github.com/embassy-rs/trouble/blob/main/examples/serial-hci/README.md Build the HCI-UART sample for the nRF52840 dongle using west. This is a prerequisite for using the dongle as a HCI serial controller. ```bash west build -p always -b nrf52840dongle samples/bluetooth/hci_uart ``` -------------------------------- ### Run Rust BLE Scanner with CAP_NET_ADMIN Source: https://github.com/embassy-rs/trouble/blob/main/examples/linux/README.md This command runs the `ble_scanner` binary with the `CAP_NET_ADMIN` capability using `systemd-run`. Ensure the device is down (e.g., `hciconfig hci0 down`) before running. ```bash systemd-run \ --pty \ --uid=$(id -u) \ --gid=$(id -g) \ --same-dir \ --setenv RUST_LOG=info \ --setenv PATH \ --property "AmbientCapabilities=CAP_NET_ADMIN" \ cargo run --bin ble_scanner ``` -------------------------------- ### Load Kernel HCI USB Module Source: https://github.com/embassy-rs/trouble/blob/main/bt-hci-usb/README.md To re-enable the kernel's Bluetooth HCI USB support, load the 'btusb' module. This is typically done after the user-space tool has finished or is no longer needed. ```bash sudo modprobe btusb ``` -------------------------------- ### Create Trouble Stack Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/getting_started.adoc Build a Trouble host stack using the trouble_host::new() function and configure properties like the random address using StackBuilder. ```rust let stack = trouble_host::new(controller, &mut resources) .set_random_address(address) .build(); ``` -------------------------------- ### Initialize GATT Client Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/gatt.adoc Initializes a GATT client. The third generic parameter specifies the maximum number of attributes the client can discover. ```rust let conn = central.connect(&config).await.unwrap(); let client = GattClient::::new(&stack, &conn).await.unwrap(); ``` -------------------------------- ### Run BLE Host Stack as a Static Task Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/getting_started.adoc Alternatively, if the runner does not need to be generic, it can be spawned as a static embassy task. This approach is suitable for applications where the BLE runner can be managed independently. ```rust @embassy_executor::task async fn ble_task(mut runner: Runner<'static, SoftdeviceController<'static>>) { loop { runner.run().await.ok(); } } spawner.must_spawn(ble_task(runner)); ``` -------------------------------- ### Configure L2CAP Channel Parameters Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/l2cap.adoc Define L2CAP channel configuration including MTU, MPS, flow policy, and initial credits. Fields are optional with sensible defaults. ```rust let config = L2capChannelConfig { mtu: Some(PAYLOAD_LEN as u16), // Service Data Unit size mps: Some(251), // Max PDU payload size (frame size) flow_policy: CreditFlowPolicy::Every(1), // When to grant credits initial_credits: Some(10), // Credits granted at connection start }; ``` -------------------------------- ### Connect to Bluetooth Controller via Serial HCI (UART) Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/platforms.adoc Establishes a connection to a Bluetooth controller over a UART interface using Tokio. Suitable for desktop environments. ```rust use embassy_sync::blocking_mutex::raw::NoopRawMutex; use tokio_serial::SerialStream; use trouble_host::prelude::{ExternalController, SerialTransport}; #[tokio::main] async fn main() { let port = SerialStream::open( &tokio_serial::new("/dev/ttyUSB0", 1000000) ).unwrap(); let (reader, writer) = tokio::io::split(port); let reader = embedded_io_adapters::tokio_1::FromTokio::new(reader); let writer = embedded_io_adapters::tokio_1::FromTokio::new(writer); let driver: SerialTransport = SerialTransport::new(reader, writer); let controller: ExternalController<_, 10> = ExternalController::new(driver); my_app::run(controller).await; } ``` -------------------------------- ### Initialize BLE Stack with Random Generator Seed Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/security.adoc Initialize the Trouble host stack, setting a random address and providing a cryptographically secure random number generator seed. The stack will panic at build time if the generator is not seeded, unless the `dev-disable-csprng-seed-requirement` feature is enabled. ```rust let stack = trouble_host::new(controller, &mut resources) .set_random_address(address) .set_random_generator_seed(&mut rng) // Must implement RngCore + CryptoRng .build(); ``` -------------------------------- ### Send GATT Notifications and Indications Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/gatt.adoc Shows how to send updates to subscribed clients using GATT notifications and indications. Notifications are fire-and-forget, while indications wait for client confirmation. ```rust let level = server.battery_service.level; // Notification (no confirmation from client) level.notify(&conn, &new_value, true).await?; // Indication (waits for client confirmation) level.indicate(&conn, &new_value, true).await?; ``` -------------------------------- ### Read and Set GATT Characteristic Values Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/gatt.adoc Demonstrates how to read the current value of a GATT characteristic and how to set a new value for it on the server side. ```rust // Read the current value of a characteristic let value = server.get(&server.battery_service.level); // Set a characteristic value server.set(&server.battery_service.level, &75).unwrap(); ``` -------------------------------- ### Add Target Toolchain Source: https://github.com/embassy-rs/trouble/blob/main/examples/nrf54/README.md Adds the necessary Rust toolchain for the target architecture, thumbv8m.main-none-eabihf, which is commonly used for ARM Cortex-M microcontrollers. ```bash rustup target add thumbv8m.main-none-eabihf ``` -------------------------------- ### Read and Write Characteristics Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/gatt.adoc Demonstrates reading data from a characteristic, writing data with a response, and writing data without a response. ```rust // Read a characteristic let mut data = [0; 1]; client.read_characteristic(&c, &mut data[..]).await.unwrap(); // Write a characteristic (with response) client.write_characteristic(&c, &[42]).await.unwrap(); // Write without response client.write_characteristic_without_response(&c, &[42]).await.unwrap(); ``` -------------------------------- ### Discover Services and Characteristics Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/gatt.adoc Finds services by UUID and then locates a specific characteristic within a discovered service by its UUID. ```rust // Find a service by UUID let services = client.services_by_uuid(&Uuid::new_short(0x180f)).await.unwrap(); let service = services.first().unwrap().clone(); // Find a characteristic by UUID within a service let c: Characteristic = client .characteristic_by_uuid(&service, &Uuid::new_short(0x2a19)) .await .unwrap(); ``` -------------------------------- ### Accept Incoming L2CAP Channel (Peripheral) Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/l2cap.adoc Accept an incoming L2CAP CoC channel request on the peripheral. This is the simplest approach, accepting any request. ```rust let conn = advertiser.accept().await.unwrap(); let config = L2capChannelConfig { mtu: Some(PAYLOAD_LEN as u16), ..Default::default() }; let mut ch = L2capChannel::listen(&stack, &conn).accept(&config).await.unwrap(); ``` -------------------------------- ### View Defmt Logs via USB CDC Source: https://github.com/embassy-rs/trouble/blob/main/tester/nrf52/README.md Script to view defmt logs streamed over USB CDC. Connect the DK to your machine and run this script, replacing the device path with your actual USB serial port. ```bash ./defmt-log.sh /dev/cu.usbmodemXXXX ``` -------------------------------- ### Define GATT Service with Descriptors Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/gatt.adoc Illustrates defining a GATT service with multiple descriptors for a characteristic. It includes descriptors for valid range and measurement description, alongside the characteristic definition for battery level. ```rust #[gatt_service(uuid = service::BATTERY)] struct BatteryService { #[descriptor(uuid = descriptors::VALID_RANGE, read, value = [0, 100])] #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "hello", read, value = "Battery Level", type = &'static str)] #[characteristic(uuid = characteristic::BATTERY_LEVEL, read, notify, value = 10)] level: u8, } ``` -------------------------------- ### Add Rust Target Toolchain Source: https://github.com/embassy-rs/trouble/blob/main/examples/nrf52/README.md Adds the necessary Rust target toolchain for nRF development. ```bash rustup target add thumbv7em-none-eabihf ``` -------------------------------- ### Run GATT Client Task Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/gatt.adoc The GATT client requires a background task to run concurrently with your client operations. ```rust let _ = join(client.task(), async { // Your client operations here }).await; ``` -------------------------------- ### Reload udev Rules and Trigger Source: https://github.com/embassy-rs/trouble/blob/main/bt-hci-usb/README.md After creating or modifying udev rules, reload them and trigger the rules to apply them to connected devices. This is necessary for the new permissions to take effect. ```bash sudo udevadm control --reload-rules sudo udevadm trigger ``` -------------------------------- ### Create L2CAP Channel (Central) Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/l2cap.adoc Initiate an L2CAP CoC channel from the central role. Requires a connection and specifies the SPSM and channel configuration. ```rust let conn = central.connect(&connect_config).await.unwrap(); let config = L2capChannelConfig { mtu: Some(PAYLOAD_LEN as u16), ..Default::default() }; let mut ch = L2capChannel::create(&stack, &conn, 0x0081, &config).await.unwrap(); ``` -------------------------------- ### Restore and Save Bluetooth Bonds Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/security.adoc Restores saved bonds on startup and saves new bonds after pairing is complete. Requires the `serde` feature to be enabled for `BondInformation` serialization. ```rust use sequential_storage::map::{MapConfig, MapStorage, PostcardValue}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] struct StoredBondInformation(BondInformation); impl<'a> PostcardValue<'a> for StoredBondInformation {} // On startup: restore saved bonds let mut map_storage = MapStorage::<(), _, _>::new( storage, MapConfig::new(0..erase_size * 2), NoCache::new(), ); let mut buf = [0; 32]; if let Some(StoredBondInformation(bond)) = map_storage.fetch_item(&mut buf, &()).await.unwrap() { stack.add_bond_information(bond).unwrap(); } // After pairing: save the new bond match conn.next().await { GattConnectionEvent::PairingComplete { bond: Some(bond), .. } => { map_storage.store_item(&mut buf, &(), &StoredBondInformation(bond)).await.unwrap(); } _ => {} } ``` -------------------------------- ### Handle BLE Pairing Events Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/security.adoc Process incoming connection events to manage the BLE pairing lifecycle. This includes displaying or confirming passkeys, handling user input for passkeys, and reacting to successful pairings or failures. Events are delivered as `GattConnectionEvent` variants when using a GATT server. ```rust loop { match conn.next().await { GattConnectionEvent::Disconnected { reason } => break, // Display a passkey for the user to enter on the other device GattConnectionEvent::PassKeyDisplay(key) => { info!("Display this passkey: {}", key); } // User must confirm the displayed number matches GattConnectionEvent::PassKeyConfirm(key) => { info!("Confirm passkey: {}", key); // After user confirms or rejects: conn.raw().pass_key_confirm().unwrap(); // OR: conn.raw().pass_key_cancel().unwrap(); } // User must input the passkey shown on the other device GattConnectionEvent::PassKeyInput => { let user_input: u32 = 123456; // Get from user conn.raw().pass_key_input(user_input).unwrap(); } // Pairing succeeded GattConnectionEvent::PairingComplete { security_level, bond } => { info!("Paired with security level: {:?}", security_level); if let Some(bond) = bond { // Persist the bond information (see Bonding section) } } // Pairing failed GattConnectionEvent::PairingFailed(err) => { error!("Pairing failed: {:?}", err); } GattConnectionEvent::Gatt { event } => { event.accept().ok(); } _ => {} } } ``` -------------------------------- ### Control Bondable State for BLE Connections Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/security.adoc Manage whether a BLE connection can be bonded. Setting `bondable` to `true` (the default) allows for persistent encryption keys, while `false` results in temporary pairing. Bonding information is included in the `PairingComplete` event if both devices are bondable. ```rust // Allow bonding for this connection (default) conn.set_bondable(true).unwrap(); // Disable bonding (pairing will be temporary) conn.set_bondable(false).unwrap(); ``` -------------------------------- ### Bond Management API in Rust Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/security.adoc Provides functions to manage Bluetooth bonds. Use `add_bond_information` to add a stored bond, `remove_bond_information` to remove a specific bond by its identity, and `with_bond_information` to iterate over all stored bonds. ```rust // Add a previously stored bond stack.add_bond_information(bond)?; // Remove a bond stack.remove_bond_information(identity)?; // List all bonds stack.with_bond_information(|bonds| { // ... }); ``` -------------------------------- ### Configure Host Resources for L2CAP CoC Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/l2cap.adoc When using L2CAP CoC, allocate an extra L2CAP channel slot in HostResources for each CoC channel beyond the default signal and ATT channels. ```rust const CONNECTIONS_MAX: usize = 1; const L2CAP_CHANNELS_MAX: usize = 3; // Signal + ATT + 1 CoC channel let mut resources: HostResources = HostResources::new(); ``` -------------------------------- ### Unload Kernel HCI USB Module Source: https://github.com/embassy-rs/trouble/blob/main/bt-hci-usb/README.md If a 'device busy' error occurs, unload the 'btusb' kernel module to free the Bluetooth adapter for user-space access. This is a temporary measure. ```bash sudo modprobe -r btusb ``` -------------------------------- ### Configure Characteristic Permissions for Security Levels Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/security.adoc Define security requirements for Bluetooth characteristics using the `permissions` attribute. This ensures that only clients meeting the specified security level (e.g., `encrypted` or `authenticated`) can access the characteristic. ```rust @characteristic(uuid = "2a19", read, notify, permissions(encrypted)) level: u8, @characteristic(uuid = "2a4a", read, permissions(authenticated)) hid_info: [u8; 4], ``` -------------------------------- ### Request BLE Security Initiation Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/security.adoc Initiate the BLE pairing process from either the central or peripheral device. The result of the pairing attempt will be delivered as events on the connection. ```rust // Central or peripheral can request security conn.request_security().unwrap(); ``` -------------------------------- ### Linux udev Rule for USB Bluetooth Source: https://github.com/embassy-rs/trouble/blob/main/examples/usb-hci/README.md This rule grants user access to USB Bluetooth dongles on Linux. Adjust Vendor and Product IDs for your specific device. Reload rules after creating the file. ```bash SUBSYSTEM=="usb", ATTRS{idVendor}=="0b05", ATTRS{idProduct}=="190e", MODE="0666", GROUP="plugdev" ``` -------------------------------- ### Configure High-Throughput L2CAP Channel Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/l2cap.adoc Optimize L2CAP CoC for high-throughput by adjusting MTU, MPS, flow policy, and initial credits for reduced signaling overhead. ```rust let config = L2capChannelConfig { mtu: Some(2504), mps: Some(247), flow_policy: CreditFlowPolicy::Every(50), initial_credits: Some(200), }; ``` -------------------------------- ### Handle GATT Events in a Loop Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/gatt.adoc Processes incoming GATT events for a connected client. This loop handles disconnections, read, and write events for characteristics, and ensures responses are sent. ```rust let conn = advertiser.accept().await?.with_attribute_server(&server)?; let level = server.battery_service.level; loop { match conn.next().await { GattConnectionEvent::Disconnected { reason } => break, GattConnectionEvent::Gatt { event } => { match &event { GattEvent::Read(event) => { if event.handle() == level.handle { let value = conn.get(&level); info!("Read battery level: {:?}", value); } } GattEvent::Write(event) => { if event.handle() == level.handle { info!("Write to battery level: {:?}", event.data()); } } _ => {} } match event.accept() { Ok(reply) => reply.send().await, Err(e) => warn!("Error sending response: {:?}", e), } } _ => {} } } ``` -------------------------------- ### Linux udev Rule for Bluetooth USB Device Source: https://github.com/embassy-rs/trouble/blob/main/bt-hci-usb/README.md This rule grants access to the Bluetooth USB device for the 'plugdev' group, allowing non-root users to interact with it. Ensure the VID and PID match your device. ```bash SUBSYSTEM=="usb", ATTR{idVendor}=="0b05", ATTR{idProduct}=="190e", MODE="0660", GROUP="plugdev", TAG+="uaccess" ``` -------------------------------- ### Subscribe to Characteristic Notifications Source: https://github.com/embassy-rs/trouble/blob/main/docs/pages/gatt.adoc Subscribes to notifications from a characteristic and enters a loop to receive incoming data. Pass `true` to `subscribe()` for indications. ```rust // Subscribe (writes the CCCD automatically) let mut listener = client.subscribe(&c, false).await.unwrap(); // Receive notifications loop { let data = listener.next().await; info!("Got notification: {:?}", data.as_ref()); } ```