matc/clusters/codec/
electrical_energy_measurement.rs

1//! Matter TLV encoders and decoders for Electrical Energy Measurement Cluster
2//! Cluster ID: 0x0091
3//!
4//! This file is automatically generated from ElectricalEnergyMeasurement.xml
5
6use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11// Enum definitions
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14#[repr(u8)]
15pub enum MeasurementType {
16    Unspecified = 0,
17    /// Voltage in millivolts (mV)
18    Voltage = 1,
19    /// Active current in milliamps (mA)
20    Activecurrent = 2,
21    /// Reactive current in milliamps (mA)
22    Reactivecurrent = 3,
23    /// Apparent current in milliamps (mA)
24    Apparentcurrent = 4,
25    /// Active power in milliwatts (mW)
26    Activepower = 5,
27    /// Reactive power in millivolt-amps reactive (mVAR)
28    Reactivepower = 6,
29    /// Apparent power in millivolt-amps (mVA)
30    Apparentpower = 7,
31    /// Root mean squared voltage in millivolts (mV)
32    Rmsvoltage = 8,
33    /// Root mean squared current in milliamps (mA)
34    Rmscurrent = 9,
35    /// Root mean squared power in milliwatts (mW)
36    Rmspower = 10,
37    /// AC frequency in millihertz (mHz)
38    Frequency = 11,
39    /// Power Factor ratio in +/- 1/100ths of a percent.
40    Powerfactor = 12,
41    /// AC neutral current in milliamps (mA)
42    Neutralcurrent = 13,
43    /// Electrical energy in milliwatt-hours (mWh)
44    Electricalenergy = 14,
45    /// Reactive power in millivolt-amp-hours reactive (mVARh)
46    Reactiveenergy = 15,
47    /// Apparent power in millivolt-amp-hours (mVAh)
48    Apparentenergy = 16,
49}
50
51impl MeasurementType {
52    /// Convert from u8 value
53    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    /// Convert to u8 value
77    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// Struct definitions
89
90#[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
130// Attribute decoders
131
132/// Decode Accuracy attribute (0x0000)
133pub fn decode_accuracy(inp: &tlv::TlvItemValue) -> anyhow::Result<MeasurementAccuracy> {
134    if let tlv::TlvItemValue::List(_fields) = inp {
135        // Struct with fields
136        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
168/// Decode CumulativeEnergyImported attribute (0x0001)
169pub fn decode_cumulative_energy_imported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
170    if let tlv::TlvItemValue::List(_fields) = inp {
171        // Struct with fields
172        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 if let tlv::TlvItemValue::Null = inp {
183    //    // Null value for nullable struct
184    //    Ok(None)
185    } else {
186    Ok(None)
187    //    Err(anyhow::anyhow!("Expected struct fields or null"))
188    }
189}
190
191/// Decode CumulativeEnergyExported attribute (0x0002)
192pub fn decode_cumulative_energy_exported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
193    if let tlv::TlvItemValue::List(_fields) = inp {
194        // Struct with fields
195        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 if let tlv::TlvItemValue::Null = inp {
206    //    // Null value for nullable struct
207    //    Ok(None)
208    } else {
209    Ok(None)
210    //    Err(anyhow::anyhow!("Expected struct fields or null"))
211    }
212}
213
214/// Decode PeriodicEnergyImported attribute (0x0003)
215pub fn decode_periodic_energy_imported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
216    if let tlv::TlvItemValue::List(_fields) = inp {
217        // Struct with fields
218        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 if let tlv::TlvItemValue::Null = inp {
229    //    // Null value for nullable struct
230    //    Ok(None)
231    } else {
232    Ok(None)
233    //    Err(anyhow::anyhow!("Expected struct fields or null"))
234    }
235}
236
237/// Decode PeriodicEnergyExported attribute (0x0004)
238pub fn decode_periodic_energy_exported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
239    if let tlv::TlvItemValue::List(_fields) = inp {
240        // Struct with fields
241        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 if let tlv::TlvItemValue::Null = inp {
252    //    // Null value for nullable struct
253    //    Ok(None)
254    } else {
255    Ok(None)
256    //    Err(anyhow::anyhow!("Expected struct fields or null"))
257    }
258}
259
260/// Decode CumulativeEnergyReset attribute (0x0005)
261pub fn decode_cumulative_energy_reset(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<CumulativeEnergyReset>> {
262    if let tlv::TlvItemValue::List(_fields) = inp {
263        // Struct with fields
264        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 if let tlv::TlvItemValue::Null = inp {
272    //    // Null value for nullable struct
273    //    Ok(None)
274    } else {
275    Ok(None)
276    //    Err(anyhow::anyhow!("Expected struct fields or null"))
277    }
278}
279
280
281// JSON dispatcher function
282
283/// Decode attribute value and return as JSON string
284///
285/// # Parameters
286/// * `cluster_id` - The cluster identifier
287/// * `attribute_id` - The attribute identifier
288/// * `tlv_value` - The TLV value to decode
289///
290/// # Returns
291/// JSON string representation of the decoded value or error
292pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
293    // Verify this is the correct cluster
294    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
339/// Get list of all attributes supported by this cluster
340///
341/// # Returns
342/// Vector of tuples containing (attribute_id, attribute_name)
343pub 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
366// Event decoders
367
368/// Decode CumulativeEnergyMeasured event (0x00, priority: info)
369pub 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
419/// Decode PeriodicEnergyMeasured event (0x01, priority: info)
420pub 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