つくるの大好き。

つくるのが大好きな人の記録。

うんこボタンとHoloLensを繋げてみた <後編>

うんこボタンはこの度Makuakeでのクラウドファンディングに成功したインターネットボタンです。 前編ではこのうんこボタンに独自のプログラムを送り込むところまでを扱いました。

satoshi-maemoto.hatenablog.com

ソースコードはこちらで公開しています。

github.com

うんこサーバーの構築

システム構成図におけるサーバーサイドの構築を始めましょう。

f:id:peugeot-106-s16:20180415100423p:plain

サーバーサイドはASP.Net Core 2.0をフレームワークとし、RESTのAPIとWebSocketを実装します。 VisualStudio2017のプロジェクトの作成では[Visual C#]-[Cloud]-[ASP.NET Core Web アプリケーション]をテンプレートとして選択します。

f:id:peugeot-106-s16:20180415213138p:plain

次のダイアログでは [API] を選択し、REST API用コントローラーを備えたプロジェクトを作成します。

f:id:peugeot-106-s16:20180415213408p:plain

さらに、WebSocketを備えたサービスにしてゆきます。
基本的にはこちらの記事とサンプルを参考にさせて頂きました。

tamafuyou.hatenablog.com

幾つか変更点に言及してゆきしょう。

コントローラーで使いたい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


Unko button feat HoloLens

IoTデバイスとHoloLensを連携させる際の参考にしていただければ幸いです。