デバイスシャドウ、デバイスツインは、デバイスの状態に関する情報 (メタデータ、構成、状態など) を格納する ことを目的としています。
詳しくは AWS IoT Core の Device Shadow や Microsoft Azure の Device Twin を読んでください。
(以下、AWS IoT Core を対象にし、名称はシャドウで統一します)
ここではシャドウの実装に関するベストプラクティスを紹介します。
2019年1月2日追記: Wio LTE を対象とした実装を公開しました
実装すべき内容3点
実装すべきは以下3点です。便宜上 reporter
updater
restore
とします。1
-
Reporter
- デバイス自身の状態を取得し、取得できた内容を
/update
へ送信する
- デバイス自身の状態を取得し、取得できた内容を
-
updater
-
/update/delta
から得られた内容を基にデバイス自身の状態を更新する
-
-
restore
-
/get
に問い合わせをして、返ってきた内容を基にデバイス自身の状態を更新する
-
Reporter
デバイス自身の状態を取得し、取得できた内容を /update
へ送信します。
Reporter の実装は必須です。
送信時の JSON ですが、例えば Digital I/O の D38 番の状態を 0 もしくは 1 で表現するなら、以下の通りです。(d38
とか、値は自由に設定できます)
{
"state": {
"reported": {
"d38": 1
},
"desired": null
}
}
詳しくは AWS IoT Core であれば シャドウの更新 を見てください。
Reporter の実行は任意ですが、 デバイスへの更新が発生した時には必ず実行してください
そうしないと、デバイスとシャドウが同期しなくなり、シャドウの中身が役に立たなくなります。
例えば後述する Updater や Restore を実行した結果、デバイスの状態が変化したら無論 Reporter を実行する必要がありますが、実世界上からの干渉によってデバイスの状態が変化したときも Reporter を実行してください。(例えば、実世界でボタンが押された、等)
desired を「空」にする
delta
は state
が更新された毎に算出され、必要があれば出力されます。
これは desired
を送った際にはあたりまえの動作となりますが、見落としがちなのが reported
を送った時にも delta
が計算されるということです。
結論から言えば、 reported
送付時に desired
を「空」(実装レベルで言えば null
) にする必要があります。
さもないと、以下のような問題が発生します。(後日記載します)
Updater
/update/delta
から得られた内容を基にデバイス自身の状態を更新します。
Updater の実装は任意です。AWS IoT Core からの状態更新要求を受け取らないのであれば実装しなくても問題ありません。
Reporter のところでも解説しましたが、 Device に反映したら Reporter を起動するようにします。
Restore
/get
に問い合わせをして、返ってきた内容を基にデバイス自身の状態を更新します。
Restore の実装は任意です。ここで解説する挙動が不要であれば実装しなくても問題ありません。
AWS IoT Core の場合 /get
に空文字 (例えば {}
) を Publish すると /get/accpeted
に以下のような JSON が返ります。
{
"state": {
"desired":{"d38":1},
"reported":{"d38":0},
"delta":{"d38":1}
},
"metadata":{...}
}
この中で重要なのは reported
と delta
です。
state.reported
には後述する reporter
が送信した "状態" が入っています。この内容を参照しながらデバイスを構成することで「復元」ができます。
state.delta
には、オフライン中に発生した要求で、かつデバイスが適用すべき要求が入っています。この内容を参照しながらデバイスを構成することで「オフラインへの対応」ができます。
それぞれ実行するか否かは任意です。
たとえば状態復元しないのであれば reported は無視すれば良いわけです。
また「定期実行」と書いてありますが、定期的に sync することもお勧めです。
Updater との実装共有について
「デバイスの状態を更新する」という関数/メソッドを作る際、/get/accpeted
と /update/delta
では JSON の階層が違うため、ちょっと工夫が必要です。
{"state":{"delta":{"d38":1}...}
{"state":{"d38":1}}
以上のようにトピックに応じて state
の下の階層を掘り出してから使ってください。
全部を実装した場合のシーケンス
その他に実装すべきこと
接続を維持する方法
シャドウはステートフル・プロトコル、すなわちセッションを維持する必要があるため、セッション切断時における再接続も実装が必要です。
再接続の必要性判定&処理は Publish 直前に入れるのが良いでしょう。疑似コードで解説します。
def publish(topic, payload) {
if (!MQTT.is_connected) {
MQTT.connect()
}
MQTT.publish(topic, payload)
}
ここで解説した Restore
は /get
へのpublishが含まれているため、Restore
を定期的に実行することで自動的に再接続判定&処理がなされるという事になります。
Device ID
デバイスの ID をデバイスに埋め込むと、生産や交換が大変です。これを
(記載中: TODO)
Appendix
シーケンス図は PlantUML で記述しました。(レンダリングには PlantUML previewer を使いました)
participant Device
participant "AWS IoT Core"
Device -> Device: Device の状態を取得
Device -> "AWS IoT Core": `{state: {reported: ...}} ` を `/update` に Publish
participant Device
participant "AWS IoT Core"
group 起動時
Device --> "AWS IoT Core": Subscribe to `/update/delta`
end
group `/update/delta` 着信時のコールバック
Device <- "AWS IoT Core": `{state: {...}}` が返ってくる
Device -> Device: `state` の内容を Device に反映
Device -> "AWS IoT Core": **Reporter**
end
participant Device
participant "AWS IoT Core"
group 起動時
Device --> "AWS IoT Core": Subscribe to `/get/accepted`
end
group 起動時 もしくは 定期実行
Device -> "AWS IoT Core": `/get` に Publush
Device <- "AWS IoT Core": `{state: {...}}` が返ってくる
Device -> Device: `state.reported` の内容を Device に反映
note right: 最後にデバイス自身が報告した状態を復元する
Device -> Device: `state.delta` の内容を Device に反映
note right: オフライン中に発生した要求を反映するため
Device -> "AWS IoT Core": **Reporter**
end
あとがき
新年からこんなことやってます。
-
センスがないのは許してください ↩