我已经挣扎了好一阵子,试图让我的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(接收新数据并处理)。
现在,有两件事困扰着我。
当连接到设备时,有时它能马上工作,有时不能。如果不能,我必须通过另一个应用程序(如Bluefruit Connect)去连接到设备,然后再次启动我的应用程序,然后它将连接 。
当它连接时,它会通过服务发现和所有,在setNotificationForCharacteristic函数中,所有的东西都被正确调用(我得到onDescriptorWrite被调用和所有),但我从未收到任何通知。
因为我是在我的BLE外设上运行代码的人,所以我可以保证我试图获取数据的特性是NOTIFY类型(而不是例如INDICATE)。
如果能帮上忙的话,唯一的NOTIFY特性是尽可能频繁地发送一个56字节的数组,里面充满了14个浮点数。早期使用Web蓝牙或NativeScript(使用nativescript-bluetooth插件)的原型向我展示了这实际上是可行的,在这些情况下,我大约每90毫秒就能得到结果。
我想我已经把这段代码重写了3次,我有点绝望了,所以任何在正确方向上的帮助都是感激的。
非常感谢!
编辑:我想我已经把这段代码重写了3次了,所以非常感谢你的帮助。: 为了科学起见,我试着把设备上的特性转换为READ特性,然后每隔一秒生成一个线程读取它,而不是等待通知。好吧,onCharacteristicRead被调用了,但是传递给它的字节数组总是长度为0......
我发现了问题--它与Java代码完全无关,而是由我的BLE设备中的一个硬件故障引起的,该故障导致所有的值更新被丢弃。