matter-controller 0.6.0

High-level Matter controller API: commission, read, write, invoke, subscribe.
Documentation

matter-controller

The high-level Matter controller API — the single crate a consumer depends on to commission and control Matter devices from pure Rust. It wraps every other matter-* crate behind a small, async (Tokio) surface.

Part of matter-rust.

Status: 0.5.0. The v1.0 controller (M8) plus Matter-1.4 completeness work (M9): BLE→Wi-Fi/Thread commissioning, full interaction model, groups, OTA provider, ICD client, multi-admin/ACL — extensively validated against real silicon (ESP32-C6 over Wi-Fi and Thread).

What it does

  • Fabric & identitycreate_fabric mints and persists the controller's stable operational identity once per fabric, through a pluggable ControllerStore (a default FileStore ships). Opt-in per-fabric ICAC for a 3-tier RCAC→ICAC→NOC chain. fabrics() -> Vec<FabricInfo> enumerates fabrics already created — check it before calling create_fabric again; a second call with an existing fabric_id is refused.
  • Commissioningcommission("MT:…" | "<manual-code>", label) brings a device onto the fabric over IP, verifying device attestation against an AttestationTrust (example_device_roots(), or production PAA/CD roots via from_dirs). commission_ble (feature ble) commissions a fresh device over BLE onto Wi-Fi or Thread. Both return a typed NodeInfo.
  • Node lifecyclenodes() -> Vec<NodeInfo> enumerates commissioned devices (node id, fabric id, vendor/product id, label) with no snapshot deserialization; forget_node(node_id) drops all local state for a device without needing it to cooperate (reclaim an unreachable/reset node).
  • InteractionNode::read / write / invoke over raw matter_codec::Value (plus invoke_tlv for pre-encoded matter-clusters command TLV), wildcard + chunked reads, events, and timed interactions.
  • SubscriptionsNode::subscribe returns a Subscription stream of attribute/event reports that transparently auto-resubscribes across session loss or a device reboot (validated on hardware).
  • Groups — provision group keys and invoke_group over IPv6 multicast.
  • OTA providerserve_ota announces + serves a .ota image over BDX to a commissioned requestor.
  • ICD client — register as a check-in client and receive a Long-Idle-Time device's periodic Check-In.

The operational CASE session is established, cached, and reused transparently — callers address a device by node id and never manage sessions.

Quickstart

use std::sync::Arc;
use matter_controller::{AttestationTrust, FabricConfig, FileStore, MatterController, MatterTime, ReadPath};

let store = Arc::new(FileStore::new("controller-state.bin"));
let controller = MatterController::builder(store)
    .attestation_trust(AttestationTrust::example_device_roots())
    .build()
    .await?;

// Only create a fabric that doesn't exist yet — `create_fabric` refuses a
// `fabric_id` it already has, so check `fabrics()` first (or gate on a fresh
// store). `not_before` must be a real wall-clock time, backdated a little (an
// hour is plenty) for device clock skew: MatterTime(0) / from_unix_secs(0)
// (the Matter epoch) is rejected, and so is a time far in the future — see
// `FabricConfig::validity` and issue #111.
if controller.fabrics().await?.is_empty() {
    let now_unix = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)?
        .as_secs();
    controller.create_fabric(FabricConfig::new(
        1, 1, 1,
        (
            MatterTime::from_unix_secs(now_unix.saturating_sub(3600)),
            MatterTime::NO_EXPIRY,
        ),
    )).await?;
}

let info = controller
    .commission("MT:Y.K90AFN00KA0648G00", Some("kitchen plug".into()))
    .await?;
let node = controller.node(info.node_id);

// Read all OnOff attributes; subscribe to changes.
let report = node.read(&[ReadPath::cluster(1, 0x0006)]).await?;
let mut sub = node.subscribe(&[ReadPath::cluster(1, 0x0006)], &[], 1, 30).await?;
// `next()` yields `SubscriptionEvent` — attribute/event reports plus
// (re-)establishment status changes.
while let Some(event) = sub.next().await { /**/ }

// Enumerate and manage commissioned nodes.
for n in controller.nodes().await? {
    println!("node 0x{:016X} — {:?}", n.node_id, n.label);
}

See examples/ (controller_quickstart, list_nodes, e3_group_multicast, serve_ota, …) for end-to-end runs, and docs/matter-js-migration-guide.md if you're coming from matter.js.

Known limitations

  • BLE commissioning on macOS cannot complete. Root-caused to btleplug 0.12.0 / CoreBluetooth: the CHIPoBLE GATT characteristics draw CBError.uuidNotAllowed on descriptor discovery and the C1 write, and btleplug drops the errored delegate events. The former infinite hang is now bounded to a fast, clear failure, but success needs an upstream fix — drive live BLE commissioning from Linux. IP commissioning and everything else is unaffected on all platforms. Instrumentation: MATTER_BLE_PUMP_TRACE=1.
  • Thread network commissioning and BLE transport require the ble feature.

License

Apache-2.0.