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 OperationalStatus {
18 Pending = 0,
20 Operating = 1,
22 Skipped = 2,
24 Completed = 3,
26}
27
28impl OperationalStatus {
29 pub fn from_u8(value: u8) -> Option<Self> {
31 match value {
32 0 => Some(OperationalStatus::Pending),
33 1 => Some(OperationalStatus::Operating),
34 2 => Some(OperationalStatus::Skipped),
35 3 => Some(OperationalStatus::Completed),
36 _ => None,
37 }
38 }
39
40 pub fn to_u8(self) -> u8 {
42 self as u8
43 }
44}
45
46impl From<OperationalStatus> for u8 {
47 fn from(val: OperationalStatus) -> Self {
48 val as u8
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
53#[repr(u8)]
54pub enum SelectAreasStatus {
55 Success = 0,
57 Unsupportedarea = 1,
59 Invalidinmode = 2,
61 Invalidset = 3,
63}
64
65impl SelectAreasStatus {
66 pub fn from_u8(value: u8) -> Option<Self> {
68 match value {
69 0 => Some(SelectAreasStatus::Success),
70 1 => Some(SelectAreasStatus::Unsupportedarea),
71 2 => Some(SelectAreasStatus::Invalidinmode),
72 3 => Some(SelectAreasStatus::Invalidset),
73 _ => None,
74 }
75 }
76
77 pub fn to_u8(self) -> u8 {
79 self as u8
80 }
81}
82
83impl From<SelectAreasStatus> for u8 {
84 fn from(val: SelectAreasStatus) -> Self {
85 val as u8
86 }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
90#[repr(u8)]
91pub enum SkipAreaStatus {
92 Success = 0,
94 Invalidarealist = 1,
96 Invalidinmode = 2,
98 Invalidskippedarea = 3,
100}
101
102impl SkipAreaStatus {
103 pub fn from_u8(value: u8) -> Option<Self> {
105 match value {
106 0 => Some(SkipAreaStatus::Success),
107 1 => Some(SkipAreaStatus::Invalidarealist),
108 2 => Some(SkipAreaStatus::Invalidinmode),
109 3 => Some(SkipAreaStatus::Invalidskippedarea),
110 _ => None,
111 }
112 }
113
114 pub fn to_u8(self) -> u8 {
116 self as u8
117 }
118}
119
120impl From<SkipAreaStatus> for u8 {
121 fn from(val: SkipAreaStatus) -> Self {
122 val as u8
123 }
124}
125
126#[derive(Debug, serde::Serialize)]
129pub struct AreaInfo {
130 pub location_info: Option<LocationDescriptor>,
131 pub landmark_info: Option<LandmarkInfo>,
132}
133
134#[derive(Debug, serde::Serialize)]
135pub struct Area {
136 pub area_id: Option<u32>,
137 pub map_id: Option<u32>,
138 pub area_info: Option<AreaInfo>,
139}
140
141#[derive(Debug, serde::Serialize)]
142pub struct LandmarkInfo {
143 pub landmark_tag: Option<u8>,
144 pub relative_position_tag: Option<u8>,
145}
146
147#[derive(Debug, serde::Serialize)]
148pub struct Map {
149 pub map_id: Option<u32>,
150 pub name: Option<String>,
151}
152
153#[derive(Debug, serde::Serialize)]
154pub struct Progress {
155 pub area_id: Option<u32>,
156 pub status: Option<OperationalStatus>,
157 pub total_operational_time: Option<u32>,
158 pub estimated_time: Option<u32>,
159}
160
161#[derive(Debug, serde::Serialize)]
162pub struct LocationDescriptor {
163 pub location_name: Option<String>,
164 pub floor_number: Option<u16>,
165 pub area_type: Option<u8>,
166}
167
168pub fn encode_select_areas(new_areas: Vec<u32>) -> anyhow::Result<Vec<u8>> {
172 let tlv = tlv::TlvItemEnc {
173 tag: 0,
174 value: tlv::TlvItemValueEnc::StructInvisible(vec![
175 (0, tlv::TlvItemValueEnc::StructAnon(new_areas.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt32(v)).into()).collect())).into(),
176 ]),
177 };
178 Ok(tlv.encode()?)
179}
180
181pub fn encode_skip_area(skipped_area: u32) -> anyhow::Result<Vec<u8>> {
183 let tlv = tlv::TlvItemEnc {
184 tag: 0,
185 value: tlv::TlvItemValueEnc::StructInvisible(vec![
186 (0, tlv::TlvItemValueEnc::UInt32(skipped_area)).into(),
187 ]),
188 };
189 Ok(tlv.encode()?)
190}
191
192pub fn decode_supported_areas(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Area>> {
196 let mut res = Vec::new();
197 if let tlv::TlvItemValue::List(v) = inp {
198 for item in v {
199 res.push(Area {
200 area_id: item.get_int(&[0]).map(|v| v as u32),
201 map_id: item.get_int(&[1]).map(|v| v as u32),
202 area_info: {
203 if let Some(nested_tlv) = item.get(&[2]) {
204 if let tlv::TlvItemValue::List(_) = nested_tlv {
205 let nested_item = tlv::TlvItem { tag: 2, value: nested_tlv.clone() };
206 Some(AreaInfo {
207 location_info: {
208 if let Some(nested_tlv) = nested_item.get(&[0]) {
209 if let tlv::TlvItemValue::List(_) = nested_tlv {
210 let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
211 Some(LocationDescriptor {
212 location_name: nested_item.get_string_owned(&[0]),
213 floor_number: nested_item.get_int(&[1]).map(|v| v as u16),
214 area_type: nested_item.get_int(&[2]).map(|v| v as u8),
215 })
216 } else {
217 None
218 }
219 } else {
220 None
221 }
222 },
223 landmark_info: {
224 if let Some(nested_tlv) = nested_item.get(&[1]) {
225 if let tlv::TlvItemValue::List(_) = nested_tlv {
226 let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
227 Some(LandmarkInfo {
228 landmark_tag: nested_item.get_int(&[0]).map(|v| v as u8),
229 relative_position_tag: nested_item.get_int(&[1]).map(|v| v as u8),
230 })
231 } else {
232 None
233 }
234 } else {
235 None
236 }
237 },
238 })
239 } else {
240 None
241 }
242 } else {
243 None
244 }
245 },
246 });
247 }
248 }
249 Ok(res)
250}
251
252pub fn decode_supported_maps(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Map>> {
254 let mut res = Vec::new();
255 if let tlv::TlvItemValue::List(v) = inp {
256 for item in v {
257 res.push(Map {
258 map_id: item.get_int(&[0]).map(|v| v as u32),
259 name: item.get_string_owned(&[1]),
260 });
261 }
262 }
263 Ok(res)
264}
265
266pub fn decode_selected_areas(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<u32>> {
268 let mut res = Vec::new();
269 if let tlv::TlvItemValue::List(v) = inp {
270 for item in v {
271 if let tlv::TlvItemValue::Int(i) = &item.value {
272 res.push(*i as u32);
273 }
274 }
275 }
276 Ok(res)
277}
278
279pub fn decode_current_area(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u32>> {
281 if let tlv::TlvItemValue::Int(v) = inp {
282 Ok(Some(*v as u32))
283 } else {
284 Ok(None)
285 }
286}
287
288pub fn decode_estimated_end_time(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
290 if let tlv::TlvItemValue::Int(v) = inp {
291 Ok(Some(*v))
292 } else {
293 Ok(None)
294 }
295}
296
297pub fn decode_progress(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Progress>> {
299 let mut res = Vec::new();
300 if let tlv::TlvItemValue::List(v) = inp {
301 for item in v {
302 res.push(Progress {
303 area_id: item.get_int(&[0]).map(|v| v as u32),
304 status: item.get_int(&[1]).and_then(|v| OperationalStatus::from_u8(v as u8)),
305 total_operational_time: item.get_int(&[2]).map(|v| v as u32),
306 estimated_time: item.get_int(&[3]).map(|v| v as u32),
307 });
308 }
309 }
310 Ok(res)
311}
312
313
314pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
326 if cluster_id != 0x0150 {
328 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0150, got {}\"}}", cluster_id);
329 }
330
331 match attribute_id {
332 0x0000 => {
333 match decode_supported_areas(tlv_value) {
334 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
335 Err(e) => format!("{{\"error\": \"{}\"}}", e),
336 }
337 }
338 0x0001 => {
339 match decode_supported_maps(tlv_value) {
340 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
341 Err(e) => format!("{{\"error\": \"{}\"}}", e),
342 }
343 }
344 0x0002 => {
345 match decode_selected_areas(tlv_value) {
346 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
347 Err(e) => format!("{{\"error\": \"{}\"}}", e),
348 }
349 }
350 0x0003 => {
351 match decode_current_area(tlv_value) {
352 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
353 Err(e) => format!("{{\"error\": \"{}\"}}", e),
354 }
355 }
356 0x0004 => {
357 match decode_estimated_end_time(tlv_value) {
358 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
359 Err(e) => format!("{{\"error\": \"{}\"}}", e),
360 }
361 }
362 0x0005 => {
363 match decode_progress(tlv_value) {
364 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
365 Err(e) => format!("{{\"error\": \"{}\"}}", e),
366 }
367 }
368 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
369 }
370}
371
372pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
377 vec![
378 (0x0000, "SupportedAreas"),
379 (0x0001, "SupportedMaps"),
380 (0x0002, "SelectedAreas"),
381 (0x0003, "CurrentArea"),
382 (0x0004, "EstimatedEndTime"),
383 (0x0005, "Progress"),
384 ]
385}
386
387pub fn get_command_list() -> Vec<(u32, &'static str)> {
390 vec![
391 (0x00, "SelectAreas"),
392 (0x02, "SkipArea"),
393 ]
394}
395
396pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
397 match cmd_id {
398 0x00 => Some("SelectAreas"),
399 0x02 => Some("SkipArea"),
400 _ => None,
401 }
402}
403
404pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
405 match cmd_id {
406 0x00 => Some(vec![
407 crate::clusters::codec::CommandField { tag: 0, name: "new_areas", kind: crate::clusters::codec::FieldKind::List { entry_type: "uint32" }, optional: false, nullable: false },
408 ]),
409 0x02 => Some(vec![
410 crate::clusters::codec::CommandField { tag: 0, name: "skipped_area", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
411 ]),
412 _ => None,
413 }
414}
415
416pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
417 match cmd_id {
418 0x00 => Err(anyhow::anyhow!("command \"SelectAreas\" has complex args: use raw mode")),
419 0x02 => {
420 let skipped_area = crate::clusters::codec::json_util::get_u32(args, "skipped_area")?;
421 encode_skip_area(skipped_area)
422 }
423 _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
424 }
425}
426
427#[derive(Debug, serde::Serialize)]
428pub struct SelectAreasResponse {
429 pub status: Option<u8>,
430 pub status_text: Option<String>,
431}
432
433#[derive(Debug, serde::Serialize)]
434pub struct SkipAreaResponse {
435 pub status: Option<u8>,
436 pub status_text: Option<String>,
437}
438
439pub fn decode_select_areas_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SelectAreasResponse> {
443 if let tlv::TlvItemValue::List(_fields) = inp {
444 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
445 Ok(SelectAreasResponse {
446 status: item.get_int(&[0]).map(|v| v as u8),
447 status_text: item.get_string_owned(&[1]),
448 })
449 } else {
450 Err(anyhow::anyhow!("Expected struct fields"))
451 }
452}
453
454pub fn decode_skip_area_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SkipAreaResponse> {
456 if let tlv::TlvItemValue::List(_fields) = inp {
457 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
458 Ok(SkipAreaResponse {
459 status: item.get_int(&[0]).map(|v| v as u8),
460 status_text: item.get_string_owned(&[1]),
461 })
462 } else {
463 Err(anyhow::anyhow!("Expected struct fields"))
464 }
465}
466
467pub async fn select_areas(conn: &crate::controller::Connection, endpoint: u16, new_areas: Vec<u32>) -> anyhow::Result<SelectAreasResponse> {
471 let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_SERVICE_AREA, crate::clusters::defs::CLUSTER_SERVICE_AREA_CMD_ID_SELECTAREAS, &encode_select_areas(new_areas)?).await?;
472 decode_select_areas_response(&tlv)
473}
474
475pub async fn skip_area(conn: &crate::controller::Connection, endpoint: u16, skipped_area: u32) -> anyhow::Result<SkipAreaResponse> {
477 let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_SERVICE_AREA, crate::clusters::defs::CLUSTER_SERVICE_AREA_CMD_ID_SKIPAREA, &encode_skip_area(skipped_area)?).await?;
478 decode_skip_area_response(&tlv)
479}
480
481pub async fn read_supported_areas(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<Area>> {
483 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_SERVICE_AREA, crate::clusters::defs::CLUSTER_SERVICE_AREA_ATTR_ID_SUPPORTEDAREAS).await?;
484 decode_supported_areas(&tlv)
485}
486
487pub async fn read_supported_maps(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<Map>> {
489 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_SERVICE_AREA, crate::clusters::defs::CLUSTER_SERVICE_AREA_ATTR_ID_SUPPORTEDMAPS).await?;
490 decode_supported_maps(&tlv)
491}
492
493pub async fn read_selected_areas(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<u32>> {
495 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_SERVICE_AREA, crate::clusters::defs::CLUSTER_SERVICE_AREA_ATTR_ID_SELECTEDAREAS).await?;
496 decode_selected_areas(&tlv)
497}
498
499pub async fn read_current_area(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u32>> {
501 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_SERVICE_AREA, crate::clusters::defs::CLUSTER_SERVICE_AREA_ATTR_ID_CURRENTAREA).await?;
502 decode_current_area(&tlv)
503}
504
505pub async fn read_estimated_end_time(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
507 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_SERVICE_AREA, crate::clusters::defs::CLUSTER_SERVICE_AREA_ATTR_ID_ESTIMATEDENDTIME).await?;
508 decode_estimated_end_time(&tlv)
509}
510
511pub async fn read_progress(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<Progress>> {
513 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_SERVICE_AREA, crate::clusters::defs::CLUSTER_SERVICE_AREA_ATTR_ID_PROGRESS).await?;
514 decode_progress(&tlv)
515}
516