#![allow(clippy::too_many_arguments)]
use crate::tlv;
use anyhow;
use serde_json;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum ChannelType {
Satellite = 0,
Cable = 1,
Terrestrial = 2,
Ott = 3,
}
impl ChannelType {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(ChannelType::Satellite),
1 => Some(ChannelType::Cable),
2 => Some(ChannelType::Terrestrial),
3 => Some(ChannelType::Ott),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<ChannelType> for u8 {
fn from(val: ChannelType) -> Self {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum LineupInfoType {
Mso = 0,
}
impl LineupInfoType {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(LineupInfoType::Mso),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<LineupInfoType> for u8 {
fn from(val: LineupInfoType) -> Self {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum Status {
Success = 0,
Multiplematches = 1,
Nomatches = 2,
}
impl Status {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(Status::Success),
1 => Some(Status::Multiplematches),
2 => Some(Status::Nomatches),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<Status> for u8 {
fn from(val: Status) -> Self {
val as u8
}
}
pub type RecordingFlag = u8;
pub mod recordingflag {
pub const SCHEDULED: u8 = 0x01;
pub const RECORD_SERIES: u8 = 0x02;
pub const RECORDED: u8 = 0x04;
}
#[derive(Debug, serde::Serialize)]
pub struct ChannelInfo {
pub major_number: Option<u16>,
pub minor_number: Option<u16>,
pub name: Option<String>,
pub call_sign: Option<String>,
pub affiliate_call_sign: Option<String>,
pub identifier: Option<String>,
pub type_: Option<ChannelType>,
}
#[derive(Debug, serde::Serialize)]
pub struct ChannelPaging {
pub previous_token: Option<PageToken>,
pub next_token: Option<PageToken>,
}
#[derive(Debug, serde::Serialize)]
pub struct LineupInfo {
pub operator_name: Option<String>,
pub lineup_name: Option<String>,
pub postal_code: Option<String>,
pub lineup_info_type: Option<LineupInfoType>,
}
#[derive(Debug, serde::Serialize)]
pub struct PageToken {
pub limit: Option<u16>,
pub after: Option<String>,
pub before: Option<String>,
}
#[derive(Debug, serde::Serialize)]
pub struct ProgramCast {
pub name: Option<String>,
pub role: Option<String>,
}
#[derive(Debug, serde::Serialize)]
pub struct ProgramCategory {
pub category: Option<String>,
pub sub_category: Option<String>,
}
#[derive(Debug, serde::Serialize)]
pub struct Program {
pub identifier: Option<String>,
pub channel: Option<ChannelInfo>,
pub start_time: Option<u64>,
pub end_time: Option<u64>,
pub title: Option<String>,
pub subtitle: Option<String>,
pub description: Option<String>,
pub audio_languages: Option<Vec<String>>,
pub ratings: Option<Vec<String>>,
pub thumbnail_url: Option<String>,
pub poster_art_url: Option<String>,
pub dvbi_url: Option<String>,
pub release_date: Option<String>,
pub parental_guidance_text: Option<String>,
pub recording_flag: Option<RecordingFlag>,
pub series_info: Option<SeriesInfo>,
pub category_list: Option<Vec<ProgramCategory>>,
pub cast_list: Option<Vec<ProgramCast>>,
}
#[derive(Debug, serde::Serialize)]
pub struct SeriesInfo {
pub season: Option<String>,
pub episode: Option<String>,
}
pub fn encode_change_channel(match_: String) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::String(match_)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_change_channel_by_number(major_number: u16, minor_number: u16) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt16(major_number)).into(),
(1, tlv::TlvItemValueEnc::UInt16(minor_number)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_skip_channel(count: i16) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::Int16(count)).into(),
]),
};
Ok(tlv.encode()?)
}
pub 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>> {
let page_token_enc = if let Some(s) = page_token {
let mut fields = Vec::new();
if let Some(x) = s.limit { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
if let Some(x) = s.after { fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
if let Some(x) = s.before { fields.push((2, tlv::TlvItemValueEnc::String(x.clone())).into()); }
tlv::TlvItemValueEnc::StructInvisible(fields)
} else {
tlv::TlvItemValueEnc::StructInvisible(Vec::new())
};
let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
tlv_fields.push((0, tlv::TlvItemValueEnc::UInt64(start_time)).into());
tlv_fields.push((1, tlv::TlvItemValueEnc::UInt64(end_time)).into());
if let Some(channel_list) = channel_list {
tlv_fields.push((2, tlv::TlvItemValueEnc::Array(channel_list.into_iter().map(|v| {
let mut fields = Vec::new();
if let Some(x) = v.major_number { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
if let Some(x) = v.minor_number { fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
if let Some(x) = v.name { fields.push((2, tlv::TlvItemValueEnc::String(x.clone())).into()); }
if let Some(x) = v.call_sign { fields.push((3, tlv::TlvItemValueEnc::String(x.clone())).into()); }
if let Some(x) = v.affiliate_call_sign { fields.push((4, tlv::TlvItemValueEnc::String(x.clone())).into()); }
if let Some(x) = v.identifier { fields.push((5, tlv::TlvItemValueEnc::String(x.clone())).into()); }
if let Some(x) = v.type_ { fields.push((6, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
(0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
}).collect())).into());
}
tlv_fields.push((3, page_token_enc).into());
tlv_fields.push((5, tlv::TlvItemValueEnc::UInt8(recording_flag.unwrap_or_default())).into());
if let Some(x) = data { tlv_fields.push((7, tlv::TlvItemValueEnc::OctetString(x)).into()); }
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
};
Ok(tlv.encode()?)
}
pub fn encode_record_program(program_identifier: String, should_record_series: bool, data: Option<Vec<u8>>) -> anyhow::Result<Vec<u8>> {
let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
tlv_fields.push((0, tlv::TlvItemValueEnc::String(program_identifier)).into());
tlv_fields.push((1, tlv::TlvItemValueEnc::Bool(should_record_series)).into());
if let Some(x) = data { tlv_fields.push((3, tlv::TlvItemValueEnc::OctetString(x)).into()); }
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
};
Ok(tlv.encode()?)
}
pub fn encode_cancel_record_program(program_identifier: String, should_record_series: bool, data: Option<Vec<u8>>) -> anyhow::Result<Vec<u8>> {
let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
tlv_fields.push((0, tlv::TlvItemValueEnc::String(program_identifier)).into());
tlv_fields.push((1, tlv::TlvItemValueEnc::Bool(should_record_series)).into());
if let Some(x) = data { tlv_fields.push((3, tlv::TlvItemValueEnc::OctetString(x)).into()); }
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
};
Ok(tlv.encode()?)
}
pub fn decode_channel_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<ChannelInfo>> {
let mut res = Vec::new();
if let tlv::TlvItemValue::List(v) = inp {
for item in v {
res.push(ChannelInfo {
major_number: item.get_int(&[0]).map(|v| v as u16),
minor_number: item.get_int(&[1]).map(|v| v as u16),
name: item.get_string_owned(&[2]),
call_sign: item.get_string_owned(&[3]),
affiliate_call_sign: item.get_string_owned(&[4]),
identifier: item.get_string_owned(&[5]),
type_: item.get_int(&[6]).and_then(|v| ChannelType::from_u8(v as u8)),
});
}
}
Ok(res)
}
pub fn decode_lineup(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<LineupInfo>> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(Some(LineupInfo {
operator_name: item.get_string_owned(&[0]),
lineup_name: item.get_string_owned(&[1]),
postal_code: item.get_string_owned(&[2]),
lineup_info_type: item.get_int(&[3]).and_then(|v| LineupInfoType::from_u8(v as u8)),
}))
} else {
Ok(None)
}
}
pub fn decode_current_channel(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<ChannelInfo>> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(Some(ChannelInfo {
major_number: item.get_int(&[0]).map(|v| v as u16),
minor_number: item.get_int(&[1]).map(|v| v as u16),
name: item.get_string_owned(&[2]),
call_sign: item.get_string_owned(&[3]),
affiliate_call_sign: item.get_string_owned(&[4]),
identifier: item.get_string_owned(&[5]),
type_: item.get_int(&[6]).and_then(|v| ChannelType::from_u8(v as u8)),
}))
} else {
Ok(None)
}
}
pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
if cluster_id != 0x0504 {
return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0504, got {}\"}}", cluster_id);
}
match attribute_id {
0x0000 => {
match decode_channel_list(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0001 => {
match decode_lineup(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0002 => {
match decode_current_channel(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
_ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
}
}
pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
vec![
(0x0000, "ChannelList"),
(0x0001, "Lineup"),
(0x0002, "CurrentChannel"),
]
}
pub fn get_command_list() -> Vec<(u32, &'static str)> {
vec![
(0x00, "ChangeChannel"),
(0x02, "ChangeChannelByNumber"),
(0x03, "SkipChannel"),
(0x04, "GetProgramGuide"),
(0x06, "RecordProgram"),
(0x07, "CancelRecordProgram"),
]
}
pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
match cmd_id {
0x00 => Some("ChangeChannel"),
0x02 => Some("ChangeChannelByNumber"),
0x03 => Some("SkipChannel"),
0x04 => Some("GetProgramGuide"),
0x06 => Some("RecordProgram"),
0x07 => Some("CancelRecordProgram"),
_ => None,
}
}
pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
match cmd_id {
0x00 => Some(vec![
crate::clusters::codec::CommandField { tag: 0, name: "match_", kind: crate::clusters::codec::FieldKind::String, optional: false, nullable: false },
]),
0x02 => Some(vec![
crate::clusters::codec::CommandField { tag: 0, name: "major_number", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
crate::clusters::codec::CommandField { tag: 1, name: "minor_number", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
]),
0x03 => Some(vec![
crate::clusters::codec::CommandField { tag: 0, name: "count", kind: crate::clusters::codec::FieldKind::I16, optional: false, nullable: false },
]),
0x04 => Some(vec![
crate::clusters::codec::CommandField { tag: 0, name: "start_time", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: false },
crate::clusters::codec::CommandField { tag: 1, name: "end_time", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: false },
crate::clusters::codec::CommandField { tag: 2, name: "channel_list", kind: crate::clusters::codec::FieldKind::List { entry_type: "ChannelInfoStruct" }, optional: true, nullable: false },
crate::clusters::codec::CommandField { tag: 3, name: "page_token", kind: crate::clusters::codec::FieldKind::Struct { name: "PageTokenStruct" }, optional: true, nullable: true },
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 },
crate::clusters::codec::CommandField { tag: 7, name: "data", kind: crate::clusters::codec::FieldKind::OctetString, optional: true, nullable: false },
]),
0x06 => Some(vec![
crate::clusters::codec::CommandField { tag: 0, name: "program_identifier", kind: crate::clusters::codec::FieldKind::String, optional: false, nullable: false },
crate::clusters::codec::CommandField { tag: 1, name: "should_record_series", kind: crate::clusters::codec::FieldKind::Bool, optional: false, nullable: false },
crate::clusters::codec::CommandField { tag: 3, name: "data", kind: crate::clusters::codec::FieldKind::OctetString, optional: true, nullable: false },
]),
0x07 => Some(vec![
crate::clusters::codec::CommandField { tag: 0, name: "program_identifier", kind: crate::clusters::codec::FieldKind::String, optional: false, nullable: false },
crate::clusters::codec::CommandField { tag: 1, name: "should_record_series", kind: crate::clusters::codec::FieldKind::Bool, optional: false, nullable: false },
crate::clusters::codec::CommandField { tag: 3, name: "data", kind: crate::clusters::codec::FieldKind::OctetString, optional: true, nullable: false },
]),
_ => None,
}
}
pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
match cmd_id {
0x00 => {
let match_ = crate::clusters::codec::json_util::get_string(args, "match_")?;
encode_change_channel(match_)
}
0x02 => {
let major_number = crate::clusters::codec::json_util::get_u16(args, "major_number")?;
let minor_number = crate::clusters::codec::json_util::get_u16(args, "minor_number")?;
encode_change_channel_by_number(major_number, minor_number)
}
0x03 => {
let count = crate::clusters::codec::json_util::get_i16(args, "count")?;
encode_skip_channel(count)
}
0x04 => Err(anyhow::anyhow!("command \"GetProgramGuide\" has complex args: use raw mode")),
0x06 => {
let program_identifier = crate::clusters::codec::json_util::get_string(args, "program_identifier")?;
let should_record_series = crate::clusters::codec::json_util::get_bool(args, "should_record_series")?;
let data = crate::clusters::codec::json_util::get_opt_octstr(args, "data")?;
encode_record_program(program_identifier, should_record_series, data)
}
0x07 => {
let program_identifier = crate::clusters::codec::json_util::get_string(args, "program_identifier")?;
let should_record_series = crate::clusters::codec::json_util::get_bool(args, "should_record_series")?;
let data = crate::clusters::codec::json_util::get_opt_octstr(args, "data")?;
encode_cancel_record_program(program_identifier, should_record_series, data)
}
_ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
}
}
#[derive(Debug, serde::Serialize)]
pub struct ChangeChannelResponse {
pub status: Option<Status>,
pub data: Option<String>,
}
#[derive(Debug, serde::Serialize)]
pub struct ProgramGuideResponse {
pub paging: Option<ChannelPaging>,
pub program_list: Option<Vec<Program>>,
}
pub fn decode_change_channel_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ChangeChannelResponse> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(ChangeChannelResponse {
status: item.get_int(&[0]).and_then(|v| Status::from_u8(v as u8)),
data: item.get_string_owned(&[1]),
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}
pub fn decode_program_guide_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ProgramGuideResponse> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(ProgramGuideResponse {
paging: {
if let Some(nested_tlv) = item.get(&[0]) {
if let tlv::TlvItemValue::List(_) = nested_tlv {
let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
Some(ChannelPaging {
previous_token: {
if let Some(nested_tlv) = nested_item.get(&[0]) {
if let tlv::TlvItemValue::List(_) = nested_tlv {
let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
Some(PageToken {
limit: nested_item.get_int(&[0]).map(|v| v as u16),
after: nested_item.get_string_owned(&[1]),
before: nested_item.get_string_owned(&[2]),
})
} else {
None
}
} else {
None
}
},
next_token: {
if let Some(nested_tlv) = nested_item.get(&[1]) {
if let tlv::TlvItemValue::List(_) = nested_tlv {
let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
Some(PageToken {
limit: nested_item.get_int(&[0]).map(|v| v as u16),
after: nested_item.get_string_owned(&[1]),
before: nested_item.get_string_owned(&[2]),
})
} else {
None
}
} else {
None
}
},
})
} else {
None
}
} else {
None
}
},
program_list: {
if let Some(tlv::TlvItemValue::List(l)) = item.get(&[1]) {
let mut items = Vec::new();
for list_item in l {
items.push(Program {
identifier: list_item.get_string_owned(&[0]),
channel: {
if let Some(nested_tlv) = list_item.get(&[1]) {
if let tlv::TlvItemValue::List(_) = nested_tlv {
let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
Some(ChannelInfo {
major_number: nested_item.get_int(&[0]).map(|v| v as u16),
minor_number: nested_item.get_int(&[1]).map(|v| v as u16),
name: nested_item.get_string_owned(&[2]),
call_sign: nested_item.get_string_owned(&[3]),
affiliate_call_sign: nested_item.get_string_owned(&[4]),
identifier: nested_item.get_string_owned(&[5]),
type_: nested_item.get_int(&[6]).and_then(|v| ChannelType::from_u8(v as u8)),
})
} else {
None
}
} else {
None
}
},
start_time: list_item.get_int(&[2]),
end_time: list_item.get_int(&[3]),
title: list_item.get_string_owned(&[4]),
subtitle: list_item.get_string_owned(&[5]),
description: list_item.get_string_owned(&[6]),
audio_languages: {
if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[7]) {
let items: Vec<String> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::String(v) = &e.value { Some(v.clone()) } else { None } }).collect();
Some(items)
} else {
None
}
},
ratings: {
if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[8]) {
let items: Vec<String> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::String(v) = &e.value { Some(v.clone()) } else { None } }).collect();
Some(items)
} else {
None
}
},
thumbnail_url: list_item.get_string_owned(&[9]),
poster_art_url: list_item.get_string_owned(&[10]),
dvbi_url: list_item.get_string_owned(&[11]),
release_date: list_item.get_string_owned(&[12]),
parental_guidance_text: list_item.get_string_owned(&[13]),
recording_flag: list_item.get_int(&[14]).map(|v| v as u8),
series_info: {
if let Some(nested_tlv) = list_item.get(&[15]) {
if let tlv::TlvItemValue::List(_) = nested_tlv {
let nested_item = tlv::TlvItem { tag: 15, value: nested_tlv.clone() };
Some(SeriesInfo {
season: nested_item.get_string_owned(&[0]),
episode: nested_item.get_string_owned(&[1]),
})
} else {
None
}
} else {
None
}
},
category_list: {
if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[16]) {
let mut items = Vec::new();
for list_item in l {
items.push(ProgramCategory {
category: list_item.get_string_owned(&[0]),
sub_category: list_item.get_string_owned(&[1]),
});
}
Some(items)
} else {
None
}
},
cast_list: {
if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[17]) {
let mut items = Vec::new();
for list_item in l {
items.push(ProgramCast {
name: list_item.get_string_owned(&[0]),
role: list_item.get_string_owned(&[1]),
});
}
Some(items)
} else {
None
}
},
});
}
Some(items)
} else {
None
}
},
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}
pub async fn change_channel(conn: &crate::controller::Connection, endpoint: u16, match_: String) -> anyhow::Result<ChangeChannelResponse> {
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?;
decode_change_channel_response(&tlv)
}
pub async fn change_channel_by_number(conn: &crate::controller::Connection, endpoint: u16, major_number: u16, minor_number: u16) -> anyhow::Result<()> {
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?;
Ok(())
}
pub async fn skip_channel(conn: &crate::controller::Connection, endpoint: u16, count: i16) -> anyhow::Result<()> {
conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_CHANNEL, crate::clusters::defs::CLUSTER_CHANNEL_CMD_ID_SKIPCHANNEL, &encode_skip_channel(count)?).await?;
Ok(())
}
pub 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> {
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?;
decode_program_guide_response(&tlv)
}
pub async fn record_program(conn: &crate::controller::Connection, endpoint: u16, program_identifier: String, should_record_series: bool, data: Option<Vec<u8>>) -> anyhow::Result<()> {
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?;
Ok(())
}
pub async fn cancel_record_program(conn: &crate::controller::Connection, endpoint: u16, program_identifier: String, should_record_series: bool, data: Option<Vec<u8>>) -> anyhow::Result<()> {
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?;
Ok(())
}
pub async fn read_channel_list(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<ChannelInfo>> {
let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CHANNEL, crate::clusters::defs::CLUSTER_CHANNEL_ATTR_ID_CHANNELLIST).await?;
decode_channel_list(&tlv)
}
pub async fn read_lineup(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<LineupInfo>> {
let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CHANNEL, crate::clusters::defs::CLUSTER_CHANNEL_ATTR_ID_LINEUP).await?;
decode_lineup(&tlv)
}
pub async fn read_current_channel(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<ChannelInfo>> {
let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_CHANNEL, crate::clusters::defs::CLUSTER_CHANNEL_ATTR_ID_CURRENTCHANNEL).await?;
decode_current_channel(&tlv)
}