matc/clusters/codec/
general_commissioning_cluster.rs

1//! Matter TLV encoders and decoders for General Commissioning Cluster
2//! Cluster ID: 0x0030
3//!
4//! This file is automatically generated from GeneralCommissioningCluster.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 CommissioningError {
16    /// No error
17    Ok = 0,
18    /// Attempting to set regulatory configuration to a region or indoor/outdoor mode for which the server does not have proper configuration.
19    Valueoutsiderange = 1,
20    /// Executed CommissioningComplete outside CASE session.
21    Invalidauthentication = 2,
22    /// Executed CommissioningComplete when there was no active Fail-Safe context.
23    Nofailsafe = 3,
24    /// Attempting to arm fail-safe or execute CommissioningComplete from a fabric different than the one associated with the current fail-safe context.
25    Busywithotheradmin = 4,
26    /// One or more required TC features from the Enhanced Setup Flow were not accepted.
27    Requiredtcnotaccepted = 5,
28    /// TCAcknowledgementsNotReceived No or insufficient acknowledgements from the user for the TC features were received.
29    Tcacknowledgementsnotreceived = 6,
30    /// TCMinVersionNotMet The version of the TC features acknowledged by the user did not meet the minimum required version.
31    Tcminversionnotmet = 7,
32}
33
34impl CommissioningError {
35    /// Convert from u8 value
36    pub fn from_u8(value: u8) -> Option<Self> {
37        match value {
38            0 => Some(CommissioningError::Ok),
39            1 => Some(CommissioningError::Valueoutsiderange),
40            2 => Some(CommissioningError::Invalidauthentication),
41            3 => Some(CommissioningError::Nofailsafe),
42            4 => Some(CommissioningError::Busywithotheradmin),
43            5 => Some(CommissioningError::Requiredtcnotaccepted),
44            6 => Some(CommissioningError::Tcacknowledgementsnotreceived),
45            7 => Some(CommissioningError::Tcminversionnotmet),
46            _ => None,
47        }
48    }
49
50    /// Convert to u8 value
51    pub fn to_u8(self) -> u8 {
52        self as u8
53    }
54}
55
56impl From<CommissioningError> for u8 {
57    fn from(val: CommissioningError) -> Self {
58        val as u8
59    }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
63#[repr(u8)]
64pub enum NetworkRecoveryReason {
65    /// Unspecified / unknown reason of network failure
66    Unspecified = 0,
67    /// Credentials for the configured operational network are not valid
68    Auth = 1,
69    /// Configured network cannot be found (e.g. the device cannot see the configured Wi-Fi SSID, Thread end-node is unable to find a parent router on the PAN)
70    Visibility = 2,
71}
72
73impl NetworkRecoveryReason {
74    /// Convert from u8 value
75    pub fn from_u8(value: u8) -> Option<Self> {
76        match value {
77            0 => Some(NetworkRecoveryReason::Unspecified),
78            1 => Some(NetworkRecoveryReason::Auth),
79            2 => Some(NetworkRecoveryReason::Visibility),
80            _ => None,
81        }
82    }
83
84    /// Convert to u8 value
85    pub fn to_u8(self) -> u8 {
86        self as u8
87    }
88}
89
90impl From<NetworkRecoveryReason> for u8 {
91    fn from(val: NetworkRecoveryReason) -> Self {
92        val as u8
93    }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
97#[repr(u8)]
98pub enum RegulatoryLocationType {
99    /// Indoor only
100    Indoor = 0,
101    /// Outdoor only
102    Outdoor = 1,
103    /// Indoor/Outdoor
104    Indooroutdoor = 2,
105}
106
107impl RegulatoryLocationType {
108    /// Convert from u8 value
109    pub fn from_u8(value: u8) -> Option<Self> {
110        match value {
111            0 => Some(RegulatoryLocationType::Indoor),
112            1 => Some(RegulatoryLocationType::Outdoor),
113            2 => Some(RegulatoryLocationType::Indooroutdoor),
114            _ => None,
115        }
116    }
117
118    /// Convert to u8 value
119    pub fn to_u8(self) -> u8 {
120        self as u8
121    }
122}
123
124impl From<RegulatoryLocationType> for u8 {
125    fn from(val: RegulatoryLocationType) -> Self {
126        val as u8
127    }
128}
129
130// Struct definitions
131
132#[derive(Debug, serde::Serialize)]
133pub struct BasicCommissioningInfo {
134    pub fail_safe_expiry_length_seconds: Option<u16>,
135    pub max_cumulative_failsafe_seconds: Option<u16>,
136}
137
138// Command encoders
139
140/// Encode ArmFailSafe command (0x00)
141pub fn encode_arm_fail_safe(expiry_length_seconds: u16, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
142    let tlv = tlv::TlvItemEnc {
143        tag: 0,
144        value: tlv::TlvItemValueEnc::StructInvisible(vec![
145        (0, tlv::TlvItemValueEnc::UInt16(expiry_length_seconds)).into(),
146        (1, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
147        ]),
148    };
149    Ok(tlv.encode()?)
150}
151
152/// Encode SetRegulatoryConfig command (0x02)
153pub fn encode_set_regulatory_config(new_regulatory_config: RegulatoryLocationType, country_code: String, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
154    let tlv = tlv::TlvItemEnc {
155        tag: 0,
156        value: tlv::TlvItemValueEnc::StructInvisible(vec![
157        (0, tlv::TlvItemValueEnc::UInt8(new_regulatory_config.to_u8())).into(),
158        (1, tlv::TlvItemValueEnc::String(country_code)).into(),
159        (2, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
160        ]),
161    };
162    Ok(tlv.encode()?)
163}
164
165/// Encode SetTCAcknowledgements command (0x06)
166pub fn encode_set_tc_acknowledgements(tc_version: u16, tc_user_response: u8) -> anyhow::Result<Vec<u8>> {
167    let tlv = tlv::TlvItemEnc {
168        tag: 0,
169        value: tlv::TlvItemValueEnc::StructInvisible(vec![
170        (0, tlv::TlvItemValueEnc::UInt16(tc_version)).into(),
171        (1, tlv::TlvItemValueEnc::UInt8(tc_user_response)).into(),
172        ]),
173    };
174    Ok(tlv.encode()?)
175}
176
177// Attribute decoders
178
179/// Decode Breadcrumb attribute (0x0000)
180pub fn decode_breadcrumb(inp: &tlv::TlvItemValue) -> anyhow::Result<u64> {
181    if let tlv::TlvItemValue::Int(v) = inp {
182        Ok(*v)
183    } else {
184        Err(anyhow::anyhow!("Expected UInt64"))
185    }
186}
187
188/// Decode BasicCommissioningInfo attribute (0x0001)
189pub fn decode_basic_commissioning_info(inp: &tlv::TlvItemValue) -> anyhow::Result<BasicCommissioningInfo> {
190    if let tlv::TlvItemValue::List(_fields) = inp {
191        // Struct with fields
192        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
193        Ok(BasicCommissioningInfo {
194                fail_safe_expiry_length_seconds: item.get_int(&[0]).map(|v| v as u16),
195                max_cumulative_failsafe_seconds: item.get_int(&[1]).map(|v| v as u16),
196        })
197    } else {
198        Err(anyhow::anyhow!("Expected struct fields"))
199    }
200}
201
202/// Decode RegulatoryConfig attribute (0x0002)
203pub fn decode_regulatory_config(inp: &tlv::TlvItemValue) -> anyhow::Result<RegulatoryLocationType> {
204    if let tlv::TlvItemValue::Int(v) = inp {
205        RegulatoryLocationType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
206    } else {
207        Err(anyhow::anyhow!("Expected Integer"))
208    }
209}
210
211/// Decode LocationCapability attribute (0x0003)
212pub fn decode_location_capability(inp: &tlv::TlvItemValue) -> anyhow::Result<RegulatoryLocationType> {
213    if let tlv::TlvItemValue::Int(v) = inp {
214        RegulatoryLocationType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
215    } else {
216        Err(anyhow::anyhow!("Expected Integer"))
217    }
218}
219
220/// Decode SupportsConcurrentConnection attribute (0x0004)
221pub fn decode_supports_concurrent_connection(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
222    if let tlv::TlvItemValue::Bool(v) = inp {
223        Ok(*v)
224    } else {
225        Err(anyhow::anyhow!("Expected Bool"))
226    }
227}
228
229/// Decode TCAcceptedVersion attribute (0x0005)
230pub fn decode_tc_accepted_version(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
231    if let tlv::TlvItemValue::Int(v) = inp {
232        Ok(*v as u16)
233    } else {
234        Err(anyhow::anyhow!("Expected UInt16"))
235    }
236}
237
238/// Decode TCMinRequiredVersion attribute (0x0006)
239pub fn decode_tc_min_required_version(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
240    if let tlv::TlvItemValue::Int(v) = inp {
241        Ok(*v as u16)
242    } else {
243        Err(anyhow::anyhow!("Expected UInt16"))
244    }
245}
246
247/// Decode TCAcknowledgements attribute (0x0007)
248pub fn decode_tc_acknowledgements(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
249    if let tlv::TlvItemValue::Int(v) = inp {
250        Ok(*v as u8)
251    } else {
252        Err(anyhow::anyhow!("Expected UInt8"))
253    }
254}
255
256/// Decode TCAcknowledgementsRequired attribute (0x0008)
257pub fn decode_tc_acknowledgements_required(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
258    if let tlv::TlvItemValue::Bool(v) = inp {
259        Ok(*v)
260    } else {
261        Err(anyhow::anyhow!("Expected Bool"))
262    }
263}
264
265/// Decode TCUpdateDeadline attribute (0x0009)
266pub fn decode_tc_update_deadline(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u32>> {
267    if let tlv::TlvItemValue::Int(v) = inp {
268        Ok(Some(*v as u32))
269    } else {
270        Ok(None)
271    }
272}
273
274/// Decode RecoveryIdentifier attribute (0x000A)
275pub fn decode_recovery_identifier(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<u8>> {
276    if let tlv::TlvItemValue::OctetString(v) = inp {
277        Ok(v.clone())
278    } else {
279        Err(anyhow::anyhow!("Expected OctetString"))
280    }
281}
282
283/// Decode NetworkRecoveryReason attribute (0x000B)
284pub fn decode_network_recovery_reason(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<NetworkRecoveryReason>> {
285    if let tlv::TlvItemValue::Int(v) = inp {
286        Ok(NetworkRecoveryReason::from_u8(*v as u8))
287    } else {
288        Ok(None)
289    }
290}
291
292/// Decode IsCommissioningWithoutPower attribute (0x000C)
293pub fn decode_is_commissioning_without_power(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
294    if let tlv::TlvItemValue::Bool(v) = inp {
295        Ok(*v)
296    } else {
297        Err(anyhow::anyhow!("Expected Bool"))
298    }
299}
300
301
302// JSON dispatcher function
303
304/// Decode attribute value and return as JSON string
305///
306/// # Parameters
307/// * `cluster_id` - The cluster identifier
308/// * `attribute_id` - The attribute identifier
309/// * `tlv_value` - The TLV value to decode
310///
311/// # Returns
312/// JSON string representation of the decoded value or error
313pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
314    // Verify this is the correct cluster
315    if cluster_id != 0x0030 {
316        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0030, got {}\"}}", cluster_id);
317    }
318
319    match attribute_id {
320        0x0000 => {
321            match decode_breadcrumb(tlv_value) {
322                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
323                Err(e) => format!("{{\"error\": \"{}\"}}", e),
324            }
325        }
326        0x0001 => {
327            match decode_basic_commissioning_info(tlv_value) {
328                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
329                Err(e) => format!("{{\"error\": \"{}\"}}", e),
330            }
331        }
332        0x0002 => {
333            match decode_regulatory_config(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        0x0003 => {
339            match decode_location_capability(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        0x0004 => {
345            match decode_supports_concurrent_connection(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        0x0005 => {
351            match decode_tc_accepted_version(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        0x0006 => {
357            match decode_tc_min_required_version(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        0x0007 => {
363            match decode_tc_acknowledgements(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        0x0008 => {
369            match decode_tc_acknowledgements_required(tlv_value) {
370                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
371                Err(e) => format!("{{\"error\": \"{}\"}}", e),
372            }
373        }
374        0x0009 => {
375            match decode_tc_update_deadline(tlv_value) {
376                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
377                Err(e) => format!("{{\"error\": \"{}\"}}", e),
378            }
379        }
380        0x000A => {
381            match decode_recovery_identifier(tlv_value) {
382                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
383                Err(e) => format!("{{\"error\": \"{}\"}}", e),
384            }
385        }
386        0x000B => {
387            match decode_network_recovery_reason(tlv_value) {
388                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
389                Err(e) => format!("{{\"error\": \"{}\"}}", e),
390            }
391        }
392        0x000C => {
393            match decode_is_commissioning_without_power(tlv_value) {
394                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
395                Err(e) => format!("{{\"error\": \"{}\"}}", e),
396            }
397        }
398        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
399    }
400}
401
402/// Get list of all attributes supported by this cluster
403///
404/// # Returns
405/// Vector of tuples containing (attribute_id, attribute_name)
406pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
407    vec![
408        (0x0000, "Breadcrumb"),
409        (0x0001, "BasicCommissioningInfo"),
410        (0x0002, "RegulatoryConfig"),
411        (0x0003, "LocationCapability"),
412        (0x0004, "SupportsConcurrentConnection"),
413        (0x0005, "TCAcceptedVersion"),
414        (0x0006, "TCMinRequiredVersion"),
415        (0x0007, "TCAcknowledgements"),
416        (0x0008, "TCAcknowledgementsRequired"),
417        (0x0009, "TCUpdateDeadline"),
418        (0x000A, "RecoveryIdentifier"),
419        (0x000B, "NetworkRecoveryReason"),
420        (0x000C, "IsCommissioningWithoutPower"),
421    ]
422}
423
424#[derive(Debug, serde::Serialize)]
425pub struct ArmFailSafeResponse {
426    pub error_code: Option<CommissioningError>,
427    pub debug_text: Option<String>,
428}
429
430#[derive(Debug, serde::Serialize)]
431pub struct SetRegulatoryConfigResponse {
432    pub error_code: Option<CommissioningError>,
433    pub debug_text: Option<String>,
434}
435
436#[derive(Debug, serde::Serialize)]
437pub struct CommissioningCompleteResponse {
438    pub error_code: Option<CommissioningError>,
439    pub debug_text: Option<String>,
440}
441
442#[derive(Debug, serde::Serialize)]
443pub struct SetTCAcknowledgementsResponse {
444    pub error_code: Option<CommissioningError>,
445}
446
447// Command response decoders
448
449/// Decode ArmFailSafeResponse command response (01)
450pub fn decode_arm_fail_safe_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ArmFailSafeResponse> {
451    if let tlv::TlvItemValue::List(_fields) = inp {
452        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
453        Ok(ArmFailSafeResponse {
454                error_code: item.get_int(&[0]).and_then(|v| CommissioningError::from_u8(v as u8)),
455                debug_text: item.get_string_owned(&[1]),
456        })
457    } else {
458        Err(anyhow::anyhow!("Expected struct fields"))
459    }
460}
461
462/// Decode SetRegulatoryConfigResponse command response (03)
463pub fn decode_set_regulatory_config_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SetRegulatoryConfigResponse> {
464    if let tlv::TlvItemValue::List(_fields) = inp {
465        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
466        Ok(SetRegulatoryConfigResponse {
467                error_code: item.get_int(&[0]).and_then(|v| CommissioningError::from_u8(v as u8)),
468                debug_text: item.get_string_owned(&[1]),
469        })
470    } else {
471        Err(anyhow::anyhow!("Expected struct fields"))
472    }
473}
474
475/// Decode CommissioningCompleteResponse command response (05)
476pub fn decode_commissioning_complete_response(inp: &tlv::TlvItemValue) -> anyhow::Result<CommissioningCompleteResponse> {
477    if let tlv::TlvItemValue::List(_fields) = inp {
478        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
479        Ok(CommissioningCompleteResponse {
480                error_code: item.get_int(&[0]).and_then(|v| CommissioningError::from_u8(v as u8)),
481                debug_text: item.get_string_owned(&[1]),
482        })
483    } else {
484        Err(anyhow::anyhow!("Expected struct fields"))
485    }
486}
487
488/// Decode SetTCAcknowledgementsResponse command response (07)
489pub fn decode_set_tc_acknowledgements_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SetTCAcknowledgementsResponse> {
490    if let tlv::TlvItemValue::List(_fields) = inp {
491        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
492        Ok(SetTCAcknowledgementsResponse {
493                error_code: item.get_int(&[0]).and_then(|v| CommissioningError::from_u8(v as u8)),
494        })
495    } else {
496        Err(anyhow::anyhow!("Expected struct fields"))
497    }
498}
499