1use std::collections::HashMap;
7
8#[derive(Debug, Clone, PartialEq)]
9pub enum ScalarValue {
10 Bool(bool),
11 I8(i8),
12 I16(i16),
13 I32(i32),
14 I64(i64),
15 U8(u8),
16 U16(u16),
17 U32(u32),
18 U64(u64),
19 F32(f32),
20 F64(f64),
21 Str(String),
22}
23
24#[derive(Debug, Clone, PartialEq)]
25pub enum ScalarArrayValue {
26 Bool(Vec<bool>),
27 I8(Vec<i8>),
28 I16(Vec<i16>),
29 I32(Vec<i32>),
30 I64(Vec<i64>),
31 U8(Vec<u8>),
32 U16(Vec<u16>),
33 U32(Vec<u32>),
34 U64(Vec<u64>),
35 F32(Vec<f32>),
36 F64(Vec<f64>),
37 Str(Vec<String>),
38}
39
40impl ScalarArrayValue {
41 pub fn len(&self) -> usize {
42 match self {
43 Self::Bool(v) => v.len(),
44 Self::I8(v) => v.len(),
45 Self::I16(v) => v.len(),
46 Self::I32(v) => v.len(),
47 Self::I64(v) => v.len(),
48 Self::U8(v) => v.len(),
49 Self::U16(v) => v.len(),
50 Self::U32(v) => v.len(),
51 Self::U64(v) => v.len(),
52 Self::F32(v) => v.len(),
53 Self::F64(v) => v.len(),
54 Self::Str(v) => v.len(),
55 }
56 }
57
58 pub fn is_empty(&self) -> bool {
59 self.len() == 0
60 }
61
62 pub fn element_size_bytes(&self) -> usize {
63 match self {
64 Self::Bool(_) => 1,
65 Self::I8(_) => 1,
66 Self::I16(_) => 2,
67 Self::I32(_) => 4,
68 Self::I64(_) => 8,
69 Self::U8(_) => 1,
70 Self::U16(_) => 2,
71 Self::U32(_) => 4,
72 Self::U64(_) => 8,
73 Self::F32(_) => 4,
74 Self::F64(_) => 8,
75 Self::Str(v) => v.iter().map(|s| s.len()).sum(),
76 }
77 }
78
79 pub fn type_label(&self) -> &'static str {
80 match self {
81 Self::Bool(_) => "boolean[]",
82 Self::I8(_) => "byte[]",
83 Self::I16(_) => "short[]",
84 Self::I32(_) => "int[]",
85 Self::I64(_) => "long[]",
86 Self::U8(_) => "ubyte[]",
87 Self::U16(_) => "ushort[]",
88 Self::U32(_) => "uint[]",
89 Self::U64(_) => "ulong[]",
90 Self::F32(_) => "float[]",
91 Self::F64(_) => "double[]",
92 Self::Str(_) => "string[]",
93 }
94 }
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Default)]
98pub struct NtAlarm {
99 pub severity: i32,
100 pub status: i32,
101 pub message: String,
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, Default)]
105pub struct NtTimeStamp {
106 pub seconds_past_epoch: i64,
107 pub nanoseconds: i32,
108 pub user_tag: i32,
109}
110
111#[derive(Debug, Clone, PartialEq)]
112pub struct NtDisplay {
113 pub limit_low: f64,
114 pub limit_high: f64,
115 pub description: String,
116 pub units: String,
117 pub precision: i32,
118}
119
120impl Default for NtDisplay {
121 fn default() -> Self {
122 Self {
123 limit_low: 0.0,
124 limit_high: 0.0,
125 description: String::new(),
126 units: String::new(),
127 precision: 0,
128 }
129 }
130}
131
132#[derive(Debug, Clone, PartialEq)]
133pub struct NtControl {
134 pub limit_low: f64,
135 pub limit_high: f64,
136 pub min_step: f64,
137}
138
139impl Default for NtControl {
140 fn default() -> Self {
141 Self {
142 limit_low: 0.0,
143 limit_high: 0.0,
144 min_step: 0.0,
145 }
146 }
147}
148
149#[derive(Debug, Clone, PartialEq)]
150pub struct NtScalar {
151 pub value: ScalarValue,
152 pub alarm_severity: i32,
153 pub alarm_status: i32,
154 pub alarm_message: String,
155 pub alarm_low: Option<f64>,
156 pub alarm_high: Option<f64>,
157 pub alarm_lolo: Option<f64>,
158 pub alarm_hihi: Option<f64>,
159 pub display_low: f64,
160 pub display_high: f64,
161 pub display_description: String,
162 pub display_precision: i32,
163 pub display_form_index: i32,
164 pub display_form_choices: Vec<String>,
165 pub control_low: f64,
166 pub control_high: f64,
167 pub control_min_step: f64,
168 pub units: String,
169 pub value_alarm_active: bool,
170 pub value_alarm_low_alarm_limit: f64,
171 pub value_alarm_low_warning_limit: f64,
172 pub value_alarm_high_warning_limit: f64,
173 pub value_alarm_high_alarm_limit: f64,
174 pub value_alarm_low_alarm_severity: i32,
175 pub value_alarm_low_warning_severity: i32,
176 pub value_alarm_high_warning_severity: i32,
177 pub value_alarm_high_alarm_severity: i32,
178 pub value_alarm_hysteresis: u8,
179}
180
181impl NtScalar {
182 pub fn from_value(value: ScalarValue) -> Self {
183 Self {
184 value,
185 alarm_severity: 0,
186 alarm_status: 0,
187 alarm_message: String::new(),
188 alarm_low: None,
189 alarm_high: None,
190 alarm_lolo: None,
191 alarm_hihi: None,
192 display_low: 0.0,
193 display_high: 0.0,
194 display_description: String::new(),
195 display_precision: 0,
196 display_form_index: 0,
197 display_form_choices: default_form_choices(),
198 control_low: 0.0,
199 control_high: 0.0,
200 control_min_step: 0.0,
201 units: String::new(),
202 value_alarm_active: false,
203 value_alarm_low_alarm_limit: 0.0,
204 value_alarm_low_warning_limit: 0.0,
205 value_alarm_high_warning_limit: 0.0,
206 value_alarm_high_alarm_limit: 0.0,
207 value_alarm_low_alarm_severity: 0,
208 value_alarm_low_warning_severity: 0,
209 value_alarm_high_warning_severity: 0,
210 value_alarm_high_alarm_severity: 0,
211 value_alarm_hysteresis: 0,
212 }
213 }
214
215 pub fn with_limits(mut self, low: f64, high: f64) -> Self {
216 self.display_low = low;
217 self.display_high = high;
218 self.control_low = low;
219 self.control_high = high;
220 self
221 }
222
223 pub fn with_units(mut self, units: String) -> Self {
224 self.units = units;
225 self
226 }
227
228 pub fn with_description(mut self, description: String) -> Self {
229 self.display_description = description;
230 self
231 }
232
233 pub fn with_precision(mut self, precision: i32) -> Self {
234 self.display_precision = precision;
235 self
236 }
237
238 pub fn with_alarm_limits(
239 mut self,
240 low: Option<f64>,
241 high: Option<f64>,
242 lolo: Option<f64>,
243 hihi: Option<f64>,
244 ) -> Self {
245 self.alarm_low = low;
246 self.alarm_high = high;
247 self.alarm_lolo = lolo;
248 self.alarm_hihi = hihi;
249 if let Some(v) = low {
250 self.value_alarm_low_warning_limit = v;
251 }
252 if let Some(v) = high {
253 self.value_alarm_high_warning_limit = v;
254 }
255 if let Some(v) = lolo {
256 self.value_alarm_low_alarm_limit = v;
257 }
258 if let Some(v) = hihi {
259 self.value_alarm_high_alarm_limit = v;
260 }
261 self
262 }
263
264 pub fn update_alarm_from_value(&mut self) {
265 let val = match self.value {
266 ScalarValue::F64(v) => v,
267 ScalarValue::F32(v) => v as f64,
268 ScalarValue::I8(v) => v as f64,
269 ScalarValue::I16(v) => v as f64,
270 ScalarValue::I32(v) => v as f64,
271 ScalarValue::I64(v) => v as f64,
272 ScalarValue::U8(v) => v as f64,
273 ScalarValue::U16(v) => v as f64,
274 ScalarValue::U32(v) => v as f64,
275 ScalarValue::U64(v) => v as f64,
276 _ => {
277 self.alarm_severity = 0;
278 self.alarm_status = 0;
279 self.alarm_message.clear();
280 return;
281 }
282 };
283
284 let mut severity = 0;
285 let mut message = String::new();
286
287 if let Some(hihi) = self.alarm_hihi {
288 if val >= hihi {
289 severity = 2;
290 message = "HIHI".to_string();
291 }
292 }
293 if severity == 0 {
294 if let Some(high) = self.alarm_high {
295 if val >= high {
296 severity = 1;
297 message = "HIGH".to_string();
298 }
299 }
300 }
301 if severity == 0 {
302 if let Some(lolo) = self.alarm_lolo {
303 if val <= lolo {
304 severity = 2;
305 message = "LOLO".to_string();
306 }
307 }
308 }
309 if severity == 0 {
310 if let Some(low) = self.alarm_low {
311 if val <= low {
312 severity = 1;
313 message = "LOW".to_string();
314 }
315 }
316 }
317
318 if severity == 0 {
319 self.alarm_severity = 0;
320 self.alarm_status = 0;
321 self.alarm_message.clear();
322 } else {
323 self.alarm_severity = severity;
324 self.alarm_status = 1;
325 self.alarm_message = message;
326 }
327 }
328}
329
330#[derive(Debug, Clone, PartialEq)]
331pub struct NtScalarArray {
332 pub value: ScalarArrayValue,
333 pub alarm: NtAlarm,
334 pub time_stamp: NtTimeStamp,
335 pub display: NtDisplay,
336 pub control: NtControl,
337}
338
339impl NtScalarArray {
340 pub fn from_value(value: ScalarArrayValue) -> Self {
341 Self {
342 value,
343 alarm: NtAlarm::default(),
344 time_stamp: NtTimeStamp::default(),
345 display: NtDisplay::default(),
346 control: NtControl::default(),
347 }
348 }
349}
350
351#[derive(Debug, Clone, PartialEq)]
352pub struct NtTableColumn {
353 pub name: String,
354 pub values: ScalarArrayValue,
355}
356
357#[derive(Debug, Clone, PartialEq)]
358pub struct NtTable {
359 pub labels: Vec<String>,
360 pub columns: Vec<NtTableColumn>,
361 pub descriptor: Option<String>,
362 pub alarm: Option<NtAlarm>,
363 pub time_stamp: Option<NtTimeStamp>,
364}
365
366impl NtTable {
367 pub fn validate(&self) -> Result<(), String> {
368 let mut expected_len: Option<usize> = None;
369 for col in &self.columns {
370 let len = col.values.len();
371 if let Some(expected) = expected_len {
372 if expected != len {
373 return Err(format!(
374 "table column '{}' length {} does not match expected {}",
375 col.name, len, expected
376 ));
377 }
378 } else {
379 expected_len = Some(len);
380 }
381 }
382 Ok(())
383 }
384}
385
386#[derive(Debug, Clone, PartialEq, Eq)]
387pub struct NdCodec {
388 pub name: String,
389 pub parameters: HashMap<String, String>,
390}
391
392#[derive(Debug, Clone, PartialEq, Eq)]
393pub struct NdDimension {
394 pub size: i32,
395 pub offset: i32,
396 pub full_size: i32,
397 pub binning: i32,
398 pub reverse: bool,
399}
400
401#[derive(Debug, Clone, PartialEq)]
402pub struct NtAttribute {
403 pub name: String,
404 pub value: ScalarValue,
405 pub descriptor: String,
406 pub source_type: i32,
407 pub source: String,
408}
409
410#[derive(Debug, Clone, PartialEq)]
411pub struct NtNdArray {
412 pub value: ScalarArrayValue,
413 pub codec: NdCodec,
414 pub compressed_size: i64,
415 pub uncompressed_size: i64,
416 pub dimension: Vec<NdDimension>,
417 pub unique_id: i32,
418 pub data_time_stamp: NtTimeStamp,
419 pub attribute: Vec<NtAttribute>,
420 pub descriptor: Option<String>,
421 pub alarm: Option<NtAlarm>,
422 pub time_stamp: Option<NtTimeStamp>,
423 pub display: Option<NtDisplay>,
424}
425
426impl NtNdArray {
427 pub fn validate(&self) -> Result<(), String> {
428 if self
429 .attribute
430 .iter()
431 .any(|a| a.descriptor.trim().is_empty())
432 {
433 return Err("ntndarray attribute descriptor must be set".to_string());
434 }
435 let element_size = self.value.element_size_bytes().max(1) as i64;
436 let logical_elements = self
437 .dimension
438 .iter()
439 .map(|d| d.size.max(0) as i64)
440 .product::<i64>()
441 .max(0);
442 let expected_uncompressed = logical_elements.saturating_mul(element_size);
443 if self.uncompressed_size > 0 && self.uncompressed_size != expected_uncompressed {
444 return Err(format!(
445 "uncompressed_size {} does not match dimension*element_size {}",
446 self.uncompressed_size, expected_uncompressed
447 ));
448 }
449 if self.compressed_size > 0 && self.compressed_size > self.uncompressed_size {
450 return Err(format!(
451 "compressed_size {} cannot exceed uncompressed_size {}",
452 self.compressed_size, self.uncompressed_size
453 ));
454 }
455 Ok(())
456 }
457}
458
459#[derive(Debug, Clone, PartialEq)]
460pub enum NtPayload {
461 Scalar(NtScalar),
462 ScalarArray(NtScalarArray),
463 Table(NtTable),
464 NdArray(NtNdArray),
465}
466
467pub(crate) fn default_form_choices() -> Vec<String> {
468 vec![
469 "Default".to_string(),
470 "String".to_string(),
471 "Binary".to_string(),
472 "Decimal".to_string(),
473 "Hex".to_string(),
474 "Exponential".to_string(),
475 "Engineering".to_string(),
476 ]
477}