AWSで遊ぶ 〜AWS IoT+Lambda+CloudWatchでM5StickC (ESP32) にメッセージを送る〜

前々回は双方向通信の土管作り、前回はデバイス起点のメッセージ送受信をやりましたので、今回はクラウド起点のメッセージ送信をやりたいと思います。

やりたいことのイメージ

CloudWatch Eventsで定期的にLambdaを起動させて、AWS IoTのトピックにメッセージをPublishさせます。送信内容としては、毎回同じメッセージだと差分がわからないので、現在時刻(JST)を送ってもらうようにします。

「LambdaをCloudWatch Eventsで定期実行させる」というのは、Lambdaのコールドスタートを防ぐための定石としてよく行われる話かと思いますので、おそらく情報は豊富にあるのではないかと思います。

AWS側の作業

前提条件

前々回に作成したAWS IoTのモノ・ポリシー・証明書、それから前回に作成したLambda関連のIAMロールをそのまま引き継いで使用するので、お手数ですが前々回前回の内容を参照or実施頂いた上で、以下をご参照頂きますよう宜しくお願い致します。

IAM設定

前回から差分はありません。作業用ユーザに以下のポリシーが設定されているものとします。

  • AWSLambdaFullAccess
  • CloudWatchFullAccess
  • AWSIoTFullAccess

また、この作業用ユーザはAWSのリソースに対してロールを付与(PassRole)する権限を有しているものとします。このあたりの話は、必要に応じて前回記事をご参照ください。

Lambda関数の作成

作り方としては前回と全く同じになりますので、ここでは設定内容だけ示します。詳しい手順が必要な方は、前回の「Lambda関数の作成」部分をご参照ください。

オプション 一から作成
関数名 send_IoT_message_regularly
ランタイム python 3.7
アクセス権限 既存のロールを使用する: AWSLambdaExecutionWithIoTPub

ロール”AWSLambdaExecutionWithIoTPub”は前回作成したLambda用のロールで、自分のアカウントの”myTopic/fromCloud”というトピックにのみメッセージをPublishする権限を与えます。

続いてLambda関数の内容として、以下を記述します。

from datetime import datetime, timedelta, timezone
import json
import boto3

# Publish先のトピックを指定
PUB_TOPIC = 'myTopic/fromCloud'

print('Loading function')  

# AWS IoT Data Planeオブジェクト取得
iot = boto3.client('iot-data')

# Lambdaメイン関数
def lambda_handler(event, context):

    # 現在時刻(JST)を取得する
    JST = timezone(timedelta(hours=+9), 'JST')
    dt_now = datetime.now(JST).strftime('%Y-%m-%d %H:%M:%S')
    print("JST Now: ", dt_now)

    # 送信メッセージを作成
    payload = {
        "message": dt_now
    }

    try:
        iot.publish(
            topic = PUB_TOPIC,
            qos   = 0, # 届こうが届くまいが1回だけ送信
            payload=json.dumps(payload)
        )
        print("Publish Succeeded!")

        return "Succeeded."

    except Exception as e:
        print(e)
        print("Publish Failed.")
        return "Failed."

前回作成したものから、AWS IoTトピックからのメッセージ受信処理をなくして、代わりに現在時刻(JST)を取得してメッセージとして送信するよう書き換えています。

 

さて、ここからが本題です。Lambda関数のトリガーとしてCloudWatch Eventsを設定します。

トリガーとしてCloudWatch Eventsを選択し、以下のように設定します。

ルール 新規ルールの作成
ルール名 call_lambda_regularly
ルールの説明 (任意)
ルールタイプ スケジュール式
スケジュール式 rate(5 minutes)
トリガーの有効化 チェックなし

「スケジュール式」で5分ごとにLambdaをコールするように設定しています。スケジュール式の書き方ですが、これはAWSの公式ドキュメントをご覧頂くのが一番確実でわかりやすいかと思います。

「トリガーの有効化」はデフォルトではチェックありになっていますが、それだとトリガーを追加した瞬間即有効になってしまうようです。それはさすがにちょっと怖いので、チェックは外しておきます。

すべて入力したら、「トリガーを追加」で完了です。

トリガーが追加されていますが、先の「トリガーの有効化」で設定したとおり、まだ有効になっていません。これを手動で有効化します。

「有効」にしただけだと有効になっていませんので、必ず「保存」を押してください。

以上でAWS側の作業は完了です。お疲れ様でした。

デバイス側の作業

前回もそうでしたが、前々回のソースコードをそのまま使いますので、特に変更することはありません。そのままM5StickCに書き込んでもらえばOKです。

動作確認

さて、AWS側の設定が済んてトリガーを有効にした時点で、既に5分ごとにLambdaがコールされているハズですので、あとはメッセージがちゃんとデバイス(M5StickC)まで届いてるかをチェックします。

M5StickCの電源を入れて、AWS IoTに繋がった直後の状態です。時刻は18時40分。このまま放置して、メッセージが届くのを待ちます。

 

18時43~44分頃、メッセージが届きました。M5StickCがAWS IoTに繋がったのは、前回のLambdaのコールから1〜2分経過したタイミングだった、ということですね。ここに表示されている時刻はあくまでLambdaが処理した時刻でM5StickCに届いた時刻とは微妙に差があるはずですが、前々回に通信を試した感じだとその差はごく僅かだと思います。

 

続いて、18時48〜49分頃にメッセージが更新されました。うん、確かに5分間隔で問題なく処理されてそうです。

 

最後に、動作確認が終わったら、CloudWatch Eventsのトリガーを無効化して保存することを忘れずに。これぐらいの量・頻度でのLambdaコールぐらい大したことはないハズですが、塵も積もれば山となる、で費用請求されることになるかもしれませんので。

 

ということで、クラウド起点でのメッセージ送信も特に問題なく検証できました。さらっとやってしまいましたが、クラウド(サーバ)との通信をHTTPメインでやってた昔ながらの人間としては、サーバからクライアントへメッセージをこうも簡単に送れてしまうのはなかなか感動ものです。

さて、これで自分がAWSを使って最低限確認したかったことは確認できました。あとは、Lambdaを起動させるためのトリガーをCloudWatch以外も使っていくことで、かなりいろんなことができそうになる気がしています。

今後は、AWSの検証を続けるかもしれませんし、あるいは原点回帰(?)でArduinoとかのデバイス側の話になるかもしれません。予定は未定ですが、気が向いたらまた何か書いていきます。

 

以下は自分が勉強用にパラパラと読んでいる本です。広範囲に渡るAWSサービスの概要を知るのに丁度良い感じがしています。