Skip to main content

rs_matter_stack/wireless/
wifi.rs

1use core::future::Future;
2use core::marker::PhantomData;
3use core::pin::pin;
4
5use embassy_futures::select::{select, select3, select4};
6
7use rs_matter::crypto::{Crypto, RngCore};
8use rs_matter::dm::clusters::gen_comm::CommPolicy;
9use rs_matter::dm::clusters::gen_diag::GenDiag;
10use rs_matter::dm::clusters::gen_diag::NetifDiag;
11use rs_matter::dm::clusters::net_comm::{NetCtl, NetCtlStatus, NetworkType};
12use rs_matter::dm::clusters::sw_diag::SwDiag;
13use rs_matter::dm::clusters::time_sync::TimeSync;
14use rs_matter::dm::clusters::wifi_diag::WifiDiag;
15use rs_matter::dm::endpoints::{wifi_sys_handler, WifiSysHandler, ROOT_ENDPOINT_ID};
16use rs_matter::dm::networks::wireless::{self, NetCtlWithStatusImpl, NoopWirelessNetCtl};
17use rs_matter::dm::networks::NetChangeNotif;
18use rs_matter::dm::{ChainedHandler, DataModel, Endpoint, EpClMatcher};
19use rs_matter::error::Error;
20use rs_matter::persist::KvBlobStoreAccess;
21use rs_matter::root_endpoint;
22use rs_matter::transport::network::NoNetwork;
23use rs_matter::utils::select::Coalesce;
24
25use crate::mdns::Mdns;
26use crate::nal::NetStack;
27use crate::network::Embedding;
28use crate::wireless::{GattPeripheral, MatterStackWirelessTask, WirelessNetCtl};
29use crate::{pin_alloc, UserTask};
30
31use super::{Gatt, GattTask, PreexistingWireless, WirelessMatterStack};
32
33/// A type alias for a Matter stack running over Wifi (and BLE, during commissioning).
34pub type WifiMatterStack<'a, const B: usize, E = ()> =
35    WirelessMatterStack<'a, B, wireless::Wifi, E>;
36
37impl<const B: usize, E> WirelessMatterStack<'_, B, wireless::Wifi, E>
38where
39    E: Embedding,
40{
41    /// Run the Matter stack for an already pre-established wireless network where the BLE and the Wifi stacks can co-exist.
42    ///
43    /// # Arguments
44    /// - `net_stack` - a user-provided `NetStack` implementation
45    /// - `netif` - a user-provided `Netif` implementation
46    /// - `controller` - a user-provided `Controller` implementation
47    /// - `mdns` - a user-provided `Mdns` implementation
48    /// - `gatt` - a user-provided `GattPeripheral` implementation
49    /// - `crypto` - a user-provided `Crypto` implementation
50    /// - `handler` - a user-provided DM handler implementation
51    /// - `kv` - a user-provided `KvBlobStoreAccess` implementation
52    /// - `user` - a user-provided future that will be polled only when the netif interface is up
53    #[allow(clippy::too_many_arguments)]
54    pub async fn run_preex<'t, U, N, Q, D, G, C, H, K, X>(
55        &'t self,
56        net_stack: U,
57        netif: N,
58        net_ctl: Q,
59        mdns: D,
60        gatt: G,
61        crypto: C,
62        handler: H,
63        kv: K,
64        user: X,
65    ) -> impl Future<Output = Result<(), Error>> + 't
66    where
67        U: NetStack + 't,
68        N: NetifDiag + NetChangeNotif + 't,
69        Q: NetCtl + WifiDiag + NetChangeNotif + 't,
70        D: Mdns + 't,
71        G: GattPeripheral + 't,
72        C: Crypto + 't,
73        H: DataModel + 't,
74        K: KvBlobStoreAccess + 't,
75        X: UserTask + 't,
76    {
77        self.run_coex(
78            PreexistingWireless::new(net_stack, netif, net_ctl, mdns, gatt),
79            crypto,
80            handler,
81            kv,
82            user,
83        )
84    }
85
86    /// Run the Matter stack for a wireless network where the BLE and the Wifi stacks can co-exist.
87    ///
88    /// # Arguments
89    /// - `wifi` - a user-provided `WifiCoex` implementation
90    /// - `crypto` - a user-provided `Crypto` implementation
91    /// - `handler` - a user-provided DM handler implementation
92    /// - `kv` - a user-provided `KvBlobStoreAccess` implementation
93    /// - `user` - a user-provided future that will be polled only when the netif interface is up
94    pub async fn run_coex<W, C, H, K, U>(
95        &self,
96        mut wifi: W,
97        crypto: C,
98        handler: H,
99        kv: K,
100        user: U,
101    ) -> Result<(), Error>
102    where
103        W: WifiCoex,
104        C: Crypto,
105        H: DataModel,
106        K: KvBlobStoreAccess,
107        U: UserTask,
108    {
109        let _lock = self.run_lock.lock().await;
110
111        info!("Matter Stack memory: {}b", core::mem::size_of_val(self));
112
113        // Since this is the last code executed in the method, resetting the allocator should be safe
114        // because all boxes returned by it should be dropped by then
115        let _defer = scopeguard::guard((), |_| unsafe {
116            self.bump.reset();
117        });
118
119        self.matter().reset_transport()?;
120
121        let net_task = pin_alloc!(
122            self.bump,
123            self.run_wifi_coex(&mut wifi, crypto, handler, kv, user)
124        );
125
126        net_task.await
127    }
128
129    /// Run the Matter stack for a wireless network where the BLE and the Wifi stacks cannot co-exist.
130    ///
131    /// # Arguments
132    /// - `wifi` - a user-provided `Wifi` + `Gatt` implementation
133    /// - `crypto` - a user-provided `Crypto` implementation
134    /// - `handler` - a user-provided DM handler implementation
135    /// - `kv` - a user-provided `KvBlobStoreAccess` implementation
136    /// - `user` - a user-provided future that will be polled only when the netif interface is up
137    pub async fn run<W, C, H, K, U>(
138        &self,
139        wifi: W,
140        crypto: C,
141        handler: H,
142        kv: K,
143        user: U,
144    ) -> Result<(), Error>
145    where
146        W: Wifi + Gatt,
147        C: Crypto,
148        H: DataModel,
149        K: KvBlobStoreAccess,
150        U: UserTask,
151    {
152        let _lock = self.run_lock.lock().await;
153
154        info!("Matter Stack memory: {}b", core::mem::size_of_val(self));
155
156        // Since this is the last code executed in the method, resetting the allocator should be safe
157        // because all boxes returned by it should be dropped by then
158        let _defer = scopeguard::guard((), |_| unsafe {
159            self.bump.reset();
160        });
161
162        self.matter().reset_transport()?;
163
164        let net_task = pin_alloc!(self.bump, self.run_wifi(wifi, crypto, handler, kv, user));
165
166        net_task.await
167    }
168
169    async fn run_wifi_coex<W, C, H, K, U>(
170        &self,
171        wifi: &mut W,
172        crypto: C,
173        handler: H,
174        kv: K,
175        user: U,
176    ) -> Result<(), Error>
177    where
178        W: WifiCoex,
179        C: Crypto,
180        H: DataModel,
181        K: KvBlobStoreAccess,
182        U: UserTask,
183    {
184        // The coex task never builds a `WirelessNetCtl` chain via `Q`, so its
185        // phantom net-ctl type is an irrelevant placeholder.
186        // `&kv` is also lent to the driver so it can persist its own state.
187        wifi.run(
188            MatterStackWirelessTask::<'_, _, _, _, _, _, _, _, NoopWirelessNetCtl> {
189                stack: self,
190                crypto,
191                handler,
192                kv,
193                user_task: user,
194                _net_ctl: PhantomData,
195            },
196        )
197        .await
198    }
199
200    async fn run_wifi<W, C, H, K, U>(
201        &self,
202        mut wifi: W,
203        crypto: C,
204        handler: H,
205        kv: K,
206        mut user: U,
207    ) -> Result<(), Error>
208    where
209        W: Wifi + Gatt,
210        C: Crypto,
211        H: DataModel,
212        K: KvBlobStoreAccess,
213        U: UserTask,
214    {
215        loop {
216            let commissioned = self.is_commissioned();
217
218            if !commissioned {
219                Gatt::run(
220                    &mut wifi,
221                    MatterStackWirelessTask::<'_, _, _, _, _, _, _, _, <W as Wifi>::NetCtl<'_>> {
222                        stack: self,
223                        crypto: &crypto,
224                        handler: &handler,
225                        kv: &kv,
226                        user_task: &mut user,
227                        _net_ctl: PhantomData,
228                    },
229                )
230                .await?;
231            }
232
233            if commissioned {
234                let net_ctl = NetCtlWithStatusImpl::new(
235                    &self.network.net_state,
236                    WirelessNetCtl::<<W as Wifi>::NetCtl<'_>>::Commissioning(NetworkType::Wifi),
237                );
238
239                let sys =
240                    self.root_handler(&false, &(), &(), &net_ctl, &(), &(), crypto.weak_rand()?);
241                let combined = ChainedHandler::new(
242                    EpClMatcher::new(Some(ROOT_ENDPOINT_ID), None),
243                    sys,
244                    &handler,
245                );
246                let im = self.im(&crypto, (&handler, combined), &kv, &net_ctl);
247
248                im.close_comm_window()?;
249            }
250
251            Wifi::run(
252                &mut wifi,
253                MatterStackWirelessTask::<'_, _, _, _, _, _, _, _, <W as Wifi>::NetCtl<'_>> {
254                    stack: self,
255                    crypto: &crypto,
256                    handler: &handler,
257                    kv: &kv,
258                    user_task: &mut user,
259                    _net_ctl: PhantomData,
260                },
261            )
262            .await?;
263        }
264    }
265
266    /// Return a metadata for the root (Endpoint 0) of the Matter Node
267    /// configured for BLE+Wifi network.
268    pub const fn root_endpoint() -> Endpoint<'static> {
269        const ENDPOINT: Endpoint<'static> = root_endpoint!(wifi);
270
271        ENDPOINT
272    }
273
274    /// Return a handler for the root (Endpoint 0) of the Matter Node
275    /// configured for BLE+Wifi network.
276    #[allow(clippy::too_many_arguments)]
277    fn root_handler<'a, C>(
278        &'a self,
279        comm_policy: &'a dyn CommPolicy,
280        gen_diag: &'a dyn GenDiag,
281        netif_diag: &'a dyn NetifDiag,
282        net_ctl: &'a C,
283        time_sync: &'a dyn TimeSync,
284        sw_diag: &'a dyn SwDiag,
285        rand: impl RngCore + Copy,
286    ) -> WifiSysHandler<'a, &'a C>
287    where
288        C: NetCtl + NetCtlStatus + WifiDiag,
289    {
290        wifi_sys_handler(
291            comm_policy,
292            gen_diag,
293            netif_diag,
294            net_ctl,
295            time_sync,
296            sw_diag,
297            net_ctl,
298            rand,
299        )
300    }
301}
302
303/// A trait representing a task that needs access to the operational wireless interface (Wifi or Thread)
304/// (Netif, UDP stack and Wireless controller) to perform its work.
305pub trait WifiTask {
306    /// Run the task with the given network stack, network interface, wireless controller and mDNS
307    async fn run<S, N, C, M>(
308        &mut self,
309        net_stack: S,
310        netif: N,
311        net_ctl: C,
312        mdns: M,
313    ) -> Result<(), Error>
314    where
315        S: NetStack,
316        N: NetifDiag + NetChangeNotif,
317        C: NetCtl + WifiDiag + NetChangeNotif,
318        M: Mdns;
319}
320
321impl<T> WifiTask for &mut T
322where
323    T: WifiTask,
324{
325    fn run<S, N, C, M>(
326        &mut self,
327        net_stack: S,
328        netif: N,
329        net_ctl: C,
330        mdns: M,
331    ) -> impl Future<Output = Result<(), Error>>
332    where
333        S: NetStack,
334        N: NetifDiag + NetChangeNotif,
335        C: NetCtl + WifiDiag + NetChangeNotif,
336        M: Mdns,
337    {
338        T::run(*self, net_stack, netif, net_ctl, mdns)
339    }
340}
341
342/// A trait for running a task within a context where the wireless interface is initialized and operable
343pub trait Wifi {
344    /// The Wifi network controller type this driver produces in its operational
345    /// phase. Naming it here lets the commissioning and operational handler chains
346    /// be built with the SAME `WirelessNetCtl<Self::NetCtl<'_>>` net-ctl type,
347    /// yielding a single handler-chain monomorphization. The bound is Wifi's own
348    /// (`WifiDiag`) — a Wifi controller is never asked to be a Thread one.
349    type NetCtl<'a>: NetCtl + WifiDiag + NetChangeNotif
350    where
351        Self: 'a;
352
353    /// Setup the radio to operate in wireless (Wifi or Thread) mode
354    /// and run the given task.
355    async fn run<T>(&mut self, task: T) -> Result<(), Error>
356    where
357        T: WifiTask;
358}
359
360impl<T> Wifi for &mut T
361where
362    T: Wifi,
363{
364    type NetCtl<'a>
365        = T::NetCtl<'a>
366    where
367        Self: 'a;
368
369    fn run<A>(&mut self, task: A) -> impl Future<Output = Result<(), Error>>
370    where
371        A: WifiTask,
372    {
373        T::run(self, task)
374    }
375}
376
377/// A trait representing a task that needs access to the operational wireless interface (Wifi or Thread)
378/// as well as to the commissioning BTP GATT peripheral.
379///
380/// Typically, tasks performing the Matter concurrent commissioning workflow will implement this trait.
381pub trait WifiCoexTask {
382    /// Run the task with the given network stack, network interface, wireless controller and mDNS
383    async fn run<S, N, C, M, G>(
384        &mut self,
385        net_stack: S,
386        netif: N,
387        net_ctl: C,
388        mdns: M,
389        gatt: G,
390    ) -> Result<(), Error>
391    where
392        S: NetStack,
393        N: NetifDiag + NetChangeNotif,
394        C: NetCtl + WifiDiag + NetChangeNotif,
395        M: Mdns,
396        G: GattPeripheral;
397}
398
399impl<T> WifiCoexTask for &mut T
400where
401    T: WifiCoexTask,
402{
403    fn run<S, N, C, M, G>(
404        &mut self,
405        net_stack: S,
406        netif: N,
407        net_ctl: C,
408        mdns: M,
409        gatt: G,
410    ) -> impl Future<Output = Result<(), Error>>
411    where
412        S: NetStack,
413        N: NetifDiag + NetChangeNotif,
414        C: NetCtl + WifiDiag + NetChangeNotif,
415        M: Mdns,
416        G: GattPeripheral,
417    {
418        T::run(*self, net_stack, netif, net_ctl, mdns, gatt)
419    }
420}
421
422/// A trait for running a task within a context where both the wireless interface (Thread or Wifi)
423/// is initialized and operable, as well as the BLE GATT peripheral is also operable.
424///
425/// Typically, tasks performing the Matter concurrent commissioning workflow will ran by implementations
426/// of this trait.
427pub trait WifiCoex {
428    /// Setup the radio to operate in wireless coexist mode (Wifi or Thread + BLE)
429    /// and run the given task.
430    async fn run<T>(&mut self, task: T) -> Result<(), Error>
431    where
432        T: WifiCoexTask;
433}
434
435impl<T> WifiCoex for &mut T
436where
437    T: WifiCoex,
438{
439    fn run<A>(&mut self, task: A) -> impl Future<Output = Result<(), Error>>
440    where
441        A: WifiCoexTask,
442    {
443        T::run(self, task)
444    }
445}
446
447impl<S, N, C, M, P> Wifi for PreexistingWireless<S, N, C, M, P>
448where
449    S: NetStack,
450    N: NetifDiag + NetChangeNotif,
451    C: NetCtl + WifiDiag + NetChangeNotif,
452    M: Mdns,
453{
454    // The task receives `&self.net_ctl` (a `&C`), so the chain net-ctl type is
455    // `&'a C` (which satisfies the bounds via the blanket `impl Trait for &T`).
456    type NetCtl<'a>
457        = &'a C
458    where
459        Self: 'a;
460
461    async fn run<T>(&mut self, mut task: T) -> Result<(), Error>
462    where
463        T: WifiTask,
464    {
465        task.run(&self.net_stack, &self.netif, &self.net_ctl, &mut self.mdns)
466            .await
467    }
468}
469
470impl<S, N, C, M, P> WifiCoex for PreexistingWireless<S, N, C, M, P>
471where
472    S: NetStack,
473    N: NetifDiag + NetChangeNotif,
474    C: NetCtl + WifiDiag + NetChangeNotif,
475    M: Mdns,
476    P: GattPeripheral,
477{
478    async fn run<T>(&mut self, mut task: T) -> Result<(), Error>
479    where
480        T: WifiCoexTask,
481    {
482        task.run(
483            &self.net_stack,
484            &self.netif,
485            &self.net_ctl,
486            &mut self.mdns,
487            &mut self.gatt,
488        )
489        .await
490    }
491}
492
493impl<'a, const B: usize, E, C, H, K, U, Q> GattTask
494    for MatterStackWirelessTask<'a, B, wireless::Wifi, E, C, H, K, U, Q>
495where
496    E: Embedding,
497    C: Crypto,
498    H: DataModel,
499    K: KvBlobStoreAccess,
500    Q: NetCtl + WifiDiag + NetChangeNotif,
501{
502    async fn run<P>(&mut self, peripheral: P) -> Result<(), Error>
503    where
504        P: GattPeripheral,
505    {
506        let net_ctl = NetCtlWithStatusImpl::new(
507            &self.stack.network.net_state,
508            WirelessNetCtl::<Q>::Commissioning(NetworkType::Wifi),
509        );
510
511        let sys = self.stack.root_handler(
512            &false,
513            &(),
514            &(),
515            &net_ctl,
516            &(),
517            &(),
518            self.crypto.weak_rand()?,
519        );
520        let combined = ChainedHandler::new(
521            EpClMatcher::new(Some(ROOT_ENDPOINT_ID), None),
522            sys,
523            &self.handler,
524        );
525        // The network store comes from the stack's `state`; the (commissioning)
526        // net-ctl is threaded into the engine, whose `run` keeps its connection
527        // manager dormant while not commissioned.
528        let im = self
529            .stack
530            .im(&self.crypto, (&self.handler, combined), &self.kv, &net_ctl);
531
532        let mut btp_task = pin!(self.stack.run_btp(&self.crypto, peripheral));
533
534        let mut im_task = pin!(self.stack.run_im(&im));
535
536        select(&mut btp_task, &mut im_task).coalesce().await
537    }
538}
539
540impl<'a, const B: usize, E, C, H, K, X, Z> WifiTask
541    for MatterStackWirelessTask<'a, B, wireless::Wifi, E, C, H, K, X, Z>
542where
543    E: Embedding,
544    C: Crypto,
545    H: DataModel,
546    K: KvBlobStoreAccess,
547    X: UserTask,
548    Z: NetCtl + WifiDiag + NetChangeNotif,
549{
550    async fn run<T, N, Q, D>(
551        &mut self,
552        net_stack: T,
553        netif: N,
554        net_ctl: Q,
555        mut mdns: D,
556    ) -> Result<(), Error>
557    where
558        T: NetStack,
559        N: NetifDiag + NetChangeNotif,
560        Q: NetCtl + WifiDiag + NetChangeNotif,
561        D: Mdns,
562    {
563        info!("Wifi driver started");
564
565        let net_ctl_s = NetCtlWithStatusImpl::new(
566            &self.stack.network.net_state,
567            WirelessNetCtl::Operational(&net_ctl),
568        );
569
570        let sys = self.stack.root_handler(
571            &false,
572            &(),
573            &netif,
574            &net_ctl_s,
575            &(),
576            &(),
577            self.crypto.weak_rand()?,
578        );
579        let combined = ChainedHandler::new(
580            EpClMatcher::new(Some(ROOT_ENDPOINT_ID), None),
581            sys,
582            &self.handler,
583        );
584        // The operational `net_ctl` is threaded into the engine, which now drives
585        // the maintenance `WirelessMgr` itself (against the stack's networks store).
586        let im = self.stack.im(
587            &self.crypto,
588            (&self.handler, combined),
589            &self.kv,
590            &net_ctl_s,
591        );
592
593        let stack = &self.stack;
594
595        let mut net_task = pin!(stack.run_oper_net(
596            &self.crypto,
597            &net_stack,
598            0, // TODO
599            core::future::pending(),
600            Option::<(NoNetwork, NoNetwork)>::None
601        ));
602
603        let mut mdns_task =
604            pin!(stack.run_oper_netif_mdns(&self.crypto, &net_stack, &netif, &mut mdns));
605
606        // Non-concurrent commissioning deferred connect. In BLE-only commissioning
607        // the commissioner's `ConnectNetwork` is deferred (Wifi can't run during
608        // BLE) and must be replayed once the operational network is up but *before*
609        // `CommissioningComplete`. The engine's maintenance manager only connects
610        // *after* the device is commissioned, so this one-shot connect is still
611        // performed here. The target network ID is the one the commissioner
612        // selected, remembered in `NetCtlState`.
613        let deferred_connect_id = self.stack.network.net_state.lock(|state| {
614            let state = state.borrow();
615            state.is_prov_ready().then(|| state.network_id.clone())
616        });
617
618        if let Some(network_id) = deferred_connect_id {
619            info!("Non-concurrent commissioning: performing the deferred connect");
620
621            // The engine owns the networks + net-ctl; ask it to replay the
622            // deferred connect (no stack-owned `WirelessMgr`).
623            im.connect_once(&network_id).await?;
624        }
625
626        let mut im_task = pin!(self.stack.run_im(&im));
627
628        let mut user_task = pin!(self.user_task.run(&net_stack, &netif));
629
630        select4(&mut net_task, &mut mdns_task, &mut im_task, &mut user_task)
631            .coalesce()
632            .await
633    }
634}
635
636impl<'a, const B: usize, E, C, H, K, X, Z> WifiCoexTask
637    for MatterStackWirelessTask<'a, B, wireless::Wifi, E, C, H, K, X, Z>
638where
639    E: Embedding,
640    C: Crypto,
641    H: DataModel,
642    K: KvBlobStoreAccess,
643    X: UserTask,
644    Z: NetCtl + WifiDiag + NetChangeNotif,
645{
646    async fn run<T, N, Q, D, G>(
647        &mut self,
648        net_stack: T,
649        netif: N,
650        net_ctl: Q,
651        mut mdns: D,
652        mut gatt: G,
653    ) -> Result<(), Error>
654    where
655        T: NetStack,
656        N: NetifDiag + NetChangeNotif,
657        Q: NetCtl + WifiDiag + NetChangeNotif,
658        D: Mdns,
659        G: GattPeripheral,
660    {
661        info!("Wifi and BLE drivers started");
662
663        let net_ctl_s = NetCtlWithStatusImpl::new(
664            &self.stack.network.net_state,
665            WirelessNetCtl::Operational(&net_ctl),
666        );
667
668        let sys = self.stack.root_handler(
669            &true,
670            &(),
671            &netif,
672            &net_ctl_s,
673            &(),
674            &(),
675            self.crypto.weak_rand()?,
676        );
677        let combined = ChainedHandler::new(
678            EpClMatcher::new(Some(ROOT_ENDPOINT_ID), None),
679            sys,
680            &self.handler,
681        );
682        // The operational `net_ctl` is threaded into the engine, which drives the
683        // maintenance `WirelessMgr` itself; `run_net_coex` only runs the BTP coex
684        // transport now.
685        let im = self.stack.im(
686            &self.crypto,
687            (&self.handler, combined),
688            &self.kv,
689            &net_ctl_s,
690        );
691
692        let stack = &self.stack;
693        let bump = &stack.bump;
694
695        let mut net_task = pin_alloc!(
696            bump,
697            stack.run_net_coex(&self.crypto, &net_stack, &netif, &mut mdns, &mut gatt)
698        );
699
700        let mut im_task = pin_alloc!(bump, self.stack.run_im_with_bump(&im));
701
702        let mut user_task = pin_alloc!(bump, self.user_task.run(&net_stack, &netif));
703
704        select3(&mut net_task, &mut im_task, &mut user_task)
705            .coalesce()
706            .await
707    }
708}