matc/clusters/codec/
camera_av_settings_user_level_management.rs

1//! Matter TLV encoders and decoders for Camera AV Settings User Level Management Cluster
2//! Cluster ID: 0x0552
3//!
4//! This file is automatically generated from CameraAVSettingsUserLevelManagement.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 PhysicalMovement {
16    /// The camera is idle.
17    Idle = 0,
18    /// The camera is moving to a new value of Pan, Tilt, and/or Zoom.
19    Moving = 1,
20}
21
22impl PhysicalMovement {
23    /// Convert from u8 value
24    pub fn from_u8(value: u8) -> Option<Self> {
25        match value {
26            0 => Some(PhysicalMovement::Idle),
27            1 => Some(PhysicalMovement::Moving),
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<PhysicalMovement> for u8 {
39    fn from(val: PhysicalMovement) -> Self {
40        val as u8
41    }
42}
43
44// Struct definitions
45
46#[derive(Debug, serde::Serialize)]
47pub struct DPTZ {
48    pub video_stream_id: Option<u8>,
49}
50
51#[derive(Debug, serde::Serialize)]
52pub struct MPTZPreset {
53    pub preset_id: Option<u8>,
54    pub name: Option<String>,
55    pub settings: Option<MPTZ>,
56}
57
58#[derive(Debug, serde::Serialize)]
59pub struct MPTZ {
60    pub pan: Option<i16>,
61    pub tilt: Option<i16>,
62    pub zoom: Option<u8>,
63}
64
65// Command encoders
66
67/// Encode MPTZSetPosition command (0x00)
68pub fn encode_mptz_set_position(pan: i16, tilt: i16, zoom: u8) -> anyhow::Result<Vec<u8>> {
69    let tlv = tlv::TlvItemEnc {
70        tag: 0,
71        value: tlv::TlvItemValueEnc::StructInvisible(vec![
72        (0, tlv::TlvItemValueEnc::Int16(pan)).into(),
73        (1, tlv::TlvItemValueEnc::Int16(tilt)).into(),
74        (2, tlv::TlvItemValueEnc::UInt8(zoom)).into(),
75        ]),
76    };
77    Ok(tlv.encode()?)
78}
79
80/// Encode MPTZRelativeMove command (0x01)
81pub fn encode_mptz_relative_move(pan_delta: i16, tilt_delta: i16, zoom_delta: i8) -> anyhow::Result<Vec<u8>> {
82    let tlv = tlv::TlvItemEnc {
83        tag: 0,
84        value: tlv::TlvItemValueEnc::StructInvisible(vec![
85        (0, tlv::TlvItemValueEnc::Int16(pan_delta)).into(),
86        (1, tlv::TlvItemValueEnc::Int16(tilt_delta)).into(),
87        (2, tlv::TlvItemValueEnc::Int8(zoom_delta)).into(),
88        ]),
89    };
90    Ok(tlv.encode()?)
91}
92
93/// Encode MPTZMoveToPreset command (0x02)
94pub fn encode_mptz_move_to_preset(preset_id: u8) -> anyhow::Result<Vec<u8>> {
95    let tlv = tlv::TlvItemEnc {
96        tag: 0,
97        value: tlv::TlvItemValueEnc::StructInvisible(vec![
98        (0, tlv::TlvItemValueEnc::UInt8(preset_id)).into(),
99        ]),
100    };
101    Ok(tlv.encode()?)
102}
103
104/// Encode MPTZSavePreset command (0x03)
105pub fn encode_mptz_save_preset(preset_id: u8, name: String) -> anyhow::Result<Vec<u8>> {
106    let tlv = tlv::TlvItemEnc {
107        tag: 0,
108        value: tlv::TlvItemValueEnc::StructInvisible(vec![
109        (0, tlv::TlvItemValueEnc::UInt8(preset_id)).into(),
110        (1, tlv::TlvItemValueEnc::String(name)).into(),
111        ]),
112    };
113    Ok(tlv.encode()?)
114}
115
116/// Encode MPTZRemovePreset command (0x04)
117pub fn encode_mptz_remove_preset(preset_id: u8) -> anyhow::Result<Vec<u8>> {
118    let tlv = tlv::TlvItemEnc {
119        tag: 0,
120        value: tlv::TlvItemValueEnc::StructInvisible(vec![
121        (0, tlv::TlvItemValueEnc::UInt8(preset_id)).into(),
122        ]),
123    };
124    Ok(tlv.encode()?)
125}
126
127/// Encode DPTZSetViewport command (0x05)
128pub fn encode_dptz_set_viewport(video_stream_id: u8) -> anyhow::Result<Vec<u8>> {
129    let tlv = tlv::TlvItemEnc {
130        tag: 0,
131        value: tlv::TlvItemValueEnc::StructInvisible(vec![
132        (0, tlv::TlvItemValueEnc::UInt8(video_stream_id)).into(),
133        ]),
134    };
135    Ok(tlv.encode()?)
136}
137
138/// Encode DPTZRelativeMove command (0x06)
139pub fn encode_dptz_relative_move(video_stream_id: u8, delta_x: i16, delta_y: i16, zoom_delta: i8) -> anyhow::Result<Vec<u8>> {
140    let tlv = tlv::TlvItemEnc {
141        tag: 0,
142        value: tlv::TlvItemValueEnc::StructInvisible(vec![
143        (0, tlv::TlvItemValueEnc::UInt8(video_stream_id)).into(),
144        (1, tlv::TlvItemValueEnc::Int16(delta_x)).into(),
145        (2, tlv::TlvItemValueEnc::Int16(delta_y)).into(),
146        (3, tlv::TlvItemValueEnc::Int8(zoom_delta)).into(),
147        ]),
148    };
149    Ok(tlv.encode()?)
150}
151
152// Attribute decoders
153
154/// Decode MPTZPosition attribute (0x0000)
155pub fn decode_mptz_position(inp: &tlv::TlvItemValue) -> anyhow::Result<MPTZ> {
156    if let tlv::TlvItemValue::List(_fields) = inp {
157        // Struct with fields
158        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
159        Ok(MPTZ {
160                pan: item.get_int(&[0]).map(|v| v as i16),
161                tilt: item.get_int(&[1]).map(|v| v as i16),
162                zoom: item.get_int(&[2]).map(|v| v as u8),
163        })
164    } else {
165        Err(anyhow::anyhow!("Expected struct fields"))
166    }
167}
168
169/// Decode MaxPresets attribute (0x0001)
170pub fn decode_max_presets(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
171    if let tlv::TlvItemValue::Int(v) = inp {
172        Ok(*v as u8)
173    } else {
174        Err(anyhow::anyhow!("Expected UInt8"))
175    }
176}
177
178/// Decode MPTZPresets attribute (0x0002)
179pub fn decode_mptz_presets(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<MPTZPreset>> {
180    let mut res = Vec::new();
181    if let tlv::TlvItemValue::List(v) = inp {
182        for item in v {
183            res.push(MPTZPreset {
184                preset_id: item.get_int(&[0]).map(|v| v as u8),
185                name: item.get_string_owned(&[1]),
186                settings: {
187                    if let Some(nested_tlv) = item.get(&[2]) {
188                        if let tlv::TlvItemValue::List(_) = nested_tlv {
189                            let nested_item = tlv::TlvItem { tag: 2, value: nested_tlv.clone() };
190                            Some(MPTZ {
191                pan: nested_item.get_int(&[0]).map(|v| v as i16),
192                tilt: nested_item.get_int(&[1]).map(|v| v as i16),
193                zoom: nested_item.get_int(&[2]).map(|v| v as u8),
194                            })
195                        } else {
196                            None
197                        }
198                    } else {
199                        None
200                    }
201                },
202            });
203        }
204    }
205    Ok(res)
206}
207
208/// Decode DPTZStreams attribute (0x0003)
209pub fn decode_dptz_streams(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<DPTZ>> {
210    let mut res = Vec::new();
211    if let tlv::TlvItemValue::List(v) = inp {
212        for item in v {
213            res.push(DPTZ {
214                video_stream_id: item.get_int(&[0]).map(|v| v as u8),
215            });
216        }
217    }
218    Ok(res)
219}
220
221/// Decode ZoomMax attribute (0x0004)
222pub fn decode_zoom_max(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
223    if let tlv::TlvItemValue::Int(v) = inp {
224        Ok(*v as u8)
225    } else {
226        Err(anyhow::anyhow!("Expected UInt8"))
227    }
228}
229
230/// Decode TiltMin attribute (0x0005)
231pub fn decode_tilt_min(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
232    if let tlv::TlvItemValue::Int(v) = inp {
233        Ok(*v as i16)
234    } else {
235        Err(anyhow::anyhow!("Expected Int16"))
236    }
237}
238
239/// Decode TiltMax attribute (0x0006)
240pub fn decode_tilt_max(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
241    if let tlv::TlvItemValue::Int(v) = inp {
242        Ok(*v as i16)
243    } else {
244        Err(anyhow::anyhow!("Expected Int16"))
245    }
246}
247
248/// Decode PanMin attribute (0x0007)
249pub fn decode_pan_min(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
250    if let tlv::TlvItemValue::Int(v) = inp {
251        Ok(*v as i16)
252    } else {
253        Err(anyhow::anyhow!("Expected Int16"))
254    }
255}
256
257/// Decode PanMax attribute (0x0008)
258pub fn decode_pan_max(inp: &tlv::TlvItemValue) -> anyhow::Result<i16> {
259    if let tlv::TlvItemValue::Int(v) = inp {
260        Ok(*v as i16)
261    } else {
262        Err(anyhow::anyhow!("Expected Int16"))
263    }
264}
265
266/// Decode MovementState attribute (0x0009)
267pub fn decode_movement_state(inp: &tlv::TlvItemValue) -> anyhow::Result<PhysicalMovement> {
268    if let tlv::TlvItemValue::Int(v) = inp {
269        PhysicalMovement::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
270    } else {
271        Err(anyhow::anyhow!("Expected Integer"))
272    }
273}
274
275
276// JSON dispatcher function
277
278/// Decode attribute value and return as JSON string
279///
280/// # Parameters
281/// * `cluster_id` - The cluster identifier
282/// * `attribute_id` - The attribute identifier
283/// * `tlv_value` - The TLV value to decode
284///
285/// # Returns
286/// JSON string representation of the decoded value or error
287pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
288    // Verify this is the correct cluster
289    if cluster_id != 0x0552 {
290        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0552, got {}\"}}", cluster_id);
291    }
292
293    match attribute_id {
294        0x0000 => {
295            match decode_mptz_position(tlv_value) {
296                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
297                Err(e) => format!("{{\"error\": \"{}\"}}", e),
298            }
299        }
300        0x0001 => {
301            match decode_max_presets(tlv_value) {
302                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
303                Err(e) => format!("{{\"error\": \"{}\"}}", e),
304            }
305        }
306        0x0002 => {
307            match decode_mptz_presets(tlv_value) {
308                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
309                Err(e) => format!("{{\"error\": \"{}\"}}", e),
310            }
311        }
312        0x0003 => {
313            match decode_dptz_streams(tlv_value) {
314                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
315                Err(e) => format!("{{\"error\": \"{}\"}}", e),
316            }
317        }
318        0x0004 => {
319            match decode_zoom_max(tlv_value) {
320                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
321                Err(e) => format!("{{\"error\": \"{}\"}}", e),
322            }
323        }
324        0x0005 => {
325            match decode_tilt_min(tlv_value) {
326                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
327                Err(e) => format!("{{\"error\": \"{}\"}}", e),
328            }
329        }
330        0x0006 => {
331            match decode_tilt_max(tlv_value) {
332                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
333                Err(e) => format!("{{\"error\": \"{}\"}}", e),
334            }
335        }
336        0x0007 => {
337            match decode_pan_min(tlv_value) {
338                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
339                Err(e) => format!("{{\"error\": \"{}\"}}", e),
340            }
341        }
342        0x0008 => {
343            match decode_pan_max(tlv_value) {
344                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
345                Err(e) => format!("{{\"error\": \"{}\"}}", e),
346            }
347        }
348        0x0009 => {
349            match decode_movement_state(tlv_value) {
350                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
351                Err(e) => format!("{{\"error\": \"{}\"}}", e),
352            }
353        }
354        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
355    }
356}
357
358/// Get list of all attributes supported by this cluster
359///
360/// # Returns
361/// Vector of tuples containing (attribute_id, attribute_name)
362pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
363    vec![
364        (0x0000, "MPTZPosition"),
365        (0x0001, "MaxPresets"),
366        (0x0002, "MPTZPresets"),
367        (0x0003, "DPTZStreams"),
368        (0x0004, "ZoomMax"),
369        (0x0005, "TiltMin"),
370        (0x0006, "TiltMax"),
371        (0x0007, "PanMin"),
372        (0x0008, "PanMax"),
373        (0x0009, "MovementState"),
374    ]
375}
376