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
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
93
graphicsLayer
lvla
0
210
BluetoothDevice.getName()に裏切られた話
lvla
0
350
Jetpack Composeで画像クロップ機能を実装する
lvla
0
1.2k
Jetpack Compose drag gesture and pinch gesture
lvla
1
3.8k
Jetpack Compose Layout API
lvla
1
660
RecyclerView.ItemAnimator
lvla
1
320
RecycledViewPool
lvla
1
220
CameraX
lvla
2
2.4k
Other Decks in Programming
See All in Programming
Workers を定期実行する方法は一つじゃない
rokuosan
0
130
GPUを計算資源として使おう!
primenumber
1
290
Advanced Micro Frontends: Multi Version/ Framework Scenarios
manfredsteyer
PRO
0
110
코딩 에이전트 체크리스트: Claude Code ver.
nacyot
0
1k
Gemini CLI のはじめ方
ttnyt8701
1
110
Claude Code で Astro blog を Pages から Workers へ移行してみた
codehex
0
160
Comparing decimals in Swift Testing
417_72ki
0
110
NEWT Backend Evolution
xpromx
1
160
Gemini CLIの"強み"を知る! Gemini CLIとClaude Codeを比較してみた!
kotahisafuru
2
200
[DevinMeetupTokyo2025] コード書かせないDevinの使い方
takumiyoshikawa
2
220
プロダクトという一杯を作る - プロダクトチームが味の責任を持つまでの煮込み奮闘記
hiliteeternal
0
290
Claude Code派?Gemini CLI派? みんなで比較LT会!_20250716
junholee
1
750
Featured
See All Featured
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
29
9.6k
The Pragmatic Product Professional
lauravandoore
35
6.8k
Building a Modern Day E-commerce SEO Strategy
aleyda
42
7.4k
Stop Working from a Prison Cell
hatefulcrawdad
271
21k
Documentation Writing (for coders)
carmenintech
72
4.9k
Site-Speed That Sticks
csswizardry
10
720
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
47
9.6k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
53k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
21
1.4k
RailsConf 2023
tenderlove
30
1.2k
Why You Should Never Use an ORM
jnunemaker
PRO
58
9.5k
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