matc/clusters/codec/
network_commissioning_cluster.rs

1//! Matter TLV encoders and decoders for Network Commissioning Cluster
2//! Cluster ID: 0x0031
3//!
4//! This file is automatically generated from NetworkCommissioningCluster.xml
5
6use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11// Import serialization helpers for octet strings
12use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
13
14// Enum definitions
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
17#[repr(u8)]
18pub enum NetworkCommissioningStatus {
19    /// OK, no error
20    Success = 0,
21    /// Value Outside Range
22    Outofrange = 1,
23    /// A collection would exceed its size limit
24    Boundsexceeded = 2,
25    /// The NetworkID is not among the collection of added networks
26    Networkidnotfound = 3,
27    /// The NetworkID is already among the collection of added networks
28    Duplicatenetworkid = 4,
29    /// Cannot find AP: SSID Not found
30    Networknotfound = 5,
31    /// Cannot find AP: Mismatch on band/channels/regulatory domain / 2.4GHz vs 5GHz
32    Regulatoryerror = 6,
33    /// Cannot associate due to authentication failure
34    Authfailure = 7,
35    /// Cannot associate due to unsupported security mode
36    Unsupportedsecurity = 8,
37    /// Other association failure
38    Otherconnectionfailure = 9,
39    /// Failure to generate an IPv6 address
40    Ipv6failed = 10,
41    /// Failure to bind Wi-Fi +<->+ IP interfaces
42    Ipbindfailed = 11,
43    /// Unknown error
44    Unknownerror = 12,
45}
46
47impl NetworkCommissioningStatus {
48    /// Convert from u8 value
49    pub fn from_u8(value: u8) -> Option<Self> {
50        match value {
51            0 => Some(NetworkCommissioningStatus::Success),
52            1 => Some(NetworkCommissioningStatus::Outofrange),
53            2 => Some(NetworkCommissioningStatus::Boundsexceeded),
54            3 => Some(NetworkCommissioningStatus::Networkidnotfound),
55            4 => Some(NetworkCommissioningStatus::Duplicatenetworkid),
56            5 => Some(NetworkCommissioningStatus::Networknotfound),
57            6 => Some(NetworkCommissioningStatus::Regulatoryerror),
58            7 => Some(NetworkCommissioningStatus::Authfailure),
59            8 => Some(NetworkCommissioningStatus::Unsupportedsecurity),
60            9 => Some(NetworkCommissioningStatus::Otherconnectionfailure),
61            10 => Some(NetworkCommissioningStatus::Ipv6failed),
62            11 => Some(NetworkCommissioningStatus::Ipbindfailed),
63            12 => Some(NetworkCommissioningStatus::Unknownerror),
64            _ => None,
65        }
66    }
67
68    /// Convert to u8 value
69    pub fn to_u8(self) -> u8 {
70        self as u8
71    }
72}
73
74impl From<NetworkCommissioningStatus> for u8 {
75    fn from(val: NetworkCommissioningStatus) -> Self {
76        val as u8
77    }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
81#[repr(u8)]
82pub enum WiFiBand {
83    /// 2.4GHz - 2.401GHz to 2.495GHz (802.11b/g/n/ax)
84    _2g4 = 0,
85    /// 3.65GHz - 3.655GHz to 3.695GHz (802.11y)
86    _3g65 = 1,
87    /// 5GHz - 5.150GHz to 5.895GHz (802.11a/n/ac/ax)
88    _5g = 2,
89    /// 6GHz - 5.925GHz to 7.125GHz (802.11ax / Wi-Fi 6E)
90    _6g = 3,
91    /// 60GHz - 57.24GHz to 70.20GHz (802.11ad/ay)
92    _60g = 4,
93    /// Sub-1GHz - 755MHz to 931MHz (802.11ah)
94    _1g = 5,
95}
96
97impl WiFiBand {
98    /// Convert from u8 value
99    pub fn from_u8(value: u8) -> Option<Self> {
100        match value {
101            0 => Some(WiFiBand::_2g4),
102            1 => Some(WiFiBand::_3g65),
103            2 => Some(WiFiBand::_5g),
104            3 => Some(WiFiBand::_6g),
105            4 => Some(WiFiBand::_60g),
106            5 => Some(WiFiBand::_1g),
107            _ => None,
108        }
109    }
110
111    /// Convert to u8 value
112    pub fn to_u8(self) -> u8 {
113        self as u8
114    }
115}
116
117impl From<WiFiBand> for u8 {
118    fn from(val: WiFiBand) -> Self {
119        val as u8
120    }
121}
122
123// Bitmap definitions
124
125/// ThreadCapabilities bitmap type
126pub type ThreadCapabilities = u8;
127
128/// Constants for ThreadCapabilities
129pub mod threadcapabilities {
130    /// Thread Border Router functionality is present
131    pub const IS_BORDER_ROUTER_CAPABLE: u8 = 0x01;
132    /// Router mode is supported (interface could be in router or REED mode)
133    pub const IS_ROUTER_CAPABLE: u8 = 0x02;
134    /// Sleepy end-device mode is supported
135    pub const IS_SLEEPY_END_DEVICE_CAPABLE: u8 = 0x04;
136    /// Device is a full Thread device (opposite of Minimal Thread Device)
137    pub const IS_FULL_THREAD_DEVICE: u8 = 0x08;
138    /// Synchronized sleepy end-device mode is supported
139    pub const IS_SYNCHRONIZED_SLEEPY_END_DEVICE_CAPABLE: u8 = 0x10;
140}
141
142/// WiFiSecurity bitmap type
143pub type WiFiSecurity = u8;
144
145/// Constants for WiFiSecurity
146pub mod wifisecurity {
147    /// Supports unencrypted Wi-Fi
148    pub const UNENCRYPTED: u8 = 0x01;
149    /// Supports Wi-Fi using WEP security
150    pub const WEP: u8 = 0x02;
151    /// Supports Wi-Fi using WPA-Personal security
152    pub const WPA_PERSONAL: u8 = 0x04;
153    /// Supports Wi-Fi using WPA2-Personal security
154    pub const WPA2_PERSONAL: u8 = 0x08;
155    /// Supports Wi-Fi using WPA3-Personal security
156    pub const WPA3_PERSONAL: u8 = 0x10;
157}
158
159// Struct definitions
160
161#[derive(Debug, serde::Serialize)]
162pub struct NetworkInfo {
163    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
164    pub network_id: Option<Vec<u8>>,
165    pub connected: Option<bool>,
166}
167
168#[derive(Debug, serde::Serialize)]
169pub struct ThreadInterfaceScanResult {
170    pub pan_id: Option<u16>,
171    pub extended_pan_id: Option<u64>,
172    pub network_name: Option<String>,
173    pub channel: Option<u16>,
174    pub version: Option<u8>,
175    pub extended_address: Option<u8>,
176    pub rssi: Option<i8>,
177    pub lqi: Option<u8>,
178}
179
180#[derive(Debug, serde::Serialize)]
181pub struct WiFiInterfaceScanResult {
182    pub security: Option<WiFiSecurity>,
183    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
184    pub ssid: Option<Vec<u8>>,
185    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
186    pub bssid: Option<Vec<u8>>,
187    pub channel: Option<u16>,
188    pub wifi_band: Option<WiFiBand>,
189    pub rssi: Option<i8>,
190}
191
192// Command encoders
193
194/// Encode ScanNetworks command (0x00)
195pub fn encode_scan_networks(ssid: Option<Vec<u8>>, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
196    let tlv = tlv::TlvItemEnc {
197        tag: 0,
198        value: tlv::TlvItemValueEnc::StructInvisible(vec![
199        (0, tlv::TlvItemValueEnc::OctetString(ssid.unwrap_or_default())).into(),
200        (1, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
201        ]),
202    };
203    Ok(tlv.encode()?)
204}
205
206/// Encode AddOrUpdateWiFiNetwork command (0x02)
207pub fn encode_add_or_update_wifi_network(ssid: Vec<u8>, credentials: Vec<u8>, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
208    let tlv = tlv::TlvItemEnc {
209        tag: 0,
210        value: tlv::TlvItemValueEnc::StructInvisible(vec![
211        (0, tlv::TlvItemValueEnc::OctetString(ssid)).into(),
212        (1, tlv::TlvItemValueEnc::OctetString(credentials)).into(),
213        (2, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
214        ]),
215    };
216    Ok(tlv.encode()?)
217}
218
219/// Encode AddOrUpdateThreadNetwork command (0x03)
220pub fn encode_add_or_update_thread_network(operational_dataset: Vec<u8>, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
221    let tlv = tlv::TlvItemEnc {
222        tag: 0,
223        value: tlv::TlvItemValueEnc::StructInvisible(vec![
224        (0, tlv::TlvItemValueEnc::OctetString(operational_dataset)).into(),
225        (1, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
226        ]),
227    };
228    Ok(tlv.encode()?)
229}
230
231/// Encode RemoveNetwork command (0x04)
232pub fn encode_remove_network(network_id: Vec<u8>, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
233    let tlv = tlv::TlvItemEnc {
234        tag: 0,
235        value: tlv::TlvItemValueEnc::StructInvisible(vec![
236        (0, tlv::TlvItemValueEnc::OctetString(network_id)).into(),
237        (1, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
238        ]),
239    };
240    Ok(tlv.encode()?)
241}
242
243/// Encode ConnectNetwork command (0x06)
244pub fn encode_connect_network(network_id: Vec<u8>, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
245    let tlv = tlv::TlvItemEnc {
246        tag: 0,
247        value: tlv::TlvItemValueEnc::StructInvisible(vec![
248        (0, tlv::TlvItemValueEnc::OctetString(network_id)).into(),
249        (1, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
250        ]),
251    };
252    Ok(tlv.encode()?)
253}
254
255/// Encode ReorderNetwork command (0x08)
256pub fn encode_reorder_network(network_id: Vec<u8>, network_index: u8, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
257    let tlv = tlv::TlvItemEnc {
258        tag: 0,
259        value: tlv::TlvItemValueEnc::StructInvisible(vec![
260        (0, tlv::TlvItemValueEnc::OctetString(network_id)).into(),
261        (1, tlv::TlvItemValueEnc::UInt8(network_index)).into(),
262        (2, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
263        ]),
264    };
265    Ok(tlv.encode()?)
266}
267
268// Attribute decoders
269
270/// Decode MaxNetworks attribute (0x0000)
271pub fn decode_max_networks(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
272    if let tlv::TlvItemValue::Int(v) = inp {
273        Ok(*v as u8)
274    } else {
275        Err(anyhow::anyhow!("Expected UInt8"))
276    }
277}
278
279/// Decode Networks attribute (0x0001)
280pub fn decode_networks(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<NetworkInfo>> {
281    let mut res = Vec::new();
282    if let tlv::TlvItemValue::List(v) = inp {
283        for item in v {
284            res.push(NetworkInfo {
285                network_id: item.get_octet_string_owned(&[0]),
286                connected: item.get_bool(&[1]),
287            });
288        }
289    }
290    Ok(res)
291}
292
293/// Decode ScanMaxTimeSeconds attribute (0x0002)
294pub fn decode_scan_max_time_seconds(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
295    if let tlv::TlvItemValue::Int(v) = inp {
296        Ok(*v as u8)
297    } else {
298        Err(anyhow::anyhow!("Expected UInt8"))
299    }
300}
301
302/// Decode ConnectMaxTimeSeconds attribute (0x0003)
303pub fn decode_connect_max_time_seconds(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
304    if let tlv::TlvItemValue::Int(v) = inp {
305        Ok(*v as u8)
306    } else {
307        Err(anyhow::anyhow!("Expected UInt8"))
308    }
309}
310
311/// Decode InterfaceEnabled attribute (0x0004)
312pub fn decode_interface_enabled(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
313    if let tlv::TlvItemValue::Bool(v) = inp {
314        Ok(*v)
315    } else {
316        Err(anyhow::anyhow!("Expected Bool"))
317    }
318}
319
320/// Decode LastNetworkingStatus attribute (0x0005)
321pub fn decode_last_networking_status(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<NetworkCommissioningStatus>> {
322    if let tlv::TlvItemValue::Int(v) = inp {
323        Ok(NetworkCommissioningStatus::from_u8(*v as u8))
324    } else {
325        Ok(None)
326    }
327}
328
329/// Decode LastNetworkID attribute (0x0006)
330pub fn decode_last_network_id(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Vec<u8>>> {
331    if let tlv::TlvItemValue::OctetString(v) = inp {
332        Ok(Some(v.clone()))
333    } else {
334        Ok(None)
335    }
336}
337
338/// Decode LastConnectErrorValue attribute (0x0007)
339pub fn decode_last_connect_error_value(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<i32>> {
340    if let tlv::TlvItemValue::Int(v) = inp {
341        Ok(Some(*v as i32))
342    } else {
343        Ok(None)
344    }
345}
346
347/// Decode SupportedWiFiBands attribute (0x0008)
348pub fn decode_supported_wifi_bands(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<WiFiBand>> {
349    let mut res = Vec::new();
350    if let tlv::TlvItemValue::List(v) = inp {
351        for item in v {
352            if let tlv::TlvItemValue::Int(i) = &item.value {
353                if let Some(enum_val) = WiFiBand::from_u8(*i as u8) {
354                    res.push(enum_val);
355                }
356            }
357        }
358    }
359    Ok(res)
360}
361
362/// Decode SupportedThreadFeatures attribute (0x0009)
363pub fn decode_supported_thread_features(inp: &tlv::TlvItemValue) -> anyhow::Result<ThreadCapabilities> {
364    if let tlv::TlvItemValue::Int(v) = inp {
365        Ok(*v as u8)
366    } else {
367        Err(anyhow::anyhow!("Expected Integer"))
368    }
369}
370
371/// Decode ThreadVersion attribute (0x000A)
372pub fn decode_thread_version(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
373    if let tlv::TlvItemValue::Int(v) = inp {
374        Ok(*v as u16)
375    } else {
376        Err(anyhow::anyhow!("Expected UInt16"))
377    }
378}
379
380
381// JSON dispatcher function
382
383/// Decode attribute value and return as JSON string
384///
385/// # Parameters
386/// * `cluster_id` - The cluster identifier
387/// * `attribute_id` - The attribute identifier
388/// * `tlv_value` - The TLV value to decode
389///
390/// # Returns
391/// JSON string representation of the decoded value or error
392pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
393    // Verify this is the correct cluster
394    if cluster_id != 0x0031 {
395        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0031, got {}\"}}", cluster_id);
396    }
397
398    match attribute_id {
399        0x0000 => {
400            match decode_max_networks(tlv_value) {
401                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
402                Err(e) => format!("{{\"error\": \"{}\"}}", e),
403            }
404        }
405        0x0001 => {
406            match decode_networks(tlv_value) {
407                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
408                Err(e) => format!("{{\"error\": \"{}\"}}", e),
409            }
410        }
411        0x0002 => {
412            match decode_scan_max_time_seconds(tlv_value) {
413                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
414                Err(e) => format!("{{\"error\": \"{}\"}}", e),
415            }
416        }
417        0x0003 => {
418            match decode_connect_max_time_seconds(tlv_value) {
419                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
420                Err(e) => format!("{{\"error\": \"{}\"}}", e),
421            }
422        }
423        0x0004 => {
424            match decode_interface_enabled(tlv_value) {
425                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
426                Err(e) => format!("{{\"error\": \"{}\"}}", e),
427            }
428        }
429        0x0005 => {
430            match decode_last_networking_status(tlv_value) {
431                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
432                Err(e) => format!("{{\"error\": \"{}\"}}", e),
433            }
434        }
435        0x0006 => {
436            match decode_last_network_id(tlv_value) {
437                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
438                Err(e) => format!("{{\"error\": \"{}\"}}", e),
439            }
440        }
441        0x0007 => {
442            match decode_last_connect_error_value(tlv_value) {
443                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
444                Err(e) => format!("{{\"error\": \"{}\"}}", e),
445            }
446        }
447        0x0008 => {
448            match decode_supported_wifi_bands(tlv_value) {
449                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
450                Err(e) => format!("{{\"error\": \"{}\"}}", e),
451            }
452        }
453        0x0009 => {
454            match decode_supported_thread_features(tlv_value) {
455                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
456                Err(e) => format!("{{\"error\": \"{}\"}}", e),
457            }
458        }
459        0x000A => {
460            match decode_thread_version(tlv_value) {
461                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
462                Err(e) => format!("{{\"error\": \"{}\"}}", e),
463            }
464        }
465        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
466    }
467}
468
469/// Get list of all attributes supported by this cluster
470///
471/// # Returns
472/// Vector of tuples containing (attribute_id, attribute_name)
473pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
474    vec![
475        (0x0000, "MaxNetworks"),
476        (0x0001, "Networks"),
477        (0x0002, "ScanMaxTimeSeconds"),
478        (0x0003, "ConnectMaxTimeSeconds"),
479        (0x0004, "InterfaceEnabled"),
480        (0x0005, "LastNetworkingStatus"),
481        (0x0006, "LastNetworkID"),
482        (0x0007, "LastConnectErrorValue"),
483        (0x0008, "SupportedWiFiBands"),
484        (0x0009, "SupportedThreadFeatures"),
485        (0x000A, "ThreadVersion"),
486    ]
487}
488
489#[derive(Debug, serde::Serialize)]
490pub struct ScanNetworksResponse {
491    pub networking_status: Option<NetworkCommissioningStatus>,
492    pub debug_text: Option<String>,
493    pub wifi_scan_results: Option<Vec<WiFiInterfaceScanResult>>,
494    pub thread_scan_results: Option<Vec<ThreadInterfaceScanResult>>,
495}
496
497#[derive(Debug, serde::Serialize)]
498pub struct NetworkConfigResponse {
499    pub networking_status: Option<NetworkCommissioningStatus>,
500    pub debug_text: Option<String>,
501    pub network_index: Option<u8>,
502}
503
504#[derive(Debug, serde::Serialize)]
505pub struct ConnectNetworkResponse {
506    pub networking_status: Option<NetworkCommissioningStatus>,
507    pub debug_text: Option<String>,
508    pub error_value: Option<i32>,
509}
510
511// Command response decoders
512
513/// Decode ScanNetworksResponse command response (01)
514pub fn decode_scan_networks_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ScanNetworksResponse> {
515    if let tlv::TlvItemValue::List(_fields) = inp {
516        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
517        Ok(ScanNetworksResponse {
518                networking_status: item.get_int(&[0]).and_then(|v| NetworkCommissioningStatus::from_u8(v as u8)),
519                debug_text: item.get_string_owned(&[1]),
520                wifi_scan_results: {
521                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
522                        let mut items = Vec::new();
523                        for list_item in l {
524                            items.push(WiFiInterfaceScanResult {
525                security: list_item.get_int(&[0]).map(|v| v as u8),
526                ssid: list_item.get_octet_string_owned(&[1]),
527                bssid: list_item.get_octet_string_owned(&[2]),
528                channel: list_item.get_int(&[3]).map(|v| v as u16),
529                wifi_band: list_item.get_int(&[4]).and_then(|v| WiFiBand::from_u8(v as u8)),
530                rssi: list_item.get_int(&[5]).map(|v| v as i8),
531                            });
532                        }
533                        Some(items)
534                    } else {
535                        None
536                    }
537                },
538                thread_scan_results: {
539                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[3]) {
540                        let mut items = Vec::new();
541                        for list_item in l {
542                            items.push(ThreadInterfaceScanResult {
543                pan_id: list_item.get_int(&[0]).map(|v| v as u16),
544                extended_pan_id: list_item.get_int(&[1]),
545                network_name: list_item.get_string_owned(&[2]),
546                channel: list_item.get_int(&[3]).map(|v| v as u16),
547                version: list_item.get_int(&[4]).map(|v| v as u8),
548                extended_address: list_item.get_int(&[5]).map(|v| v as u8),
549                rssi: list_item.get_int(&[6]).map(|v| v as i8),
550                lqi: list_item.get_int(&[7]).map(|v| v as u8),
551                            });
552                        }
553                        Some(items)
554                    } else {
555                        None
556                    }
557                },
558        })
559    } else {
560        Err(anyhow::anyhow!("Expected struct fields"))
561    }
562}
563
564/// Decode NetworkConfigResponse command response (05)
565pub fn decode_network_config_response(inp: &tlv::TlvItemValue) -> anyhow::Result<NetworkConfigResponse> {
566    if let tlv::TlvItemValue::List(_fields) = inp {
567        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
568        Ok(NetworkConfigResponse {
569                networking_status: item.get_int(&[0]).and_then(|v| NetworkCommissioningStatus::from_u8(v as u8)),
570                debug_text: item.get_string_owned(&[1]),
571                network_index: item.get_int(&[2]).map(|v| v as u8),
572        })
573    } else {
574        Err(anyhow::anyhow!("Expected struct fields"))
575    }
576}
577
578/// Decode ConnectNetworkResponse command response (07)
579pub fn decode_connect_network_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ConnectNetworkResponse> {
580    if let tlv::TlvItemValue::List(_fields) = inp {
581        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
582        Ok(ConnectNetworkResponse {
583                networking_status: item.get_int(&[0]).and_then(|v| NetworkCommissioningStatus::from_u8(v as u8)),
584                debug_text: item.get_string_owned(&[1]),
585                error_value: item.get_int(&[2]).map(|v| v as i32),
586        })
587    } else {
588        Err(anyhow::anyhow!("Expected struct fields"))
589    }
590}
591