BLEによるシリアル通信の無線化

# 概要
M5Stamp C3U MateのBLEにてシリアル通信の無線化を行いました。
以前Bluetooth Classicの時にSPPによるシリアル通信を使っていたのですが、
BLEでは使えなくなりました。SPPは手軽で良かったのですが、BLEではなにやら難しくて。
色々探していたところ良い情報がありやっと実現できました。

私の使い勝手から送受信データは'テキスト'、終端文字'\n'としています。
M5Stamp C3U Mate2台を使い片方をServer、もう片方をClientに設定して
相互にBLE通信を行います。ラベル無しがServer、ラベル有りがClient側です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611426/4bedc151-6382-c38a-4b48-957654673250.png)

下記が通信の状況です。COM3：Server、COM4：Clientとなっています。
ServerからのテキストデータがClient側で受信されています。
逆も同じように受信されています。
今回はUSBSerialで行いましたがUART通信も同様に無線化が可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611426/42d30822-e7ba-cbb4-a632-b057115f5c74.png)

起動直後はLEDが緑に点灯します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611426/f22f3bb6-3db1-40a1-3282-1e09e2ebebf9.png)


7,8秒ほどで接続が完了し、LEDが青に点灯します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611426/44b8a8ea-948c-39a6-2e87-c9b8e19a0901.png)

接続が切れるとLEDが赤に点灯します。（Client側の電源を切っています）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2611426/08067924-12c5-b55d-9043-5e2607005e89.png)

# 環境など
　エディタ：VisualStudioCode（PlatformIO IDE）
　マイコン：M5Stamp C3U Mate　2台
　フレームワーク：Aruduino

 # コード（Server側）　
``` cpp
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <Adafruit_NeoPixel.h>

BLEServer *pServer = NULL;
BLECharacteristic *pTxCharacteristic;
BLECharacteristic *pRxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;
bool receivedflg=false;//受信フラグ

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

//serverとcliantでTX,RXは逆転するのに注意
#define SERVICE_UUID "e942c8e4-1a8e-11ef-9262-0242ac120002"
#define CHARACTERISTIC_UUID_RX "e942cbbe-1a8e-11ef-9262-0242ac120002"
#define CHARACTERISTIC_UUID_TX "e942cdee-1a8e-11ef-9262-0242ac120002"

#define SERVER_NAME "esp32ble"
std::string rx_Data;
String rx_Str;//受信文字列
String tx_Str;//送信文字列

//LED設定
#define LED_PIN 2
Adafruit_NeoPixel pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800);

class MyServerCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer *pServer) {
    deviceConnected = true;

    // BLE接続でLED青点灯
    pixels.setPixelColor(0, pixels.Color(0, 0, 50));
    pixels.show();
  };

  void onDisconnect(BLEServer *pServer) {
    deviceConnected = false;
    // BLE接続でLED赤点灯
    pixels.setPixelColor(0, pixels.Color(50, 0, 0));
    pixels.show();
  }
};

//受信時コールバック　文字列取得
class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string rxValue = pCharacteristic->getValue();
      rx_Str=rxValue.c_str(); //受信文字列取得
      receivedflg=true;       //受信フラグセット
    }
};

void setup() {
  // 起動時LED緑点灯
  pixels.begin();//NeoPixel制御開始
  pixels.setPixelColor(0, pixels.Color(0, 50, 0));//LED設定（赤, 緑, 青）
  pixels.show();//LED点灯

  Serial.begin(115200);
  Serial.println("BLE_Server_start");

  // Create the BLE Device
  BLEDevice::init(SERVER_NAME);

  // Create the BLE Server
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic
  pTxCharacteristic = pService->createCharacteristic(
										CHARACTERISTIC_UUID_TX,
										BLECharacteristic::PROPERTY_NOTIFY
									);
  pTxCharacteristic->addDescriptor(new BLE2902());

  BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(
                                             CHARACTERISTIC_UUID_RX,
                                            BLECharacteristic::PROPERTY_WRITE
                                        );
  pRxCharacteristic->setCallbacks(new MyCallbacks());                                      

  // Start the service
  pService->start();
  
  // Start advertising
  pServer->getAdvertising()->start();
  Serial.println("Waiting a client connection to notify...");
}

void loop() {

  if (deviceConnected) {

    // BLEデータ受信時
    if(receivedflg==true){  
      Serial.println("rev:" + rx_Str);
      receivedflg=false;
    }

    // USBシリアルデータ送信
    if (Serial.available() > 0) {
      String serial_data = Serial.readStringUntil('\n');
      tx_Str = serial_data + "\n";//終端文字追加
      pTxCharacteristic->setValue(tx_Str.c_str());
      pTxCharacteristic->notify();
    }
  }

  // disconnecting
  if (!deviceConnected && oldDeviceConnected) {
      delay(500); // give the bluetooth stack the chance to get things ready
      pServer->startAdvertising(); // restart advertising
      Serial.println("start advertising");
      oldDeviceConnected = deviceConnected;
  }

  // connecting
  if (deviceConnected && !oldDeviceConnected) {
    // do stuff here on connecting
    oldDeviceConnected = deviceConnected;
  }

  delay(10);
}
```

# コード（Client側）　
``` cpp
#include <Arduino.h>
#include "BLEDevice.h"
#include <Adafruit_NeoPixel.h>

#define SERVICE_UUID "e942c8e4-1a8e-11ef-9262-0242ac120002"
#define CHARACTERISTIC_UUID_TX "e942cbbe-1a8e-11ef-9262-0242ac120002"
#define CHARACTERISTIC_UUID_RX "e942cdee-1a8e-11ef-9262-0242ac120002"
#define SERVER_NAME         "esp32ble"

static BLEUUID  serviceUUID(SERVICE_UUID);
static BLEUUID  charUUID_RX(CHARACTERISTIC_UUID_RX);
static BLEUUID  charUUID_TX(CHARACTERISTIC_UUID_TX);

static BLEAddress *pServerAddress;
static boolean doConnect = false;
static boolean connected = false;
static BLERemoteCharacteristic* pRemoteCharacteristicRX;
static BLERemoteCharacteristic* pRemoteCharacteristicTX;


bool receivedflg=false;//受信フラグ
String rx_Str;//受信文字列
String tx_Str;//送信文字列

//LED設定
#define LED_PIN 2
Adafruit_NeoPixel pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800);

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {
    rx_Str = (char*)pData;// BLEデータ取得
    receivedflg = true;//受信フラグセット
}

class MyClientCallback : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {
    // BLE接続でLED青点灯
    pixels.setPixelColor(0, pixels.Color(0, 0, 50));
    pixels.show();
  }

  void onDisconnect(BLEClient* pclient) {
    connected = false;
    Serial.println("onDisconnect");

    // BLE接続でLED赤点灯
    pixels.setPixelColor(0, pixels.Color(50, 0, 0));
    pixels.show();
  }
};

bool connectToServer(BLEAddress pAddress) {
    Serial.print("Forming a connection to ");
    Serial.println(pAddress.toString().c_str());
    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());
    
    // Connect to the remove BLE Server.            
    pClient->connect(pAddress);
    Serial.println(" - Connected to server");
    pClient->setMTU(517); //set client to request maximum MTU from server (default is 23 otherwise)

    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    Serial.print(" - Connected to server :");
    Serial.println(serviceUUID.toString().c_str());
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(serviceUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    
    pRemoteCharacteristicRX = pRemoteService->getCharacteristic(charUUID_RX);
    if (pRemoteCharacteristicRX == nullptr) {
      Serial.print("Failed to find characteristic RX ID: ");
      Serial.println(charUUID_RX.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.print(" - Found characteristic RX ID: ");
    Serial.println(charUUID_RX.toString().c_str());

    // Send用キャラクタリスティックの参照を取得する
    pRemoteCharacteristicTX = pRemoteService->getCharacteristic(charUUID_TX);
    if (pRemoteCharacteristicTX == nullptr) {
      Serial.print("Failed to find characteristic TX ID ");
      Serial.println(charUUID_TX.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.print(" - Found characteristic TX ID: ");
    Serial.println(charUUID_TX.toString().c_str());
    
    // Read the value of the characteristic.
    if(pRemoteCharacteristicRX->canRead()) {
      std::string value = pRemoteCharacteristicRX->readValue();
      Serial.print("The characteristic value was: ");
      Serial.println(value.c_str());
    }

    if(pRemoteCharacteristicRX->canNotify())    
    pRemoteCharacteristicRX->registerForNotify(notifyCallback);
    Serial.println("End of notify");
    
    return true;
}


class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.print("BLE Advertised Device found: ");
    Serial.println(advertisedDevice.toString().c_str());
    Serial.println(advertisedDevice.getName().c_str());
    
    if(advertisedDevice.getName()==SERVER_NAME){
      Serial.println(advertisedDevice.getAddress().toString().c_str());
      advertisedDevice.getScan()->stop();
      pServerAddress = new BLEAddress(advertisedDevice.getAddress());
      doConnect = true;
    }
  }
};

void get_scan(){
  BLEScan* pBLEScan = BLEDevice::getScan();
  Serial.println("getScan");
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  Serial.println("setAdvertisedDeviceCallbacks");
  pBLEScan->setActiveScan(true);
  pBLEScan->start(10); 
  Serial.println("");
  Serial.println("End of scan");
}


void setup() {
  // 起動時LED緑点灯
  pixels.begin();//NeoPixel制御開始
  pixels.setPixelColor(0, pixels.Color(0, 50, 0));//LED設定（赤, 緑, 青）
  pixels.show();//LED点灯

  Serial.begin(115200);
  delay(10000);

  Serial.println("BLE_Cliant_start");
  BLEDevice::init("");
  get_scan();
  Serial.println("End of setup");
}


void loop() {
  if (doConnect == true) {
    if (connectToServer(*pServerAddress)) {
      Serial.println("We are now connected to the BLE Server.");
      connected = true;
      

    } else {
      Serial.println("We have failed to connect to the server; there is nothin more we will do.");
      connected = false;
    }
    doConnect = false;
  }
  if (connected) {

    // BLEデータ受信時
    if(receivedflg==true){
      Serial.println("rev:"+rx_Str);
      receivedflg = false;
    }

    // USBシリアルデータ送信
    if (Serial.available() > 0) {
      String serial_data = Serial.readStringUntil('\n');
      tx_Str = serial_data + "\n";//終端文字追加
      pRemoteCharacteristicTX->writeValue(tx_Str.c_str());
    }

  } else{
    Serial.println("Not connected");
  //conectできないときにsscanループさせておくため
    doConnect = false;
    get_scan();
  }
  delay(10);
}
```
# plathomeio.ini設定
[env:esp32-c3-devkitm-1]
platform = espressif32
board = esp32-c3-devkitm-1
framework = arduino
monitor_speed = 115200
build_flags = 
	-DARDUINO_USB_CDC_ON_BOOT
	-DARDUINO_USB_MODE
board_build.partitions = no_ota.csv
lib_deps = 
	m5stack/M5Unified@^0.1.14
	adafruit/Adafruit NeoPixel@^1.12.2

# 参考サイト
https://akiba-watching.com/archives/1884

https://github.com/espressif/arduino-esp32/blob/master/libraries/BLE/examples/UART/UART.ino