Everything necessary to run rs-matter
with Embassy:
- Implementation of
rs-matter
'sGattPeripheral
for BLE comissioning support based ontrouble
. rs-matter-stack
support withNetif
,Gatt
,Wireless
(for both Wifi and Thread) andKvBlobStore
implementations.
(See All examples and how to build them)
//! An example utilizing the `EmbassyWifiMatterStack` struct.
//!
//! 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 concurrent commissioning, call `run_coex` instead of `run`.
//! (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::mem::MaybeUninit;
use core::pin::pin;
use alloc::boxed::Box;
use embassy_executor::Spawner;
use embassy_futures::select::select;
use embassy_time::{Duration, Timer};
use esp_backtrace as _;
use esp_hal::timer::timg::TimerGroup;
use log::info;
use rs_matter_embassy::epoch::epoch;
use rs_matter_embassy::matter::dm::clusters::desc::{self, ClusterHandler as _};
use rs_matter_embassy::matter::dm::clusters::on_off::{self, ClusterHandler as _};
use rs_matter_embassy::matter::dm::devices::test::{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::utils::init::InitMaybeUninit;
use rs_matter_embassy::matter::utils::select::Coalesce;
use rs_matter_embassy::matter::{clusters, devices};
use rs_matter_embassy::rand::esp::{esp_init_rand, esp_rand};
use rs_matter_embassy::stack::persist::DummyKvBlobStore;
use rs_matter_embassy::wireless::esp::EspWifiDriver;
use rs_matter_embassy::wireless::{EmbassyWifi, EmbassyWifiMatterStack};
extern crate alloc;
esp_bootloader_esp_idf::esp_app_desc!();
#[esp_hal_embassy::main]
async fn main(_s: Spawner) {
esp_println::logger::init_logger(log::LevelFilter::Info);
info!("Starting...");
// Heap strictly necessary only for Wifi+BLE and for the only Matter dependency which needs (~4KB) alloc - `x509`
// However since `esp32` specifically has a disjoint heap which causes bss size troubles, it is easier
// to allocate the statics once from heap as well
init_heap();
// == Step 1: ==
// Necessary `esp-hal` and `esp-wifi` initialization boilerplate
let peripherals = esp_hal::init(esp_hal::Config::default());
let timg0 = TimerGroup::new(peripherals.TIMG0);
let rng = esp_hal::rng::Rng::new(peripherals.RNG);
// To erase generics, `Matter` takes a rand `fn` rather than a trait or a closure,
// so we need to initialize the global `rand` fn once
esp_init_rand(rng);
let init = esp_wifi::init(timg0.timer0, rng).unwrap();
#[cfg(not(feature = "esp32"))]
{
esp_hal_embassy::init(
esp_hal::timer::systimer::SystemTimer::new(peripherals.SYSTIMER).alarm0,
);
}
#[cfg(feature = "esp32")]
{
esp_hal_embassy::init(timg0.timer1);
}
// == Step 2: ==
// 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 = &*Box::leak(Box::new_uninit()).init_with(EmbassyWifiMatterStack::<()>::init(
&TEST_DEV_DET,
TEST_DEV_COMM,
&TEST_DEV_ATT,
epoch,
esp_rand,
));
// == Step 3: ==
// Our "light" on-off cluster.
// Can be anything implementing `rs_matter::data_model::AsyncHandler`
let on_off = on_off::OnOffHandler::new(Dataver::new_rand(stack.matter().rand()));
// Chain our endpoint clusters
let handler = EmptyHandler
// Our on-off cluster, on Endpoint 1
.chain(
EpClMatcher::new(
Some(LIGHT_ENDPOINT_ID),
Some(on_off::OnOffHandler::CLUSTER.id),
),
Async(on_off::HandlerAdaptor(&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(stack.matter().rand())).adapt()),
);
// == Step 4: ==
// Run the Matter stack with our handler
// Using `pin!` is completely optional, but saves some memory due to `rustc`
// not being very intelligent w.r.t. stack usage in async functions
//
// This step can be repeated in that the stack can be stopped and started multiple times, as needed.
let store = stack.create_shared_store(DummyKvBlobStore);
let mut matter = pin!(stack.run(
// The Matter stack needs to instantiate an `embassy-net` `Driver` and `Controller`
EmbassyWifi::new(
EspWifiDriver::new(&init, peripherals.WIFI, peripherals.BT),
stack
),
// The Matter stack needs a persister to store its state
&store,
// Our `AsyncHandler` + `AsyncMetadata` impl
(NODE, handler),
// No user future to run
(),
));
// Just for demoing purposes:
//
// Run a sample loop that simulates state changes triggered by the HAL
// Changes will be properly communicated to the Matter controllers
// (i.e. Google Home, Alexa) and other Matter devices thanks to subscriptions
let mut device = pin!(async {
loop {
// Simulate user toggling the light with a physical switch every 5 seconds
Timer::after(Duration::from_secs(5)).await;
// Toggle
on_off.set(!on_off.get());
// Let the Matter stack know that we have changed
// the state of our Light device
stack.notify_changed();
info!("Light toggled");
}
});
// Schedule the Matter run & the device loop together
select(&mut matter, &mut device).coalesce().await.unwrap();
}
/// 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::<()>::root_endpoint(),
Endpoint {
id: LIGHT_ENDPOINT_ID,
device_types: devices!(DEV_TYPE_ON_OFF_LIGHT),
clusters: clusters!(desc::DescHandler::CLUSTER, on_off::OnOffHandler::CLUSTER),
},
],
};
#[allow(static_mut_refs)]
fn init_heap() {
fn add_region<const N: usize>(region: &'static mut MaybeUninit<[u8; N]>) {
unsafe {
esp_alloc::HEAP.add_region(esp_alloc::HeapRegion::new(
region.as_mut_ptr() as *mut u8,
N,
esp_alloc::MemoryCapability::Internal.into(),
));
}
}
#[cfg(feature = "esp32")]
{
// The esp32 has two disjoint memory regions for heap
// Also, it has 64KB reserved for the BT stack in the first region, so we can't use that
static mut HEAP1: MaybeUninit<[u8; 30 * 1024]> = MaybeUninit::uninit();
#[link_section = ".dram2_uninit"]
static mut HEAP2: MaybeUninit<[u8; 96 * 1024]> = MaybeUninit::uninit();
add_region(unsafe { &mut HEAP1 });
add_region(unsafe { &mut HEAP2 });
}
#[cfg(not(feature = "esp32"))]
{
static mut HEAP: MaybeUninit<[u8; 186 * 1024]> = MaybeUninit::uninit();
add_region(unsafe { &mut HEAP });
}
}
- Device Attestation data support using secure flash storage
- Setting system time via Matter
- Matter OTA support