MRが楽しい

MRやVRについて学習したことを書き残す

HoloLensの3DアプリでBlueToothのアドバタイズを取得する

本日は HoloLens の技術調査枠です。
HoloLens の3Dアプリで BlueTooth のアドバタイズを受信する方法を記事にします。
以下の記事の続きです。
bluebirdofoz.hatenablog.com

プロジェクトとシーンの準備

以下の記事を元にHoloLens(WindowsMR)プロジェクトを作成します。
bluebirdofoz.hatenablog.com

2019/5/3現在、MRTK 2017 の最新バージョンは 2017.4.3.0 です。
f:id:bluebirdofoz:20190504225944j:plain

BluetoothAdvertisementを参考に作成したサンプルコード

BluetoothAdvertisement のウォッチャークラスのコードを参考に、テキストオブジェクトに結果を出力するクラスを作成しました。
・BluetoothAdvertiseWatcher.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#if WINDOWS_UWP
using System;
using Windows.Storage.Streams;
using Windows.Devices.Bluetooth.Advertisement;
#endif

public class BluetoothAdvertiseWatcher : MonoBehaviour {

    /// <summary>
    /// テキストオブジェクトの参照
    /// </summary>
    public UnityEngine.UI.Text MessageText;
    string showmessage;

    /// <summary>
    /// 起動時処理
    /// </summary>
    void Start ()
    {
        App_Message("Start");
#if WINDOWS_UWP
        // 初期化
        Awake_BluetoothLEAdvertisementWatcher();

        // ハンドラ登録
        OnEnable_BluetoothLEAdvertisementWatcher();

        // ウォッチャー起動
        Run_BluetoothLEAdvertisementWatcher();
#endif
    }

    /// <summary>
    /// 定期実行
    /// </summary>
    private void Update()
    {
        if (showmessage.Length != 0)
        {
            MessageText.text = showmessage;
            showmessage = "";
        }
    }

    /// <summary>
    /// テキスト更新
    /// </summary>
    /// <param name="message">メッセージ</param>
    void App_Message(string message)
    {
        showmessage = message;
    }

    /// <summary>
    /// 終了時処理
    /// </summary>
    void OnApplicationQuit()
    {
#if WINDOWS_UWP
        // 終了
        OnDisable_BluetoothLEAdvertisementWatcher();
#endif
    }

#if WINDOWS_UWP
        /// <summary>
        /// Bluetooth LE スキャンを制御およびカスタマイズするための BluetoothLEAdvertisementWatcher 
        /// </summary>
        private BluetoothLEAdvertisementWatcher watcher;
        
        /// <summary>
        /// BluetoothLEAdvertisementWatcher初期化(Awake)
        /// </summary>
        public void Awake_BluetoothLEAdvertisementWatcher()
        {
            // ウォッチャーインスタンスを作成して初期化します。
            watcher = new BluetoothLEAdvertisementWatcher();

            // 指定のパブリッシャによってアドバタイズされたデータを探すようにアドバタイズフィルタを設定します。
            // すべてのフィルタを削除する場合はウォッチャ設定をコメントアウトします。
            // フィルタを指定しない場合、受信したすべての BluetoothAdvertisement がイベントハンドラを通じて通知されます。

            // API間でフィルタ制限を決定するには、以下のプロパティを使用します。
            //      MinSamplingInterval, MaxSamplingInterval, MinOutOfRangeTimeout, MaxOutOfRangeTimeout

            // 特定の advertisement ペイロードを監視するための advertisement フィルタの設定を行います。
            // マッチングしたい製造元データセクションを作成します。
            var manufacturerData = new BluetoothLEManufacturerData();

            // 製造元データのIDを設定します。ここでは 0xFFFE の値を指定します。
            manufacturerData.CompanyId = 0xFFFE;

            // manufacturer-specific セクション内にデータペイロードを設定します
            // ここでは以下の16ビットのUUIDを使用します。
            // 0x1234 - > { 0x34、0x12}(リトルエンディアン)
            var writer = new DataWriter();
            writer.WriteUInt16(0x1234);

            // 書き込みデータのバッファ長はアドバタイズペイロードに収まる必要があります。
            // 収まらない場合は例外が発生します。
            manufacturerData.Data = writer.DetachBuffer();

            // 製造元データをウォッチャーの advertisement フィルターに追加します。
            watcher.AdvertisementFilter.Advertisement.ManufacturerData.Add(manufacturerData);


            // 近接通信用の信号強度フィルタの設定を行います。
            // 機器が範囲内にある場合にのみイベントを伝播するように信号強度フィルターを構成します。
            // 環境によって advertisement が表示されない場合は、これらの値を調整します。
            // ここでは範囲内閾値を - 70dBmに設定します。
            // RSSI > = -70dBm の advertisement が「範囲内」と見なされます。
            watcher.SignalStrengthFilter.InRangeThresholdInDBm = -70;

            // 範囲外の閾値を-75dBmに設定します(範囲内閾値との差にある程度のバッファを入れます)。
            // advertisement が「範囲内」と見なされなくなったタイミングを判断するため、
            // OutOfRangeTimeout と組み合わせて使用します。
            watcher.SignalStrengthFilter.OutOfRangeThresholdInDBm = -75;

            // 範囲外タイムアウトを2秒に設定します。
            // advertisement が「範囲内」と見なされなくなった時期を判断するため、
            // OutOfRangeThresholdInDBm と組み合わせて使用します。
            watcher.SignalStrengthFilter.OutOfRangeTimeout = TimeSpan.FromMilliseconds(2000);

            // デフォルトでは、サンプリング間隔はゼロに設定されています。
            // この場合、サンプリングは行われず、受信したすべての情報は Received イベントで返されます。
            // ウォッチャー設定は以上です。
        }

        /// <summary>
        /// BluetoothLEAdvertisementWatcher初期化(OnEnable)
        /// </summary>
        public void OnEnable_BluetoothLEAdvertisementWatcher()
        {
            // 受け取った提供情報を処理するためのハンドラーを登録します。
            // 受信ハンドラがアタッチされていないとウォッチャーを起動できません。
            watcher.Received += OnReceived_AdvertisementWatcher;

            // 様々な条件で停止処理を行うためのハンドラーを登録します。
            // Bluetooth無線がオフになった、またはStopメソッドが呼び出された場合に実行されます。
            watcher.Stopped += OnStopped_AdvertisementWatcher;
        }

        /// <summary>
        /// BluetoothLEAdvertisementWatcher終了(OnDisable)
        /// </summary>
        public void OnDisable_BluetoothLEAdvertisementWatcher()
        {
            // コンテキストを離れるときは、ウォッチャーを停止します。
            // ウォッチャーが停止されていなくても、ウォッチャーが破棄されるとスキャンは自動的に停止します。
            watcher.Stop();
            // リークを防ぐためにリソースを解放する場合、ハンドラの登録を解除します。
            watcher.Received -= OnReceived_AdvertisementWatcher;
            watcher.Stopped -= OnStopped_AdvertisementWatcher;
        }

        /// <summary>
        /// BluetoothLEAdvertisementWatcher実行処理(Run)
        /// </summary>
        public void Run_BluetoothLEAdvertisementWatcher()
        {
            // ウォッチャーの Start() を呼び出すとスキャンが開始されます。
            watcher.Start();
        }

        /// <summary>
        /// BluetoothLEAdvertisementWatcher停止処理(Stop)
        /// </summary>
        public void Stop_BluetoothLEAdvertisementWatcher()
        {
            // ウォッチャーの Stop() を呼び出すとスキャンが停止します。
            watcher.Stop();
        }

        /// <summary>
        /// advertisement を受信したときにイベントハンドラとして呼び出されます。
        /// </summary>
        /// <param name="watcher">イベントをトリガーしたウォッチャーのインスタンス</param>
        /// <param name="eventArgs">advertisement イベントに関する情報を含むイベントデータ</param>
        private async void OnReceived_AdvertisementWatcher(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs)
        {
            App_Message("Reseived");
            // EventArgsクラスのプロパティにアクセスすることで、受け取った advertisement に関するさまざまな情報を入手できます。
            
            // イベントのタイムスタンプを取得します。
            DateTimeOffset timestamp = eventArgs.Timestamp;
            
            // advertisement の種類を取得します。
            BluetoothLEAdvertisementType advertisementType = eventArgs.AdvertisementType;

            // 受信信号強度インジケータ(RSSI)を取得します。
            Int16 rssi = eventArgs.RawSignalStrengthInDBm;

            // Bluetooth LE Advertisement を送信しているデバイスのBluetoothアドレスを取得します。
            ulong BlueToothAddress = eventArgs.BluetoothAddress;

            // ペイロードに含まれる advertisement デバイスのローカル名(存在する場合)を取得します。
            string localName = eventArgs.Advertisement.LocalName;

            // manufacturer-specific セクションがあるかどうかを確認します。
            // ある場合は、manufacturer-specific セクションのデータを取得します。
            // 複数ある場合、最初のセクションのみ取得します。
            string manufacturerDataString = "";
            var manufacturerSections = eventArgs.Advertisement.ManufacturerData;
            if (manufacturerSections.Count > 0)
            {
                // リストの最初のものだけを取得する
                BluetoothLEManufacturerData manufacturerData = manufacturerSections[0];
                byte[] data = new byte[manufacturerData.Data.Length];
                using (DataReader reader = DataReader.FromBuffer(manufacturerData.Data))
                {
                    reader.ReadBytes(data);
                }
                // IDとmanufacturer-specific セクションのデータを16進形式で取得します。
                manufacturerDataString = string.Format("0x{0}: {1}",
                    manufacturerData.CompanyId.ToString("X"),
                    BitConverter.ToString(data));
            }

            // 受信処理を行う
            // 受信データをテキストオブジェクトに表示する
            App_Message(manufacturerDataString);
        }

        /// <summary>
        /// ウォッチャが停止または中止されたときにイベントハンドラとして呼び出されます。
        /// </summary>
        /// <param name="watcher">イベントをトリガーしたウォッチャーのインスタンス</param>
        /// <param name="eventArgs">ウォッチャーが停止または中断した理由に関する情報を含むイベントデータ</param>
        private async void OnStopped_AdvertisementWatcher(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementWatcherStoppedEventArgs eventArgs)
        {
            // 終了処理を行う
            // 終了メッセージをテキストオブジェクトに表示する
            App_Message("Stop Event");
        }
#endif
}

Empty オブジェクトを作成し、本スクリプトをアタッチしました。
f:id:bluebirdofoz:20190504230019j:plain

スクリプトの MessageText にテキストオブジェクトの参照を追加します。
f:id:bluebirdofoz:20190504230029j:plain

Capabilityの追加

UWPアプリで Bluetooth を利用する場合、Capability の設定が必要です。
メニューから Edit -> Project Settings - Player を開きます。
f:id:bluebirdofoz:20190504230042j:plain

Inspector ビューから Publishing Setttings を開きます。
[Capabilities]欄の[Bluetooth]にチェックを入れます。
f:id:bluebirdofoz:20190504230054j:plain

後は HoloLens 向けにプロジェクトをビルドしてインストールします。
UnityプロジェクトのビルドとHoloLensへのインストール手順については以下を参照してください。
bluebirdofoz.hatenablog.com

送信側アプリの実行

PC側で前回ビルドした BluetoothAdvertisement アプリを起動します。
[Foreground publisher]のタブを開き、[Run]を実行して BlueTooth のアドバタイズを実行しておきます。
f:id:bluebirdofoz:20190504230125j:plain

HoloLensでの動作確認

この状態で HoloLens 上で動作確認を行います。
正常に BlueTooth のアドバタイズを受信すると、テキストオブジェクトにIDとデータ値が表示されます。
f:id:bluebirdofoz:20190504230149j:plain