まとめ
BLEを使ってRSSIを取得したいけど、よく分からないという方向けの記事です。
RSSIをリアルタイムに取得するためには、対象のBLEデバイスを検出するたびにBLE.stopScan();
とBLE.scan();
で再スキャンする必要がある。
やりたかったこと
M5Stack Core2を2台用いて、デバイス同士が近づいたか遠くなったかを検出したかった。
そのため、片方のデバイスからBLEでアドバタイズを発信し続け、そのRSSIを監視することで実現しようと考えた。
単純に以下のコードで確認していたが、RSSIの更新が分オーダーとなってしまい、とてもリアルタイムな更新とは言えなかった。
セントラル側の処理
#include <M5Core2.h>
#include <ArduinoBLE.h>
void setup() {
M5.begin();
M5.Lcd.setTextSize(2);
M5.Lcd.clear();
M5.Lcd.setCursor(0, 0);
M5.Lcd.println("RSSI Monitor");
BLE.begin();
BLE.scanForUuid("180D"); // 180DのUUIDをスキャンする
}
void loop() {
// BLEデバイスを検出したらRSSIを取得し、LCDに表示する
BLEDevice peripheral = BLE.available();
if (peripheral) {
int rssi = peripheral.rssi();
M5.Lcd.clear();
M5.Lcd.setCursor(0, 0);
M5.Lcd.printf("RSSI: %d dBm\n", rssi);
M5.Lcd.printf("Device: %s", peripheral.address().c_str());
}
}
原因
BLE.scanForUuid("180D");
の仕様上、対象のデバイスを見つけたらその情報をキューに入れて処理が完了する仕様のよう。(BLE.scan();
も同じ仕様っぽい)
そのため、最新の情報に更新したい場合は一度スキャンを止めて、再スキャンする必要がある。
ちなみに以下が公式リファレンスだが、この仕様に関しては明記しておらず、そこまで詳しく書いてなかった。
修正後のソースコード
ペリフェラル側処理
シンプルに20ms間隔でアドバタイズを送り続ける処理。
今回はRSSIが知りたいだけなので、データ部は指定しない。
#include <M5Core2.h>
#include <ArduinoBLE.h>
#include "esp_bt.h" // ESP32 Arduinoコアに含まれる
void setup() {
// M5Stack Core2の初期化
M5.begin();
// BLEの初期化
BLE.begin();
// LCDの初期設定
M5.Lcd.setTextSize(2);
M5.Lcd.clear();
M5.Lcd.setCursor(0, 0);
// 距離が離れていても検出するようにBLE出力パワーを最大に設定
// ※効果があるのか謎
if (esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, ESP_PWR_LVL_P9) == ESP_OK) {
M5.Lcd.println("BLE Power Up");
}
// BLE設定
BLE.setLocalName("M5Stack-Core2");
BLE.setAdvertisedServiceUuid("180D"); // UUIDの指定。任意でOK(他とIDが被らないように)
BLE.setAdvertisingInterval(32); // アドバタイズ間隔(0.625ms単位)
BLE.advertise();
// デバイスアドレスをLCDに表示
String address = BLE.address();
M5.Lcd.printf("Device Address:\n%s", address.c_str());
}
void loop() {
// アドバタイズのみのため処理なし
delay(10);
}
セントラル側処理
こちらもシンプルに、アドバタイズをスキャンしてデバイスを見つけたらRSSIを取得するのみの処理。
デバイスを見つけたタイミングで、再スキャンする処理を入れている。
近くにBLEデバイスが多かったのでUUIDを指定してスキャンしているが、指定せずにスキャンするにはBLE.scan();
を用いる。
#include <M5Core2.h>
#include <ArduinoBLE.h>
void setup() {
M5.begin();
M5.Lcd.setTextSize(2);
M5.Lcd.clear();
M5.Lcd.setCursor(0, 0);
M5.Lcd.println("RSSI Monitor");
BLE.begin();
BLE.scanForUuid("180D"); // 必要に応じてBLE.scan()に変更可能
}
void loop() {
BLEDevice peripheral = BLE.available();
if (peripheral) {
// 追加箇所:再スキャンを開始(ESP32 BLEライブラリの仕様により必要)
BLE.stopScan();
BLE.scanForUuid("180D");
int rssi = peripheral.rssi();
M5.Lcd.clear();
M5.Lcd.setCursor(0, 0);
M5.Lcd.printf("RSSI: %d dBm\n", rssi);
M5.Lcd.printf("Device: %s", peripheral.address().c_str());
}
}
まとめ
以上の方法により、約40ms間隔でRSSI情報を更新できた。
LCDへの表示処理を消せばもう少し早くなるかもしれない。
スキャンの仕様に関して少し調べても出てこなかったため、備忘録として投稿する。