Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
BLEを使ったアプリを継続的に開発するために
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Moyuru Aizawa
October 06, 2022
Programming
1.1k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
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
160
graphicsLayer
lvla
0
320
BluetoothDevice.getName()に裏切られた話
lvla
0
440
Jetpack Composeで画像クロップ機能を実装する
lvla
0
1.3k
Jetpack Compose drag gesture and pinch gesture
lvla
1
4.4k
Jetpack Compose Layout API
lvla
1
730
RecyclerView.ItemAnimator
lvla
1
390
RecycledViewPool
lvla
1
310
CameraX
lvla
2
2.6k
Other Decks in Programming
See All in Programming
Long Living Tokens, Die, Die, Die! (or: The One Ring is Banned in Modern)
sylph01
0
330
AI主導のプログラミング学習アプリを作ってみた
econanringo
0
180
Herb in Rails 8.2: Your ERB views, now HTML-aware @ Rails World 2026, Austin, Texas
marcoroth
0
220
[2026-09-26]空論ジェネリックプロセス~テスト資産とAIで紡ぐ、再現可能なパフォーマンスチューニングの話~
tosite
0
270
なぜCTOを降りてFDEを選んだのか?〜なぜプロダクト企業がFDEで顧客の現場に踏み込むのか〜
gonta
1
220
GraphQL の onError と Nullability のこれから
quramy
0
120
コードレビューのボトルネックを"する側"と"される側"の両面から解消する
yub0n
2
1.6k
ChromeでローカルLLM
ryu1013
0
150
AWS CDKのカスタムリソースでContinuum(旧Security Agent)を実装した話 | Implementing Continuum (formerly Security Agent) via AWS CDK Custom Resource
akihisaikeda
2
150
AWSに止められる覚悟してますか?
morizo_1984
2
540
IBM Bob でAI駆動開発『QC-Playbook』
ichirokano2
0
120
作って学ぶSpring BootのAuto Configuration / JJUG Night Seminar 2026-10-19
rshindo
0
190
Featured
See All Featured
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
141
35k
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.5k
Writing Fast Ruby
sferik
630
63k
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
360
Code Review Best Practice
trishagee
74
20k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
23k
First, design no harm
axbom
PRO
2
1.3k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.6k
For a Future-Friendly Web
brad_frost
184
10k
Future Trends and Review - Lecture 12 - Web Technologies (1019888BNR)
signer
PRO
0
3.8k
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
590
Become a Pro
speakerdeck
PRO
31
6.3k
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