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
6#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13// Import serialization helpers for octet strings
14use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
15
16// Enum definitions
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
19#[repr(u8)]
20pub enum NetworkCommissioningStatus {
21    /// OK, no error
22    Success = 0,
23    /// Value Outside Range
24    Outofrange = 1,
25    /// A collection would exceed its size limit
26    Boundsexceeded = 2,
27    /// The NetworkID is not among the collection of added networks
28    Networkidnotfound = 3,
29    /// The NetworkID is already among the collection of added networks
30    Duplicatenetworkid = 4,
31    /// Cannot find AP: SSID Not found
32    Networknotfound = 5,
33    /// Cannot find AP: Mismatch on band/channels/regulatory domain / 2.4GHz vs 5GHz
34    Regulatoryerror = 6,
35    /// Cannot associate due to authentication failure
36    Authfailure = 7,
37    /// Cannot associate due to unsupported security mode
38    Unsupportedsecurity = 8,
39    /// Other association failure
40    Otherconnectionfailure = 9,
41    /// Failure to generate an IPv6 address
42    Ipv6failed = 10,
43    /// Failure to bind Wi-Fi +<->+ IP interfaces
44    Ipbindfailed = 11,
45    /// Unknown error
46    Unknownerror = 12,
47}
48
49impl NetworkCommissioningStatus {
50    /// Convert from u8 value
51    pub fn from_u8(value: u8) -> Option<Self> {
52        match value {
53            0 => Some(NetworkCommissioningStatus::Success),
54            1 => Some(NetworkCommissioningStatus::Outofrange),
55            2 => Some(NetworkCommissioningStatus::Boundsexceeded),
56            3 => Some(NetworkCommissioningStatus::Networkidnotfound),
57            4 => Some(NetworkCommissioningStatus::Duplicatenetworkid),
58            5 => Some(NetworkCommissioningStatus::Networknotfound),
59            6 => Some(NetworkCommissioningStatus::Regulatoryerror),
60            7 => Some(NetworkCommissioningStatus::Authfailure),
61            8 => Some(NetworkCommissioningStatus::Unsupportedsecurity),
62            9 => Some(NetworkCommissioningStatus::Otherconnectionfailure),
63            10 => Some(NetworkCommissioningStatus::Ipv6failed),
64            11 => Some(NetworkCommissioningStatus::Ipbindfailed),
65            12 => Some(NetworkCommissioningStatus::Unknownerror),
66            _ => None,
67        }
68    }
69
70    /// Convert to u8 value
71    pub fn to_u8(self) -> u8 {
72        self as u8
73    }
74}
75
76impl From<NetworkCommissioningStatus> for u8 {
77    fn from(val: NetworkCommissioningStatus) -> Self {
78        val as u8
79    }
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
83#[repr(u8)]
84pub enum WiFiBand {
85    /// 2.4GHz - 2.401GHz to 2.495GHz (802.11b/g/n/ax)
86    _2g4 = 0,
87    /// 3.65GHz - 3.655GHz to 3.695GHz (802.11y)
88    _3g65 = 1,
89    /// 5GHz - 5.150GHz to 5.895GHz (802.11a/n/ac/ax)
90    _5g = 2,
91    /// 6GHz - 5.925GHz to 7.125GHz (802.11ax / Wi-Fi 6E)
92    _6g = 3,
93    /// 60GHz - 57.24GHz to 70.20GHz (802.11ad/ay)
94    _60g = 4,
95    /// Sub-1GHz - 755MHz to 931MHz (802.11ah)
96    _1g = 5,
97}
98
99impl WiFiBand {
100    /// Convert from u8 value
101    pub fn from_u8(value: u8) -> Option<Self> {
102        match value {
103            0 => Some(WiFiBand::_2g4),
104            1 => Some(WiFiBand::_3g65),
105            2 => Some(WiFiBand::_5g),
106            3 => Some(WiFiBand::_6g),
107            4 => Some(WiFiBand::_60g),
108            5 => Some(WiFiBand::_1g),
109            _ => None,
110        }
111    }
112
113    /// Convert to u8 value
114    pub fn to_u8(self) -> u8 {
115        self as u8
116    }
117}
118
119impl From<WiFiBand> for u8 {
120    fn from(val: WiFiBand) -> Self {
121        val as u8
122    }
123}
124
125// Bitmap definitions
126
127/// ThreadCapabilities bitmap type
128pub type ThreadCapabilities = u8;
129
130/// Constants for ThreadCapabilities
131pub mod threadcapabilities {
132    /// Thread Border Router functionality is present
133    pub const IS_BORDER_ROUTER_CAPABLE: u8 = 0x01;
134    /// Router mode is supported (interface could be in router or REED mode)
135    pub const IS_ROUTER_CAPABLE: u8 = 0x02;
136    /// Sleepy end-device mode is supported
137    pub const IS_SLEEPY_END_DEVICE_CAPABLE: u8 = 0x04;
138    /// Device is a full Thread device (opposite of Minimal Thread Device)
139    pub const IS_FULL_THREAD_DEVICE: u8 = 0x08;
140    /// Synchronized sleepy end-device mode is supported
141    pub const IS_SYNCHRONIZED_SLEEPY_END_DEVICE_CAPABLE: u8 = 0x10;
142}
143
144/// WiFiSecurity bitmap type
145pub type WiFiSecurity = u8;
146
147/// Constants for WiFiSecurity
148pub mod wifisecurity {
149    /// Supports unencrypted Wi-Fi
150    pub const UNENCRYPTED: u8 = 0x01;
151    /// Supports Wi-Fi using WEP security
152    pub const WEP: u8 = 0x02;
153    /// Supports Wi-Fi using WPA-Personal security
154    pub const WPA_PERSONAL: u8 = 0x04;
155    /// Supports Wi-Fi using WPA2-Personal security
156    pub const WPA2_PERSONAL: u8 = 0x08;
157    /// Supports Wi-Fi using WPA3-Personal security
158    pub const WPA3_PERSONAL: u8 = 0x10;
159}
160
161// Struct definitions
162
163#[derive(Debug, serde::Serialize)]
164pub struct NetworkInfo {
165    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
166    pub network_id: Option<Vec<u8>>,
167    pub connected: Option<bool>,
168}
169
170#[derive(Debug, serde::Serialize)]
171pub struct ThreadInterfaceScanResult {
172    pub pan_id: Option<u16>,
173    pub extended_pan_id: Option<u64>,
174    pub network_name: Option<String>,
175    pub channel: Option<u16>,
176    pub version: Option<u8>,
177    pub extended_address: Option<u8>,
178    pub rssi: Option<i8>,
179    pub lqi: Option<u8>,
180}
181
182#[derive(Debug, serde::Serialize)]
183pub struct WiFiInterfaceScanResult {
184    pub security: Option<WiFiSecurity>,
185    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
186    pub ssid: Option<Vec<u8>>,
187    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
188    pub bssid: Option<Vec<u8>>,
189    pub channel: Option<u16>,
190    pub wifi_band: Option<WiFiBand>,
191    pub rssi: Option<i8>,
192}
193
194// Command encoders
195
196/// Encode ScanNetworks command (0x00)
197pub fn encode_scan_networks(ssid: Option<Vec<u8>>, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
198    let tlv = tlv::TlvItemEnc {
199        tag: 0,
200        value: tlv::TlvItemValueEnc::StructInvisible(vec![
201        (0, tlv::TlvItemValueEnc::OctetString(ssid.unwrap_or_default())).into(),
202        (1, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
203        ]),
204    };
205    Ok(tlv.encode()?)
206}
207
208/// Encode AddOrUpdateWiFiNetwork command (0x02)
209pub fn encode_add_or_update_wifi_network(ssid: Vec<u8>, credentials: Vec<u8>, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
210    let tlv = tlv::TlvItemEnc {
211        tag: 0,
212        value: tlv::TlvItemValueEnc::StructInvisible(vec![
213        (0, tlv::TlvItemValueEnc::OctetString(ssid)).into(),
214        (1, tlv::TlvItemValueEnc::OctetString(credentials)).into(),
215        (2, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
216        ]),
217    };
218    Ok(tlv.encode()?)
219}
220
221/// Encode AddOrUpdateThreadNetwork command (0x03)
222pub fn encode_add_or_update_thread_network(operational_dataset: Vec<u8>, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
223    let tlv = tlv::TlvItemEnc {
224        tag: 0,
225        value: tlv::TlvItemValueEnc::StructInvisible(vec![
226        (0, tlv::TlvItemValueEnc::OctetString(operational_dataset)).into(),
227        (1, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
228        ]),
229    };
230    Ok(tlv.encode()?)
231}
232
233/// Encode RemoveNetwork command (0x04)
234pub fn encode_remove_network(network_id: Vec<u8>, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
235    let tlv = tlv::TlvItemEnc {
236        tag: 0,
237        value: tlv::TlvItemValueEnc::StructInvisible(vec![
238        (0, tlv::TlvItemValueEnc::OctetString(network_id)).into(),
239        (1, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
240        ]),
241    };
242    Ok(tlv.encode()?)
243}
244
245/// Encode ConnectNetwork command (0x06)
246pub fn encode_connect_network(network_id: Vec<u8>, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
247    let tlv = tlv::TlvItemEnc {
248        tag: 0,
249        value: tlv::TlvItemValueEnc::StructInvisible(vec![
250        (0, tlv::TlvItemValueEnc::OctetString(network_id)).into(),
251        (1, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
252        ]),
253    };
254    Ok(tlv.encode()?)
255}
256
257/// Encode ReorderNetwork command (0x08)
258pub fn encode_reorder_network(network_id: Vec<u8>, network_index: u8, breadcrumb: u64) -> anyhow::Result<Vec<u8>> {
259    let tlv = tlv::TlvItemEnc {
260        tag: 0,
261        value: tlv::TlvItemValueEnc::StructInvisible(vec![
262        (0, tlv::TlvItemValueEnc::OctetString(network_id)).into(),
263        (1, tlv::TlvItemValueEnc::UInt8(network_index)).into(),
264        (2, tlv::TlvItemValueEnc::UInt64(breadcrumb)).into(),
265        ]),
266    };
267    Ok(tlv.encode()?)
268}
269
270// Attribute decoders
271
272/// Decode MaxNetworks attribute (0x0000)
273pub fn decode_max_networks(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
274    if let tlv::TlvItemValue::Int(v) = inp {
275        Ok(*v as u8)
276    } else {
277        Err(anyhow::anyhow!("Expected UInt8"))
278    }
279}
280
281/// Decode Networks attribute (0x0001)
282pub fn decode_networks(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<NetworkInfo>> {
283    let mut res = Vec::new();
284    if let tlv::TlvItemValue::List(v) = inp {
285        for item in v {
286            res.push(NetworkInfo {
287                network_id: item.get_octet_string_owned(&[0]),
288                connected: item.get_bool(&[1]),
289            });
290        }
291    }
292    Ok(res)
293}
294
295/// Decode ScanMaxTimeSeconds attribute (0x0002)
296pub fn decode_scan_max_time_seconds(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
297    if let tlv::TlvItemValue::Int(v) = inp {
298        Ok(*v as u8)
299    } else {
300        Err(anyhow::anyhow!("Expected UInt8"))
301    }
302}
303
304/// Decode ConnectMaxTimeSeconds attribute (0x0003)
305pub fn decode_connect_max_time_seconds(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
306    if let tlv::TlvItemValue::Int(v) = inp {
307        Ok(*v as u8)
308    } else {
309        Err(anyhow::anyhow!("Expected UInt8"))
310    }
311}
312
313/// Decode InterfaceEnabled attribute (0x0004)
314pub fn decode_interface_enabled(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
315    if let tlv::TlvItemValue::Bool(v) = inp {
316        Ok(*v)
317    } else {
318        Err(anyhow::anyhow!("Expected Bool"))
319    }
320}
321
322/// Decode LastNetworkingStatus attribute (0x0005)
323pub fn decode_last_networking_status(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<NetworkCommissioningStatus>> {
324    if let tlv::TlvItemValue::Int(v) = inp {
325        Ok(NetworkCommissioningStatus::from_u8(*v as u8))
326    } else {
327        Ok(None)
328    }
329}
330
331/// Decode LastNetworkID attribute (0x0006)
332pub fn decode_last_network_id(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Vec<u8>>> {
333    if let tlv::TlvItemValue::OctetString(v) = inp {
334        Ok(Some(v.clone()))
335    } else {
336        Ok(None)
337    }
338}
339
340/// Decode LastConnectErrorValue attribute (0x0007)
341pub fn decode_last_connect_error_value(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<i32>> {
342    if let tlv::TlvItemValue::Int(v) = inp {
343        Ok(Some(*v as i32))
344    } else {
345        Ok(None)
346    }
347}
348
349/// Decode SupportedWiFiBands attribute (0x0008)
350pub fn decode_supported_wifi_bands(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<WiFiBand>> {
351    let mut res = Vec::new();
352    if let tlv::TlvItemValue::List(v) = inp {
353        for item in v {
354            if let tlv::TlvItemValue::Int(i) = &item.value {
355                if let Some(enum_val) = WiFiBand::from_u8(*i as u8) {
356                    res.push(enum_val);
357                }
358            }
359        }
360    }
361    Ok(res)
362}
363
364/// Decode SupportedThreadFeatures attribute (0x0009)
365pub fn decode_supported_thread_features(inp: &tlv::TlvItemValue) -> anyhow::Result<ThreadCapabilities> {
366    if let tlv::TlvItemValue::Int(v) = inp {
367        Ok(*v as u8)
368    } else {
369        Err(anyhow::anyhow!("Expected Integer"))
370    }
371}
372
373/// Decode ThreadVersion attribute (0x000A)
374pub fn decode_thread_version(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
375    if let tlv::TlvItemValue::Int(v) = inp {
376        Ok(*v as u16)
377    } else {
378        Err(anyhow::anyhow!("Expected UInt16"))
379    }
380}
381
382
383// JSON dispatcher function
384
385/// Decode attribute value and return as JSON string
386///
387/// # Parameters
388/// * `cluster_id` - The cluster identifier
389/// * `attribute_id` - The attribute identifier
390/// * `tlv_value` - The TLV value to decode
391///
392/// # Returns
393/// JSON string representation of the decoded value or error
394pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
395    // Verify this is the correct cluster
396    if cluster_id != 0x0031 {
397        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0031, got {}\"}}", cluster_id);
398    }
399
400    match attribute_id {
401        0x0000 => {
402            match decode_max_networks(tlv_value) {
403                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
404                Err(e) => format!("{{\"error\": \"{}\"}}", e),
405            }
406        }
407        0x0001 => {
408            match decode_networks(tlv_value) {
409                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
410                Err(e) => format!("{{\"error\": \"{}\"}}", e),
411            }
412        }
413        0x0002 => {
414            match decode_scan_max_time_seconds(tlv_value) {
415                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
416                Err(e) => format!("{{\"error\": \"{}\"}}", e),
417            }
418        }
419        0x0003 => {
420            match decode_connect_max_time_seconds(tlv_value) {
421                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
422                Err(e) => format!("{{\"error\": \"{}\"}}", e),
423            }
424        }
425        0x0004 => {
426            match decode_interface_enabled(tlv_value) {
427                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
428                Err(e) => format!("{{\"error\": \"{}\"}}", e),
429            }
430        }
431        0x0005 => {
432            match decode_last_networking_status(tlv_value) {
433                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
434                Err(e) => format!("{{\"error\": \"{}\"}}", e),
435            }
436        }
437        0x0006 => {
438            match decode_last_network_id(tlv_value) {
439                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
440                Err(e) => format!("{{\"error\": \"{}\"}}", e),
441            }
442        }
443        0x0007 => {
444            match decode_last_connect_error_value(tlv_value) {
445                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
446                Err(e) => format!("{{\"error\": \"{}\"}}", e),
447            }
448        }
449        0x0008 => {
450            match decode_supported_wifi_bands(tlv_value) {
451                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
452                Err(e) => format!("{{\"error\": \"{}\"}}", e),
453            }
454        }
455        0x0009 => {
456            match decode_supported_thread_features(tlv_value) {
457                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
458                Err(e) => format!("{{\"error\": \"{}\"}}", e),
459            }
460        }
461        0x000A => {
462            match decode_thread_version(tlv_value) {
463                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
464                Err(e) => format!("{{\"error\": \"{}\"}}", e),
465            }
466        }
467        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
468    }
469}
470
471/// Get list of all attributes supported by this cluster
472///
473/// # Returns
474/// Vector of tuples containing (attribute_id, attribute_name)
475pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
476    vec![
477        (0x0000, "MaxNetworks"),
478        (0x0001, "Networks"),
479        (0x0002, "ScanMaxTimeSeconds"),
480        (0x0003, "ConnectMaxTimeSeconds"),
481        (0x0004, "InterfaceEnabled"),
482        (0x0005, "LastNetworkingStatus"),
483        (0x0006, "LastNetworkID"),
484        (0x0007, "LastConnectErrorValue"),
485        (0x0008, "SupportedWiFiBands"),
486        (0x0009, "SupportedThreadFeatures"),
487        (0x000A, "ThreadVersion"),
488    ]
489}
490
491// Command listing
492
493pub fn get_command_list() -> Vec<(u32, &'static str)> {
494    vec![
495        (0x00, "ScanNetworks"),
496        (0x02, "AddOrUpdateWiFiNetwork"),
497        (0x03, "AddOrUpdateThreadNetwork"),
498        (0x04, "RemoveNetwork"),
499        (0x06, "ConnectNetwork"),
500        (0x08, "ReorderNetwork"),
501    ]
502}
503
504pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
505    match cmd_id {
506        0x00 => Some("ScanNetworks"),
507        0x02 => Some("AddOrUpdateWiFiNetwork"),
508        0x03 => Some("AddOrUpdateThreadNetwork"),
509        0x04 => Some("RemoveNetwork"),
510        0x06 => Some("ConnectNetwork"),
511        0x08 => Some("ReorderNetwork"),
512        _ => None,
513    }
514}
515
516pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
517    match cmd_id {
518        0x00 => Some(vec![
519            crate::clusters::codec::CommandField { tag: 0, name: "ssid", kind: crate::clusters::codec::FieldKind::OctetString, optional: true, nullable: true },
520            crate::clusters::codec::CommandField { tag: 1, name: "breadcrumb", kind: crate::clusters::codec::FieldKind::U64, optional: true, nullable: false },
521        ]),
522        0x02 => Some(vec![
523            crate::clusters::codec::CommandField { tag: 0, name: "ssid", kind: crate::clusters::codec::FieldKind::OctetString, optional: false, nullable: false },
524            crate::clusters::codec::CommandField { tag: 1, name: "credentials", kind: crate::clusters::codec::FieldKind::OctetString, optional: false, nullable: false },
525            crate::clusters::codec::CommandField { tag: 2, name: "breadcrumb", kind: crate::clusters::codec::FieldKind::U64, optional: true, nullable: false },
526        ]),
527        0x03 => Some(vec![
528            crate::clusters::codec::CommandField { tag: 0, name: "operational_dataset", kind: crate::clusters::codec::FieldKind::OctetString, optional: false, nullable: false },
529            crate::clusters::codec::CommandField { tag: 1, name: "breadcrumb", kind: crate::clusters::codec::FieldKind::U64, optional: true, nullable: false },
530        ]),
531        0x04 => Some(vec![
532            crate::clusters::codec::CommandField { tag: 0, name: "network_id", kind: crate::clusters::codec::FieldKind::OctetString, optional: false, nullable: false },
533            crate::clusters::codec::CommandField { tag: 1, name: "breadcrumb", kind: crate::clusters::codec::FieldKind::U64, optional: true, nullable: false },
534        ]),
535        0x06 => Some(vec![
536            crate::clusters::codec::CommandField { tag: 0, name: "network_id", kind: crate::clusters::codec::FieldKind::OctetString, optional: false, nullable: false },
537            crate::clusters::codec::CommandField { tag: 1, name: "breadcrumb", kind: crate::clusters::codec::FieldKind::U64, optional: true, nullable: false },
538        ]),
539        0x08 => Some(vec![
540            crate::clusters::codec::CommandField { tag: 0, name: "network_id", kind: crate::clusters::codec::FieldKind::OctetString, optional: false, nullable: false },
541            crate::clusters::codec::CommandField { tag: 1, name: "network_index", kind: crate::clusters::codec::FieldKind::U8, optional: false, nullable: false },
542            crate::clusters::codec::CommandField { tag: 2, name: "breadcrumb", kind: crate::clusters::codec::FieldKind::U64, optional: true, nullable: false },
543        ]),
544        _ => None,
545    }
546}
547
548pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
549    match cmd_id {
550        0x00 => {
551        let ssid = crate::clusters::codec::json_util::get_opt_octstr(args, "ssid")?;
552        let breadcrumb = crate::clusters::codec::json_util::get_u64(args, "breadcrumb")?;
553        encode_scan_networks(ssid, breadcrumb)
554        }
555        0x02 => {
556        let ssid = crate::clusters::codec::json_util::get_octstr(args, "ssid")?;
557        let credentials = crate::clusters::codec::json_util::get_octstr(args, "credentials")?;
558        let breadcrumb = crate::clusters::codec::json_util::get_u64(args, "breadcrumb")?;
559        encode_add_or_update_wifi_network(ssid, credentials, breadcrumb)
560        }
561        0x03 => {
562        let operational_dataset = crate::clusters::codec::json_util::get_octstr(args, "operational_dataset")?;
563        let breadcrumb = crate::clusters::codec::json_util::get_u64(args, "breadcrumb")?;
564        encode_add_or_update_thread_network(operational_dataset, breadcrumb)
565        }
566        0x04 => {
567        let network_id = crate::clusters::codec::json_util::get_octstr(args, "network_id")?;
568        let breadcrumb = crate::clusters::codec::json_util::get_u64(args, "breadcrumb")?;
569        encode_remove_network(network_id, breadcrumb)
570        }
571        0x06 => {
572        let network_id = crate::clusters::codec::json_util::get_octstr(args, "network_id")?;
573        let breadcrumb = crate::clusters::codec::json_util::get_u64(args, "breadcrumb")?;
574        encode_connect_network(network_id, breadcrumb)
575        }
576        0x08 => {
577        let network_id = crate::clusters::codec::json_util::get_octstr(args, "network_id")?;
578        let network_index = crate::clusters::codec::json_util::get_u8(args, "network_index")?;
579        let breadcrumb = crate::clusters::codec::json_util::get_u64(args, "breadcrumb")?;
580        encode_reorder_network(network_id, network_index, breadcrumb)
581        }
582        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
583    }
584}
585
586#[derive(Debug, serde::Serialize)]
587pub struct ScanNetworksResponse {
588    pub networking_status: Option<NetworkCommissioningStatus>,
589    pub debug_text: Option<String>,
590    pub wifi_scan_results: Option<Vec<WiFiInterfaceScanResult>>,
591    pub thread_scan_results: Option<Vec<ThreadInterfaceScanResult>>,
592}
593
594#[derive(Debug, serde::Serialize)]
595pub struct NetworkConfigResponse {
596    pub networking_status: Option<NetworkCommissioningStatus>,
597    pub debug_text: Option<String>,
598    pub network_index: Option<u8>,
599}
600
601#[derive(Debug, serde::Serialize)]
602pub struct ConnectNetworkResponse {
603    pub networking_status: Option<NetworkCommissioningStatus>,
604    pub debug_text: Option<String>,
605    pub error_value: Option<i32>,
606}
607
608// Command response decoders
609
610/// Decode ScanNetworksResponse command response (01)
611pub fn decode_scan_networks_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ScanNetworksResponse> {
612    if let tlv::TlvItemValue::List(_fields) = inp {
613        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
614        Ok(ScanNetworksResponse {
615                networking_status: item.get_int(&[0]).and_then(|v| NetworkCommissioningStatus::from_u8(v as u8)),
616                debug_text: item.get_string_owned(&[1]),
617                wifi_scan_results: {
618                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
619                        let mut items = Vec::new();
620                        for list_item in l {
621                            items.push(WiFiInterfaceScanResult {
622                security: list_item.get_int(&[0]).map(|v| v as u8),
623                ssid: list_item.get_octet_string_owned(&[1]),
624                bssid: list_item.get_octet_string_owned(&[2]),
625                channel: list_item.get_int(&[3]).map(|v| v as u16),
626                wifi_band: list_item.get_int(&[4]).and_then(|v| WiFiBand::from_u8(v as u8)),
627                rssi: list_item.get_int(&[5]).map(|v| v as i8),
628                            });
629                        }
630                        Some(items)
631                    } else {
632                        None
633                    }
634                },
635                thread_scan_results: {
636                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[3]) {
637                        let mut items = Vec::new();
638                        for list_item in l {
639                            items.push(ThreadInterfaceScanResult {
640                pan_id: list_item.get_int(&[0]).map(|v| v as u16),
641                extended_pan_id: list_item.get_int(&[1]),
642                network_name: list_item.get_string_owned(&[2]),
643                channel: list_item.get_int(&[3]).map(|v| v as u16),
644                version: list_item.get_int(&[4]).map(|v| v as u8),
645                extended_address: list_item.get_int(&[5]).map(|v| v as u8),
646                rssi: list_item.get_int(&[6]).map(|v| v as i8),
647                lqi: list_item.get_int(&[7]).map(|v| v as u8),
648                            });
649                        }
650                        Some(items)
651                    } else {
652                        None
653                    }
654                },
655        })
656    } else {
657        Err(anyhow::anyhow!("Expected struct fields"))
658    }
659}
660
661/// Decode NetworkConfigResponse command response (05)
662pub fn decode_network_config_response(inp: &tlv::TlvItemValue) -> anyhow::Result<NetworkConfigResponse> {
663    if let tlv::TlvItemValue::List(_fields) = inp {
664        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
665        Ok(NetworkConfigResponse {
666                networking_status: item.get_int(&[0]).and_then(|v| NetworkCommissioningStatus::from_u8(v as u8)),
667                debug_text: item.get_string_owned(&[1]),
668                network_index: item.get_int(&[2]).map(|v| v as u8),
669        })
670    } else {
671        Err(anyhow::anyhow!("Expected struct fields"))
672    }
673}
674
675/// Decode ConnectNetworkResponse command response (07)
676pub fn decode_connect_network_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ConnectNetworkResponse> {
677    if let tlv::TlvItemValue::List(_fields) = inp {
678        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
679        Ok(ConnectNetworkResponse {
680                networking_status: item.get_int(&[0]).and_then(|v| NetworkCommissioningStatus::from_u8(v as u8)),
681                debug_text: item.get_string_owned(&[1]),
682                error_value: item.get_int(&[2]).map(|v| v as i32),
683        })
684    } else {
685        Err(anyhow::anyhow!("Expected struct fields"))
686    }
687}
688
689// Typed facade (invokes + reads)
690
691/// Invoke `ScanNetworks` command on cluster `Network Commissioning`.
692pub async fn scan_networks(conn: &crate::controller::Connection, endpoint: u16, ssid: Option<Vec<u8>>, breadcrumb: u64) -> anyhow::Result<ScanNetworksResponse> {
693    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_CMD_ID_SCANNETWORKS, &encode_scan_networks(ssid, breadcrumb)?).await?;
694    decode_scan_networks_response(&tlv)
695}
696
697/// Invoke `AddOrUpdateWiFiNetwork` command on cluster `Network Commissioning`.
698pub async fn add_or_update_wifi_network(conn: &crate::controller::Connection, endpoint: u16, ssid: Vec<u8>, credentials: Vec<u8>, breadcrumb: u64) -> anyhow::Result<NetworkConfigResponse> {
699    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_CMD_ID_ADDORUPDATEWIFINETWORK, &encode_add_or_update_wifi_network(ssid, credentials, breadcrumb)?).await?;
700    decode_network_config_response(&tlv)
701}
702
703/// Invoke `AddOrUpdateThreadNetwork` command on cluster `Network Commissioning`.
704pub async fn add_or_update_thread_network(conn: &crate::controller::Connection, endpoint: u16, operational_dataset: Vec<u8>, breadcrumb: u64) -> anyhow::Result<NetworkConfigResponse> {
705    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_CMD_ID_ADDORUPDATETHREADNETWORK, &encode_add_or_update_thread_network(operational_dataset, breadcrumb)?).await?;
706    decode_network_config_response(&tlv)
707}
708
709/// Invoke `RemoveNetwork` command on cluster `Network Commissioning`.
710pub async fn remove_network(conn: &crate::controller::Connection, endpoint: u16, network_id: Vec<u8>, breadcrumb: u64) -> anyhow::Result<NetworkConfigResponse> {
711    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_CMD_ID_REMOVENETWORK, &encode_remove_network(network_id, breadcrumb)?).await?;
712    decode_network_config_response(&tlv)
713}
714
715/// Invoke `ConnectNetwork` command on cluster `Network Commissioning`.
716pub async fn connect_network(conn: &crate::controller::Connection, endpoint: u16, network_id: Vec<u8>, breadcrumb: u64) -> anyhow::Result<ConnectNetworkResponse> {
717    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_CMD_ID_CONNECTNETWORK, &encode_connect_network(network_id, breadcrumb)?).await?;
718    decode_connect_network_response(&tlv)
719}
720
721/// Invoke `ReorderNetwork` command on cluster `Network Commissioning`.
722pub async fn reorder_network(conn: &crate::controller::Connection, endpoint: u16, network_id: Vec<u8>, network_index: u8, breadcrumb: u64) -> anyhow::Result<NetworkConfigResponse> {
723    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_CMD_ID_REORDERNETWORK, &encode_reorder_network(network_id, network_index, breadcrumb)?).await?;
724    decode_network_config_response(&tlv)
725}
726
727/// Read `MaxNetworks` attribute from cluster `Network Commissioning`.
728pub async fn read_max_networks(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
729    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_MAXNETWORKS).await?;
730    decode_max_networks(&tlv)
731}
732
733/// Read `Networks` attribute from cluster `Network Commissioning`.
734pub async fn read_networks(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<NetworkInfo>> {
735    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_NETWORKS).await?;
736    decode_networks(&tlv)
737}
738
739/// Read `ScanMaxTimeSeconds` attribute from cluster `Network Commissioning`.
740pub async fn read_scan_max_time_seconds(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
741    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_SCANMAXTIMESECONDS).await?;
742    decode_scan_max_time_seconds(&tlv)
743}
744
745/// Read `ConnectMaxTimeSeconds` attribute from cluster `Network Commissioning`.
746pub async fn read_connect_max_time_seconds(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
747    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_CONNECTMAXTIMESECONDS).await?;
748    decode_connect_max_time_seconds(&tlv)
749}
750
751/// Read `InterfaceEnabled` attribute from cluster `Network Commissioning`.
752pub async fn read_interface_enabled(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<bool> {
753    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_INTERFACEENABLED).await?;
754    decode_interface_enabled(&tlv)
755}
756
757/// Read `LastNetworkingStatus` attribute from cluster `Network Commissioning`.
758pub async fn read_last_networking_status(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<NetworkCommissioningStatus>> {
759    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_LASTNETWORKINGSTATUS).await?;
760    decode_last_networking_status(&tlv)
761}
762
763/// Read `LastNetworkID` attribute from cluster `Network Commissioning`.
764pub async fn read_last_network_id(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<Vec<u8>>> {
765    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_LASTNETWORKID).await?;
766    decode_last_network_id(&tlv)
767}
768
769/// Read `LastConnectErrorValue` attribute from cluster `Network Commissioning`.
770pub async fn read_last_connect_error_value(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<i32>> {
771    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_LASTCONNECTERRORVALUE).await?;
772    decode_last_connect_error_value(&tlv)
773}
774
775/// Read `SupportedWiFiBands` attribute from cluster `Network Commissioning`.
776pub async fn read_supported_wifi_bands(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<WiFiBand>> {
777    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_SUPPORTEDWIFIBANDS).await?;
778    decode_supported_wifi_bands(&tlv)
779}
780
781/// Read `SupportedThreadFeatures` attribute from cluster `Network Commissioning`.
782pub async fn read_supported_thread_features(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<ThreadCapabilities> {
783    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_SUPPORTEDTHREADFEATURES).await?;
784    decode_supported_thread_features(&tlv)
785}
786
787/// Read `ThreadVersion` attribute from cluster `Network Commissioning`.
788pub async fn read_thread_version(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
789    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_NETWORK_COMMISSIONING, crate::clusters::defs::CLUSTER_NETWORK_COMMISSIONING_ATTR_ID_THREADVERSION).await?;
790    decode_thread_version(&tlv)
791}
792