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
6#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13// Enum definitions
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16#[repr(u8)]
17pub enum OccupancySensorType {
18    /// Indicates a passive infrared sensor.
19    Pir = 0,
20    /// Indicates a ultrasonic sensor.
21    Ultrasonic = 1,
22    /// Indicates a passive infrared and ultrasonic sensor.
23    Pirandultrasonic = 2,
24    /// Indicates a physical contact sensor.
25    Physicalcontact = 3,
26}
27
28impl OccupancySensorType {
29    /// Convert from u8 value
30    pub fn from_u8(value: u8) -> Option<Self> {
31        match value {
32            0 => Some(OccupancySensorType::Pir),
33            1 => Some(OccupancySensorType::Ultrasonic),
34            2 => Some(OccupancySensorType::Pirandultrasonic),
35            3 => Some(OccupancySensorType::Physicalcontact),
36            _ => None,
37        }
38    }
39
40    /// Convert to u8 value
41    pub fn to_u8(self) -> u8 {
42        self as u8
43    }
44}
45
46impl From<OccupancySensorType> for u8 {
47    fn from(val: OccupancySensorType) -> Self {
48        val as u8
49    }
50}
51
52// Bitmap definitions
53
54/// Occupancy bitmap type
55pub type Occupancy = u8;
56
57/// Constants for Occupancy
58pub mod occupancy {
59    /// Indicates the sensed occupancy state
60    pub const OCCUPIED: u8 = 0x01;
61}
62
63/// OccupancySensorTypeBitmap bitmap type
64pub type OccupancySensorTypeBitmap = u8;
65
66/// Constants for OccupancySensorTypeBitmap
67pub mod occupancysensortype {
68    /// Indicates a passive infrared sensor.
69    pub const PIR: u8 = 0x01;
70    /// Indicates a ultrasonic sensor.
71    pub const ULTRASONIC: u8 = 0x02;
72    /// Indicates a physical contact sensor.
73    pub const PHYSICAL_CONTACT: u8 = 0x04;
74}
75
76// Struct definitions
77
78#[derive(Debug, serde::Serialize)]
79pub struct HoldTimeLimits {
80    pub hold_time_min: Option<u16>,
81    pub hold_time_max: Option<u16>,
82    pub hold_time_default: Option<u16>,
83}
84
85// Attribute decoders
86
87/// Decode Occupancy attribute (0x0000)
88pub fn decode_occupancy(inp: &tlv::TlvItemValue) -> anyhow::Result<Occupancy> {
89    if let tlv::TlvItemValue::Int(v) = inp {
90        Ok(*v as u8)
91    } else {
92        Err(anyhow::anyhow!("Expected Integer"))
93    }
94}
95
96/// Decode OccupancySensorType attribute (0x0001)
97pub fn decode_occupancy_sensor_type(inp: &tlv::TlvItemValue) -> anyhow::Result<OccupancySensorType> {
98    if let tlv::TlvItemValue::Int(v) = inp {
99        OccupancySensorType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
100    } else {
101        Err(anyhow::anyhow!("Expected Integer"))
102    }
103}
104
105/// Decode OccupancySensorTypeBitmap attribute (0x0002)
106pub fn decode_occupancy_sensor_type_bitmap(inp: &tlv::TlvItemValue) -> anyhow::Result<OccupancySensorTypeBitmap> {
107    if let tlv::TlvItemValue::Int(v) = inp {
108        Ok(*v as u8)
109    } else {
110        Err(anyhow::anyhow!("Expected Integer"))
111    }
112}
113
114/// Decode HoldTime attribute (0x0003)
115pub fn decode_hold_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
116    if let tlv::TlvItemValue::Int(v) = inp {
117        Ok(*v as u16)
118    } else {
119        Err(anyhow::anyhow!("Expected UInt16"))
120    }
121}
122
123/// Decode HoldTimeLimits attribute (0x0004)
124pub fn decode_hold_time_limits(inp: &tlv::TlvItemValue) -> anyhow::Result<HoldTimeLimits> {
125    if let tlv::TlvItemValue::List(_fields) = inp {
126        // Struct with fields
127        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
128        Ok(HoldTimeLimits {
129                hold_time_min: item.get_int(&[0]).map(|v| v as u16),
130                hold_time_max: item.get_int(&[1]).map(|v| v as u16),
131                hold_time_default: item.get_int(&[2]).map(|v| v as u16),
132        })
133    } else {
134        Err(anyhow::anyhow!("Expected struct fields"))
135    }
136}
137
138/// Decode PIROccupiedToUnoccupiedDelay attribute (0x0010)
139pub fn decode_pir_occupied_to_unoccupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
140    if let tlv::TlvItemValue::Int(v) = inp {
141        Ok(*v as u16)
142    } else {
143        Err(anyhow::anyhow!("Expected UInt16"))
144    }
145}
146
147/// Decode PIRUnoccupiedToOccupiedDelay attribute (0x0011)
148pub fn decode_pir_unoccupied_to_occupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
149    if let tlv::TlvItemValue::Int(v) = inp {
150        Ok(*v as u16)
151    } else {
152        Err(anyhow::anyhow!("Expected UInt16"))
153    }
154}
155
156/// Decode PIRUnoccupiedToOccupiedThreshold attribute (0x0012)
157pub fn decode_pir_unoccupied_to_occupied_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
158    if let tlv::TlvItemValue::Int(v) = inp {
159        Ok(*v as u8)
160    } else {
161        Err(anyhow::anyhow!("Expected UInt8"))
162    }
163}
164
165/// Decode UltrasonicOccupiedToUnoccupiedDelay attribute (0x0020)
166pub fn decode_ultrasonic_occupied_to_unoccupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
167    if let tlv::TlvItemValue::Int(v) = inp {
168        Ok(*v as u16)
169    } else {
170        Err(anyhow::anyhow!("Expected UInt16"))
171    }
172}
173
174/// Decode UltrasonicUnoccupiedToOccupiedDelay attribute (0x0021)
175pub fn decode_ultrasonic_unoccupied_to_occupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
176    if let tlv::TlvItemValue::Int(v) = inp {
177        Ok(*v as u16)
178    } else {
179        Err(anyhow::anyhow!("Expected UInt16"))
180    }
181}
182
183/// Decode UltrasonicUnoccupiedToOccupiedThreshold attribute (0x0022)
184pub fn decode_ultrasonic_unoccupied_to_occupied_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
185    if let tlv::TlvItemValue::Int(v) = inp {
186        Ok(*v as u8)
187    } else {
188        Err(anyhow::anyhow!("Expected UInt8"))
189    }
190}
191
192/// Decode PhysicalContactOccupiedToUnoccupiedDelay attribute (0x0030)
193pub fn decode_physical_contact_occupied_to_unoccupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
194    if let tlv::TlvItemValue::Int(v) = inp {
195        Ok(*v as u16)
196    } else {
197        Err(anyhow::anyhow!("Expected UInt16"))
198    }
199}
200
201/// Decode PhysicalContactUnoccupiedToOccupiedDelay attribute (0x0031)
202pub fn decode_physical_contact_unoccupied_to_occupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
203    if let tlv::TlvItemValue::Int(v) = inp {
204        Ok(*v as u16)
205    } else {
206        Err(anyhow::anyhow!("Expected UInt16"))
207    }
208}
209
210/// Decode PhysicalContactUnoccupiedToOccupiedThreshold attribute (0x0032)
211pub fn decode_physical_contact_unoccupied_to_occupied_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
212    if let tlv::TlvItemValue::Int(v) = inp {
213        Ok(*v as u8)
214    } else {
215        Err(anyhow::anyhow!("Expected UInt8"))
216    }
217}
218
219
220// JSON dispatcher function
221
222/// Decode attribute value and return as JSON string
223///
224/// # Parameters
225/// * `cluster_id` - The cluster identifier
226/// * `attribute_id` - The attribute identifier
227/// * `tlv_value` - The TLV value to decode
228///
229/// # Returns
230/// JSON string representation of the decoded value or error
231pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
232    // Verify this is the correct cluster
233    if cluster_id != 0x0406 {
234        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0406, got {}\"}}", cluster_id);
235    }
236
237    match attribute_id {
238        0x0000 => {
239            match decode_occupancy(tlv_value) {
240                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
241                Err(e) => format!("{{\"error\": \"{}\"}}", e),
242            }
243        }
244        0x0001 => {
245            match decode_occupancy_sensor_type(tlv_value) {
246                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
247                Err(e) => format!("{{\"error\": \"{}\"}}", e),
248            }
249        }
250        0x0002 => {
251            match decode_occupancy_sensor_type_bitmap(tlv_value) {
252                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
253                Err(e) => format!("{{\"error\": \"{}\"}}", e),
254            }
255        }
256        0x0003 => {
257            match decode_hold_time(tlv_value) {
258                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
259                Err(e) => format!("{{\"error\": \"{}\"}}", e),
260            }
261        }
262        0x0004 => {
263            match decode_hold_time_limits(tlv_value) {
264                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
265                Err(e) => format!("{{\"error\": \"{}\"}}", e),
266            }
267        }
268        0x0010 => {
269            match decode_pir_occupied_to_unoccupied_delay(tlv_value) {
270                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
271                Err(e) => format!("{{\"error\": \"{}\"}}", e),
272            }
273        }
274        0x0011 => {
275            match decode_pir_unoccupied_to_occupied_delay(tlv_value) {
276                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
277                Err(e) => format!("{{\"error\": \"{}\"}}", e),
278            }
279        }
280        0x0012 => {
281            match decode_pir_unoccupied_to_occupied_threshold(tlv_value) {
282                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
283                Err(e) => format!("{{\"error\": \"{}\"}}", e),
284            }
285        }
286        0x0020 => {
287            match decode_ultrasonic_occupied_to_unoccupied_delay(tlv_value) {
288                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
289                Err(e) => format!("{{\"error\": \"{}\"}}", e),
290            }
291        }
292        0x0021 => {
293            match decode_ultrasonic_unoccupied_to_occupied_delay(tlv_value) {
294                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
295                Err(e) => format!("{{\"error\": \"{}\"}}", e),
296            }
297        }
298        0x0022 => {
299            match decode_ultrasonic_unoccupied_to_occupied_threshold(tlv_value) {
300                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
301                Err(e) => format!("{{\"error\": \"{}\"}}", e),
302            }
303        }
304        0x0030 => {
305            match decode_physical_contact_occupied_to_unoccupied_delay(tlv_value) {
306                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
307                Err(e) => format!("{{\"error\": \"{}\"}}", e),
308            }
309        }
310        0x0031 => {
311            match decode_physical_contact_unoccupied_to_occupied_delay(tlv_value) {
312                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
313                Err(e) => format!("{{\"error\": \"{}\"}}", e),
314            }
315        }
316        0x0032 => {
317            match decode_physical_contact_unoccupied_to_occupied_threshold(tlv_value) {
318                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
319                Err(e) => format!("{{\"error\": \"{}\"}}", e),
320            }
321        }
322        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
323    }
324}
325
326/// Get list of all attributes supported by this cluster
327///
328/// # Returns
329/// Vector of tuples containing (attribute_id, attribute_name)
330pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
331    vec![
332        (0x0000, "Occupancy"),
333        (0x0001, "OccupancySensorType"),
334        (0x0002, "OccupancySensorTypeBitmap"),
335        (0x0003, "HoldTime"),
336        (0x0004, "HoldTimeLimits"),
337        (0x0010, "PIROccupiedToUnoccupiedDelay"),
338        (0x0011, "PIRUnoccupiedToOccupiedDelay"),
339        (0x0012, "PIRUnoccupiedToOccupiedThreshold"),
340        (0x0020, "UltrasonicOccupiedToUnoccupiedDelay"),
341        (0x0021, "UltrasonicUnoccupiedToOccupiedDelay"),
342        (0x0022, "UltrasonicUnoccupiedToOccupiedThreshold"),
343        (0x0030, "PhysicalContactOccupiedToUnoccupiedDelay"),
344        (0x0031, "PhysicalContactUnoccupiedToOccupiedDelay"),
345        (0x0032, "PhysicalContactUnoccupiedToOccupiedThreshold"),
346    ]
347}
348
349// Typed facade (invokes + reads)
350
351/// Read `Occupancy` attribute from cluster `Occupancy Sensing`.
352pub async fn read_occupancy(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Occupancy> {
353    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_OCCUPANCY).await?;
354    decode_occupancy(&tlv)
355}
356
357/// Read `OccupancySensorType` attribute from cluster `Occupancy Sensing`.
358pub async fn read_occupancy_sensor_type(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<OccupancySensorType> {
359    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_OCCUPANCYSENSORTYPE).await?;
360    decode_occupancy_sensor_type(&tlv)
361}
362
363/// Read `OccupancySensorTypeBitmap` attribute from cluster `Occupancy Sensing`.
364pub async fn read_occupancy_sensor_type_bitmap(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<OccupancySensorTypeBitmap> {
365    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_OCCUPANCYSENSORTYPEBITMAP).await?;
366    decode_occupancy_sensor_type_bitmap(&tlv)
367}
368
369/// Read `HoldTime` attribute from cluster `Occupancy Sensing`.
370pub async fn read_hold_time(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
371    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_HOLDTIME).await?;
372    decode_hold_time(&tlv)
373}
374
375/// Read `HoldTimeLimits` attribute from cluster `Occupancy Sensing`.
376pub async fn read_hold_time_limits(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<HoldTimeLimits> {
377    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_HOLDTIMELIMITS).await?;
378    decode_hold_time_limits(&tlv)
379}
380
381/// Read `PIROccupiedToUnoccupiedDelay` attribute from cluster `Occupancy Sensing`.
382pub async fn read_pir_occupied_to_unoccupied_delay(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
383    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_PIROCCUPIEDTOUNOCCUPIEDDELAY).await?;
384    decode_pir_occupied_to_unoccupied_delay(&tlv)
385}
386
387/// Read `PIRUnoccupiedToOccupiedDelay` attribute from cluster `Occupancy Sensing`.
388pub async fn read_pir_unoccupied_to_occupied_delay(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
389    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_PIRUNOCCUPIEDTOOCCUPIEDDELAY).await?;
390    decode_pir_unoccupied_to_occupied_delay(&tlv)
391}
392
393/// Read `PIRUnoccupiedToOccupiedThreshold` attribute from cluster `Occupancy Sensing`.
394pub async fn read_pir_unoccupied_to_occupied_threshold(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
395    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_PIRUNOCCUPIEDTOOCCUPIEDTHRESHOLD).await?;
396    decode_pir_unoccupied_to_occupied_threshold(&tlv)
397}
398
399/// Read `UltrasonicOccupiedToUnoccupiedDelay` attribute from cluster `Occupancy Sensing`.
400pub async fn read_ultrasonic_occupied_to_unoccupied_delay(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
401    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_ULTRASONICOCCUPIEDTOUNOCCUPIEDDELAY).await?;
402    decode_ultrasonic_occupied_to_unoccupied_delay(&tlv)
403}
404
405/// Read `UltrasonicUnoccupiedToOccupiedDelay` attribute from cluster `Occupancy Sensing`.
406pub async fn read_ultrasonic_unoccupied_to_occupied_delay(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
407    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_ULTRASONICUNOCCUPIEDTOOCCUPIEDDELAY).await?;
408    decode_ultrasonic_unoccupied_to_occupied_delay(&tlv)
409}
410
411/// Read `UltrasonicUnoccupiedToOccupiedThreshold` attribute from cluster `Occupancy Sensing`.
412pub async fn read_ultrasonic_unoccupied_to_occupied_threshold(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
413    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_ULTRASONICUNOCCUPIEDTOOCCUPIEDTHRESHOLD).await?;
414    decode_ultrasonic_unoccupied_to_occupied_threshold(&tlv)
415}
416
417/// Read `PhysicalContactOccupiedToUnoccupiedDelay` attribute from cluster `Occupancy Sensing`.
418pub async fn read_physical_contact_occupied_to_unoccupied_delay(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
419    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_PHYSICALCONTACTOCCUPIEDTOUNOCCUPIEDDELAY).await?;
420    decode_physical_contact_occupied_to_unoccupied_delay(&tlv)
421}
422
423/// Read `PhysicalContactUnoccupiedToOccupiedDelay` attribute from cluster `Occupancy Sensing`.
424pub async fn read_physical_contact_unoccupied_to_occupied_delay(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
425    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_PHYSICALCONTACTUNOCCUPIEDTOOCCUPIEDDELAY).await?;
426    decode_physical_contact_unoccupied_to_occupied_delay(&tlv)
427}
428
429/// Read `PhysicalContactUnoccupiedToOccupiedThreshold` attribute from cluster `Occupancy Sensing`.
430pub async fn read_physical_contact_unoccupied_to_occupied_threshold(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
431    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_OCCUPANCY_SENSING, crate::clusters::defs::CLUSTER_OCCUPANCY_SENSING_ATTR_ID_PHYSICALCONTACTUNOCCUPIEDTOOCCUPIEDTHRESHOLD).await?;
432    decode_physical_contact_unoccupied_to_occupied_threshold(&tlv)
433}
434
435#[derive(Debug, serde::Serialize)]
436pub struct OccupancyChangedEvent {
437    pub occupancy: Option<Occupancy>,
438}
439
440// Event decoders
441
442/// Decode OccupancyChanged event (0x00, priority: info)
443pub fn decode_occupancy_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<OccupancyChangedEvent> {
444    if let tlv::TlvItemValue::List(_fields) = inp {
445        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
446        Ok(OccupancyChangedEvent {
447                                occupancy: item.get_int(&[0]).map(|v| v as u8),
448        })
449    } else {
450        Err(anyhow::anyhow!("Expected struct fields"))
451    }
452}
453