Skip to main content

mabi_core/
value.rs

1//! Value types for data points.
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6/// Dynamic value type for data points.
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8#[serde(untagged)]
9#[derive(Default)]
10pub enum Value {
11    /// Boolean value.
12    Bool(bool),
13    /// 8-bit signed integer.
14    I8(i8),
15    /// 8-bit unsigned integer.
16    U8(u8),
17    /// 16-bit signed integer.
18    I16(i16),
19    /// 16-bit unsigned integer.
20    U16(u16),
21    /// 32-bit signed integer.
22    I32(i32),
23    /// 32-bit unsigned integer.
24    U32(u32),
25    /// 64-bit signed integer.
26    I64(i64),
27    /// 64-bit unsigned integer.
28    U64(u64),
29    /// 32-bit floating point.
30    F32(f32),
31    /// 64-bit floating point.
32    F64(f64),
33    /// String value.
34    String(String),
35    /// Binary data.
36    Bytes(Vec<u8>),
37    /// Array of values.
38    Array(Vec<Value>),
39    /// Null/empty value.
40    #[default]
41    Null,
42}
43
44impl Value {
45    /// Create a new boolean value.
46    pub fn bool(v: bool) -> Self {
47        Self::Bool(v)
48    }
49
50    /// Create a new float value.
51    pub fn float(v: f64) -> Self {
52        Self::F64(v)
53    }
54
55    /// Create a new integer value.
56    pub fn int(v: i64) -> Self {
57        Self::I64(v)
58    }
59
60    /// Create a new string value.
61    pub fn string(v: impl Into<String>) -> Self {
62        Self::String(v.into())
63    }
64
65    /// Get value as boolean.
66    pub fn as_bool(&self) -> Option<bool> {
67        match self {
68            Self::Bool(v) => Some(*v),
69            Self::I8(v) => Some(*v != 0),
70            Self::U8(v) => Some(*v != 0),
71            Self::I16(v) => Some(*v != 0),
72            Self::U16(v) => Some(*v != 0),
73            Self::I32(v) => Some(*v != 0),
74            Self::U32(v) => Some(*v != 0),
75            Self::I64(v) => Some(*v != 0),
76            Self::U64(v) => Some(*v != 0),
77            _ => None,
78        }
79    }
80
81    /// Get value as f64.
82    pub fn as_f64(&self) -> Option<f64> {
83        match self {
84            Self::Bool(v) => Some(if *v { 1.0 } else { 0.0 }),
85            Self::I8(v) => Some(*v as f64),
86            Self::U8(v) => Some(*v as f64),
87            Self::I16(v) => Some(*v as f64),
88            Self::U16(v) => Some(*v as f64),
89            Self::I32(v) => Some(*v as f64),
90            Self::U32(v) => Some(*v as f64),
91            Self::I64(v) => Some(*v as f64),
92            Self::U64(v) => Some(*v as f64),
93            Self::F32(v) => Some(*v as f64),
94            Self::F64(v) => Some(*v),
95            _ => None,
96        }
97    }
98
99    /// Get value as i64.
100    pub fn as_i64(&self) -> Option<i64> {
101        match self {
102            Self::Bool(v) => Some(if *v { 1 } else { 0 }),
103            Self::I8(v) => Some(*v as i64),
104            Self::U8(v) => Some(*v as i64),
105            Self::I16(v) => Some(*v as i64),
106            Self::U16(v) => Some(*v as i64),
107            Self::I32(v) => Some(*v as i64),
108            Self::U32(v) => Some(*v as i64),
109            Self::I64(v) => Some(*v),
110            Self::U64(v) => i64::try_from(*v).ok(),
111            Self::F32(v) => Some(*v as i64),
112            Self::F64(v) => Some(*v as i64),
113            _ => None,
114        }
115    }
116
117    /// Get value as u16.
118    pub fn as_u16(&self) -> Option<u16> {
119        self.as_i64().and_then(|v| u16::try_from(v).ok())
120    }
121
122    /// Get value as string.
123    pub fn as_str(&self) -> Option<&str> {
124        match self {
125            Self::String(v) => Some(v.as_str()),
126            _ => None,
127        }
128    }
129
130    /// Get value as bytes.
131    pub fn as_bytes(&self) -> Option<&[u8]> {
132        match self {
133            Self::Bytes(v) => Some(v.as_slice()),
134            Self::String(v) => Some(v.as_bytes()),
135            _ => None,
136        }
137    }
138
139    /// Check if value is numeric.
140    pub fn is_numeric(&self) -> bool {
141        matches!(
142            self,
143            Self::I8(_)
144                | Self::U8(_)
145                | Self::I16(_)
146                | Self::U16(_)
147                | Self::I32(_)
148                | Self::U32(_)
149                | Self::I64(_)
150                | Self::U64(_)
151                | Self::F32(_)
152                | Self::F64(_)
153        )
154    }
155
156    /// Check if value is null.
157    pub fn is_null(&self) -> bool {
158        matches!(self, Self::Null)
159    }
160
161    /// Get the type name of this value.
162    pub fn type_name(&self) -> &'static str {
163        match self {
164            Self::Bool(_) => "bool",
165            Self::I8(_) => "i8",
166            Self::U8(_) => "u8",
167            Self::I16(_) => "i16",
168            Self::U16(_) => "u16",
169            Self::I32(_) => "i32",
170            Self::U32(_) => "u32",
171            Self::I64(_) => "i64",
172            Self::U64(_) => "u64",
173            Self::F32(_) => "f32",
174            Self::F64(_) => "f64",
175            Self::String(_) => "string",
176            Self::Bytes(_) => "bytes",
177            Self::Array(_) => "array",
178            Self::Null => "null",
179        }
180    }
181
182    /// Convert to u16 registers for Modbus.
183    pub fn to_registers(&self) -> Vec<u16> {
184        match self {
185            Self::Bool(v) => vec![if *v { 1 } else { 0 }],
186            Self::I8(v) => vec![*v as u16],
187            Self::U8(v) => vec![*v as u16],
188            Self::I16(v) => vec![*v as u16],
189            Self::U16(v) => vec![*v],
190            Self::I32(v) => {
191                let bytes = v.to_be_bytes();
192                vec![
193                    u16::from_be_bytes([bytes[0], bytes[1]]),
194                    u16::from_be_bytes([bytes[2], bytes[3]]),
195                ]
196            }
197            Self::U32(v) => {
198                let bytes = v.to_be_bytes();
199                vec![
200                    u16::from_be_bytes([bytes[0], bytes[1]]),
201                    u16::from_be_bytes([bytes[2], bytes[3]]),
202                ]
203            }
204            Self::F32(v) => {
205                let bytes = v.to_be_bytes();
206                vec![
207                    u16::from_be_bytes([bytes[0], bytes[1]]),
208                    u16::from_be_bytes([bytes[2], bytes[3]]),
209                ]
210            }
211            Self::F64(v) => {
212                let bytes = v.to_be_bytes();
213                vec![
214                    u16::from_be_bytes([bytes[0], bytes[1]]),
215                    u16::from_be_bytes([bytes[2], bytes[3]]),
216                    u16::from_be_bytes([bytes[4], bytes[5]]),
217                    u16::from_be_bytes([bytes[6], bytes[7]]),
218                ]
219            }
220            _ => vec![],
221        }
222    }
223
224    /// Create value from u16 registers (for Modbus).
225    pub fn from_registers(registers: &[u16], value_type: ValueType) -> Option<Self> {
226        match value_type {
227            ValueType::Bool => registers.first().map(|r| Value::Bool(*r != 0)),
228            ValueType::U16 => registers.first().map(|r| Value::U16(*r)),
229            ValueType::I16 => registers.first().map(|r| Value::I16(*r as i16)),
230            ValueType::U32 if registers.len() >= 2 => {
231                let bytes = [registers[0].to_be_bytes(), registers[1].to_be_bytes()];
232                Some(Value::U32(u32::from_be_bytes([
233                    bytes[0][0],
234                    bytes[0][1],
235                    bytes[1][0],
236                    bytes[1][1],
237                ])))
238            }
239            ValueType::I32 if registers.len() >= 2 => {
240                let bytes = [registers[0].to_be_bytes(), registers[1].to_be_bytes()];
241                Some(Value::I32(i32::from_be_bytes([
242                    bytes[0][0],
243                    bytes[0][1],
244                    bytes[1][0],
245                    bytes[1][1],
246                ])))
247            }
248            ValueType::F32 if registers.len() >= 2 => {
249                let bytes = [registers[0].to_be_bytes(), registers[1].to_be_bytes()];
250                Some(Value::F32(f32::from_be_bytes([
251                    bytes[0][0],
252                    bytes[0][1],
253                    bytes[1][0],
254                    bytes[1][1],
255                ])))
256            }
257            ValueType::F64 if registers.len() >= 4 => {
258                let bytes: Vec<u8> = registers[..4]
259                    .iter()
260                    .flat_map(|r| r.to_be_bytes())
261                    .collect();
262                Some(Value::F64(f64::from_be_bytes([
263                    bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
264                ])))
265            }
266            _ => None,
267        }
268    }
269}
270
271impl fmt::Display for Value {
272    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273        match self {
274            Self::Bool(v) => write!(f, "{}", v),
275            Self::I8(v) => write!(f, "{}", v),
276            Self::U8(v) => write!(f, "{}", v),
277            Self::I16(v) => write!(f, "{}", v),
278            Self::U16(v) => write!(f, "{}", v),
279            Self::I32(v) => write!(f, "{}", v),
280            Self::U32(v) => write!(f, "{}", v),
281            Self::I64(v) => write!(f, "{}", v),
282            Self::U64(v) => write!(f, "{}", v),
283            Self::F32(v) => write!(f, "{:.3}", v),
284            Self::F64(v) => write!(f, "{:.6}", v),
285            Self::String(v) => write!(f, "{}", v),
286            Self::Bytes(v) => write!(f, "[{} bytes]", v.len()),
287            Self::Array(v) => write!(f, "[{} items]", v.len()),
288            Self::Null => write!(f, "null"),
289        }
290    }
291}
292
293
294// Conversions
295impl From<bool> for Value {
296    fn from(v: bool) -> Self {
297        Self::Bool(v)
298    }
299}
300
301impl From<i32> for Value {
302    fn from(v: i32) -> Self {
303        Self::I32(v)
304    }
305}
306
307impl From<u32> for Value {
308    fn from(v: u32) -> Self {
309        Self::U32(v)
310    }
311}
312
313impl From<i64> for Value {
314    fn from(v: i64) -> Self {
315        Self::I64(v)
316    }
317}
318
319impl From<u64> for Value {
320    fn from(v: u64) -> Self {
321        Self::U64(v)
322    }
323}
324
325impl From<f32> for Value {
326    fn from(v: f32) -> Self {
327        Self::F32(v)
328    }
329}
330
331impl From<f64> for Value {
332    fn from(v: f64) -> Self {
333        Self::F64(v)
334    }
335}
336
337impl From<String> for Value {
338    fn from(v: String) -> Self {
339        Self::String(v)
340    }
341}
342
343impl From<&str> for Value {
344    fn from(v: &str) -> Self {
345        Self::String(v.to_string())
346    }
347}
348
349impl From<Vec<u8>> for Value {
350    fn from(v: Vec<u8>) -> Self {
351        Self::Bytes(v)
352    }
353}
354
355/// Value types for conversion.
356#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
357#[serde(rename_all = "lowercase")]
358pub enum ValueType {
359    Bool,
360    I8,
361    U8,
362    I16,
363    U16,
364    I32,
365    U32,
366    I64,
367    U64,
368    F32,
369    F64,
370    String,
371    Bytes,
372}
373
374impl ValueType {
375    /// Get the size in bytes for this type.
376    pub fn size_bytes(&self) -> usize {
377        match self {
378            Self::Bool | Self::I8 | Self::U8 => 1,
379            Self::I16 | Self::U16 => 2,
380            Self::I32 | Self::U32 | Self::F32 => 4,
381            Self::I64 | Self::U64 | Self::F64 => 8,
382            Self::String | Self::Bytes => 0, // Variable
383        }
384    }
385
386    /// Get the number of registers for Modbus.
387    pub fn register_count(&self) -> usize {
388        match self {
389            Self::Bool | Self::I8 | Self::U8 | Self::I16 | Self::U16 => 1,
390            Self::I32 | Self::U32 | Self::F32 => 2,
391            Self::I64 | Self::U64 | Self::F64 => 4,
392            Self::String | Self::Bytes => 0,
393        }
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use super::*;
400
401    #[test]
402    fn test_value_conversion() {
403        let v = Value::F64(123.456);
404        assert!((v.as_f64().unwrap() - 123.456).abs() < 0.001);
405        assert_eq!(v.as_i64(), Some(123));
406    }
407
408    #[test]
409    fn test_value_to_registers() {
410        let v = Value::F32(1.5);
411        let regs = v.to_registers();
412        assert_eq!(regs.len(), 2);
413
414        let v2 = Value::from_registers(&regs, ValueType::F32).unwrap();
415        assert!((v2.as_f64().unwrap() - 1.5).abs() < 0.001);
416    }
417
418    #[test]
419    fn test_value_display() {
420        assert_eq!(Value::Bool(true).to_string(), "true");
421        assert_eq!(Value::I32(42).to_string(), "42");
422        assert_eq!(Value::F64(3.14159).to_string(), "3.141590");
423    }
424}