LoginSignup
8

More than 1 year has passed since last update.

Azure IoT EdgeのPythonモジュールでデータ収集してIoT Hubへ送信する

Last updated at Posted at 2021-01-27

はじめに

横河電機のエッジコントローラe-RT3 Plus F3RP70-2L1をAzure IoT認定デバイス2に登録する担当者として、Azure IoT Edgeについて勉強しました。
その内容を数回に分けてご紹介します。
こちらで第4回目の記事となります。
これまでの記事で、エッジデバイスへのAzure IoT EdgeランタイムのインストールからサンプルのPythonモジュールをデプロイして動作確認する流れを説明しています。

さて、今回はAzure IoT EdgeランタイムをインストールしたUbuntu 18.04搭載のエッジデバイスでPythonのデータ収集モジュールを作成し、収集したデータをIoT Hubへ送信してみます。

AWS IoT Greengrassのインストール手順の紹介はこちらからどうぞ!

環境

動作確認したデバイス(OS)

  • e-RT3 Plus F3RP70-2L(Ubuntu 18.04 32bit)+アナログ入力モジュール
    モジュール構成は以下の通りです。

    • e-RT3 Plus F3RP70-2L(CPUモジュール、Ubuntu 18.04 32bit)
      モジュール一式を制御します。
      CPUモジュールから各モジュールへアクセスし、書き込みや読み取り等を行えます。

    • F3AD08-6R(アナログ入力モジュール)
      アナログ入力モジュールは、外部から入力されたアナログデータをディジタルデータに変換します。

    • F3BU05-0D(ベースモジュール)
      各モジュールをセットするベースです。
      電源の供給や、モジュール間の通信はベースモジュールを介して行われます。

    • F3PU20-0S(電源モジュール)
      電源です。ベースモジュールにセットされます。

    module_50.png

こちらのデバイスでは armhf アーキテクチャのパッケージが動作します。
また、Windows 10 搭載のPCでIoT Edgeモジュールの開発とデバイスの操作を行っています。
(前回までの記事と同様です。)

各モジュールについてはこちらのページの左側のメニューから選択して確認いただけます。

ゴール

最終的なゴールは以下の図のような、Azure IoT EdgeランタイムをインストールしたUbuntu 18.04搭載のエッジデバイスでPythonのデータ収集モジュールとデータ書き込みモジュールを作成し、収集モジュールのデータをIoT Hubへ送信し、Power BIとWebで可視化することです。
Web AppsとPower BIでデータを可視化する手順や構成はMicrosoftの公式ドキュメント3 4を参考にし、図中のアイコンはこちらを使用しています。

今回はAzure IoT EdgeランタイムをインストールしたUbuntu 18.04搭載のエッジデバイスでPythonのデータ収集モジュールを作成し、収集したデータをIoT Hubへ送信することをゴールとします。
データの収集から送信の具体的な流れは以下の通りです。

  1. データ収集しIoT Hubへ送信するPythonのIoT Edgeモジュールを作成し、F3RP70へデプロイする
  2. IoT Edgeモジュールアナログ入力モジュールの全チャネルのデータを2秒周期で収集し、IoT Hubへ送信する
  3. Iot Hubへ送信されたデータをAzure IoT Explorerで確認する

※今回はDAモジュールを使用しないので、動作確認したデバイスの構成には含まれていません。

goal_d2c.png

準備

今回説明する内容は、Azure IoT Edgeでモジュールを作成し、デプロイできる環境であることを前提に説明しています。

開発環境

前回までに準備した環境を使用できます。
準備の手順は基本的にはMicrosoft公式のドキュメント5 6に従っており、前回の記事でWindows 10の場合を説明しています。

モジュールの作成

エッジデバイスにデプロイするIoT Edgeモジュールを作成し、Container Registryへプッシュします。
今回実装する機能は以下の通りです。

  1. 主な機能

    • アナログ入力モジュールの全チャネルのデータを任意の周期で収集する
      アナログ入力モジュールへアクセスするためのライブラリはIoT Edgeモジュールには含めず、CPUモジュール(ホスト)のライブラリをバインドして使用します。
    • 収集したデータをIoT Hubへ送信する
  2. モジュールツイン
    モジュールツインでデータの送信周期を設定できるようにします。
    実際のフォーマットは以下の通りです。
    今回作成するIoT Edgeモジュールのモジュールツインの設定例はモジュールツインセクションにあります。

    Module Twin Settings
    {
        "ert3add2c": {
            "interval_sec": 2
        }
    }
    
  3. ペイロードのフォーマット
    Microsoftが提供する公式ドキュメント4で紹介されているIoT Edgeモジュールが送信するペイロードを参考に以下のフォーマットで作成します。

    Payload
    {
      "body": {
        "messageID": 1,
        "deviceID": "{deviceID}",
        "datetime": "2020-01-01T00:00:00.000Z",
        "ch1": 123,
        "ch2": 234,
        "ch3": 345,
        "ch4": 456,
        "ch5": 567,
        "ch6": 678,
        "ch7": 789,
        "ch8": 890
      }
    }
    

    各要素については以下の通りです。

    • messageID : 1から始まる連番
    • deviceID : IoT EdgeモジュールのデバイスID
    • datetime : データ取得時のCPUモジュールの時刻
    • ch1 - ch8 : チャネル毎のデータ

モジュールのプロジェクトの作成

Pythonモジュールのプロジェクトを作成します。
モジュールの作成手順は、基本的には公式ドキュメントの「チュートリアル:Linux デバイス用の Python IoT Edge モジュールを開発およびデプロイする」に従います。
「モジュール プロジェクトを作成する」内の「新しいプロジェクトを作成する」から「ターゲット アーキテクチャを選択する」までの手順で、Visual Studio Code(VS Code)で新しくプロジェクトを作成します。
以上の手順の詳細が前回の記事の「モジュールの作成」内の「新しいプロジェクトを作成する」から「ターゲット アーキテクチャを選択する」でも紹介されています。

今回は以下の設定でプロジェクトを作成しました。

  • Solution Name: Ert3D2c
  • Module Template: Python Module
  • Module Name: Ert3D2cModule

また、ターゲットアーキテクチャは arm32v7 を選択します。

モジュールのコードと機能

モジュールのコード

作成したプロジェクトの main.py を以下のコードに置き換えて保存します。
クラスと各メソッドについてコード中に簡単にコメントしています。
機能セクションで一部の機能を解説しています。

main.py
import os
import json
import subprocess
import signal
import datetime
import ctypes
from azure.iot.device import IoTHubModuleClient
from azure.iot.device import Message

DESIRED_KEY = 'desired'
ERT3ADD2C_KEY = 'ert3add2c'
INTERVALSEC_KEY = 'interval_sec'
STATUS_KEY = 'status'
FAM3AD_CHNUM = {'AD04': 4, 'AD08': 8}
UNIT = 0
SLOT = 2
DEFAULT_INTERVAL_SEC = 2.0
LDCONFIGEXEC = 'ldconfig'
M3LIB_PATH = '/usr/local/lib/libm3.so.1'
DEVICE_ID = os.environ['IOTEDGE_DEVICEID']
OUTPUT_NAME = os.environ['IOTEDGE_MODULEID'] + 'ToIoTHub'


class AdD2C():
    """
    ADモジュールからデータ収集する各機能が実装されたクラス。
    """
    def __init__(self, module_client, unit, slot):
        """
        コンストラクタ。
        """
        self.__module_client = module_client
        self.__unit = unit
        self.__slot = slot
        self.__message_no = 1
        self.__libc = ctypes.cdll.LoadLibrary(M3LIB_PATH)
        self.__libc.getM3IoName.restype = ctypes.c_char_p
        self.__chnum = self.__get_m3ad_ch_num()
        signal.signal(signal.SIGALRM, self.__signal_handler)

    def __get_m3ad_ch_num(self):
        """
        AD08かAD04か判断する。
        """
        namebytes = self.__libc.getM3IoName(
            ctypes.c_int(self.__unit), ctypes.c_int(self.__slot))
        num = 0
        if namebytes is not None:
            num = FAM3AD_CHNUM.get(namebytes.decode(), 0)
        return num

    def __read_m3ad_ch_datas(self):
        """
        ADモジュールの全チャネルからデータを収集する。
        """
        short_arr = ctypes.c_short * self.__chnum
        ch_datas = short_arr()
        self.__libc.readM3IoRegister(
            ctypes.c_int(self.__unit),
            ctypes.c_int(self.__slot),
            ctypes.c_int(1),
            ctypes.c_int(self.__chnum),
            ch_datas)
        return ch_datas

    def __signal_handler(self, signum, frame):
        """
        メッセージを作成してedgeHubモジュールへ送信する。
        """
        bodyDict = dict(
            messageID=self.__message_no,
            deviceID=DEVICE_ID,
            datetime=datetime.datetime.utcnow().isoformat() + 'Z'
            )

        ch_datas = self.__read_m3ad_ch_datas()
        for index, ch_value in enumerate(ch_datas):
            bodyDict['ch' + str(index + 1)] = ch_value

        bodyStr = json.dumps(bodyDict)
        msg = Message(bodyStr, output_name=OUTPUT_NAME)
        self.__module_client.send_message(msg)
        print(bodyStr)

        self.__message_no = self.__message_no + 1

    def set_condition(self, desired):
        """
        データ収集周期を設定する。
        """
        if ERT3ADD2C_KEY not in desired:
            return {STATUS_KEY: False}

        interval_sec = desired[ERT3ADD2C_KEY].get(INTERVALSEC_KEY,
                                                  DEFAULT_INTERVAL_SEC)
        reported = dict()
        reported[ERT3ADD2C_KEY] = desired[ERT3ADD2C_KEY]
        if interval_sec < 0.0:
            reported[ERT3ADD2C_KEY][STATUS_KEY] = False
        else:
            signal.setitimer(signal.ITIMER_REAL, interval_sec, interval_sec)
            reported[ERT3ADD2C_KEY][STATUS_KEY] = True

        return reported


def send_ad_data():
    """
    各機能を呼び出す。
    モジュールツインの変更を監視する。
    """
    module_client = IoTHubModuleClient.create_from_edge_environment()
    module_client.connect()
    twin = module_client.get_twin()

    add2c = AdD2C(module_client, UNIT, SLOT)
    reported = add2c.set_condition(twin.get(DESIRED_KEY, {}))
    module_client.patch_twin_reported_properties(reported)

    while True:
        reported = add2c.set_condition(
            module_client.receive_twin_desired_properties_patch()
        )
        module_client.patch_twin_reported_properties(reported)

    module_client.disconnect()


if __name__ == "__main__":
    subprocess.run([LDCONFIGEXEC])

    send_ad_data()

機能

一部の機能を簡単に紹介します。

  • ライブラリ

    import ctypes
    

    今回はe-RT3の関数を使用したいことと、それがC言語のプログラムで動作するもののため、ctypes をインポートしています。

    from azure.iot.device import IoTHubModuleClient
    from azure.iot.device import Message
    

    Azure IoT HubデバイスSDK7のライブラリです。

    • IoTHubModuleClient : Azure IoT HubまたはAzure IoT Edgeインスタンスに接続する同期モジュールクライアント(詳細はこちら
    • Message : IoT Hubへ送信する/受信するメッセージ(詳細はこちら
  • 定義

    M3LIB_PATH = '/usr/local/lib/libm3.so.1'
    

    e-RT3のライブラリのシンボリックリンク(IoT Edgeモジュール内)です。

  • e-RT3の関数

    • getM3IoName : モジュールIDの取得
      ユニット、スロットを指定してモジュールID(モジュールの名称)を取得する関数です。
      C言語では以下のように使用します。

      char* getM3IoName (int unit, int slot);
      
    • readM3IoRegister : 入出力レジスタデータ読み出し
      ユニット、スロット、読み出し開始チャネル、読み出すチャネル数、データ格納位置を指定して入出力レジスタのデータを読み出す関数です。
      C言語では以下のように使用します。

      int readM3IoRegister(int unit, int slot, int pos, int num, unsigned short *data);
      
  • その他

    • subprocess.run([LDCONFIGEXEC]) : ライブラリのシンボリックリンクの生成
      メイン関数の冒頭にあり、実際に呼び出されるのは subprocess.run(['ldconfig']) です。
      後の手順でこのIoT Edgeモジュールをデプロイする際F3RP70-2L内のライブラリを使用できるように、F3RP70-2Lの /usr/local/lib/libm3.so.1.0.1 をIoT Edgeモジュールの /usr/local/lib/libm3.so.1.0.1 にバインドします。
      その後シンボリックリンクを生成する ldconfig コマンドを実行するために、この行が初めに登場します。
      実際に参照するのは生成されたシンボリックリンクの /usr/local/lib/libm3.so.1 です。

モジュールのプッシュ

main.py を書き換えて保存したら、Container RegistryへIoT Edgeモジュールをプッシュします。

  1. VS Codeのエクスプローラから deployment.template.json を探して右クリックします。
    表示されるメニューから「Build and Push IoT Edge Solution」を選択します。

  2. IoT EdgeモジュールのビルドとContainer Registryへのプッシュのコマンドが実行されます。
    VS Code下部のターミナルに進行度と実行結果が表示されます。

    ※もしProxy設定の不足でプッシュに失敗した場合は、前回の記事の補足の内容を確認し、VS CodeとDocker Desktopに設定を行ってください。

モジュールのデプロイ

IoT EdgeモジュールをF3RP70-2Lにデプロイします。
今回もAzure Portalを使用して設定します。

Image URIの取得

  1. Azure PortalからContainer Registryへアクセスし、左側のメニューのサービスカテゴリ内の「リポジトリ」をクリックします。
    表示されるリポジトリ一覧から、デプロイしたいリポジトリのIDをクリックします。
    タグが表示されるのでデプロイしたいタグをクリックします。
    このとき、<version number>-arm32v7 のようにバージョン番号に続くのが arm32v7 であることを確認してください。

  2. 「Docker pullコマンド」の隣のテキストボックスから、docker pull以降の文字列をコピーします。
    こちらがImage URIにあたるもので、デプロイの際に使用します。

    cr_jp_005.png

IoT Hubでの作業手順

モジュール

  1. IoT Hubへ移動し、デバイスの自動管理カテゴリ内の「IoT Edge」をクリックします。
    表示されるデバイスIDから、デプロイしたいデバイスを選択します。
    デバイスの情報が表示されたら、画面上部の「モジュールの設定」をクリックします。

  2. 今回は、前回までの記事でデプロイしたPythonModuleとSimulatedTemperatureSensorを消去し、新たに今回作成したIoT Edgeモジュールをデプロイします。
    まず、コンテナーレジストリの資格情報の欄に必要な各情報が既に入力されていることを確認します。
    入力が必要な場合はこちらの2. をご覧ください。
    (Azure Portalで日本語表記が増えたので、前回の記事を参照する場合は適宜読み替えてください。)
    次に、PythonModuleとSimulatedTemperatureSensorの右側にあるゴミ箱マークをクリックし、リストから削除してください。

    cr_jp_001.png

  3. IoT Edge モジュールの「+追加」をクリックし、表示されるメニューからIoT Edge モジュールをクリックします。
    右側に新たにメニューが表示されるので、以下の組み合わせの通りにテキストボックスに入力・ペーストします。

    モジュールの設定項目 入力する情報
    IoT Edgeモジュール名 任意のモジュール名
    イメージのURI Container Registryのリポジトリから取得するImage URI
    再起動ポリシー 常時(デフォルトのまま)
    必要な状態 実行中(デフォルトのまま)
    イメージのプルポリシー 空欄(デフォルトのまま)

    cr_jp_002.png

  4. 次に、「コンテナーの作成オプション」タブをクリックし、オプションを入力します。
    以下のように入力しホスト側に存在する必要なライブラリとデバイスをIoT Edgeモジュール内で使用できるようにします。
    ライブラリは "Binds"、デバイスは "Devices" に設定内容をそれぞれ入力しています。

    Container Create Options
    {
      "HostConfig": {
        "Binds": [
          "/usr/local/lib/libm3.so.1.0.1:/usr/local/lib/libm3.so.1.0.1"
        ],
        "Devices": [
          {
            "PathOnHost": "/dev/m3io",
            "PathInContainer": "/dev/m3io",
            "CgroupPermissions": "rwm"
          },
          {
            "PathOnHost": "/dev/m3sysctl",
            "PathInContainer": "/dev/m3sysctl",
            "CgroupPermissions": "rwm"
          },
          {
            "PathOnHost": "/dev/m3cpu",
            "PathInContainer": "/dev/m3cpu",
            "CgroupPermissions": "rwm"
          },
          {
            "PathOnHost": "/dev/m3mcom",
            "PathInContainer": "/dev/m3mcom",
            "CgroupPermissions": "rwm"
          },
          {
            "PathOnHost": "/dev/m3dev",
            "PathInContainer": "/dev/m3dev",
            "CgroupPermissions": "rwm"
          },
          {
            "PathOnHost": "/dev/m3ras",
            "PathInContainer": "/dev/m3ras",
            "CgroupPermissions": "rwm"
          },
          {
            "PathOnHost": "/dev/m3wdt",
            "PathInContainer": "/dev/m3wdt",
            "CgroupPermissions": "rwm"
          }
        ]
      }
    }
    

    cr_jp_003.png

    ※これらの内容は配置マニフェストの "CreateOptions" の中に挿入されます。
    Container Create Optionsについての詳細はMicrosoftのドキュメント「IoT Edge モジュールのコンテナー作成オプションを構成する方法」、設定できる内容の詳細はDockerのドキュメント「Create a container」をそれぞれご覧ください。

  5. 次に、「モジュールツインの設定」タブをクリックし、モジュールツインを入力します。
    初めは2秒周期に設定してみます。
    入力したら青い「追加」ボタンをクリックします。

    Module Twin Settings
    {
        "ert3add2c": {
            "interval_sec": 2
        }
    }
    

    cr_jp_004.png

ルート

  1. 「ルート」タブをクリックします。
    既に入力されているPythonModuleToIoTHubに倣ってIoT EdgeモジュールからIoT Hubへデータを送信するルートの設定を行います。
    以下のように名前、値を入力します。

    名前
    yourModuleToIoTHub FROM /messages/modules/yourModule/outputs/* INTO $upstream
  2. PythonModuleToIoTHubとsensorToPythonModuleの設定を右のごみ箱マークをクリックして削除します。
    1.で入力した設定のみになったら上部の「確認および作成」タブか下部の「次へ: 確認および作成」ボタンをクリックします。

    iothub_005-2.png

確認および作成

設定内容が正しいか確認します。
「検証に成功しました。」の表示があり、配置に表示されるJSON形式の設定テキスト内に入力・削除した内容が反映されていることを確認します。
問題なかったら下部の青い「作成」ボタンをクリックして設定を適用します。

モジュールの動作確認

IoT Hubでデプロイの成功と動作状況を、Azure IoT ExplorerでIoT Hubへデータを送信できていることを確認します。

IoT Hubでの確認

「作成」ボタンをクリックすると自動的にIoT Edgeモジュールの情報画面に戻ります。
以下の内容を確認します。

  • 「IoT Edgeランタイムの応答」が「200 -- OK」であること
  • 「モジュール」のリストに表示されているのが「$edgeAgent」、「$edgeHub」、デプロイした「yourModule」であり、ランタイムの状態が全て「running」であること

※新たにデプロイしたIoT Edgeモジュールはランタイムの状態がrunningになるまで数分かかります。
もし上記のようにならない場合は暫く待ち、画面上部の更新ボタンをクリックし最新の情報に更新してから再度確認します。
同様に、消去した設定が反映されるのにもしばらく時間がかかります。

iothub_006.png

Azure IoT Explorerでの確認

前回の記事のモジュールの動作確認の項目と同じ手順で確認できます。
Azure IoT Explorerで受信したTelemetryが以下のようなフォーマットであれば作成したIoT Edgeモジュールが正しく動作しています。

今回は8チャネルのF3AD08を使用しているため、8チャネル分のデータが送信されています。

Telemetry
{
  "body": {
    "messageID": 2034,
    "deviceID": "test_ert3_f3rp70",
    "datetime": "2021-01-26T03:50:58.445146Z",
    "ch1": -1,
    "ch2": 8,
    "ch3": 1,
    "ch4": 2,
    "ch5": 0,
    "ch6": 1,
    "ch7": 3,
    "ch8": 1
  },
  "enqueuedTime": "2021-01-26T03:50:58.476Z",
  "properties": {}
}

モジュールツイン

今回作成したIoT Edgeモジュールにはモジュールツインの機能を実装しています。
モジュールツインの設定手順と機能を紹介します。

モジュールツインの設定手順

今回はAzure Portalからの設定手順を紹介します。

  1. Azure Portalからモジュールセクションの1.の手順でデバイス設定を開き、モジュールのリストから変更したいIoT Edgeモジュールを選んでクリックします。

  2. IoT Edgeモジュールの更新画面が右側に表示されるので、「モジュールツインの設定」タブをクリックします。

  3. テキストボックスにモジュールツインを入力したり、既に入力されている場合は変更して更新します。

  4. 最後に、「確認および作成」ボタンをクリックし、設定内容に問題が無ければ更新します。

機能と設定例

周期の変更

interval_sec の値を任意の値にすることでデータ収集周期を変更できます。
以下の例は収集周期を5秒に変更する場合です。

Module Twin Settings
    {
        "ert3add2c": {
            "interval_sec": 5
        }
    }

収集停止

データの収集を停止したい場合は interval_sec の値を0に設定します。
設定が反映されるとメッセージがIoT Hubに送信されなくなります。
IoT Edgeモジュールそのものが停止するのではないため、再度1より大きい周期を設定するとメッセージが送信されるようになります。

Module Twin Settings
    {
        "ert3add2c": {
            "interval_sec": 0
        }
    }

次回

今回は特に外部からデータが入力されていない状態で、IoT Edgeモジュールでアナログ入力モジュールのデータを読み出しました。
次回はさらにe-RT3 Plusにアナログ出力モジュールを実装し、データを書き込みする別のIoT Edgeモジュールを作成してデプロイします。
そのデータをアナログ入力モジュールへ入力し、IoT Hubへ送信されるデータがどのように変化するかを確かめます。

次回

参考

  1. リアルタイムOSコントローラ e-RT3 Plus F3RP70-2L

  2. Azure Certified Device catalog

  3. Web アプリで Azure IoT Hub からのリアルタイム センサー データを視覚化する

  4. Power BI を使用して Azure IoT Hub からのリアルタイム センサー データを視覚化する 2

  5. チュートリアル:Linux のデバイス用の IoT Edge モジュールを開発する

  6. チュートリアル:Linux デバイス用の Python IoT Edge モジュールを開発およびデプロイする

  7. Azure IoT Hub SDK の概要と使用方法

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
8