LoginSignup
7
0

More than 1 year has passed since last update.

IoT Eventsで死活監視を実装してみる

Last updated at Posted at 2021-12-13

はじめに

AWS IoT EventsはIoTデバイス等のイベントを検出、対応できるマネージド型のIoTサービスです。
GUIを使って容易にイベント検出・アクションの定義を作成できるのが魅力だと思います。
参考:【AWS Black Belt Online Seminar】AWS IoT Events

今回はIoT Eventsを使ってデバイスの死活監視を作成します。

1 今回の構成

今回は以下の機能をIoT Eventsで実装します。

  • デバイス単位で監視する
  • 一定時間デバイスからペイロードが送られて来ない場合にアラートを通知する
  • 上記「一定時間」はユーザーが自由に設定できるようにする(分単位)
  • 死活監視の対象から外せるようにする

構成図は以下の通りです。
構成図_2.png

2 アラート通知箇所の作成

死活監視で異常があった際にメールを通知する箇所を先に作成します。
IoT Eventsで探知機モデルを作成する際にアクションとして指定するので、先に用意します。

2-1 Amazon SESの準備

今回はAmazon SESを利用してメール通知を行います。
簡単な検証のみなので、送信元・送信先共に自身のメールアドレスを利用します。

1.Amazon SESのマネジメントコンソールにアクセスし、「Create Identity」をクリックする
image.png
2.以下の通り選択、入力して、画面下部の「Create Identity」をクリックする

  • Identity Type:Email Address
  • Email address:※今回利用するメールアドレス

image.png

3.AWSから「Amazon Web Services – Email Address Verification Request in region {リージョン名}」という件名のVerify用のメールが届くので、リンクをクリックする
※リンク先はAmazon SESの紹介ページになっている
image.png

4.Amazon SESのマネジメントコンソール上で先程追加したメールアドレスが「Verified」となっていることを確認する
image.png

なお、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.今回は以下の通りコードを記述する

lambda_function.py
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の設定 > アクセス権限 > ロール名 の順にクリック
image.png

5.「ポリシーのアタッチ」をクリック
6.(今回は簡易に実装するのみなので)AmazonSESFullAccessをチェックして「ポリシーのアタッチ」をクリック
image.png

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 を選択

image.png

6.同様にして、以下の「入力」を作成する

  • 入力名:AlertMinutesInput
  • 入力属性の選択:deviceId, alertMinutes, excluded を選択

3-2 探知機モデルの作成①:「状態」の用意

いよいよ、実際に検知・アクションを行う探知機モデルを作成します。
ここでは、「状態」の用意まで行います。

1.IoT Eventsのマネジメントコンソールで「探知機モデルの作成」をクリック
image.png

2.今回は「新しく作成する」をクリック
image.png

3.探知機モデルパレットの「状態」をドラッグアンドドロップして2つ追加する
image.png

4.それぞれの状態の名前を以下の通り変更する

  • untitled_state_1 -> Normal
  • State_1 -> Excluded
  • untitled_state_2 -> Alert

5.以下の通り遷移するようにし、遷移名は「To + {遷移先の状態名}」とする
image.png

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

image.png

2.移行ToNormalのイベントのトリガーロジックを以下の通り入力する

イベントのトリガーロジック
$input.AlertMinutesInput.excluded == false

image.png

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 探知機モデルの発行

ロジックをすべて追加したので、探知機モデルを発行します。

1.画面左上の「編集」をクリックする
image.png

2.以下の通り入力、選択して「保存」をクリック

  • ディテクターモデル名:※任意の名称
  • ロール:※任意の新しいロール名
  • 探知機生成メソッド:一意のキー値ごとに探知機を作成する
  • 探知機作成キー:deviceId
  • ディテクターの評価方法:シリアル評価

3.画面右上の「分析を実行」をクリックし、エラー等がないかチェックする
image.png

4.画面右上の「発行」をクリックし、内容を確認のうえ「保存して発行する」をクリックする
image.png

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.送信したペイロードを確認できるように、今回の送信先トピックをそれぞれサブスクライブする
image.png

3.以下の通常ペイロードを送ったあと、IoT Events > 探知機モデル > 該当の探知機モデル の順にクリックし、deviceIdでディテクターが作成されていることを確認する

{
    "deviceId": "device-0001",
    "timestamp": "2021-12-13T04:49:00Z",
    "temperature": 15.5,
    "humidity": 42.5
}

image.png

4.以下の通り設定値変更ペイロードを"excluded":falseで送り、ディテクターの状態が「Normal」に遷移することを確認する

{
    "deviceId": "device-0001",
    "alertMinutes": 5,
    "excluded": false
}

image.png

5.以下の通り通常ペイロードを送り、ディテクターに先程設定したalert_minutesの時間(分)でタイマーが作成されることを確認する

{
    "deviceId": "device-0001",
    "timestamp": "2021-12-13T07:38:00Z",
    "temperature": 15.5,
    "humidity": 42.5
}

image.png

6.タイマーがtimeoutするのを待ち、以下を確認する

  • ディテクターの状態がAlertになる
  • アラート通知のメールが届く

image.png
※timeoutするまでにalert_minutesを変更したため、このキャプチャでは5から10に変わっている

メールは以下の通り通知された

件名:device-0001が接続されていません
本文:
device-0001が最後にデータを通知してから10分が経過しました。
デバイスが接続されていない可能性があります。

最終通信時刻:2021-12-13 16:38:00

7.通常ペイロードを再度送ることで状態がNormalに戻ることを確認する
image.png
※今回は既に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()を使っている
7
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
0