1use serde::{Deserialize, Serialize};
6
7use crate::com::{Address, Hardware, MeterRegister};
8use crate::enums::{Division, MeterSize, MeterType};
9use crate::traits::{Bo4eMeta, Bo4eObject};
10
11#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
29#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
30#[cfg_attr(feature = "json-schema", schemars(rename = "Zaehler"))]
31#[serde(rename_all = "camelCase")]
32pub struct Meter {
33 #[serde(flatten)]
35 pub meta: Bo4eMeta,
36
37 #[serde(skip_serializing_if = "Option::is_none", alias = "zaehlernummer")]
39 #[cfg_attr(feature = "json-schema", schemars(rename = "zaehlernummer"))]
40 pub meter_number: Option<String>,
41
42 #[serde(skip_serializing_if = "Option::is_none", alias = "sparte")]
44 #[cfg_attr(feature = "json-schema", schemars(rename = "sparte"))]
45 pub division: Option<Division>,
46
47 #[serde(skip_serializing_if = "Option::is_none", alias = "zaehlertyp")]
49 #[cfg_attr(feature = "json-schema", schemars(rename = "zaehlertyp"))]
50 pub meter_type: Option<MeterType>,
51
52 #[serde(skip_serializing_if = "Option::is_none", alias = "zaehlergroesse")]
54 #[cfg_attr(feature = "json-schema", schemars(rename = "zaehlergroesse"))]
55 pub meter_size: Option<MeterSize>,
56
57 #[serde(skip_serializing_if = "Option::is_none", alias = "standort")]
59 #[cfg_attr(feature = "json-schema", schemars(rename = "standort"))]
60 pub location: Option<Address>,
61
62 #[serde(default, skip_serializing_if = "Vec::is_empty", alias = "zaehlwerke")]
64 #[cfg_attr(feature = "json-schema", schemars(rename = "zaehlwerke"))]
65 pub registers: Vec<MeterRegister>,
66
67 #[serde(
69 default,
70 skip_serializing_if = "Vec::is_empty",
71 alias = "geraeteeigenschaften"
72 )]
73 #[cfg_attr(feature = "json-schema", schemars(rename = "geraeteeigenschaften"))]
74 pub hardware: Vec<Hardware>,
75
76 #[serde(skip_serializing_if = "Option::is_none", alias = "marktlokationsId")]
78 #[cfg_attr(feature = "json-schema", schemars(rename = "marktlokationsId"))]
79 pub market_location_id: Option<String>,
80
81 #[serde(skip_serializing_if = "Option::is_none", alias = "messlokationsId")]
83 #[cfg_attr(feature = "json-schema", schemars(rename = "messlokationsId"))]
84 pub metering_location_id: Option<String>,
85
86 #[serde(
88 skip_serializing_if = "Option::is_none",
89 alias = "eigentumsverhaeltnis"
90 )]
91 #[cfg_attr(feature = "json-schema", schemars(rename = "eigentumsverhaeltnis"))]
92 pub ownership: Option<String>,
93
94 #[serde(skip_serializing_if = "Option::is_none", alias = "hersteller")]
96 #[cfg_attr(feature = "json-schema", schemars(rename = "hersteller"))]
97 pub manufacturer: Option<String>,
98
99 #[serde(skip_serializing_if = "Option::is_none", alias = "herstellungsjahr")]
101 #[cfg_attr(feature = "json-schema", schemars(rename = "herstellungsjahr"))]
102 pub manufacturing_year: Option<i32>,
103
104 #[serde(skip_serializing_if = "Option::is_none", alias = "einbaudatum")]
106 #[cfg_attr(feature = "json-schema", schemars(rename = "einbaudatum"))]
107 pub installation_date: Option<chrono::DateTime<chrono::Utc>>,
108
109 #[serde(skip_serializing_if = "Option::is_none", alias = "ausbaudatum")]
111 #[cfg_attr(feature = "json-schema", schemars(rename = "ausbaudatum"))]
112 pub removal_date: Option<chrono::DateTime<chrono::Utc>>,
113
114 #[serde(skip_serializing_if = "Option::is_none", alias = "eichdatum")]
116 #[cfg_attr(feature = "json-schema", schemars(rename = "eichdatum"))]
117 pub calibration_date: Option<chrono::DateTime<chrono::Utc>>,
118
119 #[serde(skip_serializing_if = "Option::is_none", alias = "eichablaufdatum")]
121 #[cfg_attr(feature = "json-schema", schemars(rename = "eichablaufdatum"))]
122 pub calibration_expiry_date: Option<chrono::DateTime<chrono::Utc>>,
123}
124
125impl Bo4eObject for Meter {
126 fn type_name_german() -> &'static str {
127 "Zaehler"
128 }
129
130 fn type_name_english() -> &'static str {
131 "Meter"
132 }
133
134 fn meta(&self) -> &Bo4eMeta {
135 &self.meta
136 }
137
138 fn meta_mut(&mut self) -> &mut Bo4eMeta {
139 &mut self.meta
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_meter_creation() {
149 let meter = Meter {
150 meter_number: Some("1EMH0012345678".to_string()),
151 division: Some(Division::Electricity),
152 ..Default::default()
153 };
154
155 assert_eq!(meter.meter_number, Some("1EMH0012345678".to_string()));
156 assert_eq!(meter.division, Some(Division::Electricity));
157 }
158
159 #[test]
160 fn test_meter_with_registers() {
161 let register = MeterRegister {
162 obis_code: Some("1-0:1.8.0".to_string()),
163 ..Default::default()
164 };
165
166 let meter = Meter {
167 meter_number: Some("TEST123".to_string()),
168 registers: vec![register],
169 ..Default::default()
170 };
171
172 assert_eq!(meter.registers.len(), 1);
173 }
174
175 #[test]
176 fn test_serialize() {
177 let meter = Meter {
178 meta: Bo4eMeta::with_type("Zaehler"),
179 meter_number: Some("1EMH0012345678".to_string()),
180 division: Some(Division::Electricity),
181 ..Default::default()
182 };
183
184 let json = serde_json::to_string(&meter).unwrap();
185 assert!(json.contains(r#""_typ":"Zaehler""#));
186 assert!(json.contains(r#""meterNumber":"1EMH0012345678""#));
187 }
188
189 #[test]
190 fn test_roundtrip() {
191 let meter = Meter {
192 meta: Bo4eMeta::with_type("Zaehler"),
193 meter_number: Some("TEST123".to_string()),
194 division: Some(Division::Electricity),
195 meter_type: Some(MeterType::ModernMeasuringDevice),
196 meter_size: Some(MeterSize::G4),
197 manufacturer: Some("Acme Corp".to_string()),
198 manufacturing_year: Some(2023),
199 ..Default::default()
200 };
201
202 let json = serde_json::to_string(&meter).unwrap();
203 let parsed: Meter = serde_json::from_str(&json).unwrap();
204 assert_eq!(meter, parsed);
205 }
206
207 #[test]
208 fn test_bo4e_object_impl() {
209 assert_eq!(Meter::type_name_german(), "Zaehler");
210 assert_eq!(Meter::type_name_english(), "Meter");
211 }
212}