GreengrassのLambdaを作成してエッジデバイスにデプロイする

# はじめに
会社の業務でAWS IoT Greengrassについて勉強したので、Ubuntu搭載デバイスとAWS IoTを利用したアプリケーションを紹介したいと思います。
今回はGreengrassのLambdaを作成してデバイスへデプロイする方法を紹介します。

過去の記事
[UbuntuにAWS IoT Greengrassをインストールする](https://qiita.com/Yokogawa_Ishino/items/1fb734f091e952a16fea)

[Azure IoT Edgeを使った記事](https://qiita.com/Yokogawa_Mita/items/f7996d539b29618d7159)もあるので興味のある方は是非ご覧ください。

# 環境
動作確認済デバイス（OS）

- e-RT3 Plus F3RP70-2L[^ert3]（Ubuntu 18.04 32bit）
    横河電機のエッジコントローラです。AWS IoT Greengrassの認定デバイス[^certificate]に登録されています(e-RT3のページは[こちら](https://devices.amazonaws.com/detail/a3G0h000007dE0NEAU/e-RT3-Plus-(F3RP70-2L)))。

- Raspberry Pi 4 Model B （Ubuntu Server 20.04 32bit）

これらのデバイスでは armhf アーキテクチャのパッケージが動作します。
また、Windows 10 搭載のPCでデバイスを操作しています。

# Lambdaとは
Lambdaとは、イベントの発生をトリガーとしてコードを実行するサービスです[^lambda]。
AWS IoT GreengrassではGreengrass CoreをインストールしたエッジデバイスにLambdaをデプロイして実行することができます[^gglambda]。
これにより、クラウドからのメッセージやローカルイベントをトリガーとして、エッジデバイスで処理を行うことができます。

Greengrass Coreで実行するLambdaには以下の2つの種類があります[^lambdatype]。

- __On-demand Lambda__
呼び出されたときにコンテナを起動する方式です。同時に複数呼び出されると自動的にコンテナを起動してスケールするため、同時に処理を行うことができます。

- __Long-lived Lambda__
Greengrass Coreの起動時に一つだけコンテナを起動する方式です。同時に複数呼び出されるとタスクがキューに入れられ一つずつ順番に処理されます。

今回は2つの種類のLambdaをデプロイして動作の違いについて確認してみたいと思います。


# 作業の流れ
AWSの公式ドキュメント[^lambda3-1][^lambda3-2]を参考に、以下の流れで作業を進めます。

1. __Lambda関数コードの作成__
PC上でLambda関数コードを作成して、SDKとともにパッケージ化します。

2. __Lambdaの作成とコードのアップロード__
Lambdaを作成してLambda関数コードをクラウドにアップロードし、設定を行います。

3. __Lambdaのデプロイ__
クラウドからエッジデバイスにLambdaをデプロイします。

4. __動作確認__
クラウドとエッジデバイスの間でMQTTメッセージをやり取りして、Lambdaの動作確認を行います。
![gg-architecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/f8580d64-98b2-6c4b-8fa9-6ed4bf65f863.png)

# 1. Lambda関数コードの作成
PCでPythonのLambda関数コードを作成してSDKとともにパッケージ化します。

1. PCでLambda関数コードを作成します。
今回はAWS IoT Greengrass Core SDK for Python[^sdk]のexamplesを参考にして作成した以下のPythonコードを使用します(SDKのライセンスは[こちら](https://github.com/aws/aws-greengrass-core-sdk-python/blob/master/LICENSE))。「HelloWorld.py」という名前で保存してください。
このLambdaは呼び出されるとfunction_handler関数を実行して、MQTTメッセージ(Hello world from e-RT3 / Raspberry Pi!＋現在時刻)を「hello/world/response」トピックに送信し、5秒間スリープします。

    ```python:HelloWorld.py
    import time
    import datetime
    
    import greengrasssdk
    
    # Creating a greengrass core sdk client
    client = greengrasssdk.client("iot-data")
    
    def function_handler(event, context):
        client.publish(
            topic = "hello/world/response", queueFullPolicy="AllOrException", payload="Hello world from e-RT3 / Raspberry Pi! " + datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        )
        time.sleep(5)
        return
     ```
2. SDKをダウンロードします。
[ダウンロードページ](https://docs.aws.amazon.com/ja_jp/greengrass/latest/developerguide/what-is-gg.html#gg-core-sdk-download)のAWS IoT Greengrass Core SDK for Pythonをクリックします。
![gg1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/cb800b18-3f4d-9aa0-1161-974da7261dc7.png)
GitHubのページに飛びます。「Code」ボタンを押してメニューを開き、「Download ZIP」を押してファイルをダウンロードします。
![gg2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/961708bc-9c3a-d454-69d8-b0876d535a29.png)

3. Lambda関数コードをパッケージ化します。
ZIPを解凍して`aws-greengrass-core-sdk-python-master\greengrasssdk`フォルダと手順1で保存した`HelloWorld.py`を同一のフォルダにコピーします。
![gg3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/6184761a-821b-1340-9be4-8835a1b21275.png)
`greengrasssdk`フォルダと`HelloWorld.py`を一つのZIPファイルに圧縮します。ここでは「HelloWorld.zip」という名前で圧縮します。
![gg4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/9bfe75a0-b435-1375-d5e1-c202ad1009f3.png)

# 2. Lambdaの作成とコードのアップロード
クラウド上でLambdaを作成してコードをアップロードし、設定を行います。

1. AWSにログインして左上のサービスメニューをクリックします。検索窓に「lambda」と入力して「Lambda」をクリックします。
![gg5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/b6a74190-d4ba-2b6d-07de-21bcc72eb5c6.png)

2. 左のナビゲーションペインから「関数」へ進み、「関数の作成」をクリックします。
![gg6-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/72ea78c7-5a9a-1090-a890-b09ec5a3af56.png)

3. 「一から作成」を選択して関数に任意の名前をつけ、ランタイムに「Python3.8」を指定して、「関数の作成」をクリックします。
![gg7-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/dca50428-5376-2f5e-4578-efc1c7f10028.png)

4. Lambda関数コードをアップロードします。
関数コードの「アクション」メニューを開き、「.zipファイルをアップロード」をクリックします。
![gg8-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/25d05daf-bf28-7476-8255-6c88b5050950.png)
アップロードボタンを押して[Lambda関数コードの作成](#1-lambda関数コードの作成)の章で作成した「HelloWorld.zip」をアップロードし、「保存」をクリックします。
![gg9-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/f3a5af25-53ac-29ea-db31-f3ee59a63fc5.png)

5. ランタイムの設定を行います。
ランタイム設定の「編集」をクリックします。
![gg10-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/224375cc-673a-eed9-717c-e7e3688ea7a6.png)
ランタイムに「Python 3.8」、ハンドラに「HelloWorld.function_handler」と入力して「保存」をクリックします。
![gg11-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/bcd97678-077a-54df-c507-053535f55d7e.png)

6. Lambda関数を発行します。
「アクション」メニューの「新しいバージョンを発行」をクリックします。
![gg12-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/985bfca1-94cc-a3d8-53e7-0881e86e21a6.png)
バージョンの説明を入力し、「発行」をクリックします。
![gg13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/c31ef4fe-24d1-3b71-b879-2ded73d3d7c8.png)

7. Lambda関数バージョンのエイリアスを作成します。
「アクション」の「エイリアスの作成」をクリックします。
![gg14-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/8a9fbadc-c95f-458b-6c2d-71da4a27d585.png)
任意のエイリアス名を入力して「保存」をクリックします。
![gg15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/601de776-5024-3f21-2294-5d2432763d87.png)

# 3. Lambdaのデプロイ
作成したLambdaをエッジデバイスにデプロイします。

1. 「AWS IoT Greengrass」→「グループ」と進み、Lambdaをデプロイするグループをクリックします。
![gg16-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/29e42da8-f87d-fb76-6351-32c7b0f3fc4b.png)

2. 左側のメニューから「Lambda」を選択し、「Lambdaの追加」をクリックします。
![gg17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/0f142706-a262-0b64-ef18-b3c2bd618959.png)

3. 追加するLambdaを選択します。
「既存のLambdaの使用」をクリックします。
![gg18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/f786f9eb-f346-3610-b178-ae24fe2e1dc3.png)
作成したLambdaを選択し、「次へ」をクリックします。
![gg19-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/dffb8089-5ed7-7d82-e978-9db8a988c6fa.png)
作成したエイリアスを選択し、「完了」をクリックします。
![gg20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/52df57fa-2668-2743-b695-cbe47fdb7640.png)

4. MQTTサブスクリプションの設定を行います。
    1. クラウドからLambdaへ「hello/world/trigger」トピックのメッセージが送信されるようにします。    
    グループのページの左側のメニューから「サブスクリプション」を選択し、「サブスクリプションの追加」をクリックします。
    ![gg23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/e72f6abc-8e24-020f-8400-2bc9a75af555.png)
    ソースに「IoT Cloud」、ターゲットに「HelloWorld」を選択し、「次へ」をクリックします。
    ![gg24-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/44ebf95a-5f26-20f7-d569-b27263fc078b.png)
    トピックのフィルターに「hello/world/trigger」と入力し、「次へ」をクリックします。
    ![gg25-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/deba9c0d-39a3-a311-7304-a840d9a6f579.png)
    「完了」をクリックします。
    ![gg26-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/a90c7edc-87a5-d9e0-bffb-2ced6290cfa5.png)

    2. Lambdaからクラウドへ「hello/world/response」トピックのメッセージが送信されるようにします。  
    同様に「サブスクリプションの追加」をクリックし、ソースに「HelloWorld」、ターゲットに「IoT Cloud」を選択して「次へ」をクリックします。
    ![gg41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/3611f46d-001b-0f74-f470-6f157e1d99a7.png)
    トピックのフィルターに「hello/world/response」と入力し、「次へ」をクリックします。
    ![gg42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/16384968-f369-1c29-b2e7-bfdfe0d7dda3.png)
    「完了」をクリックします。
    ![gg43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/9f1848aa-0847-bdbc-3085-6ad3863ae67f.png)

5. Lambdaの設定を行います。
「...」をクリックし、「設定の編集」をクリックします。
![gg21-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/0cf68844-0891-cd83-6663-2f2037286381.png)
タイムアウトを「25」に設定し、Lambdaのライフサイクルには「オンデマンド関数」(On-demand Lambda)か「存続期間が長く無制限に稼働する関数にする」(Long-lived Lambda)のどちらかを選択して「更新」をクリックします。
ライフサイクルの種類によるLambdaの動作の違いについては、この後の[動作確認](#4-動作確認)の章で説明します。
![gg22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/9a38145a-2244-e323-1901-040c0ad77a7f.png)

6. Lambdaをエッジデバイスにデプロイします。
まず、エッジデバイスのGreengrassデーモンを起動しておきます。

```bash
sudo systemctl start greengrass
```
「アクション」メニューから「デプロイ」をクリックします。
![gg27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/b645fef4-5616-ccd3-2d92-206268dd0b25.png)
以下の画面が表示された場合は「自動検出」をクリックします。
![gg28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/e118e40a-32c0-084b-5556-20c7c78d4512.png)
デプロイが開始されます。左側のデプロイメニューから進捗状況を確認できます。デプロイが正常に終わると下図のようになります。
![gg29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/4fd5c1cb-61f0-2978-c2e0-ee3da4f6a1c3.png)

# 4. 動作確認
クラウドからメッセージを送信してLambdaを呼び出し、応答をコンソール上で確認します。

1. Lambdaからのメッセージを見るための設定を行います。
ナビゲーションペインの「テスト」をクリックします。
エッジデバイスのLambdaから送信される「hello/world/response」トピックのメッセージを見るために、トピックのサブスクリプションに「hello/world/response」、サービスの品質に「0」、MQTTペイロード表示に「ペイロードを文字列として表示」を指定し、「トピックへのサブスクライブ」をクリックします。
![gg30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/938a4a08-9ce4-fedb-dda8-1377bdf3856d.png)

2. エッジデバイスのLambdaにメッセージを送信します。
エッジデバイスのLambdaを呼び出すために、トピックに「hello/world/trigger」と入力し「トピックに発行」を連続で数回クリックします。
![gg38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/d61559c6-6d0e-dd37-305e-22f840052ef5.png)

3. エッジデバイスのLambdaから送信されたメッセージを確認します。
    1. On-demand Lambdaの場合  
    On-demand Lambdaはメッセージを受信するとコンテナを自動的に起動して処理を実行するため、複数のメッセージを受信するとその分だけコンテナが作られて同時に処理が行われます。
    そのため、ボタンを押すとすぐにLambdaからメッセージが届きます。
![gg39-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/f95b4e69-e759-0419-02f4-98df94ab0a44.png)

    2. Long-lived Lambdaの場合  
    Long-lived Lambdaは起動時に生成した一つのコンテナで処理を行うため、複数のメッセージを受信するとタスクはキューに入れられて一つずつ処理されます。
    今回のLambdaはメッセージを送信した後5秒間スリープするため、ボタンを連続して押すと5秒おきにLambdaからメッセージが届きます。
    ![gg40-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/51ec41fb-333a-c6b5-2e55-acac15f70db9.png)

※Lambdaの設定を変更した場合は再度デプロイを行ってください。
    
# まとめ
Lambda関数コードの作成からLambdaの作成、デバイスへのデプロイ、動作確認までを行いました。
また、Lambdaの種類による動作の違いについても確認しました。

# 参考
[^ert3]: [リアルタイムOSコントローラ　e-RT3 Plus F3RP70-2L](https://www.yokogawa.co.jp/solutions/products-platforms/control-system/real-time-os-controller/ert3-ai-platform/)
[^certificate]: [AWS Partner Device Catalog](https://devices.amazonaws.com/)
[^sdk]: [AWS IoT Greengrass Core SDK for Python](https://github.com/aws/aws-greengrass-core-sdk-python/)
[^lambda]: [AWS Lambda とは](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/welcome.html)
[^gglambda]: [AWS IoT Greengrass Core での Lambda 関数の実行](https://docs.aws.amazon.com/ja_jp/greengrass/latest/developerguide/lambda-functions.html)
[^lambdatype]: [Greengrass Lambda 関数のライフサイクル設定](https://docs.aws.amazon.com/ja_jp/greengrass/latest/developerguide/lambda-functions.html#lambda-lifecycle)
[^lambda3-1]: [モジュール 3 (パート 1): AWS IoT Greengrass での Lambda 関数](https://docs.aws.amazon.com/ja_jp/greengrass/latest/developerguide/module3-I.html)
[^lambda3-2]: [モジュール 3 (パート 2): AWS IoT Greengrass での Lambda 関数](https://docs.aws.amazon.com/ja_jp/greengrass/latest/developerguide/module3-II.html)