HoloLensとAzure Functionsを連携してDocumentDBにログ保存するメモ

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通信を受け入れます。

おおむね、以下の記事を参考に進めていくことができます。

image

Azure Functionsの追加ボタンで新しいトリガー生成します。

image

JavaScript HTTPトリガー選択します。

image

わかりやすくHoloLensらしい名前をつけます。

image

まずサンプルコードが作られます。

image

この仕組みが動作するURLをエディタ近辺にある「関数のURLの取得」から表示します。

image

こちらを控えておきます。

この時点で、たとえば、

https://<自分のAzure Functionsのサブドメイン名>.azurewebsites.net/api/<今回のトリガー名>?code=<固有の乱数ID?>&name=123456789

とURLを打つと、トリガーが値を受け入れて、

image

と返答するような仕組みになっています。

DocumentDBの出力を追加する

さらに、DocumentDBの出力を追加しましょう。以下の記事が参考になります。

image

すでに、HTTPトリガーとしてのHTTPレスポンス返答用の出力が居るので、新しい出力をクリック。

image

Azure DocumentDBを探してクリック。

image

自分の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連携するメモを下地に青と赤の立方体を用意します。

image

それぞれの立方体に割り当てるスクリプトはこんな感じです。

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 追記

検証ありがとうございます!そうなんです、以下はインポートする必要があるのでご注意下さい。

  • MiniJSON
    • POST送信時にJSONを扱いやすくするためのJSON操作ライブラリ
  • iTween
    • 立方体のアニメーションをさせるためのライブラリ

また、Origamiをベースにしているので、いちから実装する場合はIInputClickHandlerまわりも実装下さい。

動作させてみる

実際に動作させてみた動画がこちらです。

早速、無事送られたか、データ確認してみましょう。

image

まず、Azure Functions側でみてみるとトリガーが動作しているようです。

つづいて、保存先のDocument DBに保存されているか見てみます。

image

保存先のDocument DBのトップページではコレクションが無事作られているようです。

image

保存先のDocument DBのメニューにドキュメントエクスプローラーがあるので見に行きます。

image

データが保存されているようなので詳細を見てみます。

image

image

クリックした順番に無事保存されているようです!

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 を!