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
6#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13// Enum definitions
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16#[repr(u8)]
17pub enum MeasurementType {
18    Unspecified = 0,
19    /// Voltage in millivolts (mV)
20    Voltage = 1,
21    /// Active current in milliamps (mA)
22    Activecurrent = 2,
23    /// Reactive current in milliamps (mA)
24    Reactivecurrent = 3,
25    /// Apparent current in milliamps (mA)
26    Apparentcurrent = 4,
27    /// Active power in milliwatts (mW)
28    Activepower = 5,
29    /// Reactive power in millivolt-amps reactive (mVAR)
30    Reactivepower = 6,
31    /// Apparent power in millivolt-amps (mVA)
32    Apparentpower = 7,
33    /// Root mean squared voltage in millivolts (mV)
34    Rmsvoltage = 8,
35    /// Root mean squared current in milliamps (mA)
36    Rmscurrent = 9,
37    /// Root mean squared power in milliwatts (mW)
38    Rmspower = 10,
39    /// AC frequency in millihertz (mHz)
40    Frequency = 11,
41    /// Power Factor ratio in +/- 1/100ths of a percent.
42    Powerfactor = 12,
43    /// AC neutral current in milliamps (mA)
44    Neutralcurrent = 13,
45    /// Electrical energy in milliwatt-hours (mWh)
46    Electricalenergy = 14,
47    /// Reactive power in millivolt-amp-hours reactive (mVARh)
48    Reactiveenergy = 15,
49    /// Apparent power in millivolt-amp-hours (mVAh)
50    Apparentenergy = 16,
51}
52
53impl MeasurementType {
54    /// Convert from u8 value
55    pub fn from_u8(value: u8) -> Option<Self> {
56        match value {
57            0 => Some(MeasurementType::Unspecified),
58            1 => Some(MeasurementType::Voltage),
59            2 => Some(MeasurementType::Activecurrent),
60            3 => Some(MeasurementType::Reactivecurrent),
61            4 => Some(MeasurementType::Apparentcurrent),
62            5 => Some(MeasurementType::Activepower),
63            6 => Some(MeasurementType::Reactivepower),
64            7 => Some(MeasurementType::Apparentpower),
65            8 => Some(MeasurementType::Rmsvoltage),
66            9 => Some(MeasurementType::Rmscurrent),
67            10 => Some(MeasurementType::Rmspower),
68            11 => Some(MeasurementType::Frequency),
69            12 => Some(MeasurementType::Powerfactor),
70            13 => Some(MeasurementType::Neutralcurrent),
71            14 => Some(MeasurementType::Electricalenergy),
72            15 => Some(MeasurementType::Reactiveenergy),
73            16 => Some(MeasurementType::Apparentenergy),
74            _ => None,
75        }
76    }
77
78    /// Convert to u8 value
79    pub fn to_u8(self) -> u8 {
80        self as u8
81    }
82}
83
84impl From<MeasurementType> for u8 {
85    fn from(val: MeasurementType) -> Self {
86        val as u8
87    }
88}
89
90// Struct definitions
91
92#[derive(Debug, serde::Serialize)]
93pub struct CumulativeEnergyReset {
94    pub imported_reset_timestamp: Option<u64>,
95    pub exported_reset_timestamp: Option<u64>,
96    pub imported_reset_systime: Option<u8>,
97    pub exported_reset_systime: Option<u8>,
98}
99
100#[derive(Debug, serde::Serialize)]
101pub struct EnergyMeasurement {
102    pub energy: Option<u64>,
103    pub start_timestamp: Option<u64>,
104    pub end_timestamp: Option<u64>,
105    pub start_systime: Option<u8>,
106    pub end_systime: Option<u8>,
107    pub apparent_energy: Option<u8>,
108    pub reactive_energy: Option<u8>,
109}
110
111#[derive(Debug, serde::Serialize)]
112pub struct MeasurementAccuracyRange {
113    pub range_min: Option<i64>,
114    pub range_max: Option<i64>,
115    pub percent_max: Option<u8>,
116    pub percent_min: Option<u8>,
117    pub percent_typical: Option<u8>,
118    pub fixed_max: Option<u64>,
119    pub fixed_min: Option<u64>,
120    pub fixed_typical: Option<u64>,
121}
122
123#[derive(Debug, serde::Serialize)]
124pub struct MeasurementAccuracy {
125    pub measurement_type: Option<MeasurementType>,
126    pub measured: Option<bool>,
127    pub min_measured_value: Option<i64>,
128    pub max_measured_value: Option<i64>,
129    pub accuracy_ranges: Option<Vec<MeasurementAccuracyRange>>,
130}
131
132// Attribute decoders
133
134/// Decode Accuracy attribute (0x0000)
135pub fn decode_accuracy(inp: &tlv::TlvItemValue) -> anyhow::Result<MeasurementAccuracy> {
136    if let tlv::TlvItemValue::List(_fields) = inp {
137        // Struct with fields
138        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
139        Ok(MeasurementAccuracy {
140                measurement_type: item.get_int(&[0]).and_then(|v| MeasurementType::from_u8(v as u8)),
141                measured: item.get_bool(&[1]),
142                min_measured_value: item.get_int(&[2]).map(|v| v as i64),
143                max_measured_value: item.get_int(&[3]).map(|v| v as i64),
144                accuracy_ranges: {
145                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[4]) {
146                        let mut items = Vec::new();
147                        for list_item in l {
148                            items.push(MeasurementAccuracyRange {
149                range_min: list_item.get_int(&[0]).map(|v| v as i64),
150                range_max: list_item.get_int(&[1]).map(|v| v as i64),
151                percent_max: list_item.get_int(&[2]).map(|v| v as u8),
152                percent_min: list_item.get_int(&[3]).map(|v| v as u8),
153                percent_typical: list_item.get_int(&[4]).map(|v| v as u8),
154                fixed_max: list_item.get_int(&[5]),
155                fixed_min: list_item.get_int(&[6]),
156                fixed_typical: list_item.get_int(&[7]),
157                            });
158                        }
159                        Some(items)
160                    } else {
161                        None
162                    }
163                },
164        })
165    } else {
166        Err(anyhow::anyhow!("Expected struct fields"))
167    }
168}
169
170/// Decode CumulativeEnergyImported attribute (0x0001)
171pub fn decode_cumulative_energy_imported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
172    if let tlv::TlvItemValue::List(_fields) = inp {
173        // Struct with fields
174        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
175        Ok(Some(EnergyMeasurement {
176                energy: item.get_int(&[0]),
177                start_timestamp: item.get_int(&[1]),
178                end_timestamp: item.get_int(&[2]),
179                start_systime: item.get_int(&[3]).map(|v| v as u8),
180                end_systime: item.get_int(&[4]).map(|v| v as u8),
181                apparent_energy: item.get_int(&[5]).map(|v| v as u8),
182                reactive_energy: item.get_int(&[6]).map(|v| v as u8),
183        }))
184    //} else if let tlv::TlvItemValue::Null = inp {
185    //    // Null value for nullable struct
186    //    Ok(None)
187    } else {
188    Ok(None)
189    //    Err(anyhow::anyhow!("Expected struct fields or null"))
190    }
191}
192
193/// Decode CumulativeEnergyExported attribute (0x0002)
194pub fn decode_cumulative_energy_exported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
195    if let tlv::TlvItemValue::List(_fields) = inp {
196        // Struct with fields
197        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
198        Ok(Some(EnergyMeasurement {
199                energy: item.get_int(&[0]),
200                start_timestamp: item.get_int(&[1]),
201                end_timestamp: item.get_int(&[2]),
202                start_systime: item.get_int(&[3]).map(|v| v as u8),
203                end_systime: item.get_int(&[4]).map(|v| v as u8),
204                apparent_energy: item.get_int(&[5]).map(|v| v as u8),
205                reactive_energy: item.get_int(&[6]).map(|v| v as u8),
206        }))
207    //} else if let tlv::TlvItemValue::Null = inp {
208    //    // Null value for nullable struct
209    //    Ok(None)
210    } else {
211    Ok(None)
212    //    Err(anyhow::anyhow!("Expected struct fields or null"))
213    }
214}
215
216/// Decode PeriodicEnergyImported attribute (0x0003)
217pub fn decode_periodic_energy_imported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
218    if let tlv::TlvItemValue::List(_fields) = inp {
219        // Struct with fields
220        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
221        Ok(Some(EnergyMeasurement {
222                energy: item.get_int(&[0]),
223                start_timestamp: item.get_int(&[1]),
224                end_timestamp: item.get_int(&[2]),
225                start_systime: item.get_int(&[3]).map(|v| v as u8),
226                end_systime: item.get_int(&[4]).map(|v| v as u8),
227                apparent_energy: item.get_int(&[5]).map(|v| v as u8),
228                reactive_energy: item.get_int(&[6]).map(|v| v as u8),
229        }))
230    //} else if let tlv::TlvItemValue::Null = inp {
231    //    // Null value for nullable struct
232    //    Ok(None)
233    } else {
234    Ok(None)
235    //    Err(anyhow::anyhow!("Expected struct fields or null"))
236    }
237}
238
239/// Decode PeriodicEnergyExported attribute (0x0004)
240pub fn decode_periodic_energy_exported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
241    if let tlv::TlvItemValue::List(_fields) = inp {
242        // Struct with fields
243        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
244        Ok(Some(EnergyMeasurement {
245                energy: item.get_int(&[0]),
246                start_timestamp: item.get_int(&[1]),
247                end_timestamp: item.get_int(&[2]),
248                start_systime: item.get_int(&[3]).map(|v| v as u8),
249                end_systime: item.get_int(&[4]).map(|v| v as u8),
250                apparent_energy: item.get_int(&[5]).map(|v| v as u8),
251                reactive_energy: item.get_int(&[6]).map(|v| v as u8),
252        }))
253    //} else if let tlv::TlvItemValue::Null = inp {
254    //    // Null value for nullable struct
255    //    Ok(None)
256    } else {
257    Ok(None)
258    //    Err(anyhow::anyhow!("Expected struct fields or null"))
259    }
260}
261
262/// Decode CumulativeEnergyReset attribute (0x0005)
263pub fn decode_cumulative_energy_reset(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<CumulativeEnergyReset>> {
264    if let tlv::TlvItemValue::List(_fields) = inp {
265        // Struct with fields
266        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
267        Ok(Some(CumulativeEnergyReset {
268                imported_reset_timestamp: item.get_int(&[0]),
269                exported_reset_timestamp: item.get_int(&[1]),
270                imported_reset_systime: item.get_int(&[2]).map(|v| v as u8),
271                exported_reset_systime: item.get_int(&[3]).map(|v| v as u8),
272        }))
273    //} else if let tlv::TlvItemValue::Null = inp {
274    //    // Null value for nullable struct
275    //    Ok(None)
276    } else {
277    Ok(None)
278    //    Err(anyhow::anyhow!("Expected struct fields or null"))
279    }
280}
281
282
283// JSON dispatcher function
284
285/// Decode attribute value and return as JSON string
286///
287/// # Parameters
288/// * `cluster_id` - The cluster identifier
289/// * `attribute_id` - The attribute identifier
290/// * `tlv_value` - The TLV value to decode
291///
292/// # Returns
293/// JSON string representation of the decoded value or error
294pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
295    // Verify this is the correct cluster
296    if cluster_id != 0x0091 {
297        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0091, got {}\"}}", cluster_id);
298    }
299
300    match attribute_id {
301        0x0000 => {
302            match decode_accuracy(tlv_value) {
303                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
304                Err(e) => format!("{{\"error\": \"{}\"}}", e),
305            }
306        }
307        0x0001 => {
308            match decode_cumulative_energy_imported(tlv_value) {
309                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
310                Err(e) => format!("{{\"error\": \"{}\"}}", e),
311            }
312        }
313        0x0002 => {
314            match decode_cumulative_energy_exported(tlv_value) {
315                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
316                Err(e) => format!("{{\"error\": \"{}\"}}", e),
317            }
318        }
319        0x0003 => {
320            match decode_periodic_energy_imported(tlv_value) {
321                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
322                Err(e) => format!("{{\"error\": \"{}\"}}", e),
323            }
324        }
325        0x0004 => {
326            match decode_periodic_energy_exported(tlv_value) {
327                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
328                Err(e) => format!("{{\"error\": \"{}\"}}", e),
329            }
330        }
331        0x0005 => {
332            match decode_cumulative_energy_reset(tlv_value) {
333                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
334                Err(e) => format!("{{\"error\": \"{}\"}}", e),
335            }
336        }
337        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
338    }
339}
340
341/// Get list of all attributes supported by this cluster
342///
343/// # Returns
344/// Vector of tuples containing (attribute_id, attribute_name)
345pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
346    vec![
347        (0x0000, "Accuracy"),
348        (0x0001, "CumulativeEnergyImported"),
349        (0x0002, "CumulativeEnergyExported"),
350        (0x0003, "PeriodicEnergyImported"),
351        (0x0004, "PeriodicEnergyExported"),
352        (0x0005, "CumulativeEnergyReset"),
353    ]
354}
355
356// Typed facade (invokes + reads)
357
358/// Read `Accuracy` attribute from cluster `Electrical Energy Measurement`.
359pub async fn read_accuracy(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<MeasurementAccuracy> {
360    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ELECTRICAL_ENERGY_MEASUREMENT, crate::clusters::defs::CLUSTER_ELECTRICAL_ENERGY_MEASUREMENT_ATTR_ID_ACCURACY).await?;
361    decode_accuracy(&tlv)
362}
363
364/// Read `CumulativeEnergyImported` attribute from cluster `Electrical Energy Measurement`.
365pub async fn read_cumulative_energy_imported(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<EnergyMeasurement>> {
366    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ELECTRICAL_ENERGY_MEASUREMENT, crate::clusters::defs::CLUSTER_ELECTRICAL_ENERGY_MEASUREMENT_ATTR_ID_CUMULATIVEENERGYIMPORTED).await?;
367    decode_cumulative_energy_imported(&tlv)
368}
369
370/// Read `CumulativeEnergyExported` attribute from cluster `Electrical Energy Measurement`.
371pub async fn read_cumulative_energy_exported(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<EnergyMeasurement>> {
372    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ELECTRICAL_ENERGY_MEASUREMENT, crate::clusters::defs::CLUSTER_ELECTRICAL_ENERGY_MEASUREMENT_ATTR_ID_CUMULATIVEENERGYEXPORTED).await?;
373    decode_cumulative_energy_exported(&tlv)
374}
375
376/// Read `PeriodicEnergyImported` attribute from cluster `Electrical Energy Measurement`.
377pub async fn read_periodic_energy_imported(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<EnergyMeasurement>> {
378    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ELECTRICAL_ENERGY_MEASUREMENT, crate::clusters::defs::CLUSTER_ELECTRICAL_ENERGY_MEASUREMENT_ATTR_ID_PERIODICENERGYIMPORTED).await?;
379    decode_periodic_energy_imported(&tlv)
380}
381
382/// Read `PeriodicEnergyExported` attribute from cluster `Electrical Energy Measurement`.
383pub async fn read_periodic_energy_exported(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<EnergyMeasurement>> {
384    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ELECTRICAL_ENERGY_MEASUREMENT, crate::clusters::defs::CLUSTER_ELECTRICAL_ENERGY_MEASUREMENT_ATTR_ID_PERIODICENERGYEXPORTED).await?;
385    decode_periodic_energy_exported(&tlv)
386}
387
388/// Read `CumulativeEnergyReset` attribute from cluster `Electrical Energy Measurement`.
389pub async fn read_cumulative_energy_reset(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<CumulativeEnergyReset>> {
390    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ELECTRICAL_ENERGY_MEASUREMENT, crate::clusters::defs::CLUSTER_ELECTRICAL_ENERGY_MEASUREMENT_ATTR_ID_CUMULATIVEENERGYRESET).await?;
391    decode_cumulative_energy_reset(&tlv)
392}
393
394#[derive(Debug, serde::Serialize)]
395pub struct CumulativeEnergyMeasuredEvent {
396    pub energy_imported: Option<EnergyMeasurement>,
397    pub energy_exported: Option<EnergyMeasurement>,
398}
399
400#[derive(Debug, serde::Serialize)]
401pub struct PeriodicEnergyMeasuredEvent {
402    pub energy_imported: Option<EnergyMeasurement>,
403    pub energy_exported: Option<EnergyMeasurement>,
404}
405
406// Event decoders
407
408/// Decode CumulativeEnergyMeasured event (0x00, priority: info)
409pub fn decode_cumulative_energy_measured_event(inp: &tlv::TlvItemValue) -> anyhow::Result<CumulativeEnergyMeasuredEvent> {
410    if let tlv::TlvItemValue::List(_fields) = inp {
411        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
412        Ok(CumulativeEnergyMeasuredEvent {
413                                energy_imported: {
414                    if let Some(nested_tlv) = item.get(&[0]) {
415                        if let tlv::TlvItemValue::List(_) = nested_tlv {
416                            let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
417                            Some(EnergyMeasurement {
418                energy: nested_item.get_int(&[0]),
419                start_timestamp: nested_item.get_int(&[1]),
420                end_timestamp: nested_item.get_int(&[2]),
421                start_systime: nested_item.get_int(&[3]).map(|v| v as u8),
422                end_systime: nested_item.get_int(&[4]).map(|v| v as u8),
423                apparent_energy: nested_item.get_int(&[5]).map(|v| v as u8),
424                reactive_energy: nested_item.get_int(&[6]).map(|v| v as u8),
425                            })
426                        } else {
427                            None
428                        }
429                    } else {
430                        None
431                    }
432                },
433                                energy_exported: {
434                    if let Some(nested_tlv) = item.get(&[1]) {
435                        if let tlv::TlvItemValue::List(_) = nested_tlv {
436                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
437                            Some(EnergyMeasurement {
438                energy: nested_item.get_int(&[0]),
439                start_timestamp: nested_item.get_int(&[1]),
440                end_timestamp: nested_item.get_int(&[2]),
441                start_systime: nested_item.get_int(&[3]).map(|v| v as u8),
442                end_systime: nested_item.get_int(&[4]).map(|v| v as u8),
443                apparent_energy: nested_item.get_int(&[5]).map(|v| v as u8),
444                reactive_energy: nested_item.get_int(&[6]).map(|v| v as u8),
445                            })
446                        } else {
447                            None
448                        }
449                    } else {
450                        None
451                    }
452                },
453        })
454    } else {
455        Err(anyhow::anyhow!("Expected struct fields"))
456    }
457}
458
459/// Decode PeriodicEnergyMeasured event (0x01, priority: info)
460pub fn decode_periodic_energy_measured_event(inp: &tlv::TlvItemValue) -> anyhow::Result<PeriodicEnergyMeasuredEvent> {
461    if let tlv::TlvItemValue::List(_fields) = inp {
462        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
463        Ok(PeriodicEnergyMeasuredEvent {
464                                energy_imported: {
465                    if let Some(nested_tlv) = item.get(&[0]) {
466                        if let tlv::TlvItemValue::List(_) = nested_tlv {
467                            let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
468                            Some(EnergyMeasurement {
469                energy: nested_item.get_int(&[0]),
470                start_timestamp: nested_item.get_int(&[1]),
471                end_timestamp: nested_item.get_int(&[2]),
472                start_systime: nested_item.get_int(&[3]).map(|v| v as u8),
473                end_systime: nested_item.get_int(&[4]).map(|v| v as u8),
474                apparent_energy: nested_item.get_int(&[5]).map(|v| v as u8),
475                reactive_energy: nested_item.get_int(&[6]).map(|v| v as u8),
476                            })
477                        } else {
478                            None
479                        }
480                    } else {
481                        None
482                    }
483                },
484                                energy_exported: {
485                    if let Some(nested_tlv) = item.get(&[1]) {
486                        if let tlv::TlvItemValue::List(_) = nested_tlv {
487                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
488                            Some(EnergyMeasurement {
489                energy: nested_item.get_int(&[0]),
490                start_timestamp: nested_item.get_int(&[1]),
491                end_timestamp: nested_item.get_int(&[2]),
492                start_systime: nested_item.get_int(&[3]).map(|v| v as u8),
493                end_systime: nested_item.get_int(&[4]).map(|v| v as u8),
494                apparent_energy: nested_item.get_int(&[5]).map(|v| v as u8),
495                reactive_energy: nested_item.get_int(&[6]).map(|v| v as u8),
496                            })
497                        } else {
498                            None
499                        }
500                    } else {
501                        None
502                    }
503                },
504        })
505    } else {
506        Err(anyhow::anyhow!("Expected struct fields"))
507    }
508}
509