diff --git a/Cargo.toml b/Cargo.toml index 646048e..a7bbb70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,38 +10,33 @@ lsm303agr = "1.1.0" heapless = "0.8.0" defmt-rtt = "0.4" -defmt = "0.3" +defmt = "0.3.10" panic-probe = { version = "0.3", features = ["print-defmt"] } # embassy dependencies -embassy-sync = { version = "^0.6.0", default-features = false, features = [ - "defmt", -] } -embassy-futures = { version = "^0.1.0", default-features = false } -embassy-executor = { version = "^0.6.1", default-features = false, features = [ - "integrated-timers", - "defmt", +embassy-sync = { version = "0.6.2", features = ["defmt"] } +embassy-futures = { version = "0.1.1" } +embassy-executor = { version = "0.7.0", default-features = false, features = [ "arch-cortex-m", + "defmt", "executor-interrupt", "executor-thread", "task-arena-size-32768", ] } -embassy-time = { version = "^0.3.0", default-features = false, features = [ +embassy-time = { version = "0.4.0", features = [ "defmt", "defmt-timestamp-uptime", ] } # nrf52833 dependencies -microbit-bsp = { git = "https://github.com/jamessizeland/microbit-bsp.git", branch = "trouble", features = [ +microbit-bsp = { git = "https://github.com/lulf/microbit-bsp.git", features = [ "trouble", -] } -# microbit-bsp = { path = "../microbit-bsp", features = ["trouble"] } +], branch = "main" } # BLE dependencies -bt-hci = { version = "0.1.1", default-features = false, features = ["defmt"] } trouble-host = { git = "https://github.com/embassy-rs/trouble.git", features = [ "defmt", -], branch = "main" } +], rev = "a1daee269deee25fbb6236cb599a8fb5919c02b7" } static_cell = "2.1.0" [profile.release] diff --git a/src/ble/advertiser.rs b/src/ble/advertiser.rs index b432930..785ba74 100644 --- a/src/ble/advertiser.rs +++ b/src/ble/advertiser.rs @@ -1,69 +1,31 @@ use defmt::info; use trouble_host::prelude::*; -/// BLE advertiser -pub struct AdvertiserBuilder<'d, C: Controller> { - /// Name of the device - name: &'d str, - peripheral: Peripheral<'d, C>, -} - -pub struct Advertiser<'d, C: Controller> { - advertiser_data: [u8; 31], - scan_data: [u8; 4], - peripheral: Peripheral<'d, C>, -} - -/// A BLE advertiser -impl<'d, C: Controller> AdvertiserBuilder<'d, C> { - /// Create a new advertiser builder - pub fn new(name: &'d str, peripheral: Peripheral<'d, C>) -> Self { - Self { name, peripheral } - } - /// Build the advertiser - pub fn build(self) -> Result, Error> { - let name: &str; - if self.name.len() > 22 { - name = &self.name[..22]; - info!("Name truncated to {}", name); - } else { - name = self.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(name.as_bytes()), - ], - &mut advertiser_data[..], - )?; - #[rustfmt::skip] - let scan_data: [u8;4] = [0; 4]; - Ok(Advertiser { - advertiser_data, - scan_data, - peripheral: self.peripheral, - }) - } -} - -impl<'d, C: Controller> Advertiser<'d, C> { - /// Advertise and connect to a device with the given name - pub async fn advertise(&mut self) -> Result, BleHostError> { - let mut advertiser = self - .peripheral - .advertise( - &Default::default(), - Advertisement::ConnectableScannableUndirected { - adv_data: &self.advertiser_data[..], - scan_data: &self.scan_data[..], - }, - ) - .await?; - info!("advertising"); - let conn = advertiser.accept().await?; - info!("connection established"); - Ok(conn) - } +/// Create an advertiser to use to connect to a BLE Central, and wait for it to connect. +pub async fn advertise<'a, C: Controller>( + name: &'a str, + peripheral: &mut Peripheral<'a, C>, +) -> Result, BleHostError> { + 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(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?; + info!("[adv] connection established"); + Ok(conn) } diff --git a/src/ble/gatt.rs b/src/ble/gatt.rs index aa56edd..1f0b6b9 100644 --- a/src/ble/gatt.rs +++ b/src/ble/gatt.rs @@ -1,46 +1,36 @@ -use super::advertiser::{Advertiser, AdvertiserBuilder}; +use super::hid::*; use super::{ble_task, mpsl_task, BleResources}; -use super::{hid::*, BleServer}; use super::{stick::*, BleController}; -use defmt::info; +use defmt::{info, warn}; use embassy_executor::Spawner; -use embassy_futures::select::select; -use embassy_futures::select::Either; -use microbit_bsp::ble::{MultiprotocolServiceLayer, SoftdeviceError}; +use microbit_bsp::ble::{MultiprotocolServiceLayer, SoftdeviceController, SoftdeviceError}; use static_cell::StaticCell; use trouble_host::prelude::*; -use trouble_host::types::gatt_traits::GattValue; /// Allow a central to decide which player this controller belongs to #[gatt_service(uuid = "8f701cf1-b1df-42a1-bb5f-6a1028c793b0")] pub struct Player { - #[characteristic(uuid = "e3d1afe4-b414-44e3-be54-0ea26c394eba", read, write, on_write = on_write)] + #[characteristic(uuid = "e3d1afe4-b414-44e3-be54-0ea26c377eba", read, write)] index: u8, } -fn on_write(_: &Connection<'_>, value: &[u8]) -> Result<(), ()> { - if let Ok(index) = u8::from_gatt(value) { - info!("Player index set to {:?}", index); - }; - Ok(()) -} - -#[gatt_server(attribute_data_size = 100)] -pub struct Server { +#[gatt_server] +pub struct BleServer { // pub bas: BatteryService, pub hid: ButtonService, pub stick: StickService, pub player: Player, } -impl Server<'static, 'static, BleController> { +impl<'d> BleServer<'d> { + /// Build the stack for the GATT server and start background tasks required for the + /// Softdevice (Noridc's BLE stack) to run. pub fn start_gatt( - name: &'static str, + name: &'d str, spawner: Spawner, controller: BleController, - mpsl: &'static MultiprotocolServiceLayer<'static>, - ) -> Result<(&'static Self, Advertiser<'static, BleController>), BleHostError> - { + mpsl: &'static MultiprotocolServiceLayer<'_>, + ) -> Result<(&'static Self, Peripheral<'d, BleController>), BleHostError> { spawner.must_spawn(mpsl_task(mpsl)); let address = Address::random([0x42, 0x5A, 0xE3, 0x1E, 0x83, 0xE7]); @@ -48,60 +38,63 @@ impl Server<'static, 'static, BleController> { let resources = { static RESOURCES: StaticCell = StaticCell::new(); - RESOURCES.init(BleResources::new(PacketQos::None)) + RESOURCES.init(BleResources::new()) + }; + let stack = { + static STACK: StaticCell>> = StaticCell::new(); + STACK.init(trouble_host::new(controller, resources).set_random_address(address)) }; - let (stack, peripheral, _, runner) = trouble_host::new(controller, resources) - .set_random_address(address) - .build(); + let host = stack.build(); let server = { static SERVER: StaticCell> = StaticCell::new(); SERVER.init( - Server::new_with_config( - stack, - GapConfig::Peripheral(PeripheralConfig { - name, - appearance: &appearance::GAMEPAD, - }), - ) + BleServer::new_with_config(GapConfig::Peripheral(PeripheralConfig { + name, + appearance: &appearance::human_interface_device::GAMEPAD, + })) .expect("Error creating Gatt Server"), ) }; info!("Starting Gatt Server"); - spawner.must_spawn(ble_task(runner)); - let advertiser = AdvertiserBuilder::new(name, peripheral).build()?; - Ok((server, advertiser)) + spawner.must_spawn(ble_task(host.runner)); + Ok((server, host.peripheral)) } } -/// A BLE GATT server +/// A BLE GATT server. +/// +/// This is where we can interact with events from the GATT server. +/// This task will run until the connection is disconnected. pub async fn gatt_server_task(server: &BleServer<'_>, conn: &Connection<'_>) { + let index = server.player.index; loop { - if let Either::First(event) = select(conn.next(), server.run()).await { - match event { - ConnectionEvent::Disconnected { reason } => { - info!("[gatt] Disconnected: {:?}", reason); - break; - } - ConnectionEvent::Gatt { event, .. } => match event { - GattEvent::Read { value_handle } => { - if value_handle == server.player.index.handle { - let value = server.get(&server.player.index); - info!( - "[gatt] Read Event to Player Index Characteristic: {:?}", - value - ); + match conn.next().await { + ConnectionEvent::Disconnected { reason } => { + info!("[gatt] Disconnected: {:?}", reason); + break; + } + ConnectionEvent::Gatt { data, .. } => { + match data.process(server).await { + // Server processing emits + Ok(Some(GattEvent::Read(event))) => { + if event.handle() == index.handle { + let value = server.get(&index); + info!("[gatt] Read Event to index Characteristic: {:?}", value); } } - GattEvent::Write { value_handle } => { - if value_handle == server.player.index.handle { - let value = server.get(&server.player.index); + Ok(Some(GattEvent::Write(event))) => { + if event.handle() == index.handle { info!( - "[gatt] Write Event to Player Index Characteristic: {:?}", - value + "[gatt] Write Event to index Characteristic: {:?}", + event.data() ); } } - }, + Ok(_) => {} + Err(e) => { + warn!("[gatt] error processing event: {:?}", e); + } + } } } } diff --git a/src/ble/hid.rs b/src/ble/hid.rs index bbc8d74..847ec2d 100644 --- a/src/ble/hid.rs +++ b/src/ble/hid.rs @@ -45,7 +45,7 @@ pub async fn notify_button_state( loop { button.input.wait_for_low().await; info!("button {} pressed", button.name); - server.notify(&button.ble_handle, connection, &true).await?; + button.ble_handle.notify(server, connection, &true).await?; display .display( DisplayFrame::Letter(button.name), @@ -55,9 +55,7 @@ pub async fn notify_button_state( Timer::after(debounce).await; button.input.wait_for_high().await; info!("button {} released", button.name); - server - .notify(&button.ble_handle, connection, &false) - .await?; + button.ble_handle.notify(server, connection, &false).await?; Timer::after(debounce).await; } } diff --git a/src/ble/mod.rs b/src/ble/mod.rs index b5d956a..2bc7a5b 100644 --- a/src/ble/mod.rs +++ b/src/ble/mod.rs @@ -3,6 +3,7 @@ pub mod gatt; pub mod hid; pub mod stick; +pub use gatt::BleServer; use microbit_bsp::ble::{MultiprotocolServiceLayer, SoftdeviceController}; use trouble_host::prelude::*; @@ -15,12 +16,9 @@ const CONNECTIONS_MAX: usize = 1; /// Max number of L2CAP channels. const L2CAP_CHANNELS_MAX: usize = 2; // Signal + att -pub type BleServer<'d> = gatt::Server<'d, 'd, SoftdeviceController<'d>>; - pub type BleController = SoftdeviceController<'static>; -pub type BleResources = - HostResources; +pub type BleResources = HostResources; #[embassy_executor::task] pub async fn mpsl_task(mpsl: &'static MultiprotocolServiceLayer<'static>) -> ! { diff --git a/src/ble/stick.rs b/src/ble/stick.rs index 21e0ba2..009bd4c 100644 --- a/src/ble/stick.rs +++ b/src/ble/stick.rs @@ -51,7 +51,7 @@ impl Axis { let new = -((new_raw - self.offset) / self.divider) as i8; // invert the value if new != self.old { self.old = new; - Some(new as i8) + Some(new) } else { None } @@ -73,16 +73,17 @@ pub async fn analog_stick_task( let divider = 623; let mut x_axis = Axis::new(offset, divider); let mut y_axis = Axis::new(offset, divider); + let stick = &server.stick; loop { // read adc values for x and y, and if they have changed by a certain amount, notify // we are reducing the number of analogue stick levels to a range of -2 to 2 saadc.sample(&mut buf).await; // display the x and y values on the led matrix if let Some(x) = x_axis.changed(buf[0]) { - server.notify(&server.stick.x, conn, &x).await?; + stick.x.notify(server, conn, &x).await?; } if let Some(y) = y_axis.changed(buf[1]) { - server.notify(&server.stick.y, conn, &y).await?; + stick.y.notify(server, conn, &y).await?; } if !(x_axis.old == 0 && y_axis.old == 0) { // only display if the stick is not centered diff --git a/src/io/display/mod.rs b/src/io/display/mod.rs index cdcff9c..22c20d0 100644 --- a/src/io/display/mod.rs +++ b/src/io/display/mod.rs @@ -69,7 +69,7 @@ impl AsyncDisplay { #[allow(unused)] pub enum DisplayFrame { - DisplayFrame(Frame<5, 5>), + Custom(Frame<5, 5>), /// Display a single pixel at the given coordinates, where (0,0) is the center of the display. Coord { x: i8, @@ -89,7 +89,7 @@ pub enum DisplayFrame { impl DisplayFrame { fn to_frame(&self) -> Frame<5, 5> { match self { - DisplayFrame::DisplayFrame(frame) => frame.clone(), + DisplayFrame::Custom(frame) => *frame, DisplayFrame::Heart => bitmap::HEART, DisplayFrame::Smile => bitmap::SMILE, DisplayFrame::Sad => bitmap::SAD, diff --git a/src/main.rs b/src/main.rs index 05257fa..cf4fc5b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,10 +14,10 @@ use microbit_bsp::{display::Brightness, embassy_nrf::gpio::Pin as _, Microbit}; use crate::{ ble::{ - gatt::gatt_server_task, + advertiser::advertise, + gatt::{gatt_server_task, BleServer}, hid::{buttons_task, GamepadInputs}, stick::{analog_stick_task, init_analog_adc}, - BleServer, }, io::{ audio::{AsyncAudio, Tune}, @@ -39,7 +39,7 @@ async fn main(spawner: Spawner) { .ble .init(board.timer0, board.rng) .expect("BLE stack failed to initialize"); - let (server, mut advertiser) = + let (server, mut peripheral) = BleServer::start_gatt(name, spawner, sdc, mpsl).expect("Failed to start GATT server"); let mut gamepad_buttons = GamepadInputs::new( @@ -61,7 +61,7 @@ async fn main(spawner: Spawner) { loop { display.display(QuestionMark, Duration::from_secs(2)).await; // advertise for connections - if let Ok(conn) = advertiser.advertise().await { + if let Ok(conn) = advertise("Rust Gamepad", &mut peripheral).await { let pause = Duration::from_secs(1); speaker.play_tune(Tune::Connect).await; display.display_blocking(Heart, pause).await;