From 125cbaa596c622866366147d1918885da3bc062e 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 | 25 ++++++++-- bleps-macros/tests/macro_test.rs | 27 +++++++++++ bleps/src/attribute.rs | 78 ++++++++++++++++++++++++++++++-- bleps/src/attribute_server.rs | 50 +++++++++++--------- bleps/tests/test_ble.rs | 4 +- 5 files changed, 155 insertions(+), 29 deletions(-) diff --git a/bleps-macros/src/lib.rs b/bleps-macros/src/lib.rs index 8ab8703..70df5de 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,11 @@ 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); + pre.push(quote!( #[allow(non_upper_case_globals)] static mut #backing_data: [u8; 2] = [0u8; 2]; @@ -497,7 +513,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 +590,7 @@ struct Characteristic { write: Option, description: Option, notify: bool, + notify_cb: Option, name: Option, descriptors: Vec, } diff --git a/bleps-macros/tests/macro_test.rs b/bleps-macros/tests/macro_test.rs index 4234bad..673286e 100644 --- a/bleps-macros/tests/macro_test.rs +++ b/bleps-macros/tests/macro_test.rs @@ -130,3 +130,30 @@ fn test5() { println!("{:x?}", gatt_attributes); } + +#[test] +fn test6() { + let mut my_read_function = |_offset: usize, data: &mut [u8]| { + data[..5].copy_from_slice(&b"Ciao!"[..]); + 5 + }; + let mut my_write_function = |_offset, data: &[u8]| { + println!("{:?}", data); + }; + let mut my_notify = |enabled: bool| { + println!("enabled = {enabled}"); + }; + + gatt!([service { + uuid: "9e7312e0-2354-11eb-9f10-fbc30a62cf38", + characteristics: [characteristic { + uuid: "9e7312e0-2354-11eb-9f10-fbc30a62cf38", + notify: true, + notify_cb: my_notify, + read: my_read_function, + write: my_write_function, + },], + },]); + + println!("{:x?}", gatt_attributes); +} 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(), diff --git a/bleps/tests/test_ble.rs b/bleps/tests/test_ble.rs index 30e8a87..4190fee 100644 --- a/bleps/tests/test_ble.rs +++ b/bleps/tests/test_ble.rs @@ -641,7 +641,7 @@ fn attribute_server_discover_two_services() { let mut char_att_data = &char_data; let char = Attribute::new(CHARACTERISTIC_UUID16, &mut char_att_data); - let mut custom_char_att_data = (&mut rf1, &mut wf1); + let mut custom_char_att_data = (&mut rf1, &mut wf1, ()); let custom_char_att_data_attr = Attribute::new( Uuid::Uuid128([ 0xC9, 0x15, 0x15, 0x96, 0x54, 0x56, 0x64, 0xB3, 0x38, 0x45, 0x26, 0x5D, 0xF1, 0x62, @@ -681,7 +681,7 @@ fn attribute_server_discover_two_services() { let mut char_att_data2 = &char_data2; let char2 = Attribute::new(CHARACTERISTIC_UUID16, &mut char_att_data2); - let mut custom_char_att_data2 = (&mut rf2, &mut wf2); + let mut custom_char_att_data2 = (&mut rf2, &mut wf2, ()); let custom_char_att_data_attr2 = Attribute::new( Uuid::Uuid128([ 0xC9, 0x15, 0x15, 0x96, 0x54, 0x56, 0x64, 0xB3, 0x38, 0x45, 0x26, 0x5D, 0xF1, 0x62,