trouble_host_macros/lib.rs
1//! Procedural Macros for the `trouble_host` crate.
2//!
3//! This crate is enabled by the 'derive' feature of the `trouble_host` crate.
4//! It adds helper macros to simplify the creation of GATT services and servers.
5
6extern crate proc_macro;
7
8mod characteristic;
9mod ctxt;
10mod server;
11mod service;
12mod uuid;
13
14use characteristic::{Characteristic, CharacteristicArgs, DescriptorArgs};
15use ctxt::Ctxt;
16use proc_macro::TokenStream;
17use server::{ServerArgs, ServerBuilder};
18use service::{ServiceArgs, ServiceBuilder};
19use syn::spanned::Spanned;
20use syn::{parse_macro_input, Error};
21
22/// Gatt Service attribute macro.
23///
24///
25///
26/// # Example
27/// ```rust no_run
28/// use trouble_host::prelude::*;
29///
30/// #[gatt_server]
31/// struct MyGattServer {
32/// hrs: HeartRateService,
33/// bas: BatteryService,
34/// }
35///
36/// ```
37#[proc_macro_attribute]
38pub fn gatt_server(args: TokenStream, item: TokenStream) -> TokenStream {
39 let server_args = {
40 let mut attributes = ServerArgs::default();
41 let arg_parser = syn::meta::parser(|meta| attributes.parse(meta));
42
43 syn::parse_macro_input!(args with arg_parser);
44 attributes
45 };
46 let ctxt = Ctxt::new();
47 let server_properties = syn::parse_macro_input!(item as syn::ItemStruct);
48
49 let result = ServerBuilder::new(server_properties, server_args).build();
50
51 match ctxt.check() {
52 Ok(()) => result.into(),
53 Err(e) => e.into(),
54 }
55}
56
57/// Gatt Service attribute macro.
58///
59/// # Example
60///
61/// ```rust no_run
62/// use trouble_host::prelude::*;
63///
64/// const DESCRIPTOR_VALUE: &str = "Can be specified from a const";
65///
66/// #[gatt_service(uuid = "7e701cf1-b1df-42a1-bb5f-6a1028c793b0")]
67/// struct HeartRateService {
68/// /// Docstrings can be
69/// /// Multiple lines
70/// #[descriptor(uuid = "2a21", read, value = [0x00,0x01,0x02,0x03])]
71/// #[characteristic(uuid = characteristic::HEART_RATE_MEASUREMENT, read, notify, value = 3.14)]
72/// rate: f32,
73/// #[descriptor(uuid = descriptors::MEASUREMENT_DESCRIPTION, read, value = DESCRIPTOR_VALUE)]
74/// #[characteristic(uuid = "2a28", read, write, notify, value = 42.0)]
75/// /// Can be in any order
76/// location: f32,
77/// #[characteristic(uuid = "2a39", write)]
78/// control: u8,
79/// #[characteristic(uuid = "2a63", read, notify)]
80/// energy_expended: u16,
81/// }
82/// ```
83#[proc_macro_attribute]
84pub fn gatt_service(args: TokenStream, item: TokenStream) -> TokenStream {
85 // Get arguments from the gatt_service macro attribute
86 let service_arguments = parse_macro_input!(args as ServiceArgs);
87
88 // Parse the contents of the struct
89 let mut service_props = parse_macro_input!(item as syn::ItemStruct);
90
91 let ctxt = Ctxt::new(); // error handling context, must be initialized after parse_macro_input calls.
92
93 let mut fields: Vec<syn::Field> = {
94 let struct_fields = match &mut service_props.fields {
95 syn::Fields::Named(n) => n,
96 _ => {
97 let s = service_props.ident;
98 ctxt.error_spanned_by(s, "gatt_service structs must have named fields, not tuples.");
99 return ctxt.check().unwrap_err().into();
100 }
101 };
102 struct_fields.named.iter().cloned().collect()
103 };
104
105 // Parse fields tagged as characteristics, remove them from the fields vec and store them in a separate vec.
106 let mut characteristics: Vec<Characteristic> = Vec::new();
107 let mut err: Option<syn::Error> = None;
108 fields.retain(|field| check_for_characteristic(field, &mut err, &mut characteristics));
109
110 // If there was an error parsing the characteristics, return the error
111 if let Some(err) = err {
112 let desc = err.to_string();
113 ctxt.error_spanned_by(
114 err.into_compile_error(),
115 format!("Parsing characteristics was unsuccessful:\n{}", desc),
116 );
117 return ctxt.check().unwrap_err().into();
118 }
119
120 // Build the service struct
121 let result = ServiceBuilder::new(service_props, service_arguments)
122 .process_characteristics_and_fields(fields, characteristics)
123 .build();
124
125 match ctxt.check() {
126 Ok(()) => result.into(),
127 Err(e) => e.into(),
128 }
129}
130
131/// Check if a field has a characteristic attribute and parse it.
132///
133/// If so also check if that field has descriptors and/or docstrings.
134///
135/// # Example
136///
137/// ```rust
138/// use trouble_host::prelude::*;
139///
140/// #[gatt_service(uuid = "180f")]
141/// struct BatteryService {
142/// /// Docstrings can be
143/// /// Multiple lines
144/// #[characteristic(uuid = "2a19", read, write, notify, value = 99)]
145/// #[descriptor(uuid = "2a20", read, value = [0x00,0x01,0x02,0x03])]
146/// #[descriptor(uuid = "2a20", read, value = "Demo description")]
147/// level: u8,
148/// #[descriptor(uuid = "2a21", read, value = VAL)]
149/// #[characteristic(uuid = "2a22", read, write, notify, value = 42.0)]
150/// /// Can be in any order
151/// rate_of_discharge: f32,
152///}
153/// ```
154fn check_for_characteristic(
155 field: &syn::Field,
156 err: &mut Option<syn::Error>,
157 characteristics: &mut Vec<Characteristic>,
158) -> bool {
159 const RETAIN: bool = true;
160 const REMOVE: bool = false;
161
162 let Some(attr) = field.attrs.iter().find(|attr| attr.path().is_ident("characteristic")) else {
163 return RETAIN; // If the field does not have a characteristic attribute, retain it.
164 };
165 let mut descriptors = Vec::new();
166 let mut doc_string = String::new();
167 let mut characteristic_checked = false;
168 for attr in &field.attrs {
169 if let Some(ident) = attr.path().get_ident() {
170 match ident.to_string().as_str() {
171 "doc" => {
172 if let Ok(meta_name_value) = attr.meta.require_name_value() {
173 if let syn::Expr::Lit(value) = &meta_name_value.value {
174 if let Some(text) = &value.lit.span().source_text() {
175 let text: Vec<&str> = text.split("///").collect();
176 if let Some(text) = text.get(1) {
177 if !doc_string.is_empty() {
178 doc_string.push('\n');
179 }
180 doc_string.push_str(text);
181 }
182 }
183 }
184 }
185 }
186 "descriptor" => match DescriptorArgs::parse(attr) {
187 Ok(args) => descriptors.push(args),
188 Err(e) => {
189 *err = Some(e);
190 return REMOVE; // If there was an error parsing the descriptor, remove the field.
191 }
192 },
193 "characteristic" => {
194 // make sure we only have one characteristic meta tag
195 if characteristic_checked {
196 *err = Some(Error::new(
197 attr.path().span(),
198 "only one characteristic tag should be applied per field",
199 ));
200 return REMOVE; // If there was an error parsing the descriptor, remove the field.
201 } else {
202 characteristic_checked = true;
203 }
204 }
205 "descriptors" => {
206 *err = Some(Error::new(
207 attr.path().span(),
208 "specify a descriptor like: #[descriptor(uuid = \"1234\", value = \"Hello World\", read, write, notify)]\nCan be specified multiple times.",
209 ));
210 return REMOVE; // If there was an error parsing the descriptor, remove the field.
211 }
212 _ => {
213 *err = Some(Error::new(
214 attr.path().span(),
215 "only doc (///), descriptor and characteristic tags are supported.",
216 ));
217 return REMOVE; // If there was an error parsing the descriptor, remove the field.
218 }
219 }
220 }
221 }
222 let mut args = match CharacteristicArgs::parse(attr) {
223 Ok(args) => args,
224 Err(e) => {
225 *err = Some(e);
226 return REMOVE; // If there was an error parsing the characteristic, remove the field.
227 }
228 };
229 args.doc_string = doc_string;
230 args.descriptors = descriptors;
231 characteristics.push(Characteristic::new(field, args));
232 REMOVE // Successfully parsed, remove the field from the fields vec.
233}