Easily configure and run rs-matter
Configuring the rs-matter crate is not trivial, as it is more of a toolkit rather than a monolitic all-in-one runtime.
Furthermore, operating the assembled Matter stack is also challenging, as various features might need to be switched on or off depending on whether Matter is running in commissioning or operating mode, and also depending on the current network connectivity (as in e.g. Wifi signal lost).
This crate addresses these issues by providing an all-in-one MatterStack assembly that configures rs-matter for reliable operation.
Instantiate it and then call MatterStack::<...>::run(...).
Flexibility.
The Matter stack is assembled as one large future which is not Send. Using an executor to poll that future together with others is still possible, but the executor should be a local one (i.e. Embassy's embassy-executor, Tokio's LocalSet, async_executor::LocalExecutor and so on).
The core of rs-matter-stack is no_std and no-alloc.
You need to provide platform-specific implementations of the following traits for your embedded platform:
KvBlobStore- non-volatile key-value storage abstraction.- For STD,
rs-matter-stackprovidesDirKvBlobStore.
- For STD,
NetifDiag- network interface abstraction (i.e. monitoring when the network interface is up or down, and what is its IP configuration).- For Unix-like OSes,
rs-matterprovidesUnixNetifs, which uses a simple polling every 2 seconds to detect changes to the network interface. - Note that For IP (TCP & UDP) IO, the stack uses the
edge-nalcrate, and is thus compatible withSTDandEmbassyout of the box. You only need to worry about networking IO if you use other platforms than these two.
- For Unix-like OSes,
- Implementation of the UDP traits from edge-nal.
- There are out-of-the-box implementations for Rust STD BSD sockets as well as for
embassy-netand for OpenThread.
- There are out-of-the-box implementations for Rust STD BSD sockets as well as for
GattPeripheral- BLE GATT peripheral abstraction of the device radio. Not necessary for Ethernet connectivity- For Linux,
rs-matterprovidesBluerGattPeripheral, which uses the Linux BlueZ BT stack.
- For Linux,
NetCtl- Wifi controller implementation when using Wifi connectivity (Thread has a built-in one in OpenThread).NoopWirelessNetCtlis a no-op wireless implementation of a Wifi controller that is useful for testing. I.e. on Linux, one can usePreexistingWireless+NoopWirelessNetCtltogether withBluerGattPeripheralandUnixNetifsto test the stack in wireless mode. For production embedded Linux use-cases, you'll have to provide a trueNetCtlimplementation, possibly based on WPA Supplicant, or NetworkManager (which are in the meantime both available from upstreamrs-matter).
The root endpoint (Endpoint 0) hosts the Matter system clusters. Their ownership is split between you and the stack:
- You provide the clusters that do not depend on the operational network: Descriptor, Basic Information, Administrator Commissioning, Operational Credentials, Access Control, Group Key Management, Software Diagnostics and Time Synchronization.
MatterStack::root_handler(...)returns a handler chain with all of them, which you chain into your handler under an Endpoint 0 matcher, next to your application clusters. Any extra Endpoint 0 cluster you need (Diagnostic Logs, ICD Management, OTA Requestor, ...) is chained the same way. - The stack chains the operational network clusters on top of your handler: Network Commissioning, General Commissioning, General Diagnostics and the Ethernet / Wifi / Thread Network Diagnostics cluster. Only the stack knows their inputs - the network controller, the network interface and the commissioning mode - and with non-concurrent commissioning these change when the device hands the radio over from BLE to Wifi / Thread.
The metadata of the root endpoint is not split: MatterStack::root_endpoint() lists all clusters, and goes into your Node as-is.
The rs-matter-embassy crate provides implementations for KvBlobStore, NetifDiag, NetCtl, GattPeripheral and others for the embassy framework.
The esp-idf-matter crate provides implementations for KvBlobStore, NetifDiag, GattPeripheral and others for the ESP-IDF SDK.
(See also All examples)
//! An example utilizing the `EthMatterStack` struct.
//! As the name suggests, this Matter stack assembly uses Ethernet as the main transport,
//! as well as for commissioning.
//!
//! Notice that it might be that rather than Ethernet, the actual L2 transport is Wifi.
//! From the POV of Matter - this case is indistinguishable from Ethernet as long as the
//! Matter stack is not concerned with connecting to the Wifi network, managing
//! its credentials etc. and can assume it "pre-exists".
//!
//! The example implements a fictitious Light device (an On-Off Matter cluster).
#![recursion_limit = "256"]
use core::pin::pin;
use log::info;
use rs_matter_stack::eth::EthMatterStack;
use rs_matter_stack::matter::crypto::{default_crypto, Crypto};
use rs_matter_stack::matter::dm::clusters::app::on_off;
use rs_matter_stack::matter::dm::clusters::app::on_off::test::TestOnOffDeviceLogic;
use rs_matter_stack::matter::dm::clusters::app::on_off::OnOffHooks;
use rs_matter_stack::matter::dm::clusters::desc;
use rs_matter_stack::matter::dm::clusters::desc::ClusterHandler as _;
use rs_matter_stack::matter::dm::devices::test::DAC_PRIVKEY;
use rs_matter_stack::matter::dm::devices::test::{TEST_DEV_ATT, TEST_DEV_COMM, TEST_DEV_DET};
use rs_matter_stack::matter::dm::devices::DEV_TYPE_ON_OFF_LIGHT;
use rs_matter_stack::matter::dm::endpoints::ROOT_ENDPOINT_ID;
use rs_matter_stack::matter::dm::networks::unix::UnixNetifs;
use rs_matter_stack::matter::dm::EmptyHandler;
use rs_matter_stack::matter::dm::{Async, Dataver, Endpoint, Node};
use rs_matter_stack::matter::error::Error;
use rs_matter_stack::matter::persist::DirKvBlobStore;
use rs_matter_stack::matter::transport::network::mdns::zeroconf::ZeroconfMdns;
use rs_matter_stack::matter::utils::init::InitMaybeUninit;
use rs_matter_stack::matter::{clusters, devices};
use static_cell::StaticCell;
/// 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 = 23500;
fn main() -> Result<(), Error> {
env_logger::init_from_env(
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
);
info!("Starting...");
// Initialize the Matter stack (can be done only once),
// as we'll run it in this thread
let stack = MATTER_STACK.uninit().init_with(EthMatterStack::init(
&TEST_DEV_DET,
TEST_DEV_COMM,
&TEST_DEV_ATT,
));
// The default crypto provider
let crypto = default_crypto(rand::rng(), DAC_PRIVKEY);
let mut rand = crypto.weak_rand()?;
// 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 rand),
LIGHT_ENDPOINT_ID,
TestOnOffDeviceLogic::new(true),
);
// Chain our endpoint clusters with the
// (root) Endpoint 0 system clusters in the final handler
let handler = EmptyHandler
// The Endpoint 0 system clusters that are ours to provide.
// The stack adds the operational network clusters (Network Commissioning,
// General Commissioning, General Diagnostics and Wifi/Thread/Ethernet
// Diagnostics) on top, because only it knows the network driver state.
// Chain any extra Endpoint 0 clusters of your own the same way.
.chain(
|e, _| e == ROOT_ENDPOINT_ID,
Async(EthMatterStack::<0, ()>::root_handler(&(), &mut rand)),
)
.chain(
|e, c| e == LIGHT_ENDPOINT_ID && c == 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(
|e, c| e == LIGHT_ENDPOINT_ID && c == desc::DescHandler::CLUSTER.id,
Async(desc::DescHandler::new(Dataver::new_rand(&mut rand)).adapt()),
);
// Create the KV BLOB store and load any previously saved state of `rs-matter`
let mut store = DirKvBlobStore::new_default();
futures_lite::future::block_on(stack.startup(&crypto, &mut store))?;
// 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.matter().kv(store);
// Run the Matter stack with our handler
// Using `pin!` is completely optional, but reduces the size of the final future
let matter = pin!(stack.run_preex(
// The Matter stack needs UDP sockets to communicate with other Matter devices
edge_nal_std::Stack::new(),
// Will try to find a default network interface
UnixNetifs,
// Will use the mDNS implementation based on the `zeroconf` crate
ZeroconfMdns::new(),
// The crypto provider
&crypto,
// Our `AsyncHandler` + `AsyncMetadata` impl
(NODE, handler),
// Will persist in `<tmp-dir>/rs-matter`
kv,
// No user task future to run
(),
));
// Schedule the Matter run
futures_lite::future::block_on(matter)
}
/// The Matter stack is allocated statically to avoid
/// program stack blowups.
static MATTER_STACK: StaticCell<EthMatterStack<BUMP_SIZE, ()>> = StaticCell::new();
/// Endpoint 0 (the root endpoint) runs the Matter system clusters,
/// so we pick ID=1 for our light
const LIGHT_ENDPOINT_ID: u16 = 1;
/// The Matter Light device Node
const NODE: Node = Node {
endpoints: &[
EthMatterStack::<0, ()>::root_endpoint(),
Endpoint::new(
LIGHT_ENDPOINT_ID,
devices!(DEV_TYPE_ON_OFF_LIGHT),
clusters!(desc::DescHandler::CLUSTER, TestOnOffDeviceLogic::CLUSTER),
),
],
};To build all examples, use:
cargo build --examples --features examples