From 69e05023b005db4be36ad558fd0f64b8224be0eb Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Tue, 19 May 2026 17:20:33 -0700 Subject: [PATCH] Use rs-matter-stack runtime --- Cargo.lock | 235 ++++++++++++++++++++-- Cargo.toml | 27 ++- README.md | 13 +- examples/basic.rs | 11 +- src/bin/toes-matter.rs | 8 +- src/lib.rs | 429 +++++++++++++++++------------------------ 6 files changed, 423 insertions(+), 300 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c91a4c1..6911727 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -133,6 +133,19 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-compat" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ba85bc55464dcbf728b56d97e119d673f4cf9062be330a9a26f3acf504a590" +dependencies = [ + "futures-core", + "futures-io", + "once_cell", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-executor" version = "1.14.0" @@ -527,7 +540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -567,8 +580,18 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.10.2", + "darling_macro 0.10.2", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -585,17 +608,41 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "darling_macro" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ - "darling_core", + "darling_core 0.10.2", "quote", "syn 1.0.109", ] +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn 2.0.117", +] + [[package]] name = "defmt" version = "0.3.100" @@ -698,7 +745,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0" dependencies = [ - "darling", + "darling 0.10.2", "derive_builder_core", "proc-macro2", "quote", @@ -711,7 +758,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" dependencies = [ - "darling", + "darling 0.10.2", "proc-macro2", "quote", "syn 1.0.109", @@ -749,6 +796,29 @@ dependencies = [ "time", ] +[[package]] +name = "domain" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7ff15f82df7d5086fb15dfc1c1e96598a6ded9829840a9bcfa1fa3ccd8d01d" +dependencies = [ + "domain-macros", + "heapless 0.8.0", + "octseq", + "time", +] + +[[package]] +name = "domain-macros" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d1a6796ad411f6812d691955066ad27450196bfb181bb91b66a643cc3e8f5b7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "ecdsa" version = "0.16.9" @@ -762,6 +832,47 @@ dependencies = [ "signature", ] +[[package]] +name = "edge-mdns" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e6e268ceceab96a31eeefb7fc4afbb8f005313de23944a0ecb10a20f382193" +dependencies = [ + "domain 0.11.1", + "edge-nal", + "embassy-futures", + "embassy-sync", + "embassy-time", + "heapless 0.9.3", + "log", + "rand_core 0.9.5", +] + +[[package]] +name = "edge-nal" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c7d7163586cb9d457a34561a644aa957ce870226729bf6c9c8beeaead7e0d8" +dependencies = [ + "embassy-time", + "embedded-io-async 0.7.0", +] + +[[package]] +name = "edge-nal-std" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4af3f481df81463ca9a1bdb5adda6ecb956b1e4efacb29dab1835194479cdbc8" +dependencies = [ + "async-io", + "edge-nal", + "embedded-io-async 0.7.0", + "futures-lite", + "heapless 0.9.3", + "libc", + "log", +] + [[package]] name = "either" version = "1.15.0" @@ -781,7 +892,7 @@ dependencies = [ "generic-array", "group", "hkdf", - "rand_core", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -807,7 +918,7 @@ checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" dependencies = [ "cfg-if", "critical-section", - "embedded-io-async", + "embedded-io-async 0.6.1", "futures-core", "futures-sink", "heapless 0.8.0", @@ -881,13 +992,28 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "embedded-io" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" + [[package]] name = "embedded-io-async" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" dependencies = [ - "embedded-io", + "embedded-io 0.6.1", +] + +[[package]] +name = "embedded-io-async" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564b9f813c544241430e147d8bc454815ef9ac998878d30cc3055449f7fd4c0" +dependencies = [ + "embedded-io 0.7.1", ] [[package]] @@ -917,6 +1043,27 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "enumset" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839c4174b41e75c8f7306110b2c51996a293b8d1d850edd529011841d9fede7d" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd536557b58c682b217b8fb199afdff47cd3eff260623f19e77074eb073d63a" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "env_filter" version = "1.0.1" @@ -989,7 +1136,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1114,7 +1261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1877,8 +2024,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", ] [[package]] @@ -1888,7 +2035,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -1900,6 +2057,12 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + [[package]] name = "regex" version = "1.12.3" @@ -1956,7 +2119,7 @@ dependencies = [ "defmt 0.3.100", "der", "digest", - "domain", + "domain 0.10.4", "ecdsa", "either", "elliptic-curve", @@ -1981,7 +2144,7 @@ dependencies = [ "primeorder", "qrcodegen-no-heap", "rand", - "rand_core", + "rand_core 0.6.4", "rs-matter-codegen", "rs-matter-macros", "scopeguard", @@ -2031,6 +2194,29 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "rs-matter-stack" +version = "0.1.4" +source = "git+https://git.fishmt.net/nakajima/rs-matter-stack?branch=master#0a022bb2ac686707c3f5a84a790b1c3430b500ce" +dependencies = [ + "bitflags 2.11.1", + "cfg-if", + "edge-mdns", + "edge-nal", + "edge-nal-std", + "embassy-futures", + "embassy-sync", + "embassy-time", + "enumset", + "heapless 0.8.0", + "log", + "rand_chacha 0.9.0", + "rand_core 0.6.4", + "rand_core 0.9.5", + "rs-matter", + "scopeguard", +] + [[package]] name = "rustc-demangle" version = "0.1.27" @@ -2199,7 +2385,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -2224,6 +2410,15 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_cell" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23" +dependencies = [ + "portable-atomic", +] + [[package]] name = "strsim" version = "0.9.3" @@ -2396,15 +2591,15 @@ checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" name = "toes-matter" version = "0.1.12" dependencies = [ - "async-io", - "embassy-futures", - "embassy-time", + "async-compat", "embassy-time-queue-utils", "env_logger", "futures-lite", "log", "rand", "rs-matter", + "rs-matter-stack", + "static_cell", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0c55b4b..2f86779 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,13 +20,13 @@ required-features = ["device"] [features] default = ["device"] device = [ - "dep:async-io", - "dep:embassy-futures", - "dep:embassy-time", + "dep:async-compat", "dep:embassy-time-queue-utils", "dep:env_logger", "dep:futures-lite", "dep:log", + "dep:rs-matter-stack", + "dep:static_cell", "rs-matter/log", "rs-matter/os", "rs-matter/rustcrypto", @@ -35,17 +35,28 @@ device = [ "rs-matter/max-groups-per-fabric-12", "rs-matter/max-group-keys-per-fabric-2", "rs-matter/max-group-endpoints-per-fabric-3", + "rs-matter-stack/log", + "rs-matter-stack/os", + "rs-matter-stack/std", + "rs-matter-stack/zbus", + "rs-matter-stack/max-sessions-32", + "rs-matter-stack/max-groups-per-fabric-12", + "rs-matter-stack/max-group-keys-per-fabric-2", + "rs-matter-stack/max-group-endpoints-per-fabric-3", "zeroconf", ] -zeroconf = ["rs-matter/zeroconf"] +zeroconf = ["rs-matter/zeroconf", "rs-matter-stack/zeroconf"] [dependencies] -async-io = { version = "2", optional = true } -embassy-futures = { version = "0.1", optional = true } -embassy-time = { version = "0.5", features = ["std"], optional = true } +async-compat = { version = "0.2", optional = true } embassy-time-queue-utils = { version = "0.3", features = ["generic-queue-64"], optional = true } env_logger = { version = "0.11", optional = true } futures-lite = { version = "2", optional = true } log = { version = "0.4", optional = true } rand = { version = "0.8", features = ["std", "std_rng"] } -rs-matter = { git = "https://git.fishmt.net/nakajima/rs-matter", branch = "bluez-ios-reconnect", default-features = false } +rs-matter = { version = "0.1", default-features = false } +rs-matter-stack = { git = "https://git.fishmt.net/nakajima/rs-matter-stack", branch = "master", default-features = false, optional = true } +static_cell = { version = "2.1", optional = true } + +[patch.crates-io] +rs-matter = { git = "https://git.fishmt.net/nakajima/rs-matter", branch = "bluez-ios-reconnect" } diff --git a/README.md b/README.md index 72082bd..475d844 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,24 @@ # toes-matter -Tiny facade around `rs-matter` for the Toes RGB light flow: +Tiny facade around `rs-matter-stack` for the Toes RGB light flow: ```rust #![recursion_limit = "256"] +use core::pin::pin; + fn main() -> toes_matter::Result<()> { env_logger::init_from_env( env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), ); - futures_lite::future::block_on(async { - toes_matter::generate_credentials("./creds").await?; - toes_matter::provision().await?; - toes_matter::listen().await - }) + let runtime = pin!(toes_matter::run()); + futures_lite::future::block_on(async_compat::Compat::new(runtime)) } ``` +`toes_matter::run()` handles both first-boot BLE/Wi-Fi commissioning and normal operational Matter traffic. The older `provision()` and `listen()` functions are kept as compatibility aliases for the same combined runtime. + ## Runtime assumptions - Linux + BlueZ on system D-Bus diff --git a/examples/basic.rs b/examples/basic.rs index bc6ddbd..52c08f9 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,13 +1,14 @@ #![recursion_limit = "256"] +use core::pin::pin; + fn main() -> toes_matter::Result<()> { env_logger::init_from_env( env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), ); - futures_lite::future::block_on(async { - toes_matter::generate_credentials("./creds").await?; - toes_matter::provision().await?; - toes_matter::listen().await - }) + futures_lite::future::block_on(toes_matter::generate_credentials("./creds"))?; + + let runtime = pin!(toes_matter::run()); + futures_lite::future::block_on(async_compat::Compat::new(runtime)) } diff --git a/src/bin/toes-matter.rs b/src/bin/toes-matter.rs index 96b8db9..27a2354 100644 --- a/src/bin/toes-matter.rs +++ b/src/bin/toes-matter.rs @@ -1,12 +1,12 @@ #![recursion_limit = "256"] +use core::pin::pin; + fn main() -> toes_matter::Result<()> { env_logger::init_from_env( env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), ); - futures_lite::future::block_on(async { - toes_matter::provision().await?; - toes_matter::listen().await - }) + let runtime = pin!(toes_matter::run()); + futures_lite::future::block_on(async_compat::Compat::new(runtime)) } diff --git a/src/lib.rs b/src/lib.rs index 87e351f..e9fc1e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,70 +5,64 @@ use core::cell::RefCell; use core::pin::pin; -use std::net::UdpSocket; use std::path::{Path, PathBuf}; use std::process::Command; -use embassy_futures::select::{select, select4}; use log::{info, warn}; use rand::RngCore; +use static_cell::StaticCell; -use rs_matter::crypto::{default_crypto, Crypto}; -use rs_matter::dm::clusters::app::level_control::LevelControlHooks; -use rs_matter::dm::clusters::app::on_off::OnOffHooks; -use rs_matter::dm::clusters::app::{level_control, on_off}; -use rs_matter::dm::clusters::decl::color_control; -use rs_matter::dm::clusters::decl::color_control::ClusterAsyncHandler as ColorControlHandler; -use rs_matter::dm::clusters::desc::{ClusterHandler as _, DescHandler}; -use rs_matter::dm::clusters::dev_att::DeviceAttestation; -use rs_matter::dm::clusters::net_comm::{ - NetCtl, NetCtlError, NetworkScanInfo, NetworkType, SharedNetworks, ThreadCapabilitiesBitmap, - WiFiBandEnum, WirelessCreds, +use rs_matter_stack::ble::GattPeripheral; +use rs_matter_stack::matter as rs_matter; +use rs_matter_stack::matter::crypto::{default_crypto, Crypto}; +use rs_matter_stack::matter::dm::clusters::app::level_control::LevelControlHooks; +use rs_matter_stack::matter::dm::clusters::app::on_off::OnOffHooks; +use rs_matter_stack::matter::dm::clusters::app::{level_control, on_off}; +use rs_matter_stack::matter::dm::clusters::decl::color_control; +use rs_matter_stack::matter::dm::clusters::decl::color_control::ClusterAsyncHandler as ColorControlHandler; +use rs_matter_stack::matter::dm::clusters::desc::{ClusterHandler as _, DescHandler}; +use rs_matter_stack::matter::dm::clusters::dev_att::DeviceAttestation; +use rs_matter_stack::matter::dm::clusters::gen_diag::{NetifDiag, NetifInfo}; +use rs_matter_stack::matter::dm::clusters::net_comm::{ + NetCtl, NetCtlError, NetworkScanInfo, NetworkType, ThreadCapabilitiesBitmap, WiFiBandEnum, + WirelessCreds, }; -use rs_matter::dm::clusters::wifi_diag::{ +use rs_matter_stack::matter::dm::clusters::wifi_diag::{ SecurityTypeEnum, WiFiVersionEnum, WifiDiag, WirelessDiag, }; -use rs_matter::dm::devices::test::{DAC_PRIVKEY, TEST_DEV_ATT, TEST_DEV_COMM, TEST_DEV_DET}; -use rs_matter::dm::endpoints; -use rs_matter::dm::events::Events; -use rs_matter::dm::networks::unix::UnixNetifs; -use rs_matter::dm::networks::wireless::{NetCtlState, NetCtlWithStatusImpl, WifiNetworks}; -use rs_matter::dm::networks::NetChangeNotif; -use rs_matter::dm::subscriptions::Subscriptions; -use rs_matter::dm::{ - Async, Cluster, DataModel, Dataver, EmptyHandler, Endpoint, EpClMatcher, Node, +use rs_matter_stack::matter::dm::devices::test::{ + DAC_PRIVKEY, TEST_DEV_ATT, TEST_DEV_COMM, TEST_DEV_DET, }; -use rs_matter::error::{Error, ErrorCode}; -use rs_matter::pairing::qr::{no_optional_data, CommFlowType, QrPayload, QrTextType}; -use rs_matter::pairing::DiscoveryCapabilities; -use rs_matter::persist::{DirKvBlobStore, SharedKvBlobStore}; -use rs_matter::respond::DefaultResponder; -use rs_matter::sc::pase::{ - Spake2pVerifierPassword, Spake2pVerifierPasswordRef, MAX_COMM_WINDOW_TIMEOUT_SECS, +use rs_matter_stack::matter::dm::networks::unix::UnixNetifs; +use rs_matter_stack::matter::dm::networks::NetChangeNotif; +use rs_matter_stack::matter::dm::{ + Async, Cluster, Dataver, EmptyHandler, Endpoint, EpClMatcher, Node, }; -use rs_matter::tlv::Nullable; -use rs_matter::transport::network::btp::bluez; -use rs_matter::transport::network::btp::{AdvData, Btp}; -use rs_matter::transport::network::mdns::zeroconf::ZeroconfMdnsResponder; -use rs_matter::transport::network::wifi::wpa_supp::unix::DhClientCtl; -use rs_matter::transport::network::wifi::wpa_supp::WpaSuppCtl; -use rs_matter::transport::network::{Address, ChainedNetwork}; -use rs_matter::transport::MATTER_SOCKET_BIND_ADDR; -use rs_matter::utils::select::Coalesce; -use rs_matter::utils::storage::pooled::PooledBuffers; -use rs_matter::utils::sync::blocking::Mutex; -use rs_matter::utils::sync::DynBase; -use rs_matter::utils::zbus::Connection; -use rs_matter::{clusters, devices, root_endpoint, with, BasicCommData, Matter, MATTER_PORT}; +use rs_matter_stack::matter::error::{Error, ErrorCode}; +use rs_matter_stack::matter::pairing::qr::{no_optional_data, CommFlowType, QrPayload}; +use rs_matter_stack::matter::pairing::DiscoveryCapabilities; +use rs_matter_stack::matter::persist::DirKvBlobStore; +use rs_matter_stack::matter::sc::pase::{Spake2pVerifierPassword, Spake2pVerifierPasswordRef}; +use rs_matter_stack::matter::tlv::Nullable; +use rs_matter_stack::matter::transport::network::btp::{AdvData, Btp}; +use rs_matter_stack::matter::transport::network::mdns::zeroconf::ZeroconfMdnsResponder; +use rs_matter_stack::matter::transport::network::wifi::wpa_supp::unix::DhClientCtl; +use rs_matter_stack::matter::transport::network::wifi::wpa_supp::WpaSuppCtl; +use rs_matter_stack::matter::utils::init::InitMaybeUninit; +use rs_matter_stack::matter::utils::sync::blocking::Mutex; +use rs_matter_stack::matter::utils::sync::DynBase; +use rs_matter_stack::matter::utils::zbus::Connection; +use rs_matter_stack::matter::{clusters, devices, with, BasicCommData}; +use rs_matter_stack::nal::std::Stack as StdNetStack; +use rs_matter_stack::wireless::{PreexistingWireless, WifiMatterStack}; pub type Result = core::result::Result; const DEFAULT_CREDS_DIR: &str = "./creds"; const DEFAULT_WIFI_IFACE: &str = "wlan0"; const LIGHT_ENDPOINT_ID: u16 = 1; -const KV_BUF_SIZE: usize = 4096; const BLE_SERVICE_NAME: &str = "TOES"; -const COMMISSIONING_HANDOFF_SECS: u64 = 20; +const BUMP_SIZE: usize = 48_000; const DEV_TYPE_EXTENDED_COLOR_LIGHT: rs_matter::dm::DeviceType = rs_matter::dm::DeviceType { dtype: 0x010d, @@ -77,7 +71,7 @@ const DEV_TYPE_EXTENDED_COLOR_LIGHT: rs_matter::dm::DeviceType = rs_matter::dm:: const NODE: Node = Node { endpoints: &[ - root_endpoint!(wifi), + WifiMatterStack::<0, ()>::root_endpoint(), Endpoint { id: LIGHT_ENDPOINT_ID, device_types: devices!(DEV_TYPE_EXTENDED_COLOR_LIGHT), @@ -91,7 +85,9 @@ const NODE: Node = Node { ], }; -/// Runtime configuration used by [`provision_with_config`] and [`listen_with_config`]. +static MATTER_STACK: StaticCell> = StaticCell::new(); + +/// Runtime configuration used by [`run_with_config`]. #[derive(Clone, Debug)] pub struct Config { pub creds_dir: PathBuf, @@ -246,41 +242,78 @@ fn valid_setup_passcode(passcode: u32) -> bool { (1..=99_999_998).contains(&passcode) && !INVALID.contains(&passcode) } -/// Provision this device over BLE Matter commissioning and Wi-Fi Network Commissioning. +/// Run the combined rs-matter-stack runtime. /// -/// Returns immediately if a fabric is already stored in the configured state dir. -/// Otherwise advertises over BlueZ, receives Wi-Fi credentials, connects the Wi-Fi -/// interface via wpa_supplicant, persists Matter state, then returns. +/// This runtime handles both first-boot BLE/Wi-Fi commissioning and normal +/// operational Matter traffic. It runs until the process is stopped or an +/// unrecoverable transport error is returned. +pub async fn run() -> Result<()> { + run_with_config(Config::from_env()).await +} + +pub async fn run_with_config(config: Config) -> Result<()> { + run_stack(config).await +} + +/// Legacy entrypoint kept for callers that used the earlier split API. +/// +/// With `rs-matter-stack`, provisioning and listening are one runtime, so this +/// does not return after commissioning; it continues serving Matter traffic. pub async fn provision() -> Result<()> { - provision_with_config(Config::from_env()).await + info!("Starting combined Matter provisioning/listening runtime"); + run().await } pub async fn provision_with_config(config: Config) -> Result<()> { - std::fs::create_dir_all(&config.state_dir)?; - - let connection = Connection::system().await?; - let net_ctl = PersistingWifiCtl::new( - WpaSuppCtl::new( - &connection, - &config.wifi_iface, - DhClientCtl::new(&config.wifi_iface, true), - ), - &config.wifi_iface, - ); - - run_provision(&connection, net_ctl, &config).await + info!("Starting combined Matter provisioning/listening runtime"); + run_with_config(config).await } -/// Listen forever for Matter commands on the provisioned Wi-Fi/IP network. +/// Listen forever for Matter commands, opening BLE commissioning when needed. /// /// RGB commands are applied by invoking `rgbled --order `. pub async fn listen() -> Result<()> { - listen_with_config(Config::from_env()).await + run().await } pub async fn listen_with_config(config: Config) -> Result<()> { + run_with_config(config).await +} + +async fn run_stack(config: Config) -> Result<()> { std::fs::create_dir_all(&config.state_dir)?; + let comm = load_comm_data(&config)?; + let stack = MATTER_STACK + .uninit() + .init_with(WifiMatterStack::init_default( + &TEST_DEV_DET, + comm, + &TEST_DEV_ATT, + )); + + let crypto = default_crypto(rand::thread_rng(), DAC_PRIVKEY); + let mut rand = crypto.weak_rand()?; + + let rgb_light = RgbLight::new(Dataver::new_rand(&mut rand), config.rgbled_order.clone()); + let on_off = + on_off::OnOffHandler::new(Dataver::new_rand(&mut rand), LIGHT_ENDPOINT_ID, &rgb_light); + let level = level_control::LevelControlHandler::new( + Dataver::new_rand(&mut rand), + LIGHT_ENDPOINT_ID, + &rgb_light, + level_control::AttributeDefaults::default(), + ); + + on_off.init(Some(&level)); + level.init(Some(&on_off)); + + let handler = rgb_handler(&mut rand, &rgb_light, &on_off, &level); + + let mut kv = DirKvBlobStore::new(config.state_dir.clone()); + stack.startup(&crypto, &mut kv).await?; + let kv = stack.create_shared_kv(kv)?; + let connection = Connection::system().await?; let net_ctl = PersistingWifiCtl::new( WpaSuppCtl::new( @@ -291,201 +324,27 @@ pub async fn listen_with_config(config: Config) -> Result<()> { &config.wifi_iface, ); - run_listen(net_ctl, &config).await -} - -async fn run_provision(connection: &Connection, net_ctl: N, config: &Config) -> Result<()> -where - N: NetCtl + WifiDiag + NetChangeNotif, -{ - let comm = load_comm_data(config)?; - let mut matter = Matter::new_default(&TEST_DEV_DET, comm.clone(), &TEST_DEV_ATT, MATTER_PORT); - let mut networks = WifiNetworks::<3>::new(); - let mut events: Events = Events::new_default(); - let mut kv_buf = [0_u8; KV_BUF_SIZE]; - let mut kv = DirKvBlobStore::new(config.state_dir.clone()); - - matter.load_persist(&mut kv, &mut kv_buf).await?; - networks.load_persist(&mut kv, &mut kv_buf).await?; - events.load_persist(&mut kv, &mut kv_buf).await?; - - if matter.is_commissioned() { - info!("Matter fabric already exists; skipping BLE provisioning"); - return Ok(()); - } - - let networks = SharedNetworks::new(networks); - let buffers = PooledBuffers::<10, _>::new(0); - let subscriptions: Subscriptions = Subscriptions::new(); - let crypto = default_crypto(rand::thread_rng(), DAC_PRIVKEY); - let mut rand = crypto.rand()?; - - let rgb_light = RgbLight::new(Dataver::new_rand(&mut rand), config.rgbled_order.clone()); - let on_off = - on_off::OnOffHandler::new(Dataver::new_rand(&mut rand), LIGHT_ENDPOINT_ID, &rgb_light); - let level = level_control::LevelControlHandler::new( - Dataver::new_rand(&mut rand), - LIGHT_ENDPOINT_ID, - &rgb_light, - level_control::AttributeDefaults::default(), + info!( + "Matter Network Commissioning will manage Wi-Fi interface {}", + config.wifi_iface ); + info!("Running Matter via rs-matter-stack with BLE name {BLE_SERVICE_NAME}"); - on_off.init(Some(&level)); - level.init(Some(&on_off)); - - let net_ctl_state = NetCtlState::new_with_mutex(); - let net_ctl = NetCtlWithStatusImpl::new(&net_ctl_state, net_ctl); - - let handler = rgb_handler(&mut rand, &rgb_light, &on_off, &level); - let dm = DataModel::new( - &matter, - &crypto, - &buffers, - &subscriptions, - &events, - ( - NODE, - endpoints::with_wifi_sys(&true, &(), &UnixNetifs, &net_ctl, &net_ctl, rand, handler), + let matter = pin!(stack.run_coex( + PreexistingWireless::new( + StdNetStack::new(), + PreferredNetifs::new(&config.wifi_iface), + net_ctl, + ZeroconfMdnsResponder::new(), + ToesBluezGattPeripheral::new(&connection, None), ), - SharedKvBlobStore::new(kv, kv_buf.as_mut_slice()), - &networks, - ); - - let responder = DefaultResponder::new(&dm); - let mut respond = pin!(responder.run::<4, 4>()); - let mut dm_job = pin!(dm.run()); - - matter.print_standard_qr_text(DiscoveryCapabilities::BLE)?; - matter.print_standard_qr_code(QrTextType::Unicode, DiscoveryCapabilities::BLE)?; - dm.open_basic_comm_window(MAX_COMM_WINDOW_TIMEOUT_SECS)?; - - let btp = Btp::new(); - - let udp = async_io::Async::::bind(MATTER_SOCKET_BIND_ADDR)?; - let mut mdns_responder = ZeroconfMdnsResponder::new(); - let mut mdns = pin!(mdns_responder.run(&matter)); - - let adv_data = AdvData::new(&TEST_DEV_DET, comm.discriminator); - let mut bluetooth = pin!(bluez::run_peripheral( - connection, - None, - BLE_SERVICE_NAME, - &adv_data, - &btp, - )); - let mut transport = pin!(matter.run( &crypto, - ChainedNetwork::new(Address::is_udp, &udp, &btp), - ChainedNetwork::new(Address::is_udp, &udp, &btp), - &udp, + (NODE, handler), + &kv, + (), )); - info!("Running Matter over concurrent BLE commissioning and IP transport"); - let mut commissioning_done_task = pin!(async { - NetCtlState::wait_prov_ready(&net_ctl_state, &btp).await; - info!("Wi-Fi credentials accepted; waiting for Matter fabric setup"); - - while !matter.is_commissioned() { - embassy_time::Timer::after_secs(1).await; - } - - info!( - "Matter fabric added; keeping BLE/IP commissioning transports alive for {}s handoff", - COMMISSIONING_HANDOFF_SECS - ); - embassy_time::Timer::after_secs(COMMISSIONING_HANDOFF_SECS).await; - - Ok(()) - }); - - let mut matter_tasks = pin!(select4( - &mut transport, - &mut bluetooth, - &mut mdns, - select(&mut respond, &mut dm_job).coalesce(), - ) - .coalesce()); - - select(&mut matter_tasks, &mut commissioning_done_task) - .coalesce() - .await?; - - matter.reset_transport()?; - - info!("Matter provisioning completed; call toes_matter::listen().await next"); - - Ok(()) -} - -async fn run_listen(net_ctl: N, config: &Config) -> Result<()> -where - N: NetCtl + WifiDiag + NetChangeNotif, -{ - let comm = load_comm_data(config)?; - let mut matter = Matter::new_default(&TEST_DEV_DET, comm, &TEST_DEV_ATT, MATTER_PORT); - let mut networks = WifiNetworks::<3>::new(); - let mut events: Events = Events::new_default(); - let mut kv_buf = [0_u8; KV_BUF_SIZE]; - let mut kv = DirKvBlobStore::new(config.state_dir.clone()); - - matter.load_persist(&mut kv, &mut kv_buf).await?; - networks.load_persist(&mut kv, &mut kv_buf).await?; - events.load_persist(&mut kv, &mut kv_buf).await?; - - if !matter.is_commissioned() { - warn!("Matter state is not commissioned; run toes_matter::provision().await first"); - return Err(ErrorCode::InvalidState.into()); - } - - let networks = SharedNetworks::new(networks); - let buffers = PooledBuffers::<10, _>::new(0); - let subscriptions: Subscriptions = Subscriptions::new(); - let crypto = default_crypto(rand::thread_rng(), DAC_PRIVKEY); - let mut rand = crypto.rand()?; - - let rgb_light = RgbLight::new(Dataver::new_rand(&mut rand), config.rgbled_order.clone()); - let on_off = - on_off::OnOffHandler::new(Dataver::new_rand(&mut rand), LIGHT_ENDPOINT_ID, &rgb_light); - let level = level_control::LevelControlHandler::new( - Dataver::new_rand(&mut rand), - LIGHT_ENDPOINT_ID, - &rgb_light, - level_control::AttributeDefaults::default(), - ); - - on_off.init(Some(&level)); - level.init(Some(&on_off)); - - let net_ctl_state = NetCtlState::new_with_mutex(); - let net_ctl = NetCtlWithStatusImpl::new(&net_ctl_state, net_ctl); - - let handler = rgb_handler(&mut rand, &rgb_light, &on_off, &level); - let dm = DataModel::new( - &matter, - &crypto, - &buffers, - &subscriptions, - &events, - ( - NODE, - endpoints::with_wifi_sys(&false, &(), &UnixNetifs, &net_ctl, &net_ctl, rand, handler), - ), - SharedKvBlobStore::new(kv, kv_buf.as_mut_slice()), - &networks, - ); - - let responder = DefaultResponder::new(&dm); - let mut respond = pin!(responder.run::<4, 4>()); - let mut dm_job = pin!(dm.run()); - let mut mdns_responder = ZeroconfMdnsResponder::new(); - let mut mdns = pin!(mdns_responder.run(&matter)); - - let udp = async_io::Async::::bind(MATTER_SOCKET_BIND_ADDR)?; - let mut transport = pin!(matter.run(&crypto, &udp, &udp, &udp)); - - select4(&mut transport, &mut mdns, &mut respond, &mut dm_job) - .coalesce() - .await + matter.await } fn rgb_handler<'a, OH, LH>( @@ -517,6 +376,62 @@ where ) } +struct ToesBluezGattPeripheral<'a> { + connection: &'a Connection, + adapter_name: Option<&'a str>, +} + +impl<'a> ToesBluezGattPeripheral<'a> { + const fn new(connection: &'a Connection, adapter_name: Option<&'a str>) -> Self { + Self { + connection, + adapter_name, + } + } +} + +impl GattPeripheral for ToesBluezGattPeripheral<'_> { + async fn run(&mut self, btp: &Btp, _service_name: &str, service_adv: &AdvData) -> Result<()> { + rs_matter::transport::network::btp::bluez::run_peripheral( + self.connection, + self.adapter_name, + BLE_SERVICE_NAME, + service_adv, + btp, + ) + .await + } +} + +struct PreferredNetifs<'a> { + ifname: &'a str, +} + +impl<'a> PreferredNetifs<'a> { + const fn new(ifname: &'a str) -> Self { + Self { ifname } + } +} + +impl DynBase for PreferredNetifs<'_> {} + +impl NetifDiag for PreferredNetifs<'_> { + fn netifs(&self, f: &mut dyn FnMut(&NetifInfo) -> Result<()>) -> Result<()> { + UnixNetifs.netifs(&mut |netif| { + if netif.name == self.ifname { + f(netif)?; + } + Ok(()) + }) + } +} + +impl NetChangeNotif for PreferredNetifs<'_> { + async fn wait_changed(&self) { + UnixNetifs.wait_changed().await; + } +} + struct PersistingWifiCtl<'a, C> { inner: C, ifname: &'a str,