matc/clusters/codec/
channel.rs

1//! Matter TLV encoders and decoders for Channel Cluster
2//! Cluster ID: 0x0504
3//!
4//! This file is automatically generated from Channel.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 ChannelType {
16    /// The channel is sourced from a satellite provider.
17    Satellite = 0,
18    /// The channel is sourced from a cable provider.
19    Cable = 1,
20    /// The channel is sourced from a terrestrial provider.
21    Terrestrial = 2,
22    /// The channel is sourced from an OTT provider.
23    Ott = 3,
24}
25
26impl ChannelType {
27    /// Convert from u8 value
28    pub fn from_u8(value: u8) -> Option<Self> {
29        match value {
30            0 => Some(ChannelType::Satellite),
31            1 => Some(ChannelType::Cable),
32            2 => Some(ChannelType::Terrestrial),
33            3 => Some(ChannelType::Ott),
34            _ => None,
35        }
36    }
37
38    /// Convert to u8 value
39    pub fn to_u8(self) -> u8 {
40        self as u8
41    }
42}
43
44impl From<ChannelType> for u8 {
45    fn from(val: ChannelType) -> Self {
46        val as u8
47    }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
51#[repr(u8)]
52pub enum LineupInfoType {
53    /// Multi System Operator
54    Mso = 0,
55}
56
57impl LineupInfoType {
58    /// Convert from u8 value
59    pub fn from_u8(value: u8) -> Option<Self> {
60        match value {
61            0 => Some(LineupInfoType::Mso),
62            _ => None,
63        }
64    }
65
66    /// Convert to u8 value
67    pub fn to_u8(self) -> u8 {
68        self as u8
69    }
70}
71
72impl From<LineupInfoType> for u8 {
73    fn from(val: LineupInfoType) -> Self {
74        val as u8
75    }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
79#[repr(u8)]
80pub enum Status {
81    /// Command succeeded
82    Success = 0,
83    /// More than one equal match for the ChannelInfoStruct passed in.
84    Multiplematches = 1,
85    /// No matches for the ChannelInfoStruct passed in.
86    Nomatches = 2,
87}
88
89impl Status {
90    /// Convert from u8 value
91    pub fn from_u8(value: u8) -> Option<Self> {
92        match value {
93            0 => Some(Status::Success),
94            1 => Some(Status::Multiplematches),
95            2 => Some(Status::Nomatches),
96            _ => None,
97        }
98    }
99
100    /// Convert to u8 value
101    pub fn to_u8(self) -> u8 {
102        self as u8
103    }
104}
105
106impl From<Status> for u8 {
107    fn from(val: Status) -> Self {
108        val as u8
109    }
110}
111
112// Bitmap definitions
113
114/// RecordingFlag bitmap type
115pub type RecordingFlag = u8;
116
117/// Constants for RecordingFlag
118pub mod recordingflag {
119    /// The program is scheduled for recording.
120    pub const SCHEDULED: u8 = 0x01;
121    /// The program series is scheduled for recording.
122    pub const RECORD_SERIES: u8 = 0x02;
123    /// The program is recorded and available to be played.
124    pub const RECORDED: u8 = 0x04;
125}
126
127// Struct definitions
128
129#[derive(Debug, serde::Serialize)]
130pub struct ChannelInfo {
131    pub major_number: Option<u16>,
132    pub minor_number: Option<u16>,
133    pub name: Option<String>,
134    pub call_sign: Option<String>,
135    pub affiliate_call_sign: Option<String>,
136    pub identifier: Option<String>,
137    pub type_: Option<ChannelType>,
138}
139
140#[derive(Debug, serde::Serialize)]
141pub struct ChannelPaging {
142    pub previous_token: Option<PageToken>,
143    pub next_token: Option<PageToken>,
144}
145
146#[derive(Debug, serde::Serialize)]
147pub struct LineupInfo {
148    pub operator_name: Option<String>,
149    pub lineup_name: Option<String>,
150    pub postal_code: Option<String>,
151    pub lineup_info_type: Option<LineupInfoType>,
152}
153
154#[derive(Debug, serde::Serialize)]
155pub struct PageToken {
156    pub limit: Option<u16>,
157    pub after: Option<String>,
158    pub before: Option<String>,
159}
160
161#[derive(Debug, serde::Serialize)]
162pub struct ProgramCast {
163    pub name: Option<String>,
164    pub role: Option<String>,
165}
166
167#[derive(Debug, serde::Serialize)]
168pub struct ProgramCategory {
169    pub category: Option<String>,
170    pub sub_category: Option<String>,
171}
172
173#[derive(Debug, serde::Serialize)]
174pub struct Program {
175    pub identifier: Option<String>,
176    pub channel: Option<ChannelInfo>,
177    pub start_time: Option<u64>,
178    pub end_time: Option<u64>,
179    pub title: Option<String>,
180    pub subtitle: Option<String>,
181    pub description: Option<String>,
182    pub audio_languages: Option<Vec<String>>,
183    pub ratings: Option<Vec<String>>,
184    pub thumbnail_url: Option<String>,
185    pub poster_art_url: Option<String>,
186    pub dvbi_url: Option<String>,
187    pub release_date: Option<String>,
188    pub parental_guidance_text: Option<String>,
189    pub recording_flag: Option<RecordingFlag>,
190    pub series_info: Option<SeriesInfo>,
191    pub category_list: Option<Vec<ProgramCategory>>,
192    pub cast_list: Option<Vec<ProgramCast>>,
193}
194
195#[derive(Debug, serde::Serialize)]
196pub struct SeriesInfo {
197    pub season: Option<String>,
198    pub episode: Option<String>,
199}
200
201// Command encoders
202
203/// Encode ChangeChannel command (0x00)
204pub fn encode_change_channel(match_: String) -> anyhow::Result<Vec<u8>> {
205    let tlv = tlv::TlvItemEnc {
206        tag: 0,
207        value: tlv::TlvItemValueEnc::StructInvisible(vec![
208        (0, tlv::TlvItemValueEnc::String(match_)).into(),
209        ]),
210    };
211    Ok(tlv.encode()?)
212}
213
214/// Encode ChangeChannelByNumber command (0x02)
215pub fn encode_change_channel_by_number(major_number: u16, minor_number: u16) -> anyhow::Result<Vec<u8>> {
216    let tlv = tlv::TlvItemEnc {
217        tag: 0,
218        value: tlv::TlvItemValueEnc::StructInvisible(vec![
219        (0, tlv::TlvItemValueEnc::UInt16(major_number)).into(),
220        (1, tlv::TlvItemValueEnc::UInt16(minor_number)).into(),
221        ]),
222    };
223    Ok(tlv.encode()?)
224}
225
226/// Encode SkipChannel command (0x03)
227pub fn encode_skip_channel(count: i16) -> anyhow::Result<Vec<u8>> {
228    let tlv = tlv::TlvItemEnc {
229        tag: 0,
230        value: tlv::TlvItemValueEnc::StructInvisible(vec![
231        (0, tlv::TlvItemValueEnc::Int16(count)).into(),
232        ]),
233    };
234    Ok(tlv.encode()?)
235}
236
237/// Encode GetProgramGuide command (0x04)
238pub fn encode_get_program_guide(start_time: u64, end_time: u64, channel_list: Vec<ChannelInfo>, page_token: Option<PageToken>, recording_flag: Option<RecordingFlag>, data: Vec<u8>) -> anyhow::Result<Vec<u8>> {
239            // Encode optional struct PageTokenStruct
240            let page_token_enc = if let Some(s) = page_token {
241                let mut fields = Vec::new();
242                if let Some(x) = s.limit { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
243                if let Some(x) = s.after { fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
244                if let Some(x) = s.before { fields.push((2, tlv::TlvItemValueEnc::String(x.clone())).into()); }
245                tlv::TlvItemValueEnc::StructInvisible(fields)
246            } else {
247                tlv::TlvItemValueEnc::StructInvisible(Vec::new())
248            };
249    let tlv = tlv::TlvItemEnc {
250        tag: 0,
251        value: tlv::TlvItemValueEnc::StructInvisible(vec![
252        (0, tlv::TlvItemValueEnc::UInt64(start_time)).into(),
253        (1, tlv::TlvItemValueEnc::UInt64(end_time)).into(),
254        (2, tlv::TlvItemValueEnc::Array(channel_list.into_iter().map(|v| {
255                    let mut fields = Vec::new();
256                    if let Some(x) = v.major_number { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
257                    if let Some(x) = v.minor_number { fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
258                    if let Some(x) = v.name { fields.push((2, tlv::TlvItemValueEnc::String(x.clone())).into()); }
259                    if let Some(x) = v.call_sign { fields.push((3, tlv::TlvItemValueEnc::String(x.clone())).into()); }
260                    if let Some(x) = v.affiliate_call_sign { fields.push((4, tlv::TlvItemValueEnc::String(x.clone())).into()); }
261                    if let Some(x) = v.identifier { fields.push((5, tlv::TlvItemValueEnc::String(x.clone())).into()); }
262                    if let Some(x) = v.type_ { fields.push((6, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
263                    (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
264                }).collect())).into(),
265        (3, page_token_enc).into(),
266        (5, tlv::TlvItemValueEnc::UInt8(recording_flag.unwrap_or_default())).into(),
267        (7, tlv::TlvItemValueEnc::OctetString(data)).into(),
268        ]),
269    };
270    Ok(tlv.encode()?)
271}
272
273/// Encode RecordProgram command (0x06)
274pub fn encode_record_program(program_identifier: String, should_record_series: bool, data: Vec<u8>) -> anyhow::Result<Vec<u8>> {
275    let tlv = tlv::TlvItemEnc {
276        tag: 0,
277        value: tlv::TlvItemValueEnc::StructInvisible(vec![
278        (0, tlv::TlvItemValueEnc::String(program_identifier)).into(),
279        (1, tlv::TlvItemValueEnc::Bool(should_record_series)).into(),
280        (3, tlv::TlvItemValueEnc::OctetString(data)).into(),
281        ]),
282    };
283    Ok(tlv.encode()?)
284}
285
286/// Encode CancelRecordProgram command (0x07)
287pub fn encode_cancel_record_program(program_identifier: String, should_record_series: bool, data: Vec<u8>) -> anyhow::Result<Vec<u8>> {
288    let tlv = tlv::TlvItemEnc {
289        tag: 0,
290        value: tlv::TlvItemValueEnc::StructInvisible(vec![
291        (0, tlv::TlvItemValueEnc::String(program_identifier)).into(),
292        (1, tlv::TlvItemValueEnc::Bool(should_record_series)).into(),
293        (3, tlv::TlvItemValueEnc::OctetString(data)).into(),
294        ]),
295    };
296    Ok(tlv.encode()?)
297}
298
299// Attribute decoders
300
301/// Decode ChannelList attribute (0x0000)
302pub fn decode_channel_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<ChannelInfo>> {
303    let mut res = Vec::new();
304    if let tlv::TlvItemValue::List(v) = inp {
305        for item in v {
306            res.push(ChannelInfo {
307                major_number: item.get_int(&[0]).map(|v| v as u16),
308                minor_number: item.get_int(&[1]).map(|v| v as u16),
309                name: item.get_string_owned(&[2]),
310                call_sign: item.get_string_owned(&[3]),
311                affiliate_call_sign: item.get_string_owned(&[4]),
312                identifier: item.get_string_owned(&[5]),
313                type_: item.get_int(&[6]).and_then(|v| ChannelType::from_u8(v as u8)),
314            });
315        }
316    }
317    Ok(res)
318}
319
320/// Decode Lineup attribute (0x0001)
321pub fn decode_lineup(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<LineupInfo>> {
322    if let tlv::TlvItemValue::List(_fields) = inp {
323        // Struct with fields
324        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
325        Ok(Some(LineupInfo {
326                operator_name: item.get_string_owned(&[0]),
327                lineup_name: item.get_string_owned(&[1]),
328                postal_code: item.get_string_owned(&[2]),
329                lineup_info_type: item.get_int(&[3]).and_then(|v| LineupInfoType::from_u8(v as u8)),
330        }))
331    //} else if let tlv::TlvItemValue::Null = inp {
332    //    // Null value for nullable struct
333    //    Ok(None)
334    } else {
335    Ok(None)
336    //    Err(anyhow::anyhow!("Expected struct fields or null"))
337    }
338}
339
340/// Decode CurrentChannel attribute (0x0002)
341pub fn decode_current_channel(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<ChannelInfo>> {
342    if let tlv::TlvItemValue::List(_fields) = inp {
343        // Struct with fields
344        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
345        Ok(Some(ChannelInfo {
346                major_number: item.get_int(&[0]).map(|v| v as u16),
347                minor_number: item.get_int(&[1]).map(|v| v as u16),
348                name: item.get_string_owned(&[2]),
349                call_sign: item.get_string_owned(&[3]),
350                affiliate_call_sign: item.get_string_owned(&[4]),
351                identifier: item.get_string_owned(&[5]),
352                type_: item.get_int(&[6]).and_then(|v| ChannelType::from_u8(v as u8)),
353        }))
354    //} else if let tlv::TlvItemValue::Null = inp {
355    //    // Null value for nullable struct
356    //    Ok(None)
357    } else {
358    Ok(None)
359    //    Err(anyhow::anyhow!("Expected struct fields or null"))
360    }
361}
362
363
364// JSON dispatcher function
365
366/// Decode attribute value and return as JSON string
367///
368/// # Parameters
369/// * `cluster_id` - The cluster identifier
370/// * `attribute_id` - The attribute identifier
371/// * `tlv_value` - The TLV value to decode
372///
373/// # Returns
374/// JSON string representation of the decoded value or error
375pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
376    // Verify this is the correct cluster
377    if cluster_id != 0x0504 {
378        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0504, got {}\"}}", cluster_id);
379    }
380
381    match attribute_id {
382        0x0000 => {
383            match decode_channel_list(tlv_value) {
384                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
385                Err(e) => format!("{{\"error\": \"{}\"}}", e),
386            }
387        }
388        0x0001 => {
389            match decode_lineup(tlv_value) {
390                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
391                Err(e) => format!("{{\"error\": \"{}\"}}", e),
392            }
393        }
394        0x0002 => {
395            match decode_current_channel(tlv_value) {
396                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
397                Err(e) => format!("{{\"error\": \"{}\"}}", e),
398            }
399        }
400        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
401    }
402}
403
404/// Get list of all attributes supported by this cluster
405///
406/// # Returns
407/// Vector of tuples containing (attribute_id, attribute_name)
408pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
409    vec![
410        (0x0000, "ChannelList"),
411        (0x0001, "Lineup"),
412        (0x0002, "CurrentChannel"),
413    ]
414}
415
416#[derive(Debug, serde::Serialize)]
417pub struct ChangeChannelResponse {
418    pub status: Option<Status>,
419    pub data: Option<String>,
420}
421
422#[derive(Debug, serde::Serialize)]
423pub struct ProgramGuideResponse {
424    pub paging: Option<ChannelPaging>,
425    pub program_list: Option<Vec<Program>>,
426}
427
428// Command response decoders
429
430/// Decode ChangeChannelResponse command response (01)
431pub fn decode_change_channel_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ChangeChannelResponse> {
432    if let tlv::TlvItemValue::List(_fields) = inp {
433        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
434        Ok(ChangeChannelResponse {
435                status: item.get_int(&[0]).and_then(|v| Status::from_u8(v as u8)),
436                data: item.get_string_owned(&[1]),
437        })
438    } else {
439        Err(anyhow::anyhow!("Expected struct fields"))
440    }
441}
442
443/// Decode ProgramGuideResponse command response (05)
444pub fn decode_program_guide_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ProgramGuideResponse> {
445    if let tlv::TlvItemValue::List(_fields) = inp {
446        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
447        Ok(ProgramGuideResponse {
448                paging: {
449                    if let Some(nested_tlv) = item.get(&[0]) {
450                        if let tlv::TlvItemValue::List(_) = nested_tlv {
451                            let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
452                            Some(ChannelPaging {
453                previous_token: {
454                    if let Some(nested_tlv) = nested_item.get(&[0]) {
455                        if let tlv::TlvItemValue::List(_) = nested_tlv {
456                            let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
457                            Some(PageToken {
458                limit: nested_item.get_int(&[0]).map(|v| v as u16),
459                after: nested_item.get_string_owned(&[1]),
460                before: nested_item.get_string_owned(&[2]),
461                            })
462                        } else {
463                            None
464                        }
465                    } else {
466                        None
467                    }
468                },
469                next_token: {
470                    if let Some(nested_tlv) = nested_item.get(&[1]) {
471                        if let tlv::TlvItemValue::List(_) = nested_tlv {
472                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
473                            Some(PageToken {
474                limit: nested_item.get_int(&[0]).map(|v| v as u16),
475                after: nested_item.get_string_owned(&[1]),
476                before: nested_item.get_string_owned(&[2]),
477                            })
478                        } else {
479                            None
480                        }
481                    } else {
482                        None
483                    }
484                },
485                            })
486                        } else {
487                            None
488                        }
489                    } else {
490                        None
491                    }
492                },
493                program_list: {
494                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[1]) {
495                        let mut items = Vec::new();
496                        for list_item in l {
497                            items.push(Program {
498                identifier: list_item.get_string_owned(&[0]),
499                channel: {
500                    if let Some(nested_tlv) = list_item.get(&[1]) {
501                        if let tlv::TlvItemValue::List(_) = nested_tlv {
502                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
503                            Some(ChannelInfo {
504                major_number: nested_item.get_int(&[0]).map(|v| v as u16),
505                minor_number: nested_item.get_int(&[1]).map(|v| v as u16),
506                name: nested_item.get_string_owned(&[2]),
507                call_sign: nested_item.get_string_owned(&[3]),
508                affiliate_call_sign: nested_item.get_string_owned(&[4]),
509                identifier: nested_item.get_string_owned(&[5]),
510                type_: nested_item.get_int(&[6]).and_then(|v| ChannelType::from_u8(v as u8)),
511                            })
512                        } else {
513                            None
514                        }
515                    } else {
516                        None
517                    }
518                },
519                start_time: list_item.get_int(&[2]),
520                end_time: list_item.get_int(&[3]),
521                title: list_item.get_string_owned(&[4]),
522                subtitle: list_item.get_string_owned(&[5]),
523                description: list_item.get_string_owned(&[6]),
524                audio_languages: {
525                    if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[7]) {
526                        let items: Vec<String> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::String(v) = &e.value { Some(v.clone()) } else { None } }).collect();
527                        Some(items)
528                    } else {
529                        None
530                    }
531                },
532                ratings: {
533                    if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[8]) {
534                        let items: Vec<String> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::String(v) = &e.value { Some(v.clone()) } else { None } }).collect();
535                        Some(items)
536                    } else {
537                        None
538                    }
539                },
540                thumbnail_url: list_item.get_string_owned(&[9]),
541                poster_art_url: list_item.get_string_owned(&[10]),
542                dvbi_url: list_item.get_string_owned(&[11]),
543                release_date: list_item.get_string_owned(&[12]),
544                parental_guidance_text: list_item.get_string_owned(&[13]),
545                recording_flag: list_item.get_int(&[14]).map(|v| v as u8),
546                series_info: {
547                    if let Some(nested_tlv) = list_item.get(&[15]) {
548                        if let tlv::TlvItemValue::List(_) = nested_tlv {
549                            let nested_item = tlv::TlvItem { tag: 15, value: nested_tlv.clone() };
550                            Some(SeriesInfo {
551                season: nested_item.get_string_owned(&[0]),
552                episode: nested_item.get_string_owned(&[1]),
553                            })
554                        } else {
555                            None
556                        }
557                    } else {
558                        None
559                    }
560                },
561                category_list: {
562                    if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[16]) {
563                        let mut items = Vec::new();
564                        for list_item in l {
565                            items.push(ProgramCategory {
566                category: list_item.get_string_owned(&[0]),
567                sub_category: list_item.get_string_owned(&[1]),
568                            });
569                        }
570                        Some(items)
571                    } else {
572                        None
573                    }
574                },
575                cast_list: {
576                    if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[17]) {
577                        let mut items = Vec::new();
578                        for list_item in l {
579                            items.push(ProgramCast {
580                name: list_item.get_string_owned(&[0]),
581                role: list_item.get_string_owned(&[1]),
582                            });
583                        }
584                        Some(items)
585                    } else {
586                        None
587                    }
588                },
589                            });
590                        }
591                        Some(items)
592                    } else {
593                        None
594                    }
595                },
596        })
597    } else {
598        Err(anyhow::anyhow!("Expected struct fields"))
599    }
600}
601