1#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16#[repr(u8)]
17pub enum ChannelType {
18 Satellite = 0,
20 Cable = 1,
22 Terrestrial = 2,
24 Ott = 3,
26}
27
28impl ChannelType {
29 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 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 Mso = 0,
57}
58
59impl LineupInfoType {
60 pub fn from_u8(value: u8) -> Option<Self> {
62 match value {
63 0 => Some(LineupInfoType::Mso),
64 _ => None,
65 }
66 }
67
68 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 Success = 0,
85 Multiplematches = 1,
87 Nomatches = 2,
89}
90
91impl Status {
92 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 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
114pub type RecordingFlag = u8;
118
119pub mod recordingflag {
121 pub const SCHEDULED: u8 = 0x01;
123 pub const RECORD_SERIES: u8 = 0x02;
125 pub const RECORDED: u8 = 0x04;
127}
128
129#[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
203pub 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
216pub 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
228pub 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
239pub 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 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
275pub 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
288pub 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
301pub 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
322pub fn decode_lineup(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<LineupInfo>> {
324 if let tlv::TlvItemValue::List(_fields) = inp {
325 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 {
337 Ok(None)
338 }
340}
341
342pub fn decode_current_channel(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<ChannelInfo>> {
344 if let tlv::TlvItemValue::List(_fields) = inp {
345 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 {
360 Ok(None)
361 }
363}
364
365
366pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
378 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
406pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
411 vec![
412 (0x0000, "ChannelList"),
413 (0x0001, "Lineup"),
414 (0x0002, "CurrentChannel"),
415 ]
416}
417
418pub 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
521pub 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
536pub 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
695pub 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
703pub 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
709pub 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
715pub 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
721pub 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
727pub 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
733pub 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
739pub 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
745pub 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