Skip to main content

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: Option<Vec<ChannelInfo>>, page_token: Option<PageToken>, recording_flag: Option<RecordingFlag>, data: Option<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 mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
252    tlv_fields.push((0, tlv::TlvItemValueEnc::UInt64(start_time)).into());
253    tlv_fields.push((1, tlv::TlvItemValueEnc::UInt64(end_time)).into());
254    if let Some(channel_list) = channel_list {
255        tlv_fields.push((2, tlv::TlvItemValueEnc::Array(channel_list.into_iter().map(|v| {
256                    let mut fields = Vec::new();
257                    if let Some(x) = v.major_number { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
258                    if let Some(x) = v.minor_number { fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
259                    if let Some(x) = v.name { fields.push((2, tlv::TlvItemValueEnc::String(x.clone())).into()); }
260                    if let Some(x) = v.call_sign { fields.push((3, tlv::TlvItemValueEnc::String(x.clone())).into()); }
261                    if let Some(x) = v.affiliate_call_sign { fields.push((4, tlv::TlvItemValueEnc::String(x.clone())).into()); }
262                    if let Some(x) = v.identifier { fields.push((5, tlv::TlvItemValueEnc::String(x.clone())).into()); }
263                    if let Some(x) = v.type_ { fields.push((6, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
264                    (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
265                }).collect())).into());
266    }
267    tlv_fields.push((3, page_token_enc).into());
268    tlv_fields.push((5, tlv::TlvItemValueEnc::UInt8(recording_flag.unwrap_or_default())).into());
269    if let Some(x) = data { tlv_fields.push((7, tlv::TlvItemValueEnc::OctetString(x)).into()); }
270    let tlv = tlv::TlvItemEnc {
271        tag: 0,
272        value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
273    };
274    Ok(tlv.encode()?)
275}
276
277/// Encode RecordProgram command (0x06)
278pub fn encode_record_program(program_identifier: String, should_record_series: bool, data: Option<Vec<u8>>) -> anyhow::Result<Vec<u8>> {
279    let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
280    tlv_fields.push((0, tlv::TlvItemValueEnc::String(program_identifier)).into());
281    tlv_fields.push((1, tlv::TlvItemValueEnc::Bool(should_record_series)).into());
282    if let Some(x) = data { tlv_fields.push((3, tlv::TlvItemValueEnc::OctetString(x)).into()); }
283    let tlv = tlv::TlvItemEnc {
284        tag: 0,
285        value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
286    };
287    Ok(tlv.encode()?)
288}
289
290/// Encode CancelRecordProgram command (0x07)
291pub fn encode_cancel_record_program(program_identifier: String, should_record_series: bool, data: Option<Vec<u8>>) -> anyhow::Result<Vec<u8>> {
292    let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
293    tlv_fields.push((0, tlv::TlvItemValueEnc::String(program_identifier)).into());
294    tlv_fields.push((1, tlv::TlvItemValueEnc::Bool(should_record_series)).into());
295    if let Some(x) = data { tlv_fields.push((3, tlv::TlvItemValueEnc::OctetString(x)).into()); }
296    let tlv = tlv::TlvItemEnc {
297        tag: 0,
298        value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
299    };
300    Ok(tlv.encode()?)
301}
302
303// Attribute decoders
304
305/// Decode ChannelList attribute (0x0000)
306pub fn decode_channel_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<ChannelInfo>> {
307    let mut res = Vec::new();
308    if let tlv::TlvItemValue::List(v) = inp {
309        for item in v {
310            res.push(ChannelInfo {
311                major_number: item.get_int(&[0]).map(|v| v as u16),
312                minor_number: item.get_int(&[1]).map(|v| v as u16),
313                name: item.get_string_owned(&[2]),
314                call_sign: item.get_string_owned(&[3]),
315                affiliate_call_sign: item.get_string_owned(&[4]),
316                identifier: item.get_string_owned(&[5]),
317                type_: item.get_int(&[6]).and_then(|v| ChannelType::from_u8(v as u8)),
318            });
319        }
320    }
321    Ok(res)
322}
323
324/// Decode Lineup attribute (0x0001)
325pub fn decode_lineup(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<LineupInfo>> {
326    if let tlv::TlvItemValue::List(_fields) = inp {
327        // Struct with fields
328        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
329        Ok(Some(LineupInfo {
330                operator_name: item.get_string_owned(&[0]),
331                lineup_name: item.get_string_owned(&[1]),
332                postal_code: item.get_string_owned(&[2]),
333                lineup_info_type: item.get_int(&[3]).and_then(|v| LineupInfoType::from_u8(v as u8)),
334        }))
335    //} else if let tlv::TlvItemValue::Null = inp {
336    //    // Null value for nullable struct
337    //    Ok(None)
338    } else {
339    Ok(None)
340    //    Err(anyhow::anyhow!("Expected struct fields or null"))
341    }
342}
343
344/// Decode CurrentChannel attribute (0x0002)
345pub fn decode_current_channel(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<ChannelInfo>> {
346    if let tlv::TlvItemValue::List(_fields) = inp {
347        // Struct with fields
348        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
349        Ok(Some(ChannelInfo {
350                major_number: item.get_int(&[0]).map(|v| v as u16),
351                minor_number: item.get_int(&[1]).map(|v| v as u16),
352                name: item.get_string_owned(&[2]),
353                call_sign: item.get_string_owned(&[3]),
354                affiliate_call_sign: item.get_string_owned(&[4]),
355                identifier: item.get_string_owned(&[5]),
356                type_: item.get_int(&[6]).and_then(|v| ChannelType::from_u8(v as u8)),
357        }))
358    //} else if let tlv::TlvItemValue::Null = inp {
359    //    // Null value for nullable struct
360    //    Ok(None)
361    } else {
362    Ok(None)
363    //    Err(anyhow::anyhow!("Expected struct fields or null"))
364    }
365}
366
367
368// JSON dispatcher function
369
370/// Decode attribute value and return as JSON string
371///
372/// # Parameters
373/// * `cluster_id` - The cluster identifier
374/// * `attribute_id` - The attribute identifier
375/// * `tlv_value` - The TLV value to decode
376///
377/// # Returns
378/// JSON string representation of the decoded value or error
379pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
380    // Verify this is the correct cluster
381    if cluster_id != 0x0504 {
382        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0504, got {}\"}}", cluster_id);
383    }
384
385    match attribute_id {
386        0x0000 => {
387            match decode_channel_list(tlv_value) {
388                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
389                Err(e) => format!("{{\"error\": \"{}\"}}", e),
390            }
391        }
392        0x0001 => {
393            match decode_lineup(tlv_value) {
394                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
395                Err(e) => format!("{{\"error\": \"{}\"}}", e),
396            }
397        }
398        0x0002 => {
399            match decode_current_channel(tlv_value) {
400                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
401                Err(e) => format!("{{\"error\": \"{}\"}}", e),
402            }
403        }
404        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
405    }
406}
407
408/// Get list of all attributes supported by this cluster
409///
410/// # Returns
411/// Vector of tuples containing (attribute_id, attribute_name)
412pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
413    vec![
414        (0x0000, "ChannelList"),
415        (0x0001, "Lineup"),
416        (0x0002, "CurrentChannel"),
417    ]
418}
419
420// Command listing
421
422pub fn get_command_list() -> Vec<(u32, &'static str)> {
423    vec![
424        (0x00, "ChangeChannel"),
425        (0x02, "ChangeChannelByNumber"),
426        (0x03, "SkipChannel"),
427        (0x04, "GetProgramGuide"),
428        (0x06, "RecordProgram"),
429        (0x07, "CancelRecordProgram"),
430    ]
431}
432
433pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
434    match cmd_id {
435        0x00 => Some("ChangeChannel"),
436        0x02 => Some("ChangeChannelByNumber"),
437        0x03 => Some("SkipChannel"),
438        0x04 => Some("GetProgramGuide"),
439        0x06 => Some("RecordProgram"),
440        0x07 => Some("CancelRecordProgram"),
441        _ => None,
442    }
443}
444
445pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
446    match cmd_id {
447        0x00 => Some(vec![
448            crate::clusters::codec::CommandField { tag: 0, name: "match_", kind: crate::clusters::codec::FieldKind::String, optional: false, nullable: false },
449        ]),
450        0x02 => Some(vec![
451            crate::clusters::codec::CommandField { tag: 0, name: "major_number", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
452            crate::clusters::codec::CommandField { tag: 1, name: "minor_number", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
453        ]),
454        0x03 => Some(vec![
455            crate::clusters::codec::CommandField { tag: 0, name: "count", kind: crate::clusters::codec::FieldKind::I16, optional: false, nullable: false },
456        ]),
457        0x04 => Some(vec![
458            crate::clusters::codec::CommandField { tag: 0, name: "start_time", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: false },
459            crate::clusters::codec::CommandField { tag: 1, name: "end_time", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: false },
460            crate::clusters::codec::CommandField { tag: 2, name: "channel_list", kind: crate::clusters::codec::FieldKind::List { entry_type: "ChannelInfoStruct" }, optional: true, nullable: false },
461            crate::clusters::codec::CommandField { tag: 3, name: "page_token", kind: crate::clusters::codec::FieldKind::Struct { name: "PageTokenStruct" }, optional: true, nullable: true },
462            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 },
463            crate::clusters::codec::CommandField { tag: 7, name: "data", kind: crate::clusters::codec::FieldKind::OctetString, optional: true, nullable: false },
464        ]),
465        0x06 => Some(vec![
466            crate::clusters::codec::CommandField { tag: 0, name: "program_identifier", kind: crate::clusters::codec::FieldKind::String, optional: false, nullable: false },
467            crate::clusters::codec::CommandField { tag: 1, name: "should_record_series", kind: crate::clusters::codec::FieldKind::Bool, optional: false, nullable: false },
468            crate::clusters::codec::CommandField { tag: 3, name: "data", kind: crate::clusters::codec::FieldKind::OctetString, optional: true, nullable: false },
469        ]),
470        0x07 => Some(vec![
471            crate::clusters::codec::CommandField { tag: 0, name: "program_identifier", kind: crate::clusters::codec::FieldKind::String, optional: false, nullable: false },
472            crate::clusters::codec::CommandField { tag: 1, name: "should_record_series", kind: crate::clusters::codec::FieldKind::Bool, optional: false, nullable: false },
473            crate::clusters::codec::CommandField { tag: 3, name: "data", kind: crate::clusters::codec::FieldKind::OctetString, optional: true, nullable: false },
474        ]),
475        _ => None,
476    }
477}
478
479pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
480    match cmd_id {
481        0x00 => {
482        let match_ = crate::clusters::codec::json_util::get_string(args, "match_")?;
483        encode_change_channel(match_)
484        }
485        0x02 => {
486        let major_number = crate::clusters::codec::json_util::get_u16(args, "major_number")?;
487        let minor_number = crate::clusters::codec::json_util::get_u16(args, "minor_number")?;
488        encode_change_channel_by_number(major_number, minor_number)
489        }
490        0x03 => {
491        let count = crate::clusters::codec::json_util::get_i16(args, "count")?;
492        encode_skip_channel(count)
493        }
494        0x04 => Err(anyhow::anyhow!("command \"GetProgramGuide\" has complex args: use raw mode")),
495        0x06 => {
496        let program_identifier = crate::clusters::codec::json_util::get_string(args, "program_identifier")?;
497        let should_record_series = crate::clusters::codec::json_util::get_bool(args, "should_record_series")?;
498        let data = crate::clusters::codec::json_util::get_opt_octstr(args, "data")?;
499        encode_record_program(program_identifier, should_record_series, data)
500        }
501        0x07 => {
502        let program_identifier = crate::clusters::codec::json_util::get_string(args, "program_identifier")?;
503        let should_record_series = crate::clusters::codec::json_util::get_bool(args, "should_record_series")?;
504        let data = crate::clusters::codec::json_util::get_opt_octstr(args, "data")?;
505        encode_cancel_record_program(program_identifier, should_record_series, data)
506        }
507        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
508    }
509}
510
511#[derive(Debug, serde::Serialize)]
512pub struct ChangeChannelResponse {
513    pub status: Option<Status>,
514    pub data: Option<String>,
515}
516
517#[derive(Debug, serde::Serialize)]
518pub struct ProgramGuideResponse {
519    pub paging: Option<ChannelPaging>,
520    pub program_list: Option<Vec<Program>>,
521}
522
523// Command response decoders
524
525/// Decode ChangeChannelResponse command response (01)
526pub fn decode_change_channel_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ChangeChannelResponse> {
527    if let tlv::TlvItemValue::List(_fields) = inp {
528        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
529        Ok(ChangeChannelResponse {
530                status: item.get_int(&[0]).and_then(|v| Status::from_u8(v as u8)),
531                data: item.get_string_owned(&[1]),
532        })
533    } else {
534        Err(anyhow::anyhow!("Expected struct fields"))
535    }
536}
537
538/// Decode ProgramGuideResponse command response (05)
539pub fn decode_program_guide_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ProgramGuideResponse> {
540    if let tlv::TlvItemValue::List(_fields) = inp {
541        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
542        Ok(ProgramGuideResponse {
543                paging: {
544                    if let Some(nested_tlv) = item.get(&[0]) {
545                        if let tlv::TlvItemValue::List(_) = nested_tlv {
546                            let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
547                            Some(ChannelPaging {
548                previous_token: {
549                    if let Some(nested_tlv) = nested_item.get(&[0]) {
550                        if let tlv::TlvItemValue::List(_) = nested_tlv {
551                            let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
552                            Some(PageToken {
553                limit: nested_item.get_int(&[0]).map(|v| v as u16),
554                after: nested_item.get_string_owned(&[1]),
555                before: nested_item.get_string_owned(&[2]),
556                            })
557                        } else {
558                            None
559                        }
560                    } else {
561                        None
562                    }
563                },
564                next_token: {
565                    if let Some(nested_tlv) = nested_item.get(&[1]) {
566                        if let tlv::TlvItemValue::List(_) = nested_tlv {
567                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
568                            Some(PageToken {
569                limit: nested_item.get_int(&[0]).map(|v| v as u16),
570                after: nested_item.get_string_owned(&[1]),
571                before: nested_item.get_string_owned(&[2]),
572                            })
573                        } else {
574                            None
575                        }
576                    } else {
577                        None
578                    }
579                },
580                            })
581                        } else {
582                            None
583                        }
584                    } else {
585                        None
586                    }
587                },
588                program_list: {
589                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[1]) {
590                        let mut items = Vec::new();
591                        for list_item in l {
592                            items.push(Program {
593                identifier: list_item.get_string_owned(&[0]),
594                channel: {
595                    if let Some(nested_tlv) = list_item.get(&[1]) {
596                        if let tlv::TlvItemValue::List(_) = nested_tlv {
597                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
598                            Some(ChannelInfo {
599                major_number: nested_item.get_int(&[0]).map(|v| v as u16),
600                minor_number: nested_item.get_int(&[1]).map(|v| v as u16),
601                name: nested_item.get_string_owned(&[2]),
602                call_sign: nested_item.get_string_owned(&[3]),
603                affiliate_call_sign: nested_item.get_string_owned(&[4]),
604                identifier: nested_item.get_string_owned(&[5]),
605                type_: nested_item.get_int(&[6]).and_then(|v| ChannelType::from_u8(v as u8)),
606                            })
607                        } else {
608                            None
609                        }
610                    } else {
611                        None
612                    }
613                },
614                start_time: list_item.get_int(&[2]),
615                end_time: list_item.get_int(&[3]),
616                title: list_item.get_string_owned(&[4]),
617                subtitle: list_item.get_string_owned(&[5]),
618                description: list_item.get_string_owned(&[6]),
619                audio_languages: {
620                    if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[7]) {
621                        let items: Vec<String> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::String(v) = &e.value { Some(v.clone()) } else { None } }).collect();
622                        Some(items)
623                    } else {
624                        None
625                    }
626                },
627                ratings: {
628                    if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[8]) {
629                        let items: Vec<String> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::String(v) = &e.value { Some(v.clone()) } else { None } }).collect();
630                        Some(items)
631                    } else {
632                        None
633                    }
634                },
635                thumbnail_url: list_item.get_string_owned(&[9]),
636                poster_art_url: list_item.get_string_owned(&[10]),
637                dvbi_url: list_item.get_string_owned(&[11]),
638                release_date: list_item.get_string_owned(&[12]),
639                parental_guidance_text: list_item.get_string_owned(&[13]),
640                recording_flag: list_item.get_int(&[14]).map(|v| v as u8),
641                series_info: {
642                    if let Some(nested_tlv) = list_item.get(&[15]) {
643                        if let tlv::TlvItemValue::List(_) = nested_tlv {
644                            let nested_item = tlv::TlvItem { tag: 15, value: nested_tlv.clone() };
645                            Some(SeriesInfo {
646                season: nested_item.get_string_owned(&[0]),
647                episode: nested_item.get_string_owned(&[1]),
648                            })
649                        } else {
650                            None
651                        }
652                    } else {
653                        None
654                    }
655                },
656                category_list: {
657                    if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[16]) {
658                        let mut items = Vec::new();
659                        for list_item in l {
660                            items.push(ProgramCategory {
661                category: list_item.get_string_owned(&[0]),
662                sub_category: list_item.get_string_owned(&[1]),
663                            });
664                        }
665                        Some(items)
666                    } else {
667                        None
668                    }
669                },
670                cast_list: {
671                    if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[17]) {
672                        let mut items = Vec::new();
673                        for list_item in l {
674                            items.push(ProgramCast {
675                name: list_item.get_string_owned(&[0]),
676                role: list_item.get_string_owned(&[1]),
677                            });
678                        }
679                        Some(items)
680                    } else {
681                        None
682                    }
683                },
684                            });
685                        }
686                        Some(items)
687                    } else {
688                        None
689                    }
690                },
691        })
692    } else {
693        Err(anyhow::anyhow!("Expected struct fields"))
694    }
695}
696
697// Typed facade (invokes + reads)
698
699/// Invoke `ChangeChannel` command on cluster `Channel`.
700pub async fn change_channel(conn: &crate::controller::Connection, endpoint: u16, match_: String) -> anyhow::Result<ChangeChannelResponse> {
701    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?;
702    decode_change_channel_response(&tlv)
703}
704
705/// Invoke `ChangeChannelByNumber` command on cluster `Channel`.
706pub async fn change_channel_by_number(conn: &crate::controller::Connection, endpoint: u16, major_number: u16, minor_number: u16) -> anyhow::Result<()> {
707    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?;
708    Ok(())
709}
710
711/// Invoke `SkipChannel` command on cluster `Channel`.
712pub async fn skip_channel(conn: &crate::controller::Connection, endpoint: u16, count: i16) -> anyhow::Result<()> {
713    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_CHANNEL, crate::clusters::defs::CLUSTER_CHANNEL_CMD_ID_SKIPCHANNEL, &encode_skip_channel(count)?).await?;
714    Ok(())
715}
716
717/// Invoke `GetProgramGuide` command on cluster `Channel`.
718pub async fn get_program_guide(conn: &crate::controller::Connection, endpoint: u16, start_time: u64, end_time: u64, channel_list: Option<Vec<ChannelInfo>>, page_token: Option<PageToken>, recording_flag: Option<RecordingFlag>, data: Option<Vec<u8>>) -> anyhow::Result<ProgramGuideResponse> {
719    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?;
720    decode_program_guide_response(&tlv)
721}
722
723/// Invoke `RecordProgram` command on cluster `Channel`.
724pub async fn record_program(conn: &crate::controller::Connection, endpoint: u16, program_identifier: String, should_record_series: bool, data: Option<Vec<u8>>) -> anyhow::Result<()> {
725    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?;
726    Ok(())
727}
728
729/// Invoke `CancelRecordProgram` command on cluster `Channel`.
730pub async fn cancel_record_program(conn: &crate::controller::Connection, endpoint: u16, program_identifier: String, should_record_series: bool, data: Option<Vec<u8>>) -> anyhow::Result<()> {
731    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?;
732    Ok(())
733}
734
735/// Read `ChannelList` attribute from cluster `Channel`.
736pub async fn read_channel_list(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<ChannelInfo>> {
737    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CHANNEL, crate::clusters::defs::CLUSTER_CHANNEL_ATTR_ID_CHANNELLIST).await?;
738    decode_channel_list(&tlv)
739}
740
741/// Read `Lineup` attribute from cluster `Channel`.
742pub async fn read_lineup(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<LineupInfo>> {
743    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CHANNEL, crate::clusters::defs::CLUSTER_CHANNEL_ATTR_ID_LINEUP).await?;
744    decode_lineup(&tlv)
745}
746
747/// Read `CurrentChannel` attribute from cluster `Channel`.
748pub async fn read_current_channel(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<ChannelInfo>> {
749    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CHANNEL, crate::clusters::defs::CLUSTER_CHANNEL_ATTR_ID_CURRENTCHANNEL).await?;
750    decode_current_channel(&tlv)
751}
752