HoloLensはMicrosoft製品です。ということは、やはりAzureを連携するとより良いのでは!と考えたくなります。
そんなわけで、HoloLensとAzure Functionsを連携してDocumentDBにログ保存するメモです。
先日、HoloLensアプリ(Unity)でHTTP POST通信できるようになったので、さらにAzure FunctionsのHTTP トリガーと組み合わせて連携してみます。
やりたいこと
HoloLensアプリ(Unity)でHTTP POST通信できるようになった内容を発展させて、HoloLensでAirTapするとオブジェクト名を送る赤と青の立方体実装をしておいて、その送り先をAzure Functions HTTPトリガーにして、DocumentDBに出力しそのデータ保存する仕組みをやってみます。
Azure FunctionsのHTTP トリガーをつくる
まずIFTTTでのMaker Channelと同じ流れで、Azure FunctionsのHTTP トリガーを作りHoloLensのPOST通信を受け入れます。
おおむね、以下の記事を参考に進めていくことができます。
Azure Functionsの追加ボタンで新しいトリガー生成します。
JavaScript HTTPトリガー選択します。
わかりやすくHoloLensらしい名前をつけます。
まずサンプルコードが作られます。
この仕組みが動作するURLをエディタ近辺にある「関数のURLの取得」から表示します。
こちらを控えておきます。
この時点で、たとえば、
https://<自分のAzure Functionsのサブドメイン名>.azurewebsites.net/api/<今回のトリガー名>?code=<固有の乱数ID?>&name=123456789
とURLを打つと、トリガーが値を受け入れて、
と返答するような仕組みになっています。
DocumentDBの出力を追加する
さらに、DocumentDBの出力を追加しましょう。以下の記事が参考になります。
すでに、HTTPトリガーとしてのHTTPレスポンス返答用の出力が居るので、新しい出力をクリック。
Azure DocumentDBを探してクリック。
自分のAzure DocumentDBを未作成であれば作成して、以下のように設定します。
- ドキュメント パラメーター名
- outputDocument
- コレクション名
- MyCollectionHoloLens
- データベース名
- outDatabaseHoloLens
- DocumentDB のデータベースとコレクションが作成されるようにしますか?
- チェックする
とくに「DocumentDB のデータベースとコレクションが作成されるようにしますか?」をチェックすることで、コレクション名とデータベース名が自動でつくられるので無用のトラブルが減ります。
こちらに則ってDocumentDB用の受け入れコードを加えます。
module.exports = function (context, req) { context.log('JavaScript HTTP trigger function processed a request.'); if (req.query.name || (req.body && req.body.name)) { context.res = { // status: 200, /* Defaults to 200 */ body: "Hello " + (req.query.name || req.body.name) }; } else { context.res = { status: 400, body: "Please pass a name on the query string or in the request body" }; } // DocumentDBの出力部分 context.bindings.outputDocument = { text : "I'm running in a JavaScript function! Data: '" + "Hello HoloLens name : " + (req.query.name || req.body.name) + "'" } context.done(); };
context.bindings.outputDocumentあたりのコードを先ほどの記事を参考に加えています。
HoloLensアプリ(Unity)側の準備
つづいて、HoloLensアプリ(Unity)の準備です。
HoloLensアプリ(Unity)とIFTTT Maker ChannelをHTTP POST連携するメモを下地に青と赤の立方体を用意します。
それぞれの立方体に割り当てるスクリプトはこんな感じです。
using UnityEngine; using System.Collections; using System.Text; using UnityEngine.Networking; using MiniJSON; using System.Collections.Generic; public class CubeCommands : MonoBehaviour { Vector3 originalPosition; Vector3 originalRotation; Vector3 originalScale; public bool flagOnOff = false; #if UNITY_EDITOR void OnMouseDown() { Debug.Log("OnMouseDown"); OnSelect(); } #endif void Start() { originalPosition = this.transform.localPosition; originalRotation = this.transform.localRotation.eulerAngles; originalScale = this.transform.localScale; } void OnSelect() { if (flagOnOff) { flagOnOff = false; // 移動 iTween.MoveTo(this.gameObject, iTween.Hash( "y", originalPosition.y, "time", 2f, "oncomplete", "AnimationEnd", "oncompletetarget", this.gameObject, "easeType", "easeInOutBack" )); // 回転 iTween.RotateTo(this.gameObject, iTween.Hash( "x", originalRotation.x, "time", 2f )); // 大きさ iTween.ScaleTo(this.gameObject, iTween.Hash( "x", originalScale.x, "y", originalScale.y, "z", originalScale.z, "time", 2f )); } else { flagOnOff = true; var dic = Json.Deserialize("{\"value1\":\"\",\"value2\":2,\"value3\":3}") as Dictionary<string, object>; dic["name"] = this.gameObject.name; var json = Json.Serialize(dic); Debug.Log(json); StartCoroutine(Post(" <「関数のURLの取得」で得たトリガーのURL> ", (string)json)); // 移動 iTween.MoveTo(this.gameObject, iTween.Hash( "y", originalPosition.y + 0.2, "time", 2f, "oncomplete", "AnimationEnd", "oncompletetarget", this.gameObject, "easeType", "easeInOutBack" )); } } void OnReset() { } void OnDrop() { } IEnumerator Post(string url, string bodyJsonString) { Debug.Log("Post: " + url); var request = new UnityWebRequest(url, "POST"); byte[] bodyRaw = Encoding.UTF8.GetBytes(bodyJsonString); request.uploadHandler = (UploadHandler)new UploadHandlerRaw(bodyRaw); request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); yield return request.Send(); Debug.Log("Status Code: " + request.responseCode); if (request.isError) { Debug.Log(request.error); } else { Debug.Log(request.downloadHandler.text); // 回転 iTween.RotateTo(this.gameObject, iTween.Hash( "x", 90, "time", 2f )); // 大きさ iTween.ScaleTo(this.gameObject, iTween.Hash( "x", originalScale.x + 0.1, "y", originalScale.y + 0.1, "z", originalScale.z + 0.1, "time", 2f )); } } }
JSONデータでPOSTする仕組みはIFTTTへ送るときと今回のAzure FunctionsのHTTPトリガーと仕組みは同じなので、nameに今回クリックされたオブジェクトの名前を
dic["name"] = this.gameObject.name;
と送るシンプルなものです。
その他依存関係 2017/4/25 追記
試した。HoloとAzureはこれから連携必須だと思う。DBに格納できるのは拡張性ありそう。記事内ではMiniJSONとiTweenについて触れられてないけどインポートが必要。Origamiなしの状態から作ったので、IInputClickHandler周りの実装も自分で追加した。 https://t.co/KYRNGTsywa
— morio (@morio36) 2017年4月25日
検証ありがとうございます!そうなんです、以下はインポートする必要があるのでご注意下さい。
- MiniJSON
- POST送信時にJSONを扱いやすくするためのJSON操作ライブラリ
- iTween
- 立方体のアニメーションをさせるためのライブラリ
また、Origamiをベースにしているので、いちから実装する場合はIInputClickHandlerまわりも実装下さい。
動作させてみる
実際に動作させてみた動画がこちらです。
早速、無事送られたか、データ確認してみましょう。
まず、Azure Functions側でみてみるとトリガーが動作しているようです。
つづいて、保存先のDocument DBに保存されているか見てみます。
保存先のDocument DBのトップページではコレクションが無事作られているようです。
保存先のDocument DBのメニューにドキュメントエクスプローラーがあるので見に行きます。
データが保存されているようなので詳細を見てみます。
クリックした順番に無事保存されているようです!
js // DocumentDBの出力部分 context.bindings.outputDocument = { text : "I'm running in a JavaScript function! Data: '" + "Hello HoloLens name : " + (req.query.name || req.body.name) + "'" }
出力で書いたコードがしっかり動いていますね。
HoloLensとAzure Functionsを連携が無事できました。
今回はごくごくシンプルにつなぎこんでみましたが、ネットワークと連携したコンテンツを作っていくときに、このようにAzureへの絡み方がわかっていると、すぐにやりたい仕組みを試すことができプラスに働きそうです。
例えば、オブジェクト名の保存一つとっても、ユーザーの操作ログをつぶさに分析して使い勝手を改善したり、みんなの操作を集めた楽しい反応を作ったりと用途は広がりそうですね。
それは、よき HoloLens & Unity & Azure Functions Life を!