diff options
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | examples/src/bin/ble_bas_peripheral.rs | 85 | ||||
-rw-r--r-- | nrf-softdevice-macro/Cargo.toml | 15 | ||||
-rw-r--r-- | nrf-softdevice-macro/src/lib.rs | 262 | ||||
-rw-r--r-- | nrf-softdevice/Cargo.toml | 2 | ||||
-rw-r--r-- | nrf-softdevice/src/lib.rs | 2 | ||||
-rw-r--r-- | nrf-softdevice/src/softdevice.rs | 7 |
7 files changed, 294 insertions, 80 deletions
@@ -8,6 +8,7 @@ members = [ "nrf-softdevice-s122", "nrf-softdevice-s132", "nrf-softdevice-s140", + "nrf-softdevice-macro", "async-flash", diff --git a/examples/src/bin/ble_bas_peripheral.rs b/examples/src/bin/ble_bas_peripheral.rs index 0a5fe6c..131f9c1 100644 --- a/examples/src/bin/ble_bas_peripheral.rs +++ b/examples/src/bin/ble_bas_peripheral.rs @@ -22,90 +22,15 @@ async fn softdevice_task(sd: &'static Softdevice) { const GATT_BAS_SVC_UUID: Uuid = Uuid::new_16(0x180F); const GATT_BAS_BATTERY_LEVEL_CHAR_UUID: Uuid = Uuid::new_16(0x2A19); -struct BatteryServiceServer { - battery_level_value_handle: u16, - battery_level_cccd_handle: u16, -} - -enum BatteryServiceEvent { - BatteryLevelWrite(u8), - BatteryLevelNotificationsEnabled, - BatteryLevelNotificationsDisabled, -} - -// This is boilerplate, ideally it'll be generated with a proc macro in the future. -impl BatteryServiceServer { - fn battery_level_get(&self, sd: &Softdevice) -> Result<u8, gatt_server::GetValueError> { - let buf = &mut [0u8; 0]; - gatt_server::get_value(sd, self.battery_level_value_handle, buf)?; - Ok(buf[0]) - } - - fn battery_level_set( - &self, - sd: &Softdevice, - val: u8, - ) -> Result<(), gatt_server::SetValueError> { - gatt_server::set_value(sd, self.battery_level_value_handle, &[val]) - } - - fn battery_level_notify( - &self, - conn: &Connection, - val: u8, - ) -> Result<(), gatt_server::NotifyValueError> { - gatt_server::notify_value(conn, self.battery_level_value_handle, &[val]) - } -} - -// This is boilerplate, ideally it'll be generated with a proc macro in the future. -impl gatt_server::Server for BatteryServiceServer { - type Event = BatteryServiceEvent; - - fn uuid() -> Uuid { - GATT_BAS_SVC_UUID - } - - fn register<F>(service_handle: u16, mut register_char: F) -> Result<Self, RegisterError> - where - F: FnMut(Characteristic, &[u8]) -> Result<CharacteristicHandles, RegisterError>, - { - let battery_level = register_char( - Characteristic { - uuid: GATT_BAS_BATTERY_LEVEL_CHAR_UUID, - can_indicate: false, - can_notify: true, - can_read: true, - can_write: true, - max_len: 1, - }, - &[123], - )?; - - Ok(Self { - battery_level_cccd_handle: battery_level.cccd_handle, - battery_level_value_handle: battery_level.value_handle, - }) - } - - fn on_write(&self, handle: u16, data: &[u8]) -> Option<Self::Event> { - if handle == self.battery_level_value_handle { - return Some(BatteryServiceEvent::BatteryLevelWrite(data[0])); - } - if handle == self.battery_level_cccd_handle { - if data.len() != 0 && data[0] & 0x01 != 0 { - return Some(BatteryServiceEvent::BatteryLevelNotificationsEnabled); - } else { - return Some(BatteryServiceEvent::BatteryLevelNotificationsDisabled); - } - } - None - } +#[nrf_softdevice::gatt_server(uuid = "180f")] +struct BatteryService { + #[characteristic(uuid = "2a19", read, write, notify)] + battery_level: u8, } #[static_executor::task] async fn bluetooth_task(sd: &'static Softdevice) { - let server: BatteryServiceServer = gatt_server::register(sd).dewrap(); + let server: BatteryService = gatt_server::register(sd).dewrap(); #[rustfmt::skip] let adv_data = &[ diff --git a/nrf-softdevice-macro/Cargo.toml b/nrf-softdevice-macro/Cargo.toml new file mode 100644 index 0000000..81ddcb8 --- /dev/null +++ b/nrf-softdevice-macro/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "nrf-softdevice-macro" +version = "0.1.0" +authors = ["Dario Nieuwenhuis <dirbaio@dirbaio.net>"] +edition = "2018" + +[dependencies] +syn = { version = "1.0.39", features = ["full", "extra-traits"] } +quote = "1.0.7" +darling = "0.10.2" +proc-macro2 = "1.0.18" +Inflector = "0.11.4" + +[lib] +proc-macro = true diff --git a/nrf-softdevice-macro/src/lib.rs b/nrf-softdevice-macro/src/lib.rs new file mode 100644 index 0000000..158679b --- /dev/null +++ b/nrf-softdevice-macro/src/lib.rs @@ -0,0 +1,262 @@ +#![feature(proc_macro_diagnostic)] + +extern crate proc_macro; + +use darling::FromMeta; +use proc_macro::{TokenStream}; +use proc_macro2::{TokenStream as TokenStream2}; +use quote::{format_ident, quote}; +use std::iter::FromIterator; +use syn::spanned::Spanned; + +#[derive(Debug, FromMeta)] +struct ServerArgs { + uuid: String, +} +#[derive(Debug, FromMeta)] +struct CharacteristicArgs { + uuid: String, + #[darling(default)] + read: bool, + #[darling(default)] + write: bool, + #[darling(default)] + notify: bool, + #[darling(default)] + indicate: bool, +} + +#[derive(Debug)] +struct Characteristic { + name: String, + ty: syn::Type, + args: CharacteristicArgs, +} + +#[proc_macro_attribute] +pub fn gatt_server(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_server 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, + }); + + 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_register_chars = TokenStream2::new(); + let mut code_register_init = TokenStream2::new(); + let mut code_on_write = TokenStream2::new(); + let mut code_event_enum = TokenStream2::new(); + + for ch in &chars { + let name_pascal = inflector::cases::pascalcase::to_pascal_case(&ch.name); + let char_name = format_ident!("{}", ch.name); + let value_handle = format_ident!("{}_value_handle", ch.name); + let cccd_handle = format_ident!("{}_cccd_handle", ch.name); + let get_fn = format_ident!("{}_get", ch.name); + let set_fn = format_ident!("{}_set", ch.name); + let notify_fn = format_ident!("{}_notify", ch.name); + + let read = ch.args.read; + let write = ch.args.write; + let notify = ch.args.notify; + let indicate = ch.args.indicate; + let ty = &ch.ty; + + 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, + }); + + code_register_chars.extend(quote!( + let #char_name = register_char( + Characteristic { + uuid: GATT_BAS_BATTERY_LEVEL_CHAR_UUID, + can_read: #read, + can_write: #write, + can_notify: #notify, + can_indicate: #indicate, + max_len: 1, + }, + &[123], + )?; + )); + + code_register_init.extend(quote!( + #value_handle: #char_name.value_handle, + )); + + code_impl.extend(quote!( + fn #get_fn(&self) -> Result<u8, gatt_server::GetValueError> { + let sd = unsafe { ::nrf_softdevice::Softdevice::steal() }; + let buf = &mut [0u8; 1]; + gatt_server::get_value(sd, self.#value_handle, buf)?; + Ok(buf[0]) + } + + fn #set_fn(&self, val: u8) -> Result<(), gatt_server::SetValueError> { + let sd = unsafe { ::nrf_softdevice::Softdevice::steal() }; + gatt_server::set_value(sd, self.#value_handle, &[val]) + } + )); + + if ch.args.indicate || ch.args.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_register_init.extend(quote!( + #cccd_handle: #char_name.cccd_handle, + )); + } + + if ch.args.write { + let case_write = format_ident!("{}Write", name_pascal); + code_event_enum.extend(quote!( + #case_write(#ty), + )); + #[rustfmt::skip] + code_on_write.extend(quote!( + if handle == self.#value_handle { + return Some(#event_enum_name::#case_write(data[0])); + } + )); + } + if ch.args.notify { + let case_enabled = format_ident!("{}NotificationsEnabled", name_pascal); + let case_disabled = format_ident!("{}NotificationsDisabled", name_pascal); + + code_impl.extend(quote!( + fn #notify_fn( + &self, + conn: &Connection, + val: u8, + ) -> Result<(), gatt_server::NotifyValueError> { + gatt_server::notify_value(conn, self.#value_handle, &[val]) + } + )); + + code_event_enum.extend(quote!( + #case_enabled, + #case_disabled, + )); + #[rustfmt::skip] + code_on_write.extend(quote!( + + if handle == self.#cccd_handle { + if data.len() != 0 && data[0] & 0x01 != 0 { + return Some(#event_enum_name::#case_enabled); + } else { + return Some(#event_enum_name::#case_disabled); + } + } + )); + } + + //panic!(); + } + + struct_fields.named = syn::punctuated::Punctuated::from_iter(fields); + + let result = quote! { + #struc + + impl #struct_name { + #code_impl + } + + impl ::nrf_softdevice::ble::gatt_server::Server for #struct_name { + type Event = #event_enum_name; + + fn uuid() -> Uuid { + GATT_BAS_SVC_UUID + } + + fn register<F>(service_handle: u16, mut register_char: F) -> Result<Self, RegisterError> + where + F: FnMut(Characteristic, &[u8]) -> Result<CharacteristicHandles, RegisterError>, + { + #code_register_chars + + Ok(Self { + #code_register_init + }) + } + + fn on_write(&self, handle: u16, data: &[u8]) -> Option<Self::Event> { + #code_on_write + None + } + + } + + enum #event_enum_name { + #code_event_enum + } + }; + result.into() +} diff --git a/nrf-softdevice/Cargo.toml b/nrf-softdevice/Cargo.toml index 5940892..9778a50 100644 --- a/nrf-softdevice/Cargo.toml +++ b/nrf-softdevice/Cargo.toml @@ -50,6 +50,8 @@ nrf-softdevice-s122 = { version = "0.1.1", path = "../nrf-softdevice-s122", opti nrf-softdevice-s132 = { version = "0.1.1", path = "../nrf-softdevice-s132", optional = true } nrf-softdevice-s140 = { version = "0.1.1", path = "../nrf-softdevice-s140", optional = true } +nrf-softdevice-macro = { version = "0.1.0", path = "../nrf-softdevice-macro" } + [package.metadata.docs.rs] targets = ["thumbv7em-none-eabihf"] features = ["nrf52840", "s140", "ble-central", "ble-peripheral", "ble-l2cap", "ble-gatt-server", "ble-gatt-client"]
\ No newline at end of file diff --git a/nrf-softdevice/src/lib.rs b/nrf-softdevice/src/lib.rs index 448d5dd..1f0414e 100644 --- a/nrf-softdevice/src/lib.rs +++ b/nrf-softdevice/src/lib.rs @@ -43,3 +43,5 @@ pub use cortex_m_rt::interrupt; mod temperature; pub use temperature::temperature_celsius; + +pub use nrf_softdevice_macro::*; diff --git a/nrf-softdevice/src/softdevice.rs b/nrf-softdevice/src/softdevice.rs index 1c9a55c..bdf43f1 100644 --- a/nrf-softdevice/src/softdevice.rs +++ b/nrf-softdevice/src/softdevice.rs @@ -291,6 +291,13 @@ impl Softdevice { unsafe { &SOFTDEVICE } } + /// Return an instance to the softdevice without checking whether + /// it is enabled or not. This is only safe if the softdevice is enabled + /// (a call to [`enable`] has returned without error) + pub unsafe fn steal() -> &'static Softdevice { + &SOFTDEVICE + } + /// Runs the softdevice event handling loop. /// /// It must be called in its own async task after enabling the softdevice |