2017年2月12日日曜日

RaspberryPi + noble で BLE デバイスとの距離を計測してみた

概要

BLE デバイスのアドバタイジングパケットに含まれる「RSSI (受信電波強度)」と「TXPower (送信電波強度)」を使って BLE デバイスと RaspberryPi との距離を算出してみました

環境

BLE デバイスは何でも OK です
今回は手持ちの BLESerial2 を使ってみました

RaspberryPi 側セットアップ

Bluetooth ドングルを RaspberryPi に接続してください
今回は Nodejs 製のライブラリ noble を使って Bluetooth ドングルの制御を行います

noble のインストール

  • sudo apt-get install bluetooth bluez libbluetooth-dev libudev-dev
  • npm install noble

制御用スクリプト

BLE デバイスをスキャンして、スキャンできた BLE デバイスから必要な情報 (RSSI, TXPower) を取得し距離を算出します

  • vim scan.js
var noble = require('noble');
var serviceUUIDs = ['bd011f227d3c0db6e44155873d44ef40']; // BLESerial2
var allowDuplicates = false;

noble.on('stateChange', function(state) {
  if (state === 'poweredOn') {
    noble.startScanning(serviceUUIDs, allowDuplicates);
  } else {
    noble.stopScanning();
  }
});

var n = 2 
noble.on('discover', function(peripheral) {
  console.log(peripheral.advertisement.localName);
  peripheral.on('rssiUpdate', function(rssi) {
    if (peripheral.advertisement.txPowerLevel !== undefined) {
      // var tx = peripheral.advertisement.txPowerLevel;
      var tx = -67;
      var distance = Math.pow(10.0, (tx - rssi) / (10 * n));
      console.log('tx:' + tx + ', rssi: ' + rssi + ', distance: ' + distance);
    }   
  }); 
  peripheral.on('connect', function() {
    setInterval(function() {
      peripheral.updateRssi();
    }, 1000);
  }); 
  peripheral.on('disconnect', function() {
    console.log('on -> disconnect');
  }); 
  peripheral.connect();
});

処理はそれほど難しくなくスキャンして見つかったデバイスに接続して 1 秒おきに RSSI の情報を取得するリクエストを投げて、そのコールバック内で距離の計算をしています

距離を算出する公式は以下の通りです

var distance = Math.pow(10.0, (tx - rssi) / (10 * n));

n はとりあえず 2.0 で設定しています
このパラメータは各自の環境に合わせて変更する必要がありそうです

また、今回は 1 つの BLE デバイスに絞りたかったので serviceUUIDs に BLESerial2 の UUID を指定しています
ので、スキャンの対象が 1 台のデバイスになっています

動作確認

Bluetooth ドングルが RUNNING になっていることを確認したら早速動作させてみましょう
BLESerial2 側の電源を ON にしてください

  • sudo node scan.js

起動すると以下のようなログが表示されると思います

tx:-67, rssi: -74, distance: 2.2387211385683394
tx:-67, rssi: -75, distance: 2.51188643150958
tx:-67, rssi: -75, distance: 2.51188643150958
tx:-67, rssi: -71, distance: 1.5848931924611136
tx:-67, rssi: -66, distance: 0.8912509381337456
tx:-67, rssi: -70, distance: 1.4125375446227544
tx:-67, rssi: -60, distance: 0.44668359215096315
tx:-67, rssi: -59, distance: 0.3981071705534972
tx:-67, rssi: -59, distance: 0.3981071705534972
tx:-67, rssi: -59, distance: 0.3981071705534972
tx:-67, rssi: -59, distance: 0.3981071705534972
tx:-67, rssi: -56, distance: 0.28183829312644537
tx:-67, rssi: -58, distance: 0.35481338923357547

単位はメートルになります

実際に BLE デバイスを動かしながら距離を測定して精度を確かめてみた感じだと、RSSI が安定してから実際に距離を測れば、誤差は数十cm ほどに収まる感じでした
が、やはり RSSI の値がとても不安定で動きながらだと急に RSSI の値が大きくなったり小さくなったりするので、距離の精度も悪くなりました

考察とポイント

今回の一番のポイントは「tx」の値でした
これを初め TXPowerLevel という値を使っていたのですが、どうやらこれがおかしな値になっていたようです
本来この「tx」は何かというと

BLE デバイスから 1m 離れた地点での電波強度 [dbm]

を設定しなければいけません

実際に peripheral.advertisement.txPowerLevel で値を取得してみると 4 という固定値が返って来ました
しかし、実際に 1m 離れて電波強度を測ってみるとだいたい -65 から -70 くらいになっておりこれが原因でおかしな距離を計算していました

もう少し詳しく調査してみるとこんな表も見つかったので TXPowerLevel にあった電波強度を設定する感じなのかなと思いました

で、いろいろ考えた結果 RSSI を使って距離を求める場合、一番良さそうな方法は

各環境ごとに RSSI と距離の対応表を作成し、RSSI の値が安定したら距離を算出する

という方法が一番いいかなと感じました

やはり一番の問題は RSSI が不安定だということです
RSSI は建物や他の電波の影響をめちゃくちゃ受けるので基本不安定だと思っていたほうがいいです
なので、まずは測定したい地点で安定した RSSI が算出できるような仕組みを作るといいと思います
( 例えば 10 回 RSSI を測定した結果、ノイズ等を除去してその平均を RSSI とする、など)

あとは距離に関してですが、これも計算式だと環境によって、やはりばらつきが出てしまうと思います
なので、RSSI を測定しながら実際メジャーなどを使ってその距離を測定し RSSI と距離の対応表を作るのが一番いいんじゃないかなと思います
( おい、じゃあそもそも今回の計算式とかいらないじゃん!って話にはなってしまうのですが、、、今回やってみた感じだとそれが一番いいかなと)

もちろん n のパラメータなどを各環境にあった値にチューニングすることで精度を上げるようにしてもいいと思います

あとは精度をあげる方法として RSSI の値が整数ではなく浮動小数で測定できるようになるともっといい精度になると思います ( 現状の仕組みだとできないと思いますが )

最後に

BLE デバイスを使って距離を求める方法を紹介しました
何とか頑張れば使えるレベルにまではなると思います
が、かなり精度の高いオペレーションやリアルタイムでの測定などは難しいという印象です

実際に使う場合には BLE 単体ではなく Wifi や超音波センサなど他のセンサと絡めて測定を行うことにより精度のいい測定ができるんじゃないかなと思います

この辺の技術 (位置測定や距離測定) は探してみると結構いろいろ出てくるのでおもしろい分野かなと思います

参考サイト

14 件のコメント:

  1. 私も、ラズパイでBLEデバイス(半径10Mぐらいのデバイス)のアドレスとRSSiを取得出来ないか、考えておりますので、この記事は、大変参考になりました。
    そこで一つ質問なのですが、最近のラズパイはBluetoothを搭載しておりますが、これでも動作しますでしょうか?

    返信削除
  2. 手元に検証できる環境がないので何ともですが公式のスペックを見る限りは RPi3 であれば Bluetooth 4.2 に対応しているので本記事のスクリプトも動作すると思います。https://www.raspberrypi.org/products/raspberry-pi-3-model-b-plus/
    もちろんライブラリなどのインストールは必要ですが

    返信削除
  3. ありがとうございます。
    記事をよく読ませていただきましたが!!
    やはり、接続状態にあるBLE端末でないと無理でしょうか?

    返信削除
  4. discover しているだけなので peripheral との接続は不要です
    rssi は scan 時に取得できるのでそれを使って距離を測定しています

    返信削除
  5. すいません!!
    記事中の
    npmでnobleをインストールします。
    を実行すると下記のようなエラーが帰ります!!
    ~ $ npm install noble
    bash: npm: コマンドが見つかりません(パスが通っていないのでしょうか?)
    なぜでしょうか??教えてもらえますか!!

    返信削除
  6. npm コマンドは nodejs に含まれているので、nodejs をインストールしてください
    お手持ちの OS が何か不明なのですが例えば Raspbian Stretch 9.4 であれば apt コマンドでインストールできます

    sudo apt -y install nodejs npm

    これでバージョン 8.11/1.4.21 あたりがインストールできると思います
    もしくは公式からソースを持ってきて最新版をインストールしても良いかなと思います
    https://nodejs.org/ja/

    返信削除
  7. ありがとうございます!!
    一歩前にいきました。(~ $ npm install nobleは完了しましたが)
    sudo node scan.jsを実行しても無反応です!!
    ちなみに、スキャンの対象が不特定の場合、制御スプリクトは、
    var serviceUUIDs = [];
    ファイル名は、scan.js
    でいいですか?

    返信削除
  8. すいません!!
    無反応ではなく
    noble: unknown peripheral undefined RSSI update!
    でした。

    返信削除
  9. ファイル名は好きな名前で OK です
    serviceUUIDs は公式のドキュメントを見ると空でも大丈夫なようです
    https://github.com/noble/noble/wiki/Getting-started

    「noble: unknown peripheral undefined RSSI update!」に関してはこの辺りが怪しいかなと思います
    https://github.com/noble/noble/issues/413

    返信削除
  10. 前回のご指導の下、https://github.com/noble/noble/issues/413を参考に、再インストールしてみましたが、だめでした!!
    結果は、noble: unknown peripheral undefined RSSI update!
    ラズパイOS(使っているのはNOOBS_v3_0_1です。)が影響しているのでしょうか?
    記事に書いてあるバージョンで試験したほうがいいのでしょうか?

    返信削除
  11. 単純に BLE デバイスではない Bluetooth Classic なデバイスを検知しているだけではないでしょうか
    RSSI が定義されていないため undefined が表示されているような気がします
    BLESerial2 はもう手に入りませんが SensorTag などの BLE 対応のビーコンデバイスで試すとうまくできる気がします
    あとは node を使わなくてもできそうなので node を使い分ないのも手だと思います

    返信削除
  12. ありがとうございます!!
    ちまみに・・・
    私が持っているポケモンGOで接続しようとすると
    sudo node scan.js
    Pokemon GO Plus
    on -> disconnect
    noble: unknown peripheral undefined RSSI update!
    です。
    そこで、$ sudo hcitool lescanを実行すると
    LE Scan ...
    **:**:**:**:**:55 Pokemon GO Plus
    **:**:**:**:A2:55 (unknown)
    こうなります!!

    返信削除
  13. Go Plus であれば BLE デバイスですね
    hcitool で取得できているのであれば RPi や Bluetooth レシーバ側ではないと思うので noble が原因かなと思います
    noble を諦めないのであれば、あとは serviceUUIDs に Go Plus の UUID を指定してみるとかでしょうか
    python が書けるのであれば pybluez を使ってはいかがでしょうか
    サンプルも充実しているので
    https://github.com/pybluez/pybluez/blob/master/examples/advanced/inquiry-with-rssi.py

    返信削除
  14. 参考になります!
    残念ながら、以下のエラーが発生。
    can not find bluetooth-hci-socket

    返信削除