#![allow(clippy::too_many_arguments)]
use crate::tlv;
use anyhow;
use serde_json;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum MeasurementType {
Unspecified = 0,
Voltage = 1,
Activecurrent = 2,
Reactivecurrent = 3,
Apparentcurrent = 4,
Activepower = 5,
Reactivepower = 6,
Apparentpower = 7,
Rmsvoltage = 8,
Rmscurrent = 9,
Rmspower = 10,
Frequency = 11,
Powerfactor = 12,
Neutralcurrent = 13,
Electricalenergy = 14,
Reactiveenergy = 15,
Apparentenergy = 16,
}
impl MeasurementType {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(MeasurementType::Unspecified),
1 => Some(MeasurementType::Voltage),
2 => Some(MeasurementType::Activecurrent),
3 => Some(MeasurementType::Reactivecurrent),
4 => Some(MeasurementType::Apparentcurrent),
5 => Some(MeasurementType::Activepower),
6 => Some(MeasurementType::Reactivepower),
7 => Some(MeasurementType::Apparentpower),
8 => Some(MeasurementType::Rmsvoltage),
9 => Some(MeasurementType::Rmscurrent),
10 => Some(MeasurementType::Rmspower),
11 => Some(MeasurementType::Frequency),
12 => Some(MeasurementType::Powerfactor),
13 => Some(MeasurementType::Neutralcurrent),
14 => Some(MeasurementType::Electricalenergy),
15 => Some(MeasurementType::Reactiveenergy),
16 => Some(MeasurementType::Apparentenergy),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<MeasurementType> for u8 {
fn from(val: MeasurementType) -> Self {
val as u8
}
}
#[derive(Debug, serde::Serialize)]
pub struct CumulativeEnergyReset {
pub imported_reset_timestamp: Option<u64>,
pub exported_reset_timestamp: Option<u64>,
pub imported_reset_systime: Option<u8>,
pub exported_reset_systime: Option<u8>,
}
#[derive(Debug, serde::Serialize)]
pub struct EnergyMeasurement {
pub energy: Option<u64>,
pub start_timestamp: Option<u64>,
pub end_timestamp: Option<u64>,
pub start_systime: Option<u8>,
pub end_systime: Option<u8>,
pub apparent_energy: Option<u8>,
pub reactive_energy: Option<u8>,
}
#[derive(Debug, serde::Serialize)]
pub struct MeasurementAccuracyRange {
pub range_min: Option<i64>,
pub range_max: Option<i64>,
pub percent_max: Option<u16>,
pub percent_min: Option<u16>,
pub percent_typical: Option<u16>,
pub fixed_max: Option<u64>,
pub fixed_min: Option<u64>,
pub fixed_typical: Option<u64>,
}
#[derive(Debug, serde::Serialize)]
pub struct MeasurementAccuracy {
pub measurement_type: Option<MeasurementType>,
pub measured: Option<bool>,
pub min_measured_value: Option<i64>,
pub max_measured_value: Option<i64>,
pub accuracy_ranges: Option<Vec<MeasurementAccuracyRange>>,
}
pub fn decode_accuracy(inp: &tlv::TlvItemValue) -> anyhow::Result<MeasurementAccuracy> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(MeasurementAccuracy {
measurement_type: item.get_int(&[0]).and_then(|v| MeasurementType::from_u8(v as u8)),
measured: item.get_bool(&[1]),
min_measured_value: item.get_int(&[2]).map(|v| v as i64),
max_measured_value: item.get_int(&[3]).map(|v| v as i64),
accuracy_ranges: {
if let Some(tlv::TlvItemValue::List(l)) = item.get(&[4]) {
let mut items = Vec::new();
for list_item in l {
items.push(MeasurementAccuracyRange {
range_min: list_item.get_int(&[0]).map(|v| v as i64),
range_max: list_item.get_int(&[1]).map(|v| v as i64),
percent_max: list_item.get_int(&[2]).map(|v| v as u16),
percent_min: list_item.get_int(&[3]).map(|v| v as u16),
percent_typical: list_item.get_int(&[4]).map(|v| v as u16),
fixed_max: list_item.get_int(&[5]),
fixed_min: list_item.get_int(&[6]),
fixed_typical: list_item.get_int(&[7]),
});
}
Some(items)
} else {
None
}
},
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}
pub fn decode_cumulative_energy_imported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(Some(EnergyMeasurement {
energy: item.get_int(&[0]),
start_timestamp: item.get_int(&[1]),
end_timestamp: item.get_int(&[2]),
start_systime: item.get_int(&[3]).map(|v| v as u8),
end_systime: item.get_int(&[4]).map(|v| v as u8),
apparent_energy: item.get_int(&[5]).map(|v| v as u8),
reactive_energy: item.get_int(&[6]).map(|v| v as u8),
}))
} else {
Ok(None)
}
}
pub fn decode_cumulative_energy_exported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(Some(EnergyMeasurement {
energy: item.get_int(&[0]),
start_timestamp: item.get_int(&[1]),
end_timestamp: item.get_int(&[2]),
start_systime: item.get_int(&[3]).map(|v| v as u8),
end_systime: item.get_int(&[4]).map(|v| v as u8),
apparent_energy: item.get_int(&[5]).map(|v| v as u8),
reactive_energy: item.get_int(&[6]).map(|v| v as u8),
}))
} else {
Ok(None)
}
}
pub fn decode_periodic_energy_imported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(Some(EnergyMeasurement {
energy: item.get_int(&[0]),
start_timestamp: item.get_int(&[1]),
end_timestamp: item.get_int(&[2]),
start_systime: item.get_int(&[3]).map(|v| v as u8),
end_systime: item.get_int(&[4]).map(|v| v as u8),
apparent_energy: item.get_int(&[5]).map(|v| v as u8),
reactive_energy: item.get_int(&[6]).map(|v| v as u8),
}))
} else {
Ok(None)
}
}
pub fn decode_periodic_energy_exported(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<EnergyMeasurement>> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(Some(EnergyMeasurement {
energy: item.get_int(&[0]),
start_timestamp: item.get_int(&[1]),
end_timestamp: item.get_int(&[2]),
start_systime: item.get_int(&[3]).map(|v| v as u8),
end_systime: item.get_int(&[4]).map(|v| v as u8),
apparent_energy: item.get_int(&[5]).map(|v| v as u8),
reactive_energy: item.get_int(&[6]).map(|v| v as u8),
}))
} else {
Ok(None)
}
}
pub fn decode_cumulative_energy_reset(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<CumulativeEnergyReset>> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(Some(CumulativeEnergyReset {
imported_reset_timestamp: item.get_int(&[0]),
exported_reset_timestamp: item.get_int(&[1]),
imported_reset_systime: item.get_int(&[2]).map(|v| v as u8),
exported_reset_systime: item.get_int(&[3]).map(|v| v as u8),
}))
} else {
Ok(None)
}
}
pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
if cluster_id != 0x0091 {
return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0091, got {}\"}}", cluster_id);
}
match attribute_id {
0x0000 => {
match decode_accuracy(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0001 => {
match decode_cumulative_energy_imported(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0002 => {
match decode_cumulative_energy_exported(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0003 => {
match decode_periodic_energy_imported(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0004 => {
match decode_periodic_energy_exported(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0005 => {
match decode_cumulative_energy_reset(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
_ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
}
}
pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
vec![
(0x0000, "Accuracy"),
(0x0001, "CumulativeEnergyImported"),
(0x0002, "CumulativeEnergyExported"),
(0x0003, "PeriodicEnergyImported"),
(0x0004, "PeriodicEnergyExported"),
(0x0005, "CumulativeEnergyReset"),
]
}
pub async fn read_accuracy(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<MeasurementAccuracy> {
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?;
decode_accuracy(&tlv)
}
pub async fn read_cumulative_energy_imported(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<EnergyMeasurement>> {
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?;
decode_cumulative_energy_imported(&tlv)
}
pub async fn read_cumulative_energy_exported(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<EnergyMeasurement>> {
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?;
decode_cumulative_energy_exported(&tlv)
}
pub async fn read_periodic_energy_imported(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<EnergyMeasurement>> {
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?;
decode_periodic_energy_imported(&tlv)
}
pub async fn read_periodic_energy_exported(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<EnergyMeasurement>> {
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?;
decode_periodic_energy_exported(&tlv)
}
pub async fn read_cumulative_energy_reset(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<CumulativeEnergyReset>> {
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?;
decode_cumulative_energy_reset(&tlv)
}
#[derive(Debug, serde::Serialize)]
pub struct CumulativeEnergyMeasuredEvent {
pub energy_imported: Option<EnergyMeasurement>,
pub energy_exported: Option<EnergyMeasurement>,
}
#[derive(Debug, serde::Serialize)]
pub struct PeriodicEnergyMeasuredEvent {
pub energy_imported: Option<EnergyMeasurement>,
pub energy_exported: Option<EnergyMeasurement>,
}
pub fn decode_cumulative_energy_measured_event(inp: &tlv::TlvItemValue) -> anyhow::Result<CumulativeEnergyMeasuredEvent> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(CumulativeEnergyMeasuredEvent {
energy_imported: {
if let Some(nested_tlv) = item.get(&[0]) {
if let tlv::TlvItemValue::List(_) = nested_tlv {
let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
Some(EnergyMeasurement {
energy: nested_item.get_int(&[0]),
start_timestamp: nested_item.get_int(&[1]),
end_timestamp: nested_item.get_int(&[2]),
start_systime: nested_item.get_int(&[3]).map(|v| v as u8),
end_systime: nested_item.get_int(&[4]).map(|v| v as u8),
apparent_energy: nested_item.get_int(&[5]).map(|v| v as u8),
reactive_energy: nested_item.get_int(&[6]).map(|v| v as u8),
})
} else {
None
}
} else {
None
}
},
energy_exported: {
if let Some(nested_tlv) = item.get(&[1]) {
if let tlv::TlvItemValue::List(_) = nested_tlv {
let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
Some(EnergyMeasurement {
energy: nested_item.get_int(&[0]),
start_timestamp: nested_item.get_int(&[1]),
end_timestamp: nested_item.get_int(&[2]),
start_systime: nested_item.get_int(&[3]).map(|v| v as u8),
end_systime: nested_item.get_int(&[4]).map(|v| v as u8),
apparent_energy: nested_item.get_int(&[5]).map(|v| v as u8),
reactive_energy: nested_item.get_int(&[6]).map(|v| v as u8),
})
} else {
None
}
} else {
None
}
},
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}
pub fn decode_periodic_energy_measured_event(inp: &tlv::TlvItemValue) -> anyhow::Result<PeriodicEnergyMeasuredEvent> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(PeriodicEnergyMeasuredEvent {
energy_imported: {
if let Some(nested_tlv) = item.get(&[0]) {
if let tlv::TlvItemValue::List(_) = nested_tlv {
let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
Some(EnergyMeasurement {
energy: nested_item.get_int(&[0]),
start_timestamp: nested_item.get_int(&[1]),
end_timestamp: nested_item.get_int(&[2]),
start_systime: nested_item.get_int(&[3]).map(|v| v as u8),
end_systime: nested_item.get_int(&[4]).map(|v| v as u8),
apparent_energy: nested_item.get_int(&[5]).map(|v| v as u8),
reactive_energy: nested_item.get_int(&[6]).map(|v| v as u8),
})
} else {
None
}
} else {
None
}
},
energy_exported: {
if let Some(nested_tlv) = item.get(&[1]) {
if let tlv::TlvItemValue::List(_) = nested_tlv {
let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
Some(EnergyMeasurement {
energy: nested_item.get_int(&[0]),
start_timestamp: nested_item.get_int(&[1]),
end_timestamp: nested_item.get_int(&[2]),
start_systime: nested_item.get_int(&[3]).map(|v| v as u8),
end_systime: nested_item.get_int(&[4]).map(|v| v as u8),
apparent_energy: nested_item.get_int(&[5]).map(|v| v as u8),
reactive_energy: nested_item.get_int(&[6]).map(|v| v as u8),
})
} else {
None
}
} else {
None
}
},
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}