diff --git a/examples/light.rs b/examples/light.rs new file mode 100644 index 0000000..66bb31b --- /dev/null +++ b/examples/light.rs @@ -0,0 +1,191 @@ +//! An example utilizing the `EspWifiBleMatterStack` 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. +//! +//! The example implements a fictitious Light device (an On-Off Matter cluster). + +use core::borrow::Borrow; +use core::pin::pin; + +use embassy_futures::select::select; +use embassy_time::{Duration, Timer}; + +use esp_idf_matter::{init_async_io, EspKvBlobStore, EspModem, EspPersist, EspWifiBleMatterStack}; + +use esp_idf_svc::eventloop::EspSystemEventLoop; +use esp_idf_svc::hal::peripherals::Peripherals; +use esp_idf_svc::hal::task::block_on; +use esp_idf_svc::log::EspLogger; +use esp_idf_svc::nvs::EspDefaultNvsPartition; +use esp_idf_svc::timer::EspTaskTimerService; + +use log::{error, info}; + +use rs_matter::data_model::cluster_basic_information::BasicInfoConfig; +use rs_matter::data_model::cluster_on_off; +use rs_matter::data_model::device_types::DEV_TYPE_ON_OFF_LIGHT; +use rs_matter::data_model::objects::{Endpoint, HandlerCompat, Node}; +use rs_matter::data_model::system_model::descriptor; +use rs_matter::secure_channel::spake2p::VerifierData; +use rs_matter::utils::select::Coalesce; +use rs_matter::CommissioningData; + +use static_cell::ConstStaticCell; + +#[path = "dev_att/dev_att.rs"] +mod dev_att; + +fn main() -> Result<(), anyhow::Error> { + EspLogger::initialize_default(); + + info!("Starting..."); + + // Run in a higher-prio thread to avoid issues with `async-io` getting + // confused by the low priority of the ESP IDF main task + // Also allocate a very large stack (for now) as `rs-matter` futures do occupy quite some space + let thread = std::thread::Builder::new() + .stack_size(70 * 1024) + .spawn(|| { + // Eagerly initialize `async-io` to minimize the risk of stack blowups later on + init_async_io()?; + + run() + }) + .unwrap(); + + thread.join().unwrap() +} + +#[inline(never)] +#[cold] +fn run() -> Result<(), anyhow::Error> { + let result = block_on(matter()); + + if let Err(e) = &result { + error!("Matter aborted execution with error: {:?}", e); + } + { + info!("Matter finished execution successfully"); + } + + result +} + +async fn matter() -> Result<(), anyhow::Error> { + // Take the Matter stack (can be done only once), + // as we'll run it in this thread + let stack = MATTER_STACK.take(); + + // Take some generic ESP-IDF stuff we'll need later + let sysloop = EspSystemEventLoop::take()?; + let timers = EspTaskTimerService::new()?; + let nvs = EspDefaultNvsPartition::take()?; + let peripherals = Peripherals::take()?; + + // Our "light" on-off cluster. + // Can be anything implementing `rs_matter::data_model::AsyncHandler` + let on_off = cluster_on_off::OnOffCluster::new(*stack.matter().borrow()); + + // Chain our endpoint clusters with the + // (root) Endpoint 0 system clusters in the final handler + let handler = stack + .root_handler() + // Our on-off cluster, on Endpoint 1 + .chain( + LIGHT_ENDPOINT_ID, + cluster_on_off::ID, + HandlerCompat(&on_off), + ) + // Each Endpoint needs a Descriptor cluster too + // Just use the one that `rs-matter` provides out of the box + .chain( + LIGHT_ENDPOINT_ID, + descriptor::ID, + HandlerCompat(descriptor::DescriptorCluster::new(*stack.matter().borrow())), + ); + + // 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 + let mut matter = pin!(stack.run( + // The Matter stack needs a persister to store its state + // `EspPersist`+`EspKvBlobStore` saves to a user-supplied NVS partition + // under namespace `esp-idf-matter` + EspPersist::new_wifi_ble(EspKvBlobStore::new_default(nvs.clone())?, stack), + // The Matter stack needs the BT/Wifi modem peripheral - and in general - + // the Bluetooth / Wifi connections will be managed by the Matter stack itself + // For finer-grained control, call `MatterStack::is_commissioned`, + // `MatterStack::commission` and `MatterStack::operate` + EspModem::new(peripherals.modem, sysloop, timers, nvs, stack), + // Hard-coded for demo purposes + CommissioningData { + verifier: VerifierData::new_with_pw(123456, *stack.matter().borrow()), + discriminator: 250, + }, + // Our `AsyncHandler` + `AsyncMetadata` impl + (NODE, handler), + )); + + // 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?; + + Ok(()) +} + +/// 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> = + ConstStaticCell::new(EspWifiBleMatterStack::new_default( + &BasicInfoConfig { + vid: 0xFFF1, + pid: 0x8000, + hw_ver: 2, + sw_ver: 1, + sw_ver_str: "1", + serial_no: "aabbccdd", + device_name: "MyLight", + product_name: "ACME Light", + vendor_name: "ACME", + }, + &dev_att::HardCodedDevAtt::new(), + )); + +/// 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: &[ + EspWifiBleMatterStack::<()>::root_metadata(), + Endpoint { + id: LIGHT_ENDPOINT_ID, + device_type: DEV_TYPE_ON_OFF_LIGHT, + clusters: &[descriptor::CLUSTER, cluster_on_off::CLUSTER], + }, + ], +}; diff --git a/src/lib.rs b/src/lib.rs index d5e6800..198104e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -385,9 +385,7 @@ where { use core::fmt::Write as _; - use core::net::IpAddr; - - use edge_nal::Multicast; + use {edge_nal::MulticastV4, edge_nal::MulticastV6}; use rs_matter::mdns::{ Host, MDNS_IPV4_BROADCAST_ADDR, MDNS_IPV6_BROADCAST_ADDR, MDNS_PORT, @@ -404,11 +402,11 @@ where .map_err(|_| ErrorCode::StdIoError)?; socket - .join(IpAddr::V4(MDNS_IPV4_BROADCAST_ADDR)) + .join_v4(MDNS_IPV4_BROADCAST_ADDR, _netif_conf.ipv4) .await .map_err(|_| ErrorCode::StdIoError)?; // TODO: netif_conf.ipv4 socket - .join(IpAddr::V6(MDNS_IPV6_BROADCAST_ADDR)) + .join_v6(MDNS_IPV6_BROADCAST_ADDR, _netif_conf.interface) .await .map_err(|_| ErrorCode::StdIoError)?; // TODO: netif_conf.interface