1#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16#[repr(u8)]
17pub enum MeasurementType {
18 Unspecified = 0,
19 Voltage = 1,
21 Activecurrent = 2,
23 Reactivecurrent = 3,
25 Apparentcurrent = 4,
27 Activepower = 5,
29 Reactivepower = 6,
31 Apparentpower = 7,
33 Rmsvoltage = 8,
35 Rmscurrent = 9,
37 Rmspower = 10,
39 Frequency = 11,
41 Powerfactor = 12,
43 Neutralcurrent = 13,
45 Electricalenergy = 14,
47 Reactiveenergy = 15,
49 Apparentenergy = 16,
51}
52
53impl MeasurementType {
54 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 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#[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
132pub fn decode_accuracy(inp: &tlv::TlvItemValue) -> anyhow::Result<MeasurementAccuracy> {
136 if let tlv::TlvItemValue::List(_fields) = inp {
137 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
170pub fn decode_cumulative_energy_imported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
172 if let tlv::TlvItemValue::List(_fields) = inp {
173 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 {
188 Ok(None)
189 }
191}
192
193pub fn decode_cumulative_energy_exported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
195 if let tlv::TlvItemValue::List(_fields) = inp {
196 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 {
211 Ok(None)
212 }
214}
215
216pub fn decode_periodic_energy_imported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
218 if let tlv::TlvItemValue::List(_fields) = inp {
219 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 {
234 Ok(None)
235 }
237}
238
239pub fn decode_periodic_energy_exported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
241 if let tlv::TlvItemValue::List(_fields) = inp {
242 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 {
257 Ok(None)
258 }
260}
261
262pub fn decode_cumulative_energy_reset(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<CumulativeEnergyReset>> {
264 if let tlv::TlvItemValue::List(_fields) = inp {
265 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 {
277 Ok(None)
278 }
280}
281
282
283pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
295 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
341pub 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
356pub 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
364pub 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
370pub 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
376pub 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
382pub 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
388pub 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
406pub 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
459pub 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