Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concurrent commissioning, Thread support #4

Merged
merged 18 commits into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ readme = "README.md"
rust-version = "1.77"

[patch.crates-io]
rs-matter = { git = "https://github.com/ivmarkov/rs-matter", branch = "tip" }
rs-matter = { git = "https://github.com/ivmarkov/rs-matter" }
#rs-matter = { path = "../rs-matter/rs-matter" }
#edge-nal = { git = "https://github.com/ivmarkov/edge-net" }
#edge-nal = { path = "../edge-net/edge-nal" }
Expand All @@ -34,6 +34,7 @@ alloc = ["embedded-svc/alloc"]
log = { version = "0.4", default-features = false }
heapless = "0.8"
enumset = { version = "1", default-features = false }
bitflags = { version = "2.5", default-features = false }
scopeguard = { version = "1", default-features = false }
embassy-futures = "0.1"
embassy-sync = "0.6"
Expand Down
41 changes: 24 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,18 @@ You need to provide platform-specific implementations of the following traits fo
- `Netif` - 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-matter-stack` provides `UnixNetif`, 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-nal`](https://github.com/ivmarkov/edge-net/tree/master/edge-nal) crate, and is thus compatible with [`STD`](https://github.com/ivmarkov/edge-net/tree/master/edge-nal-std) and [`Embassy`](https://github.com/ivmarkov/edge-net/tree/master/edge-nal-embassy) out of the box. You only need to worry about networking IO if you use other platforms than these two.
- `Modem` (for BLE & Wifi only) - abstraction of the device radio that can operate either in Wifi, or in BLE mode.
- `DummyLinuxModem` can be used on Linux to test with BLE. This modem uses Linux BlueZ and the simple `UnixNetif` netif implementation from above, but fakes the Wifi interface with a dummy no-nop one. For production embedded Linux use-cases, you'll have to provide a `Wifi` implementation, possibly based on WPA Supplicant, or NetworkManager.
- `Ble` - BLE abstraction of the device radio. Not necessary for Ethernet connectivity
- For Linux, `rs-matter-stack` provides `BuiltinBle`, which uses the Linux BlueZ BT stack.
- `Wireless` - Wireless (Wifi or Thread) abstraction of the device radio. Not necessary for Ethernet connectivity.
- `DummyWireless` is a no-op wireless implementation that is useful for testing. I.e. on Linux, one can use `DummyWireless` together with `BuiltinBle` and `UnixNetif` to test the stack in wireless mode. For production embedded Linux use-cases, you'll have to provide a true `Wireless` implementation, possibly based on WPA Supplicant, or NetworkManager (not available out of the box in `rs-matter-stack` yet).

## ESP-IDF

The [`esp-idf-matter`](https://github.com/ivmarkov/esp-idf-matter) crate provides implementations for `Persist`, `Netif` and `Modem` for the ESP-IDF SDK.
The [`esp-idf-matter`](https://github.com/ivmarkov/esp-idf-matter) crate provides implementations for `Persist`, `Netif`, `Ble` and `Wireless` for the ESP-IDF SDK.

## Embassy

TBD - upcoming!

## Example

Expand All @@ -58,6 +64,7 @@ use core::pin::pin;
use embassy_futures::select::select;
use embassy_time::{Duration, Timer};

use env_logger::Target;
use log::info;

use rs_matter::data_model::cluster_basic_information::BasicInfoConfig;
Expand All @@ -66,13 +73,12 @@ use rs_matter::data_model::device_types::DEV_TYPE_ON_OFF_LIGHT;
use rs_matter::data_model::objects::{Dataver, Endpoint, HandlerCompat, Node};
use rs_matter::data_model::system_model::descriptor;
use rs_matter::error::Error;
use rs_matter::secure_channel::spake2p::VerifierData;
use rs_matter::utils::init::InitMaybeUninit;
use rs_matter::utils::select::Coalesce;
use rs_matter::CommissioningData;
use rs_matter::BasicCommData;

use rs_matter_stack::netif::UnixNetif;
use rs_matter_stack::persist::{DirKvBlobStore, KvBlobBuf, KvPersist};
use rs_matter_stack::persist::{new_kv, DirKvBlobStore, KvBlobBuf};
use rs_matter_stack::EthMatterStack;

use static_cell::StaticCell;
Expand All @@ -81,9 +87,11 @@ use static_cell::StaticCell;
mod dev_att;

fn main() -> Result<(), Error> {
env_logger::init_from_env(
env_logger::Builder::from_env(
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
);
)
.target(Target::Stdout)
.init();

info!("Starting...");

Expand All @@ -94,7 +102,7 @@ fn main() -> Result<(), Error> {
.init_with(EthMatterStack::init_default(
&BasicInfoConfig {
vid: 0xFFF1,
pid: 0x8000,
pid: 0x8001,
hw_ver: 2,
sw_ver: 1,
sw_ver_str: "1",
Expand All @@ -103,6 +111,10 @@ fn main() -> Result<(), Error> {
product_name: "ACME Light",
vendor_name: "ACME",
},
BasicCommData {
password: 20202021,
discriminator: 3840,
},
&DEV_ATT,
));

Expand Down Expand Up @@ -134,15 +146,10 @@ fn main() -> Result<(), Error> {
// 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(
// Will persist in `<tmp-dir>/rs-matter`
KvPersist::new_eth(DirKvBlobStore::new_default(), stack),
// Will try to find a default network interface
UnixNetif::default(),
// Hard-coded for demo purposes
CommissioningData {
verifier: VerifierData::new_with_pw(123456, stack.matter().rand()),
discriminator: 250,
},
// Will persist in `<tmp-dir>/rs-matter`
new_kv(DirKvBlobStore::new_default(), stack),
// Our `AsyncHandler` + `AsyncMetadata` impl
(NODE, handler),
// No user future to run
Expand Down Expand Up @@ -191,7 +198,7 @@ const NODE: Node = Node {
EthMatterStack::<KvBlobBuf<()>>::root_metadata(),
Endpoint {
id: LIGHT_ENDPOINT_ID,
device_type: DEV_TYPE_ON_OFF_LIGHT,
device_types: &[DEV_TYPE_ON_OFF_LIGHT],
clusters: &[descriptor::CLUSTER, cluster_on_off::CLUSTER],
},
],
Expand Down
31 changes: 18 additions & 13 deletions examples/light.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! An example utilizing the `WifiBleMatterStack` struct.
//! As the name suggests, this Matter stack assembly uses Wifi as the main transport,
//! and BLE for commissioning.
//! An example utilizing the `WifiNCMatterStack` struct.
//!
//! As the name suggests, this Matter stack assembly uses Wifi as the main transport
//! (and thus also BLE for commissioning).
//!
//! If you want to use Ethernet, utilize `EthMatterStack` instead.
//!
//! The example implements a fictitious Light device (an On-Off Matter cluster).
Expand All @@ -23,9 +25,10 @@ use rs_matter::utils::select::Coalesce;
use rs_matter::utils::sync::blocking::raw::StdRawMutex;
use rs_matter::BasicCommData;

use rs_matter_stack::modem::DummyLinuxModem;
use rs_matter_stack::persist::{DirKvBlobStore, KvBlobBuf, KvPersist};
use rs_matter_stack::WifiBleMatterStack;
use rs_matter_stack::netif::UnixNetif;
use rs_matter_stack::persist::{new_kv, DirKvBlobStore, KvBlobBuf};
use rs_matter_stack::wireless::{BuiltinBle, DummyWireless};
use rs_matter_stack::WifiNCMatterStack;

use static_cell::StaticCell;

Expand All @@ -43,7 +46,7 @@ fn main() -> Result<(), Error> {
// as we'll run it in this thread
let stack = MATTER_STACK
.uninit()
.init_with(WifiBleMatterStack::init_default(
.init_with(WifiNCMatterStack::init_default(
&BasicInfoConfig {
vid: 0xFFF1,
pid: 0x8001,
Expand Down Expand Up @@ -90,10 +93,12 @@ fn main() -> Result<(), Error> {
// 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(
// Will persist in `<tmp-dir>/rs-matter`
KvPersist::new_wifi_ble(DirKvBlobStore::new_default(), stack),
// A dummy wireless modem which does nothing
DummyWireless::new(UnixNetif::new_default()),
// A Linux-specific modem using BlueZ
DummyLinuxModem::default(),
BuiltinBle::new(None),
// Will persist in `<tmp-dir>/rs-matter`
new_kv(DirKvBlobStore::new_default(), stack),
// Our `AsyncHandler` + `AsyncMetadata` impl
(NODE, handler),
// No user future to run
Expand Down Expand Up @@ -130,7 +135,7 @@ fn main() -> Result<(), Error> {
/// 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: StaticCell<WifiBleMatterStack<StdRawMutex, KvBlobBuf<()>>> = StaticCell::new();
static MATTER_STACK: StaticCell<WifiNCMatterStack<StdRawMutex, KvBlobBuf<()>>> = StaticCell::new();

const DEV_ATT: dev_att::HardCodedDevAtt = dev_att::HardCodedDevAtt::new();

Expand All @@ -142,10 +147,10 @@ const LIGHT_ENDPOINT_ID: u16 = 1;
const NODE: Node = Node {
id: 0,
endpoints: &[
WifiBleMatterStack::<StdRawMutex, KvBlobBuf<()>>::root_metadata(),
WifiNCMatterStack::<StdRawMutex, KvBlobBuf<()>>::root_metadata(),
Endpoint {
id: LIGHT_ENDPOINT_ID,
device_type: DEV_TYPE_ON_OFF_LIGHT,
device_types: &[DEV_TYPE_ON_OFF_LIGHT],
clusters: &[descriptor::CLUSTER, cluster_on_off::CLUSTER],
},
],
Expand Down
8 changes: 4 additions & 4 deletions examples/light_eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use rs_matter::utils::select::Coalesce;
use rs_matter::BasicCommData;

use rs_matter_stack::netif::UnixNetif;
use rs_matter_stack::persist::{DirKvBlobStore, KvBlobBuf, KvPersist};
use rs_matter_stack::persist::{new_kv, DirKvBlobStore, KvBlobBuf};
use rs_matter_stack::EthMatterStack;

use static_cell::StaticCell;
Expand Down Expand Up @@ -96,10 +96,10 @@ fn main() -> Result<(), Error> {
// 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(
// Will persist in `<tmp-dir>/rs-matter`
KvPersist::new_eth(DirKvBlobStore::new_default(), stack),
// Will try to find a default network interface
UnixNetif::default(),
// Will persist in `<tmp-dir>/rs-matter`
new_kv(DirKvBlobStore::new_default(), stack),
// Our `AsyncHandler` + `AsyncMetadata` impl
(NODE, handler),
// No user future to run
Expand Down Expand Up @@ -148,7 +148,7 @@ const NODE: Node = Node {
EthMatterStack::<KvBlobBuf<()>>::root_metadata(),
Endpoint {
id: LIGHT_ENDPOINT_ID,
device_type: DEV_TYPE_ON_OFF_LIGHT,
device_types: &[DEV_TYPE_ON_OFF_LIGHT],
clusters: &[descriptor::CLUSTER, cluster_on_off::CLUSTER],
},
],
Expand Down
44 changes: 26 additions & 18 deletions src/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use core::pin::pin;

use edge_nal::UdpBind;

use embassy_futures::select::select3;
use log::info;

use rs_matter::data_model::objects::{
Expand All @@ -15,7 +16,9 @@ use rs_matter::data_model::sdm::nw_commissioning::EthNwCommCluster;
use rs_matter::data_model::sdm::{ethernet_nw_diagnostics, nw_commissioning};
use rs_matter::error::Error;
use rs_matter::pairing::DiscoveryCapabilities;
use rs_matter::transport::network::NoNetwork;
use rs_matter::utils::init::{init, Init};
use rs_matter::utils::select::Coalesce;

use crate::netif::Netif;
use crate::network::{Embedding, Network};
Expand All @@ -42,8 +45,12 @@ where
{
const INIT: Self = Self { embedding: E::INIT };

type PersistContext<'a> = ();

type Embedding = E;

fn persist_context(&self) -> Self::PersistContext<'_> {}

fn embedding(&self) -> &Self::Embedding {
&self.embedding
}
Expand Down Expand Up @@ -93,16 +100,6 @@ where
Ok(())
}

/// Enable basic commissioning over IP (mDNS) by setting up a PASE session and printing the pairing code and QR code.
///
/// The method will return an error if there is not enough space in the buffer to print the pairing code and QR code
/// or if the PASE session could not be set up (due to another PASE session already being active, for example).
pub async fn enable_basic_commissioning(&self) -> Result<(), Error> {
self.matter()
.enable_basic_commissioning(DiscoveryCapabilities::IP, 0)
.await // TODO
}

/// Run the Matter stack for Ethernet network.
///
/// Parameters:
Expand All @@ -111,30 +108,41 @@ where
/// - `dev_comm` - the commissioning data
/// - `handler` - a user-provided DM handler implementation
/// - `user` - a user-provided future that will be polled only when the netif interface is up
pub async fn run<'d, H, P, I, U>(
pub async fn run<'d, I, P, H, U>(
&self,
persist: P,
netif: I,
persist: P,
handler: H,
user: U,
) -> Result<(), Error>
where
H: AsyncHandler + AsyncMetadata,
P: Persist,
I: Netif + UdpBind,
P: Persist,
H: AsyncHandler + AsyncMetadata,
U: Future<Output = Result<(), Error>>,
{
info!("Matter Stack memory: {}B", core::mem::size_of_val(self));

let mut user = pin!(user);

// TODO persist.load().await?;

self.matter().reset_transport()?;

if !self.is_commissioned().await? {
self.enable_basic_commissioning().await?;
self.matter()
.enable_basic_commissioning(DiscoveryCapabilities::IP, 0)
.await?; // TODO
}

self.run_with_netif(persist, netif, handler, &mut user)
let mut net_task = pin!(self.run_oper_net(
netif,
core::future::pending(),
Option::<(NoNetwork, NoNetwork)>::None
));
let mut handler_task = pin!(self.run_handlers(persist, handler));
let mut user_task = pin!(user);

select3(&mut net_task, &mut handler_task, &mut user_task)
.coalesce()
.await
}
}
Expand Down
Loading
Loading