Я уже давно борюсь, пытаясь заставить мое устройство BLE связаться с моим android приложением.
Прежде всего, вот мой полный код для обработки BLE:
BleCentral. java
import java.util.HashMap;
import java.util.function.Supplier;
import android.util.Log;
import android.content.Context;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothManager;
public class BleCentral
{
private HashMap<String, BluetoothGatt> m_connectedDevices;
public BleCentral()
{
m_connectedDevices = new HashMap<>();
}
private BluetoothAdapter m_GetAdapter(Context ctx)
{
final BluetoothManager bleMgr = (BluetoothManager)(ctx.getSystemService(Context.BLUETOOTH_SERVICE));
BluetoothAdapter adapter = bleMgr.getAdapter();
if (adapter == null || !adapter.isEnabled())
{
Log.e("BLE Central", "BLE either not available or not enabled. Please do something about it.");
return null;
}
return adapter;
}
public BluetoothDevice GetDevice(Context ctx, String address)
{
return m_GetAdapter(ctx).getRemoteDevice(address);
}
public <T extends BlePeripheral>
T Connect(Context ctx, String address, Supplier<T> supplier)
{
BluetoothDevice device = GetDevice(ctx, address);
T result = supplier.get();
m_connectedDevices.put(address, device.connectGatt(ctx, false, result, BluetoothDevice.TRANSPORT_LE));
return result;
}
}
BlePeripheral. java
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.UUID;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattService;
public abstract class BlePeripheral
extends BluetoothGattCallback
{
private final String kCCC_DESCRIPTOR_UUID = "00002902-0000-1000-8000-00805f9b34fb";
private Handler m_handler;
private boolean m_deviceReady;
private BluetoothGatt m_bluetoothGatt;
private Queue<CmdQueueItem> m_cmdQueue;
private boolean m_cmdQueueProcessing;
// ------------------------------------------------------------------------
// -- Own methods
protected BlePeripheral()
{
m_handler = new Handler();
m_deviceReady = false;
m_bluetoothGatt = null;
m_cmdQueue = new LinkedList<>();
m_cmdQueueProcessing = false;
}
public boolean IsDeviceReady()
{ return m_deviceReady; }
public void EnableNotifications(UUID service, UUID characteristic)
{ EnqueueSetNotificationForCharacteristic(m_bluetoothGatt.getService(service).getCharacteristic(characteristic), true); }
public void DisableNotifications(UUID service, UUID characteristic)
{ EnqueueSetNotificationForCharacteristic(m_bluetoothGatt.getService(service).getCharacteristic(characteristic), false); }
protected void WriteCharacteristic(UUID service, UUID characteristic, byte[] value, boolean requestResponse)
{ EnqueueWriteCharacteristic(m_bluetoothGatt.getService(service).getCharacteristic(characteristic), value, requestResponse); }
protected void ReadCharacteristic(UUID service, UUID characteristic)
{ EnqueueReadCharacteristic(m_bluetoothGatt.getService(service).getCharacteristic(characteristic)); }
// ------------------------------------------------------------------------
// -- BluetoothGattCallback overrides
@Override
public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState)
{
super.onConnectionStateChange(gatt, status, newState);
final BluetoothDevice device = gatt.getDevice();
switch (status)
{
case 133: /* GATT_ERROR */
Log.e("BLE", "GATT_ERROR");
gatt.close();
try { Thread.sleep(150); }
catch (InterruptedException e) { e.printStackTrace(); }
break;
case 0: /* GATT_SUCCESS */
switch (newState)
{
case BluetoothGatt.STATE_CONNECTED:
Log.i("BLE", "Connected to " + device.getAddress() + " (" + device.getName() + ")");
m_bluetoothGatt = gatt;
int delayWhenBonded = (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) ? 2000 : 0;
switch (device.getBondState())
{
case BluetoothDevice.BOND_NONE:
delayWhenBonded = 0;
case BluetoothDevice.BOND_BONDED:
m_handler.postDelayed(new Runnable() {
@Override
public void run() {
boolean result = gatt.discoverServices();
if (!result)
Log.e("BLE", "discoverServices() failed to start");
}
}, delayWhenBonded);
break;
case BluetoothDevice.BOND_BONDING:
Log.i("BLE", "Waiting for bonding to complete");
break;
}
break;
case BluetoothGatt.STATE_DISCONNECTED:
gatt.close();
break;
}
break;
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status)
{
super.onServicesDiscovered(gatt, status);
if (status == 129 /* GATT_INTERNAL_ERROR */)
{
Log.e("BLE", "Service discovery failed");
gatt.disconnect();
return;
}
final List<BluetoothGattService> services = gatt.getServices();
Log.i("BLE", "Discovered " + services.size() + " services for " + gatt.getDevice().getAddress());
m_deviceReady = SetupDevice(gatt);
if (!m_deviceReady)
Log.e("BLE", "Peripheral does not comply to this device's requirements");
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic)
{
super.onCharacteristicChanged(gatt, characteristic);
Log.i("BLE", "onCharacteristicChanged: " + characteristic.getUuid());
final byte[] value = new byte[characteristic.getValue().length];
System.arraycopy(characteristic.getValue(), 0, value, 0, characteristic.getValue().length);
OnUpdate(characteristic.getService().getUuid(), characteristic.getUuid(), value);
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status)
{
super.onCharacteristicRead(gatt, characteristic, status);
ProcessCmdQueue();
Log.i("BLE", "onCharacteristicRead: " + characteristic.getUuid());
final byte[] value = new byte[characteristic.getValue().length];
System.arraycopy(characteristic.getValue(), 0, value, 0, characteristic.getValue().length);
OnUpdate(characteristic.getService().getUuid(), characteristic.getUuid(), value);
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status)
{
super.onCharacteristicWrite(gatt, characteristic, status);
ProcessCmdQueue();
Log.i("BLE", "onCharacteristicWrite: " + characteristic.getUuid());
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status)
{
super.onDescriptorWrite(gatt, descriptor, status);
ProcessCmdQueue();
final BluetoothGattCharacteristic parentCharacteristic = descriptor.getCharacteristic();
if(status != 0 /* GATT_SUCCESS */)
{
Log.e("BLE", "WriteDescriptor failed for characteristic " + parentCharacteristic.getUuid());
return;
}
if(descriptor.getUuid().equals(UUID.fromString(kCCC_DESCRIPTOR_UUID)))
{
if(status == 0 /* GATT_SUCCESS */)
{
byte[] value = descriptor.getValue();
if (value != null)
{
if (value[0] != 0)
Log.i("BLE", "Characteristic " + parentCharacteristic.getUuid() + " is now notifying");
else
Log.i("BLE", "Characteristic " + parentCharacteristic.getUuid() + " is now NOT notifying");
}
}
}
}
// ------------------------------------------------------------------------
// -- Command Queue implementation
/* An enqueueable write operation - notification subscription or characteristic write */
private class CmdQueueItem
{
BluetoothGattCharacteristic characteristic;
byte[] dataToWrite; // Only used for characteristic write
boolean writeWoRsp; // Only used for characteristic write
boolean enabled; // Only used for characteristic notification subscription
public m_queueItemType type;
}
private enum m_queueItemType
{
SubscribeCharacteristic,
ReadCharacteristic,
WriteCharacteristic
}
/* queues enables/disables notification for characteristic */
public void EnqueueSetNotificationForCharacteristic(BluetoothGattCharacteristic ch, boolean enabled)
{
// Add to queue because shitty Android GATT stuff is only synchronous
CmdQueueItem m_queueItem = new CmdQueueItem();
m_queueItem.characteristic = ch;
m_queueItem.enabled = enabled;
m_queueItem.type = m_queueItemType.SubscribeCharacteristic;
EnqueueBleCommand(m_queueItem);
}
/* queues enables/disables notification for characteristic */
public void EnqueueWriteCharacteristic(final BluetoothGattCharacteristic ch, final byte[] dataToWrite, boolean requestResponse)
{
// Add to queue because shitty Android GATT stuff is only synchronous
CmdQueueItem m_queueItem = new CmdQueueItem();
m_queueItem.characteristic = ch;
m_queueItem.dataToWrite = dataToWrite;
m_queueItem.writeWoRsp = !requestResponse;
m_queueItem.type = m_queueItemType.WriteCharacteristic;
EnqueueBleCommand(m_queueItem);
}
/* request to fetch newest value stored on the remote device for particular characteristic */
public void EnqueueReadCharacteristic(BluetoothGattCharacteristic ch)
{
// Add to queue because shitty Android GATT stuff is only synchronous
CmdQueueItem m_queueItem = new CmdQueueItem();
m_queueItem.characteristic = ch;
m_queueItem.type = m_queueItemType.ReadCharacteristic;
EnqueueBleCommand(m_queueItem);
}
/**
* Add a transaction item to transaction queue
* @param m_queueItem
*/
private void EnqueueBleCommand(CmdQueueItem m_queueItem)
{
m_cmdQueue.add(m_queueItem);
// If there is no other transmission processing, go do this one!
if (!m_cmdQueueProcessing)
ProcessCmdQueue();
}
/**
* Call when a transaction has been completed.
* Will process next transaction if queued
*/
private void ProcessCmdQueue()
{
if (m_cmdQueue.size() <= 0)
{
m_cmdQueueProcessing = false;
return;
}
m_cmdQueueProcessing = true;
CmdQueueItem m_queueItem = m_cmdQueue.remove();
switch (m_queueItem.type)
{
case WriteCharacteristic:
writeDataToCharacteristic(m_queueItem.characteristic, m_queueItem.dataToWrite, m_queueItem.writeWoRsp);
break;
case SubscribeCharacteristic:
setNotificationForCharacteristic(m_queueItem.characteristic, m_queueItem.enabled);
break;
case ReadCharacteristic:
requestCharacteristicValue(m_queueItem.characteristic);
break;
}
}
public void requestCharacteristicValue(BluetoothGattCharacteristic ch)
{
if (m_bluetoothGatt == null)
return;
m_bluetoothGatt.readCharacteristic(ch);
}
private void writeDataToCharacteristic(final BluetoothGattCharacteristic ch, final byte[] dataToWrite, boolean writeWoRsp)
{
if (m_bluetoothGatt == null || ch == null)
return;
ch.setValue(dataToWrite);
ch.setWriteType(writeWoRsp ? BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE : BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
m_bluetoothGatt.writeCharacteristic(ch);
}
/* enables/disables notification for characteristic */
private void setNotificationForCharacteristic(BluetoothGattCharacteristic ch, boolean enabled)
{
if (m_bluetoothGatt == null || ch == null)
return;
ch.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
boolean success = m_bluetoothGatt.setCharacteristicNotification(ch, enabled);
if(success)
{
// This is also sometimes required (e.g. for heart rate monitors) to enable notifications/indications
// see: https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
BluetoothGattDescriptor descriptor = ch.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if(descriptor != null)
{
if (enabled)
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
else
descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
if (m_bluetoothGatt.writeDescriptor(descriptor))
{
Log.i("BLE Peripheral", "SetNotification (Set + CCC) succeeded!");
}
}
else
Log.i("BLE Peripheral", "SetNotification (Set only) succeeded!");
}
else
Log.e("BLE Peripheral", "SetNotification failed!");
}
// ------------------------------------------------------------------------
// -- Abstract methods
protected abstract boolean SetupDevice(BluetoothGatt gatt);
protected abstract void OnUpdate(UUID service, UUID characteristic, final byte[] value);
}
Код для создания и подключиться к устройству
BleCentral central = new BleCentral();
m_customDevice = central.Connect(this, deviceMacAddress, () -> new CustomDevice());
CustomDevice просто наследует класс BlePeripheral, реализуя SetupDevice (который проверяет наличие всех служб и характеристик) и OnUpdate (который получает новые данные и обрабатывает их).
Теперь меня беспокоят две вещи:
При подключении к устройству иногда работает сразу, а иногда нет. Если нет, мне нужно go подключиться к устройству через другое приложение, такое как Bluefruit Connect, затем снова запустить мое приложение, и оно подключится;
Когда оно подключится, оно проходит поиск службы и все, и в функции setNotificationForCharacteristi c все вызывается правильно (вызывается onDescriptorWrite и все), но я никогда не получаю никаких уведомлений.
Поскольку я тот, что стоит за кодом, работающим на моем BLE-периферии, я могу гарантировать, что характеристика c, от которой я пытаюсь получить данные, имеет тип NOTIFY (а не, например, INDICATE).
Если это может помочь в любом случае, единственная характеристика NOTIFY c - это посылает 56-байтовый массив, заполненный 14 числами с плавающей запятой, так часто, как это возможно. Ранние прототипы с Web Bluetooth или NativeScript (с плагином nativescript-bluetooth) показали мне, что это действительно работает, и в этих случаях я получаю результаты примерно каждые 90 миллисекунд.
Я думаю, что переписал этот код около 3 времена уже и я немного отчаялся, поэтому любая помощь в правильном направлении приветствуется. : D
Большое спасибо!
Редактировать : Просто для науки я попытался переключить характеристики c на ЧИТАТЬ на устройство, затем порождает поток, читающий его каждую секунду, вместо ожидания уведомлений. Хорошо, вызывается onCharacteristicRead, но переданный ему байтовый массив всегда имеет длину ноль ...