はじめに
横河電機のエッジコントローラe-RT3 Plus F3RP70-2L1をAzure IoT認定デバイス2に登録する担当者として、Azure IoT Edgeについて勉強しました。
その内容を数回に分けてご紹介します。
こちらで第4回目の記事となります。
これまでの記事で、エッジデバイスへのAzure IoT EdgeランタイムのインストールからサンプルのPythonモジュールをデプロイして動作確認する流れを説明しています。
- 第1回目: Ubuntu 18.04にAzure IoT Edgeランタイムをインストールする
- 第2回目: Azure IoT EdgeランタイムをインストールしたUbuntu 18.04搭載のエッジデバイスをIoT Hubへ接続する
- 第3回目: Azure IoT EdgeランタイムをインストールしたUbuntu 18.04搭載のエッジデバイスで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(電源モジュール)
電源です。ベースモジュールにセットされます。
-
こちらのデバイスでは 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へ送信することをゴールとします。
データの収集から送信の具体的な流れは以下の通りです。
- データ収集しIoT Hubへ送信するPythonのIoT Edgeモジュールを作成し、F3RP70へデプロイする
- IoT Edgeモジュールがアナログ入力モジュールの全チャネルのデータを2秒周期で収集し、IoT Hubへ送信する
- Iot Hubへ送信されたデータをAzure IoT Explorerで確認する
※今回はDAモジュールを使用しないので、動作確認したデバイスの構成には含まれていません。
準備
今回説明する内容は、Azure IoT Edgeでモジュールを作成し、デプロイできる環境であることを前提に説明しています。
開発環境
前回までに準備した環境を使用できます。
準備の手順は基本的にはMicrosoft公式のドキュメント5 6に従っており、前回の記事でWindows 10の場合を説明しています。
モジュールの作成
エッジデバイスにデプロイするIoT Edgeモジュールを作成し、Container Registryへプッシュします。
今回実装する機能は以下の通りです。
-
主な機能
- アナログ入力モジュールの全チャネルのデータを任意の周期で収集する
アナログ入力モジュールへアクセスするためのライブラリはIoT Edgeモジュールには含めず、CPUモジュール(ホスト)のライブラリをバインドして使用します。 - 収集したデータをIoT Hubへ送信する
- アナログ入力モジュールの全チャネルのデータを任意の周期で収集する
-
モジュールツイン
モジュールツインでデータの送信周期を設定できるようにします。
実際のフォーマットは以下の通りです。
今回作成するIoT Edgeモジュールのモジュールツインの設定例はモジュールツインセクションにあります。Module Twin Settings{ "ert3add2c": { "interval_sec": 2 } }
-
ペイロードのフォーマット
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
を以下のコードに置き換えて保存します。
クラスと各メソッドについてコード中に簡単にコメントしています。
機能セクションで一部の機能を解説しています。
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のライブラリです。
-
定義
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モジュールをプッシュします。
-
VS Codeのエクスプローラから
deployment.template.json
を探して右クリックします。
表示されるメニューから「Build and Push IoT Edge Solution」を選択します。 -
IoT EdgeモジュールのビルドとContainer Registryへのプッシュのコマンドが実行されます。
VS Code下部のターミナルに進行度と実行結果が表示されます。※もしProxy設定の不足でプッシュに失敗した場合は、前回の記事の補足の内容を確認し、VS CodeとDocker Desktopに設定を行ってください。
モジュールのデプロイ
IoT EdgeモジュールをF3RP70-2Lにデプロイします。
今回もAzure Portalを使用して設定します。
Image URIの取得
-
Azure PortalからContainer Registryへアクセスし、左側のメニューのサービスカテゴリ内の「リポジトリ」をクリックします。
表示されるリポジトリ一覧から、デプロイしたいリポジトリのIDをクリックします。
タグが表示されるのでデプロイしたいタグをクリックします。
このとき、<version number>-arm32v7
のようにバージョン番号に続くのがarm32v7
であることを確認してください。 -
「Docker pullコマンド」の隣のテキストボックスから、docker pull以降の文字列をコピーします。
こちらがImage URIにあたるもので、デプロイの際に使用します。
IoT Hubでの作業手順
モジュール
-
IoT Hubへ移動し、デバイスの自動管理カテゴリ内の「IoT Edge」をクリックします。
表示されるデバイスIDから、デプロイしたいデバイスを選択します。
デバイスの情報が表示されたら、画面上部の「モジュールの設定」をクリックします。 -
今回は、前回までの記事でデプロイしたPythonModuleとSimulatedTemperatureSensorを消去し、新たに今回作成したIoT Edgeモジュールをデプロイします。
まず、コンテナーレジストリの資格情報の欄に必要な各情報が既に入力されていることを確認します。
入力が必要な場合はこちらの2. をご覧ください。
(Azure Portalで日本語表記が増えたので、前回の記事を参照する場合は適宜読み替えてください。)
次に、PythonModuleとSimulatedTemperatureSensorの右側にあるゴミ箱マークをクリックし、リストから削除してください。 -
IoT Edge モジュールの「+追加」をクリックし、表示されるメニューからIoT Edge モジュールをクリックします。
右側に新たにメニューが表示されるので、以下の組み合わせの通りにテキストボックスに入力・ペーストします。モジュールの設定項目 入力する情報 IoT Edgeモジュール名 任意のモジュール名 イメージのURI Container Registryのリポジトリから取得するImage URI 再起動ポリシー 常時(デフォルトのまま) 必要な状態 実行中(デフォルトのまま) イメージのプルポリシー 空欄(デフォルトのまま) -
次に、「コンテナーの作成オプション」タブをクリックし、オプションを入力します。
以下のように入力しホスト側に存在する必要なライブラリとデバイスを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" } ] } }
※これらの内容は配置マニフェストの
"CreateOptions"
の中に挿入されます。
Container Create Optionsについての詳細はMicrosoftのドキュメント「IoT Edge モジュールのコンテナー作成オプションを構成する方法」、設定できる内容の詳細はDockerのドキュメント「Create a container」をそれぞれご覧ください。 -
次に、「モジュールツインの設定」タブをクリックし、モジュールツインを入力します。
初めは2秒周期に設定してみます。
入力したら青い「追加」ボタンをクリックします。Module Twin Settings{ "ert3add2c": { "interval_sec": 2 } }
ルート
-
「ルート」タブをクリックします。
既に入力されているPythonModuleToIoTHubに倣ってIoT EdgeモジュールからIoT Hubへデータを送信するルートの設定を行います。
以下のように名前、値を入力します。名前 値 yourModuleToIoTHub FROM /messages/modules/yourModule/outputs/* INTO $upstream -
PythonModuleToIoTHubとsensorToPythonModuleの設定を右のごみ箱マークをクリックして削除します。
1.で入力した設定のみになったら上部の「確認および作成」タブか下部の「次へ: 確認および作成」ボタンをクリックします。
確認および作成
設定内容が正しいか確認します。
「検証に成功しました。」の表示があり、配置に表示されるJSON形式の設定テキスト内に入力・削除した内容が反映されていることを確認します。
問題なかったら下部の青い「作成」ボタンをクリックして設定を適用します。
モジュールの動作確認
IoT Hubでデプロイの成功と動作状況を、Azure IoT ExplorerでIoT Hubへデータを送信できていることを確認します。
IoT Hubでの確認
「作成」ボタンをクリックすると自動的にIoT Edgeモジュールの情報画面に戻ります。
以下の内容を確認します。
- 「IoT Edgeランタイムの応答」が「200 -- OK」であること
- 「モジュール」のリストに表示されているのが「$edgeAgent」、「$edgeHub」、デプロイした「yourModule」であり、ランタイムの状態が全て「running」であること
※新たにデプロイしたIoT Edgeモジュールはランタイムの状態がrunningになるまで数分かかります。
もし上記のようにならない場合は暫く待ち、画面上部の更新ボタンをクリックし最新の情報に更新してから再度確認します。
同様に、消去した設定が反映されるのにもしばらく時間がかかります。
Azure IoT Explorerでの確認
前回の記事のモジュールの動作確認の項目と同じ手順で確認できます。
Azure IoT Explorerで受信したTelemetryが以下のようなフォーマットであれば作成したIoT Edgeモジュールが正しく動作しています。
今回は8チャネルのF3AD08を使用しているため、8チャネル分のデータが送信されています。
{
"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からの設定手順を紹介します。
-
Azure Portalからモジュールセクションの1.の手順でデバイス設定を開き、モジュールのリストから変更したいIoT Edgeモジュールを選んでクリックします。
-
IoT Edgeモジュールの更新画面が右側に表示されるので、「モジュールツインの設定」タブをクリックします。
-
テキストボックスにモジュールツインを入力したり、既に入力されている場合は変更して更新します。
-
最後に、「確認および作成」ボタンをクリックし、設定内容に問題が無ければ更新します。
機能と設定例
周期の変更
interval_sec
の値を任意の値にすることでデータ収集周期を変更できます。
以下の例は収集周期を5秒に変更する場合です。
{
"ert3add2c": {
"interval_sec": 5
}
}
収集停止
データの収集を停止したい場合は interval_sec
の値を0に設定します。
設定が反映されるとメッセージがIoT Hubに送信されなくなります。
IoT Edgeモジュールそのものが停止するのではないため、再度1より大きい周期を設定するとメッセージが送信されるようになります。
{
"ert3add2c": {
"interval_sec": 0
}
}
次回
今回は特に外部からデータが入力されていない状態で、IoT Edgeモジュールでアナログ入力モジュールのデータを読み出しました。
次回はさらにe-RT3 Plusにアナログ出力モジュールを実装し、データを書き込みする別のIoT Edgeモジュールを作成してデプロイします。
そのデータをアナログ入力モジュールへ入力し、IoT Hubへ送信されるデータがどのように変化するかを確かめます。
→次回