Armadillo-IoT G3でMicrosoft Azure IoT Edgeを動かす

## この記事の目的

独自のMicrosoft Azure IoT Edge(以下IoT Edge)のモジュールをビルドし、Armadillo-IoT G3(以下Armadillo-IoT)上で動作させます。

## 前提条件

+ Azureへの登録が完了し、ポータルをすぐに利用できる状態にあることをご確認ください。

+ Azure CLIを利用します。右のウェブサイトに従い、インストール及びログインを完了させてください。 [Windows での Azure CLI のインストール](https://docs.microsoft.com/ja-jp/cli/azure/install-azure-cli-windows?view=azure-cli-latest)

+ 次の記事の内容を理解する必要があります。 [Azure IoT Edgeとは | Microsoft Docs](https://docs.microsoft.com/ja-jp/azure/iot-edge/about-iot-edge)

+ VSCodeの拡張機能を利用してモジュールを作成/ビルド/デプロイします。VSCodeを利用できる状態にしてください。

## IoT Edgeの準備

モジュールを動作させるために、IoT Edgeを利用する準備を行います。

以下の手順に従って操作してください。

1. 左のペインから、全てのサービスをクリックし、その中からIoT Hubを選択してください。（画面上部のフィルターと表記されたテキストボックスより検索が行えます。）

2. 左上の「追加」をクリックします。

3. IoT Hub Nameを設定します。英数字とハイフンが利用できますが、英数字で始まり英数字で終わる名前にする必要があります。

4. Size and scaleへ移動し、Pricing and scale tierからF1: Free tierを選択します。

5. 入力内容を確認し、createをクリックします。

6. 先ほど作成したIoT Hubをクリックし、内部左のペインからIoT Edgeを選択してください。

7. 左上のAdd an IoT Edge deviceをクリックします。

8. 有効なDevice IDを入力し、Saveをクリックします。

9. 作成されたIoT Edge deviceを選択し、上から4番目のConnection string (primary key)をコピーします。何らかの方法でメモすることをお勧めします。

## Armadillo-IoTへのIoT Edgeの導入

モジュールをArmadillo-IoT上にデプロイする前に、Armadillo-IoTへのIoT Edgeの導入が必要です。

以下のテキストをターミナルにペーストすることで、全てのインストールが完了します。
ただし、rootでのログインが前提です。

```
# Install moby-engine
curl -L https://aka.ms/moby-engine-armhf-latest -o moby_engine.deb && dpkg -i ./moby_engine.deb

# Install moby-cli
curl -L https://aka.ms/moby-cli-armhf-latest -o moby_cli.deb && dpkg -i ./moby_cli.deb

# Install libiothsm-std
curl -L https://aka.ms/libiothsm-std-linux-armhf-latest -o libiothsm-std.deb && dpkg -i ./libiothsm-std.deb

# Install IoT Edge Security Daemon
curl -L https://aka.ms/iotedged-linux-armhf-latest -o iotedge.deb && dpkg -i ./iotedge.deb

apt install -f
```

その後、`/etc/iotedge/config.yaml`をエディタで開き、provisioningのセクションのコメントを解除し、先ほどコピーしたConnection string (primary key)をペーストします。

```
...
provisioning:
  source: "manual"
  device_connection_string: "ここにペースト"
...
```

その後、デーモンを再起動します。

```
# systemctl restart iotedge
```

## VSCodeへのAzure IoT Edge拡張機能のインストール及び準備

VSCodeにAzure IoT Edge拡張機能をインストールすることで、モジュールを開発することができます。以下はその手順です。

1. VSCodeを起動し、左のバーから拡張機能を選択し、「IoT Edge」でMarketplaceを検索してください。

2. Azure IoT Edgeを選択し、インストールします。依存している拡張機能も同時にインストールされます。

3. Ctrl+Shift+Pよりコマンドパレットを開き、Azure: Sign inをクリックしてください。(テキストボックスに一部を入力することで補完されます。)

## コンテナーレジストリの準備

モジュールはAzureのコンテナーレジストリにプッシュしてからデプロイされます。

ソリューションを作成する段階でコンテナーレジストリが必要になるので、先に作成します。

1. 「すべてのサービス」からコンテナーレジストリを選択します。

2. 左上の「追加」をクリックします。

3. レジストリ名を入力し、他の項目を適宜変更します。

レジストリ名は次の項で使用します。

## モジュールの開発

先ほどインストールした拡張機能を用いて、モジュールを開発します。

以下の手順でソリューションを作成します。

1. Ctrl+Shift+Pでコマンドパレットを開き、Azure IoT Edge: New IoT Edge Solutionをクリックしてください。

2. ソリューションを作成するディレクトリを選択してください。

3. ソリューションの名前を入力します。今回はCPUTempとします。

4. 今回はPythonを用いてモジュールを開発するため、Python Moduleを選択します。

5. モジュール名を入力します。ここではCPUTempとします。

6. コンテナーレジストリのURLを入力します。ここでは、localhost:5000の部分を「(レジストリ名).azurecr.io」に置き換えます。これ以外を置き換えると正しく動作しない可能性があります。

指定されたディレクトリにソリューションが作成され、自動で開かれます。

## ソリューションへのファイルの追加

ソリューションは、作成された時点ではモジュールとして即座に動作することはできません。

そこで、一部のファイルを書き換えます。

以下に、書き換えるべきファイルの名前と、その内容を表示します。

---

`Dockerfile.arm32v7`

()で囲まれた部分を置き換えてください。

``` Dockerfile
FROM arm32v7/debian:stretch-slim

WORKDIR /app

RUN apt-get update && \
    apt-get install -y --no-install-recommends libboost-python1.62.0 python3-pip libpython3-dev libcurl3 && \
    rm -rf /var/lib/apt/lists/* 

COPY requirements.txt ./
RUN pip3 install -r requirements.txt

COPY . .

RUN useradd -ms /bin/bash moduleuser
USER moduleuser

ENTRYPOINT [ "python3", "-u", "./main.py" ]

ENV IOTEDGE_AUTHSCHEME=sasToken \ 
    IOTEDGE_DEVICEID=(Azure IoT Edgeに登録したデバイスのID) \
    IOTEDGE_WORKLOADURI=unix:///var/run/iotedge/workload.sock \
    IOTEDGE_MODULEGENERATIONID=(下記の方法で取得したID) \
    IOTEDGE_GATEWAYHOSTNAME=Armadillo \
    IOTEDGE_MODULEID=CPUTemp \
    IOTEDGE_IOTHUBHOSTNAME=(Azure PortalのIoT Hubの概要から閲覧できるホスト名)
```

VSCodeの左下の「AZURE IOT HUB DEVICES」からデバイス名を右クリックし、「Get Device Info」を選択してください。

JSONでデバイスの情報が表示され、その先頭に近いところにgenerationIdが表示されます。これを上記のファイルの指定された場所にペーストしてください。

`main.py`

``` Python
# 次の行のコメントは、pylintによるモジュールが存在しないことの指摘を防ぎます。
# pylint: disable=E0611
from iothub_client import IoTHubModuleClient, IoTHubClientError, IoTHubTransportProvider
from iothub_client import IoTHubMessage, IoTHubMessageDispositionResult, IoTHubError
import time
import traceback

# IoT Edgeを操作するクラスです。このファイルをインポートすることで利用できます。
class MQTTHubManager:
	def __init__(self):
		protocol = IoTHubTransportProvider.MQTT
		
		timeout = 10000
		self.client = IoTHubModuleClient()
		self.client.create_from_environment(protocol)

		self.client.set_option("messageTimeout", timeout)
	
	# メッセージ文字列を送信します。
	def sendMessage(self, queueName, messageString):
		message = IoTHubMessage(messageString)
		self.client.send_event_async(queueName, message, self.callback, 0)

	# コールバック関数です。通常、手動で呼び出すことはありません。
	def callback(self, message, result, context):
		print("result %s" % result)

def main():
	try:
		print("Starting Module...")
		hubManager = MQTTHubManager()
		tempFile = "/sys/class/thermal/thermal_zone1/temp" # CPUの温度情報ファイルです。現在の温度の1000倍の整数値が格納されています。
		print("Ready")

		while True:
			fileHandler = open(tempFile)
			tempStr = fileHandler.read()
			if tempStr is not None:
				temp = str(int(tempStr.strip()) / 1000.0)
				hubManager.sendMessage("temp", temp)
			fileHandler.close()
			time.sleep(300)
	except IoTHubError as e: # IoT Edgeからの例外に対応します。
		print("Unexpected Error: %s" % e)
		return
	except KeyboardInterrupt: # CTRL+Cの入力に対応します。
		print("OK.")
		return
	except Exception as e: # その他の例外についてはトレースバックを表示します。
		traceback.print_exc()

# このファイルがモジュールとしてインポートされていない場合にはmain()を呼びます。
if __name__ == "__main__":
	main()
```

`../config/deployment.json`

このファイルとディレクトリは最初は存在しないため、手動で作成してください。

()で囲まれた部分を置き換えてください。

``` JSON
{
    "modulesContent": {
        "$edgeAgent": {
            "properties.desired": {
                "schemaVersion": "1.0",
                "runtime": {
                    "type": "docker",
                    "settings": {
                        "minDockerVersion": "v1.25",
                        "loggingOptions": "",
                        "registryCredentials": {
							"(コンテナーレジストリ名)": {
								"username": "(コンテナーレジストリ名)",
								"password": "(下記の方法で取得できるパスワード)",
								"address": "(コンテナーレジストリ名).azurecr.io"
                            }
                        }
                    }
                },
                "systemModules": {
                    "edgeAgent": {
                        "type": "docker",
                        "settings": {
                            "image": "mcr.microsoft.com/azureiotedge-agent:1.0",
                            "createOptions": "{}"
                        }
                    },
                    "edgeHub": {
                        "type": "docker",
                        "status": "running",
                        "restartPolicy": "always",
                        "settings": {
                            "image": "mcr.microsoft.com/azureiotedge-hub:1.0",
                            "createOptions": "{}"
                        }
                    }
                },
                "modules": {
					"CPUTemp": {
						"version": "0.0.1",
						"type": "docker",
						"status": "running",
						"restartPolicy": "always",
						"settings": {
							"image": "(コンテナーレジストリ名).azurecr.io/cputemp:0.0.1-arm32v7",
							"createOptions": "{}"
						}
					}
                }
            }
        },
        "$edgeHub": {
            "properties.desired": {
                "schemaVersion": "1.0",
                "routes": {
                    "route": "FROM /* INTO $upstream"
                },
                "storeAndForwardConfiguration": {
                    "timeToLiveSecs": 7200
                }
            }
        },
        "tempSensor": {
            "properties.desired": {}
        }
    }
}
```

パスワードはポータルより、

コンテナーレジストリ -> (コンテナーレジストリ名) -> アクセスキー

にて取得できます。

---

これらのファイルを置き換えることで、モジュールのビルドの準備が完了します。

## モジュールのビルドとプッシュ

モジュールのビルドはコマンドパレットより行えます。

Ctrl+Shift+Pでコマンドパレットを開き、Build and Push IoT Edge Module Imageを選択します。

ソリューション内に作成したモジュールを選択し、プラットフォームとしてarm32v7を選択します。

これにより、モジュールイメージのビルドとプッシュが完了します。

## モジュールのデプロイ

その後、モジュールをデプロイします。

AZURE IOT HUB DEVICESから対象のデバイスを右クリックし、「Create Deployment for Single Device」を選択します。

そして、先ほど作成した`config/deployment.json`を選択します。

OUTPUTに「Deployment succeeded」と表示されればデプロイ完了です。

右下のデバイス横の更新マークを押し、CPUTempモジュールの状態がrunningであることをご確認ください。

## モジュールの動作の観測

モジュールが動作しているか確認するために、PC側のAzure CLIからイベントの監視を行います。

まず、Azure CLIに拡張機能をインストールします。

```
$ az extension add --name azure-cli-iot-ext
```

そして、イベントの監視を開始します。

```
az iot hub monitor-events --login '接続文字列'
```

接続文字列は、「AZURE IOT HUB DEVICES」のデバイス名を右クリックし、「Copy Connection String」をクリックすることで取得できます。

5分ごとにCPUの温度が数値で送られてきていれば成功です。

(JSON形式で送られているデータもありますが、これはtempSensorという別のモジュールのものです。)

---

以上でモジュールの開発手順は終了です。お疲れさまでした。