-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use ChaCha12 in security manager. Tested on ESP32-C6, nrf52833 and Serial HCI.
- Loading branch information
Showing
36 changed files
with
4,799 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
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, const L2CAP_MTU: usize>(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<CONNECTIONS_MAX, L2CAP_CHANNELS_MAX, L2CAP_MTU> = 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, 10, 24>::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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
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, const L2CAP_MTU: usize>(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<CONNECTIONS_MAX, L2CAP_CHANNELS_MAX, L2CAP_MTU> = 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>(mut runner: Runner<'_, C>) { | ||
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<'_, '_>) -> Result<(), Error> { | ||
let level = server.battery_service.level; | ||
loop { | ||
match conn.next().await { | ||
GattConnectionEvent::Disconnected { reason } => { | ||
info!("[gatt] disconnected: {:?}", reason); | ||
break; | ||
} | ||
GattConnectionEvent::Gatt { event } => match event { | ||
Ok(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 | ||
} | ||
}; | ||
|
||
// This step is also performed at drop(), but writing it explicitly is necessary | ||
// in order to ensure reply is sent. | ||
let result = if let Some(code) = result { | ||
event.reject(code) | ||
} else { | ||
event.accept() | ||
}; | ||
match result { | ||
Ok(reply) => { | ||
reply.send().await; | ||
} | ||
Err(e) => { | ||
warn!("[gatt] error sending response: {:?}", e); | ||
} | ||
} | ||
} | ||
Err(e) => warn!("[gatt] error processing event: {:?}", e), | ||
}, | ||
} | ||
} | ||
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, 'b, C: Controller>( | ||
name: &'a str, | ||
peripheral: &mut Peripheral<'a, C>, | ||
server: &'b Server<'_>, | ||
) -> Result<GattConnection<'a, 'b>, BleHostError<C::Error>> { | ||
let mut advertiser_data = [0; 31]; | ||
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[..], | ||
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>(server: &Server<'_>, conn: &GattConnection<'_, '_>, stack: &Stack<'_, C>) { | ||
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.