Raspberry PiのGatewayにGreengrassを入れて使っていますが、機器からの値を取得する周期や、パラメータ等、Gateway側のLambdaから利用するパラメータを永続値として持っておきたいと思いました。
その上、その値はAWS IoT側を通して変更したい。
ということでLocal Shadowを利用して実現してみました。
概要図
今回やろうとしていることです。
Local Shadow
とAWS IoTの Shadow
の同期が一番やりたいことです。
Lambdaコード
gg-shadow-init
test/settings/initialize
というTOPICをトリガーとして1度だけ実行されます。
これにより、Local Shadowを初期化します。初期化しないで get_thing_shadow()
を実行すると以下のような Shadow Not Found
エラーとなりShadowを取得できませんでした。
※2020/10/23追記 Greengrass GroupにShadowが設定済みのDeviceを登録して「クラウドへのシャドウ同期」を設定し、デプロイすることで、この処理無しでもローカルShadowにデータが登録されることがわかりました。
exception: Request for shadow state returned error code 404 with message "Shadow Not Found"
初期化LambdaはMQTTでパラメータを渡してキックして一回だけ動かすことにします。 update_thing_shadow()
で初期データを入れることでLocal Shadowができます。
import logging import json import greengrasssdk THING_NAME = "my-ggc" logger = logging.getLogger(__name__) gg = greengrasssdk.client("iot-data") def update(event): data = {"state": {"reported": event}} response = gg.update_thing_shadow( thingName=THING_NAME, payload=bytes(json.dumps(data), "utf-8"), ) logger.info(f"Shadow Updated -> {response}") def lambda_handler(event, context): logger.info("Start update") update(event) return
gg-shadow-publish-prams
Greengrassで良くあるように、Timerを使って再帰的にLoopを書いてます。
get_thing_shadow()
でLocal Shadowから取得したパラメータをMQTTでAWS IoTにPublishします。これをAWS IoT側でSubscribeしてパラメータが変更されたことを確認します。
import datetime import logging import json from threading import Timer import greengrasssdk THING_NAME = "my-ggc" TARGET_TOPIC = "test/settings/get" logger = logging.getLogger(__name__) gg = greengrasssdk.client("iot-data") def publish_params(): response = gg.get_thing_shadow( thingName=THING_NAME, ) logger.info(response) state = json.loads(response["payload"])["state"]["reported"] state['time'] = str(datetime.datetime.utcnow()) gg.publish( topic=TARGET_TOPIC, queueFullPolicy="AllOrException", payload=bytes(json.dumps(state), "utf-8"), ) logger.info(f"Published -> {state}") # loop Timer(10, publish_params).start() logger.info("Start publish") publish_params() def lambda_handler(event, context): return
Greengrass Group設定
では、Greengrass Groupの設定に移ります。
Lambda
まずは上記の二つのLambdaをグループに設定します。
この際に gg-shadow-publish-prams
は永続的にloopするので以下のように設定します。
サブスクリプション
以下の通り設定します。
src | target | topic | usage |
---|---|---|---|
AWS IoT | gg-shadow-init | test/settings/initialize | Local Shadowの初期化に一回だけ利用 |
gg-shadow-publish-prams | AWS IoT | test/settings/get | AWS IoT側にShadowの内容をPublishして確認するため利用 |
以下のようになります。
Device
Local Shadowと同期するためのデバイスをグループに含めます。
1 Click Deployで my-ggc
というデバイスを作成しておきます。証明書等は特にダウンロードしなくても大丈夫です。
デプロイ
Greengrass Groupをデプロイします。
動きを見てみる
ログ確認
まず、この時点でRaspberry Piに入ってログを確認してみます。
sudo tail -F /greengrass/ggc/var/log/user/ap-northeast-1/123456789012/gg-shadow-publish-params.log
上にも書きましたが、Local Shadowが無いので以下のようなエラーが出ています。
Request for shadow state returned error code 404 with message "Shadow Not Found"
Local Shadow確認(初期化前)
参考URLにもある通りLocal ShadowはsqliteのDBにて管理されているので確認してみます。
sudo sqlite3 /greengrass/ggc/var/state/shadow/shadow.db SQLite version 3.27.2 2019-02-25 16:06:06 Enter ".help" for usage hints. sqlite> select count(*) from doc; 0
0件ですね。上記の Shadow Not Found
エラーも納得です。
Local Shadow初期化
AWS IoTのMQTTクライアントから test/settings/get
をSubscribeしてみます。この時点では何も表示されないはずです。
その上で、test/settings/initialize
に以下のデータをPublishしてみます。
{ "param_str": "abc", "param_int": 123, "param_float": 1.23 }
初期化が成功すると以下のようにデータがtest/settings/get
でSubscribeできるようになります。このデータは gg-shadow-publish-prams
のLambdaからPublishされたものです。
Local Shadow確認(初期化後)
今度はShadowのDBにデータが入っています。
sudo sqlite3 /greengrass/ggc/var/state/shadow/shadow.db sqlite> select count(*) from doc; 1 sqlite> select * from doc; my-ggc|my-ggc|{"ggFlv":{"reported":{"param_float":{"version":1},"param_int":{"version":1},"param_str":{"version":1}}},"isDeleted":false,"metadata":{"reported":{"param_float":{"timestamp":1601043622},"param_int":{"timestamp":1601043622},"param_str":{"timestamp":1601043622}}},"state":{"reported":{"param_float":1.23,"param_int":123,"param_str":"abc"}},"version":1} sqlite>
Shadowを更新して設定を変更
Device my-ggc
の設定を クラウドへのシャドウ同期
と変更します。ここでグループをデプロイします。
Shadowを確認すると、Local Shadowに設定した値が同期されていることがわかります。
Shadowの値を変更してみます。
MQTTクライアントに戻ってみるとShadowの変更前後で設定が変わっていることがわかります。
当然、sqliteで確認してみるとShadowDBの中身も変わっています。
sudo sqlite3 /greengrass/ggc/var/state/shadow/shadow.db sqlite> select * from doc; my-ggc|my-ggc|{"ggFlv":{"reported":{"param_float":{"version":2},"param_int":{"version":2},"param_str":{"version":2}}},"isDeleted":false,"metadata":{"reported":{"param_float":{"timestamp":1601044265},"param_int":{"timestamp":1601044265},"param_str":{"timestamp":1601044265}}},"state":{"reported":{"param_float":9.87,"param_int":987,"param_str":"zyx"}},"version":2}
Shadowの操作はここに記載されています。例えばプロパティをnullにすれば、そのプロパティを消せるなどが書いてあります。
Shadowの正しい使い方
今回はわかりやすさを優先して、無視していましたが、Shadowには desired
や reported
などの項目が分かれており正しく使うにはShadowの考え方の理解が必要になります。
実は一番わかりやすい資料はAzureのDevice Twinという同様の機能の説明になりますので、こちらで記載しておきます。