From d5abd9d45762064592d94dcea8c8c033950a023f Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 8 May 2025 17:51:36 +0000 Subject: [PATCH] Switch to latest rs-matter clusters --- Cargo.toml | 17 +- examples/light.rs | 67 ++- examples/light_eth.rs | 63 ++- src/eth.rs | 169 ++++---- src/lib.rs | 332 +++++++++----- src/mdns.rs | 2 +- src/netif.rs | 383 ----------------- src/persist.rs | 68 +-- src/rand.rs | 28 +- src/test_device.rs | 174 -------- src/wireless.rs | 583 ++++++------------------- src/wireless/comm.rs | 567 ------------------------ src/wireless/diag.rs | 2 - src/wireless/diag/thread.rs | 129 ------ src/wireless/diag/wifi.rs | 142 ------ src/wireless/gatt.rs | 59 +++ src/wireless/mgmt.rs | 148 ------- src/wireless/proxy.rs | 338 --------------- src/wireless/store.rs | 322 -------------- src/wireless/svc.rs | 211 --------- src/wireless/thread.rs | 473 ++++++++++++++++++++ src/wireless/traits.rs | 832 ------------------------------------ src/wireless/wifi.rs | 463 ++++++++++++++++++++ 23 files changed, 1563 insertions(+), 4009 deletions(-) delete mode 100644 src/netif.rs delete mode 100644 src/test_device.rs delete mode 100644 src/wireless/comm.rs delete mode 100644 src/wireless/diag.rs delete mode 100644 src/wireless/diag/thread.rs delete mode 100644 src/wireless/diag/wifi.rs create mode 100644 src/wireless/gatt.rs delete mode 100644 src/wireless/mgmt.rs delete mode 100644 src/wireless/proxy.rs delete mode 100644 src/wireless/store.rs delete mode 100644 src/wireless/svc.rs create mode 100644 src/wireless/thread.rs delete mode 100644 src/wireless/traits.rs create mode 100644 src/wireless/wifi.rs diff --git a/Cargo.toml b/Cargo.toml index 8d5e7d3..6eafee8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,12 +16,12 @@ rust-version = "1.77" #rs-matter = { git = "https://github.com/project-chip/rs-matter" } rs-matter = { git = "https://github.com/ivmarkov/rs-matter", branch = "next" } #rs-matter = { path = "../rs-matter/rs-matter" } -edge-nal = { git = "https://github.com/ivmarkov/edge-net" } -edge-nal-std = { git = "https://github.com/ivmarkov/edge-net" } -edge-mdns = { git = "https://github.com/ivmarkov/edge-net" } +#edge-nal = { git = "https://github.com/ivmarkov/edge-net" } +#edge-nal-std = { git = "https://github.com/ivmarkov/edge-net" } +#edge-mdns = { git = "https://github.com/ivmarkov/edge-net" } [features] -default = [] +default = ["examples"] log = ["dep:log", "rs-matter/log", "embedded-svc/log", "edge-mdns?/log"] defmt = ["dep:defmt", "rs-matter/defmt", "embedded-svc/defmt", "heapless/defmt-03", "edge-mdns?/defmt"] zeroconf = ["os", "rs-matter/zeroconf"] @@ -30,7 +30,7 @@ os = ["backtrace", "rs-matter/os", "rustcrypto", "embassy-time/std"] backtrace = ["std", "rs-matter/backtrace"] std = ["alloc", "rs-matter/std", "edge-nal-std"] alloc = ["embedded-svc/alloc"] -examples = ["os", "nix", "embassy-time-queue-utils/generic-queue-64"] +examples = ["log", "os", "nix", "embassy-time-queue-utils/generic-queue-64"] [dependencies] log = { version = "0.4", default-features = false, optional = true } @@ -40,14 +40,15 @@ enumset = { version = "1", default-features = false } bitflags = { version = "2.5", default-features = false } scopeguard = { version = "1", default-features = false } embassy-futures = "0.1" -embassy-sync = "0.6" +embassy-sync = "0.7" embassy-time = "0.4" embedded-svc = { version = "0.28", default-features = false } rs-matter = { version = "0.1", default-features = false } edge-nal = "0.5" edge-nal-std = { version = "0.5", optional = true } -edge-mdns = { version = "0.5", optional = true } -rand_core = "0.6" +edge-mdns = { version = "0.6", optional = true } +rand_core = "0.9" +rand_core06 = { package = "rand_core", version = "0.6" } # For now, until `openthread` gets upgraded to 0.9 as well [target.'cfg(all(unix, not(target_os = "espidf")))'.dependencies] bitflags = "2" diff --git a/examples/light.rs b/examples/light.rs index 01efa81..fe8c09c 100644 --- a/examples/light.rs +++ b/examples/light.rs @@ -14,21 +14,26 @@ use embassy_time::{Duration, Timer}; use log::info; -use rs_matter_stack::matter::data_model::cluster_basic_information::BasicInfoConfig; -use rs_matter_stack::matter::data_model::cluster_on_off; +use rs_matter::data_model::networks::unix::UnixNetifs; +use rs_matter::data_model::networks::wireless::NoopWirelessNetCtl; +use rs_matter::data_model::objects::EmptyHandler; +use rs_matter::data_model::sdm::net_comm::NetworkType; +use rs_matter::test_device::TEST_DEV_DET; use rs_matter_stack::matter::data_model::device_types::DEV_TYPE_ON_OFF_LIGHT; -use rs_matter_stack::matter::data_model::objects::{Dataver, Endpoint, HandlerCompat, Node}; -use rs_matter_stack::matter::data_model::system_model::descriptor; +use rs_matter_stack::matter::data_model::objects::{Async, Dataver, Endpoint, Node}; +use rs_matter_stack::matter::data_model::on_off; +use rs_matter_stack::matter::data_model::on_off::{ClusterHandler as _, OnOffHandler}; +use rs_matter_stack::matter::data_model::system_model::desc::{ClusterHandler as _, DescHandler}; use rs_matter_stack::matter::error::Error; +use rs_matter_stack::matter::test_device::{TEST_DEV_ATT, TEST_DEV_COMM}; use rs_matter_stack::matter::transport::network::btp::BuiltinGattPeripheral; use rs_matter_stack::matter::utils::init::InitMaybeUninit; use rs_matter_stack::matter::utils::select::Coalesce; use rs_matter_stack::matter::utils::sync::blocking::raw::StdRawMutex; -use rs_matter_stack::netif::UnixNetif; +use rs_matter_stack::matter::{clusters, devices}; use rs_matter_stack::persist::DirKvBlobStore; -use rs_matter_stack::test_device::{TEST_BASIC_COMM_DATA, TEST_DEV_ATT, TEST_PID, TEST_VID}; +use rs_matter_stack::wireless::PreexistingWireless; use rs_matter_stack::wireless::WifiMatterStack; -use rs_matter_stack::wireless::{DisconnectedController, PreexistingWireless}; use static_cell::StaticCell; @@ -44,46 +49,30 @@ fn main() -> Result<(), Error> { let stack = MATTER_STACK .uninit() .init_with(WifiMatterStack::init_default( - &BasicInfoConfig { - vid: TEST_VID, - pid: TEST_PID, - hw_ver: 2, - hw_ver_str: "2", - sw_ver: 1, - sw_ver_str: "1", - serial_no: "aabbccdd", - device_name: "MyLight", - product_name: "ACME Light", - vendor_name: "ACME", - sai: None, - sii: None, - }, - TEST_BASIC_COMM_DATA, + &TEST_DEV_DET, + TEST_DEV_COMM, &TEST_DEV_ATT, )); // Our "light" on-off cluster. // Can be anything implementing `rs_matter::data_model::AsyncHandler` - let on_off = cluster_on_off::OnOffCluster::new(Dataver::new_rand(stack.matter().rand())); + let on_off = on_off::OnOffHandler::new(Dataver::new_rand(stack.matter().rand())); // Chain our endpoint clusters with the // (root) Endpoint 0 system clusters in the final handler - let handler = stack - .root_handler() + let handler = EmptyHandler // Our on-off cluster, on Endpoint 1 .chain( LIGHT_ENDPOINT_ID, - cluster_on_off::ID, - HandlerCompat(&on_off), + OnOffHandler::CLUSTER.id, + Async(on_off::HandlerAdaptor(&on_off)), ) // Each Endpoint needs a Descriptor cluster too // Just use the one that `rs-matter` provides out of the box .chain( LIGHT_ENDPOINT_ID, - descriptor::ID, - HandlerCompat(descriptor::DescriptorCluster::new(Dataver::new_rand( - stack.matter().rand(), - ))), + DescHandler::CLUSTER.id, + Async(DescHandler::new(Dataver::new_rand(stack.matter().rand())).adapt()), ); // Run the Matter stack with our handler @@ -91,19 +80,19 @@ fn main() -> Result<(), Error> { // not being very intelligent w.r.t. stack usage in async functions let store = stack.create_shared_store(DirKvBlobStore::new_default()); let mut matter = pin!(stack.run( - // A dummy wireless modem which does nothing PreexistingWireless::new( - UnixNetif::new_default(), edge_nal_std::Stack::new(), - DisconnectedController::new(), + UnixNetifs, + // A dummy wireless controller that does nothing + NoopWirelessNetCtl::new(NetworkType::Wifi), BuiltinGattPeripheral::new(None), ), // Will persist in `/rs-matter` &store, // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), - // No user future to run - core::future::pending(), + // No user task to run + (), )); // Just for demoing purposes: @@ -146,11 +135,11 @@ const LIGHT_ENDPOINT_ID: u16 = 1; const NODE: Node = Node { id: 0, endpoints: &[ - WifiMatterStack::::root_metadata(), + WifiMatterStack::::root_endpoint(), Endpoint { id: LIGHT_ENDPOINT_ID, - device_types: &[DEV_TYPE_ON_OFF_LIGHT], - clusters: &[descriptor::CLUSTER, cluster_on_off::CLUSTER], + device_types: devices!(DEV_TYPE_ON_OFF_LIGHT), + clusters: clusters!(DescHandler::CLUSTER, OnOffHandler::CLUSTER), }, ], }; diff --git a/examples/light_eth.rs b/examples/light_eth.rs index 2a11b24..94e792d 100644 --- a/examples/light_eth.rs +++ b/examples/light_eth.rs @@ -17,18 +17,22 @@ use embassy_time::{Duration, Timer}; use env_logger::Target; use log::info; +use rs_matter::data_model::networks::unix::UnixNetifs; +use rs_matter::data_model::objects::EmptyHandler; +use rs_matter::data_model::on_off::ClusterHandler as _; +use rs_matter::data_model::system_model::desc::ClusterHandler as _; +use rs_matter::test_device::TEST_DEV_DET; +use rs_matter::{clusters, devices}; use rs_matter_stack::eth::EthMatterStack; -use rs_matter_stack::matter::data_model::cluster_basic_information::BasicInfoConfig; -use rs_matter_stack::matter::data_model::cluster_on_off; use rs_matter_stack::matter::data_model::device_types::DEV_TYPE_ON_OFF_LIGHT; -use rs_matter_stack::matter::data_model::objects::{Dataver, Endpoint, HandlerCompat, Node}; -use rs_matter_stack::matter::data_model::system_model::descriptor; +use rs_matter_stack::matter::data_model::objects::{Async, Dataver, Endpoint, Node}; +use rs_matter_stack::matter::data_model::on_off; +use rs_matter_stack::matter::data_model::system_model::desc; use rs_matter_stack::matter::error::Error; +use rs_matter_stack::matter::test_device::{TEST_DEV_ATT, TEST_DEV_COMM}; use rs_matter_stack::matter::utils::init::InitMaybeUninit; use rs_matter_stack::matter::utils::select::Coalesce; -use rs_matter_stack::netif::UnixNetif; use rs_matter_stack::persist::DirKvBlobStore; -use rs_matter_stack::test_device::{TEST_BASIC_COMM_DATA, TEST_DEV_ATT, TEST_PID, TEST_VID}; use static_cell::StaticCell; @@ -46,46 +50,29 @@ fn main() -> Result<(), Error> { let stack = MATTER_STACK .uninit() .init_with(EthMatterStack::init_default( - &BasicInfoConfig { - vid: TEST_VID, - pid: TEST_PID, - hw_ver: 2, - hw_ver_str: "2", - sw_ver: 1, - sw_ver_str: "1", - serial_no: "aabbccdd", - device_name: "MyLight", - product_name: "ACME Light", - vendor_name: "ACME", - sai: None, - sii: None, - }, - TEST_BASIC_COMM_DATA, + &TEST_DEV_DET, + TEST_DEV_COMM, &TEST_DEV_ATT, )); // Our "light" on-off cluster. // Can be anything implementing `rs_matter::data_model::AsyncHandler` - let on_off = cluster_on_off::OnOffCluster::new(Dataver::new_rand(stack.matter().rand())); + let on_off = on_off::OnOffHandler::new(Dataver::new_rand(stack.matter().rand())); // Chain our endpoint clusters with the // (root) Endpoint 0 system clusters in the final handler - let handler = stack - .root_handler() - // Our on-off cluster, on Endpoint 1 + let handler = EmptyHandler .chain( LIGHT_ENDPOINT_ID, - cluster_on_off::ID, - HandlerCompat(&on_off), + on_off::OnOffHandler::CLUSTER.id, + Async(on_off::HandlerAdaptor(&on_off)), ) // Each Endpoint needs a Descriptor cluster too // Just use the one that `rs-matter` provides out of the box .chain( LIGHT_ENDPOINT_ID, - descriptor::ID, - HandlerCompat(descriptor::DescriptorCluster::new(Dataver::new_rand( - stack.matter().rand(), - ))), + desc::DescHandler::CLUSTER.id, + Async(desc::DescHandler::new(Dataver::new_rand(stack.matter().rand())).adapt()), ); // Run the Matter stack with our handler @@ -93,16 +80,16 @@ fn main() -> Result<(), Error> { // not being very intelligent w.r.t. stack usage in async functions let store = stack.create_shared_store(DirKvBlobStore::new_default()); let mut matter = pin!(stack.run_preex( - // Will try to find a default network interface - UnixNetif::default(), // The Matter stack needs UDP sockets to communicate with other Matter devices edge_nal_std::Stack::new(), + // Will try to find a default network interface + UnixNetifs, // Will persist in `/rs-matter` &store, // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), - // No user future to run - core::future::pending(), + // No user task future to run + (), )); // Just for demoing purposes: @@ -142,11 +129,11 @@ const LIGHT_ENDPOINT_ID: u16 = 1; const NODE: Node = Node { id: 0, endpoints: &[ - EthMatterStack::<()>::root_metadata(), + EthMatterStack::<()>::root_endpoint(), Endpoint { id: LIGHT_ENDPOINT_ID, - device_types: &[DEV_TYPE_ON_OFF_LIGHT], - clusters: &[descriptor::CLUSTER, cluster_on_off::CLUSTER], + device_types: devices!(DEV_TYPE_ON_OFF_LIGHT), + clusters: clusters!(desc::DescHandler::CLUSTER, on_off::OnOffHandler::CLUSTER), }, ], }; diff --git a/src/eth.rs b/src/eth.rs index 07a0ef4..47b7a96 100644 --- a/src/eth.rs +++ b/src/eth.rs @@ -1,29 +1,25 @@ -use core::future::Future; use core::pin::pin; use edge_nal::UdpBind; -use embassy_futures::select::select3; +use embassy_futures::select::{select, select3}; -use rs_matter::data_model::objects::{ - AsyncHandler, AsyncMetadata, Dataver, Endpoint, HandlerCompat, -}; -use rs_matter::data_model::root_endpoint; -use rs_matter::data_model::root_endpoint::{handler, OperNwType, RootEndpointHandler}; -use rs_matter::data_model::sdm::ethernet_nw_diagnostics::EthNwDiagCluster; -use rs_matter::data_model::sdm::nw_commissioning::EthNwCommCluster; -use rs_matter::data_model::sdm::{ethernet_nw_diagnostics, nw_commissioning}; +use rs_matter::data_model::networks::NetChangeNotif; +use rs_matter::data_model::objects::{AsyncHandler, AsyncMetadata, Endpoint}; +use rs_matter::data_model::root_endpoint::{self, with_eth, with_sys, EthHandler, SysHandler}; +use rs_matter::data_model::sdm::gen_comm::CommPolicy; +use rs_matter::data_model::sdm::gen_diag::{GenDiag, NetifDiag}; +use rs_matter::data_model::sdm::net_comm::NetworkType; use rs_matter::error::Error; use rs_matter::pairing::DiscoveryCapabilities; use rs_matter::transport::network::NoNetwork; use rs_matter::utils::init::{init, Init}; use rs_matter::utils::select::Coalesce; -use crate::netif::Netif; use crate::network::{Embedding, Network}; use crate::persist::{KvBlobStore, SharedKvBlobStore}; use crate::private::Sealed; -use crate::MatterStack; +use crate::{MatterStack, UserTask}; /// An implementation of the `Network` trait for Ethernet. /// @@ -70,22 +66,22 @@ pub type EthMatterStack<'a, E = ()> = MatterStack<'a, Eth>; /// (Netif and UDP stack) to perform its work. pub trait EthernetTask { /// Run the task with the given network interface and UDP stack - async fn run(&mut self, netif: N, udp: U) -> Result<(), Error> + async fn run(&mut self, udp: U, netif: N) -> Result<(), Error> where - N: Netif, - U: UdpBind; + U: UdpBind, + N: NetifDiag + NetChangeNotif; } impl EthernetTask for &mut T where T: EthernetTask, { - async fn run(&mut self, netif: N, udp: U) -> Result<(), Error> + async fn run(&mut self, udp: U, netif: N) -> Result<(), Error> where - N: Netif, U: UdpBind, + N: NetifDiag + NetChangeNotif, { - (*self).run(netif, udp).await + (*self).run(udp, netif).await } } @@ -111,28 +107,28 @@ where /// A utility type for running an ethernet task with a pre-existing ethernet interface /// rather than bringing up / tearing down the ethernet interface for the task. -pub struct PreexistingEthernet { - netif: N, +pub struct PreexistingEthernet { udp: U, + netif: N, } -impl PreexistingEthernet { +impl PreexistingEthernet { /// Create a new `PreexistingEthernet` instance with the given network interface and UDP stack. - pub const fn new(netif: N, udp: U) -> Self { - Self { netif, udp } + pub const fn new(udp: U, netif: N) -> Self { + Self { udp, netif } } } -impl Ethernet for PreexistingEthernet +impl Ethernet for PreexistingEthernet where - N: Netif, U: UdpBind, + N: NetifDiag + NetChangeNotif, { async fn run(&mut self, mut task: T) -> Result<(), Error> where T: EthernetTask, { - task.run(&mut self.netif, &mut self.udp).await + task.run(&mut self.udp, &self.netif).await } } @@ -143,24 +139,24 @@ where { /// Return a metadata for the root (Endpoint 0) of the Matter Node /// configured for Ethernet network. - pub const fn root_metadata() -> Endpoint<'static> { - root_endpoint::endpoint(0, OperNwType::Ethernet) + pub const fn root_endpoint() -> Endpoint<'static> { + root_endpoint::root_endpoint(NetworkType::Ethernet) } /// Return a handler for the root (Endpoint 0) of the Matter Node /// configured for Ethernet network. - pub fn root_handler(&self) -> EthRootEndpointHandler<'_> { - handler( - 0, - HandlerCompat(EthNwCommCluster::new(Dataver::new_rand( - self.matter().rand(), - ))), - ethernet_nw_diagnostics::ID, - HandlerCompat(EthNwDiagCluster::new(Dataver::new_rand( - self.matter().rand(), - ))), - &true, + fn root_handler<'a, H>( + &self, + gen_diag: &'a dyn GenDiag, + comm_policy: &'a dyn CommPolicy, + netif_diag: &'a dyn NetifDiag, + handler: H, + ) -> EthHandler<'a, SysHandler<'a, H>> { + with_eth( + gen_diag, + netif_diag, self.matter().rand(), + with_sys(comm_policy, self.matter().rand(), handler), ) } @@ -180,22 +176,22 @@ where /// - `persist` - a user-provided `Persist` implementation /// - `handler` - a user-provided DM handler implementation /// - `user` - a user-provided future that will be polled only when the netif interface is up - pub async fn run_preex( + pub async fn run_preex( &self, - netif: N, udp: U, + netif: N, store: &SharedKvBlobStore<'_, S>, handler: H, user: X, ) -> Result<(), Error> where - N: Netif, + N: NetifDiag + NetChangeNotif, U: UdpBind, S: KvBlobStore, H: AsyncHandler + AsyncMetadata, - X: Future>, + X: UserTask, { - self.run(PreexistingEthernet::new(netif, udp), store, handler, user) + self.run(PreexistingEthernet::new(udp, netif), store, handler, user) .await } @@ -217,7 +213,7 @@ where N: Ethernet, S: KvBlobStore, H: AsyncHandler + AsyncMetadata, - X: Future>, + X: UserTask, { info!("Matter Stack memory: {}B", core::mem::size_of_val(self)); @@ -233,54 +229,55 @@ where let persist = self.create_persist(store); - let mut net_task = pin!(self.run_ethernet(ethernet)); - let mut handler_task = pin!(self.run_handlers(&persist, handler)); - let mut user_task = pin!(user); + let mut net_task = pin!(self.run_ethernet(ethernet, handler, user)); + let mut persist_task = pin!(self.run_psm(&persist)); - select3(&mut net_task, &mut handler_task, &mut user_task) - .coalesce() - .await + select(&mut net_task, &mut persist_task).coalesce().await } - async fn run_ethernet(&self, mut ethernet: N) -> Result<(), Error> + async fn run_ethernet(&self, mut ethernet: N, handler: H, user: X) -> Result<(), Error> where N: Ethernet, + H: AsyncHandler + AsyncMetadata, + X: UserTask, { - #[allow(non_local_definitions)] - impl EthernetTask for MatterStackEthernetTask<'_, E> - where - E: Embedding + 'static, - { - async fn run(&mut self, netif: N, udp: U) -> Result<(), Error> - where - N: Netif, - U: UdpBind, - { - info!("Ethernet driver started"); - - self.0 - .run_oper_net( - netif, - udp, - core::future::pending(), - Option::<(NoNetwork, NoNetwork)>::None, - ) - .await - } - } - - Ethernet::run(&mut ethernet, MatterStackEthernetTask(self)).await + Ethernet::run(&mut ethernet, MatterStackEthernetTask(self, handler, user)).await } } -struct MatterStackEthernetTask<'a, E>(&'a MatterStack<'a, Eth>) +struct MatterStackEthernetTask<'a, E, H, X>(&'a MatterStack<'a, Eth>, H, X) +where + E: Embedding + 'static, + H: AsyncMetadata + AsyncHandler, + X: UserTask; + +impl EthernetTask for MatterStackEthernetTask<'_, E, H, X> where - E: Embedding + 'static; - -/// The type of the handler for the root (Endpoint 0) of the Matter Node -/// when configured for Ethernet network. -pub type EthRootEndpointHandler<'a> = RootEndpointHandler< - 'a, - HandlerCompat, - HandlerCompat, ->; + E: Embedding + 'static, + H: AsyncMetadata + AsyncHandler, + X: UserTask, +{ + async fn run(&mut self, udp: U, netif: C) -> Result<(), Error> + where + U: UdpBind, + C: NetifDiag + NetChangeNotif, + { + info!("Ethernet driver started"); + + let mut net_task = pin!(self.0.run_oper_net( + &udp, + &netif, + core::future::pending(), + Option::<(NoNetwork, NoNetwork)>::None, + )); + + let handler = self.0.root_handler(&(), &true, &netif, &self.1); + let mut handler_task = pin!(self.0.run_handler((&self.1, handler))); + + let mut user_task = pin!(self.2.run(&udp, &netif)); + + select3(&mut net_task, &mut handler_task, &mut user_task) + .coalesce() + .await + } +} diff --git a/src/lib.rs b/src/lib.rs index 9c7dd98..ffae7ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,19 +10,21 @@ #![warn(clippy::large_types_passed_by_value)] use core::future::Future; -use core::net::{Ipv6Addr, SocketAddr, SocketAddrV6}; +use core::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV6}; use core::pin::pin; use edge_nal::{UdpBind, UdpSplit}; -use embassy_futures::select::{select, select4, Either4}; +use embassy_futures::select::{select4, Either4}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use persist::{KvBlobBuffer, KvBlobStore, MatterPersist, NetworkPersist}; -use rs_matter::data_model::cluster_basic_information::BasicInfoConfig; +use rs_matter::data_model::basic_info::BasicInfoConfig; use rs_matter::data_model::core::IMBuffer; +use rs_matter::data_model::networks::NetChangeNotif; use rs_matter::data_model::objects::{AsyncHandler, AsyncMetadata}; use rs_matter::data_model::sdm::dev_att::DevAttDataFetcher; +use rs_matter::data_model::sdm::gen_diag::NetifDiag; use rs_matter::data_model::subscriptions::Subscriptions; use rs_matter::error::{Error, ErrorCode}; use rs_matter::mdns::{Mdns, MdnsService}; @@ -31,12 +33,9 @@ use rs_matter::transport::network::{Address, ChainedNetwork, NetworkReceive, Net use rs_matter::utils::epoch::Epoch; use rs_matter::utils::init::{init, Init}; use rs_matter::utils::rand::Rand; -use rs_matter::utils::select::Coalesce; use rs_matter::utils::storage::pooled::PooledBuffers; -use rs_matter::utils::sync::Signal; use rs_matter::{BasicCommData, Matter, MATTER_PORT}; -use crate::netif::{Netif, NetifConf}; use crate::network::Network; use crate::persist::SharedKvBlobStore; @@ -56,11 +55,9 @@ pub mod eth; pub mod matter; pub mod mdns; pub mod nal; -pub mod netif; pub mod network; pub mod persist; pub mod rand; -pub mod test_device; pub mod udp; pub mod utils; pub mod wireless; @@ -68,6 +65,8 @@ pub mod wireless; mod private { /// A marker super-trait for sealed traits pub trait Sealed {} + + impl Sealed for () {} } const MAX_SUBSCRIPTIONS: usize = 3; @@ -119,7 +118,7 @@ where network: N, #[allow(unused)] mdns: MdnsType<'a>, - netif_conf: Signal>, + //netif_conf: Signal>, } impl<'a, N> MatterStack<'a, N> @@ -171,7 +170,7 @@ where store_buf: PooledBuffers::new(0), network: N::INIT, mdns, - netif_conf: Signal::new(None), + //netif_conf: Signal::new(None), } } @@ -217,7 +216,7 @@ where store_buf <- PooledBuffers::init(0), network <- N::init(), mdns, - netif_conf: Signal::new(None), + //netif_conf: Signal::new(None), }) } @@ -294,41 +293,41 @@ where self.subscriptions.notify_changed(); } - /// User code hook to get the state of the netif passed to the - /// `run_with_netif` method. - /// - /// Useful when user code needs to bring up/down its own IP services depending on - /// when the netif controlled by Matter goes up, down or changes its IP configuration. - pub async fn get_netif_conf(&self) -> Option { - self.netif_conf - .wait(|netif_conf| Some(netif_conf.clone())) - .await - } - - fn update_netif_conf(&self, netif_conf: Option<&NetifConf>) -> bool { - self.netif_conf.modify(|global_ip_info| { - if global_ip_info.as_ref() != netif_conf { - *global_ip_info = netif_conf.cloned(); - (true, true) - } else { - (false, false) - } - }) - } - - /// User code hook to detect changes to the IP state of the netif passed to the - /// `run_with_netif` method. - /// - /// Useful when user code needs to bring up/down its own IP services depending on - /// when the netif controlled by Matter goes up, down or changes its IP configuration. - pub async fn wait_netif_changed( - &self, - prev_netif_info: Option<&NetifConf>, - ) -> Option { - self.netif_conf - .wait(|netif_info| (netif_info.as_ref() != prev_netif_info).then(|| netif_info.clone())) - .await - } + // /// User code hook to get the state of the netif passed to the + // /// `run_with_netif` method. + // /// + // /// Useful when user code needs to bring up/down its own IP services depending on + // /// when the netif controlled by Matter goes up, down or changes its IP configuration. + // pub async fn get_netif_conf(&self) -> Option { + // self.netif_conf + // .wait(|netif_conf| Some(netif_conf.clone())) + // .await + // } + + // fn update_netif_conf(&self, netif_conf: Option<&NetifConf>) -> bool { + // self.netif_conf.modify(|global_ip_info| { + // if global_ip_info.as_ref() != netif_conf { + // *global_ip_info = netif_conf.cloned(); + // (true, true) + // } else { + // (false, false) + // } + // }) + // } + + // /// User code hook to detect changes to the IP state of the netif passed to the + // /// `run_with_netif` method. + // /// + // /// Useful when user code needs to bring up/down its own IP services depending on + // /// when the netif controlled by Matter goes up, down or changes its IP configuration. + // pub async fn wait_netif_changed( + // &self, + // prev_netif_info: Option<&NetifConf>, + // ) -> Option { + // self.netif_conf + // .wait(|netif_info| (netif_info.as_ref() != prev_netif_info).then(|| netif_info.clone())) + // .await + // } /// Return information whether the Matter instance is already commissioned. pub async fn is_commissioned(&self) -> Result { @@ -347,59 +346,122 @@ where /// - `comm` - a tuple of additional and optional `NetworkReceive` and `NetworkSend` transport implementations /// (useful when a second transport needs to run in parallel with the operational Matter transport, /// i.e. when using concurrent commissisoning) - async fn run_oper_net( + async fn run_oper_net( &self, - netif: I, udp: U, + netif: I, until: X, mut comm: Option<(R, S)>, ) -> Result<(), Error> where - I: Netif, U: UdpBind, + I: NetifDiag + NetChangeNotif, X: Future>, R: NetworkReceive, S: NetworkSend, { + #[derive(Clone, Debug, Eq, PartialEq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + struct Netif { + ipv6: Ipv6Addr, + ipv4: Ipv4Addr, + mac: [u8; 8], + operational: bool, + } + + impl Netif { + pub const fn new() -> Self { + Self { + ipv6: Ipv6Addr::UNSPECIFIED, + ipv4: Ipv4Addr::UNSPECIFIED, + mac: [0; 8], + operational: false, + } + } + } + + fn load_netif(net_ctl: C, netif: &mut Netif) -> Result<(), Error> + where + C: NetifDiag, + { + netif.operational = false; + netif.ipv6 = Ipv6Addr::UNSPECIFIED; + netif.ipv4 = Ipv4Addr::UNSPECIFIED; + netif.mac = [0; 8]; + + net_ctl.netifs(&mut |ni| { + if ni.operational && !ni.ipv6_addrs.is_empty() { + netif.operational = true; + netif.ipv6 = ni.ipv6_addrs[0]; + netif.ipv4 = ni + .ipv4_addrs + .first() + .copied() + .unwrap_or(Ipv4Addr::UNSPECIFIED); + netif.mac = *ni.hw_addr; + } + + Ok(()) + }) + } + + async fn wait_changed( + net_ctl: C, + cur_netif: &Netif, + new_netif: &mut Netif, + ) -> Result<(), Error> + where + C: NetifDiag + NetChangeNotif, + { + loop { + load_netif(&net_ctl, new_netif)?; + + if &*new_netif != cur_netif { + trace!("Change detected: {:?}", new_netif); + break Ok(()); + } + + trace!("No change"); + net_ctl.wait_changed().await; + } + } + let mut until_task = pin!(until); - let _guard = scopeguard::guard((), |_| { - self.update_netif_conf(None); - }); + // let _guard = scopeguard::guard((), |_| { + // self.update_netif_conf(None); + // }); + + let mut new_netif = Netif::new(); + load_netif(&netif, &mut new_netif)?; loop { - let netif_conf = netif.get_conf().await?; - self.update_netif_conf(netif_conf.as_ref()); - - let mut netif_changed_task = pin!(async { - loop { - trace!("Waiting..."); - if netif_conf != netif.get_conf().await? { - trace!("Change detected: {:?}", netif_conf); - break Ok::<_, Error>(()); - } else { - trace!("No change"); - netif.wait_conf_change().await?; - } - } - }); + let cur_netif = new_netif.clone(); - let result = if let Some(netif_conf) = netif_conf.as_ref() { - info!("Netif up: {}", netif_conf); + let mut netif_changed_task = pin!(wait_changed(&netif, &cur_netif, &mut new_netif)); + + let result = if cur_netif.operational { + info!("Netif up: {:?}", cur_netif); let mut socket = udp .bind(SocketAddr::V6(SocketAddrV6::new( Ipv6Addr::UNSPECIFIED, MATTER_PORT, 0, - netif_conf.interface, + 0, ))) .await .map_err(|_| ErrorCode::StdIoError)?; let (recv, send) = socket.split(); - let mut mdns_task = pin!(self.run_builtin_mdns(&udp, netif_conf)); + let mut mdns_task = pin!(self.run_builtin_mdns( + &udp, + &cur_netif.mac, + cur_netif.ipv4, + cur_netif.ipv6, + 0, + )); if let Some((comm_recv, comm_send)) = comm.as_mut() { info!("Running operational and extra networks"); @@ -461,37 +523,15 @@ where } } - /// A transport-agnostic method to run the main Matter loop. - /// The user is expected to provide a transport implementation in the form of - /// `NetworkSend` and `NetworkReceive` implementations. - /// - /// The utility runs the following tasks: - /// - The main Matter transport via the user-provided traits - /// - The PSM task handling changes to fabrics and ACLs as well as initial load of these from NVS - /// - The Matter responder (i.e. handling incoming exchanges) - /// - /// Unlike `run_with_netif`, this utility method does _not_ run the mDNS service, as the - /// user-provided transport might not be IP-based (i.e. BLE). - /// - /// It also has no facilities for monitoring the transport network state. - async fn run_handlers( - &self, - persist: &MatterPersist<'_, S, C>, - handler: H, - ) -> Result<(), Error> + async fn run_handler(&self, handler: H) -> Result<(), Error> where H: AsyncHandler + AsyncMetadata, - S: KvBlobStore, - C: NetworkPersist, { // TODO // Reset the Matter transport buffers and all sessions first // self.matter().reset_transport()?; - let mut psm = pin!(self.run_psm(persist)); - let mut respond = pin!(self.run_responder(handler)); - - select(&mut psm, &mut respond).coalesce().await + self.run_responder(handler).await } async fn run_psm(&self, persist: &MatterPersist<'_, S, C>) -> Result<(), Error> @@ -528,7 +568,14 @@ where Ok(()) } - async fn run_builtin_mdns(&self, _udp: U, _netif_conf: &NetifConf) -> Result<(), Error> + async fn run_builtin_mdns( + &self, + _udp: U, + _mac: &[u8], + _ipv4: Ipv4Addr, + _ipv6: Ipv6Addr, + _interface: u32, + ) -> Result<(), Error> where U: UdpBind, { @@ -551,33 +598,50 @@ where Ipv6Addr::UNSPECIFIED, MDNS_PORT, 0, - _netif_conf.interface, + _interface, ))) .await .map_err(|_| ErrorCode::StdIoError)?; socket - .join_v4(MDNS_IPV4_BROADCAST_ADDR, _netif_conf.ipv4) + .join_v4(MDNS_IPV4_BROADCAST_ADDR, _ipv4) .await .map_err(|_| ErrorCode::StdIoError)?; socket - .join_v6(MDNS_IPV6_BROADCAST_ADDR, _netif_conf.interface) + .join_v6(MDNS_IPV6_BROADCAST_ADDR, _interface) .await .map_err(|_| ErrorCode::StdIoError)?; let (recv, send) = socket.split(); - let mut hostname = heapless::String::<12>::new(); - write_unwrap!( - hostname, - "{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}", - _netif_conf.mac[0], - _netif_conf.mac[1], - _netif_conf.mac[2], - _netif_conf.mac[3], - _netif_conf.mac[4], - _netif_conf.mac[5] - ); + let mut hostname = heapless::String::<16>::new(); + if _mac.len() == 6 { + write_unwrap!( + hostname, + "{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}", + _mac[0], + _mac[1], + _mac[2], + _mac[3], + _mac[4], + _mac[5] + ); + } else if _mac.len() == 8 { + write_unwrap!( + hostname, + "{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}", + _mac[0], + _mac[1], + _mac[2], + _mac[3], + _mac[4], + _mac[5], + _mac[6], + _mac[7] + ); + } else { + panic!("Invalid MAC address length: should be 6 or 8 bytes"); + } self.matter() .run_builtin_mdns( @@ -586,11 +650,11 @@ where &Host { id: 0, hostname: &hostname, - ip: _netif_conf.ipv4, - ipv6: _netif_conf.ipv6, + ip: _ipv4, + ipv6: _ipv6, }, - Some(_netif_conf.ipv4), - Some(_netif_conf.interface), + Some(_ipv4), + Some(_interface), ) .await?; } @@ -617,3 +681,43 @@ where Ok(()) } } + +/// A trait representing a user task that needs access to the operational network interface +/// (Netif and UDP stack) to perform its work. +/// +/// Note that the task would be started only when `rs-matter` +/// brings up the operational interface (eth, wifi or thread) +/// and if the interface goes down, the user task would be stopped. +/// Upon re-connection, the task would be started again. +pub trait UserTask { + /// Run the task with the given network interface and UDP stack + async fn run(&mut self, udp: U, netif: N) -> Result<(), Error> + where + U: UdpBind, + N: NetifDiag + NetChangeNotif; +} + +impl UserTask for &mut T +where + T: UserTask, +{ + async fn run(&mut self, udp: U, netif: N) -> Result<(), Error> + where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + { + (*self).run(udp, netif).await + } +} + +impl UserTask for () { + async fn run(&mut self, _udp: U, _netif: N) -> Result<(), Error> + where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + { + core::future::pending::<()>().await; + + Ok(()) + } +} diff --git a/src/mdns.rs b/src/mdns.rs index a546e0e..3472663 100644 --- a/src/mdns.rs +++ b/src/mdns.rs @@ -17,7 +17,7 @@ use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::signal::Signal; -use rs_matter::data_model::cluster_basic_information::BasicInfoConfig; +use rs_matter::data_model::basic_info::BasicInfoConfig; use rs_matter::error::{Error, ErrorCode}; use rs_matter::mdns::{Mdns, Service, ServiceMode}; use rs_matter::utils::cell::RefCell; diff --git a/src/netif.rs b/src/netif.rs deleted file mode 100644 index d72bbf9..0000000 --- a/src/netif.rs +++ /dev/null @@ -1,383 +0,0 @@ -use core::net::{Ipv4Addr, Ipv6Addr}; - -use rs_matter::error::Error; - -#[cfg(all(unix, feature = "os", feature = "nix", not(target_os = "espidf")))] -pub use unix::UnixNetif; - -/// Async trait for accessing the network interface (netif) of a driver. -/// -/// Allows sharing the network interface between multiple tasks, where one task -/// may be waiting for the network interface to be ready, while the other might -/// be mutably operating on the L2 driver below the netif, or on the netif itself. -pub trait Netif { - /// Return the active configuration of the network interface, if any - async fn get_conf(&self) -> Result, Error>; - - /// Wait until the network interface configuration changes. - async fn wait_conf_change(&self) -> Result<(), Error>; -} - -impl Netif for &T -where - T: Netif, -{ - async fn get_conf(&self) -> Result, Error> { - (**self).get_conf().await - } - - async fn wait_conf_change(&self) -> Result<(), Error> { - (**self).wait_conf_change().await - } -} - -impl Netif for &mut T -where - T: Netif, -{ - async fn get_conf(&self) -> Result, Error> { - (**self).get_conf().await - } - - async fn wait_conf_change(&self) -> Result<(), Error> { - (**self).wait_conf_change().await - } -} - -/// The current IP configuration of a network interface (if the netif is configured and up) -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct NetifConf { - /// Ipv4 address - pub ipv4: Ipv4Addr, - // Ipv6 address - pub ipv6: Ipv6Addr, - // Interface index - pub interface: u32, - // MAC address - pub mac: [u8; 6], -} - -impl NetifConf { - pub const fn new() -> Self { - Self { - ipv4: Ipv4Addr::UNSPECIFIED, - ipv6: Ipv6Addr::UNSPECIFIED, - interface: 0, - mac: [0; 6], - } - } -} - -impl Default for NetifConf { - fn default() -> Self { - Self::new() - } -} - -impl core::fmt::Display for NetifConf { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!( - f, - "IPv4: {}, IPv6: {}, Interface: {}, MAC: {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", - self.ipv4, - self.ipv6, - self.interface, - self.mac[0], - self.mac[1], - self.mac[2], - self.mac[3], - self.mac[4], - self.mac[5] - ) - } -} - -#[cfg(feature = "defmt")] -impl defmt::Format for NetifConf { - fn format(&self, f: defmt::Formatter<'_>) { - defmt::write!( - f, - "IPv4: {}, IPv6: {}, Interface: {}, MAC: {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", - self.ipv4, - self.ipv6, - self.interface, - self.mac[0], - self.mac[1], - self.mac[2], - self.mac[3], - self.mac[4], - self.mac[5] - ) - } -} - -/// This is a `Netif` implementation that does not really track any changes of an underlying network interface, -/// and therefore assumes the network interface is always up. -/// -/// Furthermore, it always reports fixed IPs and interface ID -/// (by default `Ipv4Addr::UNSPECIFIED` / `Ipv6Addr::UNSPECIFIED` and 0). -/// -/// Useful for demoing purposes -pub struct DummyNetif { - conf: Option, -} - -impl DummyNetif { - /// Create a new `DummyNetif` with the given IP configuration and MAC address - pub const fn new(conf: Option) -> Self { - Self { conf } - } -} - -impl Default for DummyNetif { - fn default() -> Self { - Self { - conf: Some(NetifConf::default()), - } - } -} - -impl Netif for DummyNetif { - async fn get_conf(&self) -> Result, Error> { - Ok(self.conf.clone()) - } - - async fn wait_conf_change(&self) -> Result<(), Error> { - // DummyNetif does not track any changes - core::future::pending().await - } -} - -#[cfg(all(unix, feature = "os", feature = "nix", not(target_os = "espidf")))] -mod unix { - use alloc::string::String; - - use bitflags::bitflags; - - use embassy_time::{Duration, Timer}; - - use nix::net::if_::InterfaceFlags; - use nix::sys::socket::{SockaddrIn6, SockaddrStorage}; - - use rs_matter::error::Error; - - use super::{Netif, NetifConf}; - - bitflags! { - /// DefaultNetif is a set of flags that can be used to filter network interfaces - /// when calling `default_if` - #[repr(transparent)] - #[cfg_attr(not(feature = "defmt"), derive(Debug, Clone, Copy, PartialEq, Eq, Hash))] - pub struct NetifSearchFlags: u16 { - /// Consider only interfaces which have the Loopback flag set - const LOOPBACK = 0x0001; - /// Consider only interfaces which do not have the Loopback flag set - const NON_LOOPBACK = 0x0002; - /// Consider only interfaces which have the Point-to-Point flag set - const PEER_TO_PEER = 0x0004; - /// Consider only interfaces which do not have the Point-to-Point flag set - const NON_PEER_TO_PEER = 0x0008; - /// Consider only interfaces which have the Broadcast flag set - const BROADCAST = 0x0010; - /// Consider only interfaces which do have an IPv4 address assigned - const IPV4 = 0x0020; - /// Consider only interfaces which do have an IPv6 address assigned - const IPV6 = 0x0040; - /// Consider only interfaces which do have a Link-Local IPv6 address assigned - const IPV6_LINK_LOCAL = 0x0080; - /// Consider only interfaces which do have a non-Link-Local IPv6 address assigned - const IPV6_NON_LINK_LOCAL = 0x0100; - } - } - - impl NetifSearchFlags { - fn contains_if_flags(&self) -> InterfaceFlags { - let mut flags = InterfaceFlags::empty(); - - if self.contains(NetifSearchFlags::LOOPBACK) { - flags |= InterfaceFlags::IFF_LOOPBACK; - } - - if self.contains(NetifSearchFlags::PEER_TO_PEER) { - flags |= InterfaceFlags::IFF_POINTOPOINT; - } - - if self.contains(NetifSearchFlags::BROADCAST) { - flags |= InterfaceFlags::IFF_BROADCAST; - } - - flags - } - - fn not_contains_if_flags(&self) -> InterfaceFlags { - let mut flags = InterfaceFlags::empty(); - - if self.contains(NetifSearchFlags::NON_LOOPBACK) { - flags |= InterfaceFlags::IFF_LOOPBACK; - } - - if self.contains(NetifSearchFlags::NON_PEER_TO_PEER) { - flags |= InterfaceFlags::IFF_POINTOPOINT; - } - - flags - } - } - - impl Default for NetifSearchFlags { - fn default() -> Self { - NetifSearchFlags::NON_LOOPBACK - | NetifSearchFlags::NON_PEER_TO_PEER - | NetifSearchFlags::BROADCAST - | NetifSearchFlags::IPV4 - | NetifSearchFlags::IPV6 - | NetifSearchFlags::IPV6_LINK_LOCAL - } - } - - /// UnixNetif works on any Unix-like OS - /// - /// It is a simple implementation of the `Netif` trait that uses polling instead of actual notifications - /// to detect changes in the network interface configuration. - pub struct UnixNetif(String); - - impl UnixNetif { - /// Create a new `UnixNetif`. The implementation will try - /// to find and use a suitable interface automatically. - pub fn new_default() -> Self { - unwrap!(Self::search(NetifSearchFlags::default()).next()) - } - - /// Create a new `UnixNetif` for the given interface name - pub const fn new(if_name: String) -> Self { - Self(if_name) - } - - pub fn get_conf(&self) -> Option { - get_if_conf(&self.0) - } - - /// Search for network interfaces that match the given flags - pub fn search(flags: NetifSearchFlags) -> impl Iterator { - nix::ifaddrs::getifaddrs() - .ok() - .into_iter() - .flatten() - .filter(move |ia| { - ia.flags.contains(flags.contains_if_flags()) - && !ia.flags.intersects(flags.not_contains_if_flags()) - }) - .filter(move |ia| { - !flags.contains(NetifSearchFlags::IPV4) - || ia - .address - .map(|addr| addr.as_sockaddr_in().is_some()) - .unwrap_or(false) - }) - .map(|ia| ia.interface_name) - .filter(move |ifname| { - // Now also check the Ipv6 conditions - - if !flags.intersects( - NetifSearchFlags::IPV6 - | NetifSearchFlags::IPV6_LINK_LOCAL - | NetifSearchFlags::IPV6_NON_LINK_LOCAL, - ) { - return true; - } - - let Ok(iter) = nix::ifaddrs::getifaddrs() else { - return false; - }; - - let mut ipv6 = false; - let mut ipv6_link_local = !flags.contains(NetifSearchFlags::IPV6_LINK_LOCAL); - let mut ipv6_non_link_local = - !flags.contains(NetifSearchFlags::IPV6_NON_LINK_LOCAL); - - for ia2 in iter { - if &ia2.interface_name == ifname { - if let Some(ip) = ia2 - .address - .and_then(|addr| addr.as_sockaddr_in6().map(SockaddrIn6::ip)) - { - ipv6 = true; - - if flags.contains(NetifSearchFlags::IPV6_LINK_LOCAL) - && ip.octets()[..2] == [0xfe, 0x80] - { - ipv6_link_local = true; - } - - if flags.contains(NetifSearchFlags::IPV6_NON_LINK_LOCAL) - && ip.octets()[..2] != [0xfe, 0x80] - { - ipv6_non_link_local = true; - } - } - } - } - - ipv6 && ipv6_link_local && ipv6_non_link_local - }) - .map(UnixNetif) - } - } - - impl Default for UnixNetif { - fn default() -> Self { - Self::new_default() - } - } - - impl Netif for UnixNetif { - async fn get_conf(&self) -> Result, Error> { - Ok(UnixNetif::get_conf(self)) - } - - async fn wait_conf_change(&self) -> Result<(), Error> { - // Just poll every two seconds - Timer::after(Duration::from_secs(2)).await; - - Ok(()) - } - } - - fn get_if_conf(if_name: &str) -> Option { - extract_if_conf( - unwrap!(nix::ifaddrs::getifaddrs()) - .filter(|ia| ia.interface_name == if_name) - .filter_map(|ia| ia.address), - ) - } - - fn extract_if_conf(addrs: impl Iterator) -> Option { - let mut ipv4 = None; - let mut ipv6 = None; - let mut interface = None; - let mut mac = None; - - for addr in addrs { - if let Some(addr_ipv4) = addr.as_sockaddr_in() { - ipv4 = Some(addr_ipv4.ip().into()); - } else if let Some(addr_ipv6) = addr.as_sockaddr_in6() { - ipv6 = Some(addr_ipv6.ip()); - } else if let Some(addr_link) = addr.as_link_addr() { - if mac.is_none() { - mac = addr_link.addr(); - } - - if interface.is_none() { - interface = Some(addr_link.ifindex() as _); - } - } - } - - Some(NetifConf { - ipv4: ipv4?, - ipv6: ipv6?, - interface: interface?, - mac: mac?, - }) - } -} diff --git a/src/persist.rs b/src/persist.rs index 0cc95db..52d1e0d 100644 --- a/src/persist.rs +++ b/src/persist.rs @@ -1,8 +1,9 @@ use core::fmt::Display; use embassy_futures::select::select; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::raw::{NoopRawMutex, RawMutex}; +use rs_matter::data_model::networks::wireless::{WirelessNetwork, WirelessNetworks}; use rs_matter::error::Error; use rs_matter::utils::storage::pooled::{BufferAccess, PooledBuffer, PooledBuffers}; use rs_matter::utils::sync::{IfMutex, IfMutexGuard}; @@ -41,12 +42,7 @@ where let (mut kv, mut buf) = self.store.get().await; kv.remove(MatterStackKey::Fabrics as _, &mut buf).await?; - kv.remove(MatterStackKey::EthNetworks as _, &mut buf) - .await?; - kv.remove(MatterStackKey::WifiNetworks as _, &mut buf) - .await?; - kv.remove(MatterStackKey::ThreadNetworks as _, &mut buf) - .await?; + kv.remove(MatterStackKey::Networks as _, &mut buf).await?; self.networks.reset()?; @@ -66,7 +62,7 @@ where }) .await?; - kv.load(C::KEY as _, &mut buf, |data| { + kv.load(MatterStackKey::Networks as _, &mut buf, |data| { if let Some(data) = data { self.networks.load(data)?; } @@ -100,8 +96,10 @@ where } if self.networks.changed() { - kv.store(C::KEY as _, &mut buf, |buf| self.networks.store(buf)) - .await?; + kv.store(MatterStackKey::Networks as _, &mut buf, |buf| { + self.networks.store(buf) + }) + .await?; } Ok(true) @@ -129,8 +127,6 @@ where /// - `()` - which is used with the `Eth` network /// - `&NetworkContext` - which is used with the `WirelessBle` network. pub trait NetworkPersist: Sealed { - const KEY: MatterStackKey; - /// Reset all networks, removing all stored data from the memory fn reset(&self) -> Result<(), Error>; @@ -155,13 +151,41 @@ pub trait NetworkPersist: Sealed { async fn wait_state_changed(&self); } +impl Sealed for &WirelessNetworks where M: RawMutex {} + +impl NetworkPersist for &WirelessNetworks +where + M: RawMutex, + T: WirelessNetwork, +{ + fn reset(&self) -> Result<(), Error> { + WirelessNetworks::reset(self); + + Ok(()) + } + + fn load(&self, data: &[u8]) -> Result<(), Error> { + WirelessNetworks::load(self, data) + } + + fn store(&self, buf: &mut [u8]) -> Result { + WirelessNetworks::store(self, buf).map(|data| data.map(|data| data.len()).unwrap_or(0)) + } + + fn changed(&self) -> bool { + WirelessNetworks::changed(self) + } + + async fn wait_state_changed(&self) { + WirelessNetworks::wait_persist(self).await + } +} + /// A no-op implementation of the `NetworksPersist` trait. /// /// Used when the Matter stack is configured for Ethernet, as in that case /// there is no network state that needs to be saved impl NetworkPersist for () { - const KEY: MatterStackKey = MatterStackKey::EthNetworks; // Does not matter really as Ethernet networks do not have a persistence story - fn reset(&self) -> Result<(), Error> { Ok(()) } @@ -192,9 +216,7 @@ pub const VENDOR_KEYS_START: u16 = 0x1000; #[repr(u16)] pub enum MatterStackKey { Fabrics = 0, - EthNetworks = 1, - WifiNetworks = 2, - ThreadNetworks = 3, + Networks = 1, } impl TryFrom for MatterStackKey { @@ -203,9 +225,7 @@ impl TryFrom for MatterStackKey { fn try_from(value: u16) -> Result { match value { 0 => Ok(MatterStackKey::Fabrics), - 1 => Ok(MatterStackKey::EthNetworks), - 2 => Ok(MatterStackKey::WifiNetworks), - 3 => Ok(MatterStackKey::ThreadNetworks), + 1 => Ok(MatterStackKey::Networks), _ => Err(()), } } @@ -215,9 +235,7 @@ impl Display for MatterStackKey { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let s = match self { MatterStackKey::Fabrics => "fabrics", - MatterStackKey::EthNetworks => "eth-net", - MatterStackKey::WifiNetworks => "wifi-net", - MatterStackKey::ThreadNetworks => "thread-net", + MatterStackKey::Networks => "networks", }; write!(f, "{}", s) @@ -229,9 +247,7 @@ impl defmt::Format for MatterStackKey { fn format(&self, f: defmt::Formatter<'_>) { let s = match self { MatterStackKey::Fabrics => "fabrics", - MatterStackKey::EthNetworks => "eth-net", - MatterStackKey::WifiNetworks => "wifi-net", - MatterStackKey::ThreadNetworks => "thread-net", + MatterStackKey::Networks => "networks", }; defmt::write!(f, "{}", s) diff --git a/src/rand.rs b/src/rand.rs index 1e7d8f3..41cabe4 100644 --- a/src/rand.rs +++ b/src/rand.rs @@ -1,4 +1,4 @@ -pub use rand_core::{CryptoRng, Error, RngCore}; +pub use rand_core::{CryptoRng, RngCore}; use rs_matter::utils::rand::Rand; @@ -29,12 +29,34 @@ impl RngCore for MatterRngCore { fn fill_bytes(&mut self, dest: &mut [u8]) { (self.0)(dest); } +} + +impl CryptoRng for MatterRngCore {} + +impl rand_core06::RngCore for MatterRngCore { + fn next_u32(&mut self) -> u32 { + let mut bytes = [0; 4]; + (self.0)(&mut bytes); + + u32::from_ne_bytes(bytes) + } + + fn next_u64(&mut self) -> u64 { + let mut bytes = [0; 8]; + (self.0)(&mut bytes); + + u64::from_ne_bytes(bytes) + } - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + fn fill_bytes(&mut self, dest: &mut [u8]) { + (self.0)(dest); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core06::Error> { (self.0)(dest); Ok(()) } } -impl CryptoRng for MatterRngCore {} +impl rand_core06::CryptoRng for MatterRngCore {} diff --git a/src/test_device.rs b/src/test_device.rs deleted file mode 100644 index 5f5d2c3..0000000 --- a/src/test_device.rs +++ /dev/null @@ -1,174 +0,0 @@ -/* - * - * Copyright (c) 2020-2022 Project CHIP Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -use rs_matter::data_model::sdm::dev_att::{DataType, DevAttDataFetcher}; -use rs_matter::error::{Error, ErrorCode}; -use rs_matter::BasicCommData; - -/// Test Device Attestation credentials -pub const TEST_DEV_ATT: TestDevAtt = TestDevAtt(()); -/// Test Vendor ID -pub const TEST_VID: u16 = 0xfff1; -/// Test Product ID -pub const TEST_PID: u16 = 0x8001; -/// Test Basic commissioning Data -pub const TEST_BASIC_COMM_DATA: BasicCommData = BasicCommData { - password: 20202021, - discriminator: 3840, -}; - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct TestDevAtt(()); - -// credentials/examples/ExamplePAI.cpp FFF1 -const PAI_CERT: [u8; 463] = [ - 0x30, 0x82, 0x01, 0xcb, 0x30, 0x82, 0x01, 0x71, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x08, 0x56, - 0xad, 0x82, 0x22, 0xad, 0x94, 0x5b, 0x64, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, - 0x04, 0x03, 0x02, 0x30, 0x30, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0f, - 0x4d, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x50, 0x41, 0x41, 0x31, - 0x14, 0x30, 0x12, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xa2, 0x7c, 0x02, 0x01, 0x0c, - 0x04, 0x46, 0x46, 0x46, 0x31, 0x30, 0x20, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x32, 0x30, 0x35, 0x30, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x18, 0x0f, 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, - 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x3d, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, - 0x04, 0x03, 0x0c, 0x1c, 0x4d, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, 0x44, 0x65, 0x76, 0x20, 0x50, - 0x41, 0x49, 0x20, 0x30, 0x78, 0x46, 0x46, 0x46, 0x31, 0x20, 0x6e, 0x6f, 0x20, 0x50, 0x49, 0x44, - 0x31, 0x14, 0x30, 0x12, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xa2, 0x7c, 0x02, 0x01, - 0x0c, 0x04, 0x46, 0x46, 0x46, 0x31, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, - 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, - 0x04, 0x41, 0x9a, 0x93, 0x15, 0xc2, 0x17, 0x3e, 0x0c, 0x8c, 0x87, 0x6d, 0x03, 0xcc, 0xfc, 0x94, - 0x48, 0x52, 0x64, 0x7f, 0x7f, 0xec, 0x5e, 0x50, 0x82, 0xf4, 0x05, 0x99, 0x28, 0xec, 0xa8, 0x94, - 0xc5, 0x94, 0x15, 0x13, 0x09, 0xac, 0x63, 0x1e, 0x4c, 0xb0, 0x33, 0x92, 0xaf, 0x68, 0x4b, 0x0b, - 0xaf, 0xb7, 0xe6, 0x5b, 0x3b, 0x81, 0x62, 0xc2, 0xf5, 0x2b, 0xf9, 0x31, 0xb8, 0xe7, 0x7a, 0xaa, - 0x82, 0xa3, 0x66, 0x30, 0x64, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, - 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, - 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, - 0x04, 0x16, 0x04, 0x14, 0x63, 0x54, 0x0e, 0x47, 0xf6, 0x4b, 0x1c, 0x38, 0xd1, 0x38, 0x84, 0xa4, - 0x62, 0xd1, 0x6c, 0x19, 0x5d, 0x8f, 0xfb, 0x3c, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, - 0x18, 0x30, 0x16, 0x80, 0x14, 0x6a, 0xfd, 0x22, 0x77, 0x1f, 0x51, 0x1f, 0xec, 0xbf, 0x16, 0x41, - 0x97, 0x67, 0x10, 0xdc, 0xdc, 0x31, 0xa1, 0x71, 0x7e, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, - 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, 0x02, 0x21, 0x00, 0xb2, 0xef, 0x27, - 0xf4, 0x9a, 0xe9, 0xb5, 0x0f, 0xb9, 0x1e, 0xea, 0xc9, 0x4c, 0x4d, 0x0b, 0xdb, 0xb8, 0xd7, 0x92, - 0x9c, 0x6c, 0xb8, 0x8f, 0xac, 0xe5, 0x29, 0x36, 0x8d, 0x12, 0x05, 0x4c, 0x0c, 0x02, 0x20, 0x65, - 0x5d, 0xc9, 0x2b, 0x86, 0xbd, 0x90, 0x98, 0x82, 0xa6, 0xc6, 0x21, 0x77, 0xb8, 0x25, 0xd7, 0xd0, - 0x5e, 0xdb, 0xe7, 0xc2, 0x2f, 0x9f, 0xea, 0x71, 0x22, 0x0e, 0x7e, 0xa7, 0x03, 0xf8, 0x91, -]; - -// credentials/examples/ExampleDACs.cpp FFF1-8000-0002-Cert -const DAC_CERT: [u8; 492] = [ - 0x30, 0x82, 0x01, 0xe8, 0x30, 0x82, 0x01, 0x8e, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x08, 0x52, - 0x72, 0x4d, 0x21, 0xe2, 0xc1, 0x74, 0xaf, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, - 0x04, 0x03, 0x02, 0x30, 0x3d, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x1c, - 0x4d, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, 0x44, 0x65, 0x76, 0x20, 0x50, 0x41, 0x49, 0x20, 0x30, - 0x78, 0x46, 0x46, 0x46, 0x31, 0x20, 0x6e, 0x6f, 0x20, 0x50, 0x49, 0x44, 0x31, 0x14, 0x30, 0x12, - 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xa2, 0x7c, 0x02, 0x01, 0x0c, 0x04, 0x46, 0x46, - 0x46, 0x31, 0x30, 0x20, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x32, 0x30, 0x35, 0x30, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x5a, 0x18, 0x0f, 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, - 0x39, 0x35, 0x39, 0x5a, 0x30, 0x53, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, - 0x1c, 0x4d, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, 0x44, 0x65, 0x76, 0x20, 0x44, 0x41, 0x43, 0x20, - 0x30, 0x78, 0x46, 0x46, 0x46, 0x31, 0x2f, 0x30, 0x78, 0x38, 0x30, 0x30, 0x32, 0x31, 0x14, 0x30, - 0x12, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xa2, 0x7c, 0x02, 0x01, 0x0c, 0x04, 0x46, - 0x46, 0x46, 0x31, 0x31, 0x14, 0x30, 0x12, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xa2, - 0x7c, 0x02, 0x02, 0x0c, 0x04, 0x38, 0x30, 0x30, 0x32, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, - 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, - 0x03, 0x42, 0x00, 0x04, 0xda, 0x93, 0xf1, 0x67, 0x36, 0x25, 0x67, 0x50, 0xd9, 0x03, 0xb0, 0x34, - 0xba, 0x45, 0x88, 0xab, 0xaf, 0x58, 0x95, 0x4f, 0x77, 0xaa, 0x9f, 0xd9, 0x98, 0x9d, 0xfd, 0x40, - 0x0d, 0x7a, 0xb3, 0xfd, 0xc9, 0x75, 0x3b, 0x3b, 0x92, 0x1b, 0x29, 0x4c, 0x95, 0x0f, 0xd9, 0xd2, - 0x80, 0xd1, 0x4c, 0x43, 0x86, 0x2f, 0x16, 0xdc, 0x85, 0x4b, 0x00, 0xed, 0x39, 0xe7, 0x50, 0xba, - 0xbf, 0x1d, 0xc4, 0xca, 0xa3, 0x60, 0x30, 0x5e, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, - 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, - 0x04, 0x04, 0x03, 0x02, 0x07, 0x80, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, - 0x14, 0xef, 0x06, 0x56, 0x11, 0x9c, 0x1c, 0x91, 0xa7, 0x9a, 0x94, 0xe6, 0xdc, 0xf3, 0x79, 0x79, - 0xdb, 0xd0, 0x7f, 0xf8, 0xa3, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, - 0x80, 0x14, 0x63, 0x54, 0x0e, 0x47, 0xf6, 0x4b, 0x1c, 0x38, 0xd1, 0x38, 0x84, 0xa4, 0x62, 0xd1, - 0x6c, 0x19, 0x5d, 0x8f, 0xfb, 0x3c, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, - 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, 0x02, 0x20, 0x46, 0x86, 0x81, 0x07, 0x33, 0xbf, 0x0d, - 0xc8, 0xff, 0x4c, 0xb5, 0x14, 0x5a, 0x6b, 0xfa, 0x1a, 0xec, 0xff, 0xa8, 0xb6, 0xda, 0xb6, 0xc3, - 0x51, 0xaa, 0xee, 0xcd, 0xaf, 0xb8, 0xbe, 0x95, 0x7d, 0x02, 0x21, 0x00, 0xe8, 0xc2, 0x8d, 0x6b, - 0xfc, 0xc8, 0x7a, 0x7d, 0x54, 0x2e, 0xad, 0x6e, 0xda, 0xca, 0x14, 0x8d, 0x5f, 0xa5, 0x06, 0x1e, - 0x51, 0x7c, 0xbe, 0x4f, 0x24, 0xa7, 0x20, 0xe1, 0xc0, 0x59, 0xde, 0x1a, -]; - -const DAC_PUBKEY: [u8; 65] = [ - 0x04, 0xda, 0x93, 0xf1, 0x67, 0x36, 0x25, 0x67, 0x50, 0xd9, 0x03, 0xb0, 0x34, 0xba, 0x45, 0x88, - 0xab, 0xaf, 0x58, 0x95, 0x4f, 0x77, 0xaa, 0x9f, 0xd9, 0x98, 0x9d, 0xfd, 0x40, 0x0d, 0x7a, 0xb3, - 0xfd, 0xc9, 0x75, 0x3b, 0x3b, 0x92, 0x1b, 0x29, 0x4c, 0x95, 0x0f, 0xd9, 0xd2, 0x80, 0xd1, 0x4c, - 0x43, 0x86, 0x2f, 0x16, 0xdc, 0x85, 0x4b, 0x00, 0xed, 0x39, 0xe7, 0x50, 0xba, 0xbf, 0x1d, 0xc4, - 0xca, -]; - -const DAC_PRIVKEY: [u8; 32] = [ - 0xda, 0xf2, 0x1a, 0x7e, 0xa4, 0x7a, 0x70, 0x48, 0x02, 0xa7, 0xe6, 0x6c, 0x50, 0xeb, 0x10, 0xba, - 0xc3, 0xbd, 0xd1, 0x68, 0x80, 0x39, 0x80, 0x66, 0xff, 0xda, 0xd7, 0xf5, 0x20, 0x98, 0xb6, 0x85, -]; - -// -const CERT_DECLARATION: [u8; 541] = [ - 0x30, 0x82, 0x02, 0x19, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02, 0xa0, - 0x82, 0x02, 0x0a, 0x30, 0x82, 0x02, 0x06, 0x02, 0x01, 0x03, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x09, - 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x30, 0x82, 0x01, 0x71, 0x06, 0x09, 0x2a, - 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x82, 0x01, 0x62, 0x04, 0x82, 0x01, 0x5e, - 0x15, 0x24, 0x00, 0x01, 0x25, 0x01, 0xf1, 0xff, 0x36, 0x02, 0x05, 0x00, 0x80, 0x05, 0x01, 0x80, - 0x05, 0x02, 0x80, 0x05, 0x03, 0x80, 0x05, 0x04, 0x80, 0x05, 0x05, 0x80, 0x05, 0x06, 0x80, 0x05, - 0x07, 0x80, 0x05, 0x08, 0x80, 0x05, 0x09, 0x80, 0x05, 0x0a, 0x80, 0x05, 0x0b, 0x80, 0x05, 0x0c, - 0x80, 0x05, 0x0d, 0x80, 0x05, 0x0e, 0x80, 0x05, 0x0f, 0x80, 0x05, 0x10, 0x80, 0x05, 0x11, 0x80, - 0x05, 0x12, 0x80, 0x05, 0x13, 0x80, 0x05, 0x14, 0x80, 0x05, 0x15, 0x80, 0x05, 0x16, 0x80, 0x05, - 0x17, 0x80, 0x05, 0x18, 0x80, 0x05, 0x19, 0x80, 0x05, 0x1a, 0x80, 0x05, 0x1b, 0x80, 0x05, 0x1c, - 0x80, 0x05, 0x1d, 0x80, 0x05, 0x1e, 0x80, 0x05, 0x1f, 0x80, 0x05, 0x20, 0x80, 0x05, 0x21, 0x80, - 0x05, 0x22, 0x80, 0x05, 0x23, 0x80, 0x05, 0x24, 0x80, 0x05, 0x25, 0x80, 0x05, 0x26, 0x80, 0x05, - 0x27, 0x80, 0x05, 0x28, 0x80, 0x05, 0x29, 0x80, 0x05, 0x2a, 0x80, 0x05, 0x2b, 0x80, 0x05, 0x2c, - 0x80, 0x05, 0x2d, 0x80, 0x05, 0x2e, 0x80, 0x05, 0x2f, 0x80, 0x05, 0x30, 0x80, 0x05, 0x31, 0x80, - 0x05, 0x32, 0x80, 0x05, 0x33, 0x80, 0x05, 0x34, 0x80, 0x05, 0x35, 0x80, 0x05, 0x36, 0x80, 0x05, - 0x37, 0x80, 0x05, 0x38, 0x80, 0x05, 0x39, 0x80, 0x05, 0x3a, 0x80, 0x05, 0x3b, 0x80, 0x05, 0x3c, - 0x80, 0x05, 0x3d, 0x80, 0x05, 0x3e, 0x80, 0x05, 0x3f, 0x80, 0x05, 0x40, 0x80, 0x05, 0x41, 0x80, - 0x05, 0x42, 0x80, 0x05, 0x43, 0x80, 0x05, 0x44, 0x80, 0x05, 0x45, 0x80, 0x05, 0x46, 0x80, 0x05, - 0x47, 0x80, 0x05, 0x48, 0x80, 0x05, 0x49, 0x80, 0x05, 0x4a, 0x80, 0x05, 0x4b, 0x80, 0x05, 0x4c, - 0x80, 0x05, 0x4d, 0x80, 0x05, 0x4e, 0x80, 0x05, 0x4f, 0x80, 0x05, 0x50, 0x80, 0x05, 0x51, 0x80, - 0x05, 0x52, 0x80, 0x05, 0x53, 0x80, 0x05, 0x54, 0x80, 0x05, 0x55, 0x80, 0x05, 0x56, 0x80, 0x05, - 0x57, 0x80, 0x05, 0x58, 0x80, 0x05, 0x59, 0x80, 0x05, 0x5a, 0x80, 0x05, 0x5b, 0x80, 0x05, 0x5c, - 0x80, 0x05, 0x5d, 0x80, 0x05, 0x5e, 0x80, 0x05, 0x5f, 0x80, 0x05, 0x60, 0x80, 0x05, 0x61, 0x80, - 0x05, 0x62, 0x80, 0x05, 0x63, 0x80, 0x18, 0x24, 0x03, 0x16, 0x2c, 0x04, 0x13, 0x5a, 0x49, 0x47, - 0x32, 0x30, 0x31, 0x34, 0x32, 0x5a, 0x42, 0x33, 0x33, 0x30, 0x30, 0x30, 0x33, 0x2d, 0x32, 0x34, - 0x24, 0x05, 0x00, 0x24, 0x06, 0x00, 0x25, 0x07, 0x94, 0x26, 0x24, 0x08, 0x00, 0x18, 0x31, 0x7d, - 0x30, 0x7b, 0x02, 0x01, 0x03, 0x80, 0x14, 0x62, 0xfa, 0x82, 0x33, 0x59, 0xac, 0xfa, 0xa9, 0x96, - 0x3e, 0x1c, 0xfa, 0x14, 0x0a, 0xdd, 0xf5, 0x04, 0xf3, 0x71, 0x60, 0x30, 0x0b, 0x06, 0x09, 0x60, - 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, - 0x3d, 0x04, 0x03, 0x02, 0x04, 0x47, 0x30, 0x45, 0x02, 0x20, 0x24, 0xe5, 0xd1, 0xf4, 0x7a, 0x7d, - 0x7b, 0x0d, 0x20, 0x6a, 0x26, 0xef, 0x69, 0x9b, 0x7c, 0x97, 0x57, 0xb7, 0x2d, 0x46, 0x90, 0x89, - 0xde, 0x31, 0x92, 0xe6, 0x78, 0xc7, 0x45, 0xe7, 0xf6, 0x0c, 0x02, 0x21, 0x00, 0xf8, 0xaa, 0x2f, - 0xa7, 0x11, 0xfc, 0xb7, 0x9b, 0x97, 0xe3, 0x97, 0xce, 0xda, 0x66, 0x7b, 0xae, 0x46, 0x4e, 0x2b, - 0xd3, 0xff, 0xdf, 0xc3, 0xcc, 0xed, 0x7a, 0xa8, 0xca, 0x5f, 0x4c, 0x1a, 0x7c, -]; - -impl DevAttDataFetcher for TestDevAtt { - fn get_devatt_data(&self, data_type: DataType, data: &mut [u8]) -> Result { - let src = match data_type { - DataType::CertDeclaration => &CERT_DECLARATION[..], - DataType::PAI => &PAI_CERT[..], - DataType::DAC => &DAC_CERT[..], - DataType::DACPubKey => &DAC_PUBKEY[..], - DataType::DACPrivKey => &DAC_PRIVKEY[..], - }; - if src.len() <= data.len() { - let data = &mut data[0..src.len()]; - data.copy_from_slice(src); - Ok(src.len()) - } else { - Err(ErrorCode::NoSpace.into()) - } - } -} diff --git a/src/wireless.rs b/src/wireless.rs index 3aa4ba4..a9f39c8 100644 --- a/src/wireless.rs +++ b/src/wireless.rs @@ -1,50 +1,43 @@ -use core::future::Future; use core::pin::pin; -use diag::thread::ThreadNwDiagCluster; -use diag::wifi::WifiNwDiagCluster; - use edge_nal::UdpBind; use embassy_futures::select::{select, select3}; use embassy_sync::blocking_mutex::raw::RawMutex; -use embassy_time::{Duration, Timer}; -use rs_matter::data_model::objects::{AsyncHandler, AsyncMetadata, Dataver, Endpoint}; -use rs_matter::data_model::root_endpoint; -use rs_matter::data_model::root_endpoint::{handler, OperNwType, RootEndpointHandler}; -use rs_matter::data_model::sdm::thread_nw_diagnostics; -use rs_matter::data_model::sdm::wifi_nw_diagnostics; +use rs_matter::data_model::networks::wireless::{ + NetCtlState, WirelessMgr, WirelessNetwork, WirelessNetworks, MAX_CREDS_SIZE, +}; +use rs_matter::data_model::networks::NetChangeNotif; +use rs_matter::data_model::sdm::gen_diag::NetifDiag; +use rs_matter::data_model::sdm::net_comm::NetCtl; +use rs_matter::data_model::sdm::wifi_diag::WirelessDiag; use rs_matter::error::Error; use rs_matter::pairing::DiscoveryCapabilities; -use rs_matter::tlv::{FromTLV, ToTLV}; use rs_matter::transport::network::btp::{Btp, BtpContext, GattPeripheral}; use rs_matter::transport::network::NoNetwork; -use rs_matter::utils::init::{init, Init}; +use rs_matter::utils::cell::RefCell; +use rs_matter::utils::init::{init, zeroed, Init}; use rs_matter::utils::select::Coalesce; +use rs_matter::utils::sync::{blocking, IfMutex}; -pub use traits::*; - -use crate::netif::Netif; use crate::network::{Embedding, Network}; -use crate::persist::{KvBlobStore, SharedKvBlobStore}; use crate::private::Sealed; -use crate::wireless::mgmt::WirelessManager; -use crate::wireless::store::NetworkContext; use crate::MatterStack; -use self::proxy::ControllerProxy; +pub use gatt::*; +pub use thread::*; +pub use wifi::*; -pub mod comm; -pub mod diag; -pub mod mgmt; -pub mod proxy; -pub mod store; -pub mod svc; -mod traits; +mod gatt; +mod thread; +mod wifi; const MAX_WIRELESS_NETWORKS: usize = 2; +/// A type alias for a Matter stack running over either Wifi or Thread (and BLE, during commissioning). +pub type WirelessMatterStack<'a, M, T, E = ()> = MatterStack<'a, WirelessBle>; + /// An implementation of the `Network` trait for a Matter stack running over /// BLE during commissioning, and then over either WiFi or Thread when operating. /// @@ -60,38 +53,28 @@ const MAX_WIRELESS_NETWORKS: usize = 2; pub struct WirelessBle where M: RawMutex, - T: WirelessConfig, - ::NetworkCredentials: Clone + for<'a> FromTLV<'a> + ToTLV, + T: WirelessNetwork, { btp_context: BtpContext, - network_context: NetworkContext, + networks: WirelessNetworks, + net_state: blocking::Mutex>, + creds_buf: IfMutex, embedding: E, } -impl Default for WirelessBle -where - M: RawMutex, - T: WirelessConfig, - ::NetworkCredentials: Clone + for<'a> FromTLV<'a> + ToTLV, - E: Embedding, -{ - fn default() -> Self { - Self::new() - } -} - impl WirelessBle where M: RawMutex, - T: WirelessConfig, - ::NetworkCredentials: Clone + for<'a> FromTLV<'a> + ToTLV, + T: WirelessNetwork, E: Embedding, { /// Creates a new instance of the `WirelessBle` network type. pub const fn new() -> Self { Self { btp_context: BtpContext::new(), - network_context: NetworkContext::new(), + networks: WirelessNetworks::new(), + net_state: NetCtlState::new_with_mutex(), + creds_buf: IfMutex::new([0; MAX_CREDS_SIZE]), embedding: E::INIT, } } @@ -100,22 +83,34 @@ where pub fn init() -> impl Init { init!(Self { btp_context <- BtpContext::init(), - network_context <- NetworkContext::init(), + networks <- WirelessNetworks::init(), + net_state <- NetCtlState::init_with_mutex(), + creds_buf <- IfMutex::init(zeroed()), embedding <- E::init(), }) } - /// Return a reference to the BTP context. - pub fn network_context(&self) -> &NetworkContext { - &self.network_context + /// Return a reference to the networks storage. + pub fn networks(&self) -> &WirelessNetworks { + &self.networks + } +} + +impl Default for WirelessBle +where + M: RawMutex, + T: WirelessNetwork, + E: Embedding, +{ + fn default() -> Self { + Self::new() } } impl Sealed for WirelessBle where M: RawMutex, - T: WirelessConfig, - ::NetworkCredentials: Clone + for<'a> FromTLV<'a> + ToTLV, + T: WirelessNetwork, E: Embedding, { } @@ -123,18 +118,17 @@ where impl Network for WirelessBle where M: RawMutex + 'static, - T: WirelessConfig, - ::NetworkCredentials: Clone + for<'a> FromTLV<'a> + ToTLV, + T: WirelessNetwork, E: Embedding + 'static, { const INIT: Self = Self::new(); - type PersistContext<'a> = &'a NetworkContext; + type PersistContext<'a> = &'a WirelessNetworks; type Embedding = E; fn persist_context(&self) -> Self::PersistContext<'_> { - &self.network_context + &self.networks } fn embedding(&self) -> &Self::Embedding { @@ -146,17 +140,10 @@ where } } -/// A type alias for a Matter stack running over Wifi (and BLE, during commissioning). -pub type WifiMatterStack<'a, M, E = ()> = MatterStack<'a, WirelessBle>; - -/// A type alias for a Matter stack running over Thread (and BLE, during commissioning). -pub type ThreadMatterStack<'a, M, E = ()> = MatterStack<'a, WirelessBle>; - impl MatterStack<'_, WirelessBle> where M: RawMutex + Send + Sync + 'static, - T: WirelessConfig, - ::NetworkCredentials: Clone + for<'t> FromTLV<'t> + ToTLV, + T: WirelessNetwork, E: Embedding + 'static, { /// Reset the Matter instance to the factory defaults putting it into a @@ -165,175 +152,23 @@ where // TODO: Reset fabrics and ACLs // TODO self.network.btp_gatt_context.reset()?; // TODO self.network.btp_context.reset(); - self.network.network_context.reset(); + self.network.networks.reset(); Ok(()) } - /// Run the Matter stack for an already pre-existing wireless network where the BLE and the operational network can co-exist. - /// - /// Parameters: - /// - `netif` - a user-provided `Netif` implementation - /// - `udp` - a user-provided `UdpBind` implementation - /// - `controller` - a user-provided `Controller` implementation - /// - `gatt` - a user-provided `GattPeripheral` implementation - /// - `store` - a `SharedKvBlobStore` implementation wrapping a user-provided `KvBlobStore` - /// - `handler` - a user-provided DM handler implementation - /// - `user` - a user-provided future that will be polled only when the netif interface is up - #[allow(clippy::too_many_arguments)] - pub async fn run_preex( + async fn run_net_coex( &'static self, - netif: N, udp: U, - controller: C, - gatt: G, - store: &SharedKvBlobStore<'_, S>, - handler: H, - user: X, + netif: N, + net_ctl: C, + mut gatt: G, ) -> Result<(), Error> where - N: Netif, U: UdpBind, - C: Controller, + N: NetifDiag + NetChangeNotif, + C: NetCtl + WirelessDiag + NetChangeNotif, G: GattPeripheral, - S: KvBlobStore, - H: AsyncHandler + AsyncMetadata, - X: Future>, - { - self.run_coex( - PreexistingWireless::new(netif, udp, controller, gatt), - store, - handler, - user, - ) - .await - } - - /// Run the Matter stack for a wireless network where the BLE and the operational network can co-exist. - /// - /// Parameters: - /// - `wireless` - a user-provided `Wireless` implementation - /// - `store` - a `SharedKvBlobStore` implementation wrapping a user-provided `KvBlobStore` - /// - `handler` - a user-provided DM handler implementation - /// - `user` - a user-provided future that will be polled only when the netif interface is up - pub async fn run_coex( - &'static self, - wireless: W, - store: &SharedKvBlobStore<'_, S>, - handler: H, - user: U, - ) -> Result<(), Error> - where - W: WirelessCoex, - S: KvBlobStore, - H: AsyncHandler + AsyncMetadata, - U: Future>, - { - info!("Matter Stack memory: {}B", core::mem::size_of_val(self)); - - self.network - .network_context - .concurrent_connection - .lock(|s| s.set(true)); - - let persist = self.create_persist(store); - - // TODO persist.load().await?; - - self.matter().reset_transport()?; - - let mut net_task = pin!(self.run_wireless_coex(wireless)); - let mut handler_task = pin!(self.run_handlers(&persist, handler)); - let mut user_task = pin!(user); - - select3(&mut net_task, &mut handler_task, &mut user_task) - .coalesce() - .await - } - - /// Run the Matter stack for a wireless network where the BLE and the operational network cannot co-exist. - /// - /// Parameters: - /// - `wireless` - a user-provided `Wireless` + `Gatt` implementation - /// - `store` - a `SharedKvBlobStore` implementation wrapping a user-provided `KvBlobStore` - /// - `handler` - a user-provided DM handler implementation - /// - `user` - a user-provided future that will be polled only when the netif interface is up - pub async fn run( - &'static self, - wireless: W, - store: &SharedKvBlobStore<'_, S>, - handler: H, - user: U, - ) -> Result<(), Error> - where - W: Wireless + Gatt, - S: KvBlobStore, - H: AsyncHandler + AsyncMetadata, - U: Future>, - { - info!("Matter Stack memory: {}B", core::mem::size_of_val(self)); - - self.network - .network_context - .concurrent_connection - .lock(|s| s.set(false)); - - let persist = self.create_persist(store); - - // TODO persist.load().await?; - - self.matter().reset_transport()?; - - let mut net_task = pin!(self.run_wireless(wireless)); - let mut handler_task = pin!(self.run_handlers(&persist, handler)); - let mut user_task = pin!(user); - - select3(&mut net_task, &mut handler_task, &mut user_task) - .coalesce() - .await - } - - async fn run_wireless_coex(&'static self, mut wireless: W) -> Result<(), Error> - where - W: WirelessCoex, - { - #[allow(non_local_definitions)] - impl WirelessCoexTask for MatterStackWirelessTask<'static, M, T, E> - where - M: RawMutex + Send + Sync + 'static, - T: WirelessConfig, - ::NetworkCredentials: Clone + for<'t> FromTLV<'t> + ToTLV, - E: Embedding + 'static, - { - type Data = T::Data; - - async fn run( - &mut self, - netif: N, - udp: U, - controller: C, - gatt: G, - ) -> Result<(), Error> - where - N: Netif, - U: UdpBind, - C: Controller, - G: GattPeripheral, - { - info!("Wireless driver started"); - - let stack = &mut self.0; - - stack.run_net_coex(&netif, &udp, controller, gatt).await - } - } - - wireless.run(MatterStackWirelessTask(self)).await - } - - async fn run_wireless(&'static self, mut wireless: W) -> Result<(), Error> - where - W: Wireless + Gatt, { loop { let commissioned = self.is_commissioned().await?; @@ -343,160 +178,68 @@ where .enable_basic_commissioning(DiscoveryCapabilities::BLE, 0) .await?; // TODO - #[allow(non_local_definitions)] - impl GattTask for MatterStackWirelessTask<'static, M, T, E> - where - M: RawMutex + Send + Sync + 'static, - T: WirelessConfig, - ::NetworkCredentials: - Clone + for<'t> FromTLV<'t> + ToTLV, - E: Embedding + 'static, - { - async fn run

(&mut self, peripheral: P) -> Result<(), Error> - where - P: GattPeripheral, - { - let btp = Btp::new(peripheral, &self.0.network.btp_context); - - info!("BLE driver started"); - - self.0.run_btp(&btp).await - } - } - - Gatt::run(&mut wireless, MatterStackWirelessTask(self)).await?; - } + // // Return the requested network with priority + // if let Some(network_id) = self.connect_requested.take() { + // let network = self + // .networks + // .iter() + // .find(|network| network.id() == network_id); - if commissioned { - self.matter().disable_commissioning()?; - } + // if let Some(network) = network { + // info!( + // "Trying with requested network first - ID: {}", + // network.display() + // ); - #[allow(non_local_definitions)] - impl WirelessTask for MatterStackWirelessTask<'static, M, T, E> - where - M: RawMutex + Send + Sync + 'static, - T: WirelessConfig, - ::NetworkCredentials: Clone + for<'t> FromTLV<'t> + ToTLV, - E: Embedding + 'static, - { - type Data = T::Data; - - async fn run( - &mut self, - netif: N, - udp: U, - mut controller: C, - ) -> Result<(), Error> - where - N: Netif, - U: UdpBind, - C: Controller, - { - info!("Wireless driver started"); - - let mut mgr = - WirelessManager::new(&self.0.network.network_context.controller_proxy); - - let stack = &mut self.0; - - let mut net_task = pin!(stack.run_oper_net( - &netif, - &udp, - core::future::pending(), - Option::<(NoNetwork, NoNetwork)>::None - )); - let mut mgr_task = pin!(mgr.run(&stack.network.network_context)); - let mut proxy_task = pin!(stack - .network - .network_context - .controller_proxy - .process_with(&mut controller)?); - - select3(&mut net_task, &mut mgr_task, &mut proxy_task) - .coalesce() - .await - } - } + // f(network)?; + // return Ok(true); + // } + // } - Wireless::run(&mut wireless, MatterStackWirelessTask(self)).await?; - } - } + let mut buf = self.network.creds_buf.lock().await; - async fn run_net_coex( - &'static self, - netif: N, - udp: U, - mut controller: C, - gatt: G, - ) -> Result<(), Error> - where - C: Controller, - N: Netif, - U: UdpBind, - G: GattPeripheral, - { - loop { - let commissioned = self.is_commissioned().await?; + let mut mgr = WirelessMgr::new(&self.network.networks, &net_ctl, &mut buf); - if !commissioned { - self.matter() - .enable_basic_commissioning(DiscoveryCapabilities::BLE, 0) - .await?; // TODO - - let btp = Btp::new(&gatt, &self.network.btp_context); + let mut net_task = pin!(self.run_btp_coex(&udp, &netif, &mut gatt)); + let mut mgr_task = pin!(mgr.run()); - info!("BLE driver started"); + select(&mut net_task, &mut mgr_task).coalesce().await?; + } else { + info!("Running in commissioned mode (wireless only)"); - let mut mgr = WirelessManager::new(&self.network.network_context.controller_proxy); + let mut buf = self.network.creds_buf.lock().await; - let mut net_task = pin!(self.run_btp_coex(&netif, &udp, &btp)); - let mut mgr_task = pin!(mgr.run(&self.network.network_context)); - let mut proxy_task = pin!(self - .network - .network_context - .controller_proxy - .process_with(&mut controller)?); - - select3(&mut net_task, &mut mgr_task, &mut proxy_task) - .coalesce() - .await?; - } else { - let mut mgr = WirelessManager::new(&self.network.network_context.controller_proxy); + let mut mgr = WirelessMgr::new(&self.network.networks, &net_ctl, &mut buf); self.matter().disable_commissioning()?; let mut net_task = pin!(self.run_oper_net( - &netif, &udp, + &netif, core::future::pending(), Option::<(NoNetwork, NoNetwork)>::None )); - let mut mgr_task = pin!(mgr.run(&self.network.network_context)); - let mut proxy_task = pin!(self - .network - .network_context - .controller_proxy - .process_with(&mut controller)?); - - select3(&mut net_task, &mut mgr_task, &mut proxy_task) - .coalesce() - .await?; + let mut mgr_task = pin!(mgr.run()); + + select(&mut net_task, &mut mgr_task).coalesce().await?; } } } - async fn run_btp_coex( - &self, - mut netif: N, + async fn run_btp_coex( + &'static self, mut udp: U, - btp: &Btp<&'static BtpContext, M, B>, + netif: N, + peripheral: P, ) -> Result<(), Error> where - N: Netif, U: UdpBind, - B: GattPeripheral, + N: NetifDiag + NetChangeNotif, + P: GattPeripheral, { - info!("Running Matter in concurrent commissioning mode (BLE and Wireless)"); + info!("Running in concurrent commissioning mode (BLE and Wireless)"); + + let btp = Btp::new(peripheral, &self.network.btp_context); let mut btp_task = pin!(btp.run( "BT", @@ -506,20 +249,24 @@ where // TODO: Run till commissioning is complete let mut net_task = pin!(self.run_oper_net( - &mut netif, &mut udp, + &netif, core::future::pending(), - Some((btp, btp)) + Some((&btp, &btp)) )); select(&mut btp_task, &mut net_task).coalesce().await } - async fn run_btp(&'static self, btp: &Btp<&'static BtpContext, M, B>) -> Result<(), Error> + async fn run_btp

(&'static self, peripheral: P) -> Result<(), Error> where - B: GattPeripheral, + P: GattPeripheral, { - info!("Running Matter in non-concurrent commissioning mode (BLE only)"); + let btp = Btp::new(peripheral, &self.network.btp_context); + + info!("BLE driver started"); + + info!("Running in non-concurrent commissioning mode (BLE only)"); let mut btp_task = pin!(btp.run( "BT", @@ -527,19 +274,15 @@ where self.matter().dev_comm().discriminator, )); - let mut net_task = pin!(self.run_transport_net(btp, btp)); - + let mut net_task = pin!(self.run_transport_net(&btp, &btp)); let mut oper_net_act_task = pin!(async { - const WAIT_SECS: u64 = 4; - - self.network.network_context.wait_network_activated().await; + NetCtlState::wait_prov_ready(&self.network.net_state, &btp).await; - warn!( - "Giving BLE/BTP extra {} seconds for any outstanding messages before switching to the operational network", - WAIT_SECS - ); - - Timer::after(Duration::from_secs(WAIT_SECS)).await; + // TODO: Workaround for a bug in the `esp-wifi` BLE stack: + // ====================== PANIC ====================== + // panicked at /home/ivan/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/esp-wifi-0.12.0/src/ble/npl.rs:914:9: + // timed eventq_get not yet supported - go implement it! + embassy_time::Timer::after(embassy_time::Duration::from_secs(2)).await; Ok(()) }); @@ -550,89 +293,37 @@ where } } -struct MatterStackWirelessTask<'a, M, T, E>(&'a MatterStack<'a, WirelessBle>) -where - M: RawMutex + Send + Sync + 'static, - T: WirelessConfig, - ::NetworkCredentials: Clone + for<'t> FromTLV<'t> + ToTLV, - E: Embedding + 'static; - -impl MatterStack<'_, WirelessBle> -where - M: RawMutex + Send + Sync + 'static, - E: Embedding + 'static, -{ - /// Return a metadata for the root (Endpoint 0) of the Matter Node - /// configured for BLE+Wifi network. - pub const fn root_metadata() -> Endpoint<'static> { - root_endpoint::endpoint(0, OperNwType::Wifi) - } +/// A utility type for running a wireless task with a pre-existing wireless interface +/// rather than bringing up / tearing down the wireless interface for the task. +/// +/// This utility can only be used with hardware that implements wireless coexist mode +/// (i.e. the Thread/Wifi interface as well as the BLE GATT peripheral are available at the same time). +pub struct PreexistingWireless { + pub(crate) udp: U, + pub(crate) netif: N, + pub(crate) net_ctl: C, + pub(crate) gatt: G, +} - /// Return a handler for the root (Endpoint 0) of the Matter Node - /// configured for BLE+Wifi network. - pub fn root_handler(&self) -> WifiRootEndpointHandler<'_, M, &ControllerProxy> { - handler( - 0, - comm::WirelessNwCommCluster::new( - Dataver::new_rand(self.matter().rand()), - &self.network.network_context, - &self.network.network_context.controller_proxy, - ), - wifi_nw_diagnostics::ID, - WifiNwDiagCluster::new( - Dataver::new_rand(self.matter().rand()), - &self.network.network_context.controller_proxy, - ), - &self.network.network_context, - self.matter().rand(), - ) +impl PreexistingWireless { + /// Create a new `PreexistingWireless` instance with the given UDP stack, + /// network interface, network controller and GATT peripheral. + pub const fn new(udp: U, netif: N, net_ctl: C, gatt: G) -> Self { + Self { + udp, + netif, + net_ctl, + gatt, + } } } -impl MatterStack<'_, WirelessBle> +pub(crate) struct MatterStackWirelessTask<'a, M, T, E, H, U>( + &'a MatterStack<'a, WirelessBle>, + H, + U, +) where M: RawMutex + Send + Sync + 'static, - E: Embedding + 'static, -{ - /// Return a metadata for the root (Endpoint 0) of the Matter Node - /// configured for BLE+Thread network. - pub const fn root_metadata() -> Endpoint<'static> { - root_endpoint::endpoint(0, OperNwType::Thread) - } - - /// Return a handler for the root (Endpoint 0) of the Matter Node - /// configured for BLE+Wifi network. - pub fn root_handler( - &self, - ) -> ThreadRootEndpointHandler<'_, M, &ControllerProxy> { - handler( - 0, - comm::WirelessNwCommCluster::new( - Dataver::new_rand(self.matter().rand()), - &self.network.network_context, - &self.network.network_context.controller_proxy, - ), - thread_nw_diagnostics::ID, - ThreadNwDiagCluster::new( - Dataver::new_rand(self.matter().rand()), - &self.network.network_context.controller_proxy, - ), - &self.network.network_context, - self.matter().rand(), - ) - } -} - -/// The root endpoint handler for a Wifi network. -pub type WifiRootEndpointHandler<'a, M, T> = RootEndpointHandler< - 'a, - comm::WirelessNwCommCluster<'a, MAX_WIRELESS_NETWORKS, M, T>, - WifiNwDiagCluster, ->; - -/// The root endpoint handler for a Thread network. -pub type ThreadRootEndpointHandler<'a, M, T> = RootEndpointHandler< - 'a, - comm::WirelessNwCommCluster<'a, MAX_WIRELESS_NETWORKS, M, T>, - ThreadNwDiagCluster, ->; + T: WirelessNetwork, + E: Embedding + 'static; diff --git a/src/wireless/comm.rs b/src/wireless/comm.rs deleted file mode 100644 index ebc6737..0000000 --- a/src/wireless/comm.rs +++ /dev/null @@ -1,567 +0,0 @@ -//! Wireless network commissioning cluster. - -use core::ops::DerefMut; - -use embassy_sync::blocking_mutex::raw::RawMutex; - -use embassy_sync::mutex::Mutex; - -use rs_matter::data_model::objects::{ - AsyncHandler, AttrDataEncoder, AttrDataWriter, AttrDetails, AttrType, CmdDataEncoder, - CmdDataWriter, CmdDetails, Dataver, -}; -use rs_matter::data_model::sdm::nw_commissioning::{ - AddThreadNetworkRequest, AddWifiNetworkRequest, Attributes, Commands, ConnectNetworkRequest, - ConnectNetworkResponse, NetworkCommissioningStatus, NetworkConfigResponse, NwInfo, - RemoveNetworkRequest, ReorderNetworkRequest, ResponseCommands, ScanNetworksRequest, - ScanNetworksResponseTag, THR_CLUSTER, WIFI_CLUSTER, -}; -use rs_matter::error::Error; -use rs_matter::tlv::{FromTLV, Octets, TLVElement, TLVTag, TLVWrite, ToTLV}; -use rs_matter::transport::exchange::Exchange; - -use super::store::NetworkContext; -use super::traits::{Controller, WirelessData}; -use super::NetworkCredentials; - -/// A cluster implementing the Matter Network Commissioning Cluster -/// for managing wireless networks. -/// -/// `N` is the maximum number of networks that can be stored. -pub struct WirelessNwCommCluster<'a, const N: usize, M, T> -where - M: RawMutex, - T: Controller, - ::NetworkCredentials: Clone + for<'b> FromTLV<'b> + ToTLV, -{ - data_ver: Dataver, - networks: &'a NetworkContext, - controller: Mutex, -} - -impl<'a, const N: usize, M, T> WirelessNwCommCluster<'a, N, M, T> -where - M: RawMutex, - T: Controller, - ::NetworkCredentials: Clone + for<'b> FromTLV<'b> + ToTLV, - ::ScanResult: ToTLV, -{ - /// Create a new instance. - pub const fn new( - data_ver: Dataver, - networks: &'a NetworkContext, - controller: T, - ) -> Self { - Self { - data_ver, - networks, - controller: Mutex::new(controller), - } - } - - /// Read an attribute. - pub async fn read( - &self, - _exchange: &Exchange<'_>, - attr: &AttrDetails<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - if let Some(mut writer) = encoder.with_dataver(self.data_ver.get())? { - if attr.is_system() { - if ::WIFI { - WIFI_CLUSTER.read(attr.attr_id, writer) - } else { - THR_CLUSTER.read(attr.attr_id, writer) - } - } else { - match attr.attr_id.try_into()? { - Attributes::MaxNetworks => AttrType::::new().encode(writer, N as u8), - Attributes::Networks => { - writer.start_array(&AttrDataWriter::TAG)?; - - self.networks.state.lock(|state| { - let state = state.borrow(); - - for network in &state.networks { - let network_id = network.network_id(); - - let nw_info = NwInfo { - network_id: Octets(network_id.as_ref()), - connected: state - .status - .as_ref() - .map(|status| { - status.network_id == network_id - && matches!( - status.status, - NetworkCommissioningStatus::Success - ) - }) - .unwrap_or(false), - }; - - nw_info.to_tlv(&TLVTag::Anonymous, &mut *writer)?; - } - - Ok::<_, Error>(()) - })?; - - writer.end_container()?; - writer.complete() - } - Attributes::ScanMaxTimeSecs => AttrType::new().encode(writer, 30_u8), - Attributes::ConnectMaxTimeSecs => AttrType::new().encode(writer, 60_u8), - Attributes::InterfaceEnabled => AttrType::new().encode(writer, true), - Attributes::LastNetworkingStatus => self.networks.state.lock(|state| { - AttrType::new().encode( - writer, - state.borrow().status.as_ref().map(|o| o.status as u8), - ) - }), - Attributes::LastNetworkID => self.networks.state.lock(|state| { - AttrType::new().encode( - writer, - state - .borrow() - .status - .as_ref() - .map(|o| Octets(o.network_id.as_ref())), - ) - }), - Attributes::LastConnectErrorValue => self.networks.state.lock(|state| { - AttrType::new() - .encode(writer, state.borrow().status.as_ref().map(|o| o.value)) - }), - } - } - } else { - Ok(()) - } - } - - /// Invoke a command. - pub async fn invoke( - &self, - exchange: &Exchange<'_>, - cmd: &CmdDetails<'_>, - data: &TLVElement<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - match cmd.cmd_id.try_into()? { - Commands::ScanNetworks => { - info!("ScanNetworks"); - self.scan_networks(exchange, &ScanNetworksRequest::from_tlv(data)?, encoder) - .await?; - } - Commands::AddOrUpdateWifiNetwork => { - info!("AddOrUpdateWifiNetwork"); - self.add_wifi_network(exchange, &AddWifiNetworkRequest::from_tlv(data)?, encoder)?; - } - Commands::AddOrUpdateThreadNetwork => { - info!("AddOrUpdateThreadNetwork"); - self.add_thread_network( - exchange, - &AddThreadNetworkRequest::from_tlv(data)?, - encoder, - )?; - } - Commands::RemoveNetwork => { - info!("RemoveNetwork"); - self.remove_network(exchange, &RemoveNetworkRequest::from_tlv(data)?, encoder)?; - } - Commands::ConnectNetwork => { - info!("ConnectNetwork"); - self.connect_network(exchange, &ConnectNetworkRequest::from_tlv(data)?, encoder) - .await?; - } - Commands::ReorderNetwork => { - info!("ReorderNetwork"); - self.reorder_network(exchange, &ReorderNetworkRequest::from_tlv(data)?, encoder)?; - } - } - - self.data_ver.changed(); - - Ok(()) - } - - async fn scan_networks( - &self, - _exchange: &Exchange<'_>, - req: &ScanNetworksRequest<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - // NOTE: - // Unfortunately Alexa calls `ScanNetworks` even if we have explicitly communicated - // that we do not support concurrent commissioning - - info!("ScanNetworks req: {:?}", req); - - let mut controller = self.controller.lock().await; - - let mut encoder = Some(encoder); - let mut owriter: Option> = None; - - controller - .scan( - req.ssid.map(|ssid| ssid.0.try_into()).transpose()?.as_ref(), - |result| { - let Some(result) = result else { - return Ok(()); - }; - - if owriter.is_none() { - let mut writer = unwrap!(encoder.take()) - .with_command(ResponseCommands::ScanNetworksResponse as _)?; - - writer.start_struct(&CmdDataWriter::TAG)?; - - NetworkCommissioningStatus::Success.to_tlv( - &TLVTag::Context(ScanNetworksResponseTag::Status as _), - &mut *writer, - )?; - - writer.utf8( - &TLVTag::Context(ScanNetworksResponseTag::DebugText as _), - "", - )?; - - writer.start_array(&TLVTag::Context( - ScanNetworksResponseTag::WifiScanResults as _, - ))?; - - owriter = Some(writer); - } - - let writer = unwrap!(owriter.as_mut()); - - result.to_tlv(&TLVTag::Anonymous, writer.deref_mut())?; - - info!("Wrote scan result {:?}", result); - - Ok(()) - }, - ) - .await?; // TODO - - if let Some(mut writer) = owriter { - writer.end_container()?; - writer.end_container()?; - - writer.complete()?; - } - - Ok(()) - } - - fn add_wifi_network( - &self, - exchange: &Exchange<'_>, - req: &AddWifiNetworkRequest<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - // TODO: Check failsafe status - - self.add_network( - exchange, - ::NetworkCredentials::try_from(req)?, - encoder, - ) - } - - fn add_thread_network( - &self, - exchange: &Exchange<'_>, - req: &AddThreadNetworkRequest<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - // TODO: Check failsafe status - - self.add_network( - exchange, - ::NetworkCredentials::try_from(req)?, - encoder, - ) - } - - fn add_network( - &self, - _exchange: &Exchange<'_>, - network: ::NetworkCredentials, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - self.networks.state.lock(|state| { - let mut state = state.borrow_mut(); - - let index = state - .networks - .iter() - .position(|nw| nw.network_id() == network.network_id()); - - let writer = encoder.with_command(ResponseCommands::NetworkConfigResponse as _)?; - - if let Some(index) = index { - // Update - state.networks[index] = network; - - state.changed = true; - self.networks.state_changed.notify(); - - info!( - "Updated network with ID {}", - state.networks[index].network_id() - ); - - writer.set(NetworkConfigResponse { - status: NetworkCommissioningStatus::Success, - debug_text: None, - network_index: Some(index as _), - })?; - } else { - // Add - match state.networks.push(network) { - Ok(_) => { - state.changed = true; - self.networks.state_changed.notify(); - - info!( - "Added network with ID {}", - unwrap!(state.networks.last()).network_id() - ); - - writer.set(NetworkConfigResponse { - status: NetworkCommissioningStatus::Success, - debug_text: None, - network_index: Some((state.networks.len() - 1) as _), - })?; - } - Err(network) => { - warn!( - "Adding network with ID {} failed: too many", - network.network_id() - ); - - writer.set(NetworkConfigResponse { - status: NetworkCommissioningStatus::BoundsExceeded, - debug_text: None, - network_index: None, - })?; - } - } - } - - Ok(()) - }) - } - - fn remove_network( - &self, - _exchange: &Exchange<'_>, - req: &RemoveNetworkRequest<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - // TODO: Check failsafe status - - let network_id: <::NetworkCredentials as NetworkCredentials>::NetworkId = - req.network_id.0.try_into()?; - - self.networks.state.lock(|state| { - let mut state = state.borrow_mut(); - - let index = state - .networks - .iter() - .position(|conf| conf.network_id().as_ref() == req.network_id.0); - - let writer = encoder.with_command(ResponseCommands::NetworkConfigResponse as _)?; - - if let Some(index) = index { - // Found - let network = state.networks.remove(index); - state.changed = true; - self.networks.state_changed.notify(); - - info!("Removed network with ID {}", network.network_id()); - - writer.set(NetworkConfigResponse { - status: NetworkCommissioningStatus::Success, - debug_text: None, - network_index: Some(index as _), - })?; - } else { - warn!("Network with ID {} not found", network_id); - - // Not found - writer.set(NetworkConfigResponse { - status: NetworkCommissioningStatus::NetworkIdNotFound, - debug_text: None, - network_index: None, - })?; - } - - Ok(()) - }) - } - - async fn connect_network( - &self, - _exchange: &Exchange<'_>, - req: &ConnectNetworkRequest<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - // TODO: Check failsafe status - - // Non-concurrent commissioning scenario - // (i.e. only BLE is active, and the device BLE+Wifi/Thread co-exist - // driver is not running, or does not even exist) - - let network_id: <::NetworkCredentials as NetworkCredentials>::NetworkId = - req.network_id.0.try_into()?; - - info!( - "Request to connect to network with ID {} received", - network_id - ); - - let mut controller = self.controller.lock().await; - - let creds = self.networks.state.lock(|state| { - let state = state.borrow(); - - state - .networks - .iter() - .find(|conf| conf.network_id() == network_id) - .cloned() - }); - - controller.connect(unwrap!(creds.as_ref())).await?; // TODO - - self.networks.state.lock(|state| { - let mut state = state.borrow_mut(); - - state.connect_requested = Some(network_id.clone()); - state.changed = true; - self.networks.state_changed.notify(); - }); - - let writer = encoder.with_command(ResponseCommands::ConnectNetworkResponse as _)?; - - // As per spec, return success even though though whether we'll be able to connect to the network - // will become apparent later, once we switch to Wifi/Thread - writer.set(ConnectNetworkResponse { - status: NetworkCommissioningStatus::Success, - debug_text: None, - error_value: 0, - })?; - - // Notify that we have received a connect command - self.networks.network_connect_requested.notify(); - - Ok(()) - } - - fn reorder_network( - &self, - _exchange: &Exchange<'_>, - req: &ReorderNetworkRequest<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - // TODO: Check failsafe status - - let network_id: <::NetworkCredentials as NetworkCredentials>::NetworkId = - req.network_id.0.try_into()?; - - self.networks.state.lock(|state| { - let mut state = state.borrow_mut(); - - let index = state - .networks - .iter() - .position(|conf| conf.network_id().as_ref() == req.network_id.0); - - let writer = encoder.with_command(ResponseCommands::NetworkConfigResponse as _)?; - - if let Some(index) = index { - // Found - - if req.index < state.networks.len() as u8 { - let conf = state.networks.remove(index); - unwrap!(state - .networks - .insert(req.index as usize, conf) - .map_err(|_| ())); - - state.changed = true; - self.networks.state_changed.notify(); - - info!( - "Network with ID {} reordered to index {}", - network_id, req.index - ); - - writer.set(NetworkConfigResponse { - status: NetworkCommissioningStatus::Success, - debug_text: None, - network_index: Some(req.index as _), - })?; - } else { - warn!( - "Reordering network with ID {} to index {} failed: out of range", - network_id, req.index - ); - - writer.set(NetworkConfigResponse { - status: NetworkCommissioningStatus::OutOfRange, - debug_text: None, - network_index: Some(req.index as _), - })?; - } - } else { - warn!("Network with ID {} not found", network_id); - - // Not found - writer.set(NetworkConfigResponse { - status: NetworkCommissioningStatus::NetworkIdNotFound, - debug_text: None, - network_index: None, - })?; - } - - Ok(()) - }) - } -} - -impl AsyncHandler for WirelessNwCommCluster<'_, N, M, T> -where - M: RawMutex, - T: Controller, - ::NetworkCredentials: Clone + for<'b> FromTLV<'b> + ToTLV, - ::ScanResult: ToTLV, -{ - async fn read( - &self, - exchange: &Exchange<'_>, - attr: &AttrDetails<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - WirelessNwCommCluster::read(self, exchange, attr, encoder).await - } - - async fn invoke( - &self, - exchange: &Exchange<'_>, - cmd: &CmdDetails<'_>, - data: &TLVElement<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - WirelessNwCommCluster::invoke(self, exchange, cmd, data, encoder).await - } -} - -// impl ChangeNotifier<()> for WirelessCommCluster { -// fn consume_change(&mut self) -> Option<()> { -// self.data_ver.consume_change(()) -// } -// } diff --git a/src/wireless/diag.rs b/src/wireless/diag.rs deleted file mode 100644 index 6b273dd..0000000 --- a/src/wireless/diag.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod thread; -pub mod wifi; diff --git a/src/wireless/diag/thread.rs b/src/wireless/diag/thread.rs deleted file mode 100644 index 50becfe..0000000 --- a/src/wireless/diag/thread.rs +++ /dev/null @@ -1,129 +0,0 @@ -use embassy_sync::blocking_mutex::raw::RawMutex; -use embassy_sync::mutex::Mutex; - -use rs_matter::data_model::objects::*; -use rs_matter::data_model::sdm::thread_nw_diagnostics::{Attributes, Commands, CLUSTER}; -use rs_matter::error::{Error, ErrorCode}; -use rs_matter::tlv::{TLVElement, TLVWrite}; -use rs_matter::transport::exchange::Exchange; - -use crate::wireless::traits::{Controller, ThreadData}; - -/// A cluster implementing the Matter Thread Diagnostics Cluster. -pub struct ThreadNwDiagCluster -where - M: RawMutex, -{ - data_ver: Dataver, - controller: Mutex, -} - -impl ThreadNwDiagCluster -where - M: RawMutex, - T: Controller, -{ - /// Create a new instance. - pub const fn new(data_ver: Dataver, controller: T) -> Self { - Self { - data_ver, - controller: Mutex::new(controller), - } - } - - /// Read the value of an attribute. - pub async fn read( - &self, - _exchange: &Exchange<'_>, - attr: &AttrDetails<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - if let Some(mut writer) = encoder.with_dataver(self.data_ver.get())? { - if attr.is_system() { - CLUSTER.read(attr.attr_id, writer) - } else { - let mut controller = self.controller.lock().await; - - // TODO: Implement proper statistics - controller.stats().await?; - - if Attributes::try_from(attr.attr_id).is_ok() { - writer.null(&AttrDataWriter::TAG)?; - } else { - Err(ErrorCode::AttributeNotFound)?; - } - - Ok(()) - } - } else { - Ok(()) - } - } - - /// Write the value of an attribute. - pub async fn write( - &self, - _exchange: &Exchange<'_>, - _attr: &AttrDetails<'_>, - data: AttrData<'_>, - ) -> Result<(), Error> { - let _data = data.with_dataver(self.data_ver.get())?; - - self.data_ver.changed(); - - Ok(()) - } - - /// Invoke a command. - pub async fn invoke( - &self, - _exchange: &Exchange<'_>, - cmd: &CmdDetails<'_>, - _data: &TLVElement<'_>, - _encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - match cmd.cmd_id.try_into()? { - Commands::ResetCounts => { - info!("ResetCounts: Not yet supported"); - } - } - - self.data_ver.changed(); - - Ok(()) - } -} - -impl AsyncHandler for ThreadNwDiagCluster -where - M: RawMutex, - T: Controller, -{ - async fn read( - &self, - exchange: &Exchange<'_>, - attr: &AttrDetails<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - ThreadNwDiagCluster::read(self, exchange, attr, encoder).await - } - - async fn write( - &self, - exchange: &Exchange<'_>, - attr: &AttrDetails<'_>, - data: AttrData<'_>, - ) -> Result<(), Error> { - ThreadNwDiagCluster::write(self, exchange, attr, data).await - } - - async fn invoke( - &self, - exchange: &Exchange<'_>, - cmd: &CmdDetails<'_>, - data: &TLVElement<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - ThreadNwDiagCluster::invoke(self, exchange, cmd, data, encoder).await - } -} diff --git a/src/wireless/diag/wifi.rs b/src/wireless/diag/wifi.rs deleted file mode 100644 index 19e79b6..0000000 --- a/src/wireless/diag/wifi.rs +++ /dev/null @@ -1,142 +0,0 @@ -use embassy_sync::blocking_mutex::raw::RawMutex; -use embassy_sync::mutex::Mutex; - -use rs_matter::data_model::objects::*; -use rs_matter::data_model::sdm::wifi_nw_diagnostics::{Attributes, Commands, CLUSTER}; -use rs_matter::error::{Error, ErrorCode}; -use rs_matter::tlv::{TLVElement, TLVTag, TLVWrite}; -use rs_matter::transport::exchange::Exchange; - -use crate::wireless::traits::{Controller, WifiData}; - -/// A cluster implementing the Matter Wifi Diagnostics Cluster. -pub struct WifiNwDiagCluster -where - M: RawMutex, -{ - data_ver: Dataver, - controller: Mutex, -} - -impl WifiNwDiagCluster -where - M: RawMutex, - T: Controller, -{ - /// Create a new instance. - pub const fn new(data_ver: Dataver, controller: T) -> Self { - Self { - data_ver, - controller: Mutex::new(controller), - } - } - - /// Read the value of an attribute. - pub async fn read( - &self, - _exchange: &Exchange<'_>, - attr: &AttrDetails<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - if let Some(mut writer) = encoder.with_dataver(self.data_ver.get())? { - if attr.is_system() { - CLUSTER.read(attr.attr_id, writer) - } else { - let mut controller = self.controller.lock().await; - - let data = controller.stats().await?; - - if let Some(data) = data { - match attr.attr_id.try_into()? { - Attributes::Bssid => writer.str(&TLVTag::Anonymous, &data.bssid), - Attributes::SecurityType(codec) => codec.encode(writer, data.security_type), - Attributes::WifiVersion(codec) => codec.encode(writer, data.wifi_version), - Attributes::ChannelNumber(codec) => { - codec.encode(writer, data.channel_number) - } - Attributes::Rssi(codec) => codec.encode(writer, data.rssi), - _ => Err(ErrorCode::AttributeNotFound.into()), - } - } else { - match attr.attr_id.try_into()? { - Attributes::Bssid - | Attributes::SecurityType(_) - | Attributes::WifiVersion(_) - | Attributes::ChannelNumber(_) - | Attributes::Rssi(_) => writer.null(&TLVTag::Anonymous), - _ => Err(ErrorCode::AttributeNotFound.into()), - } - } - } - } else { - Ok(()) - } - } - - /// Write the value of an attribute. - pub async fn write( - &self, - _exchange: &Exchange<'_>, - _attr: &AttrDetails<'_>, - data: AttrData<'_>, - ) -> Result<(), Error> { - let _data = data.with_dataver(self.data_ver.get())?; - - self.data_ver.changed(); - - Ok(()) - } - - /// Invoke a command. - pub async fn invoke( - &self, - _exchange: &Exchange<'_>, - cmd: &CmdDetails<'_>, - _data: &TLVElement<'_>, - _encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - match cmd.cmd_id.try_into()? { - Commands::ResetCounts => { - info!("ResetCounts: Not yet supported"); - } - } - - self.data_ver.changed(); - - Ok(()) - } -} - -impl AsyncHandler for WifiNwDiagCluster -where - M: RawMutex, - T: Controller, -{ - async fn read( - &self, - exchange: &Exchange<'_>, - attr: &AttrDetails<'_>, - encoder: AttrDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - WifiNwDiagCluster::read(self, exchange, attr, encoder).await - } - - async fn write( - &self, - exchange: &Exchange<'_>, - attr: &AttrDetails<'_>, - data: AttrData<'_>, - ) -> Result<(), Error> { - WifiNwDiagCluster::write(self, exchange, attr, data).await - } - - async fn invoke( - &self, - exchange: &Exchange<'_>, - cmd: &CmdDetails<'_>, - data: &TLVElement<'_>, - encoder: CmdDataEncoder<'_, '_, '_>, - ) -> Result<(), Error> { - WifiNwDiagCluster::invoke(self, exchange, cmd, data, encoder).await - } -} diff --git a/src/wireless/gatt.rs b/src/wireless/gatt.rs new file mode 100644 index 0000000..aa7c833 --- /dev/null +++ b/src/wireless/gatt.rs @@ -0,0 +1,59 @@ +use rs_matter::error::Error; +use rs_matter::transport::network::btp::GattPeripheral; + +use super::PreexistingWireless; + +/// A trait representing a task that needs access to the BLE GATT peripheral to perform its work +/// (e.g. the first part of a non-concurrent commissioning flow) +pub trait GattTask { + /// Run the task with the given GATT peripheral + async fn run

(&mut self, peripheral: P) -> Result<(), Error> + where + P: GattPeripheral; +} + +impl GattTask for &mut T +where + T: GattTask, +{ + async fn run

(&mut self, peripheral: P) -> Result<(), Error> + where + P: GattPeripheral, + { + T::run(*self, peripheral).await + } +} + +/// A trait for running a task within a context where the BLE peripheral is initialized and operable +/// (e.g. the first part of a non-concurrent commissioning workflow) +pub trait Gatt { + /// Setup the radio to operate in wireless (Wifi or Thread) mode + /// and run the given task + async fn run(&mut self, task: T) -> Result<(), Error> + where + T: GattTask; +} + +impl Gatt for &mut T +where + T: Gatt, +{ + async fn run(&mut self, task: A) -> Result<(), Error> + where + A: GattTask, + { + T::run(self, task).await + } +} + +impl Gatt for PreexistingWireless +where + P: GattPeripheral, +{ + async fn run(&mut self, mut task: T) -> Result<(), Error> + where + T: GattTask, + { + task.run(&mut self.gatt).await + } +} diff --git a/src/wireless/mgmt.rs b/src/wireless/mgmt.rs deleted file mode 100644 index 85b5bdd..0000000 --- a/src/wireless/mgmt.rs +++ /dev/null @@ -1,148 +0,0 @@ -//! Wireless manager module. - -use embassy_sync::blocking_mutex::raw::RawMutex; -use embassy_time::{Duration, Timer}; - -use rs_matter::data_model::sdm::nw_commissioning::NetworkCommissioningStatus; -use rs_matter::error::Error; - -use crate::wireless::NetworkCredentials; - -use super::store::{NetworkContext, NetworkStatus}; -use super::traits::WirelessData; -use super::Controller; - -/// A generic Wireless manager. -/// -/// Utilizes the information w.r.t. wireless networks that the -/// Matter stack pushes into the `WirelessContext` struct to connect -/// to one of these networks, in preference order matching the order of the -/// networks there, and the connect request that might be provided by the -/// Matter stack. -/// -/// Also monitors the Wireless connection status and retries the connection -/// with a backoff strategy and in a round-robin fashion with the other -/// networks in case of a failure. -/// -/// The comminication with the wireless device is done through the `Controller` trait. -pub struct WirelessManager(T); - -impl WirelessManager -where - T: Controller, - ::NetworkCredentials: Clone, -{ - /// Create a new wireless manager. - pub const fn new(controller: T) -> Self { - Self(controller) - } - - /// Runs the wireless manager. - /// - /// This function will try to connect to the networks in the `NetworkContext` - /// and will retry the connection in case of a failure. - pub async fn run( - &mut self, - context: &NetworkContext, - ) -> Result<(), Error> - where - M: RawMutex, - { - let mut network_id = None; - - loop { - let creds = context.state.lock(|state| { - let mut state = state.borrow_mut(); - - state.get_next_network(network_id.as_ref()) - }); - - let Some(creds) = creds else { - context.wait_network_activated().await; - continue; - }; - - network_id = Some(creds.network_id().clone()); - - let _ = self.connect_with_retries(&creds, context).await; - } - } - - async fn connect_with_retries( - &mut self, - creds: &::NetworkCredentials, - context: &NetworkContext, - ) -> Result<(), Error> - where - M: RawMutex, - { - loop { - let mut result = Ok(()); - - for delay in [2, 5, 10, 20, 30, 60].iter().copied() { - info!("Connecting to network with ID {}", creds.network_id()); - - result = self.0.connect(creds).await; - - if result.is_ok() { - break; - } else { - warn!( - "Connection to network with ID {} failed: {:?}, retrying in {}s", - creds.network_id(), - result, - delay - ); - } - - Timer::after(Duration::from_secs(delay)).await; - } - - context.state.lock(|state| { - let mut state = state.borrow_mut(); - - if result.is_ok() { - state.connected_once = true; - } - - state.status = Some(NetworkStatus { - network_id: creds.network_id().clone(), - status: if result.is_ok() { - NetworkCommissioningStatus::Success - } else { - NetworkCommissioningStatus::OtherConnectionFailure - }, - value: 0, - }); - }); - - if let Err(e) = result.as_ref() { - error!( - "Failed to connect to network with ID {}: {:?}", - creds.network_id(), - e - ); - - break result; - } else { - info!("Connected to network with ID {}", creds.network_id()); - - self.wait_disconnect().await?; - } - } - } - - async fn wait_disconnect(&mut self) -> Result<(), Error> { - loop { - { - if self.0.connected_network().await?.is_none() { - break Ok(()); - } - } - - let timer = Timer::after(Duration::from_secs(5)); - - timer.await; - } - } -} diff --git a/src/wireless/proxy.rs b/src/wireless/proxy.rs deleted file mode 100644 index 57d5201..0000000 --- a/src/wireless/proxy.rs +++ /dev/null @@ -1,338 +0,0 @@ -//! `Controller` proxy for bridging the wireless clusters with the actual wireless controller. -//! -//! Necessary because the wireless clusters have a different life-cycle from the controller. - -use core::future::Future; -use core::pin::pin; - -use embassy_futures::select::{select, Either}; -use embassy_sync::blocking_mutex::raw::RawMutex; - -use rs_matter::error::{Error, ErrorCode}; -use rs_matter::utils::cell::RefCell; -use rs_matter::utils::init::{init, Init}; -use rs_matter::utils::storage::Vec; -use rs_matter::utils::sync::{IfMutex, Signal}; - -use super::traits::{Controller, NetworkCredentials, WirelessData}; - -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -enum ControllerExchange -where - T: WirelessData, -{ - Empty, - Processing, - Scan(Option<::NetworkId>), - ScanResult(Result, Error>), - Connect(T::NetworkCredentials), - ConnectResult(Result<(), Error>), - ConnectedNetwork, - ConnectedNetworkResult( - Result::NetworkId>, Error>, - ), - Stats, - StatsResult(Result), -} - -impl ControllerExchange -where - T: WirelessData, -{ - fn is_reply(&self) -> bool { - !self.is_command() && !matches!(self, ControllerExchange::Processing) - } - - fn is_command(&self) -> bool { - matches!( - self, - ControllerExchange::Scan(_) - | ControllerExchange::Connect(_) - | ControllerExchange::ConnectedNetwork - | ControllerExchange::Stats - ) - } -} - -/// A proxy for a wireless controller. -/// -/// Solves lifetime/lifecycle issues between the wireless clusters and the controller - -/// in other words, allows the proxy to be created earlier and live longer than the controller. -/// -/// When there is no controller (i.e. the wireless network is disconnected), the proxy will ignore -/// some commands, return default values for others (statistics) and will error out on the remaining. -pub struct ControllerProxy -where - M: RawMutex, - T: WirelessData, -{ - connected: Signal, - pipe: IfMutex>>, -} - -impl Default for ControllerProxy -where - M: RawMutex, - T: WirelessData, -{ - fn default() -> Self { - Self::new() - } -} - -impl ControllerProxy -where - M: RawMutex, - T: WirelessData, -{ - /// Create a new controller proxy. - pub const fn new() -> Self { - Self { - connected: Signal::new(false), - pipe: IfMutex::new(RefCell::new(ControllerExchange::Empty)), - } - } - - pub fn init() -> impl Init { - init!(Self { - connected <- Signal::init(false), - pipe <- IfMutex::init(RefCell::init(ControllerExchange::Empty)), - }) - } - - /// Connect the proxy to an active controller - pub fn process_with<'a, C>( - &'a self, - mut controller: C, - ) -> Result> + 'a, Error> - where - C: Controller + 'a, - T::ScanResult: Clone, - { - // Ensure that the controller is not already connected - // Also, eagerly mark the controller as connected before returning the future, - // so as to avoid other futures faster than ours still using the controller - // in a disconnected state - self.connected.modify(|connected| { - if !*connected { - *connected = true; - (true, Ok::<_, Error>(())) - } else { - (false, Err(ErrorCode::Busy.into())) - } - })?; - - Ok(async move { - let _guard = scopeguard::guard((), |_| { - self.connected.modify(|connected| { - assert!(*connected); - *connected = false; - (true, ()) - }); - }); - - loop { - let pipe = self.pipe.lock_if(|data| data.borrow().is_command()).await; - - let command = - core::mem::replace(&mut *pipe.borrow_mut(), ControllerExchange::Processing); - - match command { - ControllerExchange::Connect(creds) => { - debug!("Connect request: {:?}", creds); - - let result = controller.connect(&creds).await; - - debug!("Sending connect reply: {:?}", result); - - *pipe.borrow_mut() = ControllerExchange::ConnectResult(result); - } - ControllerExchange::ConnectedNetwork => { - debug!("Connected network request"); - - let result = controller.connected_network().await; - - debug!("Sending connected network reply: {:?}", result); - - *pipe.borrow_mut() = ControllerExchange::ConnectedNetworkResult(result); - } - ControllerExchange::Scan(network_id) => { - debug!("Scan request: {:?}", network_id); - - let mut vec = Vec::new(); - - let result = controller - .scan(network_id.as_ref(), |result| { - if let Some(result) = result { - let _ = vec.push(result.clone()); - } - - Ok(()) - }) - .await; - - debug!("Sending scan reply: {:?}", result); - - match result { - Ok(_) => *pipe.borrow_mut() = ControllerExchange::ScanResult(Ok(vec)), - Err(e) => *pipe.borrow_mut() = ControllerExchange::ScanResult(Err(e)), - } - } - ControllerExchange::Stats => { - debug!("Stats request"); - - let result = controller.stats().await; - - debug!("Sending stats reply: {:?}", result); - - *pipe.borrow_mut() = ControllerExchange::StatsResult(result); - } - _ => unreachable!(), - } - } - }) - } - - async fn pipe(&self, predicate: P, modifier: O) -> bool - where - P: Fn(&ControllerExchange) -> bool, - O: FnOnce(&mut ControllerExchange), - { - let mut signal = pin!(self.connected.wait(|connected| (!*connected).then_some(()))); - let mut pipe = pin!(self.pipe.lock_if(|data| predicate(&data.borrow()))); - - let result = select(&mut signal, &mut pipe).await; - - match result { - Either::First(_) => false, - Either::Second(pipe) => { - modifier(&mut *pipe.borrow_mut()); - - true - } - } - } - - async fn command(&self, command: ControllerExchange) -> Option> { - debug!("Sending command: {:?}", command); - - let connected = self - .pipe( - |data| matches!(*data, ControllerExchange::Empty), - |data| *data = command, - ) - .await; - - if !connected { - debug!("Not connected"); - return None; - } - - let mut reply = None; - - let connected = self - .pipe(ControllerExchange::is_reply, |data| { - reply = Some(core::mem::replace(data, ControllerExchange::Empty)); - }) - .await; - - if !connected { - debug!("Not connected"); - return None; - } - - debug!("Got reply: {:?}", reply); - - reply - } -} - -impl Controller for &ControllerProxy -where - M: RawMutex, - T: WirelessData, - T::ScanResult: Clone, - T::Stats: Default, -{ - type Data = T; - - async fn scan( - &mut self, - network_id: Option< - &<::NetworkCredentials as NetworkCredentials>::NetworkId, - >, - mut callback: F, - ) -> Result<(), Error> - where - F: FnMut(Option<&::ScanResult>) -> Result<(), Error>, - { - let reply = self - .command(ControllerExchange::Scan(network_id.cloned())) - .await; - - match reply { - Some(ControllerExchange::ScanResult(result)) => match result { - Ok(result) => { - for r in &result { - callback(Some(r))? - } - - callback(None)?; - - Ok(()) - } - Err(e) => Err(e), - }, - Some(_) => unreachable!(), - None => { - warn!("Scan network not supported"); - - Err(ErrorCode::Busy.into()) - } - } - } - - async fn connect( - &mut self, - creds: &::NetworkCredentials, - ) -> Result<(), Error> { - // TODO: Fire a signal on network connection attempt, if in disconnected mode - // (non-concurrent commissioning) - - let reply = self - .command(ControllerExchange::Connect(creds.clone())) - .await; - - match reply { - Some(ControllerExchange::ConnectResult(result)) => result, - Some(_) => unreachable!(), - None => Ok(()), // Pretend that we had connected successfully (for non-concurrent commissioning) - } - } - - async fn connected_network( - &mut self, - ) -> Result< - Option<<::NetworkCredentials as NetworkCredentials>::NetworkId>, - Error, - > { - let reply = self.command(ControllerExchange::ConnectedNetwork).await; - - match reply { - Some(ControllerExchange::ConnectedNetworkResult(result)) => result, - Some(_) => unreachable!(), - None => Ok(None), - } - } - - async fn stats(&mut self) -> Result<::Stats, Error> { - let reply = self.command(ControllerExchange::Stats).await; - - match reply { - Some(ControllerExchange::StatsResult(result)) => result, - Some(_) => unreachable!(), - None => Ok(Default::default()), - } - } -} diff --git a/src/wireless/store.rs b/src/wireless/store.rs deleted file mode 100644 index e2a8b77..0000000 --- a/src/wireless/store.rs +++ /dev/null @@ -1,322 +0,0 @@ -//! The network state store for the wireless module. - -use core::cell::Cell; - -use embassy_sync::blocking_mutex::raw::RawMutex; - -use rs_matter::data_model::sdm::general_commissioning::ConcurrentConnectionPolicy; -use rs_matter::data_model::sdm::nw_commissioning::NetworkCommissioningStatus; -use rs_matter::error::{Error, ErrorCode}; -use rs_matter::tlv::{FromTLV, TLVElement, TLVTag, ToTLV}; -use rs_matter::utils::cell::RefCell; -use rs_matter::utils::init::{init, Init}; -use rs_matter::utils::storage::WriteBuf; -use rs_matter::utils::sync::blocking::Mutex; -use rs_matter::utils::sync::Notification; - -use crate::persist::{MatterStackKey, NetworkPersist}; -use crate::private::Sealed; - -use super::proxy::ControllerProxy; -use super::traits::WirelessData; -use super::NetworkCredentials; - -pub(crate) struct NetworkStatus { - pub(crate) network_id: I, - pub(crate) status: NetworkCommissioningStatus, - pub(crate) value: i32, -} - -pub(crate) struct NetworkState -where - T: NetworkCredentials, -{ - pub(crate) networks: rs_matter::utils::storage::Vec, - pub(crate) connected_once: bool, - pub(crate) connect_requested: Option, - pub(crate) status: Option>, - pub(crate) changed: bool, -} - -impl NetworkState -where - T: NetworkCredentials + Clone, -{ - const fn new() -> Self { - Self { - networks: rs_matter::utils::storage::Vec::new(), - connected_once: false, - connect_requested: None, - status: None, - changed: false, - } - } - - fn init() -> impl Init { - init!(Self { - networks <- rs_matter::utils::storage::Vec::init(), - connected_once: false, - connect_requested: None, - status: None, - changed: false, - }) - } - - pub(crate) fn get_next_network(&mut self, last_network_id: Option<&T::NetworkId>) -> Option { - // Return the requested network with priority - if let Some(network_id) = self.connect_requested.take() { - let creds = self - .networks - .iter() - .find(|creds| creds.network_id() == network_id); - - if let Some(creds) = creds { - info!( - "Trying with requested network first - ID: {}", - creds.network_id() - ); - - return Some(creds.clone()); - } - } - - if let Some(last_network_id) = last_network_id { - info!( - "Looking for network after the one with ID: {}", - last_network_id - ); - - // Return the network positioned after the last one used - - let mut networks = self.networks.iter(); - - for network in &mut networks { - if network.network_id() == *last_network_id { - break; - } - } - - let creds = networks.next(); - if let Some(creds) = creds { - info!("Trying with next network - ID: {}", creds.network_id()); - - return Some(creds.clone()); - } - } - - // Wrap over - info!("Wrapping over"); - - self.networks.first().cloned() - } - - fn reset(&mut self) { - self.networks.clear(); - self.connected_once = false; - self.connect_requested = None; - self.status = None; - self.changed = false; - } - - fn load(&mut self, data: &[u8]) -> Result<(), Error> - where - T: for<'a> FromTLV<'a>, - { - let root = TLVElement::new(data); - - let iter = root.array()?.iter(); - - self.networks.clear(); - - for creds in iter { - let creds = creds?; - - self.networks - .push_init(T::init_from_tlv(creds), || ErrorCode::NoSpace.into())?; - } - - self.changed = false; - - Ok(()) - } - - fn store(&mut self, buf: &mut [u8]) -> Result - where - T: ToTLV, - { - let mut wb = WriteBuf::new(buf); - - self.networks.to_tlv(&TLVTag::Anonymous, &mut wb)?; - - self.changed = false; - - Ok(wb.get_tail()) - } -} - -/// The `'static` state of the Wifi module. -/// Isolated as a separate struct to allow for `const fn` construction -/// and static allocation. -pub struct NetworkContext -where - M: RawMutex, - T: WirelessData, -{ - pub(crate) state: Mutex>>, - pub(crate) state_changed: Notification, - pub(crate) controller_proxy: ControllerProxy, - pub(crate) concurrent_connection: Mutex>, - pub(crate) network_connect_requested: Notification, -} - -impl NetworkContext -where - M: RawMutex, - T: WirelessData, - T::NetworkCredentials: Clone, -{ - /// Create a new instance. - pub const fn new() -> Self { - Self { - state: Mutex::new(RefCell::new(NetworkState::new())), - state_changed: Notification::new(), - controller_proxy: ControllerProxy::new(), - concurrent_connection: Mutex::new(Cell::new(false)), - network_connect_requested: Notification::new(), - } - } - - /// Return an in-place initializer for the struct. - pub fn init() -> impl Init { - init!(Self { - state <- Mutex::init(RefCell::init(NetworkState::init())), - state_changed: Notification::new(), - controller_proxy <- ControllerProxy::init(), - concurrent_connection <- Mutex::init(Cell::new(false)), - network_connect_requested: Notification::new(), - }) - } - - /// Reset the state. - pub fn reset(&self) { - self.state.lock(|state| state.borrow_mut().reset()); - } - - /// Load the state from a byte slice. - pub fn load(&self, data: &[u8]) -> Result<(), Error> - where - T::NetworkCredentials: for<'a> FromTLV<'a>, - { - self.state.lock(|state| state.borrow_mut().load(data)) - } - - /// Store the state into a byte slice. - pub fn store(&self, buf: &mut [u8]) -> Result - where - T::NetworkCredentials: ToTLV, - { - self.state.lock(|state| state.borrow_mut().store(buf)) - } - - /// Return `true` if the state has changed. - pub fn changed(&self) -> bool { - self.state.lock(|state| state.borrow().changed) - } - - pub fn is_network_activated(&self) -> bool { - self.state - .lock(|state| state.borrow().connect_requested.is_some()) - } - - /// Wait until signalled by the Matter stack that a network connect request is issued during commissioning. - /// - /// Typically, this is a signal that the BLE/BTP transport should be teared down and - /// the Wifi transport should be brought up for the Matter non-concurrent connections' case. - pub async fn wait_network_activated(&self) { - loop { - if self - .state - .lock(|state| state.borrow().connect_requested.is_some()) - { - break; - } - - self.network_connect_requested.wait().await; - } - } - - pub async fn wait_state_changed(&self) { - loop { - if self.state.lock(|state| state.borrow().changed) { - break; - } - - self.state_changed.wait().await; - } - } -} - -impl Default for NetworkContext -where - M: RawMutex, - T: WirelessData, - T::NetworkCredentials: Clone + for<'a> FromTLV<'a> + ToTLV, -{ - fn default() -> Self { - Self::new() - } -} - -impl Sealed for &NetworkContext -where - M: RawMutex, - T: WirelessData, - T::NetworkCredentials: Clone + for<'a> FromTLV<'a> + ToTLV, -{ -} - -impl NetworkPersist for &NetworkContext -where - M: RawMutex, - T: WirelessData, - T::NetworkCredentials: Clone + for<'a> FromTLV<'a> + ToTLV, -{ - const KEY: crate::persist::MatterStackKey = if T::WIFI { - MatterStackKey::WifiNetworks - } else { - MatterStackKey::ThreadNetworks - }; - - fn reset(&self) -> Result<(), Error> { - NetworkContext::reset(self); - - Ok(()) - } - - fn load(&self, data: &[u8]) -> Result<(), Error> { - NetworkContext::load(self, data) - } - - fn store(&self, buf: &mut [u8]) -> Result { - NetworkContext::store(self, buf) - } - - fn changed(&self) -> bool { - NetworkContext::changed(self) - } - - async fn wait_state_changed(&self) { - NetworkContext::wait_state_changed(self).await; - } -} - -impl ConcurrentConnectionPolicy for NetworkContext -where - M: RawMutex, - T: WirelessData, - T::NetworkCredentials: Clone + for<'a> FromTLV<'a> + ToTLV, -{ - fn concurrent_connection_supported(&self) -> bool { - self.concurrent_connection.lock(|state| state.get()) - } -} diff --git a/src/wireless/svc.rs b/src/wireless/svc.rs deleted file mode 100644 index e006db3..0000000 --- a/src/wireless/svc.rs +++ /dev/null @@ -1,211 +0,0 @@ -//! Implementation of `Controller` over types implementing `embedded_svc::wifi::asynch::Wifi` - -use embedded_svc::wifi::{asynch::Wifi, AuthMethod, ClientConfiguration, Configuration}; - -use rs_matter::data_model::sdm::nw_commissioning::{WiFiSecurity, WifiBand}; -use rs_matter::error::{Error, ErrorCode}; -use rs_matter::tlv::OctetsOwned; -use rs_matter::utils::storage::Vec; - -use super::traits::{ - Controller, NetworkCredentials, WifiData, WifiScanResult, WifiSsid, WirelessData, -}; - -/// A wireless controller for the `embedded_svc::wifi::asynch::Wifi` type. -pub struct SvcWifiController(W); - -impl SvcWifiController { - /// Create a new `SvcWifi` instance. - pub const fn new(wifi: W) -> Self { - Self(wifi) - } - - /// Get a reference to the inner `embedded_svc::wifi::asynch::Wifi` instance. - pub fn wifi(&self) -> &W { - &self.0 - } - - /// Get a mutable reference to the inner `embedded_svc::wifi::asynch::Wifi` instance. - pub fn wifi_mut(&mut self) -> &mut W { - &mut self.0 - } -} - -impl SvcWifiController -where - W: Wifi, -{ - fn to_err(e: W::Error) -> Error { - error!("Wifi error: {:?}", debug2format!(e)); - Error::new(ErrorCode::NoNetworkInterface) - } -} - -impl Controller for SvcWifiController -where - T: Wifi, -{ - type Data = WifiData; - - async fn scan(&mut self, network_id: Option<&WifiSsid>, mut callback: F) -> Result<(), Error> - where - F: FnMut(Option<&::ScanResult>) -> Result<(), Error>, - { - info!("Wifi scan request"); - - if !matches!( - self.0.get_configuration().await, - Ok(Configuration::Client(_)) - ) { - info!("Reconfiguring wifi to scan"); - - let _ = self.0.stop().await; - - // Set a fake STA configuration, so that we can scan - self.0 - .set_configuration(&Configuration::Client(ClientConfiguration { - auth_method: AuthMethod::None, - ..Default::default() - })) - .await - .map_err(Self::to_err)?; - } - - if !self.0.is_started().await.map_err(Self::to_err)? { - self.0.start().await.map_err(Self::to_err)?; - info!("Wifi started"); - } - - let (result, len) = self.0.scan_n::<5>().await.map_err(Self::to_err)?; - - info!( - "Wifi scan complete, reporting {} results out of {} total", - result.len(), - len - ); - - for r in &result { - if network_id - .map(|id| r.ssid.as_bytes() == id.0.vec.as_slice()) - .unwrap_or(true) - { - fn to_sec(value: Option) -> WiFiSecurity { - let Some(value) = value else { - // Best guess - return WiFiSecurity::WPA2_PERSONAL; - }; - - match value { - AuthMethod::None => WiFiSecurity::UNENCRYPTED, - AuthMethod::WEP => WiFiSecurity::WEP, - AuthMethod::WPA => WiFiSecurity::WPA_PERSONAL, - AuthMethod::WPA2Personal => WiFiSecurity::WPA2_PERSONAL, - AuthMethod::WPAWPA2Personal => { - WiFiSecurity::WPA_PERSONAL | WiFiSecurity::WPA2_PERSONAL - } - AuthMethod::WPA2WPA3Personal => { - WiFiSecurity::WPA2_PERSONAL | WiFiSecurity::WPA3_PERSONAL - } - AuthMethod::WPA2Enterprise => WiFiSecurity::WPA2_PERSONAL, - _ => WiFiSecurity::WPA2_PERSONAL, - } - } - - let result = WifiScanResult { - security: to_sec(r.auth_method), - ssid: WifiSsid(OctetsOwned { - vec: unwrap!(r.ssid.as_bytes().try_into()), - }), - bssid: OctetsOwned { - vec: unwrap!(Vec::from_slice(&r.bssid)), - }, - channel: r.channel as _, - band: Some(WifiBand::B2G4), - rssi: Some(r.signal_strength), - }; - - info!("Scan result {:?}", result); - - callback(Some(&result))?; - } - } - - callback(None)?; - - info!("Wifi scan complete"); - - Ok(()) - } - - async fn connect( - &mut self, - creds: &::NetworkCredentials, - ) -> Result<(), Error> { - let ssid = core::str::from_utf8(creds.ssid.0.vec.as_slice()).unwrap_or("???"); - - info!("Wifi connect request for SSID {}", ssid); - - for auth_method in [ - AuthMethod::WPA2Personal, - AuthMethod::WPA2WPA3Personal, - AuthMethod::WPAWPA2Personal, - AuthMethod::WEP, - AuthMethod::None, - ] { - if (auth_method == AuthMethod::None) != creds.password.is_empty() { - // Try open wifi networks only if the provided password is empty - continue; - } - - info!("Trying with auth method {:?}", auth_method); - - let _ = self.0.stop().await; - info!("Wifi stopped"); - - self.0 - .set_configuration(&Configuration::Client(ClientConfiguration { - ssid: unwrap!(ssid.try_into()), - auth_method, - password: creds.password.clone(), - ..Default::default() - })) - .await - .map_err(Self::to_err)?; - info!("Wifi configuration updated"); - - self.0.start().await.map_err(Self::to_err)?; - info!("Wifi started"); - - if self.0.connect().await.is_ok() { - info!("Wifi connected"); - return Ok(()); - } - } - - warn!("Failed to connect to wifi {}", ssid); - - Err(ErrorCode::NoNetworkInterface.into()) // TODO - } - - async fn connected_network( - &mut self, - ) -> Result< - Option<<::NetworkCredentials as NetworkCredentials>::NetworkId>, - Error, - > { - let conf = self.0.get_configuration().await.map_err(Self::to_err)?; - - Ok(match conf { - Configuration::Client(ClientConfiguration { ssid, .. }) => { - Some(WifiSsid(OctetsOwned { - vec: unwrap!(ssid.as_bytes().try_into()), - })) - } - _ => None, - }) - } - - async fn stats(&mut self) -> Result<::Stats, Error> { - Ok(None) - } -} diff --git a/src/wireless/thread.rs b/src/wireless/thread.rs new file mode 100644 index 0000000..5703573 --- /dev/null +++ b/src/wireless/thread.rs @@ -0,0 +1,473 @@ +use core::pin::pin; + +use edge_nal::UdpBind; + +use embassy_futures::select::{select, select3, select4}; +use embassy_sync::blocking_mutex::raw::RawMutex; + +use rs_matter::data_model::networks::wireless::{ + self, NetCtlWithStatusImpl, NoopWirelessNetCtl, WirelessMgr, +}; +use rs_matter::data_model::networks::NetChangeNotif; +use rs_matter::data_model::objects::{AsyncMetadata, Endpoint}; +use rs_matter::data_model::root_endpoint::{ + self, with_sys, with_thread, SysHandler, ThreadHandler, +}; +use rs_matter::data_model::sdm::gen_comm::CommPolicy; +use rs_matter::data_model::sdm::gen_diag::GenDiag; +use rs_matter::data_model::sdm::net_comm::{NetCtl, NetCtlStatus, NetworkType}; +use rs_matter::data_model::sdm::thread_diag::ThreadDiag; +use rs_matter::data_model::{objects::AsyncHandler, sdm::gen_diag::NetifDiag}; +use rs_matter::error::Error; +use rs_matter::pairing::DiscoveryCapabilities; +use rs_matter::transport::network::btp::GattPeripheral; +use rs_matter::transport::network::NoNetwork; +use rs_matter::utils::select::Coalesce; + +use crate::network::Embedding; +use crate::persist::{KvBlobStore, SharedKvBlobStore}; +use crate::wireless::{GattTask, MatterStackWirelessTask}; +use crate::UserTask; + +use super::{Gatt, PreexistingWireless, WirelessMatterStack}; + +/// A type alias for a Matter stack running over Thread (and BLE, during commissioning). +pub type ThreadMatterStack<'a, M, E = ()> = WirelessMatterStack<'a, M, wireless::Thread, E>; + +impl WirelessMatterStack<'_, M, wireless::Thread, E> +where + M: RawMutex + Send + Sync + 'static, + E: Embedding + 'static, +{ + /// Run the Matter stack for an already pre-existing wireless network where the BLE and the operational network can co-exist. + /// + /// Parameters: + /// - `netif` - a user-provided `Netif` implementation + /// - `udp` - a user-provided `UdpBind` implementation + /// - `controller` - a user-provided `Controller` implementation + /// - `gatt` - a user-provided `GattPeripheral` implementation + /// - `store` - a `SharedKvBlobStore` implementation wrapping a user-provided `KvBlobStore` + /// - `handler` - a user-provided DM handler implementation + /// - `user` - a user-provided future that will be polled only when the netif interface is up + #[allow(clippy::too_many_arguments)] + pub async fn run_preex( + &'static self, + udp: U, + netif: N, + net_ctl: C, + gatt: G, + store: &SharedKvBlobStore<'_, S>, + handler: H, + user: X, + ) -> Result<(), Error> + where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + ThreadDiag + NetChangeNotif, + G: GattPeripheral, + S: KvBlobStore, + H: AsyncHandler + AsyncMetadata, + X: UserTask, + { + self.run_coex( + PreexistingWireless::new(udp, netif, net_ctl, gatt), + store, + handler, + user, + ) + .await + } + + /// Run the Matter stack for a wireless network where the BLE and the operational network can co-exist. + /// + /// Parameters: + /// - `wireless` - a user-provided `Wireless` implementation + /// - `store` - a `SharedKvBlobStore` implementation wrapping a user-provided `KvBlobStore` + /// - `handler` - a user-provided DM handler implementation + /// - `user` - a user-provided future that will be polled only when the netif interface is up + pub async fn run_coex( + &'static self, + thread: W, + store: &SharedKvBlobStore<'_, S>, + handler: H, + user: U, + ) -> Result<(), Error> + where + W: ThreadCoex, + S: KvBlobStore, + H: AsyncHandler + AsyncMetadata, + U: UserTask, + { + info!("Matter Stack memory: {}B", core::mem::size_of_val(self)); + + let persist = self.create_persist(store); + + // TODO persist.load().await?; + + self.matter().reset_transport()?; + + let mut net_task = pin!(self.run_thread_coex(thread, handler, user)); + let mut persist_task = pin!(self.run_psm(&persist)); + + select(&mut net_task, &mut persist_task).coalesce().await + } + + /// Run the Matter stack for a wireless network where the BLE and the operational network cannot co-exist. + /// + /// Parameters: + /// - `wireless` - a user-provided `Wireless` + `Gatt` implementation + /// - `store` - a `SharedKvBlobStore` implementation wrapping a user-provided `KvBlobStore` + /// - `handler` - a user-provided DM handler implementation + /// - `user` - a user-provided future that will be polled only when the netif interface is up + pub async fn run( + &'static self, + thread: W, + store: &SharedKvBlobStore<'_, S>, + handler: H, + user: U, + ) -> Result<(), Error> + where + W: Thread + Gatt, + S: KvBlobStore, + H: AsyncHandler + AsyncMetadata, + U: UserTask, + { + info!("Matter Stack memory: {}B", core::mem::size_of_val(self)); + + let persist = self.create_persist(store); + + // TODO persist.load().await?; + + self.matter().reset_transport()?; + + let mut net_task = pin!(self.run_thread(thread, handler, user)); + let mut persist_task = pin!(self.run_psm(&persist)); + + select(&mut net_task, &mut persist_task).coalesce().await + } + + async fn run_thread_coex( + &'static self, + mut thread: W, + handler: H, + user: U, + ) -> Result<(), Error> + where + W: ThreadCoex, + H: AsyncHandler + AsyncMetadata, + U: UserTask, + { + thread + .run(MatterStackWirelessTask(self, handler, user)) + .await + } + + async fn run_thread( + &'static self, + mut thread: W, + handler: H, + mut user: U, + ) -> Result<(), Error> + where + W: Thread + Gatt, + H: AsyncHandler + AsyncMetadata, + U: UserTask, + { + loop { + let commissioned = self.is_commissioned().await?; + + if !commissioned { + self.matter() + .enable_basic_commissioning(DiscoveryCapabilities::BLE, 0) + .await?; // TODO + + Gatt::run( + &mut thread, + MatterStackWirelessTask(self, &handler, &mut user), + ) + .await?; + } + + if commissioned { + self.matter().disable_commissioning()?; + } + + Thread::run( + &mut thread, + MatterStackWirelessTask(self, &handler, &mut user), + ) + .await?; + } + } + + /// Return a metadata for the root (Endpoint 0) of the Matter Node + /// configured for BLE+Thread network. + pub const fn root_endpoint() -> Endpoint<'static> { + root_endpoint::root_endpoint(NetworkType::Thread) + } + + /// Return a handler for the root (Endpoint 0) of the Matter Node + /// configured for BLE+Wifi network. + fn root_handler<'a, N, H>( + &'a self, + gen_diag: &'a dyn GenDiag, + netif_diag: &'a dyn NetifDiag, + net_ctl: &'a N, + comm_policy: &'a dyn CommPolicy, + handler: H, + ) -> ThreadHandler<'a, &'a N, SysHandler<'a, H>> + where + N: NetCtl + NetCtlStatus + ThreadDiag, + { + with_thread( + gen_diag, + netif_diag, + net_ctl, + &self.network.networks, + self.matter().rand(), + with_sys(comm_policy, self.matter().rand(), handler), + ) + } +} + +/// A trait representing a task that needs access to the operational wireless interface (Wifi or Thread) +/// (Netif, UDP stack and Wireless controller) to perform its work. +pub trait ThreadTask { + /// Run the task with the given network interface, UDP stack and wireless controller + async fn run(&mut self, udp: U, netif: N, net_ctl: C) -> Result<(), Error> + where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + ThreadDiag + NetChangeNotif; +} + +impl ThreadTask for &mut T +where + T: ThreadTask, +{ + async fn run(&mut self, udp: U, netif: N, net_ctl: C) -> Result<(), Error> + where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + ThreadDiag + NetChangeNotif, + { + T::run(*self, udp, netif, net_ctl).await + } +} + +/// A trait for running a task within a context where the wireless interface is initialized and operable +pub trait Thread { + /// Setup the radio to operate in wireless (Wifi or Thread) mode + /// and run the given task + async fn run(&mut self, task: T) -> Result<(), Error> + where + T: ThreadTask; +} + +impl Thread for &mut T +where + T: Thread, +{ + async fn run(&mut self, task: A) -> Result<(), Error> + where + A: ThreadTask, + { + T::run(self, task).await + } +} + +/// A trait representing a task that needs access to the operational wireless interface (Wifi or Thread) +/// as well as to the commissioning BTP GATT peripheral. +/// +/// Typically, tasks performing the Matter concurrent commissioning workflow will implement this trait. +pub trait ThreadCoexTask { + /// Run the task with the given network interface, UDP stack and wireless controller + async fn run( + &mut self, + udp: U, + netif: N, + net_task: C, + gatt: G, + ) -> Result<(), Error> + where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + ThreadDiag + NetChangeNotif, + G: GattPeripheral; +} + +impl ThreadCoexTask for &mut T +where + T: ThreadCoexTask, +{ + async fn run(&mut self, udp: U, netif: N, net_ctl: C, gatt: G) -> Result<(), Error> + where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + ThreadDiag + NetChangeNotif, + G: GattPeripheral, + { + T::run(*self, udp, netif, net_ctl, gatt).await + } +} + +/// A trait for running a task within a context where both the wireless interface (Thread or Wifi) +/// is initialized and operable, as well as the BLE GATT peripheral is also operable. +/// +/// Typically, tasks performing the Matter concurrent commissioning workflow will ran by implementations +/// of this trait. +pub trait ThreadCoex { + /// Setup the radio to operate in wireless coexist mode (Wifi or Thread + BLE) + /// and run the given task + async fn run(&mut self, task: T) -> Result<(), Error> + where + T: ThreadCoexTask; +} + +impl ThreadCoex for &mut T +where + T: ThreadCoex, +{ + async fn run(&mut self, task: A) -> Result<(), Error> + where + A: ThreadCoexTask, + { + T::run(self, task).await + } +} + +impl Thread for PreexistingWireless +where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + ThreadDiag + NetChangeNotif, +{ + async fn run(&mut self, mut task: T) -> Result<(), Error> + where + T: ThreadTask, + { + task.run(&mut self.udp, &self.netif, &self.net_ctl).await + } +} + +impl ThreadCoex for PreexistingWireless +where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + ThreadDiag + NetChangeNotif, + P: GattPeripheral, +{ + async fn run(&mut self, mut task: T) -> Result<(), Error> + where + T: ThreadCoexTask, + { + task.run(&mut self.udp, &self.netif, &self.net_ctl, &mut self.gatt) + .await + } +} + +impl GattTask for MatterStackWirelessTask<'static, M, wireless::Thread, E, H, X> +where + M: RawMutex + Send + Sync + 'static, + E: Embedding + 'static, + H: AsyncMetadata + AsyncHandler, +{ + async fn run

(&mut self, peripheral: P) -> Result<(), Error> + where + P: GattPeripheral, + { + let net_ctl = NetCtlWithStatusImpl::new( + &self.0.network.net_state, + NoopWirelessNetCtl::new(NetworkType::Thread), + ); + + let mut btp_task = pin!(self.0.run_btp(peripheral)); + + let handler = self.0.root_handler(&(), &(), &net_ctl, &false, &self.1); + let mut handler_task = pin!(self.0.run_handler((&self.1, handler))); + + select(&mut btp_task, &mut handler_task).coalesce().await + } +} + +impl ThreadTask for MatterStackWirelessTask<'static, M, wireless::Thread, E, H, X> +where + M: RawMutex + Send + Sync + 'static, + E: Embedding + 'static, + H: AsyncMetadata + AsyncHandler, + X: UserTask, +{ + async fn run(&mut self, udp: U, netif: N, net_ctl: C) -> Result<(), Error> + where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + ThreadDiag + NetChangeNotif, + { + info!("Thread driver started"); + + let mut buf = self.0.network.creds_buf.lock().await; + + let mut mgr = WirelessMgr::new(&self.0.network.networks, &net_ctl, &mut buf); + + let stack = &mut self.0; + + let mut net_task = pin!(stack.run_oper_net( + &udp, + &netif, + core::future::pending(), + Option::<(NoNetwork, NoNetwork)>::None + )); + let mut mgr_task = pin!(mgr.run()); + + let net_ctl_s = NetCtlWithStatusImpl::new(&self.0.network.net_state, &net_ctl); + + let handler = self + .0 + .root_handler(&(), &netif, &net_ctl_s, &false, &self.1); + let mut handler_task = pin!(self.0.run_handler((&self.1, handler))); + + let mut user_task = pin!(self.2.run(&udp, &netif)); + + select4( + &mut net_task, + &mut mgr_task, + &mut handler_task, + &mut user_task, + ) + .coalesce() + .await + } +} + +impl ThreadCoexTask for MatterStackWirelessTask<'static, M, wireless::Thread, E, H, X> +where + M: RawMutex + Send + Sync + 'static, + E: Embedding + 'static, + H: AsyncMetadata + AsyncHandler, + X: UserTask, +{ + async fn run(&mut self, udp: U, netif: N, net_ctl: C, gatt: G) -> Result<(), Error> + where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + ThreadDiag + NetChangeNotif, + G: GattPeripheral, + { + info!("Thread and BLE drivers started"); + + let stack = &mut self.0; + + let mut net_task = pin!(stack.run_net_coex(&udp, &netif, &net_ctl, gatt)); + + let net_ctl_s = NetCtlWithStatusImpl::new(&self.0.network.net_state, &net_ctl); + + let handler = self.0.root_handler(&(), &netif, &net_ctl_s, &true, &self.1); + let mut handler_task = pin!(self.0.run_handler((&self.1, handler))); + + let mut user_task = pin!(self.2.run(&udp, &netif)); + + select3(&mut net_task, &mut handler_task, &mut user_task) + .coalesce() + .await + } +} diff --git a/src/wireless/traits.rs b/src/wireless/traits.rs deleted file mode 100644 index 3b28a99..0000000 --- a/src/wireless/traits.rs +++ /dev/null @@ -1,832 +0,0 @@ -//! Types and traits for wireless network commissioning and operation - -use core::fmt::{self, Debug, Display}; -use core::marker::PhantomData; - -use edge_nal::UdpBind; -use rs_matter::data_model::sdm::nw_commissioning::{ - AddThreadNetworkRequest, AddWifiNetworkRequest, WiFiSecurity, WifiBand, -}; -use rs_matter::data_model::sdm::wifi_nw_diagnostics::WifiNwDiagData; -use rs_matter::error::{Error, ErrorCode}; -use rs_matter::tlv::{FromTLV, OctetsOwned, ToTLV}; -use rs_matter::transport::network::btp::GattPeripheral; -use rs_matter::utils::storage::Vec; - -use crate::netif::Netif; -use crate::private::Sealed; - -impl Sealed for () {} - -/// A trait representing the credentials of a wireless network (Wifi or Thread). -/// -/// The trait is sealed and has only two implementations: `WifiCredentials` and `ThreadCredentials`. -#[cfg(not(feature = "defmt"))] -pub trait NetworkCredentials: - Sealed - + for<'a> TryFrom<&'a AddWifiNetworkRequest<'a>, Error = Error> - + for<'a> TryFrom<&'a AddThreadNetworkRequest<'a>, Error = Error> - + Clone - + Debug - + 'static -{ - /// The ID of the network (SSID for Wifi and Extended PAN ID for Thread) - type NetworkId: Display - + Clone - + Debug - + PartialEq - + AsRef<[u8]> - + for<'a> TryFrom<&'a [u8], Error = Error> - + 'static; - - /// Return the network ID - fn network_id(&self) -> Self::NetworkId; -} - -/// A trait representing the credentials of a wireless network (Wifi or Thread). -/// -/// The trait is sealed and has only two implementations: `WifiCredentials` and `ThreadCredentials`. -#[cfg(feature = "defmt")] -pub trait NetworkCredentials: - Sealed - + for<'a> TryFrom<&'a AddWifiNetworkRequest<'a>, Error = Error> - + for<'a> TryFrom<&'a AddThreadNetworkRequest<'a>, Error = Error> - + Clone - + Debug - + defmt::Format - + 'static -{ - /// The ID of the network (SSID for Wifi and Extended PAN ID for Thread) - type NetworkId: Display - + Clone - + Debug - + defmt::Format - + PartialEq - + AsRef<[u8]> - + for<'a> TryFrom<&'a [u8], Error = Error> - + 'static; - - /// Return the network ID - fn network_id(&self) -> Self::NetworkId; -} - -/// Concrete Network ID type for Wifi networks -#[derive(Debug, Clone, PartialEq, FromTLV, ToTLV)] -pub struct WifiSsid(pub OctetsOwned<32>); - -impl TryFrom<&[u8]> for WifiSsid { - type Error = Error; - - fn try_from(value: &[u8]) -> Result { - let ssid = Vec::try_from(value).map_err(|_| ErrorCode::NoSpace)?; - - Ok(Self(OctetsOwned { vec: ssid })) - } -} - -impl AsRef<[u8]> for WifiSsid { - fn as_ref(&self) -> &[u8] { - self.0.vec.as_slice() - } -} - -impl Display for WifiSsid { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Ok(str) = core::str::from_utf8(self.0.vec.as_slice()) { - write!(f, "SSID::{}", str) - } else { - write!(f, "SSID::???") - } - } -} - -#[cfg(feature = "defmt")] -impl defmt::Format for WifiSsid { - fn format(&self, f: defmt::Formatter<'_>) { - if let Ok(str) = core::str::from_utf8(self.0.vec.as_slice()) { - defmt::write!(f, "SSID::{}", str) - } else { - defmt::write!(f, "SSID::???") - } - } -} - -/// A struct implementing the `NetworkCredentials` trait for Wifi networks. -#[derive(Debug, Clone, ToTLV, FromTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct WifiCredentials { - pub ssid: WifiSsid, - pub password: heapless::String<64>, -} - -impl TryFrom<&AddWifiNetworkRequest<'_>> for WifiCredentials { - type Error = Error; - - fn try_from(value: &AddWifiNetworkRequest) -> Result { - let ssid = WifiSsid::try_from(value.ssid.0)?; - - let password = - core::str::from_utf8(value.credentials.0).map_err(|_| ErrorCode::InvalidData)?; - let password = heapless::String::try_from(password).map_err(|_| ErrorCode::InvalidData)?; - - Ok(Self { ssid, password }) - } -} - -impl TryFrom<&AddThreadNetworkRequest<'_>> for WifiCredentials { - type Error = Error; - - fn try_from(_value: &AddThreadNetworkRequest) -> Result { - Err(ErrorCode::InvalidCommand.into()) - } -} - -impl Sealed for WifiCredentials {} - -impl NetworkCredentials for WifiCredentials { - type NetworkId = WifiSsid; - - fn network_id(&self) -> Self::NetworkId { - self.ssid.clone() - } -} - -#[derive(Debug, Clone, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct WifiScanResult { - pub security: WiFiSecurity, - pub ssid: WifiSsid, - pub bssid: OctetsOwned<6>, - pub channel: u16, - pub band: Option, - pub rssi: Option, -} - -/// A simple Thread TLV reader -pub struct ThreadTlvRead<'a>(&'a [u8]); - -impl<'a> ThreadTlvRead<'a> { - /// Create a new `ThreadTlvRead` instance with the given TLV data - pub const fn new(tlv: &'a [u8]) -> Self { - Self(tlv) - } - - /// Get the next TLV from the data - /// - /// Returns `Some` with the TLV type and value if there is a TLV available, - /// otherwise returns `None`. - pub fn next_tlv(&mut self) -> Option<(u8, &'a [u8])> { - const LONG_VALUE_ID: u8 = 255; - - // Adopted from here: - // https://github.com/openthread/openthread/blob/main/tools/tcat_ble_client/tlv/tlv.py - - let mut slice = self.0; - - (slice.len() >= 2).then_some(())?; - - let tlv_type = slice[0]; - slice = &slice[1..]; - - let tlv_len_size = if slice[0] == LONG_VALUE_ID { - slice = &slice[1..]; - 3 - } else { - 1 - }; - - (slice.len() >= tlv_len_size).then_some(())?; - - let tlv_len = if tlv_len_size == 1 { - slice[0] as usize - } else { - u32::from_be_bytes([0, slice[0], slice[1], slice[2]]) as usize - }; - - slice = &slice[tlv_len_size..]; - (slice.len() >= tlv_len).then_some(())?; - - let tlv_value = &slice[..tlv_len]; - - slice = &slice[tlv_len..]; - - self.0 = slice; - - Some((tlv_type, tlv_value)) - } -} - -impl<'a> Iterator for ThreadTlvRead<'a> { - type Item = (u8, &'a [u8]); - - fn next(&mut self) -> Option { - self.next_tlv() - } -} - -/// Concrete Network ID type for Thread networks -#[derive(Debug, Clone, PartialEq, FromTLV, ToTLV)] -pub struct ThreadId(pub OctetsOwned<8>); - -impl TryFrom<&[u8]> for ThreadId { - type Error = Error; - - fn try_from(value: &[u8]) -> Result { - if value.len() > 8 { - Err(ErrorCode::InvalidData)?; - } - - let mut octets = OctetsOwned::new(); - unwrap!(octets.vec.extend_from_slice(value)); - - Ok(Self(octets)) - } -} - -impl AsRef<[u8]> for ThreadId { - fn as_ref(&self) -> &[u8] { - &self.0.vec - } -} - -impl Display for ThreadId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "EPAN ID::0x{:08x}", - u64::from_be_bytes(unwrap!(self.0.vec.clone().into_array())) - ) - } -} - -#[cfg(feature = "defmt")] -impl defmt::Format for ThreadId { - fn format(&self, f: defmt::Formatter<'_>) { - defmt::write!( - f, - "EPAN ID::0x{:08x}", - u64::from_be_bytes(unwrap!(self.0.vec.clone().into_array())) - ) - } -} - -/// A struct implementing the `NetworkCredentials` trait for Thread networks. -#[derive(Debug, Clone, ToTLV, FromTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ThreadCredentials { - pub op_dataset: rs_matter::utils::storage::Vec, -} - -impl ThreadCredentials { - const EMPTY_ID: ThreadId = ThreadId(OctetsOwned { vec: Vec::new() }); - - /// Get the Extended PAN ID from the operational dataset - fn ext_pan_id(&self) -> Result { - const EXT_PAN_ID: u8 = 2; - - let ext_pan_id = ThreadTlvRead::new(self.op_dataset.as_slice()) - .find_map(|(tlv_type, tlv_value)| (tlv_type == EXT_PAN_ID).then_some(tlv_value)); - - let Some(ext_pan_id) = ext_pan_id else { - return Ok(Self::EMPTY_ID); - }; - - ThreadId::try_from(ext_pan_id) - } -} - -impl TryFrom<&AddWifiNetworkRequest<'_>> for ThreadCredentials { - type Error = Error; - - fn try_from(_value: &AddWifiNetworkRequest) -> Result { - Err(ErrorCode::InvalidCommand.into()) - } -} - -impl TryFrom<&AddThreadNetworkRequest<'_>> for ThreadCredentials { - type Error = Error; - - fn try_from(value: &AddThreadNetworkRequest) -> Result { - let op_dataset = rs_matter::utils::storage::Vec::try_from(value.op_dataset.0) - .map_err(|_| ErrorCode::NoSpace)?; - - Ok(Self { op_dataset }) - } -} - -impl Sealed for ThreadCredentials {} - -impl NetworkCredentials for ThreadCredentials { - type NetworkId = ThreadId; - - fn network_id(&self) -> Self::NetworkId { - self.ext_pan_id().unwrap_or(Self::EMPTY_ID) - } -} - -#[derive(Debug, Clone, FromTLV, ToTLV)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ThreadScanResult { - pub pan_id: u16, - pub extended_pan_id: u64, - pub network_name: heapless::String<32>, // TODO: Enough - pub channel: u16, - pub version: u8, - pub extended_address: Vec, - pub rssi: i8, - pub lqi: u8, -} - -/// A trait representing a wireless controller for either Wifi or Thread networks. -pub trait Controller { - /// The type of the wireless data (WifiData or ThreadData) - type Data: WirelessData; - - /// Scan for available networks - async fn scan( - &mut self, - network_id: Option< - &<::NetworkCredentials as NetworkCredentials>::NetworkId, - >, - callback: F, - ) -> Result<(), Error> - where - F: FnMut(Option<&::ScanResult>) -> Result<(), Error>; - - /// Connect to a network - async fn connect( - &mut self, - creds: &::NetworkCredentials, - ) -> Result<(), Error>; - - /// Return the network ID of the currently connected network, if any - async fn connected_network( - &mut self, - ) -> Result< - Option<<::NetworkCredentials as NetworkCredentials>::NetworkId>, - Error, - >; - - /// Return the current statistics of the wireless interface - async fn stats(&mut self) -> Result<::Stats, Error>; -} - -impl Controller for &mut T -where - T: Controller, -{ - type Data = T::Data; - - async fn scan( - &mut self, - network_id: Option< - &<::NetworkCredentials as NetworkCredentials>::NetworkId, - >, - callback: F, - ) -> Result<(), Error> - where - F: FnMut(Option<&::ScanResult>) -> Result<(), Error>, - { - T::scan(*self, network_id, callback).await - } - - async fn connect( - &mut self, - creds: &::NetworkCredentials, - ) -> Result<(), Error> { - T::connect(*self, creds).await - } - - async fn connected_network( - &mut self, - ) -> Result< - Option<<::NetworkCredentials as NetworkCredentials>::NetworkId>, - Error, - > { - T::connected_network(*self).await - } - - async fn stats(&mut self) -> Result<::Stats, Error> { - T::stats(*self).await - } -} - -/// A no-op controller. -/// -/// Useful for simulating non-concurrent wireless connectivity in tests and examples. -pub struct DisconnectedController(PhantomData) -where - T: WirelessData, - T::Stats: Default; - -impl Default for DisconnectedController -where - T: WirelessData, - T::Stats: Default, -{ - fn default() -> Self { - Self::new() - } -} - -impl DisconnectedController -where - T: WirelessData, - T::Stats: Default, -{ - /// Create a new disconnected controller - pub const fn new() -> Self { - Self(PhantomData) - } -} - -impl Controller for DisconnectedController -where - T: WirelessData, - T::ScanResult: Clone, - T::Stats: Default, -{ - type Data = T; - - async fn scan( - &mut self, - _network_id: Option< - &<::NetworkCredentials as NetworkCredentials>::NetworkId, - >, - _callback: F, - ) -> Result<(), Error> - where - F: FnMut(Option<&::ScanResult>) -> Result<(), Error>, - { - // Scan requests should not arrive in non-concurrent commissioning workflow - Err(ErrorCode::InvalidCommand.into()) - } - - async fn connect( - &mut self, - _creds: &::NetworkCredentials, - ) -> Result<(), Error> { - // In non-concurrent commissioning workflow, we pretend to connect to the network - // but this is of course not possible to do for real. - Ok(()) - } - - async fn connected_network( - &mut self, - ) -> Result< - Option<<::NetworkCredentials as NetworkCredentials>::NetworkId>, - Error, - > { - Ok(None) - } - - async fn stats(&mut self) -> Result<::Stats, Error> { - Ok(Default::default()) - } -} - -/// A trait representing all DTOs required for wireless network commissioning and operation. -/// -/// The trait is sealed and has only two implementations: `WifiData` and `ThreadData`. -#[cfg(not(feature = "defmt"))] -pub trait WirelessData: Sealed + Debug + 'static { - /// The type of the network credentials (e.g. WifiCredentials or ThreadCredentials) - type NetworkCredentials: NetworkCredentials + Clone; - - /// The type of the scan result (e.g. WiFiInterfaceScanResult or ThreadInterfaceScanResult) - type ScanResult: Debug + Clone; - - /// The type of the statistics (they are different for Wifi vs Thread) - type Stats: Debug + Default; - - // Whether this wireless data is for Wifi networks (`true`) or Thread networks (`false`) - const WIFI: bool; -} - -/// A trait representing all DTOs required for wireless network commissioning and operation. -/// -/// The trait is sealed and has only two implementations: `WifiData` and `ThreadData`. -#[cfg(feature = "defmt")] -pub trait WirelessData: Sealed + Debug + defmt::Format + 'static { - /// The type of the network credentials (e.g. WifiCredentials or ThreadCredentials) - type NetworkCredentials: NetworkCredentials + Clone; - - /// The type of the scan result (e.g. WiFiInterfaceScanResult or ThreadInterfaceScanResult) - type ScanResult: Debug + defmt::Format + Clone; - - /// The type of the statistics (they are different for Wifi vs Thread) - type Stats: Debug + defmt::Format + Default; - - // Whether this wireless data is for Wifi networks (`true`) or Thread networks (`false`) - const WIFI: bool; -} - -/// A struct implementing the `WirelessData` trait for Wifi networks. -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct WifiData; - -impl Sealed for WifiData {} - -impl WirelessData for WifiData { - type NetworkCredentials = WifiCredentials; - type ScanResult = WifiScanResult; - type Stats = Option; - - const WIFI: bool = true; -} - -/// A struct implementing the `WirelessData` trait for Thread networks. -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ThreadData; - -impl Sealed for ThreadData {} - -impl WirelessData for ThreadData { - type NetworkCredentials = ThreadCredentials; - type ScanResult = ThreadScanResult; - type Stats = (); - - const WIFI: bool = false; -} - -/// A trait representing a wireless configuration, i.e. what data (Wifi or Thread). -/// -/// The trait is sealed and has only two implementations: `Wifi` and `Thread`. -pub trait WirelessConfig: Sealed + 'static { - /// The type of the wireless data (WifiData or ThreadData) - type Data: WirelessData; -} - -/// A struct representing a Wifi wireless configuration -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Wifi; - -impl Sealed for Wifi {} - -impl WirelessConfig for Wifi { - type Data = WifiData; -} - -/// A struct representing a Thread wireless configuration -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Thread; - -impl Sealed for Thread {} - -impl WirelessConfig for Thread { - type Data = ThreadData; -} - -/// A trait representing a task that needs access to the BLE GATT peripheral to perform its work -/// (e.g. the first part of a non-concurrent commissioning flow) -pub trait GattTask { - /// Run the task with the given GATT peripheral - async fn run

(&mut self, peripheral: P) -> Result<(), Error> - where - P: GattPeripheral; -} - -impl GattTask for &mut T -where - T: GattTask, -{ - async fn run

(&mut self, peripheral: P) -> Result<(), Error> - where - P: GattPeripheral, - { - T::run(*self, peripheral).await - } -} - -/// A trait for running a task within a context where the BLE peripheral is initialized and operable -/// (e.g. the first part of a non-concurrent commissioning workflow) -pub trait Gatt { - /// Setup the radio to operate in wireless (Wifi or Thread) mode - /// and run the given task - async fn run(&mut self, task: T) -> Result<(), Error> - where - T: GattTask; -} - -impl Gatt for &mut T -where - T: Gatt, -{ - async fn run(&mut self, task: A) -> Result<(), Error> - where - A: GattTask, - { - T::run(self, task).await - } -} - -/// A trait representing a task that needs access to the operational wireless interface (Wifi or Thread) -/// (Netif, UDP stack and Wireless controller) to perform its work. -pub trait WirelessTask { - type Data: WirelessData; - - /// Run the task with the given network interface, UDP stack and wireless controller - async fn run(&mut self, netif: N, udp: U, controller: C) -> Result<(), Error> - where - N: Netif, - U: UdpBind, - C: Controller; -} - -impl WirelessTask for &mut T -where - T: WirelessTask, -{ - type Data = T::Data; - - async fn run(&mut self, netif: N, udp: U, controller: C) -> Result<(), Error> - where - N: Netif, - U: UdpBind, - C: Controller, - { - T::run(*self, netif, udp, controller).await - } -} - -/// A trait for running a task within a context where the wireless interface is initialized and operable -pub trait Wireless { - /// The type of the wireless data (WifiData or ThreadData) - type Data: WirelessData; - - /// Setup the radio to operate in wireless (Wifi or Thread) mode - /// and run the given task - async fn run(&mut self, task: T) -> Result<(), Error> - where - T: WirelessTask; -} - -impl Wireless for &mut T -where - T: Wireless, -{ - type Data = T::Data; - - async fn run(&mut self, task: A) -> Result<(), Error> - where - A: WirelessTask, - { - T::run(self, task).await - } -} - -/// A trait representing a task that needs access to the operational wireless interface (Wifi or Thread) -/// as well as to the commissioning BTP GATT peripheral. -/// -/// Typically, tasks performing the Matter concurrent commissioning workflow will implement this trait. -pub trait WirelessCoexTask { - type Data: WirelessData; - - /// Run the task with the given network interface, UDP stack and wireless controller - async fn run( - &mut self, - netif: N, - udp: U, - controller: C, - gatt: G, - ) -> Result<(), Error> - where - N: Netif, - U: UdpBind, - C: Controller, - G: GattPeripheral; -} - -impl WirelessCoexTask for &mut T -where - T: WirelessCoexTask, -{ - type Data = T::Data; - - async fn run( - &mut self, - netif: N, - udp: U, - controller: C, - gatt: G, - ) -> Result<(), Error> - where - N: Netif, - U: UdpBind, - C: Controller, - G: GattPeripheral, - { - T::run(*self, netif, udp, controller, gatt).await - } -} - -/// A trait for running a task within a context where both the wireless interface (Thread or Wifi) -/// is initialized and operable, as well as the BLE GATT peripheral is also operable. -/// -/// Typically, tasks performing the Matter concurrent commissioning workflow will ran by implementations -/// of this trait. -pub trait WirelessCoex { - /// The type of the wireless data (WifiData or ThreadData) - type Data: WirelessData; - - /// Setup the radio to operate in wireless coexist mode (Wifi or Thread + BLE) - /// and run the given task - async fn run(&mut self, task: T) -> Result<(), Error> - where - T: WirelessCoexTask; -} - -impl WirelessCoex for &mut T -where - T: WirelessCoex, -{ - type Data = T::Data; - - async fn run(&mut self, task: A) -> Result<(), Error> - where - A: WirelessCoexTask, - { - T::run(self, task).await - } -} - -/// A utility type for running a wireless task with a pre-existing wireless interface -/// rather than bringing up / tearing down the wireless interface for the task. -/// -/// This utility can only be used with hardware that implements wireless coexist mode -/// (i.e. the Thread/Wifi interface as well as the BLE GATT peripheral are available at the same time). -pub struct PreexistingWireless { - netif: N, - udp: U, - controller: C, - gatt: G, -} - -impl PreexistingWireless { - /// Create a new `PreexistingWireless` instance with the given network interface, UDP stack, - /// wireless controller and GATT peripheral. - pub const fn new(netif: N, udp: U, controller: C, gatt: G) -> Self { - Self { - netif, - udp, - controller, - gatt, - } - } -} - -impl WirelessCoex for PreexistingWireless -where - N: Netif, - U: UdpBind, - C: Controller, - P: GattPeripheral, -{ - type Data = C::Data; - - async fn run(&mut self, mut task: T) -> Result<(), Error> - where - T: WirelessCoexTask, - { - task.run( - &mut self.netif, - &mut self.udp, - &mut self.controller, - &mut self.gatt, - ) - .await - } -} - -impl Wireless for PreexistingWireless -where - N: Netif, - U: UdpBind, - C: Controller, -{ - type Data = C::Data; - - async fn run(&mut self, mut task: T) -> Result<(), Error> - where - T: WirelessTask, - { - task.run(&mut self.netif, &mut self.udp, &mut self.controller) - .await - } -} - -impl Gatt for PreexistingWireless -where - P: GattPeripheral, -{ - async fn run(&mut self, mut task: T) -> Result<(), Error> - where - T: GattTask, - { - task.run(&mut self.gatt).await - } -} diff --git a/src/wireless/wifi.rs b/src/wireless/wifi.rs new file mode 100644 index 0000000..52099c3 --- /dev/null +++ b/src/wireless/wifi.rs @@ -0,0 +1,463 @@ +use core::pin::pin; + +use edge_nal::UdpBind; + +use embassy_futures::select::{select, select3, select4}; +use embassy_sync::blocking_mutex::raw::RawMutex; + +use rs_matter::data_model::networks::wireless::{ + self, NetCtlWithStatusImpl, NoopWirelessNetCtl, WirelessMgr, +}; +use rs_matter::data_model::networks::NetChangeNotif; +use rs_matter::data_model::objects::{AsyncMetadata, Endpoint}; +use rs_matter::data_model::root_endpoint::{self, with_sys, with_wifi, SysHandler, WifiHandler}; +use rs_matter::data_model::sdm::gen_comm::CommPolicy; +use rs_matter::data_model::sdm::gen_diag::GenDiag; +use rs_matter::data_model::sdm::net_comm::{NetCtl, NetCtlStatus, NetworkType}; +use rs_matter::data_model::sdm::wifi_diag::WifiDiag; +use rs_matter::data_model::{objects::AsyncHandler, sdm::gen_diag::NetifDiag}; +use rs_matter::error::Error; +use rs_matter::pairing::DiscoveryCapabilities; +use rs_matter::transport::network::btp::GattPeripheral; +use rs_matter::transport::network::NoNetwork; +use rs_matter::utils::select::Coalesce; + +use crate::network::Embedding; +use crate::persist::{KvBlobStore, SharedKvBlobStore}; +use crate::wireless::MatterStackWirelessTask; +use crate::UserTask; + +use super::{Gatt, GattTask, PreexistingWireless, WirelessMatterStack}; + +/// A type alias for a Matter stack running over Wifi (and BLE, during commissioning). +pub type WifiMatterStack<'a, M, E = ()> = WirelessMatterStack<'a, M, wireless::Wifi, E>; + +impl WirelessMatterStack<'_, M, wireless::Wifi, E> +where + M: RawMutex + Send + Sync + 'static, + E: Embedding + 'static, +{ + /// Run the Matter stack for an already pre-existing wireless network where the BLE and the operational network can co-exist. + /// + /// Parameters: + /// - `netif` - a user-provided `Netif` implementation + /// - `udp` - a user-provided `UdpBind` implementation + /// - `controller` - a user-provided `Controller` implementation + /// - `gatt` - a user-provided `GattPeripheral` implementation + /// - `store` - a `SharedKvBlobStore` implementation wrapping a user-provided `KvBlobStore` + /// - `handler` - a user-provided DM handler implementation + /// - `user` - a user-provided future that will be polled only when the netif interface is up + #[allow(clippy::too_many_arguments)] + pub async fn run_preex( + &'static self, + udp: U, + netif: N, + net_ctl: C, + gatt: G, + store: &SharedKvBlobStore<'_, S>, + handler: H, + user: X, + ) -> Result<(), Error> + where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + WifiDiag + NetChangeNotif, + G: GattPeripheral, + S: KvBlobStore, + H: AsyncHandler + AsyncMetadata, + X: UserTask, + { + self.run_coex( + PreexistingWireless::new(udp, netif, net_ctl, gatt), + store, + handler, + user, + ) + .await + } + + /// Run the Matter stack for a wireless network where the BLE and the operational network can co-exist. + /// + /// Parameters: + /// - `wireless` - a user-provided `Wireless` implementation + /// - `store` - a `SharedKvBlobStore` implementation wrapping a user-provided `KvBlobStore` + /// - `handler` - a user-provided DM handler implementation + /// - `user` - a user-provided future that will be polled only when the netif interface is up + pub async fn run_coex( + &'static self, + wifi: W, + store: &SharedKvBlobStore<'_, S>, + handler: H, + user: U, + ) -> Result<(), Error> + where + W: WifiCoex, + S: KvBlobStore, + H: AsyncHandler + AsyncMetadata, + U: UserTask, + { + info!("Matter Stack memory: {}B", core::mem::size_of_val(self)); + + let persist = self.create_persist(store); + + // TODO persist.load().await?; + + self.matter().reset_transport()?; + + let mut net_task = pin!(self.run_wifi_coex(wifi, handler, user)); + let mut persist_task = pin!(self.run_psm(&persist)); + + select(&mut net_task, &mut persist_task).coalesce().await + } + + /// Run the Matter stack for a wireless network where the BLE and the operational network cannot co-exist. + /// + /// Parameters: + /// - `wireless` - a user-provided `Wireless` + `Gatt` implementation + /// - `store` - a `SharedKvBlobStore` implementation wrapping a user-provided `KvBlobStore` + /// - `handler` - a user-provided DM handler implementation + /// - `user` - a user-provided future that will be polled only when the netif interface is up + pub async fn run( + &'static self, + wifi: W, + store: &SharedKvBlobStore<'_, S>, + handler: H, + user: U, + ) -> Result<(), Error> + where + W: Wifi + Gatt, + S: KvBlobStore, + H: AsyncHandler + AsyncMetadata, + U: UserTask, + { + info!("Matter Stack memory: {}B", core::mem::size_of_val(self)); + + let persist = self.create_persist(store); + + // TODO persist.load().await?; + + self.matter().reset_transport()?; + + let mut net_task = pin!(self.run_wifi(wifi, handler, user)); + let mut persist_task = pin!(self.run_psm(&persist)); + + select(&mut net_task, &mut persist_task).coalesce().await + } + + async fn run_wifi_coex( + &'static self, + mut wifi: W, + handler: H, + user: U, + ) -> Result<(), Error> + where + W: WifiCoex, + H: AsyncHandler + AsyncMetadata, + U: UserTask, + { + wifi.run(MatterStackWirelessTask(self, handler, user)).await + } + + async fn run_wifi( + &'static self, + mut wifi: W, + handler: H, + mut user: U, + ) -> Result<(), Error> + where + W: Wifi + Gatt, + H: AsyncHandler + AsyncMetadata, + U: UserTask, + { + loop { + let commissioned = self.is_commissioned().await?; + + if !commissioned { + self.matter() + .enable_basic_commissioning(DiscoveryCapabilities::BLE, 0) + .await?; // TODO + + Gatt::run( + &mut wifi, + MatterStackWirelessTask(self, &handler, &mut user), + ) + .await?; + } + + if commissioned { + self.matter().disable_commissioning()?; + } + + Wifi::run( + &mut wifi, + MatterStackWirelessTask(self, &handler, &mut user), + ) + .await?; + } + } + + /// Return a metadata for the root (Endpoint 0) of the Matter Node + /// configured for BLE+Wifi network. + pub const fn root_endpoint() -> Endpoint<'static> { + root_endpoint::root_endpoint(NetworkType::Wifi) + } + + /// Return a handler for the root (Endpoint 0) of the Matter Node + /// configured for BLE+Wifi network. + fn root_handler<'a, C, H>( + &'a self, + gen_diag: &'a dyn GenDiag, + netif_diag: &'a dyn NetifDiag, + net_ctl: &'a C, + comm_policy: &'a dyn CommPolicy, + handler: H, + ) -> WifiHandler<'a, &'a C, SysHandler<'a, H>> + where + C: NetCtl + NetCtlStatus + WifiDiag, + { + with_wifi( + gen_diag, + netif_diag, + net_ctl, + &self.network.networks, + self.matter().rand(), + with_sys(comm_policy, self.matter().rand(), handler), + ) + } +} + +/// A trait representing a task that needs access to the operational wireless interface (Wifi or Thread) +/// (Netif, UDP stack and Wireless controller) to perform its work. +pub trait WifiTask { + /// Run the task with the given network interface, UDP stack and wireless controller + async fn run(&mut self, udp: U, netif: N, net_ctl: C) -> Result<(), Error> + where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + WifiDiag + NetChangeNotif; +} + +impl WifiTask for &mut T +where + T: WifiTask, +{ + async fn run(&mut self, udp: U, netif: N, net_ctl: C) -> Result<(), Error> + where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + WifiDiag + NetChangeNotif, + { + T::run(*self, udp, netif, net_ctl).await + } +} + +/// A trait for running a task within a context where the wireless interface is initialized and operable +pub trait Wifi { + /// Setup the radio to operate in wireless (Wifi or Thread) mode + /// and run the given task + async fn run(&mut self, task: T) -> Result<(), Error> + where + T: WifiTask; +} + +impl Wifi for &mut T +where + T: Wifi, +{ + async fn run(&mut self, task: A) -> Result<(), Error> + where + A: WifiTask, + { + T::run(self, task).await + } +} + +/// A trait representing a task that needs access to the operational wireless interface (Wifi or Thread) +/// as well as to the commissioning BTP GATT peripheral. +/// +/// Typically, tasks performing the Matter concurrent commissioning workflow will implement this trait. +pub trait WifiCoexTask { + /// Run the task with the given network interface, UDP stack and wireless controller + async fn run(&mut self, udp: U, netif: N, net_ctl: C, gatt: G) -> Result<(), Error> + where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + WifiDiag + NetChangeNotif, + G: GattPeripheral; +} + +impl WifiCoexTask for &mut T +where + T: WifiCoexTask, +{ + async fn run(&mut self, udp: U, netif: N, net_ctl: C, gatt: G) -> Result<(), Error> + where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + WifiDiag + NetChangeNotif, + G: GattPeripheral, + { + T::run(*self, udp, netif, net_ctl, gatt).await + } +} + +/// A trait for running a task within a context where both the wireless interface (Thread or Wifi) +/// is initialized and operable, as well as the BLE GATT peripheral is also operable. +/// +/// Typically, tasks performing the Matter concurrent commissioning workflow will ran by implementations +/// of this trait. +pub trait WifiCoex { + /// Setup the radio to operate in wireless coexist mode (Wifi or Thread + BLE) + /// and run the given task + async fn run(&mut self, task: T) -> Result<(), Error> + where + T: WifiCoexTask; +} + +impl WifiCoex for &mut T +where + T: WifiCoex, +{ + async fn run(&mut self, task: A) -> Result<(), Error> + where + A: WifiCoexTask, + { + T::run(self, task).await + } +} + +impl Wifi for PreexistingWireless +where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + WifiDiag + NetChangeNotif, +{ + async fn run(&mut self, mut task: T) -> Result<(), Error> + where + T: WifiTask, + { + task.run(&mut self.udp, &self.netif, &self.net_ctl).await + } +} + +impl WifiCoex for PreexistingWireless +where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + WifiDiag + NetChangeNotif, + P: GattPeripheral, +{ + async fn run(&mut self, mut task: T) -> Result<(), Error> + where + T: WifiCoexTask, + { + task.run(&mut self.udp, &self.netif, &self.net_ctl, &mut self.gatt) + .await + } +} + +impl GattTask for MatterStackWirelessTask<'static, M, wireless::Wifi, E, H, U> +where + M: RawMutex + Send + Sync + 'static, + E: Embedding + 'static, + H: AsyncMetadata + AsyncHandler, +{ + async fn run

(&mut self, peripheral: P) -> Result<(), Error> + where + P: GattPeripheral, + { + let net_ctl = NetCtlWithStatusImpl::new( + &self.0.network.net_state, + NoopWirelessNetCtl::new(NetworkType::Wifi), + ); + + let mut btp_task = pin!(self.0.run_btp(peripheral)); + + let handler = self.0.root_handler(&(), &(), &net_ctl, &false, &self.1); + let mut handler_task = pin!(self.0.run_handler((&self.1, handler))); + + select(&mut btp_task, &mut handler_task).coalesce().await + } +} + +impl WifiTask for MatterStackWirelessTask<'static, M, wireless::Wifi, E, H, X> +where + M: RawMutex + Send + Sync + 'static, + E: Embedding + 'static, + H: AsyncMetadata + AsyncHandler, + X: UserTask, +{ + async fn run(&mut self, udp: U, netif: N, net_ctl: C) -> Result<(), Error> + where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + WifiDiag + NetChangeNotif, + { + info!("Wifi driver started"); + + let mut buf = self.0.network.creds_buf.lock().await; + + let mut mgr = WirelessMgr::new(&self.0.network.networks, &net_ctl, &mut buf); + + let stack = &mut self.0; + + let mut net_task = pin!(stack.run_oper_net( + &udp, + &netif, + core::future::pending(), + Option::<(NoNetwork, NoNetwork)>::None + )); + let mut mgr_task = pin!(mgr.run()); + + let net_ctl_s = NetCtlWithStatusImpl::new(&self.0.network.net_state, &net_ctl); + + let handler = self + .0 + .root_handler(&(), &netif, &net_ctl_s, &false, &self.1); + let mut handler_task = pin!(self.0.run_handler((&self.1, handler))); + + let mut user_task = pin!(self.2.run(&udp, &netif)); + + select4( + &mut net_task, + &mut mgr_task, + &mut handler_task, + &mut user_task, + ) + .coalesce() + .await + } +} + +impl WifiCoexTask for MatterStackWirelessTask<'static, M, wireless::Wifi, E, H, X> +where + M: RawMutex + Send + Sync + 'static, + E: Embedding + 'static, + H: AsyncMetadata + AsyncHandler, + X: UserTask, +{ + async fn run(&mut self, udp: U, netif: N, net_ctl: C, gatt: G) -> Result<(), Error> + where + U: UdpBind, + N: NetifDiag + NetChangeNotif, + C: NetCtl + WifiDiag + NetChangeNotif, + G: GattPeripheral, + { + info!("Wifi and BLE drivers started"); + + let stack = &mut self.0; + + let mut net_task = pin!(stack.run_net_coex(&udp, &netif, &net_ctl, gatt)); + + let net_ctl_s = NetCtlWithStatusImpl::new(&self.0.network.net_state, &net_ctl); + + let handler = self.0.root_handler(&(), &netif, &net_ctl_s, &true, &self.1); + let mut handler_task = pin!(self.0.run_handler((&self.1, handler))); + + let mut user_task = pin!(self.2.run(&udp, &netif)); + + select3(&mut net_task, &mut handler_task, &mut user_task) + .coalesce() + .await + } +}