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: Option<Vec<ChannelInfo>>, page_token: Option<PageToken>, recording_flag: Option<RecordingFlag>, data: Option<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 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
277pub 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
290pub 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
303pub 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
324pub fn decode_lineup(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<LineupInfo>> {
326 if let tlv::TlvItemValue::List(_fields) = inp {
327 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 {
339 Ok(None)
340 }
342}
343
344pub fn decode_current_channel(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<ChannelInfo>> {
346 if let tlv::TlvItemValue::List(_fields) = inp {
347 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 {
362 Ok(None)
363 }
365}
366
367
368pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
380 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
408pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
413 vec![
414 (0x0000, "ChannelList"),
415 (0x0001, "Lineup"),
416 (0x0002, "CurrentChannel"),
417 ]
418}
419
420pub 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
523pub 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
538pub 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
697pub 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
705pub 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
711pub 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
717pub 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
723pub 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
729pub 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
735pub 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
741pub 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
747pub 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