summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--examples/src/bin/ble_bas_peripheral.rs85
-rw-r--r--nrf-softdevice-macro/Cargo.toml15
-rw-r--r--nrf-softdevice-macro/src/lib.rs262
-rw-r--r--nrf-softdevice/Cargo.toml2
-rw-r--r--nrf-softdevice/src/lib.rs2
-rw-r--r--nrf-softdevice/src/softdevice.rs7
7 files changed, 294 insertions, 80 deletions
diff --git a/Cargo.toml b/Cargo.toml
index dd5114e..3fe1af8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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