CoreBluetoothForUnity サンプル 02_ButtonInformation

## 本記事について

この記事は [CoreBluetoothForUnity Advent Calendar 2023](https://qiita.com/advent-calendar/2023/core-bluetooth-for-unity) の3日目の記事です。

02_ButtonInformation サンプルの解説をします。

## 前提

- 前回の記事
- 02_ButtonInformation サンプルを動かすことができること。

## 環境

- CoreBluetoothForUnity 0.4.3

## 02_ButtonInformation の概要

02_ButtonInformation は、Peripheral に配置されたボタンの状態を Central に送信するサンプルです。

### SampleButtonInformation_PeripheralScene

<img width="660" alt="button_information_peripheral" src="https://github.com/teach310/CoreBluetoothForUnity/assets/16421323/00fb797a-28e2-4337-9846-fae393941f65">

ボタンが6つ配置されており、それぞれのボタンが以下のように登録されています。

![buttons.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/42e4503b-8d93-6933-9f31-078ba16d9d30.png)

それぞれのボタンのインデックス + 1 が id となるような仕様にしています。

例: A のインデックスは4なため、id は5。

### SampleButtonInformation_CentralScene

![mov_button_information](https://github.com/teach310/CoreBluetoothForUnity/assets/16421323/8b119fda-73c0-44d8-aa53-7690526f88cf)

送られてきたデータによって画面中央の Cube..(1)と右下のテキスト..(2)を更新しています。

![screen_button_central.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113504/3ad8a43e-bba2-6003-7125-5fe09fa3582c.png)

上下左右 -> Cube が上下移動
AB -> Cube の拡大・縮小

### 01_LightControl との違い

|サンプル|データの読み書きの方向|名称|
|:--:|:--:|:--:|
|01_LightControl|Central -> Peripheral|Write|
|02_ButtonInformation|Peripheral -> Central|Notify|

Write と Notify は書き方が全然違います。
本記事では Notify の書き方を解説します。

## Service, Characteristic の作成 (Peripheral)

https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.3/Assets/CoreBluetooth/Samples/02_ButtonInformation/SampleButtonInformation_Peripheral.cs

```SampleButtonInformation_Peripheral.cs
var service = new CBMutableService(SampleButtonInformation_Data.ServiceUUID, true);
_buttonInformationCharacteristic = new CBMutableCharacteristic(
    SampleButtonInformation_Data.ButtonInformationCharacteristicUUID,
    CBCharacteristicProperties.Read | CBCharacteristicProperties.Notify,
    null,
    CBAttributePermissions.Readable
);
```

Service の作成は特に変わりません。
Peripheral -> Central とデータを送信するために、今回は Characteristic の権限に Notify を追加しています。

(Read については権限追加していますが、本記事では使いません。)

## Notify できるように設定

### SetNotifyValue (Central)

https://github.com/teach310/CoreBluetoothForUnity/blob/0.4.3/Assets/CoreBluetooth/Samples/02_ButtonInformation/SampleButtonInformation_Central.cs

```SampleButtonInformation_Central.cs
void ICBPeripheralDelegate.DidDiscoverCharacteristics(CBPeripheral peripheral, CBService service, CBError error)
{
...
    foreach (var characteristic in service.Characteristics)
    {
        if (characteristic.UUID.Equals(SampleButtonInformation_Data.ButtonInformationCharacteristicUUID))
        {
            if (characteristic.Properties.HasFlag(CBCharacteristicProperties.Notify))
            {
                peripheral.SetNotifyValue(true, characteristic);
            }
        }
    }
}
```

Characteristic に Notify 権限がある場合、SetNotifyValue の引数 enabled に true を渡すことで Notify が有効になります。

[SetNotifyValue](https://developer.apple.com/documentation/corebluetooth/cbperipheral/1518949-setnotifyvalue)

この処理を 「Characteristic のデータを購読する」(Subscribe)と言います。

## Characteristic のデータが購読されたことを検知 (Peripheral)

Central がデータを購読すると Peripheral 側では以下の処理が呼ばれます。

```SampleButtonInformation_Peripheral.cs
void ICBPeripheralManagerDelegate.DidSubscribeToCharacteristic(CBPeripheralManager peripheral, CBCentral central, CBCharacteristic characteristic)
{
    if (_peripheralManager.IsAdvertising)
    {
        peripheral.StopAdvertising();
    }
    _stateLabel.text = "Connected";
    _central = central;
}
```

これによってどの Central が Peripheral を購読しているかを検知することができます。
Peripheral では Central から接続されることで呼び出されるイベントはないため、DidSubscribeToCharacteristic は接続されたことを把握する方法でもあります。

サンプルでは電池の消費を抑えるために、Peripheral が Central に接続されたらアドバタイズを停止しています。

## Peripheral から Central へデータを送信 (Peripheral)

```SampleButtonInformation_Peripheral.cs
        void OnButtonDown(int buttonID)
        {
            if (_central == null)
            {
                return;
            }

            var data = SampleButtonInformation_Data.GetButtonInformation(buttonID, true);
            _peripheralManager.UpdateValue(data, _buttonInformationCharacteristic, new CBCentral[] { _central });
        }
```

```SampleButtonInformation_Data.cs
        /// <summary>
        /// Convert button information to byte array.
        /// This data format is same as toio button information.
        /// https://toio.github.io/toio-spec/en/docs/ble_button#read-operations
        /// </summary>
        /// <param name="isPressed"></param>
        /// <returns></returns>
        public static byte[] GetButtonInformation(int buttonId, bool isPressed)
        {
            byte[] buff = new byte[2];
            buff[0] = BitConverter.GetBytes(Mathf.Clamp(buttonId, 0, 255))[0];
            buff[1] = (byte)(isPressed ? 0x80 : 0x00);
            return buff;
        }
```

Peripheral から Central へデータを送信するには、UpdateValue を使います。
送信対象を指定するために、第3引数に CBCentral[] を渡します。

[UpdateValue](https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager/1393281-updatevalue)

## Central でデータを受信

DidUpdateValueForCharacteristic でデータを受信します。

```SampleButtonInformation_Central.cs
        void ICBPeripheralDelegate.DidUpdateValueForCharacteristic(CBPeripheral peripheral, CBCharacteristic characteristic, CBError error)
        {
            if (error != null)
            {
                Debug.LogError($"[DidUpdateValue] error: {error}");
                return;
            }

            if (SampleButtonInformation_Data.ParseButtonInformation(characteristic.Value, out int buttonId, out bool isPressed))
            {
                _log.AppendLog(buttonId, isPressed);
                _cube.Action(buttonId, isPressed);
            }
        }
```

Notify は Peripheral -> Central の一方通行なため、返答する必要はありません。

## おわりに

本記事では、02_ButtonInformation サンプルを通して Notify の使い方を解説しました。
Write よりも Notify のほうが使う機会多いと思いますので、ぜひ参考にしてください！