From 79a231b7676954895964a18c9cb51e3a9a5556d7 Mon Sep 17 00:00:00 2001 From: James Sizeland Date: Thu, 21 Nov 2024 16:31:53 +0000 Subject: [PATCH] rework peripheral gatt example for more complex use of gatt interactions with other tasks --- examples/apps/src/ble_bas_peripheral.rs | 167 ++++++++++++++---------- 1 file changed, 98 insertions(+), 69 deletions(-) diff --git a/examples/apps/src/ble_bas_peripheral.rs b/examples/apps/src/ble_bas_peripheral.rs index 7bf1b84..1306d90 100644 --- a/examples/apps/src/ble_bas_peripheral.rs +++ b/examples/apps/src/ble_bas_peripheral.rs @@ -1,8 +1,5 @@ -use embassy_futures::{ - join::join3, - select::{select, Either}, -}; -use embassy_time::{Duration, Timer}; +use embassy_futures::select::{select, Either}; +use embassy_time::Timer; use trouble_host::prelude::*; /// Size of L2CAP packets (ATT MTU is this - 4) @@ -42,6 +39,7 @@ fn battery_level_on_write(_connection: &Connection, data: &[u8]) -> Result<(), ( Ok(()) } +/// Run the BLE stack. pub async fn run(controller: C) where C: Controller, @@ -50,10 +48,11 @@ where info!("Our address = {:?}", address); let mut resources = Resources::new(PacketQos::None); - let (stack, peripheral, _, runner) = trouble_host::new(controller, &mut resources) + let (stack, mut peripheral, _, mut runner) = trouble_host::new(controller, &mut resources) .set_random_address(address) .build(); + info!("Starting advertising and GATT service"); let server = Server::new_with_config( stack, GapConfig::Peripheral(PeripheralConfig { @@ -62,81 +61,111 @@ where }), ) .unwrap(); - - info!("Starting advertising and GATT service"); - let _ = join3( - ble_task(runner), - gatt_task(&server), - advertise_task(peripheral, &server), - ) - .await; -} - -async fn ble_task(mut runner: Runner<'_, C>) -> Result<(), BleHostError> { - runner.run().await + let ble_task = ble_task(&mut runner); + let app_task = async { + loop { + info!("[adv] advertising"); + match advertise("Trouble Example", &mut peripheral).await { + Ok(conn) => { + info!("[adv] connection established"); + // set up tasks when the connection is established to a central, so they don't run when no one is connected. + let gatt = gatt_task(&server, &conn); + let counter_task = example_application_task(&server, &conn); + // run until any task ends (usually because the connection has been closed), + // then return to advertising state. + select(gatt, counter_task).await; + } + Err(err) => info!("[adv] error: {:?}", err), + } + } + }; + select(ble_task, app_task).await; } -async fn gatt_task(server: &Server<'_, '_, C>) -> Result<(), BleHostError> { - server.run().await +async fn ble_task(runner: &mut Runner<'_, C>) -> Result<(), BleHostError> { + runner.run().await?; + info!("BLE task finished"); + Ok(()) } -async fn advertise_task( - mut peripheral: Peripheral<'_, C>, +/// Stream Events until the connection closes. +async fn gatt_task( server: &Server<'_, '_, C>, + conn: &Connection<'_>, ) -> Result<(), BleHostError> { - let mut adv_data = [0; 31]; + let level = server.battery_service.level; + loop { + if let Either::First(event) = select(conn.next(), server.run()).await { + match event { + ConnectionEvent::Disconnected { reason } => { + info!("[adv] disconnected: {:?}", reason); + break; + } + ConnectionEvent::Gatt { event, .. } => match event { + GattEvent::Read { value_handle } => { + if value_handle == level.handle { + let value = server.get(&level); + info!("[gatt] Read Event to Level Characteristic: {:?}", value); + } + } + GattEvent::Write { value_handle } => { + if value_handle == level.handle { + let value = server.get(&level); + info!("[gatt] Write Event to Level Characteristic: {:?}", value); + } + } + }, + } + } + } + info!("[gatt] task finished"); + Ok(()) +} + +/// Create an advertiser to use to connect to a BLE Central, and wait for it to connect. +async fn advertise<'a, C: Controller>( + name: &'a str, + peripheral: &mut Peripheral<'a, C>, +) -> Result, BleHostError> { + let name= if name.len() > 22 { + let truncated_name = &name[..22]; + info!("Name truncated to {}", truncated_name); + truncated_name + } else { + name + }; + let mut advertiser_data = [0; 31]; AdStructure::encode_slice( &[ AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), AdStructure::ServiceUuids16(&[Uuid::Uuid16([0x0f, 0x18])]), - AdStructure::CompleteLocalName(b"Trouble"), + AdStructure::CompleteLocalName(name.as_bytes()), ], - &mut adv_data[..], + &mut advertiser_data[..], )?; + let mut advertiser = peripheral + .advertise( + &Default::default(), + Advertisement::ConnectableScannableUndirected { + adv_data: &advertiser_data[..], + scan_data: &[], + }, + ) + .await?; + Ok(advertiser.accept().await?) +} + +/// Example task to use the BLE notifier interface. +async fn example_application_task(server: &Server<'_, '_, C>, conn: &Connection<'_>) { + let mut tick: u8 = 0; + let level = server.battery_service.level; loop { - info!("[adv] advertising"); - let mut advertiser = peripheral - .advertise( - &Default::default(), - Advertisement::ConnectableScannableUndirected { - adv_data: &adv_data[..], - scan_data: &[], - }, - ) - .await?; - let conn = advertiser.accept().await?; - info!("[adv] connection established"); - let mut tick: u8 = 0; - let level = server.battery_service.level; - loop { - match select(conn.next(), Timer::after(Duration::from_secs(2))).await { - Either::First(event) => match event { - ConnectionEvent::Disconnected { reason } => { - info!("[adv] disconnected: {:?}", reason); - break; - } - ConnectionEvent::Gatt { event, .. } => match event { - GattEvent::Read { value_handle } => { - if value_handle == level.handle { - let value = server.get(&level); - info!("[gatt] Read Event to Level Characteristic: {:?}", value); - } - }, - GattEvent::Write { value_handle } => { - if value_handle == level.handle { - let value = server.get(&level); - info!("[gatt] Write Event to Level Characteristic: {:?}", value); - } - }, - }, - - }, - Either::Second(_) => { - tick = tick.wrapping_add(1); - info!("[adv] notifying connection of tick {}", tick); - let _ = server.notify(&server.battery_service.level, &conn, &tick).await; - } - } - } + tick = tick.wrapping_add(1); + info!("[adv] notifying connection of tick {}", tick); + if let Err(err) = server.notify(&level, &conn, &tick).await { + info!("[adv] error notifying connection: {:?}", err); + break; + }; + Timer::after_secs(2).await; } }