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
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 CommissioningError {
18    /// No error
19    Ok = 0,
20    /// Attempting to set regulatory configuration to a region or indoor/outdoor mode for which the server does not have proper configuration.
21    Valueoutsiderange = 1,
22    /// Executed CommissioningComplete outside CASE session.
23    Invalidauthentication = 2,
24    /// Executed CommissioningComplete when there was no active Fail-Safe context.
25    Nofailsafe = 3,
26    /// Attempting to arm fail-safe or execute CommissioningComplete from a fabric different than the one associated with the current fail-safe context.
27    Busywithotheradmin = 4,
28    /// One or more required TC features from the Enhanced Setup Flow were not accepted.
29    Requiredtcnotaccepted = 5,
30    /// TCAcknowledgementsNotReceived No or insufficient acknowledgements from the user for the TC features were received.
31    Tcacknowledgementsnotreceived = 6,
32    /// TCMinVersionNotMet The version of the TC features acknowledged by the user did not meet the minimum required version.
33    Tcminversionnotmet = 7,
34}
35
36impl CommissioningError {
37    /// Convert from u8 value
38    pub fn from_u8(value: u8) -> Option<Self> {
39        match value {
40            0 => Some(CommissioningError::Ok),
41            1 => Some(CommissioningError::Valueoutsiderange),
42            2 => Some(CommissioningError::Invalidauthentication),
43            3 => Some(CommissioningError::Nofailsafe),
44            4 => Some(CommissioningError::Busywithotheradmin),
45            5 => Some(CommissioningError::Requiredtcnotaccepted),
46            6 => Some(CommissioningError::Tcacknowledgementsnotreceived),
47            7 => Some(CommissioningError::Tcminversionnotmet),
48            _ => None,
49        }
50    }
51
52    /// Convert to u8 value
53    pub fn to_u8(self) -> u8 {
54        self as u8
55    }
56}
57
58impl From<CommissioningError> for u8 {
59    fn from(val: CommissioningError) -> Self {
60        val as u8
61    }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
65#[repr(u8)]
66pub enum NetworkRecoveryReason {
67    /// Unspecified / unknown reason of network failure
68    Unspecified = 0,
69    /// Credentials for the configured operational network are not valid
70    Auth = 1,
71    /// 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)
72    Visibility = 2,
73}
74
75impl NetworkRecoveryReason {
76    /// Convert from u8 value
77    pub fn from_u8(value: u8) -> Option<Self> {
78        match value {
79            0 => Some(NetworkRecoveryReason::Unspecified),
80            1 => Some(NetworkRecoveryReason::Auth),
81            2 => Some(NetworkRecoveryReason::Visibility),
82            _ => None,
83        }
84    }
85
86    /// Convert to u8 value
87    pub fn to_u8(self) -> u8 {
88        self as u8
89    }
90}
91
92impl From<NetworkRecoveryReason> for u8 {
93    fn from(val: NetworkRecoveryReason) -> Self {
94        val as u8
95    }
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
99#[repr(u8)]
100pub enum RegulatoryLocationType {
101    /// Indoor only
102    Indoor = 0,
103    /// Outdoor only
104    Outdoor = 1,
105    /// Indoor/Outdoor
106    Indooroutdoor = 2,
107}
108
109impl RegulatoryLocationType {
110    /// Convert from u8 value
111    pub fn from_u8(value: u8) -> Option<Self> {
112        match value {
113            0 => Some(RegulatoryLocationType::Indoor),
114            1 => Some(RegulatoryLocationType::Outdoor),
115            2 => Some(RegulatoryLocationType::Indooroutdoor),
116            _ => None,
117        }
118    }
119
120    /// Convert to u8 value
121    pub fn to_u8(self) -> u8 {
122        self as u8
123    }
124}
125
126impl From<RegulatoryLocationType> for u8 {
127    fn from(val: RegulatoryLocationType) -> Self {
128        val as u8
129    }
130}
131
132// Struct definitions
133
134#[derive(Debug, serde::Serialize)]
135pub struct BasicCommissioningInfo {
136    pub fail_safe_expiry_length_seconds: Option<u16>,
137    pub max_cumulative_failsafe_seconds: Option<u16>,
138}
139
140// Command encoders
141
142/// Encode ArmFailSafe command (0x00)
143pub fn encode_arm_fail_safe(expiry_length_seconds: u16, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
144    let tlv = tlv::TlvItemEnc {
145        tag: 0,
146        value: tlv::TlvItemValueEnc::StructInvisible(vec![
147        (0, tlv::TlvItemValueEnc::UInt16(expiry_length_seconds)).into(),
148        (1, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
149        ]),
150    };
151    Ok(tlv.encode()?)
152}
153
154/// Encode SetRegulatoryConfig command (0x02)
155pub fn encode_set_regulatory_config(new_regulatory_config: RegulatoryLocationType, country_code: String, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
156    let tlv = tlv::TlvItemEnc {
157        tag: 0,
158        value: tlv::TlvItemValueEnc::StructInvisible(vec![
159        (0, tlv::TlvItemValueEnc::UInt8(new_regulatory_config.to_u8())).into(),
160        (1, tlv::TlvItemValueEnc::String(country_code)).into(),
161        (2, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
162        ]),
163    };
164    Ok(tlv.encode()?)
165}
166
167/// Encode SetTCAcknowledgements command (0x06)
168pub fn encode_set_tc_acknowledgements(tc_version: u16, tc_user_response: u8) -> anyhow::Result<Vec<u8>> {
169    let tlv = tlv::TlvItemEnc {
170        tag: 0,
171        value: tlv::TlvItemValueEnc::StructInvisible(vec![
172        (0, tlv::TlvItemValueEnc::UInt16(tc_version)).into(),
173        (1, tlv::TlvItemValueEnc::UInt8(tc_user_response)).into(),
174        ]),
175    };
176    Ok(tlv.encode()?)
177}
178
179// Attribute decoders
180
181/// Decode Breadcrumb attribute (0x0000)
182pub fn decode_breadcrumb(inp: &tlv::TlvItemValue) -> anyhow::Result<u64> {
183    if let tlv::TlvItemValue::Int(v) = inp {
184        Ok(*v)
185    } else {
186        Err(anyhow::anyhow!("Expected UInt64"))
187    }
188}
189
190/// Decode BasicCommissioningInfo attribute (0x0001)
191pub fn decode_basic_commissioning_info(inp: &tlv::TlvItemValue) -> anyhow::Result<BasicCommissioningInfo> {
192    if let tlv::TlvItemValue::List(_fields) = inp {
193        // Struct with fields
194        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
195        Ok(BasicCommissioningInfo {
196                fail_safe_expiry_length_seconds: item.get_int(&[0]).map(|v| v as u16),
197                max_cumulative_failsafe_seconds: item.get_int(&[1]).map(|v| v as u16),
198        })
199    } else {
200        Err(anyhow::anyhow!("Expected struct fields"))
201    }
202}
203
204/// Decode RegulatoryConfig attribute (0x0002)
205pub fn decode_regulatory_config(inp: &tlv::TlvItemValue) -> anyhow::Result<RegulatoryLocationType> {
206    if let tlv::TlvItemValue::Int(v) = inp {
207        RegulatoryLocationType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
208    } else {
209        Err(anyhow::anyhow!("Expected Integer"))
210    }
211}
212
213/// Decode LocationCapability attribute (0x0003)
214pub fn decode_location_capability(inp: &tlv::TlvItemValue) -> anyhow::Result<RegulatoryLocationType> {
215    if let tlv::TlvItemValue::Int(v) = inp {
216        RegulatoryLocationType::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
217    } else {
218        Err(anyhow::anyhow!("Expected Integer"))
219    }
220}
221
222/// Decode SupportsConcurrentConnection attribute (0x0004)
223pub fn decode_supports_concurrent_connection(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
224    if let tlv::TlvItemValue::Bool(v) = inp {
225        Ok(*v)
226    } else {
227        Err(anyhow::anyhow!("Expected Bool"))
228    }
229}
230
231/// Decode TCAcceptedVersion attribute (0x0005)
232pub fn decode_tc_accepted_version(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
233    if let tlv::TlvItemValue::Int(v) = inp {
234        Ok(*v as u16)
235    } else {
236        Err(anyhow::anyhow!("Expected UInt16"))
237    }
238}
239
240/// Decode TCMinRequiredVersion attribute (0x0006)
241pub fn decode_tc_min_required_version(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
242    if let tlv::TlvItemValue::Int(v) = inp {
243        Ok(*v as u16)
244    } else {
245        Err(anyhow::anyhow!("Expected UInt16"))
246    }
247}
248
249/// Decode TCAcknowledgements attribute (0x0007)
250pub fn decode_tc_acknowledgements(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
251    if let tlv::TlvItemValue::Int(v) = inp {
252        Ok(*v as u8)
253    } else {
254        Err(anyhow::anyhow!("Expected UInt8"))
255    }
256}
257
258/// Decode TCAcknowledgementsRequired attribute (0x0008)
259pub fn decode_tc_acknowledgements_required(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
260    if let tlv::TlvItemValue::Bool(v) = inp {
261        Ok(*v)
262    } else {
263        Err(anyhow::anyhow!("Expected Bool"))
264    }
265}
266
267/// Decode TCUpdateDeadline attribute (0x0009)
268pub fn decode_tc_update_deadline(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u32>> {
269    if let tlv::TlvItemValue::Int(v) = inp {
270        Ok(Some(*v as u32))
271    } else {
272        Ok(None)
273    }
274}
275
276/// Decode RecoveryIdentifier attribute (0x000A)
277pub fn decode_recovery_identifier(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<u8>> {
278    if let tlv::TlvItemValue::OctetString(v) = inp {
279        Ok(v.clone())
280    } else {
281        Err(anyhow::anyhow!("Expected OctetString"))
282    }
283}
284
285/// Decode NetworkRecoveryReason attribute (0x000B)
286pub fn decode_network_recovery_reason(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<NetworkRecoveryReason>> {
287    if let tlv::TlvItemValue::Int(v) = inp {
288        Ok(NetworkRecoveryReason::from_u8(*v as u8))
289    } else {
290        Ok(None)
291    }
292}
293
294/// Decode IsCommissioningWithoutPower attribute (0x000C)
295pub fn decode_is_commissioning_without_power(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
296    if let tlv::TlvItemValue::Bool(v) = inp {
297        Ok(*v)
298    } else {
299        Err(anyhow::anyhow!("Expected Bool"))
300    }
301}
302
303
304// JSON dispatcher function
305
306/// Decode attribute value and return as JSON string
307///
308/// # Parameters
309/// * `cluster_id` - The cluster identifier
310/// * `attribute_id` - The attribute identifier
311/// * `tlv_value` - The TLV value to decode
312///
313/// # Returns
314/// JSON string representation of the decoded value or error
315pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
316    // Verify this is the correct cluster
317    if cluster_id != 0x0030 {
318        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0030, got {}\"}}", cluster_id);
319    }
320
321    match attribute_id {
322        0x0000 => {
323            match decode_breadcrumb(tlv_value) {
324                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
325                Err(e) => format!("{{\"error\": \"{}\"}}", e),
326            }
327        }
328        0x0001 => {
329            match decode_basic_commissioning_info(tlv_value) {
330                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
331                Err(e) => format!("{{\"error\": \"{}\"}}", e),
332            }
333        }
334        0x0002 => {
335            match decode_regulatory_config(tlv_value) {
336                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
337                Err(e) => format!("{{\"error\": \"{}\"}}", e),
338            }
339        }
340        0x0003 => {
341            match decode_location_capability(tlv_value) {
342                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
343                Err(e) => format!("{{\"error\": \"{}\"}}", e),
344            }
345        }
346        0x0004 => {
347            match decode_supports_concurrent_connection(tlv_value) {
348                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
349                Err(e) => format!("{{\"error\": \"{}\"}}", e),
350            }
351        }
352        0x0005 => {
353            match decode_tc_accepted_version(tlv_value) {
354                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
355                Err(e) => format!("{{\"error\": \"{}\"}}", e),
356            }
357        }
358        0x0006 => {
359            match decode_tc_min_required_version(tlv_value) {
360                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
361                Err(e) => format!("{{\"error\": \"{}\"}}", e),
362            }
363        }
364        0x0007 => {
365            match decode_tc_acknowledgements(tlv_value) {
366                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
367                Err(e) => format!("{{\"error\": \"{}\"}}", e),
368            }
369        }
370        0x0008 => {
371            match decode_tc_acknowledgements_required(tlv_value) {
372                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
373                Err(e) => format!("{{\"error\": \"{}\"}}", e),
374            }
375        }
376        0x0009 => {
377            match decode_tc_update_deadline(tlv_value) {
378                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
379                Err(e) => format!("{{\"error\": \"{}\"}}", e),
380            }
381        }
382        0x000A => {
383            match decode_recovery_identifier(tlv_value) {
384                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
385                Err(e) => format!("{{\"error\": \"{}\"}}", e),
386            }
387        }
388        0x000B => {
389            match decode_network_recovery_reason(tlv_value) {
390                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
391                Err(e) => format!("{{\"error\": \"{}\"}}", e),
392            }
393        }
394        0x000C => {
395            match decode_is_commissioning_without_power(tlv_value) {
396                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
397                Err(e) => format!("{{\"error\": \"{}\"}}", e),
398            }
399        }
400        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
401    }
402}
403
404/// Get list of all attributes supported by this cluster
405///
406/// # Returns
407/// Vector of tuples containing (attribute_id, attribute_name)
408pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
409    vec![
410        (0x0000, "Breadcrumb"),
411        (0x0001, "BasicCommissioningInfo"),
412        (0x0002, "RegulatoryConfig"),
413        (0x0003, "LocationCapability"),
414        (0x0004, "SupportsConcurrentConnection"),
415        (0x0005, "TCAcceptedVersion"),
416        (0x0006, "TCMinRequiredVersion"),
417        (0x0007, "TCAcknowledgements"),
418        (0x0008, "TCAcknowledgementsRequired"),
419        (0x0009, "TCUpdateDeadline"),
420        (0x000A, "RecoveryIdentifier"),
421        (0x000B, "NetworkRecoveryReason"),
422        (0x000C, "IsCommissioningWithoutPower"),
423    ]
424}
425
426// Command listing
427
428pub fn get_command_list() -> Vec<(u32, &'static str)> {
429    vec![
430        (0x00, "ArmFailSafe"),
431        (0x02, "SetRegulatoryConfig"),
432        (0x04, "CommissioningComplete"),
433        (0x06, "SetTCAcknowledgements"),
434    ]
435}
436
437pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
438    match cmd_id {
439        0x00 => Some("ArmFailSafe"),
440        0x02 => Some("SetRegulatoryConfig"),
441        0x04 => Some("CommissioningComplete"),
442        0x06 => Some("SetTCAcknowledgements"),
443        _ => None,
444    }
445}
446
447pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
448    match cmd_id {
449        0x00 => Some(vec![
450            crate::clusters::codec::CommandField { tag: 0, name: "expiry_length_seconds", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
451            crate::clusters::codec::CommandField { tag: 1, name: "breadcrumb", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: false },
452        ]),
453        0x02 => Some(vec![
454            crate::clusters::codec::CommandField { tag: 0, name: "new_regulatory_config", kind: crate::clusters::codec::FieldKind::Enum { name: "RegulatoryLocationType", variants: &[(0, "Indoor"), (1, "Outdoor"), (2, "Indooroutdoor")] }, optional: false, nullable: false },
455            crate::clusters::codec::CommandField { tag: 1, name: "country_code", kind: crate::clusters::codec::FieldKind::String, optional: false, nullable: false },
456            crate::clusters::codec::CommandField { tag: 2, name: "breadcrumb", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: false },
457        ]),
458        0x04 => Some(vec![]),
459        0x06 => Some(vec![
460            crate::clusters::codec::CommandField { tag: 0, name: "tc_version", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
461            crate::clusters::codec::CommandField { tag: 1, name: "tc_user_response", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
462        ]),
463        _ => None,
464    }
465}
466
467pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
468    match cmd_id {
469        0x00 => {
470        let expiry_length_seconds = crate::clusters::codec::json_util::get_u16(args, "expiry_length_seconds")?;
471        let breadcrumb = crate::clusters::codec::json_util::get_u64(args, "breadcrumb")?;
472        encode_arm_fail_safe(expiry_length_seconds, breadcrumb)
473        }
474        0x02 => {
475        let new_regulatory_config = {
476            let n = crate::clusters::codec::json_util::get_u64(args, "new_regulatory_config")?;
477            RegulatoryLocationType::from_u8(n as u8).ok_or_else(|| anyhow::anyhow!("invalid RegulatoryLocationType: {}", n))?
478        };
479        let country_code = crate::clusters::codec::json_util::get_string(args, "country_code")?;
480        let breadcrumb = crate::clusters::codec::json_util::get_u64(args, "breadcrumb")?;
481        encode_set_regulatory_config(new_regulatory_config, country_code, breadcrumb)
482        }
483        0x04 => Ok(vec![]),
484        0x06 => {
485        let tc_version = crate::clusters::codec::json_util::get_u16(args, "tc_version")?;
486        let tc_user_response = crate::clusters::codec::json_util::get_u8(args, "tc_user_response")?;
487        encode_set_tc_acknowledgements(tc_version, tc_user_response)
488        }
489        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
490    }
491}
492
493#[derive(Debug, serde::Serialize)]
494pub struct ArmFailSafeResponse {
495    pub error_code: Option<CommissioningError>,
496    pub debug_text: Option<String>,
497}
498
499#[derive(Debug, serde::Serialize)]
500pub struct SetRegulatoryConfigResponse {
501    pub error_code: Option<CommissioningError>,
502    pub debug_text: Option<String>,
503}
504
505#[derive(Debug, serde::Serialize)]
506pub struct CommissioningCompleteResponse {
507    pub error_code: Option<CommissioningError>,
508    pub debug_text: Option<String>,
509}
510
511#[derive(Debug, serde::Serialize)]
512pub struct SetTCAcknowledgementsResponse {
513    pub error_code: Option<CommissioningError>,
514}
515
516// Command response decoders
517
518/// Decode ArmFailSafeResponse command response (01)
519pub fn decode_arm_fail_safe_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ArmFailSafeResponse> {
520    if let tlv::TlvItemValue::List(_fields) = inp {
521        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
522        Ok(ArmFailSafeResponse {
523                error_code: item.get_int(&[0]).and_then(|v| CommissioningError::from_u8(v as u8)),
524                debug_text: item.get_string_owned(&[1]),
525        })
526    } else {
527        Err(anyhow::anyhow!("Expected struct fields"))
528    }
529}
530
531/// Decode SetRegulatoryConfigResponse command response (03)
532pub fn decode_set_regulatory_config_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SetRegulatoryConfigResponse> {
533    if let tlv::TlvItemValue::List(_fields) = inp {
534        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
535        Ok(SetRegulatoryConfigResponse {
536                error_code: item.get_int(&[0]).and_then(|v| CommissioningError::from_u8(v as u8)),
537                debug_text: item.get_string_owned(&[1]),
538        })
539    } else {
540        Err(anyhow::anyhow!("Expected struct fields"))
541    }
542}
543
544/// Decode CommissioningCompleteResponse command response (05)
545pub fn decode_commissioning_complete_response(inp: &tlv::TlvItemValue) -> anyhow::Result<CommissioningCompleteResponse> {
546    if let tlv::TlvItemValue::List(_fields) = inp {
547        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
548        Ok(CommissioningCompleteResponse {
549                error_code: item.get_int(&[0]).and_then(|v| CommissioningError::from_u8(v as u8)),
550                debug_text: item.get_string_owned(&[1]),
551        })
552    } else {
553        Err(anyhow::anyhow!("Expected struct fields"))
554    }
555}
556
557/// Decode SetTCAcknowledgementsResponse command response (07)
558pub fn decode_set_tc_acknowledgements_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SetTCAcknowledgementsResponse> {
559    if let tlv::TlvItemValue::List(_fields) = inp {
560        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
561        Ok(SetTCAcknowledgementsResponse {
562                error_code: item.get_int(&[0]).and_then(|v| CommissioningError::from_u8(v as u8)),
563        })
564    } else {
565        Err(anyhow::anyhow!("Expected struct fields"))
566    }
567}
568
569// Typed facade (invokes + reads)
570
571/// Invoke `ArmFailSafe` command on cluster `General Commissioning`.
572pub async fn arm_fail_safe(conn: &crate::controller::Connection, endpoint: u16, expiry_length_seconds: u16, breadcrumb: u64) -> anyhow::Result<ArmFailSafeResponse> {
573    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GENERAL_COMMISSIONING, crate::clusters::defs::CLUSTER_GENERAL_COMMISSIONING_CMD_ID_ARMFAILSAFE, &encode_arm_fail_safe(expiry_length_seconds, breadcrumb)?).await?;
574    decode_arm_fail_safe_response(&tlv)
575}
576
577/// Invoke `SetRegulatoryConfig` command on cluster `General Commissioning`.
578pub async fn set_regulatory_config(conn: &crate::controller::Connection, endpoint: u16, new_regulatory_config: RegulatoryLocationType, country_code: String, breadcrumb: u64) -> anyhow::Result<SetRegulatoryConfigResponse> {
579    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GENERAL_COMMISSIONING, crate::clusters::defs::CLUSTER_GENERAL_COMMISSIONING_CMD_ID_SETREGULATORYCONFIG, &encode_set_regulatory_config(new_regulatory_config, country_code, breadcrumb)?).await?;
580    decode_set_regulatory_config_response(&tlv)
581}
582
583/// Invoke `CommissioningComplete` command on cluster `General Commissioning`.
584pub async fn commissioning_complete(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<CommissioningCompleteResponse> {
585    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GENERAL_COMMISSIONING, crate::clusters::defs::CLUSTER_GENERAL_COMMISSIONING_CMD_ID_COMMISSIONINGCOMPLETE, &[]).await?;
586    decode_commissioning_complete_response(&tlv)
587}
588
589/// Invoke `SetTCAcknowledgements` command on cluster `General Commissioning`.
590pub async fn set_tc_acknowledgements(conn: &crate::controller::Connection, endpoint: u16, tc_version: u16, tc_user_response: u8) -> anyhow::Result<SetTCAcknowledgementsResponse> {
591    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GENERAL_COMMISSIONING, crate::clusters::defs::CLUSTER_GENERAL_COMMISSIONING_CMD_ID_SETTCACKNOWLEDGEMENTS, &encode_set_tc_acknowledgements(tc_version, tc_user_response)?).await?;
592    decode_set_tc_acknowledgements_response(&tlv)
593}
594
595/// Read `Breadcrumb` attribute from cluster `General Commissioning`.
596pub async fn read_breadcrumb(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u64> {
597    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GENERAL_COMMISSIONING, crate::clusters::defs::CLUSTER_GENERAL_COMMISSIONING_ATTR_ID_BREADCRUMB).await?;
598    decode_breadcrumb(&tlv)
599}
600
601/// Read `BasicCommissioningInfo` attribute from cluster `General Commissioning`.
602pub async fn read_basic_commissioning_info(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<BasicCommissioningInfo> {
603    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GENERAL_COMMISSIONING, crate::clusters::defs::CLUSTER_GENERAL_COMMISSIONING_ATTR_ID_BASICCOMMISSIONINGINFO).await?;
604    decode_basic_commissioning_info(&tlv)
605}
606
607/// Read `RegulatoryConfig` attribute from cluster `General Commissioning`.
608pub async fn read_regulatory_config(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<RegulatoryLocationType> {
609    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GENERAL_COMMISSIONING, crate::clusters::defs::CLUSTER_GENERAL_COMMISSIONING_ATTR_ID_REGULATORYCONFIG).await?;
610    decode_regulatory_config(&tlv)
611}
612
613/// Read `LocationCapability` attribute from cluster `General Commissioning`.
614pub async fn read_location_capability(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<RegulatoryLocationType> {
615    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GENERAL_COMMISSIONING, crate::clusters::defs::CLUSTER_GENERAL_COMMISSIONING_ATTR_ID_LOCATIONCAPABILITY).await?;
616    decode_location_capability(&tlv)
617}
618
619/// Read `SupportsConcurrentConnection` attribute from cluster `General Commissioning`.
620pub async fn read_supports_concurrent_connection(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
621    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GENERAL_COMMISSIONING, crate::clusters::defs::CLUSTER_GENERAL_COMMISSIONING_ATTR_ID_SUPPORTSCONCURRENTCONNECTION).await?;
622    decode_supports_concurrent_connection(&tlv)
623}
624
625/// Read `TCAcceptedVersion` attribute from cluster `General Commissioning`.
626pub async fn read_tc_accepted_version(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
627    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GENERAL_COMMISSIONING, crate::clusters::defs::CLUSTER_GENERAL_COMMISSIONING_ATTR_ID_TCACCEPTEDVERSION).await?;
628    decode_tc_accepted_version(&tlv)
629}
630
631/// Read `TCMinRequiredVersion` attribute from cluster `General Commissioning`.
632pub async fn read_tc_min_required_version(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
633    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GENERAL_COMMISSIONING, crate::clusters::defs::CLUSTER_GENERAL_COMMISSIONING_ATTR_ID_TCMINREQUIREDVERSION).await?;
634    decode_tc_min_required_version(&tlv)
635}
636
637/// Read `TCAcknowledgements` attribute from cluster `General Commissioning`.
638pub async fn read_tc_acknowledgements(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
639    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GENERAL_COMMISSIONING, crate::clusters::defs::CLUSTER_GENERAL_COMMISSIONING_ATTR_ID_TCACKNOWLEDGEMENTS).await?;
640    decode_tc_acknowledgements(&tlv)
641}
642
643/// Read `TCAcknowledgementsRequired` attribute from cluster `General Commissioning`.
644pub async fn read_tc_acknowledgements_required(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
645    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GENERAL_COMMISSIONING, crate::clusters::defs::CLUSTER_GENERAL_COMMISSIONING_ATTR_ID_TCACKNOWLEDGEMENTSREQUIRED).await?;
646    decode_tc_acknowledgements_required(&tlv)
647}
648
649/// Read `TCUpdateDeadline` attribute from cluster `General Commissioning`.
650pub async fn read_tc_update_deadline(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u32>> {
651    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GENERAL_COMMISSIONING, crate::clusters::defs::CLUSTER_GENERAL_COMMISSIONING_ATTR_ID_TCUPDATEDEADLINE).await?;
652    decode_tc_update_deadline(&tlv)
653}
654
655/// Read `RecoveryIdentifier` attribute from cluster `General Commissioning`.
656pub async fn read_recovery_identifier(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<u8>> {
657    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GENERAL_COMMISSIONING, crate::clusters::defs::CLUSTER_GENERAL_COMMISSIONING_ATTR_ID_RECOVERYIDENTIFIER).await?;
658    decode_recovery_identifier(&tlv)
659}
660
661/// Read `NetworkRecoveryReason` attribute from cluster `General Commissioning`.
662pub async fn read_network_recovery_reason(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<NetworkRecoveryReason>> {
663    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GENERAL_COMMISSIONING, crate::clusters::defs::CLUSTER_GENERAL_COMMISSIONING_ATTR_ID_NETWORKRECOVERYREASON).await?;
664    decode_network_recovery_reason(&tlv)
665}
666
667/// Read `IsCommissioningWithoutPower` attribute from cluster `General Commissioning`.
668pub async fn read_is_commissioning_without_power(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
669    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GENERAL_COMMISSIONING, crate::clusters::defs::CLUSTER_GENERAL_COMMISSIONING_ATTR_ID_ISCOMMISSIONINGWITHOUTPOWER).await?;
670    decode_is_commissioning_without_power(&tlv)
671}
672