diff --git a/Cargo.toml b/Cargo.toml index cd97810..61c54b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,11 @@ exclude = ["examples"] [patch.crates-io] #rs-matter = { git = "https://github.com/project-chip/rs-matter" } -rs-matter = { git = "https://github.com/sysgrok/rs-matter", branch = "next" } +#rs-matter = { git = "https://github.com/sysgrok/rs-matter", branch = "next" } +rs-matter = { git = "https://github.com/sysgrok/rs-matter", branch = "inband-persistence" } #rs-matter = { path = "../../rs-matter/rs-matter" } -rs-matter-stack = { git = "https://github.com/ivmarkov/rs-matter-stack.git", branch = "next" } +#rs-matter-stack = { git = "https://github.com/ivmarkov/rs-matter-stack.git", branch = "next" } +rs-matter-stack = { git = "https://github.com/ivmarkov/rs-matter-stack.git", branch = "inband-persistence" } #rs-matter-stack = { path = "../../rs-matter-stack" } openthread = { git = "https://github.com/sysgrok/openthread.git", branch = "next" } #openthread = { path = "../../../openthread/openthread" } diff --git a/examples/esp/.cargo/config.toml b/examples/esp/.cargo/config.toml index 6e2e16e..d6af79f 100644 --- a/examples/esp/.cargo/config.toml +++ b/examples/esp/.cargo/config.toml @@ -20,3 +20,5 @@ build-std = ["core", "alloc", "panic_abort"] [env] DEFMT_LOG="info" ESP_LOG="info" +# Uncomment and change this if you change the partition table offset (relevant for the light_wifi_persistent example) +#ESP_BOOTLOADER_ESP_IDF_CONFIG_PARTITION_TABLE_OFFSET="0x8000" diff --git a/examples/esp/Cargo.toml b/examples/esp/Cargo.toml index bd01951..2ae7881 100644 --- a/examples/esp/Cargo.toml +++ b/examples/esp/Cargo.toml @@ -9,6 +9,11 @@ name = "light_wifi" required-features = ["wifi"] harness = false +[[bin]] +name = "light_wifi_persistent" +required-features = ["wifi"] +harness = false + [[bin]] name = "light_eth" required-features = ["wifi"] @@ -30,9 +35,11 @@ required-features = ["thread"] harness = false [patch.crates-io] -rs-matter = { git = "https://github.com/sysgrok/rs-matter", branch = "next" } +#rs-matter = { git = "https://github.com/sysgrok/rs-matter", branch = "next" } +rs-matter = { git = "https://github.com/sysgrok/rs-matter", branch = "inband-persistence" } #rs-matter = { path = "../../../rs-matter/rs-matter" } -rs-matter-stack = { git = "https://github.com/sysgrok/rs-matter-stack.git", branch = "next" } +#rs-matter-stack = { git = "https://github.com/sysgrok/rs-matter-stack.git", branch = "next" } +rs-matter-stack = { git = "https://github.com/ivmarkov/rs-matter-stack.git", branch = "inband-persistence" } #rs-matter-stack = { path = "../../../rs-matter-stack" } openthread = { git = "https://github.com/sysgrok/openthread.git", branch = "next" } #openthread = { path = "../../../openthread/openthread" } @@ -42,6 +49,7 @@ esp-hal = { git = "https://github.com/esp-rs/esp-hal", rev = "e156b5453a8adc123b esp-rtos = { git = "https://github.com/esp-rs/esp-hal", rev = "e156b5453a8adc123b7a8b6aa08e0698ec87dfe8" } esp-alloc = { git = "https://github.com/esp-rs/esp-hal", rev = "e156b5453a8adc123b7a8b6aa08e0698ec87dfe8" } esp-println = { git = "https://github.com/esp-rs/esp-hal", rev = "e156b5453a8adc123b7a8b6aa08e0698ec87dfe8" } +esp-storage = { git = "https://github.com/esp-rs/esp-hal", rev = "e156b5453a8adc123b7a8b6aa08e0698ec87dfe8" } esp-bootloader-esp-idf = { git = "https://github.com/esp-rs/esp-hal", rev = "e156b5453a8adc123b7a8b6aa08e0698ec87dfe8" } esp-metadata-generated = { git = "https://github.com/esp-rs/esp-hal", rev = "e156b5453a8adc123b7a8b6aa08e0698ec87dfe8" } #esp-radio = { path = "../../../esp-hal/esp-radio" } @@ -56,7 +64,7 @@ esp-metadata-generated = { git = "https://github.com/esp-rs/esp-hal", rev = "e15 [profile.dev] # Rust debug is too slow. # For debug builds always builds with some optimization -opt-level = "z" +opt-level = "s" [profile.release] codegen-units = 1 # LLVM can perform better optimizations using a single thread @@ -67,24 +75,26 @@ overflow-checks = false [features] default = ["esp32c6"] -esp32 = ["esp-rtos/esp32", "esp-hal/esp32", "esp-backtrace/esp32", "esp-println/esp32", "esp-radio/esp32", "esp-bootloader-esp-idf/esp32", "esp-metadata-generated/esp32", "portable-atomic/critical-section", "wifi"] -esp32c2 = ["esp-rtos/esp32c2", "esp-hal/esp32c2", "esp-backtrace/esp32c2", "esp-println/esp32c2", "esp-radio/esp32c2", "esp-bootloader-esp-idf/esp32c2", "esp-metadata-generated/esp32c2", "portable-atomic/critical-section", "wifi"] -esp32c3 = ["esp-rtos/esp32c3", "esp-hal/esp32c3", "esp-backtrace/esp32c3", "esp-println/esp32c3", "esp-radio/esp32c3", "esp-bootloader-esp-idf/esp32c3", "esp-metadata-generated/esp32c3", "portable-atomic/unsafe-assume-single-core", "wifi"] -esp32c6 = ["esp-rtos/esp32c6", "esp-hal/esp32c6", "esp-backtrace/esp32c6", "esp-println/esp32c6", "esp-radio/esp32c6", "esp-bootloader-esp-idf/esp32c6", "esp-metadata-generated/esp32c6", "portable-atomic/critical-section"] -esp32s3 = ["esp-rtos/esp32s3", "esp-hal/esp32s3", "esp-backtrace/esp32s3", "esp-println/esp32s3", "esp-radio/esp32s3", "esp-bootloader-esp-idf/esp32s3", "esp-metadata-generated/esp32s3", "portable-atomic/critical-section", "wifi"] -esp32h2 = ["esp-rtos/esp32h2", "esp-hal/esp32h2", "esp-backtrace/esp32h2", "esp-println/esp32h2", "esp-radio/esp32h2", "esp-bootloader-esp-idf/esp32h2", "esp-metadata-generated/esp32h2", "portable-atomic/critical-section", "thread"] +esp32 = ["esp-rtos/esp32", "esp-hal/esp32", "esp-backtrace/esp32", "esp-println/esp32", "esp-radio/esp32", "esp-storage/esp32", "esp-bootloader-esp-idf/esp32", "esp-metadata-generated/esp32", "portable-atomic/critical-section", "wifi"] +esp32c2 = ["esp-rtos/esp32c2", "esp-hal/esp32c2", "esp-backtrace/esp32c2", "esp-println/esp32c2", "esp-radio/esp32c2", "esp-storage/esp32c2", "esp-bootloader-esp-idf/esp32c2", "esp-metadata-generated/esp32c2", "portable-atomic/critical-section", "wifi"] +esp32c3 = ["esp-rtos/esp32c3", "esp-hal/esp32c3", "esp-backtrace/esp32c3", "esp-println/esp32c3", "esp-radio/esp32c3", "esp-storage/esp32c3", "esp-bootloader-esp-idf/esp32c3", "esp-metadata-generated/esp32c3", "portable-atomic/unsafe-assume-single-core", "wifi"] +esp32c6 = ["esp-rtos/esp32c6", "esp-hal/esp32c6", "esp-backtrace/esp32c6", "esp-println/esp32c6", "esp-radio/esp32c6", "esp-storage/esp32c6", "esp-bootloader-esp-idf/esp32c6", "esp-metadata-generated/esp32c6", "portable-atomic/critical-section"] +esp32s3 = ["esp-rtos/esp32s3", "esp-hal/esp32s3", "esp-backtrace/esp32s3", "esp-println/esp32s3", "esp-radio/esp32s3", "esp-storage/esp32s3", "esp-bootloader-esp-idf/esp32s3", "esp-metadata-generated/esp32s3", "portable-atomic/critical-section", "wifi"] +esp32h2 = ["esp-rtos/esp32h2", "esp-hal/esp32h2", "esp-backtrace/esp32h2", "esp-println/esp32h2", "esp-radio/esp32h2", "esp-storage/esp32h2", "esp-bootloader-esp-idf/esp32h2", "esp-metadata-generated/esp32h2", "portable-atomic/critical-section", "thread"] wifi = ["rs-matter-embassy/embassy-net"] thread = ["rs-matter-embassy/openthread"] [dependencies] log = "0.4" embassy-executor = "0.9" +embassy-embedded-hal = "0.3" esp-backtrace = { version = "0.18.1", features = ["panic-handler", "println"] } esp-hal = { version = "1", features = ["log-04", "unstable", "exception-handler"] } esp-rtos = { version = "0.2", features = ["esp-radio", "embassy"] } esp-alloc = { version = "0.9" } esp-println = { version = "0.16", features = ["log-04"] } esp-radio = { version = "0.17", features = ["ble", "log-04", "unstable"] } +esp-storage = "0.8" esp-bootloader-esp-idf = { version = "0.4", features = ["log-04"] } esp-metadata-generated = "0.3" rs-matter-embassy = { path = "../../rs-matter-embassy", features = ["esp", "log"] } diff --git a/examples/esp/src/bin/light_eth.rs b/examples/esp/src/bin/light_eth.rs index 8a0c964..48c8587 100644 --- a/examples/esp/src/bin/light_eth.rs +++ b/examples/esp/src/bin/light_eth.rs @@ -37,10 +37,10 @@ use rs_matter_embassy::matter::dm::devices::test::{ }; use rs_matter_embassy::matter::dm::devices::DEV_TYPE_ON_OFF_LIGHT; use rs_matter_embassy::matter::dm::{Async, Dataver, EmptyHandler, Endpoint, EpClMatcher, Node}; +use rs_matter_embassy::matter::persist::DummyKvBlobStore; use rs_matter_embassy::matter::utils::init::InitMaybeUninit; use rs_matter_embassy::matter::utils::select::Coalesce; use rs_matter_embassy::matter::{clusters, devices}; -use rs_matter_embassy::stack::persist::DummyKvBlobStore; use rs_matter_embassy::stack::rand::reseeding_csprng; use rs_matter_embassy::stack::utils::futures::IntoFaillble; @@ -151,13 +151,14 @@ async fn main(_s: Spawner) { Async(desc::DescHandler::new(Dataver::new_rand(&mut weak_rand)).adapt()), ); - // Create the persister & load any previously saved state - // `EmbassyPersist`+`EmbassyKvBlobStore` saves to a user-supplied NOR Flash region - // However, for this demo and for simplicity, we use a dummy persister that does nothing - let persist = stack - .create_persist_with_comm_window(&crypto, DummyKvBlobStore) - .await - .unwrap(); + // Create a KV BLOB store and load any previously saved state of `rs-matter` + // `SeqMapKvBlobStore` saves to a user-supplied NOR Flash region + // However, for this demo and for simplicity, we use a dummy KV BLOB store that does nothing + let mut kv = DummyKvBlobStore; + stack.startup(&crypto, &mut kv).await.unwrap(); + + // Wrap the KV BLOB store as a shared reference, so that it can be used both by `rs-matter` and the user + let kv = stack.create_shared_kv(kv).unwrap(); // Run the Matter stack with our handler // Using `pin!` is completely optional, but reduces the size of the final future @@ -168,12 +169,12 @@ async fn main(_s: Spawner) { weak_rand, stack, ), - // The Matter stack needs a persister to store its state - &persist, // The crypto provider &crypto, // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), + // The Matter stack needs a blob store to store its state + &kv, // No user future to run (), )); diff --git a/examples/esp/src/bin/light_eth2.rs b/examples/esp/src/bin/light_eth2.rs index 2368bcd..8f8bd0c 100644 --- a/examples/esp/src/bin/light_eth2.rs +++ b/examples/esp/src/bin/light_eth2.rs @@ -36,6 +36,7 @@ use rs_matter_embassy::matter::dm::devices::test::{ }; use rs_matter_embassy::matter::dm::devices::DEV_TYPE_ON_OFF_LIGHT; use rs_matter_embassy::matter::dm::{Async, Dataver, EmptyHandler, Endpoint, EpClMatcher, Node}; +use rs_matter_embassy::matter::persist::DummyKvBlobStore; use rs_matter_embassy::matter::utils::init::InitMaybeUninit; use rs_matter_embassy::matter::utils::select::Coalesce; use rs_matter_embassy::matter::{clusters, devices, BasicCommData}; @@ -43,7 +44,6 @@ use rs_matter_embassy::ot::openthread::esp::EspRadio; use rs_matter_embassy::ot::openthread::{OpenThread, RamSettings}; use rs_matter_embassy::ot::{OtMatterResources, OtMdns, OtNetStack, OtNetif}; use rs_matter_embassy::stack::eth::EthMatterStack; -use rs_matter_embassy::stack::persist::DummyKvBlobStore; use rs_matter_embassy::stack::rand::{reseeding_csprng, RngAdaptor}; use tinyrlibc as _; @@ -189,13 +189,14 @@ async fn main(_s: Spawner) { Async(desc::DescHandler::new(Dataver::new_rand(&mut weak_rand)).adapt()), ); - // Create the persister & load any previously saved state - // `EmbassyPersist`+`EmbassyKvBlobStore` saves to a user-supplied NOR Flash region - // However, for this demo and for simplicity, we use a dummy persister that does nothing - let persist = stack - .create_persist_with_comm_window(&crypto, DummyKvBlobStore) - .await - .unwrap(); + // Create a KV BLOB store and load any previously saved state of `rs-matter` + // `SeqMapKvBlobStore` saves to a user-supplied NOR Flash region + // However, for this demo and for simplicity, we use a dummy KV BLOB store that does nothing + let mut kv = DummyKvBlobStore; + stack.startup(&crypto, &mut kv).await.unwrap(); + + // Wrap the KV BLOB store as a shared reference, so that it can be used both by `rs-matter` and the user + let kv = stack.create_shared_kv(kv).unwrap(); // Run the Matter stack with our handler // Using `pin!` is completely optional, but reduces the size of the final future @@ -206,12 +207,12 @@ async fn main(_s: Spawner) { OtNetif::new(ot.clone()), // The Matter stack needs an mDNS instance to run OtMdns::new(ot.clone()), - // The Matter stack needs a persister to store its state - &persist, // The crypto provider &crypto, // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), + // The Matter stack needs a blob store to store its state + &kv, // No user future to run (), )); diff --git a/examples/esp/src/bin/light_thread.rs b/examples/esp/src/bin/light_thread.rs index 599b829..a90b893 100644 --- a/examples/esp/src/bin/light_thread.rs +++ b/examples/esp/src/bin/light_thread.rs @@ -33,9 +33,9 @@ use rs_matter_embassy::matter::dm::devices::test::{ }; use rs_matter_embassy::matter::dm::devices::DEV_TYPE_ON_OFF_LIGHT; use rs_matter_embassy::matter::dm::{Async, Dataver, EmptyHandler, Endpoint, EpClMatcher, Node}; +use rs_matter_embassy::matter::persist::DummyKvBlobStore; use rs_matter_embassy::matter::utils::init::InitMaybeUninit; use rs_matter_embassy::matter::{clusters, devices, BasicCommData}; -use rs_matter_embassy::stack::persist::DummyKvBlobStore; use rs_matter_embassy::stack::rand::reseeding_csprng; use rs_matter_embassy::wireless::esp::EspThreadDriver; use rs_matter_embassy::wireless::{EmbassyThread, EmbassyThreadMatterStack}; @@ -157,13 +157,14 @@ async fn main(_s: Spawner) { Async(desc::DescHandler::new(Dataver::new_rand(&mut weak_rand)).adapt()), ); - // Create the persister & load any previously saved state - // `EmbassyPersist`+`EmbassyKvBlobStore` saves to a user-supplied NOR Flash region - // However, for this demo and for simplicity, we use a dummy persister that does nothing - let persist = stack - .create_persist_with_comm_window(&crypto, DummyKvBlobStore) - .await - .unwrap(); + // Create a KV BLOB store and load any previously saved state of `rs-matter` + // `SeqMapKvBlobStore` saves to a user-supplied NOR Flash region + // However, for this demo and for simplicity, we use a dummy KV BLOB store that does nothing + let mut kv = DummyKvBlobStore; + stack.startup(&crypto, &mut kv).await.unwrap(); + + // Wrap the KV BLOB store as a shared reference, so that it can be used both by `rs-matter` and the user + let kv = stack.create_shared_kv(kv).unwrap(); // Run the Matter stack with our handler // Using `pin!` is completely optional, but reduces the size of the final future @@ -175,16 +176,16 @@ async fn main(_s: Spawner) { EspThreadDriver::new(peripherals.IEEE802154, peripherals.BT), crypto.rand().unwrap(), ieee_eui64, - persist.store(), + &kv, stack, true, // Use a random BLE address ), - // The Matter stack needs a persister to store its state - &persist, // The crypto provider &crypto, // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), + // The Matter stack needs a blob store to store its state + &kv, // No user future to run (), )); diff --git a/examples/esp/src/bin/light_thread_coex.rs b/examples/esp/src/bin/light_thread_coex.rs index 4251d46..ece482f 100644 --- a/examples/esp/src/bin/light_thread_coex.rs +++ b/examples/esp/src/bin/light_thread_coex.rs @@ -34,9 +34,9 @@ use rs_matter_embassy::matter::dm::devices::test::{ }; use rs_matter_embassy::matter::dm::devices::DEV_TYPE_ON_OFF_LIGHT; use rs_matter_embassy::matter::dm::{Async, Dataver, EmptyHandler, Endpoint, EpClMatcher, Node}; +use rs_matter_embassy::matter::persist::DummyKvBlobStore; use rs_matter_embassy::matter::utils::init::InitMaybeUninit; use rs_matter_embassy::matter::{clusters, devices, BasicCommData}; -use rs_matter_embassy::stack::persist::DummyKvBlobStore; use rs_matter_embassy::stack::rand::reseeding_csprng; use rs_matter_embassy::wireless::esp::EspThreadDriver; use rs_matter_embassy::wireless::{EmbassyThread, EmbassyThreadMatterStack}; @@ -158,13 +158,14 @@ async fn main(_s: Spawner) { Async(desc::DescHandler::new(Dataver::new_rand(&mut weak_rand)).adapt()), ); - // Create the persister & load any previously saved state - // `EmbassyPersist`+`EmbassyKvBlobStore` saves to a user-supplied NOR Flash region - // However, for this demo and for simplicity, we use a dummy persister that does nothing - let persist = stack - .create_persist_with_comm_window(&crypto, DummyKvBlobStore) - .await - .unwrap(); + // Create a KV BLOB store and load any previously saved state of `rs-matter` + // `SeqMapKvBlobStore` saves to a user-supplied NOR Flash region + // However, for this demo and for simplicity, we use a dummy KV BLOB store that does nothing + let mut kv = DummyKvBlobStore; + stack.startup(&crypto, &mut kv).await.unwrap(); + + // Wrap the KV BLOB store as a shared reference, so that it can be used both by `rs-matter` and the user + let kv = stack.create_shared_kv(kv).unwrap(); // Run the Matter stack with our handler // Using `pin!` is completely optional, but reduces the size of the final future @@ -176,16 +177,16 @@ async fn main(_s: Spawner) { EspThreadDriver::new(peripherals.IEEE802154, peripherals.BT), crypto.rand().unwrap(), ieee_eui64, - persist.store(), + &kv, stack, true, // Use a random BLE address ), - // The Matter stack needs a persister to store its state - &persist, // The crypto provider &crypto, // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), + // The Matter stack needs a blob store to store its state + &kv, // No user future to run (), )); diff --git a/examples/esp/src/bin/light_wifi.rs b/examples/esp/src/bin/light_wifi.rs index 678542d..d09d01c 100644 --- a/examples/esp/src/bin/light_wifi.rs +++ b/examples/esp/src/bin/light_wifi.rs @@ -36,9 +36,9 @@ use rs_matter_embassy::matter::dm::devices::test::{ }; use rs_matter_embassy::matter::dm::devices::DEV_TYPE_ON_OFF_LIGHT; use rs_matter_embassy::matter::dm::{Async, Dataver, EmptyHandler, Endpoint, EpClMatcher, Node}; +use rs_matter_embassy::matter::persist::DummyKvBlobStore; use rs_matter_embassy::matter::utils::init::InitMaybeUninit; use rs_matter_embassy::matter::{clusters, devices}; -use rs_matter_embassy::stack::persist::DummyKvBlobStore; use rs_matter_embassy::stack::rand::reseeding_csprng; use rs_matter_embassy::wireless::esp::EspWifiDriver; use rs_matter_embassy::wireless::{EmbassyWifi, EmbassyWifiMatterStack}; @@ -143,13 +143,14 @@ async fn main(_s: Spawner) { Async(desc::DescHandler::new(Dataver::new_rand(&mut weak_rand)).adapt()), ); - // Create the persister & load any previously saved state - // `EmbassyPersist`+`EmbassyKvBlobStore` saves to a user-supplied NOR Flash region - // However, for this demo and for simplicity, we use a dummy persister that does nothing - let persist = stack - .create_persist_with_comm_window(&crypto, DummyKvBlobStore) - .await - .unwrap(); + // Create a KV BLOB store and load any previously saved state of `rs-matter` + // `SeqMapKvBlobStore` saves to a user-supplied NOR Flash region + // However, for this demo and for simplicity, we use a dummy KV BLOB store that does nothing + let mut kv = DummyKvBlobStore; + stack.startup(&crypto, &mut kv).await.unwrap(); + + // Wrap the KV BLOB store as a shared reference, so that it can be used both by `rs-matter` and the user + let kv = stack.create_shared_kv(kv).unwrap(); // Run the Matter stack with our handler // Using `pin!` is completely optional, but reduces the size of the final future @@ -163,12 +164,12 @@ async fn main(_s: Spawner) { true, // Use a random BLE address stack, ), - // The Matter stack needs a persister to store its state - &persist, // The crypto provider &crypto, // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), + // The Matter stack needs a blob store to store its state + &kv, // No user future to run (), )); diff --git a/examples/esp/src/bin/light_wifi_persistent.rs b/examples/esp/src/bin/light_wifi_persistent.rs new file mode 100644 index 0000000..297a54d --- /dev/null +++ b/examples/esp/src/bin/light_wifi_persistent.rs @@ -0,0 +1,294 @@ +//! An example utilizing the `EmbassyWifiMatterStack` struct +//! and additionally persisting the `rs-matter` state to the NOR Flash. +//! +//! As the name suggests, this Matter stack assembly uses Wifi as the main transport, +//! and thus BLE for commissioning. +//! +//! If you want to use Ethernet, utilize `EmbassyEthMatterStack` instead. +//! If you want to use non-concurrent commissioning, call `run` instead of `run_coex` +//! and provision a higher `BUMP_SIZE` because the non-concurrent commissioning has slightly higher +//! memory requirements on the futures' sizes. +//! (Note: Alexa does not work (yet) with non-concurrent commissioning.) +//! +//! The example implements a fictitious Light device (an On-Off Matter cluster). +#![no_std] +#![no_main] +#![recursion_limit = "256"] + +use core::borrow::BorrowMut; +use core::pin::pin; + +use embassy_embedded_hal::adapter::BlockingAsync; +use embassy_executor::Spawner; + +use embassy_futures::select::{select, Either}; +use esp_alloc::heap_allocator; +use esp_backtrace as _; +use esp_bootloader_esp_idf::partitions::{ + read_partition_table, DataPartitionSubType, PartitionType, PARTITION_TABLE_MAX_LEN, +}; +use esp_hal::gpio::{Input, InputConfig, Pull}; +use esp_hal::ram; +use esp_hal::timer::timg::TimerGroup; +use esp_metadata_generated::memory_range; +use esp_storage::FlashStorage; + +use log::{info, warn}; + +use rs_matter_embassy::epoch::epoch; +use rs_matter_embassy::matter::crypto::{default_crypto, Crypto}; +use rs_matter_embassy::matter::dm::clusters::desc::{self, ClusterHandler as _}; +use rs_matter_embassy::matter::dm::clusters::on_off::test::TestOnOffDeviceLogic; +use rs_matter_embassy::matter::dm::clusters::on_off::{self, OnOffHooks}; +use rs_matter_embassy::matter::dm::devices::test::{ + DAC_PRIVKEY, TEST_DEV_ATT, TEST_DEV_COMM, TEST_DEV_DET, +}; +use rs_matter_embassy::matter::dm::devices::DEV_TYPE_ON_OFF_LIGHT; +use rs_matter_embassy::matter::dm::{Async, Dataver, EmptyHandler, Endpoint, EpClMatcher, Node}; +use rs_matter_embassy::matter::error::Error; +use rs_matter_embassy::matter::persist::KvBlobStore; +use rs_matter_embassy::matter::utils::init::InitMaybeUninit; +use rs_matter_embassy::matter::utils::select::Coalesce; +use rs_matter_embassy::matter::{clusters, devices}; +use rs_matter_embassy::persist::SeqMapKvBlobStore; +use rs_matter_embassy::stack::rand::reseeding_csprng; +use rs_matter_embassy::wireless::esp::EspWifiDriver; +use rs_matter_embassy::wireless::{EmbassyWifi, EmbassyWifiMatterStack}; + +extern crate alloc; + +macro_rules! mk_static { + ($t:ty) => {{ + #[cfg(not(feature = "esp32"))] + { + static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new(); + STATIC_CELL.uninit() + } + #[cfg(feature = "esp32")] + alloc::boxed::Box::leak(alloc::boxed::Box::<$t>::new_uninit()) + }}; +} + +/// The amount of memory for allocating all `rs-matter-stack` futures created during +/// the execution of the `run*` methods. +/// This does NOT include the rest of the Matter stack. +/// +/// The futures of `rs-matter-stack` created during the execution of the `run*` methods +/// are allocated in a special way using a small bump allocator which results +/// in a much lower memory usage by those. +/// +/// If - for your platform - this size is not enough, increase it until +/// the program runs without panics during the stack initialization. +const BUMP_SIZE: usize = 18000; + +/// Heap strictly necessary only for Wifi+BLE and for the only Matter dependency which needs (~4KB) alloc - `x509` +#[cfg(not(feature = "esp32"))] +const HEAP_SIZE: usize = 100 * 1024; +/// On the esp32, we allocate the Matter Stack from heap as well, due to the non-contiguous memory regions on that chip +#[cfg(feature = "esp32")] +const HEAP_SIZE: usize = 140 * 1024; + +const RECLAIMED_RAM: usize = + memory_range!("DRAM2_UNINIT").end - memory_range!("DRAM2_UNINIT").start; + +const RESET_SECS: u64 = 3; + +esp_bootloader_esp_idf::esp_app_desc!(); + +#[esp_rtos::main] +async fn main(_s: Spawner) { + esp_println::logger::init_logger_from_env(); + + info!("Starting..."); + + heap_allocator!(size: HEAP_SIZE - RECLAIMED_RAM); + heap_allocator!(#[ram(reclaimed)] size: RECLAIMED_RAM); + + // Necessary `esp-hal` and `esp-wifi` initialization boilerplate + + let peripherals = esp_hal::init(esp_hal::Config::default()); + + let timg0 = TimerGroup::new(peripherals.TIMG0); + esp_rtos::start( + timg0.timer0, + #[cfg(target_arch = "riscv32")] + esp_hal::interrupt::software::SoftwareInterruptControl::new(peripherals.SW_INTERRUPT) + .software_interrupt0, + ); + + // Allocate the Matter stack. + // For MCUs, it is best to allocate it statically, so as to avoid program stack blowups (its memory footprint is ~ 35 to 50KB). + // It is also (currently) a mandatory requirement when the wireless stack variation is used. + let stack = mk_static!(EmbassyWifiMatterStack::).init_with( + EmbassyWifiMatterStack::init(&TEST_DEV_DET, TEST_DEV_COMM, &TEST_DEV_ATT, epoch), + ); + + // Create the crypto provider, using the `esp-hal` TRNG/ADC1 as the source of randomness for a reseeding CSPRNG. + let _trng_source = esp_hal::rng::TrngSource::new(peripherals.RNG, peripherals.ADC1); + let crypto = default_crypto( + reseeding_csprng(esp_hal::rng::Trng::try_new().unwrap(), 1000).unwrap(), + DAC_PRIVKEY, + ); + + let mut weak_rand = crypto.weak_rand().unwrap(); + + // Our "light" on-off cluster. + // It will toggle the light state every 5 seconds + let on_off = on_off::OnOffHandler::new_standalone( + Dataver::new_rand(&mut weak_rand), + LIGHT_ENDPOINT_ID, + TestOnOffDeviceLogic::new(true), + ); + + // Chain our endpoint clusters + let handler = EmptyHandler + // Our on-off cluster, on Endpoint 1 + .chain( + EpClMatcher::new( + Some(LIGHT_ENDPOINT_ID), + Some(TestOnOffDeviceLogic::CLUSTER.id), + ), + on_off::HandlerAsyncAdaptor(&on_off), + ) + // Each Endpoint needs a Descriptor cluster too + // Just use the one that `rs-matter` provides out of the box + .chain( + EpClMatcher::new(Some(LIGHT_ENDPOINT_ID), Some(desc::DescHandler::CLUSTER.id)), + Async(desc::DescHandler::new(Dataver::new_rand(&mut weak_rand)).adapt()), + ); + + // Create a KV BLOB store and load any previously saved state of `rs-matter` + let mut kv = get_persistent_store(peripherals.FLASH, stack.kv_store_buf().unwrap()); + stack.startup(&crypto, &mut kv).await.unwrap(); + + if stack.is_commissioned() { + info!( + "To reset, press and hold the Boot Mode pin (GPIO9) for {} or more seconds", + RESET_SECS + ); + } + + { + // Wrap the KV BLOB store as a shared reference, so that it can be used both by `rs-matter` and the user + let kv = stack.create_shared_kv(&mut kv).unwrap(); + + // Run the Matter stack with our handler + // Using `pin!` is completely optional, but reduces the size of the final future + // + // This step can be repeated in that the stack can be stopped and started multiple times, as needed. + let mut matter = pin!(stack.run_coex( + // The Matter stack needs to instantiate an `embassy-net` `Driver` and `Controller` + EmbassyWifi::new( + EspWifiDriver::new(peripherals.WIFI, peripherals.BT), + weak_rand, + true, // Use a random BLE address + stack, + ), + // The crypto provider + &crypto, + // Our `AsyncHandler` + `AsyncMetadata` impl + (NODE, handler), + // The Matter stack needs a blob store to store its state + &kv, + // No user future to run + (), + )); + + // Run Matter and also wait for a reset signal + let mut wait_reset = pin!(wait_pin_low(Input::new( + peripherals.GPIO9, + InputConfig::default().with_pull(Pull::Down) + ))); + + select(&mut matter, &mut wait_reset) + .coalesce() + .await + .unwrap(); + } + + // If we get here, with no errors, this means the user is willing to reset the storage + // by holding the BOOT pin low 3 or more seconds + warn!("Resetting storage"); + + stack.reset(kv).await.unwrap(); + + warn!("Rebooting..."); + + esp_hal::system::software_reset() +} + +/// Endpoint 0 (the root endpoint) always runs +/// the hidden Matter system clusters, so we pick ID=1 +const LIGHT_ENDPOINT_ID: u16 = 1; + +/// The Matter Light device Node +const NODE: Node = Node { + id: 0, + endpoints: &[ + EmbassyWifiMatterStack::<0, ()>::root_endpoint(), + Endpoint { + id: LIGHT_ENDPOINT_ID, + device_types: devices!(DEV_TYPE_ON_OFF_LIGHT), + clusters: clusters!(desc::DescHandler::CLUSTER, TestOnOffDeviceLogic::CLUSTER), + }, + ], +}; + +/// The BLOB storage returned by this function is persisting in the first partition of type 'NVS' +/// found in the NOR-FLASH of the chip. +/// +/// If no such partition is found, the function will panic. +/// +/// You can alter this function to persist to a different partition, +/// or to use a completely different storage backend, as long as you return an implementation of `KvBlobStore`. +fn get_persistent_store<'d>( + flash: esp_hal::peripherals::FLASH<'d>, + mut buf: impl BorrowMut<[u8]>, +) -> impl KvBlobStore + 'd { + let mut flash = FlashStorage::new(flash); + let pt_buf = &mut buf.borrow_mut()[..PARTITION_TABLE_MAX_LEN]; + let pt = read_partition_table(&mut flash, pt_buf).unwrap(); + let nvs = pt + .find_partition(PartitionType::Data(DataPartitionSubType::Nvs)) + .unwrap() + .unwrap(); + + let start = nvs.offset(); + let end = nvs.offset() + nvs.len(); + info!( + "Will use NVS partition \"{}\" at {:#x}..{:#x}", + nvs.label_as_str(), + start, + end + ); + + SeqMapKvBlobStore::new(BlockingAsync::new(flash), start..end) +} + +async fn wait_pin_low(mut pin: Input<'_>) -> Result<(), Error> { + loop { + pin.wait_for_low().await; + + // Debounce + embassy_time::Timer::after_millis(50).await; + + if pin.is_low() { + warn!( + "Detected Boot Mode pin low, keep it low for {} more seconds to reset the storage", + RESET_SECS + ); + + let result = select( + pin.wait_for_high(), + embassy_time::Timer::after_secs(RESET_SECS), + ) + .await; + + if matches!(result, Either::Second(())) { + break; + } + } + } + + Ok(()) +} diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index 5db2968..3e1dd4b 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -29,10 +29,11 @@ incremental = false overflow-checks = false [patch.crates-io] -#rs-matter = { git = "https://github.com/project-chip/rs-matter" } -rs-matter = { git = "https://github.com/sysgrok/rs-matter", branch = "next" } +#rs-matter = { git = "https://github.com/sysgrok/rs-matter", branch = "next" } +rs-matter = { git = "https://github.com/sysgrok/rs-matter", branch = "inband-persistence" } #rs-matter = { path = "../../../rs-matter/rs-matter" } -rs-matter-stack = { git = "https://github.com/sysgrok/rs-matter-stack.git", branch = "next" } +#rs-matter-stack = { git = "https://github.com/sysgrok/rs-matter-stack.git", branch = "next" } +rs-matter-stack = { git = "https://github.com/ivmarkov/rs-matter-stack.git", branch = "inband-persistence" } #rs-matter-stack = { path = "../../../rs-matter-stack" } openthread = { git = "https://github.com/sysgrok/openthread.git", branch = "next" } #openthread = { version = "0.1", path = "../../../openthread/openthread" } diff --git a/examples/nrf/src/bin/light_eth.rs b/examples/nrf/src/bin/light_eth.rs index bebc247..a92c0a7 100644 --- a/examples/nrf/src/bin/light_eth.rs +++ b/examples/nrf/src/bin/light_eth.rs @@ -38,6 +38,7 @@ use rs_matter_embassy::matter::dm::devices::test::{ }; use rs_matter_embassy::matter::dm::devices::DEV_TYPE_ON_OFF_LIGHT; use rs_matter_embassy::matter::dm::{Async, Dataver, EmptyHandler, Endpoint, EpClMatcher, Node}; +use rs_matter_embassy::matter::persist::DummyKvBlobStore; use rs_matter_embassy::matter::utils::init::InitMaybeUninit; use rs_matter_embassy::matter::utils::select::Coalesce; use rs_matter_embassy::matter::{clusters, devices, BasicCommData}; @@ -49,7 +50,6 @@ use rs_matter_embassy::ot::openthread::{ }; use rs_matter_embassy::ot::{OtMatterResources, OtMdns, OtNetStack, OtNetif}; use rs_matter_embassy::stack::eth::EthMatterStack; -use rs_matter_embassy::stack::persist::DummyKvBlobStore; use panic_rtt_target as _; @@ -216,10 +216,14 @@ async fn main(_s: Spawner) { Async(desc::DescHandler::new(Dataver::new_rand(&mut weak_rand)).adapt()), ); - let persist = stack - .create_persist_with_comm_window(&crypto, DummyKvBlobStore) - .await - .unwrap(); + // Create a KV BLOB store and load any previously saved state of `rs-matter` + // `SeqMapKvBlobStore` saves to a user-supplied NOR Flash region + // However, for this demo and for simplicity, we use a dummy KV BLOB store that does nothing + let mut kv = DummyKvBlobStore; + stack.startup(&crypto, &mut kv).await.unwrap(); + + // Wrap the KV BLOB store as a shared reference, so that it can be used both by `rs-matter` and the user + let kv = stack.create_shared_kv(kv).unwrap(); // Run the Matter stack with our handler // Using `pin!` is completely optional, but reduces the size of the final future @@ -232,12 +236,12 @@ async fn main(_s: Spawner) { OtNetif::new(ot.clone()), // The Matter stack needs an mDNS to run OtMdns::new(ot.clone()), - // The Matter stack needs a persister to store its state - &persist, // The crypto provider &crypto, // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), + // The Matter stack needs a blob store to store its state + &kv, // No user future to run (), )); diff --git a/examples/nrf/src/bin/light_thread.rs b/examples/nrf/src/bin/light_thread.rs index 562cd95..d797f09 100644 --- a/examples/nrf/src/bin/light_thread.rs +++ b/examples/nrf/src/bin/light_thread.rs @@ -35,9 +35,9 @@ use rs_matter_embassy::matter::dm::devices::test::{ }; use rs_matter_embassy::matter::dm::devices::DEV_TYPE_ON_OFF_LIGHT; use rs_matter_embassy::matter::dm::{Async, Dataver, EmptyHandler, Endpoint, EpClMatcher, Node}; +use rs_matter_embassy::matter::persist::DummyKvBlobStore; use rs_matter_embassy::matter::utils::init::InitMaybeUninit; use rs_matter_embassy::matter::{clusters, devices, BasicCommData}; -use rs_matter_embassy::stack::persist::DummyKvBlobStore; use rs_matter_embassy::stack::rand::reseeding_csprng; use rs_matter_embassy::wireless::nrf::{ NrfThreadClockInterruptHandler, NrfThreadHighPrioInterruptHandler, @@ -206,10 +206,14 @@ async fn main(_s: Spawner) { Async(desc::DescHandler::new(Dataver::new_rand(&mut weak_rand)).adapt()), ); - let persist = stack - .create_persist_with_comm_window(&crypto, DummyKvBlobStore) - .await - .unwrap(); + // Create a KV BLOB store and load any previously saved state of `rs-matter` + // `SeqMapKvBlobStore` saves to a user-supplied NOR Flash region + // However, for this demo and for simplicity, we use a dummy KV BLOB store that does nothing + let mut kv = DummyKvBlobStore; + stack.startup(&crypto, &mut kv).await.unwrap(); + + // Wrap the KV BLOB store as a shared reference, so that it can be used both by `rs-matter` and the user + let kv = stack.create_shared_kv(kv).unwrap(); // Run the Matter stack with our handler // Using `pin!` is completely optional, but reduces the size of the final future @@ -221,16 +225,16 @@ async fn main(_s: Spawner) { thread_driver, crypto.rand().unwrap(), ieee_eui64, - persist.store(), + &kv, stack, true, // Use a random BLE address ), - // The Matter stack needs a persister to store its state - &persist, // The crypto provider &crypto, // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), + // The Matter stack needs a blob store to store its state + &kv, // No user future to run (), )); diff --git a/examples/nrf/src/bin/light_thread_coex.rs b/examples/nrf/src/bin/light_thread_coex.rs index 33cbcb2..4ad5b7c 100644 --- a/examples/nrf/src/bin/light_thread_coex.rs +++ b/examples/nrf/src/bin/light_thread_coex.rs @@ -32,9 +32,9 @@ use rs_matter_embassy::matter::dm::devices::test::{ }; use rs_matter_embassy::matter::dm::devices::DEV_TYPE_ON_OFF_LIGHT; use rs_matter_embassy::matter::dm::{Async, Dataver, EmptyHandler, Endpoint, EpClMatcher, Node}; +use rs_matter_embassy::matter::persist::DummyKvBlobStore; use rs_matter_embassy::matter::utils::init::InitMaybeUninit; use rs_matter_embassy::matter::{clusters, devices, BasicCommData}; -use rs_matter_embassy::stack::persist::DummyKvBlobStore; use rs_matter_embassy::stack::rand::reseeding_csprng; use rs_matter_embassy::wireless::nrf::{ Egu0InterruptHandler, LpTimerInterruptHandler, NrfThreadClockInterruptHandler, @@ -179,10 +179,14 @@ async fn main(_s: Spawner) { Async(desc::DescHandler::new(Dataver::new_rand(&mut weak_rand)).adapt()), ); - let persist = stack - .create_persist_with_comm_window(&crypto, DummyKvBlobStore) - .await - .unwrap(); + // Create a KV BLOB store and load any previously saved state of `rs-matter` + // `SeqMapKvBlobStore` saves to a user-supplied NOR Flash region + // However, for this demo and for simplicity, we use a dummy KV BLOB store that does nothing + let mut kv = DummyKvBlobStore; + stack.startup(&crypto, &mut kv).await.unwrap(); + + // Wrap the KV BLOB store as a shared reference, so that it can be used both by `rs-matter` and the user + let kv = stack.create_shared_kv(kv).unwrap(); // Run the Matter stack with our handler // Using `pin!` is completely optional, but reduces the size of the final future @@ -194,16 +198,16 @@ async fn main(_s: Spawner) { thread_driver, crypto.rand().unwrap(), ieee_eui64, - persist.store(), + &kv, stack, true, // Use a random BLE address ), - // The Matter stack needs a persister to store its state - &persist, // The crypto provider &crypto, // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), + // The Matter stack needs a blob store to store its state + &kv, // No user future to run (), )); diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index cfa258b..d4c40a5 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -25,10 +25,11 @@ incremental = false overflow-checks = false [patch.crates-io] -#rs-matter = { git = "https://github.com/project-chip/rs-matter" } -rs-matter = { git = "https://github.com/sysgrok/rs-matter", branch = "next" } +#rs-matter = { git = "https://github.com/sysgrok/rs-matter", branch = "next" } +rs-matter = { git = "https://github.com/sysgrok/rs-matter", branch = "inband-persistence" } #rs-matter = { path = "../../../rs-matter/rs-matter" } -rs-matter-stack = { git = "https://github.com/sysgrok/rs-matter-stack.git", branch = "next" } +#rs-matter-stack = { git = "https://github.com/sysgrok/rs-matter-stack.git", branch = "next" } +rs-matter-stack = { git = "https://github.com/ivmarkov/rs-matter-stack.git", branch = "inband-persistence" } #rs-matter-stack = { path = "../../../rs-matter-stack" } openthread = { git = "https://github.com/sysgrok/openthread.git", branch = "next" } #openthread = { version = "0.1", path = "../../../openthread/openthread" } diff --git a/examples/rp/src/bin/light_eth.rs b/examples/rp/src/bin/light_eth.rs index b9e031e..f445bf1 100644 --- a/examples/rp/src/bin/light_eth.rs +++ b/examples/rp/src/bin/light_eth.rs @@ -42,9 +42,9 @@ use rs_matter_embassy::matter::dm::devices::test::{ }; use rs_matter_embassy::matter::dm::devices::DEV_TYPE_ON_OFF_LIGHT; use rs_matter_embassy::matter::dm::{Async, Dataver, EmptyHandler, Endpoint, EpClMatcher, Node}; +use rs_matter_embassy::matter::persist::DummyKvBlobStore; use rs_matter_embassy::matter::utils::init::InitMaybeUninit; use rs_matter_embassy::matter::{clusters, devices}; -use rs_matter_embassy::stack::persist::DummyKvBlobStore; use defmt::{info, unwrap}; @@ -160,10 +160,14 @@ async fn main(spawner: Spawner) { Async(desc::DescHandler::new(Dataver::new_rand(&mut weak_rand)).adapt()), ); - let persist = stack - .create_persist_with_comm_window(&crypto, DummyKvBlobStore) - .await - .unwrap(); + // Create a KV BLOB store and load any previously saved state of `rs-matter` + // `SeqMapKvBlobStore` saves to a user-supplied NOR Flash region + // However, for this demo and for simplicity, we use a dummy KV BLOB store that does nothing + let mut kv = DummyKvBlobStore; + stack.startup(&crypto, &mut kv).await.unwrap(); + + // Wrap the KV BLOB store as a shared reference, so that it can be used both by `rs-matter` and the user + let kv = stack.create_shared_kv(kv).unwrap(); // Run the Matter stack with our handler // Using `pin!` is completely optional, but reduces the size of the final future @@ -176,12 +180,12 @@ async fn main(spawner: Spawner) { crypto.rand().unwrap(), stack, ), - // The Matter stack needs a persister to store its state - &persist, // The crypto provider &crypto, // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), + // The Matter stack needs a blob store to store its state + &kv, // No user future to run (), )); diff --git a/examples/rp/src/bin/light_wifi.rs b/examples/rp/src/bin/light_wifi.rs index abd3c64..8e20880 100644 --- a/examples/rp/src/bin/light_wifi.rs +++ b/examples/rp/src/bin/light_wifi.rs @@ -44,9 +44,9 @@ use rs_matter_embassy::matter::dm::devices::test::{ }; use rs_matter_embassy::matter::dm::devices::DEV_TYPE_ON_OFF_LIGHT; use rs_matter_embassy::matter::dm::{Async, Dataver, EmptyHandler, Endpoint, EpClMatcher, Node}; +use rs_matter_embassy::matter::persist::DummyKvBlobStore; use rs_matter_embassy::matter::utils::init::InitMaybeUninit; use rs_matter_embassy::matter::{clusters, devices}; -use rs_matter_embassy::stack::persist::DummyKvBlobStore; use rs_matter_embassy::stack::rand::reseeding_csprng; use rs_matter_embassy::wireless::rp::RpWifiDriver; use rs_matter_embassy::wireless::{EmbassyWifi, EmbassyWifiMatterStack}; @@ -155,10 +155,14 @@ async fn main(_spawner: Spawner) { Async(desc::DescHandler::new(Dataver::new_rand(&mut weak_rand)).adapt()), ); - let persist = stack - .create_persist_with_comm_window(&crypto, DummyKvBlobStore) - .await - .unwrap(); + // Create a KV BLOB store and load any previously saved state of `rs-matter` + // `SeqMapKvBlobStore` saves to a user-supplied NOR Flash region + // However, for this demo and for simplicity, we use a dummy KV BLOB store that does nothing + let mut kv = DummyKvBlobStore; + stack.startup(&crypto, &mut kv).await.unwrap(); + + // Wrap the KV BLOB store as a shared reference, so that it can be used both by `rs-matter` and the user + let kv = stack.create_shared_kv(kv).unwrap(); // Run the Matter stack with our handler // Using `pin!` is completely optional, but reduces the size of the final future @@ -175,14 +179,12 @@ async fn main(_spawner: Spawner) { true, // Use a random BLE address stack, ), - // The Matter stack needs a persister to store its state - // `EmbassyPersist`+`EmbassyKvBlobStore` saves to a user-supplied NOR Flash region - // However, for this demo and for simplicity, we use a dummy persister that does nothing - &persist, // The crypto provider &crypto, // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), + // The Matter stack needs a blob store to store its state + &kv, // No user future to run (), )); diff --git a/rs-matter-embassy/README.md b/rs-matter-embassy/README.md index 3687027..3bf2515 100644 --- a/rs-matter-embassy/README.md +++ b/rs-matter-embassy/README.md @@ -54,9 +54,9 @@ use rs_matter_embassy::matter::dm::devices::test::{ }; use rs_matter_embassy::matter::dm::devices::DEV_TYPE_ON_OFF_LIGHT; use rs_matter_embassy::matter::dm::{Async, Dataver, EmptyHandler, Endpoint, EpClMatcher, Node}; +use rs_matter_embassy::matter::persist::DummyKvBlobStore; use rs_matter_embassy::matter::utils::init::InitMaybeUninit; use rs_matter_embassy::matter::{clusters, devices}; -use rs_matter_embassy::stack::persist::DummyKvBlobStore; use rs_matter_embassy::stack::rand::reseeding_csprng; use rs_matter_embassy::wireless::esp::EspWifiDriver; use rs_matter_embassy::wireless::{EmbassyWifi, EmbassyWifiMatterStack}; @@ -161,13 +161,14 @@ async fn main(_s: Spawner) { Async(desc::DescHandler::new(Dataver::new_rand(&mut weak_rand)).adapt()), ); - // Create the persister & load any previously saved state - // `EmbassyPersist`+`EmbassyKvBlobStore` saves to a user-supplied NOR Flash region - // However, for this demo and for simplicity, we use a dummy persister that does nothing - let persist = stack - .create_persist_with_comm_window(&crypto, DummyKvBlobStore) - .await - .unwrap(); + // Create a KV BLOB store and load any previously saved state of `rs-matter` + // `SeqMapKvBlobStore` saves to a user-supplied NOR Flash region + // However, for this demo and for simplicity, we use a dummy KV BLOB store that does nothing + let mut kv = DummyKvBlobStore; + stack.startup(&crypto, &mut kv).await.unwrap(); + + // Wrap the KV BLOB store as a shared reference, so that it can be used both by `rs-matter` and the user + let kv = stack.create_shared_kv(kv).unwrap(); // Run the Matter stack with our handler // Using `pin!` is completely optional, but reduces the size of the final future @@ -181,12 +182,12 @@ async fn main(_s: Spawner) { true, // Use a random BLE address stack, ), - // The Matter stack needs a persister to store its state - &persist, // The crypto provider &crypto, // Our `AsyncHandler` + `AsyncMetadata` impl (NODE, handler), + // The Matter stack needs a blob store to store its state + &kv, // No user future to run (), )); diff --git a/rs-matter-embassy/src/ot.rs b/rs-matter-embassy/src/ot.rs index 80bac1f..645d07f 100644 --- a/rs-matter-embassy/src/ot.rs +++ b/rs-matter-embassy/src/ot.rs @@ -1,6 +1,7 @@ //! Network interface: `OtNetif` - a `Netif` trait implementation for `openthread` //! mDNS impl: `OtMdns` - an mDNS trait implementation for `openthread` using Thread SRP +use core::borrow::BorrowMut; use core::fmt::Write; use core::future::poll_fn; @@ -15,6 +16,7 @@ use openthread::{ use rs_matter_stack::matter::crypto::Crypto; use rs_matter_stack::matter::dm::ChangeNotify; +use rs_matter_stack::matter::persist::KvBlobStoreAccess; use rs_matter_stack::matter::transport::network::mdns::Service; use rs_matter_stack::matter::Matter; use rs_matter_stack::mdns::Mdns; @@ -35,13 +37,13 @@ use crate::matter::dm::clusters::wifi_diag::WirelessDiag; use crate::matter::dm::networks::NetChangeNotif; use crate::matter::error::Error; use crate::matter::error::ErrorCode; +use crate::matter::persist::{KvBlobStore, SharedKvBlobStore, VENDOR_KEYS_START}; use crate::matter::transport::network::MAX_RX_PACKET_SIZE; use crate::matter::utils::init::zeroed; use crate::matter::utils::init::{init, Init}; use crate::matter::utils::storage::Vec; use crate::matter::utils::sync::blocking::raw::MatterRawMutex; use crate::matter::utils::sync::DynBase; -use crate::stack::persist::{KvBlobStore, SharedKvBlobStore, VENDOR_KEYS_START}; /// Re-export the `openthread` crate pub mod openthread { @@ -429,12 +431,7 @@ impl<'d> OtMdns<'d> { } /// Run the `OtMdns` instance by listening to the mDNS services and registering them with the SRP server - pub async fn run( - &self, - matter: &Matter<'_>, - crypto: C, - notify: &dyn ChangeNotify, - ) -> Result<(), OtError> { + pub async fn run(&self, matter: &Matter<'_>, notify: &dyn ChangeNotify) -> Result<(), OtError> { loop { // TODO: Not very efficient to remove and re-add everything @@ -484,35 +481,38 @@ impl<'d> OtMdns<'d> { info!("Registered SRP host {}", hostname); - unwrap!(matter.mdns_services(&crypto, notify, |matter_service| { - Service::call_with( - &matter_service, - matter.dev_det(), - matter.port(), - |service| { - unwrap!(self.ot.srp_add_service(&SrpService { - name: service.service_protocol, - instance_name: service.name, - port: service.port, - subtype_labels: service.service_subtypes.iter().cloned(), - txt_entries: service - .txt_kvs - .iter() - .cloned() - .filter(|(k, _)| !k.is_empty()) - .map(|(k, v)| (k, v.as_bytes())), - priority: 0, - weight: 0, - lease_secs: 0, - key_lease_secs: 0, - })); // TODO - - info!("Added service {:?}", matter_service); - - Ok(()) - }, - ) - })); + unwrap!(matter.mdns_services( + |e, c, a| notify.notify(e, c, a), + |matter_service| { + Service::call_with( + &matter_service, + matter.dev_det(), + matter.port(), + |service| { + unwrap!(self.ot.srp_add_service(&SrpService { + name: service.service_protocol, + instance_name: service.name, + port: service.port, + subtype_labels: service.service_subtypes.iter().cloned(), + txt_entries: service + .txt_kvs + .iter() + .cloned() + .filter(|(k, _)| !k.is_empty()) + .map(|(k, v)| (k, v.as_bytes())), + priority: 0, + weight: 0, + lease_secs: 0, + key_lease_secs: 0, + })); // TODO + + info!("Added service {:?}", matter_service); + + Ok(()) + }, + ) + } + )); matter.wait_mdns().await; } @@ -523,7 +523,7 @@ impl Mdns for OtMdns<'_> { async fn run( &mut self, matter: &Matter<'_>, - crypto: C, + _crypto: C, notify: &dyn ChangeNotify, _udp: U, _mac: &[u8], @@ -535,7 +535,7 @@ impl Mdns for OtMdns<'_> { C: Crypto, U: UdpBind, { - OtMdns::run(self, matter, crypto, notify) + OtMdns::run(self, matter, notify) .await .map_err(to_matter_err) } @@ -550,21 +550,22 @@ const OT_SRP_ECDSA_KEY: u16 = VENDOR_KEYS_START; /// A struct for implementing persistance of `openthread` settings - volatitle and /// non-volatile (for selected keys) -pub struct OtPersist<'a, 'd, S> { +pub struct OtPersist<'a, 'd, S, T> { settings: SharedRamSettings<'d, MatterRawMutex, fn(RamSettingsChange) -> bool>, - store: &'a SharedKvBlobStore<'a, S>, + store: &'a SharedKvBlobStore, } -impl<'a, 'd, S> OtPersist<'a, 'd, S> +impl<'a, 'd, S, T> OtPersist<'a, 'd, S, T> where S: KvBlobStore, + T: BorrowMut<[u8]>, { /// Create a new `OtPersist` instance /// /// # Arguments /// - `settings_buf`: A mutable reference to a buffer for storing `openthread` settings before they are persisted /// - `store`: A reference to the `KvBlobStore` instance used for persisting a subset of the settings to non-volatile storage - pub const fn new(settings_buf: &'d mut [u8], store: &'a SharedKvBlobStore<'a, S>) -> Self { + pub const fn new(settings_buf: &'d mut [u8], store: &'a SharedKvBlobStore) -> Self { Self { settings: SharedRamSettings::new(RamSettings::new_with_signal_change( settings_buf, @@ -591,11 +592,9 @@ where } /// Load (a selected subset of) the settings from the `KvBlobStore` non-volatile storage - pub async fn load(&self) -> Result<(), Error> { - let (mut kv, mut buf) = self.store.get().await; - - kv.load(OT_SRP_ECDSA_KEY, &mut buf, |data| { - if let Some(data) = data { + pub fn load(&self) -> Result<(), Error> { + self.store.access(|kv, buf| { + if let Some(data) = kv.load(OT_SRP_ECDSA_KEY, buf)? { self.settings.with(|settings| { let mut offset = 0; @@ -609,20 +608,17 @@ where offset += value.len(); } - }) + }); } Ok(()) }) - .await } /// Store (a selected subset of) the settings to the `KvBlobStore` non-volatile storage - pub async fn store(&self) -> Result<(), Error> { - let (mut kv, mut buf) = self.store.get().await; - - kv.store(OT_SRP_ECDSA_KEY, &mut buf, |buf| { - self.settings.with(|settings| { + pub fn store(&self) -> Result<(), Error> { + self.store.access(|kv, buf| { + let offset = self.settings.with(|settings| { let mut offset = 0; for (key, value) in settings @@ -638,10 +634,13 @@ where offset += value.len(); } - Ok(offset) - }) + offset + }); + + let (data, buf) = buf.split_at_mut(offset); + + kv.store(OT_SRP_ECDSA_KEY, data, buf) }) - .await } /// Run the `OtPersist` instance by waiting for changes in the settings and persisting them @@ -652,7 +651,7 @@ where loop { wait_changed().await; - self.store().await?; + self.store()?; } } } diff --git a/rs-matter-embassy/src/persist.rs b/rs-matter-embassy/src/persist.rs index 1a29703..6e94c7d 100644 --- a/rs-matter-embassy/src/persist.rs +++ b/rs-matter-embassy/src/persist.rs @@ -1,33 +1,31 @@ -//! Persistence: `EmbassyPersist` - an implementation of the `Persist` trait that uses the `sequential_storage::map` API +//! Persistence: `SeqMapKvBlobStore` - an implementation of the `KvBlobStore` trait that uses the `sequential_storage::map` API use core::ops::Range; use embedded_storage_async::nor_flash::MultiwriteNorFlash; use rs_matter_stack::matter::error::Error; -use rs_matter_stack::persist::{KvBlobStore, MatterPersist}; +use rs_matter_stack::matter::persist::KvBlobStore; use sequential_storage::cache::NoCache; use crate::error::to_persist_error; use crate::fmt::Bytes; -pub type EmbassyPersist<'a, S, N> = MatterPersist<'a, EmbassyKvBlobStore, N>; - /// A `KvBlobStore`` implementation that uses the `sequential_storage::map` API /// on top of NOR Flash. -pub struct EmbassyKvBlobStore { +pub struct SeqMapKvBlobStore { flash: S, flash_range: Range, cache: NoCache, } -impl EmbassyKvBlobStore +impl SeqMapKvBlobStore where S: MultiwriteNorFlash, { /// Create a new KV blob store instance. - pub fn new(flash: S, flash_range: Range) -> Self { + pub const fn new(flash: S, flash_range: Range) -> Self { Self { flash, flash_range, @@ -35,10 +33,7 @@ where } } - async fn load(&mut self, key: u16, buf: &mut [u8], cb: F) -> Result<(), Error> - where - F: FnOnce(Option<&[u8]>) -> Result<(), Error>, - { + async fn load<'a>(&mut self, key: u16, buf: &'a mut [u8]) -> Result, Error> { let data: Option<&[u8]> = sequential_storage::map::fetch_item( &mut self.flash, self.flash_range.clone(), @@ -49,8 +44,6 @@ where .await .map_err(to_persist_error)?; - cb(data)?; - debug!( "Blob {}: loaded {:?} bytes", key, @@ -63,35 +56,26 @@ where data.map(Bytes) ); - Ok(()) + Ok(data) } - async fn store(&mut self, key: u16, buf: &mut [u8], cb: F) -> Result<(), Error> - where - F: FnOnce(&mut [u8]) -> Result, - { - // Not ideal, but both `rs-matter-stack` and `sequential-storage` need a buffer. - let (matter_buf, seqs_buf) = buf.split_at_mut(buf.len() / 2); - - let len = cb(matter_buf)?; - let data = &matter_buf[..len]; - + async fn store(&mut self, key: u16, data: &[u8], buf: &mut [u8]) -> Result<(), Error> { sequential_storage::map::store_item( &mut self.flash, self.flash_range.clone(), &mut self.cache, - seqs_buf, + buf, &key, &data, ) .await .map_err(to_persist_error)?; - debug!("Blob {}: stored {} bytes", key, len); + debug!("Blob {}: stored {} bytes", key, data.len()); trace!( "Blob {} store details: stored {} bytes, data: {:?}", key, - len, + data.len(), data ); @@ -115,25 +99,19 @@ where } } -impl KvBlobStore for EmbassyKvBlobStore +impl KvBlobStore for SeqMapKvBlobStore where S: MultiwriteNorFlash, { - async fn load(&mut self, key: u16, buf: &mut [u8], f: F) -> Result<(), Error> - where - F: FnOnce(Option<&[u8]>) -> Result<(), Error>, - { - EmbassyKvBlobStore::load(self, key, buf, f).await + fn load<'a>(&mut self, key: u16, buf: &'a mut [u8]) -> Result, Error> { + embassy_futures::block_on(SeqMapKvBlobStore::load(self, key, buf)) } - async fn store(&mut self, key: u16, buf: &mut [u8], f: F) -> Result<(), Error> - where - F: FnOnce(&mut [u8]) -> Result, - { - EmbassyKvBlobStore::store(self, key, buf, f).await + fn store(&mut self, key: u16, data: &[u8], buf: &mut [u8]) -> Result<(), Error> { + embassy_futures::block_on(SeqMapKvBlobStore::store(self, key, data, buf)) } - async fn remove(&mut self, key: u16, buf: &mut [u8]) -> Result<(), Error> { - EmbassyKvBlobStore::remove(self, key, buf).await + fn remove(&mut self, key: u16, buf: &mut [u8]) -> Result<(), Error> { + embassy_futures::block_on(SeqMapKvBlobStore::remove(self, key, buf)) } } diff --git a/rs-matter-embassy/src/wireless/thread.rs b/rs-matter-embassy/src/wireless/thread.rs index 9ec3442..0d62081 100644 --- a/rs-matter-embassy/src/wireless/thread.rs +++ b/rs-matter-embassy/src/wireless/thread.rs @@ -3,6 +3,8 @@ use core::pin::pin; use embassy_futures::select::select3; use openthread::{OpenThread, Radio}; +use rs_matter_stack::matter::persist::KvBlobStore; +use rs_matter_stack::persist::MatterSharedKvBlobStore; use crate::ble::{ControllerRef, TroubleBtpGattContext, TroubleBtpGattPeripheral}; use crate::matter::crypto::{CryptoRngCore, RngCore}; @@ -14,7 +16,6 @@ use crate::matter::utils::sync::IfMutex; use crate::ot::{to_matter_err, OtNetCtl, OtNetStack, OtPersist}; use crate::ot::{OtMatterResources, OtMdns, OtNetif}; use crate::stack::network::{Embedding, Network}; -use crate::stack::persist::{KvBlobStore, SharedKvBlobStore}; use crate::stack::rand::RngAdaptor; use crate::stack::wireless::{self, Gatt, GattTask}; @@ -164,17 +165,17 @@ where } /// A `Wireless` trait implementation for `openthread`'s Thread stack. -pub struct EmbassyThread<'a, T, S, R> { +pub struct EmbassyThread<'a, 'd, T, S, R> { driver: T, ieee_eui64: [u8; 8], - store: &'a SharedKvBlobStore<'a, S>, + store: &'a MatterSharedKvBlobStore<'d, S>, context: &'a OtNetContext, ble_context: &'a TroubleBtpGattContext, use_ble_random_addr: bool, rand: R, } -impl<'a, T, S, R> EmbassyThread<'a, T, S, R> +impl<'a, 'd, T, S, R> EmbassyThread<'a, 'd, T, S, R> where T: ThreadDriver, S: KvBlobStore, @@ -185,7 +186,7 @@ where driver: T, rand: R, ieee_eui64: [u8; 8], - store: &'a SharedKvBlobStore<'a, S>, + store: &'a MatterSharedKvBlobStore<'d, S>, stack: &'a EmbassyThreadMatterStack<'a, B, E>, use_ble_random_addr: bool, ) -> Self @@ -208,7 +209,7 @@ where driver: T, rand: R, ieee_eui64: [u8; 8], - store: &'a SharedKvBlobStore<'a, S>, + store: &'a MatterSharedKvBlobStore<'d, S>, context: &'a OtNetContext, ble_context: &'a TroubleBtpGattContext, use_ble_random_addr: bool, @@ -225,7 +226,7 @@ where } } -impl wireless::Thread for EmbassyThread<'_, T, S, R> +impl wireless::Thread for EmbassyThread<'_, '_, T, S, R> where T: ThreadDriver, S: KvBlobStore, @@ -247,7 +248,7 @@ where } } -impl wireless::ThreadCoex for EmbassyThread<'_, T, S, R> +impl wireless::ThreadCoex for EmbassyThread<'_, '_, T, S, R> where T: ThreadCoexDriver, S: KvBlobStore, @@ -271,7 +272,7 @@ where } } -impl Gatt for EmbassyThread<'_, T, S, R> +impl Gatt for EmbassyThread<'_, '_, T, S, R> where T: BleDriver, S: KvBlobStore, @@ -326,15 +327,15 @@ impl Embedding for OtNetContext { } } -struct ThreadDriverTaskImpl<'a, A, S, C> { +struct ThreadDriverTaskImpl<'a, 'd, A, S, C> { ieee_eui64: [u8; 8], rand: C, - store: &'a SharedKvBlobStore<'a, S>, + store: &'a MatterSharedKvBlobStore<'d, S>, context: &'a OtNetContext, task: A, } -impl ThreadDriverTask for ThreadDriverTaskImpl<'_, A, S, C> +impl ThreadDriverTask for ThreadDriverTaskImpl<'_, '_, A, S, C> where A: wireless::ThreadTask, S: KvBlobStore, @@ -348,7 +349,7 @@ where let resources = &mut *resources; let persister = OtPersist::new(&mut resources.settings_buf, self.store); - persister.load().await?; + persister.load()?; let mut settings = persister.settings(); let mut rand = RngAdaptor::new(self.rand); @@ -390,17 +391,17 @@ where } } -struct ThreadCoexDriverTaskImpl<'a, A, S, C> { +struct ThreadCoexDriverTaskImpl<'a, 'd, A, S, C> { ieee_eui64: [u8; 8], rand: C, - store: &'a SharedKvBlobStore<'a, S>, + store: &'a MatterSharedKvBlobStore<'d, S>, context: &'a OtNetContext, ble_context: &'a TroubleBtpGattContext, task: A, use_ble_random_addr: bool, } -impl ThreadCoexDriverTask for ThreadCoexDriverTaskImpl<'_, A, S, C> +impl ThreadCoexDriverTask for ThreadCoexDriverTaskImpl<'_, '_, A, S, C> where A: wireless::ThreadCoexTask, S: KvBlobStore, @@ -415,7 +416,7 @@ where let resources = &mut *resources; let persister = OtPersist::new(&mut resources.settings_buf, self.store); - persister.load().await?; + persister.load()?; let mut settings = persister.settings(); let mut rand = RngAdaptor::new(self.rand);