Skip to content

Commit

Permalink
(WIP) Wifi provisioning
Browse files Browse the repository at this point in the history
  • Loading branch information
ivmarkov committed Apr 28, 2024
1 parent 5a37326 commit 3d42149
Show file tree
Hide file tree
Showing 12 changed files with 2,036 additions and 124 deletions.
26 changes: 23 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ authors = ["ivmarkov <[email protected]>"]
edition = "2021"
resolver = "2"
categories = ["embedded", "hardware-support"]
keywords = ["embedded", "svc", "idf", "esp-idf", "esp32"]
keywords = ["matter", "embedded", "esp-idf", "esp32"]
description = "Implementation of the embedded-svc traits for ESP-IDF (Espressif's IoT Development Framework)"
repository = "https://github.com/ivmarkov/esp-idf-matter"
license = "MIT OR Apache-2.0"
Expand All @@ -15,15 +15,35 @@ build = "build.rs"
rust-version = "1.77"

[patch.crates-io]
embedded-svc = { git = "https://github.com/esp-rs/embedded-svc" }
esp-idf-svc = { path = "../esp-idf-svc" }
rs-matter = { path = "../rs-matter" }
rs-matter = { path = "../rs-matter/rs-matter" }
rs-matter-macros = { path = "../rs-matter/rs-matter-macros" }

[features]
default = ["std"]
std = ["async-io", "rs-matter/std", "rs-matter/async-io"]

[dependencies]
log = { version = "0.4", default-features = false }
esp-idf-svc = { version = "0.48", default-features = false, fatures = ["experimental"] }
heapless = "0.8"
enumset = { version = "1", default-features = false }
strum = { version = "0.26", default-features = false, features = ["derive"] }
embassy-futures = "0.1"
embassy-sync = "0.5"
esp-idf-svc = { version = "0.48", default-features = false, features = ["alloc", "embassy-sync", "embassy-time-driver", "experimental"] }
rs-matter = { version = "0.1", default-features = false }
rs-matter-macros = "0.1"
async-io = { version = "2", optional = true, default-features = false }

[build-dependencies]
embuild = "0.31.3"

[dev-dependencies]
embassy-time = { version = "0.3", features = ["generic-queue"] }
static_cell = "2.1"

[[example]]
name = "light"
path = "examples/light.rs"
required-features = ["std"]
187 changes: 179 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,195 @@
# Run [rs-matter](https://github.com/project-chip/rs-matter) on top of the [Rust ESP IDF SDK wrappers](https://github.com/esp-rs/esp-idf-svc)
# (WIP) Run [rs-matter](https://github.com/project-chip/rs-matter) on Espressif chips with [ESP IDF](https://github.com/esp-rs/esp-idf-svc)

[![CI](https://github.com/ivmarkov/esp-idf-matter/actions/workflows/ci.yml/badge.svg)](https://github.com/ivmarkov/esp-idf-matter/actions/workflows/ci.yml)
[![crates.io](https://img.shields.io/crates/v/esp-idf-matter.svg)](https://crates.io/crates/esp-idf-matter)
[![Documentation](https://img.shields.io/badge/docs-esp--rs-brightgreen)](https://ivmarkov.github.io/esp-idf-matter/esp_idf_matter/index.html)
[![Matrix](https://img.shields.io/matrix/ivmarkov:matrix.org?label=join%20matrix&color=BEC5C9&logo=matrix)](https://matrix.to/#/#esp-rs:matrix.org)
[![Wokwi](https://img.shields.io/endpoint?url=https%3A%2F%2Fwokwi.com%2Fbadge%2Fclick-to-simulate.json)](https://wokwi.com/projects/332188235906155092)

## Highlights
## Overview

This boring crate provides the necessary glue to operate the [`rs-matter` Rust Matter stack]() on Espressif chips with the ESP IDF SDK.
Configuring and running the [`rs-matter`]() stack is not trivial.

In particular:
* [Bluetooth commissioning support]() with the ESP IDF Bluedroid stack
Users are expected to provide implementations for various `rs-matter` abstractions, like a UDP stack, BLE stack, randomizer, epoch time, responder and so on and so forth.

Furthermore, _operating_ the assembled Matter stack is also challenging, as various networking stacks might need to be brought up or down 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 provides an easy-to-use all-in-one `MatterStack` assembly.
Instantiate it and then call the `MatterStack::run(...)` method.

```rust
//! An example utilizing the `MatterStack<WifiBle>` struct.
//! As the name suggests, this Matter stack assembly uses Wifi as the main transport, and BLE for commissioning.
//!
//! 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 esp_idf_matter::{error::Error, MatterStack, WifiBle};

use esp_idf_svc::eventloop::EspSystemEventLoop;
use esp_idf_svc::hal::peripherals::Peripherals;
use esp_idf_svc::nvs::EspDefaultNvsPartition;

use log::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<(), Error> {
// Take the Matter stack (can be done only once), as we'll run it in this thread
let stack = MATTER_STACK.take();

// We need to pass the `modem` peripheral to the Matter stack, as it manages Bluetooth & Wifi by itself
//
// If we want to use Bluetooth for our own needs, that's possible post-commissioning, as long as the
// coexist ESP IDF driver is loaded. We just need to then split the `modem` peripheral into BT and Wifi,
// and pass the Wifi half only to method `run_commissioned` instead of `run`.
let peripherals = Peripherals::take()?;

// Matter needs (a clone of) the system event loop
let sysloop = EspSystemEventLoop::take()?;

// Matter needs (a clone of) the default ESP IDF NVS partition too
let nvs = EspDefaultNvsPartition::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 = HandlerCompat(
stack
.root_handler()
// Our on-off cluster, on Endpoint 1
.chain(LIGHT_ENDPOINT_ID, cluster_on_off::ID, &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,
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 matter = pin!(stack.run(
sysloop,
nvs,
peripherals.modem,
CommissioningData {
// Hard-coded for demo purposes
verifier: VerifierData::new_with_pw(123456, *stack.matter().borrow()),
discriminator: 250,
},
(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 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 Lamp device
stack.notify_changed();

info!("Lamp toggled");
}
});

// Schedule both the Matter loop & the device loop together
esp_idf_svc::hal::task::block_on(select(matter, device).coalesce())?;

Ok(())
}

/// The Matter stack is allocated statically to avoid program stack blowups
static MATTER_STACK: ConstStaticCell<MatterStack<WifiBle>> =
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],
},
],
};
```

(See also [Examples])

### Advanced use cases

If the provided `MatterStack` does not cut it, users can implement their own stacks because the building blocks are also exposed as a public API.

#### Building blocks

* [Bluetooth commissioning support]() with the ESP IDF Bluedroid stack (not necessary if you plan to run Matter over Ethernet)
* WiFi provisioning support via an [ESP IDF specific Matter Network Commissioning Cluster implementation]()
* [Non-volatile storage for Matter data (fabrics and ACLs)]() on top of the ESP IDF NVS flash API
* [Non-volatile storage for Matter persistent data (fabrics, ACLs and network connections)]() on top of the ESP IDF NVS flash API
* mDNS:
* Optional [Matter mDNS responder implementation]() based on the ESP IDF mDNS responder (use if you need to register other services besides Matter in mDNS)
* [UDP-multicast workarounds]() for `rs-matter`'s built-in mDNS responder, specifc to the Rust wrappers of ESP IDF
* [UDP-multicast workarounds]() for `rs-matter`'s built-in mDNS responder, addressing bugs in the Rust STD wrappers of ESP IDF

#### Future
* Device Attestation data support using secure flash storage
* Setting system time via Matter
* Matter OTA support based on the ESP IDF OTA API
* Thread networking (for ESP32H2 and ESP32C6)
* Wifi Access-Point based commissioning (for ESP32S2 which does not have Bluetooth support)

For enabling UDP and TCP networking in `rs-matter`, just use the [`async-io`]() crate which has support for ESP IDF out of the box.
#### Additional building blocks provided by `rs-matter` and compatible with ESP IDF:
* UDP and (in future) TCP support
* Enable the [`async-io`]() and [`std`] features on `rs-matter` and use `async-io` sockets. The `async-io` crate has support for ESP IDF out of the box
* Random number generator
* Enable the [`std`] feature on `rs-matter`. This way, the [`rng`]() crate will be utilized, which has support for ESP IDF out of the box
* UNIX epoch
* Enable the [`std`] feature on `rs-matter`. This way, the [`rng`]() crate will be utilized, which has support for ESP IDF out of the box

## Build Prerequisites

Expand Down
Loading

0 comments on commit 3d42149

Please sign in to comment.