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