1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, Fields, ItemEnum, ItemStruct, Path};
4
5#[proc_macro_attribute]
6pub fn homie_device(_attr: TokenStream, item: TokenStream) -> TokenStream {
7 let mut input = parse_macro_input!(item as ItemStruct);
9
10 let fields = match &mut input.fields {
12 Fields::Named(fields_named) => &mut fields_named.named,
13 _ => {
14 return syn::Error::new_spanned(
15 input,
16 "implement_homie_device only supports structs with named fields",
17 )
18 .to_compile_error()
19 .into();
20 }
21 };
22
23 fields.push(syn::parse_quote! { device_ref: DeviceRef });
25 fields.push(syn::parse_quote! { status: HomieDeviceStatus });
26 fields.push(syn::parse_quote! { device_desc: HomieDeviceDescription });
27 fields.push(syn::parse_quote! { homie_proto: Homie5DeviceProtocol });
28 fields.push(syn::parse_quote! { homie_client: HomieMQTTClient });
29
30 let struct_name = &input.ident;
32
33 let use_statements = quote! {
35 use hc_homie5::{HomieDeviceCore, HomieMQTTClient};
36 use homie5::{
37 device_description::HomieDeviceDescription, Homie5DeviceProtocol, HomieDeviceStatus,
38 HomieDomain, HomieID, DeviceRef,
39 };
40 };
41
42 let trait_impl = quote! {
44 impl HomieDeviceCore for #struct_name {
45
46 fn homie_domain(&self) -> &HomieDomain {
47 self.device_ref.homie_domain()
48 }
49
50 fn homie_id(&self) -> &HomieID {
51 self.device_ref.device_id()
52 }
53
54 fn device_ref(&self) -> &DeviceRef {
55 &self.device_ref
56 }
57
58 fn description(&self) -> &HomieDeviceDescription {
59 &self.device_desc
60 }
61
62 fn client(&self) -> &HomieMQTTClient {
63 &self.homie_client
64 }
65
66 fn homie_proto(&self) -> &Homie5DeviceProtocol {
67 &self.homie_proto
68 }
69
70 fn state(&self) -> HomieDeviceStatus {
71 self.status
72 }
73
74 fn set_state(&mut self, state: HomieDeviceStatus) {
75 self.status = state;
76 }
77
78 }
79 };
80
81 let expanded = quote! {
83 #use_statements
84
85 #input
86
87 #trait_impl
88 };
89
90 expanded.into()
91}
92
93#[proc_macro_attribute]
106pub fn homie_device_enum(attr: TokenStream, item: TokenStream) -> TokenStream {
107 let error_ty: Path = parse_macro_input!(attr as Path);
110
111 let input = parse_macro_input!(item as ItemEnum);
113 let enum_name = &input.ident;
114 let variants = &input.variants;
115
116 for v in variants {
118 match &v.fields {
119 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {}
120 _ => {
121 return syn::Error::new_spanned(
122 v,
123 "homie_device_enum only supports tuple variants with a single field, e.g. Variant(InnerType)",
124 )
125 .to_compile_error()
126 .into();
127 }
128 }
129 }
130
131 let homie_domain_arms = variants.iter().map(|v| {
133 let vident = &v.ident;
134 quote! { #enum_name::#vident(inner) => inner.homie_domain(), }
135 });
136
137 let homie_id_arms = variants.iter().map(|v| {
138 let vident = &v.ident;
139 quote! { #enum_name::#vident(inner) => inner.homie_id(), }
140 });
141
142 let device_ref_arms = variants.iter().map(|v| {
143 let vident = &v.ident;
144 quote! { #enum_name::#vident(inner) => inner.device_ref(), }
145 });
146
147 let description_arms = variants.iter().map(|v| {
148 let vident = &v.ident;
149 quote! { #enum_name::#vident(inner) => inner.description(), }
150 });
151
152 let client_arms = variants.iter().map(|v| {
153 let vident = &v.ident;
154 quote! { #enum_name::#vident(inner) => inner.client(), }
155 });
156
157 let proto_arms = variants.iter().map(|v| {
158 let vident = &v.ident;
159 quote! { #enum_name::#vident(inner) => inner.homie_proto(), }
160 });
161
162 let state_arms = variants.iter().map(|v| {
163 let vident = &v.ident;
164 quote! { #enum_name::#vident(inner) => inner.state(), }
165 });
166
167 let set_state_arms = variants.iter().map(|v| {
168 let vident = &v.ident;
169 quote! { #enum_name::#vident(inner) => inner.set_state(state), }
170 });
171
172 let publish_prop_values_arms = variants.iter().map(|v| {
174 let vident = &v.ident;
175 quote! { #enum_name::#vident(inner) => inner.publish_property_values().await, }
176 });
177
178 let handle_set_command_arms = variants.iter().map(|v| {
179 let vident = &v.ident;
180 quote! {
181 #enum_name::#vident(inner) => inner.handle_set_command(property, set_value).await,
182 }
183 });
184
185 let publish_meta_arms = variants.iter().map(|v| {
186 let vident = &v.ident;
187 quote! { #enum_name::#vident(inner) => inner.publish_meta().await, }
188 });
189
190 let expanded = quote! {
195 #input
196
197 impl hc_homie5::HomieDeviceCore for #enum_name {
198 fn homie_domain(&self) -> &homie5::HomieDomain {
199 match self {
200 #(#homie_domain_arms)*
201 }
202 }
203
204 fn homie_id(&self) -> &homie5::HomieID {
205 match self {
206 #(#homie_id_arms)*
207 }
208 }
209
210 fn device_ref(&self) -> &homie5::DeviceRef {
211 match self {
212 #(#device_ref_arms)*
213 }
214 }
215
216 fn description(&self) -> &homie5::device_description::HomieDeviceDescription {
217 match self {
218 #(#description_arms)*
219 }
220 }
221
222 fn client(&self) -> &hc_homie5::HomieMQTTClient {
223 match self {
224 #(#client_arms)*
225 }
226 }
227
228 fn homie_proto(&self) -> &homie5::Homie5DeviceProtocol {
229 match self {
230 #(#proto_arms)*
231 }
232 }
233
234 fn state(&self) -> homie5::HomieDeviceStatus {
235 match self {
236 #(#state_arms)*
237 }
238 }
239
240 fn set_state(&mut self, state: homie5::HomieDeviceStatus) {
241 match self {
242 #(#set_state_arms)*
243 }
244 }
245 }
246
247 impl hc_homie5::HomieDevice for #enum_name {
248 type ResultError = #error_ty;
249
250 fn publish_property_values(
251 &mut self,
252 ) -> impl std::future::Future<Output = Result<(), Self::ResultError>> + Send {
253 async move {
254 match self {
255 #(#publish_prop_values_arms)*
256 }
257 }
258 }
259
260 fn handle_set_command(
261 &mut self,
262 property: &homie5::PropertyRef,
263 set_value: &str,
264 ) -> impl std::future::Future<Output = Result<(), Self::ResultError>> + Send {
265 async move {
266 match self {
267 #(#handle_set_command_arms)*
268 }
269 }
270 }
271
272 fn publish_meta(
273 &mut self,
274 ) -> impl std::future::Future<Output = Result<(), Self::ResultError>> + Send {
275 async move {
276 match self {
277 #(#publish_meta_arms)*
278 }
279 }
280 }
281 }
282 };
283
284 TokenStream::from(expanded)
285}