1use std::collections::HashMap;
7use std::time::Duration;
8
9pub use spvirit_types::*;
10
11fn now_nt_timestamp() -> NtTimeStamp {
13 let now = std::time::SystemTime::now()
14 .duration_since(std::time::UNIX_EPOCH)
15 .unwrap_or_default();
16 NtTimeStamp {
17 seconds_past_epoch: now.as_secs() as i64,
18 nanoseconds: now.subsec_nanos() as i32,
19 user_tag: 0,
20 }
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum RecordType {
25 Ai,
26 Ao,
27 Bi,
28 Bo,
29 StringIn,
30 StringOut,
31 Waveform,
32 Aai,
33 Aao,
34 SubArray,
35 NtTable,
36 NtNdArray,
37 Mbbi,
38 Mbbo,
39 Generic,
40 LongIn,
41 LongOut,
42}
43
44impl RecordType {
45 pub fn from_db_name(name: &str) -> Option<Self> {
49 match name.to_ascii_lowercase().as_str() {
50 "ai" => Some(Self::Ai),
51 "ao" => Some(Self::Ao),
52 "bi" => Some(Self::Bi),
53 "bo" => Some(Self::Bo),
54 "stringin" => Some(Self::StringIn),
55 "stringout" => Some(Self::StringOut),
56 "waveform" => Some(Self::Waveform),
57 "aai" => Some(Self::Aai),
58 "aao" => Some(Self::Aao),
59 "subarray" => Some(Self::SubArray),
60 "mbbi" | "ntenum" => Some(Self::Mbbi),
61 "mbbo" => Some(Self::Mbbo),
62 _ => None,
63 }
64 }
65
66 pub fn is_output(&self) -> bool {
67 matches!(
68 self,
69 Self::Ao | Self::Bo | Self::StringOut | Self::Aao | Self::Mbbo | Self::LongOut
70 )
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub enum ScanMode {
76 Passive,
77 Periodic(Duration),
78 Event(String),
79 IoEvent(String),
80}
81
82#[derive(Debug, Clone, PartialEq)]
83pub enum LinkExpr {
84 Constant(ScalarValue),
85 DbLink {
86 target: String,
87 process_passive: bool,
88 maximize_severity: bool,
89 },
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
93pub enum OutputMode {
94 Supervisory,
95 ClosedLoop,
96}
97
98#[derive(Debug, Clone, PartialEq)]
99pub struct DbCommonState {
100 pub desc: String,
101 pub scan: ScanMode,
102 pub pini: bool,
103 pub phas: i32,
104 pub pact: bool,
105 pub disa: bool,
106 pub sdis: Option<LinkExpr>,
107 pub diss: i32,
108 pub flnk: Option<LinkExpr>,
109}
110
111impl Default for DbCommonState {
112 fn default() -> Self {
113 Self {
114 desc: String::new(),
115 scan: ScanMode::Passive,
116 pini: false,
117 phas: 0,
118 pact: false,
119 disa: false,
120 sdis: None,
121 diss: 0,
122 flnk: None,
123 }
124 }
125}
126
127#[derive(Debug, Clone, PartialEq)]
128pub enum RecordData {
129 Ai {
130 nt: NtScalar,
131 inp: Option<LinkExpr>,
132 siml: Option<LinkExpr>,
133 siol: Option<LinkExpr>,
134 simm: bool,
135 },
136 Ao {
137 nt: NtScalar,
138 out: Option<LinkExpr>,
139 dol: Option<LinkExpr>,
140 omsl: OutputMode,
141 drvl: Option<f64>,
142 drvh: Option<f64>,
143 oroc: Option<f64>,
144 siml: Option<LinkExpr>,
145 siol: Option<LinkExpr>,
146 simm: bool,
147 },
148 Bi {
149 nt: NtScalar,
150 inp: Option<LinkExpr>,
151 znam: String,
152 onam: String,
153 siml: Option<LinkExpr>,
154 siol: Option<LinkExpr>,
155 simm: bool,
156 },
157 Bo {
158 nt: NtScalar,
159 out: Option<LinkExpr>,
160 dol: Option<LinkExpr>,
161 omsl: OutputMode,
162 znam: String,
163 onam: String,
164 siml: Option<LinkExpr>,
165 siol: Option<LinkExpr>,
166 simm: bool,
167 },
168 StringIn {
169 nt: NtScalar,
170 inp: Option<LinkExpr>,
171 siml: Option<LinkExpr>,
172 siol: Option<LinkExpr>,
173 simm: bool,
174 },
175 StringOut {
176 nt: NtScalar,
177 out: Option<LinkExpr>,
178 dol: Option<LinkExpr>,
179 omsl: OutputMode,
180 siml: Option<LinkExpr>,
181 siol: Option<LinkExpr>,
182 simm: bool,
183 },
184 Waveform {
185 nt: NtScalarArray,
186 inp: Option<LinkExpr>,
187 ftvl: String,
188 nelm: usize,
189 nord: usize,
190 },
191 Aai {
192 nt: NtScalarArray,
193 inp: Option<LinkExpr>,
194 ftvl: String,
195 nelm: usize,
196 nord: usize,
197 },
198 Aao {
199 nt: NtScalarArray,
200 out: Option<LinkExpr>,
201 dol: Option<LinkExpr>,
202 omsl: OutputMode,
203 ftvl: String,
204 nelm: usize,
205 nord: usize,
206 },
207 SubArray {
208 nt: NtScalarArray,
209 inp: Option<LinkExpr>,
210 ftvl: String,
211 malm: usize,
212 nelm: usize,
213 nord: usize,
214 indx: usize,
215 },
216 NtTable {
217 nt: NtTable,
218 inp: Option<LinkExpr>,
219 out: Option<LinkExpr>,
220 omsl: OutputMode,
221 },
222 NtNdArray {
223 nt: NtNdArray,
224 inp: Option<LinkExpr>,
225 out: Option<LinkExpr>,
226 omsl: OutputMode,
227 },
228 NtEnum {
229 nt: NtEnum,
230 inp: Option<LinkExpr>,
231 out: Option<LinkExpr>,
232 omsl: OutputMode,
233 },
234 Generic {
235 struct_id: String,
236 fields: Vec<(String, PvValue)>,
237 inp: Option<LinkExpr>,
238 out: Option<LinkExpr>,
239 omsl: OutputMode,
240 },
241}
242
243impl RecordData {
244 pub fn nt(&self) -> &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 nt_mut(&mut self) -> &mut NtScalar {
257 match self {
258 Self::Ai { nt, .. } => nt,
259 Self::Ao { nt, .. } => nt,
260 Self::Bi { nt, .. } => nt,
261 Self::Bo { nt, .. } => nt,
262 Self::StringIn { nt, .. } => nt,
263 Self::StringOut { nt, .. } => nt,
264 _ => panic!("record variant does not expose NtScalar"),
265 }
266 }
267
268 pub fn payload(&self) -> NtPayload {
269 match self {
270 Self::Ai { nt, .. }
271 | Self::Ao { nt, .. }
272 | Self::Bi { nt, .. }
273 | Self::Bo { nt, .. }
274 | Self::StringIn { nt, .. }
275 | Self::StringOut { nt, .. } => NtPayload::Scalar(nt.clone()),
276 Self::Waveform { nt, .. }
277 | Self::Aai { nt, .. }
278 | Self::Aao { nt, .. }
279 | Self::SubArray { nt, .. } => NtPayload::ScalarArray(nt.clone()),
280 Self::NtTable { nt, .. } => NtPayload::Table(nt.clone()),
281 Self::NtNdArray { nt, .. } => NtPayload::NdArray(nt.clone()),
282 Self::NtEnum { nt, .. } => NtPayload::Enum(nt.clone()),
283 Self::Generic {
284 struct_id, fields, ..
285 } => NtPayload::Generic {
286 struct_id: struct_id.clone(),
287 fields: fields.clone(),
288 },
289 }
290 }
291}
292
293#[derive(Debug, Clone, PartialEq)]
294pub struct RecordInstance {
295 pub name: String,
296 pub record_type: RecordType,
297 pub common: DbCommonState,
298 pub data: RecordData,
299 pub raw_fields: HashMap<String, String>,
300}
301
302impl RecordInstance {
303 pub fn writable(&self) -> bool {
304 if self.record_type.is_output() {
305 return true;
306 }
307 match &self.data {
308 RecordData::Ai { simm: true, .. } => true,
309 RecordData::Waveform { .. } => true,
310 RecordData::NtTable { .. } => true,
311 RecordData::NtNdArray { .. } => true,
312 RecordData::NtEnum { .. } => true,
313 RecordData::Generic { .. } => true,
314 _ => false,
315 }
316 }
317
318 pub fn to_ntpayload(&self) -> NtPayload {
319 self.data.payload()
320 }
321
322 pub fn to_ntscalar(&self) -> NtScalar {
323 match self.to_ntpayload() {
324 NtPayload::Scalar(nt) => nt,
325 NtPayload::ScalarArray(nt) => {
326 let mut scalar = NtScalar::from_value(ScalarValue::I32(nt.value.len() as i32));
327 scalar.display_description = "Array length".to_string();
328 scalar
329 }
330 NtPayload::Table(nt) => {
331 let mut scalar = NtScalar::from_value(ScalarValue::I32(nt.columns.len() as i32));
332 scalar.display_description = "Table columns".to_string();
333 scalar
334 }
335 NtPayload::NdArray(nt) => {
336 let mut scalar = NtScalar::from_value(ScalarValue::I32(nt.dimension.len() as i32));
337 scalar.display_description = "NDArray dimensions".to_string();
338 scalar
339 }
340 NtPayload::Enum(nt) => {
341 let mut scalar = NtScalar::from_value(ScalarValue::I32(nt.index));
342 scalar.display_description = nt.selected().unwrap_or("").to_string();
343 scalar
344 }
345 NtPayload::Generic { fields, .. } => {
346 let mut scalar = NtScalar::from_value(ScalarValue::I32(fields.len() as i32));
347 scalar.display_description = "Generic structure".to_string();
348 scalar
349 }
350 }
351 }
352 pub fn nt_mut(&mut self) -> &mut NtScalar {
354 self.data.nt_mut()
355 }
356
357 pub(crate) fn nt_scalar_mut(&mut self) -> Option<&mut NtScalar> {
360 match &mut self.data {
361 RecordData::Ai { nt, .. }
362 | RecordData::Ao { nt, .. }
363 | RecordData::Bi { nt, .. }
364 | RecordData::Bo { nt, .. }
365 | RecordData::StringIn { nt, .. }
366 | RecordData::StringOut { nt, .. } => Some(nt),
367 _ => None,
368 }
369 }
370
371 pub fn current_value(&self) -> ScalarValue {
372 match self.to_ntpayload() {
373 NtPayload::Scalar(nt) => nt.value,
374 NtPayload::ScalarArray(nt) => ScalarValue::I32(nt.value.len() as i32),
375 NtPayload::Table(nt) => ScalarValue::I32(nt.columns.len() as i32),
376 NtPayload::NdArray(nt) => ScalarValue::I32(nt.dimension.len() as i32),
377 NtPayload::Enum(nt) => ScalarValue::I32(nt.index),
378 NtPayload::Generic { fields, .. } => ScalarValue::I32(fields.len() as i32),
379 }
380 }
381
382 pub(crate) fn stamp_missing_timestamps(&mut self) {
388 let ts = now_nt_timestamp();
389 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, .. } => {
396 if nt.time_stamp.is_none() {
397 nt.time_stamp = Some(ts);
398 }
399 }
400 RecordData::Waveform { nt, .. }
401 | RecordData::Aai { nt, .. }
402 | RecordData::Aao { nt, .. }
403 | RecordData::SubArray { nt, .. } => {
404 if nt.time_stamp == NtTimeStamp::default() {
405 nt.time_stamp = ts;
406 }
407 }
408 RecordData::NtEnum { nt, .. } => {
409 if nt.time_stamp == NtTimeStamp::default() {
410 nt.time_stamp = ts;
411 }
412 }
413 RecordData::NtTable { nt, .. } => {
414 if nt.time_stamp.is_none() {
415 nt.time_stamp = Some(ts);
416 }
417 }
418 RecordData::NtNdArray { nt, .. } => {
419 if nt.time_stamp.is_none() {
420 nt.time_stamp = Some(ts.clone());
421 }
422 if nt.data_time_stamp == NtTimeStamp::default() {
423 nt.data_time_stamp = ts;
424 }
425 }
426 RecordData::Generic { .. } => {}
427 }
428 }
429
430 pub fn set_scalar_value(&mut self, value: ScalarValue, compute_alarms: bool) -> bool {
431 if let RecordData::NtEnum { nt, .. } = &mut self.data {
432 let idx = match &value {
433 ScalarValue::I32(i) => *i,
434 ScalarValue::I16(i) => *i as i32,
435 ScalarValue::I8(i) => *i as i32,
436 ScalarValue::I64(i) => *i as i32,
437 _ => return false,
438 };
439 if idx < 0 || (idx as usize) >= nt.choices.len() {
440 return false; }
442 if nt.index == idx {
443 return false;
444 }
445 nt.index = idx;
446 nt.time_stamp = now_nt_timestamp();
449 return true;
450 }
451
452 let nt = match &mut self.data {
453 RecordData::Ai { nt, .. }
454 | RecordData::Ao { nt, .. }
455 | RecordData::Bi { nt, .. }
456 | RecordData::Bo { nt, .. }
457 | RecordData::StringIn { nt, .. }
458 | RecordData::StringOut { nt, .. } => nt,
459 _ => return false,
460 };
461
462 let changed = match (&mut nt.value, value) {
463 (ScalarValue::I8(current), ScalarValue::I8(v)) => {
469 if *current == v {
470 false
471 } else {
472 *current = v;
473 true
474 }
475 }
476 (ScalarValue::I16(current), ScalarValue::I16(v)) => {
477 if *current == v {
478 false
479 } else {
480 *current = v;
481 true
482 }
483 }
484 (ScalarValue::I64(current), ScalarValue::I64(v)) => {
485 if *current == v {
486 false
487 } else {
488 *current = v;
489 true
490 }
491 }
492 (ScalarValue::U8(current), ScalarValue::U8(v)) => {
493 if *current == v {
494 false
495 } else {
496 *current = v;
497 true
498 }
499 }
500 (ScalarValue::U16(current), ScalarValue::U16(v)) => {
501 if *current == v {
502 false
503 } else {
504 *current = v;
505 true
506 }
507 }
508 (ScalarValue::U32(current), ScalarValue::U32(v)) => {
509 if *current == v {
510 false
511 } else {
512 *current = v;
513 true
514 }
515 }
516 (ScalarValue::U64(current), ScalarValue::U64(v)) => {
517 if *current == v {
518 false
519 } else {
520 *current = v;
521 true
522 }
523 }
524 (ScalarValue::F32(current), ScalarValue::F32(v)) => {
525 if (*current - v).abs() < f32::EPSILON {
526 false
527 } else {
528 *current = v;
529 true
530 }
531 }
532 (ScalarValue::Bool(current), ScalarValue::Bool(v)) => {
533 if *current == v {
534 false
535 } else {
536 *current = v;
537 true
538 }
539 }
540 (ScalarValue::I32(current), ScalarValue::I32(v)) => {
541 if *current == v {
542 false
543 } else {
544 *current = v;
545 true
546 }
547 }
548 (ScalarValue::F64(current), ScalarValue::F64(v)) => {
549 if (*current - v).abs() < f64::EPSILON {
550 false
551 } else {
552 *current = v;
553 true
554 }
555 }
556 (ScalarValue::Str(current), ScalarValue::Str(v)) => {
557 if *current == v {
558 false
559 } else {
560 *current = v;
561 true
562 }
563 }
564 (ScalarValue::Bool(current), ScalarValue::I32(v)) => {
565 let next = v != 0;
566 if *current == next {
567 false
568 } else {
569 *current = next;
570 true
571 }
572 }
573 (ScalarValue::Bool(current), ScalarValue::F64(v)) => {
574 let next = v != 0.0;
575 if *current == next {
576 false
577 } else {
578 *current = next;
579 true
580 }
581 }
582 (ScalarValue::I32(current), ScalarValue::Bool(v)) => {
583 let next = if v { 1 } else { 0 };
584 if *current == next {
585 false
586 } else {
587 *current = next;
588 true
589 }
590 }
591 (ScalarValue::I32(current), ScalarValue::F64(v)) => {
592 let next = v as i32;
593 if *current == next {
594 false
595 } else {
596 *current = next;
597 true
598 }
599 }
600 (ScalarValue::F64(current), ScalarValue::Bool(v)) => {
601 let next = if v { 1.0 } else { 0.0 };
602 if (*current - next).abs() < f64::EPSILON {
603 false
604 } else {
605 *current = next;
606 true
607 }
608 }
609 (ScalarValue::F64(current), ScalarValue::I32(v)) => {
610 let next = v as f64;
611 if (*current - next).abs() < f64::EPSILON {
612 false
613 } else {
614 *current = next;
615 true
616 }
617 }
618 (ScalarValue::Str(current), ScalarValue::Bool(v)) => {
619 let next = if v { "1" } else { "0" }.to_string();
620 if *current == next {
621 false
622 } else {
623 *current = next;
624 true
625 }
626 }
627 (ScalarValue::Str(current), ScalarValue::I32(v)) => {
628 let next = v.to_string();
629 if *current == next {
630 false
631 } else {
632 *current = next;
633 true
634 }
635 }
636 (ScalarValue::Str(current), ScalarValue::F64(v)) => {
637 let next = v.to_string();
638 if *current == next {
639 false
640 } else {
641 *current = next;
642 true
643 }
644 }
645 (ScalarValue::Bool(current), ScalarValue::Str(v)) => {
646 let next = parse_bool_like(&v).unwrap_or(*current);
647 if *current == next {
648 false
649 } else {
650 *current = next;
651 true
652 }
653 }
654 (ScalarValue::I32(current), ScalarValue::Str(v)) => {
655 let next = v.parse::<i32>().unwrap_or(*current);
656 if *current == next {
657 false
658 } else {
659 *current = next;
660 true
661 }
662 }
663 (ScalarValue::F64(current), ScalarValue::Str(v)) => {
664 let next = v.parse::<f64>().unwrap_or(*current);
665 if (*current - next).abs() < f64::EPSILON {
666 false
667 } else {
668 *current = next;
669 true
670 }
671 }
672 (target, other) => {
675 let as_f64 = match &other {
676 ScalarValue::I8(v) => *v as f64,
677 ScalarValue::I16(v) => *v as f64,
678 ScalarValue::I64(v) => *v as f64,
679 ScalarValue::U8(v) => *v as f64,
680 ScalarValue::U16(v) => *v as f64,
681 ScalarValue::U32(v) => *v as f64,
682 ScalarValue::U64(v) => *v as f64,
683 ScalarValue::F32(v) => *v as f64,
684 ScalarValue::I32(v) => *v as f64,
690 ScalarValue::F64(v) => *v,
691 ScalarValue::Bool(v) => {
692 if *v {
693 1.0
694 } else {
695 0.0
696 }
697 }
698 _ => return false,
699 };
700 let as_i128: Option<i128> = match &other {
706 ScalarValue::I8(v) => Some(*v as i128),
707 ScalarValue::I16(v) => Some(*v as i128),
708 ScalarValue::I32(v) => Some(*v as i128),
709 ScalarValue::I64(v) => Some(*v as i128),
710 ScalarValue::U8(v) => Some(*v as i128),
711 ScalarValue::U16(v) => Some(*v as i128),
712 ScalarValue::U32(v) => Some(*v as i128),
713 ScalarValue::U64(v) => Some(*v as i128),
714 ScalarValue::Bool(v) => Some(if *v { 1 } else { 0 }),
715 _ => None,
716 };
717 match target {
718 ScalarValue::Bool(current) => {
719 let next = as_f64 != 0.0;
720 if *current == next {
721 false
722 } else {
723 *current = next;
724 true
725 }
726 }
727 ScalarValue::I32(current) => {
728 let next = as_f64 as i32;
729 if *current == next {
730 false
731 } else {
732 *current = next;
733 true
734 }
735 }
736 ScalarValue::F64(current) => {
737 if (*current - as_f64).abs() < f64::EPSILON {
738 false
739 } else {
740 *current = as_f64;
741 true
742 }
743 }
744 ScalarValue::Str(current) => {
745 let next = as_f64.to_string();
746 if *current == next {
747 false
748 } else {
749 *current = next;
750 true
751 }
752 }
753 ScalarValue::I8(current) => {
759 let next = as_i128.map(|i| i as i8).unwrap_or(as_f64 as i8);
760 if *current == next {
761 false
762 } else {
763 *current = next;
764 true
765 }
766 }
767 ScalarValue::I16(current) => {
768 let next = as_i128.map(|i| i as i16).unwrap_or(as_f64 as i16);
769 if *current == next {
770 false
771 } else {
772 *current = next;
773 true
774 }
775 }
776 ScalarValue::I64(current) => {
777 let next = as_i128.map(|i| i as i64).unwrap_or(as_f64 as i64);
778 if *current == next {
779 false
780 } else {
781 *current = next;
782 true
783 }
784 }
785 ScalarValue::U8(current) => {
786 let next = as_i128.map(|i| i as u8).unwrap_or(as_f64 as u8);
787 if *current == next {
788 false
789 } else {
790 *current = next;
791 true
792 }
793 }
794 ScalarValue::U16(current) => {
795 let next = as_i128.map(|i| i as u16).unwrap_or(as_f64 as u16);
796 if *current == next {
797 false
798 } else {
799 *current = next;
800 true
801 }
802 }
803 ScalarValue::U32(current) => {
804 let next = as_i128.map(|i| i as u32).unwrap_or(as_f64 as u32);
805 if *current == next {
806 false
807 } else {
808 *current = next;
809 true
810 }
811 }
812 ScalarValue::U64(current) => {
813 let next = as_i128.map(|i| i as u64).unwrap_or(as_f64 as u64);
814 if *current == next {
815 false
816 } else {
817 *current = next;
818 true
819 }
820 }
821 ScalarValue::F32(current) => {
822 let next = as_f64 as f32;
823 if (*current - next).abs() < f32::EPSILON {
824 false
825 } else {
826 *current = next;
827 true
828 }
829 }
830 }
831 }
832 };
833 if changed {
834 nt.time_stamp = Some(now_nt_timestamp());
841 if compute_alarms {
842 nt.update_alarm_from_value();
843 }
844 }
845 changed
846 }
847
848 pub fn set_array_value(&mut self, value: ScalarArrayValue) -> bool {
849 let ts = now_nt_timestamp();
853 if let RecordData::NtNdArray { nt, .. } = &mut self.data {
855 if nt.value == value {
856 return false;
857 }
858 nt.value = value;
859 nt.time_stamp = Some(ts.clone());
860 nt.data_time_stamp = ts;
861 return true;
862 }
863 let (nt, nord, nelm) = match &mut self.data {
864 RecordData::Waveform { nt, nord, nelm, .. }
865 | RecordData::Aai { nt, nord, nelm, .. }
866 | RecordData::Aao { nt, nord, nelm, .. }
867 | RecordData::SubArray { nt, nord, nelm, .. } => (nt, nord, *nelm),
868 _ => return false,
869 };
870
871 let mut next = value;
872 truncate_scalar_array_to_nelm(&mut next, nelm);
873 if nt.value == next {
874 return false;
875 }
876
877 *nord = next.len();
878 nt.value = next;
879 nt.time_stamp = ts;
880 true
881 }
882
883 pub fn set_nt_payload(&mut self, payload: NtPayload) -> bool {
884 match (&mut self.data, payload) {
885 (
886 RecordData::Ai { nt, .. }
887 | RecordData::Ao { nt, .. }
888 | RecordData::Bi { nt, .. }
889 | RecordData::Bo { nt, .. }
890 | RecordData::StringIn { nt, .. }
891 | RecordData::StringOut { nt, .. },
892 NtPayload::Scalar(mut next),
893 ) => {
894 if *nt == next {
895 false
896 } else {
897 if next.time_stamp.is_none() {
901 next.time_stamp = Some(now_nt_timestamp());
902 }
903 *nt = next;
904 true
905 }
906 }
907 (
908 RecordData::Waveform { nt, nord, nelm, .. }
909 | RecordData::Aai { nt, nord, nelm, .. }
910 | RecordData::Aao { nt, nord, nelm, .. }
911 | RecordData::SubArray { nt, nord, nelm, .. },
912 NtPayload::ScalarArray(mut next),
913 ) => {
914 truncate_scalar_array_to_nelm(&mut next.value, *nelm);
915 let next_len = next.value.len();
916 if *nt == next {
917 false
918 } else {
919 if next.time_stamp == NtTimeStamp::default() {
922 next.time_stamp = now_nt_timestamp();
923 }
924 *nord = next_len;
925 *nt = next;
926 true
927 }
928 }
929 (RecordData::NtTable { nt, .. }, NtPayload::Table(mut next)) => {
930 if next.validate().is_err() || *nt == next {
931 false
932 } else {
933 if next.time_stamp.is_none() {
934 next.time_stamp = Some(now_nt_timestamp());
935 }
936 *nt = next;
937 true
938 }
939 }
940 (RecordData::NtNdArray { nt, .. }, NtPayload::NdArray(mut next)) => {
941 if next.validate().is_err() || *nt == next {
942 false
943 } else {
944 let ts = now_nt_timestamp();
945 if next.time_stamp.is_none() {
946 next.time_stamp = Some(ts.clone());
947 }
948 if next.data_time_stamp == NtTimeStamp::default() {
949 next.data_time_stamp = ts;
950 }
951 *nt = next;
952 true
953 }
954 }
955 (RecordData::NtEnum { nt, .. }, NtPayload::Enum(mut next)) => {
956 if *nt == next {
957 false
958 } else {
959 if next.time_stamp == NtTimeStamp::default() {
960 next.time_stamp = now_nt_timestamp();
961 }
962 *nt = next;
963 true
964 }
965 }
966 (
967 RecordData::Generic {
968 struct_id, fields, ..
969 },
970 NtPayload::Generic {
971 struct_id: next_id,
972 fields: next_fields,
973 },
974 ) => {
975 if *struct_id == next_id && *fields == next_fields {
976 false
977 } else {
978 *struct_id = next_id;
979 *fields = next_fields;
980 true
981 }
982 }
983 _ => false,
984 }
985 }
986}
987
988fn truncate_scalar_array_to_nelm(value: &mut ScalarArrayValue, nelm: usize) {
989 match value {
990 ScalarArrayValue::Bool(v) => v.truncate(nelm),
991 ScalarArrayValue::I8(v) => v.truncate(nelm),
992 ScalarArrayValue::I16(v) => v.truncate(nelm),
993 ScalarArrayValue::I32(v) => v.truncate(nelm),
994 ScalarArrayValue::I64(v) => v.truncate(nelm),
995 ScalarArrayValue::U8(v) => v.truncate(nelm),
996 ScalarArrayValue::U16(v) => v.truncate(nelm),
997 ScalarArrayValue::U32(v) => v.truncate(nelm),
998 ScalarArrayValue::U64(v) => v.truncate(nelm),
999 ScalarArrayValue::F32(v) => v.truncate(nelm),
1000 ScalarArrayValue::F64(v) => v.truncate(nelm),
1001 ScalarArrayValue::Str(v) => v.truncate(nelm),
1002 }
1003}
1004
1005fn parse_bool_like(input: &str) -> Option<bool> {
1006 match input.trim().to_ascii_lowercase().as_str() {
1007 "1" | "true" | "yes" | "on" => Some(true),
1008 "0" | "false" | "no" | "off" => Some(false),
1009 _ => None,
1010 }
1011}