[Azure] Azure IoT Edge を使って取得した画像を Azure に送ってみる

# やりたいこと
今回、デバイス側で撮影した動画から、キャプチャーを取得して、取得した画像を Azure Blob Storage に格納するというシナリオを、IoT Edge を利用して試してみました。参考にしたサイトは[こちら](https://github.com/ms-iotkithol-jp/IoTKitHoLV5)。
少し長くなりましたが、分かりやすく記載しましたので参考にしてみてください。

このシナリオ、いろいろなシーンに応用が可能だと思います。
例えば、Factory IoT（工場内の検査工程とか）、車載器（車とかAGV等）、建物内の監視など。

![Overview.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/6cf9230b-e0d3-2f4a-5572-69d0aab1b145.jpeg)



# IoT Edge とは
これまでクラウドで行っていた分析とカスタムビジネスロジックをデバイス側で実行できるようにするものです。
IoT Edge は、次の3つのコンポーネントで構成されています。詳細は[こちら](https://docs.microsoft.com/ja-jp/azure/iot-edge/about-iot-edge)を参照ください。

+ IoT Edge モジュール
  + IoT Edge のモジュールは、実行の単位となるもので、Docker 互換のコンテナ―として実装されます。
  + 互いに通信を行う複数のモジュールを構成することで、データ処理のパイプラインを作成することが可能です。
  + モジュールには、Azure のサービスである、Azure Functions、Azure Stream Analytics、Azure Machine Learning が利用可能です。
+ IoT Edge ランタイム
  + 個々の IoT Edge デバイス上で動作し、各デバイスにデプロイされたモジュールを管理します。
  + デバイスにワークロードをインストールし、更新します。
  + デバイス上の IoT Edge セキュリティ標準を維持します。
  + モジュールの正常性をクラウドにレポート（IoT Hub に対し）してリモート監視を可能にします。
+ クラウドベースのインターフェイス
  + IoT Edge デバイスをリモートから監視して管理します。
  + まとまったデバイスに対してワークロードを送信します。

個人的に <b>IoT Edge の優位性は、Docker 互換のコンテナーである</b>ところだと思っています。
あとで手順がありますが、Azure Container Registry に Push した Docker Image の最新情報を IoT Hub を介して取得して、最新の Docker Image を Pull できる点は、Edge デバイスの管理を楽にし、開発を容易にしていると思います。そのため、昨今話題の DevOps にも対応しやすい作りになっていると感じています。 


# 環境情報
### デバイス側の環境情報
+ Device : Raspberry Pi 3 Model b+
+ OS : Raspbian-stretch
  + https://downloads.raspberrypi.org/raspbian/images/raspbian-2019-04-09/ からダウンロード
+ Camera : [Raspberry Pi 用のカメラ](https://www.amazon.co.jp/gp/product/B07NSS1QRW/ref=ppx_yo_dt_b_asin_title_o03_s00?ie=UTF8&psc=1)

[IoT Edge によってサポートされている OS 情報](https://docs.microsoft.com/ja-jp/azure/iot-edge/support) を参照してください。
Raspbian でサポートしているのは、Raspbian-stretch なので、これをあらかじめダウンロードしておく必要があります。
Raspberry Pi を購入した時点では、Raspbian の最新 OS である Raspbian Buster の場合がありますので、注意してください。

Raspberry Pi の OS のバージョンを確認するには、[こちら](https://qiita.com/fujit33/items/16ce964c228a63a9d8dd)を参考にしました。
取り付けたカメラがきちんと動作するかどうかの確認は[こちら](https://www.pc-koubou.jp/magazine/17276)を参考にしました。


### クラウド側の環境情報
+ 利用可能な Azure サブスクリプション
+ 利用するサービス
  + Azure IoT Hub
  + Azure Container Registry
  + Azure Blob Storage


### 開発環境
+ Visual Studio Code
  + ない場合は[こちら](https://code.visualstudio.com/download)からダウンロード
+ 拡張オプションのインストール
  + Azure Tools
  + Azure IoT Hub Toolkit
  + Azure IoT Edge
  + Python （開発に使用する言語向け拡張）


# 手順
それでは実際にはじめます。今回は、以下の12手順で実施しました。

1）IoT Hub の作成
2）IoT Edge デバイスの登録
3）IoT Edge のインストール
4）Azure Container Registry の作成
5）Storage Account の作成
6）開発環境のセットアップ
7）Visual Studio Code で IoT Edge Solution を作成
8）Blob on Edge Module の追加
9）camera-capture Module の追加
10) Solution の Build と Azure Container Registry への Push
11) Module 配置情報を IoT Hub に転送
12) 動作確認


## 1）IoT Hub の作成
[Azure Portal](https://ms.portal.azure.com/) にアクセスし、左のメニューで「リリースの作成」をクリックします。
次に、「モノのインターネット(IoT)」をクリックし、「IoT Hub」をクリックします。
![IoTHub01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/31f1237e-825e-a3ff-5eee-fdcdb0a2fd2a.jpeg)

画面が変わるので、以下の項目を設定し、「次へ:サイズとスケール」をクリックします。

| 設定項目 | 設定値 |
|:----|:----|
| サブスクリプション | お持ちのサブスクリプションを選択。 |
| リソースグループ | 新規作成の場合は、下にある青字の「新規作成」を選択し、任意の名称を入力。既存のものがある場合は選択。 |
| リージョン | 任意の場所を選択する。 |
| IoT Hub 名 | 任意の名称を入力。（緑のチェックマークが出ればOK） |
![IoTHub02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/bdd7c6ca-506f-f24a-45fa-c9b52ef0516c.jpeg)

ここでは、標準の設定のままで良いです。「価格とスケールティア」は自分の環境に応じて設定してください。
「確認および作成」をクリックします。
![IoTHub03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/28f56aee-3f26-2749-886c-5eb3a9523ed3.jpeg)

確認画面が表示されるので、内容を確認し、「作成」をクリックします。
![IoTHub04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/49f344b1-7ef5-e38d-95d6-b313c7b663c1.jpeg)

IoT Hub の作成が完了です。
![IoTHub05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/4c34e09d-a5ce-e210-bff3-205e85a04171.jpeg)


## 2）IoT Edge デバイスの登録
次に、IoT Hub 上で管理する IoT Edge デバイスの登録を行います。

IoT Hub のメニューの中から「IoT Edge」をクリックします。
右側の画面上部にある「IoT Edge デバイスを追加する」をクリックし、IoT Edge をインストールするデバイスの情報を登録します。
この登録によって、IoT Hub と IoT Edge をインストールするデバイスを紐づけます。
![IoTHub06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/9a3c1df1-e0ba-414e-98d3-e1299361b8e4.jpeg)

以下の項目を設定し、「保存」をクリックします。

| 設定項目 | 設定値 |
|:----|:----|
| デバイス ID | 任意の名称を入力。（緑のチェックマークが出ればOK） |
| 認証の種類 | 対称キー/X.509 自己署名済み　のいずれかを選択。ここでは「対称キー」を選択します。 |
| 主キー | 空欄で OK。 |
| セカンダリキー | 空欄で OK。 |
| 自動生成キー | チェックを入れる。これで主キーとセカンダリキーを自動で作成してくれる。 |
| このデバイスを IoT ハブに接続する | 有効/無効　のいずれかを選択。ここでは「有効」を選択します。 |
| 子デバイス | IoT Edge に紐づく子デバイスの設定。ここでは子デバイスはないので、何も設定しない。 |
![IoTHub07.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/c46c2a2a-b62f-5e33-1440-9b5b97c26e87.jpeg)

IoT Edge デバイスが登録されたことを確認します。
先ほど作成したデバイス ID をクリックして、接続文字列を確認します。
![IoTHub08.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/d62c49b3-ac83-da29-de2d-79555dc6fc4c.jpeg)

キーや接続文字列は秘密情報のため、*にて伏字になっている。
プライマリ接続文字列をこのあとの手順で利用するためひかえておきます。
「HostName=myIoTH001.azure-devices.net;DeviceId=raspi3bp;SharedAccessKey=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX」のような形式。
![IoTHub09.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/a1e13f81-42db-8f45-52cc-bbcf6124021b.jpeg)


## 3）IoT Edge のインストール
これまでは Azure Portal で作業をしていたが、この手順では Raspberry Pi へ IoT Edge をインストールします。
まず最初に Raspberry Pi の初期設定を行います。

+ SD カードに Raspbian-stretch をインストールし、Raspberry Pi を起動します
+ Raspberry Pi 上で初期設定を行います
  + WiFi の設定
  + ラズパイアイコン → 設定 → Raspberry Pi の設定
  + Interfaces タブを選択
  + Camera、SSH、SPI、I2C、Serial Port、1-Wire を有効に設定し再起動
+ 再起動後、右上の WiFi マークにカーソルをあわせて、IP アドレスを確認します
+ これで、開発用マシンから Tera Term や Putty 等のツールでリモートでの接続が可能になります

上記が完了したら、Raspberry Pi 上か Tera Term などでリモート接続を行い、以下のコマンドを実行します。
詳細は[こちら](https://docs.microsoft.com/ja-jp/azure/iot-edge/how-to-install-iot-edge-linux)のサイトを参照ください。

### Microsoft キーとソフトウェアリポジトリフィードを登録
以下の4つのコマンドを bash 環境で入力します。

```bash
curl https://packages.microsoft.com/config/debian/stretch/multiarch/prod.list > ./microsoft-prod.list
sudo cp ./microsoft-prod.list /etc/apt/sources.list.d/
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
sudo cp ./microsoft.gpg /etc/apt/trusted.gpg.d/
```

### コンテナランタイムをインストール
以下の3つのコマンドを bash 環境で入力します。
Moby ベースのコンテナエンジンは、IoT Edge で公式にサポートされている唯一のものです。
Docker CE/EE コンテナー イメージは、Moby ランタイムと互換性があります。

```bash
sudo apt-get update
sudo apt-get install moby-engine
sudo apt-get install moby-cli
```

### Azure IoT Edge セキュリティデーモンのインストール
以下の2つのコマンドを bash 環境で入力します。

```bash
sudo apt-get update
sudo apt-get install iotedge
```

### セキュリティデーモンを構成する
以下のコマンドを bash 環境で入力します。

```bash
sudo vi /etc/iotedge/config.yaml
```

上記コマンドを入力したことで、config.yaml の設定が可能になります。
以下の「Manual provisioning configuration」部分に、IoT Hub にて登録した IoT Edge デバイスの接続文字列を、以下の「device_connection_string」にコピーし、保存します。（先ほどの手順でひかえておいた文字列です）

```
# Manual provisioning configuration
provisioning:
  source: "manual"
  device_connection_string: "HostName=myIoTH001.azure-devices.net;DeviceId=raspi3bp;SharedAccessKey=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
```

### セキュリティデーモンの再起動
以下のコマンドを bash 環境で入力し再起動します。

```bash
sudo systemctl restart iotedge
```

### インストールの成功を確認する
以下の3つのコマンドを bash 環境で入力。Error 等がないことを確認します。
インストールした後は、edgeAgent だけが実行されています。環境によっては少し時間がかかります。

```bash
systemctl status iotedge
journalctl -u iotedge --no-pager --no-full
sudo iotedge list
```

IoT Edge は IoT Hub と通信を行い、ステータス情報を通知します。
Azure Portal の IoT Edge 画面にてランタイムの状態を確認できます。
![IoTHub10.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/e7c9ae9f-55be-57b3-12ac-7b0121a088be.jpeg)


## 4）Azure Container Registry の作成
[Azure Portal](https://ms.portal.azure.com/) にアクセスし、左のメニューで「リリースの作成」をクリックします。
次に、「コンテナ―」をクリックし、「Container Registry」をクリックします。
![ACR01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/3ebefa89-0c3f-0090-80ba-67dcccdb2b82.jpeg)

以下の項目を設定し、「作成」をクリックします。

| 設定項目 | 設定値 |
|:----|:----|
| レジストリ名 | 任意の名称を入力。（緑のチェックマークが出ればOK） |
| サブスクリプション | お持ちのサブスクリプションを選択。 |
| リソースグループ | 新規作成の場合は、下にある青字の「新規作成」を選択し、任意の名称を入力。既存のものがある場合は選択。 |
| 場所 | 任意の場所を選択する。 |
| 管理者ユーザー | 有効/無効 から選択。今回は「有効」を選択。 |
| SKU | Basic を選択。 |
![ACR02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/774d1b22-042a-6333-5fbc-5abbe08b40f0.jpeg)

Azure Container Registry の作成が完了です。
![ACR03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/7fd3ce78-5c6f-d6d9-3957-41ff2d12400b.jpeg)

Azure Container Registry のメニューで、「アクセスキー」をクリックし、後ほど利用する以下の3つの設定値をひかえておきます。

+ ログインサーバー
+ ユーザー名
+ password

![ACR04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/d85f0b1e-2c22-a4e2-203b-5c001aa1dc8d.jpeg)


## 5）Storage Account の作成
[Azure Portal](https://ms.portal.azure.com/) にアクセスし、左のメニューで「リリースの作成」をクリックします。
次に、「ストレージ」をクリックし、「ストレージアカウント」をクリックします。
![Storage01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/6f831c6b-0b1a-a3a6-e69c-ca0245d07521.jpeg)

以下の項目を設定し、「詳細」をクリックします。

| 設定項目 | 設定値 |
|:----|:----|
| サブスクリプション | お持ちのサブスクリプションを選択。 |
| リソースグループ | 新規作成の場合は、下にある青字の「新規作成」を選択し、任意の名称を入力。既存のものがある場合は選択。 |
| ストレージアカウント名 | 任意の名称を入力。（緑のチェックマークが出ればOK） |
| 場所 | 任意の場所を選択する。 |
| パフォーマンス | Standard/Premium から選択。今回は「Standard」を選択。 |
| アカウントの種類 | StorageV2(汎用) を選択。 |
| レプリケーション | ローカル冗長ストレージ(LRS) を選択。 |
| アクセス層(既定) | クール/ホット から選択。今回は「ホット」を選択。 |
![Storage02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/2c24679c-d2a8-6e98-f3ab-e173b1efafba.jpeg)

標準の設定のまま「タグ」をクリックします。
![Storage03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/b773803b-5a1b-9ebe-acf1-683ad9bcb64f.jpeg)

標準の設定のまま「確認および作成」をクリックします。
![Storage04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/26f0d99d-32e3-0146-f510-79446801baad.jpeg)

確認画面が表示されるので、設定内容を確認し、「作成」をクリックします。
![Storage05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/07812dc3-e70e-12f6-638f-510406a699af.jpeg)

Storage Account が無事に作成されました。
次に、画面中央の「BLOB」をクリックし、コンテナ―を作成します。
![Storage06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/c7973537-57ce-8373-136f-c5ebc336907f.jpeg)

画面中央上部にある「コンテナ―」をクリックします。
![Storage07.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/679f13c7-d022-3d56-4278-ef2c2ef92656.jpeg)

名前に「edgephotos」と入力し、「OK」をクリックします。
![Storage08.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/a0c2cb70-8710-e3f2-432b-d789b2f24f0d.jpeg)

Blob コンテナ―が作成されました。
(下図には recognised というコンテナ―もありますが、今回は使用しないので気にしないでください。)
![Storage10.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/87c42948-a8f9-0c92-639c-f6d83db83aba.jpeg)


## 6）開発環境のセットアップ
次に IoT Edge 上にインストールする Module を開発するための開発環境をセットアップします。
ローカル PC を使っていただいても良いですし、Azure 上に開発用 VM 環境を作成しても良いです。

+ Visual Studio Code のインストール
  + https://code.visualstudio.com/download から各自の環境にあわせた Visual Studio Code をダウンロードします
+ 拡張オプションのインストール
  + Azure Tools
  + Azure IoT Hub Toolkit
  + Azure IoT Edge
  + Python (開発に使用する言語向け拡張。今回は Python にした。)

Python のバージョンは3.6を使用するようにしてください。


## 7）Visual Studio Code で IoT Edge Solution を作成
開発用マシンで、任意の場所に「UploadCameraImageEdgeToCloud」というフォルダを作成します。
![VSCode01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/150fcc0b-bd25-ce34-e36b-4b1c3de8399c.jpeg)

Visual Studio Code を起動し、メニューバーの「ファイル」→「フォルダーを開く」→先ほど作成したフォルダ「UploadCameraImageEdgeToCloud」を選択して開きます。
![VSCode02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/541da540-efbb-d48a-2ec7-f0825ddca210.jpeg)

メニューバーの「表示」→「コマンドパレット」でコマンドパレットを起動し、表示された窓に「Azure IoT Edge」と入力します。一覧が表示されるので、「Azure IoT Edge: New IoT Edge Solution」を選択します。
![VSCode03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/376f6540-ea94-6a4f-fdcd-e8b60fa191ef.jpeg)

フォルダーの選択画面にて、現在開いているフォルダー「UploadCameraImageEdgeToCloud」を選択します。
![VSCode04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/15413794-3008-ac34-d776-ef28fecacc9e.jpeg)

ソリューション名「UploadCameraImageEdgeToCloud」を入力し、Enter を押下します。
![VSCode05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/bb2d49ce-9faa-49fb-74bb-c48a53e0f074.jpeg)

Module のプログラミング言語「Python」を選択します。
![VSCode06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/88c27692-eb57-5d6b-b139-294f165494ee.jpeg)

Module 名「StoreImageToLocalBlob」を入力し、Enter を押下します。
![VSCode07.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/11131e26-3172-a349-61e7-5cb5b646a3c5.jpeg)

Docker Container の名前を設定します。「localhost:5000」の部分を、先ほど作成した Azure Container Registry のログインサーバーで置き換えます。ここでは「iotacr001.azurecr.io」で置き換えます。
![VSCode08.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/8c5472ff-3b76-0d88-1bc5-73c517ac8151.jpeg)

以上で、Solution と Python Module のひな型が完成です。
![VSCode09.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/928060dc-a8b3-ef21-9748-7aa49db4e5c2.jpeg)

次に、「.env」に、Azure Container Registry の USERNAME と PASSWORD を設定します。修正したら必ずファイルを保存します。
![VSCode10.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/c4f1469c-1ec6-9e1c-0a41-8d07df8f9719.jpeg)

「deployment.template.json」を編集します。
「SimulatedTemperatureSensor」は、template として勝手に作成されているもので不要なため、削除します（赤枠部分）。
![VSCode11.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/6dba30df-8cb6-5644-185f-035613c9dcf2.jpeg)

次に、Module 名を「SimulatedTemperatureSensor」→「store-image-to-local-blob」に変更します。
![VSCode13.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/855dd4c8-40de-8c52-dd18-5ef73fa5b577.jpeg)

「createOptions」に、以下の「Env」の定義を追加します。

```
"Env":[
  "BLOB_ON_EDGE_MODULE=local-blob-storage",
  "BLOB_ON_EDGE_ACCOUNT_NAME=< local account >",
  "BLOB_ON_EDGE_ACCOUNT_KEY=< local account key >",
  "IMAGE_CONTAINER_NAME=photos",
  "BLOB_UPLOAD_DURATION_SEC=60"
]
```
![VSCode14.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/ad0ecf5a-53a5-3e81-a843-1026e1b0dcd4.jpeg)

Docker Image の種別を変更します。
StoreImageToLocalBlob の後ろに「.」(ピリオド)を入力すると、リストが表示されるので、「arm32v7」を選択します。
修正後、ファイルを保存しておきます。
![VSCode15.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/b4807990-d2e3-61db-a995-68419db140be.jpeg)

「Dockerfile.amd64」と「Dockerfile.amd64.debug」は不要なファイルのため、削除します。今回は Raspberry Pi/Raspbian のみで動作する Module を開発するためです。
![VSCode16.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/c37a7c56-d566-a011-6981-823f0c10de0a.jpeg)

先ほど、「Dockerfile.amd64」と「Dockerfile.amd64.debug」を削除したため、「module.json」から、「amd64」と「amd64.debug」の行を削除します。削除後、ファイルを保存しておきます。
![VSCode17.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/7f3b7485-6568-6b0b-de83-bacabb53be4f.jpeg)

次に「Dockerfile.arm32v7」を以下で丸っと置き換えます。その後ファイルを保存しておきます。

```docker
FROM resin/raspberrypi3-debian:stretch

RUN [ "cross-build-start" ]

WORKDIR /app

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

RUN pip3 install --upgrade pip 
RUN pip install --upgrade setuptools 
COPY requirements.txt ./
RUN pip install -r requirements.txt


RUN [ "cross-build-end" ]

COPY . .

# Expose the port
EXPOSE 80

ENTRYPOINT [ "python3", "./main.py" ]
```
![VSCode18.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/9d42d636-a784-5a22-5949-0a03415279c1.jpeg)

「requirements.txt」を開き、以下で置き換えます。置き換えた後、ファイルを保存しておきます。

```
azure-iothub-device-client
flask
azure-storage-blob
azure-mgmt-storage
```
![VSCode19.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/a523eec5-ef66-5ae5-35e7-242d24be1973.jpeg)


最後に「main.py」を修正します。以下のコードで置き換えます。置き換えた後、ファイルを保存しておきます。
ここまでで、いったん自作 Module の作成が完了です。

```python
# Copyright (c) Microsoft. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for
# full license information.

import os
import random
import time
import sys
import io
import json
import time
import datetime
from azure.storage.blob import BlockBlobService, PublicAccess

# Imports for the REST API
from flask import Flask, request

import iothub_client
# pylint: disable=E0611
from iothub_client import IoTHubModuleClient, IoTHubClientError, IoTHubTransportProvider
from iothub_client import IoTHubMessage, IoTHubMessageDispositionResult, IoTHubError

# messageTimeout - the maximum time in milliseconds until a message times out.
# The timeout period starts at IoTHubModuleClient.send_event_async.
# By default, messages do not expire.
MESSAGE_TIMEOUT = 10000

# global counters
RECEIVE_CALLBACKS = 0
SEND_CALLBACKS = 0

# Choose HTTP, AMQP or MQTT as transport protocol.  Currently only MQTT is supported.
PROTOCOL = IoTHubTransportProvider.MQTT

app = Flask(__name__)

# 4MB Max image size limit
app.config['MAX_CONTENT_LENGTH'] = 4 * 1024 * 1024 

# Blob Upload Setting 
BLOB_UPLOAD_DURATION_SEC = 10 
IMAGE_CONTAINER_NAME = "edgephotos"
blobLastUploadTime = time.time()

blobService = None
storeImageRequestCount = 0

# Default route just shows simple text
@app.route('/')
def index():
    return 'store image to blob on edge harness'

# Like the CustomVision.ai Prediction service /image route handles either
#     - octet-stream image file 
#     - a multipart/form-data with files in the imageData parameter
@app.route('/image', methods=['POST'])
def store_image_handler():
    global storeImageRequestCount
    global blobService
    try:
        storeImageRequestCount += 1
        if ((storeImageRequestCount%10) ==0):
            print("images received - " + str(storeImageRequestCount))
            imageData = None
            if ('imageData' in request.files):
                imageData = request.files['imageData']
            else:
                imageData = io.BytesIO(request.get_data())
                blobService.upload_image_to_blob(imageData)
                print('stored image')
        results = []
        return json.dumps(results)
    except Exception as e:
        print('EXCEPTION:', str(e))
        return 'Error processing image', 500

class BlobService(object):
    def __init__(
            self,
            blobonedge_module_name,
            blob_account_name,
            blob_account_key,
            image_container_name,
            image_upload_duration_sec,
            edge_id
        ):
        localblob_connectionstring = 'DefaultEndpointsProtocol=http;BlobEndpoint=http://'+blobonedge_module_name+':11002/'+blob_account_name+';AccountName='+blob_account_name+';AccountKey='+blob_account_key+';'
        print("Try to connect to blob on edge by "+localblob_connectionstring)
        self.blockBlobService = BlockBlobService(endpoint_suffix='', connection_string=localblob_connectionstring)
        print("Connected to blob on edge")
        self.blockBlobService.create_container(image_container_name)
        print('Created image container - '+image_container_name)
        self.imageContainerName = image_container_name
        self.imageUploadDurationSec = image_upload_duration_sec
        self.edgeId = edge_id
        self.blobLastUploadTime = time.time()

    def upload_image_to_blob(self, image):
        now = datetime.datetime.now()
        currentTime = time.time()
        print('Last Time:'+str(self.blobLastUploadTime)+'->Current:'+str(currentTime))
        if (currentTime - self.blobLastUploadTime > self.imageUploadDurationSec):
            image_file_name = self.edgeId + "-img{0:%Y%m%d%H%M%S}".format(now) +".jpg"
            # image_file_name = "image{0:%Y%m%d%H%M%S}".format(now) +".jpg"
            print('Uploading image as '+image_file_name + ' at ' + str(currentTime))
            self.blockBlobService.create_blob_from_stream(self.imageContainerName, image_file_name, image)
            print('Upload done')
            self.blobLastUploadTime = currentTime


def main(protocol):
    try:
        print ( "\nPython %s\n" % sys.version )

    except KeyboardInterrupt:
        print ( "IoTHubModuleClient sample stopped" )

def initialize_blob_on_edge(blobOnEdgeModule,blobOnEdgeAccountName,blobOnEdgeAccountKey,imageContainerName, blobUploadDurationSec, edgeId):
    blobService = BlobService(blobOnEdgeModule, blobOnEdgeAccountName, blobOnEdgeAccountKey,imageContainerName,blobUploadDurationSec, edgeId)
    return blobService

if __name__ == '__main__':
    print('version : 0.3.0')
    print('app is '+__name__)
    # main(PROTOCOL)
    # for key in os.environ.keys():
    #    print('key='+key+":value="+os.environ[key])

    BLOB_ON_EDGE_MODULE = os.environ['BLOB_ON_EDGE_MODULE']
    BLOB_ON_EDGE_ACCOUNT_NAME = os.environ['BLOB_ON_EDGE_ACCOUNT_NAME']
    BLOB_ON_EDGE_ACCOUNT_KEY = os.environ['BLOB_ON_EDGE_ACCOUNT_KEY']
    IMAGE_CONTAINER_NAME=os.environ['IMAGE_CONTAINER_NAME']
    IOTEDGE_DEVICEID=os.environ['IOTEDGE_DEVICEID']
    BLOB_UPLOAD_DURATION_SEC = float(os.environ["BLOB_UPLOAD_DURATION_SEC"])
    

    print('IOTEDGE_DEVICEID:'+ IOTEDGE_DEVICEID)
    print('BLOB_ON_EDGE_MODULE:'+BLOB_ON_EDGE_MODULE)
    print('BLOB_ON_EDGE_ACCOUNT_NAME:'+BLOB_ON_EDGE_ACCOUNT_NAME)
    print('BLOB_ON_EDGE_ACCOUNT_KEY:'+BLOB_ON_EDGE_ACCOUNT_KEY)
    print('IMAGE_CONTAINER_NAME:'+IMAGE_CONTAINER_NAME)
    print('BLOB_UPLOAD_DURATION_SEC='+str(BLOB_UPLOAD_DURATION_SEC))

    blobService = initialize_blob_on_edge(BLOB_ON_EDGE_MODULE,BLOB_ON_EDGE_ACCOUNT_NAME,BLOB_ON_EDGE_ACCOUNT_KEY,IMAGE_CONTAINER_NAME,BLOB_UPLOAD_DURATION_SEC,IOTEDGE_DEVICEID)

    app.run(host='0.0.0.0', port=80, debug=True)
```

## 8）Blob on Edge Module の追加
ここからは、Raspberry Pi 上の Blob Storage に格納した画像を、Azure 上の Blob コンテナに転送し、Azure 上で受信した画像ファイルを削除する Module を追加します。これは、Marketplace にあるものを利用します。

Visual Studio Code のメニューバー「表示」→「コマンドパレット」を起動し、表示された窓に「Add IoT Edge Module」と入力します。一覧が表示されるので、「Azure IoT Edge: Add IoT Edge Module」を選択します。
![VSCode20.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/27e8d6ae-a079-a85d-019a-876da2f4b6d0.jpeg)

「deployment.template.json」を選択します。
![VSCode21.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/84a70e89-106d-421e-e334-e23b630c6133.jpeg)

「Module from Azure Marketplace」を選択します。
![VSCode22.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/e8c27e06-197d-92d5-c46a-d2a3abfedaa5.jpeg)

一覧表示されるので、「Azure Blob Storage on IoT Edge」を選択します。
![VSCode23.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/4f677759-d253-d513-4d44-d9f6910fa92c.jpeg)

Import する Module を選択する画面が表示されるので、Tags で「arm32v7」を選択し、「Import」をクリックします。
![VSCode24.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/f0f3f710-3eb0-8d63-0348-18b1190ddea3.jpeg)

「deployment.template.json」に赤枠部分が追加される。この後、追加された部分を修正します。
![VSCode25.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/05b80d5f-5e5b-4878-82a5-a92c7d9e4bb7.jpeg)

先ほど、Marketplace で選択した「arm32v7」の部分を「latest」に修正します。
![VSCode26.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/11069c33-ffac-2fcc-14cf-90f03404e79b.jpeg)



Module 名を「AzureBlobStorageonIoTEdge」→「local-blob-storage」に修正します。
![VSCode27.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/483b53b7-820c-80a8-8da6-9af12e89842f.jpeg)

以下のBind 情報を追加します。追加する場所に注意。
今回、バインドマウントを使っているので、サービスが正しく起動するために、Raspberry Pi にコンテナが Pull された後、この手順[Linux のコンテナー ユーザーにディレクトリ アクセスを許可する](https://docs.microsoft.com/ja-jp/azure/iot-edge/how-to-store-data-blob#granting-directory-access-to-container-user-on-linux)を実行する必要があります。

```
"Binds":["/srv/containerdata:/blobroot"],
```
![VSCode28.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/6001dd58-65b8-2890-7f42-9f3e818877e2.jpeg)


https://generate.plus/en/base64?gp_base64_base%5Blength%5D=64 にアクセスしてキーを生成し、キーをコピーします。
![VSCode29.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/47392844-8826-94e6-3dd0-9a0b70245cc3.jpeg)

上記で作成したキーと、「localaccount」というアカウント名を使用して、下図の2か所を修正し、ファイルを保存します。

```
"BLOB_ON_EDGE_ACCOUNT_NAME=localaccount",
"BLOB_ON_EDGE_ACCOUNT_KEY=生成したキー",

"LOCAL_STORAGE_ACCOUNT_NAME=localaccount",
"LOCAL_STORAGE_ACCOUNT_KEY=生成したキー"
```
![VSCode30.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/533bb5bf-cc9b-a757-ebcb-cdf5b57388c9.jpeg)

次に、Cloud 側の Blob Storage と対応づけるために、以下を「deployment.template.json」に追加します。
追加する際、「,」を忘れないように（$edgeHub と local-blob-storage の境界です）してください。

```
"local-blob-storage":{
  "properties.desired": {
    "deviceAutoDeleteProperties": {
      "deleteOn": true,
      "deleteAfterMinutes": 5,
      "retainWhileUploading":true
    },
    "deviceToCloudUploadProperties": {
      "uploadOn": true,
      "uploadOrder": "OldestFirst",
      "cloudStorageConnectionString": "< cloud storage account connection string >",
      "storageContainersForUpload": {
        "photos": {
          "target": "edgephotos"
        }
      },
      "deleteAfterUpload":true
    }
  }
}
```
![VSCode31.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/0e5a9438-f45b-b8a8-e1a6-d5b9810cd336.jpeg)

上記で追加した中で、「"cloudStorageConnectionString": "< cloud storage account connection string >"」を修正します。
接続文字列が不明な方は、ひとつ下の図を参照ください。
![VSCode32.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/d17e859a-1d7b-4ebd-445c-0f2a49090f8d.jpeg)

Azure の Storage Account の接続文字列は、以下のように、「作成した Storage Account」→「アクセスキー」で表示される画面から取得可能です。接続文字列の右側にある「コピー」ボタンをクリックしてコピーするのが良いです。この値を使用します。
![VSCode33.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/61ac3803-4b03-7f3c-b428-d5cd50791756.jpeg)


## 9）camera-capture Module の追加
次に camera-capture Module を追加します。この Module は、https://github.com/ms-iotkithol-jp/Custom-vision-service-iot-edge-raspberry-pi のものを流用します。

Visual Studio Code のメニューバー「表示」→「コマンドパレット」を起動し、表示された窓に「Add IoT Edge Module」と入力します。一覧が表示されるので、「Azure IoT Edge: Add IoT Edge Module」を選択します。
![VSCode34.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/258c7f75-3a69-39e3-629f-1b54018f612c.jpeg)

「deployment.template.json」を選択します。
![VSCode35.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/00406b5c-bd68-d4ca-3f9d-f9c4493d8afe.jpeg)

「Existing Module(Enter Full Image URL)」を選択します。
![VSCode36.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/e3a6ba48-57d9-b6be-16b0-c63ec90886ad.jpeg)

「CameraCapture」と入力し、Enter を押下します。
![VSCode37.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/4e909802-3799-2d5e-dc98-bfed3ae8cefd.jpeg)

「embeddedgeorge/cameracapture:0.3.0-arm32v7」と入力し、Enter を押下します。
![VSCode38.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/1d212dc4-2b0e-7c54-93eb-ea9303043f3e.jpeg)

下の図のように3か所修正します。
Module 名を「CameraCapture」→「camera-capture」に変更します。
「restartPolicy」の次に以下の「env」ブロックを追加します。

```
"env": {
  "VIDEO_PATH": {
    "value": 0
  },
  "IMAGE_PROCESSING_ENDPOINT": {
    "value": "http://store-image-to-local-blob:80/image"
  },
  "RESIZE_WIDTH": {
    "value": 256
  },
  "RESIZE_HEIGHT": {
    "value": 256
  },
  "SHOW_VIDEO": {
    "value": "True"
  }
},
```
さらに、「settings」の「createOptions」に以下を追加します。

```
"HostConfig": {
  "PortBindings": {
    "5012/tcp": [
      {
        "HostPort": "5012"
      }
    ]
  },
  "Binds": [
    "/dev/video0:/dev/video0"
  ],
  "Devices": [
    {
      "PathOnHost": "/dev/video0",
      "PathInContainer": "/dev/video0",
      "CgroupPermissions": "mrw"
    }
  ]
}
```
修正、追加が完了したら、ファイルを保存しておきます。
![VSCode39.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/65f60a99-a383-55fd-fe75-49983551e2ed.jpeg)


## 10）Solution の Build と Azure Container Registry への Push
これまでの手順で、IoT Edge 上の Module の準備が終わったので、Solution を Build していきます。

Visual Studio Code のメニューバー「表示」→「コマンドパレット」を起動し、「Azure IoT Edge: Build and Push IoT Edge Solution」を選択します。
![VSCode40.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/2ebefdf6-7b89-11f7-41c1-39503b81e5b0.jpeg)

「deployment.template.json」を選択します。
Build が開始され、終了後 Image が Azure Container Registry に Push されます。
![VSCode41.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/b570da6d-9fd2-779c-dd01-a5cb1ede9b65.jpeg)

Push 時に Unauthorized エラーが出た場合、Build で使ったターミナルで以下のコマンドを使ってログインすること。その後、上記の手順を再実行します。

+ acr-user-name : Azure Container Registry のユーザー名
+ acr-password : Azure Container Registry のパスワード
+ acr-url : Azure Container Registry の URL

```
docker login -u <acr-user-name> -p <acr-password> <acr-url>
```

Azure Container Registry に Push されていることを確認します。
![VSCode42.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/5961b31c-8e0d-bdc9-df4f-cfc48351fdef.jpeg)


## 11）Module 配置情報を IoT Hub に転送
Azure Container Registry に Docker Image を Push できたので、Module の配置情報を IoT Hub に転送します。
これによって、IoT Edge が IoT Hub から情報を取得し、Azure Container Registry に Pull しにいきます。

Visual Studio Code のメニューバー「表示」→「コマンドパレット」を起動し、「Azure IoT Hub: IoT Hub を選択」を選択します。
![VSCode43.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/669e2249-63ab-ad4b-8675-47491f48716e.jpeg)

今回利用しているサブスクリプションを選択します。
![VSCode44.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/b3cb1de6-3508-15b7-dc08-972a5a6c7841.jpeg)

転送する IoT Hub を選択します。
![VSCode45.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/44dc1f8a-0bb9-14b4-204e-23683bb8ba63.jpeg)

Visual Studio Code のメニューバー「表示」→「コマンドパレット」を起動し、「Azure IoT Edge: 1つの IoT Edge に配置」を選択します。
![VSCode46.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/4b417cf2-ceba-bf3b-ded5-9a2501b0e1ac.jpeg)

IoT Edge のデバイス名を選択します。
![VSCode47.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/4f79881e-95e1-212e-e9bb-d5db9b43d1ab.jpeg)

Solution Build 時に、deployment.template.json から生成された config/deployment.arm32v7.json が IoT Hub へ転送されます。
![VSCode48.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/dd74df51-b890-ea3b-7d01-cb0837b7c455.jpeg)
![VSCode49.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/480296d0-3f08-17a8-06e8-f024abcf9f85.jpeg)

IoT Hub へ転送されました。
![VSCode50.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/5ee1b3fa-6f0b-2295-917d-73dc612b8083.jpeg)


## 12）動作確認
最後に動作確認を行います。
Raspberry Pi に接続したカメラで撮影した画像が、Azure Blob Storage に格納されていれば OK です。

最初に、IoT Edge に作成した Module がデプロイできていることを確認します。
IoT Hub のメニューの中から「IoT Edge」をクリックし、設定したデバイス ID をクリックします。ここでは「raspi3bp」です。
![Check01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/94d4bfb6-0fc1-ebf6-819c-97039b15f0fb.jpeg)

5つの Module すべてが「running」の状態となっており、問題なく稼働していることが確認できます。
![Check02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/4ba87c60-9f30-8273-164d-b4f8dd963414.jpeg)

次に Blob Storage の確認を行います。
ストレージを選択し、「BLOB」をクリックし、「edgephotos」をクリックします。
![Check03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/df9f8fb9-7bd1-a3d1-bcaa-d742e673f93b.jpeg)

「raspi3bp-img20190915060645.jpg」というファイルが格納されているのが分かります。これらは、Raspberry Pi のカメラが撮影した画像になります。無事に転送されていることが確認できました。
![Check04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157479/b6d2dab6-798e-317d-8c8e-a194d1b89fe3.jpeg)


# 動かない場合
Raspberry Pi のシェル上（リモート接続の Tera Term 等でも良い）で、以下のコマンドを実行し、Module が正常に動いているか確認します。

```bash
sudo iotedge list
```
正常に動いていない Module がある場合、実行ログを確認します。例えば camera-capture の場合は以下となります。

```bash
sudo iotedge logs camera-capture
```
Pull してきた Module を更新する場合、通常はバージョンやタグをつけなおして Azure Container Registry に Push するが、開発環境で試している場合はやらないケースもあると思います。そのような場合、IoT Edge できちんと新しいものを Pull してくれるように、Docker Image を削除しておく必要があります。

```bash
sudo docker images
sudo systemctl stop iotedge
sudo docker image rm -f <docker image id>
sudo systemctl start iotedge
```
色々と調べていくのも手ですが、新規に作り直すほうが早い場合もあります。
 

# まとめ
いかがでしたでしょうか？無事に画像を Azure Blob Storage に格納できましたでしょうか？
今回 IoT Edge を触ってみて、以下の3点が理解できたのではないかと思います。

+ IoT Edge は Docker 互換のコンテナ―である
+ 開発した IoT Edge のモジュールを Azure Container Registry に Push し、IoT Edge は IoT Hub 経由で配置情報を取得、新しい Docker Image を Pull することができる
+ クラウドのサービスである Azure Functions、Azure Stream Analytics、Azure Machine Learning も IoT Edge 上にデプロイでき、Edge デバイスでクラウドのテクノロジーを利用できる（今回は Marketplace にある Azure Blob Storage on IoT Edge を利用しました）