#![allow(clippy::too_many_lines)]
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::{Context, Result};
use clap::Parser;
use matter_controller::{
AttestationTrust, CommandPath, FabricConfig, FileStore, MatterController, MatterTime, ReadPath,
SubscriptionEvent, Value,
};
const ONOFF_ENDPOINT: u16 = 1;
const ONOFF_CLUSTER: u32 = 0x0006;
const ONOFF_ATTR: u32 = 0x0000;
const ONOFF_CMD_TOGGLE: u32 = 0x02;
#[derive(Parser)]
#[command(about = "matter-controller quickstart: commission + control a device")]
struct Args {
#[arg(long, default_value = "matter-controller.bin")]
store: PathBuf,
#[arg(long, conflicts_with = "node")]
commission: Option<String>,
#[arg(long)]
node: Option<u64>,
#[arg(long, requires = "cd_dir")]
paa_dir: Option<PathBuf>,
#[arg(long, requires = "paa_dir")]
cd_dir: Option<PathBuf>,
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
let trust = match (&args.paa_dir, &args.cd_dir) {
(Some(paa), Some(cd)) => {
AttestationTrust::from_dirs(paa, cd).context("loading production attestation roots")?
}
_ => AttestationTrust::example_device_roots(),
};
let fresh = !args.store.exists();
let store = Arc::new(FileStore::new(&args.store));
let controller = MatterController::builder(store)
.attestation_trust(trust)
.build()
.await
.context("opening controller")?;
if fresh {
let now_unix = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(1_700_000_000, |d| d.as_secs());
controller
.create_fabric(FabricConfig::new(
1,
1,
1,
(
MatterTime::from_unix_secs(now_unix.saturating_sub(3600)),
MatterTime::NO_EXPIRY,
),
))
.await
.context("creating fabric")?;
println!("created a new fabric (stable commissioner identity persisted)");
}
let node_id = match (&args.commission, args.node) {
(Some(code), _) => {
println!("commissioning…");
let info = controller
.commission(code, Some("quickstart device".into()))
.await
.context("commissioning")?;
println!(
"commissioned device as node 0x{:016X} (vendor {:?}, product {:?}, label {:?})",
info.node_id, info.vendor_id, info.product_id, info.label
);
info.node_id
}
(None, Some(id)) => {
println!("reconnecting to persisted node 0x{id:016X} (no commissioning)");
id
}
(None, None) => {
anyhow::bail!("pass --commission <code> for a new device, or --node <id> to reconnect");
}
};
let node = controller.node(node_id);
let report = node
.read(&[ReadPath::concrete(
ONOFF_ENDPOINT,
ONOFF_CLUSTER,
ONOFF_ATTR,
)])
.await
.context("reading OnOff")?;
let on = matches!(report.first().map(|(_, v)| v), Some(Value::Bool(true)));
println!("OnOff = {on}");
node.invoke(
CommandPath {
endpoint: ONOFF_ENDPOINT,
cluster: ONOFF_CLUSTER,
command: ONOFF_CMD_TOGGLE,
},
Value::Structure(vec![]),
)
.await
.context("invoking Toggle")?;
println!("toggled");
let mut sub = node
.subscribe(
&[ReadPath::cluster(ONOFF_ENDPOINT, ONOFF_CLUSTER)],
&[],
1,
30,
)
.await
.context("subscribing")?;
println!("subscribed; printing up to 3 reports (Ctrl-C to stop)…");
for _ in 0..3 {
match sub.next().await {
Some(SubscriptionEvent::Report(change)) => {
println!(" report: {:?} = {:?}", change.path, change.value);
}
Some(SubscriptionEvent::Established { subscription_id }) => {
println!(" subscription established (id 0x{subscription_id:08X})");
}
Some(SubscriptionEvent::Resubscribing { cause }) => {
println!(" resubscribing: {cause}");
}
Some(SubscriptionEvent::Lagged { dropped }) => {
println!(" lagged: dropped {dropped} report(s) (consumer too slow)");
}
Some(_) => {}
None => break,
}
}
sub.cancel().await.ok();
println!("done — state persisted to {}", args.store.display());
Ok(())
}