From ea7a3908df6816225641da342de2466d91ff6f2b Mon Sep 17 00:00:00 2001 From: alexmoon Date: Mon, 11 Jul 2022 18:02:35 -0400 Subject: Modify macros to use the builder interface and add an builder example --- examples/src/bin/ble_bas_peripheral.rs | 7 +- examples/src/bin/ble_dis_bas_peripheral_builder.rs | 290 +++++++++++++++++++++ examples/src/bin/ble_peripheral_onoff.rs | 12 +- 3 files changed, 301 insertions(+), 8 deletions(-) create mode 100644 examples/src/bin/ble_dis_bas_peripheral_builder.rs (limited to 'examples') diff --git a/examples/src/bin/ble_bas_peripheral.rs b/examples/src/bin/ble_bas_peripheral.rs index 872ddcf..f685513 100644 --- a/examples/src/bin/ble_bas_peripheral.rs +++ b/examples/src/bin/ble_bas_peripheral.rs @@ -49,9 +49,7 @@ struct Server { } #[embassy::task] -async fn bluetooth_task(sd: &'static Softdevice) { - let server: Server = unwrap!(gatt_server::register(sd)); - +async fn bluetooth_task(sd: &'static Softdevice, server: Server) { #[rustfmt::skip] let adv_data = &[ 0x02, 0x01, raw::BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE as u8, @@ -148,7 +146,8 @@ fn main() -> ! { let executor = EXECUTOR.put(Executor::new()); executor.run(move |spawner| { + let server = unwrap!(Server::new(sd)); unwrap!(spawner.spawn(softdevice_task(sd))); - unwrap!(spawner.spawn(bluetooth_task(sd))); + unwrap!(spawner.spawn(bluetooth_task(sd, server))); }); } diff --git a/examples/src/bin/ble_dis_bas_peripheral_builder.rs b/examples/src/bin/ble_dis_bas_peripheral_builder.rs new file mode 100644 index 0000000..858b74b --- /dev/null +++ b/examples/src/bin/ble_dis_bas_peripheral_builder.rs @@ -0,0 +1,290 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(alloc_error_handler)] +#![allow(incomplete_features)] + +#[path = "../example_common.rs"] +mod example_common; + +use core::mem; +use cortex_m_rt::entry; +use defmt::info; +use defmt::*; +use embassy::executor::Executor; +use embassy::util::Forever; +use nrf_softdevice::ble::gatt_server::builder::ServiceBuilder; +use nrf_softdevice::ble::gatt_server::characteristic::{Attribute, Metadata, Properties}; +use nrf_softdevice::ble::gatt_server::{CharacteristicHandles, RegisterError}; +use nrf_softdevice::ble::{gatt_server, peripheral, Connection, Uuid}; +use nrf_softdevice::{raw, Softdevice}; + +const DEVICE_INFORMATION: Uuid = Uuid::new_16(0x180a); +const BATTERY_SERVICE: Uuid = Uuid::new_16(0x180f); + +const BATTERY_LEVEL: Uuid = Uuid::new_16(0x2a19); +const MODEL_NUMBER: Uuid = Uuid::new_16(0x2a24); +const SERIAL_NUMBER: Uuid = Uuid::new_16(0x2a25); +const FIRMWARE_REVISION: Uuid = Uuid::new_16(0x2a26); +const HARDWARE_REVISION: Uuid = Uuid::new_16(0x2a27); +const SOFTWARE_REVISION: Uuid = Uuid::new_16(0x2a28); +const MANUFACTURER_NAME: Uuid = Uuid::new_16(0x2a29); +const PNP_ID: Uuid = Uuid::new_16(0x2a50); + +static EXECUTOR: Forever = Forever::new(); + +#[embassy::task] +async fn softdevice_task(sd: &'static Softdevice) { + sd.run().await; +} + +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum VidSource { + BluetoothSIG = 1, + UsbIF = 2, +} + +#[repr(C, packed)] +#[derive(Clone, Copy)] +pub struct PnPID { + pub vid_source: VidSource, + pub vendor_id: u16, + pub product_id: u16, + pub product_version: u16, +} + +#[derive(Debug, Default, defmt::Format)] +pub struct DeviceInformation { + pub manufacturer_name: Option<&'static str>, + pub model_number: Option<&'static str>, + pub serial_number: Option<&'static str>, + pub hw_rev: Option<&'static str>, + pub fw_rev: Option<&'static str>, + pub sw_rev: Option<&'static str>, +} + +pub struct DeviceInformationService {} + +impl DeviceInformationService { + pub fn new( + sd: &mut Softdevice, + pnp_id: &PnPID, + info: DeviceInformation, + ) -> Result { + let mut sb = ServiceBuilder::new(sd, DEVICE_INFORMATION)?; + + Self::add_pnp_characteristic(&mut sb, pnp_id)?; + Self::add_opt_str_characteristic(&mut sb, MANUFACTURER_NAME, info.manufacturer_name)?; + Self::add_opt_str_characteristic(&mut sb, MODEL_NUMBER, info.model_number)?; + Self::add_opt_str_characteristic(&mut sb, SERIAL_NUMBER, info.serial_number)?; + Self::add_opt_str_characteristic(&mut sb, HARDWARE_REVISION, info.hw_rev)?; + Self::add_opt_str_characteristic(&mut sb, FIRMWARE_REVISION, info.fw_rev)?; + Self::add_opt_str_characteristic(&mut sb, SOFTWARE_REVISION, info.sw_rev)?; + + let _service_handle = sb.build(); + + Ok(DeviceInformationService {}) + } + + fn add_opt_str_characteristic( + sb: &mut ServiceBuilder, + uuid: Uuid, + val: Option<&'static str>, + ) -> Result, RegisterError> { + if let Some(val) = val { + let attr = Attribute::new(val); + let md = Metadata::new(Properties::new().read()); + Ok(Some(sb.add_characteristic(uuid, attr, md)?.build())) + } else { + Ok(None) + } + } + + fn add_pnp_characteristic( + sb: &mut ServiceBuilder, + pnp_id: &PnPID, + ) -> Result { + // SAFETY: `PnPID` is `repr(C, packed)` so viewing it as an immutable slice of bytes is safe. + let val = unsafe { + core::slice::from_raw_parts( + pnp_id as *const _ as *const u8, + core::mem::size_of::(), + ) + }; + + let attr = Attribute::new(val); + let md = Metadata::new(Properties::new().read()); + Ok(sb.add_characteristic(PNP_ID, attr, md)?.build()) + } +} + +pub struct BatteryService { + value_handle: u16, + cccd_handle: u16, +} + +impl BatteryService { + pub fn new(sd: &mut Softdevice) -> Result { + let mut service_builder = ServiceBuilder::new(sd, BATTERY_SERVICE)?; + + let attr = Attribute::new(&[0u8]); + let metadata = Metadata::new(Properties::new().read().notify()); + let characteristic_builder = + service_builder.add_characteristic(BATTERY_LEVEL, attr, metadata)?; + let characteristic_handles = characteristic_builder.build(); + + let _service_handle = service_builder.build(); + + Ok(BatteryService { + value_handle: characteristic_handles.value_handle, + cccd_handle: characteristic_handles.cccd_handle, + }) + } + + pub fn battery_level_get(&self, sd: &Softdevice) -> Result { + let buf = &mut [0u8]; + gatt_server::get_value(sd, self.value_handle, buf)?; + Ok(buf[0]) + } + + pub fn battery_level_set( + &self, + sd: &Softdevice, + val: u8, + ) -> Result<(), gatt_server::SetValueError> { + gatt_server::set_value(sd, self.value_handle, &[val]) + } + pub fn battery_level_notify( + &self, + conn: &Connection, + val: u8, + ) -> Result<(), gatt_server::NotifyValueError> { + gatt_server::notify_value(conn, self.value_handle, &[val]) + } + + pub fn on_write(&self, handle: u16, data: &[u8]) { + if handle == self.cccd_handle && !data.is_empty() { + info!("battery notifications: {}", (data[0] & 0x01) != 0); + } + } +} + +struct Server { + _dis: DeviceInformationService, + bas: BatteryService, +} + +impl Server { + pub fn new(sd: &mut Softdevice, serial_number: &'static str) -> Result { + let dis = DeviceInformationService::new( + sd, + &PnPID { + vid_source: VidSource::UsbIF, + vendor_id: 0xDEAD, + product_id: 0xBEEF, + product_version: 0x0000, + }, + DeviceInformation { + manufacturer_name: Some("Embassy"), + model_number: Some("M1234"), + serial_number: Some(serial_number), + ..Default::default() + }, + )?; + + let bas = BatteryService::new(sd)?; + + Ok(Self { _dis: dis, bas }) + } +} + +impl gatt_server::Server for Server { + type Event = (); + + fn on_write(&self, handle: u16, data: &[u8]) -> Option { + self.bas.on_write(handle, data); + None + } +} + +#[embassy::task] +async fn bluetooth_task(sd: &'static Softdevice, server: Server) { + #[rustfmt::skip] + let adv_data = &[ + 0x02, 0x01, raw::BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE as u8, + 0x03, 0x03, 0x09, 0x18, + 0x0a, 0x09, b'H', b'e', b'l', b'l', b'o', b'R', b'u', b's', b't', + ]; + #[rustfmt::skip] + let scan_data = &[ + 0x03, 0x03, 0x09, 0x18, + ]; + + loop { + let config = peripheral::Config::default(); + let adv = peripheral::ConnectableAdvertisement::ScannableUndirected { + adv_data, + scan_data, + }; + let conn = unwrap!(peripheral::advertise_connectable(sd, adv, &config).await); + + info!("advertising done!"); + + // Run the GATT server on the connection. This returns when the connection gets disconnected. + let res = gatt_server::run(&conn, &server, |_| {}).await; + + if let Err(e) = res { + info!("gatt_server run exited with error: {:?}", e); + } + } +} + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let config = nrf_softdevice::Config { + clock: Some(raw::nrf_clock_lf_cfg_t { + source: raw::NRF_CLOCK_LF_SRC_RC as u8, + rc_ctiv: 4, + rc_temp_ctiv: 2, + accuracy: 7, + }), + conn_gap: Some(raw::ble_gap_conn_cfg_t { + conn_count: 6, + event_length: 24, + }), + conn_gatt: Some(raw::ble_gatt_conn_cfg_t { att_mtu: 256 }), + gatts_attr_tab_size: Some(raw::ble_gatts_cfg_attr_tab_size_t { + attr_tab_size: 32768, + }), + gap_role_count: Some(raw::ble_gap_cfg_role_count_t { + adv_set_count: 1, + periph_role_count: 3, + central_role_count: 3, + central_sec_count: 0, + _bitfield_1: raw::ble_gap_cfg_role_count_t::new_bitfield_1(0), + }), + gap_device_name: Some(raw::ble_gap_cfg_device_name_t { + p_value: b"HelloRust" as *const u8 as _, + current_len: 9, + max_len: 9, + write_perm: unsafe { mem::zeroed() }, + _bitfield_1: raw::ble_gap_cfg_device_name_t::new_bitfield_1( + raw::BLE_GATTS_VLOC_STACK as u8, + ), + }), + ..Default::default() + }; + + let sd = Softdevice::enable(&config); + + let server = unwrap!(Server::new(sd, "12345678")); + + let executor = EXECUTOR.put(Executor::new()); + executor.run(move |spawner| { + unwrap!(spawner.spawn(softdevice_task(sd))); + unwrap!(spawner.spawn(bluetooth_task(sd, server))); + }); +} diff --git a/examples/src/bin/ble_peripheral_onoff.rs b/examples/src/bin/ble_peripheral_onoff.rs index a12f46e..539b168 100644 --- a/examples/src/bin/ble_peripheral_onoff.rs +++ b/examples/src/bin/ble_peripheral_onoff.rs @@ -79,9 +79,7 @@ async fn run_bluetooth(sd: &'static Softdevice, server: &Server) { } #[embassy::task] -async fn bluetooth_task(sd: &'static Softdevice, button1: AnyPin, button2: AnyPin) { - let server: Server = unwrap!(gatt_server::register(sd)); - +async fn bluetooth_task(sd: &'static Softdevice, server: Server, button1: AnyPin, button2: AnyPin) { info!("Bluetooth is OFF"); info!("Press nrf52840-dk button 1 to enable, button 2 to disable"); @@ -171,7 +169,13 @@ fn main() -> ! { let executor = EXECUTOR.put(Executor::new()); executor.run(move |spawner| { + let server = unwrap!(Server::new(sd)); unwrap!(spawner.spawn(softdevice_task(sd))); - unwrap!(spawner.spawn(bluetooth_task(sd, p.P0_11.degrade(), p.P0_12.degrade()))); + unwrap!(spawner.spawn(bluetooth_task( + sd, + server, + p.P0_11.degrade(), + p.P0_12.degrade() + ))); }); } -- cgit v1.2.3