SwitchBotの温湿度計のデータをESP32で拾ってmrubyでsqlite3に入れてみました。
何かマイコンを使って作ろうかとも思ったのですが、部品代くらいで買える製品があったので、購入することにしました。
#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で投げます。
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に突っ込みます。
#!/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でデータが飛んでくるということは、自分以外の人もこのデータを見ることができますが、温度や湿度を知られても問題になることはないでしょう。
と思ったのですが、このデータに連動してエアコンのスイッチを入れるような仕組みがあると、偽装してデータを送り込んで勝手にスイッチを入れることができます。
とりあえず問題なく動いているようです。
BLE温湿度計は2FでESP32は1Fに置いて試したところ、受信できなくなりました。木造家屋なのですが、結構電波が弱まるようです。Androdのアプリで確認したところ-80dBm以下になると受信できないようです。ESP32の置く場所によっては受信できたりします。モルトの箱で30センチくらい浮かせたらどうにか受信できるようになりました。結構難しいです。
10%くらい受けられてないですが、9割方受信できているので十分実用になると思います。 まったく受信できなくなり、不安定なのでPIC32MX+USB ドングルで作り直しました。