環境構築
balenaを使ってしまっていますが、debian環境なので install_packages
はそのまま apt install
等で置き換えてください。
またNode.jsでBLEを扱うためのnobleというライブラリを今回は使いますが、本家よりも @abandonware/noble
の方がメンテされているので、今回はこちらを使います。
FROM balenalib/%%BALENA_MACHINE_NAME%%-node:12-buster-build as build
RUN install_packages bluetooth bluez libbluetooth-dev libudev-dev
RUN npm install @abandonware/noble rxjs
FROM balenalib/%%BALENA_MACHINE_NAME%%-node:12-buster-run
WORKDIR /work
COPY --from=build ./node_modules ./node_modules
COPY app.js .
CMD ["node", "app.js"]
ソースコード
SwitchBotの温湿度計のMACアドレスを予め取得しておいてください。わからない場合は、まずfilterせずにScanしてみて、それっぽいのをしぼります(filterの記述はソースコードのコメントを参照)。
まず冒頭のRxを準備するソースコード。
const noble = require('@abandonware/noble');
const SWITCHBOT_MAC_ADDR = process.env.SWITCHBOT_MAC_ADDR || 'aa:bb:cc:dd:ee:ff'; // コロンあり小文字で
const {
fromEvent,
forkJoin,
interval,
empty,
} = require('rxjs');
const {
filter,
map,
mergeMap,
take,
} = require('rxjs/operators');
// BLEのデバイスがスタンバイ状態になるのを待つストリーム
const blePowerdOn = fromEvent(
noble,
'stateChange'
).pipe(
filter((state) => {
console.log(state);
return state === 'poweredOn';
}),
map(() => console.log('[ble]poweredOn')),
take(1)
);
// ペリフェラルを発見したらペリフェラルを流すストリーム
// MACアドレスが不明な場合はfilterの記述を削除
// bleDevices = fromEvent(noble, 'discover')
bleDevices = fromEvent(noble, 'discover').pipe(
filter((peripheral) => {
return peripheral.address === SWITCHBOT_MAC_ADDR;
})
);
アドバタイズのデータから温度と湿度とバッテリー残量を取得する処理。
参考: https://qiita.com/c60evaporator/items/7c3156a6bbb7c6c59052#%E3%82%BB%E3%83%B3%E3%82%B5%E5%80%A4%E5%8F%96%E5%BE%97%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90
const decodeSensorData = (data) => {
const batt = data[2] & 0b01111111;
const isTemperatureAboveFreezing = data[4] & 0b10000000;
let temp = (data[3] & 0b00001111) / 10 + (data[4] & 0b01111111);
if (isTemperatureAboveFreezing < 0) temp = -temp;
const humid = data[5] & 0b01111111;
return {
'SensorType': 'SwitchBot',
'Temperature': temp,
'Humidity': humid,
'BatteryVoltage': batt
};
};
最後に実際に動かす部分。
forkJoin({
ble: blePowerdOn
}).pipe(
mergeMap(() => {
console.log('[ble]scanning...');
noble.startScanning([]);
// 10秒ごとにスキャンする(この中でやる必要はなさそう)
interval(10 * 1000).subscribe(() => {
noble.stopScanning();
noble.startScanning([]);
});
return bleDevices;
}),
).subscribe((peripheral) => {
// 取得したセンサー情報を出力する
console.log(decodeSensorData(peripheral.advertisement.serviceData[0].data));
});
出力
このように値を出力できました。あとは、好きなクラウドにアップロードしたり、画面に出力したりして楽しんでください。