1#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
19#[repr(u8)]
20pub enum Status {
21 Success = 0,
23 Appnotavailable = 1,
25 Systembusy = 2,
27 Pendinguserapproval = 3,
29 Downloading = 4,
31 Installing = 5,
33}
34
35impl Status {
36 pub fn from_u8(value: u8) -> Option<Self> {
38 match value {
39 0 => Some(Status::Success),
40 1 => Some(Status::Appnotavailable),
41 2 => Some(Status::Systembusy),
42 3 => Some(Status::Pendinguserapproval),
43 4 => Some(Status::Downloading),
44 5 => Some(Status::Installing),
45 _ => None,
46 }
47 }
48
49 pub fn to_u8(self) -> u8 {
51 self as u8
52 }
53}
54
55impl From<Status> for u8 {
56 fn from(val: Status) -> Self {
57 val as u8
58 }
59}
60
61#[derive(Debug, serde::Serialize)]
64pub struct ApplicationEP {
65 pub application: Option<Application>,
66 pub endpoint: Option<u16>,
67}
68
69#[derive(Debug, serde::Serialize)]
70pub struct Application {
71 pub catalog_vendor_id: Option<u16>,
72 pub application_id: Option<String>,
73}
74
75pub fn encode_launch_app(application: Application, data: Vec<u8>) -> anyhow::Result<Vec<u8>> {
79 let mut application_fields = Vec::new();
81 if let Some(x) = application.catalog_vendor_id { application_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
82 if let Some(x) = application.application_id { application_fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
83 let tlv = tlv::TlvItemEnc {
84 tag: 0,
85 value: tlv::TlvItemValueEnc::StructInvisible(vec![
86 (0, tlv::TlvItemValueEnc::StructInvisible(application_fields)).into(),
87 (1, tlv::TlvItemValueEnc::OctetString(data)).into(),
88 ]),
89 };
90 Ok(tlv.encode()?)
91}
92
93pub fn encode_stop_app(application: Application) -> anyhow::Result<Vec<u8>> {
95 let mut application_fields = Vec::new();
97 if let Some(x) = application.catalog_vendor_id { application_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
98 if let Some(x) = application.application_id { application_fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
99 let tlv = tlv::TlvItemEnc {
100 tag: 0,
101 value: tlv::TlvItemValueEnc::StructInvisible(vec![
102 (0, tlv::TlvItemValueEnc::StructInvisible(application_fields)).into(),
103 ]),
104 };
105 Ok(tlv.encode()?)
106}
107
108pub fn encode_hide_app(application: Application) -> anyhow::Result<Vec<u8>> {
110 let mut application_fields = Vec::new();
112 if let Some(x) = application.catalog_vendor_id { application_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
113 if let Some(x) = application.application_id { application_fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
114 let tlv = tlv::TlvItemEnc {
115 tag: 0,
116 value: tlv::TlvItemValueEnc::StructInvisible(vec![
117 (0, tlv::TlvItemValueEnc::StructInvisible(application_fields)).into(),
118 ]),
119 };
120 Ok(tlv.encode()?)
121}
122
123pub fn decode_catalog_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<u16>> {
127 let mut res = Vec::new();
128 if let tlv::TlvItemValue::List(v) = inp {
129 for item in v {
130 if let tlv::TlvItemValue::Int(i) = &item.value {
131 res.push(*i as u16);
132 }
133 }
134 }
135 Ok(res)
136}
137
138pub fn decode_current_app(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<ApplicationEP>> {
140 if let tlv::TlvItemValue::List(_fields) = inp {
141 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
143 Ok(Some(ApplicationEP {
144 application: {
145 if let Some(nested_tlv) = item.get(&[0]) {
146 if let tlv::TlvItemValue::List(_) = nested_tlv {
147 let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
148 Some(Application {
149 catalog_vendor_id: nested_item.get_int(&[0]).map(|v| v as u16),
150 application_id: nested_item.get_string_owned(&[1]),
151 })
152 } else {
153 None
154 }
155 } else {
156 None
157 }
158 },
159 endpoint: item.get_int(&[1]).map(|v| v as u16),
160 }))
161 } else {
165 Ok(None)
166 }
168}
169
170
171pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
183 if cluster_id != 0x050C {
185 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x050C, got {}\"}}", cluster_id);
186 }
187
188 match attribute_id {
189 0x0000 => {
190 match decode_catalog_list(tlv_value) {
191 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
192 Err(e) => format!("{{\"error\": \"{}\"}}", e),
193 }
194 }
195 0x0001 => {
196 match decode_current_app(tlv_value) {
197 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
198 Err(e) => format!("{{\"error\": \"{}\"}}", e),
199 }
200 }
201 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
202 }
203}
204
205pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
210 vec![
211 (0x0000, "CatalogList"),
212 (0x0001, "CurrentApp"),
213 ]
214}
215
216pub fn get_command_list() -> Vec<(u32, &'static str)> {
219 vec![
220 (0x00, "LaunchApp"),
221 (0x01, "StopApp"),
222 (0x02, "HideApp"),
223 ]
224}
225
226pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
227 match cmd_id {
228 0x00 => Some("LaunchApp"),
229 0x01 => Some("StopApp"),
230 0x02 => Some("HideApp"),
231 _ => None,
232 }
233}
234
235pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
236 match cmd_id {
237 0x00 => Some(vec![
238 crate::clusters::codec::CommandField { tag: 0, name: "application", kind: crate::clusters::codec::FieldKind::Struct { name: "ApplicationStruct" }, optional: false, nullable: false },
239 crate::clusters::codec::CommandField { tag: 1, name: "data", kind: crate::clusters::codec::FieldKind::OctetString, optional: true, nullable: false },
240 ]),
241 0x01 => Some(vec![
242 crate::clusters::codec::CommandField { tag: 0, name: "application", kind: crate::clusters::codec::FieldKind::Struct { name: "ApplicationStruct" }, optional: false, nullable: false },
243 ]),
244 0x02 => Some(vec![
245 crate::clusters::codec::CommandField { tag: 0, name: "application", kind: crate::clusters::codec::FieldKind::Struct { name: "ApplicationStruct" }, optional: false, nullable: false },
246 ]),
247 _ => None,
248 }
249}
250
251pub fn encode_command_json(cmd_id: u32, _args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
252 match cmd_id {
253 0x00 => Err(anyhow::anyhow!("command \"LaunchApp\" has complex args: use raw mode")),
254 0x01 => Err(anyhow::anyhow!("command \"StopApp\" has complex args: use raw mode")),
255 0x02 => Err(anyhow::anyhow!("command \"HideApp\" has complex args: use raw mode")),
256 _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
257 }
258}
259
260#[derive(Debug, serde::Serialize)]
261pub struct LauncherResponse {
262 pub status: Option<Status>,
263 #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
264 pub data: Option<Vec<u8>>,
265}
266
267pub fn decode_launcher_response(inp: &tlv::TlvItemValue) -> anyhow::Result<LauncherResponse> {
271 if let tlv::TlvItemValue::List(_fields) = inp {
272 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
273 Ok(LauncherResponse {
274 status: item.get_int(&[0]).and_then(|v| Status::from_u8(v as u8)),
275 data: item.get_octet_string_owned(&[1]),
276 })
277 } else {
278 Err(anyhow::anyhow!("Expected struct fields"))
279 }
280}
281
282pub async fn launch_app(conn: &crate::controller::Connection, endpoint: u16, application: Application, data: Vec<u8>) -> anyhow::Result<LauncherResponse> {
286 let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_APPLICATION_LAUNCHER, crate::clusters::defs::CLUSTER_APPLICATION_LAUNCHER_CMD_ID_LAUNCHAPP, &encode_launch_app(application, data)?).await?;
287 decode_launcher_response(&tlv)
288}
289
290pub async fn stop_app(conn: &crate::controller::Connection, endpoint: u16, application: Application) -> anyhow::Result<LauncherResponse> {
292 let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_APPLICATION_LAUNCHER, crate::clusters::defs::CLUSTER_APPLICATION_LAUNCHER_CMD_ID_STOPAPP, &encode_stop_app(application)?).await?;
293 decode_launcher_response(&tlv)
294}
295
296pub async fn hide_app(conn: &crate::controller::Connection, endpoint: u16, application: Application) -> anyhow::Result<LauncherResponse> {
298 let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_APPLICATION_LAUNCHER, crate::clusters::defs::CLUSTER_APPLICATION_LAUNCHER_CMD_ID_HIDEAPP, &encode_hide_app(application)?).await?;
299 decode_launcher_response(&tlv)
300}
301
302pub async fn read_catalog_list(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<u16>> {
304 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_APPLICATION_LAUNCHER, crate::clusters::defs::CLUSTER_APPLICATION_LAUNCHER_ATTR_ID_CATALOGLIST).await?;
305 decode_catalog_list(&tlv)
306}
307
308pub async fn read_current_app(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<ApplicationEP>> {
310 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_APPLICATION_LAUNCHER, crate::clusters::defs::CLUSTER_APPLICATION_LAUNCHER_ATTR_ID_CURRENTAPP).await?;
311 decode_current_app(&tlv)
312}
313