dsmr5/
state.rs

1//! Convenience structs to get and keep the current state of the meter in memory.
2//!
3//! Usage of these types is entirely optional.
4//! When only needing a few or single record, it is more efficient to directly filter on the
5//! telegram objects iterator.
6
7use serde::{Deserialize, Serialize};
8
9use crate::{obis::*, types::*};
10
11/// A reading from a power meter, per Tariff.
12#[derive(Default, Debug, Serialize, Deserialize)]
13pub struct MeterReading {
14    pub to: Option<f64>,
15    pub by: Option<f64>,
16}
17
18/// One of three possible lines in the meter.
19#[derive(Default, Debug, Serialize, Deserialize)]
20pub struct Line {
21    pub voltage_sags: Option<u64>,
22    pub voltage_swells: Option<u64>,
23    pub voltage: Option<f64>,
24    pub current: Option<u64>,
25    pub active_power_plus: Option<f64>,
26    pub active_power_neg: Option<f64>,
27}
28
29/// One of 4 possible slaves to the meter.
30///
31/// Such as a gas meter, water meter or heat supply.
32#[derive(Default, Debug, Serialize, Deserialize)]
33pub struct Slave {
34    pub device_type: Option<u64>,
35    pub meter_reading: Option<(TST, f64)>,
36}
37
38/// The metering state surmised for a single Telegram.
39#[derive(Default, Debug, Serialize, Deserialize)]
40pub struct State {
41    pub datetime: Option<TST>,
42    pub meterreadings: [MeterReading; 2],
43    pub tariff_indicator: Option<[u8; 2]>,
44    pub power_delivered: Option<f64>,
45    pub power_received: Option<f64>,
46    pub power_failures: Option<u64>,
47    pub long_power_failures: Option<u64>,
48    pub lines: [Line; 3],
49    pub slaves: [Slave; 4],
50}
51
52impl<'a> core::convert::TryFrom<&crate::Telegram<'a>> for State {
53    type Error = crate::Error;
54
55    fn try_from(t: &crate::Telegram<'a>) -> Result<Self, Self::Error> {
56        t.objects().try_fold(State::default(), |mut state, o| {
57            match o? {
58                OBIS::DateTime(tst) => {
59                    state.datetime = Some(tst);
60                }
61                OBIS::MeterReadingTo(t, mr) => {
62                    state.meterreadings[t as usize].to = Some(f64::from(&mr));
63                }
64                OBIS::MeterReadingBy(t, mr) => {
65                    state.meterreadings[t as usize].by = Some(f64::from(&mr));
66                }
67                OBIS::TariffIndicator(ti) => {
68                    let mut buf = [0u8; 2];
69                    let mut octets = ti.as_octets();
70                    buf[0] = octets.next().unwrap_or(Err(crate::Error::InvalidFormat))?;
71                    buf[1] = octets.next().unwrap_or(Err(crate::Error::InvalidFormat))?;
72
73                    state.tariff_indicator = Some(buf);
74                }
75                OBIS::PowerDelivered(p) => {
76                    state.power_delivered = Some(f64::from(&p));
77                }
78                OBIS::PowerReceived(p) => {
79                    state.power_received = Some(f64::from(&p));
80                }
81                OBIS::PowerFailures(UFixedInteger(pf)) => {
82                    state.power_failures = Some(pf);
83                }
84                OBIS::LongPowerFailures(UFixedInteger(lpf)) => {
85                    state.long_power_failures = Some(lpf);
86                }
87                OBIS::VoltageSags(l, UFixedInteger(n)) => {
88                    state.lines[l as usize].voltage_sags = Some(n);
89                }
90                OBIS::VoltageSwells(l, UFixedInteger(n)) => {
91                    state.lines[l as usize].voltage_swells = Some(n);
92                }
93                OBIS::InstantaneousVoltage(l, v) => {
94                    state.lines[l as usize].voltage = Some(f64::from(&v));
95                }
96                OBIS::InstantaneousCurrent(l, UFixedInteger(a)) => {
97                    state.lines[l as usize].current = Some(a);
98                }
99                OBIS::InstantaneousActivePowerPlus(l, p) => {
100                    state.lines[l as usize].active_power_plus = Some(f64::from(&p));
101                }
102                OBIS::InstantaneousActivePowerNeg(l, p) => {
103                    state.lines[l as usize].active_power_neg = Some(f64::from(&p));
104                }
105                OBIS::SlaveDeviceType(s, value_x) => {
106                    if let Some(UFixedInteger(dt)) = value_x {
107                        state.slaves[s as usize].device_type = Some(dt);
108                    } else {
109                        state.slaves[s as usize].device_type = None;
110                    }
111                }
112                OBIS::SlaveMeterReading(s, tst, mr) => {
113                    if let Some(mr_value) = mr {
114                        state.slaves[s as usize].meter_reading = Some((tst, f64::from(&mr_value)));
115                    } else {
116                        state.slaves[s as usize].meter_reading = None;
117                    }
118                }
119                _ => {} // Ignore rest.
120            }
121            Ok(state)
122        })
123    }
124}
125
126impl<'a> core::convert::From<&crate::Telegram<'a>> for crate::Result<State> {
127    fn from(t: &crate::Telegram<'a>) -> Self {
128        t.try_into()
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    #[test]
135    fn example() {
136        let mut buffer = [0u8; 2048];
137        let file = std::fs::read("test/isk.txt").unwrap();
138
139        let (left, _right) = buffer.split_at_mut(file.len());
140        left.copy_from_slice(file.as_slice());
141
142        let readout = crate::Readout { buffer };
143        let telegram = &readout.to_telegram().unwrap();
144        let state: super::State = telegram.try_into().unwrap();
145
146        assert_eq!(
147            state.datetime.as_ref().unwrap(),
148            &crate::types::TST {
149                year: 19,
150                month: 3,
151                day: 20,
152                hour: 18,
153                minute: 14,
154                second: 3,
155                dst: false
156            }
157        );
158
159        use crate::obis::Tariff::*;
160
161        assert_eq!(state.meterreadings[Tariff1 as usize].to.unwrap(), 576.239);
162        assert_eq!(state.meterreadings[Tariff2 as usize].to.unwrap(), 465.162);
163        assert_eq!(state.tariff_indicator.unwrap(), [0, 2]);
164
165        eprintln!("{:?}", state);
166    }
167}