Подключитесь к серверу GATT

Первый шаг во взаимодействии с BLE-устройством — это подключение к нему. Точнее, подключение к GATT-серверу на устройстве. Для подключения к GATT-серверу на BLE-устройстве используйте метод connectGatt() . Этот метод принимает три параметра: объект Context , autoConnect (логическое значение, указывающее, следует ли автоматически подключаться к BLE-устройству, как только оно станет доступно) и ссылку на объект BluetoothGattCallback :

var bluetoothGatt: BluetoothGatt? = null
// ...
bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)

Эта функция подключается к GATT-серверу, размещенному на BLE-устройстве, и возвращает экземпляр BluetoothGatt , который затем можно использовать для выполнения операций GATT-клиента. Вызывающая сторона (приложение Android) является GATT-клиентом. Функция BluetoothGattCallback используется для передачи результатов клиенту, таких как статус соединения, а также для выполнения любых дальнейших операций GATT-клиента.

Настройте привязанную службу

В следующем примере BLE-приложение предоставляет активность ( DeviceControlActivity ) для подключения к устройствам Bluetooth, отображения данных устройства, а также отображения поддерживаемых устройством служб и характеристик GATT. На основе пользовательского ввода эта активность взаимодействует со Service BluetoothLeService , которая взаимодействует с BLE-устройством через BLE API. Связь осуществляется с помощью связанной службы , которая позволяет активности подключаться к BluetoothLeService и вызывать функции для подключения к устройствам. Для BluetoothLeService требуется реализация Binder , которая предоставляет доступ к службе для активности.

class BluetoothLeService : Service() {

    private val binder = LocalBinder()

    override fun onBind(intent: Intent): IBinder? {
        return binder
    }

    inner class LocalBinder : Binder() {
        fun getService(): BluetoothLeService {
            return this@BluetoothLeService
        }
    }
}

Активность может запустить службу с помощью bindService() , передав в качестве Intent для запуска службы, реализацию ServiceConnection для прослушивания событий подключения и отключения, а также флаг для указания дополнительных параметров подключения.

class DeviceControlActivity : AppCompatActivity() {

    private var bluetoothService: BluetoothLeService? = null
    // Code to manage Service lifecycle.
    private val serviceConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(
            componentName: ComponentName,
            service: IBinder
        ) {
            bluetoothService = (service as LocalBinder).getService()
            bluetoothService?.let { bluetooth ->
                // call functions on service to check connection and connect to devices
            }
        }

        override fun onServiceDisconnected(componentName: ComponentName) {
            bluetoothService = null
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.gatt_services_characteristics)

        val gattServiceIntent = Intent(this, BluetoothLeService::class.java)
        bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE)
    }
}

Настройте Bluetooth-адаптер

После привязки службы ей необходимо получить доступ к BluetoothAdapter . Следует проверить, доступен ли адаптер на устройстве. Дополнительную информацию о BluetoothAdapter см. в разделе «Настройка Bluetooth» . В следующем примере этот код настройки заключен в функцию initialize() , которая возвращает Boolean значение, указывающее на успех.

private const val TAG = "BluetoothLeService"

class BluetoothLeService : Service() {

    private var bluetoothAdapter: BluetoothAdapter? = null

    fun initialize(): Boolean {
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
        if (bluetoothAdapter == null) {
            Log.e(TAG, "Unable to obtain a BluetoothAdapter.")
            return false
        }
        return true
    }

    // ...
}

Действие вызывает эту функцию внутри своей реализации ServiceConnection . Обработка возвращаемого значения false из функции initialize() зависит от вашего приложения. Вы можете показать пользователю сообщение об ошибке, указывающее на то, что текущее устройство не поддерживает работу Bluetooth, или отключить любые функции, требующие работы Bluetooth. В следующем примере finish() вызывается в действии, чтобы вернуть пользователя на предыдущий экран.

class DeviceControlActivity : AppCompatActivity() {

    // Code to manage Service lifecycle.
    private val serviceConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(
            componentName: ComponentName,
            service: IBinder
        ) {
            bluetoothService = (service as LocalBinder).getService()
            bluetoothService?.let { bluetooth ->
                if (!bluetooth.initialize()) {
                    Log.e(TAG, "Unable to initialize Bluetooth")
                    finish()
                }
                // perform device connection
            }
        }

        override fun onServiceDisconnected(componentName: ComponentName) {
            bluetoothService = null
        }
    }

    // ...
}

Подключиться к устройству

После инициализации экземпляра BluetoothLeService он может подключиться к BLE-устройству. Для установления соединения службе необходимо отправить адрес устройства. Сначала служба вызовет getRemoteDevice() у BluetoothAdapter для доступа к устройству. Если адаптер не сможет найти устройство с таким адресом, getRemoteDevice() вызовет исключение IllegalArgumentException .

fun connect(address: String): Boolean {
    bluetoothAdapter?.let { adapter ->
        try {
            val device = adapter.getRemoteDevice(address)
        } catch (exception: IllegalArgumentException) {
            Log.w(TAG, "Device not found with provided address.")
            return false
        }
        // connect to the GATT server on the device
        return true
    } ?: run {
        Log.w(TAG, "BluetoothAdapter not initialized")
        return false
    }
}

После инициализации службы объект DeviceControlActivity вызывает функцию connect() . Активность должна передать адрес BLE-устройства. В следующем примере адрес устройства передается в активность в качестве дополнительного параметра Intent.

// Code to manage Service lifecycle.
private val serviceConnection: ServiceConnection = object : ServiceConnection {
    override fun onServiceConnected(
        componentName: ComponentName,
        service: IBinder
    ) {
        bluetoothService = (service as LocalBinder).getService()
        bluetoothService?.let { bluetooth ->
            if (!bluetooth.initialize()) {
                Log.e(TAG, "Unable to initialize Bluetooth")
                finish()
            }
            // perform device connection
            deviceAddress?.let { bluetooth.connect(it) }
        }
    }

    override fun onServiceDisconnected(componentName: ComponentName) {
        bluetoothService = null
    }
}

Объявить обратный вызов ГАТТ

После того как событие сообщит сервису, к какому устройству следует подключиться, и сервис подключится к устройству, ему необходимо подключиться к GATT-серверу на BLE-устройстве. Для этого подключения требуется объект BluetoothGattCallback для получения уведомлений о состоянии подключения, обнаружении сервиса, считывании характеристик и уведомлениях о характеристиках.

В этой теме основное внимание уделяется уведомлениям о состоянии соединения. Информацию о том, как выполнять обнаружение служб, чтение характеристик и запрашивать уведомления о характеристиках, см. в разделе «Передача данных BLE» .

Функция onConnectionStateChange() срабатывает при изменении состояния соединения с GATT-сервером устройства. В следующем примере функция обратного вызова определена в классе Service , чтобы ее можно было использовать с BluetoothDevice после того, как служба подключится к нему.

private val bluetoothGattCallback = object : BluetoothGattCallback() {
    override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            // successfully connected to the GATT Server
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            // disconnected from the GATT Server
        }
    }
}

Подключитесь к сервису ГАТТ

После объявления объекта BluetoothGattCallback служба может использовать объект BluetoothDevice из функции connect() для подключения к службе GATT на устройстве.

Используется функция connectGatt() . Для её работы требуется объект Context , логический флаг autoConnect и объект BluetoothGattCallback . В этом примере приложение напрямую подключается к BLE-устройству, поэтому для autoConnect передаётся значение false .

Также добавлено свойство BluetoothGatt . Это позволяет сервису закрывать соединение, когда оно больше не требуется.

class BluetoothLeService : Service() {

    // ...
    private var bluetoothGatt: BluetoothGatt? = null

    // ...
    fun connect(address: String): Boolean {
        bluetoothAdapter?.let { adapter ->
            try {
                val device = adapter.getRemoteDevice(address)
                // connect to the GATT server on the device
                bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)
                return true
            } catch (exception: IllegalArgumentException) {
                Log.w(TAG, "Device not found with provided address.  Unable to connect.")
                return false
            }
        } ?: run {
            Log.w(TAG, "BluetoothAdapter not initialized")
            return false
        }
    }
}

Обновления трансляций

Когда сервер подключается или отключается от сервера GATT, ему необходимо уведомить об этом активность. Существует несколько способов сделать это. В следующем примере используются широковещательные сообщения для отправки информации от службы к активности.

Сервис объявляет функцию для трансляции нового состояния. Эта функция принимает строку действия, которая передается объекту Intent перед трансляцией в систему.

private fun broadcastUpdate(action: String) {
    val intent = Intent(action)
    sendBroadcast(intent)
}

После активации функции широковещательной рассылки она используется в объекте BluetoothGattCallback для отправки информации о состоянии соединения с сервером GATT. Константы и текущее состояние соединения сервиса объявляются в сервисе, представляя действия Intent .

class BluetoothLeService : Service() {

    private var connectionState = STATE_DISCONNECTED

    private val bluetoothGattCallback = object : BluetoothGattCallback() {
        override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                // successfully connected to the GATT Server
                connectionState = STATE_CONNECTED
                broadcastUpdate(ACTION_GATT_CONNECTED)
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                // disconnected from the GATT Server
                connectionState = STATE_DISCONNECTED
                broadcastUpdate(ACTION_GATT_DISCONNECTED)
            }
        }
    }

    // ...
    companion object {
        const val ACTION_GATT_CONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_CONNECTED"
        const val ACTION_GATT_DISCONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"

        private const val STATE_DISCONNECTED = 0
        private const val STATE_CONNECTED = 2
    }
}

Следите за обновлениями в режиме реального времени.

После того как сервис отправит широковещательные обновления соединения, действию необходимо реализовать интерфейс BroadcastReceiver . Зарегистрируйте этот приемник при настройке действия и отмените его регистрацию, когда действие покинет экран. Слушая события от сервиса, действие сможет обновлять пользовательский интерфейс в зависимости от текущего состояния соединения с BLE-устройством.

class DeviceControlActivity : AppCompatActivity() {

    private val gattUpdateReceiver: BroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            when (intent.action) {
                BluetoothLeService.ACTION_GATT_CONNECTED -> {
                    connected = true
                    updateConnectionState(R.string.connected)
                }
                BluetoothLeService.ACTION_GATT_DISCONNECTED -> {
                    connected = false
                    updateConnectionState(R.string.disconnected)
                }
            }
        }
    }

    override fun onResume() {
        super.onResume()
        registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter())
        bluetoothService?.let { service ->
            deviceAddress?.let { address ->
                val result = service.connect(address)
                Log.d(TAG, "Connect request result=$result")
            }
        }
    }

    override fun onPause() {
        super.onPause()
        unregisterReceiver(gattUpdateReceiver)
    }

    private fun makeGattUpdateIntentFilter(): IntentFilter {
        return IntentFilter().apply {
            addAction(BluetoothLeService.ACTION_GATT_CONNECTED)
            addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED)
        }
    }
}

В функции передачи данных BLE BroadcastReceiver также используется для передачи информации об обнаружении сервиса, а также характеристических данных устройства.

Закрыть соединение ГАТТ

Важным шагом при работе с Bluetooth-соединениями является закрытие соединения по завершении его использования. Для этого вызовите функцию close() объекта BluetoothGatt . В следующем примере служба хранит ссылку на объект BluetoothGatt . Когда активность отключается от службы, соединение закрывается, чтобы избежать разрядки батареи устройства.

class BluetoothLeService : Service() {

    // ...
    override fun onUnbind(intent: Intent?): Boolean {
        close()
        return super.onUnbind(intent)
    }

    private fun close() {
        bluetoothGatt?.let { gatt ->
            gatt.close()
            bluetoothGatt = null
        }
    }
}