UnityでAndroid用のBLEプラグインを開発した

UnityやC#で開発するのは今回が初めてなので、拙い部分があると思いますがよろしくお願いします。  

プロジェクトのリポジトリ
[https://github.com/Hanjuku-Kobo/UnityBLE](https://github.com/Hanjuku-Kobo/UnityBLE)
[こちら](https://github.com/Hanjuku-Kobo/UnityBLE/releases)から最新のバージョンをダウンロードして使用してください

# はじめに
このプラグインは、チームのメンバーから「既存の Unity BLE プラグインがうまく使えない」とのことで開発しました。そのため、個人的に使うための変数や関数など入っていますので、その部分は各々で置き換えて読み進めていただければ幸いです。

# 使い方
Sampleアプリでは、目的のデバイスが決まっている場合の処理になっています。詳細については[BLEデバイスをScanする](#bleデバイスをscanする)や[デバイスと通信をする](#デバイスと通信をする)の解説文をご覧ください。
機能ごとにクラス分けをして実装してありますが、同一クラスでも実装するも可能です。
クラス分けをして実装しているため、状態管理にUniRxを利用し画面を更新する処理などを実装しています。

:::note warn
SampleアプリではUniRxを利用していますので、コピペ利用される方は[こちら]()からご自身のプロジェクトへダウンロードしてください
:::

## 初期化やBLE操作をする
```csharp:Main.cs
using System.Collections;
using System.Collections.Generic;
using UniRx;
using UnityEngine;
using UnityEngine.UI;
using BlePlugin.Ble;
using BlePlugin.Util;
using BlePlugin.Data;

public class Main : MonoBehaviour, Callbacks
{
    // クラス呼び出し時に呼ばれる
    void Start()
    {
        // 位置情報の権限をリクエストする
        PermissionUtil.RequestLocation();

        // BLEの初期化
        BleController.Initialize(OnInitialize, OnError);
    }

    // アプリ終了時に呼ばれる
    void OnDestroy()
    {
        // BLEの終了処理をする
        BleController.Close(OnError);
    }

    // フレームの更新ごとに呼ばれる
    void Update() { }

    // callbacks
    private void OnInitialize()
    {
        // 初期化後にする処理を実装
        Debug.Log("initialize");
    }
    private void OnError(string errorMessage)
    {
        // BLEを初期化できない原因がエラーメッセージで返ってくる
        // 再び初期化をするかその他の処理を実装
        Debug.Log("Ble Initialized Error: "+errorMessage);
    }
}
```
BLEを使用される際の初期化時には必ず`PermissionUtil.RequestLocation();`により権限をリクエストしてください。位置情報の権限をリクエストすることにより、BLEの機能が使えるようになります。`BleController.Initialize(OnInitialize, OnError);`でBLEを初期化した場合には、アプリ終了時に`BleController.Close(OnError);`を呼び出しリソースを解放してください。

## BLEデバイスをScanする
```csharp:BleScanner.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BlePlugin.Ble;
using BlePlugin.Data;

public class BleScanner : Callbacks {
    // 状態管理
    public static bool isScanning = false;
    private FlagStream flagStream = FlagStream.GetInstance();

    public static List<BleDevice> discoveredDevices = new List<BleDevice>();

    // 目的のデバイスを検索するときに使用 - 設定用
    private string deviceName = "圧力計";
    private string deviceAddress = "device address";

    // デバイスをスキャンする
    public void StartScan()
    {
        discoveredDevices.Clear();
        flagStream.SetScanFlag(false);

        isScanning = BleController.StartScan(OnScan, OnError);

        Debug.Log("start scan: scanning -> "+isScanning);
    }
    // スキャンを停止する
    public void StopScan()
    {
        if (isScanning)
        {
            BleController.StopScan(OnError);
            isScanning = false;
        }
    }

    // callbacks
    private void OnScan(string name, string address)
    {
        // 発見したデバイスをストックし、ユーザーが選択する場合
        // 重複しているかしらべる
        bool duplication = false;
        for (int i=0; i<discoveredDevices.Count; i++) 
        {
            if (discoveredDevices[i].address == address) 
            {
                duplication = true;
            }
        }

        // 新しく発見したデバイスであれば配列に追加
        if (!duplication) 
        {
            Debug.Log("scan result: "+name+" , "+address);

            discoveredDevices.Add(
                new BleDevice(
                    name: name,
                    address: address
                )
            );

            // 目的のデバイスがある場合
            if (deviceName == name || deviceAddress == address) {
                BleDataCache.deviceName = name;
                BleDataCache.deviceAddress = address;
                flagStream.SetScanFlag(true);
            }
        }
    }
    private void OnError(string message)
    {
        Debug.Log(message);
    }
}
```
Scanをする処理は目的のデバイスがわかっている場合と、ユーザーに選択してもらう場合で処理が異なります。
もし目的のデバイスが決まっている場合は、`private string deviceName`か`private static string deviceAddress;`のどちらかに device name もしくは device address を入力してください。

## デバイスとの接続を確立する
```csharp:BleConnector.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BlePlugin.Ble;
using BlePlugin.Data;

public class BleConnector : Callbacks {
    // 状態管理
    private FlagStream flagStream = FlagStream.GetInstance();

    public void Connect(string deviceAddress) 
    {
        if (BleController.connectionStatus != ConnectionStatus.connected) 
        {
            BleController.Connect(
                deviceAddress,
                OnConnect, 
                OnDisconnect, 
                OnError
            );
        }
    }

    public void Disconnect() 
    {
        if (BleController.connectionStatus != ConnectionStatus.disconnected) 
        {
            BleController.Disconnect(OnError);
        }
    }

    // callbacks
    private void OnConnect()
    {
        flagStream.SetConnectFlag(true);
        Debug.Log("connected");
    }
    private void OnDisconnect()
    {
        Debug.Log("disconnected");
    }
    private void OnError(string message)
    {
        Debug.Log(message);
    }
}
```

## デバイスと通信をする
```csharp:BleInteractor.cs
using System.Collections;
using System.Collections.Generic;
using UniRx;
using UnityEngine;
using BlePlugin.Ble;
using BlePlugin.Data;

// bleとの通信の役割を担う
public class BleInteractor : Callbacks {
    private static string serviceUUID = "3a77058c-dfa3-415a-ad71-1848ea0d5513";
    private static string readCharacteristic = "13a3e1a3-41f9-4a49-bdc3-c3de47deffa3";
    private static string writeCharacteristic = "write characteristic";
    private static string notifyCharacteristic = "13a3e1a3-41f9-4a49-bdc3-c3de47deffa3";

    // characteristicからデータを読みとる
    public static void ReadCharacteristic()  
    {
        if (BleController.connectionStatus != ConnectionStatus.connected) return;
        BleController.ReadCharacteristic(serviceUUID, readCharacteristic, OnRead, OnError);
    }

    // characteristicにデータを書き込む
    public static void WriteWithCharacteristic(byte[] writeValue) 
    {
        if (BleController.connectionStatus != ConnectionStatus.connected) return;
        BleController.WriteCharacteristic(serviceUUID, writeCharacteristic, writeValue, OnWrite, OnError);
    }

    // notifyを受け取れるように許可・設定をする
    public static void StartNotification() 
    {
        if (BleController.connectionStatus != ConnectionStatus.connected) return;
        BleController.StartNotification(serviceUUID, notifyCharacteristic, OnNotify, OnError);
    }

    // notifyを停止する
    public static void StopNotification()
    {
        if (BleController.connectionStatus != ConnectionStatus.connected) return;
        BleController.StopNotification(serviceUUID, notifyCharacteristic, OnError);
    }

    // callbacks
    private static void OnRead(string value)
    {
        Debug.Log("Value: "+value);
        // ストリームのデータを変更しイベントを発行する
        ReadValueStream readValueStream = ReadValueStream.GetInstance();
        readValueStream.SetValue(value);
    }
    private static void OnWrite()
    {
        // 書き込みが完了した際に呼ばれる
        Debug.Log("Write result: True");
    }
    private static void OnNotify(string value)
    {
        Debug.Log("Value: "+value);
        // ストリームのデータを変更しイベントを発行する
        NotifyValueStream notifyValueStream = NotifyValueStream.GetInstance();
        notifyValueStream.SetValue(value);
    }
    private static void OnError(string message)
    {
        Debug.Log(message);
    }
}
```
今回は目的のデバイスが決まっているため、使用するUUIDをあらかじめセットしてあります。
検索したデバイスを利用する場合は、`Connect(string deviceAddress)`呼び出し後に接続が確立した時点で、内部で`DiscoverService()`によりデバイスのServiceUUIDとCharacteristicUUIDが検索されList化されています。
それらを利用したいときは、`BleController.discoveredService`を呼び出しDiscoveredServiceクラスのメンバーメソッドを利用してください。

# おわりに
このプラグインは開発途中なうえに車輪の再発名なので全然未完成で使いづらいところもあると思います。  
また、プラグインの最新版を出すたびにこちらの記事も更新していこうと思っていますので、よろしくお願いします。