Slide 1

Slide 1 text

BLE Advertiseは RxJavaで 何かが変わるのか Yoshihiro Wada a.k.a. @e10dokup 2017/10/25 @ potatotips #44

Slide 2

Slide 2 text

1 Yoshihiro Wada  CyberAgent, Inc. / Adtech Studio (新卒) @e10dokup

Slide 3

Slide 3 text

1 Yoshihiro Wada  CyberAgent, Inc. / Adtech Studio (新卒) @e10dokup

Slide 4

Slide 4 text

2 今日の話題 BLE ってご存知ですか

Slide 5

Slide 5 text

3 BLE とは BLE (Bluetooth Low Energy) Bluetooth 4.0 の規格の一部。 超低電力でボタン電池で最大 1 年くらい駆動可能。

Slide 6

Slide 6 text

4 そのなかでも BLE Advertise 未接続の BLE デバイス (ペリフェラル) が スマホ等の接続可能なデバイス (セントラル) に対し 自身の存在を伝えるためにパケットの発信をする機能 BLE デバイス ワタシハココヨー

Slide 7

Slide 7 text

5 BLE Advertise BLE Advertise 時に最大 31byte のパケットを送信できる Advertising Packet この中に規格に合わせて Service UUID 等の情報を埋めていく iBeacon や EddyStone は Advertising Packet の規格

Slide 8

Slide 8 text

6 BLE Advertise を Android で受け取る BluetoothLeScanner クラスを使う private val scanner: BluetoothLeScanner private var scanCallback: ScanCallback? = null init { // Kotlinならrunとか使ってこんな風に書けそう scanner = context.run { val manager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager return@run manager.adapter.bluetoothLeScanner } }

Slide 9

Slide 9 text

7 BLE Advertise を Android で受け取る Scan に対してオプションが設定できる ScanFilter : スキャン対象を Advertising Packet の中身で フィルタリングする ScanSetting : スキャン間隔や検出個数、 遅延スキャンや スキャン時の callback をどうトリガするなどを設定する startScan(ScanCallack, ScanFilter, ScanSetting) で Scan 開始 stopScan(ScanCallack) で Scan 停止

Slide 10

Slide 10 text

7 BLE Advertise を Android で受け取る Scan に対してオプションが設定できる ScanFilter : スキャン対象を Advertising Packet の中身で フィルタリングする ScanSetting : スキャン間隔や検出個数、 遅延スキャンや スキャン時の callback をどうトリガするなどを設定する startScan(ScanCallack, ScanFilter, ScanSetting) で Scan 開始 stopScan(ScanCallack) で Scan 停止

Slide 11

Slide 11 text

8 ScanCallback onScanResult(int callbackType, ScanResult result) 一台の BLE Advertise を Scan できた際に呼ばれる ScanFilter を設定していればそれにあったものが呼ばれる onScanFailed(int errorCode) BLE Advertise の Scan を実行できなかった際に呼ばれる すでに Scan を開始しているとか onBatchScanResults(List results) 遅延 Scan を実行している際、 その遅延の間に Scan できた デバイスのリストが通知される

Slide 12

Slide 12 text

9 BLE Beacon デバイスで稀によくあること iBeacon の Major/Minor で Beacon の位置を定義してみたり EddyStone で URL を伝えるようにしてみたり Advertising Packet で何かしら伝えようとしてくる 独自規格でセンサの状態をパケットに設定してきたり

Slide 13

Slide 13 text

9 BLE Beacon デバイスで稀によくあること iBeacon の Major/Minor で Beacon の位置を定義してみたり EddyStone で URL を伝えるようにしてみたり Advertising Packet で何かしら伝えようとしてくる 独自規格でセンサの状態をパケットに設定してきたり いい感じに受け取りたい

Slide 14

Slide 14 text

10 RxJava を使ってみる filter を使って重複回避や本当に必要な分だけ取得したり map を使って Advertising Packet から欲しいデータを得たり Scan 中は拾えた BLE デバイスがひたすら通知される Observable に扱えれば得られたデータを Web API に流したり とかできそう Observable に扱えるはず

Slide 15

Slide 15 text

11 実際に実装してみた 取得された Advertise から iBeacon とわかるものをフィルタ フィルタされた Advertise から iBeacon の Major/Minor、 DeviceName だけを取り出して通知 呼び出し元に 「Scan した iBeacon のデータのみ」 を通知したい Observable.create で Scan 開始から callback までを包む Scan 停止時に disposable を破棄する

Slide 16

Slide 16 text

Scan 開始を Observable.create で包む // 単純にObservableを返すメソッドに変更 fun start(): Observable { return Observable.create { subscriber -> scanCallback = object: ScanCallback() { override fun onScanFailed(errorCode: Int) { subscriber.onError(Throwable(ERROR_MESSAGE)) } override fun onScanResult(callbackType: Int, result: ScanResult?) { super.onScanResult(callbackType, result) result ?: return subscriber.onNext(result) } // 他は省略 } scanner.startScan(scanCallback) } }

Slide 17

Slide 17 text

iBeacon とわかるものをフィルタ fun check(result: ScanResult): Boolean { // 取得したScan結果からAdvertising Packetを取得 val packet = result.scanRecord.bytes // iBeacon固定値の4byte目の0x02、5byte目の0x15があればtrue return packet[4] == 0x02.toByte() && packet[5] == 0x15.toByte() } scanDisposable = detector.start() .filter { check(it) } .map { transform(it) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ // onNext Log.d("MainActivity", String(it.major)) }, { // onError Log.d("MainActivity", it.message) })

Slide 18

Slide 18 text

必要なデータだけを取得 fun transform(result: ScanResult): IBeaconValue { val name = result.device.name // デバイス名を取得 val packet = result.scanRecord.bytes val major = byteArrayOf(packet[22], packet[23]) val minor = byteArrayOf(packet[24], packet[25]) return IBeaconValue(name, major, minor) } scanDisposable = detector.start() .filter { check(it) } .map { transform(it) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ // onNext Log.d("MainActivity", String(it.major)) }, { // onError Log.d("MainActivity", it.message) })

Slide 19

Slide 19 text

Scan 開始と終了 // Scan開始 fun startScan() { scanDisposable = scanner.start() .filter { check(it) } .map { transform(it) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ // onNext Log.d("MainActivity", String(it.major)) }, { // onError Log.d("MainActivity", it.message) }) } // Scan終了 fun stopScan() { scanner.stop() // スキャン自体の終了 scanDisposable?.dispose() // 明示的にDisposeさせる }

Slide 20

Slide 20 text

16 まとめ Scan 自体は Callback 地獄と言う訳ではないので嬉しさは…? おそらく RxJava で書けて嬉しいのは接続した後の処理から BLE Advertise Scan は RxJava でそれっぽく書ける気がする BLE Advertise だけなら闇はほぼないのでお気軽に試せる ただ iBeacon の発信機は微妙に高い (製品だと 4000 円くらい) Android 5.0 以降なら Advertise の発信もできる いわゆる 「すれ違い」 アプリみたいなものはパパっとできる