[Azure x IoT] IoTHubで受け取ったメッセージをリアルタイムにクライアントに通知しよう


※本記事は以下の記事のうち、AzureIoTHubの作成とBroadcast関数の実装部分が異なるだけです。そのため、以下の記事を既にご覧になった方は,IoTHubの作成及びIoTHub連携したAzureFunctionを作成する部分以外は読み飛ばしていただいて大丈夫です!

[Azure] AzureでSingalRを使ってリアルタイム通知を行う

本記事のゴール

本記事では、IoTHubでのメッセージ受信をトリガーにFunctionApp/SignalR経由でメッセージを送り、それをReactアプリケーションで受信できるようにします。

AzureSignalRとは?

Azureの提供するリアルタイムプッシュ通信を提供するサービスです。

このサービスを利用すればサーバーサイド側のイベントをトリガーにクライアントアプリケーションに通知を行うことができます。

SignalR Service - リアルタイム Web | Microsoft Azure

準備するリソース

先の概略図ではAzureFunctionsを1つしか描きませんでしたが、実際にAzureSignalRとクラインアントの接続を確立するには以下の図の①から③手順を踏むため、Functionsは2つ必要になります。

したがって、AzureFunctionsはnegotiation用とbroadcast用で2つ用意します。

上記を踏まえて必要なリソースは以下のようになります。

  • AzureIoTHub
  • AzureSignalR
  • AzureFunctions(HttpTrigger)
  • AzureFunctions(SingalRのnegotiation用)
  • Azure WebApp(ReactAppのホスティング用)
  • AzureContainerRegistry(ReactAppのイメージ格納用)

1. Azure IoTHubを作成する

AzurePortalでAzure IoTHubサービスを作成します。

プランはF1(無料)で大丈夫です。

2. Azure SignalRを作成する

AzurePortalでAzureSignalRサービスを作成します。この際、サービスのモードはDefaultではなくServerlessとなるようにします。

またプランはFreeで大丈夫です。

3.Azure Functionsを用意する

次にnegotiation関数とbroadcast関数を作成します。

negotiation関数の作成

FunctionAppの作成

関数の入れ物になるFunctionAppを作成します。どのプランでも大丈夫です。

関数の中身の作成

テンプレートが用意されているので以下のコマンドで一発で作成できます

func new --name SingalrNegotiator --template "SignalR negotiate HTTP trigger"

negotiation関数は中身をいじる必要がないのでコードはテンプレートのままで大丈夫です。

関数のデプロイ

関数を作成したFunctionAppにデプロイします。

npm i 
npm run build

func azure functionapp publish ${作成したNegotiation用FunctionAppの名前}

FunctionApp上の環境変数の設定

FunctionAppの構成ページで,templateで生成されたfunctions.jsonに記載されているAzureSignalRConnectionStringアプリケーション設定として追加します。

値は、SignalRの"キー"ページの"接続文字列"を入力します。

  • SignalR

  • FunctionAppのアプリケーション設定

これによってFunctionAppとSignalRを連携させます。****

broadcast関数の作成

FunctionAppの作成

関数の入れ物になるFunctionAppを作成します。どのプランでも大丈夫です。

関数の中身の作成

よくあるエラーやより詳細な手順は筆者の以下の記事を参考にしてみてください!

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

今回はテストしやすいようにHttpTriggerの関数を作成します。こちらもテンプレートで作成できます。

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

次にfunction.jsonにiothub/signalrとの連携用の記述を変更または追加します.

{
      "type": "signalR",
      "name": "signalRMessages",
      "hubName": "default",
      "connectionStringSetting": "AzureSignalRConnectionString",
      "direction": "out"
},
{
      "type": "eventHubTrigger",
      "name": "IoTHubMessages",
      "direction": "in",
      "eventHubName": "MyEventHub",  ←テンプレートから変更
      "connection": "myEventHubReadConnectionAppSetting",  ←テンプレートから変更
      "cardinality": "one", ←テンプレートから変更
      "consumerGroup": "$Default"
}

最後にindex.tsを以下のように書き換えます
context.bindings.signalRMessagesの部分がsignalRを使って送るメッセージを構築する部分です。targetで指定した文字列をクライアント側でも指定してサブスクライブします。

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

const IoTHubTrigger: AzureFunction = async function (context: Context, IoTHubMessage: any): Promise<void> {

// ここがsignalRを使って送るメッセージを構築する部分
context.bindings.signalRMessages = [{
        target:'test',
        arguments:[{message:IoTHubMessage.value,timestamp:new Date()}]
    }]

};

export default IoTHubTrigger;

関数のデプロイ

関数を作成したFunctionAppにデプロイします。

npm i 
npm run build

func azure functionapp publish ${作成したBroadcast用FunctionAppの名前}

FunctionApp上の環境変数の設定

Negotiation関数の時と同様に、FunctionAppの構成ページで,templateで生成されたfunctions.jsonに記載されている以下の2つをアプリケーション設定として追加します

  • AzureSignalRConnectionString(Negotiation関数の設定を参照)
  • myEventHubReadConnectionAppSetting

myEventHubReadConnectionAppSettingに入れる値は、Event Hub-compatible endpointで、その値はIoTHubから確認できます

これによってFunctionAppとIoTHub/SignalRを連携させます。

4. クライアントアプリケーションを作成する

ホスティング方法はなんでも良いのですが本記事では次のチュートリアルに倣ってContainerRegistryにpushしたimageをWebAppがpullして利用する形を採用します。

ContainerRegistryとWebAppの作成方法は以下を参考にしてください。

Azure App Service を使ってコンテナー化された Web アプリをデプロイして実行する - Learn

クライアントアプリの中身の作成

雛形の作成

npx create-react-app webapp --template typescript

次にsignalrを使う上で必要な次のパッケージをインストールしてください。

npm i @microsoft/signalr

最後にApp.tsxを以下のように編集してください。メッセージを受け取ったらその内容と時刻を表示するだけのアプリケーションです。

.envにREACT_APP_NEGOTIATOR_NAMEを追加するのを忘れないようにしてください。

import React, {useEffect, useState} from 'react';
import './App.css';
import * as signalR from '@microsoft/signalr'
const connection = new signalR.HubConnectionBuilder()
  .withUrl(`https://${process.env.REACT_APP_NEGOTIATOR_NAME}.azurewebsites.net/api`)
  .configureLogging(signalR.LogLevel.Trace)
  .withAutomaticReconnect()
  .build()

function App() {
  const [data,setData] = useState({message:'',timestamp:''})

  useEffect(()=>{
    connection.on('test', data => {
      console.log("Received data from signalR")
      console.log(data)
      setData(data)
    })

    connection.onclose(function() {
      console.log('signalr disconnected')
    })
    connection.onreconnecting(err =>
      console.log('err reconnecting  ', err)
    )
    connection
      .start()
      .then((res:any) => {})
      .catch(console.error)
  },[])

  return (
    <>
      <div>Message from SignalR</div>
      <div>{data.message}</div>
      <div>arrived at {data.timestamp}</div>
    </>
  );
}

export default App;

デプロイ

以下のコマンドでデプロイします

acr build --registry ${コンテナレジストリの名前} --image ${イメージ名} ${DockerFileのあるディレクトリへのパス}"

参考までにDockerfileも記載しておきます.以下を配置いただいて

FROM node:14
RUN npm install -g serve # A simple webserver
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["serve", "-s", "build", "-l", "3000"]

5.SignalRとnegotiation関数でCORSの設定を行う

デフォルトではそれぞれ、同じオリジンからのアクセスしか受け付けてないので、ホスティングしたWebAppからもアクセスできるようにします。WebAppのURLはリソースのトップページで確認できます。

SignalRもnegotiation関数(FunctionApp)もサイドメニューにCORSメニューがあるのでそこから設定します。

ローカルで立ち上げたアプリでも接続したい場合は、http://localhost:3000なども登録すれば大丈夫です。

6. 動作確認

WebAppにアクセスしてアプリケーションを開いた状態で、FunctionAppをテスト実行します

  1. WebAppにアクセスしてアプリケーションを開きます
    こんな感じの画面になるはずです。

  2. IotHubに対してメッセージを送信します

    必要な環境変数を設定して以下を実行します。

    curl -i -X POST \
    -H "Content-Type:application/json" \
    -H "Authorization:$SAS" \
    -d '{"value":"hello,world with https"}' \
    "https://$IOTHUB_NAME.azure-devices.net/devices/$DEVICE_NAME/messages/events?api-version=2018-06-30"
    

    SASは次のコマンドで取得できます

    
    az iot hub generate-sas-token -n $IOTHUB_NAME --du $EXPIRATION_SECONDS
    

    詳しくは筆者のこちらの投稿をご覧ください

  3. アプリケーション上にIoTHubに送ったメッセージと実行時間が表示されたら成功です!

おわりに

いかがでしたでしょうか?これでIoTデバイスからのメッセージをトリガーにリアルタイムにクライアントアプリケーションに通知を行えるようになりました!
あとは自宅のいろんなところにセンサーを置いてマイホームをスマートホーム化してもいいかもしれないですね!