diff options
author | Dario Nieuwenhuis <dirbaio@dirbaio.net> | 2020-11-10 00:28:51 +0100 |
---|---|---|
committer | Dario Nieuwenhuis <dirbaio@dirbaio.net> | 2020-11-10 00:28:51 +0100 |
commit | 48ee44be070c7634fb2a9aaaf25a6cf33b73422a (patch) | |
tree | c9f66868d9e083d86daaf6b6e608e1084608bfcb /nrf-softdevice-macro | |
parent | 140bb3946d39aaacc6c0503557ab81c5e0ded85f (diff) | |
download | nrf-softdevice-48ee44be070c7634fb2a9aaaf25a6cf33b73422a.zip |
gatt_client: add macro. Fixes #8
Diffstat (limited to 'nrf-softdevice-macro')
-rw-r--r-- | nrf-softdevice-macro/src/lib.rs | 246 |
1 files changed, 243 insertions, 3 deletions
diff --git a/nrf-softdevice-macro/src/lib.rs b/nrf-softdevice-macro/src/lib.rs index 46c36c7..7e2a2e5 100644 --- a/nrf-softdevice-macro/src/lib.rs +++ b/nrf-softdevice-macro/src/lib.rs @@ -173,7 +173,7 @@ pub fn gatt_server(args: TokenStream, item: TokenStream) -> TokenStream { } )); - if ch.args.indicate || ch.args.notify { + if indicate || notify { fields.push(syn::Field { ident: Some(cccd_handle.clone()), ty: syn::Type::Verbatim(quote!(u16).into()), @@ -186,7 +186,7 @@ pub fn gatt_server(args: TokenStream, item: TokenStream) -> TokenStream { )); } - if ch.args.write { + if write { let case_write = format_ident!("{}Write", name_pascal); code_event_enum.extend(quote_spanned!(ch.span=> #case_write(#ty), @@ -197,7 +197,7 @@ pub fn gatt_server(args: TokenStream, item: TokenStream) -> TokenStream { } )); } - if ch.args.notify { + if notify { let case_enabled = format_ident!("{}NotificationsEnabled", name_pascal); let case_disabled = format_ident!("{}NotificationsDisabled", name_pascal); @@ -268,3 +268,243 @@ pub fn gatt_server(args: TokenStream, item: TokenStream) -> TokenStream { }; result.into() } + +#[proc_macro_attribute] +pub fn gatt_client(args: TokenStream, item: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(args as syn::AttributeArgs); + let mut struc = syn::parse_macro_input!(item as syn::ItemStruct); + + let args = match ServerArgs::from_list(&args) { + Ok(v) => v, + Err(e) => { + return e.write_errors().into(); + } + }; + + let mut chars = Vec::new(); + + let struct_fields = match &mut struc.fields { + syn::Fields::Named(n) => n, + _ => { + struc + .ident + .span() + .unwrap() + .error("gatt_client structs must have named fields, not tuples.") + .emit(); + return TokenStream::new(); + } + }; + let mut fields = struct_fields + .named + .iter() + .cloned() + .collect::<Vec<syn::Field>>(); + let mut err = None; + fields.retain(|field| { + if let Some(attr) = field.attrs.iter().find(|attr| { + attr.path.segments.len() == 1 + && attr.path.segments.first().unwrap().ident.to_string() == "characteristic" + }) { + let args = attr.parse_meta().unwrap(); + + let args = match CharacteristicArgs::from_meta(&args) { + Ok(v) => v, + Err(e) => { + err = Some(e.write_errors().into()); + return false; + } + }; + + chars.push(Characteristic { + name: field.ident.as_ref().unwrap().to_string(), + ty: field.ty.clone(), + args, + span: field.ty.span(), + }); + + false + } else { + true + } + }); + + if let Some(err) = err { + return err; + } + + //panic!("chars {:?}", chars); + let struct_name = struc.ident.clone(); + let event_enum_name = format_ident!("{}Event", struct_name); + + let mut code_impl = TokenStream2::new(); + let mut code_disc_new = TokenStream2::new(); + let mut code_disc_char = TokenStream2::new(); + let mut code_disc_done = TokenStream2::new(); + let mut code_event_enum = TokenStream2::new(); + + let ble = quote!(::nrf_softdevice::ble); + + fields.push(syn::Field { + ident: Some(format_ident!("conn")), + ty: syn::Type::Verbatim(quote!(#ble::Connection).into()), + attrs: Vec::new(), + colon_token: Default::default(), + vis: syn::Visibility::Inherited, + }); + + for ch in &chars { + let name_pascal = inflector::cases::pascalcase::to_pascal_case(&ch.name); + let uuid_field = format_ident!("{}_uuid", ch.name); + let value_handle = format_ident!("{}_value_handle", ch.name); + let cccd_handle = format_ident!("{}_cccd_handle", ch.name); + let read_fn = format_ident!("{}_read", ch.name); + let write_fn = format_ident!("{}_write", ch.name); + + let uuid = ch.args.uuid; + let read = ch.args.read; + let write = ch.args.write; + let notify = ch.args.notify; + let indicate = ch.args.indicate; + let ty = &ch.ty; + let ty_as_val = quote!(<#ty as #ble::GattValue>); + + fields.push(syn::Field { + ident: Some(value_handle.clone()), + ty: syn::Type::Verbatim(quote!(u16).into()), + attrs: Vec::new(), + colon_token: Default::default(), + vis: syn::Visibility::Inherited, + }); + + fields.push(syn::Field { + ident: Some(uuid_field.clone()), + ty: syn::Type::Verbatim(quote!(#ble::Uuid).into()), + attrs: Vec::new(), + colon_token: Default::default(), + vis: syn::Visibility::Inherited, + }); + + code_disc_new.extend(quote_spanned!(ch.span=> + #value_handle: 0, + #uuid_field: #uuid, + )); + + let mut code_descs = TokenStream2::new(); + if indicate || notify { + code_descs.extend(quote_spanned!(ch.span=> + if _desc_uuid == #ble::Uuid::new_16(::nrf_softdevice::raw::BLE_UUID_DESCRIPTOR_CLIENT_CHAR_CONFIG as u16) { + self.#cccd_handle = desc.handle; + } + )); + } + + code_disc_char.extend(quote_spanned!(ch.span=> + if let Some(char_uuid) = characteristic.uuid { + if char_uuid == self.#uuid_field { + // TODO maybe check the char_props have the necessary operations allowed? read/write/notify/etc + self.#value_handle = characteristic.handle_value; + for desc in descriptors { + if let Some(_desc_uuid) = desc.uuid { + #code_descs + } + } + } + } + )); + + code_disc_done.extend(quote_spanned!(ch.span=> + if self.#value_handle == 0 { + return Err(#ble::gatt_client::DiscoverError::ServiceIncomplete); + } + )); + + if read { + code_impl.extend(quote_spanned!(ch.span=> + async fn #read_fn(&self) -> Result<#ty, #ble::gatt_client::ReadError> { + let mut buf = [0; #ty_as_val::MAX_SIZE]; + let len = #ble::gatt_client::read(&self.conn, self.#value_handle, &mut buf).await?; + Ok(#ty_as_val::from_gatt(&buf[..len])) + } + )); + } + + if write { + code_impl.extend(quote_spanned!(ch.span=> + async fn #write_fn(&self, val: #ty) -> Result<(), #ble::gatt_client::WriteError> { + let buf = #ty_as_val::to_gatt(&val); + #ble::gatt_client::write(&self.conn, self.#value_handle, buf).await + } + )); + } + + if indicate || notify { + fields.push(syn::Field { + ident: Some(cccd_handle.clone()), + ty: syn::Type::Verbatim(quote!(u16).into()), + attrs: Vec::new(), + colon_token: Default::default(), + vis: syn::Visibility::Inherited, + }); + code_disc_new.extend(quote_spanned!(ch.span=> + #cccd_handle: 0, + )); + code_disc_done.extend(quote_spanned!(ch.span=> + if self.#value_handle == 0 { + return Err(#ble::gatt_client::DiscoverError::ServiceIncomplete); + } + )); + } + + if notify { + let case_notification = format_ident!("{}Notification", name_pascal); + code_event_enum.extend(quote_spanned!(ch.span=> + #case_notification(#ty), + )); + } + } + + let uuid = args.uuid; + struct_fields.named = syn::punctuated::Punctuated::from_iter(fields); + + let result = quote! { + #struc + + impl #struct_name { + #code_impl + } + + impl #ble::gatt_client::Client for #struct_name { + //type Event = #event_enum_name; + + fn uuid() -> #ble::Uuid { + #uuid + } + + fn new_undiscovered(conn: #ble::Connection) -> Self { + Self { + conn, + #code_disc_new + } + } + + fn discovered_characteristic( + &mut self, + characteristic: &#ble::gatt_client::Characteristic, + descriptors: &[#ble::gatt_client::Descriptor], + ) { + #code_disc_char + } + + fn discovery_complete(&mut self) -> Result<(), #ble::gatt_client::DiscoverError> { + #code_disc_done + Ok(()) + } + } + + enum #event_enum_name { + #code_event_enum + } + }; + result.into() +} |