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 +- nrf-softdevice-macro/src/lib.rs | 84 +++--- nrf-softdevice/src/ble/gatt_server.rs | 86 ------ 5 files changed, 345 insertions(+), 134 deletions(-) create mode 100644 examples/src/bin/ble_dis_bas_peripheral_builder.rs 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() + ))); }); } diff --git a/nrf-softdevice-macro/src/lib.rs b/nrf-softdevice-macro/src/lib.rs index b0382d6..020da80 100644 --- a/nrf-softdevice-macro/src/lib.rs +++ b/nrf-softdevice-macro/src/lib.rs @@ -45,6 +45,7 @@ struct Characteristic { pub fn gatt_server(_args: TokenStream, item: TokenStream) -> TokenStream { let mut struc = syn::parse_macro_input!(item as syn::ItemStruct); + let struct_vis = &struc.vis; let struct_fields = match &mut struc.fields { syn::Fields::Named(n) => n, _ => { @@ -74,9 +75,10 @@ pub fn gatt_server(_args: TokenStream, item: TokenStream) -> TokenStream { for field in fields.iter() { let name = field.ident.as_ref().unwrap(); + let ty = &field.ty; let span = field.ty.span(); code_register_init.extend(quote_spanned!(span=> - #name: #ble::gatt_server::register_service(sd)?, + #name: #ty::new(sd)?, )); if let syn::Type::Path(p) = &field.ty { @@ -105,6 +107,12 @@ pub fn gatt_server(_args: TokenStream, item: TokenStream) -> TokenStream { #struc impl #struct_name { + #struct_vis fn new(sd: &mut ::nrf_softdevice::Softdevice) -> Result + { + Ok(Self { + #code_register_init + }) + } } #struc_vis enum #event_enum_name { @@ -114,13 +122,6 @@ pub fn gatt_server(_args: TokenStream, item: TokenStream) -> TokenStream { impl #ble::gatt_server::Server for #struct_name { type Event = #event_enum_name; - fn register(sd: &::nrf_softdevice::Softdevice) -> Result - { - Ok(Self { - #code_register_init - }) - } - fn on_write(&self, handle: u16, data: &[u8]) -> Option { use #ble::gatt_server::Service; @@ -146,6 +147,7 @@ pub fn gatt_service(args: TokenStream, item: TokenStream) -> TokenStream { let mut chars = Vec::new(); + let struct_vis = &struc.vis; let struct_fields = match &mut struc.fields { syn::Fields::Named(n) => n, _ => { @@ -202,8 +204,8 @@ pub fn gatt_service(args: TokenStream, item: TokenStream) -> TokenStream { let event_enum_name = format_ident!("{}Event", struct_name); let mut code_impl = TokenStream2::new(); - let mut code_register_chars = TokenStream2::new(); - let mut code_register_init = TokenStream2::new(); + let mut code_build_chars = TokenStream2::new(); + let mut code_struct_init = TokenStream2::new(); let mut code_on_write = TokenStream2::new(); let mut code_event_enum = TokenStream2::new(); @@ -237,23 +239,27 @@ pub fn gatt_service(args: TokenStream, item: TokenStream) -> TokenStream { vis: syn::Visibility::Inherited, }); - code_register_chars.extend(quote_spanned!(ch.span=> - let #char_name = register_char( - #ble::gatt_server::Characteristic { - uuid: #uuid, - can_read: #read, - can_write: #write, - can_write_without_response: #write_without_response, - can_notify: #notify, - can_indicate: #indicate, - max_len: #ty_as_val::MAX_SIZE as _, - vlen: #ty_as_val::MAX_SIZE != #ty_as_val::MIN_SIZE, - }, - &[123], - )?; + code_build_chars.extend(quote_spanned!(ch.span=> + let #char_name = { + let val = [123u8; #ty_as_val::MIN_SIZE]; + let mut attr = #ble::gatt_server::characteristic::Attribute::new(&val); + if #ty_as_val::MAX_SIZE != #ty_as_val::MIN_SIZE { + attr = attr.variable_len(#ty_as_val::MAX_SIZE as u16); + } + let props = #ble::gatt_server::characteristic::Properties { + read: #read, + write: #write, + write_without_response: #write_without_response, + notify: #notify, + indicate: #indicate, + ..Default::default() + }; + let metadata = #ble::gatt_server::characteristic::Metadata::new(props); + service_builder.add_characteristic(#uuid, attr, metadata)?.build() + }; )); - code_register_init.extend(quote_spanned!(ch.span=> + code_struct_init.extend(quote_spanned!(ch.span=> #value_handle: #char_name.value_handle, )); @@ -280,7 +286,7 @@ pub fn gatt_service(args: TokenStream, item: TokenStream) -> TokenStream { colon_token: Default::default(), vis: syn::Visibility::Inherited, }); - code_register_init.extend(quote_spanned!(ch.span=> + code_struct_init.extend(quote_spanned!(ch.span=> #cccd_handle: #char_name.cccd_handle, )); } @@ -385,27 +391,25 @@ pub fn gatt_service(args: TokenStream, item: TokenStream) -> TokenStream { #struc impl #struct_name { - #code_impl - } - - impl #ble::gatt_server::Service for #struct_name { - type Event = #event_enum_name; + #struct_vis fn new(sd: &mut ::nrf_softdevice::Softdevice) -> Result + { + let mut service_builder = #ble::gatt_server::builder::ServiceBuilder::new(sd, #uuid)?; - fn uuid() -> #ble::Uuid { - #uuid - } + #code_build_chars - fn register(service_handle: u16, mut register_char: F) -> Result - where - F: FnMut(#ble::gatt_server::Characteristic, &[u8]) -> Result<#ble::gatt_server::CharacteristicHandles, #ble::gatt_server::RegisterError>, - { - #code_register_chars + let _ = service_builder.build(); Ok(Self { - #code_register_init + #code_struct_init }) } + #code_impl + } + + impl #ble::gatt_server::Service for #struct_name { + type Event = #event_enum_name; + fn on_write(&self, handle: u16, data: &[u8]) -> Option { #code_on_write None diff --git a/nrf-softdevice/src/ble/gatt_server.rs b/nrf-softdevice/src/ble/gatt_server.rs index 494b540..ad12702 100644 --- a/nrf-softdevice/src/ble/gatt_server.rs +++ b/nrf-softdevice/src/ble/gatt_server.rs @@ -3,8 +3,6 @@ //! Typically the peripheral device is the GATT server, but it is not necessary. //! In a connection any device can be server and client, and even both can be both at the same time. -use core::mem; - use crate::ble::*; use crate::raw; use crate::util::{get_flexarray, get_union_field, Portal}; @@ -64,19 +62,12 @@ impl DescriptorHandle { pub trait Server: Sized { type Event; - fn register(sd: &Softdevice) -> Result; fn on_write(&self, handle: u16, data: &[u8]) -> Option; } pub trait Service: Sized { type Event; - fn uuid() -> Uuid; - - fn register(service_handle: u16, register_char: F) -> Result - where - F: FnMut(Characteristic, &[u8]) -> Result; - fn on_write(&self, handle: u16, data: &[u8]) -> Option; } @@ -92,83 +83,6 @@ impl From for RegisterError { } } -pub fn register(sd: &Softdevice) -> Result { - S::register(sd) -} - -pub fn register_service(_sd: &Softdevice) -> Result { - let uuid = S::uuid(); - let mut service_handle: u16 = 0; - let ret = unsafe { - raw::sd_ble_gatts_service_add( - raw::BLE_GATTS_SRVC_TYPE_PRIMARY as u8, - uuid.as_raw_ptr(), - &mut service_handle as _, - ) - }; - RawError::convert(ret)?; - - S::register(service_handle, |char, initial_value| { - let mut cccd_attr_md: raw::ble_gatts_attr_md_t = unsafe { mem::zeroed() }; - cccd_attr_md.read_perm = raw::ble_gap_conn_sec_mode_t { - _bitfield_1: raw::ble_gap_conn_sec_mode_t::new_bitfield_1(1, 1), - }; - cccd_attr_md.write_perm = raw::ble_gap_conn_sec_mode_t { - _bitfield_1: raw::ble_gap_conn_sec_mode_t::new_bitfield_1(1, 1), - }; - cccd_attr_md.set_vloc(raw::BLE_GATTS_VLOC_STACK as u8); - - let mut attr_md: raw::ble_gatts_attr_md_t = unsafe { mem::zeroed() }; - attr_md.read_perm = raw::ble_gap_conn_sec_mode_t { - _bitfield_1: raw::ble_gap_conn_sec_mode_t::new_bitfield_1(1, 1), - }; - attr_md.write_perm = raw::ble_gap_conn_sec_mode_t { - _bitfield_1: raw::ble_gap_conn_sec_mode_t::new_bitfield_1(1, 1), - }; - attr_md.set_vloc(raw::BLE_GATTS_VLOC_STACK as u8); - if char.vlen { - attr_md.set_vlen(1); - } - - let mut attr: raw::ble_gatts_attr_t = unsafe { mem::zeroed() }; - attr.p_uuid = unsafe { char.uuid.as_raw_ptr() }; - attr.p_attr_md = &attr_md as _; - attr.max_len = char.max_len as _; - - attr.p_value = initial_value.as_ptr() as *mut u8; - attr.init_len = initial_value.len() as _; - - let mut char_md: raw::ble_gatts_char_md_t = unsafe { mem::zeroed() }; - char_md.char_props.set_read(char.can_read as u8); - char_md.char_props.set_write(char.can_write as u8); - char_md - .char_props - .set_write_wo_resp(char.can_write_without_response as u8); - char_md.char_props.set_notify(char.can_notify as u8); - char_md.char_props.set_indicate(char.can_indicate as u8); - char_md.p_cccd_md = &mut cccd_attr_md; - - let mut handles: raw::ble_gatts_char_handles_t = unsafe { mem::zeroed() }; - - let ret = unsafe { - raw::sd_ble_gatts_characteristic_add( - service_handle, - &mut char_md as _, - &mut attr as _, - &mut handles as _, - ) - }; - RawError::convert(ret)?; - - Ok(CharacteristicHandles { - value_handle: handles.value_handle, - user_desc_handle: handles.user_desc_handle, - cccd_handle: handles.cccd_handle, - sccd_handle: handles.sccd_handle, - }) - }) -} - #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum RunError { -- cgit v1.2.3