CoreBluetoothForUnity で M5Stack と通信する

## 本記事について

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

本記事では CoreBluetoothForUnity のサンプル 02_ButtonInformation と通信するための M5Stack 側のプログラムを紹介します。

## 環境

- MacBook Air M2
- M5AtomS3

## M5Stack の開発関連記事

Hello World や 段階的に BLE 通信を行う方法等は別記事ですでに書いているためそちらのリンクを貼っておきます。

https://qiita.com/Teach/items/9d0f21736808f44e2abc

https://qiita.com/Teach/items/18013e1e34d649c7947d

https://qiita.com/Teach/items/629c338da05a3134a1eb


## 02_ButtonInformation と通信するときのポイント

1. Service, Characteristic の UUID を適切に指定
2. ボタンの押下状態の取得
3. ボタンの状態を BLE 通信で送信できる形にフォーマット

これらが基本的な Notify のコードから変える必要のある部分です。

## 実装方法

長いですが、読みづらくはないと思うため先に全文載せてしまいます。

```main.cpp
#include <M5Unified.h>
#include "BLEDevice.h"
#include "BLEServer.h"
#include <BLE2902.h>

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID "068C47B7-FC04-4D47-975A-7952BE1A576F"
#define CHARACTERISTIC_UUID "E91E5ECB-A460-4DB1-97F7-F13D52222E15"

static int8_t state = 0;

#define STATE_ADVERTISING 0
#define STATE_CONNECTING 1
#define STATE_CONNECTED 2
#define STATE_DISCONNECTED 3

static bool redraw = false;

BLECharacteristic *pButtonInformationCharacteristic;

class MyServerCallbacks : public BLEServerCallbacks
{
  void onConnect(BLEServer *pServer)
  {
    state = STATE_CONNECTED;
    BLEDevice::stopAdvertising();
    redraw = true;
  };

  void onDisconnect(BLEServer *pServer)
  {
    state = STATE_DISCONNECTED;
    redraw = true;
  }
};

void startServer()
{
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  BLEService *pService = pServer->createService(SERVICE_UUID);
  pButtonInformationCharacteristic = pService->createCharacteristic(
      CHARACTERISTIC_UUID,
      BLECharacteristic::PROPERTY_READ |
          BLECharacteristic::PROPERTY_NOTIFY);
  pButtonInformationCharacteristic->addDescriptor(new BLE2902());

  pService->start();

  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  BLEDevice::startAdvertising();
}

void setup()
{
  auto cfg = M5.config();
  M5.begin(cfg);
  USBSerial.begin(115200);
  M5.Display.setTextSize(2);

  BLEDevice::init("M5AtomS3");
  startServer();
  state = STATE_ADVERTISING;
  redraw = true;
}

std::string getButtonInformation(int buttonId, bool isPressed)
{
  std::string buff = "";
  buff += (char)buttonId;
  buff += (char)(isPressed ? 0x80 : 0x00);
  return buff;
}

void onPressBtnA()
{
  if (state != STATE_CONNECTED)
  {
    return;
  }

  std::string buff = getButtonInformation(1, true);
  pButtonInformationCharacteristic->setValue(buff);
  pButtonInformationCharacteristic->notify();
}

void onReleaseBtnA()
{
  if (state != STATE_CONNECTED)
  {
    return;
  }

  std::string buff = getButtonInformation(1, false);
  pButtonInformationCharacteristic->setValue(buff);
  pButtonInformationCharacteristic->notify();
}

void handleButtonEvent()
{
  if (M5.BtnA.wasPressed())
  {
    onPressBtnA();
  }

  if (M5.BtnA.wasReleased())
  {
    onReleaseBtnA();
  }
}

void drawAdvertising()
{
  M5.Display.startWrite();
  M5.Display.clear(BLACK);
  M5.Display.setCursor(0, 20);
  M5.Display.printf("Advertising");
  M5.Display.endWrite();
}

void drawConnected()
{
  M5.Display.startWrite();
  M5.Display.clear(BLACK);
  M5.Display.setCursor(0, 20);
  M5.Display.println("Connected");
  M5.Display.endWrite();
}

void drawDisplay()
{
  switch (state)
  {
  case STATE_ADVERTISING:
    drawAdvertising();
    break;
  case STATE_CONNECTED:
    drawConnected();
    break;
  }
}

void loop()
{
  M5.update();
  handleButtonEvent();

  if (state == STATE_DISCONNECTED)
  {
    state = STATE_ADVERTISING;
    BLEDevice::startAdvertising();
    redraw = true;
  }

  if (!redraw)
    return;
  redraw = false;
  drawDisplay();
}
```

UUID は Unity 側と同じ値を設定しています。

```SampleButtonInformation_Data.cs
public class SampleButtonInformation_Data
{
...
    public static readonly string ServiceUUID = "068C47B7-FC04-4D47-975A-7952BE1A576F";
    public static readonly string ButtonInformationCharacteristicUUID = "E91E5ECB-A460-4DB1-97F7-F13D52222E15";
```

### ボタンの押下状態

```main.cpp
void handleButtonEvent()
{
  if (M5.BtnA.wasPressed())
  {
    onPressBtnA();
  }

  if (M5.BtnA.wasReleased())
  {
    onReleaseBtnA();
  }
}
```

wasPressed() と　wasReleased() で取得しています。

ボタンの状態の取得方法は以下の M5Unified のドキュメントに書かれています。

https://docs.m5stack.com/ja/quick_start/m5unified/migration

### ボタンの状態を BLE 通信で送信できる形にフォーマット

ここが本記事の本題です。

Characteristic の値を更新する setValue() の引数は複数ありますが、byte[] を渡すような感覚で使用できるのは以下の2つです。

```
void BLECharacteristic::setValue(uint8_t *data, size_t size)
void BLECharacteristic::setValue(std::string value)
```

[setValue](https://lang-ship.com/reference/unofficial/M5StickC/Class/ESP32/BLECharacteristic/#setvalue)

上記の実装では std::stringを用いています。

```main.cpp
std::string getButtonInformation(int buttonId, bool isPressed)
{
  std::string buff = "";
  buff += (char)buttonId;
  buff += (char)(isPressed ? 0x80 : 0x00);
  return buff;
}
```
数字を char に変換して std::string に追加しています。

uint8_t で渡す場合には以下のように書くことができます。

```main.cpp
void getButtonInformation(uint8_t buff[2], int buttonId, bool isPressed)
{
  buff[0] = buttonId;
  buff[1] = (isPressed ? 0x80 : 0x00);
}

void onPressBtnA()
{
  if (state != STATE_CONNECTED)
  {
    return;
  }

  uint8_t buff[2];
  getButtonInformation(buff, 1, true);
  pButtonInformationCharacteristic->setValue(buff, sizeof(buff));
  pButtonInformationCharacteristic->notify();
}
```

どちらを使っても動作することは確認できました。

## おわりに

本記事では CoreBluetoothForUnity のサンプル 02_ButtonInformation と通信するための M5Stack 側のプログラムを紹介しました。

M5Stack を使うと Unity で作ったゲームのコントローラーを作ったり、なんらかのアウトプットに利用したりなど表現の幅は広がるかなと思います。
こちらのプログラムが参考になれば幸いです。