summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoralexmoon <alex.r.moon@gmail.com>2022-07-11 18:02:35 -0400
committeralexmoon <alex.r.moon@gmail.com>2022-07-12 14:34:16 -0400
commitea7a3908df6816225641da342de2466d91ff6f2b (patch)
treeb3691429eef48000076d0c4c28ff769ab7497ea8
parent5f651f57f6d5d04e82a7733fe6b7b2b4fbbff15c (diff)
downloadnrf-softdevice-ea7a3908df6816225641da342de2466d91ff6f2b.zip
Modify macros to use the builder interface and add an builder example
-rw-r--r--examples/src/bin/ble_bas_peripheral.rs7
-rw-r--r--examples/src/bin/ble_dis_bas_peripheral_builder.rs290
-rw-r--r--examples/src/bin/ble_peripheral_onoff.rs12
-rw-r--r--nrf-softdevice-macro/src/lib.rs84
-rw-r--r--nrf-softdevice/src/ble/gatt_server.rs86
5 files changed, 345 insertions, 134 deletions
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<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_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<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 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<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>;
}
@@ -92,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 {