BLE温湿度計をいじってみた

SwitchBotの温湿度計のデータをESP32で拾ってmrubyでsqlite3に入れてみました。

何かマイコンを使って作ろうかとも思ったのですが、部品代くらいで買える製品があったので、購入することにしました。

![sbmeter (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/e2b981d7-c936-f177-9486-d6f45d26b0dd.png)

```c
#include <BLEDevice.h>

//#define USE_CALLBACK

BLEScan* g_pBLEScan;

BLEUUID serviceDataUUID = BLEUUID("0000fd3d-0000-1000-8000-00805f9b34fb");

char bleData[6];
int battery;
float temperature;
int humidity;
int haveData;

void GetData(BLEAdvertisedDevice advertisedDevice)
{
        int serviceDataSize;
        const char* serviceData;
        if(advertisedDevice.haveServiceData()) {
                if(!advertisedDevice.getServiceDataUUID().equals(serviceDataUUID)) return;

                serviceDataSize = advertisedDevice.getServiceData().size();
                serviceData = advertisedDevice.getServiceData().c_str();

                int i;
//              char strbuf[4];
                for (i = 0;i < serviceDataSize; ++i) {
                        bleData[i] = serviceData[i];
//                      sprintf(strbuf, "%02x ", serviceData[i]);
//                      Serial.print(strbuf);
                }

                battery = bleData[2] & 0x7f;
                temperature = (float)(bleData[3] & 0x0f) / 10 + (bleData[4] & 0x7f);
                if (bleData[4] & 0x80 == 0)
                        temperature = -temperature;
                humidity = bleData[5] & 0x7f;
                haveData = true;
        }
}

#ifdef USE_CALLBACK
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
        void onResult(BLEAdvertisedDevice advertisedDevice) {
                GetData(advertisedDevice);
        }
};
#endif

void setup() {
        Serial.begin(115200);  // ログ出力準備

        BLEDevice::init("");   // BLEデバイス初期化
        g_pBLEScan = BLEDevice::getScan();  // Scanオブジェクト取得
#ifdef USE_CALLBACK
        g_pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(), true);
#endif
        g_pBLEScan->setActiveScan(true);   // パッシブスキャンに設定

}

void loop() {
        int count;

        /* Advertised interval is 2 sec */
        haveData = false;
        BLEScanResults res = g_pBLEScan->start(5);
#ifndef USE_CALLBACK
        count = res.getCount();
        int i;
        for (i = 0; i < count; i++) {
                GetData(res.getDevice(i));
        }
#endif
        g_pBLEScan->clearResults();

        if (haveData) {
                Serial.print(temperature);
                Serial.print(",");
                Serial.print(humidity);
                Serial.print(",");
                Serial.println(battery);
        }

        delay(60000);
}
```

FreeBSD/mipsなモジュールのmrubyでシリアルでデータを受け取ってhttpで投げます。

```ruby
begin

port = "/dev/cuau0"

ser = SerialPort.new(port, 115200, 8, 1, 0)
ser.flow_control=0

str = ""

loop do
  ch = ser.getc
  if ch == "\n"
    str.chomp!
    arr = str.split(",")
    req = "/cgi/wsupdate2.cgi?field1=" + arr[0] + "&field2=" + arr[1] + "&field3=" + arr[2]
    SimpleHttp.new("http", "10.0.1.18", 80).request("GET", req, {'User-Agent' => "test-agent"})
    str = ""
  else
    str += ch.to_s
  end
end

rescue

sleep 10

retry

end
```

メンテなどでhttpサーバにアクセスできないと例外になり終了してしまうので、リトライするようにしました。

おうちモニターで受け取ったデータをsqlite3のdbに突っ込みます。

```ruby
#!/usr/local/bin/mruby

print "Content-type: text/html\n\n"

params = {}
que = ENV['QUERY_STRING']
if que.to_s != ""
para = que.to_s.split('&')
para.each do |p|
  a = p.split('=')
  params[a[0]] = a[1]
end

if params.has_key?("field1") && params.has_key?("field2") && params.has_key?("field3")

t = params["field1"].to_f
h = params["field2"].to_f
b = params["field3"].to_f

db = SQLite3::Database.new('/tmp/ouchi.db')
tc =  db.execute("select count(*) from sqlite_master where type='table' and name='ws2'").next[0]
if tc == 0
  db.execute_batch("create table ws2(date default CURRENT_DATE, time default CURRENT_TIME, temp REAL, humi REAL, batt REAL)")
end
db.execute_batch("insert into ws2(temp, humi, batt) values(?, ?, ?)", t.to_s, h.to_s, b.to_s)

print "OK"

else

print "NG"

end
else

print "NG"

end
```

dbは現在の時間のデータを入れておくものとそれを集計して1時間ごとのデータを入れておくテーブルの二段構成にしてあります。

ESP32でWIFIのライブラリを追加するとバイナリが大きくなりflashに入りませんでした。

SwitchBotの温湿度計は表示機能があるものと、防水でないものがあります。ほぼ同じ値段でした。

BLEのコネクトせずにAdvertisedでデータが取得できるという仕組みはこの製品を購入するまで知りませんでした。

なにも設定しなくてもAdvertisedは飛んできます。

Advertisedでデータが飛んでくるということは、自分以外の人もこのデータを見ることができますが、温度や湿度を知られても問題になることはないでしょう。

と思ったのですが、このデータに連動してエアコンのスイッチを入れるような仕組みがあると、偽装してデータを送り込んで勝手にスイッチを入れることができます。

とりあえず問題なく動いているようです。

![filename.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/df123a8a-8542-ce57-8ba3-ff402dab21e2.png)

BLE温湿度計は2FでESP32は1Fに置いて試したところ、受信できなくなりました。木造家屋なのですが、結構電波が弱まるようです。Androdのアプリで確認したところ-80dBm以下になると受信できないようです。ESP32の置く場所によっては受信できたりします。モルトの箱で30センチくらい浮かせたらどうにか受信できるようになりました。結構難しいです。

![BLE1F2F.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104066/c2d3c9dc-58fe-96f0-3f1a-33e74d108b27.png)

~~10%くらい受けられてないですが、9割方受信できているので十分実用になると思います。~~ まったく受信できなくなり、不安定なのでPIC32MX+USB ドングルで作り直しました。