2020/11/12Update
VSCodeでちょっとやってみました
https://qiita.com/mstakaha1113/items/30fe58e37de0a2801307
※この記事は Microsoft Azure Tech Advent Calendar 2019 の 19 日目の記事です。
書いてみたら長くなっちゃいましてごめんなさい。
これを知っててIoTサービス作るか 知らないで作るかで、イニシャルコストが全然違うと思いますので、必要と思う方は取り組んでいただけるとうれしいです。
また、ボクもたくさん間違いをするので、フィードバックもありがたいです。
はじめに
この記事では、"IoT"と呼ばれているものに必要な基本的な機能を、Azure上に作成していきます。
詳細はのちほど触れますが、ここでは5つのシナリオに沿ってデバイスとAzureを接続してみます。
全ての中心は Azure IoT Hub というサービスになります。
基本的にAzureのドキュメントベースで進めます。
IoTの基本的な機能は自分で作成してはいけない
IoTを語る上で重大な事実があります。それは『IoTの基本的な部分はお金をうまない』ということです。IoTを支える基礎部分であるデバイスとデータを送受信するような仕掛けそのものは、ほぼコストでしかありません。よって、基礎部分の製造・運用のコストは最小限に抑えて、皆様にはIoTで価値を生み出す"うわもの"に心血を注いでいただきたいのです。
こちらの記事では、IoTの基本的な部分をピックアップしてご紹介します。
対象となる方
- IoTは多少わかっている方
- Azureの基礎学習ができている方(ポータルを使って、マイクロソフトのドキュメントにあるクイックスタートを読んでサンプルが作れる方)
- 開発言語はC#(ですが、基本的にAzureのサービスを連結して作っていますので、C#そのものはわからなくても大丈夫です。)
IoTの全体像
これを読むとわかります→Microsoft Azure IoT Reference Architecture (Microsoft Azure IoT リファレンス アーキテクチャ)
こちらで色々おはなししましたので良かったらご確認ください→de:code 2019
IoTを最初から手取り足取りやってみたい方は、こちら↓をご覧ください。
ゼロから学ぶIoT(SlideShare)
ゼロから学ぶIoT ハンズオン資料(SlideShare)
Azureの画面とかが少しずつ変わっていると思いますが、ご容赦ください。
この記事で扱うシナリオ
シナリオ0:デバイスとIoT Hubを接続し、デバイスとAzureがやり取りできるようにします。
シナリオ1:デバイスからのデータ(テレメトリーデータ)を取得したら、それをStorageに保存します。
シナリオ2:テレメトリーデータを計測して、異常を検知した時はメールを送信します。
シナリオ3:デバイスからファイルをアップロードします。
シナリオ4:デバイスからアラートイベントを送信します。イベントを取得したサービス側(Azure側)は、デバイスに制御メッセージを送ります。
シナリオ5:サービス側からデバイスに対して、直接的に命令を送り付けます。
この記事では扱わないことの代表例
- Device Provisioning
- デバイス認証あれこれ
- セキュリティ(ログとか)あれこれ
- Edge
- Machine Learningその他派生するサービス
- Web画面等の表示系
- 冗長構成
シナリオ0:デバイスとIoT Hubの準備
まずはクイック スタート:デバイスから IoT ハブに利用統計情報を送信してバックエンド アプリケーションで読み取る (.NET)を実施して環境を構築しましょう。
このシナリオで最低限実施いただきたいのはシミュレートされた利用統計情報の送信までです。
ハブから利用統計情報を読み取るはデータを読み出すサンプルですので、作成いただくことをお勧めしますが、Device Explorerをご利用いただいても良いと思います。(SetupDeviceExplorer.msiというものがありますのでダウンロードしてご利用ください)
こちらでは
- IoT Hub の作成
- デバイスの登録(IoT Hub からデバイスのIDを払い出す)
- デバイスからテレメトリーデータを送信
を実施します。
ここで覚えておくべき IoT Hub の特徴
IoT Hub の基本的な特徴は、
- デバイスを管理できる
- データを収集できる
- データを配信できる(デバイスを管理しているからできる)
という3つです。ほかに2つ紹介します。
データを一時保存してくれる
試しに、データを送信する側のプログラムだけをしばらく動作させてみてください。その後データを受信する側のプログラム(もしくはExplorer)を起動すると、送信したデータを一気に読みだしてくれると思います。
これはIoT Hubがデータを一時的に保存しているということを意味します。しかし、これは一時的なものであることを覚えておいてください。放置するとお腹がいっぱいになって、受信を拒否します。具体的にはIoT Hubにデータを送信した際にエラーが返ってきます。そうなる前にデータを読み出してあげましょう。(注意:勝手に過去データを読み捨ててはくれません。過去データの読み捨てについては、"IoT Hub のデータ保持期間について"をご確認ください。)
組み込みエンドポイント
データの読み出しに利用した出力口のことを"組み込みエンドポイント"と呼びますので覚えておきましょう。IoT Hubに最初から組み込まれているエンドポイント(出力口)です。最初は存在しないエンドポイントについてはシナリオ4でふれます(カスタムエンドポイントという名前です)。
シナリオ1:テレメトリーデータをStorageに保存(コールドパス)
シナリオ0で、テレメトリーデータの収集ができるようになりました。現在このデータは読み捨てている状態です。このデータを活用したいと思います。決して、データが飛んだだけで満足しないでください。
シナリオ0で、デバイスからIoT Hubまではできています。
ここでは、Function App(関数アプリ)でデータを読み出して、Storageに保存するということを実施したいと思います。IoT のシナリオで最も基本的な動作となります。
すぐに作りたいところですが、先のシナリオも意識して、まずは"ホットパス"と"コールドパス"、それを実現するためのIoT Hubの仕掛け"コンシューマーグループ"を理解しましょう。
ホットパスとコールドパス
あなたはデータを取得しました。さて、このデータを活用したいと思います。どんなシナリオが思い浮かびますか?
<例>
- 今の状態をリアルタイムに知る
- 温度上昇が継続したら警告する
- 過去の経緯を確認する
- 機械学習で他のデバイスとの利用パターンの差を検知する
テレメトリーデータの活用で最初に意識することは、活用タイミングが大きく2つに分かれるということです。
1つは今を分析するホットパス、1つは過去からの学びを活かすコールドパスです。上記例では1と2がホットパス、3と4がコールドパスです。
ホットパスでは、今流れているデータを分析します。例えば、機器の温度上昇具合を検知するとしましょう。「絶対値で何度か?」ではなく、「過去10分の間に8度上昇した」というような検知方法です。このホットパスでは、今まさに流れているデータが大切です。こちらはシナリオ2で作成します。(とはいえ、クエリが面倒なのでもっと単純なものを作成します。)
コールドパスでは、収集済データを分析します。例えば、故障予知のようなことを機械学習で実施する場合を想定しましょう。教師ありの学習をする場合、イイ感じデータとヤな感じデータが必要になります。また、様々な環境で動作している情報もあった方が良いでしょう。そこで、過去データが必要となります。このコールドパスでは、まずデータを保存することが大切です。
本番でやってはいけないこと(ホットパスでデータ保存も実現する)
ホットパスでついでにデータを保存するような仕掛けを組み込むことはやめてください。
勉強であれば良いですが、本番には組み込まないでください。
ザックリだけ書いておきますが、世の中のホットパス系の仕掛けは概ね、エラーハンドリングが面倒(もしくはできない)です。エラーが発生したあとの復旧が大変なので、気を付けてください。
基本はコールドパスでデータを保持して、ホットパスで使ったデータは捨てるです。
具体的なパスの分岐方法
端的に言いますと、「1つのデータを2か所から読む」ということになります。前で触れていますとおり、IoT Hubはデータを保存する機能が備わっています。そして、読み込みは各々が "自分のタイミング"で、 "さっき読んだ次のデータを欲しい" と思っています。この機能を実現するために、IoT Hubの組み込みエンドポイントには、コンシューマーグループ(消費者グループ)という設定が備わっています。
シナリオ0では、"$Default"という最初から備わっているコンシューマーグループからの読み出しを行っていました。この "$Default" はホットパスで利用するとして、今回はコールドパス用にコンシューマーグループを追加してみましょう。
具体的にはIoT Hubの"組み込みエンドポイント(Built-in endpoints)"の中になります。
(下図の緑色で囲われた部分です。)
この"Create new consumer group"と記載のあるテキストボックスに任意の名前を入れて、テキストボックスからフォーカスを外しましょう。私は"coldpath"という名前で作りました。
シナリオ1の作成
Storageの作成
まず保存先を作成しましょう。今回はStorageに保存します。本番系だとCosmosDBとかがオススメですが、今回は楽に安くStorageでいきます。
ストレージ アカウントの作成を実施してください。(今後全部ですが、クリーンアップしないでください)
これで保存先は作成完了です。
Function App(関数アプリ)の作成
まずはローカルで作成して動作確認、その後直接デプロイ(発行)したいと思います。
作成順序として今回は
- 入力をIoT HubへバインドしたFunctionを作成
- 出力にStorageをバインド
- データを保存
という形にします。
ここではVisual Studio 2019を使いますが、VS Codeでも同じです。Functionに関するExtentionをインストールしてください。
1. 入力をIoT HubへバインドしたFunctionを作成
VS2019を起動、新しいプロジェクトの作成、プロジェクトテンプレートでAzure Functionsを選択し"次へ"
"Azure Functions v2(.NET Core)"の"IoT Hub trigger"を選択し、"Connection string setting name"に**"EventHubConnectionAppSetting"**と入力し"作成"
EventHubConnectionAppSettingは、別に任意の名前で良いのですが、マイクロソフトのドキュメントに沿うので誤解がなくなり便利です。とりあえず勉強なので、この名前を入力しておいてください。
では入力バインドを整えます。
Functionsは(どんなプログラムでも基本的にはそうですが)、どこかから入力してどこかに出力します。その入力となるトリガーを何にするかを設定するのが入力バインドだと理解ください。
入力する値はこちら
さきほどの"組み込みエンドポイント"にある"Endpoint=~~"という値です。右の方のコピーボタンを押してコピーしましょう。
local.settings.jsonに以下を追加します。
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"EventHubConnectionAppSetting": "コピーした値(この行を追加してください)",
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
}
最後に、コンシューマーグループをセットします。
[IoTHubTrigger("messages/events", Connection = "EventHubConnectionAppSetting")]EventData message,
を
[IoTHubTrigger("messages/events", Connection = "EventHubConnectionAppSetting",ConsumerGroup = "coldpath")]EventData message,
としましょう。
デバイスのシミュレーターと共に実行してみると、データが取れてるのがわかると思います。
余談ですが、IoT Hubを扱っているのに"Event Hubs"という名前が出てきます。
これは、Event HubsとIoT Hubは兄弟なんだと理解ください。これまた余談ですが、デバイス管理が不要で、テレメトリーデータの収集のみを目的とする場合は、IoT HubではなくEvent Hubsを使うのも手段の1つです。(もしくは、IoT HubのBasicプランをご検討ください。)
これらの差異についてはIoT デバイスの Azure への接続: IoT Hub と Event Hubsやソリューションに適した IoT Hub のレベルを選択するをご確認ください。
2. 出力にStorageをバインド
Visual Studio を使用して関数を Azure Storage に接続する
こちらを参考にして実装してください。
local.settings.jsonがこんな感じかと思います。
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "ここにBlobへの接続文字列(ここを変更する)",
"EventHubConnectionAppSetting": "~~~~~",
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
}
coldpath.csは
msg.Add(Encoding.UTF8.GetString(message.Body.Array));
こんなのが入ってくるかと思います。
こんな感じになりました。
3. データを保存
すでにできています。実行してStorageの中身を確認してください。
バインドしてメッセージをAddしたらできています。バインドいいですね。もちろんバインド無しで普通に書いてもかまいません。この後のシナリオではバインド無しでも実装します。
(両者を見比べて、開発・検証・本番とリリースする過程を想像いただくと、結果的にバインドの良さがわかっていただけると思います。)
最後に、Azure上にFunctionを準備してデプロイ(発行)してください。
参考となるドキュメントはこちら
ここまで実施したものをAzure Portal上に並べました
シナリオ2:テレメトリーデータを計測して、異常を検知した時はメールを送信します。
シナリオ2は、"取得データをStream Analytics でリアルタイムにチェックして、異常検知したらService Bus にデータを格納、データが入ったらLogic App でメールを配信する。"というシナリオです。
シナリオ1で説明しました"ホットパス"のシナリオとなります。入ってきたデータそのものは保存しませんが、データの時系列を意識した警告を発することができます。
ここで覚えていただきたいことは2つです。1つはタイムウィンドウという概念、もう1つはイベントドリブンな構成です。(今回のシナリオではウィンドウは長くなるので触れません)
タイムウィンドウ
例えばひとことで「5分で平均2度上昇する」と言っても、とらえ方はいろいろあります。
- 時計の0分から計測して5分単位で切ったときに、その間で平均2度上昇
- 今この瞬間から過去5分で確認すると、平均2度上昇
その他いろいろとあると思います。
この計測を開始するタイミングに起因する話題を"タイムウィンドウ"と言います。
Stream Analytics には、"ウィンドウ関数"なるものがあります。こちらをご確認いただくと、"ウィンドウってなに?"の雰囲気と共に、これから実施することがご理解いただけると思います。
(
Stream Analytics ウィンドウ関数の概要
イベントドリブン
少し乱暴な言い方かもしれませんが、プログラムはInとOutでできていて、プログラムとプログラムをつなぐものはイベント(ボタンを押すとか、必要なデータがそろったとか、時間になったとか)です。
クラウドでは、この"イベント(プログラムの区切り)"を意識して、各々の処理を得意とするサービスを使うと、楽にサービスを組み立てることができます。
今回のシナリオではこんな感じです。
- IoT Hubがテレメトリデータの収集と保持に集中
- Stream Analytics が時間軸を意識した処理に集中
- Service Bus が発生イベントの保持に集中
- Logic Apps がメール配信処理に集中
これらは要件は1サーバー内に構築することも可能です。しかし、それでは部分的なスケールアウトやサーバー停止に対して脆弱な状態となってしまいます。
そこで、"処理"と"保持"をイベントの切り替えタイミングでうまく利用してそれぞれの作業を得意とするサービスを使います。
こうすることで、例えばメールサーバーの停止に伴うエラーがたくさん出たとしても、少なくともテレメトリデータの保持や時間軸を意識した警告には影響が無い仕組みを簡単に作ることができます。
構築順序
コツは、"保持"から作ることです。
今回は、
- Service Bus の作成
- Stream Analytics の作成
- Stream Analytics のクエリを修正
- Logic Apps の作成
という順序にします。メールはOutlookのフリーメールを使います。
1. Service Bus の作成
こちらを実施ください。
クイック スタート:Azure portal を使用して Service Bus キューを作成する
2. Stream Analytics の作成
注意事項は下に記載しますが、基本的にはこちらを実施ください。
Stream Analytics のジョブの作成
<注意>
- ドキュメントでは出力先がStorageですが、今回は Service Bus に変更ください
- どんな情報が取得できるのかが見えないとクエリが組めないので、クエリはとりあえずデフォルトの"Select * "のままにしてください(Into句とFrom句の名前は正しく変更してください)
ここまで実施したら、1度Stream Analytics を実行してService Busの中身を確認しましょう。
確認用に、キューからメッセージを受け取るを作成いただくと簡単です。数分で作れます。
3. Stream Analytics のクエリを修正
先ほどのStream Analyticsを停止して、クエリを以下のように書き換えましょう。
1回前に取得した温度より5度以上あがったらイベントが発生します。
SELECT
temperature
INTO
outQueue
FROM
IoTHubInput TIMESTAMP BY EventProcessedUtcTime
WHERE
temperature - LAG(temperature, 1) OVER (LIMIT DURATION(second, 5)) >= 5
再度Stream Analytics を動かしてテレメトリーデータを流すと、Service Busにデータが流れ込むのがわかると思います。
4. Logic Apps の作成
ロジック アプリを作成する
ここだけ実施ください。
その後、こんな感じの画面になると思いますので、"メッセージがService Bus キューで受信されたとき"というのを選択してください。
Logic Appsの細かい使い方は省略しますが、すぐにわかると思います。
まずどのService Busとつなぐかを選択します。また、どのキューを採用するのかを選択します。
つぎに"新しいステップ"でOutlookにつなぎました。
あとは"メール送信"を実施します。
このような感じになります。
デバイスからデータを送信すると、条件に合致した時だけメールが飛んでくると思います。
ここまで実装したものをAzure Portal上に並べてみました。
シナリオ3:デバイスからファイルをアップロードします。
シナリオ3は、"収集しているログデータをファイルのままアップロードする。"というシナリオです。
シナリオ1と2は、収集したテレメトリーデータを収集したタイミングで送信していました。
ログを取得するたび、Azureに行単位で送り付けているイメージです。
"IoT"としては、まっとうな方法ではありますが、現実はそれだけというワケにもいきません。
- 既存のシステムに組み込むのは困難
- 1日1回ログが収集できれば、まずは十分(そこから始めないと効果測定できないよ)
- 動画や画像も定期的に収集したい
- 潤沢な通信環境じゃないから接続時に圧縮したログを一気に送りたい
世の中のIoTの仕掛けはテレメトリーデータを送信できるシナリオばかりではありません。
そこで出てくるのがファイルのアップロードになります。
注意事項としましては、"そこそこ大容量のファイルもOKだけど、高頻度での送信はNGだよ"です。
とりあえずこれだけ覚えておきましょう。
実はIoT Hub、ファイルの送受信してません
今は気にしなくて良いですが、本番適用時は採用可能なのかどうか実ネットワークでご確認ください。
IoT Hubが実施するのは"ファイルアップしたい?じゃあこの鍵使ってアップしていいよ"というディスパッチ作業だけです。
事前にIoT Hubにディスパッチ先のBlob Storageを登録します。クライアントがファイルアップ命令を発行するとIoT Hubは専用のSAS URIを発行して渡します。
クライアントはそれを利用してファイルをアップします。
これは非常に大切でステキなことです。なぜなら、ファイル容量そのものは送信コストに影響しないからです。
- IoT Hubsのメッセージのコスト算出:送信データを4KB単位で切って"1メッセージ"としてカウント、そのカウント数でコストを算出する(価格について)
- Blob Storage:データの書き込みは無料(価格について)
- Azureに入ってくるデータはネットワーク通信に関する料金は無料(価格について)
今回の場合ですと"ファイル送るよ"メッセージと"ファイル送り切ったよ"メッセージがIoT Hubとしての課金対象となります。
それほど急ぎではないデータを送る分には、ファイルに詰めて定期的に圧縮して送った方が断然お買い得になります。
急ぎのデータだけをテレメトリーデータとして送るというのは、現実的な判断の1つです。
構築順序
今回も"保持"から作ります。
- Blob Storageの作成
- IoT Hubの設定変更
- クライアントの作成
という順序にします。
1. Blob Storageの作成
作るのは面倒なので使いまわそうと思います。
シナリオ1で作成したStorage、Blobは使っていませんよね?
そのまま使いましょう。
作っていない方はこちら↓
ストレージ アカウントの作成を実施してください。
2. IoT Hubの設定変更 と 3. クライアントの作成
IoT Hub を使用してデバイスからクラウドにファイルをアップロードする (.NET)を実施してください。
ちなみに上記ドキュメントでは、アップロードしたことを通知する仕組みも導入していますが、今回は省いていただいていいです。(もちろん実施いただいても良いです。すぐできます。)
ファイルがアップロードされるたびに処理が実施される"イベントドリブン"な仕掛けを構築する場合はファイルアップロードを通知してください。
全体的に収集したデータをバッチ的に処理する場合は通知は不要です。
シナリオ4:デバイスからアラートイベントを送信します。イベントを取得したサービス側(Azure側)は、デバイスに制御メッセージを送ります。
シナリオ4は、今までのシナリオとは少し異なります。Azureからデバイスに向かって通信が発生します。
IoTのシナリオにおいて利用サービス検討の分岐点の1つがこれです。
・デバイスに向かって通信する
=どのデバイスと通信をするのかAzureサイドが認識しなければならない
=デバイスおよびデバイスとの通信状態を把握しなければならない
=IoT Hubを使った方が良い
ということです。
そうではなくテレメトリデータを収集したいだけならEvent Hubsを採用した方がお買い得です。
(ほかにも検討ポイントはありますので、これだけでサービスを絞らないでください)
もう1つ、このシナリオではメッセージルーティングというものを扱います。
メッセージルーティング
今までのシナリオでは、すべてのテレメトリーデータを"組み込みエンドポイント"に送っていました。コンシューマーグループは、その組み込みエンドポイントでデータのコピーを作っていました。
しかし場合によっては、そもそも"組み込みエンドポイント"に送信しなくて良いデータも存在します。
- 開発中や問題発生時だけ確認したいデバッグログ
- エンドユーザーにとって価値を産まないが運用に必要なテレメトリーデータ
- 発生場所に関係なく、優先対応すべきアラートメッセージ
このような情報を、通常のルート(組み込みエンドポイント)に入る前に刈り取ってしまおうというのがメッセージルーティングです。
ここでは細かくは説明しませんが、絵にするとこんな雰囲気です。
①でメッセージルーティングしています。条件を指定して、合致したデータを刈り取っています。そのデータは組み込みエンドポイントには流れません。
残ったデータを②に送り、コンシューマグループでコピーしています。
余談ですが、"①で刈り取れなかったデータは捨てる"という選択も可能です。
Azureからデバイスへの通信方法
大きく3つ存在します。1つはデバイスツイン、1つはダイレクトメソッド、1つはメッセージの送信です。
シナリオ4ではデバイスツイン、シナリオ5ではダイレクトメソッドを扱います。メッセージの送信は今回扱いません。実運用上も意外と必要無いと個人的には思っています。
デバイスツイン
詳細はIoT Hub のデバイス ツインの理解と使用を参照ください。
デバイスツインとは、直訳すると"デバイスの双子"的になりますがイメージで捉えましょう。
デバイスの状態をデジタルにAzure側に保持する機能です。通信が確立していれば、デバイスとAzureで同じ情報を保持しあいます。情報とはプロパティ、属性情報です。
例えば
- 通信間隔を1分とする
- Wi-Fiでつながってる
- ロケーションがどこどこ
- バッテリー残量がいくつ
- ソフトウェアバージョンが1.2.3だ
そんなデバイスの情報です。これをIoT Hubではデバイスツインという機能で保持することができます。デバイスツインには、"Tags"と"Properties"が存在し、"Properties"は"Desired"と"Reported"に分かれます("Tags"は今回説明省略します)。
DesiredはAzure側(サービス側)から申したいプロパティを流します。
Reportedはデバイス側から申したいプロパティを流します。
各々相手側のプロパティは読み取りのみです。
このシナリオ4では、この"Desired Properties"に"お願い事項"を載せてみます。
ダイレクトメソッド
ダイレクトメソッドは、その名の通りデバイスに命令を直接送り付けます。
例えば
- ファンをまわして
- ログを送れ
- 再起動しろ
(口調はともあれ)このような情報を直接的に送り付けます。
シナリオ5では、このダイレクトメソッドを実施します。
どんな時にどっちを使えばいいのか?
端的に書きますと、平常時はデバイスツイン、緊急時はダイレクトメソッドという理解で良いと思います。
大きな違いは、通信状態と命令に対する応答方式が異なります。
デバイスツインは通信が繋がったら情報を送ります。それまでIoT Hubが保持しています。
ダイレクトメソッドは通信が繋がっていないと命令を送ることができません。
デバイスツインはいつかは命令が到達しますが、今届いているかは不明瞭です。
ダイレクトメソッドは直接命令を送り込むので今届きます。(当然、デバイスの状況如何で命令が実行されるか保証まではできない部分もあると思いますが。)
どちらでも必要なこと
デバイス側に、"その情報が到達したときの処理"を事前に準備してあげてください。
デバイスの何を検知するか
今回はサンプルプログラムのここに注目したいと思います。
送信メッセージにプロパティを付与しているのが見てとれると思います。
"温度が30度を超えていたらtemperatureAlertというプロパティをtrueという文字列をセットする"と書いてあります。
このtrueをアラートとします。
余談ですが、このようにメッセージにはプロパティを添えることも可能です。
シナリオ4の構築順序
- Event Hubsの作成(保持)
- IoT Hubの設定変更(メッセージルーティング)
- Functionsの作成(イベント受けるまで)
- クライアントの作成
- Functionsの修正(クライアントにプロパティを送信)
1. Event Hubsの作成
クイック スタート: Azure portal を使用したイベント ハブの作成
こちらを実施ください。
2. IoT Hubの設定変更(メッセージルーティング)
こちらを参考にしつつ作業します。
作業は
- ルーティング先(今回はEvent Hub)を追加
- ルーティングルール(クエリ)を追加
という順番です。
IoT Hubの"メッセージルーティング" - "カスタム エンドポイント"の"+追加"をクリック
"イベントハブ"を選択したのち、適当な名前("forscenario4")を付けて、先ほど作成したEvent Hubを選択し登録します。
正しく登録が完了していると、このように登録したエンドポイントが表示されます。
皆さんの環境がどうなるかはわからないのですが、ボクの環境だとポータルが若干香ばしく、なかなか表示に至りません。不思議な挙動をしたときは、別のメニュー(例えば1つ上の"ファイルのアップロード")を開いてから再度見直してみてください。それでも表示までに少し時間がかかりました。
ルーティングルール(クエリ)の追加
クエリの詳細はこちらのドキュメント、IoT Hub メッセージ ルーティングのクエリ構文を参照ください。
IoT Hubの"メッセージルーティング" - "ルート"の"+追加"をクリック
適当な名前("scenario4query")を付けて、先ほど作成したエンドポイントを選択します。
ルーティングクエリに"temperatureAlert = 'true'"と書き、"保存"します。
(本当に実装する時は下のテストとかを活用してください。テストする時は、"ルートのテスト"というボタンが下の方に出てきますので押してテストしてください。誤って保存ボタンを押すのが"あるある"ですのでご注意を。)
3. Functionsの作成(イベント受けるまで)
とりあえず"今まで作ったものが動くのか?"を確認できる状態にしたいと思います。
できる方はシナリオ1で作成したソリューションに追加いただいても良いですが、ここから読む方やVSCodeな方にも配慮し、プログラムを1から作成したいと思います。
シナリオ1のFunctionsと作業内容はほぼ同じですので、基本的に手順だけ記載します。
- VS2019を起動、新しいプロジェクトの作成、プロジェクトテンプレートでAzure Functionsを選択し"次へ"
- 任意の名前を付けて"作成"
- "Azure Functions v2(.NET Core)"の"Event Hub trigger"を選択し、"Connection string setting name"に**"EventHubConnectionAppSetting"**と入力し"作成"
local.settings.jsonに"EventHubConnectionAppSetting(入力バインドで利用するEvent Hubの接続文字列)"を追加するため、以下の作業を実施します。この接続情報を"共有アクセスポリシー"と言います。
ここは慣れるまで間違いやすいので、以下のイメージを参考にしてください。
ざっくり言うと、"イベントハブ"は日本語だと"イベントハブ"だけどサービス名は"Event Hub s "であり、中に複数の"Event Hub"が入ります。"Event Hub s "にも"Event Hub"にも"共有アクセスポリシー"と呼ばれるものが存在します。
今回は"Event Hub"に入ったデータを読む(リッスン)ために必要な"共有アクセスポリシー"を作成します。
まずはポータルに戻り、先ほど作成したEvent Hubsを開きます。
赤と緑の線を、自分のポータルとよく見比べながら作業をしてください。
"Event Hubs" - "イベントハブ名(scenario4)" - "共有アクセスポリシー"を選択し、"+追加"をクリックします。
適当な名前("listen")を入力、"リッスン"にチェックを入れて"作成"ボタンをクリックします。
作成した"共有アクセスポリシー"をクリックして再表示すると、接続文字列が出ていますので、これをコピーします。
このコピーした値を保持したまま、local.setting.jsonにいきます。
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"EventHubConnectionAppSetting": "コピーした値(この行を追加してください)",
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
}
ここまでできたら、デバイスとFunctionsを動かしてみましょう。
うまく動作していれば、30度を超える値が発生した時のみ、Functionsが反応します。
ここで折り返し地点、ゴール(デバイスへの通知)に向かうため、まずはゴール(デバイス側の処理)を作ります。
4. クライアントの作成
クライアントには"デバイスツイン"の"Desired Properties"が変化したことを受け取る機能が必要です。
シナリオ0のデバイスシミュレーターが皆様のPC上、"azure-iot-samples-csharp-master/iot-hub/Quickstarts/simulated-device"に入っているかと思います。
今回参考にするのは"azure-iot-samples-csharp-master/iot-hub/Samples/device/TwinSample"です。
まずは例にならってTwinSample.csを作成(クラスファイルを追加)します。
例えばこんな感じになります。
TwinSample.cs ファイルの先頭に次の using ステートメントを追加します。
using Microsoft.Azure.Devices.Client;
using Microsoft.Azure.Devices.Shared;
using System.Threading.Tasks;
TwinSampleクラスに次のフィールドを追加します。
private DeviceClient _deviceClient;
TwinSampleクラスに次のメソッドを追加します。
public TwinSample(DeviceClient deviceClient)
{
_deviceClient = deviceClient;
}
public async Task RunSampleAsync()
{
await _deviceClient.SetDesiredPropertyUpdateCallbackAsync(OnDesiredPropertyChanged, null).ConfigureAwait(false);
Console.WriteLine("Retrieving twin...");
Twin twin = await _deviceClient.GetTwinAsync().ConfigureAwait(false);
Console.WriteLine("\tInitial twin value received:");
Console.WriteLine($"\t{twin.ToJson()}");
Console.WriteLine("Sending sample start time as reported property");
TwinCollection reportedProperties = new TwinCollection();
reportedProperties["DateTimeLastAppLaunch"] = DateTime.Now;
await _deviceClient.UpdateReportedPropertiesAsync(reportedProperties).ConfigureAwait(false);
}
private async Task OnDesiredPropertyChanged(TwinCollection desiredProperties, object userContext)
{
Console.WriteLine("\tDesired property changed:");
Console.WriteLine($"\t{desiredProperties.ToJson()}");
Console.WriteLine("\tSending current time as reported property");
TwinCollection reportedProperties = new TwinCollection();
reportedProperties["DateTimeLastDesiredPropertyChangeReceived"] = DateTime.Now;
await _deviceClient.UpdateReportedPropertiesAsync(reportedProperties).ConfigureAwait(false);
}
最後に、Main メソッドに次の行を追加します。
var sample = new TwinSample(s_deviceClient);
sample.RunSampleAsync().GetAwaiter().GetResult();
こんな感じになります。
軽く動作することだけご確認ください。
5. Functionsの修正(クライアントにプロパティを送信)
デバイスシミュレーターを作成する際、IoT Hubへの接続文字列をコピーしてプログラムに記載したかと思います。
その時は、デバイスが利用する接続文字列を入れました。
今回は、デバイスに関係なくIoT Hubに接続するための文字列が必要となります。(サービス側からのIoT Hubへの接続文字列です。)
これにはEvent Hubsでも出てきましたキーワード、"共有アクセスポリシー"を使います。
場所はこちらです。
コピーしておきます。
では、**3. Functionsの作成(イベント受けるまで)**で作成したプログラムに戻ります。
ここに、"おーい、IoT Hub。君のところに所属している"MyDotnetDevice"っていうデバイスに~~っていうプロパティを送っておいてよ"を作ります。
ファイルの先頭に次の using ステートメントを追加します。
using Microsoft.Azure.Devices;
メッセージ取得後のところに、プロパティを登録する処理を追加します。
string s_connectionString = "コピーした値";
RegistryManager registryManager = RegistryManager.CreateFromConnectionString(s_connectionString);
var twin = await registryManager.GetTwinAsync("MyDotnetDevice");
twin.Properties.Desired["cooldown"] = DateTime.Now.ToString();
await registryManager.UpdateTwinAsync("MyDotnetDevice",twin, twin.ETag);
デバイス、Functions共に実行してみてください。
- デバイスがプロパティを取得して表示
- テレメトリとファイル送信
- 30度より高い温度が発生する
- Functions起動
- デバイス側にプロパティ:"cooldown"が伝わる
といった動きになったのではないでしょうか?
デバッグ:デバイスの問題? or Functionsの問題?
Functionsが起動してるのにデバイスが反応しないという問題が起きた方、デバッグ大変ですよね?
なぜならこの2つのプログラムはダイレクトにつながっていないからです。
間に介在するIoT Hubに、プロパティ情報を確認する画面がありますのでご利用ください。
IoT Hub - IoT デバイス - MyDotnetDevice(デバイス名)とクリックします。
デバイス情報の中のメニュー "デバイスツイン"をクリックします。
デバイスツインの情報が見えます。
"desired"の欄を確認して書き換わっていたらFunctionsには問題はありません。
デバイスだけを動作確認する場合、ここの値を更新することでも確認ができます。
シナリオ5:サービス側からデバイスに対して、直接的に命令を送り付けます。]
いよいよ最後のシナリオです。
ここでは、ダイレクトメソッドを扱います。今実行してほしい、今実行してもらうことに意義がある命令を送ります。
構築順序は以下とします。
- デバイスの改修
- Functionsの作成
デバイスの改修
クライアントには"ダイレクトメソッド"を受け取る機能が必要です。
シナリオ0のデバイスシミュレーターが皆様のPC上、"azure-iot-samples-csharp-master/iot-hub/Quickstarts/simulated-device"に入っているかと思います。
今回参考にするのは"azure-iot-samples-csharp-master/iot-hub/Samples/device/simulated-device-2"です。
SimulatedDeviceクラスに次のフィールドを追加します。
private static int s_telemetryInterval = 1;
SimulatedDeviceクラスに次のメソッドを追加します。
// Handle the direct method call
private static Task<MethodResponse> SetTelemetryInterval(MethodRequest methodRequest, object userContext)
{
var data = Encoding.UTF8.GetString(methodRequest.Data);
// Check the payload is a single integer value
if (Int32.TryParse(data, out s_telemetryInterval))
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Telemetry interval set to {0} seconds", data);
Console.ResetColor();
// Acknowlege the direct method call with a 200 success message
string result = "{\"result\":\"Executed direct method: " + methodRequest.Name + "\"}";
return Task.FromResult(new MethodResponse(Encoding.UTF8.GetBytes(result), 200));
}
else
{
// Acknowlege the direct method call with a 400 error message
string result = "{\"result\":\"Invalid parameter\"}";
return Task.FromResult(new MethodResponse(Encoding.UTF8.GetBytes(result), 400));
}
}
最後に、Main メソッドに次の行を追加します。
s_deviceClient.SetMethodHandlerAsync("SetTelemetryInterval", SetTelemetryInterval, null).Wait();
こんな感じになります。
軽く動作することだけご確認ください。
Functionsの作成
今回はタイマートリガーのFunctionsを作成します。
デフォルトでは"5分に1回"の設定になっていますが、私は"1分に1回"の設定に変更しました。
Nugetパッケージをインストールします。
Microsoft.Azure.Devices
ファイルの先頭に次の using ステートメントを追加します。
using Microsoft.Azure.Devices;
using System.Threading.Tasks;
クラスの先頭で変数を宣言します。
private static ServiceClient s_serviceClient;
private readonly static string s_connectionString = "コピーした値(シナリオ4と同じ)";
クラスに、ダイレクトメソッドを送信する処理を追加します。
private static async Task InvokeMethod()
{
var methodInvocation = new CloudToDeviceMethod("SetTelemetryInterval") { ResponseTimeout = TimeSpan.FromSeconds(30) };
methodInvocation.SetPayloadJson("10");
// Invoke the direct method asynchronously and get the response from the simulated device.
var response = await s_serviceClient.InvokeDeviceMethodAsync("MyDotnetDevice", methodInvocation);
Console.WriteLine("Response status: {0}, payload:", response.Status);
Console.WriteLine(response.GetPayloadAsJson());
}
最後に、Main メソッドに次の行を追加します。
s_serviceClient = ServiceClient.CreateFromConnectionString(s_connectionString);
InvokeMethod().GetAwaiter().GetResult();
クライアントとFunctionsを動作させてみましょう。
Functionsから1分に1回、"10"を送信し、それをクライアントがキャッチしているのがわかると思います。
試しに、クライアントを停止した状態でFunctionsを動かしてみましょう。
エラーが起きると思います。
ダイレクトメソッドは、クライアントにダイレクトに接続できない限り動作しないようにできているからです。(本番は、きちんとエラー処理をしてください。)
同じことをシナリオ4で実施してみると、差がわかります。シナリオ4は、IoT Hubにプロパティを書き込んで終わりますので、エラーにはなりません。その代わり、"通知を出した=クライアントが受け取った"にはなりません。
まとめ
5つのシナリオをベースにして、IoT Hubの基本的な活用方法を学びました。
ここに記載した内容だけで本番を迎えるのは稚拙すぎます。しかしながら、これらを自身で作ること、1人で学ぶことを思えば、早くステップアップできたのではないでしょうか。
皆様の学びの一助になれば幸いです。
[おまけ]ドキュメントを読む上で最低限覚えておいた方が良い言葉
デバイス:そのままデバイスですが、データ発生個所よりも通信を実装する場所を"デバイス"と記載していることが多いです
デバイスSDK:デバイス上で動作するものを実装する上で便利なSDK
サービス:Azure側もしくはAzureを使ってデバイス全体を掌握する側
サービスSDK:サービスを実装する上で便利なSDK
D2C:Device to Cloudの略、デバイスからクラウドに向かう通信
C2D:Cloud to Deviceの略、クラウドからデバイスに向かう通信
HTTP,MQTT,AMQP:デバイスとAzureがIoT Hubを介して会話するプロトコル