From 4264cadb1ac06bd3c933dd8c2298d8c343bd1dee Mon Sep 17 00:00:00 2001 From: Pete Kubiak Date: Thu, 21 Nov 2024 17:34:22 +0000 Subject: [PATCH 1/9] Implement attribute counting for each service --- host-macros/src/service.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/host-macros/src/service.rs b/host-macros/src/service.rs index 7b27805..b6feee4 100644 --- a/host-macros/src/service.rs +++ b/host-macros/src/service.rs @@ -64,6 +64,7 @@ impl syn::parse::Parse for ServiceArgs { pub(crate) struct ServiceBuilder { properties: syn::ItemStruct, args: ServiceArgs, + attribute_count: usize, code_impl: TokenStream2, code_build_chars: TokenStream2, code_struct_init: TokenStream2, @@ -75,6 +76,7 @@ impl ServiceBuilder { Self { properties, args, + attribute_count: 1, // Service counts as an attribute code_struct_init: TokenStream2::new(), code_impl: TokenStream2::new(), code_fields: TokenStream2::new(), @@ -91,6 +93,7 @@ impl ServiceBuilder { let fields = self.code_fields; let code_build_chars = self.code_build_chars; let uuid = self.args.uuid; + let attribute_count = self.attribute_count; let read_callback = self .args .on_read @@ -104,6 +107,8 @@ impl ServiceBuilder { #[allow(unused)] impl #struct_name { + const ATTRIBUTE_COUNT: usize = #attribute_count; + #visibility fn new(table: &mut AttributeTable<'_, M, MAX_ATTRIBUTES>) -> Self where M: embassy_sync::blocking_mutex::raw::RawMutex, @@ -152,6 +157,7 @@ impl ServiceBuilder { #write_callback // TODO: Descriptors + // NOTE: Descriptors are attributes too - will need to increment self.attribute_count builder.build() }; @@ -194,6 +200,8 @@ impl ServiceBuilder { }); self.construct_characteristic_static(ch); + + self.attribute_count += 1; } // Processing common to all fields From 08bbd96a7aa02ae0747d423cdd9e0f3f43b042b5 Mon Sep 17 00:00:00 2001 From: Pete Kubiak Date: Thu, 21 Nov 2024 18:14:44 +0000 Subject: [PATCH 2/9] [WIP] Add summation of attribute count to server macro --- host-macros/src/server.rs | 30 +++++++++++++++++++----------- host/src/gap.rs | 3 +++ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/host-macros/src/server.rs b/host-macros/src/server.rs index 61785c3..b317731 100644 --- a/host-macros/src/server.rs +++ b/host-macros/src/server.rs @@ -4,6 +4,7 @@ //! It should contain one or more Gatt Services, which are decorated with the `#[gatt_service(uuid = "...")]` attribute. use darling::Error; +use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::{quote, quote_spanned}; use syn::{meta::ParseNestedMeta, parse_quote, spanned::Spanned, Expr, Result}; @@ -65,12 +66,6 @@ impl ServerBuilder { let mutex_type = self.arguments.mutex_type.unwrap_or(syn::Type::Verbatim(quote!( embassy_sync::blocking_mutex::raw::NoopRawMutex ))); - let attribute_data_size = if let Some(value) = self.arguments.attribute_data_size { - value - } else { - let tokens = quote!(#DEFAULT_ATTRIBUTE_DATA_SIZE); - parse_quote!(#tokens) - }; let mtu = if let Some(value) = self.arguments.mtu { value } else { @@ -81,6 +76,7 @@ impl ServerBuilder { let mut code_service_definition = TokenStream2::new(); let mut code_service_init = TokenStream2::new(); let mut code_server_populate = TokenStream2::new(); + let mut code_attribute_summation = TokenStream2::new(); for service in &self.properties.fields { let vis = &service.vis; let service_span = service.span(); @@ -98,12 +94,24 @@ impl ServerBuilder { code_server_populate.extend(quote_spanned! {service_span=> #service_name, }); + + code_attribute_summation.extend(quote_spanned! {service_span=> + + #service_type::ATTRIBUTE_COUNT + }) } + let attribute_table_summation = if let Some(value) = self.arguments.attribute_data_size { + value + } else { + parse_quote!(code_attribute_summation) + }; + quote! { + const ATTRIBUTE_TABLE_SIZE: usize = GAP_SERVICE_ATTRIBUTE_COUNT #attribute_table_summation; + #visibility struct #name<'reference, 'values, C: Controller> { - server: GattServer<'reference, 'values, C, #mutex_type, #attribute_data_size, #mtu>, + server: GattServer<'reference, 'values, C, #mutex_type, ATTRIBUTE_TABLE_SIZE, #mtu>, #code_service_definition } @@ -112,7 +120,7 @@ impl ServerBuilder { /// Create a new Gatt Server instance. /// /// Requires you to add your own GAP Service. Use `new_default(name)` or `new_with_config(name, gap_config)` if you want to add a GAP Service. - #visibility fn new(stack: Stack<'reference, C>, mut table: AttributeTable<'values, #mutex_type, #attribute_data_size>) -> Self { + #visibility fn new(stack: Stack<'reference, C>, mut table: AttributeTable<'values, #mutex_type, ATTRIBUTE_TABLE_SIZE>) -> Self { #code_service_init @@ -127,7 +135,7 @@ impl ServerBuilder { /// The maximum length which the name can be is 22 bytes (limited by the size of the advertising packet). /// If a name longer than this is passed, Err() is returned. #visibility fn new_default(stack: Stack<'reference, C>, name: &'values str) -> Result { - let mut table: AttributeTable<'_, #mutex_type, #attribute_data_size> = AttributeTable::new(); + let mut table: AttributeTable<'_, #mutex_type, ATTRIBUTE_TABLE_SIZE> = AttributeTable::new(); GapConfig::default(name).build(&mut table)?; @@ -145,7 +153,7 @@ impl ServerBuilder { /// The maximum length which the device name can be is 22 bytes (limited by the size of the advertising packet). /// If a name longer than this is passed, Err() is returned. #visibility fn new_with_config(stack: Stack<'reference, C>, gap: GapConfig<'values>) -> Result { - let mut table: AttributeTable<'_, #mutex_type, #attribute_data_size> = AttributeTable::new(); + let mut table: AttributeTable<'_, #mutex_type, ATTRIBUTE_TABLE_SIZE> = AttributeTable::new(); gap.build(&mut table)?; @@ -168,7 +176,7 @@ impl ServerBuilder { impl<'reference, 'values, C: Controller> core::ops::Deref for #name<'reference, 'values, C> { - type Target = GattServer<'reference, 'values, C, #mutex_type, #attribute_data_size, #mtu>; + type Target = GattServer<'reference, 'values, C, #mutex_type, ATTRIBUTE_TABLE_SIZE, #mtu>; fn deref(&self) -> &Self::Target { &self.server diff --git a/host/src/gap.rs b/host/src/gap.rs index 7a50460..9a35d25 100644 --- a/host/src/gap.rs +++ b/host/src/gap.rs @@ -24,6 +24,9 @@ const APPEARANCE_UUID: u16 = 0x2a01; /// Advertising packet is limited to 31 bytes. 9 of these are used by other GAP data, leaving 22 bytes for the Device Name characteristic const DEVICE_NAME_MAX_LENGTH: usize = 22; +/// The number of attributes added by the GAP and GATT services +pub const GAP_SERVICE_ATTRIBUTE_COUNT: usize = 4; + pub mod appearance { //! The representation of the external appearance of the device. //! From 06fd8010dda0b6d6db91ec9c18a6af70f2b3a45e Mon Sep 17 00:00:00 2001 From: Pete Kubiak Date: Fri, 22 Nov 2024 17:26:41 +0000 Subject: [PATCH 3/9] Remove unnecessary specification of attribute table size from example gatt_server --- examples/apps/src/ble_bas_peripheral.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/apps/src/ble_bas_peripheral.rs b/examples/apps/src/ble_bas_peripheral.rs index 7bf1b84..d8d1a26 100644 --- a/examples/apps/src/ble_bas_peripheral.rs +++ b/examples/apps/src/ble_bas_peripheral.rs @@ -18,10 +18,8 @@ const MAX_ATTRIBUTES: usize = 10; type Resources = HostResources; -const ATTRIBUTE_DATA_SIZE: usize = 10; - // GATT Server definition -#[gatt_server(attribute_data_size = ATTRIBUTE_DATA_SIZE)] +#[gatt_server] struct Server { battery_service: BatteryService, } From 0ddd0e851a0ce89c855613a9932ac10aca786c11 Mon Sep 17 00:00:00 2001 From: Pete Kubiak Date: Fri, 22 Nov 2024 17:33:24 +0000 Subject: [PATCH 4/9] Tidy up variable names --- host-macros/src/server.rs | 29 ++++++++++++++++------------- host/tests/gatt_derive.rs | 2 +- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/host-macros/src/server.rs b/host-macros/src/server.rs index b317731..41db642 100644 --- a/host-macros/src/server.rs +++ b/host-macros/src/server.rs @@ -17,7 +17,7 @@ const LEGACY_BLE_MTU: usize = 27; #[derive(Default)] pub(crate) struct ServerArgs { mutex_type: Option, - attribute_data_size: Option, + attribute_table_size: Option, mtu: Option, } @@ -34,15 +34,15 @@ impl ServerArgs { let buffer = meta.value().map_err(|_| Error::custom("mutex_type must be followed by `= [type]`. e.g. mutex_type = NoopRawMutex".to_string()))?; self.mutex_type = Some(buffer.parse()?); } - "attribute_data_size" => { - let buffer = meta.value().map_err(|_| Error::custom("attribute_data_size msut be followed by `= [size]`. e.g. attribute_data_size = 32".to_string()))?; - self.attribute_data_size = Some(buffer.parse()?); + "attribute_table_size" => { + let buffer = meta.value().map_err(|_| Error::custom("attribute_table_size msut be followed by `= [size]`. e.g. attribute_table_size = 32".to_string()))?; + self.attribute_table_size = Some(buffer.parse()?); } "mtu" => { let buffer = meta.value().map_err(|_| Error::custom("mtu must be followed by `= [size]`. e.g. mtu = 27".to_string()))?; self.mtu = Some(buffer.parse()?); } - other => return Err(meta.error(format!("Unsupported server property: '{other}'.\nSupported properties are: mutex_type, attribute_data_size, mtu"))), + other => return Err(meta.error(format!("Unsupported server property: '{other}'.\nSupported properties are: mutex_type, attribute_table_size, mtu"))), } Ok(()) } @@ -100,18 +100,21 @@ impl ServerBuilder { }) } - let attribute_table_summation = if let Some(value) = self.arguments.attribute_data_size { + let attribute_table_size = if let Some(value) = self.arguments.attribute_table_size { value } else { - parse_quote!(code_attribute_summation) + parse_quote!(GAP_SERVICE_ATTRIBUTE_COUNT #code_attribute_summation) }; quote! { - const ATTRIBUTE_TABLE_SIZE: usize = GAP_SERVICE_ATTRIBUTE_COUNT #attribute_table_summation; + const _ATTRIBUTE_TABLE_SIZE: usize = #attribute_table_size; + const _: () = { + assert!(_ATTRIBUTE_TABLE_SIZE >= GAP_SERVICE_ATTRIBUTE_COUNT #code_attribute_summation, "Specified attribute table size is insufficient. Please increase attribute_table_size"); + }; #visibility struct #name<'reference, 'values, C: Controller> { - server: GattServer<'reference, 'values, C, #mutex_type, ATTRIBUTE_TABLE_SIZE, #mtu>, + server: GattServer<'reference, 'values, C, #mutex_type, _ATTRIBUTE_TABLE_SIZE, #mtu>, #code_service_definition } @@ -120,7 +123,7 @@ impl ServerBuilder { /// Create a new Gatt Server instance. /// /// Requires you to add your own GAP Service. Use `new_default(name)` or `new_with_config(name, gap_config)` if you want to add a GAP Service. - #visibility fn new(stack: Stack<'reference, C>, mut table: AttributeTable<'values, #mutex_type, ATTRIBUTE_TABLE_SIZE>) -> Self { + #visibility fn new(stack: Stack<'reference, C>, mut table: AttributeTable<'values, #mutex_type, _ATTRIBUTE_TABLE_SIZE>) -> Self { #code_service_init @@ -135,7 +138,7 @@ impl ServerBuilder { /// The maximum length which the name can be is 22 bytes (limited by the size of the advertising packet). /// If a name longer than this is passed, Err() is returned. #visibility fn new_default(stack: Stack<'reference, C>, name: &'values str) -> Result { - let mut table: AttributeTable<'_, #mutex_type, ATTRIBUTE_TABLE_SIZE> = AttributeTable::new(); + let mut table: AttributeTable<'_, #mutex_type, _ATTRIBUTE_TABLE_SIZE> = AttributeTable::new(); GapConfig::default(name).build(&mut table)?; @@ -153,7 +156,7 @@ impl ServerBuilder { /// The maximum length which the device name can be is 22 bytes (limited by the size of the advertising packet). /// If a name longer than this is passed, Err() is returned. #visibility fn new_with_config(stack: Stack<'reference, C>, gap: GapConfig<'values>) -> Result { - let mut table: AttributeTable<'_, #mutex_type, ATTRIBUTE_TABLE_SIZE> = AttributeTable::new(); + let mut table: AttributeTable<'_, #mutex_type, _ATTRIBUTE_TABLE_SIZE> = AttributeTable::new(); gap.build(&mut table)?; @@ -176,7 +179,7 @@ impl ServerBuilder { impl<'reference, 'values, C: Controller> core::ops::Deref for #name<'reference, 'values, C> { - type Target = GattServer<'reference, 'values, C, #mutex_type, ATTRIBUTE_TABLE_SIZE, #mtu>; + type Target = GattServer<'reference, 'values, C, #mutex_type, _ATTRIBUTE_TABLE_SIZE, #mtu>; fn deref(&self) -> &Self::Target { &self.server diff --git a/host/tests/gatt_derive.rs b/host/tests/gatt_derive.rs index 515a5f8..24e4eb3 100644 --- a/host/tests/gatt_derive.rs +++ b/host/tests/gatt_derive.rs @@ -18,7 +18,7 @@ const VALUE_UUID: Uuid = Uuid::new_long([ 0x00, 0x00, 0x10, 0x01, 0xb0, 0xcd, 0x11, 0xec, 0x87, 0x1f, 0xd4, 0x5d, 0xdf, 0x13, 0x88, 0x40, ]); -#[gatt_server(mutex_type = NoopRawMutex, attribute_data_size = 10, mtu = 27)] +#[gatt_server(mutex_type = NoopRawMutex, attribute_table_size = 10, mtu = 27)] struct Server { service: CustomService, } From 76f1246be080aa5819b11e0deb727a778fb20c8d Mon Sep 17 00:00:00 2001 From: Pete Kubiak Date: Fri, 22 Nov 2024 17:34:17 +0000 Subject: [PATCH 5/9] Remove unused items --- host-macros/src/server.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/host-macros/src/server.rs b/host-macros/src/server.rs index 41db642..ac2e2dc 100644 --- a/host-macros/src/server.rs +++ b/host-macros/src/server.rs @@ -4,13 +4,10 @@ //! It should contain one or more Gatt Services, which are decorated with the `#[gatt_service(uuid = "...")]` attribute. use darling::Error; -use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::{quote, quote_spanned}; use syn::{meta::ParseNestedMeta, parse_quote, spanned::Spanned, Expr, Result}; -/// Default size for the memory block storing attribute data in bytes -const DEFAULT_ATTRIBUTE_DATA_SIZE: usize = 32; /// MTU for a legacy BLE packet const LEGACY_BLE_MTU: usize = 27; From 36fa7dd14dde4005b9d1d5f2f14b6aba2d5eb4da Mon Sep 17 00:00:00 2001 From: Pete Kubiak Date: Fri, 22 Nov 2024 17:35:24 +0000 Subject: [PATCH 6/9] Fix computation of required table size --- host-macros/src/service.rs | 9 +++++++-- host/src/gap.rs | 8 +++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/host-macros/src/service.rs b/host-macros/src/service.rs index b6feee4..d445a4f 100644 --- a/host-macros/src/service.rs +++ b/host-macros/src/service.rs @@ -199,9 +199,14 @@ impl ServiceBuilder { mutability: syn::FieldMutability::None, }); - self.construct_characteristic_static(ch); + // At least two attributes will be added to the attribute table for each characteristic: + // - The characteristic declaration + // - The characteristic's value declaration + // + // If the characteristic has either the notify or indicate property, a Client Characteristic Configuration Descriptor (CCCD) declaration will also be added. + self.attribute_count += if ch.args.notify || ch.args.indicate { 3 } else { 2 }; - self.attribute_count += 1; + self.construct_characteristic_static(ch); } // Processing common to all fields diff --git a/host/src/gap.rs b/host/src/gap.rs index 9a35d25..62560c3 100644 --- a/host/src/gap.rs +++ b/host/src/gap.rs @@ -25,7 +25,13 @@ const APPEARANCE_UUID: u16 = 0x2a01; const DEVICE_NAME_MAX_LENGTH: usize = 22; /// The number of attributes added by the GAP and GATT services -pub const GAP_SERVICE_ATTRIBUTE_COUNT: usize = 4; +/// GAP_SERVICE: 1 +/// ├── DEVICE_NAME: 2 +/// └── APPEARANCE: 2 +/// GATT_SERVICE: + 1 +/// --- +/// = 6 +pub const GAP_SERVICE_ATTRIBUTE_COUNT: usize = 6; pub mod appearance { //! The representation of the external appearance of the device. From 1882ffa854ead4b47c2d9f3c4c0804e19cbb1081 Mon Sep 17 00:00:00 2001 From: Pete Kubiak Date: Fri, 22 Nov 2024 17:36:24 +0000 Subject: [PATCH 7/9] Implement cargo fmt changes --- examples/apps/src/ble_bas_peripheral.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/apps/src/ble_bas_peripheral.rs b/examples/apps/src/ble_bas_peripheral.rs index d8d1a26..6320df7 100644 --- a/examples/apps/src/ble_bas_peripheral.rs +++ b/examples/apps/src/ble_bas_peripheral.rs @@ -114,20 +114,19 @@ async fn advertise_task( break; } ConnectionEvent::Gatt { event, .. } => match event { - GattEvent::Read { value_handle } => { + GattEvent::Read { value_handle } => { if value_handle == level.handle { let value = server.get(&level); info!("[gatt] Read Event to Level Characteristic: {:?}", value); } - }, + } GattEvent::Write { value_handle } => { if value_handle == level.handle { let value = server.get(&level); info!("[gatt] Write Event to Level Characteristic: {:?}", value); } - }, + } }, - }, Either::Second(_) => { tick = tick.wrapping_add(1); From 8275d909bc6e460452ff783c51366054b2dce2a0 Mon Sep 17 00:00:00 2001 From: Pete Kubiak Date: Fri, 22 Nov 2024 17:40:33 +0000 Subject: [PATCH 8/9] Specify core::assert for static assertion --- host-macros/src/server.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/host-macros/src/server.rs b/host-macros/src/server.rs index ac2e2dc..08bcf16 100644 --- a/host-macros/src/server.rs +++ b/host-macros/src/server.rs @@ -105,8 +105,9 @@ impl ServerBuilder { quote! { const _ATTRIBUTE_TABLE_SIZE: usize = #attribute_table_size; + // This pattern causes the assertion to happen at compile time const _: () = { - assert!(_ATTRIBUTE_TABLE_SIZE >= GAP_SERVICE_ATTRIBUTE_COUNT #code_attribute_summation, "Specified attribute table size is insufficient. Please increase attribute_table_size"); + core::assert!(_ATTRIBUTE_TABLE_SIZE >= GAP_SERVICE_ATTRIBUTE_COUNT #code_attribute_summation, "Specified attribute table size is insufficient. Please increase attribute_table_size or remove the argument entirely to allow automatic sizing of the attribute table."); }; #visibility struct #name<'reference, 'values, C: Controller> From 661f21b321c1554b30c64db42473d75807e0ac3c Mon Sep 17 00:00:00 2001 From: Pete Kubiak Date: Fri, 22 Nov 2024 18:55:37 +0000 Subject: [PATCH 9/9] Fix typo 'msut' --- host-macros/src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/host-macros/src/server.rs b/host-macros/src/server.rs index 08bcf16..0a1715b 100644 --- a/host-macros/src/server.rs +++ b/host-macros/src/server.rs @@ -32,7 +32,7 @@ impl ServerArgs { self.mutex_type = Some(buffer.parse()?); } "attribute_table_size" => { - let buffer = meta.value().map_err(|_| Error::custom("attribute_table_size msut be followed by `= [size]`. e.g. attribute_table_size = 32".to_string()))?; + let buffer = meta.value().map_err(|_| Error::custom("attribute_table_size must be followed by `= [size]`. e.g. attribute_table_size = 32".to_string()))?; self.attribute_table_size = Some(buffer.parse()?); } "mtu" => {