bo4e_core/bo/
meter.rs

1//! Meter (Zaehler) business object.
2//!
3//! Represents a metering device for measuring energy consumption or production.
4
5use serde::{Deserialize, Serialize};
6
7use crate::com::{Address, Hardware, MeterRegister};
8use crate::enums::{Division, MeterSize, MeterType};
9use crate::traits::{Bo4eMeta, Bo4eObject};
10
11/// A meter (Zähler) for measuring energy consumption or production.
12///
13/// German: Zaehler
14///
15/// # Example
16///
17/// ```rust
18/// use bo4e_core::bo::Meter;
19/// use bo4e_core::enums::{Division, MeterType};
20///
21/// let meter = Meter {
22///     meter_number: Some("1EMH0012345678".to_string()),
23///     division: Some(Division::Electricity),
24///     meter_type: Some(MeterType::ModernMeasuringDevice),
25///     ..Default::default()
26/// };
27/// ```
28#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct Meter {
31    /// BO4E metadata
32    #[serde(flatten)]
33    pub meta: Bo4eMeta,
34
35    /// Unique meter identification number (Zaehlernummer)
36    #[serde(skip_serializing_if = "Option::is_none", alias = "zaehlernummer")]
37    pub meter_number: Option<String>,
38
39    /// Energy division (Sparte)
40    #[serde(skip_serializing_if = "Option::is_none", alias = "sparte")]
41    pub division: Option<Division>,
42
43    /// Type of meter (Zaehlertyp)
44    #[serde(skip_serializing_if = "Option::is_none", alias = "zaehlertyp")]
45    pub meter_type: Option<MeterType>,
46
47    /// Meter size classification (Zaehlergroesse)
48    #[serde(skip_serializing_if = "Option::is_none", alias = "zaehlergroesse")]
49    pub meter_size: Option<MeterSize>,
50
51    /// Installation location address (Standort)
52    #[serde(skip_serializing_if = "Option::is_none", alias = "standort")]
53    pub location: Option<Address>,
54
55    /// Registers on this meter (Zaehlwerke)
56    #[serde(default, skip_serializing_if = "Vec::is_empty", alias = "zaehlwerke")]
57    pub registers: Vec<MeterRegister>,
58
59    /// Hardware components (Geraeteeigenschaften)
60    #[serde(
61        default,
62        skip_serializing_if = "Vec::is_empty",
63        alias = "geraeteeigenschaften"
64    )]
65    pub hardware: Vec<Hardware>,
66
67    /// Reference to associated market location ID (Marktlokation)
68    #[serde(skip_serializing_if = "Option::is_none", alias = "marktlokationsId")]
69    pub market_location_id: Option<String>,
70
71    /// Reference to associated metering location ID (Messlokation)
72    #[serde(skip_serializing_if = "Option::is_none", alias = "messlokationsId")]
73    pub metering_location_id: Option<String>,
74
75    /// Ownership status (Eigentumsverhaeltnis)
76    #[serde(
77        skip_serializing_if = "Option::is_none",
78        alias = "eigentumsverhaeltnis"
79    )]
80    pub ownership: Option<String>,
81
82    /// Manufacturer (Hersteller)
83    #[serde(skip_serializing_if = "Option::is_none", alias = "hersteller")]
84    pub manufacturer: Option<String>,
85
86    /// Manufacturing year (Herstellungsjahr)
87    #[serde(skip_serializing_if = "Option::is_none", alias = "herstellungsjahr")]
88    pub manufacturing_year: Option<i32>,
89
90    /// Installation date (Einbaudatum)
91    #[serde(skip_serializing_if = "Option::is_none", alias = "einbaudatum")]
92    pub installation_date: Option<chrono::DateTime<chrono::Utc>>,
93
94    /// Removal date (Ausbaudatum)
95    #[serde(skip_serializing_if = "Option::is_none", alias = "ausbaudatum")]
96    pub removal_date: Option<chrono::DateTime<chrono::Utc>>,
97
98    /// Calibration date (Eichdatum)
99    #[serde(skip_serializing_if = "Option::is_none", alias = "eichdatum")]
100    pub calibration_date: Option<chrono::DateTime<chrono::Utc>>,
101
102    /// Calibration expiry date (Eichablaufdatum)
103    #[serde(skip_serializing_if = "Option::is_none", alias = "eichablaufdatum")]
104    pub calibration_expiry_date: Option<chrono::DateTime<chrono::Utc>>,
105}
106
107impl Bo4eObject for Meter {
108    fn type_name_german() -> &'static str {
109        "Zaehler"
110    }
111
112    fn type_name_english() -> &'static str {
113        "Meter"
114    }
115
116    fn meta(&self) -> &Bo4eMeta {
117        &self.meta
118    }
119
120    fn meta_mut(&mut self) -> &mut Bo4eMeta {
121        &mut self.meta
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn test_meter_creation() {
131        let meter = Meter {
132            meter_number: Some("1EMH0012345678".to_string()),
133            division: Some(Division::Electricity),
134            ..Default::default()
135        };
136
137        assert_eq!(meter.meter_number, Some("1EMH0012345678".to_string()));
138        assert_eq!(meter.division, Some(Division::Electricity));
139    }
140
141    #[test]
142    fn test_meter_with_registers() {
143        let register = MeterRegister {
144            obis_code: Some("1-0:1.8.0".to_string()),
145            ..Default::default()
146        };
147
148        let meter = Meter {
149            meter_number: Some("TEST123".to_string()),
150            registers: vec![register],
151            ..Default::default()
152        };
153
154        assert_eq!(meter.registers.len(), 1);
155    }
156
157    #[test]
158    fn test_serialize() {
159        let meter = Meter {
160            meta: Bo4eMeta::with_type("Zaehler"),
161            meter_number: Some("1EMH0012345678".to_string()),
162            division: Some(Division::Electricity),
163            ..Default::default()
164        };
165
166        let json = serde_json::to_string(&meter).unwrap();
167        assert!(json.contains(r#""_typ":"Zaehler""#));
168        assert!(json.contains(r#""meterNumber":"1EMH0012345678""#));
169    }
170
171    #[test]
172    fn test_roundtrip() {
173        let meter = Meter {
174            meta: Bo4eMeta::with_type("Zaehler"),
175            meter_number: Some("TEST123".to_string()),
176            division: Some(Division::Electricity),
177            meter_type: Some(MeterType::ModernMeasuringDevice),
178            meter_size: Some(MeterSize::G4),
179            manufacturer: Some("Acme Corp".to_string()),
180            manufacturing_year: Some(2023),
181            ..Default::default()
182        };
183
184        let json = serde_json::to_string(&meter).unwrap();
185        let parsed: Meter = serde_json::from_str(&json).unwrap();
186        assert_eq!(meter, parsed);
187    }
188
189    #[test]
190    fn test_bo4e_object_impl() {
191        assert_eq!(Meter::type_name_german(), "Zaehler");
192        assert_eq!(Meter::type_name_english(), "Meter");
193    }
194}