matc/clusters/codec/
service_area.rs

1//! Matter TLV encoders and decoders for Service Area Cluster
2//! Cluster ID: 0x0150
3//!
4//! This file is automatically generated from ServiceArea.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 OperationalStatus {
18    /// The device has not yet started operating at the given area, or has not finished operating at that area but it is not currently operating at the area
19    Pending = 0,
20    /// The device is currently operating at the given area
21    Operating = 1,
22    /// The device has skipped the given area, before or during operating at it, due to a SkipArea command, due an out of band command (e.g. from the vendor's application), due to a vendor specific reason, such as a time limit used by the device, or due the device ending operating unsuccessfully
23    Skipped = 2,
24    /// The device has completed operating at the given area
25    Completed = 3,
26}
27
28impl OperationalStatus {
29    /// Convert from u8 value
30    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    /// Convert to u8 value
41    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    /// Attempting to operate in the areas identified by the entries of the NewAreas field is allowed and possible. The SelectedAreas attribute is set to the value of the NewAreas field.
56    Success = 0,
57    /// The value of at least one of the entries of the NewAreas field doesn't match any entries in the SupportedAreas attribute.
58    Unsupportedarea = 1,
59    /// The received request cannot be handled due to the current mode of the device.
60    Invalidinmode = 2,
61    /// The set of values is invalid. For example, areas on different floors, that a robot knows it can't reach on its own.
62    Invalidset = 3,
63}
64
65impl SelectAreasStatus {
66    /// Convert from u8 value
67    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    /// Convert to u8 value
78    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    /// Skipping the area is allowed and possible, or the device was operating at the last available area and has stopped.
93    Success = 0,
94    /// The SelectedAreas attribute is empty.
95    Invalidarealist = 1,
96    /// The received request cannot be handled due to the current mode of the device. For example, the CurrentArea attribute is null or the device is not operating.
97    Invalidinmode = 2,
98    /// The SkippedArea field doesn't match an entry in the SupportedAreas list.
99    Invalidskippedarea = 3,
100}
101
102impl SkipAreaStatus {
103    /// Convert from u8 value
104    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    /// Convert to u8 value
115    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// Struct definitions
127
128#[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
168// Command encoders
169
170/// Encode SelectAreas command (0x00)
171pub 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
181/// Encode SkipArea command (0x02)
182pub 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
192// Attribute decoders
193
194/// Decode SupportedAreas attribute (0x0000)
195pub 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
252/// Decode SupportedMaps attribute (0x0001)
253pub 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
266/// Decode SelectedAreas attribute (0x0002)
267pub 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
279/// Decode CurrentArea attribute (0x0003)
280pub 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
288/// Decode EstimatedEndTime attribute (0x0004)
289pub 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
297/// Decode Progress attribute (0x0005)
298pub 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
314// JSON dispatcher function
315
316/// Decode attribute value and return as JSON string
317///
318/// # Parameters
319/// * `cluster_id` - The cluster identifier
320/// * `attribute_id` - The attribute identifier
321/// * `tlv_value` - The TLV value to decode
322///
323/// # Returns
324/// JSON string representation of the decoded value or error
325pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
326    // Verify this is the correct cluster
327    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
372/// Get list of all attributes supported by this cluster
373///
374/// # Returns
375/// Vector of tuples containing (attribute_id, attribute_name)
376pub 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
387// Command listing
388
389pub 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
439// Command response decoders
440
441/// Decode SelectAreasResponse command response (01)
442pub 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
454/// Decode SkipAreaResponse command response (03)
455pub 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
467// Typed facade (invokes + reads)
468
469/// Invoke `SelectAreas` command on cluster `Service Area`.
470pub 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
475/// Invoke `SkipArea` command on cluster `Service Area`.
476pub 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
481/// Read `SupportedAreas` attribute from cluster `Service Area`.
482pub 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
487/// Read `SupportedMaps` attribute from cluster `Service Area`.
488pub 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
493/// Read `SelectedAreas` attribute from cluster `Service Area`.
494pub 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
499/// Read `CurrentArea` attribute from cluster `Service Area`.
500pub 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
505/// Read `EstimatedEndTime` attribute from cluster `Service Area`.
506pub 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
511/// Read `Progress` attribute from cluster `Service Area`.
512pub 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