Bluetooth SensorTagとHoloLens連携を多くのセンサー値読み込みのためにプログラムを整理するメモ

Bluetooth SensorTagとHoloLens連携を多くのセンサー値読み込みのためにプログラムを整理するメモです。

今後センサー取得が増えることを想像する

Bluetooth SensorTagとHoloLens連携してジャイロセンサー取得する先日の記事で使った、Bluetooth SensorTag CC2650は本来であればジャイロだけでなくセンサーで読み込むことが可能です。

10種類のセンサ情報をスマホへ送信!わずか3STEPと手ごろに扱えるSensorTag | 富士エレクトロニクス

温度・湿度センサ HDC1000(TI製品)
照度センサ    OPT3001(TI製品)
非接触温度センサ TMP007(TI製品)
大気圧センサ
9軸モーショントラッキングデバイス
磁気センサ温度・湿度

以前の記事でも、ジャイロのデータを読むために、

  • GATTサービスの取得
  • データ取得
  • データ設定
  • Characteristic値の保持
  • Unityのフローに戻す構造(UnityEngine.WSA.Application.InvokeOnAppThread)

といったことを、あれこれ非同期でやらねばならず、これをセンサーの数だけやるとなると、プログラム書いているときは変数名が脳内の短期メモリを越えるし、冗長になるし良いことがないということで整理しようと思います。

整理してみる

以下の流れで整理してみました。

  • 先ほど挙げた要素を包んでクラスにするのが軸
  • ラムダ式(charGyroData.ValueChanged += (sender, eventArgs) =>)をうまく移植する
  • Unityのフローに戻す構造はイベントでやりとりしてみる
  • アクセシビリティに一貫性がありません と言われるのは修正
  • ソースコード追記
    • 6/22 UnityのエディタでUWP固有のコードが反応してしまうため UNITY_UWP 修飾子で囲う部分を調整
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#if UNITY_UWP
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Enumeration;
using Windows.Foundation;
using Windows.Storage.Streams;
using Windows.UI.Core;
#endif

public class ActionBluetooth : MonoBehaviour
{

    SensorTagGyroSensor sensorTagGyroSensor;

    // Use this for initialization
    void Start()
    {

        // ジャイロセンサー担当クラス
        sensorTagGyroSensor = new SensorTagGyroSensor(this);
        
        connect();
    }

    // Update is called once per frame
    void Update()
    {

    }

    void connect()
    {
        Debug.Log("onConnect");

        // 接続待ちアニメーション
        iTween.MoveBy(this.gameObject, iTween.Hash(
            "y", 0.2,
            "time", 1.0,
            "loopType", "pingPong",
            "islocal", true
        ));

#if UNITY_UWP

        // ジャイロデータ取得イベント
        sensorTagGyroSensor.GyroSensorDataEvent += eventGyroValueChanged;
        // センサーデータ取得試行エラーイベント
        sensorTagGyroSensor.SensorTagLoadErrorEvent += eventGyroLoadError;
        // センサーデータ取得試行成功イベント
        sensorTagGyroSensor.SensorTagLoadSuccessEvent += eventGyroLoadSuccess;

        sensorTagGyroSensor.connect();
#endif
    }

#if UNITY_UWP
    void eventGyroValueChanged(object sender, GyroSensorDataEvent e)
    {
        // ジャイロの変化値をX・Y・Zごとに加算回転
        iTween.RotateAdd(this.gameObject, iTween.Hash(
            "x", e.gyroX,
            "y", e.gyroY,
            "z", e.gyroY,
            "time", 1.0,
            "islocal", true
        ));
    }

    void eventGyroLoadError(object sender, SensorTagLoadErrorEvent e)
    {
        // 待ちアニメloop停止
        iTween.Stop(this.gameObject);
        // しおしおと小さくなる
        iTween.ScaleAdd(this.gameObject, iTween.Hash(
            "x", -0.1f,
            "y", -0.1f,
            "z", -0.1f,
            "time", 1.0,
            "islocal", true,
            "delay", 0.05f
        ));
    }

    void eventGyroLoadSuccess(object sender, SensorTagLoadSuccessEvent e)
    {
        // 待ちアニメloop停止
        iTween.Stop(this.gameObject);
        // 接続うまくいったよ回転
        iTween.RotateAdd(this.gameObject, iTween.Hash(
            "x", 360,
            "time", 0.5,
            "islocal", true,
            "delay", 0.05f
        ));
    }
#endif

}

/**
 * ジャイロセンサー担当クラス
 * 
 * 
*/

public class SensorTagGyroSensor : MonoBehaviour
{

    ActionBluetooth mainClass;

#if UNITY_UWP
    // ↓ アクセシビリティに一貫性がありませんと出るのでpublicでなくinternalに
    internal event EventHandler<GyroSensorDataEvent> GyroSensorDataEvent;
    internal event EventHandler<SensorTagLoadSuccessEvent> SensorTagLoadSuccessEvent;
    internal event EventHandler<SensorTagLoadErrorEvent> SensorTagLoadErrorEvent;
#endif

    public SensorTagGyroSensor( ActionBluetooth _mainClass )
    {
        mainClass = _mainClass;
    }

    public void connect()
    {
#if UNITY_UWP
        Task.Run(async () =>
        {

            var UUID_GYR_SERV = new Guid("f000aa80-0451-4000-b000-000000000000");
            var UUID_GYR_DATA = new Guid("f000aa81-0451-4000-b000-000000000000");
            var UUID_GYR_CONF = new Guid("f000aa82-0451-4000-b000-000000000000");

            var gattGyroServices = await DeviceInformation.FindAllAsync(GattDeviceService.GetDeviceSelectorFromUuid(UUID_GYR_SERV), null);

            Debug.Log("gattGyroServices");
            var gattGyroService = await GattDeviceService.FromIdAsync(gattGyroServices[0].Id);

            Debug.Log("gattGyroService");
            var charGyroData = gattGyroService.GetCharacteristics(UUID_GYR_DATA)[0];
            var charGyroConfig = gattGyroService.GetCharacteristics(UUID_GYR_CONF)[0];

            GattReadResult Resultat = await charGyroConfig.ReadValueAsync();
            var Output = Resultat.Value.ToArray();

            Debug.Log(Output.Length);
            Debug.Log("Registre 0:" + Output[0].ToString());
            Debug.Log("Registre 1:" + Output[1].ToString());

            charGyroData.ValueChanged += ValueChanged;

            Output[0] = 0x7F; // センサーの取得方法を与える 7
            var status = await charGyroConfig.WriteValueAsync(Output.AsBuffer());
            if (status == GattCommunicationStatus.Unreachable)
            {
                // 接続失敗
                Debug.Log("Initialize failed");

                UnityEngine.WSA.Application.InvokeOnAppThread(() =>
                {
                    // センサーデータ取得試行エラーイベント発火
                    SensorTagLoadErrorEvent(this,new SensorTagLoadErrorEvent("gyro"));

                }, true);
                //
                await Task.Delay(100);
            }
            else
            {
                // 成功
                Debug.Log("Initialize Success!!");

                UnityEngine.WSA.Application.InvokeOnAppThread(() =>
                {
                    // センサーデータ取得試行成功イベント発火
                    SensorTagLoadSuccessEvent(this, new SensorTagLoadSuccessEvent("gyro"));

                }, true);

                // データ通知を有効にする
                await charGyroData.WriteClientCharacteristicConfigurationDescriptorAsync(
                            GattClientCharacteristicConfigurationDescriptorValue.Notify);
            }

        });
#endif
    }

#if UNITY_UWP
    public void ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs eventArgs)
    {
        var data = eventArgs.CharacteristicValue.ToArray();

        // Gyro
        var gyroX = (BitConverter.ToInt16(data, 0) * 1.0) / (65536 / 500);
        var gyroY = (BitConverter.ToInt16(data, 2) * 1.0) / (65536 / 500);
        var gyroZ = (BitConverter.ToInt16(data, 4) * 1.0) / (65536 / 500);

        Debug.Log("gyroX : " + gyroX + " gyroY : " + gyroY + " gyroZ : " + gyroZ);

        if (Math.Abs(gyroX) < 20 && Math.Abs(gyroY) < 20 && Math.Abs(gyroZ) < 20)
        {
            // 小さい動き静止とする。影響を与えない。
        }
        else
        {
            Task.Run(async () =>
            {

                UnityEngine.WSA.Application.InvokeOnAppThread(() => {
                    // ジャイロデータ取得イベント発火
                    GyroSensorDataEvent(this, new GyroSensorDataEvent((float)gyroX, (float)gyroY, (float)gyroZ));
                }, true);

                await Task.Delay(100);
            });
        }
        
    }
#endif

}

/**
 * ジャイロデータ取得イベント
 * 
 *
*/

#if UNITY_UWP

class GyroSensorDataEvent : EventArgs
{
    private float _gyroX;
    private float _gyroY;
    private float _gyroZ;

    public GyroSensorDataEvent(float __gyroX, float __gyroY, float __gyroZ)
    {
        this._gyroX = __gyroX;
        this._gyroY = __gyroY;
        this._gyroZ = __gyroZ;
    }

    public float gyroX
    {
        get { return _gyroX; }
    }

    public float gyroY
    {
        get { return _gyroY; }
    }

    public float gyroZ
    {
        get { return _gyroZ; }
    }
}

/**
 * センサーデータ取得試行エラーイベント
 * 
 * 
*/

class SensorTagLoadSuccessEvent : EventArgs
{
    private string _sensorName;

    public SensorTagLoadSuccessEvent(string __sensorName)
    {
        this._sensorName = __sensorName;
    }

    public string sensorName
    {
        get { return _sensorName; }
    }
}

/**
 * センサーデータ取得試行成功イベント
 * 
 * 
*/

class SensorTagLoadErrorEvent : EventArgs
{
    private string _sensorName;

    public SensorTagLoadErrorEvent(string __sensorName)
    {
        this._sensorName = __sensorName;
    }

    public string sensorName
    {
        get { return _sensorName; }
    }
}

#endif

以前のActionScriptやTypeScriptでのクラス分けの感覚を取り入れながらC#で試行錯誤してやってみました。

各クラスの役割分担が見通しは良くなりましたね!