[Azure x IoT]Azure IoTHub とAzure Functionsを連携しよう

# はじめに

[こちらの記事](https://qiita.com/iron-samurai/items/3015971f15a20ccf954c)でデバイスからAzure IoTHubへデータを送信する方法は紹介しました。
今回は、Azure IoTHubで受け取ったデータをAzure Functionsに連携する方法を紹介します.

**本記事のゴール**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/afc3ba62-3ca1-2b15-0101-1e4a8d741b96.png)



# 準備

以下の準備はAzurePortalから関数を作成する場合は不要です。

ローカル環境でAzure Functionsの雛形作成、デプロイのためには`Azure Functions Core Tools`と呼ばれるCLIツールが`Azure CLI`とは別に必要です。

以下を参考にインストールしてください。

[Work with Azure Functions Core Tools](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=v4%2Cmacos%2Ccsharp%2Cportal%2Cbash)

Macの場合は以下です

```bash
brew tap azure/functions
brew install azure-functions-core-tools@4
```

# Azure Functionsを作成する

## ポータルから作成する(今回は割愛)

本記事では、コマンドを使って作成する方法をメインで書くのでこちらは割愛させていただきます。

以下の記事がとても丁寧でわかりやすいです

[Azure IoT Hubで受け取ったデータを利用する方法（Azure function） - Qiita](https://qiita.com/kmaepu/items/016608d1b810a9bd6470)

## コマンドで作成する

実は、複雑なことはしなくてもAzure側でIoTHub連携用のテンプレートが用意されおり、以下のコマンド１発で連携に必要なFunctionの雛形を作成できます

```bash
func new --name IoTHubTriggeredFunc --template "IoT Hub (Event Hub)"
```

コマンドを実行するとプロンプトが立ち上がり、runtime,languageを聞かれます。

今回はそれぞれ 3. node 2.typescriptを選択しました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/5827f118-580d-3c74-30fb-be72866a4cd1.png)


成功すると以下のようなディレクトリ構成で雛形が作成されます

```bash
.
├── IoTHubTriggeredFunc
│   ├── function.json
│   └── index.ts
├── host.json
├── local.settings.json
├── package.json
└── tsconfig.json
```

index.tsの中身

```tsx
import { AzureFunction, Context } from "@azure/functions"

const IoTHubTrigger: AzureFunction = async function (context: Context, IoTHubMessages: any[]): Promise<void> {
    context.log(`Eventhub trigger function called for message array: ${IoTHubMessages}`);
    
    IoTHubMessages.forEach(message => {
        context.log(`Processed message: ${message}`);
    });
};

export default IoTHubTrigger;
```

余談ですが、他にも様々なテンプレートがあり以下のコマンドで確認できます

```bash
func templates list
```

TypeScriptの部分だけ一部抜粋するとこんな感じです

```bash
TypeScript Templates:
  Azure Blob Storage trigger
  Azure Cosmos DB trigger
  Durable Functions activity
  Durable Functions entity
  Durable Functions Entity HTTP starter
  Durable Functions HTTP starter
  Durable Functions orchestrator
  Azure Event Grid trigger
  Azure Event Hub trigger
  HTTP trigger
  IoT Hub (Event Hub)←今回使うのはこれ
```

## 関数をデプロイする

1. デプロイ対象になる関数アプリをAzurePortal上で作成する
2. function.jsonを修正
ここにIoTHubとの連携に関わる情報を設定します。

まず今回はバッチではなく、1メッセージずつ受信するために`cardinality`をoneにします。([参考](https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-event-iot-trigger?tabs=csharp#configuration))
ここをmanyにしておくとメッセージをバッチで受け取れるので、例えばDBに直接データを入れたい場合にバルクインサートができたり、パフォーマンス改善につながります。

次に`eventHubName`の部分に`MyEventHub`を設定し、`connection`部分に`myEventHubReadConnectionAppSetting`を入力します。
これらは実は任意の文字列でよく、後ほどAzurePortalから環境変数として値を設定します。
    
    
    修正前
    
    ```json
    {
      "bindings": [
        {
          "type": "eventHubTrigger",
          "name": "IoTHubMessages",
          "direction": "in",
          "eventHubName": "samples-workitems",
          "connection": "",
          "cardinality": "many",
          "consumerGroup": "$Default"
        }
      ],
      "scriptFile": "../dist/IoTHubTriggeredFunc/index.js"
    }
    ```
    
    修正後
    
    ```json
    {
      "bindings": [
        {
          "type": "eventHubTrigger",
          "name": "IoTHubMessages",
          "direction": "in",
          "eventHubName": "MyEventHub",  ←変更
          "connection": "myEventHubReadConnectionAppSetting",  ←変更
          "cardinality": "one", ←変更
          "consumerGroup": "$Default"
        }
      ],
      "scriptFile": "../dist/IoTHubTriggeredFunc/index.js"
    }
    ```

3. index.tsを修正
修正前はFunctionの引数が`IoTHubMessages: any[]`となっていますが、今回はステップ２で1メッセージずつ受信するようにしたので以下のように引数の型を修正します。もちろん、送られてくるデータの型がわかっていればanyではなくきちんと定義するのが望ましいです。
    
    ```tsx
    import { AzureFunction, Context } from "@azure/functions"
    
    const IoTHubTrigger: AzureFunction = async function (context: Context, IoTHubMessage: any): Promise<void> {
        context.log(`Eventhub trigger function called for message array: ${IoTHubMessage}`);
    
    };
    
    export default IoTHubTrigger;
    ```

4. 以下のコマンドでデプロイ
    
    ```bash
    # まずアプリケーションをビルドする
    npm run build
    
    # 指定した関数アプリにデプロイする
    func azure functionapp publish $FUNCTION_APP_NAME
    ```
    

以下のような表示がされれば成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/e89bf8c1-6898-1edf-9ae2-41f4d65ee564.png)



# IoTHubとFunctionの連携設定をする

仕上げに先程functions.jsonに設定した`MyEventHub`と`myEventHubReadConnectionAppSetting`をAzurePortal上で環境変数として設定します。

1. AzurePortalでデプロイした関数アプリのページに行き,Configuration(構成)を開きます
    
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/6f92cd1c-7398-8528-4d3e-96d729b80f17.png)

    
2. New application settingを選択し、環境変数を設定していきます
    
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/c479364d-7360-c872-5e1b-41e21279e393.png)

    
    `MyEventHub`と`myEventHubReadConnectionAppSetting`に設定するIoTHubの名前とEvent Hub-compatible endpointはそれぞれIoTHubから確認できます
    
  - IoTHubの名前
        ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/052919e3-9759-3832-5cf7-975f34ab2a4d.png)

        
  - Event Hub-compatible endpoint      
   ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/58aea510-278b-0de6-9653-cdbd314cb5a8.png)

        
3. 保存ボタンを押して環境変数を保存する.  
意外と気づきづらいのでしっかり保存してください。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/d661d06b-4429-719f-ee4f-4a1dcf28192c.png)

    

もしAzurePortal上で警告が出た時

- EventHub拡張機能のバージョンが古いと怒られたとき
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/a50d66b3-afb0-aeb2-a094-d96dcb83325a.png)

    
    警告全文
    
    ```bash
    Microsoft.Azure.WebJobs.Script: One or more loaded extensions do not meet the minimum requirements. For more information see https://aka.ms/func-min-extension-versions.
    ExtensionStartupType EventHubsWebJobsStartup from assembly 'Microsoft.Azure.WebJobs.EventHubs, Version=4.1.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' does not meet the required minimum version of 4.3.0.0. Update your NuGet package reference for Microsoft.Azure.WebJobs.Extensions.EventHubs to 4.3.0 or later.
    ```
    
    次のコマンドでバージョンアップしてください
    
    ```bash
    func extensions install --package Microsoft.Azure.WebJobs.Extensions.EventHubs --version 5.0.0
    ```
    
- `Event Hub-compatible endpoint`に`eventHubName`が載っているのに二重で指定してくるな！と怒ってきたとき
`eventHubName`を空文字にしてデプロイし直してください。

# メッセージを送ってみる

[この記事](https://qiita.com/iron-samurai/items/3015971f15a20ccf954c)に従って以下のスクリプトでメッセージを送ってみます

```bash
IOTHUB_NAME=xxxx
DEVICE_NAME=xxxx
SAS=xxxx
mosquitto_pub -d -q 1 \
  -V mqttv311 \
  -p 8883 \
  -h "$IOTHUB_NAME.azure-devices.net" \
  -i $DEVICE_NAME \
  -u "$IOTHUB_NAME.azure-devices.net/$DEVICE_NAME/api-version=2016-11-14" \
  -P $SAS \
  -t "devices/$DEVICE_NAME/messages/events/$.ct=application%2Fjson&$.ce=utf-8" \
  -m '{"value":"Hello,world with MQTT"}'
```

関数アプリのApp Insightsログでメッセージが確認できたら成功です！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/23d75fc1-4099-c003-5bd2-3fcfe2f155e5.png)


# 終わりに

これでデバイス→IoTHub→Azure Functionsまでを繋ぐことが出来ました！

次はAzureFunctionsとその他のAzureリソースを連携する方法を紹介する記事も執筆予定です、お楽しみに！