#はじめに
AWS IoT EventsはIoTデバイス等のイベントを検出、対応できるマネージド型のIoTサービスです。
GUIを使って容易にイベント検出・アクションの定義を作成できるのが魅力だと思います。
参考:【AWS Black Belt Online Seminar】AWS IoT Events
今回はIoT Eventsを使ってデバイスの死活監視を作成します。
#1 今回の構成
今回は以下の機能をIoT Eventsで実装します。
- デバイス単位で監視する
- 一定時間デバイスからペイロードが送られて来ない場合にアラートを通知する
- 上記「一定時間」はユーザーが自由に設定できるようにする(分単位)
- 死活監視の対象から外せるようにする
#2 アラート通知箇所の作成
死活監視で異常があった際にメールを通知する箇所を先に作成します。
IoT Eventsで探知機モデルを作成する際にアクションとして指定するので、先に用意します。
##2-1 Amazon SESの準備
今回はAmazon SESを利用してメール通知を行います。
簡単な検証のみなので、送信元・送信先共に自身のメールアドレスを利用します。
1.Amazon SESのマネジメントコンソールにアクセスし、「Create Identity」をクリックする
2.以下の通り選択、入力して、画面下部の「Create Identity」をクリックする
- Identity Type:Email Address
- Email address:※今回利用するメールアドレス
3.AWSから「Amazon Web Services – Email Address Verification Request in region {リージョン名}」という件名のVerify用のメールが届くので、リンクをクリックする
※リンク先はAmazon SESの紹介ページになっている
4.Amazon SESのマネジメントコンソール上で先程追加したメールアドレスが「Verified」となっていることを確認する
なお、SESは利用開始時点ではSandBoxという扱いになります。
AWSにSandBoxから外へ移動するようリクエストするまでは、送信先として指定できるのはVerifyしたメールアドレスになるなど制限がかかります。
参考:Amazon SES サンドボックス外への移動
##2-2 Lambdaの作成
探知機モデルでアラートを検知した際にメール通知を行うLambdaを作成します。
1.以下の通りLambda関数を作成する
- 一から作成
- 関数名:※任意の関数名
- ランタイム:Python 3.8
- アーキテクチャ:x86_64
2.File > New File の順にクリックし、メッセージ用のテキスト「message.txt」を追加する
{0}が最後にデータを通知してから{1}分が経過しました。
デバイスが接続されていない可能性があります。
最終通信時刻:{2}
3.今回は以下の通りコードを記述する
import boto3
import datetime
#client設定
ses = boto3.client("ses")
#変数設定(今回は送信元/送信先共に同じ)
src_mail = "xxx@test.jp"
dst_mail = "xxx@test.jp"
def lambda_handler(payload, context):
#探知機モデルから受け取ったペイロードより変数設定
device_id = payload["deviceId"]
timestamp = payload["timestamp"]
alert_minutes = payload["alertMinutes"]
#timestampを現在の時刻(JST)に変換し、通知用のJST時刻(YYYY-MM-DD HH:MM:SS形式)にする
format_timestamp = datetime.datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%SZ")
jst_timestamp = datetime.datetime.strftime(format_timestamp + datetime.timedelta(hours=9), "%Y-%m-%d %H:%M:%S")
#メールメッセージ本文を作成する
subject = "{0}が接続されていません".format(device_id)
with open("message.txt", "r") as f:
text = f.read()
mail_msg = text.format(device_id, alert_minutes, jst_timestamp)
#メールを送信する
ses.send_email(
Source = src_mail,
Destination={
'ToAddresses': [dst_mail]
},
Message={
'Subject': {
'Data': subject,
},
'Body': {
'Text': {
'Data': mail_msg,
},
}
}
)
4.Lambdaの設定 > アクセス権限 > ロール名 の順にクリック
5.「ポリシーのアタッチ」をクリック
6.(今回は簡易に実装するのみなので)AmazonSESFullAccessをチェックして「ポリシーのアタッチ」をクリック
#3 探知機モデルの作成
実際に死活監視を行う探知機モデルを作成します。
##3-1 入力の作成
IoT Eventsでは「入力」を作成し、そこで受け取るペイロードの形式を定義します。
IoT RuleのアクションやLambdaからペイロードを受け取る際は、ここで作成する「入力」を指定します。
1.ローカルで以下のJSONファイルを作成する
{
"deviceId": "",
"timestamp": "",
"alertMinutes": "",
"excluded": ""
}
2.IoT Eventsのマネジメントコンソールで左メニューより「入力」をクリック
3.「入力の作成」をクリック
4.「ファイルをアップロード」をクリックして、手順1で作成したJSONファイルを選択する
5.以下の通り入力、選択して「作成」をクリック
- 入力名:DetectionInput
- 入力属性の選択:deviceId, timestamp を選択
6.同様にして、以下の「入力」を作成する
- 入力名:AlertMinutesInput
- 入力属性の選択:deviceId, alertMinutes, excluded を選択
##3-2 探知機モデルの作成①:「状態」の用意
いよいよ、実際に検知・アクションを行う探知機モデルを作成します。
ここでは、「状態」の用意まで行います。
1.IoT Eventsのマネジメントコンソールで「探知機モデルの作成」をクリック
3.探知機モデルパレットの「状態」をドラッグアンドドロップして2つ追加する
4.それぞれの状態の名前を以下の通り変更する
- untitled_state_1 -> Normal
- State_1 -> Excluded
- untitled_state_2 -> Alert
5.以下の通り遷移するようにし、遷移名は「To + {遷移先の状態名}」とする
##3-3 探知機モデルの作成②:Excludedのロジック作成
3-2で作成した「状態」のうち、Excludedにロジックを追加していきます。
ここでは、excludedをfalseに設定されたらNormalに移行するようにします。
具体的には以下のロジックを追加します。
- AlertMinutesInputに入力があった際に、変数alert_minutesをセットする
- ペイロードのexcludedがfalseならば、状態Normalに遷移する
1.OnInputの「イベントの追加」をクリックし、以下の通りイベントを作成する
- イベント名:set_variables
- イベントの条件:
currentInput("AlertMinutesInput")
- イベントアクション:変数の設定
- 変数オペレーション:値の割り当て
- 変数名:alert_minutes
- 値の割り当て:
$input.AlertMinutesInput.alertMinutes
2.移行ToNormalのイベントのトリガーロジックを以下の通り入力する
$input.AlertMinutesInput.excluded == false
##3-4 探知機モデルの作成②:Normalのロジック作成
3-2で作成した「状態」のうち、Normalにロジックを追加していきます。
具体的には以下のロジックを追加します。
- AlertMinutesInputに入力があった際に、変数alert_minutesをセットする
- ペイロードのexcludedがtrueならば、状態Excludedに遷移する
- DetectionInputに入力があった際に、タイマーAlertTimerを変数alert_minutesに設定された時間にリセットする
- タイマーAlertTimerがタイムアウトしたらAlertに遷移する
1.OnInputの「イベントの追加」をクリックし、以下の通りイベントを作成する
- イベント名:set_variables
- イベントの条件:
currentInput("AlertMinutesInput")
- イベントアクション:変数の設定
- 変数オペレーション:値の割り当て
- 変数名:alert_minutes
- 値の割り当て:
$input.AlertMinutesInput.alertMinutes
2.続けてOnInputの「イベントの追加」をクリックし、以下の通りイベントを作成する
- イベント名:update_timer
- イベントの条件:
currentInput("DetectionInput")
- イベントアクション:タイマーの設定
- オペレーションの選択:作成
- タイマー名:AlertTimer
- 式を入力:
$variable.alert_minutes * 60
3.移行ToExcludedのイベントのトリガーロジックを以下の通り入力する
$input.AlertMinutesInput.excluded == true
4.移行ToAlertのイベントのトリガーロジックを以下の通り入力する
timeout("AlertTimer")
##3-5 探知機モデルの作成③:Alertのロジック作成
3-2で作成した「状態」のうち、Alertにロジックを追加していきます。
具体的には以下のロジックを追加します。
- Alertに遷移したタイミングで2-2で作成したLambdaを実行する
- AlertMinutesInputに入力があった際に、変数alert_minutesをセットする
- ペイロードのexcludedがtrueならば、状態Excludedに遷移する
- DetectionInputに入力があった際に、状態Normalに遷移する
- タイマーAlertTimerを変数alert_minutesに設定された時間でセットする
1.OnEnterの「イベントの追加」をクリックし、以下の通りイベントを作成する
- イベント名:send_alert
- イベントの条件:
true
- イベントアクション:Lambda
- 関数のARN:※2-2で作成したLambdaのARN
- カスタムペイロード:JSON ※ペイロードの中身は次の通り
'{
"deviceId": "${$input.DetectionInput.deviceId}",
"timestamp": "${$input.DetectionInput.timestamp}",
"alertMinutes": "${$variable.alert_minutes}"
}'
2.OnInputの「イベントの追加」をクリックし、以下の通りイベントを作成する
- イベント名:set_variables
- イベントの条件:
currentInput("AlertMinutesInput")
- イベントアクション:変数の設定
- 変数オペレーション:値の割り当て
- 変数名:alert_minutes
- 値の割り当て:
$input.AlertMinutesInput.alertMinutes
3.移行ToExcludedのイベントのトリガーロジックを以下の通り入力する
$input.AlertMinutesInput.excluded == true
4.移行ToNormalのイベントのトリガーロジックを以下の通り入力する
currentInput("DetectionInput")
5.移行ToNormalに以下のアクションを追加する
- イベントアクション:タイマーの設定
- オペレーションの選択:作成
- タイマー名:AlertTimer
- 式を入力:
$variable.alert_minutes * 60
##3-6 探知機モデルの発行
ロジックをすべて追加したので、探知機モデルを発行します。
2.以下の通り入力、選択して「保存」をクリック
- ディテクターモデル名:※任意の名称
- ロール:※任意の新しいロール名
- 探知機生成メソッド:一意のキー値ごとに探知機を作成する
- 探知機作成キー:deviceId
- ディテクターの評価方法:シリアル評価
3.画面右上の「分析を実行」をクリックし、エラー等がないかチェックする
4.画面右上の「発行」をクリックし、内容を確認のうえ「保存して発行する」をクリックする
#4 動作検証
以上で探知機モデルを作成したので、ペイロードを送って動作を確認します。
##4-1 IoT Ruleの準備
IoT Eventsからもテストペイロードを送ることはできますが、入力等やや煩雑なので今回はIoT CoreのMQTTテストクライアントからルールアクションを利用して探知機モデル(正確には「入力」)にペイロードを渡します。
まず、探知機モデルにペイロードを渡すIoT Ruleを作成します。
1.IoT Coreのマネジメントコンソールで 左メニューのACT > ルール の順にクリック
2.「作成」をクリック
3.先に「アクションの追加」をクリック
4.「IoT Events入力にメッセージを送信する」を選択して、「アクションの選択」をクリック
5.ロールの作成をクリックし、今回のルール用にIAMロールを任意の名前で作成する
6.以下の通り入力、選択して「アクションの追加」をクリック
- 入力名:DetectionInput
- ロール:※手順5で作成したIAMロール
7.ルールの作成画面に戻るので、以下の通り入力して「ルールの作成」をクリック
- 名前:※任意の名前
- ルールクエリステートメント:※以下の通り
select * FROM 'test/DetectionInput'
8.入力AlertMinutesInputにペイロードを渡すルールも同様に作成する
- 名前:※任意の名前
- ルールクエリステートメント:※以下の通り
select * FROM 'test/AlertMinutesInput'
##4-2 動作検証
IoT CoreのMQTTテストクライアントからペイロードを送信します。
今回送るのは2種類のペイロードです。
デバイスから通常送信される想定のペイロード
トピックtest/DetectionInput
に送ります。
探知機モデルは通常ペイロードが最後に届いてから変数alert_minutesに設定した時間(分)の間に次の通常ペイロードがくるかどうかで、死活の判定をします。
{
"deviceId": "device-0001",
"timestamp": "2021-12-13T00:00:00Z",
"temperature": 15.5,
"humidity": 42.5
}
設定変更のペイロード
トピックtest/AlertMinutesInput
に送ります。
alertMinutesで変数alert_minutesに設定する時間を変更します。
また、excludedをTrueにすることで、死活監視の対象から外します。
{
"deviceId": "device-0001",
"alertMinutes": 30,
"excluded": false
}
1.IoT Coreのマネジメントコンソール上で左メニューの テスト > MQTTテストクライアント の順にクリック
2.送信したペイロードを確認できるように、今回の送信先トピックをそれぞれサブスクライブする
3.以下の通常ペイロードを送ったあと、IoT Events > 探知機モデル > 該当の探知機モデル の順にクリックし、deviceIdでディテクターが作成されていることを確認する
{
"deviceId": "device-0001",
"timestamp": "2021-12-13T04:49:00Z",
"temperature": 15.5,
"humidity": 42.5
}
4.以下の通り設定値変更ペイロードを"excluded":false
で送り、ディテクターの状態が「Normal」に遷移することを確認する
{
"deviceId": "device-0001",
"alertMinutes": 5,
"excluded": false
}
5.以下の通り通常ペイロードを送り、ディテクターに先程設定したalert_minutesの時間(分)でタイマーが作成されることを確認する
{
"deviceId": "device-0001",
"timestamp": "2021-12-13T07:38:00Z",
"temperature": 15.5,
"humidity": 42.5
}
6.タイマーがtimeoutするのを待ち、以下を確認する
- ディテクターの状態がAlertになる
- アラート通知のメールが届く
※timeoutするまでにalert_minutesを変更したため、このキャプチャでは5から10に変わっている
メールは以下の通り通知された
件名:device-0001が接続されていません
本文:
device-0001が最後にデータを通知してから10分が経過しました。
デバイスが接続されていない可能性があります。
最終通信時刻:2021-12-13 16:38:00
7.通常ペイロードを再度送ることで状態がNormalに戻ることを確認する
※今回は既にalert_minutes設定後なので、Normalに遷移した段階でまたタイマーが設定され、時間内にペイロードが届かないと再びAlertに遷移し、アラートが通知される
#5 備忘録:「入力」について
①あくまでも探知機モデルで扱うペイロード上のデータに関する定義なので、以下の場合も問題なく動く
- 「入力」で指定していないデータがペイロード上に含まれる
- 「入力」で指定したデータが一部ペイロード上に含まれない
②「入力」の中身をあとから変更することができる
1.IoT Eventsのマネジメントコンソール > 入力 > 該当の入力名 の順にクリックする
2.「編集」ボタンをクリック
3.以降は「入力」作成時同様の手順で更新ができる
③探知機モデルの「入力」を付け替えるGUIが用意されていない
ここまでの手順でも分かる通り、「入力」は単一の探知機モデルに紐付けられたものではありません。
IoT Eventsに登録された「入力」が受け取ったペイロードを探知機モデルが参照しに行っている、という関係のようです。
#おわりに
IoT Eventsを使って死活監視を作成してきました。
GUIからサクサクとこうした機能を作れるのはとても便利な半面、ロジックを増やすほどにIoT Eventsではどう実装すればいいか、という難しさも出てくると思います。
簡単に作れる分、細かい制御を組み込むのはやや難しいのかな、という印象はあります。
今回はAlert時のみ通知を出しましたが、AlertからNormalに戻るタイミングでも通知を出したり、APIを使って現在のディテクターの状態を取得して画面表示するなど、更に色々なことをさせることもできると思います。
#参考文献(文中には登場していないもの)
-
Expressions - IoT Events
- 評価式で使える符号や組込み関数はこちらを参照
- 今回はcurentInput()、timeout()を使っている