Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
BLEを使ったアプリを継続的に開発するために
Search
Moyuru Aizawa
October 06, 2022
Programming
0
890
BLEを使ったアプリを継続的に開発するために
DroidKaigi 2022
BLEを使ったアプリを継続的に開発するために
Ensure maintainability of apps that use BLE
Moyuru Aizawa
October 06, 2022
Tweet
Share
More Decks by Moyuru Aizawa
See All by Moyuru Aizawa
graphicsLayer
lvla
0
160
BluetoothDevice.getName()に裏切られた話
lvla
0
250
Jetpack Composeで画像クロップ機能を実装する
lvla
0
890
Jetpack Compose drag gesture and pinch gesture
lvla
1
3.1k
Jetpack Compose Layout API
lvla
1
600
RecyclerView.ItemAnimator
lvla
1
280
RecycledViewPool
lvla
1
160
CameraX
lvla
2
2.2k
Hack RecyclerView.ItemDecoration
lvla
5
580
Other Decks in Programming
See All in Programming
GitHub Actionsのキャッシュと手を挙げることの大切さとそれに必要なこと
satoshi256kbyte
5
390
What’s New in Compose Multiplatform - A Live Tour (droidcon London 2024)
zsmb
1
330
/←このスケジュール表に立ち向かう フロントエンド開発戦略 / A front-end development strategy to tackle a single-slash schedule.
nrslib
1
590
推し活の ハイトラフィックに立ち向かう Railsとアーキテクチャ - Kaigi on Rails 2024
falcon8823
6
2.1k
Progressive Web Apps für Desktop und Mobile mit Angular (Hands-on)
christianliebel
PRO
0
110
飲食業界向けマルチプロダクトを実現させる開発体制とリアルな現状
hiroya0601
1
390
C#/.NETのこれまでのふりかえり
tomokusaba
1
150
ピラミッド、アイスクリームコーン、SMURF: 自動テストの最適バランスを求めて / Pyramid Ice-Cream-Cone and SMURF
twada
PRO
9
980
開発効率向上のためのリファクタリングの一歩目の選択肢 ~コード分割~ / JJUG CCC 2024 Fall
ryounasso
0
360
Kotlin2でdataクラスの copyメソッドを禁止する/Data class copy function to have the same visibility as constructor
eichisanden
1
120
RailsのPull requestsのレビューの時に私が考えていること
yahonda
5
1.7k
Modern Angular: Renovation for Your Applications
manfredsteyer
PRO
0
200
Featured
See All Featured
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
159
15k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
364
22k
The Pragmatic Product Professional
lauravandoore
31
6.3k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
9
680
Art, The Web, and Tiny UX
lynnandtonic
296
20k
What's in a price? How to price your products and services
michaelherold
243
12k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
31
2.7k
The MySQL Ecosystem @ GitHub 2015
samlambert
250
12k
GraphQLとの向き合い方2022年版
quramy
43
13k
Designing for Performance
lara
604
68k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
504
140k
Rails Girls Zürich Keynote
gr2m
93
13k
Transcript
BLEΛͬͨΞϓϦΛ ܧଓతʹ։ൃ͢ΔͨΊʹ @MoyuruAizawa
Moyuru Aizawa Software Engineer of Catlog, RABO. Previously at Azit,
CyberAgent, and Eureka. Love Metal, Hardcore and EDM. Author of “ΈΜͳͷKotlin”. MoyuruAizawa Rank
None
None
None
‣ BLEʹΑΔσόΠεͱͷ௨৴ํ๏ ‣ GATT ‣ BluetoothGatt ‣ Coroutine, FlowΛͬͨվળ ‣
Tips ͳ͢͜ͱ
‣ BLEΛ͏ΞϓϦ࡞ͬͯͱݴΘΕͨΒԿͱͳ͘͜ͷηογϣϯͷ͜ͱ Λࢥ͍ग़͢ ‣ ͦͯ͠BLEؔ࿈ͷAPIͷ͍ํΛͳΜͱͳ͘ΩϟονΞοϓ͢Δ ‣ ͍ͭ͜CoroutineͱFlow࣮ͬͯͨ͠Μͩ΄ʙΜ🤔ͬͯࢥ͏ ‣ ΅͘ͷ͔Μ͕͍͖͑ͨ͞ΐ͏ͷBLEΞϓϦΛ࣮͢Δ ΰʔϧ
BLE BLE (Bluetooth Low Energy) Bluetooth Low Energy (Bluetooth LE,
BLE) ͱɺແઢPANٕज़Ͱ ͋Δ Bluetooth ͷҰ෦Ͱɺόʔδϣϯ 4.0 ͔ΒՃʹͳͬͨফඅ ిྗͷ௨৴Ϟʔυɻ Ҿ༻: https://ja.wikipedia.org/wiki/Bluetooth_Low_Energy
‣ BluetoothLeScanner ‣ पลͷBluetoothDeviceΛscan͢Δ ‣ BluetoothDevice ‣ deviceͷใ(name, address…)Λऔಘ͢Δ ‣
deviceͱconnectionΛுΔ ‣ BluetoothGatt, BluetoothGattService, BluetoothGattCharacteristic, BluetoothGattDescriptor ‣ GATTΛͬͯdeviceͱ௨৴͢Δ BLEͰѻ͏ओͳAPI
‣ BluetoothLeScanner ‣ पลͷBluetoothDeviceΛscan͢Δ ‣ BluetoothDevice ‣ deviceͷใ(name, address…)Λऔಘ͢Δ ‣
deviceͱconnectionΛுΔ ‣ BluetoothGatt, BluetoothGattService, BluetoothGattCharacteristic, BluetoothGattDescriptor ‣ GATTΛͬͯdeviceͱ௨৴͢Δ BLEͰѻ͏ओͳAPI
GATT? 🤔
GATT (Generic Attribute) GATT Service Characteristic Declaration, Value, Descriptor Characteristic
Declaration, Value, Descriptor
ͳΔ΄ͲΘ͔ΒΜ
None
‣ ؾԹΛऔಘͰ͖Δ ‣ ࣪ΛऔಘͰ͖Δ ‣ ֎ઢͷൃ৴Λ໋ྩͰ͖Δ ‣ લʹड৴ͨ͠֎ઢͷσʔλΛऔಘͰ͖Δ ՍۭͷεϚʔτϦϞίϯΛߟ͑ͯΈΔ e.g.
ImaginaryRemo BLEΛ௨ͯ͠
GATT (Generic Attribute) GATT MeterService Temperature Characteristic 24.6℃ Humidity Characteristic
52% IRService Send Characteristic Received Characteristic e.g. ImaginaryRemo
ࢼ͠ʹImaginaryRemo͔Β ࣪Λऔಘͯ͠ΈΑ͏
‣ ImaginaryRemoͱͷίωΫγϣϯΛுΔ ‣ onConnectionStateChangeͰCONNECTED͕དྷΔͷΛͭ ‣ ίωΫγϣϯ͕ுΕͨΒdiscoverServicesͰServicesΛ୳͢ ‣ onServicesDiscoveredͰServices͕ݟ͔ͭΔͷΛͭ ‣ BluetoothGattΛ௨ͯ͡తͷServiceΛऔಘ͠ɺ
BluetoothGattService͔ΒతCharacteristicΛऔಘ͠ɺૢ࡞͢Δ େ·͔ͳྲྀΕ
imaginaryRemoDevice.connectGatt( context = context, autoConnect = false, callback = object
: BluetoothGattCallback() { … } ) BluetoothDevice#connectGatt
imaginaryRemoDevice.connectGatt( context = context, autoConnect = false, callback = object
: BluetoothGattCallback() { … } ) BluetoothDevice#connectGatt
override fun onConnectionStateChange( gatt: BluetoothGatt, status: Int, newState: Int )
{ if (newState == BluetoothGatt.STATE_CONNECTED) { val isDiscoveryStarted = gatt.discoverServices() if (!isDiscoveryStarted) { // operationͷࣦഊΛϢʔβʔʹ௨ͨ͠ΓͳͲɻ } } BluetoothGattCallback#onConnectionStateChange
override fun onConnectionStateChange( gatt: BluetoothGatt, status: Int, newState: Int )
{ if (newState == BluetoothGatt.STATE_CONNECTED) { val isDiscoveryStarted = gatt.discoverServices() if (!isDiscoveryStarted) { // operationͷࣦഊΛϢʔβʔʹ௨ͨ͠ΓͳͲɻ } } BluetoothGattCallback#onConnectionStateChange
override fun onConnectionStateChange( gatt: BluetoothGatt, status: Int, newState: Int )
{ if (newState == BluetoothGatt.STATE_CONNECTED) { val isDiscoveryStarted = gatt.discoverServices() if (!isDiscoveryStarted) { // operationͷࣦഊΛϢʔβʔʹ௨ͨ͠ΓͳͲɻ } } BluetoothGattCallback#onConnectionStateChange
override fun onServicesDiscovered( gatt: BluetoothGatt, status: Int ) { val
characteristic = gatt.getService(METER_SERVICE_UUID) ?.getCharacteristic(HUMIDITY_CHARACTERISTIC_UUID) requireNotNull(characteristic) val isSuccess = gatt.readCharacteristic(characteristic) if (!isSuccess) { // operationͷࣦഊΛϢʔβʔʹ௨ͨ͠ΓͳͲɻ Ҏ߱εϥΠυͰলུ } } BluetoothGattCallback#onServiceDiscovered
override fun onServicesDiscovered( gatt: BluetoothGatt, status: Int ) { val
characteristic = gatt.getService(METER_SERVICE_UUID) ?.getCharacteristic(HUMIDITY_CHARACTERISTIC_UUID) requireNotNull(characteristic) val isSuccess = gatt.readCharacteristic(characteristic) if (!isSuccess) { // operationͷࣦഊΛϢʔβʔʹ௨ͨ͠ΓͳͲɻ Ҏ߱εϥΠυͰলུ } } BluetoothGattCallback#onServiceDiscovered
override fun onServicesDiscovered( gatt: BluetoothGatt, status: Int ) { val
characteristic = gatt.getService(METER_SERVICE_UUID) ?.getCharacteristic(HUMIDITY_CHARACTERISTIC_UUID) requireNotNull(characteristic) val isSuccess = gatt.readCharacteristic(characteristic) if (!isSuccess) { // operationͷࣦഊΛϢʔβʔʹ௨ͨ͠ΓͳͲɻ Ҏ߱εϥΠυͰলུ } } BluetoothGattCallback#onServiceDiscovered
override fun onServicesDiscovered( gatt: BluetoothGatt, status: Int ) { val
characteristic = gatt.getService(METER_SERVICE_UUID) ?.getCharacteristic(HUMIDITY_CHARACTERISTIC_UUID) requireNotNull(characteristic) val isSuccess = gatt.readCharacteristic(characteristic) if (!isSuccess) { // operationͷࣦഊΛϢʔβʔʹ௨ͨ͠ΓͳͲɻ Ҏ߱εϥΠυͰলུ } } BluetoothGattCallback#onServiceDiscovered
override fun onCharacteristicRead( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int )
{ if ( characteristic.uuid == HUMIDITY_CHARACTERISTIC_UUID ) { characteristic.value // ࣪ GET!! } } BluetoothGattCallback#onCharacteristicRead
override fun onCharacteristicRead( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int )
{ if ( characteristic.uuid == HUMIDITY_CHARACTERISTIC_UUID ) { characteristic.value // ࣪ GET!! } } BluetoothGattCallback#onCharacteristicRead
‣ onConnectionStateChangeͰCONNECTEDΛͭ ‣ discoverServicesΛ࣮ߦ͢Δ ‣ onServicesDiscoveredΛͭ ‣ onServicesDiscoveredҎ߱ ҙͷService, CharacteristicʹΞΫηεՄೳʹͳΔ
‣ READ/WRITEΛߦͬͨΒͦͷྃͨͳ͚ΕͳΒͳ͍ ͓͖͍֮͑ͯͨ͜ͱ
None
ImaginaryRemoʹ͍ͭͯ ͏গ͠ෳࡶͳέʔεΛߟ͑ͯΈΔ
‣ αʔόʔΛܦ༝ͯ͠ɺ֎ग़ઌ͔ΒՈిΛૢ࡞Ͱ͖Δ ‣ Wi-Fiʹଓ͢Δඞཁ͕͋Δɻ ‣ σόΠε͕ΞΫηεՄೳͳWi-FiͷSSIDҰཡΛεϚʔτϑΥϯʹฦ͢ ‣ Wi-FiͷSSIDͱPASSWORDΛσόΠεʹૹ৴͢Δ ྫ͑ɺσόΠεͷॳظઃఆϑϩʔ
Service͓ͦΒ͘͜ͷΑ͏ͳઃܭʹͳΔ ConfigurationService SSID Characteristic Password Characteristic FoundSSID Characteristic RequestSearchSSID Characteristic
override fun onConnectionStateChange( gatt: BluetoothGatt, status: Int, newState: Int )
{ if (newState == BluetoothGatt.STATE_CONNECTED) gatt.discoverServices() } ͱΓ͋͑ͣconnectedΛͭ
override fun onServicesDiscovered( gatt: BluetoothGatt, status: Int ) { val
characteristics = gatt.getService(CONFIGURATION_SERVICE) ?.getCharacteristic(FOUND_SSID_CHARACTERISTIC) requireNotNull(characteristics) gatt.setCharacteristicNotification(characteristics, true) val descriptor = characteristics.getDescriptor(CCCD) descriptor .value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE gatt.writeDescriptor(descriptor) } servicesDiscoveredΛͭ
override fun onServicesDiscovered( gatt: BluetoothGatt, status: Int ) { val
characteristics = gatt.getService(CONFIGURATION_SERVICE) ?.getCharacteristic(FOUND_SSID_CHARACTERISTIC) requireNotNull(characteristics) gatt.setCharacteristicNotification(characteristics, true) val descriptor = characteristics.getDescriptor(CCCD) descriptor .value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE gatt.writeDescriptor(descriptor) } σόΠε͕ݟ͚ͭͨSSIDΛ௨͢ΔΑ͏ઃఆ͢Δ
override fun onServicesDiscovered( gatt: BluetoothGatt, status: Int ) { val
characteristics = gatt.getService(CONFIGURATION_SERVICE) ?.getCharacteristic(FOUND_SSID_CHARACTERISTIC) requireNotNull(characteristics) gatt.setCharacteristicNotification(characteristics, true) val descriptor = characteristics.getDescriptor(CCCD) descriptor .value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE gatt.writeDescriptor(descriptor) } σόΠε͕ݟ͚ͭͨSSIDΛ௨͢ΔΑ͏ઃఆ͢Δ
override fun onServicesDiscovered( gatt: BluetoothGatt, status: Int ) { val
characteristics = gatt.getService(CONFIGURATION_SERVICE) ?.getCharacteristic(FOUND_SSID_CHARACTERISTIC) requireNotNull(characteristics) gatt.setCharacteristicNotification(characteristics, true) val descriptor = characteristics.getDescriptor(CCCD) descriptor .value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE gatt.writeDescriptor(descriptor) } σόΠε͕ݟ͚ͭͨSSIDΛ௨͢ΔΑ͏ઃఆ͢Δ
override fun onServicesDiscovered( gatt: BluetoothGatt, status: Int ) { val
characteristics = gatt.getService(CONFIGURATION_SERVICE) ?.getCharacteristic(FOUND_SSID_CHARACTERISTIC) requireNotNull(characteristics) gatt.setCharacteristicNotification(characteristics, true) val descriptor = characteristics.getDescriptor(CCCD) descriptor .value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE gatt.writeDescriptor(descriptor) } σόΠε͕ݟ͚ͭͨSSIDΛ௨͢ΔΑ͏ઃఆ͢Δ BluetoothSIGͰ༧Ί ఆٛ͞Ε͍ͯΔUUID
override fun onDescriptorWrite( gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int )
{ if (descriptor.uuid == CCCD) { val characteristic = gatt.getService(CONFIGURATION_SERVICE) ?.getCharacteristic(REQUEST_SEARCH_SSID_CHARACTERISTIC) requireNotNull(characteristic) characteristic.value = flag gatt.writeCharacteristic(characteristic) } } ௨ͷઃఆྃΛͭ
override fun onDescriptorWrite( gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int )
{ if (descriptor.uuid == CCCD) { val characteristic = gatt.getService(CONFIGURATION_SERVICE) ?.getCharacteristic(REQUEST_SEARCH_SSID_CHARACTERISTIC) requireNotNull(characteristic) characteristic.value = flag gatt.writeCharacteristic(characteristic) } } SSIDͷݕࡧΛ໋ྩ͢Δ
override fun onDescriptorWrite( gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int )
{ if (descriptor.uuid == CCCD) { val characteristic = gatt.getService(CONFIGURATION_SERVICE) ?.getCharacteristic(REQUEST_SEARCH_SSID_CHARACTERISTIC) requireNotNull(characteristic) characteristic.value = flag gatt.writeCharacteristic(characteristic) } } SSIDͷݕࡧΛ໋ྩ͢Δ
override fun onCharacteristicChanged( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic ) { if
(characteristic.uuid == FOUND_SSID_CHARACTERISTIC) { appendSsidToList(characteristic.value) } } ݟ͔ͭͬͨSSIDͷ௨Λͬͯஞ࣍UIʹө͢Δ
override fun onCharacteristicChanged( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic ) { if
(characteristic.uuid == FOUND_SSID_CHARACTERISTIC) { appendSsidToList(characteristic.value) } } ݟ͔ͭͬͨSSIDͷ௨Λͬͯஞ࣍UIʹө͢Δ
onClick { val characteristic = gatt.getService(CONFIGURATION_SERVICE) ?.getCharacteristic(SSID_CHARACTERISTIC) requireNotNull(characteristic) characteristic.value =
selectedSsid gatt.writeCharacteristic(characteristic) } બ͞ΕͨSSIDΛॻ͖ࠐΉ
onClick { val characteristic = gatt.getService(CONFIGURATION_SERVICE) ?.getCharacteristic(SSID_CHARACTERISTIC) requireNotNull(characteristic) characteristic.value =
selectedSsid gatt.writeCharacteristic(characteristic) } બ͞ΕͨSSIDΛॻ͖ࠐΉ
override fun onCharacteristicWrite( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int )
{ when (characteristic.uuid) { SSID_CHARACTERISTIC -> { val characteristic = gatt.getService(CONFIGURATION_SERVICE) ?.getCharacteristic(PASSWORD_CHARACTERISTIC) requireNotNull(characteristic) characteristic.value = password gatt.writeCharacteristic(characteristic) } PASSWORD_CHARACTERISTIC -> { … } } } SSIDͷॻ͖ࠐΈྃΛͭ
override fun onCharacteristicWrite( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int )
{ when (characteristic.uuid) { SSID_CHARACTERISTIC -> { val characteristic = gatt.getService(CONFIGURATION_SERVICE) ?.getCharacteristic(PASSWORD_CHARACTERISTIC) requireNotNull(characteristic) characteristic.value = password gatt.writeCharacteristic(characteristic) } PASSWORD_CHARACTERISTIC -> { … } } } PASSWORDΛॻ͖ࠐΉ
override fun onCharacteristicWrite( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int )
{ when (characteristic.uuid) { SSID_CHARACTERISTIC -> { … } PASSWORD_CHARACTERISTIC -> { // PASSWORDॻ͖ࠐΈྃ // Զͨͪͷઓ͍·ͩ͜Ε͔Βͩ!!! // ૄ௨֬ೝͳΓͳΜͳΓͷ໋ྩଓ͘… } } } PASSWORDॻ͖ࠐΈྃΛͭ
override fun onCharacteristicWrite( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int )
{ when (characteristic.uuid) { SSID_CHARACTERISTIC -> { … } PASSWORD_CHARACTERISTIC -> { // PASSWORDॻ͖ࠐΈྃ // Զͨͪͷઓ͍·ͩ͜Ε͔Βͩ!!! // ૄ௨֬ೝͳΓͳΜͳΓͷ໋ྩଓ͘… } } } PASSWORDॻ͖ࠐΈྃΛͭ
ແཧʔ!!
Catlog࠷ॳ͜Μͳײ͡ͷίʔυͩͬͨ ͳͥ͜ΜͳίʔϧόοΫࠈʹ…
Catlog࠷ॳ͜Μͳײ͡ͷίʔυͩͬͨ onCharacteristicWriteͱ͔onDescriptorWriteͱ ͔ͷதͰ࣍ͷ໋ྩͬͯΔ͚ͲͳΜͰ…?
Catlog࠷ॳ͜Μͳײ͡ͷίʔυͩͬͨ Ұؾʹ໋ྩ͢ΓΌ͑͑Μ
Catlog࠷ॳ͜Μͳײ͡ͷίʔυͩͬͨ ͳΔ΄ͲͶɺͳΜ໋͔ྩͨ͠Βɺͦͷ݁Ռ͕ฦͬ ͯ͘Δ·Ͱ࣍ͷ໋ྩͰ͖ͳ͍ͷ͔ɻ
Catlog࠷ॳ͜Μͳײ͡ͷίʔυͩͬͨ ͔ͩΒ͜Μͳ͜ͱʹ…
!
CoroutineͱFlowͰ͍͍ ײ͡ʹͰ͖ΔͷͰ?
Catlog࠷ॳ͜Μͳײ͡ͷίʔυͩͬͨ CoroutineͰॻ͚ΔΑ͏ʹ͢Εɺ໋ྩͩͨ͠Β໋ ྩͷྃΛawait͢Δͬͯ͜ͱ͕Ͱ͖ͦ͏ͳ༧ײ
Catlog࠷ॳ͜Μͳײ͡ͷίʔυͩͬͨ “Ұؾʹ໋ྩ͢ΓΌ͑͑Μ” Λίʔυͷݟ্࣮ͨݱͰ͖ͦ͏ͩ
None
BLE x Coroutine x Flow
‣ BLEؔ࿈ͷAPIΛCoroutineFlowͰwrap͢Δ ‣ BluetoothDeviceͱͷ௨৴Λ୲͏BluetoothClientΫϥεΛ࣮͢Δ ‣ ֤eventΛawait͢ΔؔΛఏڙ͢Δ ུ֓
‣ sealed interfaceΛͬͯBluetoothGattCallbackͷ֤ؔʹରԠ͢ Δdata classΛఆٛ͢Δ ‣ લड़ͷdata classΛFlowͰแΜͰฦ֦͢ுؔΛ࣮͢Δ ‣ RxBindingͱ͔CorbindͰΒΕͯΔख๏
BluetoothDevice#connectGatt
sealed interface BluetoothGattEvent { val gatt: BluetoothGatt data class ConnectionStateChange(
override val gatt: BluetoothGatt, val status: Int, val newState: Int ) : BluetoothGattEvent data class ServicesDiscovered( override val gatt: BluetoothGatt, val status: Int ) : BluetoothGattEvent // ུ } BluetoothDevice#connectGatt
fun BluetoothDevice.connectGatt(context: Context, autoConnect: Boolean) = channelFlow { val gatt
= connectGatt(context, autoConnect, object : BluetoothGattCallback(){ override fun onConnectionStateChange( gatt: BluetoothGatt, status: Int, newState: Int ) { trySend(BluetoothGattEvent.ConnectionStateChange(gatt, status, newState)) } override fun onServicesDiscovered( gatt: BluetoothGatt, status: Int ) { trySend(BluetoothGattEvent.ServicesDiscovered(gatt, status)) } // ུ }) } BluetoothDevice#connectGatt
fun BluetoothDevice.connectGatt(context: Context, autoConnect: Boolean) = channelFlow { // ུ
awaitClose { gatt.disconnect() gatt.close() } } BluetoothDevice#connectGatt
class BluetoothClient( private val context: Context, private val device: BluetoothDevice
) { private val gattEvent = MutableStateFlow<BluetoothGattEvent?>(null) private lateinit var gatt: BluetoothGatt // ུ } BluetoothClient
class BluetoothClient( private val context: Context, private val device: BluetoothDevice
) { private val gattEvent = MutableStateFlow<BluetoothGattEvent?>(null) private lateinit var gatt: BluetoothGatt // ུ } BluetoothClient
class BluetoothClient( private val context: Context, private val device: BluetoothDevice
) { private val gattEvent = MutableStateFlow<BluetoothGattEvent?>(null) private lateinit var gatt: BluetoothGatt // ུ } BluetoothClient
fun connect(autoConnect: Boolean, coroutineScope: CoroutineScope) { device.connectGatt(context, autoConnect) .onEach {
gatt = it.gatt gattEvent.value = it } .launchIn(coroutineScope) } BluetoothClient#connect
fun connect(autoConnect: Boolean, coroutineScope: CoroutineScope) { device.connectGatt(context, autoConnect) .onEach {
gatt = it.gatt gattEvent.value = it } .launchIn(coroutineScope) } BluetoothClient#connect
suspend fun awaitConnected() { if (connectionState == BluetoothProfile.STATE_CONNECTED) return gattEvent.first
{ it is BluetoothGattEvent.ConnectionStateChange && it.newState == BluetoothProfile.STATE_CONNECTED } } CONNECTEDΛawaitͰ͖Δ
suspend fun awaitConnected() { if (connectionState == BluetoothProfile.STATE_CONNECTED) return gattEvent.first
{ it is BluetoothGattEvent.ConnectionStateChange && it.newState == BluetoothProfile.STATE_CONNECTED } } CONNECTEDΛawaitͰ͖Δ ͜ͷsuspend functionΛݺΜͩCoroutine ͜ͷ͕݅ຬͨ͞ΕΔ·Ͱதஅ͞ΕΔ
suspend fun awaitServicesDiscovered() { gattEvent .first { it is BluetoothGattEvent.ServicesDiscovered
} } suspend fun awaitCharacteristicWritten(characteristicUuid: UUID) { gattEvent .first { it is BluetoothGattEvent.CharacteristicWrite && it.characteristic.uuid == characteristicUuid } } ֤event·Ͱawait͢Δ͜ͱͰ͖Δ
bleClient.run { connect(false, coroutineScope) awaitConnected() discoverServices() awaitServicesDiscovered() setNotificationEnabled( CONFIGURATION_SERVICE, FOUND_SSID_CHARACTERISTIC,
true ) awaitNotificationEnabled() observeNotification(FOUND_SSID_CHARACTERISTIC) .onEach { appendSsidToList(it.characteristic.value) } .launchIn(coroutineScope) writeCharacteristic( CONFIGURATION_SERVICE, REQUEST_SEARCH_SSID_CHARACTERISTIC, flag ) … σόΠεͱͷ௨৴खॱͱίʔυͷྲྀΕ͕Ұக͢Δ
bleClient.run { connect(false, coroutineScope) awaitConnected() discoverServices() awaitServicesDiscovered() setNotificationEnabled( CONFIGURATION_SERVICE, FOUND_SSID_CHARACTERISTIC,
true ) awaitNotificationEnabled() observeNotification(FOUND_SSID_CHARACTERISTIC) .onEach { appendSsidToList(it.characteristic.value) } .launchIn(coroutineScope) writeCharacteristic( CONFIGURATION_SERVICE, REQUEST_SEARCH_SSID_CHARACTERISTIC, flag ) } σόΠεͱͷ௨৴खॱͱίʔυͷྲྀΕ͕Ұக͢Δ
bleClient.run { connect(false, coroutineScope) awaitConnected() discoverServices() awaitServicesDiscovered() setNotificationEnabled( CONFIGURATION_SERVICE, FOUND_SSID_CHARACTERISTIC,
true ) awaitNotificationEnabled() observeNotification(FOUND_SSID_CHARACTERISTIC) .onEach { appendSsidToList(it.characteristic.value) } .launchIn(coroutineScope) writeCharacteristic( CONFIGURATION_SERVICE, REQUEST_SEARCH_SSID_CHARACTERISTIC, flag ) } σόΠεͱͷ௨৴खॱͱίʔυͷྲྀΕ͕Ұக͢Δ
bleClient.run { connect(false, coroutineScope) awaitConnected() discoverServices() awaitServicesDiscovered() setNotificationEnabled( CONFIGURATION_SERVICE, FOUND_SSID_CHARACTERISTIC,
true ) awaitNotificationEnabled() observeNotification(FOUND_SSID_CHARACTERISTIC) .onEach { appendSsidToList(it.characteristic.value) } .launchIn(coroutineScope) writeCharacteristic( CONFIGURATION_SERVICE, REQUEST_SEARCH_SSID_CHARACTERISTIC, flag ) } σόΠεͱͷ௨৴खॱͱίʔυͷྲྀΕ͕Ұக͢Δ
bleClient.run { connect(false, coroutineScope) awaitConnected() discoverServices() awaitServicesDiscovered() setNotificationEnabled( CONFIGURATION_SERVICE, FOUND_SSID_CHARACTERISTIC,
true ) awaitNotificationEnabled() observeNotification(FOUND_SSID_CHARACTERISTIC) .onEach { appendSsidToList(it.characteristic.value) } .launchIn(coroutineScope) writeCharacteristic( CONFIGURATION_SERVICE, REQUEST_SEARCH_SSID_CHARACTERISTIC, flag ) } σόΠεͱͷ௨৴खॱͱίʔυͷྲྀΕ͕Ұக͢Δ
None
ͦΜͳʹ͘ͳ͍BLE
։ൃऀʹݫ͍͠BLE͞Μ BLE͏Μͱ͢Μͱ͍Θͳ͘ͳͬͨΜ͚ͩͲ…
‣ BluetoothͷAPI͕ΩϝΔࣄ͕͋Δ ‣ ͷઃఆͰBluetoothΛҰ୴OFFʹͯ͠࠶ONʹ… ‣ ͦΕͰ࣏Βͳ͔ͬͨΒΛ࠶ىಈͰ… ‣ ͢Μ·ͤΜ… શʹ͢ΔBLE͞Μ
‣ ϢʔβʔͷखݩͰͲΜͳෆ۩߹͕ى͖͔ͨ ௐࠪͰ͖ΔΑ͏ʹͨ͠΄͏͕ྑͦ͞͏ɻ ‣ Crashlytics ‣ Ϋϥογϡൃੜ࣌ʹͦͷखલͰى͖͍ͯͨϩάݟΕΔ ‣ ΫϥογϡΛ͏ෆ۩߹͋·Γͳ͍ͷͰɺ༗༻Ͱͳ͍ ‣
Bugfender ‣ Logcatͷग़ྗͯ͢ɺ͘͠બఆͨ͠ϩάΛऩूͰ͖Δ ‣ Bugfenderศརͦ͏(ݕ౼த) ෆ໌ͳෆ۩߹ʹରԠ͢ΔͨΊʹ
None
BLEΛͬͨΞϓϦ ͱ͍ͬͯBLEͷϢʔεέʔε༷ʑ
Catlog Series BLE BLE HTTP ઃఆ ϩά ϩ ά BLE
HTTP ઃఆ ϩά
Catlog Series BLE BLE HTTP ઃఆ ϩά ϩ ά BLE
HTTP ઃఆ ϩά
SwitchBot & SwitchBot Hub (ͨͿΜ͜Μͳײ͡) BLE ઃఆ ࡞ಈ໋ྩ HTTP SwitchBotͷ࡞ಈ໋ྩ
SwitchBotͷ࡞ಈ໋ྩ BLE ࡞ಈ໋ྩ HTTP?
SwitchBot & SwitchBot Hub (ͨͿΜ͜Μͳײ͡) BLE ઃఆ ࡞ಈ໋ྩ HTTP SwitchBotͷ࡞ಈ໋ྩ
SwitchBotͷ࡞ಈ໋ྩ BLE ࡞ಈ໋ྩ HTTP?
‣ CatlogσόΠεͷઃఆͷΈBLEΛ͍ͬͯΔ ‣ ઃఆը໘͕ੜ͖͍ͯΔؒͷΈCatlogͱίωΫγϣϯΛு͍ͬͯΕ ͍͍ ‣ ઃఆը໘ͰconnectionΛone-shotͰ͍ࣺͯΒΕΔ ‣ SwitchBotઃఆ͓Αͼ࡞ಈ໋ྩʹBLEΛ͍ͬͯΔ ‣
ΞϓϦ͕ىಈதৗ࣌SwitchBotͱίωΫγϣϯΛுΓɺ͍ͭͰ SwitchBotʹରͯ͠BLEΛ௨໋ͯ͠ྩͰ͖ΔΑ͏ʹ͍ͯ͠Δ(ͱ༧) BLEͷϢʔεέʔεΞϓϦʹΑ༷ͬͯʑ
‣ CatlogFragmentͷlifecycleͷதͰBLEΛ͑Ε͍͍ ‣ SwitchBotɺ1ActivityߏͳΒActivityͷlifecycleͷதɺͦ͏Ͱͳ ͍ͳΒServiceͷதͰBLEΛ͏ඞཁ͕͋Γͦ͏ BLEͷϢʔεέʔεΞϓϦʹΑ༷ͬͯʑ
‣ CatlogFragmentͷlifecycleͷதͰBLEΛ͑Ε͍͍ ‣ SwitchBotɺ1ActivityߏͳΒActivityͷlifecycleͷதɺͦ͏Ͱͳ ͍ͳΒServiceͷதͰBLEΛ͏ඞཁ͕͋Γͦ͏ ‣ ΞϓϦʹΑͬͯBLE·ΘΓͷઃܭมΘΓͦ͏ BLEͷϢʔεέʔεΞϓϦʹΑ༷ͬͯʑ
Ϣʔεέʔεʹ߹Θͤͯ ࠷ߴͷBLEΞϓϦΛ࡞Ζ͏
Thank you