Skip to content

Commit

Permalink
Split stack.rs, remove code duplication
Browse files Browse the repository at this point in the history
  • Loading branch information
ivmarkov committed May 2, 2024
1 parent 0fb8025 commit 3d0b88b
Show file tree
Hide file tree
Showing 5 changed files with 698 additions and 403 deletions.
209 changes: 209 additions & 0 deletions examples/light_eth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
//! An example utilizing the `MatterStack<Eth>` struct.
//! As the name suggests, this Matter stack assembly uses Ethernet as the main transport, as well as for commissioning.
//!
//! Notice thart we actually don't use Ethernet for real, as ESP32s don't have Ethernet ports out of the box.
//! Instead, we utilize Wifi, which - from the POV of Matter - 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 cluster).

use core::borrow::Borrow;
use core::pin::pin;

use embassy_futures::select::select;
use embassy_time::{Duration, Timer};

use embedded_svc::wifi;
use esp_idf_matter::{init_async_io, Error, Eth, MatterStack, WifiBle};

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 esp_idf_svc::wifi::{AsyncWifi, EspWifi};
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;

const WIFI_SSID: &str = env!("WIFI_SSID");
const WIFI_PASS: &str = env!("WIFI_PASS");

fn main() -> Result<(), 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(65 * 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<(), 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<(), Error> {
let sysloop = EspSystemEventLoop::take()?;
let nvs = EspDefaultNvsPartition::take()?;

// Configure and start the Wifi first
let mut wifi = Box::new(AsyncWifi::wrap(
EspWifi::new(
Peripherals::take()?.modem,
sysloop.clone(),
Some(nvs.clone()),
)?,
sysloop.clone(),
EspTaskTimerService::new()?,
)?);
wifi.set_configuration(&wifi::Configuration::Client(wifi::ClientConfiguration {
ssid: WIFI_SSID.try_into().unwrap(),
password: WIFI_PASS.try_into().unwrap(),
..Default::default()
}))?;
wifi.start().await?;
wifi.connect().await?;

// Take the Matter stack (can be done only once),
// as we'll run it in this thread
let stack = MATTER_STACK.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 clone of) the system event loop
sysloop,
// The Matter stack needs (a clone of) the default ESP IDF NVS partition
nvs,
// For the Ethernet case, the Matter stack needs access to the network
// interface (i.e. something that implements the `NetifAccess` trait)
// so that it can track when the interface goes up/down. `EspNetif` obviously
// does implement this trait
wifi.wifi().sta_netif(),
// 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
}

/// The Matter stack is allocated statically to avoid
/// program stack blowups.
static MATTER_STACK: ConstStaticCell<MatterStack<Eth>> = ConstStaticCell::new(MatterStack::new(
&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<'static> = Node {
id: 0,
endpoints: &[
MatterStack::<WifiBle>::root_metadata(),
Endpoint {
id: LIGHT_ENDPOINT_ID,
device_type: DEV_TYPE_ON_OFF_LIGHT,
clusters: &[descriptor::CLUSTER, cluster_on_off::CLUSTER],
},
],
};
26 changes: 26 additions & 0 deletions src/netif.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use core::pin::pin;
use alloc::sync::Arc;

use embassy_futures::select::select;
use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_sync::mutex::Mutex;
use embassy_time::{Duration, Timer};

use esp_idf_svc::eventloop::EspSystemEventLoop;
Expand Down Expand Up @@ -74,6 +76,30 @@ where
}
}

impl NetifAccess for EspNetif {
async fn with_netif<F, R>(&self, f: F) -> R
where
F: FnOnce(&EspNetif) -> R,
{
f(self)
}
}

impl<M, T> NetifAccess for Mutex<M, T>
where
M: RawMutex,
T: NetifAccess,
{
async fn with_netif<F, R>(&self, f: F) -> R
where
F: FnOnce(&EspNetif) -> R,
{
let netif = self.lock().await;

netif.with_netif(f).await
}
}

pub fn get_ips(netif: &EspNetif) -> Result<(Ipv4Addr, Ipv6Addr, u32), Error> {
let ip_info = netif.get_ip_info()?;

Expand Down
Loading

0 comments on commit 3d0b88b

Please sign in to comment.