【製造業 IoTも簡単可視化!】AWS IoT SiteWise に収集した設備機器の稼働データを Amazon Managed Grafana で可視化してみた

2022.08.05

AWS IoT SiteWise は SiteWiseモニターという可視化ツールで簡単に収集したデータを可視化することができますが、サポートしているグラフ形式が少なかったり、あまり複雑な可視化ができません。

一方で、Amazon Managed Grafana は SiteWise をデータソースとしてサポートしているので、スピードメーターのようなビジュアルである「ゲージ表示」など様々な形で SiteWise のデータを可視化することができます。

今回は、Managed Grafana で簡単なダッシュボードの作り方までをご紹介したいと思います。

前提

今回は SiteWise のデータは API を使ってダミーデータを投入しています。
ダミーデータを投入したコードは本記事の末尾に記載しましたので必要に応じてご利用いただければと思います。

Amazon Managed Grafanaワークスペースの作成

まず最初に、Amazon Managed Grafana のワークスペースを作ります。

01-make-workspace

適当なワークスペース名と説明を記入して「次へ」をクリックします。

02-named-workspace

今回は、認証に Auth0 で SAML 認証を使うので「認証アクセス」は「SAML」を選択します。
「アクセス許可タイプ」は「サービスマネージド」を選択して「次へ」進みます。

03-select-auth-method

アカウントの指定では、「現在のアカウント」を選択して、データソースには「AWS IoT SiteWise」を選択します。

04-select-data-source

レビュー画面の内容で問題なければ「ワークスペースを作成」をクリックします。

05-review-before-make-workspace

作成できたら、ワークスペースの画面に遷移するので「セットアップを完了にする」をクリックして SAML の設定を行います。

なお、ここでは Auth0 側の作業は割愛いたします。詳細は下記のブログの通りですのでご参考にしていただければと思います。

06-click-saml-settings

Auth0 の作業と並行して、下記の「ステップ1」〜「ステップ3」の作業を完了させます。
「ステップ1」では Auth0 に設定する 3つの URL を確認しておきます。

07-step1-saml-urls

「ステップ2」では、Auth0 からダウンロードした メタデータの XML ファイルをアップロードします。

08-step2-upload-metadata

「ステップ3」では Auth0 側の設定に合わせた内容をそれぞれセットします。
「管理者ロールの値」では、Auth0 で複数ユーザーが存在して、それらも管理者としたい場合にカンマ区切りで登録することもできます。
管理者は、Grafana の画面でダッシュボードを作ったり変更することが可能です。

09-step3-mapping-assertion

Amazon Managed Grafana へログイン

設定が完了したら「Grafana ワークスペース URL」にアクセスしてログインします。

10-login-workspace

「Sign in with SAML」をクリックします。

11-sign-in-saml

ログイン画面では Auth0 の設定に基づき認証情報を入れてログインします。

12-login-auth0

Grafana のデータソースを設定する

無事にログインできたら、早速設定していきましょう。 メニューから「AWS のアイコン」から「Data Sources」をクリックします。

13-data-source-grafana

データソースの設定では、それぞれ次のように指定します。

  • Service:IoT SiteWise
  • Default Region:Asia Pacific (Tokyo)

選択できたら「Add data source」をクリックします。

14-add-data-source

「Add data source」をクリックすると、画面の下に追加したデータソースが出現するので、「Go to settings」をクリックしてデータソースの設定画面を開きます。

15-click-settings

ここではそのまま「Save & test」をクリックして設定を保存します。

16-save-and-test

ダッシュボードを作ってみる

次にダッシュボードを作ります。
メニューの「+」から「Dashboard」をクリックします。

17-create-dashboard

次の画面で「Add a new panel」をクリックします。

18-add-new-panel

下記はパネル追加直後の画面です。プロットされているグラフはダミーなので無視して構いません。

19-default-dashboard

「Data source」で先程設定したデータソースである「AWS IoT SiteWise」を選択します。

20-select-data-source

次にクエリタイプとプロットしたいアセットを指定します。今回は次のようにしました。

  • Query type:Get property value history
  • Property Alias:/test/device/1/temperature

今回は直接プロパティエイリアスを指定しましたが、下の図のように Grafana の画面から対象のアセットとプロパティを選択して設定することも可能です。

29-select-asset-property

今回はデータとして2つのアセットがあるので、1つ目のアセットの設定が終わったら同じように2つ目も「+Query」ボタンを押して追加で設定します。

21-setting-query

余談ですが、設定したクエリの名前も好きなものに編集することもできます。

22-query-title

先程と同じように2つ目のアセットに対しても設定ができました。

23-add-query

最後に「Title」を編集して適当なものに変更しておきます。
ここまでできたら「Apply」を押して設定内容を適用しましょう。

24-apply

保存した設定でグラフが描画される様になりました。ここでは「描画期間」と「グラフの更新間隔」をそれぞれ「過去5分」、「5秒で更新」としています。

25-last5min-auto-refresh-5s

これで一通りの作業が完了したので「保存」ボタンをクリックして保存しましょう。

26-save-dashboard

適当なダッシュボード名を付けて保存します。

27-save-as

毎度同じようなサンプルになりますが、今回はとりあえず下記のようなグラフの作成までとしました。

28-my-first-dashboard

最後に

SiteWise データの可視化として、これまで次のようなことをやってきました。

  • AWS IoT SiteWise モニターで可視化
  • Amazon QuickSight で可視化
  • Google データポータルで可視化

「簡単さ」という観点では、SiteWise モニターが最も簡単に感じますが、「簡単であり様々な可視化表現ができる」という点では Grafana がとても使いやすいと感じました。
(なにげにタイムゾーンが簡単にプルダウンから変更できるのは嬉しいですね)

一方で、認証が「AWS IAM Identity Center(旧 AWS SSO)」と「SAML」のみのサポートである点が導入のネックになりそうに感じました。
SiteWIse モニターのように IAM による認証もサポートされると、導入のハードルが更に下がるのではと思いました。今後のアップデートに期待しましょう。

これからもっと使っていきたいと思います。

おまけ:SiteWise API でダミーデータを送るスクリプト

本記事で可視化に使ったデータを生成する Python スクリプトです。一定時間ごとに値の高低を繰り返します。

import boto3
import uuid
import random
import time
import logging
import sys
import json
import math
import threading

logger = logging.getLogger()
logger.setLevel(logging.INFO)
streamHandler = logging.StreamHandler(stream=sys.stdout)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
streamHandler.setFormatter(formatter)
logger.addHandler(streamHandler)

client = boto3.client('iotsitewise')

def batch_put_asset(device_id, sending_period):
    start = time.time()
    mode = 'low' # 最初はLOWモードでスタートすることにする
    logger.info("starting low mode... ")

    while True:
        timer = int(time.time() - start)
        # 現在時刻の取得 (小数部, 整数部)
        timestamp_float, timestamp_int = math.modf(time.time())

        if mode == 'low':
            if timer < sending_period:
                MeasureValueTemp = random.uniform(1, 30)
                MeasureValueClock = random.randint(600000000,800000000)
                #print('mode low')
                logger.info("temperature: {}".format(MeasureValueTemp))
                logger.info("clock: {}".format(MeasureValueClock))
            else:
                mode = 'high'
                logger.info("starting high mode... ")
                start = time.time() + 1  # reset timer
                continue
        else: #if mode == 'high':
            if timer < sending_period: # Mode: High load
                MeasureValueTemp = random.uniform(50, 90)
                MeasureValueClock = random.randint(1200000000,1500000000)
                #print('mode high')
                logger.info("temperature: {}".format(MeasureValueTemp))
                logger.info("clock: {}".format(MeasureValueClock))
            else:
                mode = 'low'
                logger.info("starting low mode... ")
                #print(str(int(time.time())) + ' start low mode...')
                start = time.time() + 1 # reset timer
                continue

        try:
            response = client.batch_put_asset_property_value(
                entries=[
                    {
                        'entryId': '{}'.format(uuid.uuid4()),
                        'propertyAlias': '/test/device/{}/temperature'.format(device_id),
                        'propertyValues': [
                            {
                                'value': {
                                    'doubleValue': MeasureValueTemp
                                },
                                'timestamp': {
                                    'timeInSeconds': int(timestamp_int),
                                    'offsetInNanos': int(round((timestamp_float * 1000000000), 0))
                                },
                                'quality': 'GOOD'
                            },
                        ]
                    },
                    {
                        'entryId': '{}'.format(uuid.uuid4()),
                        'propertyAlias': '/test/device/{}/clock'.format(device_id),
                        'propertyValues': [
                            {
                                'value': {
                                    'doubleValue': MeasureValueClock
                                },
                                'timestamp': {
                                    'timeInSeconds': int(timestamp_int),
                                    'offsetInNanos': int(round((timestamp_float * 1000000000), 0))
                                },
                                'quality': 'GOOD'
                            },
                        ]
                    },
                ]
            )
            logger.info("response: {}\n".format(json.dumps(response, indent=2)))
            logger.info("propertyAlias: {}\n".format(device_id))

            if response['errorEntries']:
                    logger.error("temperature: {} clock: {}".format(MeasureValueTemp, MeasureValueClock))
            #else:
            #    logger.info("temperature: {} clock: {}".format(MeasureValueTemp, MeasureValueClock))
        except Exception as e:
            logger.error("{}".format(e))
            logger.error("temperature: {} clock: {}".format(MeasureValueTemp, MeasureValueClock))

        time.sleep(5)


# args: デバイスID,負荷の高低の継続時間
device_1 = threading.Thread(target=batch_put_asset,args=(1,120))
device_2 = threading.Thread(target=batch_put_asset,args=(2,60))
device_1.start()
device_2.start()