3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

エッジデバイスにデータを書き込むAzure IoT EdgeのPythonモジュールを作成する

Last updated at Posted at 2021-05-12

はじめに

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

また、前回の記事でエッジデバイスのデータを収集しIoT Hubへ送信するPythonモジュールを作成し、デプロイする手順を説明しています。

さて、今回はUbuntu 18.04搭載のエッジデバイスにデータを書き込むIoT EdgeのPythonモジュールを作成してみます。

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

環境

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

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

    • e-RT3 Plus F3RP70-2L(CPUモジュール)
      モジュール一式を制御します。
      CPUモジュールから各モジュールへアクセスし、書き込みや読み取り等を行えます。
    • F3AD08-6R(アナログ入力モジュール)
      アナログ入力モジュールは、外部から入力されたアナログデータをデジタルデータに変換します。
    • F3DA04-6R(アナログ出力モジュール)
      CPUモジュールから入力されたデジタルデータをアナログデータに変換して出力します。
      今回はこちらのモジュールにデータを書き込みます。
    • F3BU05-0D(ベースモジュール)
      各モジュールをセットするベースです。
      電源の供給や、モジュール間の通信はベースモジュールを介して行われます。
    • F3PU20-0S(電源モジュール)
      電源です。ベースモジュールにセットされます。

    ※写真はチャネル1、2のみ接続していますが、実験時はチャネル1~4を接続しています。

    moduleset_n002.png

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

IoT EdgeとDockerのバージョン

Terminal
$ iotedge --version
iotedge 1.1.1
$ docker --version
Docker version 20.10.5+azure, build 55c4c88966a912ddb365e2d73a4969e700fc458f

ゴール

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

今回はAzure IoT EdgeランタイムをインストールしたUbuntu 18.04搭載のエッジデバイスで動作するPythonのデータ書き込みモジュールを作成し、アナログ出力モジュールへデータを書き込むことをゴールとします。

具体的な流れは以下の通りです。

  1. PythonのIoT Edge書き込みモジュールを作成し、F3RP70へデプロイする
  2. IoT Edge書き込みモジュールアナログ出力モジュールのチャネル1~チャネル4に100ミリ秒周期でデータを書き込む
  3. 前回デプロイしたIoT Edgeデータ収集モジュールアナログ入力モジュールの全チャネルのデータを2秒周期で収集し、IoT Hubへ送信する
  4. Iot Hubへ送信されたデータをAzure IoT Explorerで確認する

goal_001-1.png

準備

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

開発環境

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

モジュールの作成

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

  1. 主な機能

    • アナログ出力モジュールの各出力チャネルに4種類の波形を個別に出力できる
      sin波、三角波、矩形波、のこぎり波の4種類です。
      また、アナログ出力モジュールにアクセスするためのライブラリはIoT Edgeモジュールには含めず、CPUモジュール(ホスト)のライブラリをバインドして使用します。
    • 各出力チャネルへの書込み周期は固定で100ミリ秒
  2. モジュールツイン
    各出力チャネルに書き込む波形、周期、振幅、オフセットの設定はモジュールツインを使用します。
    各パラメータの説明と設定例は以下の通りです。

    パラメータ 内容
    1ch - 4ch チャネル毎の設定
    form 波形
    cycle_sec 波形の周期
    amplitude 波形の振幅
    offset 波形のオフセット

    例:チャネル1に周期60秒・振幅2000・オフセット100のsin波、チャネル2に周期120秒・振幅1000・オフセット-100の三角波、チャネル3に周期30秒・振幅500・オフセット0の矩形波、チャネル4に周期40秒・振幅1500・オフセット-500ののこぎり波を書き込む設定

モジュールツインの設定
{
    "ert3daout": {
        "ch1": {
            "form": "sin",
            "cycle_sec": 60,
            "amplitude": 2000,
            "offset": 100
        },
        "ch2": {
            "form": "triangle",
            "cycle_sec": 120,
            "amplitude": 1000,
            "offset": -100
        },
        "ch3": {
            "form": "square",
            "cycle_sec": 30,
            "amplitude": 500,
            "offset": 0
        },
        "ch4": {
            "form": "sawtooth",
            "cycle_sec": 40,
            "amplitude": 1500,
            "offset": -500
        }
    }
}

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

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

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

  • Solution Name: Ert3WriteDA
  • Module Template: Python Module
  • Module Name: Ert3WriteDAModule

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

モジュールのコード

作成したプロジェクトの main.py を以下のコードに置き換えて保存します。

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

DESIRED_KEY = 'desired'
ERT3DAOUT_KEY = 'ert3daout'
FORM_KEY = 'form'
FORM_SIN_TAG = 'sin'
FORM_TRIANGLE_TAG = 'triangle'
FORM_SQUARE_TAG = 'square'
FORM_SAWTOOTH_TAG = 'sawtooth'
CYCLE_SEC_KEY = 'cycle_sec'
AMPLITUDE_KEY = 'amplitude'
OFFSET_KEY = 'offset'
STATUS_KEY = 'status'
CYCLE_SEC_DEFAULT = 60
AMPLITUDE_DEFAULT = 2000
OFFSET_DEFAULT = 0
CHTAG_KEYS = ['ch1', 'ch2', 'ch3', 'ch4', 'ch5', 'ch6', 'ch7', 'ch8']
FAM3DA_CHNUM = {'DA04': 4, 'DA08': 8}
UNIT = 0
SLOT = 3
INTERVAL_SEC = 0.1
LDCONFIGEXEC = 'ldconfig'
M3LIB_PATH = '/usr/local/lib/libm3.so.1'


class DaOut():
    def __init__(self, unit, slot, interval_sec):
        self.__unit = unit
        self.__slot = slot
        self.__interval_sec = interval_sec
        self.__libc = ctypes.cdll.LoadLibrary(M3LIB_PATH)
        self.__libc.getM3IoName.restype = ctypes.c_char_p
        self.__chnum = self.__get_m3da_ch_num()
        self.__counters = [0 for x in range(self.__chnum)]
        self.__configs = {CHTAG_KEYS[x]: {} for x in range(self.__chnum)}
        signal.signal(signal.SIGALRM, self.__signal_handler)
        signal.setitimer(signal.ITIMER_REAL, interval_sec, interval_sec)

    def __get_m3da_ch_num(self):
        namebytes = self.__libc.getM3IoName(
            ctypes.c_int(self.__unit), ctypes.c_int(self.__slot))
        num = 0
        if namebytes is not None:
            num = FAM3DA_CHNUM.get(namebytes.decode(), 0)
        return num

    def __write_m3da_ch_data(self, ch, data):
        short_arr = ctypes.c_short * 1
        ch_data = short_arr(data)

        self.__libc.writeM3IoRegister(
            ctypes.c_int(self.__unit),
            ctypes.c_int(self.__slot),
            ctypes.c_int(ch),
            ctypes.c_int(1),
            ch_data)

    def __da_form_out(self, ch, form, interval, cycle, ampl, offset, count):
        data = 0.0
        count = count % (cycle / interval)

        if form == FORM_SIN_TAG:
            data = ampl * math.sin(math.pi * 2 * interval * count / cycle)

        elif form == FORM_TRIANGLE_TAG:
            data = count * interval * ampl * 4 / cycle
            if ampl * 3 < data:
                data = data - (ampl * 4)
            elif ampl < data:
                data = (ampl * 2) - data

        elif form == FORM_SQUARE_TAG:
            data = ampl
            if ((cycle / interval) / 2) < count:
                data = - data

        elif form == FORM_SAWTOOTH_TAG:
            data = (count * interval * ampl * 2 / cycle) - ampl

        data = data + offset

        self.__write_m3da_ch_data(ch, round(data))

    def __signal_handler(self, signum, frame):
        for id in range(self.__chnum):
            if FORM_KEY in self.__configs[CHTAG_KEYS[id]]:
                self.__da_form_out(
                    id + 1,
                    self.__configs[CHTAG_KEYS[id]][FORM_KEY],
                    INTERVAL_SEC,
                    self.__configs[CHTAG_KEYS[id]].get(CYCLE_SEC_KEY, CYCLE_SEC_DEFAULT),
                    self.__configs[CHTAG_KEYS[id]].get(AMPLITUDE_KEY, AMPLITUDE_DEFAULT),
                    self.__configs[CHTAG_KEYS[id]].get(OFFSET_KEY, OFFSET_DEFAULT),
                    self.__counters[id]
                    )
                self.__counters[id] = self.__counters[id] + 1

    def set_condition(self, desired):
        if ERT3DAOUT_KEY not in desired:
            return {STATUS_KEY: False}

        for id in range(self.__chnum):
            if CHTAG_KEYS[id] not in desired[ERT3DAOUT_KEY]:
                self.__configs[CHTAG_KEYS[id]] = {}
                self.__counters[id] = 0
            elif not self.__configs[CHTAG_KEYS[id]] == desired[ERT3DAOUT_KEY].get(CHTAG_KEYS[id], {}):
                self.__configs[CHTAG_KEYS[id]] = desired[ERT3DAOUT_KEY].get(CHTAG_KEYS[id], {})
                self.__counters[id] = 0

        reported = {STATUS_KEY: True}
        return reported


def da_out():
    module_client = IoTHubModuleClient.create_from_edge_environment()
    module_client.connect()
    twin = module_client.get_twin()

    daout = DaOut(UNIT, SLOT, INTERVAL_SEC)
    reported = daout.set_condition(twin.get(DESIRED_KEY, {}))
    module_client.patch_twin_reported_properties(reported)

    while True:
        reported = daout.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])

    da_out()

モジュールのプッシュ

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

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

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

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

モジュールのデプロイ

モジュールツインの設定とルートの設定手順以外は前回の記事で紹介したものと同じのため、前回のモジュールのデプロイの章を見ながらデプロイしてください。
異なる点のみ説明します。

  • モジュールツインの設定
    モジュールの作成のモジュールツインの設定例にならったフォーマットで入力します。

  • ルートの設定
    このモジュールのデプロイに伴う新たな設定は不要なので何もしません。

モジュールの動作確認

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

IoT Hubでの確認

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

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

※変更を加えたモジュールの設定が適用されるまで数分かかります。
もし上記の通りにならない場合は暫く待ち、画面上部の更新ボタンをクリックし最新の情報に更新してから再度確認します。

iothub_001.png

Azure IoT Explorerでの確認

前回の記事のモジュールの動作確認の項目と同じ手順で確認できます。
1~4チャネルの値を見ると、今回デプロイしたモジュールの入力が反映されていることが分かります。

Telemetry
{
  "body": {
    "messageID": 336,
    "deviceID": "test_ert3_f3rp70",
    "datetime": "2021-04-07T02:30:53.839398Z",
    "ch1": -358,
    "ch2": 827,
    "ch3": 501,
    "ch4": 413,
    "ch5": 0,
    "ch6": 2,
    "ch7": 5,
    "ch8": 1
  },
  "enqueuedTime": "2021-04-07T02:30:53.847Z",
  "properties": {}
}

次回

今回はアナログ出力モジュールにデータを書きこむIoT Edgeモジュールを作成し、デプロイしました。
そしてIoT Edgeデータ収集モジュールでアナログ入力モジュールのデータを収集してIoT Hubへ送信し、前回とは異なり値が入力されていることを確認できました。
次回は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 からのリアルタイム センサー データを視覚化する

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

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

3
2
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
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?