HoloLensとNode-REDでMQTTをやり取りするメモ

ここ半年ほど安定して使えているので、HoloLensとNode-REDでMQTTをやり取りするメモを残しておきます。

バージョン状況

  • Unityバージョン
    • 2017.1.2f1
  • VIsual Studio 2017
    • 15.8.3

サクッと作るときに自分の手元で安定しているものなので、他のバージョンでも使えるナレッジかと思われます。

ソースコード

MQTTはM2Mqttライブラリを利用して使います。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MiniJSON;

#if UNITY_UWP
using System;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

// M2Mqtt
using uPLibrary.Networking.M2Mqtt;
using uPLibrary.Networking.M2Mqtt.Messages;
#endif

public class MQTTManager : MonoBehaviour
{

#if UNITY_UWP
    MqttClient client;
    string clientId;
    string topicPublishPath;
    string topicSubscribePath;
    string BrokerAddress;
#endif

    // Use this for initialization
    void Start()
    {
        Debug.Log(string.Format("[DISPLAY] MQTTManager START"));
#if UNITY_UWP
        
        // 接続先のアドレス
        BrokerAddress = "<BrokerAddress>";

        clientId = Guid.NewGuid().ToString();

        client = new MqttClient(BrokerAddress);
        client.ProtocolVersion = MqttProtocolVersion.Version_3_1;
        
        client.MqttMsgPublishReceived += client_MqttMsgPublishReceived;

        // publish先のtopic
        topicPublishPath = "pub/HoloLens";
        // subscribe先のtopic
        topicSubscribePath = "sub/HoloLens";

        try
        {
            client.Connect(clientId);
        }
        catch (Exception e)
        {
            Debug.Log(string.Format("Exception has occurred in connecting to MQTT {0} ", e ));
            throw new Exception("Exception has occurred in connecting to MQTT", e.InnerException);
        }

        Debug.Log("Connect OK");

        // Subscribe
        client.Subscribe(new string[] { topicSubscribePath }, new byte[] { 2 });

        // Publish
        try
        {
            // JSONデータを作る
            var data = new Dictionary<string, object>();
            data["status"] = "HoloLens connected";
            string json = Json.Serialize(data);

            // publish a message with QoS 0
            client.Publish(topicPublishPath, Encoding.UTF8.GetBytes(json), MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE, true);
        }
        catch (Exception e)
        {
            Debug.Log("Exception has occurred in publishEvent");
            throw new Exception("Exception has occurred in publishEvent ", e);
        }

#endif

    }

    // Update is called once per frame
    void Update()
    {

    }



#if UNITY_UWP

    // this code runs when a message was received
    void client_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
    {
        string ReceivedMessage = Encoding.UTF8.GetString(e.Message);
        
        Debug.Log("client_MqttMsgPublishReceived");
        Debug.Log(ReceivedMessage);

        Task.Run(async () =>
        {
            // Unityの描画に処理を戻す
            UnityEngine.WSA.Application.InvokeOnAppThread(() => {

                var jsonDataTree = Json.Deserialize(ReceivedMessage) as Dictionary<string, object>;

                // JSONデータのmessage値を取得する
                Debug.Log(jsonDataTree["message"]);

               // ここでGameObjectなど描画に関する処理を書く

            }, true);

            await Task.Delay(100);

        });

        
    }
#endif

    void OnClosed()
    {
#if UNITY_UWP
        client.Disconnect();
#endif
    }
}

こちらを同名のGameObjectを作って適用しておきます。

image

image

MiniJSONもインストール

Unity3D: MiniJSON Decodes and encodes simple JSON strings. Not intended for use with massive JSON strings, probably < 32k preferred. Handy for parsing JSON from inside Unity3d.

上記のソースでは、JSONデータを扱いやすくするため、MiniJSONもインストールします。

Unityからビルドすると

image

UnityからVisual Studioプロジェクトとして書き出します。

image

Visual Studioプロジェクト(~.sln)開いてみると以下のようなエラーが出ます。

image

ひとつひとつ解決します。

一旦 x86 でリビルド

書き出されたプロジェクトによるかもしれませんが、私の場合、初期設定がARMのビルドになっているのか、いろいろとエラーが出ます。

image

まず、x86に指定してから、

image

ソリューションのリビルドをします。

M2Mqttライブラリが無いAssembly-CSharpエラーを解決

image

重大度レベル コード 説明 プロジェクト ファイル 行 抑制状態
エラー CS0103 現在のコンテキストに ‘MqttProtocolVersion’ という名前は存在しません。 Assembly-CSharp F:\Creative\workspace\hololens\holo_mqtt_blog_0911\Assets\Scripts\MQTTManager.cs 40 アクティブ

のように、がっつり、

using uPLibrary.Networking.M2Mqtt;
using uPLibrary.Networking.M2Mqtt.Messages;

あたりでM2Mqttライブラリがないとエラーが出ています。

image

パッケージマネージャータブを選択します。

image

既存のプロジェクトをAssembly-CSharpにします。

Install-Package M2Mqtt

で、パッケージマネージャーでコマンドを打ってインストールします。

PM> Install-Package M2Mqtt
  GET https://api.nuget.org/v3/registration3-gz/m2mqtt/index.json
  OK https://api.nuget.org/v3/registration3-gz/m2mqtt/index.json 190 ミリ秒
C:\          \holo_mqtt_blog_0911\build\GeneratedProjects\UWP\Assembly-CSharp\project.json のパッケージを復元しています...
復元をコミットしています...
Writing lock file to disk. Path: C:\Creative\workspace\git\holo_mqtt_blog_0911\build\GeneratedProjects\UWP\Assembly-CSharp\project.lock.json
C:\          \holo_mqtt_blog_0911\build\GeneratedProjects\UWP\Assembly-CSharp\Assembly-CSharp.csproj の復元が 505.67 ms で完了しました。
'M2Mqtt 4.3.0' が Assembly-CSharp に正常にインストールされました
C:\          \holo_mqtt_blog_0911\build\HoloSimple1.1\project.json のパッケージを復元しています...
復元をコミットしています...
Writing lock file to disk. Path: C:\Creative\workspace\git\holo_mqtt_blog_0911\build\HoloSimple1.1\project.lock.json
C:\          \holo_mqtt_blog_0911\build\HoloSimple1.1\HoloSimple1.1.csproj の復元が 538.04 ms で完了しました。
NuGet の操作の実行に 1.29 sec かかりました
経過した時間: 00:00:04.5622172
PM> 

このようにインストールされるはずです。

image

無事エラーが解消されました。

動かしてみる

Node-REDでやりとりしてみます。

Choregraphe入っているPCにNode-REDを入れてPepperとMQTT連携するメモ

の記事のように、Node-RED上でMQTTブローカーも作っている前提で進めます。

image

フローのJSONはこちらです。

[{"id":"5770fe87.186e9","type":"mqtt in","z":"88d61bce.c3bcd8","name":"","topic":"pub/HoloLens","qos":"2","broker":"f732bc1c.35538","x":260,"y":200,"wires":[["cf09c3a5.55a11"]]},{"id":"cf09c3a5.55a11","type":"debug","z":"88d61bce.c3bcd8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":450,"y":200,"wires":[]},{"id":"9b69e751.b39ad8","type":"inject","z":"88d61bce.c3bcd8","name":"","topic":"","payload":"{\"message\":\"Hello HoloLens!!\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":250,"y":260,"wires":[["bd2f11d.55d13f"]]},{"id":"bd2f11d.55d13f","type":"mqtt out","z":"88d61bce.c3bcd8","name":"","topic":"sub/HoloLens","qos":"","retain":"","broker":"f732bc1c.35538","x":460,"y":260,"wires":[]},{"id":"f732bc1c.35538","type":"mqtt-broker","z":"","broker":"127.0.0.1","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"willTopic":"","willQos":"0","willPayload":"","birthTopic":"","birthQos":"0","birthPayload":""}]

トピック pub/HoloLens でHoloLensからデータを受け取り、トピック sub/HoloLens でHoloLensにデータを送るシンプルなやり取りです。

トピック pub/HoloLens でHoloLensからデータを受け取る

実際に起動してみると、MQTTManagerは起動時に動き出すので、

image

このようにNode-REDのデバッグタブにメッセージが表示されています。

            // JSONデータを作る
            var data = new Dictionary<string, object>();
            data["status"] = "HoloLens connected";
            string json = Json.Serialize(data);

            // publish a message with QoS 0
            client.Publish(topicPublishPath, Encoding.UTF8.GetBytes(json), MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE, true);

この部分が動作していてMiniJSONでSerializeされたJSON文字列が送信されています。

注意ポイントは、Oculus GoとNode-REDでMQTTをやり取りするメモで言及した QOS を 0 で送るところですね。QOS 2 で、なぜうまく行かないかが理論的に分かってないのですが、経験則に寄せています。

トピック sub/HoloLens でHoloLensにデータを送る

image

image

また、injectノードでは「Hello HoloLens!!」という文字列をメッセージを送ります。

image

injectノードを押してみると、無事、Visual Studioのコンソールでメッセージが表示されます。

    void client_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
    {
        string ReceivedMessage = Encoding.UTF8.GetString(e.Message);
        
        Debug.Log("client_MqttMsgPublishReceived");
        Debug.Log(ReceivedMessage);

        Task.Run(async () =>
        {
            // Unityの描画に処理を戻す
            UnityEngine.WSA.Application.InvokeOnAppThread(() => {

                var jsonDataTree = Json.Deserialize(ReceivedMessage) as Dictionary<string, object>;

                // JSONデータのmessage値を取得する
                Debug.Log(jsonDataTree["message"]);

               // ここでGameObjectなど描画に関する処理を書く

            }, true);

            await Task.Delay(100);

        });

        
    }

HoloLensがデータを受け取ったときに反応するのはこのあたり。

string ReceivedMessage で文字列として受け取りつつ、Json.Deserializeをかけて、JSONデータツリーのmessage値をDebug.Logで出力しています。

UnityEngine.WSA.Application.InvokeOnAppThread に関する使い方は、

HoloLensアプリ(Unity)とNode-REDで動くWebSocketサーバーをつなぐメモ

の記事のような、Unityの描画に処理を戻してアニメーションをさせる流れを参考にしてください。

実際使っていて

HoloLensからサーバーにデータを送る場合はHTTPでも事足りるのですが、やはり、HoloLensへ何かしらを制御する場合、このMQTTのナレッジは重宝しています。

明星和楽2018でIoT&MixedReality展示をしてきました

たとえば、こちらのコンテンツで、IoTでHoloLens内の映像を変化させるときに活躍しました。もちろん、HTTPやWebSocketを使う場合もあるので、このあたりは、案件の状況に合わせて使い分けています。

それでは、よき MQTT & HoloLens Lifeを。