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


はじめに

こちらの記事でデバイスからAzure IoTHubへデータを送信する方法は紹介しました。
今回は、Azure IoTHubで受け取ったデータをAzure Functionsに連携する方法を紹介します.

本記事のゴール

準備

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

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

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

Work with Azure Functions Core Tools

Macの場合は以下です

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

Azure Functionsを作成する

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

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

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

Azure IoT Hubで受け取ったデータを利用する方法(Azure function) - Qiita

コマンドで作成する

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

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

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

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

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

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

index.tsの中身

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;

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

func templates list

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

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. index.tsを修正
    修正前はFunctionの引数がIoTHubMessages: any[]となっていますが、今回は単一オブジェクトのみを送信する予定なのでanyに修正します.(本来はメッセージの型に合わせてきっちり定義するのが望ましい)

    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;
    
  3. function.jsonを修正
    ここにIoTHubとの連携に関わる情報を設定します。
    まず今回はバッチではなく、単一のメッセージしか送らないのでcardinalityをoneにします。(参考)
    次にeventHubNameの部分にMyEventHubを設定し、connection部分にmyEventHubReadConnectionAppSettingを入力します。
    これらは実は任意の文字列でよく、後ほどAzurePortalから環境変数として値を設定します。

    修正前

    {
      "bindings": [
        {
          "type": "eventHubTrigger",
          "name": "IoTHubMessages",
          "direction": "in",
          "eventHubName": "samples-workitems",
          "connection": "",
          "cardinality": "many",
          "consumerGroup": "$Default"
        }
      ],
      "scriptFile": "../dist/IoTHubTriggeredFunc/index.js"
    }
    

    修正後

    {
      "bindings": [
        {
          "type": "eventHubTrigger",
          "name": "IoTHubMessages",
          "direction": "in",
          "eventHubName": "MyEventHub",  ←変更
          "connection": "myEventHubReadConnectionAppSetting",  ←変更
          "cardinality": "one", ←変更
          "consumerGroup": "$Default"
        }
      ],
      "scriptFile": "../dist/IoTHubTriggeredFunc/index.js"
    }
    
  4. 以下のコマンドでデプロイ

    # まずアプリケーションをビルドする
    npm run build
    
    # 指定した関数アプリにデプロイする
    func azure functionapp publish $FUNCTION_APP_NAME
    

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

IoTHubとFunctionの連携設定をする

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

  1. AzurePortalでデプロイした関数アプリのページに行き,Configuration(構成)を開きます

  2. New application settingを選択し、環境変数を設定していきます

    MyEventHubmyEventHubReadConnectionAppSettingに設定するIoTHubの名前とEvent Hub-compatible endpointはそれぞれIoTHubから確認できます

  • IoTHubの名前

  • Event Hub-compatible endpoint

  1. 保存ボタンを押して環境変数を保存する.
    意外と気づきづらいのでしっかり保存してください。

もしAzurePortal上で警告が出た時

  • EventHub拡張機能のバージョンが古いと怒られたとき

    警告全文

    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.
    

    次のコマンドでバージョンアップしてください

    func extensions install --package Microsoft.Azure.WebJobs.Extensions.EventHubs --version 5.0.0
    
  • Event Hub-compatible endpointeventHubNameが載っているのに二重で指定してくるな!と怒ってきたとき
    eventHubNameを空文字にしてデプロイし直してください。

メッセージを送ってみる

この記事に従って以下のスクリプトでメッセージを送ってみます

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ログでメッセージが確認できたら成功です!

終わりに

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

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