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