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
6use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11// Enum definitions
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14#[repr(u8)]
15pub enum OperationalStatus {
16    /// 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
17    Pending = 0,
18    /// The device is currently operating at the given area
19    Operating = 1,
20    /// 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
21    Skipped = 2,
22    /// The device has completed operating at the given area
23    Completed = 3,
24}
25
26impl OperationalStatus {
27    /// Convert from u8 value
28    pub fn from_u8(value: u8) -> Option<Self> {
29        match value {
30            0 => Some(OperationalStatus::Pending),
31            1 => Some(OperationalStatus::Operating),
32            2 => Some(OperationalStatus::Skipped),
33            3 => Some(OperationalStatus::Completed),
34            _ => None,
35        }
36    }
37
38    /// Convert to u8 value
39    pub fn to_u8(self) -> u8 {
40        self as u8
41    }
42}
43
44impl From<OperationalStatus> for u8 {
45    fn from(val: OperationalStatus) -> Self {
46        val as u8
47    }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
51#[repr(u8)]
52pub enum SelectAreasStatus {
53    /// 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.
54    Success = 0,
55    /// The value of at least one of the entries of the NewAreas field doesn't match any entries in the SupportedAreas attribute.
56    Unsupportedarea = 1,
57    /// The received request cannot be handled due to the current mode of the device.
58    Invalidinmode = 2,
59    /// The set of values is invalid. For example, areas on different floors, that a robot knows it can't reach on its own.
60    Invalidset = 3,
61}
62
63impl SelectAreasStatus {
64    /// Convert from u8 value
65    pub fn from_u8(value: u8) -> Option<Self> {
66        match value {
67            0 => Some(SelectAreasStatus::Success),
68            1 => Some(SelectAreasStatus::Unsupportedarea),
69            2 => Some(SelectAreasStatus::Invalidinmode),
70            3 => Some(SelectAreasStatus::Invalidset),
71            _ => None,
72        }
73    }
74
75    /// Convert to u8 value
76    pub fn to_u8(self) -> u8 {
77        self as u8
78    }
79}
80
81impl From<SelectAreasStatus> for u8 {
82    fn from(val: SelectAreasStatus) -> Self {
83        val as u8
84    }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
88#[repr(u8)]
89pub enum SkipAreaStatus {
90    /// Skipping the area is allowed and possible, or the device was operating at the last available area and has stopped.
91    Success = 0,
92    /// The SelectedAreas attribute is empty.
93    Invalidarealist = 1,
94    /// 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.
95    Invalidinmode = 2,
96    /// The SkippedArea field doesn't match an entry in the SupportedAreas list.
97    Invalidskippedarea = 3,
98}
99
100impl SkipAreaStatus {
101    /// Convert from u8 value
102    pub fn from_u8(value: u8) -> Option<Self> {
103        match value {
104            0 => Some(SkipAreaStatus::Success),
105            1 => Some(SkipAreaStatus::Invalidarealist),
106            2 => Some(SkipAreaStatus::Invalidinmode),
107            3 => Some(SkipAreaStatus::Invalidskippedarea),
108            _ => None,
109        }
110    }
111
112    /// Convert to u8 value
113    pub fn to_u8(self) -> u8 {
114        self as u8
115    }
116}
117
118impl From<SkipAreaStatus> for u8 {
119    fn from(val: SkipAreaStatus) -> Self {
120        val as u8
121    }
122}
123
124// Struct definitions
125
126#[derive(Debug, serde::Serialize)]
127pub struct AreaInfo {
128    pub location_info: Option<LocationDescriptor>,
129    pub landmark_info: Option<LandmarkInfo>,
130}
131
132#[derive(Debug, serde::Serialize)]
133pub struct Area {
134    pub area_id: Option<u32>,
135    pub map_id: Option<u32>,
136    pub area_info: Option<AreaInfo>,
137}
138
139#[derive(Debug, serde::Serialize)]
140pub struct LandmarkInfo {
141    pub landmark_tag: Option<u8>,
142    pub relative_position_tag: Option<u8>,
143}
144
145#[derive(Debug, serde::Serialize)]
146pub struct Map {
147    pub map_id: Option<u32>,
148    pub name: Option<String>,
149}
150
151#[derive(Debug, serde::Serialize)]
152pub struct Progress {
153    pub area_id: Option<u32>,
154    pub status: Option<OperationalStatus>,
155    pub total_operational_time: Option<u32>,
156    pub estimated_time: Option<u32>,
157}
158
159#[derive(Debug, serde::Serialize)]
160pub struct LocationDescriptor {
161    pub location_name: Option<String>,
162    pub floor_number: Option<u16>,
163    pub area_type: Option<u8>,
164}
165
166// Command encoders
167
168/// Encode SelectAreas command (0x00)
169pub fn encode_select_areas(new_areas: Vec<u32>) -> anyhow::Result<Vec<u8>> {
170    let tlv = tlv::TlvItemEnc {
171        tag: 0,
172        value: tlv::TlvItemValueEnc::StructInvisible(vec![
173        (0, tlv::TlvItemValueEnc::StructAnon(new_areas.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt32(v)).into()).collect())).into(),
174        ]),
175    };
176    Ok(tlv.encode()?)
177}
178
179/// Encode SkipArea command (0x02)
180pub fn encode_skip_area(skipped_area: u32) -> anyhow::Result<Vec<u8>> {
181    let tlv = tlv::TlvItemEnc {
182        tag: 0,
183        value: tlv::TlvItemValueEnc::StructInvisible(vec![
184        (0, tlv::TlvItemValueEnc::UInt32(skipped_area)).into(),
185        ]),
186    };
187    Ok(tlv.encode()?)
188}
189
190// Attribute decoders
191
192/// Decode SupportedAreas attribute (0x0000)
193pub fn decode_supported_areas(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Area>> {
194    let mut res = Vec::new();
195    if let tlv::TlvItemValue::List(v) = inp {
196        for item in v {
197            res.push(Area {
198                area_id: item.get_int(&[0]).map(|v| v as u32),
199                map_id: item.get_int(&[1]).map(|v| v as u32),
200                area_info: {
201                    if let Some(nested_tlv) = item.get(&[2]) {
202                        if let tlv::TlvItemValue::List(_) = nested_tlv {
203                            let nested_item = tlv::TlvItem { tag: 2, value: nested_tlv.clone() };
204                            Some(AreaInfo {
205                location_info: {
206                    if let Some(nested_tlv) = nested_item.get(&[0]) {
207                        if let tlv::TlvItemValue::List(_) = nested_tlv {
208                            let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
209                            Some(LocationDescriptor {
210                location_name: nested_item.get_string_owned(&[0]),
211                floor_number: nested_item.get_int(&[1]).map(|v| v as u16),
212                area_type: nested_item.get_int(&[2]).map(|v| v as u8),
213                            })
214                        } else {
215                            None
216                        }
217                    } else {
218                        None
219                    }
220                },
221                landmark_info: {
222                    if let Some(nested_tlv) = nested_item.get(&[1]) {
223                        if let tlv::TlvItemValue::List(_) = nested_tlv {
224                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
225                            Some(LandmarkInfo {
226                landmark_tag: nested_item.get_int(&[0]).map(|v| v as u8),
227                relative_position_tag: nested_item.get_int(&[1]).map(|v| v as u8),
228                            })
229                        } else {
230                            None
231                        }
232                    } else {
233                        None
234                    }
235                },
236                            })
237                        } else {
238                            None
239                        }
240                    } else {
241                        None
242                    }
243                },
244            });
245        }
246    }
247    Ok(res)
248}
249
250/// Decode SupportedMaps attribute (0x0001)
251pub fn decode_supported_maps(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Map>> {
252    let mut res = Vec::new();
253    if let tlv::TlvItemValue::List(v) = inp {
254        for item in v {
255            res.push(Map {
256                map_id: item.get_int(&[0]).map(|v| v as u32),
257                name: item.get_string_owned(&[1]),
258            });
259        }
260    }
261    Ok(res)
262}
263
264/// Decode SelectedAreas attribute (0x0002)
265pub fn decode_selected_areas(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<u32>> {
266    let mut res = Vec::new();
267    if let tlv::TlvItemValue::List(v) = inp {
268        for item in v {
269            if let tlv::TlvItemValue::Int(i) = &item.value {
270                res.push(*i as u32);
271            }
272        }
273    }
274    Ok(res)
275}
276
277/// Decode CurrentArea attribute (0x0003)
278pub fn decode_current_area(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u32>> {
279    if let tlv::TlvItemValue::Int(v) = inp {
280        Ok(Some(*v as u32))
281    } else {
282        Ok(None)
283    }
284}
285
286/// Decode EstimatedEndTime attribute (0x0004)
287pub fn decode_estimated_end_time(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
288    if let tlv::TlvItemValue::Int(v) = inp {
289        Ok(Some(*v))
290    } else {
291        Ok(None)
292    }
293}
294
295/// Decode Progress attribute (0x0005)
296pub fn decode_progress(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Progress>> {
297    let mut res = Vec::new();
298    if let tlv::TlvItemValue::List(v) = inp {
299        for item in v {
300            res.push(Progress {
301                area_id: item.get_int(&[0]).map(|v| v as u32),
302                status: item.get_int(&[1]).and_then(|v| OperationalStatus::from_u8(v as u8)),
303                total_operational_time: item.get_int(&[2]).map(|v| v as u32),
304                estimated_time: item.get_int(&[3]).map(|v| v as u32),
305            });
306        }
307    }
308    Ok(res)
309}
310
311
312// JSON dispatcher function
313
314/// Decode attribute value and return as JSON string
315///
316/// # Parameters
317/// * `cluster_id` - The cluster identifier
318/// * `attribute_id` - The attribute identifier
319/// * `tlv_value` - The TLV value to decode
320///
321/// # Returns
322/// JSON string representation of the decoded value or error
323pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
324    // Verify this is the correct cluster
325    if cluster_id != 0x0150 {
326        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0150, got {}\"}}", cluster_id);
327    }
328
329    match attribute_id {
330        0x0000 => {
331            match decode_supported_areas(tlv_value) {
332                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
333                Err(e) => format!("{{\"error\": \"{}\"}}", e),
334            }
335        }
336        0x0001 => {
337            match decode_supported_maps(tlv_value) {
338                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
339                Err(e) => format!("{{\"error\": \"{}\"}}", e),
340            }
341        }
342        0x0002 => {
343            match decode_selected_areas(tlv_value) {
344                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
345                Err(e) => format!("{{\"error\": \"{}\"}}", e),
346            }
347        }
348        0x0003 => {
349            match decode_current_area(tlv_value) {
350                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
351                Err(e) => format!("{{\"error\": \"{}\"}}", e),
352            }
353        }
354        0x0004 => {
355            match decode_estimated_end_time(tlv_value) {
356                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
357                Err(e) => format!("{{\"error\": \"{}\"}}", e),
358            }
359        }
360        0x0005 => {
361            match decode_progress(tlv_value) {
362                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
363                Err(e) => format!("{{\"error\": \"{}\"}}", e),
364            }
365        }
366        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
367    }
368}
369
370/// Get list of all attributes supported by this cluster
371///
372/// # Returns
373/// Vector of tuples containing (attribute_id, attribute_name)
374pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
375    vec![
376        (0x0000, "SupportedAreas"),
377        (0x0001, "SupportedMaps"),
378        (0x0002, "SelectedAreas"),
379        (0x0003, "CurrentArea"),
380        (0x0004, "EstimatedEndTime"),
381        (0x0005, "Progress"),
382    ]
383}
384
385#[derive(Debug, serde::Serialize)]
386pub struct SelectAreasResponse {
387    pub status: Option<u8>,
388    pub status_text: Option<String>,
389}
390
391#[derive(Debug, serde::Serialize)]
392pub struct SkipAreaResponse {
393    pub status: Option<u8>,
394    pub status_text: Option<String>,
395}
396
397// Command response decoders
398
399/// Decode SelectAreasResponse command response (01)
400pub fn decode_select_areas_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SelectAreasResponse> {
401    if let tlv::TlvItemValue::List(_fields) = inp {
402        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
403        Ok(SelectAreasResponse {
404                status: item.get_int(&[0]).map(|v| v as u8),
405                status_text: item.get_string_owned(&[1]),
406        })
407    } else {
408        Err(anyhow::anyhow!("Expected struct fields"))
409    }
410}
411
412/// Decode SkipAreaResponse command response (03)
413pub fn decode_skip_area_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SkipAreaResponse> {
414    if let tlv::TlvItemValue::List(_fields) = inp {
415        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
416        Ok(SkipAreaResponse {
417                status: item.get_int(&[0]).map(|v| v as u8),
418                status_text: item.get_string_owned(&[1]),
419        })
420    } else {
421        Err(anyhow::anyhow!("Expected struct fields"))
422    }
423}
424