1use core::fmt::{self, Debug};
21use core::future::{ready, Future};
22
23use crate::dm::clusters::gen_comm::GenCommHandler;
24use crate::dm::clusters::wifi_diag::WirelessDiag;
25use crate::dm::networks::wireless::{Thread, ThreadTLV, MAX_WIRELESS_NETWORK_ID_LEN};
26use crate::dm::networks::NetChangeNotif;
27use crate::dm::{ArrayAttributeRead, Cluster, Dataver, InvokeContext, ReadContext, WriteContext};
28use crate::error::{Error, ErrorCode};
29use crate::persist::{Persist, NETWORKS_KEY};
30use crate::tlv::{
31 Nullable, NullableBuilder, Octets, OctetsBuilder, TLVBuilder, TLVBuilderParent, TLVWrite,
32 ToTLVArrayBuilder, ToTLVBuilder,
33};
34use crate::utils::cell::RefCell;
35use crate::utils::init::{init, Init};
36use crate::utils::sync::blocking::Mutex;
37use crate::utils::sync::{DynBase, Notification};
38use crate::with;
39
40pub use crate::dm::clusters::decl::network_commissioning::*;
41
42#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
44#[cfg_attr(feature = "defmt", derive(defmt::Format))]
45pub enum NetworkType {
46 Ethernet,
47 Wifi,
48 Thread,
49}
50
51impl NetworkType {
52 pub const fn cluster(&self) -> Cluster<'static> {
54 match self {
55 Self::Ethernet => FULL_CLUSTER
56 .with_features(Feature::ETHERNET_NETWORK_INTERFACE.bits())
57 .with_attrs(with!(required))
58 .with_cmds(with!()),
59 Self::Wifi => FULL_CLUSTER
60 .with_features(Feature::WI_FI_NETWORK_INTERFACE.bits())
61 .with_attrs(with!(required; AttributeId::ScanMaxTimeSeconds | AttributeId::ConnectMaxTimeSeconds | AttributeId::SupportedWiFiBands))
62 .with_cmds(with!(CommandId::AddOrUpdateWiFiNetwork | CommandId::ScanNetworks | CommandId::RemoveNetwork | CommandId::ConnectNetwork | CommandId::ReorderNetwork)),
63 Self::Thread => FULL_CLUSTER
64 .with_features(Feature::THREAD_NETWORK_INTERFACE.bits())
65 .with_attrs(with!(required; AttributeId::ScanMaxTimeSeconds | AttributeId::ConnectMaxTimeSeconds | AttributeId::ThreadVersion | AttributeId::SupportedThreadFeatures))
66 .with_cmds(with!(CommandId::AddOrUpdateThreadNetwork | CommandId::ScanNetworks | CommandId::RemoveNetwork | CommandId::ConnectNetwork | CommandId::ReorderNetwork)),
67 }
68 }
69}
70
71fn network_read_into<P: TLVBuilderParent>(
78 network_id: &[u8],
79 connected: bool,
80 builder: NetworkInfoStructBuilder<P>,
81) -> Result<P, Error> {
82 builder
89 .network_id(Octets::new(network_id))?
90 .connected(connected)?
91 .end()
94}
95
96#[derive(Debug, Clone, Eq, PartialEq, Hash)]
98#[cfg_attr(feature = "defmt", derive(defmt::Format))]
99pub enum NetworkScanInfo<'a> {
100 Wifi {
102 security: WiFiSecurityBitmap,
103 ssid: &'a [u8],
104 bssid: &'a [u8],
105 channel: u16,
106 band: WiFiBandEnum,
107 rssi: i8,
108 },
109 Thread {
111 pan_id: u16,
112 ext_pan_id: u64,
113 network_name: &'a str,
114 channel: u16,
115 version: u8,
116 ext_addr: &'a [u8],
117 rssi: i8,
118 lqi: u8,
119 },
120}
121
122impl NetworkScanInfo<'_> {
123 pub fn wifi_read_into<P: TLVBuilderParent>(
126 &self,
127 builder: WiFiInterfaceScanResultStructBuilder<P>,
128 ) -> Result<P, Error> {
129 let NetworkScanInfo::Wifi {
130 security,
131 ssid,
132 bssid,
133 channel,
134 band,
135 rssi,
136 } = self
137 else {
138 panic!("Wifi scan info expected");
139 };
140
141 builder
142 .security(*security)?
143 .ssid(Octets::new(ssid))?
144 .bssid(Octets::new(bssid))?
145 .channel(*channel)?
146 .wi_fi_band(*band)?
147 .rssi(*rssi)?
148 .end()
149 }
150
151 pub fn thread_read_into<P: TLVBuilderParent>(
154 &self,
155 builder: ThreadInterfaceScanResultStructBuilder<P>,
156 ) -> Result<P, Error> {
157 let NetworkScanInfo::Thread {
158 pan_id,
159 ext_pan_id: extended_pan_id,
160 network_name,
161 channel,
162 version,
163 ext_addr,
164 rssi,
165 lqi,
166 } = self
167 else {
168 panic!("Thread scan info expected");
169 };
170
171 builder
172 .pan_id(*pan_id)?
173 .extended_pan_id(*extended_pan_id)?
174 .network_name(network_name)?
175 .channel(*channel)?
176 .version(*version)?
177 .extended_address(Octets::new(ext_addr))?
178 .rssi(*rssi)?
179 .lqi(*lqi)?
180 .end()
181 }
182}
183
184#[derive(Debug, Clone, Eq, PartialEq, Hash)]
186pub enum WirelessCreds<'a> {
187 Wifi { ssid: &'a [u8], pass: &'a [u8] },
189 Thread { dataset_tlv: &'a [u8] },
191}
192
193impl WirelessCreds<'_> {
194 pub fn id(&self) -> Result<&[u8], Error> {
198 match self {
199 WirelessCreds::Wifi { ssid, .. } => Ok(ssid),
200 WirelessCreds::Thread { dataset_tlv } => Thread::dataset_ext_pan_id(dataset_tlv),
201 }
202 }
203
204 pub fn check_match(&self, net_type: NetworkType) -> Result<(), Error> {
206 match self {
207 WirelessCreds::Wifi { .. } if matches!(net_type, NetworkType::Wifi) => Ok(()),
208 WirelessCreds::Thread { .. } if matches!(net_type, NetworkType::Thread) => Ok(()),
209 _ => Err(ErrorCode::InvalidAction.into()),
210 }
211 }
212}
213
214impl fmt::Display for WirelessCreds<'_> {
215 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216 match self {
217 WirelessCreds::Wifi { ssid, .. } => write!(
218 f,
219 "SSID({})",
220 core::str::from_utf8(ssid).ok().unwrap_or("???")
221 ),
222 WirelessCreds::Thread { dataset_tlv } => write!(
223 f,
224 "ExtEpanId({:?})",
225 ThreadTLV::new(dataset_tlv).ext_pan_id().ok().unwrap_or(&[])
226 ),
227 }
228 }
229}
230
231#[cfg(feature = "defmt")]
232impl defmt::Format for WirelessCreds<'_> {
233 fn format(&self, fmt: defmt::Formatter) {
234 match self {
235 WirelessCreds::Wifi { ssid, .. } => defmt::write!(
236 fmt,
237 "SSID({})",
238 core::str::from_utf8(ssid).ok().unwrap_or("???")
239 ),
240 WirelessCreds::Thread { dataset_tlv } => defmt::write!(
241 fmt,
242 "ExtEpanId({:?})",
243 ThreadTLV::new(dataset_tlv).ext_pan_id().ok().unwrap_or(&[])
244 ),
245 }
246 }
247}
248
249#[derive(Debug)]
251#[cfg_attr(feature = "defmt", derive(defmt::Format))]
252pub enum NetworksError {
253 NetworkIdNotFound,
254 DuplicateNetworkId,
255 OutOfRange,
256 BoundsExceeded,
257 Other(Error),
258}
259
260impl From<Error> for NetworksError {
261 fn from(err: Error) -> Self {
262 NetworksError::Other(err)
263 }
264}
265
266#[derive(Debug)]
268#[cfg_attr(feature = "defmt", derive(defmt::Format))]
269pub enum NetCtlError {
270 NetworkNotFound,
271 UnsupportedSecurity,
272 AuthFailure,
273 OtherConnectionFailure,
274 IpBindFailed,
275 IpV6Failed,
276 Other(Error),
277}
278
279impl From<Error> for NetCtlError {
280 fn from(err: Error) -> Self {
281 NetCtlError::Other(err)
282 }
283}
284
285impl NetworkCommissioningStatusEnum {
286 pub fn map<T>(
289 result: Result<T, NetworksError>,
290 ) -> Result<(NetworkCommissioningStatusEnum, Option<i32>, Option<T>), Error> {
291 if let Some((status, err_code)) = NetworkCommissioningStatusEnum::map_status(&result) {
292 Ok((status, err_code, result.ok()))
293 } else {
294 match result {
295 Err(NetworksError::Other(e)) => Err(e),
296 _ => unreachable!(),
297 }
298 }
299 }
300
301 pub fn map_status<T>(
304 result: &Result<T, NetworksError>,
305 ) -> Option<(NetworkCommissioningStatusEnum, Option<i32>)> {
306 match result {
307 Ok(_) => Some((NetworkCommissioningStatusEnum::Success, None)),
308 Err(NetworksError::NetworkIdNotFound) => {
309 Some((NetworkCommissioningStatusEnum::NetworkIDNotFound, None))
310 }
311 Err(NetworksError::DuplicateNetworkId) => {
312 Some((NetworkCommissioningStatusEnum::DuplicateNetworkID, None))
313 }
314 Err(NetworksError::OutOfRange) => {
315 Some((NetworkCommissioningStatusEnum::OutOfRange, None))
316 }
317 Err(NetworksError::BoundsExceeded) => {
318 Some((NetworkCommissioningStatusEnum::BoundsExceeded, None))
319 }
320 Err(NetworksError::Other(_)) => None,
321 }
322 }
323
324 pub fn map_ctl<T>(
327 result: Result<T, NetCtlError>,
328 ) -> Result<(NetworkCommissioningStatusEnum, Option<i32>, Option<T>), Error> {
329 if let Some((status, err_code)) = NetworkCommissioningStatusEnum::map_ctl_status(&result) {
330 Ok((status, err_code, result.ok()))
331 } else {
332 match result {
333 Err(NetCtlError::Other(e)) => Err(e),
334 _ => unreachable!(),
335 }
336 }
337 }
338
339 pub fn map_ctl_status<T>(
342 result: &Result<T, NetCtlError>,
343 ) -> Option<(NetworkCommissioningStatusEnum, Option<i32>)> {
344 match result {
345 Ok(_) => Some((NetworkCommissioningStatusEnum::Success, None)),
346 Err(NetCtlError::UnsupportedSecurity) => {
347 Some((NetworkCommissioningStatusEnum::UnsupportedSecurity, None))
348 }
349 Err(NetCtlError::AuthFailure) => {
350 Some((NetworkCommissioningStatusEnum::AuthFailure, None))
351 }
352 Err(NetCtlError::IpBindFailed) => {
353 Some((NetworkCommissioningStatusEnum::IPBindFailed, None))
354 }
355 Err(NetCtlError::IpV6Failed) => {
356 Some((NetworkCommissioningStatusEnum::IPV6Failed, None))
357 }
358 Err(NetCtlError::OtherConnectionFailure) => {
359 Some((NetworkCommissioningStatusEnum::OtherConnectionFailure, None))
360 }
361 Err(NetCtlError::NetworkNotFound) => {
362 Some((NetworkCommissioningStatusEnum::NetworkNotFound, None))
363 }
364 Err(NetCtlError::Other(_)) => None,
365 }
366 }
367
368 pub fn read_into<P: TLVBuilderParent>(
370 &self,
371 index: Option<u8>,
372 builder: NetworkConfigResponseBuilder<P>,
373 ) -> Result<P, Error> {
374 builder
378 .networking_status(*self)?
379 .debug_text(None)?
380 .network_index(index)?
381 .end()
384 }
385}
386
387pub trait Networks {
389 fn max_networks(&self) -> Result<u8, Error>;
393
394 fn networks(&self, f: &mut dyn FnMut(&[u8]) -> Result<(), Error>) -> Result<(), Error>;
396
397 fn creds(
405 &self,
406 network_id: &[u8],
407 f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
408 ) -> Result<u8, NetworksError>;
409
410 fn next_creds(
423 &self,
424 last_network_id: Option<&[u8]>,
425 f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
426 ) -> Result<bool, Error>;
427
428 fn enabled(&self) -> Result<bool, Error>;
430
431 fn set_enabled(&mut self, enabled: bool) -> Result<(), Error>;
433
434 fn add_or_update(&mut self, creds: &WirelessCreds<'_>) -> Result<u8, NetworksError>;
442
443 fn reorder(&mut self, index: u8, network_id: &[u8]) -> Result<u8, NetworksError>;
451
452 fn remove(&mut self, network_id: &[u8]) -> Result<u8, NetworksError>;
458
459 fn managed(&self) -> Result<bool, Error>;
482
483 fn set_managed(&mut self, managed: bool) -> Result<(), Error>;
485
486 fn reset(&mut self) -> Result<(), Error>;
488
489 fn load(&mut self, data: &[u8]) -> Result<(), Error>;
491
492 fn save(&self, buf: &mut [u8]) -> Result<Option<usize>, Error>;
495}
496
497impl<T> Networks for &mut T
498where
499 T: Networks,
500{
501 fn max_networks(&self) -> Result<u8, Error> {
502 (**self).max_networks()
503 }
504
505 fn networks(&self, f: &mut dyn FnMut(&[u8]) -> Result<(), Error>) -> Result<(), Error> {
506 (**self).networks(f)
507 }
508
509 fn creds(
510 &self,
511 network_id: &[u8],
512 f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
513 ) -> Result<u8, NetworksError> {
514 (**self).creds(network_id, f)
515 }
516
517 fn next_creds(
518 &self,
519 last_network_id: Option<&[u8]>,
520 f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
521 ) -> Result<bool, Error> {
522 (**self).next_creds(last_network_id, f)
523 }
524
525 fn enabled(&self) -> Result<bool, Error> {
526 (**self).enabled()
527 }
528
529 fn set_enabled(&mut self, enabled: bool) -> Result<(), Error> {
530 (*self).set_enabled(enabled)
531 }
532
533 fn add_or_update(&mut self, creds: &WirelessCreds<'_>) -> Result<u8, NetworksError> {
534 (*self).add_or_update(creds)
535 }
536
537 fn reorder(&mut self, index: u8, network_id: &[u8]) -> Result<u8, NetworksError> {
538 (*self).reorder(index, network_id)
539 }
540
541 fn remove(&mut self, network_id: &[u8]) -> Result<u8, NetworksError> {
542 (*self).remove(network_id)
543 }
544
545 fn managed(&self) -> Result<bool, Error> {
546 (**self).managed()
547 }
548
549 fn set_managed(&mut self, managed: bool) -> Result<(), Error> {
550 (**self).set_managed(managed)
551 }
552
553 fn reset(&mut self) -> Result<(), Error> {
554 (**self).reset()
555 }
556
557 fn load(&mut self, data: &[u8]) -> Result<(), Error> {
558 (**self).load(data)
559 }
560
561 fn save(&self, buf: &mut [u8]) -> Result<Option<usize>, Error> {
562 (**self).save(buf)
563 }
564}
565
566impl Networks for &mut dyn Networks {
567 fn max_networks(&self) -> Result<u8, Error> {
568 (**self).max_networks()
569 }
570
571 fn networks(&self, f: &mut dyn FnMut(&[u8]) -> Result<(), Error>) -> Result<(), Error> {
572 (**self).networks(f)
573 }
574
575 fn creds(
576 &self,
577 network_id: &[u8],
578 f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
579 ) -> Result<u8, NetworksError> {
580 (**self).creds(network_id, f)
581 }
582
583 fn next_creds(
584 &self,
585 last_network_id: Option<&[u8]>,
586 f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
587 ) -> Result<bool, Error> {
588 (**self).next_creds(last_network_id, f)
589 }
590
591 fn enabled(&self) -> Result<bool, Error> {
592 (**self).enabled()
593 }
594
595 fn set_enabled(&mut self, enabled: bool) -> Result<(), Error> {
596 (**self).set_enabled(enabled)
597 }
598
599 fn add_or_update(&mut self, creds: &WirelessCreds<'_>) -> Result<u8, NetworksError> {
600 (**self).add_or_update(creds)
601 }
602
603 fn reorder(&mut self, index: u8, network_id: &[u8]) -> Result<u8, NetworksError> {
604 (**self).reorder(index, network_id)
605 }
606
607 fn remove(&mut self, network_id: &[u8]) -> Result<u8, NetworksError> {
608 (**self).remove(network_id)
609 }
610
611 fn managed(&self) -> Result<bool, Error> {
612 (**self).managed()
613 }
614
615 fn set_managed(&mut self, managed: bool) -> Result<(), Error> {
616 (**self).set_managed(managed)
617 }
618
619 fn reset(&mut self) -> Result<(), Error> {
620 (**self).reset()
621 }
622
623 fn load(&mut self, data: &[u8]) -> Result<(), Error> {
624 (**self).load(data)
625 }
626
627 fn save(&self, buf: &mut [u8]) -> Result<Option<usize>, Error> {
628 (**self).save(buf)
629 }
630}
631
632pub trait NetworksAccess {
633 fn access<F: FnOnce(&mut dyn Networks) -> R, R>(&self, f: F) -> R;
634}
635
636impl<T> NetworksAccess for &T
637where
638 T: NetworksAccess,
639{
640 fn access<F: FnOnce(&mut dyn Networks) -> R, R>(&self, f: F) -> R {
641 (*self).access(f)
642 }
643}
644
645pub struct DummyNetworkAccess;
646
647impl NetworksAccess for DummyNetworkAccess {
648 fn access<F: FnOnce(&mut dyn Networks) -> R, R>(&self, f: F) -> R {
649 f(&mut DummyNetworks)
650 }
651}
652
653pub struct DummyNetworks;
654
655impl Networks for DummyNetworks {
656 fn max_networks(&self) -> Result<u8, Error> {
657 Ok(0)
658 }
659
660 fn networks(&self, _f: &mut dyn FnMut(&[u8]) -> Result<(), Error>) -> Result<(), Error> {
661 Ok(())
662 }
663
664 fn creds(
665 &self,
666 _network_id: &[u8],
667 _f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
668 ) -> Result<u8, NetworksError> {
669 Err(NetworksError::NetworkIdNotFound)
670 }
671
672 fn next_creds(
673 &self,
674 _last_network_id: Option<&[u8]>,
675 _f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
676 ) -> Result<bool, Error> {
677 Ok(false)
678 }
679
680 fn enabled(&self) -> Result<bool, Error> {
681 Ok(false)
682 }
683
684 fn set_enabled(&mut self, _enabled: bool) -> Result<(), Error> {
685 Ok(())
686 }
687
688 fn add_or_update(&mut self, _creds: &WirelessCreds<'_>) -> Result<u8, NetworksError> {
689 Err(NetworksError::Other(ErrorCode::InvalidAction.into()))
690 }
691
692 fn reorder(&mut self, _index: u8, _network_id: &[u8]) -> Result<u8, NetworksError> {
693 Err(NetworksError::Other(ErrorCode::InvalidAction.into()))
694 }
695
696 fn remove(&mut self, _network_id: &[u8]) -> Result<u8, NetworksError> {
697 Err(NetworksError::Other(ErrorCode::InvalidAction.into()))
698 }
699
700 fn managed(&self) -> Result<bool, Error> {
701 Ok(false)
702 }
703
704 fn set_managed(&mut self, _managed: bool) -> Result<(), Error> {
705 Ok(())
706 }
707
708 fn reset(&mut self) -> Result<(), Error> {
709 Ok(())
710 }
711
712 fn load(&mut self, _data: &[u8]) -> Result<(), Error> {
713 Ok(())
714 }
715
716 fn save(&self, _buf: &mut [u8]) -> Result<Option<usize>, Error> {
717 Ok(None)
718 }
719}
720
721pub trait NetCtl {
723 fn net_type(&self) -> NetworkType;
725
726 fn connect_max_time_seconds(&self) -> u8 {
730 30
731 }
732
733 fn scan_max_time_seconds(&self) -> u8 {
737 30
738 }
739
740 fn supported_wifi_bands<F>(&self, mut f: F) -> Result<(), Error>
746 where
747 F: FnMut(WiFiBandEnum) -> Result<(), Error>,
748 {
749 f(WiFiBandEnum::V2G4)
750 }
751
752 fn supported_thread_features(&self) -> ThreadCapabilitiesBitmap {
758 ThreadCapabilitiesBitmap::empty()
759 }
760
761 fn thread_version(&self) -> u16 {
767 4
768 }
769
770 async fn scan<F>(&self, network: Option<&[u8]>, f: F) -> Result<(), NetCtlError>
774 where
775 F: FnMut(&NetworkScanInfo) -> Result<(), Error>;
776
777 async fn connect(&self, creds: &WirelessCreds) -> Result<(), NetCtlError>;
781}
782
783impl<T> NetCtl for &T
784where
785 T: NetCtl,
786{
787 fn net_type(&self) -> NetworkType {
788 (*self).net_type()
789 }
790
791 fn connect_max_time_seconds(&self) -> u8 {
792 (*self).connect_max_time_seconds()
793 }
794
795 fn scan_max_time_seconds(&self) -> u8 {
796 (*self).scan_max_time_seconds()
797 }
798
799 fn supported_wifi_bands<F>(&self, f: F) -> Result<(), Error>
800 where
801 F: FnMut(WiFiBandEnum) -> Result<(), Error>,
802 {
803 (*self).supported_wifi_bands(f)
804 }
805
806 fn supported_thread_features(&self) -> ThreadCapabilitiesBitmap {
807 (*self).supported_thread_features()
808 }
809
810 fn thread_version(&self) -> u16 {
811 (*self).thread_version()
812 }
813
814 fn scan<F>(&self, network: Option<&[u8]>, f: F) -> impl Future<Output = Result<(), NetCtlError>>
815 where
816 F: FnMut(&NetworkScanInfo) -> Result<(), Error>,
817 {
818 (*self).scan(network, f)
819 }
820
821 fn connect(&self, creds: &WirelessCreds<'_>) -> impl Future<Output = Result<(), NetCtlError>> {
822 (*self).connect(creds)
823 }
824}
825
826pub trait NetCtlStatus {
828 fn last_networking_status(&self) -> Result<Option<NetworkCommissioningStatusEnum>, Error>;
832
833 fn last_network_id<F, R>(&self, f: F) -> Result<R, Error>
837 where
838 F: FnOnce(Option<&[u8]>) -> Result<R, Error>;
839
840 fn last_connect_error_value(&self) -> Result<Option<i32>, Error>;
844}
845
846impl<T> NetCtlStatus for &T
847where
848 T: NetCtlStatus,
849{
850 fn last_networking_status(&self) -> Result<Option<NetworkCommissioningStatusEnum>, Error> {
851 (*self).last_networking_status()
852 }
853
854 fn last_network_id<F, R>(&self, f: F) -> Result<R, Error>
855 where
856 F: FnOnce(Option<&[u8]>) -> Result<R, Error>,
857 {
858 (*self).last_network_id(f)
859 }
860
861 fn last_connect_error_value(&self) -> Result<Option<i32>, Error> {
862 (*self).last_connect_error_value()
863 }
864}
865
866pub struct SharedNetworks<N> {
868 state: Mutex<RefCell<N>>,
869 state_changed: Notification,
870}
871
872impl<N> SharedNetworks<N> {
873 pub const fn new(networks: N) -> Self {
875 Self {
876 state: Mutex::new(RefCell::new(networks)),
877 state_changed: Notification::new(),
878 }
879 }
880
881 pub fn init(networks: impl Init<N>) -> impl Init<Self> {
883 init!(Self {
884 state <- Mutex::init(RefCell::init(networks)),
885 state_changed <- Notification::init(),
886 })
887 }
888
889 pub fn get_mut(&mut self) -> &mut RefCell<N> {
891 self.state.get_mut()
892 }
893
894 pub(crate) fn with_raw<F, R>(&self, f: F) -> R
900 where
901 F: FnOnce(&mut N) -> R,
902 {
903 self.state.lock(|state| f(&mut state.borrow_mut()))
904 }
905
906 pub fn wait_state_changed(&self) -> impl Future<Output = ()> + '_ {
908 self.state_changed.wait()
909 }
910}
911
912impl<N> DynBase for SharedNetworks<N> where N: Send {}
913
914impl<N> NetworksAccess for SharedNetworks<N>
915where
916 N: Networks,
917{
918 fn access<F: FnOnce(&mut dyn Networks) -> R, R>(&self, f: F) -> R {
919 self.state.lock(|state| {
920 let mut networks = state.borrow_mut();
921
922 let mut instance = SharedNetworksInstance {
923 networks: &mut *networks,
924 changed: &self.state_changed,
925 };
926
927 f(&mut instance)
928 })
929 }
930}
931
932impl<N> NetChangeNotif for SharedNetworks<N> {
933 fn wait_changed(&self) -> impl Future<Output = ()> {
934 self.state_changed.wait()
935 }
936}
937
938pub struct SharedNetworksInstance<'a> {
940 networks: &'a mut dyn Networks,
941 changed: &'a Notification,
942}
943
944impl Networks for SharedNetworksInstance<'_> {
945 fn max_networks(&self) -> Result<u8, Error> {
946 self.networks.max_networks()
947 }
948
949 fn networks(&self, f: &mut dyn FnMut(&[u8]) -> Result<(), Error>) -> Result<(), Error> {
950 self.networks.networks(f)
951 }
952
953 fn creds(
954 &self,
955 network_id: &[u8],
956 f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
957 ) -> Result<u8, NetworksError> {
958 self.networks.creds(network_id, f)
959 }
960
961 fn next_creds(
962 &self,
963 last_network_id: Option<&[u8]>,
964 f: &mut dyn FnMut(&WirelessCreds) -> Result<(), Error>,
965 ) -> Result<bool, Error> {
966 self.networks.next_creds(last_network_id, f)
967 }
968
969 fn enabled(&self) -> Result<bool, Error> {
970 self.networks.enabled()
971 }
972
973 fn set_enabled(&mut self, enabled: bool) -> Result<(), Error> {
974 self.networks.set_enabled(enabled)?;
975
976 self.changed.notify();
977
978 Ok(())
979 }
980
981 fn add_or_update(&mut self, creds: &WirelessCreds<'_>) -> Result<u8, NetworksError> {
982 let index = self.networks.add_or_update(creds)?;
983
984 self.changed.notify();
985
986 Ok(index)
987 }
988
989 fn reorder(&mut self, index: u8, network_id: &[u8]) -> Result<u8, NetworksError> {
990 let index = self.networks.reorder(index, network_id)?;
991
992 self.changed.notify();
993
994 Ok(index)
995 }
996
997 fn remove(&mut self, network_id: &[u8]) -> Result<u8, NetworksError> {
998 let index = self.networks.remove(network_id)?;
999
1000 self.changed.notify();
1001
1002 Ok(index)
1003 }
1004
1005 fn managed(&self) -> Result<bool, Error> {
1006 self.networks.managed()
1007 }
1008
1009 fn set_managed(&mut self, managed: bool) -> Result<(), Error> {
1010 self.networks.set_managed(managed)?;
1011
1012 self.changed.notify();
1013
1014 Ok(())
1015 }
1016
1017 fn reset(&mut self) -> Result<(), Error> {
1018 self.networks.reset()?;
1019
1020 self.changed.notify();
1021
1022 Ok(())
1023 }
1024
1025 fn load(&mut self, data: &[u8]) -> Result<(), Error> {
1026 self.networks.load(data)?;
1027
1028 self.changed.notify();
1035
1036 Ok(())
1037 }
1038
1039 fn save(&self, buf: &mut [u8]) -> Result<Option<usize>, Error> {
1040 let len = self.networks.save(buf)?;
1041
1042 Ok(len)
1043 }
1044}
1045
1046#[derive(Clone)]
1048pub struct NetCommHandler<'a, T> {
1049 dataver: Dataver,
1050 net_ctl: T,
1051 wireless_diag: &'a dyn WirelessDiag,
1056}
1057
1058impl<'a, T> NetCommHandler<'a, T> {
1059 pub const fn new(dataver: Dataver, net_ctl: T, wireless_diag: &'a dyn WirelessDiag) -> Self {
1062 Self {
1063 dataver,
1064 net_ctl,
1065 wireless_diag,
1066 }
1067 }
1068
1069 fn apply_breadcrumb(
1075 ctx: &impl InvokeContext,
1076 status: NetworkCommissioningStatusEnum,
1077 breadcrumb: Option<u64>,
1078 ) -> Result<(), Error> {
1079 if !matches!(status, NetworkCommissioningStatusEnum::Success) {
1080 return Ok(());
1081 }
1082
1083 let Some(breadcrumb) = breadcrumb else {
1084 return Ok(());
1085 };
1086
1087 ctx.exchange().with_state(|state| {
1088 state.failsafe.set_breadcrumb(breadcrumb);
1089
1090 Ok(())
1091 })
1092 }
1093
1094 fn has_networks(ctx: &impl ReadContext) -> Result<bool, Error> {
1100 ctx.networks().access(|networks| {
1101 let mut any = false;
1102
1103 networks.networks(&mut |_| {
1104 any = true;
1105
1106 Ok(())
1107 })?;
1108
1109 Ok(any)
1110 })
1111 }
1112
1113 pub const fn adapt(self) -> HandlerAsyncAdaptor<Self> {
1115 HandlerAsyncAdaptor(self)
1116 }
1117}
1118
1119impl<T> ClusterAsyncHandler for NetCommHandler<'_, T>
1120where
1121 T: NetCtl + NetCtlStatus,
1122{
1123 const CLUSTER: Cluster<'static> = NetworkType::Ethernet.cluster(); fn dataver(&self) -> u32 {
1126 self.dataver.get()
1127 }
1128
1129 fn dataver_changed(&self) {
1130 self.dataver.changed();
1131 }
1132
1133 fn max_networks(&self, ctx: impl ReadContext) -> impl Future<Output = Result<u8, Error>> {
1134 ready(ctx.networks().access(|networks| networks.max_networks()))
1135 }
1136
1137 fn connect_max_time_seconds(
1138 &self,
1139 _ctx: impl ReadContext,
1140 ) -> impl Future<Output = Result<u8, Error>> {
1141 ready(Ok(self.net_ctl.connect_max_time_seconds()))
1142 }
1143
1144 fn scan_max_time_seconds(
1145 &self,
1146 _ctx: impl ReadContext,
1147 ) -> impl Future<Output = Result<u8, Error>> {
1148 ready(Ok(self.net_ctl.scan_max_time_seconds()))
1149 }
1150
1151 fn supported_wi_fi_bands<P: TLVBuilderParent>(
1152 &self,
1153 _ctx: impl ReadContext,
1154 builder: ArrayAttributeRead<
1155 ToTLVArrayBuilder<P, WiFiBandEnum>,
1156 ToTLVBuilder<P, WiFiBandEnum>,
1157 >,
1158 ) -> impl Future<Output = Result<P, Error>> {
1159 ready(match builder {
1160 ArrayAttributeRead::ReadAll(builder) => builder.with(|builder| {
1161 let mut builder = Some(builder);
1162
1163 self.net_ctl.supported_wifi_bands(|band| {
1164 builder = Some(unwrap!(builder.take()).push(&band)?);
1165
1166 Ok(())
1167 })?;
1168
1169 unwrap!(builder.take()).end()
1170 }),
1171 ArrayAttributeRead::ReadOne(index, builder) => {
1172 let mut current = 0;
1173 let mut builder = Some(builder);
1174 let mut parent = None;
1175
1176 match self.net_ctl.supported_wifi_bands(|band| {
1177 if current == index {
1178 parent = Some(unwrap!(builder.take()).set(&band)?);
1179 }
1180
1181 current += 1;
1182
1183 Ok(())
1184 }) {
1185 Err(e) => Err(e),
1186 Ok(()) => {
1187 if let Some(parent) = parent {
1188 Ok(parent)
1189 } else {
1190 Err(ErrorCode::ConstraintError.into())
1191 }
1192 }
1193 }
1194 }
1195 ArrayAttributeRead::ReadNone(builder) => builder.end(),
1196 })
1197 }
1198
1199 fn supported_thread_features(
1200 &self,
1201 _ctx: impl ReadContext,
1202 ) -> impl Future<Output = Result<ThreadCapabilitiesBitmap, Error>> {
1203 ready(Ok(self.net_ctl.supported_thread_features()))
1204 }
1205
1206 fn thread_version(&self, _ctx: impl ReadContext) -> impl Future<Output = Result<u16, Error>> {
1207 ready(Ok(self.net_ctl.thread_version()))
1208 }
1209
1210 fn networks<P: TLVBuilderParent>(
1211 &self,
1212 ctx: impl ReadContext,
1213 builder: ArrayAttributeRead<NetworkInfoStructArrayBuilder<P>, NetworkInfoStructBuilder<P>>,
1214 ) -> impl Future<Output = Result<P, Error>> {
1215 let connected = |network_id: &[u8]| -> Result<bool, Error> {
1221 if !self.wireless_diag.connected()? {
1222 return Ok(false);
1223 }
1224
1225 self.net_ctl
1226 .last_network_id(|last| Ok(last == Some(network_id)))
1227 };
1228
1229 ready(ctx.networks().access(|networks| match builder {
1230 ArrayAttributeRead::ReadAll(builder) => builder.with(|builder| {
1231 let mut builder = Some(builder);
1232
1233 networks.networks(&mut |network_id| {
1234 builder = Some(network_read_into(
1235 network_id,
1236 connected(network_id)?,
1237 unwrap!(builder.take()).push()?,
1238 )?);
1239
1240 Ok(())
1241 })?;
1242
1243 unwrap!(builder.take()).end()
1244 }),
1245 ArrayAttributeRead::ReadOne(index, builder) => {
1246 let mut current = 0;
1247 let mut builder = Some(builder);
1248 let mut parent = None;
1249
1250 networks.networks(&mut |network_id| {
1251 if current == index {
1252 parent = Some(network_read_into(
1253 network_id,
1254 connected(network_id)?,
1255 unwrap!(builder.take()),
1256 )?);
1257 }
1258
1259 current += 1;
1260
1261 Ok(())
1262 })?;
1263
1264 if let Some(parent) = parent {
1265 Ok(parent)
1266 } else {
1267 Err(ErrorCode::ConstraintError.into())
1268 }
1269 }
1270 ArrayAttributeRead::ReadNone(builder) => builder.end(),
1271 }))
1272 }
1273
1274 fn interface_enabled(
1275 &self,
1276 ctx: impl ReadContext,
1277 ) -> impl Future<Output = Result<bool, Error>> {
1278 ready(ctx.networks().access(|networks| networks.enabled()))
1279 }
1280
1281 fn last_networking_status(
1282 &self,
1283 ctx: impl ReadContext,
1284 ) -> impl Future<Output = Result<Nullable<NetworkCommissioningStatusEnum>, Error>> {
1285 ready((|| {
1286 if !Self::has_networks(&ctx)? {
1287 return Ok(Nullable::none());
1288 }
1289
1290 self.net_ctl.last_networking_status().map(Nullable::new)
1291 })())
1292 }
1293
1294 fn last_network_id<P: TLVBuilderParent>(
1295 &self,
1296 ctx: impl ReadContext,
1297 builder: NullableBuilder<P, OctetsBuilder<P>>,
1298 ) -> impl Future<Output = Result<P, Error>> {
1299 ready((|| {
1300 if !Self::has_networks(&ctx)? {
1301 return builder.null();
1302 }
1303
1304 self.net_ctl.last_network_id(|network_id| {
1305 if let Some(network_id) = network_id {
1306 builder.non_null()?.set(Octets::new(network_id))
1307 } else {
1308 builder.null()
1309 }
1310 })
1311 })())
1312 }
1313
1314 fn last_connect_error_value(
1315 &self,
1316 ctx: impl ReadContext,
1317 ) -> impl Future<Output = Result<Nullable<i32>, Error>> {
1318 ready((|| {
1319 if !Self::has_networks(&ctx)? {
1320 return Ok(Nullable::none());
1321 }
1322
1323 self.net_ctl.last_connect_error_value().map(Nullable::new)
1324 })())
1325 }
1326
1327 async fn set_interface_enabled(
1328 &self,
1329 ctx: impl WriteContext,
1330 value: bool,
1331 ) -> Result<(), Error> {
1332 let mut persist = Persist::new(ctx.kv());
1333
1334 ctx.exchange().with_state(|state| {
1335 ctx.networks().access(|networks| {
1336 networks.set_enabled(value)?;
1337
1338 if !state.failsafe.is_armed() {
1342 persist.store(NETWORKS_KEY, |buf| networks.save(buf))?;
1343 }
1344
1345 Ok(())
1346 })
1347 })?;
1348
1349 persist.run()
1350 }
1351
1352 async fn handle_scan_networks<P: TLVBuilderParent>(
1353 &self,
1354 ctx: impl InvokeContext,
1355 request: ScanNetworksRequest<'_>,
1356 response: ScanNetworksResponseBuilder<P>,
1357 ) -> Result<P, Error> {
1358 match self.net_ctl.net_type() {
1359 NetworkType::Thread => {
1360 let mut builder = Some(response);
1361 let mut array_builder = None;
1362
1363 let (status, _, _) = NetworkCommissioningStatusEnum::map_ctl(
1364 self.net_ctl
1365 .scan(
1366 request
1367 .ssid()?
1368 .as_ref()
1369 .and_then(|ssid| ssid.as_opt_ref())
1370 .map(|ssid| ssid.0),
1371 |network| {
1372 let abuilder = if let Some(builder) = builder.take() {
1373 builder
1374 .networking_status(NetworkCommissioningStatusEnum::Success)?
1375 .debug_text(None)?
1376 .wi_fi_scan_results()?
1377 .none()
1378 .thread_scan_results()?
1379 .some()?
1380 } else {
1381 unwrap!(array_builder.take())
1382 };
1383
1384 array_builder = Some(network.thread_read_into(abuilder.push()?)?);
1385
1386 Ok(())
1387 },
1388 )
1389 .await
1390 .map(|_| 0),
1391 )?;
1392
1393 Self::apply_breadcrumb(&ctx, status, request.breadcrumb()?)?;
1394
1395 if let Some(builder) = builder {
1396 builder
1397 .networking_status(status)?
1398 .debug_text(None)?
1399 .wi_fi_scan_results()?
1400 .none()
1401 .thread_scan_results()?
1402 .none()
1403 .end()
1404 } else {
1405 unwrap!(array_builder.take()).end()?.end()
1406 }
1407 }
1408 NetworkType::Wifi => {
1409 let mut builder = Some(response);
1410 let mut array_builder = None;
1411
1412 let (status, _, _) = NetworkCommissioningStatusEnum::map_ctl(
1413 self.net_ctl
1414 .scan(
1415 request
1416 .ssid()?
1417 .as_ref()
1418 .and_then(|ssid| ssid.as_opt_ref())
1419 .map(|ssid| ssid.0),
1420 |network| {
1421 let abuilder = if let Some(builder) = builder.take() {
1422 builder
1423 .networking_status(NetworkCommissioningStatusEnum::Success)?
1424 .debug_text(None)?
1425 .wi_fi_scan_results()?
1426 .some()?
1427 } else {
1428 unwrap!(array_builder.take())
1429 };
1430
1431 array_builder = Some(network.wifi_read_into(abuilder.push()?)?);
1432
1433 Ok(())
1434 },
1435 )
1436 .await
1437 .map(|_| 0),
1438 )?;
1439
1440 Self::apply_breadcrumb(&ctx, status, request.breadcrumb()?)?;
1441
1442 if let Some(builder) = builder {
1443 builder
1444 .networking_status(status)?
1445 .debug_text(None)?
1446 .wi_fi_scan_results()?
1447 .none()
1448 .thread_scan_results()?
1449 .none()
1450 .end()
1451 } else {
1452 unwrap!(array_builder.take())
1453 .end()?
1454 .thread_scan_results()?
1455 .none()
1456 .end()
1457 }
1458 }
1459 NetworkType::Ethernet => Err(ErrorCode::InvalidAction.into()),
1460 }
1461 }
1462
1463 async fn handle_add_or_update_wi_fi_network<P: TLVBuilderParent>(
1464 &self,
1465 ctx: impl InvokeContext,
1466 request: AddOrUpdateWiFiNetworkRequest<'_>,
1467 response: NetworkConfigResponseBuilder<P>,
1468 ) -> Result<P, Error> {
1469 let (status, _, index) = NetworkCommissioningStatusEnum::map(
1470 GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
1471 ctx.networks().access(|networks| {
1472 let index = networks.add_or_update(&WirelessCreds::Wifi {
1473 ssid: request.ssid()?.0,
1474 pass: request.credentials()?.0,
1475 })?;
1476
1477 Ok(index)
1478 })
1479 }),
1480 )?;
1481
1482 Self::apply_breadcrumb(&ctx, status, request.breadcrumb()?)?;
1483
1484 ctx.notify_own_cluster_changed();
1486
1487 status.read_into(index, response)
1488 }
1489
1490 async fn handle_add_or_update_thread_network<P: TLVBuilderParent>(
1491 &self,
1492 ctx: impl InvokeContext,
1493 request: AddOrUpdateThreadNetworkRequest<'_>,
1494 response: NetworkConfigResponseBuilder<P>,
1495 ) -> Result<P, Error> {
1496 let (status, _, index) = NetworkCommissioningStatusEnum::map(
1497 GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
1498 ctx.networks().access(|networks| {
1499 let index = networks.add_or_update(&WirelessCreds::Thread {
1500 dataset_tlv: request.operational_dataset()?.0,
1501 })?;
1502
1503 Ok(index)
1504 })
1505 }),
1506 )?;
1507
1508 Self::apply_breadcrumb(&ctx, status, request.breadcrumb()?)?;
1509
1510 ctx.notify_own_cluster_changed();
1512
1513 status.read_into(index, response)
1514 }
1515
1516 async fn handle_remove_network<P: TLVBuilderParent>(
1517 &self,
1518 ctx: impl InvokeContext,
1519 request: RemoveNetworkRequest<'_>,
1520 response: NetworkConfigResponseBuilder<P>,
1521 ) -> Result<P, Error> {
1522 let (status, _, index) = NetworkCommissioningStatusEnum::map(
1523 GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
1524 ctx.networks().access(|networks| {
1525 let index = networks.remove(request.network_id()?.0)?;
1526
1527 Ok(index)
1528 })
1529 }),
1530 )?;
1531
1532 Self::apply_breadcrumb(&ctx, status, request.breadcrumb()?)?;
1533
1534 ctx.notify_own_cluster_changed();
1536
1537 status.read_into(index, response)
1538 }
1539
1540 async fn handle_connect_network<P: TLVBuilderParent>(
1541 &self,
1542 ctx: impl InvokeContext,
1543 request: ConnectNetworkRequest<'_>,
1544 mut response: ConnectNetworkResponseBuilder<P>,
1545 ) -> Result<P, Error> {
1546 if request.network_id()?.0.len() > MAX_WIRELESS_NETWORK_ID_LEN {
1547 return Err(ErrorCode::ConstraintError.into());
1548 }
1549
1550 let (status, err_code) = match self.net_ctl.net_type() {
1551 NetworkType::Thread => {
1552 let dataset_buf = response.writer().available_space();
1553 let mut dataset_len = 0;
1554
1555 let (mut status, mut err_code, _) = NetworkCommissioningStatusEnum::map(
1556 GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
1557 ctx.networks().access(|networks| {
1558 let index = networks.creds(request.network_id()?.0, &mut |creds| {
1559 let WirelessCreds::Thread { dataset_tlv } = creds else {
1560 error!("Thread creds expected");
1561 return Err(ErrorCode::InvalidAction.into());
1562 };
1563
1564 if dataset_tlv.len() > dataset_buf.len() {
1565 error!("Dataset too large");
1566 return Err(ErrorCode::ConstraintError.into());
1567 }
1568
1569 dataset_buf[..dataset_tlv.len()].copy_from_slice(dataset_tlv);
1570 dataset_len = dataset_tlv.len();
1571
1572 Ok(())
1573 })?;
1574
1575 networks.set_managed(false)?;
1581
1582 Ok(index)
1583 })
1584 }),
1585 )?;
1586
1587 if matches!(status, NetworkCommissioningStatusEnum::Success) {
1588 (status, err_code, _) = NetworkCommissioningStatusEnum::map_ctl(
1589 self.net_ctl
1590 .connect(&WirelessCreds::Thread {
1591 dataset_tlv: &dataset_buf[..dataset_len],
1592 })
1593 .await,
1594 )?;
1595 }
1596
1597 (status, err_code)
1598 }
1599 NetworkType::Wifi => {
1600 let buf = response.writer().available_space();
1601 let (ssid_buf, pass_buf) = buf.split_at_mut(buf.len() / 2);
1602 let mut ssid_len = 0;
1603 let mut pass_len = 0;
1604
1605 let (mut status, mut err_code, _) = NetworkCommissioningStatusEnum::map(
1606 GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
1607 ctx.networks().access(|networks| {
1608 let index = networks.creds(request.network_id()?.0, &mut |creds| {
1609 let WirelessCreds::Wifi { ssid, pass } = creds else {
1610 error!("Wifi creds expected");
1611 return Err(ErrorCode::InvalidAction.into());
1612 };
1613
1614 if ssid.len() > ssid_buf.len() {
1615 error!("SSID too large");
1616 return Err(ErrorCode::ConstraintError.into());
1617 }
1618
1619 if pass.len() > pass_buf.len() {
1620 error!("Password too large");
1621 return Err(ErrorCode::ConstraintError.into());
1622 }
1623
1624 ssid_buf[..ssid.len()].copy_from_slice(ssid);
1625 ssid_len = ssid.len();
1626 pass_buf[..pass.len()].copy_from_slice(pass);
1627 pass_len = pass.len();
1628
1629 Ok(())
1630 })?;
1631
1632 networks.set_managed(false)?;
1635
1636 Ok(index)
1637 })
1638 }),
1639 )?;
1640
1641 if matches!(status, NetworkCommissioningStatusEnum::Success) {
1642 (status, err_code, _) = NetworkCommissioningStatusEnum::map_ctl(
1643 self.net_ctl
1644 .connect(&WirelessCreds::Wifi {
1645 ssid: &ssid_buf[..ssid_len],
1646 pass: &pass_buf[..pass_len],
1647 })
1648 .await,
1649 )?;
1650 }
1651
1652 (status, err_code)
1653 }
1654 NetworkType::Ethernet => {
1655 return Err(ErrorCode::InvalidAction.into());
1656 }
1657 };
1658
1659 Self::apply_breadcrumb(&ctx, status, request.breadcrumb()?)?;
1660
1661 ctx.notify_own_cluster_changed();
1663
1664 response
1665 .networking_status(status)?
1666 .debug_text(None)?
1667 .error_value(Nullable::new(err_code))?
1668 .end()
1669 }
1670
1671 async fn handle_reorder_network<P: TLVBuilderParent>(
1672 &self,
1673 ctx: impl InvokeContext,
1674 request: ReorderNetworkRequest<'_>,
1675 response: NetworkConfigResponseBuilder<P>,
1676 ) -> Result<P, Error> {
1677 let (status, _, index) = NetworkCommissioningStatusEnum::map(
1678 GenCommHandler::with_armed_failsafe_ex(&ctx, |_, _| {
1679 ctx.networks().access(|networks| {
1680 let index =
1681 networks.reorder(request.network_index()? as _, request.network_id()?.0)?;
1682
1683 Ok(index)
1684 })
1685 }),
1686 )?;
1687
1688 Self::apply_breadcrumb(&ctx, status, request.breadcrumb()?)?;
1689
1690 ctx.notify_own_cluster_changed();
1692
1693 status.read_into(index, response)
1694 }
1695
1696 }
1712
1713impl<T> Debug for NetCommHandler<'_, T> {
1714 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1715 f.debug_struct("NetCommHandler")
1716 .field("dataver", &self.dataver.get())
1717 .finish()
1718 }
1719}
1720
1721#[cfg(feature = "defmt")]
1722impl<T> defmt::Format for NetCommHandler<'_, T> {
1723 fn format(&self, f: defmt::Formatter) {
1724 defmt::write!(f, "NetCommHandler {{ dataver: {} }}", self.dataver.get());
1725 }
1726}