うんこボタンとHoloLensを繋げてみた <後編>
うんこボタンはこの度Makuakeでのクラウドファンディングに成功したインターネットボタンです。 前編ではこのうんこボタンに独自のプログラムを送り込むところまでを扱いました。
satoshi-maemoto.hatenablog.com
ソースコードはこちらで公開しています。
うんこサーバーの構築
システム構成図におけるサーバーサイドの構築を始めましょう。
サーバーサイドはASP.Net Core 2.0をフレームワークとし、RESTのAPIとWebSocketを実装します。 VisualStudio2017のプロジェクトの作成では[Visual C#]-[Cloud]-[ASP.NET Core Web アプリケーション]をテンプレートとして選択します。
次のダイアログでは [API] を選択し、REST API用コントローラーを備えたプロジェクトを作成します。
さらに、WebSocketを備えたサービスにしてゆきます。
基本的にはこちらの記事とサンプルを参考にさせて頂きました。
幾つか変更点に言及してゆきしょう。
コントローラーで使いたいSingletonオブジェクトを追加
アプリ全体で共有したいシングルトンなオブジェクトはStartup時のConfigureServices()で追加できます。 うんこぼたんからのRESTリクエストをコントローラーが受けた際、WebSocketのサーバークラス[WSServer]にアクセスし、接続中全クライアントへの一斉通知をさせるためにこのようにしています。
UnkoButton/Startup.cs at master · satoshi-maemoto/UnkoButton · GitHub
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddSingleton<WSServer>(); }
するとコントローラーのコンストラクタでオブジェクトを受け取ることができます。
UnkoButton/UnkosController.cs at master · satoshi-maemoto/UnkoButton · GitHub
public UnkosController(WSServer wsServer) { this.WSServer = wsServer; }
このUnkosCotrollerのPost()メソッドでWSServerに全クライアントへのdidUnko通知を依頼しています。
public void Post([FromBody]string value) { this.WSServer.BroadcastMessage(new Message() { MessageType = "DidUnko", ClientName = "UnkoButton", What = "Unko", }); }
WebSocketクライアント間のオブザーバーパターン
元々参考にさせて頂いたチャットサンプルがWebSocketクライアント間のイベント通知をReactを使ったオブザーバーパターンで行うようになっており、とてもスマートな感じになっています。
はじめはうんこボタンもWebSocketクライアントの一つとして、双方向リアルタイムにやりとりをしたいと思ったのですが、Wi-Fi接続やコネクションを保持したままスリープしないのは電池消費につながるのでワンショットのREST方式としました。
なので、オブザーバーパターンは現状それほど生きていないのですが、今後HoloLens複数台やその他のリッチなデバイスがうんこクライアントになった際には双方向にうんこを投げ合うなどの楽しいことができそうです。
HoloLens側でうんこを投げたらうんこボタンが光る、というのをまたそのうちやりたいです。
なお、今のコードではうんこを送ると他クライアントすべてにブロードキャストしつつ自分にもエコーバックでうんこが投げ返されてくるクソ仕様となっています(笑)
HoloLensアプリ
HoloLensアプリはうんこサーバーにWebSocket接続を行い、didUnko通知を待ち受けます。 UWPでWebSocketを利用するため、下記のようなクラスを作成しました。 Unityへの依存はないので、どのようなUWPアプリでも利用できます。
UnkoButton/UnkoServiceClient.cs at master · satoshi-maemoto/UnkoButton · GitHub
using System; #if NETFX_CORE using System.Threading.Tasks; using Windows.Networking.Sockets; using Windows.Storage.Streams; #endif namespace UnkoService { public class UnkoServiceClient { public delegate void MessageReceivedEventHandler(object sender, string message); public event MessageReceivedEventHandler OnMessageReceived; #if NETFX_CORE private MessageWebSocket WebSocket { get; set; } public void Initialize() { this.WebSocket = new MessageWebSocket(); this.WebSocket.Control.MessageType = SocketMessageType.Utf8; this.WebSocket.MessageReceived += this.MessageReceived; this.WebSocket.Closed += this.Closed; } public void Connect(Uri uri, string connectedMessage) { Task.Run(async () => { await Task.Run(async () => { await this.WebSocket.ConnectAsync(uri); await this.SendMessage(connectedMessage); }); }); } public async Task SendMessage(string message) { await this.SendMessage(this.WebSocket, message); } private async Task SendMessage(MessageWebSocket webSocket, string message) { var messageWriter = new DataWriter(webSocket.OutputStream); messageWriter.WriteString(message); await messageWriter.StoreAsync(); } private void MessageReceived(MessageWebSocket sender, MessageWebSocketMessageReceivedEventArgs args) { var messageReader = args.GetDataReader(); messageReader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8; var messageString = messageReader.ReadString(messageReader.UnconsumedBufferLength); this.OnMessageReceived?.Invoke(this, messageString); } private void Closed(IWebSocket sender, WebSocketClosedEventArgs args) { } #endif } }
リポジトリには入れていませんが、このUnkoServiceClientをUnityのスクリプトからは下記のように利用します。
private UnkoServiceClient unkoServiceClient; void Start() { #if NETFX_CORE var context = SynchronizationContext.Current; this.unkoServiceClient = new UnkoServiceClient(); this.unkoServiceClient.OnMessageReceived += (s, m) => { context.Post((state) => { this.GenerateUnko(); }, null); }; this.unkoServiceClient.Initialize(); this.unkoServiceClient.Connect(new Uri("ws://xxxx.azurewebsites.net/ws"), JsonConvert.SerializeObject(new JoinMessage() { ClientName = "HoloLens", MessageType = "JoinMessage" })); #endif }
まとめ
以上でうんこボタンを押すとうんこが出せるようになりました。 なんかうんこうんこ書きすぎて気分が悪くなってきましたww
IoTデバイスとHoloLensを連携させる際の参考にしていただければ幸いです。