matc 0.1.2

Matter protocol library (controller side)
Documentation
//! Matter TLV encoders and decoders for Occupancy Sensing Cluster
//! Cluster ID: 0x0406
//!
//! This file is automatically generated from OccupancySensing.xml

use crate::tlv;
use anyhow;
use serde_json;


// Enum definitions

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum OccupancySensorType {
    /// Indicates a passive infrared sensor.
    Pir = 0,
    /// Indicates a ultrasonic sensor.
    Ultrasonic = 1,
    /// Indicates a passive infrared and ultrasonic sensor.
    Pirandultrasonic = 2,
    /// Indicates a physical contact sensor.
    Physicalcontact = 3,
}

impl OccupancySensorType {
    /// Convert from u8 value
    pub fn from_u8(value: u8) -> Option<Self> {
        match value {
            0 => Some(OccupancySensorType::Pir),
            1 => Some(OccupancySensorType::Ultrasonic),
            2 => Some(OccupancySensorType::Pirandultrasonic),
            3 => Some(OccupancySensorType::Physicalcontact),
            _ => None,
        }
    }

    /// Convert to u8 value
    pub fn to_u8(self) -> u8 {
        self as u8
    }
}

impl From<OccupancySensorType> for u8 {
    fn from(val: OccupancySensorType) -> Self {
        val as u8
    }
}

// Bitmap definitions

/// Occupancy bitmap type
pub type Occupancy = u8;

/// Constants for Occupancy
pub mod occupancy {
    /// Indicates the sensed occupancy state
    pub const OCCUPIED: u8 = 0x01;
}

/// OccupancySensorTypeBitmap bitmap type
pub type OccupancySensorTypeBitmap = u8;

/// Constants for OccupancySensorTypeBitmap
pub mod occupancysensortype {
    /// Indicates a passive infrared sensor.
    pub const PIR: u8 = 0x01;
    /// Indicates a ultrasonic sensor.
    pub const ULTRASONIC: u8 = 0x02;
    /// Indicates a physical contact sensor.
    pub const PHYSICAL_CONTACT: u8 = 0x04;
}

// Struct definitions

#[derive(Debug, serde::Serialize)]
pub struct HoldTimeLimits {
    pub hold_time_min: Option<u16>,
    pub hold_time_max: Option<u16>,
    pub hold_time_default: Option<u16>,
}

// Attribute decoders

/// Decode Occupancy attribute (0x0000)
pub fn decode_occupancy(inp: &tlv::TlvItemValue) -> anyhow::Result<Occupancy> {
    if let tlv::TlvItemValue::Int(v) = inp {
        Ok(*v as u8)
    } else {
        Err(anyhow::anyhow!("Expected Integer"))
    }
}

/// Decode OccupancySensorType attribute (0x0001)
pub fn decode_occupancy_sensor_type(inp: &tlv::TlvItemValue) -> anyhow::Result<OccupancySensorType> {
    if let tlv::TlvItemValue::Int(v) = inp {
        OccupancySensorType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
    } else {
        Err(anyhow::anyhow!("Expected Integer"))
    }
}

/// Decode OccupancySensorTypeBitmap attribute (0x0002)
pub fn decode_occupancy_sensor_type_bitmap(inp: &tlv::TlvItemValue) -> anyhow::Result<OccupancySensorTypeBitmap> {
    if let tlv::TlvItemValue::Int(v) = inp {
        Ok(*v as u8)
    } else {
        Err(anyhow::anyhow!("Expected Integer"))
    }
}

/// Decode HoldTime attribute (0x0003)
pub fn decode_hold_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
    if let tlv::TlvItemValue::Int(v) = inp {
        Ok(*v as u16)
    } else {
        Err(anyhow::anyhow!("Expected UInt16"))
    }
}

/// Decode HoldTimeLimits attribute (0x0004)
pub fn decode_hold_time_limits(inp: &tlv::TlvItemValue) -> anyhow::Result<HoldTimeLimits> {
    if let tlv::TlvItemValue::List(_fields) = inp {
        // Struct with fields
        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
        Ok(HoldTimeLimits {
                hold_time_min: item.get_int(&[0]).map(|v| v as u16),
                hold_time_max: item.get_int(&[1]).map(|v| v as u16),
                hold_time_default: item.get_int(&[2]).map(|v| v as u16),
        })
    } else {
        Err(anyhow::anyhow!("Expected struct fields"))
    }
}

/// Decode PIROccupiedToUnoccupiedDelay attribute (0x0010)
pub fn decode_pir_occupied_to_unoccupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
    if let tlv::TlvItemValue::Int(v) = inp {
        Ok(*v as u16)
    } else {
        Err(anyhow::anyhow!("Expected UInt16"))
    }
}

/// Decode PIRUnoccupiedToOccupiedDelay attribute (0x0011)
pub fn decode_pir_unoccupied_to_occupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
    if let tlv::TlvItemValue::Int(v) = inp {
        Ok(*v as u16)
    } else {
        Err(anyhow::anyhow!("Expected UInt16"))
    }
}

/// Decode PIRUnoccupiedToOccupiedThreshold attribute (0x0012)
pub fn decode_pir_unoccupied_to_occupied_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
    if let tlv::TlvItemValue::Int(v) = inp {
        Ok(*v as u8)
    } else {
        Err(anyhow::anyhow!("Expected UInt8"))
    }
}

/// Decode UltrasonicOccupiedToUnoccupiedDelay attribute (0x0020)
pub fn decode_ultrasonic_occupied_to_unoccupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
    if let tlv::TlvItemValue::Int(v) = inp {
        Ok(*v as u16)
    } else {
        Err(anyhow::anyhow!("Expected UInt16"))
    }
}

/// Decode UltrasonicUnoccupiedToOccupiedDelay attribute (0x0021)
pub fn decode_ultrasonic_unoccupied_to_occupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
    if let tlv::TlvItemValue::Int(v) = inp {
        Ok(*v as u16)
    } else {
        Err(anyhow::anyhow!("Expected UInt16"))
    }
}

/// Decode UltrasonicUnoccupiedToOccupiedThreshold attribute (0x0022)
pub fn decode_ultrasonic_unoccupied_to_occupied_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
    if let tlv::TlvItemValue::Int(v) = inp {
        Ok(*v as u8)
    } else {
        Err(anyhow::anyhow!("Expected UInt8"))
    }
}

/// Decode PhysicalContactOccupiedToUnoccupiedDelay attribute (0x0030)
pub fn decode_physical_contact_occupied_to_unoccupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
    if let tlv::TlvItemValue::Int(v) = inp {
        Ok(*v as u16)
    } else {
        Err(anyhow::anyhow!("Expected UInt16"))
    }
}

/// Decode PhysicalContactUnoccupiedToOccupiedDelay attribute (0x0031)
pub fn decode_physical_contact_unoccupied_to_occupied_delay(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
    if let tlv::TlvItemValue::Int(v) = inp {
        Ok(*v as u16)
    } else {
        Err(anyhow::anyhow!("Expected UInt16"))
    }
}

/// Decode PhysicalContactUnoccupiedToOccupiedThreshold attribute (0x0032)
pub fn decode_physical_contact_unoccupied_to_occupied_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
    if let tlv::TlvItemValue::Int(v) = inp {
        Ok(*v as u8)
    } else {
        Err(anyhow::anyhow!("Expected UInt8"))
    }
}


// JSON dispatcher function

/// Decode attribute value and return as JSON string
///
/// # Parameters
/// * `cluster_id` - The cluster identifier
/// * `attribute_id` - The attribute identifier
/// * `tlv_value` - The TLV value to decode
///
/// # Returns
/// JSON string representation of the decoded value or error
pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
    // Verify this is the correct cluster
    if cluster_id != 0x0406 {
        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0406, got {}\"}}", cluster_id);
    }

    match attribute_id {
        0x0000 => {
            match decode_occupancy(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0001 => {
            match decode_occupancy_sensor_type(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0002 => {
            match decode_occupancy_sensor_type_bitmap(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0003 => {
            match decode_hold_time(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0004 => {
            match decode_hold_time_limits(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0010 => {
            match decode_pir_occupied_to_unoccupied_delay(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0011 => {
            match decode_pir_unoccupied_to_occupied_delay(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0012 => {
            match decode_pir_unoccupied_to_occupied_threshold(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0020 => {
            match decode_ultrasonic_occupied_to_unoccupied_delay(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0021 => {
            match decode_ultrasonic_unoccupied_to_occupied_delay(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0022 => {
            match decode_ultrasonic_unoccupied_to_occupied_threshold(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0030 => {
            match decode_physical_contact_occupied_to_unoccupied_delay(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0031 => {
            match decode_physical_contact_unoccupied_to_occupied_delay(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0032 => {
            match decode_physical_contact_unoccupied_to_occupied_threshold(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),
    }
}

/// Get list of all attributes supported by this cluster
///
/// # Returns
/// Vector of tuples containing (attribute_id, attribute_name)
pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
    vec![
        (0x0000, "Occupancy"),
        (0x0001, "OccupancySensorType"),
        (0x0002, "OccupancySensorTypeBitmap"),
        (0x0003, "HoldTime"),
        (0x0004, "HoldTimeLimits"),
        (0x0010, "PIROccupiedToUnoccupiedDelay"),
        (0x0011, "PIRUnoccupiedToOccupiedDelay"),
        (0x0012, "PIRUnoccupiedToOccupiedThreshold"),
        (0x0020, "UltrasonicOccupiedToUnoccupiedDelay"),
        (0x0021, "UltrasonicUnoccupiedToOccupiedDelay"),
        (0x0022, "UltrasonicUnoccupiedToOccupiedThreshold"),
        (0x0030, "PhysicalContactOccupiedToUnoccupiedDelay"),
        (0x0031, "PhysicalContactUnoccupiedToOccupiedDelay"),
        (0x0032, "PhysicalContactUnoccupiedToOccupiedThreshold"),
    ]
}

#[derive(Debug, serde::Serialize)]
pub struct OccupancyChangedEvent {
    pub occupancy: Option<Occupancy>,
}

// Event decoders

/// Decode OccupancyChanged event (0x00, priority: info)
pub fn decode_occupancy_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<OccupancyChangedEvent> {
    if let tlv::TlvItemValue::List(_fields) = inp {
        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
        Ok(OccupancyChangedEvent {
                                occupancy: item.get_int(&[0]).map(|v| v as u8),
        })
    } else {
        Err(anyhow::anyhow!("Expected struct fields"))
    }
}