用于测试的设备:Nexus 4,Android 4.3
连接工作正常,但我的回调的onCharacteristicChanged
方法永远不会被调用。但是我在setCharacteristicNotification(char, true)
内使用onServicesDiscovered
注册通知,该函数甚至返回true。
设备日志(当通过蓝牙设备发送 时,实际上 no 消息):
07-28 18:15:06.936 16777-16809/de.ffuf.leica.sketch D/BluetoothGatt: setCharacteristicNotification() - uuid: 3ab10101-f831-4395-b29d-570977d5bf94 enable: true
07-28 18:15:06.936 4372-7645/com.android.bluetooth D/BtGatt.GattService: registerForNotification() - address=C9:79:25:34:19:6C enable: true
07-28 18:15:06.936 4372-7645/com.android.bluetooth D/BtGatt.btif: btif_gattc_reg_for_notification
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1018
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.GattService: onRegisterForNotifications() - address=null, status=0, registered=1, charUuid=3ab10101-f831-4395-b29d-570977d5bf94
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1016
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1018
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.GattService: onRegisterForNotifications() - address=null, status=0, registered=1, charUuid=3ab10102-f831-4395-b29d-570977d5bf94
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1016
07-28 18:15:06.946 4372-7684/com.android.bluetooth E/bt-btif: already has a pending command!!
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1013
07-28 18:15:06.946 4372-7684/com.android.bluetooth E/bt-btif: already has a pending command!!
07-28 18:15:06.946 4372-7645/com.android.bluetooth D/BtGatt.btif: btgattc_handle_event: Event 1013
07-28 18:15:06.946 4372-7684/com.android.bluetooth E/bt-btif: already has a pending command!!
07-28 18:15:06.976 4372-7645/com.android.bluetooth D/BtGatt.btif: btif_gattc_upstreams_evt: Event 9
GATT通知使用iOS工作正常,应用基本上与Android相同(注册通知等)。
有没有其他人在可能的解决方案中遇到过这种情况?
答案 0 :(得分:79)
好像你忘了写Descriptor来告诉你的BLE设备进入这种模式。请参阅处理http://developer.android.com/guide/topics/connectivity/bluetooth-le.html#notification
处描述符的代码行如果不设置此描述符,您将永远不会收到特征的更新。拨打setCharacteristicNotification
是不够的。这是一个常见的错误。
代码已截断
protected static final UUID CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
public boolean setCharacteristicNotification(BluetoothDevice device, UUID serviceUuid, UUID characteristicUuid,
boolean enable) {
if (IS_DEBUG)
Log.d(TAG, "setCharacteristicNotification(device=" + device.getName() + device.getAddress() + ", UUID="
+ characteristicUuid + ", enable=" + enable + " )");
BluetoothGatt gatt = mGattInstances.get(device.getAddress()); //I just hold the gatt instances I got from connect in this HashMap
BluetoothGattCharacteristic characteristic = gatt.getService(serviceUuid).getCharacteristic(characteristicUuid);
gatt.setCharacteristicNotification(characteristic, enable);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID);
descriptor.setValue(enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : new byte[] { 0x00, 0x00 });
return gatt.writeDescriptor(descriptor); //descriptor write operation successfully started?
}
答案 1 :(得分:40)
@ Boni2k - 我有同样的问题。就我而言,我有3个通知特性和少数读/写特性。
我找到的是writeGattDescriptor
和readCharacteristic
之间存在某种依赖关系。在发出任何readCharacteristic调用之前,所有的writeGattDescriptors 必须先完成和。
以下是我使用Queues
的解决方案。现在我收到通知,其他一切正常:
创建两个这样的队列:
private Queue<BluetoothGattDescriptor> descriptorWriteQueue = new LinkedList<BluetoothGattDescriptor>();
private Queue<BluetoothGattCharacteristic> characteristicReadQueue = new LinkedList<BluetoothGattCharacteristic>();
然后使用此方法在发现后立即编写所有描述符:
public void writeGattDescriptor(BluetoothGattDescriptor d){
//put the descriptor into the write queue
descriptorWriteQueue.add(d);
//if there is only 1 item in the queue, then write it. If more than 1, we handle asynchronously in the callback above
if(descriptorWriteQueue.size() == 1){
mBluetoothGatt.writeDescriptor(d);
}
}
和这个回调:
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "Callback: Wrote GATT Descriptor successfully.");
}
else{
Log.d(TAG, "Callback: Error writing GATT Descriptor: "+ status);
}
descriptorWriteQueue.remove(); //pop the item that we just finishing writing
//if there is more to write, do it!
if(descriptorWriteQueue.size() > 0)
mBluetoothGatt.writeDescriptor(descriptorWriteQueue.element());
else if(readCharacteristicQueue.size() > 0)
mBluetoothGatt.readCharacteristic(readQueue.element());
};
通常读取特征的方法如下:
public void readCharacteristic(String characteristicName) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
BluetoothGattService s = mBluetoothGatt.getService(UUID.fromString(kYourServiceUUIDString));
BluetoothGattCharacteristic c = s.getCharacteristic(UUID.fromString(characteristicName));
//put the characteristic into the read queue
readCharacteristicQueue.add(c);
//if there is only 1 item in the queue, then read it. If more than 1, we handle asynchronously in the callback above
//GIVE PRECEDENCE to descriptor writes. They must all finish first.
if((readCharacteristicQueue.size() == 1) && (descriptorWriteQueue.size() == 0))
mBluetoothGatt.readCharacteristic(c);
}
和我的回读:
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
readCharacteristicQueue.remove();
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
else{
Log.d(TAG, "onCharacteristicRead error: " + status);
}
if(readCharacteristicQueue.size() > 0)
mBluetoothGatt.readCharacteristic(readCharacteristicQueue.element());
}
答案 2 :(得分:11)
将值设置为描述符而不是放置descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
时,请设置descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)
。现在调用onCharacteristicChanged的回调。
答案 3 :(得分:6)
早期版本的Android接收通知(已注册的指示)中遇到的问题,之后总是有一个奇怪的断开连接事件。事实证明,这是因为我们注册了五个特征的通知。
LogCat中发现的错误是:
02-05 16:14:24.990 1271-1601/? E/bt-btif﹕ Max Notification Reached, registration failed.
在4.4.2之前,注册数量上限为4! 4.4.2将此限制增加到7。
通过减少早期版本中的注册数量,我们能够绕过这一限制。
答案 4 :(得分:6)
我假设(您未提供源代码)未将其实现为Google wanted:
(1)
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
然后
(2)
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
我想2失踪了。在这种情况下,我相信会触发低级通知,但它们永远不会报告给应用程序层。
答案 5 :(得分:4)
嗯,如果他/她不是蓝牙背景程序员,这个API名称肯定会给应用程序开发人员带来一些困惑。
从蓝牙核心规范的角度来看,引用核心规范4.2第3卷,第G部分第3.3.3.3节“客户端特性配置”:
特征描述符值是位字段。设置某个位时,应启用该操作,否则不会使用该操作。
和第4.10节
可以使用客户端特性配置描述符配置通知(参见第3.3.3.3节)。
明确指出,如果客户端想要从服务器接收通知(或指示,需要响应),则应将“通知”位写入1(否则“指示”位也为1)。
然而,名称“setCharacteristicNotification”给我们一个提示是,如果我们将此API的参数设置为TURE,客户端将收到通知;遗憾的是,此API仅设置本地位以允许在发出远程通知时发送给应用程序的通知。请参阅Bluedroid的代码:
/*******************************************************************************
**
** Function BTA_GATTC_RegisterForNotifications
**
** Description This function is called to register for notification of a service.
**
** Parameters client_if - client interface.
** bda - target GATT server.
** p_char_id - pointer to GATT characteristic ID.
**
** Returns OK if registration succeed, otherwise failed.
**
*******************************************************************************/
tBTA_GATT_STATUS BTA_GATTC_RegisterForNotifications (tBTA_GATTC_IF client_if,
BD_ADDR bda,
tBTA_GATTC_CHAR_ID *p_char_id)
{
tBTA_GATTC_RCB *p_clreg;
tBTA_GATT_STATUS status = BTA_GATT_ILLEGAL_PARAMETER;
UINT8 i;
if (!p_char_id)
{
APPL_TRACE_ERROR("deregistration failed, unknow char id");
return status;
}
if ((p_clreg = bta_gattc_cl_get_regcb(client_if)) != NULL)
{
for (i = 0; i < BTA_GATTC_NOTIF_REG_MAX; i ++)
{
if ( p_clreg->notif_reg[i].in_use &&
!memcmp(p_clreg->notif_reg[i].remote_bda, bda, BD_ADDR_LEN) &&
bta_gattc_charid_compare(&p_clreg->notif_reg[i].char_id, p_char_id))
{
APPL_TRACE_WARNING("notification already registered");
status = BTA_GATT_OK;
break;
}
}
if (status != BTA_GATT_OK)
{
for (i = 0; i < BTA_GATTC_NOTIF_REG_MAX; i ++)
{
if (!p_clreg->notif_reg[i].in_use)
{
memset((void *)&p_clreg->notif_reg[i], 0, sizeof(tBTA_GATTC_NOTIF_REG));
p_clreg->notif_reg[i].in_use = TRUE;
memcpy(p_clreg->notif_reg[i].remote_bda, bda, BD_ADDR_LEN);
p_clreg->notif_reg[i].char_id.srvc_id.is_primary = p_char_id->srvc_id.is_primary;
bta_gattc_cpygattid(&p_clreg->notif_reg[i].char_id.srvc_id.id, &p_char_id->srvc_id.id);
bta_gattc_cpygattid(&p_clreg->notif_reg[i].char_id.char_id, &p_char_id->char_id);
status = BTA_GATT_OK;
break;
}
}
if (i == BTA_GATTC_NOTIF_REG_MAX)
{
status = BTA_GATT_NO_RESOURCES;
APPL_TRACE_ERROR("Max Notification Reached, registration failed.");
}
}
}
else
{
APPL_TRACE_ERROR("Client_if: %d Not Registered", client_if);
}
return status;
}'
所以最重要的是描述符写操作。
答案 6 :(得分:1)
这是一种简单的方法,但如果您发现任何缺点,请告诉我。
第1步 声明布尔变量
private boolean char_1_subscribed = false;
private boolean char_2_subscribed = false;
private boolean char_3_subscribed = false;
第2步 订阅onServicesDiscovered回调中的第一个特性:
@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);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(!char_1_subscribed)
subscribeToNotification(gatt.getService(UUID_SERVICE).getCharacteristic(UUID_CHAR_1)); char_1_subscribed = true;
}
第3步
在onCharacteristicChanged回调触发后订阅任何其他人
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
if(UUID_CHAR_1.equals(characteristic.getUuid()))
{
if(!char_1_subscribed)
subscribeToNotification(gatt.getService(UUID_SERVICE).getCharacteristic(UUID_CHAR_2)); char_2_subscribed = true;
}
if(UUID_CHAR_2.equals(characteristic.getUuid()))
{
if(!char_3_subscribed)
subscribeToNotification(gatt.getService(UUID_SERVICE).getCharacteristic(UUID_CHAR_3)); char_3_subscribed = true;
}
}
答案 7 :(得分:0)
我有另一个原因,我想补充一下,因为它让我整天疯狂:
在我的Samsung Note 3上,我没有收到更改值的通知,而相同的代码在我测试过的任何其他设备上都有效。
重启设备解决了所有问题。很明显,但当你遇到问题时,你会忘记思考。
答案 8 :(得分:0)
这个适合我:
通知主设备某些特性发生变化,请在您的外围设备上调用此功能:
private BluetoothGattServer server;
//init....
//on BluetoothGattServerCallback...
//call this after change the characteristic
server.notifyCharacteristicChanged(device, characteristic, false);
主设备中的:发现服务后启用setCharacteristicNotification:
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
services = mGatt.getServices();
for(BluetoothGattService service : services){
if( service.getUuid().equals(SERVICE_UUID)) {
characteristicData = service.getCharacteristic(CHAR_UUID);
for (BluetoothGattDescriptor descriptor : characteristicData.getDescriptors()) {
descriptor.setValue( BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
mGatt.writeDescriptor(descriptor);
}
gatt.setCharacteristicNotification(characteristicData, true);
}
}
if (dialog.isShowing()){
mHandler.post(new Runnable() {
@Override
public void run() {
dialog.hide();
}
});
}
}
现在您可以检查您的特征值是否发生变化,例如onCharacteristicRead函数(这也适用于onCharacteristicChanged函数):
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
Log.i("onCharacteristicRead", characteristic.toString());
byte[] value=characteristic.getValue();
String v = new String(value);
Log.i("onCharacteristicRead", "Value: " + v);
}
答案 9 :(得分:-1)
我也遇到过Android上BLE通知的问题。然而,这是一个完全有效的演示,包括BluetoothAdapter
周围的蓝牙包装。包装器称为BleWrapper
,随附在 Application Accelerator 包中的名为 BLEDemo 的演示应用程序。在此下载:https://developer.bluetooth.org/Pages/Bluetooth-Android-Developers.aspx。在下载之前,您需要在右上角注册您的电子邮件地址。该项目的许可证允许免费使用,代码修改和发布。
根据我的经验,Android演示应用程序可以很好地处理BLE通知订阅。我还没有在代码中潜入太多,看看包装器实际上是如何包装的。
Play商店中提供了一个Android应用程序,它是应用程序加速器演示的自定义。由于用户界面看起来几乎相同,我认为它也使用BleWrapper
。在此处下载应用:https://play.google.com/store/apps/details?id=com.macdom.ble.blescanner