From 76d880af9a72583e8bd96a425ab7cf1f85bae1d9 Mon Sep 17 00:00:00 2001 From: ivmarkov Date: Sun, 2 Jun 2024 16:29:39 +0000 Subject: [PATCH] Persistence in examples; Real netif in DummyLinuxModem --- README.md | 15 ++--- examples/light.rs | 16 +++-- examples/light_eth.rs | 15 ++--- src/modem.rs | 83 +++++++++++------------ src/netif.rs | 3 + src/persist.rs | 151 ++++++++++++++++++++++++------------------ 6 files changed, 150 insertions(+), 133 deletions(-) diff --git a/README.md b/README.md index 1e959f3..672adbb 100644 --- a/README.md +++ b/README.md @@ -48,11 +48,6 @@ The [`esp-idf-matter`](https://github.com/ivmarkov/esp-idf-matter) crate provide //! its credentials etc. and can assume it "pre-exists". //! //! The example implements a fictitious Light device (an On-Off Matter cluster). -//! -//! Note that there is no real persistence in this example, so the device will always -//! start in commissioning mode. -//! Note also that the network interface is not monitored for connectivity changes, -//! so the device will always assume it is connected. use core::borrow::Borrow; use core::pin::pin; @@ -73,7 +68,7 @@ use rs_matter::utils::select::Coalesce; use rs_matter::CommissioningData; use rs_matter_stack::netif::UnixNetif; -use rs_matter_stack::persist::DummyPersist; +use rs_matter_stack::persist::{DirKvBlobStore, KvBlobBuf, KvPersist}; use rs_matter_stack::EthMatterStack; use static_cell::ConstStaticCell; @@ -118,8 +113,8 @@ fn main() -> Result<(), 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( - // Will not persist anything - DummyPersist, + // Will persist in `/rs-matter` + KvPersist::new_eth(DirKvBlobStore::new_default(), stack), // Will try to find a default network interface UnixNetif::default(), // Hard-coded for demo purposes @@ -158,7 +153,7 @@ fn main() -> Result<(), Error> { /// The Matter stack is allocated statically to avoid /// program stack blowups. -static MATTER_STACK: ConstStaticCell> = +static MATTER_STACK: ConstStaticCell>> = ConstStaticCell::new(EthMatterStack::new_default( &BasicInfoConfig { vid: 0xFFF1, @@ -182,7 +177,7 @@ const LIGHT_ENDPOINT_ID: u16 = 1; const NODE: Node = Node { id: 0, endpoints: &[ - EthMatterStack::<()>::root_metadata(), + EthMatterStack::>::root_metadata(), Endpoint { id: LIGHT_ENDPOINT_ID, device_type: DEV_TYPE_ON_OFF_LIGHT, diff --git a/examples/light.rs b/examples/light.rs index b4f1a20..1ab662c 100644 --- a/examples/light.rs +++ b/examples/light.rs @@ -1,7 +1,7 @@ -//! An example utilizing the `EspWifiBleMatterStack` struct. +//! An example utilizing the `WifiBleMatterStack` struct. //! As the name suggests, this Matter stack assembly uses Wifi as the main transport, //! and BLE for commissioning. -//! If you want to use Ethernet, utilize `EspEthMatterStack` instead. +//! If you want to use Ethernet, utilize `EthMatterStack` instead. //! //! The example implements a fictitious Light device (an On-Off Matter cluster). @@ -25,9 +25,9 @@ use rs_matter::utils::std_mutex::StdRawMutex; use rs_matter::CommissioningData; use rs_matter_stack::modem::DummyLinuxModem; -use rs_matter_stack::persist::DummyPersist; - +use rs_matter_stack::persist::{DirKvBlobStore, KvBlobBuf, KvPersist}; use rs_matter_stack::WifiBleMatterStack; + use static_cell::ConstStaticCell; #[path = "dev_att/dev_att.rs"] @@ -70,7 +70,9 @@ fn main() -> Result<(), 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( - DummyPersist, + // Will persist in `/rs-matter` + KvPersist::new_wifi_ble(DirKvBlobStore::new_default(), stack), + // A Linux-specific modem using BlueZ DummyLinuxModem::default(), // Hard-coded for demo purposes CommissioningData { @@ -111,7 +113,7 @@ fn main() -> Result<(), 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> = +static MATTER_STACK: ConstStaticCell>> = ConstStaticCell::new(WifiBleMatterStack::new_default( &BasicInfoConfig { vid: 0xFFF1, @@ -135,7 +137,7 @@ const LIGHT_ENDPOINT_ID: u16 = 1; const NODE: Node = Node { id: 0, endpoints: &[ - WifiBleMatterStack::::root_metadata(), + WifiBleMatterStack::>::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 09e9225..34dc919 100644 --- a/examples/light_eth.rs +++ b/examples/light_eth.rs @@ -8,11 +8,6 @@ //! its credentials etc. and can assume it "pre-exists". //! //! The example implements a fictitious Light device (an On-Off Matter cluster). -//! -//! Note that there is no real persistence in this example, so the device will always -//! start in commissioning mode. -//! Note also that the network interface is not monitored for connectivity changes, -//! so the device will always assume it is connected. use core::borrow::Borrow; use core::pin::pin; @@ -33,7 +28,7 @@ use rs_matter::utils::select::Coalesce; use rs_matter::CommissioningData; use rs_matter_stack::netif::UnixNetif; -use rs_matter_stack::persist::DummyPersist; +use rs_matter_stack::persist::{DirKvBlobStore, KvBlobBuf, KvPersist}; use rs_matter_stack::EthMatterStack; use static_cell::ConstStaticCell; @@ -78,8 +73,8 @@ fn main() -> Result<(), 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( - // Will not persist anything - DummyPersist, + // Will persist in `/rs-matter` + KvPersist::new_eth(DirKvBlobStore::new_default(), stack), // Will try to find a default network interface UnixNetif::default(), // Hard-coded for demo purposes @@ -118,7 +113,7 @@ fn main() -> Result<(), Error> { /// The Matter stack is allocated statically to avoid /// program stack blowups. -static MATTER_STACK: ConstStaticCell> = +static MATTER_STACK: ConstStaticCell>> = ConstStaticCell::new(EthMatterStack::new_default( &BasicInfoConfig { vid: 0xFFF1, @@ -142,7 +137,7 @@ const LIGHT_ENDPOINT_ID: u16 = 1; const NODE: Node = Node { id: 0, endpoints: &[ - EthMatterStack::<()>::root_metadata(), + EthMatterStack::>::root_metadata(), Endpoint { id: LIGHT_ENDPOINT_ID, device_type: DEV_TYPE_ON_OFF_LIGHT, diff --git a/src/modem.rs b/src/modem.rs index fff8973..c477fad 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -6,7 +6,7 @@ use enumset::EnumSet; use rs_matter::transport::network::btp::GattPeripheral; -use crate::netif::{DummyNetif, Netif, NetifConf}; +use crate::netif::Netif; #[cfg(feature = "alloc")] extern crate alloc; @@ -77,63 +77,43 @@ where /// /// The "dummy" aspects of this implementation are related to the Wifi network: /// - The L2 (Wifi) network is simulated by a dummy network interface that does not actually connect to Wifi networks. -/// - The L3 (IP) network is simulated by a `DummyNetif` instance that uses a hard-coded `NetifConf` and assumes the -/// netif is always up -/// -/// The BLE network is not simulated and is expected to be user-provided, because - without a functioning BLE stack - -/// the device cannot even be commissioned. +/// - The L3 (IP) network is not simulated and is expected to be user-provided. +/// - The BLE network is not simulated and is expected to be user-provided as well, +/// because - without a functioning BLE stack - the device cannot even be commissioned. /// /// On Linux, the BlueR-based BLE gatt peripheral can be used. -pub struct DummyModem { - netif: DummyNetif, - gf: F, +pub struct DummyModem { + netif_factory: N, + bt_factory: B, } -impl DummyModem { +impl DummyModem { /// Create a new `DummyModem` with the given configuration, UDP `bind` stack and BLE peripheral factory - pub const fn new(conf: Option, bind: U, gf: F) -> Self { + pub const fn new(netif_factory: N, bt_factory: B) -> Self { Self { - netif: DummyNetif::new(conf, bind), - gf, + netif_factory, + bt_factory, } } } -/// An instantiation of `DummyModem` for Linux specifically, -/// that uses the `edge-nal-std` stack and the BlueR GATT peripheral from `rs-matter`. -#[cfg(all(feature = "std", target_os = "linux"))] -pub type DummyLinuxModem = DummyModem< - edge_nal_std::Stack, - fn() -> rs_matter::transport::network::btp::BuiltinGattPeripheral, ->; - -#[cfg(all(feature = "std", target_os = "linux"))] -impl Default for DummyLinuxModem { - fn default() -> Self { - Self::new( - Some(NetifConf::default()), - edge_nal_std::Stack::new(), - || rs_matter::transport::network::btp::BuiltinGattPeripheral::new(None), - ) - } -} - -impl Modem for DummyModem +impl Modem for DummyModem where - U: UdpBind, - F: FnMut() -> G, + N: FnMut() -> I, + B: FnMut() -> G, + I: Netif + UdpBind + 'static, G: GattPeripheral, { type BleDevice<'t> = G where Self: 't; - type WifiDevice<'t> = DummyWifiDevice<'t, U> where Self: 't; + type WifiDevice<'t> = DummyWifiDevice where Self: 't; async fn ble(&mut self) -> Self::BleDevice<'_> { - (self.gf)() + (self.bt_factory)() } async fn wifi(&mut self) -> Self::WifiDevice<'_> { - DummyWifiDevice(&self.netif) + DummyWifiDevice((self.netif_factory)()) } } @@ -143,18 +123,18 @@ where /// - The L2 (Wifi) network is simulated by a dummy network interface that does not actually connect to Wifi networks. /// - The L3 (IP) network is simulated by a `DummyNetif` instance that uses a hard-coded `NetifConf` and assumes the /// netif is always up -pub struct DummyWifiDevice<'a, U>(&'a DummyNetif); +pub struct DummyWifiDevice(N); -impl<'a, U> WifiDevice for DummyWifiDevice<'a, U> +impl WifiDevice for DummyWifiDevice where - U: UdpBind, + N: Netif + UdpBind, { type L2<'t> = DummyL2 where Self: 't; - type L3<'t> = &'t DummyNetif where Self: 't; + type L3<'t> = &'t N where Self: 't; async fn split(&mut self) -> (Self::L2<'_>, Self::L3<'_>) { - (DummyL2::new(), self.0) + (DummyL2::new(), &self.0) } } @@ -236,3 +216,20 @@ impl Wifi for DummyL2 { Ok(alloc::vec::Vec::new()) } } + +/// An instantiation of `DummyModem` for Linux specifically, +/// that uses the `UnixNetif` instance and the BlueR GATT peripheral from `rs-matter`. +#[cfg(all(feature = "std", target_os = "linux"))] +pub type DummyLinuxModem = DummyModem< + fn() -> crate::netif::UnixNetif, + fn() -> rs_matter::transport::network::btp::BuiltinGattPeripheral, +>; + +#[cfg(all(feature = "std", target_os = "linux"))] +impl Default for DummyLinuxModem { + fn default() -> Self { + Self::new(crate::netif::UnixNetif::new_default, || { + rs_matter::transport::network::btp::BuiltinGattPeripheral::new(None) + }) + } +} diff --git a/src/netif.rs b/src/netif.rs index b179d9f..e21c18b 100644 --- a/src/netif.rs +++ b/src/netif.rs @@ -166,10 +166,13 @@ mod unix { pub struct UnixNetif(String); impl UnixNetif { + /// Create a new `UnixNetif`. The implementation will try + /// to find and use a suitable interface automatically. pub fn new_default() -> Self { Self(default_if().unwrap()) } + /// Create a new `UnixNetif` for the given interface name pub const fn new(if_name: String) -> Self { Self(if_name) } diff --git a/src/persist.rs b/src/persist.rs index 509dfc2..f9488ff 100644 --- a/src/persist.rs +++ b/src/persist.rs @@ -9,6 +9,9 @@ use crate::network::{Embedding, Network}; use crate::wifi::WifiContext; use crate::{Eth, MatterStack, WifiBle, MAX_WIFI_NETWORKS}; +#[cfg(feature = "std")] +pub use file::DirKvBlobStore; + /// A persistent storage manager for the Matter stack. pub trait Persist { /// Reset the persist instance, removing all stored data from the non-volatile storage. @@ -251,101 +254,123 @@ where } } -/// An implementation of the `KvBlobStore` trait that stores the BLOBs in a directory. #[cfg(feature = "std")] -pub struct DirKvBlobStore(std::path::PathBuf); +mod file { + use rs_matter::error::Error; -#[cfg(feature = "std")] -impl DirKvBlobStore { - /// Create a new `DirKvStore` instance. - pub const fn new(path: std::path::PathBuf) -> Self { - Self(path) - } + use super::KvBlobStore; - /// Load a BLOB with the specified key from the directory. - pub fn load<'b>(&self, key: &str, buf: &'b mut [u8]) -> Result, Error> { - use log::info; - use std::io::Read; + extern crate std; - let path = self.key_path(key); + /// An implementation of the `KvBlobStore` trait that stores the BLOBs in a directory. + pub struct DirKvBlobStore(std::path::PathBuf); - match std::fs::File::open(path) { - Ok(mut file) => { - let mut offset = 0; + impl DirKvBlobStore { + /// Create a new `DirKvStore` instance, which will persist + /// its settings in `/rs-matter`. + pub fn new_default() -> Self { + Self(std::env::temp_dir().join("rs-matter")) + } - loop { - if offset == buf.len() { - Err(rs_matter::error::ErrorCode::NoSpace)?; - } + /// Create a new `DirKvStore` instance. + pub const fn new(path: std::path::PathBuf) -> Self { + Self(path) + } - let len = file.read(&mut buf[offset..])?; + /// Load a BLOB with the specified key from the directory. + pub fn load<'b>(&self, key: &str, buf: &'b mut [u8]) -> Result, Error> { + use log::info; + use std::io::Read; - if len == 0 { - break; - } + let path = self.key_path(key); - offset += len; - } + match std::fs::File::open(path) { + Ok(mut file) => { + let mut offset = 0; - let data = &buf[..offset]; + loop { + if offset == buf.len() { + Err(rs_matter::error::ErrorCode::NoSpace)?; + } - info!("Key {}: loaded {} bytes {:?}", key, data.len(), data); + let len = file.read(&mut buf[offset..])?; - Ok(Some(data)) + if len == 0 { + break; + } + + offset += len; + } + + let data = &buf[..offset]; + + info!("Key {}: loaded {} bytes {:?}", key, data.len(), data); + + Ok(Some(data)) + } + Err(_) => Ok(None), } - Err(_) => Ok(None), } - } - /// Store a BLOB with the specified key in the directory. - fn store(&self, key: &str, data: &[u8]) -> Result<(), Error> { - use log::info; - use std::io::Write; + /// Store a BLOB with the specified key in the directory. + fn store(&self, key: &str, data: &[u8]) -> Result<(), Error> { + use log::info; + use std::io::Write; - std::fs::create_dir_all(&self.0)?; + std::fs::create_dir_all(&self.0)?; - let path = self.key_path(key); + let path = self.key_path(key); - let mut file = std::fs::File::create(path)?; + let mut file = std::fs::File::create(path)?; - file.write_all(data)?; + file.write_all(data)?; - info!("Key {}: stored {} bytes {:?}", key, data.len(), data); + info!("Key {}: stored {} bytes {:?}", key, data.len(), data); - Ok(()) - } + Ok(()) + } - /// Remove a BLOB with the specified key from the directory. - /// If the BLOB does not exist, this method does nothing. - fn remove(&self, key: &str) -> Result<(), Error> { - use log::info; + /// Remove a BLOB with the specified key from the directory. + /// If the BLOB does not exist, this method does nothing. + fn remove(&self, key: &str) -> Result<(), Error> { + use log::info; - let path = self.key_path(key); + let path = self.key_path(key); - if std::fs::remove_file(path).is_ok() { - info!("Key {}: removed", key); + if std::fs::remove_file(path).is_ok() { + info!("Key {}: removed", key); + } + + Ok(()) } - Ok(()) + fn key_path(&self, key: &str) -> std::path::PathBuf { + self.0.join(key) + } } - fn key_path(&self, key: &str) -> std::path::PathBuf { - self.0.join(key) + impl Default for DirKvBlobStore { + fn default() -> Self { + Self::new_default() + } } -} -#[cfg(feature = "std")] -impl KvBlobStore for DirKvBlobStore { - async fn load<'a>(&mut self, key: &str, buf: &'a mut [u8]) -> Result, Error> { - DirKvBlobStore::load(self, key, buf) - } + impl KvBlobStore for DirKvBlobStore { + async fn load<'a>( + &mut self, + key: &str, + buf: &'a mut [u8], + ) -> Result, Error> { + DirKvBlobStore::load(self, key, buf) + } - async fn store(&mut self, key: &str, value: &[u8]) -> Result<(), Error> { - DirKvBlobStore::store(self, key, value) - } + async fn store(&mut self, key: &str, value: &[u8]) -> Result<(), Error> { + DirKvBlobStore::store(self, key, value) + } - async fn remove(&mut self, key: &str) -> Result<(), Error> { - DirKvBlobStore::remove(self, key) + async fn remove(&mut self, key: &str) -> Result<(), Error> { + DirKvBlobStore::remove(self, key) + } } }