BLEを使った宝さがしアプリ

BLE(Bluetooth Low Energy)のRSSI(受信信号強度)を使った簡単な宝さがしアプリを作りました。

作るもの

宝さがしを開始すると、BLEのRSSIからスマホとBLE端末の間の近さを推定して画面上に表示します。

単にそれだけです。

f:id:ytoda129:20170813181608p:plain:w200

f:id:ytoda129:20170813182159p:plain:w200

材料

  • Android端末
  • SimpleLink SensorTag (TI)

SimpleLink SensorTag

BLE端末として、TIのSimpleLink SensorTagを使用します。

CC2650STK SimpleLink SensorTag | TIJ.co.jp

SensorTagには温度、湿度、加速度など10種類ほどのセンサーが搭載されておりIoTっぽいことを試すには非常に便利です。 また、ファームウェアが公開されているので、別途、有償のデバッガを購入すれば改造したファームを書き込むことも可能です。

今回は電波強度を使った宝さがしなので、センサー類は一切使いません。

Androidアプリ

Android 6.0用です。

ソースコードはこちら。

GitHub - ytoda129/BleTreasure

権限

AndroidでBLEを使用するには権限が必要です。AndroidManifest.xmlに以下を追加します。

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

Android 6.0以降はACCESS_COARSE_LOCATIONも必要になったようです。

初回アプリ起動時にアクセス権を求めるダイアログを表示するためにrequestPermissionsを使います。

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_CODE = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 位置情報へのアクセス許可を求めるダイアログ表示
        requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, REQUEST_CODE);

        Button startSearch = (Button)findViewById(R.id.start_search);
        startSearch.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(getApplicationContext(), SearchActivity.class);
                startActivity(intent);
            }
        });
    }
}

スキャンと接続

BLE端末がアドバタイズし続けてくれれば、アプリとしてはスキャンでRSSIを取得するだけです。しかし、SensorTagは未接続状態で120sec経過するとアドバタイズをやめてしまいます。

http://processors.wiki.ti.com/index.php/CC2650_SensorTag_User’s_Guide#When_disconnected

そこで、スキャンでSensorTagを検出したら、即時接続して、BluetoothGattreadRemoteRssi()メソッドでRSSI値を取得します。

public class SearchActivity extends AppCompatActivity {

    private static final long READ_RSSI_PERIOD = 1000; // msec

    private BluetoothAdapter mAdapter;
    private BluetoothGatt mBluetoothGatt;
    private TextView mCloseness;
    private TextView mRssiText;
    private Handler mHandler = new Handler();

    private BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() {

        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { ... }

        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                setClosenessText(rssi);
            }
        }
    };

    private BluetoothAdapter.LeScanCallback mScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
            // SensorTag以外のBLE端末は無視
            if (device.getName() == null || !device.getName().contains("SensorTag")) {
                return;
            }

            if (mBluetoothGatt == null) {
                // SensorTagは120sec経過するとアドバタイズをやめてしまうので接続しておく
                mBluetoothGatt = device.connectGatt(getApplicationContext(), false, mBluetoothGattCallback);

                // READ_RSSI_PERIOD(=1sec)間隔でRSSIを取得する
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (mBluetoothGatt == null) {
                            return;
                        }
                        mBluetoothGatt.readRemoteRssi();
                        mHandler.postDelayed(this, READ_RSSI_PERIOD);
                    }
                }, READ_RSSI_PERIOD);
            }
        }
    };

    @Override
    protected void onResume() {
        // 画面表示時のみスキャン実行
        super.onResume();
        mAdapter.startLeScan(mScanCallback);
    }

    // 省略
}

近さの判定

計算でBLE端末との推定距離を求めることも可能なようですが、今回はどれくらい近いかがわかればよいだけなので、単純にRSSIの値から近さを4段階に分けます。

RSSIのレンジは実際に動作させてみてそれっぽく決定しました。

おわりに

非常に単純な宝さがしアプリを作ってみました。

今は、スキャンして最初に見つかったSensorTagの近さしか表示できませんが、

  • 複数のSensorTagを同時に扱えるようにする
  • SensorTagのセンサー値(温度とか光とか)を宝さがしのヒントとしてつかう

とかできるようになると、もっと面白いかもしれません。