Chapter 14

SwitchBotのBLE温湿度計からデータを取得する

takeru
takeru
2021.08.30に更新

SwitchBot温湿度計

この温湿度計はBLEでデータを垂れ流してくれるのでESP32でデータ取得ができるのです。

Arduino(ESP32)ならこれ。

https://qiita.com/takeru@github/items/f42381e8482c3bf484e7

Obnizでも。

https://elchika.com/article/2b8d1f38-c6bb-4a64-ab61-db25b222d69d/

esp-nimble-cppのインストール

BLEのライブラリにNimBLEをつかいます。
Arduinoの場合は https://github.com/h2zero/NimBLE-Arduino なのですが、
Arduinoではないので、https://github.com/h2zero/esp-nimble-cpp をインストールします。

git submodule add git@github.com:h2zero/esp-nimble-cpp.git ./components/esp-nimble-cpp

componentsにいれるだけで使えるようになりました。便利。

コード

switchbot_meter.h
#pragma once
#include <sys/types.h>

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

typedef void (* switchbot_meter_callback_t)(float temperature, int8_t humidity, int8_t battery);
void init_ble(switchbot_meter_callback_t cb);
void ble_task(void * parameter);

#ifdef __cplusplus
}
#endif /* __cplusplus */
switchbot_meter.cpp
#include <sys/types.h>
#include <NimBLEDevice.h>
#include "switchbot_meter.h"

static const char *TAG_BLE    = "ble";

BLEUUID serviceUUID     = BLEUUID("cba20d00-224d-11e6-9fb8-0002a5d5c51b");
BLEUUID serviceDataUUID = BLEUUID("00000d00-0000-1000-8000-00805f9b34fb");
//BLEAddress addr01     = BLEAddress("11:22:33:44:55:66");

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
    switchbot_meter_callback_t callback = NULL;

    public:
    MyAdvertisedDeviceCallbacks(switchbot_meter_callback_t cb){
        callback = cb;
    }

    void onResult(BLEAdvertisedDevice* advertisedDevice) {
        //ESP_LOGI(TAG_BLE, "Advertised Device: %s", advertisedDevice->toString().c_str());
	/*
        if(advertisedDevice->getAddress().equals(addr01)){
            // OK
        }else{
            return;
        }
	*/
        if(!advertisedDevice->haveServiceUUID()) return;
        if(!advertisedDevice->getServiceUUID().equals(serviceUUID)) return;
        //ESP_LOGI(TAG_BLE, "SwitchBot Meter: %s", advertisedDevice->toString().c_str());

        if(!advertisedDevice->haveServiceData()) return;
        //printf("ServiceDataUUID=%s\n", advertisedDevice->getServiceDataUUID().toString().c_str());
        std::string s = advertisedDevice->getServiceData();
        //printf("ServiceData len=%d [", s.length());
        //for(int i=0; i<s.length(); i++){
        //    printf("%02X ", s.c_str()[i]);
        //}
        //printf("]\n");
        if(!advertisedDevice->getServiceDataUUID().equals(serviceDataUUID)) return;

        const char* servicedata = s.c_str();
        int8_t battery = servicedata[2] & 0b01111111;
        bool isTemperatureAboveFreezing = servicedata[4] & 0b10000000;
        float temperature = ( servicedata[3] & 0b00001111 ) / 10.0 + ( servicedata[4] & 0b01111111 );
        if(!isTemperatureAboveFreezing){
            temperature = -temperature;
        }
        int8_t humidity = servicedata[5] & 0b01111111;

        // bool isEncrypted            = ( servicedata[0] & 0b10000000 ) >> 7;
        // bool isDualStateMode        = ( servicedata[1] & 0b10000000 ) >> 7;
        // bool isStatusOff            = ( servicedata[1] & 0b01000000 ) >> 6;
        // bool isTemperatureHighAlert = ( servicedata[3] & 0b10000000 ) >> 7;
        // bool isTemperatureLowAlert  = ( servicedata[3] & 0b01000000 ) >> 6;
        // bool isHumidityHighAlert    = ( servicedata[3] & 0b00100000 ) >> 5;
        // bool isHumidityLowAlert     = ( servicedata[3] & 0b00010000 ) >> 4;
        // bool isTemperatureUnitF     = ( servicedata[5] & 0b10000000 ) >> 7;

        // ESP_LOGI(TAG_BLE, "----");
        // ESP_LOGI(TAG_BLE, "address:     %s",   advertisedDevice->getAddress().toString().c_str());
        // ESP_LOGI(TAG_BLE, "battery:     %d",   battery);
        // ESP_LOGI(TAG_BLE, "temperature: %.1f", temperature);
        // ESP_LOGI(TAG_BLE, "humidity:    %d",   humidity);
        // ESP_LOGI(TAG_BLE, "isEncrypted:            %d", isEncrypted);
        // ESP_LOGI(TAG_BLE, "isDualStateMode:        %d", isDualStateMode);
        // ESP_LOGI(TAG_BLE, "isStatusOff:            %d", isStatusOff);
        // ESP_LOGI(TAG_BLE, "isTemperatureHighAlert: %d", isTemperatureHighAlert);
        // ESP_LOGI(TAG_BLE, "isTemperatureLowAlert:  %d", isTemperatureLowAlert);
        // ESP_LOGI(TAG_BLE, "isHumidityHighAlert:    %d", isHumidityHighAlert);
        // ESP_LOGI(TAG_BLE, "isHumidityLowAlert:     %d", isHumidityLowAlert);
        // ESP_LOGI(TAG_BLE, "isTemperatureUnitF:     %d", isTemperatureUnitF);
        // ESP_LOGI(TAG_BLE, "----");

        ESP_LOGI(TAG_BLE, "SwitchBotMeter: address=%s battery=%d temperature=%.1f humidity=%d",
            advertisedDevice->getAddress().toString().c_str(),
            battery,
            temperature,
            humidity
        );

        if(callback){
            (*callback)(temperature, humidity, battery);
        }
    }
};

BLEScan* pBLEScan = NULL;
int scanDurationSeconds = 5;

void init_ble(switchbot_meter_callback_t cb){
    BLEDevice::init("");
    pBLEScan = BLEDevice::getScan(); //create new scan
    pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(cb));
    pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
    pBLEScan->setInterval(100);
    pBLEScan->setWindow(99);  // less or equal setInterval value
}

void scanCompleteCB(NimBLEScanResults results){
    ESP_LOGI(TAG_BLE, "scan complate. count=%d", results.getCount());
    pBLEScan->clearResults();
}

void ble_task(void * parameter){
    for(;;) {
        bool ok = pBLEScan->start(scanDurationSeconds, scanCompleteCB, false);
        if(ok){
            ESP_LOGI(TAG_BLE, "scan begin");
        }
        while(pBLEScan->isScanning()){
            //ESP_LOGI(TAG_BLE, "scanning...");
            vTaskDelay(1000/portTICK_PERIOD_MS);
        }
        ESP_LOGI(TAG_BLE, "scan end");
        vTaskDelay(1000/portTICK_PERIOD_MS);
    }
    vTaskDelete(NULL);
}

コールバック関数を指定してinit_bleを呼び、その後ble_taskを起動します。
addr01でターゲットにするMACアドレスを指定します。