modbus_mapping_derive/
lib.rs1use proc_macro::TokenStream;
4use quote::quote;
5
6mod config;
7mod entry;
8mod mapping;
9mod utils;
10
11#[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 let words = match client.read_input_registers(#start, #len).await? {
39 Ok(words) => words,
40 Err(exc) => return Ok(Err(exc)),
41 };
42 #(
43 let #field_name: #ty = modbus_mapping::codec::Decode::#from_words(&words[(#addr - #start) as usize..(#addr - #start + #cnt) as usize]).unwrap();
45 #[allow(clippy::unnecessary_cast)]
47 let #field_name: #field_ty = (#field_name as #field_ty) * (#x as #field_ty);
48 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#[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 let words = match client.read_holding_registers(#start, #len).await? {
99 Ok(words) => words,
100 Err(exc) => return Ok(Err(exc)),
101 };
102 #(
103 let #field_name: #ty = modbus_mapping::codec::Decode::#from_words(&words[(#addr - #start) as usize..(#addr - #start + #cnt) as usize]).unwrap();
105 #[allow(clippy::unnecessary_cast)]
107 let #field_name: #field_ty = (#field_name as #field_ty) * (#x as #field_ty);
108 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 #[allow(clippy::unnecessary_cast)]
130 let #field_name: #ty = (self.#field_name / (#x as #field_ty)) as #ty;
131 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 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 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#[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 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 #[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 #[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#[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 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 #[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 #[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 let words = registers.read(#addr, #cnt)?;
321 let #field_name: #ty = modbus_mapping::codec::Decode::#from_words(&words).unwrap();
323 #[allow(clippy::unnecessary_cast)]
325 let #field_name: #field_ty = (#field_name as #field_ty) * (#x as #field_ty);
326 self.#field_name = #field_name;
328 )*
329
330 Ok(())
331 }
332 }
333
334 };
335
336 tokens.into()
337}