modbus_mapping_derive/
lib.rs

1//! Macros to `derive` the `modbus-mapping` traits
2
3use proc_macro::TokenStream;
4use quote::quote;
5
6mod config;
7mod entry;
8mod mapping;
9mod utils;
10
11/// Derive macro to implement `modbus_mapping::core::InputRegisterMap`
12#[proc_macro_derive(InputRegisterMap, attributes(modbus))]
13pub fn derive_input_register_map(input: TokenStream) -> TokenStream {
14    let ast = syn::parse_macro_input!(input as syn::DeriveInput);
15
16    let name = &ast.ident;
17
18    let block_config = config::Config::new(&ast);
19
20    let mapping = mapping::Mapping::new(&ast);
21    let block_mappings = mapping.split_into_block_mappings(&block_config);
22
23    let mut blocks = Vec::new();
24    for mapping in block_mappings {
25        let field_name = mapping.field_name_vec();
26        let field_ty = mapping.field_ty_vec();
27        let x = mapping.x_vec();
28        let addr = mapping.addr_vec();
29        let ty = mapping.ty_vec();
30        let cnt = mapping.cnt_vec();
31        let from_words = mapping.fn_from_words_vec();
32
33        let (start, end) = mapping.register_range();
34        let len = end - start;
35
36        let block = quote! {
37            // Read
38            let words = match client.read_input_registers(#start, #len).await? {
39                Ok(words) => words,
40                Err(exc) => return Ok(Err(exc)),
41            };
42            #(
43                // Decode
44                let #field_name: #ty = modbus_mapping::codec::Decode::#from_words(&words[(#addr - #start) as usize..(#addr - #start + #cnt) as usize]).unwrap();
45                // Convert and scale
46                #[allow(clippy::unnecessary_cast)]
47                let #field_name: #field_ty = (#field_name as #field_ty) * (#x as #field_ty);
48                // Set
49                self.#field_name = #field_name;
50
51            )*
52
53        };
54        blocks.push(block);
55    }
56
57    let tokens = quote! {
58        #[async_trait::async_trait]
59        impl modbus_mapping::core::InputRegisterMap for #name {
60            async fn update_from_input_registers(&mut self, client: &mut dyn tokio_modbus::client::Reader) -> tokio_modbus::Result<()>{
61                #(#blocks)*
62                Ok(Ok(()))
63            }
64        }
65
66    };
67
68    tokens.into()
69}
70
71/// Derive macro to implement `modbus_mapping::core::HoldingRegisterMap`
72#[proc_macro_derive(HoldingRegisterMap, attributes(modbus))]
73pub fn derive_holding_register_map(input: TokenStream) -> TokenStream {
74    let ast = syn::parse_macro_input!(input as syn::DeriveInput);
75
76    let name = &ast.ident;
77
78    let block_config = config::Config::new(&ast);
79
80    let mapping = mapping::Mapping::new(&ast);
81    let block_mappings = mapping.clone().split_into_block_mappings(&block_config);
82
83    let mut read_blocks = Vec::new();
84    for mapping in block_mappings {
85        let field_name = mapping.field_name_vec();
86        let field_ty = mapping.field_ty_vec();
87        let x = mapping.x_vec();
88        let addr = mapping.addr_vec();
89        let ty = mapping.ty_vec();
90        let cnt = mapping.cnt_vec();
91        let from_words = mapping.fn_from_words_vec();
92
93        let (start, end) = mapping.register_range();
94        let len = end - start;
95
96        let block = quote! {
97            // Read
98            let words = match client.read_holding_registers(#start, #len).await? {
99                Ok(words) => words,
100                Err(exc) => return Ok(Err(exc)),
101            };
102            #(
103                // Decode
104                let #field_name: #ty = modbus_mapping::codec::Decode::#from_words(&words[(#addr - #start) as usize..(#addr - #start + #cnt) as usize]).unwrap();
105                // Convert and scale
106                #[allow(clippy::unnecessary_cast)]
107                let #field_name: #field_ty = (#field_name as #field_ty) * (#x as #field_ty);
108                // Set
109                self.#field_name = #field_name;
110
111            )*
112
113        };
114        read_blocks.push(block);
115    }
116
117    let mut write_blocks = Vec::new();
118    let mut method_blocks = write_blocks.clone();
119    for entry in mapping.0 {
120        let field_name = entry.field_name_ident();
121        let field_ty = entry.field_ty_ident();
122        let x = &entry.x;
123        let addr = &entry.addr;
124        let ty = entry.ty_ident();
125        let to_words = entry.fn_to_words();
126
127        let mut block = quote! {
128            // Convert and rescale
129            #[allow(clippy::unnecessary_cast)]
130            let #field_name: #ty = (self.#field_name / (#x as #field_ty)) as #ty;
131            // Encode
132            let field_words: Vec<modbus_mapping::codec::Word> = modbus_mapping::codec::Encode::#to_words(#field_name);
133        };
134        if entry.ty.word_size() == 1 {
135            block.extend(quote! {
136                match client.write_single_register(#addr, field_words.first().unwrap().clone()).await? {
137                    Ok(_) => {},
138                    Err(exc) => return Ok(Err(exc))
139                };
140            })
141        } else {
142            block.extend(quote! {
143                // Set (use splice alternatively)
144                match client.write_multiple_registers(#addr, &field_words).await? {
145                    Ok(_) => {},
146                    Err(exc) => return Ok(Err(exc))
147                };
148            })
149        }
150
151        write_blocks.push(block.clone());
152
153        let method = entry.write_method_ident();
154        let block = quote! {
155            pub async fn #method(&self, client: &mut dyn tokio_modbus::client::Writer) -> tokio_modbus::Result<()> {
156                #block
157                Ok(Ok(()))
158            }
159        };
160        method_blocks.push(block.clone());
161    }
162
163    let tokens = quote! {
164        #[async_trait::async_trait]
165        impl modbus_mapping::core::HoldingRegisterMap for #name {
166            async fn update_from_holding_registers(&mut self, client: &mut dyn tokio_modbus::client::Reader) -> tokio_modbus::Result<()>{
167                // #(#read_blocks)*
168                Ok(Ok(()))
169            }
170
171            async fn write_to_registers(&self, client: &mut dyn tokio_modbus::client::Writer) -> tokio_modbus::Result<()> {
172                #(#write_blocks)*;
173                Ok(Ok(()))
174            }
175        }
176
177        impl #name {
178            #(#method_blocks)*
179        }
180
181    };
182
183    tokens.into()
184}
185
186#[proc_macro_attribute]
187pub fn modbus_doc(_attr: TokenStream, item: TokenStream) -> TokenStream {
188    let mut ast = syn::parse_macro_input!(item as syn::DeriveInput);
189    match &mut ast.data {
190        syn::Data::Struct(ref mut struct_data) => {
191            match &mut struct_data.fields {
192                syn::Fields::Named(fields_named) => {
193                    for field in &mut fields_named.named {
194                        if field
195                            .attrs
196                            .iter()
197                            .any(|attr| attr.path().is_ident("modbus"))
198                        {
199                            let entry: entry::Entry = field.clone().into();
200                            let doc = format!("address - `{}`, data type - `{:?}` (`{}` registers), word order - `{:?}`, scale factor - `{}`, unit - `{}`.", entry.addr, entry.ty, entry.ty.word_size(), entry.ord, entry.x, entry.unit);
201                            let doc: syn::Attribute = syn::parse_quote!(#[doc = #doc]);
202                            field.attrs.push(doc);
203                        }
204                    }
205                }
206                _ => panic!("`modbus_doc` has to be applied to structs with named fields"),
207            }
208
209            quote! {
210                #ast
211            }
212            .into()
213        }
214        _ => panic!("`modbus_doc` has to be applied with structs"),
215    }
216}
217
218/// Derive macro to implement `modbus_mapping::core::InputRegisterModel`
219#[proc_macro_derive(InputRegisterModel, attributes(modbus))]
220pub fn derive_input_register_model(input: TokenStream) -> TokenStream {
221    let ast = syn::parse_macro_input!(input as syn::DeriveInput);
222
223    let name = &ast.ident;
224
225    let mapping = mapping::Mapping::new(&ast);
226    // panic!("{:#?}", mapping);
227
228    let field_name = mapping.field_name_vec();
229    let field_ty = mapping.field_ty_vec();
230    let x = mapping.x_vec();
231    let addr = mapping.addr_vec();
232    let ty = mapping.ty_vec();
233    let to_words = mapping.fn_to_words_vec();
234
235    let tokens = quote! {
236        impl modbus_mapping::simulator::InputRegisterModel for #name {
237            fn new_registers(&self) -> modbus_mapping::simulator::Registers {
238                let mut registers = modbus_mapping::simulator::Registers::default();
239
240                #(
241                    // Divide by scale factor
242                    #[allow(clippy::unnecessary_cast)]
243                    let #field_name: #ty = (&self.#field_name / (#x as #field_ty)) as #ty;
244                    registers.insert(#addr, modbus_mapping::codec::Encode::#to_words(#field_name));
245                )*
246
247                registers
248            }
249
250            fn update_registers(
251                &self,
252                registers: &mut modbus_mapping::simulator::Registers,
253            ) -> Result<(), tokio_modbus::Exception> {
254                #(
255                    // Divide by scale factor
256                    #[allow(clippy::unnecessary_cast)]
257                    let #field_name: #ty = (self.#field_name / (#x as #field_ty)) as #ty;
258                    registers.write(#addr, &modbus_mapping::codec::Encode::#to_words(#field_name))?;
259                )*
260                Ok(())
261            }
262        }
263    };
264
265    tokens.into()
266}
267
268/// Derive macro to implement `modbus_mapping::core::HoldingRegisterModel`
269#[proc_macro_derive(HoldingRegisterModel, attributes(modbus))]
270pub fn derive_holding_register_model(input: TokenStream) -> TokenStream {
271    let ast = syn::parse_macro_input!(input as syn::DeriveInput);
272
273    let name = &ast.ident;
274
275    let mapping = mapping::Mapping::new(&ast);
276    // panic!("{:#?}", mapping);
277
278    let field_name = mapping.field_name_vec();
279    let field_ty = mapping.field_ty_vec();
280    let x = mapping.x_vec();
281    let addr = mapping.addr_vec();
282    let cnt = mapping.cnt_vec();
283    let ty = mapping.ty_vec();
284    let to_words = mapping.fn_to_words_vec();
285    let from_words = mapping.fn_from_words_vec();
286
287    let tokens = quote! {
288        impl modbus_mapping::simulator::HoldingRegisterModel for #name {
289            fn new_registers(&self) -> modbus_mapping::simulator::Registers {
290                let mut registers = modbus_mapping::simulator::Registers::default();
291
292                #(
293                    // Divide by scale factor
294                    #[allow(clippy::unnecessary_cast)]
295                    let #field_name: #ty = (&self.#field_name / (#x as #field_ty)) as #ty;
296                    registers.insert(#addr, modbus_mapping::codec::Encode::#to_words(#field_name));
297                )*
298
299                registers
300            }
301
302            fn update_registers(
303                &self,
304                registers: &mut modbus_mapping::simulator::Registers,
305            ) -> Result<(), tokio_modbus::Exception> {
306                #(
307                    // Divide by scale factor
308                    #[allow(clippy::unnecessary_cast)]
309                    let #field_name: #ty = (self.#field_name / (#x as #field_ty)) as #ty;
310                    registers.write(#addr, &modbus_mapping::codec::Encode::#to_words(#field_name))?;
311
312                )*
313
314                Ok(())
315            }
316
317            fn update_self(&mut self, registers: &modbus_mapping::simulator::Registers) -> Result<(), tokio_modbus::Exception> {
318                #(
319                    // Read
320                    let words = registers.read(#addr, #cnt)?;
321                    // Decode
322                    let #field_name: #ty = modbus_mapping::codec::Decode::#from_words(&words).unwrap();
323                    // Convert and scale
324                    #[allow(clippy::unnecessary_cast)]
325                    let #field_name: #field_ty = (#field_name as #field_ty) * (#x as #field_ty);
326                    // Set
327                    self.#field_name = #field_name;
328                )*
329
330                Ok(())
331            }
332        }
333
334    };
335
336    tokens.into()
337}