From f359fef461fb5f743233be5b2a388b611d2a237f Mon Sep 17 00:00:00 2001 From: Erik Gilling Date: Fri, 8 Mar 2024 19:55:55 -0800 Subject: [PATCH] Add ability to act on notification enables/disables. Being able to take action when a characteristic's notifications are enabled are disabled is useful when the data source needs some action to be taken to start or stop the data source like enabling interrupts or powering on a sensor. --- bleps-macros/src/lib.rs | 32 ++++++++++++-- bleps/src/attribute.rs | 78 +++++++++++++++++++++++++++++++++-- bleps/src/attribute_server.rs | 50 ++++++++++++---------- 3 files changed, 133 insertions(+), 27 deletions(-) diff --git a/bleps-macros/src/lib.rs b/bleps-macros/src/lib.rs index 8ab8703..10163af 100644 --- a/bleps-macros/src/lib.rs +++ b/bleps-macros/src/lib.rs @@ -153,6 +153,14 @@ pub fn gatt(input: TokenStream) -> TokenStream { return quote!{ compile_error!("Unexpected"); }.into(); } } + "notify_cb" => { + if let Expr::Path(p) = field.expr { + let name = path_to_string(p.path); + charact.notify_cb = Some(name); + } else { + return quote!{ compile_error!("Unexpected"); }.into(); + } + } "name" => { if let Expr::Lit(value) = field.expr { if let Lit::Str(s) = value.lit { @@ -344,7 +352,14 @@ pub fn gatt(input: TokenStream) -> TokenStream { quote!(()) }; - quote!(let mut #gen_attr_att_data_ident = (#rfunction, #wfunction);) + let nfunction = if let Some(name) = &characteristic.notify_cb { + let fname = format_ident!("{}", name); + quote!(&mut #fname) + } else { + quote!(()) + }; + + quote!(let mut #gen_attr_att_data_ident = (#rfunction, #wfunction, #nfunction);) } else if let Some(name) = &characteristic.value { let vname = format_ident!("{}", name); quote!(let mut #gen_attr_att_data_ident = #vname;) @@ -391,10 +406,17 @@ pub fn gatt(input: TokenStream) -> TokenStream { let rfunction = format_ident!("_attr_read{}", current_handle); let wfunction = format_ident!("_attr_write{}", current_handle); decls.push( - quote!(let mut #char_ccd_data_attr = (&mut #rfunction, &mut #wfunction);), + quote!(let mut #char_ccd_data_attr = (&mut #rfunction, &mut #wfunction, ());), ); let backing_data = format_ident!("_attr_data{}", current_handle); + let notify_cb_call = if let Some(cb) = &characteristic.notify_cb { + let cb = format_ident!("{}", cb); + quote!(#cb(unsafe{#backing_data[0] & 0x1 == 0x1})) + } else { + quote!() + }; + pre.push(quote!( #[allow(non_upper_case_globals)] static mut #backing_data: [u8; 2] = [0u8; 2]; @@ -424,6 +446,7 @@ pub fn gatt(input: TokenStream) -> TokenStream { } } } + #notify_cb_call; }; )); @@ -497,7 +520,9 @@ pub fn gatt(input: TokenStream) -> TokenStream { quote!(()) }; - decls.push(quote!(let mut #char_desc_data_ident = (#rfunction, #wfunction);)); + decls.push( + quote!(let mut #char_desc_data_ident = (#rfunction, #wfunction, ());), + ); } else if let Some(name) = &descriptor.value { let vname = format_ident!("{}", name); decls.push(quote!(let mut #char_desc_data_ident = #vname;)); @@ -572,6 +597,7 @@ struct Characteristic { write: Option, description: Option, notify: bool, + notify_cb: Option, name: Option, descriptors: Vec, } diff --git a/bleps/src/attribute.rs b/bleps/src/attribute.rs index 5f0649e..1beabde 100644 --- a/bleps/src/attribute.rs +++ b/bleps/src/attribute.rs @@ -21,6 +21,10 @@ pub trait AttData { fn write(&mut self, _offset: usize, _data: &[u8]) -> Result<(), AttErrorCode> { Ok(()) } + + fn enable_notification(&mut self, _enabled: bool) -> Result<(), AttErrorCode> { + Ok(()) + } } impl<'a, const N: usize> AttData for &'a [u8; N] { @@ -193,7 +197,7 @@ impl IntoResult for Result { } } -impl AttData for (R, ()) +impl AttData for (R, (), ()) where T: IntoResult, R: FnMut(usize, &mut [u8]) -> T, @@ -207,7 +211,7 @@ where } } -impl AttData for ((), W) +impl AttData for ((), W, ()) where U: IntoResult<()>, W: FnMut(usize, &[u8]) -> U, @@ -221,7 +225,7 @@ where } } -impl AttData for (R, W) +impl AttData for (R, W, ()) where T: IntoResult, U: IntoResult<()>, @@ -245,6 +249,74 @@ where } } +impl AttData for (R, (), N) +where + T: IntoResult, + U: IntoResult<()>, + R: FnMut(usize, &mut [u8]) -> T, + N: FnMut(bool) -> U, +{ + fn readable(&self) -> bool { + true + } + + fn read(&mut self, offset: usize, data: &mut [u8]) -> Result { + self.0(offset, data).into_result() + } + + fn enable_notification(&mut self, enabled: bool) -> Result<(), AttErrorCode> { + self.2(enabled).into_result() + } +} + +impl AttData for (R, W, N) +where + T: IntoResult, + U: IntoResult<()>, + R: FnMut(usize, &mut [u8]) -> T, + W: FnMut(usize, &[u8]) -> U, + N: FnMut(bool) -> U, +{ + fn readable(&self) -> bool { + true + } + + fn read(&mut self, offset: usize, data: &mut [u8]) -> Result { + self.0(offset, data).into_result() + } + + fn writable(&self) -> bool { + true + } + + fn write(&mut self, offset: usize, data: &[u8]) -> Result<(), AttErrorCode> { + self.1(offset, data).into_result() + } + + fn enable_notification(&mut self, enabled: bool) -> Result<(), AttErrorCode> { + self.2(enabled).into_result() + } +} + +impl AttData for ((), W, N) +where + U: IntoResult<()>, + W: FnMut(usize, &[u8]) -> U, + N: FnMut(bool) -> U, +{ + fn writable(&self) -> bool { + true + } + + fn write(&mut self, offset: usize, data: &[u8]) -> Result<(), AttErrorCode> { + self.1(offset, data).into_result() + } + + fn enable_notification(&mut self, enabled: bool) -> Result<(), AttErrorCode> { + self.2(enabled).into_result() + } +} + pub const ATT_READABLE: u8 = 0x02; pub const ATT_WRITEABLE: u8 = 0x08; diff --git a/bleps/src/attribute_server.rs b/bleps/src/attribute_server.rs index ccfc097..e540771 100644 --- a/bleps/src/attribute_server.rs +++ b/bleps/src/attribute_server.rs @@ -396,31 +396,39 @@ bleps_dedup::dedup! { self.write_att(src_handle, response).await; } - async fn handle_write_cmd(&mut self, _src_handle: u16, handle: u16, data: Data) { - for att in self.attributes.iter_mut() { - if att.handle == handle { - if att.data.writable() { - // Write commands can't respond with an error. - let err = att.data.write(0, data.as_slice()); - if let Err(e) = err { - log::debug!("write error: {e:?}"); - } - } - break; - } + fn handle_write(&mut self, handle: u16, data: Data) -> Result<(), AttErrorCode> { + let Some((index, att)) = self.attributes.iter_mut().enumerate().find(|(_, att)| att.handle == handle) else { + return Err(AttErrorCode::InvalidHandle); + }; + if !att.data.writable() { + return Err(AttErrorCode::WriteNotPermitted); + } + + let err = att.data.write(0, data.as_slice()); + if let Err(e) = err { + log::debug!("write error: {e:?}"); + return Err(e); + } + + // If this is a Client Characteristic Configuration descriptor, notify the parent of a change + // otherwise return immediatly. + if att.uuid != Uuid::Uuid16(0x2902) { + return Ok(()); } + + // Here we make the same assumption made in async_atribute_server that the CCCD directly follows + // the charactaristic attribute. + let parrent_att = &mut self.attributes[index-1]; + parrent_att.data.enable_notification(data.as_slice()[0] & 0x1 == 0x1) + } + + async fn handle_write_cmd(&mut self, _src_handle: u16, handle: u16, data: Data) { + // Write commands can't respond with an error. + let _ = self.handle_write(handle, data); } async fn handle_write_req(&mut self, src_handle: u16, handle: u16, data: Data) { - let mut err = Err(AttErrorCode::AttributeNotFound); - for att in self.attributes.iter_mut() { - if att.handle == handle { - if att.data.writable() { - err = att.data.write(0, data.as_slice()); - } - break; - } - } + let err = self.handle_write(handle, data); let response = match err { Ok(()) => Data::new_att_write_response(),