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. パラメータの選び方
| 用途 | MinIntervalFloor | MaxIntervalCeiling |
|---|---|---|
| 照明の状態表示 | 0〜1 秒 | 60 秒 |
| 温度センサー | 10〜60 秒 | 600 秒 |
| ドアロックの状態 | 0 秒(即座に知りたい) | 60 秒 |
| 電池残量 | 600 秒 | 3600 秒 |
| 電池駆動センサー | 長め | できるだけ長く |
MaxIntervalCeiling は「電池を食う設定」である. 短くすると、変化がなくても定期的に機器が起きて報告する。 ドアが 1 週間閉まったままでも、60 秒ごとに「閉まっています」と言い続ける。
本当に必要な生存確認の頻度を考えて設定すること。 「ハブが機器のオフラインを何分以内に検知すべきか」がそのまま答えになる。
3. Subscription Resumption と再確立
サブスクリプションは、以下で切れる。
| 原因 | 対処 |
|---|---|
| セッションの喪失(再起動など) | 再確立が必要 |
| MaxInterval を超えて報告が来ない | コントローラが「切れた」と判断 |
| リソース不足で機器が破棄 | 再確立 |
機器の再起動でサブスクリプションが全部消えることに注意. ファームウェア更新のたびに全コントローラが再購読することになる。
仕様には Subscription Resumption(永続化して再開する)の仕組みもあるが、 実装は任意である。コントローラ側は常に再確立できる設計にしておくこと。
4. リソース制約
機器が保持できるサブスクリプション数には限りがある。
| 属性 | 内容 |
|---|---|
Basic Information.CapabilityMinima.CaseSessionsPerFabric | Fabric あたりの CASE セッション数(最低 3) |
Basic Information.CapabilityMinima.SubscriptionsPerFabric | Fabric あたりのサブスクリプション数(最低 3) |
これが電池機器のメモリ設計を圧迫する. 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. まとめ
- Subscribe は「変わったら教えて」。ポーリングは電池機器では禁じ手。
- MinIntervalFloor = 報告の抑制(下限)、MaxIntervalCeiling = 生存確認(上限の希望)。 実際の MaxInterval は機器が決めて返す。コントローラはそれを受け入れる。
- サブスクリプションは機器のメモリを消費する。
*/*/*の購読は避け、必要なパスに絞る。 - イベントは取りこぼしと順序を保証する。ボタン操作や施錠履歴に使う。 Critical はバッファ設計が重要。
isUrgentで即時報告できる。 - デバイス側の最頻出バグは報告トリガの呼び忘れ。 「読めば正しいが通知が来ない」ならこれを疑う。