1use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11#[derive(Debug, serde::Serialize)]
14pub struct ChannelInfo {
15 pub major_number: Option<u16>,
16 pub minor_number: Option<u16>,
17 pub name: Option<String>,
18 pub call_sign: Option<String>,
19 pub affiliate_call_sign: Option<String>,
20 pub identifier: Option<String>,
21 pub type_: Option<u8>,
22}
23
24#[derive(Debug, serde::Serialize)]
25pub struct ChannelPaging {
26 pub previous_token: Option<PageToken>,
27 pub next_token: Option<PageToken>,
28}
29
30#[derive(Debug, serde::Serialize)]
31pub struct LineupInfo {
32 pub operator_name: Option<String>,
33 pub lineup_name: Option<String>,
34 pub postal_code: Option<String>,
35 pub lineup_info_type: Option<u8>,
36}
37
38#[derive(Debug, serde::Serialize)]
39pub struct PageToken {
40 pub limit: Option<u16>,
41 pub after: Option<String>,
42 pub before: Option<String>,
43}
44
45#[derive(Debug, serde::Serialize)]
46pub struct ProgramCast {
47 pub name: Option<String>,
48 pub role: Option<String>,
49}
50
51#[derive(Debug, serde::Serialize)]
52pub struct ProgramCategory {
53 pub category: Option<String>,
54 pub sub_category: Option<String>,
55}
56
57#[derive(Debug, serde::Serialize)]
58pub struct Program {
59 pub identifier: Option<String>,
60 pub channel: Option<ChannelInfo>,
61 pub start_time: Option<u64>,
62 pub end_time: Option<u64>,
63 pub title: Option<String>,
64 pub subtitle: Option<String>,
65 pub description: Option<String>,
66 pub audio_languages: Option<Vec<String>>,
67 pub ratings: Option<Vec<String>>,
68 pub thumbnail_url: Option<String>,
69 pub poster_art_url: Option<String>,
70 pub dvbi_url: Option<String>,
71 pub release_date: Option<String>,
72 pub parental_guidance_text: Option<String>,
73 pub recording_flag: Option<u8>,
74 pub series_info: Option<SeriesInfo>,
75 pub category_list: Option<Vec<ProgramCategory>>,
76 pub cast_list: Option<Vec<ProgramCast>>,
77}
78
79#[derive(Debug, serde::Serialize)]
80pub struct SeriesInfo {
81 pub season: Option<String>,
82 pub episode: Option<String>,
83}
84
85pub fn encode_change_channel(match_: String) -> anyhow::Result<Vec<u8>> {
89 let tlv = tlv::TlvItemEnc {
90 tag: 0,
91 value: tlv::TlvItemValueEnc::StructInvisible(vec![
92 (0, tlv::TlvItemValueEnc::String(match_)).into(),
93 ]),
94 };
95 Ok(tlv.encode()?)
96}
97
98pub fn encode_change_channel_by_number(major_number: u16, minor_number: u16) -> anyhow::Result<Vec<u8>> {
100 let tlv = tlv::TlvItemEnc {
101 tag: 0,
102 value: tlv::TlvItemValueEnc::StructInvisible(vec![
103 (0, tlv::TlvItemValueEnc::UInt16(major_number)).into(),
104 (1, tlv::TlvItemValueEnc::UInt16(minor_number)).into(),
105 ]),
106 };
107 Ok(tlv.encode()?)
108}
109
110pub fn encode_skip_channel(count: i16) -> anyhow::Result<Vec<u8>> {
112 let tlv = tlv::TlvItemEnc {
113 tag: 0,
114 value: tlv::TlvItemValueEnc::StructInvisible(vec![
115 (0, tlv::TlvItemValueEnc::Int16(count)).into(),
116 ]),
117 };
118 Ok(tlv.encode()?)
119}
120
121pub fn encode_get_program_guide(start_time: u64, end_time: u64, channel_list: Vec<u8>, page_token: Option<u8>, recording_flag: Option<u8>, external_id_list: Vec<u8>, data: Vec<u8>) -> anyhow::Result<Vec<u8>> {
123 let tlv = tlv::TlvItemEnc {
124 tag: 0,
125 value: tlv::TlvItemValueEnc::StructInvisible(vec![
126 (0, tlv::TlvItemValueEnc::UInt64(start_time)).into(),
127 (1, tlv::TlvItemValueEnc::UInt64(end_time)).into(),
128 (2, tlv::TlvItemValueEnc::StructAnon(channel_list.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt8(v)).into()).collect())).into(),
129 (3, tlv::TlvItemValueEnc::UInt8(page_token.unwrap_or_default())).into(),
130 (5, tlv::TlvItemValueEnc::UInt8(recording_flag.unwrap_or_default())).into(),
131 (6, tlv::TlvItemValueEnc::StructAnon(external_id_list.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt8(v)).into()).collect())).into(),
132 (7, tlv::TlvItemValueEnc::OctetString(data)).into(),
133 ]),
134 };
135 Ok(tlv.encode()?)
136}
137
138pub fn encode_record_program(program_identifier: String, should_record_series: bool, external_id_list: Vec<u8>, data: Vec<u8>) -> anyhow::Result<Vec<u8>> {
140 let tlv = tlv::TlvItemEnc {
141 tag: 0,
142 value: tlv::TlvItemValueEnc::StructInvisible(vec![
143 (0, tlv::TlvItemValueEnc::String(program_identifier)).into(),
144 (1, tlv::TlvItemValueEnc::Bool(should_record_series)).into(),
145 (2, tlv::TlvItemValueEnc::StructAnon(external_id_list.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt8(v)).into()).collect())).into(),
146 (3, tlv::TlvItemValueEnc::OctetString(data)).into(),
147 ]),
148 };
149 Ok(tlv.encode()?)
150}
151
152pub fn encode_cancel_record_program(program_identifier: String, should_record_series: bool, external_id_list: Vec<u8>, data: Vec<u8>) -> anyhow::Result<Vec<u8>> {
154 let tlv = tlv::TlvItemEnc {
155 tag: 0,
156 value: tlv::TlvItemValueEnc::StructInvisible(vec![
157 (0, tlv::TlvItemValueEnc::String(program_identifier)).into(),
158 (1, tlv::TlvItemValueEnc::Bool(should_record_series)).into(),
159 (2, tlv::TlvItemValueEnc::StructAnon(external_id_list.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt8(v)).into()).collect())).into(),
160 (3, tlv::TlvItemValueEnc::OctetString(data)).into(),
161 ]),
162 };
163 Ok(tlv.encode()?)
164}
165
166pub fn decode_channel_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<ChannelInfo>> {
170 let mut res = Vec::new();
171 if let tlv::TlvItemValue::List(v) = inp {
172 for item in v {
173 res.push(ChannelInfo {
174 major_number: item.get_int(&[0]).map(|v| v as u16),
175 minor_number: item.get_int(&[1]).map(|v| v as u16),
176 name: item.get_string_owned(&[2]),
177 call_sign: item.get_string_owned(&[3]),
178 affiliate_call_sign: item.get_string_owned(&[4]),
179 identifier: item.get_string_owned(&[5]),
180 type_: item.get_int(&[6]).map(|v| v as u8),
181 });
182 }
183 }
184 Ok(res)
185}
186
187pub fn decode_lineup(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<LineupInfo>> {
189 if let tlv::TlvItemValue::List(_fields) = inp {
190 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
192 Ok(Some(LineupInfo {
193 operator_name: item.get_string_owned(&[0]),
194 lineup_name: item.get_string_owned(&[1]),
195 postal_code: item.get_string_owned(&[2]),
196 lineup_info_type: item.get_int(&[3]).map(|v| v as u8),
197 }))
198 } else {
202 Ok(None)
203 }
205}
206
207pub fn decode_current_channel(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<ChannelInfo>> {
209 if let tlv::TlvItemValue::List(_fields) = inp {
210 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
212 Ok(Some(ChannelInfo {
213 major_number: item.get_int(&[0]).map(|v| v as u16),
214 minor_number: item.get_int(&[1]).map(|v| v as u16),
215 name: item.get_string_owned(&[2]),
216 call_sign: item.get_string_owned(&[3]),
217 affiliate_call_sign: item.get_string_owned(&[4]),
218 identifier: item.get_string_owned(&[5]),
219 type_: item.get_int(&[6]).map(|v| v as u8),
220 }))
221 } else {
225 Ok(None)
226 }
228}
229
230
231pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
243 if cluster_id != 0x0504 {
245 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0504, got {}\"}}", cluster_id);
246 }
247
248 match attribute_id {
249 0x0000 => {
250 match decode_channel_list(tlv_value) {
251 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
252 Err(e) => format!("{{\"error\": \"{}\"}}", e),
253 }
254 }
255 0x0001 => {
256 match decode_lineup(tlv_value) {
257 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
258 Err(e) => format!("{{\"error\": \"{}\"}}", e),
259 }
260 }
261 0x0002 => {
262 match decode_current_channel(tlv_value) {
263 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
264 Err(e) => format!("{{\"error\": \"{}\"}}", e),
265 }
266 }
267 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
268 }
269}
270
271pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
276 vec![
277 (0x0000, "ChannelList"),
278 (0x0001, "Lineup"),
279 (0x0002, "CurrentChannel"),
280 ]
281}
282