ESP32からBLEでオムロン環境センサ(2JCIE-BL)に繋ぐ

#はじめに
オムロン環境(2JCIE-BL)を手に入れまして、このセンサすごいなと思いました、簡単にBluetoothでスマホから情報みれたり・・・
そこでマイコンからも情報を手に入れたいなと思いました。
ラズパイ3から取得する情報はあったのですが
[オムロン環境センサーからBLE経由でデーターをクラウドに送る](https://ambidata.io/samples/temphumid/ble-gw-omron/)
ESP32を使ったものはありませんでした、そこでESP32から取得したいと思います。

#使ったもの
maxOS Mojave 10.14.2
ESP32 DevKitC
Arduino IDE

#BLEについて
BluetoothとBLEはそもそも違うものらしいです

BLE通信にはセントラルとペリフェラルというものがありまして
マスターとスレーブみたいなやつがいます。今回はセントラルがESP32　ペリフェラルが環境センサです、だいたいセンサはペリフェラルです。
ペリフェラルは「どんなサービスか、例：センサー値提供サービス」を示すサービスUUIDと「どんな値か、例：最新値」を示すキャラクタりスティックUUIDを持っています。フォルダとファイルの構造に似ています。
セントラルがそのサービスUUIDの中にあるキャラクタリスティックUUIDを参照することでデータのやりとりをすることができます。

###BLEの接続手順

**Advertise**
ペリフェラルが「ぼくはこんなアドレスで、こんなサービスUUIDを持ってるよ～見つけて～」といってます
**Scan**
セントラルがまわりのペリフェラルをさがします
**Connect**
セントラルが指定した、ペリフェラルに接続要請をするとセントラルに対してペリフェラルがそれに答えます。

という感じです。
詳しくわかりやすく以下のページでまとめられています

[クラゲのIoTテクノロジー 開発視点の超簡単BLE入門](http://jellyware.jp/kurage/bluejelly/ble_guide.html)

#ライブラリの導入
ArduinoからESP32を使用する方法はこちらがわかりやすいのでこちらを参考に導入ください
[Arduino core for the ESP32 のインストール方法](https://www.mgo-tec.com/arduino-core-esp32-install)


ESP32用BLEライブラリがArduinoのライブラリプラットフォーム上にあったのですがこちらバグが多すぎて使えませんでした
<img width="720" alt="スクリーンショット 2019-02-23 19.56.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/243994/e8426d0b-7917-ba33-01d1-4d22bc395a26.png">

それらのバグを修正してくれたものがこちらにありました！
wakwak-kobaさんありがとうございます
https://github.com/wakwak-koba/ESP32_BLE_Arduino

このままではエラー吐いて動かないので
src/BLEUtils.cpp　1005行目、1006行目をコメントアウトしてください

```C++:BLEUtils.cpp
//case ESP_GATTS_SEND_SERVICE_CHANGE_EVT: |
//return "ESP_GATTS_SEND_SERVICE_CHANGE_EVT"; |
```


#繋いでみよう

先ほど書いたように、オムロン環境センサからデータを取得するにはオムロン環境センサのサービスUUIDとキャラクタリスティックUUIDが必要になります。
[オムロン環境センサユーザーズマニュアル](https://omronfs.omron.com/ja_JP/ecb/products/pdf/CDSC-015.pdf)
資料によると
サービスUUID：0C4C3000-7700-46F4-AA96D5E974E32A54
キャラクタリスティックUUID：0C4C3001-7700-46F4-AA96D5E974E32A54
を参照すれば、最新のセンサーデータが取得できるようです。
<img width="656" alt="スクリーンショット 2019-02-22 0.10.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/243994/3aa6af92-9441-a1b4-d190-a0cef83936b5.png">


先ほど入れたライブラリのexample ESP32_client.inoを使用します


```C++:BLE_Client.ino12行目あたり
static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
static BLEUUID    charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8");
```

12行目あたりにこのような宣言がありますのでこの値をオムロン環境センサの値に書き換えます。

**このままうまくいくと思ったのですが繋がりませんでした**


**一度書き込んで　シリアルポートを開いてみてください**
Address:XX:XX:XX:XX:XX:XX manufacturer data:~~~ ServiceUUID:0000180a-0000-1000-8000-00805f9b34fb 

みたいなやつがいると思います。こいつがオムロンの環境センサなのですが、
つながりません。
**このXX:XX:XX:XX:XX:XXは環境センサのアドレスです、後で使うのでメモっておいてください**

サンプルコードを見てみます

```C++:BLE_Client.ino103行目あたり
if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {

      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      doScan = true;
```

**BLEAdvertisedDeviceCallbacks**関数内のこのあたりで、
接続処理をしているのですが、どうにもこうにも繋がりませんでした。
**advertisedDevice.haveServiceUUID()**
はアドバタイズしているデバイスがサービスUUIDをもっているかを判断
**advertisedDevice.isAdvertisingService(serviceUUID)**
 はそのアドバタイズしているデバイスが、指定したサービスUUIDをもっているかどうかをチェックしています。

なんで！？！？　0C4C3000-7700-46F4-AA96D5E974E32A54　もってるって書いてあるじゃん！！！
とずっとここで詰まっていました。

**BLE通信の説明で
ペリフェラル「ぼくはこんなアドレスで、こんなサービスUUIDを持ってるよ～見つけて～」と書きましたが**
**アドバタイズ時に情報に乗せているサービスUUIDは１つや２つぐらいなんです。**

そりゃ全部のっけたら通信量めちゃくちゃ増えちゃいますもんね。

オムロン環境センサがアドバタイズに乗せているサービスUUIDは
0000180a-0000-1000-8000-00805f9b34fb
というデバイス情報に関するサービスUUIDで、センサに関するサービスは乗っていません。
**なので接続してからサービスUUIDをペリフェラルからもらう必要があります**

#サンプルを書き換えよう
サンプルでは、接続する前に、そのペリフェラルが指定したサービスUUIDを持っているかどうかを判断し、接続していましたが、その方法では無理だということがわかりました。
なのでアドバタイズしているペリフェラルに対して、
**ペリフェラルのもつアドレスを用いて、環境センサを判断してから接続をします。**

```C++
#include "BLEDevice.h"

static String omronSensorAdress = "ff:f7:ef:33:f4:31";
static BLEUUID serviceUUID("0c4c3000-7700-46f4-aa96-d5e974e32a54");
static BLEUUID    charUUID("0c4C3001-7700-46F4-AA96-D5E974E32A54");

static BLEAddress *pServerAddress;
static boolean doConnect = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;


static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {
    Serial.print("Notify callback for characteristic ");
    Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
    Serial.print(" of data length ");
    Serial.println(length);
    float temp = (float)(pData[2] << 8 | pData[1]) / 100.0;  // データーから温度等を取り出す
    float press = (float)(pData[10] << 8 | pData[9]) / 10.0;
    float shodo = (float)(pData[6] << 8 | pData[5]);
    Serial.print("temp:");
    Serial.println(temp);
    Serial.print("press:");
    Serial.println(press);
    Serial.print("shodo:");
    Serial.println(shodo);
}

bool connectToServer(BLEAddress pAddress) {
    Serial.print("Forming a connection to ");
    Serial.println(pAddress.toString().c_str());

    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    // Connect to the remove BLE Server.
    pClient->connect(pAddress);
    Serial.println(" - Connected to server");

    // Obtain a reference to the service we are after in the remote BLE server.
    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(serviceUUID.toString().c_str());
      return false;
    }else{
    Serial.println(" - Found our service");
    }


    // Obtain a reference to the characteristic in the service of the remote BLE server.
    pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
    if (pRemoteCharacteristic == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID.toString().c_str());
      return false;
    }
    Serial.println(" - Found our characteristic");
    // Read the value of the characteristic.
    std::string value = pRemoteCharacteristic->readValue();
    Serial.print("The characteristic value was: ");
    Serial.println(value.c_str());

    pRemoteCharacteristic->registerForNotify(notifyCallback);
}
/**
 * Scan for BLE servers and find the first one that advertises the service we are looking for.
 */
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
 /**
   * Called for each advertising BLE server.
   */
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    BLEScan* pBLEScan = BLEDevice::getScan();
    Serial.println("BLE Advertised Device found: ");
    Serial.println(advertisedDevice.toString().c_str());

    
    String deviceAddress = advertisedDevice.getAddress().toString().c_str();
    Serial.println(deviceAddress.c_str());
    //ペリフェラルをアドレスで判断
    if(deviceAddress.equalsIgnoreCase(omronSensorAdress)){
      advertisedDevice.getScan()->stop();
      Serial.print("Found our device!  address: ");
      //サーバーのアドレスを変数に代入 
      pServerAddress = new BLEAddress(advertisedDevice.getAddress());
     doConnect = true;
    }// Found our server
  } // onResult
}; // MyAdvertisedDeviceCallbacks


void setup() {
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");
  BLEDevice::init("");
  // Retrieve a Scanner and set the callback we want to use to be informed when we
  // have detected a new device.  Specify that we want active scanning and start the
  // scan to run for 30 seconds.
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5);
} // End of setup.


// This is the Arduino main loop function.
void loop() {

  // If the flag "doConnect" is true then we have scanned for and found the desired
  // BLE Server with which we wish to connect.  Now we connect to it.  Once we are 
  // connected we set the connected flag to be true.
  if (doConnect == true) {
    if (connectToServer(*pServerAddress)) {
      Serial.println("We are now connected to the BLE Server.");
    } else {
      Serial.println("We have failed to connect to the server; there is nothin more we will do.");
    }
    doConnect = false;
  }


  delay(1000); // Delay a second between loops.
} // End of loop
```

**取得できました！！！！**

<img width="639" alt="スクリーンショット 2019-02-22 1.54.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/243994/1b26d189-57c1-19a8-149f-6fe83eee5c7e.png">

#解説

```C++
static String omronSensorAdress = "ff:f7:ef:33:f4:31";
static BLEUUID serviceUUID("0c4c3000-7700-46f4-aa96-d5e974e32a54");
static BLEUUID    charUUID("0c4C3001-7700-46F4-AA96-D5E974E32A54");
static BLEAddress *pServerAddress;
```
でオムロンセンサのアドレスとセンサーサービスUUID、キャラクタリスティックUUIDを定義し、
受け取ったサーバーのアドレスを代入する変数を宣言します。

```C++
String deviceAddress = advertisedDevice.getAddress().toString().c_str();

if(deviceAddress.equalsIgnoreCase(omronSensorAdress)){
      advertisedDevice.getScan()->stop();
      Serial.print("Found our device!  address: ");
      //サーバーのアドレスを変数に代入 
      pServerAddress = new BLEAddress(advertisedDevice.getAddress());
     doConnect = true;
    }// Found our server
```
**BLEAdvertisedDeviceCallbacks**内で　デバイスのアドレスを取得し、定義したアドレスと等しい場合、
そのアドレスを変数に代入します。

**connectToServer(BLEAddress pAddress)**でそのアドレスを受け取り

```C++
BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(serviceUUID.toString().c_str());
      return false;
    }else{
    Serial.println(" - Found our service");
    }
```

で、サービスIDを見つけ

```C++
pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
    if (pRemoteCharacteristic == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID.toString().c_str());
      return false;
    }
    Serial.println(" - Found our characteristic");
    // Read the value of the characteristic.
    std::string value = pRemoteCharacteristic->readValue();
    Serial.print("The characteristic value was: ");
    Serial.println(value.c_str());

    pRemoteCharacteristic->registerForNotify(notifyCallback);
```

でキャラクたりスティックを見つけ、そのキャラクタリスティックをnotifyCallbackに登録することで、
オムロンセンサの値が変化した時。値を取得することができます。

こうして受け取れるのは19byteの値です、これを人間が読める値にするために、オムロンユーザーズマニュアルを参考にし、
<img width="670" alt="スクリーンショット 2019-02-24 1.26.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/243994/bca2b04c-0916-bd2a-04ae-a0e3e6ffcef5.png">

とりあえず温度と気圧と照度がとりたいと思ったので

```C++
float temp = (float)(pData[2] << 8 | pData[1]) / 100.0;  // データーから温度等を取り出す
float press = (float)(pData[10] << 8 | pData[9]) / 10.0;
float shodo = (float)(pData[6] << 8 | pData[5]);
```
とな感じでそれぞれのデータを取り出しています。

#まとめ
値はとれるのでこの値を使ってみなさん好きなものを作ってください
M5stackでセンサ情報常に表示したりもできると思います！

#追記（重要）
なぜか再び起動したところ、接続できなくなりました。
調べたところ

```C++:sample.ino
connectToServer(BLEAddress pAddress) {
    Serial.print("Forming a connection to ");
    Serial.println(pAddress.toString().c_str());

    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    // Connect to the remove BLE Server.
    pClient->connect(pAddress);
    Serial.println(" - Connected to server");
    .....省略
```

pClient->connect(pAddress),を用いて接続する場合
デバイスのアドレスのタイプを指定してあげなければいけないそうです。

参考
[【解決】Arduino-ESP32 1.0.1 の BLEClient の connect が1.0.0のコードと同じだと無限待機する件](https://qiita.com/ajtajta_j/items/1ae50f189967214c10e9)

BLE機器のアドレスは製品製造時に機器ごとに書き込まれる固定のアドレス(Public)と各端末（機器）で自動生成される端末アドレス(Rondom)があるそうです。
2JCIEはランダムアドレスなので

**pClient->connect(pAddress,BLE_ADDR_TYPE_RANDOM);**

とランダムタイプを指定してあげてください。