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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Moyuru Aizawa
October 06, 2022
Programming
1.1k
0
Share
BLEを使ったアプリを継続的に開発するために
DroidKaigi 2022
BLEを使ったアプリを継続的に開発するために
Ensure maintainability of apps that use BLE
Moyuru Aizawa
October 06, 2022
More Decks by Moyuru Aizawa
See All by Moyuru Aizawa
BLUETOOTH_SCAN and iBeacon
lvla
1
140
graphicsLayer
lvla
0
260
BluetoothDevice.getName()に裏切られた話
lvla
0
400
Jetpack Composeで画像クロップ機能を実装する
lvla
0
1.3k
Jetpack Compose drag gesture and pinch gesture
lvla
1
4.2k
Jetpack Compose Layout API
lvla
1
700
RecyclerView.ItemAnimator
lvla
1
350
RecycledViewPool
lvla
1
270
CameraX
lvla
2
2.5k
Other Decks in Programming
See All in Programming
Reactive ❤️ Loom: A Forbidden Love Story
franz1981
2
220
The free-lunch guide to idea circularity
hollycummins
0
410
Everything Claude Code OSS詳細 — 5層構造の中身と導入方法
targe
0
160
20260320登壇資料
pharct
0
160
Strategy for Finding a Problem for OSS: With Real Examples
kibitan
0
130
The Monolith Strikes Back: Why AI Agents ❤️ Rails Monoliths
serradura
0
100
Goの型安全性で実現する複数プロダクトの権限管理
ishikawa_pro
2
1.4k
AI活用のコスパを最大化する方法
ochtum
0
370
Radical Imagining - LIFT 2025-2027 Policy Agenda
lift1998
0
230
実践ハーネスエンジニアリング #MOSHTech
kajitack
7
5.6k
Symfony + NelmioApiDocBundle を使った スキーマ駆動開発 / Schema Driven Development with NelmioApiDocBundle
okashoi
0
260
Kubernetes上でAgentを動かすための最新動向と押さえるべき概念まとめ
sotamaki0421
2
370
Featured
See All Featured
The untapped power of vector embeddings
frankvandijk
2
1.7k
Claude Code のすすめ
schroneko
67
220k
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
320
Facilitating Awesome Meetings
lara
57
6.8k
Building a Scalable Design System with Sketch
lauravandoore
463
34k
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
170
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
160
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
1
1.5k
The Limits of Empathy - UXLibs8
cassininazir
1
280
Mobile First: as difficult as doing things right
swwweet
225
10k
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
310
Believing is Seeing
oripsolob
1
100
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