From 3d0b88b9c90e77888dd0f77b13bb8ee19fd448a2 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Thu, 2 May 2024 13:12:59 +0000 Subject: [PATCH] Split stack.rs, remove code duplication --- examples/light_eth.rs | 209 ++++++++++++++++ src/netif.rs | 26 ++ src/stack.rs | 545 +++++++++++------------------------------- src/stack/eth.rs | 94 ++++++++ src/stack/wifible.rs | 227 ++++++++++++++++++ 5 files changed, 698 insertions(+), 403 deletions(-) create mode 100644 examples/light_eth.rs create mode 100644 src/stack/eth.rs create mode 100644 src/stack/wifible.rs diff --git a/examples/light_eth.rs b/examples/light_eth.rs new file mode 100644 index 0000000..2bd3008 --- /dev/null +++ b/examples/light_eth.rs @@ -0,0 +1,209 @@ +//! An example utilizing the `MatterStack` struct. +//! As the name suggests, this Matter stack assembly uses Ethernet as the main transport, as well as for commissioning. +//! +//! Notice thart we actually don't use Ethernet for real, as ESP32s don't have Ethernet ports out of the box. +//! Instead, we utilize Wifi, which - from the POV of Matter - is indistinguishable from Ethernet as long as the Matter +//! stack is not concerned with connecting to the Wifi network, managing its credentials etc. and can assume it "pre-exists". +//! +//! The example implements a fictitious Light device (an on-off cluster). + +use core::borrow::Borrow; +use core::pin::pin; + +use embassy_futures::select::select; +use embassy_time::{Duration, Timer}; + +use embedded_svc::wifi; +use esp_idf_matter::{init_async_io, Error, Eth, MatterStack, WifiBle}; + +use esp_idf_svc::eventloop::EspSystemEventLoop; +use esp_idf_svc::hal::peripherals::Peripherals; +use esp_idf_svc::hal::task::block_on; +use esp_idf_svc::log::EspLogger; +use esp_idf_svc::nvs::EspDefaultNvsPartition; +use esp_idf_svc::timer::EspTaskTimerService; + +use esp_idf_svc::wifi::{AsyncWifi, EspWifi}; +use log::{error, info}; + +use rs_matter::data_model::cluster_basic_information::BasicInfoConfig; +use rs_matter::data_model::cluster_on_off; +use rs_matter::data_model::device_types::DEV_TYPE_ON_OFF_LIGHT; +use rs_matter::data_model::objects::{Endpoint, HandlerCompat, Node}; +use rs_matter::data_model::system_model::descriptor; +use rs_matter::secure_channel::spake2p::VerifierData; +use rs_matter::utils::select::Coalesce; +use rs_matter::CommissioningData; + +use static_cell::ConstStaticCell; + +#[path = "dev_att/dev_att.rs"] +mod dev_att; + +const WIFI_SSID: &str = env!("WIFI_SSID"); +const WIFI_PASS: &str = env!("WIFI_PASS"); + +fn main() -> Result<(), Error> { + EspLogger::initialize_default(); + + info!("Starting..."); + + // Run in a higher-prio thread to avoid issues with `async-io` getting + // confused by the low priority of the ESP IDF main task + // Also allocate a very large stack (for now) as `rs-matter` futures do occupy quite some space + let thread = std::thread::Builder::new() + .stack_size(65 * 1024) + .spawn(|| { + // Eagerly initialize `async-io` to minimize the risk of stack blowups later on + init_async_io()?; + + run() + }) + .unwrap(); + + thread.join().unwrap() +} + +#[inline(never)] +#[cold] +fn run() -> Result<(), Error> { + let result = block_on(matter()); + + if let Err(e) = &result { + error!("Matter aborted execution with error: {:?}", e); + } + { + info!("Matter finished execution successfully"); + } + + result +} + +async fn matter() -> Result<(), Error> { + let sysloop = EspSystemEventLoop::take()?; + let nvs = EspDefaultNvsPartition::take()?; + + // Configure and start the Wifi first + let mut wifi = Box::new(AsyncWifi::wrap( + EspWifi::new( + Peripherals::take()?.modem, + sysloop.clone(), + Some(nvs.clone()), + )?, + sysloop.clone(), + EspTaskTimerService::new()?, + )?); + wifi.set_configuration(&wifi::Configuration::Client(wifi::ClientConfiguration { + ssid: WIFI_SSID.try_into().unwrap(), + password: WIFI_PASS.try_into().unwrap(), + ..Default::default() + }))?; + wifi.start().await?; + wifi.connect().await?; + + // Take the Matter stack (can be done only once), + // as we'll run it in this thread + let stack = MATTER_STACK.take(); + + // Our "light" on-off cluster. + // Can be anything implementing `rs_matter::data_model::AsyncHandler` + let on_off = cluster_on_off::OnOffCluster::new(*stack.matter().borrow()); + + // 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 + .chain( + LIGHT_ENDPOINT_ID, + cluster_on_off::ID, + HandlerCompat(&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(*stack.matter().borrow())), + ); + + // Run the Matter stack with our handler + // Using `pin!` is completely optional, but saves some memory due to `rustc` + // not being very intelligent w.r.t. stack usage in async functions + let mut matter = pin!(stack.run( + // The Matter stack needs (a clone of) the system event loop + sysloop, + // The Matter stack needs (a clone of) the default ESP IDF NVS partition + nvs, + // For the Ethernet case, the Matter stack needs access to the network + // interface (i.e. something that implements the `NetifAccess` trait) + // so that it can track when the interface goes up/down. `EspNetif` obviously + // does implement this trait + wifi.wifi().sta_netif(), + // Hard-coded for demo purposes + CommissioningData { + verifier: VerifierData::new_with_pw(123456, *stack.matter().borrow()), + discriminator: 250, + }, + // Our `AsyncHandler` + `AsyncMetadata` impl + (NODE, handler), + )); + + // Just for demoing purposes: + // + // Run a sample loop that simulates state changes triggered by the HAL + // Changes will be properly communicated to the Matter controllers + // (i.e. Google Home, Alexa) and other Matter devices thanks to subscriptions + let mut device = pin!(async { + loop { + // Simulate user toggling the light with a physical switch every 5 seconds + Timer::after(Duration::from_secs(5)).await; + + // Toggle + on_off.set(!on_off.get()); + + // Let the Matter stack know that we have changed + // the state of our Light device + stack.notify_changed(); + + info!("Light toggled"); + } + }); + + // Schedule the Matter run & the device loop together + select(&mut matter, &mut device).coalesce().await +} + +/// The Matter stack is allocated statically to avoid +/// program stack blowups. +static MATTER_STACK: ConstStaticCell> = ConstStaticCell::new(MatterStack::new( + &BasicInfoConfig { + vid: 0xFFF1, + pid: 0x8000, + hw_ver: 2, + sw_ver: 1, + sw_ver_str: "1", + serial_no: "aabbccdd", + device_name: "MyLight", + product_name: "ACME Light", + vendor_name: "ACME", + }, + &dev_att::HardCodedDevAtt::new(), +)); + +/// Endpoint 0 (the root endpoint) always runs +/// the hidden Matter system clusters, so we pick ID=1 +const LIGHT_ENDPOINT_ID: u16 = 1; + +/// The Matter Light device Node +const NODE: Node<'static> = Node { + id: 0, + endpoints: &[ + MatterStack::::root_metadata(), + Endpoint { + id: LIGHT_ENDPOINT_ID, + device_type: DEV_TYPE_ON_OFF_LIGHT, + clusters: &[descriptor::CLUSTER, cluster_on_off::CLUSTER], + }, + ], +}; diff --git a/src/netif.rs b/src/netif.rs index 370961a..576ded0 100644 --- a/src/netif.rs +++ b/src/netif.rs @@ -6,6 +6,8 @@ use core::pin::pin; use alloc::sync::Arc; use embassy_futures::select::select; +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::mutex::Mutex; use embassy_time::{Duration, Timer}; use esp_idf_svc::eventloop::EspSystemEventLoop; @@ -74,6 +76,30 @@ where } } +impl NetifAccess for EspNetif { + async fn with_netif(&self, f: F) -> R + where + F: FnOnce(&EspNetif) -> R, + { + f(self) + } +} + +impl NetifAccess for Mutex +where + M: RawMutex, + T: NetifAccess, +{ + async fn with_netif(&self, f: F) -> R + where + F: FnOnce(&EspNetif) -> R, + { + let netif = self.lock().await; + + netif.with_netif(f).await + } +} + pub fn get_ips(netif: &EspNetif) -> Result<(Ipv4Addr, Ipv6Addr, u32), Error> { let ip_info = netif.get_ip_info()?; diff --git a/src/stack.rs b/src/stack.rs index 55b1b7a..58e6853 100644 --- a/src/stack.rs +++ b/src/stack.rs @@ -1,3 +1,5 @@ +use core::borrow::Borrow; +use core::cell::RefCell; use core::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV6}; use core::pin::pin; use core::time::Duration; @@ -11,31 +13,44 @@ use esp_idf_svc::nvs::{EspNvs, EspNvsPartition, NvsPartitionId}; use log::info; -use rs_matter::data_model::cluster_basic_information::BasicInfoConfig; +use rs_matter::acl::AclMgr; +use rs_matter::data_model::cluster_basic_information::{self, BasicInfoCluster, BasicInfoConfig}; use rs_matter::data_model::core::IMBuffer; -use rs_matter::data_model::objects::{AsyncHandler, AsyncMetadata, Endpoint, HandlerCompat}; -use rs_matter::data_model::root_endpoint; +use rs_matter::data_model::objects::{AsyncHandler, AsyncMetadata, EmptyHandler, HandlerCompat}; +use rs_matter::data_model::sdm::admin_commissioning::AdminCommCluster; use rs_matter::data_model::sdm::dev_att::DevAttDataFetcher; +use rs_matter::data_model::sdm::failsafe::FailSafe; +use rs_matter::data_model::sdm::general_commissioning::GenCommCluster; +use rs_matter::data_model::sdm::general_diagnostics::GenDiagCluster; +use rs_matter::data_model::sdm::group_key_management::GrpKeyMgmtCluster; +use rs_matter::data_model::sdm::noc::NocCluster; +use rs_matter::data_model::sdm::{ + admin_commissioning, general_commissioning, general_diagnostics, group_key_management, noc, + nw_commissioning, +}; use rs_matter::data_model::subscriptions::Subscriptions; +use rs_matter::data_model::system_model::access_control::AccessControlCluster; +use rs_matter::data_model::system_model::descriptor::DescriptorCluster; +use rs_matter::data_model::system_model::{access_control, descriptor}; use rs_matter::error::ErrorCode; +use rs_matter::fabric::FabricMgr; +use rs_matter::mdns::Mdns; use rs_matter::pairing::DiscoveryCapabilities; use rs_matter::respond::DefaultResponder; +use rs_matter::secure_channel::pake::PaseMgr; use rs_matter::transport::network::{NetworkReceive, NetworkSend}; use rs_matter::utils::buf::{BufferAccess, PooledBuffers}; +use rs_matter::utils::epoch::Epoch; +use rs_matter::utils::rand::Rand; use rs_matter::utils::select::Coalesce; -use rs_matter::{CommissioningData, Matter, MATTER_PORT}; +use rs_matter::{handler_chain_type, CommissioningData, Matter, MATTER_PORT}; use crate::error::Error; use crate::multicast::{join_multicast_v4, join_multicast_v6}; use crate::netif::{get_ips, NetifAccess}; use crate::nvs; -const MAX_SUBSCRIPTIONS: usize = 3; -const MAX_IM_BUFFERS: usize = 10; -const PSM_BUFFER_SIZE: usize = 4096; -const MAX_RESPONDERS: usize = 4; -const MAX_BUSY_RESPONDERS: usize = 2; - +pub use eth::*; #[cfg(all( not(esp32h2), not(esp32s2), @@ -47,14 +62,17 @@ const MAX_BUSY_RESPONDERS: usize = 2; ))] pub use wifible::*; -pub trait Network { - const INIT: Self; -} +const MAX_SUBSCRIPTIONS: usize = 3; +const MAX_IM_BUFFERS: usize = 10; +const PSM_BUFFER_SIZE: usize = 4096; +const MAX_RESPONDERS: usize = 4; +const MAX_BUSY_RESPONDERS: usize = 2; -pub struct Eth(()); +mod eth; +mod wifible; -impl Network for Eth { - const INIT: Self = Self(()); +pub trait Network { + const INIT: Self; } pub struct MatterStack<'a, T> @@ -269,39 +287,116 @@ where } } -impl<'a> MatterStack<'a, Eth> { - pub const fn root_metadata() -> Endpoint<'static> { - root_endpoint::endpoint(0) - } - - pub fn root_handler(&self) -> impl AsyncHandler + '_ { - HandlerCompat(root_endpoint::handler(0, self.matter())) - } +pub type RootEndpointHandler<'a, NWCOMM, NWDIAG> = handler_chain_type!( + NWCOMM, + NWDIAG, + HandlerCompat>, + HandlerCompat>, + HandlerCompat>, + HandlerCompat>, + HandlerCompat>, + HandlerCompat>, + HandlerCompat, + HandlerCompat +); + +pub fn handler<'a, NWCOMM, NWDIAG, T>( + endpoint_id: u16, + matter: &'a T, + nwcomm: NWCOMM, + nwdiag_id: u32, + nwdiag: NWDIAG, +) -> RootEndpointHandler<'a, NWCOMM, NWDIAG> +where + T: Borrow> + + Borrow + + Borrow> + + Borrow> + + Borrow> + + Borrow> + + Borrow + + Borrow + + Borrow + + 'a, +{ + wrap( + endpoint_id, + matter.borrow(), + matter.borrow(), + matter.borrow(), + matter.borrow(), + matter.borrow(), + matter.borrow(), + matter.borrow(), + *matter.borrow(), + *matter.borrow(), + nwcomm, + nwdiag_id, + nwdiag, + ) +} - pub async fn run<'d, T, P, E>( - &self, - sysloop: EspSystemEventLoop, - nvs: EspNvsPartition

, - eth: E, - dev_comm: CommissioningData, - handler: T, - ) -> Result<(), Error> - where - T: AsyncHandler + AsyncMetadata, - P: NvsPartitionId, - E: NetifAccess, - { - info!("Matter Stack memory: {}B", core::mem::size_of_val(self)); - - self.run_with_netif( - sysloop, - nvs, - eth, - Some((dev_comm, DiscoveryCapabilities::new(true, false, false))), - handler, +#[allow(clippy::too_many_arguments)] +fn wrap<'a, NWCOMM, NWDIAG>( + endpoint_id: u16, + basic_info: &'a BasicInfoConfig<'a>, + dev_att: &'a dyn DevAttDataFetcher, + pase: &'a RefCell, + fabric: &'a RefCell, + acl: &'a RefCell, + failsafe: &'a RefCell, + mdns: &'a dyn Mdns, + epoch: Epoch, + rand: Rand, + nwcomm: NWCOMM, + nwdiag_id: u32, + nwdiag: NWDIAG, +) -> RootEndpointHandler<'a, NWCOMM, NWDIAG> { + EmptyHandler + .chain( + endpoint_id, + group_key_management::ID, + HandlerCompat(GrpKeyMgmtCluster::new(rand)), ) - .await - } + .chain( + endpoint_id, + general_diagnostics::ID, + HandlerCompat(GenDiagCluster::new(rand)), + ) + .chain( + endpoint_id, + access_control::ID, + HandlerCompat(AccessControlCluster::new(acl, rand)), + ) + .chain( + endpoint_id, + noc::ID, + HandlerCompat(NocCluster::new( + dev_att, fabric, acl, failsafe, mdns, epoch, rand, + )), + ) + .chain( + endpoint_id, + admin_commissioning::ID, + HandlerCompat(AdminCommCluster::new(pase, mdns, rand)), + ) + .chain( + endpoint_id, + general_commissioning::ID, + HandlerCompat(GenCommCluster::new(failsafe, false, rand)), + ) + .chain( + endpoint_id, + cluster_basic_information::ID, + HandlerCompat(BasicInfoCluster::new(basic_info, rand)), + ) + .chain( + endpoint_id, + descriptor::ID, + HandlerCompat(DescriptorCluster::new(rand)), + ) + .chain(endpoint_id, nwdiag_id, nwdiag) + .chain(endpoint_id, nw_commissioning::ID, nwcomm) } #[inline(never)] @@ -324,359 +419,3 @@ async fn init_async_io_async() { // as it consumes a lot of temp stack memory async_io::Timer::after(Duration::from_millis(100)).await; } - -#[cfg(all( - not(esp32h2), - not(esp32s2), - esp_idf_comp_esp_wifi_enabled, - esp_idf_comp_esp_event_enabled, - not(esp_idf_btdm_ctrl_mode_br_edr_only), - esp_idf_bt_enabled, - esp_idf_bt_bluedroid_enabled -))] -mod wifible { - use core::borrow::Borrow; - use core::cell::RefCell; - use core::pin::pin; - - use alloc::boxed::Box; - - use embassy_futures::select::select; - use embassy_sync::blocking_mutex::raw::NoopRawMutex; - use embassy_sync::mutex::Mutex; - - use esp_idf_svc::bt::{Ble, BleEnabled, BtDriver}; - use esp_idf_svc::eventloop::EspSystemEventLoop; - use esp_idf_svc::hal::modem::Modem; - use esp_idf_svc::hal::peripheral::Peripheral; - use esp_idf_svc::hal::task::embassy_sync::EspRawMutex; - use esp_idf_svc::nvs::EspDefaultNvsPartition; - use esp_idf_svc::timer::EspTaskTimerService; - use esp_idf_svc::wifi::{AsyncWifi, EspWifi}; - - use log::info; - - use rs_matter::acl::AclMgr; - use rs_matter::data_model::cluster_basic_information::{ - self, BasicInfoCluster, BasicInfoConfig, - }; - use rs_matter::data_model::objects::{ - AsyncHandler, AsyncMetadata, Cluster, EmptyHandler, Endpoint, HandlerCompat, - }; - use rs_matter::data_model::sdm::admin_commissioning::AdminCommCluster; - use rs_matter::data_model::sdm::dev_att::DevAttDataFetcher; - use rs_matter::data_model::sdm::failsafe::FailSafe; - use rs_matter::data_model::sdm::general_commissioning::GenCommCluster; - use rs_matter::data_model::sdm::general_diagnostics::GenDiagCluster; - use rs_matter::data_model::sdm::group_key_management::GrpKeyMgmtCluster; - use rs_matter::data_model::sdm::noc::NocCluster; - use rs_matter::data_model::sdm::{ - admin_commissioning, general_commissioning, general_diagnostics, group_key_management, noc, - nw_commissioning, - }; - use rs_matter::data_model::system_model::access_control::AccessControlCluster; - use rs_matter::data_model::system_model::descriptor::DescriptorCluster; - use rs_matter::data_model::system_model::{access_control, descriptor}; - use rs_matter::fabric::FabricMgr; - use rs_matter::mdns::Mdns; - use rs_matter::pairing::DiscoveryCapabilities; - use rs_matter::secure_channel::pake::PaseMgr; - use rs_matter::transport::network::btp::{Btp, BtpContext}; - use rs_matter::utils::epoch::Epoch; - use rs_matter::utils::rand::Rand; - use rs_matter::utils::select::Coalesce; - use rs_matter::{handler_chain_type, CommissioningData}; - - use crate::ble::{BtpGattContext, BtpGattPeripheral}; - use crate::error::Error; - use crate::wifi::comm::WifiNwCommCluster; - use crate::wifi::diag::WifiNwDiagCluster; - use crate::wifi::mgmt::WifiManager; - use crate::wifi::{comm, diag, WifiContext}; - use crate::{MatterStack, Network}; - - const MAX_WIFI_NETWORKS: usize = 2; - const GATTS_APP_ID: u16 = 0; - - pub struct WifiBle { - btp_context: BtpContext, - btp_gatt_context: BtpGattContext, - wifi_context: WifiContext, - } - - impl WifiBle { - const fn new() -> Self { - Self { - btp_context: BtpContext::new(), - btp_gatt_context: BtpGattContext::new(), - wifi_context: WifiContext::new(), - } - } - } - - impl Network for WifiBle { - const INIT: Self = Self::new(); - } - - impl<'a> MatterStack<'a, WifiBle> { - pub const fn root_metadata() -> Endpoint<'static> { - Endpoint { - id: 0, - device_type: rs_matter::data_model::device_types::DEV_TYPE_ROOT_NODE, - clusters: &CLUSTERS, - } - } - - pub fn root_handler(&self) -> RootEndpointHandler<'_> { - handler(0, self.matter(), &self.network.wifi_context) - } - - pub async fn is_commissioned(&self, _nvs: EspDefaultNvsPartition) -> Result { - // TODO - Ok(false) - } - - pub async fn operate<'d, T>( - &self, - sysloop: EspSystemEventLoop, - timer_service: EspTaskTimerService, - nvs: EspDefaultNvsPartition, - wifi: &mut EspWifi<'d>, - handler: T, - ) -> Result<(), Error> - where - T: AsyncHandler + AsyncMetadata, - { - info!("Running Matter in operating mode (Wifi)"); - - let wifi = Mutex::::new(AsyncWifi::wrap( - wifi, - sysloop.clone(), - timer_service, - )?); - - let mgr = WifiManager::new(&wifi, &self.network.wifi_context, sysloop.clone()); - - let mut main = pin!(self.run_with_netif(sysloop, nvs, &wifi, None, handler)); - let mut wifi = pin!(mgr.run()); - - select(&mut wifi, &mut main).coalesce().await - } - - pub async fn commission<'d, T, M>( - &'static self, - nvs: EspDefaultNvsPartition, - bt: &BtDriver<'d, M>, - dev_comm: CommissioningData, - handler: T, - ) -> Result<(), Error> - where - T: AsyncHandler + AsyncMetadata, - M: BleEnabled, - { - info!("Running Matter in commissioning mode (BLE)"); - - let peripheral = - BtpGattPeripheral::new(GATTS_APP_ID, bt, &self.network.btp_gatt_context); - - let btp = Btp::new(peripheral, &self.network.btp_context); - - let mut ble = pin!(async { - btp.run("BT", self.matter().dev_det(), &dev_comm) - .await - .map_err(Into::into) - }); - let mut main = pin!(self.run_once( - &btp, - &btp, - nvs, - Some(( - dev_comm.clone(), - DiscoveryCapabilities::new(false, true, false) - )), - &handler - )); - - select(&mut ble, &mut main).coalesce().await - } - - pub async fn run<'d, T>( - &'static self, - sysloop: EspSystemEventLoop, - timer_service: EspTaskTimerService, - nvs: EspDefaultNvsPartition, - mut modem: impl Peripheral

+ 'd, - dev_comm: CommissioningData, - handler: T, - ) -> Result<(), Error> - where - T: AsyncHandler + AsyncMetadata, - { - info!("Matter Stack memory: {}B", core::mem::size_of_val(self)); - - loop { - if !self.is_commissioned(nvs.clone()).await? { - let bt = BtDriver::::new(&mut modem, Some(nvs.clone()))?; - - info!("BLE driver initialized"); - - let mut main = - pin!(self.commission(nvs.clone(), &bt, dev_comm.clone(), &handler)); - let mut wait_network_connect = - pin!(self.network.wifi_context.wait_network_connect()); - - select(&mut main, &mut wait_network_connect) - .coalesce() - .await?; - } - - // Reset the matter transport to free up sessions and exchanges - self.matter().reset(); - - let mut wifi = Box::new(EspWifi::new( - &mut modem, - sysloop.clone(), - Some(nvs.clone()), - )?); - - info!("Wifi driver initialized"); - - self.operate( - sysloop.clone(), - timer_service.clone(), - nvs.clone(), - &mut wifi, - &handler, - ) - .await?; - } - } - } - - pub type RootEndpointHandler<'a> = handler_chain_type!( - HandlerCompat>, - HandlerCompat>, - HandlerCompat>, - comm::WifiNwCommCluster<'a, MAX_WIFI_NETWORKS, NoopRawMutex>, - HandlerCompat>, - HandlerCompat>, - HandlerCompat>, - HandlerCompat, - HandlerCompat, - HandlerCompat - ); - - const CLUSTERS: [Cluster<'static>; 10] = [ - descriptor::CLUSTER, - cluster_basic_information::CLUSTER, - general_commissioning::CLUSTER, - nw_commissioning::WIFI_CLUSTER, - admin_commissioning::CLUSTER, - noc::CLUSTER, - access_control::CLUSTER, - general_diagnostics::CLUSTER, - diag::CLUSTER, - group_key_management::CLUSTER, - ]; - - fn handler<'a, T>( - endpoint_id: u16, - matter: &'a T, - networks: &'a WifiContext, - ) -> RootEndpointHandler<'a> - where - T: Borrow> - + Borrow - + Borrow> - + Borrow> - + Borrow> - + Borrow> - + Borrow - + Borrow - + Borrow - + 'a, - { - wrap( - endpoint_id, - matter.borrow(), - matter.borrow(), - matter.borrow(), - matter.borrow(), - matter.borrow(), - matter.borrow(), - matter.borrow(), - *matter.borrow(), - *matter.borrow(), - networks, - ) - } - - #[allow(clippy::too_many_arguments)] - fn wrap<'a>( - endpoint_id: u16, - basic_info: &'a BasicInfoConfig<'a>, - dev_att: &'a dyn DevAttDataFetcher, - pase: &'a RefCell, - fabric: &'a RefCell, - acl: &'a RefCell, - failsafe: &'a RefCell, - mdns: &'a dyn Mdns, - epoch: Epoch, - rand: Rand, - networks: &'a WifiContext, - ) -> RootEndpointHandler<'a> { - EmptyHandler - .chain( - endpoint_id, - group_key_management::ID, - HandlerCompat(GrpKeyMgmtCluster::new(rand)), - ) - .chain( - endpoint_id, - diag::ID, - HandlerCompat(WifiNwDiagCluster::new(rand)), - ) - .chain( - endpoint_id, - general_diagnostics::ID, - HandlerCompat(GenDiagCluster::new(rand)), - ) - .chain( - endpoint_id, - access_control::ID, - HandlerCompat(AccessControlCluster::new(acl, rand)), - ) - .chain( - endpoint_id, - noc::ID, - HandlerCompat(NocCluster::new( - dev_att, fabric, acl, failsafe, mdns, epoch, rand, - )), - ) - .chain( - endpoint_id, - admin_commissioning::ID, - HandlerCompat(AdminCommCluster::new(pase, mdns, rand)), - ) - .chain( - endpoint_id, - nw_commissioning::ID, - WifiNwCommCluster::new(rand, networks), - ) - .chain( - endpoint_id, - general_commissioning::ID, - HandlerCompat(GenCommCluster::new(failsafe, false, rand)), - ) - .chain( - endpoint_id, - cluster_basic_information::ID, - HandlerCompat(BasicInfoCluster::new(basic_info, rand)), - ) - .chain( - endpoint_id, - descriptor::ID, - HandlerCompat(DescriptorCluster::new(rand)), - ) - } -} diff --git a/src/stack/eth.rs b/src/stack/eth.rs new file mode 100644 index 0000000..4d01a06 --- /dev/null +++ b/src/stack/eth.rs @@ -0,0 +1,94 @@ +use core::borrow::Borrow; + +use esp_idf_svc::eventloop::EspSystemEventLoop; +use esp_idf_svc::nvs::{EspNvsPartition, NvsPartitionId}; + +use log::info; + +use rs_matter::data_model::cluster_basic_information; +use rs_matter::data_model::objects::{ + AsyncHandler, AsyncMetadata, Cluster, Endpoint, HandlerCompat, +}; +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::{ + admin_commissioning, ethernet_nw_diagnostics, general_commissioning, general_diagnostics, + group_key_management, noc, nw_commissioning, +}; +use rs_matter::data_model::system_model::{access_control, descriptor}; +use rs_matter::pairing::DiscoveryCapabilities; +use rs_matter::CommissioningData; + +use crate::error::Error; +use crate::netif::NetifAccess; +use crate::{handler, MatterStack, Network, RootEndpointHandler}; + +pub struct Eth(()); + +impl Network for Eth { + const INIT: Self = Self(()); +} + +impl<'a> MatterStack<'a, Eth> { + pub const fn root_metadata() -> Endpoint<'static> { + Endpoint { + id: 0, + device_type: rs_matter::data_model::device_types::DEV_TYPE_ROOT_NODE, + clusters: &CLUSTERS, + } + } + + pub fn root_handler(&self) -> EthRootEndpointHandler<'_> { + handler( + 0, + self.matter(), + HandlerCompat(EthNwCommCluster::new(*self.matter().borrow())), + ethernet_nw_diagnostics::ID, + HandlerCompat(EthNwDiagCluster::new(*self.matter().borrow())), + ) + } + + pub async fn run<'d, T, P, E>( + &self, + sysloop: EspSystemEventLoop, + nvs: EspNvsPartition

, + eth: E, + dev_comm: CommissioningData, + handler: T, + ) -> Result<(), Error> + where + T: AsyncHandler + AsyncMetadata, + P: NvsPartitionId, + E: NetifAccess, + { + info!("Matter Stack memory: {}B", core::mem::size_of_val(self)); + + self.run_with_netif( + sysloop, + nvs, + eth, + Some((dev_comm, DiscoveryCapabilities::new(true, false, false))), + handler, + ) + .await + } +} + +pub type EthRootEndpointHandler<'a> = RootEndpointHandler< + 'a, + HandlerCompat, + HandlerCompat, +>; + +const CLUSTERS: [Cluster<'static>; 10] = [ + descriptor::CLUSTER, + cluster_basic_information::CLUSTER, + general_commissioning::CLUSTER, + nw_commissioning::ETH_CLUSTER, + admin_commissioning::CLUSTER, + noc::CLUSTER, + access_control::CLUSTER, + general_diagnostics::CLUSTER, + ethernet_nw_diagnostics::CLUSTER, + group_key_management::CLUSTER, +]; diff --git a/src/stack/wifible.rs b/src/stack/wifible.rs new file mode 100644 index 0000000..e1b1a86 --- /dev/null +++ b/src/stack/wifible.rs @@ -0,0 +1,227 @@ +#![cfg(all( + not(esp32h2), + not(esp32s2), + esp_idf_comp_esp_wifi_enabled, + esp_idf_comp_esp_event_enabled, + not(esp_idf_btdm_ctrl_mode_br_edr_only), + esp_idf_bt_enabled, + esp_idf_bt_bluedroid_enabled +))] + +use core::borrow::Borrow; +use core::pin::pin; + +use alloc::boxed::Box; + +use embassy_futures::select::select; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::mutex::Mutex; + +use esp_idf_svc::bt::{Ble, BleEnabled, BtDriver}; +use esp_idf_svc::eventloop::EspSystemEventLoop; +use esp_idf_svc::hal::modem::Modem; +use esp_idf_svc::hal::peripheral::Peripheral; +use esp_idf_svc::hal::task::embassy_sync::EspRawMutex; +use esp_idf_svc::nvs::EspDefaultNvsPartition; +use esp_idf_svc::timer::EspTaskTimerService; +use esp_idf_svc::wifi::{AsyncWifi, EspWifi}; + +use log::info; + +use rs_matter::data_model::cluster_basic_information; +use rs_matter::data_model::objects::{ + AsyncHandler, AsyncMetadata, Cluster, Endpoint, HandlerCompat, +}; +use rs_matter::data_model::sdm::{ + admin_commissioning, general_commissioning, general_diagnostics, group_key_management, noc, + nw_commissioning, +}; +use rs_matter::data_model::system_model::{access_control, descriptor}; +use rs_matter::pairing::DiscoveryCapabilities; +use rs_matter::transport::network::btp::{Btp, BtpContext}; +use rs_matter::utils::select::Coalesce; +use rs_matter::CommissioningData; + +use crate::ble::{BtpGattContext, BtpGattPeripheral}; +use crate::error::Error; +use crate::wifi::mgmt::WifiManager; +use crate::wifi::{comm, diag, WifiContext}; +use crate::{handler, MatterStack, Network, RootEndpointHandler}; + +const MAX_WIFI_NETWORKS: usize = 2; +const GATTS_APP_ID: u16 = 0; + +pub struct WifiBle { + btp_context: BtpContext, + btp_gatt_context: BtpGattContext, + wifi_context: WifiContext, +} + +impl WifiBle { + const fn new() -> Self { + Self { + btp_context: BtpContext::new(), + btp_gatt_context: BtpGattContext::new(), + wifi_context: WifiContext::new(), + } + } +} + +impl Network for WifiBle { + const INIT: Self = Self::new(); +} + +impl<'a> MatterStack<'a, WifiBle> { + pub const fn root_metadata() -> Endpoint<'static> { + Endpoint { + id: 0, + device_type: rs_matter::data_model::device_types::DEV_TYPE_ROOT_NODE, + clusters: &CLUSTERS, + } + } + + pub fn root_handler(&self) -> WifiBleRootEndpointHandler<'_> { + handler( + 0, + self.matter(), + comm::WifiNwCommCluster::new(*self.matter().borrow(), &self.network.wifi_context), + diag::ID, + HandlerCompat(diag::WifiNwDiagCluster::new(*self.matter().borrow())), + ) + } + + pub async fn is_commissioned(&self, _nvs: EspDefaultNvsPartition) -> Result { + // TODO + Ok(false) + } + + pub async fn operate<'d, T>( + &self, + sysloop: EspSystemEventLoop, + timer_service: EspTaskTimerService, + nvs: EspDefaultNvsPartition, + wifi: &mut EspWifi<'d>, + handler: T, + ) -> Result<(), Error> + where + T: AsyncHandler + AsyncMetadata, + { + info!("Running Matter in operating mode (Wifi)"); + + let wifi = + Mutex::::new(AsyncWifi::wrap(wifi, sysloop.clone(), timer_service)?); + + let mgr = WifiManager::new(&wifi, &self.network.wifi_context, sysloop.clone()); + + let mut main = pin!(self.run_with_netif(sysloop, nvs, &wifi, None, handler)); + let mut wifi = pin!(mgr.run()); + + select(&mut wifi, &mut main).coalesce().await + } + + pub async fn commission<'d, T, M>( + &'static self, + nvs: EspDefaultNvsPartition, + bt: &BtDriver<'d, M>, + dev_comm: CommissioningData, + handler: T, + ) -> Result<(), Error> + where + T: AsyncHandler + AsyncMetadata, + M: BleEnabled, + { + info!("Running Matter in commissioning mode (BLE)"); + + let peripheral = BtpGattPeripheral::new(GATTS_APP_ID, bt, &self.network.btp_gatt_context); + + let btp = Btp::new(peripheral, &self.network.btp_context); + + let mut ble = pin!(async { + btp.run("BT", self.matter().dev_det(), &dev_comm) + .await + .map_err(Into::into) + }); + let mut main = pin!(self.run_once( + &btp, + &btp, + nvs, + Some(( + dev_comm.clone(), + DiscoveryCapabilities::new(false, true, false) + )), + &handler + )); + + select(&mut ble, &mut main).coalesce().await + } + + pub async fn run<'d, T>( + &'static self, + sysloop: EspSystemEventLoop, + timer_service: EspTaskTimerService, + nvs: EspDefaultNvsPartition, + mut modem: impl Peripheral

+ 'd, + dev_comm: CommissioningData, + handler: T, + ) -> Result<(), Error> + where + T: AsyncHandler + AsyncMetadata, + { + info!("Matter Stack memory: {}B", core::mem::size_of_val(self)); + + loop { + if !self.is_commissioned(nvs.clone()).await? { + let bt = BtDriver::::new(&mut modem, Some(nvs.clone()))?; + + info!("BLE driver initialized"); + + let mut main = pin!(self.commission(nvs.clone(), &bt, dev_comm.clone(), &handler)); + let mut wait_network_connect = + pin!(self.network.wifi_context.wait_network_connect()); + + select(&mut main, &mut wait_network_connect) + .coalesce() + .await?; + } + + // Reset the matter transport to free up sessions and exchanges + self.matter().reset(); + + let mut wifi = Box::new(EspWifi::new( + &mut modem, + sysloop.clone(), + Some(nvs.clone()), + )?); + + info!("Wifi driver initialized"); + + self.operate( + sysloop.clone(), + timer_service.clone(), + nvs.clone(), + &mut wifi, + &handler, + ) + .await?; + } + } +} + +pub type WifiBleRootEndpointHandler<'a> = RootEndpointHandler< + 'a, + comm::WifiNwCommCluster<'a, MAX_WIFI_NETWORKS, NoopRawMutex>, + HandlerCompat, +>; + +const CLUSTERS: [Cluster<'static>; 10] = [ + descriptor::CLUSTER, + cluster_basic_information::CLUSTER, + general_commissioning::CLUSTER, + nw_commissioning::WIFI_CLUSTER, + admin_commissioning::CLUSTER, + noc::CLUSTER, + access_control::CLUSTER, + general_diagnostics::CLUSTER, + diag::CLUSTER, + group_key_management::CLUSTER, +];