This commit is contained in:
2025-06-27 16:41:18 +02:00
parent 1188995b80
commit ed701e8580
29 changed files with 3329 additions and 376 deletions

View File

@@ -0,0 +1,33 @@
[package]
name = "trouble-example-apps"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
trouble-host = { version = "0.2.0", features = ["derive", "scan"] }
bt-hci = { version = "0.3.2" }
embassy-executor = { version = "0.7.0" }
embassy-futures = "0.1.1"
embassy-sync = { version = "0.7" }
embassy-time = "0.4"
embedded-hal = "1.0"
static_cell = "2"
embedded-io = "0.6"
heapless = "0.8"
rand_core = { version = "0.6", default-features = false }
defmt = { version = "1.0.1", optional = true }
log = { version = "0.4", optional = true }
[features]
defmt = [
"dep:defmt",
"trouble-host/defmt",
"bt-hci/defmt",
"embedded-io/defmt-03",
"embedded-hal/defmt-03",
]
log = ["dep:log", "trouble-host/log", "bt-hci/log"]
security = ["trouble-host/security"]

View File

@@ -0,0 +1,62 @@
use bt_hci::cmd::le::*;
use bt_hci::controller::ControllerCmdSync;
use embassy_futures::join::join;
use embassy_time::{Duration, Timer};
use trouble_host::prelude::*;
pub async fn run<C>(controller: C)
where
C: Controller
+ for<'t> ControllerCmdSync<LeSetExtAdvData<'t>>
+ ControllerCmdSync<LeClearAdvSets>
+ ControllerCmdSync<LeSetExtAdvParams>
+ ControllerCmdSync<LeSetAdvSetRandomAddr>
+ ControllerCmdSync<LeReadNumberOfSupportedAdvSets>
+ for<'t> ControllerCmdSync<LeSetExtAdvEnable<'t>>
+ for<'t> ControllerCmdSync<LeSetExtScanResponseData<'t>>,
{
let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);
info!("Our address = {:?}", address);
let mut resources: HostResources<DefaultPacketPool, 0, 0> = HostResources::new();
let stack = trouble_host::new(controller, &mut resources).set_random_address(address);
let Host {
mut peripheral,
mut runner,
..
} = stack.build();
let mut adv_data = [0; 31];
let len = AdStructure::encode_slice(
&[
AdStructure::CompleteLocalName(b"Trouble Advert"),
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
],
&mut adv_data[..],
)
.unwrap();
info!("Starting advertising");
let _ = join(runner.run(), async {
loop {
let mut params = AdvertisementParameters::default();
params.interval_min = Duration::from_millis(100);
params.interval_max = Duration::from_millis(100);
let _advertiser = peripheral
.advertise(
&params,
Advertisement::NonconnectableScannableUndirected {
adv_data: &adv_data[..len],
scan_data: &[],
},
)
.await
.unwrap();
loop {
info!("Still running");
Timer::after(Duration::from_secs(60)).await;
}
}
})
.await;
}

View File

@@ -0,0 +1,77 @@
use bt_hci::cmd::le::*;
use bt_hci::controller::ControllerCmdSync;
use embassy_futures::join::join;
use embassy_time::{Duration, Timer};
use trouble_host::prelude::*;
pub async fn run<C>(controller: C)
where
C: Controller
+ for<'t> ControllerCmdSync<LeSetExtAdvData<'t>>
+ ControllerCmdSync<LeClearAdvSets>
+ ControllerCmdSync<LeSetExtAdvParams>
+ ControllerCmdSync<LeSetAdvSetRandomAddr>
+ ControllerCmdSync<LeReadNumberOfSupportedAdvSets>
+ for<'t> ControllerCmdSync<LeSetExtAdvEnable<'t>>
+ for<'t> ControllerCmdSync<LeSetExtScanResponseData<'t>>,
{
let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);
info!("Our address = {:?}", address);
let mut resources: HostResources<DefaultPacketPool, 0, 0, 2> = HostResources::new();
let stack = trouble_host::new(controller, &mut resources).set_random_address(address);
let Host {
mut peripheral,
mut runner,
..
} = stack.build();
let mut adv_data = [0; 31];
let len = AdStructure::encode_slice(
&[
AdStructure::CompleteLocalName(b"Trouble Multiadv"),
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
],
&mut adv_data[..],
)
.unwrap();
let mut params_1m = AdvertisementParameters::default();
params_1m.primary_phy = PhyKind::Le1M;
params_1m.secondary_phy = PhyKind::Le1M;
params_1m.interval_min = Duration::from_millis(160);
params_1m.interval_max = Duration::from_millis(160);
let mut params_coded = AdvertisementParameters::default();
params_coded.primary_phy = PhyKind::LeCoded;
params_coded.secondary_phy = PhyKind::LeCoded;
params_coded.interval_min = Duration::from_millis(400);
params_coded.interval_max = Duration::from_millis(400);
let sets = [
AdvertisementSet {
params: params_1m,
data: Advertisement::ExtNonconnectableScannableUndirected {
scan_data: &adv_data[..len],
},
},
AdvertisementSet {
params: params_coded,
data: Advertisement::ExtNonconnectableScannableUndirected {
scan_data: &adv_data[..len],
},
},
];
let mut handles = AdvertisementSet::handles(&sets);
info!("Starting advertising");
let _ = join(runner.run(), async {
loop {
let _advertiser = peripheral.advertise_ext(&sets, &mut handles).await.unwrap();
loop {
info!("Still running");
Timer::after(Duration::from_secs(60)).await;
}
}
})
.await;
}

View File

@@ -0,0 +1,86 @@
use embassy_futures::join::join;
use embassy_time::{Duration, Timer};
use trouble_host::prelude::*;
/// Max number of connections
const CONNECTIONS_MAX: usize = 1;
/// Max number of L2CAP channels.
const L2CAP_CHANNELS_MAX: usize = 3; // Signal + att + CoC
pub async fn run<C>(controller: C)
where
C: Controller,
{
// Using a fixed "random" address can be useful for testing. In real scenarios, one would
// use e.g. the MAC 6 byte array as the address (how to get that varies by the platform).
let address: Address = Address::random([0xff, 0x8f, 0x1b, 0x05, 0xe4, 0xff]);
info!("Our address = {:?}", address);
let mut resources: HostResources<DefaultPacketPool, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX> = HostResources::new();
let stack = trouble_host::new(controller, &mut resources).set_random_address(address);
let Host {
mut central,
mut runner,
..
} = stack.build();
// NOTE: Modify this to match the address of the peripheral you want to connect to.
// Currently it matches the address used by the peripheral examples
let target: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);
let config = ConnectConfig {
connect_params: Default::default(),
scan_config: ScanConfig {
filter_accept_list: &[(target.kind, &target.addr)],
..Default::default()
},
};
info!("Scanning for peripheral...");
let _ = join(runner.run(), async {
info!("Connecting");
let conn = central.connect(&config).await.unwrap();
info!("Connected, creating gatt client");
let client = GattClient::<C, DefaultPacketPool, 10>::new(&stack, &conn)
.await
.unwrap();
let _ = join(client.task(), async {
info!("Looking for battery service");
let services = client.services_by_uuid(&Uuid::new_short(0x180f)).await.unwrap();
let service = services.first().unwrap().clone();
info!("Looking for value handle");
let c: Characteristic<u8> = client
.characteristic_by_uuid(&service, &Uuid::new_short(0x2a19))
.await
.unwrap();
info!("Subscribing notifications");
let mut listener = client.subscribe(&c, false).await.unwrap();
let _ = join(
async {
loop {
let mut data = [0; 1];
client.read_characteristic(&c, &mut data[..]).await.unwrap();
info!("Read value: {}", data[0]);
Timer::after(Duration::from_secs(10)).await;
}
},
async {
loop {
let data = listener.next().await;
info!("Got notification: {:?} (val: {})", data.as_ref(), data.as_ref()[0]);
}
},
)
.await;
})
.await;
})
.await;
}

View File

@@ -0,0 +1,98 @@
use embassy_futures::join::join;
use embassy_time::{Duration, Timer};
use rand_core::{CryptoRng, RngCore};
use trouble_host::prelude::*;
/// Max number of connections
const CONNECTIONS_MAX: usize = 1;
/// Max number of L2CAP channels.
const L2CAP_CHANNELS_MAX: usize = 3; // Signal + att + CoC
pub async fn run<C, RNG>(controller: C, random_generator: &mut RNG)
where
C: Controller,
RNG: RngCore + CryptoRng,
{
// Using a fixed "random" address can be useful for testing. In real scenarios, one would
// use e.g. the MAC 6 byte array as the address (how to get that varies by the platform).
let address: Address = Address::random([0xff, 0x8f, 0x1b, 0x05, 0xe4, 0xff]);
info!("Our address = {:?}", address);
let mut resources: HostResources<DefaultPacketPool, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX> = HostResources::new();
let stack = trouble_host::new(controller, &mut resources)
.set_random_address(address)
.set_random_generator_seed(random_generator);
let Host {
mut central,
mut runner,
..
} = stack.build();
// NOTE: Modify this to match the address of the peripheral you want to connect to.
// Currently it matches the address used by the peripheral examples
let target: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);
let config = ConnectConfig {
connect_params: Default::default(),
scan_config: ScanConfig {
filter_accept_list: &[(target.kind, &target.addr)],
..Default::default()
},
};
info!("Scanning for peripheral...");
let _ = join(runner.run(), async {
info!("Connecting");
let conn = central.connect(&config).await.unwrap();
info!("Connected, creating gatt client");
#[cfg(feature = "security")]
{
if let Err(_error) = central.pairing(&conn).await {
error!("Pairing failed");
}
}
let client = GattClient::<C, DefaultPacketPool, 10>::new(&stack, &conn)
.await
.unwrap();
let _ = join(client.task(), async {
info!("Looking for battery service");
let services = client.services_by_uuid(&Uuid::new_short(0x180f)).await.unwrap();
let service = services.first().unwrap().clone();
info!("Looking for value handle");
let c: Characteristic<u8> = client
.characteristic_by_uuid(&service, &Uuid::new_short(0x2a19))
.await
.unwrap();
info!("Subscribing notifications");
let mut listener = client.subscribe(&c, false).await.unwrap();
let _ = join(
async {
loop {
let mut data = [0; 1];
client.read_characteristic(&c, &mut data[..]).await.unwrap();
info!("Read value: {}", data[0]);
Timer::after(Duration::from_secs(10)).await;
}
},
async {
loop {
let data = listener.next().await;
info!("Got notification: {:?} (val: {})", data.as_ref(), data.as_ref()[0]);
}
},
)
.await;
})
.await;
})
.await;
}

View File

@@ -0,0 +1,195 @@
use embassy_futures::join::join;
use embassy_futures::select::select;
use embassy_time::Timer;
use trouble_host::prelude::*;
/// Max number of connections
const CONNECTIONS_MAX: usize = 1;
/// Max number of L2CAP channels.
const L2CAP_CHANNELS_MAX: usize = 2; // Signal + att
// GATT Server definition
#[gatt_server]
struct Server {
battery_service: BatteryService,
}
/// Battery service
#[gatt_service(uuid = service::BATTERY)]
struct BatteryService {
/// Battery Level
#[descriptor(uuid = descriptors::VALID_RANGE, read, value = [0, 100])]
#[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "hello", read, value = "Battery Level")]
#[characteristic(uuid = characteristic::BATTERY_LEVEL, read, notify, value = 10)]
level: u8,
#[characteristic(uuid = "408813df-5dd4-1f87-ec11-cdb001100000", write, read, notify)]
status: bool,
}
/// Run the BLE stack.
pub async fn run<C>(controller: C)
where
C: Controller,
{
// Using a fixed "random" address can be useful for testing. In real scenarios, one would
// use e.g. the MAC 6 byte array as the address (how to get that varies by the platform).
let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);
info!("Our address = {:?}", address);
let mut resources: HostResources<DefaultPacketPool, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX> = HostResources::new();
let stack = trouble_host::new(controller, &mut resources).set_random_address(address);
let Host {
mut peripheral, runner, ..
} = stack.build();
info!("Starting advertising and GATT service");
let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig {
name: "TrouBLE",
appearance: &appearance::power_device::GENERIC_POWER_DEVICE,
}))
.unwrap();
let _ = join(ble_task(runner), async {
loop {
match advertise("Trouble Example", &mut peripheral, &server).await {
Ok(conn) => {
// set up tasks when the connection is established to a central, so they don't run when no one is connected.
let a = gatt_events_task(&server, &conn);
let b = custom_task(&server, &conn, &stack);
// run until any task ends (usually because the connection has been closed),
// then return to advertising state.
select(a, b).await;
}
Err(e) => {
#[cfg(feature = "defmt")]
let e = defmt::Debug2Format(&e);
panic!("[adv] error: {:?}", e);
}
}
}
})
.await;
}
/// This is a background task that is required to run forever alongside any other BLE tasks.
///
/// ## Alternative
///
/// If you didn't require this to be generic for your application, you could statically spawn this with i.e.
///
/// ```rust,ignore
///
/// #[embassy_executor::task]
/// async fn ble_task(mut runner: Runner<'static, SoftdeviceController<'static>>) {
/// runner.run().await;
/// }
///
/// spawner.must_spawn(ble_task(runner));
/// ```
async fn ble_task<C: Controller, P: PacketPool>(mut runner: Runner<'_, C, P>) {
loop {
if let Err(e) = runner.run().await {
#[cfg(feature = "defmt")]
let e = defmt::Debug2Format(&e);
panic!("[ble_task] error: {:?}", e);
}
}
}
/// Stream Events until the connection closes.
///
/// This function will handle the GATT events and process them.
/// This is how we interact with read and write requests.
async fn gatt_events_task<P: PacketPool>(server: &Server<'_>, conn: &GattConnection<'_, '_, P>) -> Result<(), Error> {
let level = server.battery_service.level;
let reason = loop {
match conn.next().await {
GattConnectionEvent::Disconnected { reason } => break reason,
GattConnectionEvent::Gatt { event } => {
match &event {
GattEvent::Read(event) => {
if event.handle() == level.handle {
let value = server.get(&level);
info!("[gatt] Read Event to Level Characteristic: {:?}", value);
}
}
GattEvent::Write(event) => {
if event.handle() == level.handle {
info!("[gatt] Write Event to Level Characteristic: {:?}", event.data());
}
}
_ => {}
};
// This step is also performed at drop(), but writing it explicitly is necessary
// in order to ensure reply is sent.
match event.accept() {
Ok(reply) => reply.send().await,
Err(e) => warn!("[gatt] error sending response: {:?}", e),
};
}
_ => {} // ignore other Gatt Connection Events
}
};
info!("[gatt] disconnected: {:?}", reason);
Ok(())
}
/// Create an advertiser to use to connect to a BLE Central, and wait for it to connect.
async fn advertise<'values, 'server, C: Controller>(
name: &'values str,
peripheral: &mut Peripheral<'values, C, DefaultPacketPool>,
server: &'server Server<'values>,
) -> Result<GattConnection<'values, 'server, DefaultPacketPool>, BleHostError<C::Error>> {
let mut advertiser_data = [0; 31];
let len = AdStructure::encode_slice(
&[
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
AdStructure::ServiceUuids16(&[[0x0f, 0x18]]),
AdStructure::CompleteLocalName(name.as_bytes()),
],
&mut advertiser_data[..],
)?;
let advertiser = peripheral
.advertise(
&Default::default(),
Advertisement::ConnectableScannableUndirected {
adv_data: &advertiser_data[..len],
scan_data: &[],
},
)
.await?;
info!("[adv] advertising");
let conn = advertiser.accept().await?.with_attribute_server(server)?;
info!("[adv] connection established");
Ok(conn)
}
/// Example task to use the BLE notifier interface.
/// This task will notify the connected central of a counter value every 2 seconds.
/// It will also read the RSSI value every 2 seconds.
/// and will stop when the connection is closed by the central or an error occurs.
async fn custom_task<C: Controller, P: PacketPool>(
server: &Server<'_>,
conn: &GattConnection<'_, '_, P>,
stack: &Stack<'_, C, P>,
) {
let mut tick: u8 = 0;
let level = server.battery_service.level;
loop {
tick = tick.wrapping_add(1);
info!("[custom_task] notifying connection of tick {}", tick);
if level.notify(conn, &tick).await.is_err() {
info!("[custom_task] error notifying connection");
break;
};
// read RSSI (Received Signal Strength Indicator) of the connection.
if let Ok(rssi) = conn.raw().rssi(stack).await {
info!("[custom_task] RSSI: {:?}", rssi);
} else {
info!("[custom_task] error getting RSSI");
break;
};
Timer::after_secs(2).await;
}
}

View File

@@ -0,0 +1,228 @@
use embassy_futures::join::join;
use embassy_futures::select::select;
use embassy_time::Timer;
use rand_core::{CryptoRng, RngCore};
use trouble_host::prelude::*;
/// Max number of connections
const CONNECTIONS_MAX: usize = 1;
/// Max number of L2CAP channels.
const L2CAP_CHANNELS_MAX: usize = 2; // Signal + att
// GATT Server definition
#[gatt_server]
struct Server {
battery_service: BatteryService,
}
/// Battery service
#[gatt_service(uuid = service::BATTERY)]
struct BatteryService {
/// Battery Level
#[descriptor(uuid = descriptors::VALID_RANGE, read, value = [0, 100])]
#[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, name = "hello", read, value = "Battery Level")]
#[characteristic(uuid = characteristic::BATTERY_LEVEL, read, notify, value = 10)]
level: u8,
#[characteristic(uuid = "408813df-5dd4-1f87-ec11-cdb001100000", write, read, notify)]
status: bool,
}
/// Run the BLE stack.
pub async fn run<C, RNG>(controller: C, random_generator: &mut RNG)
where
C: Controller,
RNG: RngCore + CryptoRng,
{
// Using a fixed "random" address can be useful for testing. In real scenarios, one would
// use e.g. the MAC 6 byte array as the address (how to get that varies by the platform).
let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);
info!("Our address = {}", address);
let mut resources: HostResources<DefaultPacketPool, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX> =
HostResources::new();
let stack = trouble_host::new(controller, &mut resources)
.set_random_address(address)
.set_random_generator_seed(random_generator);
let Host {
mut peripheral,
runner,
..
} = stack.build();
info!("Starting advertising and GATT service");
let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig {
name: "TrouBLE",
appearance: &appearance::power_device::GENERIC_POWER_DEVICE,
}))
.unwrap();
let _ = join(ble_task(runner), async {
loop {
match advertise("Trouble Example", &mut peripheral, &server).await {
Ok(conn) => {
// set up tasks when the connection is established to a central, so they don't run when no one is connected.
let a = gatt_events_task(&server, &conn);
let b = custom_task(&server, &conn, &stack);
// run until any task ends (usually because the connection has been closed),
// then return to advertising state.
select(a, b).await;
}
Err(e) => {
#[cfg(feature = "defmt")]
let e = defmt::Debug2Format(&e);
panic!("[adv] error: {:?}", e);
}
}
}
})
.await;
}
/// This is a background task that is required to run forever alongside any other BLE tasks.
///
/// ## Alternative
///
/// If you didn't require this to be generic for your application, you could statically spawn this with i.e.
///
/// ```rust,ignore
///
/// #[embassy_executor::task]
/// async fn ble_task(mut runner: Runner<'static, SoftdeviceController<'static>>) {
/// runner.run().await;
/// }
///
/// spawner.must_spawn(ble_task(runner));
/// ```
async fn ble_task<C: Controller, P: PacketPool>(mut runner: Runner<'_, C, P>) {
loop {
if let Err(e) = runner.run().await {
#[cfg(feature = "defmt")]
let e = defmt::Debug2Format(&e);
panic!("[ble_task] error: {:?}", e);
}
}
}
/// Stream Events until the connection closes.
///
/// This function will handle the GATT events and process them.
/// This is how we interact with read and write requests.
async fn gatt_events_task(
server: &Server<'_>,
conn: &GattConnection<'_, '_, DefaultPacketPool>,
) -> Result<(), Error> {
let level = server.battery_service.level;
let reason = loop {
match conn.next().await {
GattConnectionEvent::Disconnected { reason } => break reason,
GattConnectionEvent::Gatt { event } => {
let result = match &event {
GattEvent::Read(event) => {
if event.handle() == level.handle {
let value = server.get(&level);
info!("[gatt] Read Event to Level Characteristic: {:?}", value);
}
#[cfg(feature = "security")]
if conn.raw().encrypted() {
None
} else {
Some(AttErrorCode::INSUFFICIENT_ENCRYPTION)
}
#[cfg(not(feature = "security"))]
None
}
GattEvent::Write(event) => {
if event.handle() == level.handle {
info!(
"[gatt] Write Event to Level Characteristic: {:?}",
event.data()
);
}
#[cfg(feature = "security")]
if conn.raw().encrypted() {
None
} else {
Some(AttErrorCode::INSUFFICIENT_ENCRYPTION)
}
#[cfg(not(feature = "security"))]
None
}
_ => None,
};
let reply_result = if let Some(code) = result {
event.reject(code)
} else {
event.accept()
};
match reply_result {
Ok(reply) => reply.send().await,
Err(e) => warn!("[gatt] error sending response: {:?}", e),
}
}
_ => {} // ignore other Gatt Connection Events
}
};
info!("[gatt] disconnected: {:?}", reason);
Ok(())
}
/// Create an advertiser to use to connect to a BLE Central, and wait for it to connect.
async fn advertise<'values, 'server, C: Controller>(
name: &'values str,
peripheral: &mut Peripheral<'values, C, DefaultPacketPool>,
server: &'server Server<'values>,
) -> Result<GattConnection<'values, 'server, DefaultPacketPool>, BleHostError<C::Error>> {
let mut advertiser_data = [0; 31];
let len = AdStructure::encode_slice(
&[
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
AdStructure::ServiceUuids16(&[[0x0f, 0x18]]),
AdStructure::CompleteLocalName(name.as_bytes()),
],
&mut advertiser_data[..],
)?;
let advertiser = peripheral
.advertise(
&Default::default(),
Advertisement::ConnectableScannableUndirected {
adv_data: &advertiser_data[..len],
scan_data: &[],
},
)
.await?;
info!("[adv] advertising");
let conn = advertiser.accept().await?.with_attribute_server(server)?;
info!("[adv] connection established");
Ok(conn)
}
/// Example task to use the BLE notifier interface.
/// This task will notify the connected central of a counter value every 2 seconds.
/// It will also read the RSSI value every 2 seconds.
/// and will stop when the connection is closed by the central or an error occurs.
async fn custom_task<C: Controller, P: PacketPool>(
server: &Server<'_>,
conn: &GattConnection<'_, '_, P>,
stack: &Stack<'_, C, P>,
) {
let mut tick: u8 = 0;
let level = server.battery_service.level;
loop {
tick = tick.wrapping_add(1);
info!("[custom_task] notifying connection of tick {}", tick);
if level.notify(conn, &tick).await.is_err() {
info!("[custom_task] error notifying connection");
break;
};
// read RSSI (Received Signal Strength Indicator) of the connection.
if let Ok(rssi) = conn.raw().rssi(stack).await {
info!("[custom_task] RSSI: {:?}", rssi);
} else {
info!("[custom_task] error getting RSSI");
break;
};
Timer::after_secs(2).await;
}
}

View File

@@ -0,0 +1,109 @@
// BLE beacon example
//
// A beacon is a device that advertises packets that are constantly being
// updated to reflect the current state of the device, but usually does not
// accept any conections. This allows broadcasting device information.
//
use bt_hci::cmd::le::*;
use bt_hci::controller::ControllerCmdSync;
use embassy_futures::join::join;
use embassy_time::{Duration, Instant, Timer};
use trouble_host::prelude::*;
// Use your company ID (register for free with Bluetooth SIG)
const COMPANY_ID: u16 = 0xFFFF;
fn make_adv_payload(start: Instant, update_count: u32) -> [u8; 8] {
let mut data = [0u8; 8];
let elapsed_ms = Instant::now().duration_since(start).as_millis() as u32;
data[0..4].copy_from_slice(&update_count.to_be_bytes());
data[4..8].copy_from_slice(&elapsed_ms.to_be_bytes());
data
}
pub async fn run<C>(controller: C)
where
C: Controller
+ for<'t> ControllerCmdSync<LeSetExtAdvData<'t>>
+ ControllerCmdSync<LeClearAdvSets>
+ ControllerCmdSync<LeSetExtAdvParams>
+ ControllerCmdSync<LeSetAdvSetRandomAddr>
+ ControllerCmdSync<LeReadNumberOfSupportedAdvSets>
+ for<'t> ControllerCmdSync<LeSetExtAdvEnable<'t>>
+ for<'t> ControllerCmdSync<LeSetExtScanResponseData<'t>>,
{
let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);
info!("Our address = {:?}", address);
let mut resources: HostResources<DefaultPacketPool, 0, 0, 27> = HostResources::new();
let stack = trouble_host::new(controller, &mut resources).set_random_address(address);
let Host {
mut peripheral,
mut runner,
..
} = stack.build();
let mut adv_data = [0; 64];
let mut update_count = 0u32;
let start = Instant::now();
let len = AdStructure::encode_slice(
&[
AdStructure::CompleteLocalName(b"Trouble Beacon"),
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
AdStructure::ManufacturerSpecificData {
company_identifier: COMPANY_ID,
payload: &make_adv_payload(start, update_count),
},
],
&mut adv_data[..],
)
.unwrap();
info!("Starting advertising");
let _ = join(runner.run(), async {
loop {
let mut params = AdvertisementParameters::default();
params.interval_min = Duration::from_millis(25);
params.interval_max = Duration::from_millis(150);
let _advertiser = peripheral
.advertise(
&params,
Advertisement::NonconnectableNonscannableUndirected {
adv_data: &adv_data[..len],
},
)
.await
.unwrap();
loop {
Timer::after(Duration::from_millis(10)).await;
update_count = update_count.wrapping_add(1);
let len = AdStructure::encode_slice(
&[
AdStructure::CompleteLocalName(b"Trouble Beacon"),
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
AdStructure::ManufacturerSpecificData {
company_identifier: COMPANY_ID,
payload: &make_adv_payload(start, update_count),
},
],
&mut adv_data[..],
)
.unwrap();
peripheral
.update_adv_data(Advertisement::NonconnectableNonscannableUndirected {
adv_data: &adv_data[..len],
})
.await
.unwrap();
if update_count % 100 == 0 {
info!("Still running: Updated the beacon {} times", update_count);
}
}
}
})
.await;
}

View File

@@ -0,0 +1,74 @@
use embassy_futures::join::join;
use embassy_time::{Duration, Timer};
use trouble_host::prelude::*;
use crate::common::PSM_L2CAP_EXAMPLES;
/// Max number of connections
const CONNECTIONS_MAX: usize = 1;
/// Max number of L2CAP channels.
const L2CAP_CHANNELS_MAX: usize = 3; // Signal + att + CoC
pub async fn run<C>(controller: C)
where
C: Controller,
{
// Using a fixed "random" address can be useful for testing. In real scenarios, one would
// use e.g. the MAC 6 byte array as the address (how to get that varies by the platform).
let address: Address = Address::random([0xff, 0x8f, 0x1b, 0x05, 0xe4, 0xff]);
info!("Our address = {:?}", address);
let mut resources: HostResources<DefaultPacketPool, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX> = HostResources::new();
let stack = trouble_host::new(controller, &mut resources).set_random_address(address);
let Host {
mut central,
mut runner,
..
} = stack.build();
// NOTE: Modify this to match the address of the peripheral you want to connect to.
// Currently, it matches the address used by the peripheral examples
let target: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);
let config = ConnectConfig {
connect_params: Default::default(),
scan_config: ScanConfig {
filter_accept_list: &[(target.kind, &target.addr)],
..Default::default()
},
};
info!("Scanning for peripheral...");
let _ = join(runner.run(), async {
loop {
let conn = central.connect(&config).await.unwrap();
info!("Connected, creating l2cap channel");
const PAYLOAD_LEN: usize = 27;
let config = L2capChannelConfig {
mtu: Some(PAYLOAD_LEN as u16),
..Default::default()
};
let mut ch1 = L2capChannel::create(&stack, &conn, PSM_L2CAP_EXAMPLES, &config)
.await
.unwrap();
info!("New l2cap channel created, sending some data!");
for i in 0..10 {
let tx = [i; PAYLOAD_LEN];
ch1.send(&stack, &tx).await.unwrap();
}
info!("Sent data, waiting for them to be sent back");
let mut rx = [0; PAYLOAD_LEN];
for i in 0..10 {
let len = ch1.receive(&stack, &mut rx).await.unwrap();
assert_eq!(len, rx.len());
assert_eq!(rx, [i; PAYLOAD_LEN]);
}
info!("Received successfully!");
Timer::after(Duration::from_secs(60)).await;
}
})
.await;
}

View File

@@ -0,0 +1,88 @@
use embassy_futures::join::join;
use embassy_time::{Duration, Timer};
use trouble_host::prelude::*;
use crate::common::PSM_L2CAP_EXAMPLES;
/// Max number of connections
const CONNECTIONS_MAX: usize = 1;
/// Max number of L2CAP channels.
const L2CAP_CHANNELS_MAX: usize = 3; // Signal + att + CoC
pub async fn run<C>(controller: C)
where
C: Controller,
{
// Hardcoded peripheral address
let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);
info!("Our address = {:?}", address);
let mut resources: HostResources<DefaultPacketPool, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX> = HostResources::new();
let stack = trouble_host::new(controller, &mut resources).set_random_address(address);
let Host {
mut peripheral,
mut runner,
..
} = stack.build();
let mut adv_data = [0; 31];
let adv_data_len = AdStructure::encode_slice(
&[AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED)],
&mut adv_data[..],
)
.unwrap();
let mut scan_data = [0; 31];
let scan_data_len =
AdStructure::encode_slice(&[AdStructure::CompleteLocalName(b"Trouble")], &mut scan_data[..]).unwrap();
let _ = join(runner.run(), async {
loop {
info!("Advertising, waiting for connection...");
let advertiser = peripheral
.advertise(
&Default::default(),
Advertisement::ConnectableScannableUndirected {
adv_data: &adv_data[..adv_data_len],
scan_data: &scan_data[..scan_data_len],
},
)
.await
.unwrap();
let conn = advertiser.accept().await.unwrap();
info!("Connection established");
let config = L2capChannelConfig {
mtu: Some(PAYLOAD_LEN as u16),
..Default::default()
};
let mut ch1 = L2capChannel::accept(&stack, &conn, &[PSM_L2CAP_EXAMPLES], &config)
.await
.unwrap();
info!("L2CAP channel accepted");
// Size of payload we're expecting
const PAYLOAD_LEN: usize = 27;
let mut rx = [0; PAYLOAD_LEN];
for i in 0..10 {
let len = ch1.receive(&stack, &mut rx).await.unwrap();
assert_eq!(len, rx.len());
assert_eq!(rx, [i; PAYLOAD_LEN]);
}
info!("L2CAP data received, echoing");
Timer::after(Duration::from_secs(1)).await;
for i in 0..10 {
let tx = [i; PAYLOAD_LEN];
ch1.send(&stack, &tx).await.unwrap();
}
info!("L2CAP data echoed");
Timer::after(Duration::from_secs(60)).await;
}
})
.await;
}

View File

@@ -0,0 +1,66 @@
use bt_hci::cmd::le::LeSetScanParams;
use bt_hci::controller::ControllerCmdSync;
use core::cell::RefCell;
use embassy_futures::join::join;
use embassy_time::{Duration, Timer};
use heapless::Deque;
use trouble_host::prelude::*;
/// Max number of connections
const CONNECTIONS_MAX: usize = 1;
const L2CAP_CHANNELS_MAX: usize = 1;
pub async fn run<C>(controller: C)
where
C: Controller + ControllerCmdSync<LeSetScanParams>,
{
// Using a fixed "random" address can be useful for testing. In real scenarios, one would
// use e.g. the MAC 6 byte array as the address (how to get that varies by the platform).
let address: Address = Address::random([0xff, 0x8f, 0x1b, 0x05, 0xe4, 0xff]);
info!("Our address = {:?}", address);
let mut resources: HostResources<DefaultPacketPool, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX> = HostResources::new();
let stack = trouble_host::new(controller, &mut resources).set_random_address(address);
let Host {
central, mut runner, ..
} = stack.build();
let printer = Printer {
seen: RefCell::new(Deque::new()),
};
let mut scanner = Scanner::new(central);
let _ = join(runner.run_with_handler(&printer), async {
let mut config = ScanConfig::default();
config.active = true;
config.phys = PhySet::M1;
config.interval = Duration::from_secs(1);
config.window = Duration::from_secs(1);
let mut _session = scanner.scan(&config).await.unwrap();
// Scan forever
loop {
Timer::after(Duration::from_secs(1)).await;
}
})
.await;
}
struct Printer {
seen: RefCell<Deque<BdAddr, 128>>,
}
impl EventHandler for Printer {
fn on_adv_reports(&self, mut it: LeAdvReportsIter<'_>) {
let mut seen = self.seen.borrow_mut();
while let Some(Ok(report)) = it.next() {
if seen.iter().find(|b| b.raw() == report.addr.raw()).is_none() {
info!("discovered: {:?}", report.addr);
if seen.is_full() {
seen.pop_front();
}
seen.push_back(report.addr).unwrap();
}
}
}
}

View File

@@ -0,0 +1,7 @@
// PSM from the dynamic range (0x0080-0x00FF) according to the Bluetooth
// Specification for L2CAP channels using LE Credit Based Flow Control mode.
// used for the BLE L2CAP examples.
//
// https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-60/out/en/host/logical-link-control-and-adaptation-protocol-specification.html#UUID-1ffdf913-7b8a-c7ba-531e-2a9c6f6da8fb
//
pub(crate) const PSM_L2CAP_EXAMPLES: u16 = 0x0081;

View File

@@ -0,0 +1,259 @@
#![macro_use]
#![allow(unused_macros)]
use core::fmt::{Debug, Display, LowerHex};
#[cfg(all(feature = "defmt", feature = "log"))]
compile_error!("You may not enable both `defmt` and `log` features.");
macro_rules! assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert!($($x)*);
}
};
}
macro_rules! assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_eq!($($x)*);
}
};
}
macro_rules! assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_ne!($($x)*);
}
};
}
macro_rules! debug_assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert!($($x)*);
}
};
}
macro_rules! debug_assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_eq!($($x)*);
}
};
}
macro_rules! debug_assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_ne!($($x)*);
}
};
}
macro_rules! todo {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::todo!($($x)*);
#[cfg(feature = "defmt")]
::defmt::todo!($($x)*);
}
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unreachable {
($($x:tt)*) => {
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
macro_rules! unreachable {
($($x:tt)*) => {
::defmt::unreachable!($($x)*)
};
}
macro_rules! panic {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::panic!($($x)*);
#[cfg(feature = "defmt")]
::defmt::panic!($($x)*);
}
};
}
macro_rules! trace {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::trace!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::trace!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! debug {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::debug!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::debug!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! info {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::info!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::info!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! warn {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::warn!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::warn!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
macro_rules! error {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::error!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::error!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[cfg(feature = "defmt")]
macro_rules! unwrap {
($($x:tt)*) => {
::defmt::unwrap!($($x)*)
};
}
#[cfg(not(feature = "defmt"))]
macro_rules! unwrap {
($arg:expr) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
}
}
};
($arg:expr, $($msg:expr),+ $(,)? ) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
}
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct NoneError;
pub trait Try {
type Ok;
type Error;
#[allow(dead_code)]
fn into_result(self) -> Result<Self::Ok, Self::Error>;
}
impl<T> Try for Option<T> {
type Ok = T;
type Error = NoneError;
#[inline]
fn into_result(self) -> Result<T, NoneError> {
self.ok_or(NoneError)
}
}
impl<T, E> Try for Result<T, E> {
type Ok = T;
type Error = E;
#[inline]
fn into_result(self) -> Self {
self
}
}
#[allow(unused)]
pub(crate) struct Bytes<'a>(pub &'a [u8]);
impl<'a> Debug for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
impl<'a> Display for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
impl<'a> LowerHex for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
#[cfg(feature = "defmt")]
impl<'a> defmt::Format for Bytes<'a> {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(fmt, "{:02x}", self.0)
}
}

View File

@@ -0,0 +1,128 @@
use bt_hci::cmd::le::{LeReadLocalSupportedFeatures, LeSetDataLength, LeSetPhy};
use bt_hci::controller::{ControllerCmdAsync, ControllerCmdSync};
use embassy_futures::join::join;
use embassy_time::{Duration, Instant, Timer};
use trouble_host::prelude::*;
use crate::common::PSM_L2CAP_EXAMPLES;
/// Max number of connections
const CONNECTIONS_MAX: usize = 1;
/// Max number of L2CAP channels.
const L2CAP_CHANNELS_MAX: usize = 3; // Signal + att + CoC
pub async fn run<C, P>(controller: C)
where
C: Controller
+ ControllerCmdSync<LeSetDataLength>
+ ControllerCmdAsync<LeSetPhy>
+ ControllerCmdSync<LeReadLocalSupportedFeatures>,
P: PacketPool,
{
// Using a fixed "random" address can be useful for testing. In real scenarios, one would
// use e.g. the MAC 6 byte array as the address (how to get that varies by the platform).
let address: Address = Address::random([0xff, 0x8f, 0x1b, 0x05, 0xe4, 0xff]);
info!("Our address = {:?}", address);
let mut resources: HostResources<P, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX> = HostResources::new();
let stack = trouble_host::new(controller, &mut resources).set_random_address(address);
let Host {
mut central,
mut runner,
..
} = stack.build();
// NOTE: Modify this to match the address of the peripheral you want to connect to.
// Currently, it matches the address used by the peripheral examples
let target: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);
let config = ConnectConfig {
connect_params: ConnectParams {
min_connection_interval: Duration::from_millis(80),
max_connection_interval: Duration::from_millis(80),
..Default::default()
},
scan_config: ScanConfig {
filter_accept_list: &[(target.kind, &target.addr)],
..Default::default()
},
};
info!("Scanning for peripheral...");
let _ = join(runner.run(), async {
loop {
// Check that the controller used supports the necessary features for high throughput.
let res = stack
.command(LeReadLocalSupportedFeatures::new())
.await
.expect("LeReadLocalSupportedFeatures command failed");
assert!(res.supports_le_data_packet_length_extension());
assert!(res.supports_le_2m_phy());
let conn = central.connect(&config).await.expect("Connect failed");
info!("Connected, creating l2cap channel");
// Once connected, request a change in the PDU data length.
stack
.command(LeSetDataLength::new(conn.handle(), 251, 2120))
.await
.expect("LeSetDataLength command failed");
// and request changing the physical link to 2M PHY.
// *Note* Change to the PDU data length and PHY can also be initiated by the peripheral.
conn.set_phy(&stack, PhyKind::Le2M)
.await
.expect("set phy command failed");
const PAYLOAD_LEN: usize = 2510;
const L2CAP_MTU: usize = 251;
let l2cap_channel_config = L2capChannelConfig {
mtu: Some(PAYLOAD_LEN as u16 - 6),
mps: Some(L2CAP_MTU as u16 - 4),
// Ensure there will be enough credits to send data throughout the entire connection event.
flow_policy: CreditFlowPolicy::Every(50),
initial_credits: Some(200),
};
let mut ch1 = L2capChannel::create(&stack, &conn, PSM_L2CAP_EXAMPLES, &l2cap_channel_config)
.await
.expect("L2capChannel create failed");
// Wait for the ratios to switch to 2M PHY.
// If we do not wait, communication will still occur at 1M for the first 500 ms.
Timer::after(Duration::from_secs(1)).await;
info!("New l2cap channel created, sending some data!");
const NUM_PAYLOADS: u8 = 40;
let start = Instant::now();
for i in 0..NUM_PAYLOADS {
let tx = [i; PAYLOAD_LEN];
ch1.send(&stack, &tx).await.expect("L2CAP send failed");
}
let duration = start.elapsed();
info!(
"Sent {} bytes at {} kbps, waiting for them to be sent back.",
(PAYLOAD_LEN as u64 * NUM_PAYLOADS as u64),
((PAYLOAD_LEN as u64 * NUM_PAYLOADS as u64 * 8).div_ceil(duration.as_millis()))
);
let mut rx = [0; PAYLOAD_LEN];
for i in 0..NUM_PAYLOADS {
let len = ch1.receive(&stack, &mut rx).await.expect("L2CAP receive failed");
assert_eq!(len, rx.len());
assert_eq!(rx, [i; PAYLOAD_LEN]);
}
info!("Received successfully!");
Timer::after(Duration::from_secs(60)).await;
}
})
.await;
}

View File

@@ -0,0 +1,115 @@
use bt_hci::cmd::le::LeReadLocalSupportedFeatures;
use bt_hci::controller::ControllerCmdSync;
use embassy_futures::join::join;
use embassy_time::{Duration, Instant, Timer};
use trouble_host::prelude::*;
use crate::common::PSM_L2CAP_EXAMPLES;
/// Max number of connections
const CONNECTIONS_MAX: usize = 1;
/// Max number of L2CAP channels.
const L2CAP_CHANNELS_MAX: usize = 3; // Signal + att + CoC
pub async fn run<C, P>(controller: C)
where
C: Controller + ControllerCmdSync<LeReadLocalSupportedFeatures>,
P: PacketPool,
{
// Hardcoded peripheral address
let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);
info!("Our address = {:?}", address);
let mut resources: HostResources<P, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX> = HostResources::new();
let stack = trouble_host::new(controller, &mut resources).set_random_address(address);
let Host {
mut peripheral,
mut runner,
..
} = stack.build();
let mut adv_data = [0; 31];
let adv_data_len = AdStructure::encode_slice(
&[AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED)],
&mut adv_data[..],
)
.unwrap();
let mut scan_data = [0; 31];
let scan_data_len =
AdStructure::encode_slice(&[AdStructure::CompleteLocalName(b"TroubleHT")], &mut scan_data[..]).unwrap();
let _ = join(runner.run(), async {
loop {
// Check that the controller used supports the necessary features for high throughput.
let res = stack
.command(LeReadLocalSupportedFeatures::new())
.await
.expect("LeReadLocalSupportedFeatures command failed");
assert!(res.supports_le_data_packet_length_extension());
assert!(res.supports_le_2m_phy());
info!("Advertising, waiting for connection...");
let advertiser = peripheral
.advertise(
&Default::default(),
Advertisement::ConnectableScannableUndirected {
adv_data: &adv_data[..adv_data_len],
scan_data: &scan_data[..scan_data_len],
},
)
.await
.expect("Advertising failed");
let conn = advertiser.accept().await.expect("Connection failed");
info!("Connection established");
const PAYLOAD_LEN: usize = 2510;
const L2CAP_MTU: usize = 251;
let l2cap_channel_config = L2capChannelConfig {
mtu: Some(PAYLOAD_LEN as u16 - 6),
mps: Some(L2CAP_MTU as u16 - 4),
// Ensure there will be enough credits to send data throughout the entire connection event.
flow_policy: CreditFlowPolicy::Every(50),
initial_credits: Some(200),
};
let mut ch1 = L2capChannel::accept(&stack, &conn, &[PSM_L2CAP_EXAMPLES], &l2cap_channel_config)
.await
.expect("L2capChannel create failed");
info!("L2CAP channel accepted");
// Size of payload we're expecting
const NUM_PAYLOADS: u8 = 40;
let mut rx = [0; PAYLOAD_LEN];
for i in 0..NUM_PAYLOADS {
let len = ch1.receive(&stack, &mut rx).await.expect("L2CAP receive failed");
assert_eq!(len, rx.len());
assert_eq!(rx, [i; PAYLOAD_LEN]);
}
info!("L2CAP data received, echoing");
Timer::after(Duration::from_secs(1)).await;
let start = Instant::now();
for i in 0..NUM_PAYLOADS {
let tx = [i; PAYLOAD_LEN];
ch1.send(&stack, &tx).await.expect("L2CAP send failed");
}
let duration = start.elapsed();
info!(
"L2CAP data of {} bytes echoed at {} kbps.",
(PAYLOAD_LEN as u64 * NUM_PAYLOADS as u64),
((PAYLOAD_LEN as u64 * NUM_PAYLOADS as u64 * 8).div_ceil(duration.as_millis()))
);
Timer::after(Duration::from_secs(60)).await;
}
})
.await;
}

View File

@@ -0,0 +1,18 @@
#![no_std]
#![allow(dead_code)]
pub(crate) mod common;
pub(crate) mod fmt;
pub mod ble_advertise;
pub mod ble_advertise_multiple;
pub mod ble_bas_central;
pub mod ble_bas_central_sec;
pub mod ble_bas_peripheral;
pub mod ble_bas_peripheral_sec;
pub mod ble_beacon;
pub mod ble_l2cap_central;
pub mod ble_l2cap_peripheral;
pub mod ble_scanner;
pub mod high_throughput_ble_l2cap_central;
pub mod high_throughput_ble_l2cap_peripheral;