Uuid bluetooth как узнать
Перейти к содержимому

Uuid bluetooth как узнать

  • автор:

UUID. Bluetooth connect

Вопрос достаточно банальный. Какое UUID мне нужно установить при подключении к серверу через Bluetooth? Для начала объясните плиз, для чего он используется (именно «для чего», а не «что это такое» — это можно и википедии найти). То есть я пробовал через BluetoothDevice.getUuids(), но (если вы не в курсе) на андроид 4 он не работает и возвращает null.

Лучшие ответы ( 1 )
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
Ответы с готовыми решениями:

Bluetooth Connect Android
Помогите решить проблему, создаю приложение под андройд для работы с Bluetooth try< FAdapter =.

Как боротся с вирусом a-connect(i-connect,z-connect)
Итак расскажу что за вирус. Этот вирус создает новое подключение VPN a-connect и через 1-2.

Qt Bluetooth, ошибка qt.bluetooth: Dummy backend running. Qt Bluetooth module is non-functional
Начал разбираться с Qt и Bluetooth, для этого запустил пример.

Perl + MySQL = ошибка DBI connect(dbname=mysql; host=localhost’,». ) failed: Couldnt connect to
С перлом беда какая-то, третий день мучаюсь и ничего не получается. хелп. use dbi; $dbh =.

Регистрация: 13.07.2012
Сообщений: 164

Ну как я понимаю это уникальный id устройства для его идентификации в связке. Всегда туда прописывал произвольную строку, типа того:

private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);

И вопрос, чего вы ждете от BluetoothDevice.getUuids() , если его нужно предварительно задать?
Регистрация: 11.01.2012
Сообщений: 85
А правильно ли я понимаю, что uuid должны быть одинаковы как для сервера, так и для клиента?
Регистрация: 13.07.2012
Сообщений: 164

Лучший ответ

Сообщение было отмечено Максим2001 как решение

Решение

С чего это вдруг, если было бы так, то ни одно бы стороннее устройство нельзя было бы связать. Для адресации между устройствами есть MAC адрес, который как раз нужно знать. UUID передается как id, не более того, что бы не вышло ситуации что на одном MACе висело два физических устройства. Дальше писать не буду, ибо начну уже придумывать. Конкретный живой пример блютус модуль Arduino HC-06. Работает в слейв режиме, т. е. может принять принять подключение но не инициировать его. Для коннекта с ним необходим только его MAC, ну и передать ему UUID устройства с которого происходит подключение. Нам его UUID не нужен вообще.

Bluetooth Low Energy

Bluetooth Low Energy (BLE) — это цифровой радио протокол передачи данных от одного устройства к другому. Принцип работы BLE описан уже в его названии: Low Energy. Протокол подразумевает передачу данных короткими пакетами по необходимости, затем – выключение передатчика. Низкое энергопотребление частично достигается применением именно этого принципа. Вместо классического тандема в обычном Bluetooth, устройства BLE связываются друг с другом лишь при необходимости отправки или получения информации.

Одно из устройств является центральным, а остальные периферийными. Центральное устройство может держать несколько соединений с периферией, но периферийное устройство может содержать только одно соединение. Например, смартфон выступает как центральное устройство, которое может соединиться с периферией: блютуз-колонкой, лампой, умными часами и фитнес-трекером. А все эти устройства могут соединиться только с телефоном. Смартфоны 5.0 и выше (API 21) могут выступать и как периферийные устройства.

У центрального устройства есть два режима: сканирование и соединение. Периферия имеет другие два режима: объявление и соединение.

Протокол BLE строго структурирован по принципу своей коммуникации с другими устройствами. Вначале девайсы изучают доступные сервисы для отправки/принятия данных; неотъемлемая часть этих сервисов – их характеристики (characteristics), определяющие тип данных для будущей передачи. Характеристики, из соображений наглядности, могут иметь в своём составе описания-дескрипторы (descriptors), которые помогают определить тип данных. К примеру, разберём сервис под названием «Heart Rate Monitor» (монитор частоты сердцебиения) – среди его характеристик присутствуют такие, как «измерение пульса».

Большинство API для Bluetooth LE позволяют искать локальные устройства и определять доступные в них сервисы, характеристики и дескрипторы.

Далее перевод статьи Bluetooth Low Energy | Android Developers подготовил Антон Акимов.

ОС Android 4.3 (API 18) представляет встроенную поддержку Bluetooth Low Energy и API, при помощи которого приложения могут использовать поиск устройств, запрос услуг и чтение/запись характеристик. В отличие от классического Bluetooth, BLE призван обеспечить существенно меньшее энергопотребление. Это позволяет приложениям для Android общаться с BLE-устройствами, которые имеют низкие требования к питанию, таких как датчики, мониторы сердечного ритма, фитнес-устройства и так далее.

Ключевые термины и понятия

Generic Attribute Profile (GATT) – профиль GATT является общей спецификацией для отправки и получения коротких фрагментов данных, известных как «атрибуты» через BLE-соединение. Все текущие LE-профили приложений основаны на GATT. Создатели BLE определили множество профилей для низкоэнергетических устройств. Профиль представляет собой определение того, как устройство работает в конкретном приложении. Обратите внимание, что устройство может реализовывать более одного профиля. Например, устройство может содержать профили пульсометра и датчика уровня заряда батареи.

Attribute Protocol (ATT) – GATT строится на основе протокола атрибутов АТТ. Это также относится к GATT/ATT. АТТ оптимизирован для работы на BLE-устройствах. Для этого он использует настолько мало байтов, насколько возможно. Каждый атрибут идентифицируется уникальным универсальным идентификатором (UUID), который представляет собой стандартизированный 128-битный строковый идентификатор используемый для однозначной идентификации информации. Атрибуты переносятся с помощью АТТ в виде характеристик и услуг.

Характеристика (Characteristic) – содержит одно значение, и от 0 до N дескрипторов, описывающих значение характеристики. Характеристика может рассматриваться как тип, аналог класса.

Дескриптор (Descriptor) может содержать удобочитаемое описание, приемлемый диапазон значений или единицу измерения, конкретные значения характеристики.

Услуга (Service) – это набор характеристик. Например, вы можете иметь услугу под названием «пульсометр», что включает в себя такую характеристику, как «Измерение пульса». Вы можете найти список существующих на основе GATT профилей и услуг на bluetooth.org.

Роли и обязанности при взаимодействии Android с BLE-устройством

Центральная/периферическая роль. Это относится к самому BLE-соединению. Устройство в центральной роли сканирует, ищет объявления, а устройства в периферийной роли создаёт объявления.

GATT-сервер/GATT-клиент. Это определяет, каким образом два устройства общаются друг с другом, когда они установили связь.

Чтобы понять разницу, представьте, что у вас Android-телефон и фитнес-трекер, который представляет собой BLE-устройство. Телефон поддерживает центральную роль; трекер активности поддерживает периферийную роль. Чтобы установить BLE-соединение, вам нужно по одному устройству, поддерживающему каждую из ролей; два периферийных устройства не смогут общаться друг с другом, также как и два центральных.

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

В качестве примера в данном документе представлено Android-приложение, являющееся GATT-клиентом. Приложение получает данные от GATT-сервера на BLE-пульсометре. Но вы можете также спроектировать ваше приложение так, чтобы оно играло роль сервера.

BluetoothAdapter является обязательным для любых действий с Bluetooth. BluetoothAdapter представляет собственный Bluetooth-адаптер устройства (Bluetooth-приёмник). Есть один Bluetooth-адаптер для всей системы, и ваше приложение может взаимодействовать с ним, используя этот объект.

Подключение к GATT-серверу

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

 mBluetoothGatt = device.connectGatt(this, false, mGattCallback); 

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

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

 // Служба, которая взаимодействует с BLE-устройством через Android BLE API public class BluetoothLeService extends Service < private final static String TAG = BluetoothLeService.class.getSimpleName(); private BluetoothManager mBluetoothManager; private BluetoothAdapter mBluetoothAdapter; private String mBluetoothDeviceAddress; private BluetoothGatt mBluetoothGatt; private int mConnectionState = STATE_DISCONNECTED; private static final int STATE_DISCONNECTED = 0; private static final int STATE_CONNECTING = 1; private static final int STATE_CONNECTED = 2; 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"; public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED"; public final static String ACTION_DATA_AVAILABLE = "com.example.bluetooth.le.ACTION_DATA_AVAILABLE"; public final static String EXTRA_DATA = "com.example.bluetooth.le.EXTRA_DATA"; // Устанавливаем UUID, который используется для услуг измерения пульса public final static UUID UUID_HEART_RATE_MEASUREMENT = UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT); // Различные методы обратного вызова, определённые в BLE API private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() < @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) < String intentAction; if (newState == BluetoothProfile.STATE_CONNECTED) < intentAction = ACTION_GATT_CONNECTED; mConnectionState = STATE_CONNECTED; broadcastUpdate(intentAction); Log.i(TAG, "Connected to GATT server."); Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices()); >else if (newState == BluetoothProfile.STATE_DISCONNECTED) < intentAction = ACTION_GATT_DISCONNECTED; mConnectionState = STATE_DISCONNECTED; Log.i(TAG, "Disconnected from GATT server."); broadcastUpdate(intentAction); >> @Override // При обнаружении нового сервиса public void onServicesDiscovered(BluetoothGatt gatt, int status) < if (status == BluetoothGatt.GATT_SUCCESS) < broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED); >else < Log.w(TAG, "onServicesDiscovered received: " + status); >> @Override // Результат чтения характеристики public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) < if (status == BluetoothGatt.GATT_SUCCESS) < broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); >> . >; . > 

Когда срабатывает конкретный обратный вызов, он вызывает соответствующий вспомогательный метод broadcastUpdate() и передает ему действие. Обратите внимание, что извлечение данных в этот раздел выполняется в соответствии с профилем пульсометра.

 private void broadcastUpdate(final String action) < final Intent intent = new Intent(action); sendBroadcast(intent); >private void broadcastUpdate(final String action, final BluetoothGattCharacteristic characteristic) < final Intent intent = new Intent(action); // Это специальная обработка для пульсометра // Извлечение данных осуществляется согласно спецификации профиля if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) < int flag = characteristic.getProperties(); int format = -1; if ((flag & 0x01) != 0) < format = BluetoothGattCharacteristic.FORMAT_UINT16; Log.d(TAG, "Heart rate format UINT16."); >else < format = BluetoothGattCharacteristic.FORMAT_UINT8; Log.d(TAG, "Heart rate format UINT8."); >final int heartRate = characteristic.getIntValue(format, 1); Log.d(TAG, String.format("Received heart rate: %d", heartRate)); intent.putExtra(EXTRA_DATA, String.valueOf(heartRate)); > else < // For all other profiles, writes the data formatted in HEX. final byte[] data = characteristic.getValue(); if (data != null && data.length >0) < final StringBuilder stringBuilder = new StringBuilder(data.length); for(byte byteChar : data) stringBuilder.append(String.format("%02X ", byteChar)); intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString()); >> sendBroadcast(intent); > 

Ещё в DeviceControlActivity, эти события обрабатываются в BroadcastReceiver:

 // Обрабатывает различные события, запущенные службы // ACTION_GATT_CONNECTED: подключение к серверу GATT // ACTION_GATT_DISCONNECTED: отключён от сервера GATT // ACTION_GATT_SERVICES_DISCOVERED: обнаружена услуга GATT // ACTION_DATA_AVAILABLE: получил данные от устройства. Это может быть // результатом чтения или операцией уведомления private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() < @Override public void onReceive(Context context, Intent intent) < final String action = intent.getAction(); if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) < mConnected = true; updateConnectionState(R.string.connected); invalidateOptionsMenu(); >else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) < mConnected = false; updateConnectionState(R.string.disconnected); invalidateOptionsMenu(); clearUI(); >else if (BluetoothLeService. ACTION_GATT_SERVICES_DISCOVERED.equals(action)) < // Показать в UI все поддерживаемые услуги и характеристики displayGattServices(mBluetoothLeService.getSupportedGattServices()); >else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) < displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA)); >> >; 

Чтение BLE-атрибутов

Как только ваше Android-приложение подключается к GATT-серверу и обнаруживает услуги, оно может читать и писать атрибуты, где это поддерживается. Например, этот фрагмент перебирает услуги и характеристик сервера и отображает их в интерфейсе:

 public class DeviceControlActivity extends Activity < . // Демонстрирует, как перебрать поддерживаемые GATT-услуги/характеристики. // В этом примере мы заполняем структуру данных, которая привязана к ExpandableListView // в интерфейсе пользователя. private void displayGattServices(ListgattServices) < if (gattServices == null) return; String uuid = null; String unknownServiceString = getResources(). getString(R.string.unknown_service); String unknownCharaString = getResources(). getString(R.string.unknown_characteristic); ArrayList> gattServiceData = new ArrayList>(); ArrayList> gattCharacteristicData = new ArrayList>(); mGattCharacteristics = new ArrayList>(); // Перебирает доступные GATT-услуги for (BluetoothGattService gattService : gattServices) < HashMapcurrentServiceData = new HashMap(); uuid = gattService.getUuid().toString(); currentServiceData.put( LIST_NAME, SampleGattAttributes. lookup(uuid, unknownServiceString)); currentServiceData.put(LIST_UUID, uuid); gattServiceData.add(currentServiceData); ArrayList> gattCharacteristicGroupData = new ArrayList>(); List gattCharacteristics = gattService.getCharacteristics(); ArrayList charas = new ArrayList(); // Перебирает доступные GATT-характеристики for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) < charas.add(gattCharacteristic); HashMapcurrentCharaData = new HashMap(); uuid = gattCharacteristic.getUuid().toString(); currentCharaData.put( LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString)); currentCharaData.put(LIST_UUID, uuid); gattCharacteristicGroupData.add(currentCharaData); > mGattCharacteristics.add(charas); gattCharacteristicData.add(gattCharacteristicGroupData); > . >. > 

Получение GATT-уведомлений

Это общие для BLE-приложений запросы для получения уведомлений об изменении отдельных характеристик на устройстве. Этот фрагмент показывает, как настроить уведомление для какой-либо характеристики с помощью метода setCharacteristicNotification():

 private BluetoothGatt mBluetoothGatt; BluetoothGattCharacteristic characteristic; boolean enabled; . mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); . BluetoothGattDescriptor descriptor = characteristic.getDescriptor( UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); mBluetoothGatt.writeDescriptor(descriptor); Когда уведомления включены для характеристики, вызов onCharacteristicChanged() срабатывает, если характеристика была изменена на удаленном устройстве: @Override // Описание характеристики public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)

Закрытие клиента приложения

Когда приложение завершено с помощью BLE-устройства, оно должно вызывать close(), чтобы система могла должным образом освобождать ресурсы:

 public void close() < if (mBluetoothGatt == null) < return; >mBluetoothGatt.close(); mBluetoothGatt = null; > 

BluetoothManager

Менеджер высокого уровня, используемый для получения экземпляра BluetoothAdapter и общего управления Bluetooth. Используйте getSystemService(java.lang.String) с BLUETOOTH_SERVICE чтобы создать BluetoothManager; после этого вызывайте getAdapter() для получения экземпляра BluetoothAdapter. Кроме того, можно просто вызвать BluetoothAdapter.getDefaultAdapter().

BluetoothAdapter getAdapter() [Добавлено в API 18] Получить стандартный Bluetooth-адаптер данного устройства. Возвращает: Стандартный Bluetooth-адаптер данного устройства. List getConnectedDevices (int profile) [Добавлено в API 18] Получить подключённые устройства указанного профиля. Возвращает набор устройств, которые находятся в состоянии STATE_CONNECTED. Это не относится к любой конфигурации приложения, но отображает состояние Bluetooth-подключения данного профиля. Это может использоваться в приложениях как строка состояния, которая просто хотела бы знать состояние Bluetooth. Требуется разрешение BLUETOOTH. Параметры: GATT или GATT_SERVER Возвращает: Список устройств. При ошибке список будет пуст. int getConnectionState (BluetoothDevice device, int profile) [Добавлено в API 18] Получить текущее состояние подключения профиля к удаленному устройству. Это не относится к любой конфигурации приложения, но отображает состояние Bluetooth-подключения данного профиля. Это может использоваться в приложениях как строка состояния, которая просто хотела бы знать состояние Bluetooth. Требуется разрешение BLUETOOTH. Параметры: Удалённое Bluetooth-устройство; GATT или GATT_SERVER. Возвращает: Состояние соединения профиля, одно из: STATE_CONNECTED, STATE_CONNECTING, STATE_DISCONNECTED, STATE_DISCONNECTING. List getDevicesMatchingConnectionStates (int profile, int[] states) [Добавлено в API 18] Получить список устройств, которые соответствуют любому из указанных состояний соединения. Если ни одно из устройств не соответствует ни одному из указанных состояний, будет возвращён пустой список. Это не относится к любой конфигурации приложения, но представляет состояние подключения локального адаптера Bluetooth для этого профиля. Это может использоваться в приложениях, как в строке состояния, который просто хотел бы знать состояние локального адаптера. Требуется разрешение BLUETOOTH. Параметры: GATT или GATT_SERVER; массив состояний: STATE_CONNECTED, STATE_CONNECTING, STATE_DISCONNECTED или STATE_DISCONNECTING. Возвращает: Список устройств. При ошибке список будет пуст. BluetoothGattServer openGattServer (Context context, BluetoothGattServerCallback callback) [Добавлено в API 18] Открывает GATT-сервер. Обратный вызов используется для получения результатов, такие как состояние подключения, а также результаты любых других серверных операций в рамках GATT. Метод возвращает экземпляр BluetoothGattServer. Вы можете использовать BluetoothGattServer для проведения операций на сервере в рамках GATT. Параметры: Контекст; обработчика обратных вызовов GATT-сервера, который будет получать асинхронные обратные вызовы. Возвращает: Экземпляр GATT-сервера.

BluetoothGatt

Открытый API для Bluetooth-профидля GATT.

Этот класс обеспечивает функциональность Bluetooth GATT для взаимодействия со смарт-устройствами.

Чтобы подключиться к удалённому периферийному устройству, создайте BluetoothGattCallback и вызовите метод connectGatt(Context, boolean, BluetoothGattCallback), чтобы получить экземпляр этого класса. Устройства, поддерживающие GATT, могут быть найдены с помощью обычного обнаружения Bluetooth-устройств или с помощью сканирования BLE.

Константы

int CONNECTION_PRIORITY_BALANCED (приоритет соединения: сбалансированный)
[Добавлено в API 21]
Обновление параметра соединения: использовать параметры подключения, рекомендованные Bluetooth SIG. Это параметр по-умолчанию, если обновление параметров соединения не требуется. Значение константы: 0 (0x00000000)

int CONNECTION_PRIORITY_HIGH (приоритет соединения: высокий)
[Добавлено в API 21]
Обновление параметра соединения: запрос высокого приоритета, низкой задержки подключения. Приложение должно запрашивать высокий приоритет соединения только для передачи больших объемов данных через BLE. Когда передача будет завершена, приложение должно запросить параметр CONNECTION_PRIORITY_BALANCED для снижения энергопотребления. Значение константы: 1 (0x00000001)

int CONNECTION_PRIORITY_LOW_POWER (приоритет соединения: низкая энергия) [Добавлено в API 21]
Обновление параметра соединения: запрос низкой мощности, снижения скорости передачи данных.
Значение константы: 2 (0x00000002)

int GATT_CONNECTION_CONGESTED
[Добавлено в API 21]
Соединение с удалённым устройством перегружено.
Значение константы: 143 (0x0000008f)

int GATT_FAILURE
[Добавлено в API 18]
Операция с GATT не удалась. Произошла неизвестная ошибка, отличная от указанных выше.
Значение константы: 257 (0x00000101)

int GATT_INSUFFICIENT_AUTHENTICATION
[Добавлено в API 18]
Неподходящая аутентификация для данной операции. Значение константы: 5 (0x00000005)

int GATT_INSUFFICIENT_ENCRYPTION
[Добавлено в API 18]
Неподходящее шифрование для данной операции.
Значение константы: 15 (0x0000000f)

int GATT_INVALID_ATTRIBUTE_LENGTH
[Добавлено в API 18]
Операция записи превышает максимальную длину атрибута.
Значение константы: 13 (0x0000000d)

int GATT_INVALID_OFFSET
[Добавлено в API 18]
Для операции чтения или записи было запрошено недопустимое смещение.
Значение константы: 7 (0x00000007)

int GATT_READ_NOT_PERMITTED
[Добавлено в API 18]
Операция чтения не разрешена.
Значение константы: 2 (0x00000002)

int GATT_REQUEST_NOT_SUPPORTED
[Добавлено в API 18]
Данный запрос не поддерживается.
Значение константы: 6 (0x00000006)

int GATT_SUCCESS
[Добавлено в API 18]
Операция успешно выполнена.
Значение константы: 0 (0x00000000)

int GATT_WRITE_NOT_PERMITTED
[Добавлено в API 18]
Операция записи не разрешена.
Значение константы: 3 (0x00000003)

Общедоступные методы

void abortReliableWrite (BluetoothDevice mDevice) [Добавлено в API 18] Внимание: Этот метод является устаревшим для API уровня 19. Используйте метод abortReliableWrite().

void abortReliableWrite () [Добавлено в API 19] Отменяет надежную транзакцию записи для данного устройства. Вызов этой функции приведет к отклонению всех находящихся в очереди операций записи характеристик для данного удалённого устройства. Требуется разрешение BLUETOOTH.

boolean beginReliableWrite () [Добавлено в API 18] Инициирует надежную транзакцию записи для данного удалённого устройства. После того, как надёжная транзакция записи была начата, все вызовы writeCharacteristic(BluetoothGattCharacteristic) отправляются на удалённое устройство для проверки и выстраиваются в очередь на исполнение. Приложение получает результат в обратном вызове onCharacteristicWrite(BluetoothGatt, BluetoothGattCharacteristic, int) в ответ на каждый вызов writeCharacteristic(BluetoothGattCharacteristic). В этом обратном вызове производится проверка, было ли значение передано точно. После того, как все характеристики в очереди были проверены, executeReliableWrite() выполнит их запись. Если характеристика была записана неправильно, вызов abortReliableWrite() отменит текущую транзакцию без изменения каких-либо значений на удалённом устройстве. Требуется разрешение BLUETOOTH. Возвращает: true, если надёжная транзакция записи была начата.

void close () [Добавлено в API 18] Закрыть Bluetooth GATT-клиент. Приложение должно вызвать этот метод как можно раньше после того, как это делается с текущим GATT-клиентом.

boolean connect () [Добавлено в API 18] Этот метод используется, чтобы повторно подключиться к удалённому устройству после того, как соединение было разорвано. Если устройство не находится в доступном диапазоне, повторное подключение будет произведено, как только устройство окажется доступно. Возвращает: true, если попытка подключения была успешно инициирована.

void disconnect () [Добавлено в API 18] Разрывает установленное соединение или отменяет попытку подключения, происходящую в настоящее время. Требуется разрешение BLUETOOTH.

boolean discoverServices () [Добавлено в API 18] Обнаруживает услуги на удалённом устройстве, а также их характеристики и дескрипторы. Это асинхронная операция. После завершения обнаружения услуг, срабатывает обратный вызов onServicesDiscovered(BluetoothGatt, int). Если обнаружение прошло успешно, удалённые услуги можно получить с помощью функции getServices(). Требуется разрешение BLUETOOTH. Возвращает: true, если обнаружение удалённых услуг было начато.

boolean executeReliableWrite () [Добавлено в API 18] Выполняет надежные транзакции записи для данного удалённого устройства. Эта функция позволяет фиксировать все характеристики, находящиеся в очереди операций записи для указанного удалённого устройства. Обратный вызов onReliableWriteCompleted(BluetoothGatt, int) срабатывает чтобы определить, правильно ли была выполнена операция. Требуется разрешение BLUETOOTH. Возвращает: true, если запрос на выполнение операции был отправлен.

List getConnectedDevices () [Добавлено в API 18] Не поддерживается. Пожалуйста, используйте BluetoothManager.getConnectedDevices(int) с BluetoothProfile.GATT в качестве аргумента. Возвращает: Список устройств. Список будет пустым при ошибке. Исключения: UnsupportedOperationException

int getConnectionState (BluetoothDevice device) [Добавлено в API 18] Не поддерживается. Пожалуйста, используйте BluetoothManager.getConnectionState (BluetoothDevice device, int profile). Параметры: Удалённое Bluetooth-устройство Возвращает: Состояние соединеня; одно из: STATE_CONNECTED, STATE_CONNECTING, STATE_DISCONNECTED, STATE_DISCONNECTING Исключения: UnsupportedOperationException

BluetoothDevice getDevice () [Добавлено в API 18] Возвращает удалённое устройство целевого GATT-склиента.

List getDevicesMatchingConnectionStates (int[] states) [Добавлено в API 18] Не поддерживается. Пожалуйста, используйте BluetoothManager.getDevicesMatchingConnectionStates (int profile, int[] states) с BluetoothProfile.GATT в качестве первого аргумента. Параметры: Массив состояний; одно из: STATE_CONNECTED, STATE_CONNECTING, STATE_DISCONNECTED, STATE_DISCONNECTING. Возвращает: Список устройств. Список будет пустым при ошибке. Исключения: UnsupportedOperationException

BluetoothGattService getService (UUID uuid) [Добавлено в API 18] Возвращает BluetoothGattService, если запрашиваемый идентификатор uuid поддерживается на удалённом устройстве. Эта функция требует, чтобы обнаружение услуг было выполнено для данного устройства. Если существует несколько экземпляров одной услуги (с таким же UUID), возвращается первый экземпляр службы. Требуется разрешение BLUETOOTH. Параметры: UUID запрашиваемой услуги. Возвращает: BluetoothGattService, если поддерживается, или NULL, если запрашиваемая услуга не предоставляется удаленным устройством.

List getServices () [Добавлено в API 18] Возвращает список GATT-услуг, предлагаемых удаленным устройством. Эта функция требует, чтобы обнаружение услуг было выполнено для данного устройства. Требуется разрешение BLUETOOTH. Возвращает: Список услуг удалённого устройства. Пустой список, если обнаружение услуг ещё не было выполнено.

boolean readCharacteristic (BluetoothGattCharacteristic characteristic) [Добавлено в API 18] Запрос на чтение характеристики с удалённого устройства. Это асинхронная операция. Результат операции чтения получает обратный вызов onCharacteristicRead(BluetoothGatt, BluetoothGattCharacteristic, int). Требуется разрешение BLUETOOTH. Параметры: Характеристика для чтения с удалённого устройства. Возвращает: true, если операция чтения была начата успешно.

boolean readDescriptor (BluetoothGattDescriptor descriptor) [Добавлено в API 18] Считывает значение дескриптора удалённого устройства. Как только операция чтения завершена, срабатывает обратный вызов onDescriptorRead(BluetoothGatt, BluetoothGattDescriptor, int), сигнализируя о результате операции. Требуется разрешение BLUETOOTH. Параметры: Дескриптор для чтения с удалённого устройства. Возвращает: true, если операция чтения была начата успешно.

boolean readRemoteRssi () [Добавлено в API 18] Считать RSSI подключённого удалённого устройства. (RSSI – это показатель уровня принимаемого сигнала). Обратный вызов onReadRemoteRssi(BluetoothGatt, int, int) срабатывает, когда значение RSSI было прочитано. Требуется разрешение BLUETOOTH. Возвращает: true, если операция чтения была начата успешно.

boolean requestConnectionPriority (int connectionPriority) [Добавлено в API 21] Запросить обновление параметра соединения. Эта функция отправит запрос на обновление параметра подключения к удалённому устройству. Параметры: Запрашиваемый статус соединения, один из: CONNECTION_PRIORITY_BALANCED, CONNECTION_PRIORITY_HIGH, CONNECTION_PRIORITY_LOW_POWER. Исключения: IllegalArgumentException, если параметры находятся за пределами указанного диапазона.

boolean requestMtu (int mtu) [Добавлено в API 21] Запросить размер MTU, используемого для данного подключения. (MTU – это размер одного неделимого блока данных, передаваемого в текущей сети за одну итерацию). При выполнении запроса на запись (запись без ответа), отправленные данные будут усечены до размера MTU. Эта функция может использоваться, чтобы запросить больший размер MTU, чтобы иметь возможность отправлять больше данных одновременно. Обратный вызов onMtuChanged(BluetoothGatt, int, int) будет указывать, прошла ли эта операция успешно. Требуется разрешение BLUETOOTH. Возвращает: true, если новое значение MTU было запрошено успешно.

boolean setCharacteristicNotification (BluetoothGattCharacteristic characteristic, boolean enable) [Добавлено в API 18] Включить или отключить уведомления/индикацию для данной характеристики. После включения уведомлений для характеристики, обратный вызов onCharacteristicChanged(BluetoothGatt, BluetoothGattCharacteristic) будет срабатывать, если удалённое устройство сообщит, что характеристика изменилась. Требуется разрешение BLUETOOTH. Параметры: Характеристика, для которой необходимо включить уведомления; true, если нужно включить уведомления. Возвращает: true, если запрашиваемое уведомление о был установлен успешно.

boolean writeCharacteristic (BluetoothGattCharacteristic characteristic) [Добавлено в API 18] Записывает указанную характеристику и её значение на удалённое связанное устройство. После того, как операция записи будет завершена, сработает обратный вызов onCharacteristicWrite(BluetoothGatt, BluetoothGattCharacteristic, int) с результатом операции. Требуется разрешение BLUETOOTH. Параметры: Характеристика для записи на удалённом устройстве. Возвращает: true, если операция записи была начата успешно.

boolean writeDescriptor (BluetoothGattDescriptor descriptor) [Добавлено в API 18] Записывает значение дескриптора на соединённое устройство. Обратный вызов onDescriptorWrite(BluetoothGatt, BluetoothGattDescriptor, int) срабатывает чтобы сообщить о результате операции. Требуется разрешение BLUETOOTH. Параметры: Дескрипотр для записи на удалённом устройстве. Возвращает: true, если операция записи была начата успешно.

BluetoothGattCallback

void onCharacteristicChanged (BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) [Добавлено в API 18] Обратный вызов инициируется в результате получения уведомления от удалённой характеристики. Параметры: GATT-клиент; характеристика, обновлённая в результате удалённого уведомления.

void onCharacteristicRead (BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) [Добавлено в API 18] Обратный вызов, сообщающий результат чтения удалённой характеристики. Параметры: GATT-клиент, вызвавший readCharacteristic; характеристика, чьё значение было прочитано; статус: GATT_SUCCESS, если операция чтения была успено завершена.

void onCharacteristicWrite (BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) [Добавлено в API 18] Обратный вызов с указанием результата операции записи характеристики. Если этот обратный вызов срабатывает в то время, как надёжная транзакция записи находится в процессе, то значение характеристики представляет собой значение, указанное удалённым устройством. Прикладная программа должна сравнить эту величину с требуемым значением для записи. Если значения не совпадают, то приложение должно прервать транзакцию надежной записи. Параметры: GATT-клиент, вызвавший writeCharacheristic; записываемая характеристика; статус: GATT_SUCCESS, если операция записи была проведена успешно.

void onConnectionStateChange (BluetoothGatt gatt, int status, int newState) [Добавлено в API 18] Обратный вызов, срабатывающий при подключении к удалённому GATT-серверу или отключении от него. Параметры: GATT-клиент, вызвавший подключение/отключение; статус: GATT_SUCCESS, если операция подключения/отключения прошла успешно; новое состояние соединения: STATE_CONNECTED или STATE_DICONNECTED.

void onDescriptorRead (BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) [Добавлено в API 18] Обратный вызов, сообщающий о результате операции прочтения дескриптора. Параметры: GATT-клиент, вызвавший readDescriptor; дескриптор, прочитанный на удалённом устройстве; статус: GATT_SUCCESS, если операция записи была проведена успешно.

void onDescriptorWrite (BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) [Добавлено в API 18] Обратный вызов, сообщающий о результате операции записи дескриптора.

Параметры: GATT-клиент, вызвавший readDescriptor; дескриптор, прочитанный на удалённом устройстве; статус: GATT_SUCCESS, если операция записи была проведена успешно.

void onMtuChanged (BluetoothGatt gatt, int mtu, int status) [Добавлено в API 21] Обратный вызов, сообщающий об изменении MTU для данного соединения. Этот обратный вызов срабатывает в ответ на функцию requestMtu(int) или в ответ на событие соединения. Параметры: GATT-клиент, вызвавший requestMtu; новый размер MTU; статус: GATT_SUCCESS, если операция записи была проведена успешно.

void onReadRemoteRssi (BluetoothGatt gatt, int rssi, int status) [Добавлено в API 18] Обратный вызов, сообщающий RSSI для данного соединения. Этот обратный вызов срабатывает при вызове readRemoteRssi(). Параметры: GATT-клиент, вызвавший readRemoteRssi(); значение RSSI удалённого устройства; статус: GATT_SUCCESS, если операция записи была проведена успешно.

void onReliableWriteCompleted (BluetoothGatt gatt, int status) [Добавлено в API 18] Обратный вызов, срабатывающий при завершении транзакции надёжной записи. Параметры: GATT-клиент, вызвавший executeReliableWrite(); статус: GATT_SUCCESS, если операция записи была проведена успешно.

void onServicesDiscovered (BluetoothGatt gatt, int status) [Добавлено в API 18] Обратный вызов, срабатывающий когда список удалённых услуг, характеристик и дескрипторов удалённого устройства был обновлён, т.е. были обнаружены новые услуги. Параметры: GATT-клиент, вызвавший discoverServices(); статус: GATT_SUCCESS, если операция записи была проведена успешно.

Android, работа с BLE — часть 1.

В последний год я разрабатывал Bluetooth Low Energy (BLE) приложения под iOS и это оказалось довольно простым. Далее было портирование их на Android… насколько это могло быть сложным?

devices

Могу точно сказать это было сложней, чем я представлял, пришлось приложить немало усилий для стабильной работы под Android. Я изучил много статей в свободном доступе, некоторые оказались ошибочными, многие были очень полезными и помогли в деле. В этой серии статей я хочу описать свои выводы, чтобы вы не тратили уйму времени на поиски, как я.

Особенности работы BLE под Android:

  • Google документация по BLE очень общая, в некоторых случаях нет важной информации или она устарела, примеры приложений не показывают как правильно использовать BLE. Я обнаружил лишь несколько источников, как правильно сделать BLE. Презентация Stuart Kent дает замечательный материал для старта. Для некоторых продвинутых тем есть хорошая статья Nordic.
  • Android BLE API это низкоуровневые операции, в реальных приложениях нужно использовать несколько слоев абстракции (как например сделано “из коробки” в iOS-CoreBluetooth). Обычно нужно самостоятельно сделать: очередь команд, bonding, обслуживание соединений, обработка ошибок и багов, мультипоточный доступ . Самые известные библиотеки: SweetBlue, RxAndroidBle и Nordic. На мой взгляд самая легкая для изучения — Nordic, см. детали тут.
  • Производители делают изменения в Android BLE стеке или полностью заменяют на свою реализацию. И надо учитывать разницу поведения для разных устройств в приложении. То что прекрасно работает на одном телефоне, может не работать на других! В целом не все так плохо, например реализация Samsung сделана лучше собственной реализации от Google!
  • В Android есть несколько известных (и неизвестных) багов которые должны быть обработаны, особенно в версиях 4,5 и 6. Более поздние версии работают намного лучше, но тоже имеют определенные проблемы, такие как случайные сбои соединения с ошибкой 133. Подробнее об этом ниже.

Не претендую на то, что я решил все проблемы, но мне удалось выйти на “приемлемый” уровень. Начнем со сканирования.

Сканирование устройств

Перед подключением к устройству вам нужно его просканировать. Это делается при помощи класса BluetoothLeScanner :

BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); BluetoothLeScanner scanner = adapter.getBluetoothLeScanner(); if (scanner != null)  scanner.startScan(filters, scanSettings, scanCallback); Log.d(TAG, "scan started"); > else  Log.e(TAG, "could not get scanner object"); >

Сканер пытается обнаружить устройства в соответствии с filters и scanSettings и при нахождении устройства вызывается scanCallback :

private final ScanCallback scanCallback = new ScanCallback()  @Override public void onScanResult(int callbackType, ScanResult result)  BluetoothDevice device = result.getDevice(); // . do whatever you want with this found device > @Override public void onBatchScanResults(ListScanResult> results)  // Ignore for now > @Override public void onScanFailed(int errorCode)  // Ignore for now > >;

В результате сканирования ScanResult есть объект BluetoothDevice , его используют для подключения к устройству. Прежде чем начать подключаться, давайте поговорим о сканировании подробнее, ScanResult содержит несколько полезных сведений об устройстве:

  • Advertisement data — массив байтов с информацией об устройстве, для большинства устройств это имя и UUIDы сервисов, можно задать в filters имя устройства и UUID сервисов для поиска конкретных устройств.
  • RSSI уровень — уровень сигнала (насколько близко устройство).
  • … дополнительные данные, см. документацию по ScanResult здесь.

Не забывайте про жизненный цикл Activity , onScanResult может вызываться многократно для одних и тех же устройств, при пересоздании Activity сканирование может запускаться повторно, вызываю лавину вызовов onScanResult .

Настраиваем фильтр для сканирования

Вообще можно передать null вместо фильтров и получить все ближайшие устройства, иногда это полезно, но чаще требуются устройства с определенным именем или набором UUID сервисов.

Сканирование устройств по UUID сервиса

Используется если вам необходимо найти устройства определенной категории, например мониторы артериального давления со стандартным сервисным UUID: 1810. При сканировании устройство может содержать в Advertisement data UUID сервис, который характеризует это устройство. На самом деле эти данные ненадежные, фактически сервисы могут не поддерживаться, или подделываеться Advertisement data данные, в общем тут есть творческий момент.

Прим. перевочика: одно из моих устройств со специфичной прошивкой, вообще не содержало список UUID сервисов в Advertisement data, хотя все остальные прошивки работали ок.

Пример сканирования службы с артериальным давлением:

UUID BLP_SERVICE_UUID = UUID.fromString("00001810-0000-1000-8000-00805f9b34fb"); UUID[] serviceUUIDs = new UUID[]BLP_SERVICE_UUID>; ListScanFilter> filters = null; if(serviceUUIDs != null)  filters = new ArrayList<>(); for (UUID serviceUUID : serviceUUIDs)  ScanFilter filter = new ScanFilter.Builder() .setServiceUuid(new ParcelUuid(serviceUUID)) .build(); filters.add(filter); > > scanner.startScan(filters, scanSettings, scanCallback);

Обратите внимание, короткий UUID (например 1810 ), называется 16-bit UUID является частью длинного 128-bit UUID (в данном случае 00001810-000000-1000-8000-000-00805f9b34fb ). Короткий UUID это BASE_PART длинного UUID, см. спецификацию здесь

Сканирование устройств по имени

Поиск устройств использует точное совпадение имени устройства, обычно это применяется в двух случаях:

  • поиск конкретного устройства
  • поиск конкретной модели устройста Например мой нагрудный напульсник Polar H7 определяется как “Polar H7 391BBB014”, первая часть — “Polar H7” общая для всех таких устройств этой модели, а последняя часть “391BBB014” — уникальный серийный номер. Это очень распространненая практика. Если вы хотите найти все устройства “Polar H7”, то фильтр по имени вам не поможет, придется искать подстроку у всех отсканированных устройств в ScanResult . Пример с поиском точно по имени:
String[] names = new String[]"Polar H7 391BB014">; ListScanFilter> filters = null; if(names != null)  filters = new ArrayList<>(); for (String name : names)  ScanFilter filter = new ScanFilter.Builder() .setDeviceName(name) .build(); filters.add(filter); > > scanner.startScan(filters, scanSettings, scanCallback);

Сканирование устройств по MAC-адресам.

Обычно применяется для переподключения к уже известным устройствам. Обычно мы не знаем MAC-адрес девайса, если не сканировали его раньше, иногда адрес печатается на коробке или на корпусе самого устройства, особенно это касается медицинских приборов. Существует другой способ повторного подключения, но в некоторых случаях придется еще раз сканировать устройство, например при очистке кеша Bluetooth.

String[] peripheralAddresses = new String[]"01:0A:5C:7D:D0:1A">; // Build filters list ListScanFilter> filters = null; if (peripheralAddresses != null)  filters = new ArrayList<>(); for (String address : peripheralAddresses)  ScanFilter filter = new ScanFilter.Builder() .setDeviceAddress(address) .build(); filters.add(filter); > > scanner.startScan(filters, scanSettings, scanByServiceUUIDCallback);

Вероятно вы уже поняли, что можно комбинировать в фильтре UUID, имя и MAC-адрес устройства. Выглядит неплохо, но на практике я не применял такое. Хотя может быть вам это пригодится?

Настройка ScanSettings

ScanSettings объясняют Android как он должен сканировать устройства. Там несколько настроек, которые можно задать, ниже полный пример:

ScanSettings scanSettings = new ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE) .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) .setReportDelay(0L) .build();

ScanMode

Безусловно, это самый важный параметр. Определяет метод и время сканирования в Bluetooth стеке. Такая операция требует много энергии и необходим контроль над этим процессом, чтобы не разрядить батарею телефона быстро. Есть 4 режима работы, в соответствии с руководством Nordics:

  1. SCAN_MODE_LOW_POWER . В этом режиме Android сканирует 0.5с, потом делает паузу на 4.5с. Поиск может занять относительно длительное время, зависит от того насколько часто устройство посылает пакет advertisement данных.
  2. SCAN_MODE_BALANCED . Время сканирования: 2с, время паузы: 3с, “компромисный” режим работы.
  3. SCAN_MODE_LOW_LATENCY . В этом случае, Android сканирует непрерывно, что очевидно требует больше энергозатрат, при этом получаются лучшие результаты сканирования. Режим подходит если вы хотите найти свое устройство как можно быстрее. Не стоит использовать для длительного сканирования.
  4. SCAN_MODE_OPPORTUNISTIC . Результаты будут получены, если сканирование выполняется другими приложениями! Строго говоря, это вообще не гарантирует, что обнаружится ваше устройство. Стек Android использует этот режим в случае долгого сканирования, для понижения качества результатов (см. ниже “Непрерывное сканирование”).

Callback Type

Настройка контролирует как будет вызываться callback со ScanResult в соответствии с заданными фильтрами, есть 3 варианта:

  1. CALLBACK_TYPE_ALL_MATCHES . Callback будет вызывать каждый раз, при получении advertisement пакета от устройств. На практике — каждые 200-500мс будет срабатывать сallback, в зависимости от частоты отправки advertisement пакетов устройствами.
  2. CALLBACK_TYPE_FIRST_MATCH . Callback сработает один раз для устройства, даже если оно далее будет снова посылать advertisement пакеты.
  3. CALLBACK_TYPE_MATCH_LOST . Callback будет вызыван если получен первый advertisement пакет от устройства и дальнейшие advertisement пакеты не обнаружены. Немного странное поведение.

В практике обычно используются настройка CALLBACK_TYPE_ALL_MATCHES или CALLBACK_TYPE_FIRST_MATCH . Правильный тип зависит от конкретного случая. Если не знаете — используйте CALLBACK_TYPE_ALL_MATCHES , это дает больше контроля при получении callback, если вы останавливаете сканирование после получения нужных результатов — фактически это CALLBACK_TYPE_FIRST_MATCH .

Match mode

Настройка того, как Android определяет “совпадения”.

  1. MATCH_MODE_AGGRESSIVE . Агрессивность обуславливается поиском минимального количества advertisement пакетов и устройств даже со слабым сигналом.
  2. MATCH_MODE_STICKY . В противоположность, этот режим требует большего количества advertisement пакетов и хорошего уровня сигнала от устройств.

Я не тестировал эти настройки подробно, но я в основном использую MATCH_MODE_AGGRESSIVE , это помогает быстрее найти устройства.

Number of matches

Параметр определяет сколько advertisement данных необходимо для совпадения.

  1. MATCH_NUM_ONE_ADVERTISEMENT . Одного пакета достаточно.
  2. MATCH_NUM_FEW_ADVERTISEMENT . Несколько пакетов нужно для соответствия.
  3. MATCH_NUM_MAX_ADVERTISEMENT . Максимальное количество advertisement данных, которые устройство может обработать за один временной кадр.

Нет большой необходимости в таком низкоуровнем контроле. Все что вам надо — быстро найти свое устройство, используйте первые 2 варианта.

Report delay

Задержка для вызова сallback в милисекундах. Если она больше нуля, Android будет собирать результаты в течение этого времени и вышлет их сразу все в обработчике onBatchScanResults . Важно понимать что onScanResult не будет вызываться. Обычно применяется, когда есть несколько устройств одного типа и мы хотим дать пользователю выбрать одно из них. Единственная проблема здесь — предоставить информацию пользователю для выбора, это должно быть больше чем MAC-адрес.

Важно: есть известный баг для Samsung S6 / Samsung S6 Edge, когда все результаты сканирования имеют один и тот же RSSI (уровень сигнала) при задержке больше нуля.

Прим. переводчика: в моему случае требовалось обнаружить 2 однотипных устройства, при этом каждое из них имело свое имя, которое отображалось в UI.

Кеширование Android Bluetooth стека

Процесс сканирования дает вам список BLE устройств и при этом данные устройств “кешируются” в Bluetooth стеке. Там хранится основная информация: имя, MAC-адрес, тип адреса (публичный, случайный), тип устройства (Classic, Dual, BLE) и т.д. Android нужны эти данные, чтобы подключится к устройству быстрее. Он кеширует все устройства, которые видит при сканировании. Для каждого из них записывается небольшой файл с данными. Когда вы пытаетесь подключиться к устройству, стек Android ищет соответствующий файл, чтобы прочитать данные для подключения. Важный момент — одного MAC-адреса недостаточно для успешного подключения к устройству!

Очистка кеша

Bluetooth cache, как и любой другой, не существует вечно и есть 3 ситуации когда он очищается:

  1. Выключение и включение системного переключателя Bluetooth,
  2. Перезагрузка телефона,
  3. Очистка в ручном режиме в настройках телефона.

Это достаточно неудобный момент для разработчиков, потому что телефон часто перезагружается, пользователь может включать-выключать самолетный режим. Есть еще различия между производителями телефонов, например на некоторых телефонах Samsung, кеш не очищался при выключении Bluetooth.

Это значит, что нельзя полагаться на данные об устройстве из BT кеша. Есть небольшой трюк, он поможет узнать закешировано ли устройство или нет:

// Get device object for a mac address BluetoothDevice device = bluetoothAdapter.getRemoteDevice(peripheralAddress) // Check if the peripheral is cached or not int deviceType = device.getType(); if(deviceType == BluetoothDevice.DEVICE_TYPE_UNKNOWN)  // The peripheral is not cached > else  // The peripheral is cached >

Это важно, если нужно подключиться к устройству позже, не сканируя его. Подробнее об этом позже.

Непрерывное сканирование?

Вообще сканирование не следует делать непрерывно, это очень энергоемкая операция. Пользователи любят, когда батарея их смартфона работает долго. Если вам действительно нужно постоянное сканирование, например при поиске BLE-маячков, выберите настройки сканирования с низким потреблением и ограничивайте время сканирования, например когда приложение находится только на передноем плане (foreground), либо сканируйте с перерывами.

В последнее время Google ограничивает (недокументированно) непрерывное сканирование:

  • c Android 8.1 сканирование без фильтров блокируется при выключенном экране. Если у вас нет никаких ScanFilters , Android приостановит сканирование, когда экран выключен и продолжит, когда экран снова будет включен. Комментарии от Google. Это очевидно очередной способ энергосбережения от Google.
  • c Android 7 вы можете сканировать только в течение 30 минут, после чего Android меняет параметры на SCAN_MODE_OPPORTUNISTIC . Очевидное решение, перезапускать сканирование с периодом менее, чем 30 мин. Посмотрите commit в исходном коде.
  • с Android 7 запуск и останов сканирования более 5 раз за 30 секунд временно отключает сканирование.

Непрерывное сканирование в фоне

Google значительно усложнил сканирование на переднем плане. Для фонового режима вы столкнетесь с еще большими трудностями! Новые версии Android имеют лимиты на работу служб в фоновом режиме, обычно после 10 минут работы, фоновый сервис прекращает свою работу принудительно. Посмотрите возможные решения этой проблемы:

  • Обсуждение на StackOverflow
  • Статья David Young

Прим. переводчика: я использовал Foreground Service , потому что после сканирования, будет длительный обмен данными с устройствами в процессе использованя аппы. Один из плюсов этого решения — работает в Doze Mode .

Проверка разрешений (permissions)

Есть еще несколько важных моментов, прежде чем мы закончим статью. Для начала сканирования нужны системные разрешения (permissions):

 android:name="android.permission.BLUETOOTH" />  android:name="android.permission.BLUETOOTH_ADMIN" />  android:name="android.permission.ACCESS_COARSE_LOCATION" />

Убедитесь что все разрешения одобрены, или запросите их у пользователя. Разрешение ACCESS_COARSE_LOCATION Google считает “опасным” и для него требуется обязательное согласие пользователя.

private boolean hasPermissions()  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)  if (getApplicationContext().checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)  requestPermissions(new String[]  Manifest.permission.ACCESS_COARSE_LOCATION >, ACCESS_COARSE_LOCATION_REQUEST); return false; > > return true; >

Прим. переводчика, в моем проекте для корректной работы с BLE потребовалось еще 2 разрешения: ACCESS_FINE_LOCATION (для API<23) и ACCESS_BACKGROUND_LOCATION обсуждение на Stackoverflow.

В итоге полный список разрешений включая версию Android10:

 android:name="android.permission.BLUETOOTH" />  android:name="android.permission.BLUETOOTH_ADMIN" />  android:name="android.permission.ACCESS_COARSE_LOCATION" />  android:name="android.permission.ACCESS_FINE_LOCATION" />  android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

После получения всех нужный разрешений, нужно проверить включен Bluetooth, если нет — используйте Intent для запуска запроса на включение:

BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (!bluetoothAdapter.isEnabled())  Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); >

Следующая статья: подключени и отключение

На данный момент мы рассмотрели сканирование, в следующей статье мы погрузимся глубже в процесс подключения и отключения к устройствам.

Записки мобильного разраба

  • Записки мобильного разраба
  • firsoffmaxim@gmail.com

Пишу разные штуки по разработке iOS/Android, которые реально пригодились.

Android UUID Bluetooth как клиенту, подключиться к серверу, если UUID знает только сервер?

Есть приложение-сервер на котором открыт сокет, оно слушает входящие блютуз соединения, на сервере имеется метод listenUsingRfcommWithServiceRecord(UUID) со своим UUID. Цитата «Соединение считается подтвержденным, когда удаленное устройство пошлет запрос на соединение с UUID, указанным при регистрации серверного сокета». Как устройству-клиенту узнать UUID на сервере чтобы подключиться к нему?

  • Вопрос задан более трёх лет назад
  • 417 просмотров

Комментировать
Решения вопроса 1

15432

Системный программист ^_^

Для этого сервер делает себя discoverable, а клиент запускает поиск Bluetooth устройств рядом. Вспомните pairing двух телефонов для передачи файлов или наушников с смартфоном. Можно зашить в конфиг клиента BSSID сервера и сразу к нему подключаться.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *