dsmr5/
types.rs

1//! COSEM data types such as timestamps or fixed point numbers as per section 6.4.
2//!
3//! Guaranteed to either be parsed stack-compatible types or buffer references.
4
5use serde::{Deserialize, Serialize};
6
7use crate::{Error, Result};
8
9/// Octet strings as defined by tag 9.
10#[derive(Debug, Serialize, Deserialize)]
11pub struct OctetString<'a>(&'a str);
12
13impl<'a> OctetString<'a> {
14    /// Parse a fixed length string from an OBIS body.
15    pub fn parse(body: &'a str, length: usize) -> Result<OctetString<'a>> {
16        Ok(OctetString(
17            body.get(1..=length).ok_or(Error::InvalidFormat)?,
18        ))
19    }
20
21    /// Parse a variable length string with a max length from an OBIS body.
22    pub fn parse_max(body: &'a str, max_length: usize) -> Result<OctetString<'a>> {
23        let end = body.find(')').ok_or(Error::InvalidFormat)? - 1;
24        if end > max_length {
25            return Err(Error::InvalidFormat);
26        }
27
28        OctetString::parse(body, end)
29    }
30
31    /// Yield this octet string as the underlying octets.
32    pub fn as_octets(&'a self) -> impl core::iter::Iterator<Item = Result<u8>> + 'a {
33        (0..self.0.len() / 2).map(move |i| {
34            u8::from_str_radix(&self.0[i * 2..=i * 2 + 1], 16).map_err(|_| Error::InvalidFormat)
35        })
36    }
37}
38
39/// Timestamps.
40#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
41pub struct TST {
42    pub year: u8,
43    pub month: u8,
44    pub day: u8,
45    pub hour: u8,
46    pub minute: u8,
47    pub second: u8,
48
49    /// Daylight savings time.
50    pub dst: bool,
51}
52
53impl TST {
54    pub fn parse(body: &str) -> Result<TST> {
55        if body.len() < 15 {
56            return Err(Error::InvalidFormat);
57        }
58
59        let parsetwo = |i| body[i..=(i + 1)].parse().map_err(|_| Error::InvalidFormat);
60
61        Ok(TST {
62            year: parsetwo(1)?,
63            month: parsetwo(3)?,
64            day: parsetwo(5)?,
65            hour: parsetwo(7)?,
66            minute: parsetwo(9)?,
67            second: parsetwo(11)?,
68            dst: match &body[13..=13] {
69                "S" => Ok(true),
70                "W" => Ok(false),
71                _ => Err(Error::InvalidFormat),
72            }?,
73        })
74    }
75}
76
77/// Fixed length unsigned doubles as defined by tag 6.
78#[derive(Debug, Serialize, Deserialize)]
79pub struct UFixedDouble {
80    buffer: u64,
81    point: u8,
82}
83
84impl UFixedDouble {
85    pub fn parse(body: &str, length: usize, point: u8) -> Result<UFixedDouble> {
86        // Do not forget the extra '.'
87        let buffer = body.get(1..length + 2).ok_or(Error::InvalidFormat)?;
88        let (upper, lower) = buffer.split_at(length - point as usize);
89
90        let upper: u64 = upper.parse().map_err(|_| Error::InvalidFormat)?;
91        let lower: u64 = lower[1..].parse().map_err(|_| Error::InvalidFormat)?;
92
93        Ok(UFixedDouble {
94            buffer: upper * 10u64.pow(u32::from(point)) + lower,
95            point,
96        })
97    }
98}
99
100impl core::convert::From<&UFixedDouble> for f64 {
101    fn from(other: &UFixedDouble) -> Self {
102        other.buffer as f64 / (10u64.pow(u32::from(other.point)) as f64)
103    }
104}
105
106/// Fixed length unsigned integers as defined by tags 15-21.
107#[derive(Debug)]
108pub struct UFixedInteger(pub u64);
109
110impl UFixedInteger {
111    pub fn parse(body: &str, length: usize) -> Result<UFixedInteger> {
112        let buffer = body.get(1..=length).ok_or(Error::InvalidFormat)?;
113        let number = buffer.parse().map_err(|_| Error::InvalidFormat)?;
114
115        Ok(UFixedInteger(number))
116    }
117}