Skip to content

Commit

Permalink
Concurrent commissioning, Thread support (#4)
Browse files Browse the repository at this point in the history
* Accomodate Thread networks

* Concurrent commissioning

* Concurrent commissioning

* Concurrent commissioning

* Concurrent commissioning

* Concurrent commissioning

* Concurrent commissioning

* Concurrent commissioning

* Concurrent commissioning

* Concurrent commissioning

* Concurrent commissioning

* Remove the no longer used modem mod

* The example can only work in non-concurrent mode

* Fix imports

* Bugfixing

* Fix typo in a comment; assert

* Extra logging

* Bugfix: forgotten command
  • Loading branch information
ivmarkov authored Oct 5, 2024
1 parent 926062e commit cf2a8ad
Show file tree
Hide file tree
Showing 24 changed files with 2,634 additions and 1,102 deletions.
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

0 comments on commit cf2a8ad

Please sign in to comment.