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に割り当てたプログラムは以下のとおりです。
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の動作だけだと分かりにくいですが、何度もクリックしてデータが無事飛んでいます。
Node-REDからUnityへのデータのやり取りです。
無事、MQTT Subscribed!!!!と反応しています。
まとめ
OculusGoでMQTTをやり取りするメモをお伝えしました。
このようにOculus Go購入早々にうまく連携ができたと思ったのですが、TextMeshへの受信データをしたところガッツリ非同期の沼にハマって、ひと勉強してきたという次第です。
ともあれ、一度出来上がるとかなり安定して動いてくれています。これでデータの入出力がものにできたので、IoTの連携などいろいろと考えていきたいと思います。
それでは、よき Oculus Go & Node-RED & MQTT Life を!