1use crate::io::Kind;
2use eva_common::value::Value;
3use indexmap::IndexMap;
4use inflector::Inflector;
5use serde::Deserialize;
6use std::error::Error;
7use std::fmt::Write as _;
8use std::fs;
9use std::path::Path;
10
11#[derive(Deserialize, Debug)]
12#[serde(deny_unknown_fields)]
13pub struct Config {
14 version: u16,
15 #[serde(default)]
16 pub(crate) core: CoreConfig,
17 #[serde(default)]
18 context: ContextConfig,
19 #[cfg(feature = "eva")]
20 #[serde(default)]
21 pub(crate) eapi: EapiConfig,
22 #[serde(default)]
23 io: Vec<Io>,
24 #[serde(default)]
25 server: Vec<ServerConfig>,
26}
27
28fn default_stop_timeout() -> f64 {
29 crate::DEFAULT_STOP_TIMEOUT
30}
31
32#[derive(Deserialize, Debug)]
33#[serde(deny_unknown_fields)]
34pub(crate) struct CoreConfig {
35 #[serde(default = "default_stop_timeout")]
36 pub(crate) stop_timeout: f64,
37 #[serde(default)]
38 pub(crate) stack_size: Option<usize>,
39}
40
41impl Default for CoreConfig {
42 fn default() -> Self {
43 Self {
44 stop_timeout: default_stop_timeout(),
45 stack_size: None,
46 }
47 }
48}
49
50#[cfg(feature = "eva")]
51#[inline]
52fn default_eapi_action_pool_size() -> usize {
53 1
54}
55
56#[cfg(feature = "eva")]
57#[derive(Deserialize, Debug)]
58pub(crate) struct EapiConfig {
59 #[serde(default = "default_eapi_action_pool_size")]
60 pub(crate) action_pool_size: usize,
61}
62
63#[cfg(feature = "eva")]
64impl Default for EapiConfig {
65 fn default() -> Self {
66 Self {
67 action_pool_size: default_eapi_action_pool_size(),
68 }
69 }
70}
71
72#[derive(Deserialize, Debug)]
73#[serde(deny_unknown_fields)]
74struct ServerConfig {
75 kind: crate::server::Kind,
76 #[allow(dead_code)]
77 config: Value,
78}
79
80#[derive(Deserialize, Default, Debug)]
81#[serde(deny_unknown_fields)]
82struct ContextConfig {
83 #[serde(default)]
84 serialize: bool,
85 #[cfg(feature = "modbus")]
86 #[serde(default)]
87 modbus: Option<ModbusConfig>,
88 #[serde(default)]
89 fields: IndexMap<String, ContextField>,
90}
91
92#[cfg(feature = "modbus")]
93#[derive(Deserialize, Default, Debug)]
94#[serde(deny_unknown_fields)]
95pub(crate) struct ModbusConfig {
96 #[serde(default)]
97 pub(crate) c: usize,
98 #[serde(default)]
99 pub(crate) d: usize,
100 #[serde(default)]
101 pub(crate) i: usize,
102 #[serde(default)]
103 pub(crate) h: usize,
104}
105
106#[cfg(feature = "modbus")]
107impl ModbusConfig {
108 pub(crate) fn as_const_generics(&self) -> String {
109 format!("{}, {}, {}, {}", self.c, self.d, self.i, self.h)
110 }
111}
112
113#[derive(Deserialize, Debug)]
114#[serde(deny_unknown_fields)]
115struct Io {
116 id: String,
117 kind: Kind,
118 #[allow(dead_code)]
119 #[serde(default)]
120 config: Value,
121 #[allow(dead_code)]
122 #[serde(default)]
123 input: Vec<Value>,
124 #[allow(dead_code)]
125 #[serde(default)]
126 output: Vec<Value>,
127}
128
129#[derive(Deserialize, Debug)]
130#[serde(untagged)]
131enum ContextField {
132 Map(IndexMap<String, ContextField>),
133 Type(String),
134}
135
136impl Config {
137 pub fn load<P: AsRef<Path>>(path: P, context: &tera::Context) -> Result<Self, Box<dyn Error>> {
138 let config_tpl = fs::read_to_string(path)?;
139 let config: Config =
140 serde_yaml::from_str(&tera::Tera::default().render_str(&config_tpl, context)?)?;
141 if config.version != 1 {
142 unimplemented!("config version {} is not supported", config.version);
143 }
144 Ok(config)
145 }
146 #[allow(unreachable_code)]
147 pub fn generate_io<P: AsRef<Path>>(&self, path: P) -> Result<(), Box<dyn Error>> {
148 let mut m = codegen::Scope::new();
149 m.raw(crate::builder::AUTO_GENERATED);
150 m.raw("#[allow(unused_imports)]");
151 m.raw("use crate::plc::context::{Context, CONTEXT};");
152 #[allow(unused_mut)]
153 let mut funcs: Vec<String> = Vec::new();
154 for i in &self.io {
156 match i.kind {
157 #[cfg(feature = "modbus")]
158 Kind::Modbus => {
159 m.raw(
160 crate::io::modbus::generate_io(&i.id, &i.config, &i.input, &i.output)?
161 .to_string(),
162 );
163 }
164 #[cfg(feature = "opcua")]
165 Kind::OpcUa => {
166 m.raw(
167 crate::io::opcua::generate_io(&i.id, &i.config, &i.input, &i.output)?
168 .to_string(),
169 );
170 }
171 #[cfg(feature = "eva")]
172 Kind::Eapi => {
173 m.raw(
174 crate::io::eapi::generate_io(&i.id, &i.config, &i.input, &i.output)?
175 .to_string(),
176 );
177 }
178 }
179 funcs.push(format!("launch_datasync_{}", i.id.to_lowercase()));
180 }
181 let f_launch_datasync = m.new_fn("launch_datasync").vis("pub");
182 for function in funcs {
183 f_launch_datasync.line(format!("{}();", function));
184 }
185 #[allow(unused_variables)]
186 for (i, serv) in self.server.iter().enumerate() {
187 match serv.kind {
188 #[cfg(feature = "modbus")]
189 crate::server::Kind::Modbus => {
190 f_launch_datasync.push_block(crate::server::modbus::generate_server_launcher(
191 i + 1,
192 &serv.config,
193 self.context
194 .modbus
195 .as_ref()
196 .expect("modbus not specified in PLC context"),
197 )?);
198 }
199 }
200 }
201 let f_stop_datasync = m.new_fn("stop_datasync").vis("pub");
202 f_stop_datasync.line("::rplc::tasks::stop_if_no_output_or_sfn();");
203 super::write(path, m.to_string())?;
204 Ok(())
205 }
206 pub fn generate_context<P: AsRef<Path>>(&self, path: P) -> Result<(), Box<dyn Error>> {
207 let mut b = path.as_ref().to_path_buf();
208 b.pop();
209 b.pop();
210 let mut bm = b.clone();
211 b.push("plc_types.rs");
212 bm.push("plc_types");
213 bm.push("mod.rs");
214 let mut m = codegen::Scope::new();
215 m.raw(crate::builder::AUTO_GENERATED);
216 if b.exists() || bm.exists() {
217 m.raw("#[allow(clippy::wildcard_imports)]");
218 m.raw("use crate::plc_types::*;");
219 }
220 m.import("::rplc::export::parking_lot", "RwLock");
221 m.import("::rplc::export::once_cell::sync", "Lazy");
222 if self.context.serialize {
223 m.import("::rplc::export::serde", "Serialize");
224 m.import("::rplc::export::serde", "Deserialize");
225 m.import("::rplc::export::serde", "self");
226 }
227 m.raw("#[allow(dead_code)] pub(crate) static CONTEXT: Lazy<RwLock<Context>> = Lazy::new(<_>::default);");
228 generate_structs(
229 "Context",
230 &self.context.fields,
231 &mut m,
232 #[cfg(feature = "modbus")]
233 self.context.modbus.as_ref(),
234 self.context.serialize,
235 )?;
236 super::write(path, m.to_string())?;
237 Ok(())
238 }
239}
240
241fn parse_iec_type(tp: &str) -> &str {
242 match tp {
243 "BOOL" => "bool",
244 "BYTE" | "USINT" => "u8",
245 "WORD" | "UINT" => "u16",
246 "DWORD" | "UDINT" => "u32",
247 "LWORD" | "ULINT" => "u64",
248 "SINT" => "i8",
249 "INT" => "i16",
250 "DINT" => "i32",
251 "LINT" => "i64",
252 "REAL" => "f32",
253 "LREAL" => "f64",
254 _ => tp,
255 }
256}
257
258fn base_val(tp: &str) -> Option<&str> {
259 match tp {
260 "bool" => Some("false"),
261 "u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32" | "i64" | "isize" => {
262 Some("0")
263 }
264 "f32" | "f64" => Some("0.0"),
265 _ => None,
266 }
267}
268
269fn parse_type(t: &str) -> String {
270 let tp = t.trim();
271 if tp.ends_with(']') && !tp.starts_with('[') {
272 let mut sp = tp.split('[');
273 let mut result = String::new();
274 let base_tp = parse_iec_type(sp.next().unwrap());
275 for d in sp {
276 let mut size_s = d[0..d.len() - 1].trim().replace('_', "");
277 let boxed = if size_s.ends_with('!') {
278 size_s = size_s[..size_s.len() - 1].to_owned();
279 true
280 } else {
281 false
282 };
283 let size = size_s
284 .parse::<usize>()
285 .unwrap_or_else(|e| panic!("{e}: {size_s}"));
286 if result.is_empty() {
287 if boxed {
288 write!(result, "Box<[{}; {}]>", base_tp, size).unwrap();
289 } else {
290 write!(result, "[{}; {}]", base_tp, size).unwrap();
291 }
292 } else if boxed {
293 result = format!("Box<[{}; {}]>", result, size);
294 } else {
295 result = format!("[{}; {}]", result, size);
296 }
297 }
298 result
299 } else {
300 parse_iec_type(tp).to_owned()
301 }
302}
303
304fn generate_default(t: &str) -> String {
305 let tp = t.trim();
306 if tp.ends_with(']') && !tp.starts_with('[') {
307 let mut sp = tp.split('[');
308 let mut result = String::new();
309 let base_tp = parse_iec_type(sp.next().unwrap());
310 for d in sp {
311 let mut size_s = d[0..d.len() - 1].trim().replace('_', "");
312 let boxed = if size_s.ends_with('!') {
313 size_s = size_s[..size_s.len() - 1].to_owned();
314 true
315 } else {
316 false
317 };
318 let size = size_s
319 .parse::<usize>()
320 .unwrap_or_else(|e| panic!("{e}: {size_s}"));
321 if result.is_empty() {
322 if boxed {
323 write!(result, "Box::new(").unwrap();
324 }
325 if let Some(val) = base_val(base_tp) {
326 write!(result, "[{};{}]", val, size).unwrap();
327 } else {
328 write!(result, "::std::array::from_fn(|_| <_>::default())").unwrap();
329 }
330 if boxed {
331 write!(result, ")").unwrap();
332 }
333 } else {
334 let mut r = if boxed {
335 "Box::new([".to_owned()
336 } else {
337 "[".to_owned()
338 };
339 for _ in 0..size {
340 write!(r, "{},", result).unwrap();
341 }
342 write!(r, "]").unwrap();
343 if boxed {
344 write!(r, ")").unwrap();
345 }
346 result = r;
347 }
348 }
349 result
350 } else {
351 "<_>::default()".to_owned()
352 }
353}
354
355#[allow(clippy::too_many_lines)]
356fn generate_structs(
357 name: &str,
358 fields: &IndexMap<String, ContextField>,
359 scope: &mut codegen::Scope,
360 #[cfg(feature = "modbus")] modbus_config: Option<&ModbusConfig>,
361 serialize: bool,
362) -> Result<(), Box<dyn Error>> {
363 let mut st: codegen::Struct = codegen::Struct::new(name);
364 let mut st_impl: codegen::Impl = codegen::Impl::new(name);
365 st_impl.impl_trait("Default");
366 let default = st_impl.new_fn("default");
367 default.ret("Self");
368 default.line("Self {");
369 st.allow("dead_code")
370 .allow("clippy::module_name_repetitions")
371 .repr("C")
372 .vis("pub");
373 if serialize {
374 st.derive("Serialize").derive("Deserialize");
375 st.attr("serde(crate = \"self::serde\")");
376 }
377 for (k, v) in fields {
378 match v {
379 ContextField::Type(t) => {
380 let mut field = codegen::Field::new(k, parse_type(t));
381 field.vis("pub");
382 if serialize {
383 field.annotation.push("#[serde(default)]".to_owned());
384 }
385 default.line(format!("{}: {},", field.name, generate_default(t)));
386 st.push_field(field);
387 }
388 ContextField::Map(m) => {
389 let (mut field, sub_name) = if k.ends_with(']') {
390 let (number, field_name, boxed) = if let Some(pos) = k.rfind('[') {
391 let mut size_s = k[pos + 1..k.len() - 1].trim().replace('_', "");
392 let boxed = if size_s.ends_with('!') {
393 size_s = size_s[..size_s.len() - 1].to_owned();
394 true
395 } else {
396 false
397 };
398 let field = size_s.parse::<usize>().map_err(|e| {
399 eva_common::Error::invalid_params(format!(
400 "invalid struct name: {} ({})",
401 k, e
402 ))
403 })?;
404 (field, &k[..pos], boxed)
405 } else {
406 return Err(eva_common::Error::invalid_params(format!(
407 "invalid struct name: {}",
408 k
409 ))
410 .into());
411 };
412 let sub_name = format!("{}{}", name, field_name.to_title_case());
413 let field_value = if boxed {
414 format!("Box<[{}; {}]>", sub_name, number)
415 } else {
416 format!("[{}; {}]", sub_name, number)
417 };
418 (codegen::Field::new(field_name, field_value), sub_name)
419 } else {
420 let sub_name = format!("{}{}", name, k.to_title_case());
421 (codegen::Field::new(k, &sub_name), sub_name)
422 };
423 field.vis("pub");
424 if serialize {
425 field.annotation.push("#[serde(default)]".to_owned());
426 }
427 default.line(format!("{}: {},", field.name, generate_default(k)));
428 st.push_field(field);
429 generate_structs(
430 &sub_name,
431 m,
432 scope,
433 #[cfg(feature = "modbus")]
434 None,
435 serialize,
436 )?;
437 }
438 }
439 }
440 #[cfg(feature = "modbus")]
441 if let Some(c) = modbus_config {
442 let mut field = codegen::Field::new(
443 "modbus",
444 format!(
445 "::rplc::export::rmodbus::server::context::ModbusContext<{}>",
446 c.as_const_generics()
447 ),
448 );
449 field.vis("pub");
450 if serialize {
451 field.annotation.push("#[serde(default)]".to_owned());
452 }
453 st.push_field(field);
454 default.line("modbus: <_>::default(),");
455 }
456 default.line("}");
457 scope.push_struct(st);
458 scope.raw("#[allow(clippy::derivable_impls)]");
459 scope.push_impl(st_impl);
460 #[cfg(feature = "modbus")]
461 if let Some(c) = modbus_config {
462 let im = scope.new_impl(&format!(
463 "::rplc::server::modbus::SlaveContext<{}> for Context",
464 c.as_const_generics()
465 ));
466 {
467 let fn_ctx = im
468 .new_fn("modbus_context")
469 .arg_ref_self()
470 .ret(format!(
471 "&::rplc::export::rmodbus::server::context::ModbusContext<{}>",
472 c.as_const_generics()
473 ))
474 .attr("inline");
475 fn_ctx.line("&self.modbus");
476 }
477 {
478 let fn_ctx_mut = im
479 .new_fn("modbus_context_mut")
480 .arg_mut_self()
481 .ret(format!(
482 "&mut ::rplc::export::rmodbus::server::context::ModbusContext<{}>",
483 c.as_const_generics()
484 ))
485 .attr("inline");
486 fn_ctx_mut.line("&mut self.modbus");
487 }
488 }
489 Ok(())
490}