matc 0.1.2

Matter protocol library (controller side)
Documentation
//! Matter TLV encoders and decoders for Camera AV Settings User Level Management Cluster
//! Cluster ID: 0x0552
//!
//! This file is automatically generated from CameraAVSettingsUserLevelManagement.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 PhysicalMovement {
    /// The camera is idle.
    Idle = 0,
    /// The camera is moving to a new value of Pan, Tilt, and/or Zoom.
    Moving = 1,
}

impl PhysicalMovement {
    /// Convert from u8 value
    pub fn from_u8(value: u8) -> Option<Self> {
        match value {
            0 => Some(PhysicalMovement::Idle),
            1 => Some(PhysicalMovement::Moving),
            _ => None,
        }
    }

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

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

// Struct definitions

#[derive(Debug, serde::Serialize)]
pub struct DPTZ {
    pub video_stream_id: Option<u8>,
}

#[derive(Debug, serde::Serialize)]
pub struct MPTZPreset {
    pub preset_id: Option<u8>,
    pub name: Option<String>,
    pub settings: Option<MPTZ>,
}

#[derive(Debug, serde::Serialize)]
pub struct MPTZ {
    pub pan: Option<i16>,
    pub tilt: Option<i16>,
    pub zoom: Option<u8>,
}

// Command encoders

/// Encode MPTZSetPosition command (0x00)
pub fn encode_mptz_set_position(pan: i16, tilt: i16, zoom: u8) -> anyhow::Result<Vec<u8>> {
    let tlv = tlv::TlvItemEnc {
        tag: 0,
        value: tlv::TlvItemValueEnc::StructInvisible(vec![
        (0, tlv::TlvItemValueEnc::Int16(pan)).into(),
        (1, tlv::TlvItemValueEnc::Int16(tilt)).into(),
        (2, tlv::TlvItemValueEnc::UInt8(zoom)).into(),
        ]),
    };
    Ok(tlv.encode()?)
}

/// Encode MPTZRelativeMove command (0x01)
pub fn encode_mptz_relative_move(pan_delta: i16, tilt_delta: i16, zoom_delta: i8) -> anyhow::Result<Vec<u8>> {
    let tlv = tlv::TlvItemEnc {
        tag: 0,
        value: tlv::TlvItemValueEnc::StructInvisible(vec![
        (0, tlv::TlvItemValueEnc::Int16(pan_delta)).into(),
        (1, tlv::TlvItemValueEnc::Int16(tilt_delta)).into(),
        (2, tlv::TlvItemValueEnc::Int8(zoom_delta)).into(),
        ]),
    };
    Ok(tlv.encode()?)
}

/// Encode MPTZMoveToPreset command (0x02)
pub fn encode_mptz_move_to_preset(preset_id: u8) -> anyhow::Result<Vec<u8>> {
    let tlv = tlv::TlvItemEnc {
        tag: 0,
        value: tlv::TlvItemValueEnc::StructInvisible(vec![
        (0, tlv::TlvItemValueEnc::UInt8(preset_id)).into(),
        ]),
    };
    Ok(tlv.encode()?)
}

/// Encode MPTZSavePreset command (0x03)
pub fn encode_mptz_save_preset(preset_id: u8, name: String) -> anyhow::Result<Vec<u8>> {
    let tlv = tlv::TlvItemEnc {
        tag: 0,
        value: tlv::TlvItemValueEnc::StructInvisible(vec![
        (0, tlv::TlvItemValueEnc::UInt8(preset_id)).into(),
        (1, tlv::TlvItemValueEnc::String(name)).into(),
        ]),
    };
    Ok(tlv.encode()?)
}

/// Encode MPTZRemovePreset command (0x04)
pub fn encode_mptz_remove_preset(preset_id: u8) -> anyhow::Result<Vec<u8>> {
    let tlv = tlv::TlvItemEnc {
        tag: 0,
        value: tlv::TlvItemValueEnc::StructInvisible(vec![
        (0, tlv::TlvItemValueEnc::UInt8(preset_id)).into(),
        ]),
    };
    Ok(tlv.encode()?)
}

/// Encode DPTZSetViewport command (0x05)
pub fn encode_dptz_set_viewport(video_stream_id: u8) -> anyhow::Result<Vec<u8>> {
    let tlv = tlv::TlvItemEnc {
        tag: 0,
        value: tlv::TlvItemValueEnc::StructInvisible(vec![
        (0, tlv::TlvItemValueEnc::UInt8(video_stream_id)).into(),
        ]),
    };
    Ok(tlv.encode()?)
}

/// Encode DPTZRelativeMove command (0x06)
pub fn encode_dptz_relative_move(video_stream_id: u8, delta_x: i16, delta_y: i16, zoom_delta: i8) -> anyhow::Result<Vec<u8>> {
    let tlv = tlv::TlvItemEnc {
        tag: 0,
        value: tlv::TlvItemValueEnc::StructInvisible(vec![
        (0, tlv::TlvItemValueEnc::UInt8(video_stream_id)).into(),
        (1, tlv::TlvItemValueEnc::Int16(delta_x)).into(),
        (2, tlv::TlvItemValueEnc::Int16(delta_y)).into(),
        (3, tlv::TlvItemValueEnc::Int8(zoom_delta)).into(),
        ]),
    };
    Ok(tlv.encode()?)
}

// Attribute decoders

/// Decode MPTZPosition attribute (0x0000)
pub fn decode_mptz_position(inp: &tlv::TlvItemValue) -> anyhow::Result<MPTZ> {
    if let tlv::TlvItemValue::List(_fields) = inp {
        // Struct with fields
        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
        Ok(MPTZ {
                pan: item.get_int(&[0]).map(|v| v as i16),
                tilt: item.get_int(&[1]).map(|v| v as i16),
                zoom: item.get_int(&[2]).map(|v| v as u8),
        })
    } else {
        Err(anyhow::anyhow!("Expected struct fields"))
    }
}

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

/// Decode MPTZPresets attribute (0x0002)
pub fn decode_mptz_presets(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<MPTZPreset>> {
    let mut res = Vec::new();
    if let tlv::TlvItemValue::List(v) = inp {
        for item in v {
            res.push(MPTZPreset {
                preset_id: item.get_int(&[0]).map(|v| v as u8),
                name: item.get_string_owned(&[1]),
                settings: {
                    if let Some(nested_tlv) = item.get(&[2]) {
                        if let tlv::TlvItemValue::List(_) = nested_tlv {
                            let nested_item = tlv::TlvItem { tag: 2, value: nested_tlv.clone() };
                            Some(MPTZ {
                pan: nested_item.get_int(&[0]).map(|v| v as i16),
                tilt: nested_item.get_int(&[1]).map(|v| v as i16),
                zoom: nested_item.get_int(&[2]).map(|v| v as u8),
                            })
                        } else {
                            None
                        }
                    } else {
                        None
                    }
                },
            });
        }
    }
    Ok(res)
}

/// Decode DPTZStreams attribute (0x0003)
pub fn decode_dptz_streams(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<DPTZ>> {
    let mut res = Vec::new();
    if let tlv::TlvItemValue::List(v) = inp {
        for item in v {
            res.push(DPTZ {
                video_stream_id: item.get_int(&[0]).map(|v| v as u8),
            });
        }
    }
    Ok(res)
}

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

/// Decode TiltMin attribute (0x0005)
pub fn decode_tilt_min(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
    if let tlv::TlvItemValue::Int(v) = inp {
        Ok(*v as i16)
    } else {
        Err(anyhow::anyhow!("Expected Int16"))
    }
}

/// Decode TiltMax attribute (0x0006)
pub fn decode_tilt_max(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
    if let tlv::TlvItemValue::Int(v) = inp {
        Ok(*v as i16)
    } else {
        Err(anyhow::anyhow!("Expected Int16"))
    }
}

/// Decode PanMin attribute (0x0007)
pub fn decode_pan_min(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
    if let tlv::TlvItemValue::Int(v) = inp {
        Ok(*v as i16)
    } else {
        Err(anyhow::anyhow!("Expected Int16"))
    }
}

/// Decode PanMax attribute (0x0008)
pub fn decode_pan_max(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
    if let tlv::TlvItemValue::Int(v) = inp {
        Ok(*v as i16)
    } else {
        Err(anyhow::anyhow!("Expected Int16"))
    }
}

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


// 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 != 0x0552 {
        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0552, got {}\"}}", cluster_id);
    }

    match attribute_id {
        0x0000 => {
            match decode_mptz_position(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0001 => {
            match decode_max_presets(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0002 => {
            match decode_mptz_presets(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0003 => {
            match decode_dptz_streams(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0004 => {
            match decode_zoom_max(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0005 => {
            match decode_tilt_min(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0006 => {
            match decode_tilt_max(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0007 => {
            match decode_pan_min(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0008 => {
            match decode_pan_max(tlv_value) {
                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
                Err(e) => format!("{{\"error\": \"{}\"}}", e),
            }
        }
        0x0009 => {
            match decode_movement_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),
    }
}

/// 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, "MPTZPosition"),
        (0x0001, "MaxPresets"),
        (0x0002, "MPTZPresets"),
        (0x0003, "DPTZStreams"),
        (0x0004, "ZoomMax"),
        (0x0005, "TiltMin"),
        (0x0006, "TiltMax"),
        (0x0007, "PanMin"),
        (0x0008, "PanMax"),
        (0x0009, "MovementState"),
    ]
}