【ESP32・M5Stack】ペアリングしてからBluetoothシリアル通信

## この記事の目的
ESP32やM5Stack系のデバイスで、`認証コード`を比較してペアリングを行った上で`BLESerial`を行えるようにする。
## BLESerial
ESP32には[bluetoothserial.cpp](https://github.com/espressif/arduino-esp32/blob/master/libraries/BluetoothSerial/src/BluetoothSerial.cpp)を用いてBluetooth同士でシリアル通信を行うサンプルスケッチ`SerialToSerialBT`があります。
しかし、サンプルスケッチではセキュリティに関する設定は存在せず、Non-Secureモードでシリアル通信を行います。
今回の記事では[bluetoothserial.cpp](https://github.com/espressif/arduino-esp32/blob/master/libraries/BluetoothSerial/src/BluetoothSerial.cpp)を参考にしながら`認証コード`を比較(Numeric Comparison)することによってペアリングしてから、シリアル通信ができるようにするコードを作成しました。

## コード
サンプルスケッチ`SerialToSerialBT`のコードをベースにしています。

```cpp:SerialToSerialBT_Auth.ino
#include "BluetoothSerial.h"
#include "esp_gap_bt_api.h"

static void gap_callback(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param);

BluetoothSerial SerialBT;

void setup() {
  Serial.begin(115200);
  SerialBT.begin("ESP32test"); //Bluetooth device name
  esp_bt_gap_register_callback (gap_callback);
  /* Secure Simple Pairing のときの認証方法を指定*/
  esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
  esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO; //PINの表示と接続可否の入力が可能
  esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
  /* Legacy pairingのときの認証方法を指定*/
  esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE; //ランダムなPIN(接続時に動的に生成)
  esp_bt_pin_code_t pin_code;
  esp_bt_gap_set_pin(pin_type, 0, pin_code);

  Serial.println("The device started, now you can pair it with bluetooth!");

}

void loop() {
  if (Serial.available()) {
    SerialBT.write(Serial.read());
  }
  if (SerialBT.available()) {
    Serial.write(SerialBT.read());
  }
  delay(20);
}


static void gap_callback(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
{
  switch (event) {
    case ESP_BT_GAP_AUTH_CMPL_EVT:
      if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
        Serial.printf("authentication success: %s\n", param->auth_cmpl.device_name); //認証に成功
      } else {
        Serial.printf("authentication failed, status:%d\n", param->auth_cmpl.stat); //認証に失敗
      }
      break;

    case ESP_BT_GAP_PIN_REQ_EVT:
      // default pairing pins
      if (param->pin_req.min_16_digit) { //PINが16桁以上必要なとき
        Serial.println("Input pin code: 0000 0000 0000 0000");
        esp_bt_pin_code_t pin_code;
        memset(pin_code, '0', ESP_BT_PIN_CODE_LEN); //実際には1234に固定するのではなく動的に生成しユーザに提示する
        esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code);
      } else {
        Serial.println("Input pin code: 1234");
        esp_bt_pin_code_t pin_code;
        memcpy(pin_code, "1234", 4); //実際には1234に固定するのではなく動的に生成しユーザに提示する
        esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code);
      }
      break;

    case ESP_BT_GAP_CFM_REQ_EVT:
      Serial.printf("ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d\n", param->cfm_req.num_val);

      esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);  //接続を許可
      //esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, false); //接続を拒否
      break;

    case ESP_BT_GAP_KEY_NOTIF_EVT:
      Serial.printf("ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d\n", param->key_notif.passkey);
      break;
    default:
      break;
  }
}
```

## 動作確認
* M5StickC Plus
* Windows10 64bit (Bluetooth5.1 接続対応)

で動作確認を行っています。

1. プログラムを書き込んだ後、Windows10でデバイスの追加を行う画面の表示。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1123247/9e66884a-9b80-e26d-fb55-13de252403cb.png)

1. 今回は`ESP32test`というデバイス名にしているので、このデバイスを選ぶと以下のような画面になる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1123247/03668888-5db3-240a-b26b-ff6f735d512c.png)
1. `M5StickC Plus`側ではシリアル上で以下のような出力が行われる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1123247/86fb7a1b-3764-536c-bd68-d1e9ec82869b.png)
1. PINが一致していることを確認し、Windows側で接続を行う。
1. ペアリングが完了する。同時にBluetooth仮想COMポートが追加される[^1]。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1123247/378aff65-fa52-b693-e788-0ae8b2d7b330.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1123247/c9075b12-c667-472f-e6f9-eee28fafa406.png)

ただし先に示したコードの`esp_bt_gap_ssp_confirm_reply`において、第3引数を`False`にした場合は接続に失敗する。失敗と同時に`M5StickC Plus`側ではシリアル上で以下のような出力が行われる[^2]。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1123247/9251c6cc-2cf4-c70f-dd24-a276725dbf38.png)


## 動作確認(Legacy Pairingの場合)
Legacy PairingはNumeric Comparisonとは異なり、PINコードを入力することによって認証を行う方式で、バージョンの古いBluetoothデバイスで接続した際にこの方式で認証されるようです。
今回は検証に必要な端末を持っていないため、検証していません。
恐らくLegacy Pairingの際は先ほど示したコードの以下の部分が生きてくると思います。

```cpp
case ESP_BT_GAP_PIN_REQ_EVT:
      // default pairing pins
      if (param->pin_req.min_16_digit) { //PINが16桁以上必要なとき
        Serial.println("Input pin code: 0000 0000 0000 0000");
        esp_bt_pin_code_t pin_code;
        memset(pin_code, '0', ESP_BT_PIN_CODE_LEN); //実際には1234に固定するのではなく動的に生成しユーザに提示する
        esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code);
      } else {
        Serial.println("Input pin code: 1234");
        esp_bt_pin_code_t pin_code;
        memcpy(pin_code, "1234", 4); //実際には1234に固定するのではなく動的に生成しユーザに提示する
        esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code);
      }
      break;
```
コードでは`param->pin_req.min_16_digit`が`False`のときは、`1234`がPINになります。ただ実際にはPINをランダムで生成する方が安全です。





[^1]: COMポートが2つ追加されますが、Bluetooth設定のCOMポートタブで、名前に`ESP32SPP`を含むポートを選択すれば通信できます。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1123247/57e23e82-5a24-3c24-5560-e18ba6e7919c.png)

[^2]: 実際にはM5側でも接続するか否かをユーザに選択してもらい、`esp_bt_gap_pin_reply`を呼び出すという形になります。

## 参考文献
以下のページを参考にして本記事を執筆しています。

Bluetoothのセキュリティのはなし｜Wireless・のおと｜サイレックス・テクノロジー株式会社
[http://www.silex.jp/blog/wireless/2015/09/bluetooth.html](http://www.silex.jp/blog/wireless/2015/09/bluetooth.html)

CLASSIC BLUETOOTH GAP API - ESP32 -  &mdash; ESP-IDF Programming Guide latest documentation
[https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/bluetooth/esp_gap_bt.html](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/bluetooth/esp_gap_bt.html)

BluetoothSerial.cpp
[https://github.com/espressif/arduino-esp32/blob/master/libraries/BluetoothSerial/src/BluetoothSerial.cpp](https://github.com/espressif/arduino-esp32/blob/master/libraries/BluetoothSerial/src/BluetoothSerial.cpp)

SerialToSerialBT.ino
[https://github.com/espressif/arduino-esp32/blob/master/libraries/BluetoothSerial/examples/SerialToSerialBT/SerialToSerialBT.ino](https://github.com/espressif/arduino-esp32/blob/master/libraries/BluetoothSerial/examples/SerialToSerialBT/SerialToSerialBT.ino)