【问题标题】:Android BLE API: GATT Notification not receivedAndroid BLE API:未收到 GATT 通知
【发布时间】:2013-07-28 10:24:10
【问题描述】:

用于测试的设备:Nexus 4、Android 4.3

连接工作正常,但我的回调的onCharacteristicChangedMethod 从未被调用。但是我在onServicesDiscovered 内使用setCharacteristicNotification(char, true) 注册通知,该函数甚至返回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 上的工作基本相同(注册通知等)。

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

【问题讨论】:

    标签: android bluetooth bluetooth-lowenergy android-4.3-jelly-bean gatt


    【解决方案1】:

    您似乎忘记编写告诉您的 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? 
    }
    

    【讨论】:

    • 我刚刚尝试了 Android 4.3 示例中的代码,但未能通知。你能分享一段与通知相关的代码吗?谢谢。
    • @Boni2k GATT_NO_RESOURCES = -128 或 128; GATT_INTERNAL_ERROR = -127 或 129; GATT_ERROR = -123 或 133; GATT_ALREADY_OPEN = -115 或 141。我是从三星 BLE Sdk 家伙那里得到的。这些状态代码似乎与 Android BLE SDK 相同,只是类型为带符号字节。完整列表在这里:img-developer.samsung.com/onlinedocs/samsung_ble_docs_200/…
    • 我看到我必须在 setCharacteristicNotification 和 writeDescriptor 之间引入延迟(例如:Thread.sleep 或 Handler.post)。如果我不这样做,我永远不会得到 onCharacteristicChanged。谁能确认他们看到类似的东西?请注意,developer.android.com/guide/topics/connectivity/… 的示例在两个调用之间有一个微妙的“...”,我看到这表明您不能总是可靠地立即背靠背地执行这两个调用。
    • 这不是错误,这是 API 中的错误。 setCharacteristicNotification 的全部意义在于启用通知——应该设置这个。
    • 确实应该记录这些额外的必要步骤。开发人员在 Android 上实现 BLE 通信时需要注意太多且太重的未记录警告。
    【解决方案2】:

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

    我发现writeGattDescriptorreadCharacteristic 之间存在一些依赖关系。在您发出任何 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());
        }
    

    【讨论】:

    • 注意 Android 中 BLE 实现的同步特性。当你不使用这样的队列时,你可以很容易地不情愿地取消请求。请参阅stackoverflow.com/questions/18011816/… 我不确定,如果您的假设是正确的,那么 writeDescriptor() 需要在 readCharacteristic 之前完成。也许您的解决方案(队列)只是照顾同步性质并以这种方式解决您的问题。我实际上是在写描述符之前做读写特性的。
    • 我相信你是对的。事务是同步的,顺序无关紧要。任何事务都必须在发出另一个事务之前完成它的回调,并且一切都会正常工作。对所有事务使用一个队列并使用类型(写入描述符、读取特性、写入特性等)标记每个队列可能是有意义的
    • 你的代码很好用,但是我在这里遇到了一些问题,因为我是 BLE 新手。意思是 readCharacteristicQueue 和 readQueue 我得到了错误。请附上一些完整的代码以消除此错误。谢谢
    • 代码在理论上看起来不错,但有时我没有得到 onCharacteristicWrite 回调所以......
    • Nordic 编写了一个类来处理这个问题,称为GattManager。见github.com/NordicSemiconductor/puck-central-android
    【解决方案3】:

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

    【讨论】:

    • 你能澄清一下“指示”应该做什么吗?根据文档,我确定在这种情况下设置的正确标志是 ENABLE_NOTIFICATION_VALUE 但您现在是此页面上第二个建议 ENABLE_INDICATION_VALUE 是正确标志的人。每个有什么区别和用例?
    • 这取决于蓝牙设备内部的蓝牙配置文件实现。它使用“通知”或“指示”来发布更新。所以你必须找出你的设备使用的是哪一个。
    • 就像 Boni2k 说的,它取决于蓝牙配置文件。如果您的服务遵循标准配置文件,您应该能够在 [此处] (developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx) 找到该信息。如果您想以编程方式了解这一点,您可以为您的特征调用 getProperties() 方法,并对您要检查的属性执行按位与操作,以检查它是否不同于 0,然后它支持该活动。
    • 令我困扰的是,Android 文档指出 setCharacteristicNotifications 适用于通知或指示。所以我假设他们正在内部检查属性并将正确的标志写入特征。他们还在他们的示例中使用了与某些心率监视器一起使用的方法。你知道方法是否有缺陷吗?我的理解是,尽管某些设备可能会接受,但不能同时设置指示/通知标志。如果他们这样做,那么我会说他们的方法是错误的。
    【解决方案4】:

    我假设(您没有提供源代码)您没有将其实现为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】:

      在早期版本的 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?言语无法形容这是多么可悲。即使是 7 也是可笑的。
      【解决方案6】:

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

      从蓝牙核心规范的角度来看,引用核心规范 4.2 Vol 3, Part G section 3.3.3.3 “Client Characteristic Configuration”:

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

      和第 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;
      }'
      

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

      【讨论】:

        【解决方案7】:

        这是一种简单的方法,但如果您发现任何缺点,请告诉我。

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

        【讨论】:

          【解决方案8】:

          这个对我有用:

          要通知主设备某些特性发生变化,请在您的外设上调用此函数:

          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】:

            我还有一个想补充的原因,因为它让我一整天都发疯了:

            在我的三星 Note 3 上,我没有收到更改值的通知,而相同的代码在我测试过的任何其他设备上都可以运行。

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

            【讨论】:

              【解决方案10】:

              我也遇到过 Android 上的 BLE 通知问题。然而,有一个完整的演示,其中包括一个围绕BluetoothAdapter 的蓝牙包装器。包装器名为BleWrapper,并附带名为BLEDemo 的演示应用程序,包含在Application Accelerator 包中。在此处下载: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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2015-01-20
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2015-07-17
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多