[AWS IoT] シャドウの更新をトリガーする方法

1 はじめに

AIソリューション部の平内(SIN)です。

AWS IoTでは、Device Shadow サービスにより、管理システムとデバイスのデータを非同期でやり取りする仕組みがあります。

今回は、このシャドウの値が変化したタイミングでLambdaなどを起動する場面を想定して、ルールの設定について確認してみました。

2 シャドウのトピック名

Device Shadow サービスでは、次のようなトピック名が使用されます。


参考:Device Shadow サービスのデータフロー

トピック名 送信されるタイミング
$aws/things/myLightBulb/shadow/update/accepted 更新が正常に行われた
$aws/things/myLightBulb/shadow/update/rejected 更新が拒否された
$aws/things/myLightBulb/shadow/update/delta reportedとdesiredの間で差分が検出された
$aws/things/myLightBulb/shadow/update/documents 更新が正常に実行された際に、状態を送る
$aws/things/myLightBulb/shadow/get/accepted リクエストが正常に行われた
$aws/things/myLightBulb/shadow/get/rejected リクエストが拒否された
$aws/things/myLightBulb/shadow/delete/accepted 削除された
$aws/things/myLightBulb/shadow/delete/rejected 削除リクエストが拒否された

シャドウに関する操作を行った際に、上記のトピックに対してメッセージが飛び交うことになります。

トピックが利用されている様子を見るために、次のようなルールを設定してモニターしてみました(モノ:sensor1の/update/+なトピックをモニターしています)

SELECT { "payload": *, "topicName": topic()} \
  FROM "$aws/things/sensor1/shadow/update/+"

最初に、デバイス側の状態(reported)を設定しました。

$ aws iot-data update-thing-shadow --thing-name sensor1 \
   --payload '{"state": {"reported" : {"counter": "1"}}}' outfile.json

結果は、/update/documents/update/acceptedの2つが送信されていました。

{
    "payload": {
        "previous": {
            // ... 略 ... 
        },
        "current": {
            "state": {
                "reported": {
                    "counter": "1"
                }
            },
            // ... 略 ... 
    },
    "topicName": "$aws/things/sensor1/shadow/update/documents"
}
{
    "payload": {
            // ... 略 ... 
    },
    "topicName": "$aws/things/sensor1/shadow/update/accepted"
}

続いて、管理システム側から値(desired)を変化してみます。

$ aws iot-data update-thing-shadow --thing-name sensor1 \
  --payload '{"state": {"desired" : {"counter": "2"}}}' outfile.json

結果は、update/delta/update/documents/update/acceptedが送信されていました。

{
    "payload": {
            // ... 略 ... 
    "topicName": "$aws/things/sensor1/shadow/update/delta"
}
{
    "payload": {
        "previous": {
            // ... 略 ... 
        },
        "current": {
            // ... 略 ... 
        },
    },
    "topicName": "$aws/things/sensor1/shadow/update/documents"
}
{
    "payload": {
            // ... 略 ... 
    },
    "topicName": "$aws/things/sensor1/shadow/update/accepted"
}

通常は、このupdate/deltaを利用してデバイス側を非同期で更新していくことになると思います。

3 シャドウの更新をトリガーする

先の確認から、update/accepted若しくは、update/documentsを対象トピックとすることで、シャドウの値が更新されたルールは設定できそうです。

SELECT * FROM "$aws/things/sensor1/shadow/update/accepted"
SELECT * FROM "$aws/things/sensor1/shadow/update/documents"

なお、update/acceptedと、update/documentsでは、同じ、「SELECT *」でも、payloadの下の形式が違うことに注意が必要です。

4 値が変化した場合だけトリガーする

先のルールでは、値が本当に変化しているかどうかは分かりません。同じ値を上書きした際も、このトリガーは発動してしまいます。

デバイスが現在の状況を送るために、値の変化に関係なく、頻繁にreportedを送信してくる場合などは、ちょっと、このトリガーでは、コスト高となる場合もあるかも知れません。

このような場合に利用可能なのが、$aws/things/sensor1/shadow/update/documentsトピックではないでしょうか。

/update/documentsでは、下記のように、payloadに、previousという変更前の状態が格納されます。(下の例では、reportedのcounterが2から3に変更されています)

{
    "payload": {
        "previous": {
            "state": {
                "desired": {
                    "counter": "2"
                },
                "reported": {
                    "counter": 2
                }
            },
            // ... 略 ... 
        },
        "current": {
            "state": {
                "desired": {
                    "counter": "2"
                },
                "reported": {
                    "counter": 3
                }
            },
            // ... 略 ... 
        },
            // ... 略 ... 
    },
}

従って、previouscurrentの違いを判断するようにすれば、値が変化した時だけ、トリガーするルールとなるはずです。

下記のルールは、sensor1reported.counterの値が変化した時だけトリガーされるルールとなります。

SELECT * FROM "$aws/things/sensor1/shadow/update/documents" \
  WHERE previous.state.reported.counter <> current.state.reported.counter

5 最後に

今回、一部しか利用していませんが、ルールの記述には、便利な関数も利用可能です。

AWS IoT SQL リファレンス » 関数

get_thing_shadow()や、aws_lambda()などは、特に強力で、うまく使うことでコストやシステムの複雑性を下げることができるかも知れません。