Skip to main content

huawei_inverter/
value.rs

1use chrono::{DateTime, NaiveDateTime};
2
3use crate::{numbers::Number, point::DataType, Bytes, Gain, PointDefinition, Unit, ValidateRange};
4
5use core::fmt;
6use std::string::FromUtf8Error;
7
8pub type Word = u16;
9
10/// This trait contains all the conversion methods needed for
11/// working with points of the SunSpec models.
12pub trait Value: Sized + fmt::Display {
13    /// The base data type for this value, used for dynamic (non-generic) decoding.
14    const DATA_TYPE: DataType;
15
16    /// Decode value from a given slice of u16
17    fn decode(words: &[Word], pointdef: &PointDefinition) -> Result<Self, DecodeError>;
18    /// Encode value into a u16 array
19    fn encode(self) -> Box<[Word]>;
20
21    fn validate(&self) -> Result<(), ValidationError> {
22        Ok(())
23    }
24}
25
26impl Value for u16 {
27    const DATA_TYPE: DataType = DataType::U16;
28    fn decode(words: &[Word], _pointdef: &PointDefinition) -> Result<Self, DecodeError> {
29        let &[w0] = words else {
30            return Err(DecodeError::OutOfBounds);
31        };
32        Ok(w0)
33    }
34
35    fn encode(self) -> Box<[u16]> {
36        Box::new([self])
37    }
38}
39
40impl Value for u32 {
41    const DATA_TYPE: DataType = DataType::U32;
42    fn decode(words: &[Word], _pointdef: &PointDefinition) -> Result<Self, DecodeError> {
43        let &[w1, w0] = words else {
44            return Err(DecodeError::OutOfBounds);
45        };
46        Ok((w1 as u32) << 16 | w0 as u32)
47    }
48    fn encode(self) -> Box<[u16]> {
49        Box::new([(self >> 16) as u16, self as u16])
50    }
51}
52
53impl Value for u64 {
54    const DATA_TYPE: DataType = DataType::U32;
55    fn decode(words: &[Word], _pointdef: &PointDefinition) -> Result<Self, DecodeError> {
56        let &[w3, w2, w1, w0] = words else {
57            return Err(DecodeError::OutOfBounds);
58        };
59        Ok((w3 as u64) << 0x30 | (w2 as u64) << 0x20 | (w1 as u64) << 0x10 | w0 as u64)
60    }
61    fn encode(self) -> Box<[u16]> {
62        Box::new([
63            (self >> 0x30) as u16,
64            (self >> 0x20) as u16,
65            (self >> 0x10) as u16,
66            self as u16,
67        ])
68    }
69}
70
71impl Value for u128 {
72    const DATA_TYPE: DataType = DataType::U32;
73    fn decode(words: &[Word], _pointdef: &PointDefinition) -> Result<Self, DecodeError> {
74        let &[w7, w6, w5, w4, w3, w2, w1, w0] = words else {
75            return Err(DecodeError::OutOfBounds);
76        };
77        Ok((w7 as u128) << 0x70
78            | (w6 as u128) << 0x60
79            | (w5 as u128) << 0x50
80            | (w4 as u128) << 0x40
81            | (w3 as u128) << 0x30
82            | (w2 as u128) << 0x20
83            | (w1 as u128) << 0x10
84            | (w0 as u128))
85    }
86    fn encode(self) -> Box<[u16]> {
87        Box::new([
88            (self >> 0x70) as u16,
89            (self >> 0x60) as u16,
90            (self >> 0x50) as u16,
91            (self >> 0x40) as u16,
92            (self >> 0x30) as u16,
93            (self >> 0x20) as u16,
94            (self >> 0x10) as u16,
95            self as u16,
96        ])
97    }
98}
99
100impl Value for i16 {
101    const DATA_TYPE: DataType = DataType::I16;
102    fn decode(words: &[Word], pointdef: &PointDefinition) -> Result<Self, DecodeError> {
103        Ok(u16::decode(words, pointdef)? as Self)
104    }
105    fn encode(self) -> Box<[u16]> {
106        (self as u16).encode()
107    }
108}
109
110impl Value for i32 {
111    const DATA_TYPE: DataType = DataType::I32;
112    fn decode(words: &[Word], pointdef: &PointDefinition) -> Result<Self, DecodeError> {
113        Ok(u32::decode(words, pointdef)? as Self)
114    }
115    fn encode(self) -> Box<[u16]> {
116        (self as u32).encode()
117    }
118}
119
120impl Value for i64 {
121    const DATA_TYPE: DataType = DataType::I32;
122    fn decode(words: &[Word], pointdef: &PointDefinition) -> Result<Self, DecodeError> {
123        Ok(u64::decode(words, pointdef)? as Self)
124    }
125    fn encode(self) -> Box<[u16]> {
126        (self as u64).encode()
127    }
128}
129
130impl Value for f32 {
131    const DATA_TYPE: DataType = DataType::U32;
132    fn decode(words: &[Word], _pointdef: &PointDefinition) -> Result<Self, DecodeError> {
133        let &[w1, w0] = words else {
134            return Err(DecodeError::OutOfBounds);
135        };
136        Ok(f32::from_be_bytes([
137            (w1 >> 8) as u8,
138            w1 as u8,
139            (w0 >> 8) as u8,
140            w0 as u8,
141        ]))
142    }
143    fn encode(self) -> Box<[u16]> {
144        let [b3, b2, b1, b0] = self.to_be_bytes();
145        Box::new([
146            (b3 as u16) << 8 | (b2 as u16),
147            (b1 as u16) << 8 | (b0 as u16),
148        ])
149    }
150}
151
152impl Value for f64 {
153    const DATA_TYPE: DataType = DataType::U32;
154    fn decode(words: &[Word], _pointdef: &PointDefinition) -> Result<Self, DecodeError> {
155        let &[w3, w2, w1, w0] = words else {
156            return Err(DecodeError::OutOfBounds);
157        };
158        Ok(f64::from_be_bytes([
159            (w3 >> 8) as u8,
160            w3 as u8,
161            (w2 >> 8) as u8,
162            w2 as u8,
163            (w1 >> 8) as u8,
164            w1 as u8,
165            (w0 >> 8) as u8,
166            w0 as u8,
167        ]))
168    }
169    fn encode(self) -> Box<[u16]> {
170        let [b7, b6, b5, b4, b3, b2, b1, b0] = self.to_be_bytes();
171        Box::new([
172            (b7 as u16) << 8 | (b6 as u16),
173            (b5 as u16) << 8 | (b4 as u16),
174            (b3 as u16) << 8 | (b2 as u16),
175            (b1 as u16) << 8 | (b0 as u16),
176        ])
177    }
178}
179
180fn encode_bytes(octets: &[u8]) -> Box<[u16]> {
181    octets
182        .chunks(2)
183        .map(|chunk| match *chunk {
184            [b1, b0] => (b1 as u16) << 8 | (b0 as u16),
185            [b1] => (b1 as u16) << 8,
186            _ => unreachable!(),
187        })
188        .collect()
189}
190
191impl Value for String {
192    const DATA_TYPE: DataType = DataType::String;
193    fn decode(words: &[Word], pointdef: &PointDefinition) -> Result<Self, DecodeError> {
194        let bytes = Bytes::decode(words, pointdef)?;
195        Ok(String::from_utf8(bytes.take())?)
196    }
197    fn encode(self) -> Box<[u16]> {
198        encode_bytes(self.as_bytes())
199    }
200}
201
202impl Value for Bytes {
203    const DATA_TYPE: DataType = DataType::Bytes;
204    fn decode(words: &[Word], _pointdef: &PointDefinition) -> Result<Self, DecodeError> {
205        let bytes = words
206            .iter()
207            .flat_map(|word| word.to_be_bytes())
208            .take_while(|&b| b != 0)
209            .collect::<Vec<_>>();
210        Ok(bytes.into())
211    }
212    fn encode(self) -> Box<[u16]> {
213        encode_bytes(&self.take())
214    }
215}
216
217impl Value for NaiveDateTime {
218    const DATA_TYPE: DataType = DataType::DateTime;
219    fn decode(words: &[Word], pointdef: &PointDefinition) -> Result<Self, DecodeError> {
220        let decoded = u32::decode(words, pointdef)?;
221        DateTime::from_timestamp(decoded.into(), 0)
222            .ok_or(DecodeError::OutOfBounds)
223            .map(|dt| dt.naive_utc())
224    }
225    fn encode(self) -> Box<[u16]> {
226        u32::encode(self.and_utc().timestamp() as u32)
227    }
228}
229
230impl<T: Value + Copy + Into<i64>, U: Unit, G: Gain, R: ValidateRange> Value for Number<T, U, G, R>
231where
232    Number<T, U, G, R>: fmt::Display,
233{
234    const DATA_TYPE: DataType = T::DATA_TYPE;
235    fn decode(
236        words: &[crate::Word],
237        pointdef: &crate::PointDefinition,
238    ) -> Result<Self, crate::DecodeError> {
239        let val = T::decode(words, pointdef)?;
240        Ok(Self::from(val))
241    }
242
243    fn encode(self) -> Box<[crate::Word]> {
244        self.inner().encode()
245    }
246
247    fn validate(&self) -> Result<(), ValidationError> {
248        R::validate(self.into_inner())
249    }
250}
251
252/// This error is returned if there was an error decoding
253/// the value of a given point.
254#[derive(thiserror::Error, Debug, Eq, PartialEq)]
255pub enum DecodeError {
256    /// The value could not be decoded because the given data was not large
257    /// enough.
258    #[error("Out of bounds")]
259    OutOfBounds,
260    /// The given data was not valid UTF-8
261    #[error("Invalid UTF-8 data")]
262    Utf8(#[from] FromUtf8Error),
263    /// The given data was invalid for the enum point
264    #[error("Invalid enum value")]
265    InvalidEnumValue,
266}
267
268#[derive(thiserror::Error, Debug, PartialEq, Eq)]
269pub enum ValidationError {
270    /// The value was not within the defined value range
271    #[error("Value is out of range")]
272    OutOfRange,
273    #[error("Variable value ranges are currently unsupported")]
274    VariableRangeUnsupported,
275}
276
277/// A dynamically-typed decoded value, returned by [`PointDefinition::decode_dyn`].
278#[derive(Debug, Clone)]
279pub enum DynValue {
280    U16(u16),
281    U32(u32),
282    I16(i16),
283    I32(i32),
284    String(String),
285    Bytes(crate::Bytes),
286    DateTime(NaiveDateTime),
287}
288
289impl std::fmt::Display for DynValue {
290    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291        match self {
292            DynValue::U16(v) => write!(f, "{v}"),
293            DynValue::U32(v) => write!(f, "{v}"),
294            DynValue::I16(v) => write!(f, "{v}"),
295            DynValue::I32(v) => write!(f, "{v}"),
296            DynValue::String(v) => write!(f, "{v}"),
297            DynValue::Bytes(v) => write!(f, "{v:?}"),
298            DynValue::DateTime(v) => write!(f, "{v}"),
299        }
300    }
301}
302
303impl PointDefinition {
304    /// Decode raw modbus words into a [`DynValue`] based on this definition's [`DataType`].
305    pub fn decode_dyn(&self, words: &[Word]) -> Result<DynValue, DecodeError> {
306        match self.data_type {
307            DataType::U16 => Ok(DynValue::U16(u16::decode(words, self)?)),
308            DataType::U32 => Ok(DynValue::U32(u32::decode(words, self)?)),
309            DataType::I16 => Ok(DynValue::I16(i16::decode(words, self)?)),
310            DataType::I32 => Ok(DynValue::I32(i32::decode(words, self)?)),
311            DataType::String => Ok(DynValue::String(String::decode(words, self)?)),
312            DataType::Bytes => Ok(DynValue::Bytes(crate::Bytes::decode(words, self)?)),
313            DataType::DateTime => Ok(DynValue::DateTime(NaiveDateTime::decode(words, self)?)),
314        }
315    }
316}
317
318#[cfg(test)]
319mod tests {
320    use crate::{point::DataType, PointDefinition, Value};
321
322    const DUMMY_POINTDEF: PointDefinition =
323        PointDefinition::new(15000, 1, crate::ReadWrite::ReadOnly, DataType::U16);
324
325    #[test]
326    fn test_u16() {
327        assert_eq!(*0x0001i16.encode(), [0x1]);
328        assert_eq!(u16::decode(&[0x1], &DUMMY_POINTDEF), Ok(0x0001u16));
329    }
330
331    #[test]
332    fn test_u32() {
333        assert_eq!(*0x00010002u32.encode(), [0x1, 0x2]);
334        assert_eq!(u32::decode(&[0x1, 0x2], &DUMMY_POINTDEF), Ok(0x00010002u32));
335    }
336
337    #[test]
338    fn test_u64() {
339        assert_eq!(*0x0001000200030004u64.encode(), [0x1, 0x2, 0x3, 0x4]);
340        assert_eq!(
341            u64::decode(&[0x1, 0x2, 0x3, 0x4], &DUMMY_POINTDEF),
342            Ok(0x0001000200030004u64)
343        );
344    }
345
346    #[test]
347    fn test_u128() {
348        assert_eq!(
349            *0x00010002000300040005000600070008u128.encode(),
350            [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]
351        );
352        assert_eq!(
353            u128::decode(&[0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8], &DUMMY_POINTDEF),
354            Ok(0x00010002000300040005000600070008u128)
355        );
356    }
357
358    #[test]
359    fn test_i16() {
360        assert_eq!(*(-1i16).encode(), [0xffff]);
361        assert_eq!(i16::decode(&[0xffff], &DUMMY_POINTDEF), Ok(-1i16));
362    }
363
364    #[test]
365    fn test_i32() {
366        assert_eq!(*(-1i32).encode(), [0xffff, 0xffff]);
367        assert_eq!(i32::decode(&[0xffff, 0xffff], &DUMMY_POINTDEF), Ok(-1i32));
368    }
369
370    #[test]
371    fn test_i64() {
372        assert_eq!(*(-1i64).encode(), [0xffff, 0xffff, 0xffff, 0xffff]);
373        assert_eq!(
374            i64::decode(&[0xffff, 0xffff, 0xffff, 0xffff], &DUMMY_POINTDEF),
375            Ok(-1i64)
376        );
377    }
378
379    #[test]
380    fn test_f32() {
381        assert_eq!(*(0.5f32).encode(), [0x3f00, 0x0000]);
382        assert_eq!(f32::decode(&[0x3f00, 0x0000], &DUMMY_POINTDEF), Ok(0.5f32));
383    }
384
385    #[test]
386    fn test_f64() {
387        assert_eq!(*(0.5f64).encode(), [0x3fe0, 0x0000, 0x0000, 0x0000]);
388        assert_eq!(
389            f64::decode(&[0x3fe0, 0x0000, 0x0000, 0x0000], &DUMMY_POINTDEF),
390            Ok(0.5f64)
391        );
392    }
393
394    #[test]
395    fn test_string() {
396        assert_eq!(*String::from("SunS").encode(), [0x5375, 0x6e53]);
397        assert_eq!(
398            String::decode(&[0x5375, 0x6e53], &DUMMY_POINTDEF),
399            Ok(String::from("SunS"))
400        );
401        assert_eq!(*String::from("pad").encode(), [0x7061, 0x6400]);
402        assert_eq!(
403            String::decode(&[0x7061, 0x6400], &DUMMY_POINTDEF),
404            Ok(String::from("pad"))
405        );
406        assert_eq!(
407            String::decode(&[0x7061, 0x6400, 0x0000], &DUMMY_POINTDEF),
408            Ok(String::from("pad"))
409        );
410    }
411}