Первый шаг во взаимодействии с устройством 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;
}
}