$30 off During Our Annual Pro Sale. View Details »
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
BLEを使ったアプリを継続的に開発するために
Search
Moyuru Aizawa
October 06, 2022
Programming
0
1.1k
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
BLUETOOTH_SCAN and iBeacon
lvla
1
120
graphicsLayer
lvla
0
240
BluetoothDevice.getName()に裏切られた話
lvla
0
380
Jetpack Composeで画像クロップ機能を実装する
lvla
0
1.2k
Jetpack Compose drag gesture and pinch gesture
lvla
1
4k
Jetpack Compose Layout API
lvla
1
680
RecyclerView.ItemAnimator
lvla
1
340
RecycledViewPool
lvla
1
240
CameraX
lvla
2
2.4k
Other Decks in Programming
See All in Programming
ELYZA_Findy AI Engineering Summit登壇資料_AIコーディング時代に「ちゃんと」やること_toB LLMプロダクト開発舞台裏_20251216
elyza
2
270
dotfiles 式年遷宮 令和最新版
masawada
1
790
20251127_ぼっちのための懇親会対策会議
kokamoto01_metaps
2
440
令和最新版Android Studioで化石デバイス向けアプリを作る
arkw
0
410
バックエンドエンジニアによる Amebaブログ K8s 基盤への CronJobの導入・運用経験
sunabig
0
160
AtCoder Conference 2025「LLM時代のAHC」
imjk
2
520
TUIライブラリつくってみた / i-just-make-TUI-library
kazto
1
390
Graviton と Nitro と私
maroon1st
0
110
実は歴史的なアップデートだと思う AWS Interconnect - multicloud
maroon1st
0
210
関数実行の裏側では何が起きているのか?
minop1205
1
710
SwiftUIで本格音ゲー実装してみた
hypebeans
0
420
ローカルLLMを⽤いてコード補完を⾏う VSCode拡張機能を作ってみた
nearme_tech
PRO
0
110
Featured
See All Featured
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.6k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
130k
Designing for humans not robots
tammielis
254
26k
The Invisible Side of Design
smashingmag
302
51k
Principles of Awesome APIs and How to Build Them.
keavy
127
17k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.1k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.1k
How STYLIGHT went responsive
nonsquared
100
6k
How Software Deployment tools have changed in the past 20 years
geshan
0
29k
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
390
Fireside Chat
paigeccino
41
3.7k
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