bo4e_core/com/
meter_reading.rs

1//! Meter reading (Zaehlwerksstand) component.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::enums::{MeasuredValueStatus, ReadingType, Unit};
7use crate::traits::{Bo4eMeta, Bo4eObject};
8
9/// A meter reading at a specific point in time.
10///
11/// German: Zaehlwerksstand
12///
13/// # Example
14///
15/// ```rust
16/// use bo4e_core::com::MeterReading;
17/// use bo4e_core::enums::{ReadingType, Unit};
18/// use chrono::Utc;
19///
20/// let reading = MeterReading {
21///     timestamp: Some(Utc::now()),
22///     value: Some(12345.67),
23///     unit: Some(Unit::KilowattHour),
24///     reading_type: Some(ReadingType::RemoteReading),
25///     ..Default::default()
26/// };
27/// ```
28#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct MeterReading {
31    /// BO4E metadata
32    #[serde(flatten)]
33    pub meta: Bo4eMeta,
34
35    /// Timestamp of the reading (Ablesezeitpunkt)
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub timestamp: Option<DateTime<Utc>>,
38
39    /// Meter reading value (Zaehlwerksstand)
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub value: Option<f64>,
42
43    /// Unit of measurement (Einheit)
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub unit: Option<Unit>,
46
47    /// Type of reading (Ableseart)
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub reading_type: Option<ReadingType>,
50
51    /// Status/quality of the reading (Status)
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub status: Option<MeasuredValueStatus>,
54
55    /// OBIS code for the register (OBIS-Kennzahl)
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub obis_code: Option<String>,
58
59    /// Register ID (Zaehlwerkskennung)
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub register_id: Option<String>,
62}
63
64impl Bo4eObject for MeterReading {
65    fn type_name_german() -> &'static str {
66        "Zaehlwerksstand"
67    }
68
69    fn type_name_english() -> &'static str {
70        "MeterReading"
71    }
72
73    fn meta(&self) -> &Bo4eMeta {
74        &self.meta
75    }
76
77    fn meta_mut(&mut self) -> &mut Bo4eMeta {
78        &mut self.meta
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85    use chrono::TimeZone;
86
87    #[test]
88    fn test_meter_reading() {
89        let reading = MeterReading {
90            timestamp: Some(Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap()),
91            value: Some(12345.67),
92            unit: Some(Unit::KilowattHour),
93            reading_type: Some(ReadingType::RemoteReading),
94            obis_code: Some("1-0:1.8.0".to_string()),
95            ..Default::default()
96        };
97
98        let json = serde_json::to_string(&reading).unwrap();
99        assert!(json.contains("12345.67"));
100        assert!(json.contains("1-0:1.8.0"));
101    }
102
103    #[test]
104    fn test_roundtrip() {
105        let reading = MeterReading {
106            timestamp: Some(Utc::now()),
107            value: Some(999.99),
108            unit: Some(Unit::CubicMeter),
109            ..Default::default()
110        };
111
112        let json = serde_json::to_string(&reading).unwrap();
113        let parsed: MeterReading = serde_json::from_str(&json).unwrap();
114        assert_eq!(reading.value, parsed.value);
115        assert_eq!(reading.unit, parsed.unit);
116    }
117
118    #[test]
119    fn test_bo4e_object_impl() {
120        assert_eq!(MeterReading::type_name_german(), "Zaehlwerksstand");
121        assert_eq!(MeterReading::type_name_english(), "MeterReading");
122    }
123}