matc/clusters/codec/
application_launcher.rs

1//! Matter TLV encoders and decoders for Application Launcher Cluster
2//! Cluster ID: 0x050C
3//!
4//! This file is automatically generated from ApplicationLauncher.xml
5
6#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13// Import serialization helpers for octet strings
14use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
15
16// Enum definitions
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
19#[repr(u8)]
20pub enum Status {
21    /// Command succeeded
22    Success = 0,
23    /// Requested app is not available
24    Appnotavailable = 1,
25    /// Video platform unable to honor command
26    Systembusy = 2,
27    /// User approval for app download is pending
28    Pendinguserapproval = 3,
29    /// Downloading the requested app
30    Downloading = 4,
31    /// Installing the requested app
32    Installing = 5,
33}
34
35impl Status {
36    /// Convert from u8 value
37    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    /// Convert to u8 value
50    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// Struct definitions
62
63#[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
75// Command encoders
76
77/// Encode LaunchApp command (0x00)
78pub fn encode_launch_app(application: Application, data: Vec<u8>) -> anyhow::Result<Vec<u8>> {
79            // Encode struct ApplicationStruct
80            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
93/// Encode StopApp command (0x01)
94pub fn encode_stop_app(application: Application) -> anyhow::Result<Vec<u8>> {
95            // Encode struct ApplicationStruct
96            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
108/// Encode HideApp command (0x02)
109pub fn encode_hide_app(application: Application) -> anyhow::Result<Vec<u8>> {
110            // Encode struct ApplicationStruct
111            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
123// Attribute decoders
124
125/// Decode CatalogList attribute (0x0000)
126pub 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
138/// Decode CurrentApp attribute (0x0001)
139pub fn decode_current_app(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<ApplicationEP>> {
140    if let tlv::TlvItemValue::List(_fields) = inp {
141        // Struct with fields
142        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 if let tlv::TlvItemValue::Null = inp {
162    //    // Null value for nullable struct
163    //    Ok(None)
164    } else {
165    Ok(None)
166    //    Err(anyhow::anyhow!("Expected struct fields or null"))
167    }
168}
169
170
171// JSON dispatcher function
172
173/// Decode attribute value and return as JSON string
174///
175/// # Parameters
176/// * `cluster_id` - The cluster identifier
177/// * `attribute_id` - The attribute identifier
178/// * `tlv_value` - The TLV value to decode
179///
180/// # Returns
181/// JSON string representation of the decoded value or error
182pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
183    // Verify this is the correct cluster
184    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
205/// Get list of all attributes supported by this cluster
206///
207/// # Returns
208/// Vector of tuples containing (attribute_id, attribute_name)
209pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
210    vec![
211        (0x0000, "CatalogList"),
212        (0x0001, "CurrentApp"),
213    ]
214}
215
216// Command listing
217
218pub 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
267// Command response decoders
268
269/// Decode LauncherResponse command response (03)
270pub 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
282// Typed facade (invokes + reads)
283
284/// Invoke `LaunchApp` command on cluster `Application Launcher`.
285pub 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
290/// Invoke `StopApp` command on cluster `Application Launcher`.
291pub 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
296/// Invoke `HideApp` command on cluster `Application Launcher`.
297pub 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
302/// Read `CatalogList` attribute from cluster `Application Launcher`.
303pub 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
308/// Read `CurrentApp` attribute from cluster `Application Launcher`.
309pub 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