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 }
294 }
295 pub fn nt_mut(&mut self) -> &mut NtScalar {
297 self.data.nt_mut()
298 }
299
300 pub fn current_value(&self) -> ScalarValue {
301 match self.to_ntpayload() {
302 NtPayload::Scalar(nt) => nt.value,
303 NtPayload::ScalarArray(nt) => ScalarValue::I32(nt.value.len() as i32),
304 NtPayload::Table(nt) => ScalarValue::I32(nt.columns.len() as i32),
305 NtPayload::NdArray(nt) => ScalarValue::I32(nt.dimension.len() as i32),
306 }
307 }
308
309 pub fn set_scalar_value(&mut self, value: ScalarValue, compute_alarms: bool) -> bool {
310 let nt = match &mut self.data {
311 RecordData::Ai { nt, .. }
312 | RecordData::Ao { nt, .. }
313 | RecordData::Bi { nt, .. }
314 | RecordData::Bo { nt, .. }
315 | RecordData::StringIn { nt, .. }
316 | RecordData::StringOut { nt, .. } => nt,
317 _ => return false,
318 };
319
320 let changed = match (&mut nt.value, value) {
321 (ScalarValue::Bool(current), ScalarValue::Bool(v)) => {
322 if *current == v {
323 false
324 } else {
325 *current = v;
326 true
327 }
328 }
329 (ScalarValue::I32(current), ScalarValue::I32(v)) => {
330 if *current == v {
331 false
332 } else {
333 *current = v;
334 true
335 }
336 }
337 (ScalarValue::F64(current), ScalarValue::F64(v)) => {
338 if (*current - v).abs() < f64::EPSILON {
339 false
340 } else {
341 *current = v;
342 true
343 }
344 }
345 (ScalarValue::Str(current), ScalarValue::Str(v)) => {
346 if *current == v {
347 false
348 } else {
349 *current = v;
350 true
351 }
352 }
353 (ScalarValue::Bool(current), ScalarValue::I32(v)) => {
354 let next = v != 0;
355 if *current == next {
356 false
357 } else {
358 *current = next;
359 true
360 }
361 }
362 (ScalarValue::Bool(current), ScalarValue::F64(v)) => {
363 let next = v != 0.0;
364 if *current == next {
365 false
366 } else {
367 *current = next;
368 true
369 }
370 }
371 (ScalarValue::I32(current), ScalarValue::Bool(v)) => {
372 let next = if v { 1 } else { 0 };
373 if *current == next {
374 false
375 } else {
376 *current = next;
377 true
378 }
379 }
380 (ScalarValue::I32(current), ScalarValue::F64(v)) => {
381 let next = v as i32;
382 if *current == next {
383 false
384 } else {
385 *current = next;
386 true
387 }
388 }
389 (ScalarValue::F64(current), ScalarValue::Bool(v)) => {
390 let next = if v { 1.0 } else { 0.0 };
391 if (*current - next).abs() < f64::EPSILON {
392 false
393 } else {
394 *current = next;
395 true
396 }
397 }
398 (ScalarValue::F64(current), ScalarValue::I32(v)) => {
399 let next = v as f64;
400 if (*current - next).abs() < f64::EPSILON {
401 false
402 } else {
403 *current = next;
404 true
405 }
406 }
407 (ScalarValue::Str(current), ScalarValue::Bool(v)) => {
408 let next = if v { "1" } else { "0" }.to_string();
409 if *current == next {
410 false
411 } else {
412 *current = next;
413 true
414 }
415 }
416 (ScalarValue::Str(current), ScalarValue::I32(v)) => {
417 let next = v.to_string();
418 if *current == next {
419 false
420 } else {
421 *current = next;
422 true
423 }
424 }
425 (ScalarValue::Str(current), ScalarValue::F64(v)) => {
426 let next = v.to_string();
427 if *current == next {
428 false
429 } else {
430 *current = next;
431 true
432 }
433 }
434 (ScalarValue::Bool(current), ScalarValue::Str(v)) => {
435 let next = parse_bool_like(&v).unwrap_or(*current);
436 if *current == next {
437 false
438 } else {
439 *current = next;
440 true
441 }
442 }
443 (ScalarValue::I32(current), ScalarValue::Str(v)) => {
444 let next = v.parse::<i32>().unwrap_or(*current);
445 if *current == next {
446 false
447 } else {
448 *current = next;
449 true
450 }
451 }
452 (ScalarValue::F64(current), ScalarValue::Str(v)) => {
453 let next = v.parse::<f64>().unwrap_or(*current);
454 if (*current - next).abs() < f64::EPSILON {
455 false
456 } else {
457 *current = next;
458 true
459 }
460 }
461 (target, other) => {
464 let as_f64 = match &other {
465 ScalarValue::I8(v) => *v as f64,
466 ScalarValue::I16(v) => *v as f64,
467 ScalarValue::I64(v) => *v as f64,
468 ScalarValue::U8(v) => *v as f64,
469 ScalarValue::U16(v) => *v as f64,
470 ScalarValue::U32(v) => *v as f64,
471 ScalarValue::U64(v) => *v as f64,
472 ScalarValue::F32(v) => *v as f64,
473 _ => return false,
474 };
475 match target {
476 ScalarValue::Bool(current) => {
477 let next = as_f64 != 0.0;
478 if *current == next { false } else { *current = next; true }
479 }
480 ScalarValue::I32(current) => {
481 let next = as_f64 as i32;
482 if *current == next { false } else { *current = next; true }
483 }
484 ScalarValue::F64(current) => {
485 if (*current - as_f64).abs() < f64::EPSILON { false } else { *current = as_f64; true }
486 }
487 ScalarValue::Str(current) => {
488 let next = as_f64.to_string();
489 if *current == next { false } else { *current = next; true }
490 }
491 _ => false,
492 }
493 }
494 };
495 if changed && compute_alarms {
496 nt.update_alarm_from_value();
497 }
498 changed
499 }
500}
501
502fn parse_bool_like(input: &str) -> Option<bool> {
503 match input.trim().to_ascii_lowercase().as_str() {
504 "1" | "true" | "yes" | "on" => Some(true),
505 "0" | "false" | "no" | "off" => Some(false),
506 _ => None,
507 }
508}