Skip to content

Commit

Permalink
Merge pull request embassy-rs#176 from dysonltd/automatically-calcula…
Browse files Browse the repository at this point in the history
…te-attribute-table-size

Automatically calculate attribute table size
  • Loading branch information
lulf authored Nov 25, 2024
2 parents 224f371 + 661f21b commit 66681ba
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 26 deletions.
11 changes: 4 additions & 7 deletions examples/apps/src/ble_bas_peripheral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ const MAX_ATTRIBUTES: usize = 10;

type Resources<C> = HostResources<C, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX, L2CAP_MTU>;

const ATTRIBUTE_DATA_SIZE: usize = 10;

// GATT Server definition
#[gatt_server(attribute_data_size = ATTRIBUTE_DATA_SIZE)]
#[gatt_server]
struct Server {
battery_service: BatteryService,
}
Expand Down Expand Up @@ -116,20 +114,19 @@ async fn advertise_task<C: Controller>(
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);
Expand Down
45 changes: 27 additions & 18 deletions host-macros/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ 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;

#[derive(Default)]
pub(crate) struct ServerArgs {
mutex_type: Option<syn::Type>,
attribute_data_size: Option<Expr>,
attribute_table_size: Option<Expr>,
mtu: Option<Expr>,
}

Expand All @@ -33,15 +31,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 must 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(())
}
Expand All @@ -65,12 +63,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 {
Expand All @@ -81,6 +73,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();
Expand All @@ -98,12 +91,28 @@ 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_size = if let Some(value) = self.arguments.attribute_table_size {
value
} else {
parse_quote!(GAP_SERVICE_ATTRIBUTE_COUNT #code_attribute_summation)
};

quote! {
const _ATTRIBUTE_TABLE_SIZE: usize = #attribute_table_size;
// This pattern causes the assertion to happen at compile time
const _: () = {
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>
{
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
}

Expand All @@ -112,7 +121,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

Expand All @@ -127,7 +136,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<Self, &'static str> {
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)?;

Expand All @@ -145,7 +154,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<Self, &'static str> {
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)?;

Expand All @@ -168,7 +177,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
Expand Down
13 changes: 13 additions & 0 deletions host-macros/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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(),
Expand All @@ -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
Expand All @@ -104,6 +107,8 @@ impl ServiceBuilder {

#[allow(unused)]
impl #struct_name {
const ATTRIBUTE_COUNT: usize = #attribute_count;

#visibility fn new<M, const MAX_ATTRIBUTES: usize>(table: &mut AttributeTable<'_, M, MAX_ATTRIBUTES>) -> Self
where
M: embassy_sync::blocking_mutex::raw::RawMutex,
Expand Down Expand Up @@ -152,6 +157,7 @@ impl ServiceBuilder {
#write_callback

// TODO: Descriptors
// NOTE: Descriptors are attributes too - will need to increment self.attribute_count

builder.build()
};
Expand Down Expand Up @@ -193,6 +199,13 @@ impl ServiceBuilder {
mutability: syn::FieldMutability::None,
});

// 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.construct_characteristic_static(ch);
}

Expand Down
9 changes: 9 additions & 0 deletions host/src/gap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ 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
/// 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.
//!
Expand Down
2 changes: 1 addition & 1 deletion host/tests/gatt_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down

0 comments on commit 66681ba

Please sign in to comment.