1use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14#[repr(u8)]
15pub enum MeasurementType {
16 Unspecified = 0,
17 Voltage = 1,
19 Activecurrent = 2,
21 Reactivecurrent = 3,
23 Apparentcurrent = 4,
25 Activepower = 5,
27 Reactivepower = 6,
29 Apparentpower = 7,
31 Rmsvoltage = 8,
33 Rmscurrent = 9,
35 Rmspower = 10,
37 Frequency = 11,
39 Powerfactor = 12,
41 Neutralcurrent = 13,
43 Electricalenergy = 14,
45 Reactiveenergy = 15,
47 Apparentenergy = 16,
49}
50
51impl MeasurementType {
52 pub fn from_u8(value: u8) -> Option<Self> {
54 match value {
55 0 => Some(MeasurementType::Unspecified),
56 1 => Some(MeasurementType::Voltage),
57 2 => Some(MeasurementType::Activecurrent),
58 3 => Some(MeasurementType::Reactivecurrent),
59 4 => Some(MeasurementType::Apparentcurrent),
60 5 => Some(MeasurementType::Activepower),
61 6 => Some(MeasurementType::Reactivepower),
62 7 => Some(MeasurementType::Apparentpower),
63 8 => Some(MeasurementType::Rmsvoltage),
64 9 => Some(MeasurementType::Rmscurrent),
65 10 => Some(MeasurementType::Rmspower),
66 11 => Some(MeasurementType::Frequency),
67 12 => Some(MeasurementType::Powerfactor),
68 13 => Some(MeasurementType::Neutralcurrent),
69 14 => Some(MeasurementType::Electricalenergy),
70 15 => Some(MeasurementType::Reactiveenergy),
71 16 => Some(MeasurementType::Apparentenergy),
72 _ => None,
73 }
74 }
75
76 pub fn to_u8(self) -> u8 {
78 self as u8
79 }
80}
81
82impl From<MeasurementType> for u8 {
83 fn from(val: MeasurementType) -> Self {
84 val as u8
85 }
86}
87
88#[derive(Debug, serde::Serialize)]
91pub struct CumulativeEnergyReset {
92 pub imported_reset_timestamp: Option<u64>,
93 pub exported_reset_timestamp: Option<u64>,
94 pub imported_reset_systime: Option<u8>,
95 pub exported_reset_systime: Option<u8>,
96}
97
98#[derive(Debug, serde::Serialize)]
99pub struct EnergyMeasurement {
100 pub energy: Option<u64>,
101 pub start_timestamp: Option<u64>,
102 pub end_timestamp: Option<u64>,
103 pub start_systime: Option<u8>,
104 pub end_systime: Option<u8>,
105 pub apparent_energy: Option<u8>,
106 pub reactive_energy: Option<u8>,
107}
108
109#[derive(Debug, serde::Serialize)]
110pub struct MeasurementAccuracyRange {
111 pub range_min: Option<i64>,
112 pub range_max: Option<i64>,
113 pub percent_max: Option<u8>,
114 pub percent_min: Option<u8>,
115 pub percent_typical: Option<u8>,
116 pub fixed_max: Option<u64>,
117 pub fixed_min: Option<u64>,
118 pub fixed_typical: Option<u64>,
119}
120
121#[derive(Debug, serde::Serialize)]
122pub struct MeasurementAccuracy {
123 pub measurement_type: Option<MeasurementType>,
124 pub measured: Option<bool>,
125 pub min_measured_value: Option<i64>,
126 pub max_measured_value: Option<i64>,
127 pub accuracy_ranges: Option<Vec<MeasurementAccuracyRange>>,
128}
129
130pub fn decode_accuracy(inp: &tlv::TlvItemValue) -> anyhow::Result<MeasurementAccuracy> {
134 if let tlv::TlvItemValue::List(_fields) = inp {
135 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
137 Ok(MeasurementAccuracy {
138 measurement_type: item.get_int(&[0]).and_then(|v| MeasurementType::from_u8(v as u8)),
139 measured: item.get_bool(&[1]),
140 min_measured_value: item.get_int(&[2]).map(|v| v as i64),
141 max_measured_value: item.get_int(&[3]).map(|v| v as i64),
142 accuracy_ranges: {
143 if let Some(tlv::TlvItemValue::List(l)) = item.get(&[4]) {
144 let mut items = Vec::new();
145 for list_item in l {
146 items.push(MeasurementAccuracyRange {
147 range_min: list_item.get_int(&[0]).map(|v| v as i64),
148 range_max: list_item.get_int(&[1]).map(|v| v as i64),
149 percent_max: list_item.get_int(&[2]).map(|v| v as u8),
150 percent_min: list_item.get_int(&[3]).map(|v| v as u8),
151 percent_typical: list_item.get_int(&[4]).map(|v| v as u8),
152 fixed_max: list_item.get_int(&[5]),
153 fixed_min: list_item.get_int(&[6]),
154 fixed_typical: list_item.get_int(&[7]),
155 });
156 }
157 Some(items)
158 } else {
159 None
160 }
161 },
162 })
163 } else {
164 Err(anyhow::anyhow!("Expected struct fields"))
165 }
166}
167
168pub fn decode_cumulative_energy_imported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
170 if let tlv::TlvItemValue::List(_fields) = inp {
171 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
173 Ok(Some(EnergyMeasurement {
174 energy: item.get_int(&[0]),
175 start_timestamp: item.get_int(&[1]),
176 end_timestamp: item.get_int(&[2]),
177 start_systime: item.get_int(&[3]).map(|v| v as u8),
178 end_systime: item.get_int(&[4]).map(|v| v as u8),
179 apparent_energy: item.get_int(&[5]).map(|v| v as u8),
180 reactive_energy: item.get_int(&[6]).map(|v| v as u8),
181 }))
182 } else {
186 Ok(None)
187 }
189}
190
191pub fn decode_cumulative_energy_exported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
193 if let tlv::TlvItemValue::List(_fields) = inp {
194 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
196 Ok(Some(EnergyMeasurement {
197 energy: item.get_int(&[0]),
198 start_timestamp: item.get_int(&[1]),
199 end_timestamp: item.get_int(&[2]),
200 start_systime: item.get_int(&[3]).map(|v| v as u8),
201 end_systime: item.get_int(&[4]).map(|v| v as u8),
202 apparent_energy: item.get_int(&[5]).map(|v| v as u8),
203 reactive_energy: item.get_int(&[6]).map(|v| v as u8),
204 }))
205 } else {
209 Ok(None)
210 }
212}
213
214pub fn decode_periodic_energy_imported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
216 if let tlv::TlvItemValue::List(_fields) = inp {
217 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
219 Ok(Some(EnergyMeasurement {
220 energy: item.get_int(&[0]),
221 start_timestamp: item.get_int(&[1]),
222 end_timestamp: item.get_int(&[2]),
223 start_systime: item.get_int(&[3]).map(|v| v as u8),
224 end_systime: item.get_int(&[4]).map(|v| v as u8),
225 apparent_energy: item.get_int(&[5]).map(|v| v as u8),
226 reactive_energy: item.get_int(&[6]).map(|v| v as u8),
227 }))
228 } else {
232 Ok(None)
233 }
235}
236
237pub fn decode_periodic_energy_exported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
239 if let tlv::TlvItemValue::List(_fields) = inp {
240 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
242 Ok(Some(EnergyMeasurement {
243 energy: item.get_int(&[0]),
244 start_timestamp: item.get_int(&[1]),
245 end_timestamp: item.get_int(&[2]),
246 start_systime: item.get_int(&[3]).map(|v| v as u8),
247 end_systime: item.get_int(&[4]).map(|v| v as u8),
248 apparent_energy: item.get_int(&[5]).map(|v| v as u8),
249 reactive_energy: item.get_int(&[6]).map(|v| v as u8),
250 }))
251 } else {
255 Ok(None)
256 }
258}
259
260pub fn decode_cumulative_energy_reset(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<CumulativeEnergyReset>> {
262 if let tlv::TlvItemValue::List(_fields) = inp {
263 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
265 Ok(Some(CumulativeEnergyReset {
266 imported_reset_timestamp: item.get_int(&[0]),
267 exported_reset_timestamp: item.get_int(&[1]),
268 imported_reset_systime: item.get_int(&[2]).map(|v| v as u8),
269 exported_reset_systime: item.get_int(&[3]).map(|v| v as u8),
270 }))
271 } else {
275 Ok(None)
276 }
278}
279
280
281pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
293 if cluster_id != 0x0091 {
295 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0091, got {}\"}}", cluster_id);
296 }
297
298 match attribute_id {
299 0x0000 => {
300 match decode_accuracy(tlv_value) {
301 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
302 Err(e) => format!("{{\"error\": \"{}\"}}", e),
303 }
304 }
305 0x0001 => {
306 match decode_cumulative_energy_imported(tlv_value) {
307 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
308 Err(e) => format!("{{\"error\": \"{}\"}}", e),
309 }
310 }
311 0x0002 => {
312 match decode_cumulative_energy_exported(tlv_value) {
313 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
314 Err(e) => format!("{{\"error\": \"{}\"}}", e),
315 }
316 }
317 0x0003 => {
318 match decode_periodic_energy_imported(tlv_value) {
319 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
320 Err(e) => format!("{{\"error\": \"{}\"}}", e),
321 }
322 }
323 0x0004 => {
324 match decode_periodic_energy_exported(tlv_value) {
325 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
326 Err(e) => format!("{{\"error\": \"{}\"}}", e),
327 }
328 }
329 0x0005 => {
330 match decode_cumulative_energy_reset(tlv_value) {
331 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
332 Err(e) => format!("{{\"error\": \"{}\"}}", e),
333 }
334 }
335 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
336 }
337}
338
339pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
344 vec![
345 (0x0000, "Accuracy"),
346 (0x0001, "CumulativeEnergyImported"),
347 (0x0002, "CumulativeEnergyExported"),
348 (0x0003, "PeriodicEnergyImported"),
349 (0x0004, "PeriodicEnergyExported"),
350 (0x0005, "CumulativeEnergyReset"),
351 ]
352}
353
354#[derive(Debug, serde::Serialize)]
355pub struct CumulativeEnergyMeasuredEvent {
356 pub energy_imported: Option<EnergyMeasurement>,
357 pub energy_exported: Option<EnergyMeasurement>,
358}
359
360#[derive(Debug, serde::Serialize)]
361pub struct PeriodicEnergyMeasuredEvent {
362 pub energy_imported: Option<EnergyMeasurement>,
363 pub energy_exported: Option<EnergyMeasurement>,
364}
365
366pub fn decode_cumulative_energy_measured_event(inp: &tlv::TlvItemValue) -> anyhow::Result<CumulativeEnergyMeasuredEvent> {
370 if let tlv::TlvItemValue::List(_fields) = inp {
371 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
372 Ok(CumulativeEnergyMeasuredEvent {
373 energy_imported: {
374 if let Some(nested_tlv) = item.get(&[0]) {
375 if let tlv::TlvItemValue::List(_) = nested_tlv {
376 let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
377 Some(EnergyMeasurement {
378 energy: nested_item.get_int(&[0]),
379 start_timestamp: nested_item.get_int(&[1]),
380 end_timestamp: nested_item.get_int(&[2]),
381 start_systime: nested_item.get_int(&[3]).map(|v| v as u8),
382 end_systime: nested_item.get_int(&[4]).map(|v| v as u8),
383 apparent_energy: nested_item.get_int(&[5]).map(|v| v as u8),
384 reactive_energy: nested_item.get_int(&[6]).map(|v| v as u8),
385 })
386 } else {
387 None
388 }
389 } else {
390 None
391 }
392 },
393 energy_exported: {
394 if let Some(nested_tlv) = item.get(&[1]) {
395 if let tlv::TlvItemValue::List(_) = nested_tlv {
396 let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
397 Some(EnergyMeasurement {
398 energy: nested_item.get_int(&[0]),
399 start_timestamp: nested_item.get_int(&[1]),
400 end_timestamp: nested_item.get_int(&[2]),
401 start_systime: nested_item.get_int(&[3]).map(|v| v as u8),
402 end_systime: nested_item.get_int(&[4]).map(|v| v as u8),
403 apparent_energy: nested_item.get_int(&[5]).map(|v| v as u8),
404 reactive_energy: nested_item.get_int(&[6]).map(|v| v as u8),
405 })
406 } else {
407 None
408 }
409 } else {
410 None
411 }
412 },
413 })
414 } else {
415 Err(anyhow::anyhow!("Expected struct fields"))
416 }
417}
418
419pub fn decode_periodic_energy_measured_event(inp: &tlv::TlvItemValue) -> anyhow::Result<PeriodicEnergyMeasuredEvent> {
421 if let tlv::TlvItemValue::List(_fields) = inp {
422 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
423 Ok(PeriodicEnergyMeasuredEvent {
424 energy_imported: {
425 if let Some(nested_tlv) = item.get(&[0]) {
426 if let tlv::TlvItemValue::List(_) = nested_tlv {
427 let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
428 Some(EnergyMeasurement {
429 energy: nested_item.get_int(&[0]),
430 start_timestamp: nested_item.get_int(&[1]),
431 end_timestamp: nested_item.get_int(&[2]),
432 start_systime: nested_item.get_int(&[3]).map(|v| v as u8),
433 end_systime: nested_item.get_int(&[4]).map(|v| v as u8),
434 apparent_energy: nested_item.get_int(&[5]).map(|v| v as u8),
435 reactive_energy: nested_item.get_int(&[6]).map(|v| v as u8),
436 })
437 } else {
438 None
439 }
440 } else {
441 None
442 }
443 },
444 energy_exported: {
445 if let Some(nested_tlv) = item.get(&[1]) {
446 if let tlv::TlvItemValue::List(_) = nested_tlv {
447 let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
448 Some(EnergyMeasurement {
449 energy: nested_item.get_int(&[0]),
450 start_timestamp: nested_item.get_int(&[1]),
451 end_timestamp: nested_item.get_int(&[2]),
452 start_systime: nested_item.get_int(&[3]).map(|v| v as u8),
453 end_systime: nested_item.get_int(&[4]).map(|v| v as u8),
454 apparent_energy: nested_item.get_int(&[5]).map(|v| v as u8),
455 reactive_energy: nested_item.get_int(&[6]).map(|v| v as u8),
456 })
457 } else {
458 None
459 }
460 } else {
461 None
462 }
463 },
464 })
465 } else {
466 Err(anyhow::anyhow!("Expected struct fields"))
467 }
468}
469