diff options
author | alexmoon <alex.r.moon@gmail.com> | 2022-07-13 07:59:32 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-13 07:59:32 -0400 |
commit | 29fce52109fcd12cda7767b546e43270b3217264 (patch) | |
tree | b3691429eef48000076d0c4c28ff769ab7497ea8 | |
parent | 96dbdc838ac2d2a0997eebeda77cead2154d5d22 (diff) | |
parent | ea7a3908df6816225641da342de2466d91ff6f2b (diff) | |
download | nrf-softdevice-29fce52109fcd12cda7767b546e43270b3217264.zip |
Merge pull request #107 from alexmoon/gatt-service-builder
GATT service builder
-rw-r--r-- | examples/src/bin/ble_advertise.rs | 2 | ||||
-rw-r--r-- | examples/src/bin/ble_bas_central.rs | 2 | ||||
-rw-r--r-- | examples/src/bin/ble_bas_peripheral.rs | 9 | ||||
-rw-r--r-- | examples/src/bin/ble_dis_bas_peripheral_builder.rs | 290 | ||||
-rw-r--r-- | examples/src/bin/ble_l2cap_central.rs | 2 | ||||
-rw-r--r-- | examples/src/bin/ble_l2cap_peripheral.rs | 2 | ||||
-rw-r--r-- | examples/src/bin/ble_peripheral_onoff.rs | 14 | ||||
-rw-r--r-- | examples/src/bin/ble_scan.rs | 2 | ||||
-rw-r--r-- | examples/src/bin/flash.rs | 2 | ||||
-rw-r--r-- | nrf-softdevice-macro/src/lib.rs | 84 | ||||
-rw-r--r-- | nrf-softdevice/src/ble/gatt_server.rs | 119 | ||||
-rw-r--r-- | nrf-softdevice/src/ble/gatt_server/builder.rs | 208 | ||||
-rw-r--r-- | nrf-softdevice/src/ble/gatt_server/characteristic.rs | 264 | ||||
-rw-r--r-- | nrf-softdevice/src/ble/types.rs | 49 | ||||
-rw-r--r-- | nrf-softdevice/src/softdevice.rs | 5 |
15 files changed, 910 insertions, 144 deletions
diff --git a/examples/src/bin/ble_advertise.rs b/examples/src/bin/ble_advertise.rs index 23a20df..ea7824c 100644 --- a/examples/src/bin/ble_advertise.rs +++ b/examples/src/bin/ble_advertise.rs @@ -89,7 +89,7 @@ fn main() -> ! { let sd = Softdevice::enable(&config); let executor = EXECUTOR.put(Executor::new()); - executor.run(|spawner| { + executor.run(move |spawner| { unwrap!(spawner.spawn(softdevice_task(sd))); unwrap!(spawner.spawn(bluetooth_task(sd))); }); diff --git a/examples/src/bin/ble_bas_central.rs b/examples/src/bin/ble_bas_central.rs index 823d09b..fb6bd09 100644 --- a/examples/src/bin/ble_bas_central.rs +++ b/examples/src/bin/ble_bas_central.rs @@ -98,7 +98,7 @@ fn main() -> ! { let sd = Softdevice::enable(&config); let executor = EXECUTOR.put(Executor::new()); - executor.run(|spawner| { + executor.run(move |spawner| { unwrap!(spawner.spawn(softdevice_task(sd))); unwrap!(spawner.spawn(ble_central_task(sd))); }); diff --git a/examples/src/bin/ble_bas_peripheral.rs b/examples/src/bin/ble_bas_peripheral.rs index e3e6233..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, @@ -147,8 +145,9 @@ fn main() -> ! { let sd = Softdevice::enable(&config); let executor = EXECUTOR.put(Executor::new()); - executor.run(|spawner| { + 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<Executor> = 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<Self, RegisterError> { + 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<Option<CharacteristicHandles>, 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<CharacteristicHandles, RegisterError> { + // 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::<PnPID>(), + ) + }; + + 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<Self, RegisterError> { + 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<u8, gatt_server::GetValueError> { + 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<Self, RegisterError> { + 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::Event> { + 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_l2cap_central.rs b/examples/src/bin/ble_l2cap_central.rs index 2fb1f87..53951e3 100644 --- a/examples/src/bin/ble_l2cap_central.rs +++ b/examples/src/bin/ble_l2cap_central.rs @@ -170,7 +170,7 @@ fn main() -> ! { let sd = Softdevice::enable(&config); let executor = EXECUTOR.put(Executor::new()); - executor.run(|spawner| { + executor.run(move |spawner| { unwrap!(spawner.spawn(softdevice_task(sd))); unwrap!(spawner.spawn(ble_central_task(sd))); }); diff --git a/examples/src/bin/ble_l2cap_peripheral.rs b/examples/src/bin/ble_l2cap_peripheral.rs index eccd555..23ef03e 100644 --- a/examples/src/bin/ble_l2cap_peripheral.rs +++ b/examples/src/bin/ble_l2cap_peripheral.rs @@ -147,7 +147,7 @@ fn main() -> ! { unwrap!(RawError::convert(unsafe { raw::sd_clock_hfclk_request() })); let executor = EXECUTOR.put(Executor::new()); - executor.run(|spawner| { + executor.run(move |spawner| { unwrap!(spawner.spawn(softdevice_task(sd))); unwrap!(spawner.spawn(bluetooth_task(sd))); }); diff --git a/examples/src/bin/ble_peripheral_onoff.rs b/examples/src/bin/ble_peripheral_onoff.rs index 9fdc8c0..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"); @@ -170,8 +168,14 @@ fn main() -> ! { let sd = Softdevice::enable(&config); let executor = EXECUTOR.put(Executor::new()); - executor.run(|spawner| { + 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/examples/src/bin/ble_scan.rs b/examples/src/bin/ble_scan.rs index e9a9376..7c8e7f6 100644 --- a/examples/src/bin/ble_scan.rs +++ b/examples/src/bin/ble_scan.rs @@ -109,7 +109,7 @@ fn main() -> ! { let sd = Softdevice::enable(&config); let executor = EXECUTOR.put(Executor::new()); - executor.run(|spawner| { + executor.run(move |spawner| { unwrap!(spawner.spawn(softdevice_task(sd))); unwrap!(spawner.spawn(ble_task(sd))); }); diff --git a/examples/src/bin/flash.rs b/examples/src/bin/flash.rs index 2017e86..27ea01b 100644 --- a/examples/src/bin/flash.rs +++ b/examples/src/bin/flash.rs @@ -44,7 +44,7 @@ fn main() -> ! { let sd = Softdevice::enable(&Default::default()); let executor = EXECUTOR.put(Executor::new()); - executor.run(|spawner| { + executor.run(move |spawner| { unwrap!(spawner.spawn(softdevice_task(sd))); unwrap!(spawner.spawn(flash_task(sd))); }); 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<Self, #ble::gatt_server::RegisterError> + { + 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<Self, #ble::gatt_server::RegisterError> - { - Ok(Self { - #code_register_init - }) - } - fn on_write(&self, handle: u16, data: &[u8]) -> Option<Self::Event> { 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<Self, #ble::gatt_server::RegisterError> + { + let mut service_builder = #ble::gatt_server::builder::ServiceBuilder::new(sd, #uuid)?; - fn uuid() -> #ble::Uuid { - #uuid - } + #code_build_chars - fn register<F>(service_handle: u16, mut register_char: F) -> Result<Self, #ble::gatt_server::RegisterError> - 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<Self::Event> { #code_on_write None diff --git a/nrf-softdevice/src/ble/gatt_server.rs b/nrf-softdevice/src/ble/gatt_server.rs index 99d22dd..ad12702 100644 --- a/nrf-softdevice/src/ble/gatt_server.rs +++ b/nrf-softdevice/src/ble/gatt_server.rs @@ -3,14 +3,15 @@ //! 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}; use crate::RawError; use crate::Softdevice; +pub mod builder; +pub mod characteristic; + pub struct Characteristic { pub uuid: Uuid, pub can_read: bool, @@ -29,21 +30,44 @@ pub struct CharacteristicHandles { pub sccd_handle: u16, } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ServiceHandle(u16); + +impl ServiceHandle { + pub fn handle(&self) -> u16 { + self.0 + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct IncludedServiceHandle(u16); + +impl IncludedServiceHandle { + pub fn handle(&self) -> u16 { + self.0 + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DescriptorHandle(u16); + +impl DescriptorHandle { + pub fn handle(&self) -> u16 { + self.0 + } +} + pub trait Server: Sized { type Event; - fn register(sd: &Softdevice) -> Result<Self, RegisterError>; fn on_write(&self, handle: u16, data: &[u8]) -> Option<Self::Event>; } pub trait Service: Sized { type Event; - fn uuid() -> Uuid; - - fn register<F>(service_handle: u16, register_char: F) -> Result<Self, RegisterError> - where - F: FnMut(Characteristic, &[u8]) -> Result<CharacteristicHandles, RegisterError>; - fn on_write(&self, handle: u16, data: &[u8]) -> Option<Self::Event>; } @@ -59,83 +83,6 @@ impl From<RawError> for RegisterError { } } -pub fn register<S: Server>(sd: &Softdevice) -> Result<S, RegisterError> { - S::register(sd) -} - -pub fn register_service<S: Service>(_sd: &Softdevice) -> Result<S, RegisterError> { - 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 { diff --git a/nrf-softdevice/src/ble/gatt_server/builder.rs b/nrf-softdevice/src/ble/gatt_server/builder.rs new file mode 100644 index 0000000..c0bff21 --- /dev/null +++ b/nrf-softdevice/src/ble/gatt_server/builder.rs @@ -0,0 +1,208 @@ +#![allow(dead_code)] + +use core::{convert::TryInto, marker::PhantomData, mem, ptr::null}; + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +use crate::{ble::Uuid, raw, RawError, Softdevice}; + +use super::{ + characteristic::{self, AttributeMetadata}, + CharacteristicHandles, DescriptorHandle, IncludedServiceHandle, RegisterError, ServiceHandle, +}; + +pub struct ServiceBuilder<'a> { + handle: u16, + sd: PhantomData<&'a mut Softdevice>, +} + +pub struct CharacteristicBuilder<'a> { + handles: CharacteristicHandles, + sb: PhantomData<&'a ServiceBuilder<'a>>, +} + +impl<'a> ServiceBuilder<'a> { + pub fn new(_sd: &'a mut Softdevice, uuid: Uuid) -> Result<Self, RegisterError> { + 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)?; + + Ok(ServiceBuilder { + handle: service_handle, + sd: PhantomData, + }) + } + + pub fn add_characteristic<T: AsRef<[u8]>>( + &mut self, + uuid: Uuid, + attr: characteristic::Attribute<T>, + md: characteristic::Metadata, + ) -> Result<CharacteristicBuilder<'_>, RegisterError> { + let value = attr.value.as_ref(); + let attr_md = attr.metadata.into_raw(); + self.add_characteristic_inner(uuid, value, attr.max_len, &attr_md, md) + } + + #[cfg(feature = "alloc")] + pub fn add_characteristic_app( + &mut self, + uuid: Uuid, + attr: characteristic::Attribute<Box<[u8]>>, + md: characteristic::Metadata, + ) -> Result<CharacteristicBuilder<'_>, RegisterError> { + let value = Box::leak(attr.value); + let attr_md = attr.metadata.into_raw_user(); + self.add_characteristic_inner(uuid, value, attr.max_len, &attr_md, md) + } + + fn add_characteristic_inner( + &mut self, + uuid: Uuid, + value: &[u8], + max_len: u16, + attr_md: &raw::ble_gatts_attr_md_t, + char_md: characteristic::Metadata, + ) -> Result<CharacteristicBuilder<'_>, RegisterError> { + assert!(value.len() <= usize::from(max_len)); + assert!(char_md + .user_description + .map_or(true, |x| x.value.len() <= usize::from(x.max_len))); + + let (char_props, char_ext_props) = char_md.properties.into_raw(); + let user_desc_md = char_md + .user_description + .and_then(|x| x.metadata.map(AttributeMetadata::into_raw)); + let cccd_md = char_md.cccd.map(AttributeMetadata::into_raw); + let sccd_md = char_md.sccd.map(AttributeMetadata::into_raw); + + let mut char_md = raw::ble_gatts_char_md_t { + char_props, + char_ext_props, + p_char_user_desc: char_md + .user_description + .map_or(null(), |x| x.value.as_ptr()), + char_user_desc_max_size: char_md.user_description.map_or(0, |x| x.max_len), + char_user_desc_size: char_md.user_description.map_or(0, |x| x.value.len() as u16), + p_char_pf: null(), + p_user_desc_md: user_desc_md.as_ref().map_or(null(), |x| x as _), + p_cccd_md: cccd_md.as_ref().map_or(null(), |x| x as _), + p_sccd_md: sccd_md.as_ref().map_or(null(), |x| x as _), + }; + + let mut attr = raw::ble_gatts_attr_t { + p_uuid: unsafe { uuid.as_raw_ptr() }, + p_attr_md: attr_md as _, + init_len: unwrap!(value.len().try_into()), + init_offs: 0, + max_len, + p_value: value.as_ptr() as *mut _, + }; + + let mut handles: raw::ble_gatts_char_handles_t = unsafe { mem::zeroed() }; + + let ret = unsafe { + raw::sd_ble_gatts_characteristic_add( + self.handle, + &mut char_md as _, + &mut attr as _, + &mut handles as _, + ) + }; + RawError::convert(ret)?; + + let handles = CharacteristicHandles { + value_handle: handles.value_handle, + user_desc_handle: handles.user_desc_handle, + cccd_handle: handles.cccd_handle, + sccd_handle: handles.sccd_handle, + }; + + Ok(CharacteristicBuilder { + handles, + sb: PhantomData, + }) + } + + pub fn include_service( + &mut self, + service: &ServiceHandle, + ) -> Result<IncludedServiceHandle, RegisterError> { + let mut handle = 0; + let ret = + unsafe { raw::sd_ble_gatts_include_add(self.handle, service.0, &mut handle as _) }; + RawError::convert(ret)?; + + Ok(IncludedServiceHandle(handle)) + } + + pub fn build(self) -> ServiceHandle { + ServiceHandle(self.handle) + } +} + +impl<'a> CharacteristicBuilder<'a> { + pub fn add_descriptor<T: AsRef<[u8]>>( + &mut self, + uuid: Uuid, + attr: characteristic::Attribute<T>, + ) -> Result<DescriptorHandle, RegisterError> { + let value = attr.value.as_ref(); + let attr_md = attr.metadata.into_raw(); + self.add_descriptor_inner(uuid, value, attr.max_len, &attr_md) + } + + #[cfg(feature = "alloc")] + pub fn add_descriptor_app( + &mut self, + uuid: Uuid, + attr: characteristic::Attribute<Box<[u8]>>, + ) -> Result<DescriptorHandle, RegisterError> { + let value = Box::leak(attr.value); + let attr_md = attr.metadata.into_raw_user(); + self.add_descriptor_inner(uuid, value, attr.max_len, &attr_md) + } + + fn add_descriptor_inner( + &mut self, + uuid: Uuid, + value: &[u8], + max_len: u16, + attr_md: &raw::ble_gatts_attr_md_t, + ) -> Result<DescriptorHandle, RegisterError> { + let attr = raw::ble_gatts_attr_t { + p_uuid: unsafe { uuid.as_raw_ptr() }, + p_attr_md: attr_md as _, + init_len: unwrap!(value.len().try_into()), + init_offs: 0, + max_len, + p_value: value.as_ptr() as *mut _, + }; + + let mut handle = 0; + let ret = unsafe { + raw::sd_ble_gatts_descriptor_add( + self.handles.value_handle, + &attr as _, + &mut handle as _, + ) + }; + RawError::convert(ret)?; + + Ok(DescriptorHandle(handle)) + } + + pub fn build(self) -> CharacteristicHandles { + self.handles + } +} diff --git a/nrf-softdevice/src/ble/gatt_server/characteristic.rs b/nrf-softdevice/src/ble/gatt_server/characteristic.rs new file mode 100644 index 0000000..6bc33c2 --- /dev/null +++ b/nrf-softdevice/src/ble/gatt_server/characteristic.rs @@ -0,0 +1,264 @@ +use core::convert::TryInto; + +use crate::ble::SecurityMode; +use crate::raw; + +// Missing: +// - Characteristic presentation format + +#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AttributeMetadata { + pub read: SecurityMode, + pub write: SecurityMode, + pub variable_len: bool, + pub deferred_read: bool, + pub deferred_write: bool, +} + +impl AttributeMetadata { + pub fn with_security(security: SecurityMode) -> Self { + AttributeMetadata { + read: security, + write: security, + ..Default::default() + } + } + + pub fn read_security(mut self, security: SecurityMode) -> Self { + self.read = security; + self + } + + pub fn write_security(mut self, security: SecurityMode) -> Self { + self.write = security; + self + } + + pub(crate) fn into_raw(self) -> raw::ble_gatts_attr_md_t { + self.into_raw_inner(raw::BLE_GATTS_VLOC_STACK as u8) + } + + #[cfg(feature = "alloc")] + pub(crate) fn into_raw_user(self) -> raw::ble_gatts_attr_md_t { + self.into_raw_inner(raw::BLE_GATTS_VLOC_USER as u8) + } + + fn into_raw_inner(self, vloc: u8) -> raw::ble_gatts_attr_md_t { + raw::ble_gatts_attr_md_t { + read_perm: self.read.into_raw(), + write_perm: self.write.into_raw(), + _bitfield_1: raw::ble_gatts_attr_md_t::new_bitfield_1( + self.variable_len.into(), + vloc, + self.deferred_read.into(), + self.deferred_write.into(), + ), + } + } +} + +#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Attribute<T: AsRef<[u8]>> { + pub metadata: AttributeMetadata, + pub value: T, + pub max_len: u16, +} + +impl<T: AsRef<[u8]>> Attribute<T> { + pub fn new(value: T) -> Self { + let max_len = unwrap!(value.as_ref().len().try_into()); + Attribute { + max_len, + value, + metadata: Default::default(), + } + } + + pub fn security(mut self, security: SecurityMode) -> Self { + self.metadata.read = security; + self.metadata.write = security; + self + } + + pub fn read_security(mut self, security: SecurityMode) -> Self { + self.metadata.read = security; + self + } + + pub fn write_security(mut self, security: SecurityMode) -> Self { + self.metadata.write = security; + self + } + + pub fn variable_len(mut self, max_len: u16) -> Self { + self.max_len = max_len; + self.metadata.variable_len = true; + self + } + + pub fn deferred(mut self) -> Self { + self.metadata.deferred_read = true; + self.metadata.deferred_write = true; + self + } + + pub fn deferred_read(mut self) -> Self { + self.metadata.deferred_read = true; + self + } + + pub fn deferred_write(mut self) -> Self { + self.metadata.deferred_write = true; + self + } +} + +#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct UserDescription { + pub metadata: Option<AttributeMetadata>, + pub value: &'static [u8], + pub max_len: u16, +} + +#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Properties { + pub broadcast: bool, + pub read: bool, + pub write_without_response: bool, + pub write: bool, + pub notify: bool, + pub indicate: bool, + pub signed_write: bool, + pub queued_write: bool, + pub write_user_description: bool, +} + +impl Properties { + pub fn new() -> Self { + Self::default() + } + + pub fn broadcast(mut self) -> Self { + self.broadcast = true; + self + } + + pub fn read(mut self) -> Self { + self.read = true; + self + } + + pub fn write_without_response(mut self) -> Self { + self.write_without_response = true; + self + } + + pub fn write(mut self) -> Self { + self.write = true; + self + } + + pub fn notify(mut self) -> Self { + self.notify = true; + self + } + + pub fn indicate(mut self) -> Self { + self.indicate = true; + self + } + + pub fn signed_write(mut self) -> Self { + self.signed_write = true; + self + } + + pub fn queued_write(mut self) -> Self { + self.queued_write = true; + self + } + + pub fn write_user_description(mut self) -> Self { + self.write_user_description = true; + self + } + + pub(crate) fn into_raw(self) -> (raw::ble_gatt_char_props_t, raw::ble_gatt_char_ext_props_t) { + ( + raw::ble_gatt_char_props_t { + _bitfield_1: raw::ble_gatt_char_props_t::new_bitfield_1( + self.broadcast.into(), + self.read.into(), + self.write_without_response.into(), + self.write.into(), + self.notify.into(), + self.indicate.into(), + self.signed_write.into(), + ), + }, + raw::ble_gatt_char_ext_props_t { + _bitfield_1: raw::ble_gatt_char_ext_props_t::new_bitfield_1( + self.queued_write.into(), + self.write_user_description.into(), + ), + }, + ) + } +} + +#[derive(Default, Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Metadata { + pub properties: Properties, + pub user_description: Option<UserDescription>, + pub cccd: Option<AttributeMetadata>, + pub sccd: Option<AttributeMetadata>, +} + +impl Metadata { + pub fn new(properties: Properties) -> Self { + let cccd = if properties.indicate || properties.notify { + Some(AttributeMetadata::default()) + } else { + None + }; + + let sccd = if properties.broadcast { + Some(AttributeMetadata::default()) + } else { + None + }; + + Metadata { + properties, + cccd, + sccd, + ..Default::default() + } + } + + pub fn with_security(properties: Properties, write_security: SecurityMode) -> Self { + let cccd = if properties.indicate || properties.notify { + Some(AttributeMetadata::default().write_security(write_security)) + } else { + None + }; + + let sccd = if properties.broadcast { + Some(AttributeMetadata::default().write_security(write_security)) + } else { + None + }; + + Metadata { + properties, + cccd, + sccd, + ..Default::default() + } + } +} diff --git a/nrf-softdevice/src/ble/types.rs b/nrf-softdevice/src/ble/types.rs index 7763ebc..6011808 100644 --- a/nrf-softdevice/src/ble/types.rs +++ b/nrf-softdevice/src/ble/types.rs @@ -84,6 +84,55 @@ impl Role { } } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SecurityMode { + NoAccess, + Open, + JustWorks, + Mitm, + LescMitm, + Signed, + SignedMitm, +} + +impl Default for SecurityMode { + fn default() -> Self { + Self::Open + } +} + +impl SecurityMode { + pub fn try_from_raw(raw: raw::ble_gap_conn_sec_mode_t) -> Option<Self> { + match (raw.sm(), raw.lv()) { + (0, 0) => Some(SecurityMode::NoAccess), + (1, 1) => Some(SecurityMode::Open), + (1, 2) => Some(SecurityMode::JustWorks), + (1, 3) => Some(SecurityMode::Mitm), + (1, 4) => Some(SecurityMode::LescMitm), + (2, 1) => Some(SecurityMode::Signed), + (2, 2) => Some(SecurityMode::SignedMitm), + _ => None, + } + } + + pub fn into_raw(self) -> raw::ble_gap_conn_sec_mode_t { + let (sm, lv) = match self { + SecurityMode::NoAccess => (0, 0), + SecurityMode::Open => (1, 1), + SecurityMode::JustWorks => (1, 2), + SecurityMode::Mitm => (1, 3), + SecurityMode::LescMitm => (1, 4), + SecurityMode::Signed => (2, 1), + SecurityMode::SignedMitm => (2, 2), + }; + + raw::ble_gap_conn_sec_mode_t { + _bitfield_1: raw::ble_gap_conn_sec_mode_t::new_bitfield_1(sm, lv), + } + } +} + #[repr(u8)] #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] diff --git a/nrf-softdevice/src/softdevice.rs b/nrf-softdevice/src/softdevice.rs index d697cbe..282ba04 100644 --- a/nrf-softdevice/src/softdevice.rs +++ b/nrf-softdevice/src/softdevice.rs @@ -97,7 +97,7 @@ impl Softdevice { /// - Panics if the requested configuration requires more memory than reserved for the softdevice. In that case, you can give more memory to the softdevice by editing the RAM start address in `memory.x`. The required start address is logged prior to panic. /// - Panics if the requested configuration has too high memory requirements for the softdevice. The softdevice supports a maximum dynamic memory size of 64kb. /// - Panics if called multiple times. Must be called at most once. - pub fn enable(config: &Config) -> &'static Softdevice { + pub fn enable(config: &Config) -> &'static mut Softdevice { if ENABLED .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) .is_err() @@ -310,7 +310,8 @@ impl Softdevice { /// Return an instance to the softdevice without checking whether /// it is enabled or not. This is only safe if the softdevice is enabled - /// (a call to [`enable`] has returned without error) + /// (a call to [`enable`] has returned without error) and no `&mut` references + /// to the softdevice are active pub unsafe fn steal() -> &'static Softdevice { SOFTDEVICE.steal() } |