Первый шаг во взаимодействии с устройством BLE — подключение к нему. Точнее, подключение к серверу GATT на устройстве. Чтобы подключиться к серверу GATT на устройстве BLE, используйте метод connectGatt()
. Этот метод принимает три параметра: объект Context
, autoConnect
(логическое значение, указывающее, следует ли автоматически подключаться к устройству BLE, как только оно станет доступным), и ссылку на BluetoothGattCallback
:
Котлин
var bluetoothGatt: BluetoothGatt? = null ... bluetoothGatt = device.connectGatt(this, false, gattCallback)
Ява
bluetoothGatt = device.connectGatt(this, false, gattCallback);
Он подключается к серверу 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 } } }
Ява
class BluetoothLeService extends Service { private Binder binder = new LocalBinder(); @Nullable @Override public IBinder onBind(Intent intent) { return binder; } class LocalBinder extends Binder { public BluetoothLeService getService() { return BluetoothLeService.this; } } }
Активность может запустить службу с помощью 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) } }
Ява
class DeviceControlActivity extends AppCompatActivity { private BluetoothLeService bluetoothService; private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { bluetoothService = ((LocalBinder) service).getService(); if (bluetoothService != null) { // call functions on service to check connection and connect to devices } } @Override public void onServiceDisconnected(ComponentName name) { bluetoothService = null; } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.gatt_services_characteristics); Intent gattServiceIntent = new Intent(this, BluetoothLeService.class); bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE); } }
Настройте Bluetooth-адаптер
После привязки службы ей необходимо получить доступ к BluetoothAdapter
. Следует проверить наличие адаптера на устройстве. Прочтите «Настройка Bluetooth» для получения дополнительной информации о BluetoothAdapter
. В следующем примере этот код установки заключен в функцию 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 } ... }
Ява
class BluetoothLeService extends Service { public static final String TAG = "BluetoothLeService"; private BluetoothAdapter bluetoothAdapter; public boolean initialize() { bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter == null) { Log.e(TAG, "Unable to obtain a BluetoothAdapter."); return false; } return true; } ... }
Действие вызывает эту функцию в своей реализации ServiceConnection
. Обработка ложного возвращаемого значения из функции 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 } } ... }
Ява
class DeviceControlsActivity extends AppCompatActivity { private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { bluetoothService = ((LocalBinder) service).getService(); if (bluetoothService != null) { if (!bluetoothService.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth"); finish(); } // perform device connection } } @Override public void onServiceDisconnected(ComponentName name) { 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 } ?: run { Log.w(TAG, "BluetoothAdapter not initialized") return false } }
Ява
public boolean connect(final String address) { if (bluetoothAdapter == null || address == null) { Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); return false; } try { final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); } catch (IllegalArgumentException exception) { Log.w(TAG, "Device not found with provided address."); return false; } // connect to the GATT server on the device }
DeviceControlActivity
вызывает эту функцию connect()
после инициализации службы. Действие должно передаваться по адресу устройства BLE. В следующем примере адрес устройства передается действию в качестве дополнительного намерения.
Котлин
// 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 bluetooth.connect(deviceAddress) } } override fun onServiceDisconnected(componentName: ComponentName) { bluetoothService = null } }
Ява
private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { bluetoothService = ((LocalBinder) service).getService(); if (bluetoothService != null) { if (!bluetoothService.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth"); finish(); } // perform device connection bluetoothService.connect(deviceAddress); } } @Override public void onServiceDisconnected(ComponentName name) { bluetoothService = null; } };
Объявить обратный вызов GATT
Как только действие сообщает службе, к какому устройству подключиться, и служба подключается к устройству, службе необходимо подключиться к серверу 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 } } }
Ява
private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { // successfully connected to the GATT Server } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // disconnected from the GATT Server } } };
Подключиться к сервису GATT
После объявления 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 } } }
Ява
class BluetoothLeService extends Service { ... private BluetoothGatt bluetoothGatt; ... public boolean connect(final String address) { if (bluetoothAdapter == null || address == null) { Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); return false; } try { final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); // connect to the GATT server on the device bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback); return true; } catch (IllegalArgumentException exception) { Log.w(TAG, "Device not found with provided address. Unable to connect."); return false; } } }
Трансляция обновлений
Когда сервер подключается или отключается от сервера GATT, ему необходимо уведомить активность о новом состоянии. Есть несколько способов сделать это. В следующем примере широковещательные рассылки используются для отправки информации из службы в действие.
Служба объявляет функцию для трансляции нового состояния. Эта функция принимает строку действия, которая передается объекту Intent
перед трансляцией в систему.
Котлин
private fun broadcastUpdate(action: String) { val intent = Intent(action) sendBroadcast(intent) }
Ява
private void broadcastUpdate(final String action) { final Intent intent = new Intent(action); sendBroadcast(intent); }
Как только функция широковещания активирована, она используется в BluetoothGattCallback
для отправки информации о состоянии соединения с сервером GATT. Константы и текущее состояние подключения службы объявляются в службе, представляющей действия Intent
.
Котлин
class BluetoothLeService : Service() { private var connectionState = STATE_DISCONNECTED private val bluetoothGattCallback: 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 } }
Ява
class BluetoothLeService extends Service { public final static String ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED"; public final static String ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"; private static final int STATE_DISCONNECTED = 0; private static final int STATE_CONNECTED = 2; private int connectionState; ... private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { 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); } } }; … }
Слушайте обновления в активности
Как только служба передает обновления подключения, действие должно реализовать 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()) if (bluetoothService != null) { val result = bluetoothService!!.connect(deviceAddress) Log.d(DeviceControlsActivity.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) } } }
Ява
class DeviceControlsActivity extends AppCompatActivity { ... private final BroadcastReceiver gattUpdateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) { connected = true; updateConnectionState(R.string.connected); } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { connected = false; updateConnectionState(R.string.disconnected); } } }; @Override protected void onResume() { super.onResume(); registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter()); if (bluetoothService != null) { final boolean result = bluetoothService.connect(deviceAddress); Log.d(TAG, "Connect request result=" + result); } } @Override protected void onPause() { super.onPause(); unregisterReceiver(gattUpdateReceiver); } private static IntentFilter makeGattUpdateIntentFilter() { final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED); intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED); return intentFilter; } }
При передаче данных 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 } } }
Ява
class BluetoothLeService extends Service { ... @Override public boolean onUnbind(Intent intent) { close(); return super.onUnbind(intent); } private void close() { if (bluetoothGatt == null) { Return; } bluetoothGatt.close(); bluetoothGatt = null; } }