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