matc/clusters/codec/
water_heater_management.rs

1//! Matter TLV encoders and decoders for Water Heater Management Cluster
2//! Cluster ID: 0x0094
3//!
4//! This file is automatically generated from WaterHeaterManagement.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 BoostState {
16    /// Boost is not currently active
17    Inactive = 0,
18    /// Boost is currently active
19    Active = 1,
20}
21
22impl BoostState {
23    /// Convert from u8 value
24    pub fn from_u8(value: u8) -> Option<Self> {
25        match value {
26            0 => Some(BoostState::Inactive),
27            1 => Some(BoostState::Active),
28            _ => None,
29        }
30    }
31
32    /// Convert to u8 value
33    pub fn to_u8(self) -> u8 {
34        self as u8
35    }
36}
37
38impl From<BoostState> for u8 {
39    fn from(val: BoostState) -> Self {
40        val as u8
41    }
42}
43
44// Bitmap definitions
45
46/// WaterHeaterHeatSource bitmap type
47pub type WaterHeaterHeatSource = u8;
48
49/// Constants for WaterHeaterHeatSource
50pub mod waterheaterheatsource {
51    /// Immersion Heating Element 1
52    pub const IMMERSION_ELEMENT1: u8 = 0x01;
53    /// Immersion Heating Element 2
54    pub const IMMERSION_ELEMENT2: u8 = 0x02;
55    /// Heat pump Heating
56    pub const HEAT_PUMP: u8 = 0x04;
57    /// Boiler Heating (e.g. Gas or Oil)
58    pub const BOILER: u8 = 0x08;
59    /// Other Heating
60    pub const OTHER: u8 = 0x10;
61}
62
63// Struct definitions
64
65#[derive(Debug, serde::Serialize)]
66pub struct WaterHeaterBoostInfo {
67    pub duration: Option<u32>,
68    pub one_shot: Option<bool>,
69    pub emergency_boost: Option<bool>,
70    pub temporary_setpoint: Option<i16>,
71    pub target_percentage: Option<u8>,
72    pub target_reheat: Option<u8>,
73}
74
75// Command encoders
76
77/// Encode Boost command (0x00)
78pub fn encode_boost(boost_info: WaterHeaterBoostInfo) -> anyhow::Result<Vec<u8>> {
79            // Encode struct WaterHeaterBoostInfoStruct
80            let mut boost_info_fields = Vec::new();
81            if let Some(x) = boost_info.duration { boost_info_fields.push((0, tlv::TlvItemValueEnc::UInt32(x)).into()); }
82            if let Some(x) = boost_info.one_shot { boost_info_fields.push((1, tlv::TlvItemValueEnc::Bool(x)).into()); }
83            if let Some(x) = boost_info.emergency_boost { boost_info_fields.push((2, tlv::TlvItemValueEnc::Bool(x)).into()); }
84            if let Some(x) = boost_info.temporary_setpoint { boost_info_fields.push((3, tlv::TlvItemValueEnc::Int16(x)).into()); }
85            // TODO: encoding for field target_percentage (percent) not implemented
86            // TODO: encoding for field target_reheat (percent) not implemented
87    let tlv = tlv::TlvItemEnc {
88        tag: 0,
89        value: tlv::TlvItemValueEnc::StructInvisible(vec![
90        (0, tlv::TlvItemValueEnc::StructInvisible(boost_info_fields)).into(),
91        ]),
92    };
93    Ok(tlv.encode()?)
94}
95
96// Attribute decoders
97
98/// Decode HeaterTypes attribute (0x0000)
99pub fn decode_heater_types(inp: &tlv::TlvItemValue) -> anyhow::Result<WaterHeaterHeatSource> {
100    if let tlv::TlvItemValue::Int(v) = inp {
101        Ok(*v as u8)
102    } else {
103        Err(anyhow::anyhow!("Expected Integer"))
104    }
105}
106
107/// Decode HeatDemand attribute (0x0001)
108pub fn decode_heat_demand(inp: &tlv::TlvItemValue) -> anyhow::Result<WaterHeaterHeatSource> {
109    if let tlv::TlvItemValue::Int(v) = inp {
110        Ok(*v as u8)
111    } else {
112        Err(anyhow::anyhow!("Expected Integer"))
113    }
114}
115
116/// Decode TankVolume attribute (0x0002)
117pub fn decode_tank_volume(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
118    if let tlv::TlvItemValue::Int(v) = inp {
119        Ok(*v as u16)
120    } else {
121        Err(anyhow::anyhow!("Expected UInt16"))
122    }
123}
124
125/// Decode EstimatedHeatRequired attribute (0x0003)
126pub fn decode_estimated_heat_required(inp: &tlv::TlvItemValue) -> anyhow::Result<u64> {
127    if let tlv::TlvItemValue::Int(v) = inp {
128        Ok(*v)
129    } else {
130        Err(anyhow::anyhow!("Expected UInt64"))
131    }
132}
133
134/// Decode TankPercentage attribute (0x0004)
135pub fn decode_tank_percentage(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
136    if let tlv::TlvItemValue::Int(v) = inp {
137        Ok(*v as u8)
138    } else {
139        Err(anyhow::anyhow!("Expected UInt8"))
140    }
141}
142
143/// Decode BoostState attribute (0x0005)
144pub fn decode_boost_state(inp: &tlv::TlvItemValue) -> anyhow::Result<BoostState> {
145    if let tlv::TlvItemValue::Int(v) = inp {
146        BoostState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
147    } else {
148        Err(anyhow::anyhow!("Expected Integer"))
149    }
150}
151
152
153// JSON dispatcher function
154
155/// Decode attribute value and return as JSON string
156///
157/// # Parameters
158/// * `cluster_id` - The cluster identifier
159/// * `attribute_id` - The attribute identifier
160/// * `tlv_value` - The TLV value to decode
161///
162/// # Returns
163/// JSON string representation of the decoded value or error
164pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
165    // Verify this is the correct cluster
166    if cluster_id != 0x0094 {
167        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0094, got {}\"}}", cluster_id);
168    }
169
170    match attribute_id {
171        0x0000 => {
172            match decode_heater_types(tlv_value) {
173                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
174                Err(e) => format!("{{\"error\": \"{}\"}}", e),
175            }
176        }
177        0x0001 => {
178            match decode_heat_demand(tlv_value) {
179                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
180                Err(e) => format!("{{\"error\": \"{}\"}}", e),
181            }
182        }
183        0x0002 => {
184            match decode_tank_volume(tlv_value) {
185                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
186                Err(e) => format!("{{\"error\": \"{}\"}}", e),
187            }
188        }
189        0x0003 => {
190            match decode_estimated_heat_required(tlv_value) {
191                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
192                Err(e) => format!("{{\"error\": \"{}\"}}", e),
193            }
194        }
195        0x0004 => {
196            match decode_tank_percentage(tlv_value) {
197                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
198                Err(e) => format!("{{\"error\": \"{}\"}}", e),
199            }
200        }
201        0x0005 => {
202            match decode_boost_state(tlv_value) {
203                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
204                Err(e) => format!("{{\"error\": \"{}\"}}", e),
205            }
206        }
207        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
208    }
209}
210
211/// Get list of all attributes supported by this cluster
212///
213/// # Returns
214/// Vector of tuples containing (attribute_id, attribute_name)
215pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
216    vec![
217        (0x0000, "HeaterTypes"),
218        (0x0001, "HeatDemand"),
219        (0x0002, "TankVolume"),
220        (0x0003, "EstimatedHeatRequired"),
221        (0x0004, "TankPercentage"),
222        (0x0005, "BoostState"),
223    ]
224}
225
226#[derive(Debug, serde::Serialize)]
227pub struct BoostStartedEvent {
228    pub boost_info: Option<WaterHeaterBoostInfo>,
229}
230
231// Event decoders
232
233/// Decode BoostStarted event (0x00, priority: info)
234pub fn decode_boost_started_event(inp: &tlv::TlvItemValue) -> anyhow::Result<BoostStartedEvent> {
235    if let tlv::TlvItemValue::List(_fields) = inp {
236        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
237        Ok(BoostStartedEvent {
238                                boost_info: {
239                    if let Some(nested_tlv) = item.get(&[0]) {
240                        if let tlv::TlvItemValue::List(_) = nested_tlv {
241                            let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
242                            Some(WaterHeaterBoostInfo {
243                duration: nested_item.get_int(&[0]).map(|v| v as u32),
244                one_shot: nested_item.get_bool(&[1]),
245                emergency_boost: nested_item.get_bool(&[2]),
246                temporary_setpoint: nested_item.get_int(&[3]).map(|v| v as i16),
247                target_percentage: nested_item.get_int(&[4]).map(|v| v as u8),
248                target_reheat: nested_item.get_int(&[5]).map(|v| v as u8),
249                            })
250                        } else {
251                            None
252                        }
253                    } else {
254                        None
255                    }
256                },
257        })
258    } else {
259        Err(anyhow::anyhow!("Expected struct fields"))
260    }
261}
262