matc/clusters/codec/
occupancy_sensing.rs

1//! Matter TLV encoders and decoders for Occupancy Sensing Cluster
2//! Cluster ID: 0x0406
3//!
4//! This file is automatically generated from OccupancySensing.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 OccupancySensorType {
16    /// Indicates a passive infrared sensor.
17    Pir = 0,
18    /// Indicates a ultrasonic sensor.
19    Ultrasonic = 1,
20    /// Indicates a passive infrared and ultrasonic sensor.
21    Pirandultrasonic = 2,
22    /// Indicates a physical contact sensor.
23    Physicalcontact = 3,
24}
25
26impl OccupancySensorType {
27    /// Convert from u8 value
28    pub fn from_u8(value: u8) -> Option<Self> {
29        match value {
30            0 => Some(OccupancySensorType::Pir),
31            1 => Some(OccupancySensorType::Ultrasonic),
32            2 => Some(OccupancySensorType::Pirandultrasonic),
33            3 => Some(OccupancySensorType::Physicalcontact),
34            _ => None,
35        }
36    }
37
38    /// Convert to u8 value
39    pub fn to_u8(self) -> u8 {
40        self as u8
41    }
42}
43
44impl From<OccupancySensorType> for u8 {
45    fn from(val: OccupancySensorType) -> Self {
46        val as u8
47    }
48}
49
50// Bitmap definitions
51
52/// Occupancy bitmap type
53pub type Occupancy = u8;
54
55/// Constants for Occupancy
56pub mod occupancy {
57    /// Indicates the sensed occupancy state
58    pub const OCCUPIED: u8 = 0x01;
59}
60
61/// OccupancySensorTypeBitmap bitmap type
62pub type OccupancySensorTypeBitmap = u8;
63
64/// Constants for OccupancySensorTypeBitmap
65pub mod occupancysensortype {
66    /// Indicates a passive infrared sensor.
67    pub const PIR: u8 = 0x01;
68    /// Indicates a ultrasonic sensor.
69    pub const ULTRASONIC: u8 = 0x02;
70    /// Indicates a physical contact sensor.
71    pub const PHYSICAL_CONTACT: u8 = 0x04;
72}
73
74// Struct definitions
75
76#[derive(Debug, serde::Serialize)]
77pub struct HoldTimeLimits {
78    pub hold_time_min: Option<u16>,
79    pub hold_time_max: Option<u16>,
80    pub hold_time_default: Option<u16>,
81}
82
83// Attribute decoders
84
85/// Decode Occupancy attribute (0x0000)
86pub fn decode_occupancy(inp: &tlv::TlvItemValue) -> anyhow::Result<Occupancy> {
87    if let tlv::TlvItemValue::Int(v) = inp {
88        Ok(*v as u8)
89    } else {
90        Err(anyhow::anyhow!("Expected Integer"))
91    }
92}
93
94/// Decode OccupancySensorType attribute (0x0001)
95pub fn decode_occupancy_sensor_type(inp: &tlv::TlvItemValue) -> anyhow::Result<OccupancySensorType> {
96    if let tlv::TlvItemValue::Int(v) = inp {
97        OccupancySensorType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
98    } else {
99        Err(anyhow::anyhow!("Expected Integer"))
100    }
101}
102
103/// Decode OccupancySensorTypeBitmap attribute (0x0002)
104pub fn decode_occupancy_sensor_type_bitmap(inp: &tlv::TlvItemValue) -> anyhow::Result<OccupancySensorTypeBitmap> {
105    if let tlv::TlvItemValue::Int(v) = inp {
106        Ok(*v as u8)
107    } else {
108        Err(anyhow::anyhow!("Expected Integer"))
109    }
110}
111
112/// Decode HoldTime attribute (0x0003)
113pub fn decode_hold_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
114    if let tlv::TlvItemValue::Int(v) = inp {
115        Ok(*v as u16)
116    } else {
117        Err(anyhow::anyhow!("Expected UInt16"))
118    }
119}
120
121/// Decode HoldTimeLimits attribute (0x0004)
122pub fn decode_hold_time_limits(inp: &tlv::TlvItemValue) -> anyhow::Result<HoldTimeLimits> {
123    if let tlv::TlvItemValue::List(_fields) = inp {
124        // Struct with fields
125        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
126        Ok(HoldTimeLimits {
127                hold_time_min: item.get_int(&[0]).map(|v| v as u16),
128                hold_time_max: item.get_int(&[1]).map(|v| v as u16),
129                hold_time_default: item.get_int(&[2]).map(|v| v as u16),
130        })
131    } else {
132        Err(anyhow::anyhow!("Expected struct fields"))
133    }
134}
135
136/// Decode PIROccupiedToUnoccupiedDelay attribute (0x0010)
137pub fn decode_pir_occupied_to_unoccupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
138    if let tlv::TlvItemValue::Int(v) = inp {
139        Ok(*v as u16)
140    } else {
141        Err(anyhow::anyhow!("Expected UInt16"))
142    }
143}
144
145/// Decode PIRUnoccupiedToOccupiedDelay attribute (0x0011)
146pub fn decode_pir_unoccupied_to_occupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
147    if let tlv::TlvItemValue::Int(v) = inp {
148        Ok(*v as u16)
149    } else {
150        Err(anyhow::anyhow!("Expected UInt16"))
151    }
152}
153
154/// Decode PIRUnoccupiedToOccupiedThreshold attribute (0x0012)
155pub fn decode_pir_unoccupied_to_occupied_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
156    if let tlv::TlvItemValue::Int(v) = inp {
157        Ok(*v as u8)
158    } else {
159        Err(anyhow::anyhow!("Expected UInt8"))
160    }
161}
162
163/// Decode UltrasonicOccupiedToUnoccupiedDelay attribute (0x0020)
164pub fn decode_ultrasonic_occupied_to_unoccupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
165    if let tlv::TlvItemValue::Int(v) = inp {
166        Ok(*v as u16)
167    } else {
168        Err(anyhow::anyhow!("Expected UInt16"))
169    }
170}
171
172/// Decode UltrasonicUnoccupiedToOccupiedDelay attribute (0x0021)
173pub fn decode_ultrasonic_unoccupied_to_occupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
174    if let tlv::TlvItemValue::Int(v) = inp {
175        Ok(*v as u16)
176    } else {
177        Err(anyhow::anyhow!("Expected UInt16"))
178    }
179}
180
181/// Decode UltrasonicUnoccupiedToOccupiedThreshold attribute (0x0022)
182pub fn decode_ultrasonic_unoccupied_to_occupied_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
183    if let tlv::TlvItemValue::Int(v) = inp {
184        Ok(*v as u8)
185    } else {
186        Err(anyhow::anyhow!("Expected UInt8"))
187    }
188}
189
190/// Decode PhysicalContactOccupiedToUnoccupiedDelay attribute (0x0030)
191pub fn decode_physical_contact_occupied_to_unoccupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
192    if let tlv::TlvItemValue::Int(v) = inp {
193        Ok(*v as u16)
194    } else {
195        Err(anyhow::anyhow!("Expected UInt16"))
196    }
197}
198
199/// Decode PhysicalContactUnoccupiedToOccupiedDelay attribute (0x0031)
200pub fn decode_physical_contact_unoccupied_to_occupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
201    if let tlv::TlvItemValue::Int(v) = inp {
202        Ok(*v as u16)
203    } else {
204        Err(anyhow::anyhow!("Expected UInt16"))
205    }
206}
207
208/// Decode PhysicalContactUnoccupiedToOccupiedThreshold attribute (0x0032)
209pub fn decode_physical_contact_unoccupied_to_occupied_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
210    if let tlv::TlvItemValue::Int(v) = inp {
211        Ok(*v as u8)
212    } else {
213        Err(anyhow::anyhow!("Expected UInt8"))
214    }
215}
216
217
218// JSON dispatcher function
219
220/// Decode attribute value and return as JSON string
221///
222/// # Parameters
223/// * `cluster_id` - The cluster identifier
224/// * `attribute_id` - The attribute identifier
225/// * `tlv_value` - The TLV value to decode
226///
227/// # Returns
228/// JSON string representation of the decoded value or error
229pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
230    // Verify this is the correct cluster
231    if cluster_id != 0x0406 {
232        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0406, got {}\"}}", cluster_id);
233    }
234
235    match attribute_id {
236        0x0000 => {
237            match decode_occupancy(tlv_value) {
238                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
239                Err(e) => format!("{{\"error\": \"{}\"}}", e),
240            }
241        }
242        0x0001 => {
243            match decode_occupancy_sensor_type(tlv_value) {
244                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
245                Err(e) => format!("{{\"error\": \"{}\"}}", e),
246            }
247        }
248        0x0002 => {
249            match decode_occupancy_sensor_type_bitmap(tlv_value) {
250                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
251                Err(e) => format!("{{\"error\": \"{}\"}}", e),
252            }
253        }
254        0x0003 => {
255            match decode_hold_time(tlv_value) {
256                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
257                Err(e) => format!("{{\"error\": \"{}\"}}", e),
258            }
259        }
260        0x0004 => {
261            match decode_hold_time_limits(tlv_value) {
262                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
263                Err(e) => format!("{{\"error\": \"{}\"}}", e),
264            }
265        }
266        0x0010 => {
267            match decode_pir_occupied_to_unoccupied_delay(tlv_value) {
268                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
269                Err(e) => format!("{{\"error\": \"{}\"}}", e),
270            }
271        }
272        0x0011 => {
273            match decode_pir_unoccupied_to_occupied_delay(tlv_value) {
274                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
275                Err(e) => format!("{{\"error\": \"{}\"}}", e),
276            }
277        }
278        0x0012 => {
279            match decode_pir_unoccupied_to_occupied_threshold(tlv_value) {
280                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
281                Err(e) => format!("{{\"error\": \"{}\"}}", e),
282            }
283        }
284        0x0020 => {
285            match decode_ultrasonic_occupied_to_unoccupied_delay(tlv_value) {
286                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
287                Err(e) => format!("{{\"error\": \"{}\"}}", e),
288            }
289        }
290        0x0021 => {
291            match decode_ultrasonic_unoccupied_to_occupied_delay(tlv_value) {
292                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
293                Err(e) => format!("{{\"error\": \"{}\"}}", e),
294            }
295        }
296        0x0022 => {
297            match decode_ultrasonic_unoccupied_to_occupied_threshold(tlv_value) {
298                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
299                Err(e) => format!("{{\"error\": \"{}\"}}", e),
300            }
301        }
302        0x0030 => {
303            match decode_physical_contact_occupied_to_unoccupied_delay(tlv_value) {
304                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
305                Err(e) => format!("{{\"error\": \"{}\"}}", e),
306            }
307        }
308        0x0031 => {
309            match decode_physical_contact_unoccupied_to_occupied_delay(tlv_value) {
310                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
311                Err(e) => format!("{{\"error\": \"{}\"}}", e),
312            }
313        }
314        0x0032 => {
315            match decode_physical_contact_unoccupied_to_occupied_threshold(tlv_value) {
316                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
317                Err(e) => format!("{{\"error\": \"{}\"}}", e),
318            }
319        }
320        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
321    }
322}
323
324/// Get list of all attributes supported by this cluster
325///
326/// # Returns
327/// Vector of tuples containing (attribute_id, attribute_name)
328pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
329    vec![
330        (0x0000, "Occupancy"),
331        (0x0001, "OccupancySensorType"),
332        (0x0002, "OccupancySensorTypeBitmap"),
333        (0x0003, "HoldTime"),
334        (0x0004, "HoldTimeLimits"),
335        (0x0010, "PIROccupiedToUnoccupiedDelay"),
336        (0x0011, "PIRUnoccupiedToOccupiedDelay"),
337        (0x0012, "PIRUnoccupiedToOccupiedThreshold"),
338        (0x0020, "UltrasonicOccupiedToUnoccupiedDelay"),
339        (0x0021, "UltrasonicUnoccupiedToOccupiedDelay"),
340        (0x0022, "UltrasonicUnoccupiedToOccupiedThreshold"),
341        (0x0030, "PhysicalContactOccupiedToUnoccupiedDelay"),
342        (0x0031, "PhysicalContactUnoccupiedToOccupiedDelay"),
343        (0x0032, "PhysicalContactUnoccupiedToOccupiedThreshold"),
344    ]
345}
346
347#[derive(Debug, serde::Serialize)]
348pub struct OccupancyChangedEvent {
349    pub occupancy: Option<Occupancy>,
350}
351
352// Event decoders
353
354/// Decode OccupancyChanged event (0x00, priority: info)
355pub fn decode_occupancy_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<OccupancyChangedEvent> {
356    if let tlv::TlvItemValue::List(_fields) = inp {
357        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
358        Ok(OccupancyChangedEvent {
359                                occupancy: item.get_int(&[0]).map(|v| v as u8),
360        })
361    } else {
362        Err(anyhow::anyhow!("Expected struct fields"))
363    }
364}
365