1#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16#[repr(u8)]
17pub enum FutureMessagePreference {
18 Allowed = 0,
20 Increased = 1,
22 Reduced = 2,
24 Disallowed = 3,
26 Banned = 4,
28}
29
30impl FutureMessagePreference {
31 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 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 Low = 0,
60 Medium = 1,
62 High = 2,
64 Critical = 3,
66}
67
68impl MessagePriority {
69 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 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
92pub type MessageControl = u8;
96
97pub mod messagecontrol {
99 pub const CONFIRMATION_REQUIRED: u8 = 0x01;
101 pub const RESPONSE_REQUIRED: u8 = 0x02;
103 pub const REPLY_MESSAGE: u8 = 0x04;
105 pub const MESSAGE_CONFIRMED: u8 = 0x08;
107 pub const MESSAGE_PROTECTED: u8 = 0x10;
109}
110
111#[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
130pub 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
154pub 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
165pub 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
199pub 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
213pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
225 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
247pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
252 vec![
253 (0x0000, "Messages"),
254 (0x0001, "ActiveMessageIDs"),
255 ]
256}
257
258pub 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
301pub 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
309pub 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
315pub 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
321pub 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
345pub 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
359pub 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
371pub 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