はじめに
お仕事で特定のIoTデバイスとBLE接続して、WiFiの情報やらPushトークンやらをIoTデバイスに書き込むっていうアプリを作った。
BLE周りで辛かった、、、とってもハマった、、、のでつらつらとハマったこととか書いていこうかと思う。
IoTデバイス側も並行して制作してたからよりハマったっていうのもあるかもしれないけど。デバイス側の仕様をあまり教えてくれなかった感もあるし、、、。
UUIDを指定してDeviceをScanしてもHitしない
以下の感じでFilterをかけてScanしても何故かDeviceを見つけてくれなかった。
val scanFilter = ScanFilter.Builder().setServiceUuid("uuid").build()
DeviceNameでFilterをかけると見つけてくれたので、DeviceNameで対処した。
val scanFilter = ScanFilter.Builder().setDeviceName("deviceName").build()
GATT_ERROR(133)に悩まされる
Deviceに接続時、書き込み時、読み込み時、切断時にDeviceから133エラーが返されることが頻発した。エラーの定数名を見ても何が起こってるかさっぱり。。。
IoTデバイス側の開発中のROMで確認していたからとか思ってたけど、リリース版でも頻度は減ったけど、起きるは起きた。とりあえず半ば諦めつついろいろ調べて以下の対処とした。あってるかどうかはわからないw
接続時
リトライするようにした。
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
Timber.d("onConnectionStateChange#newState = $newState status = $status")
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices()
} else if (status == 133 && newState == BluetoothProfile.STATE_DISCONNECTED) {
bleDevice?.also{
bleGatt = bleDevice?.connectGatt(context, false, this)
return
}
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
bluetoothDisconnect()
}
}
書き込み時、読み取り時
Stack Overflow先生にディレイかけると良いとという書き込みがあったのでディレイかけるようにいした。このディレイ2019のDroidKaigiのスライドで思いやりタイマーって名前ついてたw
/**
* Characteristic書き込み
*/
fun writeCharacteristicNotification(uuidString: String, writeData: String?) {
hander.postDelayed({
val characteristic = getCharacteristic(uuidString)
characteristic?.setValue(writeData)
val res = bleGatt?.writeCharacteristic(characteristic)
}, 500)
}
/**
* Characteristic読み込み
*/
fun readCharacteristicNotification(uuidString: String) {
hander.postDelayed({
val res = bleGatt?.readCharacteristic(getCharacteristic(uuidString))
}, 500)
}
fun getCharacteristic(uuidString: String) = bleGatt?.getService(UUID.fromString(BuildConfig.DEVICE_SERVICE_UUID)
)?.getCharacteristic(UUID.fromString(uuidString))
切断→接続をすばやく行うと接続できない
IoTデバイスに書き込んだ内容が不揮発領域に書き込まれるタイミングがBLEを切断したときというクソ仕様(失礼)だったので、情報書き込んだら切断→接続してステータスを確認ってことをしなきゃいけなかった。
で、接続を切っても何故かonConnectionStateChange()で切断通知が来てくれず、、、しっかりと切断ができたかが見れない。端末内部的に切断できてない状態で接続要求を出すとBusyのエラーが帰ってくるとかあった。
なので切断後も思いやりタイマー(笑)を入れて接続するようにした。
Handler().postDelayed({
bluetoothConnect()
}, 5000)
※5秒っていう数字の根拠はないw
書き込んだ情報がIoTデバイス側で受信できてないと言われる
val characteristic = getCharacteristic(uuidString)
characteristic?.setValue(writeData)
val res = bleGatt?.writeCharacteristic(characteristic)
Timber.d("writeCharacteristicNotification writeData = $writeData res = $res ($uuidString)")
resはtrueで書き込み自体は成功しているように見えた。それなのに何も受信してないとIoTデバイス担当者に言われ詰んだ、、、と思っていた。そんなところiOS側の開発者がこんな記事を見つけてくれる。
Androidの仕様的に1度に書き込める情報量はデフォルト20バイトくらいだったらしい。MTUを書き換える処理を入れた。こいつも133エラーを返してくることがあるので思いやりタイマー付き。
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
hander.postDelayed({
val res = bleGatt?.requestMtu(500)
Timber.d("onServicesDiscovered requestMtu res = $res")
}, 500)
}
}
500バイト超えたらどうするかはわかってない(マテ
今回の作ったアプリの要件的に500バイト超えることは絶対にないから、とりあえずこれでって感じで。。。
さいごに
BLEを使ったアプリの開発をまたやりたい?って言われたら答えはNOですw
OS 4系自体に作ってた人から見たら今はとっても繋がりやすくなったよ!とか言われるとは思うんでしょうけど、ハマりポイントが多すぎて辛すぎましたw