工作と競馬

電子工作、プログラミング、木工といった工作の記録記事、競馬に関する考察記事を掲載するブログ

タグ:Lambda

概要

boto3を使い、CloudWatchEventsでLambda Functionを呼ぶルールを設定した。最初、うまくルールが発動せずハマったことがあるので、注意点もメモっておく。


背景と目的

boto3から、AWSのCloudWatchEventsにルールを設定し、定期的にLambda Functionを実行しようと思ったので、方法を調べ、メモしておく。


詳細

1.boto3のドキュメントで方法を確認

CloudWatchEventsのルールを設定するには、boto3.client("events").put_ruleboto3.client("events").put_targetsを使う。

また、呼び出し側のLambda関数のpolicyにおいて、CloudWatchEventsから呼び出されることを許可必要がある。ここで、ハマってしまいうまく動かなかった。注意。


2.実装

実装したのは以下。ここでは、ルール名をboto3_test、ターゲットのLambda Functionをfor_testという名前のもの(内容は、ブランク関数。Hello world!を出力するだけ)、入力パラメータはjson文字列とした。

import boto3

# ルールを追加
ret_rule = boto3.client("events").put_rule(
    Name="boto3_test",
    Description=u"説明文",
    ScheduleExpression="cron(* * * * ? *)"
)

print json.dumps(ret_rule, indent=4)

# ターゲットを追加
ret_target = boto3.client("events").put_targets(
    Rule="boto3_test",
    Targets=[
        {
            "Id": "for_test",
            "Arn": "arn:aws:lambda:リージョン:アカウントID:function:for_test",
            "Input": json.dumps({})
        }
    ]
)

ここで、上記ソースコードを実行すると、エラーなく実行され、マネジメントコンソール上にも、有効なルールとして表示される。しかし、これだけではちゃんと動かない。

put_targetsの説明をじっくり読むと、

To be able to make API calls against the resources that you own, Amazon CloudWatch Events needs the appropriate permissions. For AWS Lambda and Amazon SNS resources, CloudWatch Events relies on resource-based policies.

とある。つまり、Lambda Function側のポリシーを設定してねということ。で、リソースポリシーはどう設定すればいいのかというと、こちらに記述があり、AWS Lambda Permissionの項を参考に、呼び出されるLambda Functionのポリシーを設定。


# Lambdaにパーミッションを追加
ret_lambda = boto3.client("lambda").add_permission(
    FunctionName='for_test',
    StatementId='ステートメントID',
    Action='lambda:InvokeFunction',
    Principal='events.amazonaws.com',
    SourceArn='arn:aws:events:リージョン:アカウントID:rule/boto3_test'
)


なお、マネジメントコンソールからルールを設定した場合は、裏で勝手にこのパーミッションの設定をやってくれている。なので、動くということだ。

3.動作確認

上記の設定により、ちゃんと指定したルールに沿って、イベントが発生し、Lambda Functionが実行された。


まとめ

boto3を使い、CloudWatchEventsでLambda Functionを呼ぶルールを設定することができた。呼び出し側のLambda Functionのポリシーを適切に設定する必要があることに注意。

概要

LINE Bot APIの使ってLINEからメッセージを送ることで自宅のエアコンの電源を入れられるようにするシステムを試作した。

キーワード : LINE Bot API, AWS, Lambda, API Gateway

背景と目的

LINE Bot APIというものに興味がわいた。しかし、実際に使ってみないとよくわからないので、試しに使い方を調べ、せっかくなので以前作成したスマホからエアコンの電源を操作するシステムと連携させてみる。

詳細

1.LINE Bot APIについて知る

Web上でいろいろ情報を漁った結果、こちらにあるとおり、>>あなたのサービスとLINEユーザーの双方向コミュニケーションを可能にする機能<<とのこと。最低限使用するためには、

  • LINE Business CenterでMessaging APIを利用するビジネスアカウントを作成
  • 自分のシステムにLINEサーバからのWebhookを受け取るWebエンドポイントを用意
  • Webhookを受け取ったときの動作を自分のシステム上に実装

という感じだ。というわけで、以下順を追ってやってみる。

2.システムの実装

2.1 Messaging APIを使うためのアカウントを作成

アカウント作成手順の詳細いは、こちらなどが詳しいので、それを参照するとして、最終的に以下のように

  • Channel Secret
  • Channel Access Token
  • QR code

が見られるDeveloperのページまで無事たどり着いた。

ここで、スマホのLINEの友達追加でQR codeを読み取りこのアカウントと友達になった。

Developer

図2.1 Developerページ

2.2 AWS側にWebエンドポイントを用意

次に、先ほど作ったアカウントから、Webhookを受け取るためのエンドポイントを用意する。今回は、以前作成したエアコンの電源操作システムに、API Gatewayを使っているので、そこにリソースを追加した。設定は、

  • メソッドはPOST
  • 統合リクエスト
    • 統合タイプはLambda関数(対象は2.3で作成する関数)
    • マッピングテンプレートは特に設定なし
  • 統合レスポンス、メソッドレスポンス、メソッドリクエストはデフォルトのまま

とした。そして、このAPIのデプロイされたURLを、以下のように2.1でたどり着いた画面のWebhook URLに入力する。VERIFYボタンを押して、API GatewayのログがCloud Watch等で残っていれば、ちゃんと呼び出されたことが確認できる。(2.3のLambda関数を実装してからのほうがわかりやすいが)

Developer2

図2.2 Webhook用URLを記入

2.3 Webhook受信時の処理を実装

Lambda関数の実装を行う。Webhookを受信すると、eventという引数に呼び出し元のデータ(LINEアプリから送信したテキストなど)が入ってくる。今回は、送信されたテキストが、

  • "電源入れて"だったら、"入れました"と返答し、エアコンの電源を入れる
  • "電源切って"だったら、"切りました"と返答し、エアコンの電源を切る
  • それ以外は、"ごめんなさい。を返答する

を実装してみた。エアコン操作部分は、以前作成したLambda関数があるので今回はそちらを呼び出すだけとする。

受け取る引数のjsonの構造は、APIのドキュメント等があるので詳細は割愛するが、基本的な流れは、入ってきたメッセージを解析して、リプライ用のAPIを叩くというだけだ。(メッセージの解析とリプライ文章作成をAIのようなもので実装すれば、はやりのAIチャットボットみたいなものができるはず。)

なお、LINE Bot APIには、Python向けのSDKが用意されているが今回はAPIドキュメント通りに実装してもそんなに大変でなかったので使わなかった。(これは状況に応じて使えばよいのだろう)

# coding: utf-8

import urllib2
import json

def lambda_handler(event, context):

    for ev in event["events"]:
        
        # メッセージ解析
        message = ev["message"]["text"]
        if message in u"電源入れて":
            t = u"入れました"
        elif message in u"電源切って":
            t = u"切りました"
        else:
            t = u"ごめんなさい。わかりません。"
    
        # リプライ用APIを叩く
        url = "https://api.line.me/v2/bot/message/reply"
        headers = {
            "Content-type": "application/json",
            "Authorization": "Bearer {Channel Access Token}"
        }
        body = {
            "replyToken": ev["replyToken"],
            "messages": [
                {
                    "type": "text",
                    "text": t
                }
            ]
        }
        req = urllib2.Request(url, data=json.dumps(body), headers=headers)
        resp = urllib2.urlopen(req)
        print resp.read()
        
        # エアコン操作用の関数呼び出し
        # 詳細は省略
        :

3.動作確認

というわけで、早速LINE上からメッセージを送ってみたところ、無事電源の操作ができた。また、LINE上に返信が来た!

IMG_3436

図3 LINE上でメッセージのやり取りがなされた様子

まとめ

LINE Bot APIを使って自分の作ったWebシステムと連携させることができた。今回は、エアコン操作というIoT的なものをやってみたが、これ以外にもいろいろアイディアが思いつきそうなので、思いついたら適宜試してみたい。

概要

 Lambdaファンクションから他のLambdaファンクションを呼び出す方法を調べた。


背景と目的

 最近、あるLambdaファンクションから他のLambdaファンクションを呼び出せると、便利かもしれないと思い、方法を調べることにした。


詳細

1.方法の調査

 ここでは、PythonのSDK boto3を使うことにする。boto3のドキュメントを見ながら、いろいろ試した結果、やり方をまとめるとだいたい以下の感じ。

  • 呼び出される側のLambdaファンクションを作成
  • 呼び出す側のLambdaファンクションのロールを設定
  • 呼び出す側のLambdaファンクションに、呼び出しコードを実装

 以下、流れに沿って実装する。

2.実装

 まず、呼び出される側のLambdaファンクションを実装した。これをlambda1という名前のLambdaファンクションとした。呼び出される側は通常通り実装すればよい。今回は、引数eventをそのままリターンするだけ。

def lambda_handler(event, context):
    # TODO implement
    return event

 次に、呼び出す側のLambdaファンクションのロールを設定する。許可するべき権限は、Lambdaの呼び出しなので、以下がポリシーに含まれればよい。

        {
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction"
            ],
            "Resource": "*"
        }

 最後に、呼び出す側のLambdaファンクションのコードの実装。
boto3のドキュメントを見ると、Lambdaのclientに、invokeというメソッドがあるのでこれを使えばよい。

# coding: utf-8

import boto3
import json

def lambda_handler(event, context):
    
    # 引数
    input_event = {
        "param1": 1,
        "param2": 2,
        "param3": 3
    }
    Payload = json.dumps(input_event) # jsonシリアライズ
    
    # 呼び出し
    response = boto3.client('lambda').invoke(
        FunctionName='lambda1',
        InvocationType='RequestResponse',
        Payload=Payload
    )
    
    # レスポンス読出し
    response_payload = json.loads(response["Payload"].read()) # jsonデコード
    
    return response_payload

 FunctionNameには、呼び出したいLambdaファンクション名。ここでは先ほど実装したlambda1を指定。
呼び出したいLambdaファンクションへの入力引数は、Payloadという引数に渡す。JSONデータは、シリアライズして渡せばよい。
また、InvocationTypeというパラメータは、"RequestResponse"を指定すれば、呼び出された側の実行が完了するまで待機する。これで通常は問題ないだろう。呼び出しだけ行って終了を待たない場合は、"Event"という値を指定すればよいようだ。
レスポンスは、Payloadというキーのアイテムからreadメソッドで読みだせる。なお、ここでは呼び出す先の関数のレスポンスがJSONなので、呼び出し側では、読み出した後にJSONデコードしている。

3.動作確認

 実行してみたところ、正しく実行できた。


まとめ

 Lambdaファンクションから他のLambdaファンクションを呼ぶ方法が確認できた。

概要

 当初の目的であるエアコンの電源を出先のスマホから操作可能となり、システムが完成した。


背景と目的

 前回までで、AWS上のLambdaからRaspberry Piへトピックをパブリッシュしてエアコンの電源を操作することができた。今回は、いよいよスマートフォン等から操作可能にし、システムを完成させる。


詳細

1.方法

 システムを完成させるには、

  • AWSのAPI Gatewayにて、操作情報を受け取りとLambdaの駆動をするWebエンドポイントを用意する
  • スマホ側の操作インターフェースとして、電源ボタン操作インターフェースとWebエンドポイントへの操作情報を送信機能を持つWebアプリを作成する

 が必要。それぞれ順にやっていく。

2.Webエンドポイントの作成

 API Gatewayにて、POSTメソッドを用意し、統合タイプを前回作成したLambda関数呼び出しとする。パラメータは、何でもよいが、Webアプリ側の実装が簡単なJSONでの受け渡しになるようにした。

3.操作用Webアプリ

 図3.1、3.2のように、電源ボタンの図をタップすると、ON/OFFが切り替わり、それに応じて2で作成したエンドポイントへリクエストを送るようになっている。

IMG_3363

 図3.1 OFF状態

IMG_3368

 図3.2 ON状態

4.動作確認

 最後に、動作確認を行った。スマホで、操作用Webアプリを開き、電源ボタンを押したところ、1秒程度で無事エアコンの電源が入った!結構素早い動きで驚いた。


 以上で、エアコンの電源をインターネット経由で操作することができるようになった。作業に要したのはおよそ2日だったが、出来上がったときはさすがにちょっと感動した。作ってよかった。

概要

 boto3で、ローカルからLambdaのコードを更新する方法を調べ、正しく行えることを確認した。


背景と目的

 以前のS3へのアップロードに引き続き、ローカルPC上からboto3を使ってLambdaのコードを更新できるようにする。


詳細

0.実施環境
  • Windows10
  • Python 2.7
  • AWS CLIインストール済み
  • boto3 インストール済み
1.方法を調べる

boto3のドキュメントを見ると、Lambda.Clientに、update_function_codeというメソッドがあるので、これでできそう。

2.対象のlambda function
  • 関数名=boto3_update_test
  • 言語はPython
  • 更新内容となるソースコードはlambda_function.pyというファイルに以下を書き、zipファイルとして圧縮
# coding: utf-8

import datetime

def lambda_handler(event, context):
 # TODO implement
 return datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
3.実装

 実装したのが、以下。

import boto3

client = boto3.client('lambda')

# zipファイルの読み込み
zip_file = open(u"ソースコードのzipファイルのパス", 'rb')
zip_file_contents = zip_file.read()

# アップデートの実行
response = client.update_function_code(
    FunctionName='boto3_update_test',
    ZipFile=zip_file_contents,
    Publish=True
)

print response
4.動作確認

 実行したところ、responseの中身でステータスコード200が返ってきて、以下のようにコンソール画面でもちゃんと更新されていることが確認できた。

更新後
図4.1 更新後

 というわけで、ちゃんと更新できた。


このページのトップヘ