Skip to main content

rs_matter_stack/
wireless.rs

1use core::marker::PhantomData;
2use core::pin::pin;
3
4use embassy_futures::select::select3;
5
6use rs_matter::crypto::Crypto;
7use rs_matter::dm::clusters::gen_diag::NetifDiag;
8use rs_matter::dm::clusters::net_comm::{self, NetCtlError, NetworkType, WirelessCreds};
9use rs_matter::dm::clusters::wifi_diag::WirelessDiag;
10use rs_matter::dm::clusters::{thread_diag, wifi_diag};
11use rs_matter::dm::networks::wireless::{NetCtlState, WirelessNetwork, WirelessNetworks};
12use rs_matter::dm::networks::NetChangeNotif;
13use rs_matter::error::Error;
14use rs_matter::pairing::DiscoveryCapabilities;
15use rs_matter::persist::KvBlobStore;
16use rs_matter::transport::network::btp::{AdvData, Btp};
17use rs_matter::transport::network::NoNetwork;
18use rs_matter::utils::cell::RefCell;
19use rs_matter::utils::init::{init, Init};
20use rs_matter::utils::select::Coalesce;
21use rs_matter::utils::sync::blocking;
22use rs_matter::utils::sync::DynBase;
23
24use crate::ble::GattPeripheral;
25use crate::mdns::Mdns;
26use crate::nal::NetStack;
27use crate::network::{Embedding, Network};
28use crate::private::Sealed;
29use crate::{pin_alloc, DummyAttrNotifier, MatterStack};
30
31pub use gatt::*;
32pub use thread::*;
33pub use wifi::*;
34
35mod gatt;
36mod thread;
37mod wifi;
38
39pub const MAX_WIRELESS_NETWORKS: usize = 2;
40
41/// A type alias for a Matter stack running over either Wifi or Thread (and BLE, during commissioning).
42pub type WirelessMatterStack<'a, const B: usize, T, E = ()> = MatterStack<'a, B, WirelessBle<T, E>>;
43
44/// An implementation of the `Network` trait for a Matter stack running over
45/// BLE during commissioning, and then over either WiFi or Thread when operating.
46///
47/// The supported commissioning is either concurrent or non-concurrent (as per the Matter Core spec),
48/// where one over the other is decided at runtime with the concrete wireless implementation
49/// (`WirelessCoex` or `Wireless` + `Gatt`).
50///
51/// Non-concurrent commissioning means that the device - at any point in time - either runs Bluetooth
52/// or Wifi/Thread, but not both.
53///
54/// This is done to save memory and to avoid the usage of BLE+Wifi/Thread co-exist drivers on
55/// devices which share a single wireless radio for both BLE and Wifi/Thread.
56pub struct WirelessBle<T, E = ()>
57where
58    T: WirelessNetwork,
59{
60    btp: Btp,
61    net_state: blocking::Mutex<RefCell<NetCtlState>>,
62    embedding: E,
63    // The wireless network type is no longer stored here (the networks store lives
64    // in the stack's `InteractionModelState`), but it still parameterizes the
65    // `Network::Networks` associated type, so keep it as a phantom marker.
66    _network: PhantomData<fn() -> T>,
67}
68
69impl<T, E> WirelessBle<T, E>
70where
71    T: WirelessNetwork,
72    E: Embedding,
73{
74    /// Creates a new instance of the `WirelessBle` network type.
75    pub const fn new() -> Self {
76        Self {
77            btp: Btp::new(),
78            net_state: NetCtlState::new_with_mutex(),
79            embedding: E::INIT,
80            _network: PhantomData,
81        }
82    }
83
84    /// Return an in-place initializer for the `WirelessBle` network type.
85    pub fn init() -> impl Init<Self> {
86        init!(Self {
87            btp <- Btp::init(),
88            net_state <- NetCtlState::init_with_mutex(),
89            embedding <- E::init(),
90            _network: PhantomData,
91        })
92    }
93}
94
95/// A composite wireless network controller that is the SAME concrete type during
96/// both the (BLE) commissioning phase and the operational (Thread/Wifi) phase.
97///
98/// This exists purely to avoid building two structurally-different Matter handler
99/// chains per device. `InteractionModel::handle` and every generated cluster handler
100/// adaptor are monomorphized over the whole handler-chain tuple type; if the
101/// net-ctl slot has a different type per phase, the entire dispatch tree is
102/// compiled twice (~tens of KiB of duplicated `.text` on embedded targets).
103///
104/// By using `WirelessNetCtl<Q>` in *both* phases — `Commissioning` before the
105/// operational controller exists, `Operational(&Q)` afterwards — the chain type
106/// is identical across phases and the dispatch tree monomorphizes once.
107///
108/// The `Commissioning` variant reproduces the behavior of the former
109/// `NoopWirelessNetCtl`: `scan` errors with `InvalidAction`, `connect` only
110/// checks the creds match the network type, and every diag returns its default.
111/// The `Operational` variant delegates to the real controller `Q`.
112///
113/// `Q` is named identically at every phase via the wireless driver's associated
114/// net-ctl type (see the `Thread`/`Gatt` driver traits), so even the
115/// commissioning phase — which has no controller value — can still name the type.
116pub enum WirelessNetCtl<'a, Q> {
117    /// Commissioning phase: no operational controller yet (BLE only).
118    Commissioning(NetworkType),
119    /// Operational phase: delegate to the real controller.
120    Operational(&'a Q),
121}
122
123impl<Q> net_comm::NetCtl for WirelessNetCtl<'_, Q>
124where
125    Q: net_comm::NetCtl,
126{
127    fn net_type(&self) -> NetworkType {
128        match self {
129            Self::Commissioning(net_type) => *net_type,
130            Self::Operational(q) => q.net_type(),
131        }
132    }
133
134    fn connect_max_time_seconds(&self) -> u8 {
135        match self {
136            Self::Commissioning(_) => 0,
137            Self::Operational(q) => q.connect_max_time_seconds(),
138        }
139    }
140
141    fn scan_max_time_seconds(&self) -> u8 {
142        match self {
143            Self::Commissioning(_) => 0,
144            Self::Operational(q) => q.scan_max_time_seconds(),
145        }
146    }
147
148    fn supported_wifi_bands<F>(&self, f: F) -> Result<(), Error>
149    where
150        F: FnMut(net_comm::WiFiBandEnum) -> Result<(), Error>,
151    {
152        match self {
153            Self::Commissioning(_) => Ok(()),
154            Self::Operational(q) => q.supported_wifi_bands(f),
155        }
156    }
157
158    fn supported_thread_features(&self) -> net_comm::ThreadCapabilitiesBitmap {
159        match self {
160            Self::Commissioning(_) => net_comm::ThreadCapabilitiesBitmap::empty(),
161            Self::Operational(q) => q.supported_thread_features(),
162        }
163    }
164
165    fn thread_version(&self) -> u16 {
166        match self {
167            Self::Commissioning(_) => 0,
168            Self::Operational(q) => q.thread_version(),
169        }
170    }
171
172    async fn scan<F>(&self, network: Option<&[u8]>, f: F) -> Result<(), NetCtlError>
173    where
174        F: FnMut(&net_comm::NetworkScanInfo) -> Result<(), Error>,
175    {
176        match self {
177            // Matches the former `NoopWirelessNetCtl::scan`.
178            Self::Commissioning(_) => Err(NetCtlError::Other(
179                rs_matter::error::ErrorCode::InvalidAction.into(),
180            )),
181            Self::Operational(q) => q.scan(network, f).await,
182        }
183    }
184
185    async fn connect(&self, creds: &WirelessCreds<'_>) -> Result<(), NetCtlError> {
186        match self {
187            // Matches the former `NoopWirelessNetCtl::connect`.
188            Self::Commissioning(net_type) => Ok(creds.check_match(*net_type)?),
189            Self::Operational(q) => q.connect(creds).await,
190        }
191    }
192}
193
194impl<Q> NetChangeNotif for WirelessNetCtl<'_, Q>
195where
196    Q: NetChangeNotif,
197{
198    async fn wait_changed(&self) {
199        match self {
200            Self::Commissioning(_) => core::future::pending().await,
201            Self::Operational(q) => q.wait_changed().await,
202        }
203    }
204}
205
206#[cfg(feature = "sync-mutex")]
207impl<Q> DynBase for WirelessNetCtl<'_, Q> where Q: Send + Sync {}
208
209#[cfg(not(feature = "sync-mutex"))]
210impl<Q> DynBase for WirelessNetCtl<'_, Q> {}
211
212impl<Q> WirelessDiag for WirelessNetCtl<'_, Q>
213where
214    Q: WirelessDiag,
215{
216    fn connected(&self) -> Result<bool, Error> {
217        match self {
218            Self::Commissioning(_) => Ok(false),
219            Self::Operational(q) => q.connected(),
220        }
221    }
222}
223
224// For `WifiDiag`/`ThreadDiag`, the `Commissioning` variant reproduces each
225// method's trait default (matching the former `NoopWirelessNetCtl`, which impl'd
226// both traits empty), and the `Operational` variant delegates to the real
227// controller. The defaults are: `Ok(None)` for the scalar accessors, `Ok(())` /
228// `f(None)` for the closure-based accessors, and `Nullable::none()` for the
229// `WifiDiag` nullable accessors — kept in sync with rs-matter's trait defaults.
230impl<Q> wifi_diag::WifiDiag for WirelessNetCtl<'_, Q>
231where
232    Q: wifi_diag::WifiDiag,
233{
234    fn bssid(&self, f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>) -> Result<(), Error> {
235        match self {
236            Self::Commissioning(_) => f(None),
237            Self::Operational(q) => q.bssid(f),
238        }
239    }
240
241    fn security_type(
242        &self,
243    ) -> Result<rs_matter::tlv::Nullable<wifi_diag::SecurityTypeEnum>, Error> {
244        match self {
245            Self::Commissioning(_) => Ok(rs_matter::tlv::Nullable::none()),
246            Self::Operational(q) => q.security_type(),
247        }
248    }
249
250    fn wi_fi_version(&self) -> Result<rs_matter::tlv::Nullable<wifi_diag::WiFiVersionEnum>, Error> {
251        match self {
252            Self::Commissioning(_) => Ok(rs_matter::tlv::Nullable::none()),
253            Self::Operational(q) => q.wi_fi_version(),
254        }
255    }
256
257    fn channel_number(&self) -> Result<rs_matter::tlv::Nullable<u16>, Error> {
258        match self {
259            Self::Commissioning(_) => Ok(rs_matter::tlv::Nullable::none()),
260            Self::Operational(q) => q.channel_number(),
261        }
262    }
263
264    fn rssi(&self) -> Result<rs_matter::tlv::Nullable<i8>, Error> {
265        match self {
266            Self::Commissioning(_) => Ok(rs_matter::tlv::Nullable::none()),
267            Self::Operational(q) => q.rssi(),
268        }
269    }
270}
271
272impl<Q> thread_diag::ThreadDiag for WirelessNetCtl<'_, Q>
273where
274    Q: thread_diag::ThreadDiag,
275{
276    fn channel(&self) -> Result<Option<u16>, Error> {
277        match self {
278            Self::Commissioning(_) => Ok(None),
279            Self::Operational(q) => q.channel(),
280        }
281    }
282    fn routing_role(&self) -> Result<Option<thread_diag::RoutingRoleEnum>, Error> {
283        match self {
284            Self::Commissioning(_) => Ok(None),
285            Self::Operational(q) => q.routing_role(),
286        }
287    }
288    fn network_name(
289        &self,
290        f: &mut dyn FnMut(Option<&str>) -> Result<(), Error>,
291    ) -> Result<(), Error> {
292        match self {
293            Self::Commissioning(_) => f(None),
294            Self::Operational(q) => q.network_name(f),
295        }
296    }
297    fn pan_id(&self) -> Result<Option<u16>, Error> {
298        match self {
299            Self::Commissioning(_) => Ok(None),
300            Self::Operational(q) => q.pan_id(),
301        }
302    }
303    fn extended_pan_id(&self) -> Result<Option<u64>, Error> {
304        match self {
305            Self::Commissioning(_) => Ok(None),
306            Self::Operational(q) => q.extended_pan_id(),
307        }
308    }
309    fn mesh_local_prefix(
310        &self,
311        f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>,
312    ) -> Result<(), Error> {
313        match self {
314            Self::Commissioning(_) => f(None),
315            Self::Operational(q) => q.mesh_local_prefix(f),
316        }
317    }
318    fn neighbor_table(
319        &self,
320        f: &mut dyn FnMut(&thread_diag::NeighborTable) -> Result<(), Error>,
321    ) -> Result<(), Error> {
322        match self {
323            Self::Commissioning(_) => Ok(()),
324            Self::Operational(q) => q.neighbor_table(f),
325        }
326    }
327    fn route_table(
328        &self,
329        f: &mut dyn FnMut(&thread_diag::RouteTable) -> Result<(), Error>,
330    ) -> Result<(), Error> {
331        match self {
332            Self::Commissioning(_) => Ok(()),
333            Self::Operational(q) => q.route_table(f),
334        }
335    }
336    fn partition_id(&self) -> Result<Option<u32>, Error> {
337        match self {
338            Self::Commissioning(_) => Ok(None),
339            Self::Operational(q) => q.partition_id(),
340        }
341    }
342    fn weighting(&self) -> Result<Option<u16>, Error> {
343        match self {
344            Self::Commissioning(_) => Ok(None),
345            Self::Operational(q) => q.weighting(),
346        }
347    }
348    fn data_version(&self) -> Result<Option<u16>, Error> {
349        match self {
350            Self::Commissioning(_) => Ok(None),
351            Self::Operational(q) => q.data_version(),
352        }
353    }
354    fn stable_data_version(&self) -> Result<Option<u16>, Error> {
355        match self {
356            Self::Commissioning(_) => Ok(None),
357            Self::Operational(q) => q.stable_data_version(),
358        }
359    }
360    fn leader_router_id(&self) -> Result<Option<u8>, Error> {
361        match self {
362            Self::Commissioning(_) => Ok(None),
363            Self::Operational(q) => q.leader_router_id(),
364        }
365    }
366    fn ext_address(&self) -> Result<Option<u64>, Error> {
367        match self {
368            Self::Commissioning(_) => Ok(None),
369            Self::Operational(q) => q.ext_address(),
370        }
371    }
372    fn rloc_16(&self) -> Result<Option<u16>, Error> {
373        match self {
374            Self::Commissioning(_) => Ok(None),
375            Self::Operational(q) => q.rloc_16(),
376        }
377    }
378    fn security_policy(&self) -> Result<Option<thread_diag::SecurityPolicy>, Error> {
379        match self {
380            Self::Commissioning(_) => Ok(None),
381            Self::Operational(q) => q.security_policy(),
382        }
383    }
384    fn channel_page0_mask(
385        &self,
386        f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>,
387    ) -> Result<(), Error> {
388        match self {
389            Self::Commissioning(_) => f(None),
390            Self::Operational(q) => q.channel_page0_mask(f),
391        }
392    }
393    fn operational_dataset_components(
394        &self,
395        f: &mut dyn FnMut(Option<&thread_diag::OperationalDatasetComponents>) -> Result<(), Error>,
396    ) -> Result<(), Error> {
397        match self {
398            Self::Commissioning(_) => f(None),
399            Self::Operational(q) => q.operational_dataset_components(f),
400        }
401    }
402    fn active_network_faults_list(
403        &self,
404        f: &mut dyn FnMut(thread_diag::NetworkFaultEnum) -> Result<(), Error>,
405    ) -> Result<(), Error> {
406        match self {
407            Self::Commissioning(_) => Ok(()),
408            Self::Operational(q) => q.active_network_faults_list(f),
409        }
410    }
411}
412
413impl<T, E> Default for WirelessBle<T, E>
414where
415    T: WirelessNetwork,
416    E: Embedding,
417{
418    fn default() -> Self {
419        Self::new()
420    }
421}
422
423impl<T, E> Sealed for WirelessBle<T, E>
424where
425    T: WirelessNetwork,
426    E: Embedding,
427{
428}
429
430impl<T, E> Network for WirelessBle<T, E>
431where
432    T: WirelessNetwork,
433    E: Embedding,
434{
435    const INIT: Self = Self::new();
436
437    type Embedding<'a>
438        = E
439    where
440        Self: 'a;
441
442    // The wireless networks store, owned by the stack's `InteractionModelState`.
443    type Networks = WirelessNetworks<MAX_WIRELESS_NETWORKS, T>;
444
445    const NETWORKS: Self::Networks = WirelessNetworks::new();
446
447    fn init() -> impl Init<Self> {
448        WirelessBle::init()
449    }
450
451    fn init_networks() -> impl Init<Self::Networks> {
452        WirelessNetworks::init()
453    }
454
455    fn discovery_capabilities(&self) -> DiscoveryCapabilities {
456        DiscoveryCapabilities::BLE
457    }
458
459    fn embedding(&self) -> &Self::Embedding<'_> {
460        &self.embedding
461    }
462}
463
464impl<const B: usize, T, E> MatterStack<'_, B, WirelessBle<T, E>>
465where
466    T: WirelessNetwork,
467    E: Embedding,
468{
469    /// Reset the Matter instance to the factory defaults by removing all fabrics and basic info settings
470    pub async fn reset<S>(&mut self, store: S) -> Result<(), Error>
471    where
472        S: KvBlobStore,
473    {
474        let kv = self.matter.kv(store);
475
476        self.matter.reset_persist(&kv).await?;
477
478        // Reset the events counter and the wireless networks store so we don't
479        // carry stale state across a factory reset (Matter Core spec R1.5.1,
480        // §7.14.1.1 for the events watermark; the networks store holds the
481        // commissioned Wifi/Thread credentials).
482        self.state.reset_persist(&kv).await?;
483
484        Ok(())
485    }
486
487    /// Load the persisted state from the provided `KvBlobStore` implementation.
488    pub async fn load<S>(&mut self, store: S) -> Result<(), Error>
489    where
490        S: KvBlobStore,
491    {
492        let kv = self.matter.kv(store);
493
494        self.matter.load_persist(&kv).await?;
495
496        // Restore the events counter (so EventNumber stays monotonic across
497        // restarts - Matter Core spec R1.5.1, §7.14.1.1 SHALL) and the wireless
498        // networks store, both in one call.
499        self.state.load_persist(&kv).await?;
500
501        Ok(())
502    }
503
504    /// Run the startup sequence of the stack, which includes loading the persisted state
505    /// and opening the basic communication window if the device is not commissioned yet.
506    pub async fn startup<C, S>(&mut self, crypto: C, kv: S) -> Result<(), Error>
507    where
508        C: Crypto,
509        S: KvBlobStore,
510    {
511        self.load(kv).await?;
512
513        if !self.is_commissioned() {
514            info!("Device is not commissioned yet, opening commissioning window...");
515
516            self.open_basic_comm_window(crypto, &DummyAttrNotifier)?;
517        } else {
518            info!("Device is already commissioned");
519        }
520
521        Ok(())
522    }
523
524    /// Run the concurrent (BLE + Wireless) commissioning transport.
525    ///
526    /// The operational wireless connection manager is no longer run here; it is
527    /// driven by the data model engine (`InteractionModel::run`), which was built
528    /// with the operational `net_ctl` and the stack's networks store. This method
529    /// therefore only runs the BTP coexistence transport.
530    async fn run_net_coex<C, S, N, D, G>(
531        &self,
532        crypto: C,
533        net_stack: S,
534        netif: N,
535        mut mdns: D,
536        mut gatt: G,
537    ) -> Result<(), Error>
538    where
539        C: Crypto,
540        S: NetStack,
541        N: NetifDiag + NetChangeNotif,
542        D: Mdns,
543        G: GattPeripheral,
544    {
545        self.run_btp_coex(&crypto, &net_stack, &netif, &mut mdns, &mut gatt)
546            .await
547    }
548
549    async fn run_btp_coex<C, S, N, D, P>(
550        &self,
551        crypto: C,
552        net_stack: S,
553        netif: N,
554        mut mdns: D,
555        mut peripheral: P,
556    ) -> Result<(), Error>
557    where
558        C: Crypto,
559        S: NetStack,
560        N: NetifDiag + NetChangeNotif,
561        D: Mdns,
562        P: GattPeripheral,
563    {
564        info!("BLE driver started");
565
566        info!("Running in concurrent commissioning mode (BLE and Wireless)");
567
568        let adv_data = AdvData::new(
569            self.matter().dev_det(),
570            self.matter().dev_comm().discriminator,
571        );
572
573        let mut btp_task = pin_alloc!(
574            self.bump,
575            peripheral.run(&self.network.btp, "BT", &adv_data)
576        );
577
578        let mut net_task = pin_alloc!(
579            self.bump,
580            self.run_oper_net(
581                &crypto,
582                &net_stack,
583                0, // TODO
584                core::future::pending(),
585                Some((&self.network.btp, &self.network.btp))
586            )
587        );
588
589        let mut mdns_task = pin_alloc!(
590            self.bump,
591            self.run_oper_netif_mdns(&crypto, &net_stack, &netif, &mut mdns)
592        );
593
594        select3(&mut btp_task, &mut net_task, &mut mdns_task)
595            .coalesce()
596            .await
597    }
598
599    async fn run_btp<C, P>(&self, crypto: C, mut peripheral: P) -> Result<(), Error>
600    where
601        C: Crypto,
602        P: GattPeripheral,
603    {
604        info!("BLE driver started");
605
606        info!("Running in non-concurrent commissioning mode (BLE only)");
607
608        let adv_data = AdvData::new(
609            self.matter().dev_det(),
610            self.matter().dev_comm().discriminator,
611        );
612
613        let mut btp_task = pin_alloc!(
614            self.bump,
615            peripheral.run(&self.network.btp, "BT", &adv_data)
616        );
617
618        let mut net_task =
619            pin!(self.run_transport_net(&crypto, &self.network.btp, &self.network.btp, NoNetwork));
620        let mut oper_net_act_task = pin!(async {
621            NetCtlState::wait_prov_ready(&self.network.net_state, &self.network.btp).await;
622
623            // TODO: Workaround for a bug in the `esp-wifi` BLE stack:
624            // ====================== PANIC ======================
625            // panicked at /home/ivan/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/esp-wifi-0.12.0/src/ble/npl.rs:914:9:
626            // timed eventq_get not yet supported - go implement it!
627            embassy_time::Timer::after(embassy_time::Duration::from_secs(2)).await;
628
629            Ok(())
630        });
631
632        select3(&mut btp_task, &mut net_task, &mut oper_net_act_task)
633            .coalesce()
634            .await
635    }
636}
637
638/// A utility type for running a wireless task with a pre-existing wireless interface
639/// rather than bringing up / tearing down the wireless interface for the task.
640///
641/// This utility can only be used with hardware that implements wireless coexist mode
642/// (i.e. the Thread/Wifi interface as well as the BLE GATT peripheral are available at the same time).
643pub struct PreexistingWireless<S, N, C, M, G> {
644    pub(crate) net_stack: S,
645    pub(crate) netif: N,
646    pub(crate) net_ctl: C,
647    pub(crate) mdns: M,
648    pub(crate) gatt: G,
649}
650
651impl<S, N, C, M, G> PreexistingWireless<S, N, C, M, G> {
652    /// Create a new `PreexistingWireless` instance with the given network stack,
653    /// network interface, network controller and GATT peripheral.
654    pub const fn new(net_stack: S, netif: N, net_ctl: C, mdns: M, gatt: G) -> Self {
655        Self {
656            net_stack,
657            netif,
658            net_ctl,
659            mdns,
660            gatt,
661        }
662    }
663}
664
665pub(crate) struct MatterStackWirelessTask<'a, const B: usize, T, E, C, H, K, U, Q>
666where
667    T: WirelessNetwork,
668    E: Embedding,
669{
670    stack: &'a MatterStack<'a, B, WirelessBle<T, E>>,
671    crypto: C,
672    handler: H,
673    kv: K,
674    user_task: U,
675    _net_ctl: PhantomData<fn() -> Q>,
676}