#![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 AdjustmentCause {
Localoptimization = 0,
Gridoptimization = 1,
}
impl AdjustmentCause {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(AdjustmentCause::Localoptimization),
1 => Some(AdjustmentCause::Gridoptimization),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<AdjustmentCause> for u8 {
fn from(val: AdjustmentCause) -> Self {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum Cause {
Normalcompletion = 0,
Offline = 1,
Fault = 2,
Useroptout = 3,
Cancelled = 4,
}
impl Cause {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(Cause::Normalcompletion),
1 => Some(Cause::Offline),
2 => Some(Cause::Fault),
3 => Some(Cause::Useroptout),
4 => Some(Cause::Cancelled),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<Cause> for u8 {
fn from(val: Cause) -> Self {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum CostType {
Financial = 0,
Ghgemissions = 1,
Comfort = 2,
Temperature = 3,
}
impl CostType {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(CostType::Financial),
1 => Some(CostType::Ghgemissions),
2 => Some(CostType::Comfort),
3 => Some(CostType::Temperature),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<CostType> for u8 {
fn from(val: CostType) -> Self {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum ESAState {
Offline = 0,
Online = 1,
Fault = 2,
Poweradjustactive = 3,
Paused = 4,
}
impl ESAState {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(ESAState::Offline),
1 => Some(ESAState::Online),
2 => Some(ESAState::Fault),
3 => Some(ESAState::Poweradjustactive),
4 => Some(ESAState::Paused),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<ESAState> for u8 {
fn from(val: ESAState) -> Self {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum ESAType {
Evse = 0,
Spaceheating = 1,
Waterheating = 2,
Spacecooling = 3,
Spaceheatingcooling = 4,
Batterystorage = 5,
Solarpv = 6,
Fridgefreezer = 7,
Washingmachine = 8,
Dishwasher = 9,
Cooking = 10,
Homewaterpump = 11,
Irrigationwaterpump = 12,
Poolpump = 13,
Other = 255,
}
impl ESAType {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(ESAType::Evse),
1 => Some(ESAType::Spaceheating),
2 => Some(ESAType::Waterheating),
3 => Some(ESAType::Spacecooling),
4 => Some(ESAType::Spaceheatingcooling),
5 => Some(ESAType::Batterystorage),
6 => Some(ESAType::Solarpv),
7 => Some(ESAType::Fridgefreezer),
8 => Some(ESAType::Washingmachine),
9 => Some(ESAType::Dishwasher),
10 => Some(ESAType::Cooking),
11 => Some(ESAType::Homewaterpump),
12 => Some(ESAType::Irrigationwaterpump),
13 => Some(ESAType::Poolpump),
255 => Some(ESAType::Other),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<ESAType> for u8 {
fn from(val: ESAType) -> Self {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum ForecastUpdateReason {
Internaloptimization = 0,
Localoptimization = 1,
Gridoptimization = 2,
}
impl ForecastUpdateReason {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(ForecastUpdateReason::Internaloptimization),
1 => Some(ForecastUpdateReason::Localoptimization),
2 => Some(ForecastUpdateReason::Gridoptimization),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<ForecastUpdateReason> for u8 {
fn from(val: ForecastUpdateReason) -> Self {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum OptOutState {
Nooptout = 0,
Localoptout = 1,
Gridoptout = 2,
Optout = 3,
}
impl OptOutState {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(OptOutState::Nooptout),
1 => Some(OptOutState::Localoptout),
2 => Some(OptOutState::Gridoptout),
3 => Some(OptOutState::Optout),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<OptOutState> for u8 {
fn from(val: OptOutState) -> Self {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum PowerAdjustReason {
Noadjustment = 0,
Localoptimizationadjustment = 1,
Gridoptimizationadjustment = 2,
}
impl PowerAdjustReason {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(PowerAdjustReason::Noadjustment),
1 => Some(PowerAdjustReason::Localoptimizationadjustment),
2 => Some(PowerAdjustReason::Gridoptimizationadjustment),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<PowerAdjustReason> for u8 {
fn from(val: PowerAdjustReason) -> Self {
val as u8
}
}
#[derive(Debug, serde::Serialize)]
pub struct Constraints {
pub start_time: Option<u64>,
pub duration: Option<u32>,
pub nominal_power: Option<u32>,
pub maximum_energy: Option<u64>,
pub load_control: Option<i8>,
}
#[derive(Debug, serde::Serialize)]
pub struct Cost {
pub cost_type: Option<CostType>,
pub value: Option<i32>,
pub decimal_points: Option<u8>,
pub currency: Option<u16>,
}
#[derive(Debug, serde::Serialize)]
pub struct Forecast {
pub forecast_id: Option<u32>,
pub active_slot_number: Option<u16>,
pub start_time: Option<u64>,
pub end_time: Option<u64>,
pub earliest_start_time: Option<u64>,
pub latest_end_time: Option<u64>,
pub is_pausable: Option<bool>,
pub slots: Option<Vec<Slot>>,
pub forecast_update_reason: Option<ForecastUpdateReason>,
}
#[derive(Debug, serde::Serialize)]
pub struct PowerAdjustCapability {
pub power_adjust_capability: Option<Vec<PowerAdjust>>,
pub cause: Option<PowerAdjustReason>,
}
#[derive(Debug, serde::Serialize)]
pub struct PowerAdjust {
pub min_power: Option<u32>,
pub max_power: Option<u32>,
pub min_duration: Option<u32>,
pub max_duration: Option<u32>,
}
#[derive(Debug, serde::Serialize)]
pub struct SlotAdjustment {
pub slot_index: Option<u8>,
pub nominal_power: Option<u32>,
pub duration: Option<u32>,
}
#[derive(Debug, serde::Serialize)]
pub struct Slot {
pub min_duration: Option<u32>,
pub max_duration: Option<u32>,
pub default_duration: Option<u32>,
pub elapsed_slot_time: Option<u32>,
pub remaining_slot_time: Option<u32>,
pub slot_is_pausable: Option<bool>,
pub min_pause_duration: Option<u32>,
pub max_pause_duration: Option<u32>,
pub manufacturer_esa_state: Option<u16>,
pub nominal_power: Option<u32>,
pub min_power: Option<u32>,
pub max_power: Option<u32>,
pub nominal_energy: Option<u64>,
pub costs: Option<Vec<Cost>>,
pub min_power_adjustment: Option<u32>,
pub max_power_adjustment: Option<u32>,
pub min_duration_adjustment: Option<u32>,
pub max_duration_adjustment: Option<u32>,
}
pub fn encode_power_adjust_request(power: u32, duration: u32, cause: AdjustmentCause) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt32(power)).into(),
(1, tlv::TlvItemValueEnc::UInt32(duration)).into(),
(2, tlv::TlvItemValueEnc::UInt8(cause.to_u8())).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_start_time_adjust_request(requested_start_time: u64, cause: AdjustmentCause) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt64(requested_start_time)).into(),
(1, tlv::TlvItemValueEnc::UInt8(cause.to_u8())).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_pause_request(duration: u32, cause: AdjustmentCause) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt32(duration)).into(),
(1, tlv::TlvItemValueEnc::UInt8(cause.to_u8())).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_modify_forecast_request(forecast_id: u32, slot_adjustments: Vec<SlotAdjustment>, cause: AdjustmentCause) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt32(forecast_id)).into(),
(1, tlv::TlvItemValueEnc::Array(slot_adjustments.into_iter().map(|v| {
let mut fields = Vec::new();
if let Some(x) = v.slot_index { fields.push((0, tlv::TlvItemValueEnc::UInt8(x)).into()); }
if let Some(x) = v.nominal_power { fields.push((1, tlv::TlvItemValueEnc::UInt32(x)).into()); }
if let Some(x) = v.duration { fields.push((2, tlv::TlvItemValueEnc::UInt32(x)).into()); }
(0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
}).collect())).into(),
(2, tlv::TlvItemValueEnc::UInt8(cause.to_u8())).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_request_constraint_based_forecast(constraints: Vec<Constraints>, cause: AdjustmentCause) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::Array(constraints.into_iter().map(|v| {
let mut fields = Vec::new();
if let Some(x) = v.start_time { fields.push((0, tlv::TlvItemValueEnc::UInt64(x)).into()); }
if let Some(x) = v.duration { fields.push((1, tlv::TlvItemValueEnc::UInt32(x)).into()); }
if let Some(x) = v.nominal_power { fields.push((2, tlv::TlvItemValueEnc::UInt32(x)).into()); }
if let Some(x) = v.maximum_energy { fields.push((3, tlv::TlvItemValueEnc::UInt64(x)).into()); }
if let Some(x) = v.load_control { fields.push((4, tlv::TlvItemValueEnc::Int8(x)).into()); }
(0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
}).collect())).into(),
(1, tlv::TlvItemValueEnc::UInt8(cause.to_u8())).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn decode_esa_type(inp: &tlv::TlvItemValue) -> anyhow::Result<ESAType> {
if let tlv::TlvItemValue::Int(v) = inp {
ESAType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
} else {
Err(anyhow::anyhow!("Expected Integer"))
}
}
pub fn decode_esa_can_generate(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
if let tlv::TlvItemValue::Bool(v) = inp {
Ok(*v)
} else {
Err(anyhow::anyhow!("Expected Bool"))
}
}
pub fn decode_esa_state(inp: &tlv::TlvItemValue) -> anyhow::Result<ESAState> {
if let tlv::TlvItemValue::Int(v) = inp {
ESAState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
} else {
Err(anyhow::anyhow!("Expected Integer"))
}
}
pub fn decode_abs_min_power(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
if let tlv::TlvItemValue::Int(v) = inp {
Ok(*v as u32)
} else {
Err(anyhow::anyhow!("Expected UInt32"))
}
}
pub fn decode_abs_max_power(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
if let tlv::TlvItemValue::Int(v) = inp {
Ok(*v as u32)
} else {
Err(anyhow::anyhow!("Expected UInt32"))
}
}
pub fn decode_power_adjustment_capability(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<PowerAdjustCapability>> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(Some(PowerAdjustCapability {
power_adjust_capability: {
if let Some(tlv::TlvItemValue::List(l)) = item.get(&[0]) {
let mut items = Vec::new();
for list_item in l {
items.push(PowerAdjust {
min_power: list_item.get_int(&[0]).map(|v| v as u32),
max_power: list_item.get_int(&[1]).map(|v| v as u32),
min_duration: list_item.get_int(&[2]).map(|v| v as u32),
max_duration: list_item.get_int(&[3]).map(|v| v as u32),
});
}
Some(items)
} else {
None
}
},
cause: item.get_int(&[1]).and_then(|v| PowerAdjustReason::from_u8(v as u8)),
}))
} else {
Ok(None)
}
}
pub fn decode_forecast(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Forecast>> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(Some(Forecast {
forecast_id: item.get_int(&[0]).map(|v| v as u32),
active_slot_number: item.get_int(&[1]).map(|v| v as u16),
start_time: item.get_int(&[2]),
end_time: item.get_int(&[3]),
earliest_start_time: item.get_int(&[4]),
latest_end_time: item.get_int(&[5]),
is_pausable: item.get_bool(&[6]),
slots: {
if let Some(tlv::TlvItemValue::List(l)) = item.get(&[7]) {
let mut items = Vec::new();
for list_item in l {
items.push(Slot {
min_duration: list_item.get_int(&[0]).map(|v| v as u32),
max_duration: list_item.get_int(&[1]).map(|v| v as u32),
default_duration: list_item.get_int(&[2]).map(|v| v as u32),
elapsed_slot_time: list_item.get_int(&[3]).map(|v| v as u32),
remaining_slot_time: list_item.get_int(&[4]).map(|v| v as u32),
slot_is_pausable: list_item.get_bool(&[5]),
min_pause_duration: list_item.get_int(&[6]).map(|v| v as u32),
max_pause_duration: list_item.get_int(&[7]).map(|v| v as u32),
manufacturer_esa_state: list_item.get_int(&[8]).map(|v| v as u16),
nominal_power: list_item.get_int(&[9]).map(|v| v as u32),
min_power: list_item.get_int(&[10]).map(|v| v as u32),
max_power: list_item.get_int(&[11]).map(|v| v as u32),
nominal_energy: list_item.get_int(&[12]),
costs: {
if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[13]) {
let mut items = Vec::new();
for list_item in l {
items.push(Cost {
cost_type: list_item.get_int(&[0]).and_then(|v| CostType::from_u8(v as u8)),
value: list_item.get_int(&[1]).map(|v| v as i32),
decimal_points: list_item.get_int(&[2]).map(|v| v as u8),
currency: list_item.get_int(&[3]).map(|v| v as u16),
});
}
Some(items)
} else {
None
}
},
min_power_adjustment: list_item.get_int(&[14]).map(|v| v as u32),
max_power_adjustment: list_item.get_int(&[15]).map(|v| v as u32),
min_duration_adjustment: list_item.get_int(&[16]).map(|v| v as u32),
max_duration_adjustment: list_item.get_int(&[17]).map(|v| v as u32),
});
}
Some(items)
} else {
None
}
},
forecast_update_reason: item.get_int(&[8]).and_then(|v| ForecastUpdateReason::from_u8(v as u8)),
}))
} else {
Ok(None)
}
}
pub fn decode_opt_out_state(inp: &tlv::TlvItemValue) -> anyhow::Result<OptOutState> {
if let tlv::TlvItemValue::Int(v) = inp {
OptOutState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
} else {
Err(anyhow::anyhow!("Expected Integer"))
}
}
pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
if cluster_id != 0x0098 {
return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0098, got {}\"}}", cluster_id);
}
match attribute_id {
0x0000 => {
match decode_esa_type(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0001 => {
match decode_esa_can_generate(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0002 => {
match decode_esa_state(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0003 => {
match decode_abs_min_power(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0004 => {
match decode_abs_max_power(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0005 => {
match decode_power_adjustment_capability(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0006 => {
match decode_forecast(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0007 => {
match decode_opt_out_state(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, "ESAType"),
(0x0001, "ESACanGenerate"),
(0x0002, "ESAState"),
(0x0003, "AbsMinPower"),
(0x0004, "AbsMaxPower"),
(0x0005, "PowerAdjustmentCapability"),
(0x0006, "Forecast"),
(0x0007, "OptOutState"),
]
}
pub fn get_command_list() -> Vec<(u32, &'static str)> {
vec![
(0x00, "PowerAdjustRequest"),
(0x01, "CancelPowerAdjustRequest"),
(0x02, "StartTimeAdjustRequest"),
(0x03, "PauseRequest"),
(0x04, "ResumeRequest"),
(0x05, "ModifyForecastRequest"),
(0x06, "RequestConstraintBasedForecast"),
(0x07, "CancelRequest"),
]
}
pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
match cmd_id {
0x00 => Some("PowerAdjustRequest"),
0x01 => Some("CancelPowerAdjustRequest"),
0x02 => Some("StartTimeAdjustRequest"),
0x03 => Some("PauseRequest"),
0x04 => Some("ResumeRequest"),
0x05 => Some("ModifyForecastRequest"),
0x06 => Some("RequestConstraintBasedForecast"),
0x07 => Some("CancelRequest"),
_ => None,
}
}
pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
match cmd_id {
0x00 => Some(vec![
crate::clusters::codec::CommandField { tag: 0, name: "power", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
crate::clusters::codec::CommandField { tag: 1, name: "duration", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
crate::clusters::codec::CommandField { tag: 2, name: "cause", kind: crate::clusters::codec::FieldKind::Enum { name: "AdjustmentCause", variants: &[(0, "Localoptimization"), (1, "Gridoptimization")] }, optional: false, nullable: false },
]),
0x01 => Some(vec![]),
0x02 => Some(vec![
crate::clusters::codec::CommandField { tag: 0, name: "requested_start_time", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: false },
crate::clusters::codec::CommandField { tag: 1, name: "cause", kind: crate::clusters::codec::FieldKind::Enum { name: "AdjustmentCause", variants: &[(0, "Localoptimization"), (1, "Gridoptimization")] }, optional: false, nullable: false },
]),
0x03 => Some(vec![
crate::clusters::codec::CommandField { tag: 0, name: "duration", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
crate::clusters::codec::CommandField { tag: 1, name: "cause", kind: crate::clusters::codec::FieldKind::Enum { name: "AdjustmentCause", variants: &[(0, "Localoptimization"), (1, "Gridoptimization")] }, optional: false, nullable: false },
]),
0x04 => Some(vec![]),
0x05 => Some(vec![
crate::clusters::codec::CommandField { tag: 0, name: "forecast_id", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
crate::clusters::codec::CommandField { tag: 1, name: "slot_adjustments", kind: crate::clusters::codec::FieldKind::List { entry_type: "SlotAdjustmentStruct" }, optional: false, nullable: false },
crate::clusters::codec::CommandField { tag: 2, name: "cause", kind: crate::clusters::codec::FieldKind::Enum { name: "AdjustmentCause", variants: &[(0, "Localoptimization"), (1, "Gridoptimization")] }, optional: false, nullable: false },
]),
0x06 => Some(vec![
crate::clusters::codec::CommandField { tag: 0, name: "constraints", kind: crate::clusters::codec::FieldKind::List { entry_type: "ConstraintsStruct" }, optional: false, nullable: false },
crate::clusters::codec::CommandField { tag: 1, name: "cause", kind: crate::clusters::codec::FieldKind::Enum { name: "AdjustmentCause", variants: &[(0, "Localoptimization"), (1, "Gridoptimization")] }, optional: false, nullable: false },
]),
0x07 => Some(vec![]),
_ => None,
}
}
pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
match cmd_id {
0x00 => {
let power = crate::clusters::codec::json_util::get_u32(args, "power")?;
let duration = crate::clusters::codec::json_util::get_u32(args, "duration")?;
let cause = {
let n = crate::clusters::codec::json_util::get_u64(args, "cause")?;
AdjustmentCause::from_u8(n as u8).ok_or_else(|| anyhow::anyhow!("invalid AdjustmentCause: {}", n))?
};
encode_power_adjust_request(power, duration, cause)
}
0x01 => Ok(vec![]),
0x02 => {
let requested_start_time = crate::clusters::codec::json_util::get_u64(args, "requested_start_time")?;
let cause = {
let n = crate::clusters::codec::json_util::get_u64(args, "cause")?;
AdjustmentCause::from_u8(n as u8).ok_or_else(|| anyhow::anyhow!("invalid AdjustmentCause: {}", n))?
};
encode_start_time_adjust_request(requested_start_time, cause)
}
0x03 => {
let duration = crate::clusters::codec::json_util::get_u32(args, "duration")?;
let cause = {
let n = crate::clusters::codec::json_util::get_u64(args, "cause")?;
AdjustmentCause::from_u8(n as u8).ok_or_else(|| anyhow::anyhow!("invalid AdjustmentCause: {}", n))?
};
encode_pause_request(duration, cause)
}
0x04 => Ok(vec![]),
0x05 => Err(anyhow::anyhow!("command \"ModifyForecastRequest\" has complex args: use raw mode")),
0x06 => Err(anyhow::anyhow!("command \"RequestConstraintBasedForecast\" has complex args: use raw mode")),
0x07 => Ok(vec![]),
_ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
}
}
pub async fn power_adjust_request(conn: &crate::controller::Connection, endpoint: u16, power: u32, duration: u32, cause: AdjustmentCause) -> anyhow::Result<()> {
conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_CMD_ID_POWERADJUSTREQUEST, &encode_power_adjust_request(power, duration, cause)?).await?;
Ok(())
}
pub async fn cancel_power_adjust_request(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_CMD_ID_CANCELPOWERADJUSTREQUEST, &[]).await?;
Ok(())
}
pub async fn start_time_adjust_request(conn: &crate::controller::Connection, endpoint: u16, requested_start_time: u64, cause: AdjustmentCause) -> anyhow::Result<()> {
conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_CMD_ID_STARTTIMEADJUSTREQUEST, &encode_start_time_adjust_request(requested_start_time, cause)?).await?;
Ok(())
}
pub async fn pause_request(conn: &crate::controller::Connection, endpoint: u16, duration: u32, cause: AdjustmentCause) -> anyhow::Result<()> {
conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_CMD_ID_PAUSEREQUEST, &encode_pause_request(duration, cause)?).await?;
Ok(())
}
pub async fn resume_request(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_CMD_ID_RESUMEREQUEST, &[]).await?;
Ok(())
}
pub async fn modify_forecast_request(conn: &crate::controller::Connection, endpoint: u16, forecast_id: u32, slot_adjustments: Vec<SlotAdjustment>, cause: AdjustmentCause) -> anyhow::Result<()> {
conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_CMD_ID_MODIFYFORECASTREQUEST, &encode_modify_forecast_request(forecast_id, slot_adjustments, cause)?).await?;
Ok(())
}
pub async fn request_constraint_based_forecast(conn: &crate::controller::Connection, endpoint: u16, constraints: Vec<Constraints>, cause: AdjustmentCause) -> anyhow::Result<()> {
conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_CMD_ID_REQUESTCONSTRAINTBASEDFORECAST, &encode_request_constraint_based_forecast(constraints, cause)?).await?;
Ok(())
}
pub async fn cancel_request(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_CMD_ID_CANCELREQUEST, &[]).await?;
Ok(())
}
pub async fn read_esa_type(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ESAType> {
let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_ATTR_ID_ESATYPE).await?;
decode_esa_type(&tlv)
}
pub async fn read_esa_can_generate(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_ATTR_ID_ESACANGENERATE).await?;
decode_esa_can_generate(&tlv)
}
pub async fn read_esa_state(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ESAState> {
let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_ATTR_ID_ESASTATE).await?;
decode_esa_state(&tlv)
}
pub async fn read_abs_min_power(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u32> {
let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_ATTR_ID_ABSMINPOWER).await?;
decode_abs_min_power(&tlv)
}
pub async fn read_abs_max_power(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u32> {
let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_ATTR_ID_ABSMAXPOWER).await?;
decode_abs_max_power(&tlv)
}
pub async fn read_power_adjustment_capability(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<PowerAdjustCapability>> {
let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_ATTR_ID_POWERADJUSTMENTCAPABILITY).await?;
decode_power_adjustment_capability(&tlv)
}
pub async fn read_forecast(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<Forecast>> {
let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_ATTR_ID_FORECAST).await?;
decode_forecast(&tlv)
}
pub async fn read_opt_out_state(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<OptOutState> {
let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_DEVICE_ENERGY_MANAGEMENT, crate::clusters::defs::CLUSTER_DEVICE_ENERGY_MANAGEMENT_ATTR_ID_OPTOUTSTATE).await?;
decode_opt_out_state(&tlv)
}
#[derive(Debug, serde::Serialize)]
pub struct PowerAdjustEndEvent {
pub cause: Option<Cause>,
pub duration: Option<u32>,
pub energy_use: Option<u64>,
}
#[derive(Debug, serde::Serialize)]
pub struct ResumedEvent {
pub cause: Option<Cause>,
}
pub fn decode_power_adjust_end_event(inp: &tlv::TlvItemValue) -> anyhow::Result<PowerAdjustEndEvent> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(PowerAdjustEndEvent {
cause: item.get_int(&[0]).and_then(|v| Cause::from_u8(v as u8)),
duration: item.get_int(&[1]).map(|v| v as u32),
energy_use: item.get_int(&[2]),
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}
pub fn decode_resumed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<ResumedEvent> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(ResumedEvent {
cause: item.get_int(&[0]).and_then(|v| Cause::from_u8(v as u8)),
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}