ESP32の周りのBLEデバイスを監視

バッファローの「探せるキーホルダー」や、Logitecの「ぶるタグ」、懐かしい「Stick-N-Find」やNTTドコモのProject Linkingの「Pochiru」や「Tomoru」など、あるいは、LINE Beaconなど、常に電源On状態でアドバタイズし続けるBLEデバイスの、稼働状態や出入りを監視します。

BLEアドバタイズやレスポンスのスキャンデータには、デバイスがサポートするサービスUUIDが含まれています。
そこに、以下のUUIDが含まれていたら、生存していることとしてサーバに通知します。

　Stick-N-Find：bec26202-a8d8-4a94-80fc-9ac1de37daa6
　Project Linking：b3b36901-50d3-4044-808d-50835b13a6cd
　Immediate Alert Service：0x1802
　LINE Things：0xfe6f

スキャンするBLE CentralデバイスとしてESP32を使います。
M5StampC3が小さくてちょうど良い感じです。とはいっても、どのESP32でも動作します。

ソースコードもろもろは以下にあります。

poruruba/NearbyBleDeviceScan

https://github.com/poruruba/NearbyBleDeviceScan


# BLEデバイスの検出(@ESP32)

BLEデバイスを検出したら、HTTP Postで通知するか、MQTTで通知するか、2種類を用意しておきました。
スキャンと通知の流れは以下のようにしています。

①	30秒BLEアドバタイズをスキャンします。スキャンしたデータにさきほどのUUIDが含まれていた場合にはそのデバイスの情報を記憶しておきます。
②	スキャン完了後、検出したBLEデバイスの情報一覧をサーバにプッシュします。
③	60分経過したら、①に戻ってスキャンを再実施します。

## BLEスキャン

```cpp:arduino/src/main.cpp
  BLEDevice::init("NearbyBleDevice");
  pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
//  pBLEScan->setInterval(1349);
//  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);

  pBLEScan->start(SCAN_DURATION_SEC, myScanCompleteCallback);

  Serial.println("setup finished\n");
```

MyAdvertisedDeviceCallbacksのコールバック関数に、検出したすべてのBLEデバイスが引数に渡されて呼び出されます。
myScanCompleteCallbackのコールバック関数は、スキャン終了時に呼び出され、サーバにBLEデバイス情報一覧をプッシュするようにします。

検出したデバイスは以下の部分で、サービスUUIDによりフィルタリングしています。

```cpp:arduino/src/main.cpp
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks
{
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.println(advertisedDevice.toString().c_str());
    if (advertisedDevice.haveServiceUUID() ){
      uint8_t device_type = DEVICE_TYPE_UNKNOWN;
      if(advertisedDevice.isAdvertisingService(sticknfind_discoverServiceUUID))
        device_type = DEVICE_TYPE_STICKNFIND;
      else if(advertisedDevice.isAdvertisingService(immediate_alert_discoverServiceUUID))
        device_type = DEVICE_TYPE_IMMEDIATE_ALERT;
      else if(advertisedDevice.isAdvertisingService(linking_discoverServiceUUID))
        device_type = DEVICE_TYPE_PROJECT_LINKING;
      else if(advertisedDevice.isAdvertisingService(line_things_discoverServiceUUID))
        device_type = DEVICE_TYPE_LINE_THINGS;
      else
        return;

      BLE_DEVICE_ENTRY entry = { getCurrentTime(), true, device_type,advertisedDevice.getAddressType(),
        advertisedDevice.haveRSSI() ? advertisedDevice.getRSSI(): 0, advertisedDevice.haveName() ? advertisedDevice.getName() : "" };
      device_list[advertisedDevice.getAddress().toString()] = entry;

      Serial.print("*** BLE Advertised Device found: ");
      Serial.println(advertisedDevice.toString().c_str());
    }
  }
};
```

## サーバへプッシュ

サーバにプッシュする際に、BLEデバイス情報一覧はJSON形式にします。

```cpp:arduino/src/main.cpp
void myScanCompleteCallback(BLEScanResults results)
{
  Serial.println("counting start");

  long ret;

  time_t current_time = getCurrentTime();
  char address[18];
  json_request.clear();
  sprintf(address, "%02x:%02x:%02x:%02x:%02x:%02x", ble_mac_address[0], ble_mac_address[1], ble_mac_address[2], ble_mac_address[3], ble_mac_address[4], ble_mac_address[5]);
  json_request["ble_mac_address"] = address;
  sprintf(address, "%02x:%02x:%02x:%02x:%02x:%02x", wifi_mac_address[0], wifi_mac_address[1], wifi_mac_address[2], wifi_mac_address[3], wifi_mac_address[4], wifi_mac_address[5]);
  json_request["wifi_mac_address"] = address;
  sprintf(address, "%d.%d.%d.%d", wifi_ip_address[0], wifi_ip_address[1], wifi_ip_address[2], wifi_ip_address[3]);
  json_request["wifi_ip_address"] = address;

  int index = 0;
  for (std::unordered_map<std::string, BLE_DEVICE_ENTRY>::iterator iterator = device_list.begin(); iterator != device_list.end(); iterator++){
    std::pair<std::string, BLE_DEVICE_ENTRY> element = *iterator;
    std::string mac_address = element.first;

    if( device_list[mac_address].connected && device_list[mac_address].updated_at < (current_time - EXPIRE_DURATION_SEC) ){
      device_list[mac_address].updated_at = getCurrentTime();
      device_list[mac_address].connected = false;
    }

    BLE_DEVICE_ENTRY entry = element.second;
    json_request["device_list"][index]["mac_address"] = (char*)mac_address.c_str(),
    json_request["device_list"][index]["name"] = (char*)entry.name.c_str(),
    json_request["device_list"][index]["connected"] = entry.connected;
    json_request["device_list"][index]["device_type"] = entry.device_type;
    json_request["device_list"][index]["address_type"] = entry.address_type;
    json_request["device_list"][index]["rssi"] = entry.rssi;
    json_request["device_list"][index]["updated_at"] = entry.updated_at;
    index++;
  }
  if( serializeJson(json_request, json_buffer, sizeof(json_buffer)) <= 0 ){
    Serial.println("serializeJson error");
    return;
  }

#ifdef _MQTT_ENABLE_
  ret = mqttPublish(json_buffer);
  if( ret != 0 )
    Serial.println("mqtt publish failed");
#endif

#ifdef _HTTP_ENABLE_
  ret = httpPost(HTTP_PUT_URL, json_buffer);
  if( ret != 0 )
    Serial.println("httpPost error");
#endif
}
```


# サーバ側の実装(@Node.js)

Node.jsサーバにて、HTTP Postで送られてきたBLEデバイス情報の一覧を管理します
MQTTで送られてきた場合の処理と、HTTP Post呼び出しで送られてきた場合の両方で動くようにしておきました。

```js:nodejs/api/controllers/bledevice-api/index.js
'use strict';

const HELPER_BASE = process.env.HELPER_BASE || "/opt/";
const Response = require(HELPER_BASE + 'response');

let device_list = new Map();

exports.mqtt_handler = async (event, context) => {
  console.log(event);

  for( let device of event.payload.device_list ){
    device.host_address = event.payload.ble_mac_address;
    device_list.set(device.mac_address, device);
  }
};

exports.handler = async (event, context, callback) => {
	var body = JSON.parse(event.body);
	console.log(body);
  if( event.path == "/bledevice-list" ){
  	return new Response({ list: [...device_list.values()] });
  }else if( event.path == "/bledevice-put" ){
    for( let device of body.device_list ){
      device.host_address = body.ble_mac_address;
      device_list.set(device.mac_address, device);
    }
    return new Response({});
  }
};
```

# BLEデバイスの状態表示ページ(@ブラウザ)

Node.jsで管理されているBLEデバイス一覧は、WebAPI呼び出しで取得できるようにし、Webページから可視化するようにしました。
こんな感じです。大した処理ではないので、ソースコードをご参照ください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/bbd97230-722f-8690-80cb-5a2de50d15a1.png)
 
# 参考

　[obniz-nobleでBLEスキャンをグラフ表示してみた](https://qiita.com/poruruba/items/b6831914dfd6c7f1aa19)
　[ESP32のArduinoでBLEデバイスの出入りをモニタリングする](https://qiita.com/poruruba/items/680d4e6de1b78fe58b99)

以上