Help us understand the problem. What is going on with this article?

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

はじめに

オムロン環境(2JCIE-BL)を手に入れまして、このセンサすごいなと思いました、簡単にBluetoothでスマホから情報みれたり・・・
そこでマイコンからも情報を手に入れたいなと思いました。
ラズパイ3から取得する情報はあったのですが
オムロン環境センサーからBLE経由でデーターをクラウドに送る
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入門

ライブラリの導入

ArduinoからESP32を使用する方法はこちらがわかりやすいのでこちらを参考に導入ください
Arduino core for the ESP32 のインストール方法

ESP32用BLEライブラリがArduinoのライブラリプラットフォーム上にあったのですがこちらバグが多すぎて使えませんでした
スクリーンショット 2019-02-23 19.56.22.png

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

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

BLEUtils.cpp
//case ESP_GATTS_SEND_SERVICE_CHANGE_EVT: |
//return "ESP_GATTS_SEND_SERVICE_CHANGE_EVT"; |

繋いでみよう

先ほど書いたように、オムロン環境センサからデータを取得するにはオムロン環境センサのサービスUUIDとキャラクタリスティックUUIDが必要になります。
オムロン環境センサユーザーズマニュアル
資料によると
サービスUUID:0C4C3000-7700-46F4-AA96D5E974E32A54
キャラクタリスティックUUID:0C4C3001-7700-46F4-AA96D5E974E32A54
を参照すれば、最新のセンサーデータが取得できるようです。
スクリーンショット 2019-02-22 0.10.44.png

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

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は環境センサのアドレスです、後で使うのでメモっておいてください

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

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は1つや2つぐらいなんです。

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

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

サンプルを書き換えよう

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

#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

取得できました!!!!

スクリーンショット 2019-02-22 1.54.33.png

解説

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を定義し、
受け取ったサーバーのアドレスを代入する変数を宣言します。

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)でそのアドレスを受け取り

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を見つけ

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の値です、これを人間が読める値にするために、オムロンユーザーズマニュアルを参考にし、
スクリーンショット 2019-02-24 1.26.24.png

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

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でセンサ情報常に表示したりもできると思います!

追記(重要)

なぜか再び起動したところ、接続できなくなりました。
調べたところ

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のコードと同じだと無限待機する件

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

pClient->connect(pAddress,BLE_ADDR_TYPE_RANDOM);

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away