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