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