Chapter 09
インタラクションモデル — Read / Write / Invoke / Subscribe
この章がなぜ必要なのか——データモデルを「動かす」ための唯一の手段だから.
07 章〜08 章で「機器は Attribute と Command の集まりである」と分かった。 ではそれをどうやって読み書きするのか。
Matter のインタラクションモデル (IM) は 4 種類しかない。 Read・Write・Invoke・Subscribe。HTTP の GET/POST よりも少ない。
シンプルだが、Path とワイルドカード、そしてステータスコードの扱いに 実装上の要点が集中している。
この章で使う既出の用語(定義は各リンク先). Matter(01 章 3 節)、コミッショニング(01 章 3 節)、ネットワーク(01 章 3 節)、ACL(02 章 4 節)、Attribute(02 章 2 節)、Cluster(02 章 2 節)、Command(02 章 2 節)、Endpoint(02 章 2 節)、Event(02 章 2 節)、Node(02 章 2 節)、Report(02 章 6 節)、インタラクションモデル(02 章 1 節)、NDP(03 章 5 節)、マルチキャスト(03 章 3 節)、必要(05 章 5 節)、Exchange(06 章 5 節)、Group(06 章 4 節)、再送(06 章 3 節)、Fabric-Scoped(07 章 7 節)、Binding(08 章 2 節)、Descriptor(08 章 1 節)、OnOff(08 章 2 節)、イベント(08 章 8 節)
1. 4 つのインタラクション
| 種類 | 対象 | 用途 |
|---|---|---|
| Read | Attribute / Event | 現在の値・過去のイベントを取得 |
| Write | Attribute | 値を設定 |
| Invoke | Command | 動作を要求 |
| Subscribe | Attribute / Event | 変化を通知させる(10 章) |
これだけである。すべての操作がこの 4 つに帰着する。
2. Path — 何を指すか
インタラクションの対象は Path で指定する。
Attribute Path
{ Node, Endpoint, Cluster, Attribute }| フィールド | ワイルドカード可 |
|---|---|
| Endpoint | ○ |
| Cluster | ○ |
| Attribute | ○ |
Command Path
{ Endpoint, Cluster, Command }Invoke では原則としてワイルドカードは使えない(グループコマンドを除く)。 「どの機器に何をさせるか」は曖昧であってはならないからである。
Event Path
{ Node, Endpoint, Cluster, Event, isUrgent }3. ワイルドカードの展開
ワイルドカードは「省略」で表現する。省略されたフィールドは「すべて」を意味する。
| 指定 | 意味 | 結果の例 |
|---|---|---|
1/6/0 | Endpoint 1 / On/Off / OnOff | 1 個 |
1/6/* | Endpoint 1 の On/Off の全属性 | OnOff, StartUpOnOff, グローバル属性… |
*/6/0 | 全 Endpoint の OnOff 属性 | 3 口タップなら 3 個 |
1/*/* | Endpoint 1 の全クラスタの全属性 | 数十個 |
*/*/* | すべて | 数百個 |
# chip-tool でのワイルドカード読み出し
chip-tool onoff read on-off 1 0xFFFF # 全 Endpoint の OnOff
chip-tool any read-by-id 0xFFFFFFFF 0xFFFFFFFF 1 0xFFFF # すべて
*/*/*は強力だが危険である.コントローラが機器の全体像を把握するのに使う定石だが、 Thread 機器では応答が巨大になり、フラグメントが多発して失敗する(03 章)。
実務では:
- まず
0/29/*(Endpoint 0 の Descriptor)で PartsList を取る- 各 Endpoint の Descriptor を個別に読む
- 必要なクラスタだけを絞って読む
と段階的に読むのが安全である。 仕様には応答を分割する仕組み(後述の chunking)があるが、 それでも回数が増えれば失敗確率は上がる。
ワイルドカードで返らないもの
ワイルドカード読み出しでは、以下は返らない(明示指定が必要)。
- グローバル属性のうち一部(実装・バージョン依存)
- アクセス権限がない属性(エラーではなく、単に省略される)
「読めるはずの属性が返ってこない」の主因は権限である. ワイルドカードは「読める範囲で返す」動作をする。 明示的に指定すれば
UNSUPPORTED_ACCESSが返り、原因が分かる。 デバッグでは明示指定に切り替えて確認するとよい。
4. Read
Read Request { AttributePaths, EventPaths, DataVersionFilters, FabricFiltered }
↓
Report Data { AttributeReports[], EventReports[], SuppressResponse }DataVersion — 無駄な転送を減らす
各クラスタは DataVersion という単調増加のカウンタを持つ。 クラスタ内の属性が変わるたびに増える。
Read Request に DataVersionFilter を付ける
→ 機器側で「変わっていない」なら、その分は返さないこれが帯域とバッテリーを大きく節約する. コントローラが再接続のたびに全属性を読み直すと、Thread 機器には大きな負担になる。 前回の DataVersion を覚えておいて添えれば、 変化した部分だけが返る。
ただし DataVersion はクラスタ単位である。 1 つの属性が変わればクラスタ全体が返る。
FabricFiltered
Fabric-Scoped な属性(07 章)を読むとき、 「自分の Fabric の分だけ」か「全部(見える範囲で)」かを指定する。
Chunking
応答が MTU を超えるとき、複数のメッセージに分割して送る。 リストの途中で切れる場合、受信側は再構築する必要がある。
リスト属性の chunking は実装上の落とし穴である. 分割されたリストは「最初に空リストを送り、以降 append する」形で送られる。 コントローラ側で正しく再構築しないと、データが壊れる。 SDK を使っていれば処理されるが、独自実装では要注意。
5. Write
Write Request { WriteRequests[{ Path, Data, DataVersion }], TimedRequest, SuppressResponse }
↓
Write Response { WriteResponses[{ Path, Status }] }| 特徴 | 内容 |
|---|---|
| 複数属性を一度に書ける | 部分的な成功もありうる |
| DataVersion を添えられる | 「読んだときから変わっていなければ書く」楽観的排他制御 |
| Timed Write | 一部の属性は Timed Request が必須(次節) |
DataVersionMismatch(0x92) が返ったら、読み直してから書き直す. これは「他の誰かが先に変更した」ことを意味する。 マルチアドミン環境では実際に起きる。
6. Timed Interaction — 中間者攻撃への対策
一部の操作は、Timed Request を先行させなければならない。
Timed Request { Timeout } ← 「これから T ms 以内に本番の要求を送ります」
↓
Status Response
↓
Write / Invoke Request ← 期限内に送る| 目的 | 内容 |
|---|---|
| 遅延した再送の悪用を防ぐ | 攻撃者がパケットを保留し、後で流し込む攻撃を封じる |
どの操作が Timed 必須かは仕様が決めている. ドアロックの解錠など、遅延して実行されると危険な操作が対象である。
「後から流されると困る操作」—— 深夜に保留されていた解錠コマンドが朝に実行される、といった攻撃を想定している。
実装で
NEEDS_TIMED_INTERACTION(0xC6) が返ったら、この仕組みを使っていない。
7. Invoke
Invoke Request { InvokeRequests[{ CommandPath, CommandFields }], TimedRequest, SuppressResponse }
↓
Invoke Response { InvokeResponses[{ Command or Status }] }| 応答の形 | 例 |
|---|---|
| ステータスのみ | On → SUCCESS |
| 応答コマンド | AttestationRequest → AttestationResponse |
Group Command
グループ ID 宛にマルチキャストで送るコマンド。応答はない。
「リビングの照明」グループに Off コマンド
→ 該当する全機器が同時に消える| 特徴 | 内容 |
|---|---|
| 一斉性 | 個別送信より同時に近い(照明の一斉制御で重要) |
| 応答なし | 成否は分からない |
| グループ鍵で暗号化 | CASE セッション不要 |
照明の一斉制御でグループが必要な理由. 10 個の電球に個別に Off を送ると、最初と最後で数百 ms のずれが出て、 バラバラに消えるのが目に見える。 グループのマルチキャストなら 1 パケットで同時に届く。
Binding(07 章)と組み合わせれば、 壁スイッチが直接グループに送る——ハブなしで一斉制御ができる。
8. ステータスコード
主なものを挙げる。エラーの意味を知っていると、デバッグが劇的に速くなる。
| コード | 名前 | 意味 | よくある原因 |
|---|---|---|---|
0x00 | SUCCESS | 成功 | — |
0x01 | FAILURE | 一般的な失敗 | 実装側の内部エラー |
0x7E | UNSUPPORTED_ACCESS | 権限不足 | ACL の設定漏れ(14 章) |
0x7F | UNSUPPORTED_ENDPOINT | その Endpoint がない | Endpoint 番号の誤り |
0x80 | INVALID_ACTION | 要求の形式が不正 | パスやエンコードの誤り |
0x81 | UNSUPPORTED_COMMAND | そのコマンドがない | 未実装 |
0x85 | INVALID_COMMAND | 引数が不正 | 値域外の引数 |
0x86 | UNSUPPORTED_ATTRIBUTE | その属性がない | 未実装 or ID の誤り |
0x87 | CONSTRAINT_ERROR | 値が制約に反する | 値域外(例: Level に 255) |
0x88 | UNSUPPORTED_WRITE | 読み取り専用 | OnOff を書こうとした |
0x89 | RESOURCE_EXHAUSTED | リソース不足 | サブスクリプション数の上限 |
0x8B | NOT_FOUND | 見つからない | — |
0x8D | INVALID_DATA_TYPE | 型が違う | TLV の型の誤り |
0x8F | UNSUPPORTED_READ | 書き込み専用 | — |
0x92 | DATA_VERSION_MISMATCH | 版が古い | 他者が先に変更した |
0x94 | TIMEOUT | タイムアウト | — |
0x9C | BUSY | 処理中 | 後で再試行 |
0xC3 | UNSUPPORTED_CLUSTER | そのクラスタがない | 未実装 |
0xC6 | NEEDS_TIMED_INTERACTION | Timed 必須 | Timed Request を先行させる |
0xC7 | UNSUPPORTED_EVENT | そのイベントがない | — |
0xC8 | PATHS_EXHAUSTED | パス数の上限 | 一度に要求しすぎ |
0xCA | FAILSAFE_REQUIRED | Fail-Safe が必要 | コミッショニング手順の誤り |
0x7E(UNSUPPORTED_ACCESS) は最頻出のエラーである. 「コミッショニングは成功したのに、属性が読めない」—— ほぼ確実に ACL の問題である(14 章)。 エラーコードを見ずに「通信できない」と言うと、原因究明が何倍も遅くなる。
9. Fail-Safe — コミッショニング中の巻き戻し
コミッショニング中に失敗すると、機器が中途半端な状態で残る危険がある。
General Commissioning クラスタの Fail-Safe がこれを防ぐ。
ArmFailSafe(timeout) ← タイマーを開始
↓
… 証明書の追加、ネットワーク設定など …
↓
CommissioningComplete ← 成功: 変更を確定
or
タイムアウト ← 失敗: すべて巻き戻す| 巻き戻される対象 | 内容 |
|---|---|
| 追加された NOC / Trusted Root | 削除 |
| ネットワーク設定 | 以前の状態に |
| 未完了の Fabric 追加 | 破棄 |
これがないと、失敗した機器がゴミの設定を抱えたまま残る. ユーザーは「もう一度セットアップ」しようとして、 Fabric の枠を使い切った機器に出会うことになる。
実装側の注意: Fail-Safe の巻き戻しを正しく実装するのは意外に難しい。 「どこまで進んだか」を追跡し、正確に元へ戻す必要がある。 認証テストでは、わざと途中で切断して巻き戻しを検証する。
10. 実装上の制約
| 項目 | 考慮点 |
|---|---|
| 同時 Exchange 数 | 機器のメモリで決まる。超えたら BUSY |
| 1 要求あたりのパス数 | 上限を超えたら PATHS_EXHAUSTED |
| 応答サイズ | MTU を超えたら chunking |
| サブスクリプション数 | CapabilityMinima で公開(10 章) |
| 処理時間 | 長い処理はコマンドを即座に受理し、非同期で実行する |
「コマンドの処理に 5 秒かかる」場合の設計. ハンドラの中で 5 秒ブロックしてはいけない。 すぐに応答を返し、実際の動作は非同期で行い、 完了したら属性を更新する(購読者に通知が飛ぶ)。
ブロックするとスタック全体が止まり、他の通信が滞る。 カーテンの開閉やドアロックのように時間のかかる機器では必須の設計である。
11. まとめ
- インタラクションは Read / Write / Invoke / Subscribe の 4 つだけ。
- 対象は Path(Endpoint / Cluster / Attribute)。省略がワイルドカードになる。
*/*/*は便利だがThread では危険。段階的に読む。- DataVersion で無駄な転送を避けられる。クラスタ単位である点に注意。
- Timed Interaction は遅延再送攻撃への対策。ドアロックなどで必須。
- Group Command は一斉制御用。応答なし・グループ鍵で暗号化。
- ステータスコード、特に
0x7EUNSUPPORTED_ACCESS(=ACL) を覚えておく。 - Fail-Safe がコミッショニング失敗時の巻き戻しを担う。実装は丁寧に。