From 70083318d1a524d5e8aea24b2fbea5478aa8f7e0 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Sun, 7 Jul 2024 05:30:23 +0000 Subject: [PATCH 01/13] In-place init --- Cargo.toml | 8 ++--- README.md | 44 +++++++++++++----------- examples/light.rs | 40 ++++++++++++---------- examples/light_eth.rs | 40 ++++++++++++---------- src/ble.rs | 78 +++++++++++++++++++++++++++++++++---------- src/stack/wifible.rs | 13 ++++++++ 6 files changed, 146 insertions(+), 77 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 107d688..d6724fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ rust-version = "1.78" #rs-matter-stack = { path = "../rs-matter-stack", default-features = false, features = ["std"], optional = true } [patch.crates-io] -rs-matter = { git = "https://github.com/ivmarkov/rs-matter", branch = "wifi" } +rs-matter = { git = "https://github.com/ivmarkov/rs-matter", branch = "tip" } #rs-matter = { path = "../rs-matter/rs-matter" } #edge-nal = { git = "https://github.com/ivmarkov/edge-net" } #edge-nal = { path = "../edge-net/edge-nal" } @@ -53,9 +53,9 @@ esp-idf-svc = { version = "0.49.1", default-features = false, features = ["alloc embedded-svc = { version = "0.28", default-features = false } rs-matter = { version = "0.1", default-features = false, features = ["rustcrypto"] } async-io = { version = "=2.0.0", default-features = false } # Workaround for https://github.com/smol-rs/async-lock/issues/84 -rs-matter-stack = { git = "https://github.com/ivmarkov/rs-matter-stack", default-features = false, features = ["std"], optional = true } -edge-nal = "0.3" -edge-nal-std = { version = "0.3", default-features = false, optional = true } +rs-matter-stack = { git = "https://github.com/ivmarkov/rs-matter-stack", branch = "tip", default-features = false, features = ["std"], optional = true } +edge-nal = "0.2" +edge-nal-std = { version = "0.2", default-features = false, optional = true } [build-dependencies] embuild = "0.32" diff --git a/README.md b/README.md index cc9770e..f1b5307 100644 --- a/README.md +++ b/README.md @@ -48,12 +48,13 @@ use rs_matter::data_model::device_types::DEV_TYPE_ON_OFF_LIGHT; use rs_matter::data_model::objects::{Dataver, Endpoint, HandlerCompat, Node}; use rs_matter::data_model::system_model::descriptor; use rs_matter::secure_channel::spake2p::VerifierData; +use rs_matter::utils::init::InitMaybeUninit; use rs_matter::utils::select::Coalesce; use rs_matter::CommissioningData; use rs_matter_stack::persist::DummyPersist; -use static_cell::ConstStaticCell; +use static_cell::StaticCell; #[path = "dev_att/dev_att.rs"] mod dev_att; @@ -95,9 +96,24 @@ fn run() -> Result<(), anyhow::Error> { } async fn matter() -> Result<(), anyhow::Error> { - // Take the Matter stack (can be done only once), + // Initialize the Matter stack (can be done only once), // as we'll run it in this thread - let stack = MATTER_STACK.take(); + let stack = MATTER_STACK + .uninit() + .init_with(EspWifiBleMatterStack::init_default( + &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, + )); // Take some generic ESP-IDF stuff we'll need later let sysloop = EspSystemEventLoop::take()?; @@ -124,7 +140,9 @@ async fn matter() -> Result<(), anyhow::Error> { .chain( LIGHT_ENDPOINT_ID, descriptor::ID, - HandlerCompat(descriptor::DescriptorCluster::new(Dataver::new_rand(stack.matter().rand()))), + HandlerCompat(descriptor::DescriptorCluster::new(Dataver::new_rand( + stack.matter().rand(), + ))), ); // Run the Matter stack with our handler @@ -182,21 +200,9 @@ async fn matter() -> Result<(), anyhow::Error> { /// The Matter stack is allocated statically to avoid /// program stack blowups. /// It is also a mandatory requirement when the `WifiBle` stack variation is used. -static MATTER_STACK: ConstStaticCell> = - ConstStaticCell::new(EspWifiBleMatterStack::new_default( - &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(), - )); +static MATTER_STACK: StaticCell> = StaticCell::new(); + +static DEV_ATT: dev_att::HardCodedDevAtt = dev_att::HardCodedDevAtt::new(); /// Endpoint 0 (the root endpoint) always runs /// the hidden Matter system clusters, so we pick ID=1 diff --git a/examples/light.rs b/examples/light.rs index 544487c..ed4a0e1 100644 --- a/examples/light.rs +++ b/examples/light.rs @@ -27,12 +27,13 @@ use rs_matter::data_model::device_types::DEV_TYPE_ON_OFF_LIGHT; use rs_matter::data_model::objects::{Dataver, Endpoint, HandlerCompat, Node}; use rs_matter::data_model::system_model::descriptor; use rs_matter::secure_channel::spake2p::VerifierData; +use rs_matter::utils::init::InitMaybeUninit; use rs_matter::utils::select::Coalesce; use rs_matter::CommissioningData; use rs_matter_stack::persist::DummyPersist; -use static_cell::ConstStaticCell; +use static_cell::StaticCell; #[path = "dev_att/dev_att.rs"] mod dev_att; @@ -74,9 +75,24 @@ fn run() -> Result<(), anyhow::Error> { } async fn matter() -> Result<(), anyhow::Error> { - // Take the Matter stack (can be done only once), + // Initialize the Matter stack (can be done only once), // as we'll run it in this thread - let stack = MATTER_STACK.take(); + let stack = MATTER_STACK + .uninit() + .init_with(EspWifiBleMatterStack::init_default( + &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, + )); // Take some generic ESP-IDF stuff we'll need later let sysloop = EspSystemEventLoop::take()?; @@ -163,21 +179,9 @@ async fn matter() -> Result<(), anyhow::Error> { /// The Matter stack is allocated statically to avoid /// program stack blowups. /// It is also a mandatory requirement when the `WifiBle` stack variation is used. -static MATTER_STACK: ConstStaticCell> = - ConstStaticCell::new(EspWifiBleMatterStack::new_default( - &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(), - )); +static MATTER_STACK: StaticCell> = StaticCell::new(); + +static DEV_ATT: dev_att::HardCodedDevAtt = dev_att::HardCodedDevAtt::new(); /// Endpoint 0 (the root endpoint) always runs /// the hidden Matter system clusters, so we pick ID=1 diff --git a/examples/light_eth.rs b/examples/light_eth.rs index 332e0c3..e7697e7 100644 --- a/examples/light_eth.rs +++ b/examples/light_eth.rs @@ -34,12 +34,13 @@ use rs_matter::data_model::device_types::DEV_TYPE_ON_OFF_LIGHT; use rs_matter::data_model::objects::{Dataver, Endpoint, HandlerCompat, Node}; use rs_matter::data_model::system_model::descriptor; use rs_matter::secure_channel::spake2p::VerifierData; +use rs_matter::utils::init::InitMaybeUninit; use rs_matter::utils::select::Coalesce; use rs_matter::CommissioningData; use rs_matter_stack::persist::DummyPersist; -use static_cell::ConstStaticCell; +use static_cell::StaticCell; #[path = "dev_att/dev_att.rs"] mod dev_att; @@ -84,9 +85,24 @@ fn run() -> Result<(), anyhow::Error> { } async fn matter() -> Result<(), anyhow::Error> { - // Take the Matter stack (can be done only once), + // Initialize the Matter stack (can be done only once), // as we'll run it in this thread - let stack = MATTER_STACK.take(); + let stack = MATTER_STACK + .uninit() + .init_with(EspEthMatterStack::init_default( + &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, + )); // Take some generic ESP-IDF stuff we'll need later let sysloop = EspSystemEventLoop::take()?; @@ -187,21 +203,9 @@ async fn matter() -> Result<(), anyhow::Error> { /// The Matter stack is allocated statically to avoid /// program stack blowups. -static MATTER_STACK: ConstStaticCell> = - ConstStaticCell::new(EspEthMatterStack::new_default( - &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(), - )); +static MATTER_STACK: StaticCell> = StaticCell::new(); + +static DEV_ATT: dev_att::HardCodedDevAtt = dev_att::HardCodedDevAtt::new(); /// Endpoint 0 (the root endpoint) always runs /// the hidden Matter system clusters, so we pick ID=1 diff --git a/src/ble.rs b/src/ble.rs index 0b6db4e..57547e3 100644 --- a/src/ble.rs +++ b/src/ble.rs @@ -6,12 +6,9 @@ ))] use core::borrow::Borrow; -use core::cell::RefCell; use alloc::borrow::ToOwned; -use embassy_sync::blocking_mutex::Mutex; - use enumset::enum_set; use esp_idf_svc::bt::ble::gap::{BleGapEvent, EspBleGap}; @@ -32,7 +29,10 @@ use rs_matter::transport::network::btp::{ C2_CHARACTERISTIC_UUID, C2_MAX_LEN, MATTER_BLE_SERVICE_UUID16, MAX_BTP_SESSIONS, }; use rs_matter::transport::network::BtAddr; +use rs_matter::utils::blmutex::Mutex; use rs_matter::utils::ifmutex::IfMutex; +use rs_matter::utils::init::{init, Init}; +use rs_matter::utils::refcell::RefCell; use rs_matter::utils::signal::Signal; const MAX_CONNECTIONS: usize = MAX_BTP_SESSIONS; @@ -52,14 +52,58 @@ struct State { c1_handle: Option, c2_handle: Option, c2_cccd_handle: Option, - connections: heapless::Vec, + connections: rs_matter::utils::vec::Vec, response: GattResponse, } +impl State { + #[inline(always)] + const fn new() -> Self { + Self { + gatt_if: None, + service_handle: None, + c1_handle: None, + c2_handle: None, + c2_cccd_handle: None, + connections: rs_matter::utils::vec::Vec::new(), + response: GattResponse::new(), + } + } + + fn init() -> impl Init { + init!(Self { + gatt_if: None, + service_handle: None, + c1_handle: None, + c2_handle: None, + c2_cccd_handle: None, + connections <- rs_matter::utils::vec::Vec::init(), + response: GattResponse::new(), // TODO + }) + } +} + #[derive(Debug)] struct IndBuffer { addr: BtAddr, - data: heapless::Vec, + data: rs_matter::utils::vec::Vec, +} + +impl IndBuffer { + #[inline(always)] + const fn new() -> Self { + Self { + addr: BtAddr([0; 6]), + data: rs_matter::utils::vec::Vec::new(), + } + } + + fn init() -> impl Init { + init!(Self { + addr: BtAddr([0; 6]), + data <- rs_matter::utils::vec::Vec::init(), + }) + } } /// The `'static` state of the `BtpGattPeripheral` struct. @@ -77,23 +121,21 @@ impl BtpGattContext { #[inline(always)] pub const fn new() -> Self { Self { - state: Mutex::new(RefCell::new(State { - gatt_if: None, - service_handle: None, - c1_handle: None, - c2_handle: None, - c2_cccd_handle: None, - connections: heapless::Vec::new(), - response: GattResponse::new(), - })), - ind: IfMutex::new(IndBuffer { - addr: BtAddr([0; 6]), - data: heapless::Vec::new(), - }), + state: Mutex::new(RefCell::new(State::new())), + ind: IfMutex::new(IndBuffer::new()), ind_in_flight: Signal::new(false), } } + #[allow(clippy::large_stack_frames)] + pub fn init() -> impl Init { + init!(Self { + state <- Mutex::init(RefCell::init(State::init())), + ind <- IfMutex::init(IndBuffer::init()), + ind_in_flight: Signal::new(false), + }) + } + pub(crate) fn reset(&self) -> Result<(), EspError> { self.state.lock(|state| { let mut state = state.borrow_mut(); diff --git a/src/stack/wifible.rs b/src/stack/wifible.rs index 3799b2e..b49d6dd 100644 --- a/src/stack/wifible.rs +++ b/src/stack/wifible.rs @@ -37,6 +37,8 @@ use esp_idf_svc::timer::EspTaskTimerService; use esp_idf_svc::wifi::{AccessPointInfo, AsyncWifi, Capability, Configuration, EspWifi}; use rs_matter::error::{Error, ErrorCode}; +use rs_matter::utils::init::{init, Init}; + use rs_matter_stack::modem::{Modem, WifiDevice}; use rs_matter_stack::netif::{Netif, NetifConf}; use rs_matter_stack::network::{Embedding, Network}; @@ -77,6 +79,13 @@ where } } + fn init() -> impl Init { + init!(Self { + btp_gatt_context <- BtpGattContext::init(), + embedding <- E::init(), + }) + } + pub fn context(&self) -> &BtpGattContext { &self.btp_gatt_context } @@ -91,6 +100,10 @@ where E: Embedding, { const INIT: Self = Self::new(); + + fn init() -> impl Init { + EspGatt::init() + } } const GATTS_APP_ID: u16 = 0; From 34e6df450f9e82dd9951d27d97521cc69cf69ba2 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Sun, 7 Jul 2024 11:04:34 +0000 Subject: [PATCH 02/13] Build with Rust stable --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index d6724fd..5f97515 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ rs-matter = { git = "https://github.com/ivmarkov/rs-matter", branch = "tip" } #edge-nal = { path = "../edge-net/edge-nal" } #edge-nal-std = { git = "https://github.com/ivmarkov/edge-net" } #edge-nal-std = { path = "../edge-net/edge-nal-std" } +pinned-init = { git = "https://github.com/ivmarkov/pinned-init" } [profile.release] opt-level = "s" From 3a510898eefe0fb3e79a4dad4e0dac2f63079a35 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Tue, 9 Jul 2024 08:08:36 +0000 Subject: [PATCH 03/13] Build with Rust stable --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5f97515..d6724fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,6 @@ rs-matter = { git = "https://github.com/ivmarkov/rs-matter", branch = "tip" } #edge-nal = { path = "../edge-net/edge-nal" } #edge-nal-std = { git = "https://github.com/ivmarkov/edge-net" } #edge-nal-std = { path = "../edge-net/edge-nal-std" } -pinned-init = { git = "https://github.com/ivmarkov/pinned-init" } [profile.release] opt-level = "s" From 5aa03c5c659f952ec54c0a1acf898c108b470e9e Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Mon, 9 Sep 2024 15:25:15 +0000 Subject: [PATCH 04/13] Update to the tip of my fork --- Cargo.toml | 1 - examples/light.rs | 2 +- examples/light_eth.rs | 2 +- src/ble.rs | 19 +++++++++---------- src/stack/netif.rs | 2 +- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d6724fd..1213f9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,6 @@ examples = ["default", "esp-idf-svc/binstart", "esp-idf-svc/critical-section"] # log = { version = "0.4", default-features = false } heapless = "0.8" enumset = { version = "1", default-features = false } -scopeguard = { version = "1", default-features = false } embassy-futures = "0.1" embassy-sync = "0.6" embassy-time = { version = "0.3", features = ["generic-queue"] } diff --git a/examples/light.rs b/examples/light.rs index ed4a0e1..d383cce 100644 --- a/examples/light.rs +++ b/examples/light.rs @@ -47,7 +47,7 @@ fn main() -> Result<(), anyhow::Error> { // 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(70 * 1024) + .stack_size(40 * 1024) .spawn(|| { // Eagerly initialize `async-io` to minimize the risk of stack blowups later on init_async_io()?; diff --git a/examples/light_eth.rs b/examples/light_eth.rs index e7697e7..d74ce57 100644 --- a/examples/light_eth.rs +++ b/examples/light_eth.rs @@ -57,7 +57,7 @@ fn main() -> Result<(), anyhow::Error> { // 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) + .stack_size(40 * 1024) .spawn(|| { // Eagerly initialize `async-io` to minimize the risk of stack blowups later on init_async_io()?; diff --git a/src/ble.rs b/src/ble.rs index 57547e3..cc97db7 100644 --- a/src/ble.rs +++ b/src/ble.rs @@ -29,11 +29,10 @@ use rs_matter::transport::network::btp::{ C2_CHARACTERISTIC_UUID, C2_MAX_LEN, MATTER_BLE_SERVICE_UUID16, MAX_BTP_SESSIONS, }; use rs_matter::transport::network::BtAddr; -use rs_matter::utils::blmutex::Mutex; -use rs_matter::utils::ifmutex::IfMutex; +use rs_matter::utils::cell::RefCell; use rs_matter::utils::init::{init, Init}; -use rs_matter::utils::refcell::RefCell; -use rs_matter::utils::signal::Signal; +use rs_matter::utils::sync::blocking::Mutex; +use rs_matter::utils::sync::{IfMutex, Signal}; const MAX_CONNECTIONS: usize = MAX_BTP_SESSIONS; const MAX_MTU_SIZE: usize = 512; @@ -52,7 +51,7 @@ struct State { c1_handle: Option, c2_handle: Option, c2_cccd_handle: Option, - connections: rs_matter::utils::vec::Vec, + connections: rs_matter::utils::storage::Vec, response: GattResponse, } @@ -65,7 +64,7 @@ impl State { c1_handle: None, c2_handle: None, c2_cccd_handle: None, - connections: rs_matter::utils::vec::Vec::new(), + connections: rs_matter::utils::storage::Vec::new(), response: GattResponse::new(), } } @@ -77,7 +76,7 @@ impl State { c1_handle: None, c2_handle: None, c2_cccd_handle: None, - connections <- rs_matter::utils::vec::Vec::init(), + connections <- rs_matter::utils::storage::Vec::init(), response: GattResponse::new(), // TODO }) } @@ -86,7 +85,7 @@ impl State { #[derive(Debug)] struct IndBuffer { addr: BtAddr, - data: rs_matter::utils::vec::Vec, + data: rs_matter::utils::storage::Vec, } impl IndBuffer { @@ -94,14 +93,14 @@ impl IndBuffer { const fn new() -> Self { Self { addr: BtAddr([0; 6]), - data: rs_matter::utils::vec::Vec::new(), + data: rs_matter::utils::storage::Vec::new(), } } fn init() -> impl Init { init!(Self { addr: BtAddr([0; 6]), - data <- rs_matter::utils::vec::Vec::init(), + data <- rs_matter::utils::storage::Vec::init(), }) } } diff --git a/src/stack/netif.rs b/src/stack/netif.rs index dc879ad..f10a9a6 100644 --- a/src/stack/netif.rs +++ b/src/stack/netif.rs @@ -24,7 +24,7 @@ use esp_idf_svc::handle::RawHandle; use esp_idf_svc::netif::{EspNetif, IpEvent}; use esp_idf_svc::sys::{esp, esp_netif_get_ip6_linklocal, EspError, ESP_FAIL}; -use rs_matter::utils::notification::Notification; +use rs_matter::utils::sync::Notification; use rs_matter_stack::netif::{Netif, NetifConf}; const TIMEOUT_PERIOD_SECS: u8 = 5; From 8085b5d15c76e5b57a3947f180fbe293a614ce48 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Wed, 11 Sep 2024 06:34:42 +0000 Subject: [PATCH 05/13] Update to latest edge-net; proper init for GattResponse --- Cargo.toml | 8 ++++---- examples/light.rs | 2 +- src/ble.rs | 35 ++++++++++++++++++++++++++++++++++- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1213f9d..d0145f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ rust-version = "1.78" #[patch.'https://github.com/ivmarkov/async-io-mini'] #async-io-mini = { path = "../async-io-mini" } #[patch.'https://github.com/ivmarkov/rs-matter-stack'] -#rs-matter-stack = { path = "../rs-matter-stack", default-features = false, features = ["std"], optional = true } +#rs-matter-stack = { path = "../rs-matter-stack" } [patch.crates-io] rs-matter = { git = "https://github.com/ivmarkov/rs-matter", branch = "tip" } @@ -52,9 +52,9 @@ esp-idf-svc = { version = "0.49.1", default-features = false, features = ["alloc embedded-svc = { version = "0.28", default-features = false } rs-matter = { version = "0.1", default-features = false, features = ["rustcrypto"] } async-io = { version = "=2.0.0", default-features = false } # Workaround for https://github.com/smol-rs/async-lock/issues/84 -rs-matter-stack = { git = "https://github.com/ivmarkov/rs-matter-stack", branch = "tip", default-features = false, features = ["std"], optional = true } -edge-nal = "0.2" -edge-nal-std = { version = "0.2", default-features = false, optional = true } +rs-matter-stack = { git = "https://github.com/ivmarkov/rs-matter-stack", branch = "in-place-init", default-features = false, features = ["std"], optional = true } +edge-nal = "0.3" +edge-nal-std = { version = "0.3", default-features = false, optional = true } [build-dependencies] embuild = "0.32" diff --git a/examples/light.rs b/examples/light.rs index d383cce..05a4be3 100644 --- a/examples/light.rs +++ b/examples/light.rs @@ -47,7 +47,7 @@ fn main() -> Result<(), anyhow::Error> { // 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(40 * 1024) + .stack_size(55 * 1024) .spawn(|| { // Eagerly initialize `async-io` to minimize the risk of stack blowups later on init_async_io()?; diff --git a/src/ble.rs b/src/ble.rs index cc97db7..751f0fc 100644 --- a/src/ble.rs +++ b/src/ble.rs @@ -77,7 +77,7 @@ impl State { c2_handle: None, c2_cccd_handle: None, connections <- rs_matter::utils::storage::Vec::init(), - response: GattResponse::new(), // TODO + response <- gatt_response::init(), }) } } @@ -845,3 +845,36 @@ where Ok(()) } } + +mod gatt_response { + use esp_idf_svc::bt::ble::gatt::GattResponse; + use esp_idf_svc::sys::{esp_gatt_rsp_t, esp_gatt_value_t}; + + use rs_matter::utils::init::{init, init_from_closure, zeroed, Init}; + + /// Return an in-place initializer for `GattResponse`. + /// + /// Works by initializing the `GattResponse` struct in-place using the `esp_gatt_rsp_t` type, + /// which is possible because `GattResponse` is a `#[repr(transparent)]` newtype over `esp_gatt_rsp_t`. + pub fn init() -> impl Init { + unsafe { + init_from_closure(|slot: *mut GattResponse| { + let slot = slot as *mut esp_gatt_rsp_t; + + init_esp_gatt_response().__init(slot) + }) + } + } + + fn init_esp_gatt_response() -> impl Init { + init!(esp_gatt_rsp_t { + attr_value <- init!(esp_gatt_value_t { + len: 0, + value <- zeroed(), + handle: 0, + offset: 0, + auth_req: 0, + }), + }) + } +} From 3cdbb149210da4e759963498e9170276f2d231ab Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Fri, 4 Oct 2024 06:38:05 +0000 Subject: [PATCH 06/13] Concurrent commissioning --- Cargo.toml | 9 +- examples/light.rs | 19 +-- examples/light_eth.rs | 9 +- src/ble.rs | 32 ++-- src/{stack => }/eth.rs | 0 src/lib.rs | 81 ++++++++- src/mdns.rs | 2 - src/{stack => }/netif.rs | 34 ++-- src/{stack => }/persist.rs | 4 +- src/stack.rs | 64 ------- src/stack/wifible.rs | 290 ------------------------------- src/wireless.rs | 339 +++++++++++++++++++++++++++++++++++++ 12 files changed, 470 insertions(+), 413 deletions(-) rename src/{stack => }/eth.rs (100%) rename src/{stack => }/netif.rs (84%) rename src/{stack => }/persist.rs (94%) delete mode 100644 src/stack.rs delete mode 100644 src/stack/wifible.rs create mode 100644 src/wireless.rs diff --git a/Cargo.toml b/Cargo.toml index d0145f2..d5f1985 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,13 +13,16 @@ readme = "README.md" build = "build.rs" rust-version = "1.78" +#[lib] +#harness = false + #[patch.'https://github.com/ivmarkov/async-io-mini'] #async-io-mini = { path = "../async-io-mini" } -#[patch.'https://github.com/ivmarkov/rs-matter-stack'] -#rs-matter-stack = { path = "../rs-matter-stack" } +[patch.'https://github.com/ivmarkov/rs-matter-stack'] +rs-matter-stack = { path = "../rs-matter-stack" } [patch.crates-io] -rs-matter = { git = "https://github.com/ivmarkov/rs-matter", branch = "tip" } +rs-matter = { git = "https://github.com/ivmarkov/rs-matter" } #rs-matter = { path = "../rs-matter/rs-matter" } #edge-nal = { git = "https://github.com/ivmarkov/edge-net" } #edge-nal = { path = "../edge-net/edge-nal" } diff --git a/examples/light.rs b/examples/light.rs index 05a4be3..6b7e911 100644 --- a/examples/light.rs +++ b/examples/light.rs @@ -10,7 +10,7 @@ use core::pin::pin; use embassy_futures::select::select; use embassy_time::{Duration, Timer}; -use esp_idf_matter::{init_async_io, EspKvBlobStore, EspModem, EspPersist, EspWifiBleMatterStack}; +use esp_idf_matter::{init_async_io, EspKvBlobStore, EspMatterBle, EspPersist, EspWifiMatterStack}; use esp_idf_svc::eventloop::EspSystemEventLoop; use esp_idf_svc::hal::peripherals::Peripherals; @@ -79,7 +79,7 @@ async fn matter() -> Result<(), anyhow::Error> { // as we'll run it in this thread let stack = MATTER_STACK .uninit() - .init_with(EspWifiBleMatterStack::init_default( + .init_with(EspWifiMatterStack::init_default( &BasicInfoConfig { vid: 0xFFF1, pid: 0x8000, @@ -91,6 +91,10 @@ async fn matter() -> Result<(), anyhow::Error> { product_name: "ACME Light", vendor_name: "ACME", }, + BasicCommData { + password: 20202021, + discriminator: 3840, + }, &DEV_ATT, )); @@ -137,12 +141,7 @@ async fn matter() -> Result<(), anyhow::Error> { // the Bluetooth / Wifi connections will be managed by the Matter stack itself // For finer-grained control, call `MatterStack::is_commissioned`, // `MatterStack::commission` and `MatterStack::operate` - EspModem::new(peripherals.modem, sysloop, timers, nvs, stack), - // Hard-coded for demo purposes - CommissioningData { - verifier: VerifierData::new_with_pw(123456, stack.matter().rand()), - discriminator: 250, - }, + EspMatterBle::new(peripherals.modem, sysloop, timers, nvs, stack), // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), // No user future to run @@ -179,7 +178,7 @@ async fn matter() -> Result<(), anyhow::Error> { /// The Matter stack is allocated statically to avoid /// program stack blowups. /// It is also a mandatory requirement when the `WifiBle` stack variation is used. -static MATTER_STACK: StaticCell> = StaticCell::new(); +static MATTER_STACK: StaticCell> = StaticCell::new(); static DEV_ATT: dev_att::HardCodedDevAtt = dev_att::HardCodedDevAtt::new(); @@ -191,7 +190,7 @@ const LIGHT_ENDPOINT_ID: u16 = 1; const NODE: Node = Node { id: 0, endpoints: &[ - EspWifiBleMatterStack::<()>::root_metadata(), + EspWifiMatterStack::<()>::root_metadata(), Endpoint { id: LIGHT_ENDPOINT_ID, device_type: DEV_TYPE_ON_OFF_LIGHT, diff --git a/examples/light_eth.rs b/examples/light_eth.rs index d74ce57..b6d00ce 100644 --- a/examples/light_eth.rs +++ b/examples/light_eth.rs @@ -101,6 +101,10 @@ async fn matter() -> Result<(), anyhow::Error> { product_name: "ACME Light", vendor_name: "ACME", }, + BasicCommData { + password: 20202021, + discriminator: 3840, + }, &DEV_ATT, )); @@ -163,11 +167,6 @@ async fn matter() -> Result<(), anyhow::Error> { // Since we are pretending to use a wired Ethernet connection - yet - // we are using a Wifi STA, provide the Wifi netif here EspMatterNetif::new(wifi.wifi().sta_netif(), sysloop), - // Hard-coded for demo purposes - CommissioningData { - verifier: VerifierData::new_with_pw(123456, stack.matter().rand()), - discriminator: 250, - }, // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), // No user future to run diff --git a/src/ble.rs b/src/ble.rs index 751f0fc..05099c3 100644 --- a/src/ble.rs +++ b/src/ble.rs @@ -1,10 +1,3 @@ -#![cfg(all( - not(esp_idf_btdm_ctrl_mode_br_edr_only), - esp_idf_bt_enabled, - esp_idf_bt_bluedroid_enabled, - not(esp32s2) -))] - use core::borrow::Borrow; use alloc::borrow::ToOwned; @@ -108,13 +101,13 @@ impl IndBuffer { /// The `'static` state of the `BtpGattPeripheral` struct. /// Isolated as a separate struct to allow for `const fn` construction /// and static allocation. -pub struct BtpGattContext { +pub struct EspBtpGattContext { state: Mutex>, ind: IfMutex, ind_in_flight: Signal, } -impl BtpGattContext { +impl EspBtpGattContext { /// Create a new instance. #[allow(clippy::large_stack_frames)] #[inline(always)] @@ -126,6 +119,7 @@ impl BtpGattContext { } } + /// Return an in-place initializer for `EspBtpGattContext`. #[allow(clippy::large_stack_frames)] pub fn init() -> impl Init { init!(Self { @@ -162,7 +156,7 @@ impl BtpGattContext { } } -impl Default for BtpGattContext { +impl Default for EspBtpGattContext { // TODO #[allow(clippy::large_stack_frames)] #[inline(always)] @@ -173,16 +167,16 @@ impl Default for BtpGattContext { /// A GATT peripheral implementation for the BTP protocol in `rs-matter`. /// Implements the `GattPeripheral` trait. -pub struct BtpGattPeripheral<'a, 'd, M> +pub struct EspBtpGattPeripheral<'a, 'd, M> where M: BleEnabled, { app_id: u16, driver: BtDriver<'d, M>, - context: &'a BtpGattContext, + context: &'a EspBtpGattContext, } -impl<'a, 'd, M> BtpGattPeripheral<'a, 'd, M> +impl<'a, 'd, M> EspBtpGattPeripheral<'a, 'd, M> where M: BleEnabled, { @@ -193,7 +187,7 @@ where pub fn new( app_id: u16, driver: BtDriver<'d, M>, - context: &'a BtpGattContext, + context: &'a EspBtpGattContext, ) -> Result { context.reset()?; @@ -299,7 +293,7 @@ where } } -impl<'a, 'd, M> GattPeripheral for BtpGattPeripheral<'a, 'd, M> +impl<'a, 'd, M> GattPeripheral for EspBtpGattPeripheral<'a, 'd, M> where M: BleEnabled, { @@ -312,7 +306,7 @@ where where F: FnMut(GattPeripheralEvent) + Send + Clone + 'static, { - BtpGattPeripheral::run(self, service_name, adv_data, callback) + EspBtpGattPeripheral::run(self, service_name, adv_data, callback) .await .map_err(|_| ErrorCode::BtpError)?; @@ -320,7 +314,7 @@ where } async fn indicate(&self, data: &[u8], address: BtAddr) -> Result<(), rs_matter::error::Error> { - BtpGattPeripheral::indicate(self, data, address) + EspBtpGattPeripheral::indicate(self, data, address) .await .map_err(|_| ErrorCode::BtpError)?; @@ -336,7 +330,7 @@ where app_id: u16, gap: &'a EspBleGap<'d, M, T>, gatts: &'a EspGatts<'d, M, T>, - ctx: &'a BtpGattContext, + ctx: &'a EspBtpGattContext, } impl<'a, 'd, M, T> GattExecContext<'a, 'd, M, T> @@ -348,7 +342,7 @@ where app_id: u16, gap: &'a EspBleGap<'d, M, T>, gatts: &'a EspGatts<'d, M, T>, - ctx: &'a BtpGattContext, + ctx: &'a EspBtpGattContext, ) -> Self { Self { app_id, diff --git a/src/stack/eth.rs b/src/eth.rs similarity index 100% rename from src/stack/eth.rs rename to src/eth.rs diff --git a/src/lib.rs b/src/lib.rs index 0a71214..aa970ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,8 +18,85 @@ extern crate std; extern crate alloc; #[cfg(feature = "rs-matter-stack")] -pub use stack::*; +pub use eth::*; +#[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, + feature = "std", + feature = "rs-matter-stack" +))] +pub use wireless::*; +#[cfg(all( + not(esp_idf_btdm_ctrl_mode_br_edr_only), + esp_idf_bt_enabled, + esp_idf_bt_bluedroid_enabled, + not(esp32s2) +))] pub mod ble; +#[cfg(feature = "rs-matter-stack")] +pub mod eth; +#[cfg(any(esp_idf_comp_mdns_enabled, esp_idf_comp_espressif__mdns_enabled))] pub mod mdns; -mod stack; +#[cfg(all( + esp_idf_comp_esp_netif_enabled, + esp_idf_comp_esp_event_enabled, + feature = "std" +))] +pub mod netif; +#[cfg(esp_idf_comp_nvs_flash_enabled)] +pub mod persist; +#[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, + feature = "std", + feature = "rs-matter-stack" +))] +pub mod wireless; + +/// A utility function to initialize the `async-io` Reactor which is +/// used for IP-based networks (UDP and TCP). +/// +/// User is expected to call this method early in the application's lifecycle +/// when there is plenty of task stack space available, as the initialization +/// consumes > 10KB of stack space, so it has to be done with care. +#[inline(never)] +#[cold] +#[cfg(feature = "std")] +pub fn init_async_io() -> Result<(), esp_idf_svc::sys::EspError> { + // We'll use `async-io` for networking, so ESP IDF VFS needs to be initialized + esp_idf_svc::io::vfs::initialize_eventfd(3)?; + + esp_idf_svc::hal::task::block_on(init_async_io_async()); + + Ok(()) +} + +#[inline(never)] +#[cold] +#[cfg(feature = "std")] +async fn init_async_io_async() { + #[cfg(not(feature = "async-io-mini"))] + { + // Force the `async-io` lazy initialization to trigger earlier rather than later, + // as it consumes a lot of temp stack memory + async_io::Timer::after(core::time::Duration::from_millis(100)).await; + ::log::info!("Async IO initialized; using `async-io`"); + } + + #[cfg(feature = "async-io-mini")] + { + // Nothing to initialize for `async-io-mini` + ::log::info!("Async IO initialized; using `async-io-mini`"); + } +} diff --git a/src/mdns.rs b/src/mdns.rs index 22fab38..817ccdd 100644 --- a/src/mdns.rs +++ b/src/mdns.rs @@ -1,5 +1,3 @@ -#![cfg(any(esp_idf_comp_mdns_enabled, esp_idf_comp_espressif__mdns_enabled))] - // /// A type representing the ESP IDF mDNS service. // pub struct EspIdfMdns(()); diff --git a/src/stack/netif.rs b/src/netif.rs similarity index 84% rename from src/stack/netif.rs rename to src/netif.rs index f10a9a6..113fca8 100644 --- a/src/stack/netif.rs +++ b/src/netif.rs @@ -1,9 +1,4 @@ -#![cfg(all( - esp_idf_comp_esp_netif_enabled, - esp_idf_comp_esp_event_enabled, - feature = "std" -))] - +use core::borrow::Borrow; use core::net::{Ipv4Addr, Ipv6Addr}; use core::pin::pin; @@ -29,25 +24,28 @@ use rs_matter_stack::netif::{Netif, NetifConf}; const TIMEOUT_PERIOD_SECS: u8 = 5; -pub struct EspMatterNetif<'a> { - netif: &'a EspNetif, +pub struct EspMatterNetif { + netif: T, sysloop: EspSystemEventLoop, } -impl<'a> EspMatterNetif<'a> { - pub const fn new(netif: &'a EspNetif, sysloop: EspSystemEventLoop) -> Self { +impl EspMatterNetif +where + T: Borrow, +{ + pub const fn new(netif: T, sysloop: EspSystemEventLoop) -> Self { Self { netif, sysloop } } fn get_conf(&self) -> Result { - Self::get_netif_conf(self.netif) + Self::get_netif_conf(self.netif.borrow()) } async fn wait_conf_change(&self) -> Result<(), EspError> { Self::wait_any_conf_change(&self.sysloop).await } - pub(crate) fn get_netif_conf(netif: &EspNetif) -> Result { + pub fn get_netif_conf(netif: &EspNetif) -> Result { let ip_info = netif.get_ip_info()?; let ipv4: Ipv4Addr = ip_info.ip.octets().into(); @@ -91,7 +89,7 @@ impl<'a> EspMatterNetif<'a> { }) } - pub(crate) async fn wait_any_conf_change(sysloop: &EspSystemEventLoop) -> Result<(), EspError> { + pub async fn wait_any_conf_change(sysloop: &EspSystemEventLoop) -> Result<(), EspError> { let notification = Arc::new(Notification::::new()); let _subscription = { @@ -111,7 +109,10 @@ impl<'a> EspMatterNetif<'a> { } } -impl<'a> Netif for EspMatterNetif<'a> { +impl Netif for EspMatterNetif +where + T: Borrow, +{ async fn get_conf(&self) -> Result, Error> { Ok(EspMatterNetif::get_conf(self).ok()) } @@ -125,7 +126,10 @@ impl<'a> Netif for EspMatterNetif<'a> { } } -impl<'a> UdpBind for EspMatterNetif<'a> { +impl UdpBind for EspMatterNetif +where + T: Borrow, +{ type Error = io::Error; type Socket<'b> = UdpSocket where Self: 'b; diff --git a/src/stack/persist.rs b/src/persist.rs similarity index 94% rename from src/stack/persist.rs rename to src/persist.rs index cf1a686..12c08ad 100644 --- a/src/stack/persist.rs +++ b/src/persist.rs @@ -1,5 +1,3 @@ -#![cfg(esp_idf_comp_nvs_flash_enabled)] - use esp_idf_svc::nvs::{EspNvs, EspNvsPartition, NvsPartitionId}; use esp_idf_svc::sys::EspError; @@ -9,7 +7,7 @@ use rs_matter::error::{Error, ErrorCode}; use rs_matter_stack::persist::{KvBlobStore, KvPersist}; -pub type EspPersist<'a, T, const N: usize, M> = KvPersist<'a, EspKvBlobStore, N, M>; +pub type EspMatterPersist<'a, T, C> = KvPersist<'a, EspKvBlobStore, C>; /// A `KvBlobStore`` implementation that uses the ESP IDF NVS API /// to store and load the BLOBs. diff --git a/src/stack.rs b/src/stack.rs deleted file mode 100644 index ebbe700..0000000 --- a/src/stack.rs +++ /dev/null @@ -1,64 +0,0 @@ -#![cfg(feature = "rs-matter-stack")] - -pub use eth::*; -#[cfg(all( - esp_idf_comp_esp_netif_enabled, - esp_idf_comp_esp_event_enabled, - feature = "std" -))] -pub use netif::*; -#[cfg(esp_idf_comp_nvs_flash_enabled)] -pub use persist::*; -#[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, - feature = "std" -))] -pub use wifible::*; - -mod eth; -mod netif; -mod persist; -mod wifible; - -/// A utility function to initialize the `async-io` Reactor which is -/// used for IP-based networks (UDP and TCP). -/// -/// User is expected to call this method early in the application's lifecycle -/// when there is plenty of task stack space available, as the initialization -/// consumes > 10KB of stack space, so it has to be done with care. -#[inline(never)] -#[cold] -#[cfg(feature = "std")] -pub fn init_async_io() -> Result<(), esp_idf_svc::sys::EspError> { - // We'll use `async-io` for networking, so ESP IDF VFS needs to be initialized - esp_idf_svc::io::vfs::initialize_eventfd(3)?; - - esp_idf_svc::hal::task::block_on(init_async_io_async()); - - Ok(()) -} - -#[inline(never)] -#[cold] -#[cfg(feature = "std")] -async fn init_async_io_async() { - #[cfg(not(feature = "async-io-mini"))] - { - // Force the `async-io` lazy initialization to trigger earlier rather than later, - // as it consumes a lot of temp stack memory - async_io::Timer::after(core::time::Duration::from_millis(100)).await; - ::log::info!("Async IO initialized; using `async-io`"); - } - - #[cfg(feature = "async-io-mini")] - { - // Nothing to initialize for `async-io-mini` - ::log::info!("Async IO initialized; using `async-io-mini`"); - } -} diff --git a/src/stack/wifible.rs b/src/stack/wifible.rs deleted file mode 100644 index b49d6dd..0000000 --- a/src/stack/wifible.rs +++ /dev/null @@ -1,290 +0,0 @@ -#![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, - feature = "std" -))] - -use core::net::SocketAddr; - -use std::io; - -use alloc::boxed::Box; - -use edge_nal::UdpBind; -use edge_nal_std::{Stack, UdpSocket}; - -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_sync::mutex::Mutex; - -use embedded_svc::wifi::asynch::Wifi; - -use enumset::EnumSet; - -use esp_idf_svc::bt::{Ble, BtDriver}; -use esp_idf_svc::eventloop::EspSystemEventLoop; -use esp_idf_svc::hal::peripheral::{Peripheral, PeripheralRef}; -use esp_idf_svc::hal::task::embassy_sync::EspRawMutex; -use esp_idf_svc::hal::{into_ref, modem}; -use esp_idf_svc::handle::RawHandle; -use esp_idf_svc::nvs::EspDefaultNvsPartition; -use esp_idf_svc::sys::{esp, esp_netif_create_ip6_linklocal, EspError}; -use esp_idf_svc::timer::EspTaskTimerService; -use esp_idf_svc::wifi::{AccessPointInfo, AsyncWifi, Capability, Configuration, EspWifi}; - -use rs_matter::error::{Error, ErrorCode}; -use rs_matter::utils::init::{init, Init}; - -use rs_matter_stack::modem::{Modem, WifiDevice}; -use rs_matter_stack::netif::{Netif, NetifConf}; -use rs_matter_stack::network::{Embedding, Network}; -use rs_matter_stack::persist::KvBlobBuf; -use rs_matter_stack::{MatterStack, WifiBle}; - -use crate::ble::{BtpGattContext, BtpGattPeripheral}; - -use super::netif::EspMatterNetif; - -pub type EspWifiBleMatterStack<'a, E> = MatterStack<'a, EspWifiBle>; -pub type EspWifiBle = WifiBle>>; - -/// An embedding of the ESP IDF Bluedroid Gatt peripheral context for the `WifiBle` network type from `rs-matter-stack`. -/// Allows the memory of this context to be statically allocated and cost-initialized. -/// -/// Usage: -/// ```no_run -/// MatterStack>>::new(); -/// ``` -/// -/// ... where `E` can be a next-level, user-supplied embedding or just `()` if the user does not need to embed anything. -pub struct EspGatt { - btp_gatt_context: BtpGattContext, - embedding: E, -} - -impl EspGatt -where - E: Embedding, -{ - #[allow(clippy::large_stack_frames)] - #[inline(always)] - const fn new() -> Self { - Self { - btp_gatt_context: BtpGattContext::new(), - embedding: E::INIT, - } - } - - fn init() -> impl Init { - init!(Self { - btp_gatt_context <- BtpGattContext::init(), - embedding <- E::init(), - }) - } - - pub fn context(&self) -> &BtpGattContext { - &self.btp_gatt_context - } - - pub fn embedding(&self) -> &E { - &self.embedding - } -} - -impl Embedding for EspGatt -where - E: Embedding, -{ - const INIT: Self = Self::new(); - - fn init() -> impl Init { - EspGatt::init() - } -} - -const GATTS_APP_ID: u16 = 0; - -pub struct EspModem<'a, 'd> { - context: &'a BtpGattContext, - modem: PeripheralRef<'d, modem::Modem>, - sysloop: EspSystemEventLoop, - timers: EspTaskTimerService, - nvs: EspDefaultNvsPartition, -} - -impl<'a, 'd> EspModem<'a, 'd> { - pub fn new( - modem: impl Peripheral

+ 'd, - sysloop: EspSystemEventLoop, - timers: EspTaskTimerService, - nvs: EspDefaultNvsPartition, - stack: &'a EspWifiBleMatterStack, - ) -> Self - where - E: Embedding + 'static, - { - Self::wrap( - modem, - sysloop, - timers, - nvs, - stack.network().embedding().embedding().context(), - ) - } - - pub fn wrap( - modem: impl Peripheral

+ 'd, - sysloop: EspSystemEventLoop, - timers: EspTaskTimerService, - nvs: EspDefaultNvsPartition, - context: &'a BtpGattContext, - ) -> Self { - into_ref!(modem); - - Self { - context, - modem, - sysloop, - timers, - nvs, - } - } -} - -impl<'a, 'd> Modem for EspModem<'a, 'd> { - type BleDevice<'t> = BtpGattPeripheral<'t, 't, Ble> where Self: 't; - - type WifiDevice<'t> = EspWifiDevice<'t> where Self: 't; - - async fn ble(&mut self) -> Self::BleDevice<'_> { - let bt = BtDriver::new(&mut self.modem, Some(self.nvs.clone())).unwrap(); - - let peripheral = BtpGattPeripheral::new(GATTS_APP_ID, bt, self.context).unwrap(); - - peripheral - } - - async fn wifi(&mut self) -> Self::WifiDevice<'_> { - EspWifiDevice { - sysloop: self.sysloop.clone(), - wifi: Mutex::new(Box::new( - AsyncWifi::wrap( - EspWifi::new( - &mut self.modem, - self.sysloop.clone(), - Some(self.nvs.clone()), - ) - .unwrap(), - self.sysloop.clone(), - self.timers.clone(), - ) - .unwrap(), - )), - } - } -} - -pub struct EspWifiDevice<'d> { - sysloop: EspSystemEventLoop, - wifi: Mutex>>>, -} - -impl<'d> WifiDevice for EspWifiDevice<'d> { - type L2<'t> = EspL2<'t, 'd> where Self: 't; - - type L3<'t> = EspL3<'t, 'd> where Self: 't; - - async fn split(&mut self) -> (Self::L2<'_>, Self::L3<'_>) { - (EspL2(self), EspL3(self)) - } -} - -pub struct EspL2<'a, 'd>(&'a EspWifiDevice<'d>); - -impl<'a, 'd> Wifi for EspL2<'a, 'd> { - type Error = EspError; - - async fn get_capabilities(&self) -> Result, Self::Error> { - self.0.wifi.lock().await.get_capabilities() - } - - async fn get_configuration(&self) -> Result { - self.0.wifi.lock().await.get_configuration() - } - - async fn set_configuration(&mut self, conf: &Configuration) -> Result<(), Self::Error> { - self.0.wifi.lock().await.set_configuration(conf) - } - - async fn start(&mut self) -> Result<(), Self::Error> { - self.0.wifi.lock().await.start().await - } - - async fn stop(&mut self) -> Result<(), Self::Error> { - self.0.wifi.lock().await.stop().await - } - - async fn connect(&mut self) -> Result<(), Self::Error> { - let mut wifi = self.0.wifi.lock().await; - - wifi.connect().await?; - - esp!(unsafe { esp_netif_create_ip6_linklocal(wifi.wifi().sta_netif().handle() as _) })?; - - Ok(()) - } - - async fn disconnect(&mut self) -> Result<(), Self::Error> { - self.0.wifi.lock().await.disconnect().await - } - - async fn is_started(&self) -> Result { - self.0.wifi.lock().await.is_started() - } - - async fn is_connected(&self) -> Result { - self.0.wifi.lock().await.is_connected() - } - - async fn scan_n( - &mut self, - ) -> Result<(heapless::Vec, usize), Self::Error> { - self.0.wifi.lock().await.scan_n().await - } - - async fn scan(&mut self) -> Result, Self::Error> { - self.0.wifi.lock().await.scan().await - } -} - -pub struct EspL3<'a, 'd>(&'a EspWifiDevice<'d>); - -impl<'a, 'd> Netif for EspL3<'a, 'd> { - async fn get_conf(&self) -> Result, Error> { - let wifi = self.0.wifi.lock().await; - - Ok(EspMatterNetif::get_netif_conf(wifi.wifi().sta_netif()).ok()) - } - - async fn wait_conf_change(&self) -> Result<(), Error> { - EspMatterNetif::wait_any_conf_change(&self.0.sysloop) - .await - .map_err(|_| ErrorCode::NoNetworkInterface)?; // TODO - - Ok(()) - } -} - -impl<'a, 'd> UdpBind for EspL3<'a, 'd> { - type Error = io::Error; - - type Socket<'t> = UdpSocket where Self: 't; - - async fn bind(&self, local: SocketAddr) -> Result, Self::Error> { - Stack::new().bind(local).await - } -} diff --git a/src/wireless.rs b/src/wireless.rs new file mode 100644 index 0000000..02f9b82 --- /dev/null +++ b/src/wireless.rs @@ -0,0 +1,339 @@ +use std::io; + +use alloc::sync::Arc; + +use edge_nal::UdpBind; +use edge_nal_std::{Stack, UdpSocket}; + +use embassy_sync::mutex::Mutex; + +use embedded_svc::wifi::asynch::Wifi; + +use enumset::EnumSet; + +use esp_idf_svc::bt::{self, BtDriver}; +use esp_idf_svc::eventloop::EspSystemEventLoop; +use esp_idf_svc::hal::into_ref; +use esp_idf_svc::hal::modem::{BluetoothModemPeripheral, WifiModemPeripheral}; +use esp_idf_svc::hal::peripheral::{Peripheral, PeripheralRef}; +use esp_idf_svc::hal::task::embassy_sync::EspRawMutex; +use esp_idf_svc::nvs::EspDefaultNvsPartition; +use esp_idf_svc::sys::EspError; +use esp_idf_svc::timer::EspTaskTimerService; +use esp_idf_svc::wifi::{AccessPointInfo, AsyncWifi, Capability, Configuration, EspWifi}; + +use rs_matter::error::{Error, ErrorCode}; +use rs_matter::tlv::{FromTLV, ToTLV}; +use rs_matter::utils::init::{init, Init}; + +use rs_matter_stack::netif::{Netif, NetifConf, NetifRun}; +use rs_matter_stack::network::{Embedding, Network}; +use rs_matter_stack::persist::KvBlobBuf; +use rs_matter_stack::wireless::svc::SvcWifiController; +use rs_matter_stack::wireless::traits::{ + Ble, ThreadCredentials, WifiCredentials, WifiData, Wireless, WirelessConfig, WirelessData, +}; +use rs_matter_stack::{MatterStack, WirelessBle}; + +use crate::ble::{EspBtpGattContext, EspBtpGattPeripheral}; + +use super::netif::EspMatterNetif; + +pub type EspWifiMatterStack<'a, E> = EspWirelessMatterStack<'a, WifiCredentials, E>; +pub type EspThreadMatterStack<'a, E> = EspWirelessMatterStack<'a, ThreadCredentials, E>; + +pub type EspWirelessMatterStack<'a, T, E> = MatterStack<'a, EspWirelessBle>; +pub type EspWirelessBle = WirelessBle>>; + +/// An embedding of the ESP IDF Bluedroid Gatt peripheral context for the `WifiBle` network type from `rs-matter-stack`. +/// Allows the memory of this context to be statically allocated and cost-initialized. +/// +/// Usage: +/// ```no_run +/// MatterStack>>::new(); +/// ``` +/// +/// ... where `E` can be a next-level, user-supplied embedding or just `()` if the user does not need to embed anything. +pub struct EspGatt { + btp_gatt_context: EspBtpGattContext, + embedding: E, +} + +impl EspGatt +where + E: Embedding, +{ + /// Creates a new instance of the `EspGatt` embedding. + #[allow(clippy::large_stack_frames)] + #[inline(always)] + const fn new() -> Self { + Self { + btp_gatt_context: EspBtpGattContext::new(), + embedding: E::INIT, + } + } + + /// Return an in-place initializer for the `EspGatt` embedding. + fn init() -> impl Init { + init!(Self { + btp_gatt_context <- EspBtpGattContext::init(), + embedding <- E::init(), + }) + } + + /// Return a reference to the Bluedroid Gatt peripheral context. + pub fn context(&self) -> &EspBtpGattContext { + &self.btp_gatt_context + } + + /// Return a reference to the embedding. + pub fn embedding(&self) -> &E { + &self.embedding + } +} + +impl Embedding for EspGatt +where + E: Embedding, +{ + const INIT: Self = Self::new(); + + fn init() -> impl Init { + EspGatt::init() + } +} + +const GATTS_APP_ID: u16 = 0; + +/// A `Ble` trait implementation +pub struct EspMatterBle<'a, 'd, T> { + context: &'a EspBtpGattContext, + modem: PeripheralRef<'d, T>, + nvs: EspDefaultNvsPartition, +} + +impl<'a, 'd, T> EspMatterBle<'a, 'd, T> +where + T: BluetoothModemPeripheral, +{ + /// Create a new instance of the `EspBle` type. + pub fn new( + modem: impl Peripheral

+ 'd, + nvs: EspDefaultNvsPartition, + stack: &'a EspWirelessMatterStack, + ) -> Self + where + C: WirelessConfig, + ::NetworkCredentials: Clone + for<'t> FromTLV<'t> + ToTLV, + E: Embedding + 'static, + { + Self::wrap( + modem, + nvs, + stack.network().embedding().embedding().context(), + ) + } + + /// Wrap an existing `EspBtpGattContext` and `BluetoothModemPeripheral` into a new instance of the `EspBle` type. + pub fn wrap( + modem: impl Peripheral

+ 'd, + nvs: EspDefaultNvsPartition, + context: &'a EspBtpGattContext, + ) -> Self { + into_ref!(modem); + + Self { + context, + modem, + nvs, + } + } +} + +impl<'a, 'd, T> Ble for EspMatterBle<'a, 'd, T> +where + T: BluetoothModemPeripheral, +{ + type Peripheral<'t> = EspBtpGattPeripheral<'a, 't, bt::Ble> where Self: 't; + + async fn start(&mut self) -> Result, Error> { + let bt = BtDriver::new(&mut self.modem, Some(self.nvs.clone())).unwrap(); + + let peripheral = EspBtpGattPeripheral::new(GATTS_APP_ID, bt, self.context).unwrap(); + + Ok(peripheral) + } +} + +/// The relation between a network interface and a controller is slightly different +/// in the ESP-IDF crates compared to what `rs-matter-stack` wants, hence we need this helper type. +pub struct EspWifiSplit<'a>( + Arc>>>, + EspSystemEventLoop, +); + +impl<'a> Wifi for EspWifiSplit<'a> { + type Error = EspError; + + async fn get_capabilities(&self) -> Result, Self::Error> { + let wifi = self.0.lock().await; + + wifi.get_capabilities() + } + + async fn get_configuration(&self) -> Result { + let wifi = self.0.lock().await; + + wifi.get_configuration() + } + + async fn set_configuration(&mut self, conf: &Configuration) -> Result<(), Self::Error> { + let mut wifi = self.0.lock().await; + + wifi.set_configuration(conf) + } + + async fn start(&mut self) -> Result<(), Self::Error> { + let mut wifi = self.0.lock().await; + + wifi.start().await + } + + async fn stop(&mut self) -> Result<(), Self::Error> { + let mut wifi = self.0.lock().await; + + wifi.stop().await + } + + async fn connect(&mut self) -> Result<(), Self::Error> { + let mut wifi = self.0.lock().await; + + wifi.connect().await + } + + async fn disconnect(&mut self) -> Result<(), Self::Error> { + let mut wifi = self.0.lock().await; + + wifi.disconnect().await + } + + async fn is_started(&self) -> Result { + let wifi = self.0.lock().await; + + wifi.is_started() + } + + async fn is_connected(&self) -> Result { + let wifi = self.0.lock().await; + + wifi.is_connected() + } + + async fn scan_n( + &mut self, + ) -> Result<(heapless::Vec, usize), Self::Error> { + let mut wifi = self.0.lock().await; + + wifi.scan_n().await + } + + async fn scan(&mut self) -> Result, Self::Error> { + let mut wifi = self.0.lock().await; + + wifi.scan().await + } +} + +impl<'a> Netif for EspWifiSplit<'a> { + async fn get_conf(&self) -> Result, Error> { + let wifi = self.0.lock().await; + + EspMatterNetif::new(wifi.wifi().sta_netif(), self.1.clone()) + .get_conf() + .await + } + + async fn wait_conf_change(&self) -> Result<(), Error> { + let wifi = self.0.lock().await; + + EspMatterNetif::new(wifi.wifi().sta_netif(), self.1.clone()) + .wait_conf_change() + .await + } +} + +impl<'a> NetifRun for EspWifiSplit<'a> { + async fn run(&self) -> Result<(), Error> { + core::future::pending().await + } +} + +impl<'a> UdpBind for EspWifiSplit<'a> { + type Error = io::Error; + type Socket<'b> = UdpSocket where Self: 'b; + + async fn bind(&self, local: core::net::SocketAddr) -> Result, Self::Error> { + Stack::new().bind(local).await + } +} + +pub struct EspMatterWifi<'d, T> { + modem: PeripheralRef<'d, T>, + sysloop: EspSystemEventLoop, + timer: EspTaskTimerService, + nvs: EspDefaultNvsPartition, +} + +impl<'d, T> EspMatterWifi<'d, T> +where + T: WifiModemPeripheral, +{ + /// Create a new instance of the `EspBle` type. + pub fn new( + modem: impl Peripheral

+ 'd, + sysloop: EspSystemEventLoop, + timer: EspTaskTimerService, + nvs: EspDefaultNvsPartition, + ) -> Self { + into_ref!(modem); + + Self { + modem, + sysloop, + timer, + nvs, + } + } +} + +impl<'d, T> Wireless for EspMatterWifi<'d, T> +where + T: WifiModemPeripheral, +{ + type Data = WifiData; + + type Netif<'a> = EspWifiSplit<'a> where Self: 'a; + + type Controller<'a> = SvcWifiController> where Self: 'a; + + async fn start(&mut self) -> Result<(Self::Netif<'_>, Self::Controller<'_>), Error> { + let wifi = Arc::new(Mutex::new( + AsyncWifi::wrap( + EspWifi::new( + &mut self.modem, + self.sysloop.clone(), + Some(self.nvs.clone()), + ) + .map_err(|_| ErrorCode::NoNetworkInterface)?, // TODO + self.sysloop.clone(), + self.timer.clone(), + ) + .map_err(|_| ErrorCode::NoNetworkInterface)?, // TODO + )); + + Ok(( + EspWifiSplit(wifi.clone(), self.sysloop.clone()), + SvcWifiController::new(EspWifiSplit(wifi.clone(), self.sysloop.clone())), + )) + } +} From dc427bdb5ca108b8447f46e9646cbab93d5a4964 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Sat, 5 Oct 2024 15:07:39 +0000 Subject: [PATCH 07/13] Concurrent commissioning --- Cargo.toml | 6 +++--- examples/light.rs | 27 ++++++++++++++----------- examples/light_eth.rs | 18 ++++++++--------- src/ble.rs | 4 ++-- src/netif.rs | 4 ++++ src/wireless.rs | 46 ++++++++++++++++++++++++++++++++++--------- 6 files changed, 70 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d5f1985..a80bbc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,8 +18,8 @@ rust-version = "1.78" #[patch.'https://github.com/ivmarkov/async-io-mini'] #async-io-mini = { path = "../async-io-mini" } -[patch.'https://github.com/ivmarkov/rs-matter-stack'] -rs-matter-stack = { path = "../rs-matter-stack" } +#[patch.'https://github.com/ivmarkov/rs-matter-stack'] +#rs-matter-stack = { path = "../rs-matter-stack" } [patch.crates-io] rs-matter = { git = "https://github.com/ivmarkov/rs-matter" } @@ -55,7 +55,7 @@ esp-idf-svc = { version = "0.49.1", default-features = false, features = ["alloc embedded-svc = { version = "0.28", default-features = false } rs-matter = { version = "0.1", default-features = false, features = ["rustcrypto"] } async-io = { version = "=2.0.0", default-features = false } # Workaround for https://github.com/smol-rs/async-lock/issues/84 -rs-matter-stack = { git = "https://github.com/ivmarkov/rs-matter-stack", branch = "in-place-init", default-features = false, features = ["std"], optional = true } +rs-matter-stack = { git = "https://github.com/ivmarkov/rs-matter-stack", branch = "thread", default-features = false, features = ["std"], optional = true } edge-nal = "0.3" edge-nal-std = { version = "0.3", default-features = false, optional = true } diff --git a/examples/light.rs b/examples/light.rs index 6b7e911..e707e42 100644 --- a/examples/light.rs +++ b/examples/light.rs @@ -1,7 +1,12 @@ -//! An example utilizing the `EspWifiBleMatterStack` struct. +//! An example utilizing the `EspNCWifiMatterStack` struct. +//! //! As the name suggests, this Matter stack assembly uses Wifi as the main transport, -//! and BLE for commissioning. +//! (and thus BLE for commissioning), where `NC` stands for non-concurrent commisisoning +//! (i.e., the stack will not run the BLE and Wifi radio simultaneously, which saves memory). +//! //! If you want to use Ethernet, utilize `EspEthMatterStack` instead. +//! If you want to use concurrent commissioning, utilize `EspWifiMatterStack` instead +//! (Alexa does not work (yet) with non-concurrent commissioning). //! //! The example implements a fictitious Light device (an On-Off Matter cluster). @@ -10,7 +15,7 @@ use core::pin::pin; use embassy_futures::select::select; use embassy_time::{Duration, Timer}; -use esp_idf_matter::{init_async_io, EspKvBlobStore, EspMatterBle, EspPersist, EspWifiMatterStack}; +use esp_idf_matter::{init_async_io, EspMatterBle, EspMatterWifi, EspWifiMatterStack}; use esp_idf_svc::eventloop::EspSystemEventLoop; use esp_idf_svc::hal::peripherals::Peripherals; @@ -26,11 +31,10 @@ 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::{Dataver, Endpoint, HandlerCompat, Node}; use rs_matter::data_model::system_model::descriptor; -use rs_matter::secure_channel::spake2p::VerifierData; use rs_matter::utils::init::InitMaybeUninit; use rs_matter::utils::select::Coalesce; -use rs_matter::CommissioningData; +use rs_matter::BasicCommData; use rs_matter_stack::persist::DummyPersist; use static_cell::StaticCell; @@ -128,20 +132,21 @@ async fn matter() -> Result<(), anyhow::Error> { ))), ); + let (mut wifi_modem, mut bt_modem) = peripherals.modem.split(); + // 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 the Wifi modem peripheral + EspMatterWifi::new(&mut wifi_modem, sysloop, timers, nvs.clone()), + // The Matter stack needs the BT modem peripheral + EspMatterBle::new(&mut bt_modem, nvs, stack), // The Matter stack needs a persister to store its state // `EspPersist`+`EspKvBlobStore` saves to a user-supplied NVS partition // under namespace `esp-idf-matter` DummyPersist, //EspPersist::new_wifi_ble(EspKvBlobStore::new_default(nvs.clone())?, stack), - // The Matter stack needs the BT/Wifi modem peripheral - and in general - - // the Bluetooth / Wifi connections will be managed by the Matter stack itself - // For finer-grained control, call `MatterStack::is_commissioned`, - // `MatterStack::commission` and `MatterStack::operate` - EspMatterBle::new(peripherals.modem, sysloop, timers, nvs, stack), // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), // No user future to run @@ -193,7 +198,7 @@ const NODE: Node = Node { EspWifiMatterStack::<()>::root_metadata(), Endpoint { id: LIGHT_ENDPOINT_ID, - device_type: DEV_TYPE_ON_OFF_LIGHT, + device_types: &[DEV_TYPE_ON_OFF_LIGHT], clusters: &[descriptor::CLUSTER, cluster_on_off::CLUSTER], }, ], diff --git a/examples/light_eth.rs b/examples/light_eth.rs index b6d00ce..b347f79 100644 --- a/examples/light_eth.rs +++ b/examples/light_eth.rs @@ -12,9 +12,8 @@ use core::pin::pin; use embassy_futures::select::select; use embassy_time::{Duration, Timer}; -use esp_idf_matter::{ - init_async_io, EspEthMatterStack, EspKvBlobStore, EspMatterNetif, EspPersist, -}; +use esp_idf_matter::netif::EspMatterNetif; +use esp_idf_matter::{init_async_io, EspEthMatterStack}; use esp_idf_svc::eventloop::EspSystemEventLoop; use esp_idf_svc::hal::peripherals::Peripherals; @@ -33,11 +32,10 @@ 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::{Dataver, Endpoint, HandlerCompat, Node}; use rs_matter::data_model::system_model::descriptor; -use rs_matter::secure_channel::spake2p::VerifierData; use rs_matter::utils::init::InitMaybeUninit; use rs_matter::utils::select::Coalesce; -use rs_matter::CommissioningData; +use rs_matter::BasicCommData; use rs_matter_stack::persist::DummyPersist; use static_cell::StaticCell; @@ -158,15 +156,15 @@ async fn matter() -> Result<(), anyhow::Error> { // 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 need access to the netif on which we'll operate + // Since we are pretending to use a wired Ethernet connection - yet - + // we are using a Wifi STA, provide the Wifi netif here + EspMatterNetif::new(wifi.wifi().sta_netif(), sysloop), // The Matter stack needs a persister to store its state // `EspPersist`+`EspKvBlobStore` saves to a user-supplied NVS partition // under namespace `esp-idf-matter` DummyPersist, //EspPersist::new_eth(EspKvBlobStore::new_default(nvs.clone())?, stack), - // The Matter stack need access to the netif on which we'll operate - // Since we are pretending to use a wired Ethernet connection - yet - - // we are using a Wifi STA, provide the Wifi netif here - EspMatterNetif::new(wifi.wifi().sta_netif(), sysloop), // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), // No user future to run @@ -217,7 +215,7 @@ const NODE: Node = Node { EspEthMatterStack::<()>::root_metadata(), Endpoint { id: LIGHT_ENDPOINT_ID, - device_type: DEV_TYPE_ON_OFF_LIGHT, + device_types: &[DEV_TYPE_ON_OFF_LIGHT], clusters: &[descriptor::CLUSTER, cluster_on_off::CLUSTER], }, ], diff --git a/src/ble.rs b/src/ble.rs index 05099c3..bfd6a44 100644 --- a/src/ble.rs +++ b/src/ble.rs @@ -98,7 +98,7 @@ impl IndBuffer { } } -/// The `'static` state of the `BtpGattPeripheral` struct. +/// The `'static` state of the `EspBtpGattPeripheral` struct. /// Isolated as a separate struct to allow for `const fn` construction /// and static allocation. pub struct EspBtpGattContext { @@ -165,7 +165,7 @@ impl Default for EspBtpGattContext { } } -/// A GATT peripheral implementation for the BTP protocol in `rs-matter`. +/// A GATT peripheral implementation for the BTP protocol in `rs-matter` via ESP-IDF. /// Implements the `GattPeripheral` trait. pub struct EspBtpGattPeripheral<'a, 'd, M> where diff --git a/src/netif.rs b/src/netif.rs index 113fca8..92aeb87 100644 --- a/src/netif.rs +++ b/src/netif.rs @@ -24,6 +24,7 @@ use rs_matter_stack::netif::{Netif, NetifConf}; const TIMEOUT_PERIOD_SECS: u8 = 5; +/// A `Netif` and `UdpBind` traits implementation via ESP-IDF pub struct EspMatterNetif { netif: T, sysloop: EspSystemEventLoop, @@ -33,6 +34,7 @@ impl EspMatterNetif where T: Borrow, { + /// Create a new `EspMatterNetif` instance pub const fn new(netif: T, sysloop: EspSystemEventLoop) -> Self { Self { netif, sysloop } } @@ -45,6 +47,7 @@ where Self::wait_any_conf_change(&self.sysloop).await } + /// Get the network interface configuration pub fn get_netif_conf(netif: &EspNetif) -> Result { let ip_info = netif.get_ip_info()?; @@ -89,6 +92,7 @@ where }) } + /// Wait for any IP configuration change pub async fn wait_any_conf_change(sysloop: &EspSystemEventLoop) -> Result<(), EspError> { let notification = Arc::new(Notification::::new()); diff --git a/src/wireless.rs b/src/wireless.rs index 02f9b82..0ee2f56 100644 --- a/src/wireless.rs +++ b/src/wireless.rs @@ -7,7 +7,7 @@ use edge_nal_std::{Stack, UdpSocket}; use embassy_sync::mutex::Mutex; -use embedded_svc::wifi::asynch::Wifi; +use embedded_svc::wifi::asynch::Wifi as WifiSvc; use enumset::EnumSet; @@ -31,7 +31,7 @@ use rs_matter_stack::network::{Embedding, Network}; use rs_matter_stack::persist::KvBlobBuf; use rs_matter_stack::wireless::svc::SvcWifiController; use rs_matter_stack::wireless::traits::{ - Ble, ThreadCredentials, WifiCredentials, WifiData, Wireless, WirelessConfig, WirelessData, + Ble, Thread, Wifi, WifiData, Wireless, WirelessConfig, WirelessData, NC, }; use rs_matter_stack::{MatterStack, WirelessBle}; @@ -39,18 +39,45 @@ use crate::ble::{EspBtpGattContext, EspBtpGattPeripheral}; use super::netif::EspMatterNetif; -pub type EspWifiMatterStack<'a, E> = EspWirelessMatterStack<'a, WifiCredentials, E>; -pub type EspThreadMatterStack<'a, E> = EspWirelessMatterStack<'a, ThreadCredentials, E>; +/// A type alias for an ESP-IDF Matter stack running over Wifi (and BLE, during commissioning). +pub type EspWifiMatterStack<'a, E> = EspWirelessMatterStack<'a, Wifi, E>; +/// A type alias for an ESP-IDF Matter stack running over Thread (and BLE, during commissioning). +pub type EspThreadMatterStack<'a, E> = EspWirelessMatterStack<'a, Thread, E>; + +/// A type alias for an ESP-IDF Matter stack running over Wifi (and BLE, during commissioning). +/// +/// Unlike `EspWifiMatterStack`, this type alias runs the commissioning in a non-concurrent mode, +/// where the device runs either BLE or Wifi, but not both at the same time. +/// +/// This is useful to save memory by only having one of the stacks active at any point in time. +/// +/// Note that Alexa does not (yet) work with non-concurrent commissioning. +pub type EspWifiNCMatterStack<'a, E> = EspWirelessMatterStack<'a, Wifi, E>; + +/// A type alias for an ESP-IDF Matter stack running over Thread (and BLE, during commissioning). +/// +/// Unlike `EspThreadMatterStack`, this type alias runs the commissioning in a non-concurrent mode, +/// where the device runs either BLE or Thread, but not both at the same time. +/// +/// This is useful to save memory by only having one of the stacks active at any point in time. +/// +/// Note that Alexa does not (yet) work with non-concurrent commissioning. +pub type EspThreadNCMatterStack<'a, E> = EspWirelessMatterStack<'a, Thread, E>; + +/// A type alias for an ESP-IDF Matter stack running over a wireless network (Wifi or Thread) and BLE. pub type EspWirelessMatterStack<'a, T, E> = MatterStack<'a, EspWirelessBle>; + +/// A type alias for an ESP-IDF implementation of the `Network` trait for a Matter stack running over +/// BLE during commissioning, and then over either WiFi or Thread when operating. pub type EspWirelessBle = WirelessBle>>; -/// An embedding of the ESP IDF Bluedroid Gatt peripheral context for the `WifiBle` network type from `rs-matter-stack`. +/// An embedding of the ESP IDF Bluedroid Gatt peripheral context for the `WirelessBle` network type from `rs-matter-stack`. /// Allows the memory of this context to be statically allocated and cost-initialized. /// /// Usage: /// ```no_run -/// MatterStack>>::new(); +/// MatterStack>>>::new(...); /// ``` /// /// ... where `E` can be a next-level, user-supplied embedding or just `()` if the user does not need to embed anything. @@ -105,7 +132,7 @@ where const GATTS_APP_ID: u16 = 0; -/// A `Ble` trait implementation +/// A `Ble` trait implementation via ESP-IDF pub struct EspMatterBle<'a, 'd, T> { context: &'a EspBtpGattContext, modem: PeripheralRef<'d, T>, @@ -172,7 +199,7 @@ pub struct EspWifiSplit<'a>( EspSystemEventLoop, ); -impl<'a> Wifi for EspWifiSplit<'a> { +impl<'a> WifiSvc for EspWifiSplit<'a> { type Error = EspError; async fn get_capabilities(&self) -> Result, Self::Error> { @@ -277,6 +304,7 @@ impl<'a> UdpBind for EspWifiSplit<'a> { } } +/// A `Wireless` trait implementation via ESP-IDF's Wifi modem pub struct EspMatterWifi<'d, T> { modem: PeripheralRef<'d, T>, sysloop: EspSystemEventLoop, @@ -288,7 +316,7 @@ impl<'d, T> EspMatterWifi<'d, T> where T: WifiModemPeripheral, { - /// Create a new instance of the `EspBle` type. + /// Create a new instance of the `EspMatterWifi` type. pub fn new( modem: impl Peripheral

+ 'd, sysloop: EspSystemEventLoop, From 6b133b5a59a92b366726754855f4c7fee3a7d3a8 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Sat, 5 Oct 2024 15:09:18 +0000 Subject: [PATCH 08/13] fmt --- src/netif.rs | 5 ++++- src/wireless.rs | 20 ++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/netif.rs b/src/netif.rs index 92aeb87..31b3816 100644 --- a/src/netif.rs +++ b/src/netif.rs @@ -135,7 +135,10 @@ where T: Borrow, { type Error = io::Error; - type Socket<'b> = UdpSocket where Self: 'b; + type Socket<'b> + = UdpSocket + where + Self: 'b; async fn bind(&self, local: core::net::SocketAddr) -> Result, Self::Error> { Stack::new().bind(local).await diff --git a/src/wireless.rs b/src/wireless.rs index 0ee2f56..7e77711 100644 --- a/src/wireless.rs +++ b/src/wireless.rs @@ -181,7 +181,10 @@ impl<'a, 'd, T> Ble for EspMatterBle<'a, 'd, T> where T: BluetoothModemPeripheral, { - type Peripheral<'t> = EspBtpGattPeripheral<'a, 't, bt::Ble> where Self: 't; + type Peripheral<'t> + = EspBtpGattPeripheral<'a, 't, bt::Ble> + where + Self: 't; async fn start(&mut self) -> Result, Error> { let bt = BtDriver::new(&mut self.modem, Some(self.nvs.clone())).unwrap(); @@ -297,7 +300,10 @@ impl<'a> NetifRun for EspWifiSplit<'a> { impl<'a> UdpBind for EspWifiSplit<'a> { type Error = io::Error; - type Socket<'b> = UdpSocket where Self: 'b; + type Socket<'b> + = UdpSocket + where + Self: 'b; async fn bind(&self, local: core::net::SocketAddr) -> Result, Self::Error> { Stack::new().bind(local).await @@ -340,9 +346,15 @@ where { type Data = WifiData; - type Netif<'a> = EspWifiSplit<'a> where Self: 'a; + type Netif<'a> + = EspWifiSplit<'a> + where + Self: 'a; - type Controller<'a> = SvcWifiController> where Self: 'a; + type Controller<'a> + = SvcWifiController> + where + Self: 'a; async fn start(&mut self) -> Result<(Self::Netif<'_>, Self::Controller<'_>), Error> { let wifi = Arc::new(Mutex::new( From f9cf8e3493cc088383424dcb9db5cd99aec00a69 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Sat, 5 Oct 2024 15:09:50 +0000 Subject: [PATCH 09/13] Update example --- README.md | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index f1b5307..1001977 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,15 @@ Since ESP-IDF does support the Rust Standard Library, UDP networking just works. (See also [All examples](#all-examples)) ```rust -//! An example utilizing the `EspWifiBleMatterStack` struct. +//! An example utilizing the `EspNCWifiMatterStack` struct. +//! //! As the name suggests, this Matter stack assembly uses Wifi as the main transport, -//! and BLE for commissioning. +//! (and thus BLE for commissioning), where `NC` stands for non-concurrent commisisoning +//! (i.e., the stack will not run the BLE and Wifi radio simultaneously, which saves memory). +//! //! If you want to use Ethernet, utilize `EspEthMatterStack` instead. +//! If you want to use concurrent commissioning, utilize `EspWifiMatterStack` instead +//! (Alexa does not work (yet) with non-concurrent commissioning). //! //! The example implements a fictitious Light device (an On-Off Matter cluster). @@ -31,7 +36,7 @@ use core::pin::pin; use embassy_futures::select::select; use embassy_time::{Duration, Timer}; -use esp_idf_matter::{init_async_io, EspKvBlobStore, EspModem, EspPersist, EspWifiBleMatterStack}; +use esp_idf_matter::{init_async_io, EspMatterBle, EspMatterWifi, EspWifiMatterStack}; use esp_idf_svc::eventloop::EspSystemEventLoop; use esp_idf_svc::hal::peripherals::Peripherals; @@ -47,11 +52,10 @@ 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::{Dataver, Endpoint, HandlerCompat, Node}; use rs_matter::data_model::system_model::descriptor; -use rs_matter::secure_channel::spake2p::VerifierData; use rs_matter::utils::init::InitMaybeUninit; use rs_matter::utils::select::Coalesce; -use rs_matter::CommissioningData; +use rs_matter::BasicCommData; use rs_matter_stack::persist::DummyPersist; use static_cell::StaticCell; @@ -68,7 +72,7 @@ fn main() -> Result<(), anyhow::Error> { // 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(70 * 1024) + .stack_size(55 * 1024) .spawn(|| { // Eagerly initialize `async-io` to minimize the risk of stack blowups later on init_async_io()?; @@ -100,7 +104,7 @@ async fn matter() -> Result<(), anyhow::Error> { // as we'll run it in this thread let stack = MATTER_STACK .uninit() - .init_with(EspWifiBleMatterStack::init_default( + .init_with(EspWifiMatterStack::init_default( &BasicInfoConfig { vid: 0xFFF1, pid: 0x8000, @@ -112,6 +116,10 @@ async fn matter() -> Result<(), anyhow::Error> { product_name: "ACME Light", vendor_name: "ACME", }, + BasicCommData { + password: 20202021, + discriminator: 3840, + }, &DEV_ATT, )); @@ -145,25 +153,21 @@ async fn matter() -> Result<(), anyhow::Error> { ))), ); + let (mut wifi_modem, mut bt_modem) = peripherals.modem.split(); + // 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 the Wifi modem peripheral + EspMatterWifi::new(&mut wifi_modem, sysloop, timers, nvs.clone()), + // The Matter stack needs the BT modem peripheral + EspMatterBle::new(&mut bt_modem, nvs, stack), // The Matter stack needs a persister to store its state // `EspPersist`+`EspKvBlobStore` saves to a user-supplied NVS partition // under namespace `esp-idf-matter` DummyPersist, //EspPersist::new_wifi_ble(EspKvBlobStore::new_default(nvs.clone())?, stack), - // The Matter stack needs the BT/Wifi modem peripheral - and in general - - // the Bluetooth / Wifi connections will be managed by the Matter stack itself - // For finer-grained control, call `MatterStack::is_commissioned`, - // `MatterStack::commission` and `MatterStack::operate` - EspModem::new(peripherals.modem, sysloop, timers, nvs, stack), - // Hard-coded for demo purposes - CommissioningData { - verifier: VerifierData::new_with_pw(123456, stack.matter().rand()), - discriminator: 250, - }, // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), // No user future to run @@ -200,7 +204,7 @@ async fn matter() -> Result<(), anyhow::Error> { /// The Matter stack is allocated statically to avoid /// program stack blowups. /// It is also a mandatory requirement when the `WifiBle` stack variation is used. -static MATTER_STACK: StaticCell> = StaticCell::new(); +static MATTER_STACK: StaticCell> = StaticCell::new(); static DEV_ATT: dev_att::HardCodedDevAtt = dev_att::HardCodedDevAtt::new(); @@ -212,10 +216,10 @@ const LIGHT_ENDPOINT_ID: u16 = 1; const NODE: Node = Node { id: 0, endpoints: &[ - EspWifiBleMatterStack::<()>::root_metadata(), + EspWifiMatterStack::<()>::root_metadata(), Endpoint { id: LIGHT_ENDPOINT_ID, - device_type: DEV_TYPE_ON_OFF_LIGHT, + device_types: &[DEV_TYPE_ON_OFF_LIGHT], clusters: &[descriptor::CLUSTER, cluster_on_off::CLUSTER], }, ], From e22bc07d2f8f9dd4e2bc9c71e94450638072e3e1 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Sat, 5 Oct 2024 15:30:47 +0000 Subject: [PATCH 10/13] fmt --- src/wireless.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wireless.rs b/src/wireless.rs index 7e77711..6b87d7a 100644 --- a/src/wireless.rs +++ b/src/wireless.rs @@ -73,6 +73,7 @@ pub type EspWirelessMatterStack<'a, T, E> = MatterStack<'a, EspWirelessBle pub type EspWirelessBle = WirelessBle>>; /// An embedding of the ESP IDF Bluedroid Gatt peripheral context for the `WirelessBle` network type from `rs-matter-stack`. +/// /// Allows the memory of this context to be statically allocated and cost-initialized. /// /// Usage: From 8eb5460bcaf500728625d64debf68a3c32df08c7 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Sat, 5 Oct 2024 16:22:02 +0000 Subject: [PATCH 11/13] Fix CI --- Cargo.toml | 9 +- src/lib.rs | 9 +- src/wireless.rs | 407 ++++++++++++++++++++++++++---------------------- 3 files changed, 231 insertions(+), 194 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a80bbc5..87a92ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,11 +37,10 @@ debug = true opt-level = "z" [features] -#default = ["rs-matter-stack"] -default = ["rs-matter-stack", "async-io-mini"] -rs-matter-stack = ["dep:rs-matter-stack", "std"] +#default = ["std", "rs-matter-stack"] +default = ["std", "rs-matter-stack", "async-io-mini"] async-io-mini = ["std", "edge-nal-std/async-io-mini"] -std = ["esp-idf-svc/std", "edge-nal-std"] +std = ["esp-idf-svc/std", "edge-nal-std", "rs-matter-stack?/std"] examples = ["default", "esp-idf-svc/binstart", "esp-idf-svc/critical-section"] # Enable only when building the examples [dependencies] @@ -55,7 +54,7 @@ esp-idf-svc = { version = "0.49.1", default-features = false, features = ["alloc embedded-svc = { version = "0.28", default-features = false } rs-matter = { version = "0.1", default-features = false, features = ["rustcrypto"] } async-io = { version = "=2.0.0", default-features = false } # Workaround for https://github.com/smol-rs/async-lock/issues/84 -rs-matter-stack = { git = "https://github.com/ivmarkov/rs-matter-stack", branch = "thread", default-features = false, features = ["std"], optional = true } +rs-matter-stack = { git = "https://github.com/ivmarkov/rs-matter-stack", branch = "thread", default-features = false, optional = true } edge-nal = "0.3" edge-nal-std = { version = "0.3", default-features = false, optional = true } diff --git a/src/lib.rs b/src/lib.rs index aa970ac..46ac8a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,12 +17,11 @@ extern crate std; #[macro_use] extern crate alloc; -#[cfg(feature = "rs-matter-stack")] +#[cfg(all(feature = "std", feature = "rs-matter-stack"))] pub use eth::*; #[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, @@ -39,17 +38,19 @@ pub use wireless::*; not(esp32s2) ))] pub mod ble; -#[cfg(feature = "rs-matter-stack")] +#[cfg(all(feature = "std", feature = "rs-matter-stack"))] pub mod eth; #[cfg(any(esp_idf_comp_mdns_enabled, esp_idf_comp_espressif__mdns_enabled))] pub mod mdns; #[cfg(all( esp_idf_comp_esp_netif_enabled, esp_idf_comp_esp_event_enabled, - feature = "std" + feature = "std", + feature = "rs-matter-stack" ))] pub mod netif; #[cfg(esp_idf_comp_nvs_flash_enabled)] +#[cfg(feature = "rs-matter-stack")] pub mod persist; #[cfg(all( not(esp32h2), diff --git a/src/wireless.rs b/src/wireless.rs index 6b87d7a..ef22c9d 100644 --- a/src/wireless.rs +++ b/src/wireless.rs @@ -1,69 +1,30 @@ -use std::io; - -use alloc::sync::Arc; - -use edge_nal::UdpBind; -use edge_nal_std::{Stack, UdpSocket}; - -use embassy_sync::mutex::Mutex; - -use embedded_svc::wifi::asynch::Wifi as WifiSvc; - -use enumset::EnumSet; - use esp_idf_svc::bt::{self, BtDriver}; -use esp_idf_svc::eventloop::EspSystemEventLoop; use esp_idf_svc::hal::into_ref; -use esp_idf_svc::hal::modem::{BluetoothModemPeripheral, WifiModemPeripheral}; +use esp_idf_svc::hal::modem::BluetoothModemPeripheral; use esp_idf_svc::hal::peripheral::{Peripheral, PeripheralRef}; use esp_idf_svc::hal::task::embassy_sync::EspRawMutex; use esp_idf_svc::nvs::EspDefaultNvsPartition; -use esp_idf_svc::sys::EspError; -use esp_idf_svc::timer::EspTaskTimerService; -use esp_idf_svc::wifi::{AccessPointInfo, AsyncWifi, Capability, Configuration, EspWifi}; -use rs_matter::error::{Error, ErrorCode}; +use rs_matter::error::Error; use rs_matter::tlv::{FromTLV, ToTLV}; use rs_matter::utils::init::{init, Init}; -use rs_matter_stack::netif::{Netif, NetifConf, NetifRun}; use rs_matter_stack::network::{Embedding, Network}; use rs_matter_stack::persist::KvBlobBuf; -use rs_matter_stack::wireless::svc::SvcWifiController; -use rs_matter_stack::wireless::traits::{ - Ble, Thread, Wifi, WifiData, Wireless, WirelessConfig, WirelessData, NC, -}; +use rs_matter_stack::wireless::traits::{Ble, WirelessConfig, WirelessData}; use rs_matter_stack::{MatterStack, WirelessBle}; use crate::ble::{EspBtpGattContext, EspBtpGattPeripheral}; -use super::netif::EspMatterNetif; - -/// A type alias for an ESP-IDF Matter stack running over Wifi (and BLE, during commissioning). -pub type EspWifiMatterStack<'a, E> = EspWirelessMatterStack<'a, Wifi, E>; - -/// A type alias for an ESP-IDF Matter stack running over Thread (and BLE, during commissioning). -pub type EspThreadMatterStack<'a, E> = EspWirelessMatterStack<'a, Thread, E>; - -/// A type alias for an ESP-IDF Matter stack running over Wifi (and BLE, during commissioning). -/// -/// Unlike `EspWifiMatterStack`, this type alias runs the commissioning in a non-concurrent mode, -/// where the device runs either BLE or Wifi, but not both at the same time. -/// -/// This is useful to save memory by only having one of the stacks active at any point in time. -/// -/// Note that Alexa does not (yet) work with non-concurrent commissioning. -pub type EspWifiNCMatterStack<'a, E> = EspWirelessMatterStack<'a, Wifi, E>; +#[cfg(all( + esp_idf_comp_openthread_enabled, + esp_idf_openthread_enabled, + esp_idf_comp_vfs_enabled, +))] +pub use thread::*; -/// A type alias for an ESP-IDF Matter stack running over Thread (and BLE, during commissioning). -/// -/// Unlike `EspThreadMatterStack`, this type alias runs the commissioning in a non-concurrent mode, -/// where the device runs either BLE or Thread, but not both at the same time. -/// -/// This is useful to save memory by only having one of the stacks active at any point in time. -/// -/// Note that Alexa does not (yet) work with non-concurrent commissioning. -pub type EspThreadNCMatterStack<'a, E> = EspWirelessMatterStack<'a, Thread, E>; +#[cfg(esp_idf_comp_esp_wifi_enabled)] +pub use wifi::*; /// A type alias for an ESP-IDF Matter stack running over a wireless network (Wifi or Thread) and BLE. pub type EspWirelessMatterStack<'a, T, E> = MatterStack<'a, EspWirelessBle>; @@ -196,185 +157,261 @@ where } } -/// The relation between a network interface and a controller is slightly different -/// in the ESP-IDF crates compared to what `rs-matter-stack` wants, hence we need this helper type. -pub struct EspWifiSplit<'a>( - Arc>>>, - EspSystemEventLoop, -); +#[cfg(all( + esp_idf_comp_openthread_enabled, + esp_idf_openthread_enabled, + esp_idf_comp_vfs_enabled, +))] +mod thread { + use rs_matter_stack::wireless::traits::{Thread, NC}; + + use super::EspWirelessMatterStack; + + /// A type alias for an ESP-IDF Matter stack running over Thread (and BLE, during commissioning). + pub type EspThreadMatterStack<'a, E> = EspWirelessMatterStack<'a, Thread, E>; + + /// A type alias for an ESP-IDF Matter stack running over Thread (and BLE, during commissioning). + /// + /// Unlike `EspThreadMatterStack`, this type alias runs the commissioning in a non-concurrent mode, + /// where the device runs either BLE or Thread, but not both at the same time. + /// + /// This is useful to save memory by only having one of the stacks active at any point in time. + /// + /// Note that Alexa does not (yet) work with non-concurrent commissioning. + pub type EspThreadNCMatterStack<'a, E> = EspWirelessMatterStack<'a, Thread, E>; +} -impl<'a> WifiSvc for EspWifiSplit<'a> { - type Error = EspError; +#[cfg(esp_idf_comp_esp_wifi_enabled)] +mod wifi { + use std::io; - async fn get_capabilities(&self) -> Result, Self::Error> { - let wifi = self.0.lock().await; + use alloc::sync::Arc; - wifi.get_capabilities() - } + use edge_nal::UdpBind; + use edge_nal_std::{Stack, UdpSocket}; - async fn get_configuration(&self) -> Result { - let wifi = self.0.lock().await; + use embassy_sync::mutex::Mutex; - wifi.get_configuration() - } + use embedded_svc::wifi::asynch::Wifi as WifiSvc; - async fn set_configuration(&mut self, conf: &Configuration) -> Result<(), Self::Error> { - let mut wifi = self.0.lock().await; + use enumset::EnumSet; - wifi.set_configuration(conf) - } + use esp_idf_svc::eventloop::EspSystemEventLoop; + use esp_idf_svc::hal::into_ref; + use esp_idf_svc::hal::modem::WifiModemPeripheral; + use esp_idf_svc::hal::peripheral::{Peripheral, PeripheralRef}; + use esp_idf_svc::hal::task::embassy_sync::EspRawMutex; + use esp_idf_svc::nvs::EspDefaultNvsPartition; + use esp_idf_svc::sys::EspError; + use esp_idf_svc::timer::EspTaskTimerService; + use esp_idf_svc::wifi::{AccessPointInfo, AsyncWifi, Capability, Configuration, EspWifi}; - async fn start(&mut self) -> Result<(), Self::Error> { - let mut wifi = self.0.lock().await; + use rs_matter::error::{Error, ErrorCode}; - wifi.start().await - } + use rs_matter_stack::netif::{Netif, NetifConf, NetifRun}; + use rs_matter_stack::wireless::svc::SvcWifiController; + use rs_matter_stack::wireless::traits::{Wifi, WifiData, Wireless, NC}; - async fn stop(&mut self) -> Result<(), Self::Error> { - let mut wifi = self.0.lock().await; + use crate::netif::EspMatterNetif; - wifi.stop().await - } + use super::EspWirelessMatterStack; - async fn connect(&mut self) -> Result<(), Self::Error> { - let mut wifi = self.0.lock().await; + /// A type alias for an ESP-IDF Matter stack running over Wifi (and BLE, during commissioning). + pub type EspWifiMatterStack<'a, E> = EspWirelessMatterStack<'a, Wifi, E>; - wifi.connect().await - } + /// A type alias for an ESP-IDF Matter stack running over Wifi (and BLE, during commissioning). + /// + /// Unlike `EspWifiMatterStack`, this type alias runs the commissioning in a non-concurrent mode, + /// where the device runs either BLE or Wifi, but not both at the same time. + /// + /// This is useful to save memory by only having one of the stacks active at any point in time. + /// + /// Note that Alexa does not (yet) work with non-concurrent commissioning. + pub type EspWifiNCMatterStack<'a, E> = EspWirelessMatterStack<'a, Wifi, E>; - async fn disconnect(&mut self) -> Result<(), Self::Error> { - let mut wifi = self.0.lock().await; + /// The relation between a network interface and a controller is slightly different + /// in the ESP-IDF crates compared to what `rs-matter-stack` wants, hence we need this helper type. + pub struct EspWifiSplit<'a>( + Arc>>>, + EspSystemEventLoop, + ); - wifi.disconnect().await - } + impl<'a> WifiSvc for EspWifiSplit<'a> { + type Error = EspError; - async fn is_started(&self) -> Result { - let wifi = self.0.lock().await; + async fn get_capabilities(&self) -> Result, Self::Error> { + let wifi = self.0.lock().await; - wifi.is_started() - } + wifi.get_capabilities() + } - async fn is_connected(&self) -> Result { - let wifi = self.0.lock().await; + async fn get_configuration(&self) -> Result { + let wifi = self.0.lock().await; - wifi.is_connected() - } + wifi.get_configuration() + } - async fn scan_n( - &mut self, - ) -> Result<(heapless::Vec, usize), Self::Error> { - let mut wifi = self.0.lock().await; + async fn set_configuration(&mut self, conf: &Configuration) -> Result<(), Self::Error> { + let mut wifi = self.0.lock().await; - wifi.scan_n().await - } + wifi.set_configuration(conf) + } - async fn scan(&mut self) -> Result, Self::Error> { - let mut wifi = self.0.lock().await; + async fn start(&mut self) -> Result<(), Self::Error> { + let mut wifi = self.0.lock().await; - wifi.scan().await - } -} + wifi.start().await + } -impl<'a> Netif for EspWifiSplit<'a> { - async fn get_conf(&self) -> Result, Error> { - let wifi = self.0.lock().await; + async fn stop(&mut self) -> Result<(), Self::Error> { + let mut wifi = self.0.lock().await; - EspMatterNetif::new(wifi.wifi().sta_netif(), self.1.clone()) - .get_conf() - .await - } + wifi.stop().await + } - async fn wait_conf_change(&self) -> Result<(), Error> { - let wifi = self.0.lock().await; + async fn connect(&mut self) -> Result<(), Self::Error> { + let mut wifi = self.0.lock().await; - EspMatterNetif::new(wifi.wifi().sta_netif(), self.1.clone()) - .wait_conf_change() - .await - } -} + wifi.connect().await + } + + async fn disconnect(&mut self) -> Result<(), Self::Error> { + let mut wifi = self.0.lock().await; + + wifi.disconnect().await + } + + async fn is_started(&self) -> Result { + let wifi = self.0.lock().await; + + wifi.is_started() + } + + async fn is_connected(&self) -> Result { + let wifi = self.0.lock().await; -impl<'a> NetifRun for EspWifiSplit<'a> { - async fn run(&self) -> Result<(), Error> { - core::future::pending().await + wifi.is_connected() + } + + async fn scan_n( + &mut self, + ) -> Result<(heapless::Vec, usize), Self::Error> { + let mut wifi = self.0.lock().await; + + wifi.scan_n().await + } + + async fn scan(&mut self) -> Result, Self::Error> { + let mut wifi = self.0.lock().await; + + wifi.scan().await + } } -} -impl<'a> UdpBind for EspWifiSplit<'a> { - type Error = io::Error; - type Socket<'b> - = UdpSocket - where - Self: 'b; + impl<'a> Netif for EspWifiSplit<'a> { + async fn get_conf(&self) -> Result, Error> { + let wifi = self.0.lock().await; + + EspMatterNetif::new(wifi.wifi().sta_netif(), self.1.clone()) + .get_conf() + .await + } - async fn bind(&self, local: core::net::SocketAddr) -> Result, Self::Error> { - Stack::new().bind(local).await + async fn wait_conf_change(&self) -> Result<(), Error> { + let wifi = self.0.lock().await; + + EspMatterNetif::new(wifi.wifi().sta_netif(), self.1.clone()) + .wait_conf_change() + .await + } } -} -/// A `Wireless` trait implementation via ESP-IDF's Wifi modem -pub struct EspMatterWifi<'d, T> { - modem: PeripheralRef<'d, T>, - sysloop: EspSystemEventLoop, - timer: EspTaskTimerService, - nvs: EspDefaultNvsPartition, -} + impl<'a> NetifRun for EspWifiSplit<'a> { + async fn run(&self) -> Result<(), Error> { + core::future::pending().await + } + } -impl<'d, T> EspMatterWifi<'d, T> -where - T: WifiModemPeripheral, -{ - /// Create a new instance of the `EspMatterWifi` type. - pub fn new( - modem: impl Peripheral

+ 'd, + impl<'a> UdpBind for EspWifiSplit<'a> { + type Error = io::Error; + type Socket<'b> + = UdpSocket + where + Self: 'b; + + async fn bind( + &self, + local: core::net::SocketAddr, + ) -> Result, Self::Error> { + Stack::new().bind(local).await + } + } + + /// A `Wireless` trait implementation via ESP-IDF's Wifi modem + pub struct EspMatterWifi<'d, T> { + modem: PeripheralRef<'d, T>, sysloop: EspSystemEventLoop, timer: EspTaskTimerService, nvs: EspDefaultNvsPartition, - ) -> Self { - into_ref!(modem); - - Self { - modem, - sysloop, - timer, - nvs, - } } -} -impl<'d, T> Wireless for EspMatterWifi<'d, T> -where - T: WifiModemPeripheral, -{ - type Data = WifiData; - - type Netif<'a> - = EspWifiSplit<'a> + impl<'d, T> EspMatterWifi<'d, T> where - Self: 'a; + T: WifiModemPeripheral, + { + /// Create a new instance of the `EspMatterWifi` type. + pub fn new( + modem: impl Peripheral

+ 'd, + sysloop: EspSystemEventLoop, + timer: EspTaskTimerService, + nvs: EspDefaultNvsPartition, + ) -> Self { + into_ref!(modem); + + Self { + modem, + sysloop, + timer, + nvs, + } + } + } - type Controller<'a> - = SvcWifiController> + impl<'d, T> Wireless for EspMatterWifi<'d, T> where - Self: 'a; - - async fn start(&mut self) -> Result<(Self::Netif<'_>, Self::Controller<'_>), Error> { - let wifi = Arc::new(Mutex::new( - AsyncWifi::wrap( - EspWifi::new( - &mut self.modem, + T: WifiModemPeripheral, + { + type Data = WifiData; + + type Netif<'a> + = EspWifiSplit<'a> + where + Self: 'a; + + type Controller<'a> + = SvcWifiController> + where + Self: 'a; + + async fn start(&mut self) -> Result<(Self::Netif<'_>, Self::Controller<'_>), Error> { + let wifi = Arc::new(Mutex::new( + AsyncWifi::wrap( + EspWifi::new( + &mut self.modem, + self.sysloop.clone(), + Some(self.nvs.clone()), + ) + .map_err(|_| ErrorCode::NoNetworkInterface)?, // TODO self.sysloop.clone(), - Some(self.nvs.clone()), + self.timer.clone(), ) .map_err(|_| ErrorCode::NoNetworkInterface)?, // TODO - self.sysloop.clone(), - self.timer.clone(), - ) - .map_err(|_| ErrorCode::NoNetworkInterface)?, // TODO - )); - - Ok(( - EspWifiSplit(wifi.clone(), self.sysloop.clone()), - SvcWifiController::new(EspWifiSplit(wifi.clone(), self.sysloop.clone())), - )) + )); + + Ok(( + EspWifiSplit(wifi.clone(), self.sysloop.clone()), + SvcWifiController::new(EspWifiSplit(wifi.clone(), self.sysloop.clone())), + )) + } } } From b00db7057d01dccf2f8c62aa5d2d895e3c8d0831 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Sat, 5 Oct 2024 19:54:58 +0000 Subject: [PATCH 12/13] Bugfixing --- examples/light.rs | 12 +++++------ examples/light_eth.rs | 2 +- src/error.rs | 10 +++++++++ src/lib.rs | 1 + src/netif.rs | 6 ++++-- src/wireless.rs | 47 +++++++++++++++++++++++++++---------------- 6 files changed, 52 insertions(+), 26 deletions(-) create mode 100644 src/error.rs diff --git a/examples/light.rs b/examples/light.rs index e707e42..f8fe7c0 100644 --- a/examples/light.rs +++ b/examples/light.rs @@ -1,4 +1,4 @@ -//! An example utilizing the `EspNCWifiMatterStack` struct. +//! An example utilizing the `EspWifiNCMatterStack` struct. //! //! As the name suggests, this Matter stack assembly uses Wifi as the main transport, //! (and thus BLE for commissioning), where `NC` stands for non-concurrent commisisoning @@ -15,7 +15,7 @@ use core::pin::pin; use embassy_futures::select::select; use embassy_time::{Duration, Timer}; -use esp_idf_matter::{init_async_io, EspMatterBle, EspMatterWifi, EspWifiMatterStack}; +use esp_idf_matter::{init_async_io, EspMatterBle, EspMatterWifi, EspWifiNCMatterStack}; use esp_idf_svc::eventloop::EspSystemEventLoop; use esp_idf_svc::hal::peripherals::Peripherals; @@ -51,7 +51,7 @@ fn main() -> Result<(), anyhow::Error> { // 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(55 * 1024) + .stack_size(75 * 1024) .spawn(|| { // Eagerly initialize `async-io` to minimize the risk of stack blowups later on init_async_io()?; @@ -83,7 +83,7 @@ async fn matter() -> Result<(), anyhow::Error> { // as we'll run it in this thread let stack = MATTER_STACK .uninit() - .init_with(EspWifiMatterStack::init_default( + .init_with(EspWifiNCMatterStack::init_default( &BasicInfoConfig { vid: 0xFFF1, pid: 0x8000, @@ -183,7 +183,7 @@ async fn matter() -> Result<(), anyhow::Error> { /// The Matter stack is allocated statically to avoid /// program stack blowups. /// It is also a mandatory requirement when the `WifiBle` stack variation is used. -static MATTER_STACK: StaticCell> = StaticCell::new(); +static MATTER_STACK: StaticCell> = StaticCell::new(); static DEV_ATT: dev_att::HardCodedDevAtt = dev_att::HardCodedDevAtt::new(); @@ -195,7 +195,7 @@ const LIGHT_ENDPOINT_ID: u16 = 1; const NODE: Node = Node { id: 0, endpoints: &[ - EspWifiMatterStack::<()>::root_metadata(), + EspWifiNCMatterStack::<()>::root_metadata(), Endpoint { id: LIGHT_ENDPOINT_ID, device_types: &[DEV_TYPE_ON_OFF_LIGHT], diff --git a/examples/light_eth.rs b/examples/light_eth.rs index b347f79..9d112f1 100644 --- a/examples/light_eth.rs +++ b/examples/light_eth.rs @@ -55,7 +55,7 @@ fn main() -> Result<(), anyhow::Error> { // 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(40 * 1024) + .stack_size(60 * 1024) .spawn(|| { // Eagerly initialize `async-io` to minimize the risk of stack blowups later on init_async_io()?; diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..a993b93 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,10 @@ +use esp_idf_svc::sys::EspError; + +use rs_matter::error::{Error, ErrorCode}; + +/// Converts an ESP network error to an `rs-matter` error +pub fn to_net_error(_err: EspError) -> Error { + // TODO: The `rs-matter` error code is too generic + // TODO: Capture the backtrace and the original error + ErrorCode::NoNetworkInterface.into() +} diff --git a/src/lib.rs b/src/lib.rs index 46ac8a0..4861257 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,7 @@ pub use wireless::*; not(esp32s2) ))] pub mod ble; +pub mod error; #[cfg(all(feature = "std", feature = "rs-matter-stack"))] pub mod eth; #[cfg(any(esp_idf_comp_mdns_enabled, esp_idf_comp_espressif__mdns_enabled))] diff --git a/src/netif.rs b/src/netif.rs index 31b3816..9893e59 100644 --- a/src/netif.rs +++ b/src/netif.rs @@ -3,7 +3,7 @@ use core::net::{Ipv4Addr, Ipv6Addr}; use core::pin::pin; use alloc::sync::Arc; -use rs_matter::error::{Error, ErrorCode}; +use rs_matter::error::Error; use std::io; @@ -22,6 +22,8 @@ use esp_idf_svc::sys::{esp, esp_netif_get_ip6_linklocal, EspError, ESP_FAIL}; use rs_matter::utils::sync::Notification; use rs_matter_stack::netif::{Netif, NetifConf}; +use crate::error::to_net_error; + const TIMEOUT_PERIOD_SECS: u8 = 5; /// A `Netif` and `UdpBind` traits implementation via ESP-IDF @@ -124,7 +126,7 @@ where async fn wait_conf_change(&self) -> Result<(), Error> { EspMatterNetif::wait_conf_change(self) .await - .map_err(|_| ErrorCode::NoNetworkInterface)?; // TODO + .map_err(to_net_error)?; Ok(()) } diff --git a/src/wireless.rs b/src/wireless.rs index ef22c9d..d1bd56d 100644 --- a/src/wireless.rs +++ b/src/wireless.rs @@ -201,17 +201,20 @@ mod wifi { use esp_idf_svc::hal::modem::WifiModemPeripheral; use esp_idf_svc::hal::peripheral::{Peripheral, PeripheralRef}; use esp_idf_svc::hal::task::embassy_sync::EspRawMutex; + use esp_idf_svc::handle::RawHandle; + use esp_idf_svc::netif::EspNetif; use esp_idf_svc::nvs::EspDefaultNvsPartition; - use esp_idf_svc::sys::EspError; + use esp_idf_svc::sys::{esp, EspError}; use esp_idf_svc::timer::EspTaskTimerService; use esp_idf_svc::wifi::{AccessPointInfo, AsyncWifi, Capability, Configuration, EspWifi}; - use rs_matter::error::{Error, ErrorCode}; + use rs_matter::error::Error; use rs_matter_stack::netif::{Netif, NetifConf, NetifRun}; use rs_matter_stack::wireless::svc::SvcWifiController; use rs_matter_stack::wireless::traits::{Wifi, WifiData, Wireless, NC}; + use crate::error::to_net_error; use crate::netif::EspMatterNetif; use super::EspWirelessMatterStack; @@ -272,7 +275,16 @@ mod wifi { async fn connect(&mut self) -> Result<(), Self::Error> { let mut wifi = self.0.lock().await; - wifi.connect().await + wifi.connect().await?; + + // Matter needs an IPv6 address to work + esp!(unsafe { + esp_idf_svc::sys::esp_netif_create_ip6_linklocal( + wifi.wifi().sta_netif().handle() as _ + ) + })?; + + Ok(()) } async fn disconnect(&mut self) -> Result<(), Self::Error> { @@ -318,11 +330,14 @@ mod wifi { } async fn wait_conf_change(&self) -> Result<(), Error> { - let wifi = self.0.lock().await; + // Wait on any conf change + // We anyway cannot lock the wifi mutex here (would be a deadlock), so we just wait for the event - EspMatterNetif::new(wifi.wifi().sta_netif(), self.1.clone()) - .wait_conf_change() + EspMatterNetif::::wait_any_conf_change(&self.1) .await + .map_err(to_net_error)?; + + Ok(()) } } @@ -394,18 +409,16 @@ mod wifi { Self: 'a; async fn start(&mut self) -> Result<(Self::Netif<'_>, Self::Controller<'_>), Error> { + let wifi = EspWifi::new( + &mut self.modem, + self.sysloop.clone(), + Some(self.nvs.clone()), + ) + .map_err(to_net_error)?; + let wifi = Arc::new(Mutex::new( - AsyncWifi::wrap( - EspWifi::new( - &mut self.modem, - self.sysloop.clone(), - Some(self.nvs.clone()), - ) - .map_err(|_| ErrorCode::NoNetworkInterface)?, // TODO - self.sysloop.clone(), - self.timer.clone(), - ) - .map_err(|_| ErrorCode::NoNetworkInterface)?, // TODO + AsyncWifi::wrap(wifi, self.sysloop.clone(), self.timer.clone()) + .map_err(to_net_error)?, )); Ok(( From f84303d0160c48e66c05d347943d9bf7c674dc88 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Sat, 5 Oct 2024 19:57:36 +0000 Subject: [PATCH 13/13] Switch to the main branch --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 87a92ec..eed6899 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ esp-idf-svc = { version = "0.49.1", default-features = false, features = ["alloc embedded-svc = { version = "0.28", default-features = false } rs-matter = { version = "0.1", default-features = false, features = ["rustcrypto"] } async-io = { version = "=2.0.0", default-features = false } # Workaround for https://github.com/smol-rs/async-lock/issues/84 -rs-matter-stack = { git = "https://github.com/ivmarkov/rs-matter-stack", branch = "thread", default-features = false, optional = true } +rs-matter-stack = { git = "https://github.com/ivmarkov/rs-matter-stack", default-features = false, optional = true } edge-nal = "0.3" edge-nal-std = { version = "0.3", default-features = false, optional = true }