Development
Tasks are defined with Task (task lists them).
task build # release build -> target/release/{mat,matd}
task install # install both binaries into ~/.cargo/bin
task run -- discover # run (native backend)
task test # tests (native FakeConn + binary integration; no real devices)
task clippy # lint (-D warnings)
task fmt # format
task check # CI equivalent (fmt:check + clippy + doc:check + test)
task dist:arm64 # aarch64-gnu + BLE deploy build -> dist/arm64/{mat,matd}
task docker:build # slim x86_64 image (mat/matd only)
task docker:run -- discover
task docker:test # no local toolchain needed
The Docker image runs as an unprivileged user (mat, uid 10001); the store
lives at /home/mat/.config/mat (what task docker:run mounts the mat-store
volume on). For a bind-mounted store, match the host directory’s owner or pass
docker run --user <uid>:<gid>.
CI (GitHub Actions, .github/workflows/ci.yml) runs the same fmt / clippy /
test sequence as task check. The default build and CI do not use the ble
cargo feature; deploy builds (task dist:arm64) enable it for BLE+Thread
commissioning.
Manual E2E (with real devices; not in CI)
In practice the main path is multi-admin join: adding a device that is
already commissioned by another admin (such as Home Assistant) to mat as well.
The printed code does not work (the device left commissioning mode), so the
existing admin opens a commissioning window to issue a one-time code.
- Share from the other admin: on the other controller, run “Share” for the
target device and note the issued setup code (
MT:...or 11-digit). - Join with
mat:
It returnsmat commission --setup-code "<issued setup code>" --node 5{ "node_id": 5, "status": "success" }and records the ledger in~/.config/mat/nodes.json. - Confirm:
mat discovernow shows node 5 with"state": "commissioned".
For a factory-reset device, pass the printed setup code directly to
commission(first commission).
State operations E2E
Against a commissioned node (node 5 above), confirm read / describe / on / off on a real device.
# Introspect what you can call (endpoints and numeric cluster IDs)
mat describe --node 5
# Read the OnOff attribute (for a light, its current on/off state)
mat read --node 5 --cluster onoff --attribute on-off
# Turn on -> off (invoke of the OnOff command, not an attribute write)
mat on --node 5
mat off --node 5
# Read-after-write check (confirm the value took effect)
mat on --node 5 && mat read --node 5 --cluster onoff --attribute on-off # -> "value": true
Share E2E (mat -> another admin)
Share mat-owned node 5 with another controller.
# Open a commissioning window (get the issued code)
mat open-window --node 5 --timeout 300
# -> { "node_id": 5, "manual_code": "...", "qr_payload": "MT:...", "expires_at": "..." }
Enter the returned manual_code (11-digit) or qr_payload (render the QR with
the receiving tool) into the other controller’s “Add device” flow (Alexa / Apple
Home / Google Home). Finish before expires_at. After sharing, mat keeps its
fabric membership (multi-admin).
Each one-shot run pays mDNS resolution plus a CASE handshake, so a single call is slow (hundreds of ms to seconds). Speed-sensitive use cases run
matd, which keeps warm sessions (see ARCHITECTURE.md).
Groupcast E2E (real devices)
With several commissioned lights (say nodes 5, 6, 7), burn a wire group and fire one multicast send at it.
# Provision the group onto every node (controller-side state is set up too)
mat group provision --group 1 --nodes 5 6 7 --name living
# -> { "group_id": 1, "keyset_id": 42, "nodes": [5,6,7], "status": "provisioned", ... }
# One multicast send — all three should react together (no popcorn effect)
mat group invoke --group 1 --cluster onoff --command on
mat group invoke --group 1 --cluster onoff --command off
Groupcast is unacknowledged, so
group invokeonly confirms the send, not delivery. If a light did not react, confirm it individually (mat read --node 6 -c onoff --attribute on-off) and re-provision that node. Multicast is especially weak on Thread; Wi-Fi / Ethernet lights are more reliable. The KVS recordsmatwrites (keyset table, group table, GroupKeyMap) follow the connectedhomeip v1.4.2.0GroupDataProviderImpllink discipline, so a realchip-toolon the same store can still read them — if a devices-side provisioning step regresses, the group-settings writer is the first place to check.If no device reacts although provision reported success, suspect the device ACL first: provisions made before the ACL step (or through an old
matd≤ 0.12) never granted the group permission, and devices silently drop unauthorized groupcast.mat group grant --group 1 --nodes 5 6 7adds the missing entries idempotently.