matc/clusters/codec/
media_playback.rs

1//! Generated Matter TLV encoders and decoders for Media Playback Cluster
2//! Cluster ID: 0x0506
3//! 
4//! This file is automatically generated from MediaPlayback.xml
5
6use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11// Struct definitions
12
13#[derive(Debug, serde::Serialize)]
14pub struct PlaybackPosition {
15    pub updated_at: Option<u8>,
16    pub position: Option<u64>,
17}
18
19#[derive(Debug, serde::Serialize)]
20pub struct TrackAttributes {
21    pub language_code: Option<String>,
22    pub characteristics: Option<Vec<u8>>,
23    pub display_name: Option<String>,
24}
25
26#[derive(Debug, serde::Serialize)]
27pub struct Track {
28    pub id: Option<String>,
29    pub track_attributes: Option<TrackAttributes>,
30}
31
32// Command encoders
33
34/// Encode Rewind command (0x06)
35pub fn encode_rewind(audio_advance_unmuted: bool) -> anyhow::Result<Vec<u8>> {
36    let tlv = tlv::TlvItemEnc {
37        tag: 0,
38        value: tlv::TlvItemValueEnc::StructInvisible(vec![
39        (0, tlv::TlvItemValueEnc::Bool(audio_advance_unmuted)).into(),
40        ]),
41    };
42    Ok(tlv.encode()?)
43}
44
45/// Encode FastForward command (0x07)
46pub fn encode_fast_forward(audio_advance_unmuted: bool) -> anyhow::Result<Vec<u8>> {
47    let tlv = tlv::TlvItemEnc {
48        tag: 0,
49        value: tlv::TlvItemValueEnc::StructInvisible(vec![
50        (0, tlv::TlvItemValueEnc::Bool(audio_advance_unmuted)).into(),
51        ]),
52    };
53    Ok(tlv.encode()?)
54}
55
56/// Encode SkipForward command (0x08)
57pub fn encode_skip_forward(delta_position_milliseconds: u64) -> anyhow::Result<Vec<u8>> {
58    let tlv = tlv::TlvItemEnc {
59        tag: 0,
60        value: tlv::TlvItemValueEnc::StructInvisible(vec![
61        (0, tlv::TlvItemValueEnc::UInt64(delta_position_milliseconds)).into(),
62        ]),
63    };
64    Ok(tlv.encode()?)
65}
66
67/// Encode SkipBackward command (0x09)
68pub fn encode_skip_backward(delta_position_milliseconds: u64) -> anyhow::Result<Vec<u8>> {
69    let tlv = tlv::TlvItemEnc {
70        tag: 0,
71        value: tlv::TlvItemValueEnc::StructInvisible(vec![
72        (0, tlv::TlvItemValueEnc::UInt64(delta_position_milliseconds)).into(),
73        ]),
74    };
75    Ok(tlv.encode()?)
76}
77
78/// Encode Seek command (0x0B)
79pub fn encode_seek(position: u64) -> anyhow::Result<Vec<u8>> {
80    let tlv = tlv::TlvItemEnc {
81        tag: 0,
82        value: tlv::TlvItemValueEnc::StructInvisible(vec![
83        (0, tlv::TlvItemValueEnc::UInt64(position)).into(),
84        ]),
85    };
86    Ok(tlv.encode()?)
87}
88
89/// Encode ActivateAudioTrack command (0x0C)
90pub fn encode_activate_audio_track(track_id: String, audio_output_index: Option<u8>) -> anyhow::Result<Vec<u8>> {
91    let tlv = tlv::TlvItemEnc {
92        tag: 0,
93        value: tlv::TlvItemValueEnc::StructInvisible(vec![
94        (0, tlv::TlvItemValueEnc::String(track_id)).into(),
95        (1, tlv::TlvItemValueEnc::UInt8(audio_output_index.unwrap_or(0))).into(),
96        ]),
97    };
98    Ok(tlv.encode()?)
99}
100
101/// Encode ActivateTextTrack command (0x0D)
102pub fn encode_activate_text_track(track_id: String) -> anyhow::Result<Vec<u8>> {
103    let tlv = tlv::TlvItemEnc {
104        tag: 0,
105        value: tlv::TlvItemValueEnc::StructInvisible(vec![
106        (0, tlv::TlvItemValueEnc::String(track_id)).into(),
107        ]),
108    };
109    Ok(tlv.encode()?)
110}
111
112// Attribute decoders
113
114/// Decode CurrentState attribute (0x0000)
115pub fn decode_current_state(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
116    if let tlv::TlvItemValue::Int(v) = inp {
117        Ok(*v as u8)
118    } else {
119        Err(anyhow::anyhow!("Expected Integer"))
120    }
121}
122
123/// Decode StartTime attribute (0x0001)
124pub fn decode_start_time(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u8>> {
125    if let tlv::TlvItemValue::Int(v) = inp {
126        Ok(Some(*v as u8))
127    } else {
128        Ok(None)
129    }
130}
131
132/// Decode Duration attribute (0x0002)
133pub fn decode_duration(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
134    if let tlv::TlvItemValue::Int(v) = inp {
135        Ok(Some(*v))
136    } else {
137        Ok(None)
138    }
139}
140
141/// Decode SampledPosition attribute (0x0003)
142pub fn decode_sampled_position(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<PlaybackPosition>> {
143    if let tlv::TlvItemValue::List(_fields) = inp {
144        // Struct with fields
145        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
146        Ok(Some(PlaybackPosition {
147                updated_at: item.get_int(&[0]).map(|v| v as u8),
148                position: item.get_int(&[1]),
149        }))
150    //} else if let tlv::TlvItemValue::Null = inp {
151    //    // Null value for nullable struct
152    //    Ok(None)
153    } else {
154    Ok(None)
155    //    Err(anyhow::anyhow!("Expected struct fields or null"))
156    }
157}
158
159/// Decode PlaybackSpeed attribute (0x0004)
160pub fn decode_playback_speed(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
161    if let tlv::TlvItemValue::Int(v) = inp {
162        Ok(*v as u8)
163    } else {
164        Err(anyhow::anyhow!("Expected Integer"))
165    }
166}
167
168/// Decode SeekRangeEnd attribute (0x0005)
169pub fn decode_seek_range_end(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
170    if let tlv::TlvItemValue::Int(v) = inp {
171        Ok(Some(*v))
172    } else {
173        Ok(None)
174    }
175}
176
177/// Decode SeekRangeStart attribute (0x0006)
178pub fn decode_seek_range_start(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
179    if let tlv::TlvItemValue::Int(v) = inp {
180        Ok(Some(*v))
181    } else {
182        Ok(None)
183    }
184}
185
186/// Decode ActiveAudioTrack attribute (0x0007)
187pub fn decode_active_audio_track(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Track>> {
188    if let tlv::TlvItemValue::List(_fields) = inp {
189        // Struct with fields
190        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
191        Ok(Some(Track {
192                id: item.get_string_owned(&[0]),
193                track_attributes: {
194                    if let Some(nested_tlv) = item.get(&[1]) {
195                        if let tlv::TlvItemValue::List(_) = nested_tlv {
196                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
197                            Some(TrackAttributes {
198                language_code: nested_item.get_string_owned(&[0]),
199                characteristics: {
200                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[1]) {
201                        let items: Vec<u8> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v as u8) } else { None } }).collect();
202                        Some(items)
203                    } else {
204                        None
205                    }
206                },
207                display_name: nested_item.get_string_owned(&[2]),
208                            })
209                        } else {
210                            None
211                        }
212                    } else {
213                        None
214                    }
215                },
216        }))
217    //} else if let tlv::TlvItemValue::Null = inp {
218    //    // Null value for nullable struct
219    //    Ok(None)
220    } else {
221    Ok(None)
222    //    Err(anyhow::anyhow!("Expected struct fields or null"))
223    }
224}
225
226/// Decode AvailableAudioTracks attribute (0x0008)
227pub fn decode_available_audio_tracks(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Track>> {
228    let mut res = Vec::new();
229    if let tlv::TlvItemValue::List(v) = inp {
230        for item in v {
231            res.push(Track {
232                id: item.get_string_owned(&[0]),
233                track_attributes: {
234                    if let Some(nested_tlv) = item.get(&[1]) {
235                        if let tlv::TlvItemValue::List(_) = nested_tlv {
236                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
237                            Some(TrackAttributes {
238                language_code: nested_item.get_string_owned(&[0]),
239                characteristics: {
240                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[1]) {
241                        let items: Vec<u8> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v as u8) } else { None } }).collect();
242                        Some(items)
243                    } else {
244                        None
245                    }
246                },
247                display_name: nested_item.get_string_owned(&[2]),
248                            })
249                        } else {
250                            None
251                        }
252                    } else {
253                        None
254                    }
255                },
256            });
257        }
258    }
259    Ok(res)
260}
261
262/// Decode ActiveTextTrack attribute (0x0009)
263pub fn decode_active_text_track(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Track>> {
264    if let tlv::TlvItemValue::List(_fields) = inp {
265        // Struct with fields
266        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
267        Ok(Some(Track {
268                id: item.get_string_owned(&[0]),
269                track_attributes: {
270                    if let Some(nested_tlv) = item.get(&[1]) {
271                        if let tlv::TlvItemValue::List(_) = nested_tlv {
272                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
273                            Some(TrackAttributes {
274                language_code: nested_item.get_string_owned(&[0]),
275                characteristics: {
276                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[1]) {
277                        let items: Vec<u8> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v as u8) } else { None } }).collect();
278                        Some(items)
279                    } else {
280                        None
281                    }
282                },
283                display_name: nested_item.get_string_owned(&[2]),
284                            })
285                        } else {
286                            None
287                        }
288                    } else {
289                        None
290                    }
291                },
292        }))
293    //} else if let tlv::TlvItemValue::Null = inp {
294    //    // Null value for nullable struct
295    //    Ok(None)
296    } else {
297    Ok(None)
298    //    Err(anyhow::anyhow!("Expected struct fields or null"))
299    }
300}
301
302/// Decode AvailableTextTracks attribute (0x000A)
303pub fn decode_available_text_tracks(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Track>> {
304    let mut res = Vec::new();
305    if let tlv::TlvItemValue::List(v) = inp {
306        for item in v {
307            res.push(Track {
308                id: item.get_string_owned(&[0]),
309                track_attributes: {
310                    if let Some(nested_tlv) = item.get(&[1]) {
311                        if let tlv::TlvItemValue::List(_) = nested_tlv {
312                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
313                            Some(TrackAttributes {
314                language_code: nested_item.get_string_owned(&[0]),
315                characteristics: {
316                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[1]) {
317                        let items: Vec<u8> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v as u8) } else { None } }).collect();
318                        Some(items)
319                    } else {
320                        None
321                    }
322                },
323                display_name: nested_item.get_string_owned(&[2]),
324                            })
325                        } else {
326                            None
327                        }
328                    } else {
329                        None
330                    }
331                },
332            });
333        }
334    }
335    Ok(res)
336}
337
338
339// JSON dispatcher function
340
341/// Decode attribute value and return as JSON string
342/// 
343/// # Parameters
344/// * `cluster_id` - The cluster identifier
345/// * `attribute_id` - The attribute identifier
346/// * `tlv_value` - The TLV value to decode
347/// 
348/// # Returns
349/// JSON string representation of the decoded value or error
350pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
351    // Verify this is the correct cluster
352    if cluster_id != 0x0506 {
353        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0506, got {}\"}}", cluster_id);
354    }
355    
356    match attribute_id {
357        0x0000 => {
358            match decode_current_state(tlv_value) {
359                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
360                Err(e) => format!("{{\"error\": \"{}\"}}", e),
361            }
362        }
363        0x0001 => {
364            match decode_start_time(tlv_value) {
365                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
366                Err(e) => format!("{{\"error\": \"{}\"}}", e),
367            }
368        }
369        0x0002 => {
370            match decode_duration(tlv_value) {
371                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
372                Err(e) => format!("{{\"error\": \"{}\"}}", e),
373            }
374        }
375        0x0003 => {
376            match decode_sampled_position(tlv_value) {
377                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
378                Err(e) => format!("{{\"error\": \"{}\"}}", e),
379            }
380        }
381        0x0004 => {
382            match decode_playback_speed(tlv_value) {
383                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
384                Err(e) => format!("{{\"error\": \"{}\"}}", e),
385            }
386        }
387        0x0005 => {
388            match decode_seek_range_end(tlv_value) {
389                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
390                Err(e) => format!("{{\"error\": \"{}\"}}", e),
391            }
392        }
393        0x0006 => {
394            match decode_seek_range_start(tlv_value) {
395                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
396                Err(e) => format!("{{\"error\": \"{}\"}}", e),
397            }
398        }
399        0x0007 => {
400            match decode_active_audio_track(tlv_value) {
401                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
402                Err(e) => format!("{{\"error\": \"{}\"}}", e),
403            }
404        }
405        0x0008 => {
406            match decode_available_audio_tracks(tlv_value) {
407                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
408                Err(e) => format!("{{\"error\": \"{}\"}}", e),
409            }
410        }
411        0x0009 => {
412            match decode_active_text_track(tlv_value) {
413                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
414                Err(e) => format!("{{\"error\": \"{}\"}}", e),
415            }
416        }
417        0x000A => {
418            match decode_available_text_tracks(tlv_value) {
419                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
420                Err(e) => format!("{{\"error\": \"{}\"}}", e),
421            }
422        }
423        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
424    }
425}
426
427/// Get list of all attributes supported by this cluster
428/// 
429/// # Returns
430/// Vector of tuples containing (attribute_id, attribute_name)
431pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
432    vec![
433        (0x0000, "CurrentState"),
434        (0x0001, "StartTime"),
435        (0x0002, "Duration"),
436        (0x0003, "SampledPosition"),
437        (0x0004, "PlaybackSpeed"),
438        (0x0005, "SeekRangeEnd"),
439        (0x0006, "SeekRangeStart"),
440        (0x0007, "ActiveAudioTrack"),
441        (0x0008, "AvailableAudioTracks"),
442        (0x0009, "ActiveTextTrack"),
443        (0x000A, "AvailableTextTracks"),
444    ]
445}
446