Skip to content

Commit

Permalink
Merge pull request #190 from dysonltd/add-descriptors-and-default-values
Browse files Browse the repository at this point in the history
Add descriptors and default values
  • Loading branch information
lulf authored Dec 19, 2024
2 parents 19b02ee + 9fd6f7e commit cdae68d
Show file tree
Hide file tree
Showing 7 changed files with 392 additions and 108 deletions.
5 changes: 4 additions & 1 deletion examples/apps/src/ble_bas_peripheral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ struct Server {
// Battery service
#[gatt_service(uuid = "180f")]
struct BatteryService {
#[characteristic(uuid = "2a19", read, write, notify, on_read = battery_level_on_read, on_write = battery_level_on_write)]
/// Battery Level
#[descriptor(uuid = "2b20", read, value = "Battery Level", on_read = battery_level_on_read)]
#[descriptor(uuid = "2b21", read, value = [0x12, 0x34])]
#[characteristic(uuid = "2a19", read, write, notify, on_read = battery_level_on_read, on_write = battery_level_on_write, value = 10)]
level: u8,
}

Expand Down
223 changes: 161 additions & 62 deletions host-macros/src/characteristic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
//!
//! This module contains the parsing and handling of the characteristic attribute.
//! The characteristic attribute is used to define a characteristic in a service.
//! A characteristic is a data value that can be read, written, or notified.
//! A characteristic is a data value that can be accessed from a connected client.
use crate::uuid::Uuid;
use darling::Error;
use darling::FromMeta;
use proc_macro2::Span;
use syn::meta::ParseNestedMeta;
use syn::parse::Result;
use syn::spanned::Spanned;
use syn::Field;
Expand Down Expand Up @@ -35,24 +36,9 @@ impl Characteristic {
}
}

/// Descriptor attribute arguments.
///
/// Descriptors are optional and can be used to add additional metadata to the characteristic.
#[derive(Debug, FromMeta)]
pub(crate) struct DescriptorArgs {
/// The UUID of the descriptor.
_uuid: Uuid,
/// The value of the descriptor.
#[darling(default)]
_value: Option<syn::Expr>,
}

/// Characteristic attribute arguments
#[derive(Debug, FromMeta)]
pub(crate) struct CharacteristicArgs {
/// The UUID of the characteristic.
pub uuid: Uuid,
/// If true, the characteristic can be read.
#[derive(Debug, FromMeta, Default)]
pub(crate) struct AccessArgs {
/// If true, the characteristic can be written.
#[darling(default)]
pub read: bool,
/// If true, the characteristic can be written.
Expand All @@ -73,71 +59,184 @@ pub(crate) struct CharacteristicArgs {
/// Optional callback to be triggered on a write event
#[darling(default)]
pub on_write: Option<Ident>,
/// The initial value of the characteristic.
/// This is optional and can be used to set the initial value of the characteristic.
}

/// Descriptor attribute arguments.
///
/// Descriptors are optional and can be used to add additional metadata to the characteristic.
#[derive(Debug, FromMeta)]
pub(crate) struct DescriptorArgs {
/// The UUID of the descriptor.
pub uuid: Uuid,
/// The initial value of the descriptor (&str).
/// This is optional and can be used to set the initial value of the descriptor.
#[darling(default)]
pub default_value: Option<syn::Expr>,
#[darling(default)]
/// Capacity for writing new descriptors (u8)
pub capacity: Option<syn::Expr>,
#[darling(default)]
pub access: AccessArgs,
}

/// Characteristic attribute arguments
#[derive(Debug, FromMeta)]
pub(crate) struct CharacteristicArgs {
/// The UUID of the characteristic.
pub uuid: Uuid,
/// Starting value for this characteristic.
#[darling(default)]
pub _default_value: Option<syn::Expr>,
// /// Descriptors for the characteristic.
// /// Descriptors are optional and can be used to add additional metadata to the characteristic.
pub default_value: Option<syn::Expr>,
/// Descriptors for the characteristic.
/// Descriptors are optional and can be used to add additional metadata to the characteristic.
/// Parsed in super::check_for_characteristic.
#[darling(default, multiple)]
pub _descriptors: Vec<DescriptorArgs>,
pub descriptors: Vec<DescriptorArgs>,
/// Any '///' comments on each field, parsed in super::check_for_characteristic.
#[darling(default)]
pub doc_string: String,
#[darling(default)]
pub access: AccessArgs,
}

/// Check if this bool type has been specified more than once.
fn check_multi<T>(arg: &mut Option<T>, name: &str, meta: &ParseNestedMeta<'_>, value: T) -> Result<()> {
if arg.is_none() {
*arg = Some(value);
Ok(())
} else {
Err(meta.error(format!("'{name}' should not be specified more than once")))
}
}

impl CharacteristicArgs {
/// Parse the arguments of a characteristic attribute
pub fn parse(attribute: &syn::Attribute) -> Result<Self> {
let mut uuid: Option<Uuid> = None;
let mut read = false;
let mut write = false;
let mut write_without_response = false;
let mut notify = false;
let mut indicate = false;
let mut on_read = None;
let mut on_write = None;
let mut _default_value: Option<syn::Expr> = None;
let descriptors: Vec<DescriptorArgs> = Vec::new();
let mut read: Option<bool> = None;
let mut write: Option<bool> = None;
let mut notify: Option<bool> = None;
let mut indicate: Option<bool> = None;
let mut on_read: Option<Ident> = None;
let mut on_write: Option<Ident> = None;
let mut default_value: Option<syn::Expr> = None;
let mut write_without_response: Option<bool> = None;
attribute.parse_nested_meta(|meta| {
match meta.path.get_ident().ok_or(Error::custom("no ident"))?.to_string().as_str() {
match meta.path.get_ident().ok_or(meta.error("no ident"))?.to_string().as_str() {
"uuid" => {
let value = meta
let parser = meta
.value()
.map_err(|_| Error::custom("uuid must be followed by '= [data]'. i.e. uuid = '0x2A37'".to_string()))?;
let uuid_string: LitStr = value.parse()?;
uuid = Some(Uuid::from_string(uuid_string.value().as_str())?);
.map_err(|_| meta.error("uuid must be followed by '= [data]'. i.e. uuid = \"2a37\""))?;
let uuid_string: LitStr = parser.parse()?;
let value = Uuid::from_string(uuid_string.value().as_str())?;
check_multi(&mut uuid, "uuid", &meta, value)?
},
"read" => read = true,
"write" => write = true,
"write_without_response" => write_without_response = true,
"notify" => notify = true,
"indicate" => indicate = true,
"on_read" => on_read = Some(meta.value()?.parse()?),
"on_write" => on_write = Some(meta.value()?.parse()?),
"read" => check_multi(&mut read, "read", &meta, true)?,
"write" => check_multi(&mut write, "write", &meta, true)?,
"notify" => check_multi(&mut notify, "notify", &meta, true)?,
"indicate" => check_multi(&mut indicate, "indicate", &meta, true)?,
"on_read" => check_multi(&mut on_read, "on_read", &meta, meta.value()?.parse()?)?,
"on_write" => check_multi(&mut on_write, "on_write", &meta, meta.value()?.parse()?)?,
"write_without_response" => check_multi(&mut write_without_response, "write_without_response", &meta, true)?,
"value" => {
return Err(Error::custom("Default value is currently unsupported").with_span(&meta.path.span()).into())
// let value = meta
// .value()
// .map_err(|_| Error::custom("value must be followed by '= [data]'. i.e. value = 'hello'".to_string()))?;
// default_value = Some(value.parse()?);
},
let value = meta
.value()
.map_err(|_| meta.error("'value' must be followed by '= [data]'. i.e. value = \"42\""))?;
check_multi(&mut default_value, "value", &meta, value.parse()?)?
}
"default_value" => return Err(meta.error("Use 'value' for default value")),
"descriptor" => return Err(meta.error("Descriptors are added as separate tags i.e. #[descriptor(uuid = \"1234\", value = 42, read, write, notify, indicate)]")),
other => return Err(
meta.error(
format!(
"Unsupported characteristic property: '{other}'.\nSupported properties are: uuid, read, write, write_without_response, notify, indicate, value"
"Unsupported characteristic property: '{other}'.\nSupported properties are:\nuuid, read, write, write_without_response, notify, indicate, value\non_read, on_write"
))),
};
Ok(())
})?;
Ok(Self {
uuid: uuid.ok_or(Error::custom("Characteristic must have a UUID"))?,
read,
write,
write_without_response,
notify,
indicate,
on_read,
on_write,
_default_value,
_descriptors: descriptors,
doc_string: String::new(),
descriptors: Vec::new(),
default_value,
access: AccessArgs {
write_without_response: write_without_response.unwrap_or_default(),
indicate: indicate.unwrap_or_default(),
notify: notify.unwrap_or_default(),
write: write.unwrap_or_default(),
read: read.unwrap_or_default(),
on_write,
on_read,
},
})
}
}

impl DescriptorArgs {
pub fn parse(attribute: &syn::Attribute) -> Result<Self> {
let mut uuid: Option<Uuid> = None;
let mut read: Option<bool> = None;
// let mut write: Option<bool> = None;
let mut on_read: Option<Ident> = None;
// let mut on_write: Option<Ident> = None;
// let mut capacity: Option<syn::Expr> = None;
let mut default_value: Option<syn::Expr> = None;
// let mut write_without_response: Option<bool> = None;
attribute.parse_nested_meta(|meta| {
match meta
.path
.get_ident()
.ok_or(meta.error("no ident"))?
.to_string()
.as_str()
{
"uuid" => {
let parser = meta
.value()
.map_err(|_| meta.error("uuid must be followed by '= [data]'. i.e. uuid = \"2a37\""))?;
let uuid_string: LitStr = parser.parse()?;
let value = Uuid::from_string(uuid_string.value().as_str())?;
check_multi(&mut uuid, "uuid", &meta, value)?
}
"read" => check_multi(&mut read, "read", &meta, true)?,
// "write" => check_multi(&mut write, "write", &meta, true)?,
"on_read" => check_multi(&mut on_read, "on_read", &meta, meta.value()?.parse()?)?,
// "on_write" => check_multi(&mut on_write, "on_write", &meta, meta.value()?.parse()?)?,
// "write_without_response" => check_multi(&mut write_without_response, "write_without_response", &meta, true)?,
"value" => {
let value = meta.value().map_err(|_| {
meta.error("'value' must be followed by '= [data]'. i.e. value = \"Hello World\"")
})?;
check_multi(&mut default_value, "value", &meta, value.parse()?)?
}
// "capacity" => {
// let value = meta.value().map_err(|_| meta.error("'capacity' must be followed by '= [data]'. i.e. value = 100"))?;
// check_multi(&mut capacity, "capacity", &meta, value.parse()?)?
// }
"default_value" => return Err(meta.error("use 'value' for default value")),
other => {
return Err(meta.error(format!(
// "Unsupported descriptor property: '{other}'.\nSupported properties are: uuid, read, write, write_without_response, value,\ncapacity, on_read, on_write"
"Unsupported descriptor property: '{other}'.\nSupported properties are: uuid, read, value, on_read"
)));
}
};
Ok(())
})?;

Ok(Self {
uuid: uuid.ok_or(Error::custom("Descriptor must have a UUID"))?,
default_value,
capacity: None,
access: AccessArgs {
indicate: false, // not possible for descriptor
notify: false, // not possible for descriptor
read: read.unwrap_or_default(),
write_without_response: false,
on_write: None,
write: false,
on_read,
},
})
}
}
Loading

0 comments on commit cdae68d

Please sign in to comment.