matc/clusters/codec/
messages.rs

1//! Matter TLV encoders and decoders for Messages Cluster
2//! Cluster ID: 0x0097
3//!
4//! This file is automatically generated from Messages.xml
5
6#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13// Enum definitions
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16#[repr(u8)]
17pub enum FutureMessagePreference {
18    /// Similar messages are allowed
19    Allowed = 0,
20    /// Similar messages should be sent more often
21    Increased = 1,
22    /// Similar messages should be sent less often
23    Reduced = 2,
24    /// Similar messages should not be sent
25    Disallowed = 3,
26    /// No further messages should be sent
27    Banned = 4,
28}
29
30impl FutureMessagePreference {
31    /// Convert from u8 value
32    pub fn from_u8(value: u8) -> Option<Self> {
33        match value {
34            0 => Some(FutureMessagePreference::Allowed),
35            1 => Some(FutureMessagePreference::Increased),
36            2 => Some(FutureMessagePreference::Reduced),
37            3 => Some(FutureMessagePreference::Disallowed),
38            4 => Some(FutureMessagePreference::Banned),
39            _ => None,
40        }
41    }
42
43    /// Convert to u8 value
44    pub fn to_u8(self) -> u8 {
45        self as u8
46    }
47}
48
49impl From<FutureMessagePreference> for u8 {
50    fn from(val: FutureMessagePreference) -> Self {
51        val as u8
52    }
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
56#[repr(u8)]
57pub enum MessagePriority {
58    /// Message to be transferred with a low level of importance
59    Low = 0,
60    /// Message to be transferred with a medium level of importance
61    Medium = 1,
62    /// Message to be transferred with a high level of importance
63    High = 2,
64    /// Message to be transferred with a critical level of importance
65    Critical = 3,
66}
67
68impl MessagePriority {
69    /// Convert from u8 value
70    pub fn from_u8(value: u8) -> Option<Self> {
71        match value {
72            0 => Some(MessagePriority::Low),
73            1 => Some(MessagePriority::Medium),
74            2 => Some(MessagePriority::High),
75            3 => Some(MessagePriority::Critical),
76            _ => None,
77        }
78    }
79
80    /// Convert to u8 value
81    pub fn to_u8(self) -> u8 {
82        self as u8
83    }
84}
85
86impl From<MessagePriority> for u8 {
87    fn from(val: MessagePriority) -> Self {
88        val as u8
89    }
90}
91
92// Bitmap definitions
93
94/// MessageControl bitmap type
95pub type MessageControl = u8;
96
97/// Constants for MessageControl
98pub mod messagecontrol {
99    /// Message requires confirmation from user
100    pub const CONFIRMATION_REQUIRED: u8 = 0x01;
101    /// Message requires response from user
102    pub const RESPONSE_REQUIRED: u8 = 0x02;
103    /// Message supports reply message from user
104    pub const REPLY_MESSAGE: u8 = 0x04;
105    /// Message has already been confirmed
106    pub const MESSAGE_CONFIRMED: u8 = 0x08;
107    /// Message required PIN/password protection
108    pub const MESSAGE_PROTECTED: u8 = 0x10;
109}
110
111// Struct definitions
112
113#[derive(Debug, serde::Serialize)]
114pub struct MessageResponseOption {
115    pub message_response_id: Option<u32>,
116    pub label: Option<String>,
117}
118
119#[derive(Debug, serde::Serialize)]
120pub struct Message {
121    pub message_id: Option<u8>,
122    pub priority: Option<MessagePriority>,
123    pub message_control: Option<MessageControl>,
124    pub start_time: Option<u64>,
125    pub duration: Option<u64>,
126    pub message_text: Option<String>,
127    pub responses: Option<Vec<MessageResponseOption>>,
128}
129
130// Command encoders
131
132/// Encode PresentMessagesRequest command (0x00)
133pub fn encode_present_messages_request(message_id: u8, priority: MessagePriority, message_control: MessageControl, start_time: Option<u64>, duration: Option<u64>, message_text: String, responses: Vec<MessageResponseOption>) -> anyhow::Result<Vec<u8>> {
134    let tlv = tlv::TlvItemEnc {
135        tag: 0,
136        value: tlv::TlvItemValueEnc::StructInvisible(vec![
137        (0, tlv::TlvItemValueEnc::UInt8(message_id)).into(),
138        (1, tlv::TlvItemValueEnc::UInt8(priority.to_u8())).into(),
139        (2, tlv::TlvItemValueEnc::UInt8(message_control)).into(),
140        (3, tlv::TlvItemValueEnc::UInt64(start_time.unwrap_or(0))).into(),
141        (4, tlv::TlvItemValueEnc::UInt64(duration.unwrap_or(0))).into(),
142        (5, tlv::TlvItemValueEnc::String(message_text)).into(),
143        (6, tlv::TlvItemValueEnc::Array(responses.into_iter().map(|v| {
144                    let mut fields = Vec::new();
145                    if let Some(x) = v.message_response_id { fields.push((0, tlv::TlvItemValueEnc::UInt32(x)).into()); }
146                    if let Some(x) = v.label { fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
147                    (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
148                }).collect())).into(),
149        ]),
150    };
151    Ok(tlv.encode()?)
152}
153
154/// Encode CancelMessagesRequest command (0x01)
155pub fn encode_cancel_messages_request(message_i_ds: Vec<u8>) -> anyhow::Result<Vec<u8>> {
156    let tlv = tlv::TlvItemEnc {
157        tag: 0,
158        value: tlv::TlvItemValueEnc::StructInvisible(vec![
159        (0, tlv::TlvItemValueEnc::StructAnon(message_i_ds.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt8(v)).into()).collect())).into(),
160        ]),
161    };
162    Ok(tlv.encode()?)
163}
164
165// Attribute decoders
166
167/// Decode Messages attribute (0x0000)
168pub fn decode_messages(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Message>> {
169    let mut res = Vec::new();
170    if let tlv::TlvItemValue::List(v) = inp {
171        for item in v {
172            res.push(Message {
173                message_id: item.get_int(&[0]).map(|v| v as u8),
174                priority: item.get_int(&[1]).and_then(|v| MessagePriority::from_u8(v as u8)),
175                message_control: item.get_int(&[2]).map(|v| v as u8),
176                start_time: item.get_int(&[3]),
177                duration: item.get_int(&[4]),
178                message_text: item.get_string_owned(&[5]),
179                responses: {
180                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[6]) {
181                        let mut items = Vec::new();
182                        for list_item in l {
183                            items.push(MessageResponseOption {
184                message_response_id: list_item.get_int(&[0]).map(|v| v as u32),
185                label: list_item.get_string_owned(&[1]),
186                            });
187                        }
188                        Some(items)
189                    } else {
190                        None
191                    }
192                },
193            });
194        }
195    }
196    Ok(res)
197}
198
199/// Decode ActiveMessageIDs attribute (0x0001)
200pub fn decode_active_message_i_ds(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<u8>> {
201    let mut res = Vec::new();
202    if let tlv::TlvItemValue::List(v) = inp {
203        for item in v {
204            if let tlv::TlvItemValue::Int(i) = &item.value {
205                res.push(*i as u8);
206            }
207        }
208    }
209    Ok(res)
210}
211
212
213// JSON dispatcher function
214
215/// Decode attribute value and return as JSON string
216///
217/// # Parameters
218/// * `cluster_id` - The cluster identifier
219/// * `attribute_id` - The attribute identifier
220/// * `tlv_value` - The TLV value to decode
221///
222/// # Returns
223/// JSON string representation of the decoded value or error
224pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
225    // Verify this is the correct cluster
226    if cluster_id != 0x0097 {
227        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0097, got {}\"}}", cluster_id);
228    }
229
230    match attribute_id {
231        0x0000 => {
232            match decode_messages(tlv_value) {
233                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
234                Err(e) => format!("{{\"error\": \"{}\"}}", e),
235            }
236        }
237        0x0001 => {
238            match decode_active_message_i_ds(tlv_value) {
239                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
240                Err(e) => format!("{{\"error\": \"{}\"}}", e),
241            }
242        }
243        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
244    }
245}
246
247/// Get list of all attributes supported by this cluster
248///
249/// # Returns
250/// Vector of tuples containing (attribute_id, attribute_name)
251pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
252    vec![
253        (0x0000, "Messages"),
254        (0x0001, "ActiveMessageIDs"),
255    ]
256}
257
258// Command listing
259
260pub fn get_command_list() -> Vec<(u32, &'static str)> {
261    vec![
262        (0x00, "PresentMessagesRequest"),
263        (0x01, "CancelMessagesRequest"),
264    ]
265}
266
267pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
268    match cmd_id {
269        0x00 => Some("PresentMessagesRequest"),
270        0x01 => Some("CancelMessagesRequest"),
271        _ => None,
272    }
273}
274
275pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
276    match cmd_id {
277        0x00 => Some(vec![
278            crate::clusters::codec::CommandField { tag: 0, name: "message_id", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
279            crate::clusters::codec::CommandField { tag: 1, name: "priority", kind: crate::clusters::codec::FieldKind::Enum { name: "MessagePriority", variants: &[(0, "Low"), (1, "Medium"), (2, "High"), (3, "Critical")] }, optional: false, nullable: false },
280            crate::clusters::codec::CommandField { tag: 2, name: "message_control", kind: crate::clusters::codec::FieldKind::Bitmap { name: "MessageControl", bits: &[(1, "CONFIRMATION_REQUIRED"), (2, "RESPONSE_REQUIRED"), (4, "REPLY_MESSAGE"), (8, "MESSAGE_CONFIRMED"), (16, "MESSAGE_PROTECTED")] }, optional: false, nullable: false },
281            crate::clusters::codec::CommandField { tag: 3, name: "start_time", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: true },
282            crate::clusters::codec::CommandField { tag: 4, name: "duration", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: true },
283            crate::clusters::codec::CommandField { tag: 5, name: "message_text", kind: crate::clusters::codec::FieldKind::String, optional: false, nullable: false },
284            crate::clusters::codec::CommandField { tag: 6, name: "responses", kind: crate::clusters::codec::FieldKind::List { entry_type: "MessageResponseOptionStruct" }, optional: false, nullable: false },
285        ]),
286        0x01 => Some(vec![
287            crate::clusters::codec::CommandField { tag: 0, name: "message_i_ds", kind: crate::clusters::codec::FieldKind::List { entry_type: "message-id" }, optional: false, nullable: false },
288        ]),
289        _ => None,
290    }
291}
292
293pub fn encode_command_json(cmd_id: u32, _args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
294    match cmd_id {
295        0x00 => Err(anyhow::anyhow!("command \"PresentMessagesRequest\" has complex args: use raw mode")),
296        0x01 => Err(anyhow::anyhow!("command \"CancelMessagesRequest\" has complex args: use raw mode")),
297        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
298    }
299}
300
301// Typed facade (invokes + reads)
302
303/// Invoke `PresentMessagesRequest` command on cluster `Messages`.
304pub async fn present_messages_request(conn: &crate::controller::Connection, endpoint: u16, message_id: u8, priority: MessagePriority, message_control: MessageControl, start_time: Option<u64>, duration: Option<u64>, message_text: String, responses: Vec<MessageResponseOption>) -> anyhow::Result<()> {
305    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_MESSAGES, crate::clusters::defs::CLUSTER_MESSAGES_CMD_ID_PRESENTMESSAGESREQUEST, &encode_present_messages_request(message_id, priority, message_control, start_time, duration, message_text, responses)?).await?;
306    Ok(())
307}
308
309/// Invoke `CancelMessagesRequest` command on cluster `Messages`.
310pub async fn cancel_messages_request(conn: &crate::controller::Connection, endpoint: u16, message_i_ds: Vec<u8>) -> anyhow::Result<()> {
311    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_MESSAGES, crate::clusters::defs::CLUSTER_MESSAGES_CMD_ID_CANCELMESSAGESREQUEST, &encode_cancel_messages_request(message_i_ds)?).await?;
312    Ok(())
313}
314
315/// Read `Messages` attribute from cluster `Messages`.
316pub async fn read_messages(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<Message>> {
317    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MESSAGES, crate::clusters::defs::CLUSTER_MESSAGES_ATTR_ID_MESSAGES).await?;
318    decode_messages(&tlv)
319}
320
321/// Read `ActiveMessageIDs` attribute from cluster `Messages`.
322pub async fn read_active_message_i_ds(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<u8>> {
323    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MESSAGES, crate::clusters::defs::CLUSTER_MESSAGES_ATTR_ID_ACTIVEMESSAGEIDS).await?;
324    decode_active_message_i_ds(&tlv)
325}
326
327#[derive(Debug, serde::Serialize)]
328pub struct MessageQueuedEvent {
329    pub message_id: Option<u8>,
330}
331
332#[derive(Debug, serde::Serialize)]
333pub struct MessagePresentedEvent {
334    pub message_id: Option<u8>,
335}
336
337#[derive(Debug, serde::Serialize)]
338pub struct MessageCompleteEvent {
339    pub message_id: Option<u8>,
340    pub response_id: Option<u32>,
341    pub reply: Option<String>,
342    pub future_messages_preference: Option<FutureMessagePreference>,
343}
344
345// Event decoders
346
347/// Decode MessageQueued event (0x00, priority: info)
348pub fn decode_message_queued_event(inp: &tlv::TlvItemValue) -> anyhow::Result<MessageQueuedEvent> {
349    if let tlv::TlvItemValue::List(_fields) = inp {
350        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
351        Ok(MessageQueuedEvent {
352                                message_id: item.get_int(&[0]).map(|v| v as u8),
353        })
354    } else {
355        Err(anyhow::anyhow!("Expected struct fields"))
356    }
357}
358
359/// Decode MessagePresented event (0x01, priority: info)
360pub fn decode_message_presented_event(inp: &tlv::TlvItemValue) -> anyhow::Result<MessagePresentedEvent> {
361    if let tlv::TlvItemValue::List(_fields) = inp {
362        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
363        Ok(MessagePresentedEvent {
364                                message_id: item.get_int(&[0]).map(|v| v as u8),
365        })
366    } else {
367        Err(anyhow::anyhow!("Expected struct fields"))
368    }
369}
370
371/// Decode MessageComplete event (0x02, priority: info)
372pub fn decode_message_complete_event(inp: &tlv::TlvItemValue) -> anyhow::Result<MessageCompleteEvent> {
373    if let tlv::TlvItemValue::List(_fields) = inp {
374        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
375        Ok(MessageCompleteEvent {
376                                message_id: item.get_int(&[0]).map(|v| v as u8),
377                                response_id: item.get_int(&[1]).map(|v| v as u32),
378                                reply: item.get_string_owned(&[2]),
379                                future_messages_preference: item.get_int(&[3]).and_then(|v| FutureMessagePreference::from_u8(v as u8)),
380        })
381    } else {
382        Err(anyhow::anyhow!("Expected struct fields"))
383    }
384}
385