From f42537497b207a4766a187e237d9d44564453f1b Mon Sep 17 00:00:00 2001 From: Univa <41708691+Univa@users.noreply.github.com> Date: Wed, 4 Oct 2023 11:26:25 -0400 Subject: [PATCH 1/6] Add client support for receiving notifications --- nrf-softdevice-macro/src/lib.rs | 19 ++++++++++- nrf-softdevice/src/ble/gatt_client.rs | 48 +++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/nrf-softdevice-macro/src/lib.rs b/nrf-softdevice-macro/src/lib.rs index abcf9907..0b4b4628 100644 --- a/nrf-softdevice-macro/src/lib.rs +++ b/nrf-softdevice-macro/src/lib.rs @@ -517,6 +517,7 @@ pub fn gatt_client(args: TokenStream, item: TokenStream) -> TokenStream { let mut code_disc_char = TokenStream2::new(); let mut code_disc_done = TokenStream2::new(); let mut code_event_enum = TokenStream2::new(); + let mut code_on_notify = TokenStream2::new(); let ble = quote!(::nrf_softdevice::ble); @@ -647,6 +648,15 @@ pub fn gatt_client(args: TokenStream, item: TokenStream) -> TokenStream { code_event_enum.extend(quote_spanned!(ch.span=> #case_notification(#ty), )); + code_on_notify.extend(quote_spanned!(ch.span=> + if handle == self.#value_handle { + if data.len() < #ty_as_val::MIN_SIZE { + return None; + } else { + return Some(#event_enum_name::#case_notification(#ty_as_val::from_gatt(data))); + } + } + )); } } @@ -662,7 +672,14 @@ pub fn gatt_client(args: TokenStream, item: TokenStream) -> TokenStream { } impl #ble::gatt_client::Client for #struct_name { - //type Event = #event_enum_name; + type Event = #event_enum_name; + + fn on_notify(&self, _conn: &::nrf_softdevice::ble::Connection, handle: u16, data: &[u8]) -> Option { + use #ble::gatt_client::Client; + + #code_on_notify + None + } fn uuid() -> #ble::Uuid { #uuid diff --git a/nrf-softdevice/src/ble/gatt_client.rs b/nrf-softdevice/src/ble/gatt_client.rs index d854a957..162ebb45 100644 --- a/nrf-softdevice/src/ble/gatt_client.rs +++ b/nrf-softdevice/src/ble/gatt_client.rs @@ -23,6 +23,10 @@ pub struct Descriptor { /// Trait for implementing GATT clients. pub trait Client { + type Event; + + fn on_notify(&self, conn: &Connection, handle: u16, data: &[u8]) -> Option; + /// Get the UUID of the GATT service. This is used by [`discover`] to search for the /// service in the GATT server. fn uuid() -> Uuid; @@ -579,3 +583,47 @@ static PORTALS: [Portal<*const raw::ble_evt_t>; CONNS_MAX] = [PORTAL_NEW; CONNS_ pub(crate) fn portal(conn_handle: u16) -> &'static Portal<*const raw::ble_evt_t> { &PORTALS[conn_handle as usize] } + +pub async fn run<'a, F, C>(conn: &Connection, client: &C, mut f: F) -> DisconnectedError +where + F: FnMut(C::Event), + C: Client, +{ + let handle = match conn.with_state(|state| state.check_connected()) { + Ok(handle) => handle, + Err(e) => return e, + }; + + portal(handle) + .wait_many(|ble_evt| unsafe { + let ble_evt = &*ble_evt; + if u32::from(ble_evt.header.evt_id) == raw::BLE_GAP_EVTS_BLE_GAP_EVT_DISCONNECTED { + return Some(DisconnectedError); + } + + // We have a GATTC event + let gattc_evt = get_union_field(ble_evt, &ble_evt.evt.gattc_evt); + let conn = unwrap!(Connection::from_handle(gattc_evt.conn_handle)); + let evt = match ble_evt.header.evt_id as u32 { + raw::BLE_GATTC_EVTS_BLE_GATTC_EVT_HVX => { + let params = get_union_field(ble_evt, &gattc_evt.params.hvx); + let v = get_flexarray(ble_evt, ¶ms.data, params.len as usize); + trace!( + "GATT_HVX write handle={:?} type={:?} data={:?}", + params.handle, + params.type_, + v + ); + client.on_notify(&conn, params.handle, v) + } + _ => None, + }; + + if let Some(evt) = evt { + f(evt); + } + + None + }) + .await +} From 4c4529889f0b17669576a3c332e79d9ef71e1d5e Mon Sep 17 00:00:00 2001 From: Univa <41708691+Univa@users.noreply.github.com> Date: Wed, 4 Oct 2023 16:38:21 -0400 Subject: [PATCH 2/6] Rename on_notify to on_hvx --- nrf-softdevice-macro/src/lib.rs | 8 +++--- nrf-softdevice/src/ble/gatt_client.rs | 37 +++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/nrf-softdevice-macro/src/lib.rs b/nrf-softdevice-macro/src/lib.rs index 0b4b4628..3034228a 100644 --- a/nrf-softdevice-macro/src/lib.rs +++ b/nrf-softdevice-macro/src/lib.rs @@ -517,7 +517,7 @@ pub fn gatt_client(args: TokenStream, item: TokenStream) -> TokenStream { let mut code_disc_char = TokenStream2::new(); let mut code_disc_done = TokenStream2::new(); let mut code_event_enum = TokenStream2::new(); - let mut code_on_notify = TokenStream2::new(); + let mut code_on_hvx = TokenStream2::new(); let ble = quote!(::nrf_softdevice::ble); @@ -648,7 +648,7 @@ pub fn gatt_client(args: TokenStream, item: TokenStream) -> TokenStream { code_event_enum.extend(quote_spanned!(ch.span=> #case_notification(#ty), )); - code_on_notify.extend(quote_spanned!(ch.span=> + code_on_hvx.extend(quote_spanned!(ch.span=> if handle == self.#value_handle { if data.len() < #ty_as_val::MIN_SIZE { return None; @@ -674,10 +674,10 @@ pub fn gatt_client(args: TokenStream, item: TokenStream) -> TokenStream { impl #ble::gatt_client::Client for #struct_name { type Event = #event_enum_name; - fn on_notify(&self, _conn: &::nrf_softdevice::ble::Connection, handle: u16, data: &[u8]) -> Option { + fn on_hvx(&self, _conn: &::nrf_softdevice::ble::Connection, type_: ::nrf_softdevice::ble::gatt_client::HvxType, handle: u16, data: &[u8]) -> Option { use #ble::gatt_client::Client; - #code_on_notify + #code_on_hvx None } diff --git a/nrf-softdevice/src/ble/gatt_client.rs b/nrf-softdevice/src/ble/gatt_client.rs index 162ebb45..c80e8202 100644 --- a/nrf-softdevice/src/ble/gatt_client.rs +++ b/nrf-softdevice/src/ble/gatt_client.rs @@ -21,11 +21,37 @@ pub struct Descriptor { pub handle: u16, } +#[repr(u8)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum HvxType { + Invalid = 0, + Notification, + Indication, +} + +pub struct InvalidHvxTypeError; + +impl TryFrom for HvxType { + type Error = InvalidHvxTypeError; + + fn try_from(value: u8) -> Result { + match u32::from(value) { + raw::BLE_GATT_HVX_INVALID => Ok(HvxType::Invalid), + raw::BLE_GATT_HVX_NOTIFICATION => Ok(HvxType::Notification), + raw::BLE_GATT_HVX_INDICATION => Ok(HvxType::Indication), + _ => Err(InvalidHvxTypeError), + } + } +} + /// Trait for implementing GATT clients. pub trait Client { type Event; - fn on_notify(&self, conn: &Connection, handle: u16, data: &[u8]) -> Option; + /// Handles notification and indication events from the GATT server. + fn on_hvx(&self, conn: &Connection, type_: HvxType, handle: u16, data: &[u8]) -> Option; /// Get the UUID of the GATT service. This is used by [`discover`] to search for the /// service in the GATT server. @@ -614,7 +640,14 @@ where params.type_, v ); - client.on_notify(&conn, params.handle, v) + + match params.type_.try_into() { + Ok(type_) => client.on_hvx(&conn, type_, params.handle, v), + Err(_) => { + error!("gatt_client invalid hvx type: {}", params.type_); + None + } + } } _ => None, }; From 791f2a1a81743ce9896a8769c9ed45bfc8158005 Mon Sep 17 00:00:00 2001 From: Univa <41708691+Univa@users.noreply.github.com> Date: Mon, 9 Oct 2023 11:11:53 -0400 Subject: [PATCH 3/6] Update ble_bas_central example --- examples/src/bin/ble_bas_central.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/examples/src/bin/ble_bas_central.rs b/examples/src/bin/ble_bas_central.rs index b8475c4b..a2b0f600 100644 --- a/examples/src/bin/ble_bas_central.rs +++ b/examples/src/bin/ble_bas_central.rs @@ -82,4 +82,17 @@ async fn main(spawner: Spawner) { // Read to check it's changed let val = unwrap!(client.battery_level_read().await); info!("read battery level: {}", val); + + // Enable battery level notifications from the peripheral + gatt_client::write(&conn, client.battery_level_cccd_handle, &[0x01, 0x00]) + .await + .unwrap(); + + // Receive notifications + gatt_client::run(&conn, &client, |event| match event { + BatteryServiceClientEvent::BatteryLevelNotification(val) => { + info!("battery level notification: {}", val); + } + }) + .await; } From 1157bc65cbb35425ded9dacc766f9042d31309cb Mon Sep 17 00:00:00 2001 From: Univa <41708691+Univa@users.noreply.github.com> Date: Mon, 9 Oct 2023 12:01:54 -0400 Subject: [PATCH 4/6] Add CCCD write method to client --- nrf-softdevice-macro/src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nrf-softdevice-macro/src/lib.rs b/nrf-softdevice-macro/src/lib.rs index 3034228a..0bfde83f 100644 --- a/nrf-softdevice-macro/src/lib.rs +++ b/nrf-softdevice-macro/src/lib.rs @@ -657,6 +657,14 @@ pub fn gatt_client(args: TokenStream, item: TokenStream) -> TokenStream { } } )); + + if !indicate { + code_impl.extend(quote_spanned!(ch.span=> + #fn_vis async fn #cccd_write_fn(&self, notifications: bool) -> Result<(), #ble::gatt_client::WriteError> { + #ble::gatt_client::write(&self.conn, self.#cccd_handle, &[if notifications { 0x01 } else { 0x00 }, 0x00]).await + } + )); + } } } From 75c7fdacd2c9ddb99930a2906276014f3da8af04 Mon Sep 17 00:00:00 2001 From: Univa <41708691+Univa@users.noreply.github.com> Date: Mon, 9 Oct 2023 12:05:43 -0400 Subject: [PATCH 5/6] Update ble_bas_central example to use the CCCD write method --- examples/src/bin/ble_bas_central.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/src/bin/ble_bas_central.rs b/examples/src/bin/ble_bas_central.rs index a2b0f600..1e8f0752 100644 --- a/examples/src/bin/ble_bas_central.rs +++ b/examples/src/bin/ble_bas_central.rs @@ -84,9 +84,7 @@ async fn main(spawner: Spawner) { info!("read battery level: {}", val); // Enable battery level notifications from the peripheral - gatt_client::write(&conn, client.battery_level_cccd_handle, &[0x01, 0x00]) - .await - .unwrap(); + client.battery_level_cccd_write(true).await.unwrap(); // Receive notifications gatt_client::run(&conn, &client, |event| match event { From 7d424f3a11a0a7ac2fb7308e5d2ad41af780d3d1 Mon Sep 17 00:00:00 2001 From: Univa <41708691+Univa@users.noreply.github.com> Date: Mon, 9 Oct 2023 12:10:38 -0400 Subject: [PATCH 6/6] Add support for receiving indications --- nrf-softdevice-macro/src/lib.rs | 35 ++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/nrf-softdevice-macro/src/lib.rs b/nrf-softdevice-macro/src/lib.rs index 0bfde83f..717d119a 100644 --- a/nrf-softdevice-macro/src/lib.rs +++ b/nrf-softdevice-macro/src/lib.rs @@ -538,6 +538,7 @@ pub fn gatt_client(args: TokenStream, item: TokenStream) -> TokenStream { let write_fn = format_ident!("{}_write", ch.name); let write_wor_fn = format_ident!("{}_write_without_response", ch.name); let write_try_wor_fn = format_ident!("{}_try_write_without_response", ch.name); + let cccd_write_fn = format_ident!("{}_cccd_write", ch.name); let fn_vis = ch.vis.clone(); let uuid = ch.args.uuid; @@ -649,7 +650,7 @@ pub fn gatt_client(args: TokenStream, item: TokenStream) -> TokenStream { #case_notification(#ty), )); code_on_hvx.extend(quote_spanned!(ch.span=> - if handle == self.#value_handle { + if handle == self.#value_handle && type_ == ::nrf_softdevice::ble::gatt_client::HvxType::Notification { if data.len() < #ty_as_val::MIN_SIZE { return None; } else { @@ -666,6 +667,38 @@ pub fn gatt_client(args: TokenStream, item: TokenStream) -> TokenStream { )); } } + + if indicate { + let case_indication = format_ident!("{}Indication", name_pascal); + code_event_enum.extend(quote_spanned!(ch.span=> + #case_indication(#ty), + )); + code_on_hvx.extend(quote_spanned!(ch.span=> + if handle == self.#value_handle && type_ == ::nrf_softdevice::ble::gatt_client::HvxType::Indication { + if data.len() < #ty_as_val::MIN_SIZE { + return None; + } else { + return Some(#event_enum_name::#case_indication(#ty_as_val::from_gatt(data))); + } + } + )); + + if !notify { + code_impl.extend(quote_spanned!(ch.span=> + #fn_vis async fn #cccd_write_fn(&self, indications: bool) -> Result<(), #ble::gatt_client::WriteError> { + #ble::gatt_client::write(&self.conn, self.#cccd_handle, &[if indications { 0x02 } else { 0x00 }, 0x00]).await + } + )); + } + } + + if indicate && notify { + code_impl.extend(quote_spanned!(ch.span=> + #fn_vis async fn #cccd_write_fn(&self, indications: bool, notifications: bool) -> Result<(), #ble::gatt_client::WriteError> { + #ble::gatt_client::write(&self.conn, self.#cccd_handle, &[if indications { 0x02 } else { 0x00 } | if notifications { 0x01 } else { 0x00 }, 0x00]).await + } + )); + } } let uuid = args.uuid;