Matter 10 · サブスクリプションとイベント — ポーリングしない

Chapter 10

サブスクリプションとイベント — ポーリングしない

この章がなぜ必要なのか——電池機器の寿命と応答性が、ここで決まる.

「電球の状態をアプリに表示する」——ポーリングで 1 秒ごとに読めば実装は簡単だ。 だが電池機器なら数日で電池が切れ、Thread の帯域も食い潰す。

Matter の答えは Subscribe。「変わったら教えて」と頼む。 ただしパラメータの選び方を誤ると、ポーリングと変わらない消費になる。

この章は、コントローラ実装者にとってもデバイス実装者にとっても核心である。

この章で使う既出の用語(定義は各リンク先). Matter(01 章 3 節)、コミッショニング(01 章 3 節)、ネットワーク(01 章 3 節)、ACL(02 章 4 節)、Attribute(02 章 2 節)、Cluster(02 章 2 節)、Event(02 章 2 節)、Report(02 章 6 節)、必要(05 章 5 節)、Session(06 章 5 節)、Priority(07 章 4 節)、CurrentLevel(08 章 2 節)、Descriptor(08 章 1 節)、OnOff(08 章 2 節)、イベント(08 章 8 節)、Path(09 章 2 節)、Read(09 章 1 節)、ワイルドカード(09 章 11 節)

1. サブスクリプションの仕組み

Subscribe Request { AttributePaths, EventPaths,
                    MinIntervalFloor, MaxIntervalCeiling, KeepSubscriptions }
        ↓
Subscribe Response { SubscriptionId, MaxInterval }   ← 機器が実際の MaxInterval を決める
        ↓
Report Data (初回:現在値をすべて送る)
        ↓
… 変化があれば Report Data …
… 変化がなくても MaxInterval 以内に必ず Report Data(生存確認)…

2 つの間隔

パラメータ誰が決めるか意味
MinIntervalFloorコントローラが要求これより短い間隔では報告しない(下限)
MaxIntervalCeilingコントローラが要求これより長く沈黙しない(上限の希望)
MaxInterval機器が決めて返す実際の上限。要求以下とは限らない

MinIntervalFloor の役割は「報告の抑制」である. 明るさスライダーを動かすと CurrentLevel が毎ミリ秒変わる。 そのたびに報告したらネットワークが溢れる。 MinIntervalFloor = 1秒 にすれば、1 秒に 1 回までしか報告されない。

MaxIntervalCeiling の役割は「生存確認」である. 何も変化がなくても、この間隔以内に必ず報告が来る。 来なければコントローラは「機器が落ちた」と判断できる。

機器が MaxInterval を上書きできることが重要である. 電池機器は「30 秒ごとに報告」を要求されても、 「私は 300 秒間隔でしか起きられない」と返せる。 コントローラはこれを受け入れなければならない(拒否して再要求を繰り返すと電池を無駄にする)。

ICD の LIT(長時間アイドル)機器では、MaxInterval が 数時間になることもある(17 章)。

2. パラメータの選び方

用途MinIntervalFloorMaxIntervalCeiling
照明の状態表示0〜1 秒60 秒
温度センサー10〜60 秒600 秒
ドアロックの状態0 秒(即座に知りたい)60 秒
電池残量600 秒3600 秒
電池駆動センサー長めできるだけ長く

MaxIntervalCeiling は「電池を食う設定」である. 短くすると、変化がなくても定期的に機器が起きて報告する。 ドアが 1 週間閉まったままでも、60 秒ごとに「閉まっています」と言い続ける。

本当に必要な生存確認の頻度を考えて設定すること。 「ハブが機器のオフラインを何分以内に検知すべきか」がそのまま答えになる。

3. Subscription Resumption と再確立

サブスクリプションは、以下で切れる。

原因対処
セッションの喪失(再起動など)再確立が必要
MaxInterval を超えて報告が来ないコントローラが「切れた」と判断
リソース不足で機器が破棄再確立

機器の再起動でサブスクリプションが全部消えることに注意. ファームウェア更新のたびに全コントローラが再購読することになる。

仕様には Subscription Resumption(永続化して再開する)の仕組みもあるが、 実装は任意である。コントローラ側は常に再確立できる設計にしておくこと。

4. リソース制約

機器が保持できるサブスクリプション数には限りがある。

属性内容
Basic Information.CapabilityMinima.CaseSessionsPerFabricFabric あたりの CASE セッション数(最低 3)
Basic Information.CapabilityMinima.SubscriptionsPerFabricFabric あたりのサブスクリプション数(最低 3)
\[ \text{必要なリソース} \approx (\text{Fabric 数}) \times (\text{Fabric あたりのサブスクリプション数}) \times (\text{パス数}) \]

これが電池機器のメモリ設計を圧迫する. 5 Fabric × 3 サブスクリプション × 数十パス を保持するのは、 RAM が数百 KB の SoC では現実的な負担になる。

RESOURCE_EXHAUSTED (0x89) が返るときは、この上限に達している。 コントローラ側は、パスを絞る(必要な属性だけ購読する)ことで負担を減らせる。

ワイルドカード購読 */*/* は、この観点でも避けるべきである。

5. イベント

属性との使い分け

属性の購読イベントの購読
表すもの現在の状態起きた出来事
取りこぼしありうる(MinInterval で間引かれる)順序と個数が保証される
例「今 ON である」「12:34 に ON にされた」

ボタンのダブルクリックは属性では表現できない. 押して離して押して離す——属性の値は元に戻っているので、 購読者は「何も起きなかった」と見えるかもしれない。

イベントなら 4 回の遷移がすべて記録され、順序も保たれる。 だから Generic Switch はイベントで通知する(08 章)。

Priority

Priority用途保持
Critical施錠履歴、警報、改竄検知配信されるまで確実に保持
Info一般的な状態変化バッファが一杯なら古いものから捨てる
Debug開発用最も優先度が低い

Event Number

単調増加の 64 bit。取りこぼしの検出に使う。

受信: Event #100, #101, #103   ← #102 が抜けている
   → Read で範囲指定して #102 を取りに行ける

Critical イベントのバッファ設計が実装の要点である. ドアロックの LockOperation イベントは「誰がいつ開けたか」の記録であり、 失われてはならない。

コントローラがオフラインの間に 100 回開閉されたら、100 件を保持する必要がある。 バッファが溢れたらどうするか——仕様は Critical を優先することを求めるが、 物理的な限界は設計者が決める。フラッシュに書くのか、古いものを捨てるのか。

isUrgent

イベントパスに isUrgent を付けると、MinIntervalFloor を無視して即座に報告される。

これが「ボタンを押したら即座に電気が点く」を実現する. MinIntervalFloor = 1 秒でも、緊急イベントは待たされない。

ただし濫用すると電池を食う。本当に即時性が要るものだけに使う。

6. コントローラ側の実装パターン

定石

1. コミッショニング完了
2. CASE セッション確立
3. Descriptor を読んで機器の構造を把握([09 章](09_インタラクションモデル.md#ワイルドカードの展開))
4. 必要な属性を絞ってサブスクライブ
5. Report を受けて内部状態を更新
6. セッションが切れたら再確立 → 再購読

やってはいけないこと

アンチパターン何が起きるか
ポーリングで属性を読み続ける電池が数日で切れる
*/*/* を購読するリソース枯渇、巨大な Report
MaxInterval を極端に短くする変化がなくても機器が起き続ける
機器が返した MaxInterval を無視して再要求無限ループ、電池消耗
Report を取りこぼしても再同期しない状態がずれたまま

「アプリを開いたときだけ購読する」も検討に値する. バックグラウンドで常時購読すると、機器側のリソースを占有し続ける。 ただし再購読のたびに初回 Report で全値が飛ぶので、 頻繁な購読/解除も負荷になる。用途に応じて設計する。

7. デバイス側の実装ポイント

項目内容
変化の検知属性を変更したら報告をトリガする(SDK の API を使う)
MinInterval の遵守短時間に連続変化しても間引く
MaxInterval の決定自機の電力特性から決める。無理な値は受け入れない
バッファイベントの保持。Critical を優先
永続化Subscription Resumption を実装するか判断
// SDK では属性を変更したら報告がトリガされる
// (ZAP 生成のアクセサを使えば自動)
chip::app::Clusters::OnOff::Attributes::OnOff::Set(endpoint, true);

// 手動でトリガする場合
MatterReportingAttributeChangeCallback(endpoint, OnOff::Id, OnOff::Attributes::OnOff::Id);

報告トリガの呼び忘れが、実装で最も多いバグの 1 つである. 属性の内部変数だけ更新して、報告をトリガし忘れる。 「アプリでは状態が変わらないが、読み直すと正しい」という症状になる。

必ず ZAP 生成のセッター経由で更新するか、 明示的に MatterReportingAttributeChangeCallback を呼ぶこと。

8. デバッグ

# chip-tool でサブスクライブ(min 1 秒、max 60 秒)
chip-tool onoff subscribe on-off 1 60 <node-id> 1

# イベントを購読
chip-tool onoff subscribe-event-by-id 0xFFFFFFFF 1 60 <node-id> 1

# 属性を読んで実際の値と比較(報告が来ていないときの切り分け)
chip-tool onoff read on-off <node-id> 1
症状疑うこと
報告が来ない報告トリガの呼び忘れ/MinInterval で間引かれている
読めば正しいが報告されない報告トリガの呼び忘れ(最頻出)
購読自体が失敗するRESOURCE_EXHAUSTED/ACL/パス数
報告が遅いMinIntervalFloor/機器が SED でポーリング待ち
定期的に切れるMaxInterval を超えている/セッションの喪失

9. まとめ