Skip to main content

rs_matter/dm/clusters/
wifi_diag.rs

1/*
2 *
3 *    Copyright (c) 2025-2026 Project CHIP Authors
4 *
5 *    Licensed under the Apache License, Version 2.0 (the "License");
6 *    you may not use this file except in compliance with the License.
7 *    You may obtain a copy of the License at
8 *
9 *        http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *    Unless required by applicable law or agreed to in writing, software
12 *    distributed under the License is distributed on an "AS IS" BASIS,
13 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *    See the License for the specific language governing permissions and
15 *    limitations under the License.
16 */
17
18//! This module contains the implementation of the Wifi Network Diagnostics cluster and its handler.
19
20use core::fmt::Debug;
21
22use crate::dm::{Cluster, Dataver, InvokeContext, ReadContext};
23use crate::error::{Error, ErrorCode};
24use crate::tlv::{Nullable, NullableBuilder, Octets, OctetsBuilder, TLVBuilderParent};
25use crate::utils::sync::DynBase;
26use crate::with;
27
28pub use crate::dm::clusters::decl::wi_fi_network_diagnostics::*;
29
30/// A trait required by `WifiDiag` and `ThreadDiag` that provides information whether the
31/// device is connected to a wireless network
32pub trait WirelessDiag: DynBase {
33    /// Returns true if the device is connected to a wireless network
34    fn connected(&self) -> Result<bool, Error> {
35        Ok(false)
36    }
37}
38
39impl<T> WirelessDiag for &T
40where
41    T: WirelessDiag,
42{
43    fn connected(&self) -> Result<bool, Error> {
44        (*self).connected()
45    }
46}
47
48impl WirelessDiag for () {}
49
50/// A [`WirelessDiag`] that always reports the node as connected.
51///
52/// For links whose liveness is implied by the node answering at all - notably
53/// the wired interface behind `EthNetCtl`, which cannot carry the read of an
54/// attribute while being down.
55pub struct AlwaysConnected;
56
57impl DynBase for AlwaysConnected {}
58
59impl WirelessDiag for AlwaysConnected {
60    fn connected(&self) -> Result<bool, Error> {
61        Ok(true)
62    }
63}
64
65/// A trait for the Wifi Diagnostics cluster.
66///
67/// The names of the methods in this trait are matching 1:1 the mandatory attributes of the
68/// Wifi Diagnostics cluster.
69pub trait WifiDiag: WirelessDiag {
70    #[allow(clippy::type_complexity)]
71    fn bssid(&self, f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>) -> Result<(), Error> {
72        f(None)
73    }
74
75    fn security_type(&self) -> Result<Nullable<SecurityTypeEnum>, Error> {
76        Ok(Nullable::none())
77    }
78
79    fn wi_fi_version(&self) -> Result<Nullable<WiFiVersionEnum>, Error> {
80        Ok(Nullable::none())
81    }
82
83    fn channel_number(&self) -> Result<Nullable<u16>, Error> {
84        Ok(Nullable::none())
85    }
86
87    fn rssi(&self) -> Result<Nullable<i8>, Error> {
88        Ok(Nullable::none())
89    }
90}
91
92impl<T> WifiDiag for &T
93where
94    T: WifiDiag,
95{
96    fn bssid(&self, f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>) -> Result<(), Error> {
97        (*self).bssid(f)
98    }
99
100    fn security_type(&self) -> Result<Nullable<SecurityTypeEnum>, Error> {
101        (*self).security_type()
102    }
103
104    fn wi_fi_version(&self) -> Result<Nullable<WiFiVersionEnum>, Error> {
105        (*self).wi_fi_version()
106    }
107
108    fn channel_number(&self) -> Result<Nullable<u16>, Error> {
109        (*self).channel_number()
110    }
111
112    fn rssi(&self) -> Result<Nullable<i8>, Error> {
113        (*self).rssi()
114    }
115}
116
117impl WifiDiag for () {}
118
119/// A cluster implementing the Matter Wifi Diagnostics Cluster.
120#[derive(Clone)]
121pub struct WifiDiagHandler<'a> {
122    dataver: Dataver,
123    diag: &'a dyn WifiDiag,
124}
125
126impl<'a> WifiDiagHandler<'a> {
127    /// Create a new instance.
128    pub const fn new(dataver: Dataver, diag: &'a dyn WifiDiag) -> Self {
129        Self { dataver, diag }
130    }
131
132    /// Adapt the handler instance to the generic `rs-matter` `Handler` trait
133    pub const fn adapt(self) -> HandlerAdaptor<Self> {
134        HandlerAdaptor(self)
135    }
136}
137
138impl ClusterHandler for WifiDiagHandler<'_> {
139    const CLUSTER: Cluster<'static> = FULL_CLUSTER.with_attrs(with!(required)).with_cmds(with!());
140
141    fn dataver(&self) -> u32 {
142        self.dataver.get()
143    }
144
145    fn dataver_changed(&self) {
146        self.dataver.changed();
147    }
148
149    fn bssid<P: TLVBuilderParent>(
150        &self,
151        _ctx: impl ReadContext,
152        builder: NullableBuilder<P, OctetsBuilder<P>>,
153    ) -> Result<P, Error> {
154        let mut builder = Some(builder);
155        let mut parent = None;
156
157        self.diag.bssid(&mut |bssid| {
158            let builder = unwrap!(builder.take());
159
160            parent = Some(if let Some(bssid) = bssid {
161                builder.non_null()?.set(Octets::new(bssid))?
162            } else {
163                builder.null()?
164            });
165
166            Ok(())
167        })?;
168
169        Ok(unwrap!(parent.take()))
170    }
171
172    fn security_type(&self, _ctx: impl ReadContext) -> Result<Nullable<SecurityTypeEnum>, Error> {
173        self.diag.security_type()
174    }
175
176    fn wi_fi_version(&self, _ctx: impl ReadContext) -> Result<Nullable<WiFiVersionEnum>, Error> {
177        self.diag.wi_fi_version()
178    }
179
180    fn channel_number(&self, _ctx: impl ReadContext) -> Result<Nullable<u16>, Error> {
181        self.diag.channel_number()
182    }
183
184    fn rssi(&self, _ctx: impl ReadContext) -> Result<Nullable<i8>, Error> {
185        self.diag.rssi()
186    }
187
188    fn handle_reset_counts(&self, _ctx: impl InvokeContext) -> Result<(), Error> {
189        Err(ErrorCode::CommandNotFound.into())
190    }
191}
192
193impl Debug for WifiDiagHandler<'_> {
194    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
195        f.debug_struct("WifiDiagHandler")
196            .field("dataver", &self.dataver)
197            .finish()
198    }
199}
200
201#[cfg(feature = "defmt")]
202impl defmt::Format for WifiDiagHandler<'_> {
203    fn format(&self, f: defmt::Formatter) {
204        defmt::write!(f, "WifiDiagHandler {{ dataver: {} }}", self.dataver.get());
205    }
206}