1use std::collections::HashMap;
7use std::time::Duration;
8
9pub use spvirit_types::*;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum RecordType {
13 Ai,
14 Ao,
15 Bi,
16 Bo,
17 StringIn,
18 StringOut,
19 Waveform,
20 Aai,
21 Aao,
22 SubArray,
23 NtTable,
24 NtNdArray,
25}
26
27impl RecordType {
28 pub fn from_db_name(name: &str) -> Option<Self> {
29 match name.to_ascii_lowercase().as_str() {
30 "ai" => Some(Self::Ai),
31 "ao" => Some(Self::Ao),
32 "bi" => Some(Self::Bi),
33 "bo" => Some(Self::Bo),
34 "stringin" => Some(Self::StringIn),
35 "stringout" => Some(Self::StringOut),
36 "waveform" => Some(Self::Waveform),
37 "aai" => Some(Self::Aai),
38 "aao" => Some(Self::Aao),
39 "subarray" => Some(Self::SubArray),
40 _ => None,
41 }
42 }
43
44 pub fn is_output(&self) -> bool {
45 matches!(self, Self::Ao | Self::Bo | Self::StringOut | Self::Aao)
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub enum ScanMode {
51 Passive,
52 Periodic(Duration),
53 Event(String),
54 IoEvent(String),
55}
56
57#[derive(Debug, Clone, PartialEq)]
58pub enum LinkExpr {
59 Constant(ScalarValue),
60 DbLink {
61 target: String,
62 process_passive: bool,
63 maximize_severity: bool,
64 },
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub enum OutputMode {
69 Supervisory,
70 ClosedLoop,
71}
72
73#[derive(Debug, Clone, PartialEq)]
74pub struct DbCommonState {
75 pub desc: String,
76 pub scan: ScanMode,
77 pub pini: bool,
78 pub phas: i32,
79 pub pact: bool,
80 pub disa: bool,
81 pub sdis: Option<LinkExpr>,
82 pub diss: i32,
83 pub flnk: Option<LinkExpr>,
84}
85
86impl Default for DbCommonState {
87 fn default() -> Self {
88 Self {
89 desc: String::new(),
90 scan: ScanMode::Passive,
91 pini: false,
92 phas: 0,
93 pact: false,
94 disa: false,
95 sdis: None,
96 diss: 0,
97 flnk: None,
98 }
99 }
100}
101
102#[derive(Debug, Clone, PartialEq)]
103pub enum RecordData {
104 Ai {
105 nt: NtScalar,
106 inp: Option<LinkExpr>,
107 siml: Option<LinkExpr>,
108 siol: Option<LinkExpr>,
109 simm: bool,
110 },
111 Ao {
112 nt: NtScalar,
113 out: Option<LinkExpr>,
114 dol: Option<LinkExpr>,
115 omsl: OutputMode,
116 drvl: Option<f64>,
117 drvh: Option<f64>,
118 oroc: Option<f64>,
119 siml: Option<LinkExpr>,
120 siol: Option<LinkExpr>,
121 simm: bool,
122 },
123 Bi {
124 nt: NtScalar,
125 inp: Option<LinkExpr>,
126 znam: String,
127 onam: String,
128 siml: Option<LinkExpr>,
129 siol: Option<LinkExpr>,
130 simm: bool,
131 },
132 Bo {
133 nt: NtScalar,
134 out: Option<LinkExpr>,
135 dol: Option<LinkExpr>,
136 omsl: OutputMode,
137 znam: String,
138 onam: String,
139 siml: Option<LinkExpr>,
140 siol: Option<LinkExpr>,
141 simm: bool,
142 },
143 StringIn {
144 nt: NtScalar,
145 inp: Option<LinkExpr>,
146 siml: Option<LinkExpr>,
147 siol: Option<LinkExpr>,
148 simm: bool,
149 },
150 StringOut {
151 nt: NtScalar,
152 out: Option<LinkExpr>,
153 dol: Option<LinkExpr>,
154 omsl: OutputMode,
155 siml: Option<LinkExpr>,
156 siol: Option<LinkExpr>,
157 simm: bool,
158 },
159 Waveform {
160 nt: NtScalarArray,
161 inp: Option<LinkExpr>,
162 ftvl: String,
163 nelm: usize,
164 nord: usize,
165 },
166 Aai {
167 nt: NtScalarArray,
168 inp: Option<LinkExpr>,
169 ftvl: String,
170 nelm: usize,
171 nord: usize,
172 },
173 Aao {
174 nt: NtScalarArray,
175 out: Option<LinkExpr>,
176 dol: Option<LinkExpr>,
177 omsl: OutputMode,
178 ftvl: String,
179 nelm: usize,
180 nord: usize,
181 },
182 SubArray {
183 nt: NtScalarArray,
184 inp: Option<LinkExpr>,
185 ftvl: String,
186 malm: usize,
187 nelm: usize,
188 nord: usize,
189 indx: usize,
190 },
191 NtTable {
192 nt: NtTable,
193 inp: Option<LinkExpr>,
194 out: Option<LinkExpr>,
195 omsl: OutputMode,
196 },
197 NtNdArray {
198 nt: NtNdArray,
199 inp: Option<LinkExpr>,
200 out: Option<LinkExpr>,
201 omsl: OutputMode,
202 },
203}
204
205impl RecordData {
206 pub fn nt(&self) -> &NtScalar {
207 match self {
208 Self::Ai { nt, .. } => nt,
209 Self::Ao { nt, .. } => nt,
210 Self::Bi { nt, .. } => nt,
211 Self::Bo { nt, .. } => nt,
212 Self::StringIn { nt, .. } => nt,
213 Self::StringOut { nt, .. } => nt,
214 _ => panic!("record variant does not expose NtScalar"),
215 }
216 }
217
218 pub fn nt_mut(&mut self) -> &mut NtScalar {
219 match self {
220 Self::Ai { nt, .. } => nt,
221 Self::Ao { nt, .. } => nt,
222 Self::Bi { nt, .. } => nt,
223 Self::Bo { nt, .. } => nt,
224 Self::StringIn { nt, .. } => nt,
225 Self::StringOut { nt, .. } => nt,
226 _ => panic!("record variant does not expose NtScalar"),
227 }
228 }
229
230 pub fn payload(&self) -> NtPayload {
231 match self {
232 Self::Ai { nt, .. }
233 | Self::Ao { nt, .. }
234 | Self::Bi { nt, .. }
235 | Self::Bo { nt, .. }
236 | Self::StringIn { nt, .. }
237 | Self::StringOut { nt, .. } => NtPayload::Scalar(nt.clone()),
238 Self::Waveform { nt, .. }
239 | Self::Aai { nt, .. }
240 | Self::Aao { nt, .. }
241 | Self::SubArray { nt, .. } => NtPayload::ScalarArray(nt.clone()),
242 Self::NtTable { nt, .. } => NtPayload::Table(nt.clone()),
243 Self::NtNdArray { nt, .. } => NtPayload::NdArray(nt.clone()),
244 }
245 }
246}
247
248#[derive(Debug, Clone, PartialEq)]
249pub struct RecordInstance {
250 pub name: String,
251 pub record_type: RecordType,
252 pub common: DbCommonState,
253 pub data: RecordData,
254 pub raw_fields: HashMap<String, String>,
255}
256
257impl RecordInstance {
258 pub fn writable(&self) -> bool {
259 if self.record_type.is_output() {
260 return true;
261 }
262 match &self.data {
263 RecordData::Ai { simm: true, .. } => true,
264 RecordData::Waveform { .. } => true,
265 RecordData::NtTable { .. } => true,
266 RecordData::NtNdArray { .. } => true,
267 _ => false,
268 }
269 }
270
271 pub fn to_ntpayload(&self) -> NtPayload {
272 self.data.payload()
273 }
274
275 pub fn to_ntscalar(&self) -> NtScalar {
276 match self.to_ntpayload() {
277 NtPayload::Scalar(nt) => nt,
278 NtPayload::ScalarArray(nt) => {
279 let mut scalar = NtScalar::from_value(ScalarValue::I32(nt.value.len() as i32));
280 scalar.display_description = "Array length".to_string();
281 scalar
282 }
283 NtPayload::Table(nt) => {
284 let mut scalar = NtScalar::from_value(ScalarValue::I32(nt.columns.len() as i32));
285 scalar.display_description = "Table columns".to_string();
286 scalar
287 }
288 NtPayload::NdArray(nt) => {
289 let mut scalar = NtScalar::from_value(ScalarValue::I32(nt.dimension.len() as i32));
290 scalar.display_description = "NDArray dimensions".to_string();
291 scalar
292 }
293 NtPayload::Enum(nt) => {
294 let mut scalar = NtScalar::from_value(ScalarValue::I32(nt.index));
295 scalar.display_description = nt.selected().unwrap_or("").to_string();
296 scalar
297 }
298 NtPayload::Generic { fields, .. } => {
299 let mut scalar = NtScalar::from_value(ScalarValue::I32(fields.len() as i32));
300 scalar.display_description = "Generic structure".to_string();
301 scalar
302 }
303 }
304 }
305 pub fn nt_mut(&mut self) -> &mut NtScalar {
307 self.data.nt_mut()
308 }
309
310 pub fn current_value(&self) -> ScalarValue {
311 match self.to_ntpayload() {
312 NtPayload::Scalar(nt) => nt.value,
313 NtPayload::ScalarArray(nt) => ScalarValue::I32(nt.value.len() as i32),
314 NtPayload::Table(nt) => ScalarValue::I32(nt.columns.len() as i32),
315 NtPayload::NdArray(nt) => ScalarValue::I32(nt.dimension.len() as i32),
316 NtPayload::Enum(nt) => ScalarValue::I32(nt.index),
317 NtPayload::Generic { fields, .. } => ScalarValue::I32(fields.len() as i32),
318 }
319 }
320
321 pub fn set_scalar_value(&mut self, value: ScalarValue, compute_alarms: bool) -> bool {
322 let nt = match &mut self.data {
323 RecordData::Ai { nt, .. }
324 | RecordData::Ao { nt, .. }
325 | RecordData::Bi { nt, .. }
326 | RecordData::Bo { nt, .. }
327 | RecordData::StringIn { nt, .. }
328 | RecordData::StringOut { nt, .. } => nt,
329 _ => return false,
330 };
331
332 let changed = match (&mut nt.value, value) {
333 (ScalarValue::Bool(current), ScalarValue::Bool(v)) => {
334 if *current == v {
335 false
336 } else {
337 *current = v;
338 true
339 }
340 }
341 (ScalarValue::I32(current), ScalarValue::I32(v)) => {
342 if *current == v {
343 false
344 } else {
345 *current = v;
346 true
347 }
348 }
349 (ScalarValue::F64(current), ScalarValue::F64(v)) => {
350 if (*current - v).abs() < f64::EPSILON {
351 false
352 } else {
353 *current = v;
354 true
355 }
356 }
357 (ScalarValue::Str(current), ScalarValue::Str(v)) => {
358 if *current == v {
359 false
360 } else {
361 *current = v;
362 true
363 }
364 }
365 (ScalarValue::Bool(current), ScalarValue::I32(v)) => {
366 let next = v != 0;
367 if *current == next {
368 false
369 } else {
370 *current = next;
371 true
372 }
373 }
374 (ScalarValue::Bool(current), ScalarValue::F64(v)) => {
375 let next = v != 0.0;
376 if *current == next {
377 false
378 } else {
379 *current = next;
380 true
381 }
382 }
383 (ScalarValue::I32(current), ScalarValue::Bool(v)) => {
384 let next = if v { 1 } else { 0 };
385 if *current == next {
386 false
387 } else {
388 *current = next;
389 true
390 }
391 }
392 (ScalarValue::I32(current), ScalarValue::F64(v)) => {
393 let next = v as i32;
394 if *current == next {
395 false
396 } else {
397 *current = next;
398 true
399 }
400 }
401 (ScalarValue::F64(current), ScalarValue::Bool(v)) => {
402 let next = if v { 1.0 } else { 0.0 };
403 if (*current - next).abs() < f64::EPSILON {
404 false
405 } else {
406 *current = next;
407 true
408 }
409 }
410 (ScalarValue::F64(current), ScalarValue::I32(v)) => {
411 let next = v as f64;
412 if (*current - next).abs() < f64::EPSILON {
413 false
414 } else {
415 *current = next;
416 true
417 }
418 }
419 (ScalarValue::Str(current), ScalarValue::Bool(v)) => {
420 let next = if v { "1" } else { "0" }.to_string();
421 if *current == next {
422 false
423 } else {
424 *current = next;
425 true
426 }
427 }
428 (ScalarValue::Str(current), ScalarValue::I32(v)) => {
429 let next = v.to_string();
430 if *current == next {
431 false
432 } else {
433 *current = next;
434 true
435 }
436 }
437 (ScalarValue::Str(current), ScalarValue::F64(v)) => {
438 let next = v.to_string();
439 if *current == next {
440 false
441 } else {
442 *current = next;
443 true
444 }
445 }
446 (ScalarValue::Bool(current), ScalarValue::Str(v)) => {
447 let next = parse_bool_like(&v).unwrap_or(*current);
448 if *current == next {
449 false
450 } else {
451 *current = next;
452 true
453 }
454 }
455 (ScalarValue::I32(current), ScalarValue::Str(v)) => {
456 let next = v.parse::<i32>().unwrap_or(*current);
457 if *current == next {
458 false
459 } else {
460 *current = next;
461 true
462 }
463 }
464 (ScalarValue::F64(current), ScalarValue::Str(v)) => {
465 let next = v.parse::<f64>().unwrap_or(*current);
466 if (*current - next).abs() < f64::EPSILON {
467 false
468 } else {
469 *current = next;
470 true
471 }
472 }
473 (target, other) => {
476 let as_f64 = match &other {
477 ScalarValue::I8(v) => *v as f64,
478 ScalarValue::I16(v) => *v as f64,
479 ScalarValue::I64(v) => *v as f64,
480 ScalarValue::U8(v) => *v as f64,
481 ScalarValue::U16(v) => *v as f64,
482 ScalarValue::U32(v) => *v as f64,
483 ScalarValue::U64(v) => *v as f64,
484 ScalarValue::F32(v) => *v as f64,
485 _ => return false,
486 };
487 match target {
488 ScalarValue::Bool(current) => {
489 let next = as_f64 != 0.0;
490 if *current == next {
491 false
492 } else {
493 *current = next;
494 true
495 }
496 }
497 ScalarValue::I32(current) => {
498 let next = as_f64 as i32;
499 if *current == next {
500 false
501 } else {
502 *current = next;
503 true
504 }
505 }
506 ScalarValue::F64(current) => {
507 if (*current - as_f64).abs() < f64::EPSILON {
508 false
509 } else {
510 *current = as_f64;
511 true
512 }
513 }
514 ScalarValue::Str(current) => {
515 let next = as_f64.to_string();
516 if *current == next {
517 false
518 } else {
519 *current = next;
520 true
521 }
522 }
523 _ => false,
524 }
525 }
526 };
527 if changed && compute_alarms {
528 nt.update_alarm_from_value();
529 }
530 changed
531 }
532
533 pub fn set_array_value(&mut self, value: ScalarArrayValue) -> bool {
534 let (nt, nord, nelm) = match &mut self.data {
535 RecordData::Waveform { nt, nord, nelm, .. }
536 | RecordData::Aai { nt, nord, nelm, .. }
537 | RecordData::Aao { nt, nord, nelm, .. }
538 | RecordData::SubArray { nt, nord, nelm, .. } => (nt, nord, *nelm),
539 _ => return false,
540 };
541
542 let mut next = value;
543 truncate_scalar_array_to_nelm(&mut next, nelm);
544 if nt.value == next {
545 return false;
546 }
547
548 *nord = next.len();
549 nt.value = next;
550 true
551 }
552
553 pub fn set_nt_payload(&mut self, payload: NtPayload) -> bool {
554 match (&mut self.data, payload) {
555 (
556 RecordData::Ai { nt, .. }
557 | RecordData::Ao { nt, .. }
558 | RecordData::Bi { nt, .. }
559 | RecordData::Bo { nt, .. }
560 | RecordData::StringIn { nt, .. }
561 | RecordData::StringOut { nt, .. },
562 NtPayload::Scalar(next),
563 ) => {
564 if *nt == next {
565 false
566 } else {
567 *nt = next;
568 true
569 }
570 }
571 (
572 RecordData::Waveform { nt, nord, nelm, .. }
573 | RecordData::Aai { nt, nord, nelm, .. }
574 | RecordData::Aao { nt, nord, nelm, .. }
575 | RecordData::SubArray { nt, nord, nelm, .. },
576 NtPayload::ScalarArray(mut next),
577 ) => {
578 truncate_scalar_array_to_nelm(&mut next.value, *nelm);
579 let next_len = next.value.len();
580 if *nt == next {
581 false
582 } else {
583 *nord = next_len;
584 *nt = next;
585 true
586 }
587 }
588 (RecordData::NtTable { nt, .. }, NtPayload::Table(next)) => {
589 if next.validate().is_err() || *nt == next {
590 false
591 } else {
592 *nt = next;
593 true
594 }
595 }
596 (RecordData::NtNdArray { nt, .. }, NtPayload::NdArray(next)) => {
597 if next.validate().is_err() || *nt == next {
598 false
599 } else {
600 *nt = next;
601 true
602 }
603 }
604 _ => false,
605 }
606 }
607}
608
609fn truncate_scalar_array_to_nelm(value: &mut ScalarArrayValue, nelm: usize) {
610 match value {
611 ScalarArrayValue::Bool(v) => v.truncate(nelm),
612 ScalarArrayValue::I8(v) => v.truncate(nelm),
613 ScalarArrayValue::I16(v) => v.truncate(nelm),
614 ScalarArrayValue::I32(v) => v.truncate(nelm),
615 ScalarArrayValue::I64(v) => v.truncate(nelm),
616 ScalarArrayValue::U8(v) => v.truncate(nelm),
617 ScalarArrayValue::U16(v) => v.truncate(nelm),
618 ScalarArrayValue::U32(v) => v.truncate(nelm),
619 ScalarArrayValue::U64(v) => v.truncate(nelm),
620 ScalarArrayValue::F32(v) => v.truncate(nelm),
621 ScalarArrayValue::F64(v) => v.truncate(nelm),
622 ScalarArrayValue::Str(v) => v.truncate(nelm),
623 }
624}
625
626fn parse_bool_like(input: &str) -> Option<bool> {
627 match input.trim().to_ascii_lowercase().as_str() {
628 "1" | "true" | "yes" | "on" => Some(true),
629 "0" | "false" | "no" | "off" => Some(false),
630 _ => None,
631 }
632}