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

Oculus GoとNode-REDでMQTTをやり取りするメモです。

周辺環境

2018/07/16現在、周辺環境は以下のとおりです。

MQTTライブラリをダウンロードする

UnityでMQTTライブラリを使って、Milkcocoaに接続、さらにiPadでも動かす の記事を参考に、MQTTライブラリは

vovacooper/Unity3d_MQTT: MQTT protocol running on Unity3d

を使うことにしました。

ライブラリを見てみる

image

zipファイルを開くと、Assetsの中を見に行きます。

image

フォルダの配置

MQTTフォルダがあるのでこれを移植します。

image

Assetsのトップに入れました。

image

中はこんな感じです。

Scripting Runtime Versionを .NET 4.x Equivalent に

image

スレッドの処理を使うためにScripting Runtime Versionを .NET 4.x Equivalent にしておきます。

今回の動作

image

Node-REDはシンプルにMQTTブローカーを立てた上で、データを送っています。

UnityからNode-REDへのデータのやり取り

image

Cubeに仕込んでいる処理はこのとおりです。

ノーマル時は Exit という文字が、そしてポインタを乗せたときすこしCubeが傾き Enter となり、クリックすると傾いた上で少し大きくなります。また、クリック時にNode-REDにMQTT送信します。

image

このフローで受け取っています。

image

受信するとこのようにNode-RED側ではログが出ます。

Node-REDからUnityへのデータのやり取り

image

こちらのフローでNode-REDからUnityへデータを送っています。

image

データの中身は、

{"message":"MQTT Subscribed!!!!"}

となっています。

image

実際にデータを送ってみるとこのように反応します。

プログラム

先ほどお伝えした動作をCubeに割り当てたプログラムは以下のとおりです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

using System.Net;
using uPLibrary.Networking.M2Mqtt;
using uPLibrary.Networking.M2Mqtt.Messages;
using uPLibrary.Networking.M2Mqtt.Utility;
using uPLibrary.Networking.M2Mqtt.Exceptions;

using System;
using MiniJSON;

using System.Threading.Tasks;
using System.Threading;

public class Cube : MonoBehaviour, IPointerExitHandler, IPointerEnterHandler, IPointerClickHandler
{
    private MqttClient client;

    SynchronizationContext context;

    // クリックカウント
    private float clickCount = 0;

    // MQTT接続先
    private string MQTT_IP_ADDRESS = "MQTT_IP_ADDRESS";

    // Text
    private TextMesh TextCubeStatus;
    private TextMesh TextSubscribeMessage;

    void Start()
    {
        // メインスレッドのSynchronizationContextを記録して戻るようにしておく
        context = SynchronizationContext.Current;

        // 接続先の指定
        client = new MqttClient(MQTT_IP_ADDRESS, 1883, false, null);

        // MQTT Subscribe 時のイベントを受け取る処理の紐づけ
        client.MqttMsgPublishReceived += MqttMsgPublishReceived;

        // ユニークなID生成(重複すると通信が混乱する)
        string clientId = Guid.NewGuid().ToString();

        // 接続
        client.Connect(clientId);

        // MQTT Subscribeの指定 sub/OculusGo
        client.Subscribe(new string[] { "sub/OculusGo" }, new byte[] { MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE });

        // Text
        TextCubeStatus = GameObject.Find("TextCubeStatus").GetComponent<TextMesh>();
        TextSubscribeMessage = GameObject.Find("TextSubscribeMessage").GetComponent<TextMesh>();
    }

    async void MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
    {

        // 受信データのString変換
        string ReceivedMessage = System.Text.Encoding.UTF8.GetString(e.Message);

        Debug.LogFormat("ReceivedMessage : {0}", ReceivedMessage);

        await Task.Run(() =>
        {

            SynchronizationContext.SetSynchronizationContext(context);

            // メインスレッドに戻す
            context.Post((state) =>
            {
                // JSON化
                var jsonDataTree = Json.Deserialize(ReceivedMessage) as Dictionary<string, object>;

                // TextSubscribeMessageに結果を割り当て message値
                TextSubscribeMessage.text = (string)jsonDataTree["message"];
                TextSubscribeMessage.color = Color.green;

            }, null);
        });
    }

    // クリック時にデータ送信
    public void OnPointerClick(PointerEventData pointerEventData)
    {
        TextCubeStatus.text = "Click";
        TextCubeStatus.color = Color.red;
        this.transform.localScale = new Vector3(0.6f, 0.6f, 0.6f);

        // MQTTへのPublish //////////////////////////////////////

        // カウントアップ
        clickCount++;

        var rootData = new Dictionary<string, object>();
        rootData["message"] = string.Format("OculusGo clicked {0}", clickCount);

        var rootDataJSON = Json.Serialize(rootData);
        client.Publish("pub/OculusGo", System.Text.Encoding.UTF8.GetBytes(rootDataJSON), MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE, false);

        
    }

    public void OnPointerEnter(PointerEventData pointerEventData)
    {
        TextCubeStatus.text = "Enter";
        TextCubeStatus.color = Color.blue;
        this.transform.eulerAngles = new Vector3(5f, 5f, 5f);
        this.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
    }

    public void OnPointerExit(PointerEventData pointerEventData)
    {
        TextCubeStatus.text = "Exit";
        TextCubeStatus.color = Color.black;
        this.transform.eulerAngles = new Vector3(0f, 0f, 0f);
        this.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
    }
}

注意ポイント1:MQTTデータ受信時は非同期

MQTTデータ受信時は非同期なので、そのままTextMeshに触ろうとすると残念ながらフリーズします。

スレッド処理でメインスレッドに戻してからTextMeshに触っています。

    async void MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
    {

        // 受信データのString変換
        string ReceivedMessage = System.Text.Encoding.UTF8.GetString(e.Message);

        Debug.LogFormat("ReceivedMessage : {0}", ReceivedMessage);

        await Task.Run(() =>
        {

            SynchronizationContext.SetSynchronizationContext(context);

            // メインスレッドに戻す
            context.Post((state) =>
            {

以下の記事を参考にしつつ、モノにするにはちょっと苦労しました。

注意ポイント2:Publish時のQosはQOS_LEVEL_AT_MOST_ONCEで

client.Publish("pub/OculusGo", System.Text.Encoding.UTF8.GetBytes(rootDataJSON), MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE, false);

これはまだ経験則でしかないのですが、QOS_LEVEL_EXACTLY_ONCE (QoS = 2) にしてしまうと、確実性を高めるためなのか、何度もデータが送られるケースがあるので、私の場合はQOS_LEVEL_AT_MOST_ONCE (QoS = 0) にしています。もし、お使いの環境でQoSのレベルを変える必要があれば、現場に合わせて変更ください。

機会があれば、もう少し検証してみます。

動かしてみる

それでは実際に動かしてみましょう。

UnityからNode-REDへのデータのやり取りです。

こちらはOculus Goの動作だけだと分かりにくいですが、何度もクリックしてデータが無事飛んでいます。

image

Node-REDからUnityへのデータのやり取りです。

無事、MQTT Subscribed!!!!と反応しています。

まとめ

OculusGoでMQTTをやり取りするメモをお伝えしました。

このようにOculus Go購入早々にうまく連携ができたと思ったのですが、TextMeshへの受信データをしたところガッツリ非同期の沼にハマって、ひと勉強してきたという次第です。

ともあれ、一度出来上がるとかなり安定して動いてくれています。これでデータの入出力がものにできたので、IoTの連携などいろいろと考えていきたいと思います。

それでは、よき Oculus Go & Node-RED & MQTT Life を!