Oculus GoとNode-REDでMQTTをやり取りするメモ
Oculus GoとNode-REDでMQTTをやり取りするメモです。
周辺環境
2018/07/16現在、周辺環境は以下のとおりです。
- Unityバージョン
- 2018.1.2f1
- Oculus Go開発の初期設定済み
- 以下の記事を参考に初期設定しています
- OculusGoプロジェクトの準備
- 実際にインタラクティブ動作をするOculusGoプロジェクトのつくりかたはこちらを参考にしています。
MQTTライブラリをダウンロードする
UnityでMQTTライブラリを使って、Milkcocoaに接続、さらにiPadでも動かす の記事を参考に、MQTTライブラリは
vovacooper/Unity3d_MQTT: MQTT protocol running on Unity3d
を使うことにしました。
ライブラリを見てみる

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

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

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

中はこんな感じです。
Scripting Runtime Versionを .NET 4.x Equivalent に

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

Node-REDはシンプルにMQTTブローカーを立てた上で、データを送っています。
UnityからNode-REDへのデータのやり取り

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

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

受信するとこのようにNode-RED側ではログが出ます。
Node-REDからUnityへのデータのやり取り

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

データの中身は、
{"message":"MQTT Subscribed!!!!"}
``` となっています。

実際にデータを送ってみるとこのように反応します。
## プログラム
先ほどお伝えした動作をCubeに割り当てたプログラムは以下のとおりです。
```csharp
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へのデータのやり取りです。
https://www.youtube.com/watch?v=nSRw3n_jVr4
こちらはOculus Goの動作だけだと分かりにくいですが、何度もクリックしてデータが無事飛んでいます。

Node-REDからUnityへのデータのやり取りです。
https://www.youtube.com/watch?v=rDIeYUf3D70
無事、MQTT Subscribed!!!!と反応しています。
まとめ
OculusGoでMQTTをやり取りするメモをお伝えしました。
https://www.youtube.com/watch?v=eLCAUP484OA
このようにOculus Go購入早々にうまく連携ができたと思ったのですが、TextMeshへの受信データをしたところガッツリ非同期の沼にハマって、ひと勉強してきたという次第です。
ともあれ、一度出来上がるとかなり安定して動いてくれています。これでデータの入出力がものにできたので、IoTの連携などいろいろと考えていきたいと思います。
それでは、よき Oculus Go & Node-RED & MQTT Life を!