use anyhow::Result;
use clap::{Parser, Subcommand};
use matc::{clusters, devman::{DeviceManager, ManagerConfig}};
use std::time::Duration;
const DEFAULT_DATA_DIR: &str = "./matter-data";
const DEFAULT_LOCAL_ADDRESS: &str = "0.0.0.0:5555";
#[derive(Parser, Debug)]
#[command(name = "devman_demo", about = "Matter device manager demo")]
struct Cli {
#[clap(long)]
#[arg(global = true, default_value_t = false)]
verbose: bool,
#[clap(short, long)]
#[arg(global = true, default_value_t = DEFAULT_DATA_DIR.to_string())]
data_dir: String,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand, Debug)]
enum Commands {
Init {
#[clap(long, default_value_t = 1000)]
fabric_id: u64,
#[clap(long, default_value_t = 100)]
controller_id: u64,
#[clap(long, default_value_t = DEFAULT_LOCAL_ADDRESS.to_string())]
local_address: String,
},
Commission {
address: String,
node_id: u64,
pin: u32,
name: String,
},
CommissionWithDiscovery {
pairing_code: String,
node_id: u64,
name: String,
},
List,
On {
device: String,
},
Off {
device: String,
},
Toggle {
device: String,
},
Remove {
device: String,
},
Rename {
device: String,
new_name: String,
},
}
async fn connect_by_name_or_id(dm: &DeviceManager, device: &str) -> Result<matc::controller::Connection> {
if let Ok(node_id) = device.parse::<u64>() {
if dm.get_device(node_id)?.is_some() {
return dm.connect(node_id).await;
}
}
dm.connect_by_name(device).await
}
fn resolve_node_id(dm: &DeviceManager, device: &str) -> Result<u64> {
if let Ok(node_id) = device.parse::<u64>() {
if dm.get_device(node_id)?.is_some() {
return Ok(node_id);
}
}
let dev = dm.get_device_by_name(device)?
.ok_or_else(|| anyhow::anyhow!("device '{}' not found", device))?;
Ok(dev.node_id)
}
async fn discover(discriminator: u16) -> Result<(Vec<std::net::IpAddr>, u16)> {
let (mdns, mut receiver) = matc::mdns2::MdnsService::new().await?;
mdns.add_query("_matterc._udp.local", 0xff, Duration::from_secs(3)).await;
while let Some(dns) = receiver.recv().await {
if let matc::mdns2::MdnsEvent::ServiceDiscovered {name, records: _, target } = dns {
if name != "_matterc._udp.local." {
continue;
}
let info = matc::discover::extract_matter_info(&target, &mdns).await;
let info = match info {
Ok(info) => info,
Err(e) => {
log::debug!("Failed to extract Matter info from {}: {}", target, e);
continue;
}
};
if let Some(ref d) = info.discriminator {
if *d == discriminator.to_string() {
println!("Found device: {:?}", info);
mdns.shutdown();
return Ok((info.ips, info.port.unwrap_or(5540)));
}
}
}
}
anyhow::bail!("No device found with discriminator {}", discriminator)
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
let log_level = if cli.verbose {
log::LevelFilter::Trace
} else {
log::LevelFilter::Error
};
env_logger::Builder::new()
.parse_default_env()
.target(env_logger::Target::Stdout)
.filter_level(log_level)
.format_timestamp(Some(env_logger::TimestampPrecision::Millis))
.init();
let data_dir = &cli.data_dir;
match cli.command {
Commands::Init { fabric_id, controller_id, local_address } => {
let config = ManagerConfig {
fabric_id,
controller_id,
local_address,
};
DeviceManager::create(data_dir, config).await?;
println!("Device manager initialized in {}", data_dir);
}
Commands::Commission { address, node_id, pin, name } => {
let dm = DeviceManager::load(data_dir).await?;
let conn = dm.commission(&address, pin, node_id, &name).await?;
println!("Commissioned '{}' (node {}) at {}", name, node_id, address);
let res = conn.read_request2(0, clusters::defs::CLUSTER_ID_DESCRIPTOR,
clusters::defs::CLUSTER_DESCRIPTOR_ATTR_ID_SERVERLIST).await;
if let Ok(matc::tlv::TlvItemValue::List(l)) = res {
println!("Supported clusters:");
for c in l {
if let matc::tlv::TlvItemValue::Int(v) = c.value {
match clusters::names::get_cluster_name(v as u32) {
Some(name) => println!(" {}", name),
None => println!(" unknown (0x{:x})", v),
}
}
}
}
}
Commands::CommissionWithDiscovery { pairing_code, node_id, name } => {
let dm = DeviceManager::load(data_dir).await?;
let onboarding_info = matc::onboarding::decode_manual_pairing_code(&pairing_code).unwrap();
println!("Discovering device with discriminator {}...", onboarding_info.discriminator);
let (ips, port) = discover(onboarding_info.discriminator).await?;
if ips.is_empty() {
println!("No device found with discriminator {}", onboarding_info.discriminator);
return Ok(());
}
println!("Found device at IPs: {:?}", ips);
for ip in &ips {
println!("Attempting to commission at {}:{}...", ip, port);
let address = if ip.is_ipv6() {
format!("[{}]:{}", ip, port)
} else {
format!("{}:{}", ip, port)
};
let pin = onboarding_info.passcode;
match dm.commission(&address, pin, node_id, &name).await {
Ok(connection) => {
println!("Commissioned '{}' (node {}) at {}", name, node_id, address);
let resptlv = connection
.read_request2(
0,
clusters::defs::CLUSTER_ID_DESCRIPTOR,
clusters::defs::CLUSTER_DESCRIPTOR_ATTR_ID_PARTSLIST,
)
.await
.unwrap();
let mut endpoints = matc::clusters::codec::descriptor_cluster::decode_parts_list(&resptlv).unwrap();
println!("Endpoints: {:?}", endpoints);
endpoints.push(0);
for ep in endpoints {
let resptlv = connection
.read_request2(
ep,
clusters::defs::CLUSTER_ID_DESCRIPTOR,
clusters::defs::CLUSTER_DESCRIPTOR_ATTR_ID_SERVERLIST,
)
.await.unwrap();
let clusters = matc::clusters::codec::descriptor_cluster::decode_server_list(&resptlv).unwrap();
let names = clusters.iter().map(|c| {
match clusters::names::get_cluster_name(*c) {
Some(name) => name.to_string(),
None => format!("unknown (0x{:x})", c),
}
}).collect::<Vec<_>>();
println!("Supported clusters on endpoint {:?}: {:?}", ep, names);
}
return Ok(());
}
Err(e) => {
println!("Failed to commission at {}: {}. Trying next IP if available...", address, e);
tokio::time::sleep(Duration::from_secs(3)).await; }
}
}
println!("Failed to commission '{}' (node {}) at any discovered IPs", name, node_id);
}
Commands::List => {
let dm = DeviceManager::load(data_dir).await?;
let devices = dm.list_devices()?;
if devices.is_empty() {
println!("No devices registered.");
} else {
println!("{:<10} {:<25} Name", "Node ID", "Address");
println!("{}", "-".repeat(60));
for d in devices {
println!("{:<10} {:<25} {}", d.node_id, d.address, d.name);
}
}
}
Commands::On { device } => {
let dm = DeviceManager::load(data_dir).await?;
let conn = connect_by_name_or_id(&dm, &device).await?;
conn.invoke_request(1, clusters::defs::CLUSTER_ID_ON_OFF,
clusters::defs::CLUSTER_ON_OFF_CMD_ID_ON, &[]).await?;
println!("ON sent to '{}'", device);
}
Commands::Off { device } => {
let dm = DeviceManager::load(data_dir).await?;
let conn = connect_by_name_or_id(&dm, &device).await?;
conn.invoke_request(1, clusters::defs::CLUSTER_ID_ON_OFF,
clusters::defs::CLUSTER_ON_OFF_CMD_ID_OFF, &[]).await?;
println!("OFF sent to '{}'", device);
}
Commands::Toggle { device } => {
let dm = DeviceManager::load(data_dir).await?;
let conn = connect_by_name_or_id(&dm, &device).await?;
conn.invoke_request(1, clusters::defs::CLUSTER_ID_ON_OFF,
clusters::defs::CLUSTER_ON_OFF_CMD_ID_TOGGLE, &[]).await?;
println!("TOGGLE sent to '{}'", device);
}
Commands::Remove { device } => {
let dm = DeviceManager::load(data_dir).await?;
let node_id = resolve_node_id(&dm, &device)?;
dm.remove_device(node_id)?;
println!("Removed device (node {})", node_id);
}
Commands::Rename { device, new_name } => {
let dm = DeviceManager::load(data_dir).await?;
let node_id = resolve_node_id(&dm, &device)?;
dm.rename_device(node_id, &new_name)?;
println!("Renamed device (node {}) to '{}'", node_id, new_name);
}
}
Ok(())
}