Android BLE API:未收到GATT通知

用于testing的设备:Nexus 4,Android 4.3

连接工作正常,但我的callback的onCharacteristicChanged方法永远不会被调用。 不过,我正在注册的onServicesDiscovered内使用onServicesDiscovered setCharacteristicNotification(char, true) onServicesDiscovered ,该函数甚至返回true。

设备日志(当通知应该出现/通过蓝牙设备发送时,实际上根本没有消息):

 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上一样(注册通知等)。

有没有其他人经历过这种可能的解决scheme?

看起来你忘了写描述符,告诉你的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? } 

@ Boni2k – 我有同样的问题。 就我而言,我有3个通知特征和一些读/写特征。

我所发现的是writeGattDescriptorreadCharacteristic之间存在一些依赖关系。 在发出任何readCharacteristic调用之前, 所有 writeGattDescriptors都必须先完成完成。

这是我使用Queues解决scheme。 现在我收到通知,一切正常工作:

像这样创build两个队列:

 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); } } 

和这个callback:

 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); } 

和我读callback:

 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()); } 

将值设置为描述符而不是放置descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) ,将descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE) 。 现在调用onCharacteristicChanged的callback函数。

我假设(你没有提供你的源代码),你没有像谷歌想要的那样实现它:

(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是缺less的。 在这种情况下,我相信低层次的通知会被触发,但是他们永远不会被报告给应用层。

在早期版本的Android接收通知(已注册的指示)中经历的问题,并在之后总是有一个奇怪的断开连接事件。 事实certificate,这是因为我们注册了五个特征的通知。

在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。

通过减less早期版本的注册数量,我们能够绕过这个限制。

那么,如果他/她不是蓝牙背景程序员,这个API名称肯定会给应用程序开发者带来一些困惑。

从蓝牙核心规范的angular度来看,核心规范引用4.2第3卷,第G部分3.3.3.3节“客户端特性configuration”:

特征描述符值是一个位域。 当一个位被设置时,该操作应该被启用,否则将不被使用。

和第4.10节

通知可以使用客户特性configuration描述符进行configuration(见第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; }' 

所以重要的是描述符写入操作。

我有另外一个原因,我想补充一下,因为这让我疯狂的一整天:

在我的Samsung Note 3上,我没有收到更改值的通知,而在我testing过的任何其他设备上使用相同的代码。

重新启动设备解决了所有问题。 很明显,但是当你遇到问题的时候,你忘了想想。

这是一个简单的方法来做到这一点,但让我知道,如果你看到任何缺点。

步骤1声明布尔variables

 private boolean char_1_subscribed = false; private boolean char_2_subscribed = false; private boolean char_3_subscribed = false; 

第2步订阅onServicesDiscoveredcallback中的第一个特征:

 @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步

onCharacteristicChangedcallback触发后,订阅任何其他人

 @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; } } 

这一个是为我工作:

通知主设备有一些特性改变,在你的外设上调用这个函数:

 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); } 

我也遇到了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