HoloLens2 と Node-RED を WebSocket でやり取りするメモ
HoloLens2 と Node-RED を WebSocket でやり取りするメモです。
「おおむね」以前の HoloLens1 の知識をベースにできた
https://www.1ft-seabass.jp/memo/2017/04/06/hololens_websocket_node-red_firstcontact/
こちらの記事をベースに動かせるか試してみまして、結果としては、以下のように無事成功しました!
https://twitter.com/1ft_seabass/status/1279602514992623616
ですが、ここまでだと、「おおむね」成功。
- HoloLens2 が Node-RED の WebSocket 初回接続時にメッセージを送る
- Node-RED が HoloLens2 にタイムスタンプ文字列を送って HoloLens2 側で受けつける
の2つだけしているので、あともう一歩です。実は残りの挙動である「HoloLens2 の Cube をタッチすると Node-RED の WebSocket にメッセージを送る」の実装に、少し苦労しました。
つくる前に心配していたところ
https://www.1ft-seabass.jp/memo/2020/03/15/hololens2-build-to-emulator-firststep/
こちらで、ある程度雰囲気はつかんでいるものの、実機へのビルドというものが、いまいちピンと来ていませんでした。
既存のアプリを HoloLens 2 で利用できるようにするドキュメントにあるように、
- x86ベースではなくてARM32 および ARM64 ビルド
- プロジェクトの IL2CPP への切り替え
- 果たして、以前のUWPのコードがうまく動くのかどうか
というあたりが心配でしたが、上記の記事でだいぶ把握は追いついており、HoloLens1と同様にHoloLens2をPCに挿して、Unityで書きだされたVIsual Studio プロジェクトからデバイスにビルドすればうまくいきました。
事前準備
環境は
- Unity 2019.4.1.f1
- Visual Studio Tools for Unity 4.6.1.0
- Microsoft Visual Studio Community 2019 Version 16.6.3
で進めています。
以下の準備を完了している状態で進めます。
ボタンにする形状は薄く小さく

まずはじめは Scale を X:0.1 Y:0.1 Z:0.1 で立方体にしたときに、実際に指で押すと、当たり判定が瞬間的に複数出てしまうことがありました。

さんざん試行錯誤したのですが、ボタンの形状の話がこちらに出ていたので、Scale を X:0.1 Y:0.1 Z:0.2 にしたところ、うまく反応するようにできました。つまり、 Z:0.2 によって厚さを薄くしました。
タッチイベントの設定

このように、 NearInteractionまわりのドキュメント を参考にしつつ、上記のように、
- NearInteractionTouchable の設定
- TouchHander の割り当て
- Cube自体に独自の CubeTouchEvent.cs を加える
といったことをしています。TouchHander から OnTouchStarted , OnTouchCompleted のイベントを、CubeTouchEventのTouchStarted , TouchCompletedに関連付けています。
CubeTouchEvent.cs の中身
こちらの記事をベースにつくられた CubeTouchEvent.cs の中身です。
using System.Collections;
using System.Collections.Generic;
using Microsoft.MixedReality.Toolkit.Input;
using UnityEngine;
using UnityEngine.Networking;
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
#if WINDOWS_UWP
using Windows.Foundation;
using Windows.Networking.Sockets;
using Windows.Security.Cryptography.Certificates;
using Windows.Storage.Streams;
using System.Threading.Tasks;
#else
using System.Text;
#endif
public class CubeTouchEvent : MonoBehaviour
{
#if WINDOWS_UWP
private MessageWebSocket messageWebSocket;
#endif
void Start()
{
#if WINDOWS_UWP
// HoloLens2 実機でWebSocket接続開始
OnConnect();
#endif
}
void Update()
{
}
public void TouchStarted(HandTrackingInputEventData eventData)
{
// TouchStartedが2度送られるときはボタンの形状を薄くしたり小さめにすると1度だけになった
Debug.Log("TouchStarted");
// 直接タッチするとちょっと小さくなる
this.transform.localScale = new Vector3(0.09f, 0.09f, 0.02f);
#if WINDOWS_UWP
try
{
Task.Run(async () => {
await WebSock_SendMessage(messageWebSocket, "Touched!! 1");
});
}
catch (Exception ex)
{
Debug.Log("error : " + ex.ToString());
}
#endif
}
public void TouchCompleted(HandTrackingInputEventData eventData)
{
Debug.Log("TouchCompleted");
// 元に戻る
this.transform.localScale = new Vector3(0.1f, 0.1f, 0.02f);
}
#if WINDOWS_UWP
private void OnConnect()
{
Debug.Log("OnConnect");
messageWebSocket = new MessageWebSocket();
messageWebSocket.Control.MessageType = SocketMessageType.Utf8;
messageWebSocket.MessageReceived += WebSock_MessageReceived;
messageWebSocket.Closed += WebSock_Closed;
Uri serverUri = new Uri("ws://192.168.1.105:1880"); // 別PCのNode-REDのWebSocketにつながる
try
{
Task.Run(async () =>
{
await messageWebSocket.ConnectAsync(serverUri);
Debug.Log("Connect to the server...." + serverUri.ToString());
Debug.Log("ConnectAsync OK");
await WebSock_SendMessage(messageWebSocket, "Connect Start");
});
}
catch (Exception ex)
{
Debug.Log("error : " + ex.ToString());
}
}
private async Task WebSock_SendMessage(MessageWebSocket webSock, string message)
{
DataWriter messageWriter = new DataWriter(webSock.OutputStream);
messageWriter.WriteString(message);
await messageWriter.StoreAsync();
messageWriter.DetachStream(); // ストリームの破棄。今回加えたら何度も送られるようになった。加えないと一度だけしか送られない。
}
private void WebSock_MessageReceived(MessageWebSocket sender, MessageWebSocketMessageReceivedEventArgs args)
{
DataReader messageReader = args.GetDataReader();
messageReader.UnicodeEncoding = UnicodeEncoding.Utf8;
string messageString = messageReader.ReadString(messageReader.UnconsumedBufferLength);
Task.Run(async () =>
{
UnityEngine.WSA.Application.InvokeOnAppThread(() => {
// Node-RED からデータが来るとなんだか細くなる
this.transform.localScale = new Vector3(0.05f, 0.05f, 0.1f);
}, true);
await Task.Delay(100);
});
}
private void WebSock_Closed(IWebSocket sender, WebSocketClosedEventArgs args)
{
// WebSock_Closed
}
#endif
}
実は以前の記事のままのコードでは、「HoloLens2 の Cube をタッチすると Node-RED の WebSocket にメッセージを送る」ときに、タッチ時に1度だけ送られる、あるいはまったく送られない症状に遭遇して、困りました。
さまざまな試行錯誤の末、対応したのが以下の修正です。
private async Task WebSock_SendMessage(MessageWebSocket webSock, string message)
{
DataWriter messageWriter = new DataWriter(webSock.OutputStream);
messageWriter.WriteString(message);
await messageWriter.StoreAsync();
messageWriter.DetachStream(); // ストリームの破棄。今回加えたら何度も送られるようになった。加えないと一度だけしか送られない。
}
送り終えたら messageWriter.DetachStream(); で明確に破棄するように対応したら、今まで通り、押すたびに送られるようになりました。どうもC++でコンパイルされるせいか、振る舞いが微妙に変わっているのかもしれません。いい知見になりました。
Node-RED の準備
WebSocket をやり取りする Node-RED のフローです。

ごくシンプルに、ルートで受け取ったデータを debug ノードに表示したり、タイムスタンプを送ることができます。
フローのJSONはこちらです。
js
[{"id":"5b36a107.f83ad","type":"websocket in","z":"856fbae5.267df8","name":"","server":"b994b2c6.576df","client":"","x":410,"y":160,"wires":[["92080b90.7a2fb8"]]},{"id":"8d6d839.be84a8","type":"websocket out","z":"856fbae5.267df8","name":"","server":"b994b2c6.576df","client":"","x":650,"y":220,"wires":[]},{"id":"92080b90.7a2fb8","type":"debug","z":"856fbae5.267df8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":670,"y":160,"wires":[]},{"id":"b6be4a64.8113d8","type":"inject","z":"856fbae5.267df8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":380,"y":220,"wires":[["8d6d839.be84a8"]]},{"id":"b994b2c6.576df","type":"websocket-listener","z":"","path":"/","wholemsg":"false"}]
実際に動かしてみると、

まず、 Connect Start と接続されたことがわかります。接続数 1 と出ていることでも接続がわかります。
https://twitter.com/1ft_seabass/status/1285841402484514816
このように実際にCubeをタッチしてデータが送られます。

まとめると、
- タッチ時に複数タッチ判定が出てしまって苦労したが、これはスクリプト側が問題でなくUnityでのオブジェクトの作り方にコツが必要だった
- IL2CPP でスクリプトやアセンブリからの IL コードを C++ に変換するために、以前の挙動と少し違う場合がある模様。あいまい。
- とにもかくにも、今回で言うと毎回のデータ送信時の破棄に関して、以前は何もしなくてよかったが明示的に破棄するとうまくいった。
といったところで、何事もやってみないと見えてこないですね。試せてよかったです。