ShadowがRuleから取れるようになった!最近実装されたAWS IoTのすごい機能を使ってみる

記事タイトルとURLをコピーする

こんにちは! サーバーワークス IoT担当の中村です。

去年のre:InventでAWS IoTが発表されて早くも1年が経とうとしています。
みなさん、AWS IoT使っていますか?

AWS IoTはRuleを使って色々なAWSサービスと連携できたり、QueryのFunctionを使って動的にペイロードを加工できたりと、とても高機能です。とくに私はAWS IoTのSQL Functionが好きです。

さて、今日はAWS IoTのSQL Functionのひとつをご紹介しましょう。

今回のもくじ

今回の記事は以下の構成で進みます。

  1. get_thing_shadow()関数の登場とその利用用途
    1. 実現方法
      1. デバイスで判断する
      2. Lambdaで判断する
      3. 新機能を使って判断する
    2. 実際にやってみる(構築編)
    3. 実際にやってみる(検証編)
  2. まとめ

get_thing_shadow()関数の登場とその利用用途

今回ご紹介するAWS IoTの新機能、それはSQL Functionのひとつである get_thing_shadow()関数です。
AWS IoTのFunctionsのドキュメントを読んでいたらいつの間にかしれっと追加されていたのを見つけてとても驚いたのを覚えています。

Functinos:
http://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/iot-sql-functions.html

AWSのフォーラムを見ると8月2日に実装された機能のようです。

Announcement: AWS IoT RulesEngine adds support for inline Thing Shadow lookups:
https://forums.aws.amazon.com/ann.jspa?annID=3949

get_thing_shadow()関数で何ができるかというと、AWS IoTのクエリの中でDevice Shadowを取ってこれるんです。これができるようになると、Queryの条件にShadowの値を使ったり、ペイロードにShadowの値を追加したりすることができます。
この関数は引数を2つ取り、1つ目の引数はShadowを取得するためのIAM Roleのarn、2つ目の引数にはThing名を指定します。

これだけだと有り難みがよくわからないので、もう少し具体的に考えてみましょう。
例えば、以下のようなことを実現したいとします。

1 or 0 の値を出力するデジタルセンサーの値をRaspberry Piから定期的にAWS IoTに送っている時、値が切り替わったタイミングのみイベントを起こしたい。

実は先日構築したトイレIoTの環境がまさにこれで、ドアセンサーの値(1 or 0)をEdisonが定期的にPublishしていて、トイレの状態が変わった時(閉->開)の時にイベント(次の人を呼ぶ)を起こす必要がありました。

これを実現する方法はいくつかあります。まずはこれまでの手法を紹介した上で、最後に今回の目玉、最新機能を取り入れた手法を紹介します。

実現方法

デバイス側で判断する

Raspberry Piで動いているプログラムで前回Publishした値を記録しておき、次にPublishするタイミングにその値と比較する方法です。値が異なればイベントを起こすフラグを立てて、それをRuleで検知してRule Actionを実行します。

Lambdaで判断する

前回Publishした値をShadowやDynamoDB等に置いておき、次にPublishされてきた値を受け取った時に前回の値と比較する方法です。値の比較や保管はLambdaを使用するため、データが届く度にLambdaが起動します。

新機能を使った判断方法

さて、新機能 get_thing_shadow() を使った場合の判断方法がこちらです。Query FunctionでPublishされてきたデータと、Shadowのデータを比較して、一致しなければActionを実行します。また、Actionを実行すると同時にLambdaでShadowを最新の値に置き換えます。AWS IoTのRuleで実行するQueryからShadowの情報を取ってこれるようになったので、Shadowに前回の値が入っていればQueryの中で比較が出来てしまうのです!この構成だと、IoT ActionとLambdaが実行されるのは、前回の値と今回の値が一致しない時のみです。値が一致しない場合、Lambdaでは新しい値をShadowに反映するようにします。

ちなみに、上記2つの方法と比べると、この方法は以下の利点があります。

  • デバイス側に余計なロジックを実装する必要がない
  • Lambdaの起動回数を抑えられる

ちなみにトイレ環境は1つ目の「デバイス側で判断する」手法を取っていました。近いうちに改修したいですね!

実際にやってみる(構築編)

さて、機能と使い所を把握したところで、実際に試してみましょう。

想定されるペイロード

実際に想定されるPayloadとしては以下を想定します。ドアが閉まっているときは0、開いている時が1、という感じです。
Topicは office/toilet とします。

 

{
   "thing": "toilet",
   "door": 0
}

 

IAM Roleを作成する

まずは get_thing_shadow()用のIAM Roleを作成します。
注意点としては、ロールタイプにAWS IoTを選ぶことと、AWS IoT用のテンプレートポリシーを付与しないことです。用意されているポリシーでは権限が足りない(get_thing_shadow()できる権限がテンプレに用意されていない)ため、これらのポリシーを付与しても意味がないからです。

AWS IoT用のIAM Roleを作成したら、このIAM Roleにインラインポリシーを付与します。
Policy GeneratorからサービスはAWS IoTを選択し、アクションはGetThingShadowのみにチェックをいれます。そして、ARNにはアスタリスク(*)を入力し、ステートメントを追加をクリックします。できあがったJSONはこんな感じです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1473130645000",
            "Effect": "Allow",
            "Action": [
                "iot:GetThingShadow"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

 

これにてIAMの設定は完了です。

Lambda Functionを作成する

次に、値が変わった時にShadowをUpdateするLambda Functionを書きましょう。
Pythonで以下のように書きます。

 

import boto3
import json

def lambda_handler(event, context):
    print event
    
    client = boto3.client('iot-data')
    data = {}

    if event['door'] == 1:
        data['door'] = 1
    else:
        data['door'] = 0
        
    shadow = {'state': {'reported': data}}
    response = client.update_thing_shadow(
        thingName = event['thing'],
        payload = json.dumps(shadow, ensure_ascii=False)
    )
    return

 

AWS IoTのRuleからLambdaを起動した場合、 event には Payloadのみが入っています。(本当はTopicとかも含めて欲しい…)
このプログラムで行っていることはとても単純で、Payloadのdoor属性の値によってShadowのreported.doorの値をUpdateしているだけです。
上記のコードコピペで大丈夫なので、コペペしてサクッとLambda Functionをデプロイしてしまいましょう。
なお、Lambda用のIAM Roleでiot:UpdateThingShadowを出来るようにしておくのも忘れてはいけません。

Thingを作成する

次に、AWS IoTでThingを作成しましょう。今回は「toilet」という名前でThingを作成します。また、Thingを作成したら、Shadowのreportedに初期値を入れておきましょう。以下のJSONをShadowの初期値として入れときます。

{
  "state": {
    "reported": {
      "door": 0
    }
  }
}

 

AWS IoTのRule

続いて、AWS IoTのRuleを作成します。Queryはこんな形になります。

 

SELECT * FROM 'office/toilet' 
WHERE door <> get_thing_shadow(thing, "arn:aws:iam::xxxxxxxx:role/iot_rule_get_thing_shadow").state.reported.door

 

SELECT句とFROM句はそれぞれ、Payloadの選択と、Topicの指定です。
WHERE句はpayloadのdoorとThing「Toilet」のShadowのreported.doorが同じでない場合、という条件です。
一つ目の引数にはpayloadのthing属性の値が入ります。二つ目の引数は、先程作成したIAM RoleのARNですね。

そしてActionは先程作成したLambda Functionを指定します。

これで全ての準備は完了しました!早速試してみましょう!

実際にやってみる(検証編)

早速IoTのMQTT Clientからメッセージを送ってみます。doorの値を1や0にして何回か送ってみましょう。

今回は動作確認のため、Rule ActionにSNSを追加してメールを受け取ってみました。

ちゃんと届いていますね!
他にも、Lambdaのログ(ClodWatch Logs)や、Shadowの最終更新時刻等を見ることで、Ruleがちゃんと動いているかを確認することが可能です。

まとめ

今回の記事ではAWS IoTにおいて「値が変わったことをトリガーに何かしたい」ということを例に、AWS IoTに実装された新しい機能を紹介しました。AWS IoTのSQL Functionは今回紹介したShadowを取る機能以外にもtimestampを取得するなど、様々な機能が存在します。最近ドキュメントも見やすくリニューアルされたので、ぜひ色々なFunctionを試してみて下さい!


※おまけ
日経Linuxで「Raspberry PiでIoTを体験しよう」というテーマで連載をしています!Raspberry PiとAWSを使って、IoTを体験できる連載です。興味がある方はぜひ手に取ってみて下さい〜!
http://www.serverworks.co.jp/news/201607_nikkei-linux_iot-serialization