$30 off During Our Annual Pro Sale. View Details »

BLEを使ったアプリを継続的に開発するために

 BLEを使ったアプリを継続的に開発するために

DroidKaigi 2022
BLEを使ったアプリを継続的に開発するために
Ensure maintainability of apps that use BLE

Moyuru Aizawa

October 06, 2022
Tweet

More Decks by Moyuru Aizawa

Other Decks in Programming

Transcript

  1. BLEΛ࢖ͬͨΞϓϦΛ ܧଓతʹ։ൃ͢ΔͨΊʹ @MoyuruAizawa

  2. Moyuru Aizawa Software Engineer of Catlog, RABO. Previously at Azit,

    CyberAgent, and Eureka. Love Metal, Hardcore and EDM. Author of “ΈΜͳͷKotlin”. MoyuruAizawa Rank
  3. None
  4. None
  5. None
  6. ‣ BLEʹΑΔσόΠεͱͷ௨৴ํ๏ ‣ GATT ‣ BluetoothGatt ‣ Coroutine, FlowΛ࢖ͬͨվળ ‣

    Tips ͸ͳ͢͜ͱ
  7. ‣ BLEΛ࢖͏ΞϓϦ࡞ͬͯͱݴΘΕͨΒԿͱͳ͘͜ͷηογϣϯͷ͜ͱ Λࢥ͍ग़͢ ‣ ͦͯ͠BLEؔ࿈ͷAPIͷ࢖͍ํΛͳΜͱͳ͘ΩϟονΞοϓ͢Δ ‣ ͍ͭ͜͸CoroutineͱFlow࢖࣮ͬͯ૷ͨ͠Μͩ΄ʙΜ🤔ͬͯࢥ͏ ‣ ΅͘ͷ͔Μ͕͍͖͑ͨ͞ΐ͏ͷBLEΞϓϦΛ࣮૷͢Δ ΰʔϧ

  8. BLE BLE (Bluetooth Low Energy) Bluetooth Low Energy (Bluetooth LE,

    BLE) ͱ͸ɺແઢPANٕज़Ͱ ͋Δ Bluetooth ͷҰ෦Ͱɺόʔδϣϯ 4.0 ͔Β௥Ճʹͳͬͨ௿ফඅ ిྗͷ௨৴Ϟʔυɻ Ҿ༻: https://ja.wikipedia.org/wiki/Bluetooth_Low_Energy
  9. ‣ BluetoothLeScanner ‣ पลͷBluetoothDeviceΛscan͢Δ ‣ BluetoothDevice ‣ deviceͷ৘ใ(name, address…)Λऔಘ͢Δ ‣

    deviceͱconnectionΛுΔ ‣ BluetoothGatt, BluetoothGattService, BluetoothGattCharacteristic, BluetoothGattDescriptor ‣ GATTΛ࢖ͬͯdeviceͱ௨৴͢Δ BLEͰѻ͏ओͳAPI
  10. ‣ BluetoothLeScanner ‣ पลͷBluetoothDeviceΛscan͢Δ ‣ BluetoothDevice ‣ deviceͷ৘ใ(name, address…)Λऔಘ͢Δ ‣

    deviceͱconnectionΛுΔ ‣ BluetoothGatt, BluetoothGattService, BluetoothGattCharacteristic, BluetoothGattDescriptor ‣ GATTΛ࢖ͬͯdeviceͱ௨৴͢Δ BLEͰѻ͏ओͳAPI
  11. GATT? 🤔

  12. GATT (Generic Attribute) GATT Service Characteristic Declaration, Value, Descriptor Characteristic

    Declaration, Value, Descriptor
  13. ͳΔ΄ͲΘ͔ΒΜ

  14. None
  15. ‣ ؾԹΛऔಘͰ͖Δ ‣ ࣪౓ΛऔಘͰ͖Δ ‣ ੺֎ઢͷൃ৴Λ໋ྩͰ͖Δ ‣ ௚લʹड৴ͨ͠੺֎ઢͷσʔλΛऔಘͰ͖Δ ՍۭͷεϚʔτϦϞίϯΛߟ͑ͯΈΔ e.g.

    ImaginaryRemo BLEΛ௨ͯ͠
  16. GATT (Generic Attribute) GATT MeterService Temperature Characteristic 24.6℃ Humidity Characteristic

    52% IRService Send Characteristic Received Characteristic e.g. ImaginaryRemo
  17. ࢼ͠ʹImaginaryRemo͔Β ࣪౓Λऔಘͯ͠ΈΑ͏

  18. ‣ ImaginaryRemoͱͷίωΫγϣϯΛுΔ ‣ onConnectionStateChangeͰCONNECTED͕དྷΔͷΛ଴ͭ ‣ ίωΫγϣϯ͕ுΕͨΒdiscoverServicesͰServicesΛ୳͢ ‣ onServicesDiscoveredͰServices͕ݟ͔ͭΔͷΛ଴ͭ ‣ BluetoothGattΛ௨ͯ͡໨తͷServiceΛऔಘ͠ɺ

    BluetoothGattService͔Β໨తCharacteristicΛऔಘ͠ɺૢ࡞͢Δ େ·͔ͳྲྀΕ
  19. imaginaryRemoDevice.connectGatt( context = context, autoConnect = false, callback = object

    : BluetoothGattCallback() { … } ) BluetoothDevice#connectGatt
  20. imaginaryRemoDevice.connectGatt( context = context, autoConnect = false, callback = object

    : BluetoothGattCallback() { … } ) BluetoothDevice#connectGatt
  21. override fun onConnectionStateChange( gatt: BluetoothGatt, status: Int, newState: Int )

    { if (newState == BluetoothGatt.STATE_CONNECTED) { val isDiscoveryStarted = gatt.discoverServices() if (!isDiscoveryStarted) { // operationͷࣦഊΛϢʔβʔʹ௨஌ͨ͠ΓͳͲɻ } } BluetoothGattCallback#onConnectionStateChange
  22. override fun onConnectionStateChange( gatt: BluetoothGatt, status: Int, newState: Int )

    { if (newState == BluetoothGatt.STATE_CONNECTED) { val isDiscoveryStarted = gatt.discoverServices() if (!isDiscoveryStarted) { // operationͷࣦഊΛϢʔβʔʹ௨஌ͨ͠ΓͳͲɻ } } BluetoothGattCallback#onConnectionStateChange
  23. override fun onConnectionStateChange( gatt: BluetoothGatt, status: Int, newState: Int )

    { if (newState == BluetoothGatt.STATE_CONNECTED) { val isDiscoveryStarted = gatt.discoverServices() if (!isDiscoveryStarted) { // operationͷࣦഊΛϢʔβʔʹ௨஌ͨ͠ΓͳͲɻ } } BluetoothGattCallback#onConnectionStateChange
  24. 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
  25. 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
  26. 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
  27. 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
  28. override fun onCharacteristicRead( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int )

    { if ( characteristic.uuid == HUMIDITY_CHARACTERISTIC_UUID ) { characteristic.value // ࣪౓ GET!! } } BluetoothGattCallback#onCharacteristicRead
  29. override fun onCharacteristicRead( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int )

    { if ( characteristic.uuid == HUMIDITY_CHARACTERISTIC_UUID ) { characteristic.value // ࣪౓ GET!! } } BluetoothGattCallback#onCharacteristicRead
  30. ‣ onConnectionStateChangeͰCONNECTEDΛ଴ͭ ‣ discoverServicesΛ࣮ߦ͢Δ ‣ onServicesDiscoveredΛ଴ͭ ‣ onServicesDiscoveredҎ߱ ೚ҙͷService, CharacteristicʹΞΫηεՄೳʹͳΔ

    ‣ READ/WRITEΛߦͬͨΒͦͷ׬ྃ΋଴ͨͳ͚Ε͹ͳΒͳ͍ ͓͖͍֮͑ͯͨ͜ͱ
  31. None
  32. ImaginaryRemoʹ͍ͭͯ ΋͏গ͠ෳࡶͳέʔεΛߟ͑ͯΈΔ

  33. ‣ αʔόʔΛܦ༝ͯ͠ɺ֎ग़ઌ͔ΒՈిΛૢ࡞Ͱ͖Δ ‣ Wi-Fiʹ઀ଓ͢Δඞཁ͕͋Δɻ ‣ σόΠε͕ΞΫηεՄೳͳWi-FiͷSSIDҰཡΛεϚʔτϑΥϯʹฦ͢ ‣ Wi-FiͷSSIDͱPASSWORDΛσόΠεʹૹ৴͢Δ ྫ͑͹ɺσόΠεͷॳظઃఆϑϩʔ

  34. Service͸͓ͦΒ͘͜ͷΑ͏ͳઃܭʹͳΔ ConfigurationService SSID Characteristic Password Characteristic FoundSSID Characteristic RequestSearchSSID Characteristic

  35. override fun onConnectionStateChange( gatt: BluetoothGatt, status: Int, newState: Int )

    { if (newState == BluetoothGatt.STATE_CONNECTED) gatt.discoverServices() } ͱΓ͋͑ͣconnectedΛ଴ͭ
  36. 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Λ଴ͭ
  37. 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Λ௨஌͢ΔΑ͏ઃఆ͢Δ
  38. 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Λ௨஌͢ΔΑ͏ઃఆ͢Δ
  39. 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Λ௨஌͢ΔΑ͏ઃఆ͢Δ
  40. 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
  41. 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) } } ௨஌ͷઃఆ׬ྃΛ଴ͭ
  42. 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ͷݕࡧΛ໋ྩ͢Δ
  43. 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ͷݕࡧΛ໋ྩ͢Δ
  44. override fun onCharacteristicChanged( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic ) { if

    (characteristic.uuid == FOUND_SSID_CHARACTERISTIC) { appendSsidToList(characteristic.value) } } ݟ͔ͭͬͨSSIDͷ௨஌Λ଴ͬͯஞ࣍UIʹ൓ө͢Δ
  45. override fun onCharacteristicChanged( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic ) { if

    (characteristic.uuid == FOUND_SSID_CHARACTERISTIC) { appendSsidToList(characteristic.value) } } ݟ͔ͭͬͨSSIDͷ௨஌Λ଴ͬͯஞ࣍UIʹ൓ө͢Δ
  46. onClick { val characteristic = gatt.getService(CONFIGURATION_SERVICE) ?.getCharacteristic(SSID_CHARACTERISTIC) requireNotNull(characteristic) characteristic.value =

    selectedSsid gatt.writeCharacteristic(characteristic) } બ୒͞ΕͨSSIDΛॻ͖ࠐΉ
  47. onClick { val characteristic = gatt.getService(CONFIGURATION_SERVICE) ?.getCharacteristic(SSID_CHARACTERISTIC) requireNotNull(characteristic) characteristic.value =

    selectedSsid gatt.writeCharacteristic(characteristic) } બ୒͞ΕͨSSIDΛॻ͖ࠐΉ
  48. 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ͷॻ͖ࠐΈ׬ྃΛ଴ͭ
  49. 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Λॻ͖ࠐΉ
  50. override fun onCharacteristicWrite( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int )

    { when (characteristic.uuid) { SSID_CHARACTERISTIC -> { … } PASSWORD_CHARACTERISTIC -> { // PASSWORDॻ͖ࠐΈ׬ྃ // Զͨͪͷઓ͍͸·ͩ͜Ε͔Βͩ!!! // ૄ௨֬ೝͳΓͳΜͳΓͷ໋ྩ΁ଓ͘… } } } PASSWORDॻ͖ࠐΈ׬ྃΛ଴ͭ
  51. override fun onCharacteristicWrite( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int )

    { when (characteristic.uuid) { SSID_CHARACTERISTIC -> { … } PASSWORD_CHARACTERISTIC -> { // PASSWORDॻ͖ࠐΈ׬ྃ // Զͨͪͷઓ͍͸·ͩ͜Ε͔Βͩ!!! // ૄ௨֬ೝͳΓͳΜͳΓͷ໋ྩ΁ଓ͘… } } } PASSWORDॻ͖ࠐΈ׬ྃΛ଴ͭ
  52. ແཧʔ!!

  53. Catlog΋࠷ॳ͸͜Μͳײ͡ͷίʔυͩͬͨ ͳͥ͜ΜͳίʔϧόοΫ஍ࠈʹ…

  54. Catlog΋࠷ॳ͸͜Μͳײ͡ͷίʔυͩͬͨ onCharacteristicWriteͱ͔onDescriptorWriteͱ ͔ͷதͰ࣍ͷ໋ྩ΍ͬͯΔ͚ͲͳΜͰ…?

  55. Catlog΋࠷ॳ͸͜Μͳײ͡ͷίʔυͩͬͨ Ұؾʹ໋ྩ͢ΓΌ͑͑΍Μ

  56. Catlog΋࠷ॳ͸͜Μͳײ͡ͷίʔυͩͬͨ ͳΔ΄ͲͶɺͳΜ໋͔ྩͨ͠Βɺͦͷ݁Ռ͕ฦͬ ͯ͘Δ·Ͱ͸࣍ͷ໋ྩ͸Ͱ͖ͳ͍ͷ͔ɻ

  57. Catlog΋࠷ॳ͸͜Μͳײ͡ͷίʔυͩͬͨ ͔ͩΒ͜Μͳ͜ͱʹ…

  58. !

  59. CoroutineͱFlowͰ͍͍ ײ͡ʹͰ͖ΔͷͰ͸?

  60. Catlog΋࠷ॳ͸͜Μͳײ͡ͷίʔυͩͬͨ CoroutineͰॻ͚ΔΑ͏ʹ͢Ε͹ɺ໋ྩͩͨ͠Β໋ ྩͷ׬ྃΛawait͢Δͬͯ͜ͱ͕Ͱ͖ͦ͏ͳ༧ײ

  61. Catlog΋࠷ॳ͸͜Μͳײ͡ͷίʔυͩͬͨ “Ұؾʹ໋ྩ͢ΓΌ͑͑΍Μ” Λίʔυͷݟͨ໨্͸࣮ݱͰ͖ͦ͏ͩ

  62. None
  63. BLE x Coroutine x Flow

  64. ‣ BLEؔ࿈ͷAPIΛCoroutine΍FlowͰwrap͢Δ ‣ BluetoothDeviceͱͷ௨৴Λ୲͏BluetoothClientΫϥεΛ࣮૷͢Δ ‣ ֤eventΛawait͢Δؔ਺Λఏڙ͢Δ ུ֓

  65. ‣ sealed interfaceΛ࢖ͬͯBluetoothGattCallbackͷ֤ؔ਺ʹରԠ͢ Δdata classΛఆٛ͢Δ ‣ લड़ͷdata classΛFlowͰแΜͰฦ֦͢ுؔ਺Λ࣮૷͢Δ ‣ RxBindingͱ͔CorbindͰ΍ΒΕͯΔख๏

    BluetoothDevice#connectGatt
  66. 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
  67. 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
  68. fun BluetoothDevice.connectGatt(context: Context, autoConnect: Boolean) = channelFlow { // ུ

    awaitClose { gatt.disconnect() gatt.close() } } BluetoothDevice#connectGatt
  69. class BluetoothClient( private val context: Context, private val device: BluetoothDevice

    ) { private val gattEvent = MutableStateFlow<BluetoothGattEvent?>(null) private lateinit var gatt: BluetoothGatt // ུ } BluetoothClient
  70. class BluetoothClient( private val context: Context, private val device: BluetoothDevice

    ) { private val gattEvent = MutableStateFlow<BluetoothGattEvent?>(null) private lateinit var gatt: BluetoothGatt // ུ } BluetoothClient
  71. class BluetoothClient( private val context: Context, private val device: BluetoothDevice

    ) { private val gattEvent = MutableStateFlow<BluetoothGattEvent?>(null) private lateinit var gatt: BluetoothGatt // ུ } BluetoothClient
  72. fun connect(autoConnect: Boolean, coroutineScope: CoroutineScope) { device.connectGatt(context, autoConnect) .onEach {

    gatt = it.gatt gattEvent.value = it } .launchIn(coroutineScope) } BluetoothClient#connect
  73. fun connect(autoConnect: Boolean, coroutineScope: CoroutineScope) { device.connectGatt(context, autoConnect) .onEach {

    gatt = it.gatt gattEvent.value = it } .launchIn(coroutineScope) } BluetoothClient#connect
  74. suspend fun awaitConnected() { if (connectionState == BluetoothProfile.STATE_CONNECTED) return gattEvent.first

    { it is BluetoothGattEvent.ConnectionStateChange && it.newState == BluetoothProfile.STATE_CONNECTED } } CONNECTEDΛawaitͰ͖Δ
  75. 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͸ ͜ͷ৚͕݅ຬͨ͞ΕΔ·Ͱதஅ͞ΕΔ
  76. 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͢Δ͜ͱ΋Ͱ͖Δ
  77. 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 ) … σόΠεͱͷ௨৴खॱͱίʔυͷྲྀΕ͕Ұக͢Δ
  78. 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 ) } σόΠεͱͷ௨৴खॱͱίʔυͷྲྀΕ͕Ұக͢Δ
  79. 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 ) } σόΠεͱͷ௨৴खॱͱίʔυͷྲྀΕ͕Ұக͢Δ
  80. 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 ) } σόΠεͱͷ௨৴खॱͱίʔυͷྲྀΕ͕Ұக͢Δ
  81. 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 ) } σόΠεͱͷ௨৴खॱͱίʔυͷྲྀΕ͕Ұக͢Δ
  82. None
  83. ͦΜͳʹ؁͘ͳ͍BLE

  84. ։ൃऀʹݫ͍͠BLE͞Μ BLE͏Μͱ΋͢Μͱ΋͍Θͳ͘ͳͬͨΜ͚ͩͲ…

  85. ‣ BluetoothͷAPI͕௜໧ΩϝΔࣄ͕͋Δ ‣ ୺຤ͷઃఆͰBluetoothΛҰ୴OFFʹͯ͠࠶౓ONʹ… ‣ ͦΕͰ΋࣏Βͳ͔ͬͨΒ୺຤Λ࠶ىಈͰ… ‣ ͢Μ·ͤΜ… ׬શʹ௜໧͢ΔBLE͞Μ

  86. ‣ ϢʔβʔͷखݩͰͲΜͳෆ۩߹͕ى͖͔ͨ ௐࠪͰ͖ΔΑ͏ʹͨ͠΄͏͕ྑͦ͞͏ɻ ‣ Crashlytics ‣ Ϋϥογϡൃੜ࣌ʹͦͷखલͰى͖͍ͯͨϩά͸ݟΕΔ ‣ ΫϥογϡΛ൐͏ෆ۩߹͸͋·Γͳ͍ͷͰɺ༗༻Ͱ͸ͳ͍ ‣

    Bugfender ‣ Logcat΁ͷग़ྗ͢΂ͯɺ΋͘͠͸બఆͨ͠ϩάΛऩूͰ͖Δ ‣ Bugfenderศརͦ͏(ݕ౼த) ෆ໌ͳෆ۩߹ʹରԠ͢ΔͨΊʹ
  87. None
  88. BLEΛ࢖ͬͨΞϓϦ ͱ͍ͬͯ΋BLEͷϢʔεέʔε͸༷ʑ

  89. Catlog Series BLE BLE HTTP ઃఆ ϩά ϩ ά BLE

    HTTP ઃఆ ϩά
  90. Catlog Series BLE BLE HTTP ઃఆ ϩά ϩ ά BLE

    HTTP ઃఆ ϩά
  91. SwitchBot & SwitchBot Hub (͸ͨͿΜ͜Μͳײ͡) BLE ઃఆ ࡞ಈ໋ྩ HTTP SwitchBotͷ࡞ಈ໋ྩ

    SwitchBotͷ࡞ಈ໋ྩ BLE ࡞ಈ໋ྩ HTTP?
  92. SwitchBot & SwitchBot Hub (͸ͨͿΜ͜Μͳײ͡) BLE ઃఆ ࡞ಈ໋ྩ HTTP SwitchBotͷ࡞ಈ໋ྩ

    SwitchBotͷ࡞ಈ໋ྩ BLE ࡞ಈ໋ྩ HTTP?
  93. ‣ Catlog͸σόΠεͷઃఆͷΈBLEΛ࢖͍ͬͯΔ ‣ ઃఆը໘͕ੜ͖͍ͯΔؒͷΈCatlogͱίωΫγϣϯΛு͍ͬͯΕ͹ ͍͍ ‣ ઃఆը໘಺ͰconnectionΛone-shotͰ࢖͍ࣺͯΒΕΔ ‣ SwitchBot͸ઃఆ͓Αͼ࡞ಈ໋ྩʹBLEΛ࢖͍ͬͯΔ ‣

    ΞϓϦ͕ىಈத͸ৗ࣌SwitchBotͱίωΫγϣϯΛுΓɺ͍ͭͰ΋ SwitchBotʹରͯ͠BLEΛ௨໋ͯ͠ྩͰ͖ΔΑ͏ʹ͍ͯ͠Δ(ͱ༧૝) BLEͷϢʔεέʔε͸ΞϓϦʹΑ༷ͬͯʑ
  94. ‣ Catlog͸FragmentͷlifecycleͷதͰBLEΛ࢖͑Ε͹͍͍ ‣ SwitchBot͸ɺ1Activityߏ੒ͳΒActivityͷlifecycleͷதɺͦ͏Ͱͳ ͍ͳΒServiceͷதͰBLEΛ࢖͏ඞཁ͕͋Γͦ͏ BLEͷϢʔεέʔε͸ΞϓϦʹΑ༷ͬͯʑ

  95. ‣ Catlog͸FragmentͷlifecycleͷதͰBLEΛ࢖͑Ε͹͍͍ ‣ SwitchBot͸ɺ1Activityߏ੒ͳΒActivityͷlifecycleͷதɺͦ͏Ͱͳ ͍ͳΒServiceͷதͰBLEΛ࢖͏ඞཁ͕͋Γͦ͏ ‣ ΞϓϦʹΑͬͯBLE·ΘΓͷઃܭ͸มΘΓͦ͏ BLEͷϢʔεέʔε͸ΞϓϦʹΑ༷ͬͯʑ

  96. Ϣʔεέʔεʹ߹Θͤͯ ࠷ߴͷBLEΞϓϦΛ࡞Ζ͏

  97. Thank you