Android BLE API:未收到GATT通知


70

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

连接工作正常,但onCharacteristicChanged从未调用我的回调方法。但是我正在使用setCharacteristicNotification(char, true)inside注册通知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上的通知(注册通知等)相同。

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

Answers:


83

好像您忘了编写描述符来告诉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示例中的代码,但未通知。您能否分享与通知相关的代码?谢谢。
ZhangChn

2
@ Boni2k GATT_NO_RESOURCES = -128或128; GATT_INTERNAL_ERROR = -127或129; GATT_ERROR = -123或133; GATT_ALREADY_OPEN = -115或141。我从Samsung BLE Sdk团队那里获得了它们。这些状态代码似乎与Android BLE SDK相同,只是将符号字节作为类型。整个列表在这里:img-developer.samsung.com/onlinedocs/samsung_ble_docs_200/…–
OneWorld

1
我看到我必须在setCharacteristicNotification和writeDescriptor之间引入延迟(例如:Thread.sleep或Handler.post)。如果不这样做,就永远不会得到onCharacteristicChanged。谁能确认他们看到类似的东西?请注意,developer.android.com / guide / topics / connectivity /…上的示例在两个调用之间有一个微妙的“ ...”,我看到这表明您不能始终可靠地始终将这两个调用立即返回到背部。
swooby 2015年

3
这不是错误,而是API中的错误。setCharacteristicNotification的整点是让notifications--应该设置这个。
Glenn Maynard

6
这些额外的必要步骤应确实记录在案。在Android上实现BLE通信时,开发人员需要注意的太多且太多的未记录警告。
Lars Blumberg 2015年

42

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

我确实发现writeGattDescriptor和之间存在某些依赖关系readCharacteristic所有的writeGattDescriptors的必须是第一位的,并完成你发出任何readCharacteristic调用之前。

这是我使用的解决方案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());
    }

4
注意Android中BLE实现的同步性。不使用队列时,您很容易不愿意取消请求。请参阅stackoverflow.com/questions/18011816/…我不确定,如果您的假设正确,则必须在readCharacteristic之前完成writeDescriptor()。也许您的解决方案(队列)只是照顾了同步性,并以此方式解决了您的问题。实际上,在编写描述符之前,我确实会读写特性。
OneWorld 2013年

我相信您是正确的。事务是同步的,顺序无关紧要。任何事务必须在发出另一个事务之前完成它的回调,一切才能正常工作。在所有事务中使用一个队列,并仅用一种类型(写描述符,读特征,写特征等)标记每个事务可能很有意义
miznick

您的代码很不错,但是在这里使用它时我遇到了一些问题,因为我是BLE新手,所以他们遇到了一些问题。关于readCharacteristicQueue和readQueue的意思是我越来越错误了。请附加一些完整的代码来摆脱此错误。谢谢
Deepak

从理论上讲,代码看起来还不错,但有时我没有收到onCharacteristicWrite回调,所以……
Thomas

1
北欧编写了一个处理这个问题的课GattManager。参见github.com/NordicSemiconductor/puck-central-android
Timmmm,2015年

11

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


您能否阐明“指示”应该做什么?根据文档,我确定在这种情况下要设置的正确标志是ENABLE_NOTIFICATION_VALUE,但是您现在是此页面上第二个人,建议ENABLE_INDICATION_VALUE是正确的标志。两者的区别和用例是什么?
jkraybill

2
这取决于蓝牙设备内部的Bluetooth Profile实现。它使用“通知”或“指示”来发布更新。因此,您必须找出设备使用的设备。
Boni2k

1
就像Boni2k所说的,这取决于蓝牙配置文件。如果您的服务遵循标准配置文件,那么您应该可以在[此处]找到该信息(developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx)。如果要通过编程方式了解此信息,则可以针对您的特征调用getProperties()方法,并对要检查其是否不同于0的属性进行按位与运算,然后它支持该活动。
user2926265

这里让我感到困扰的是,Android文档指出setCharacteristicNotifications适用于EITHER通知或指示。因此,我假设他们正在内部检查属性并将正确的标志写入特征。他们还在示例中使用了与某些HeartRate监视器一起使用的方法。您知道该方法是否有缺陷吗?我的理解是,尽管某些设备可以接受,但不能同时设置两个指示/通知标志。如果他们这样做,那我会说他们的方法是错误的。
Brian Reinhold 2014年

6

我假设(您未提供源代码)您没有按照Google的要求实现它:

(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个。在那种情况下,我相信会触发低级通知,但是它们永远不会报告给应用程序层。


6

早期版本的Android中遇到的问题会收到通知(已注册指示),此后总是发生奇怪的断开连接事件。事实证明,这是因为我们注册了五个特征的通知。

在LogCat中发现的错误是:

02-05 16:14:24.990    1271-1601/? E/bt-btifMax Notification Reached, registration failed.

在4.4.2之前,注册数量上限为4!4.4.2将该限制增加到7。

通过减少早期版本中的注册数量,我们能够克服这一限制。


5
上限为4?言语无法形容这是多么可悲。甚至7也太可笑了。
格雷戈里·希格利2015年

4

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

从蓝牙核心规范角度看,引用核心规范4.2第3卷第G部分第3.3.3.3节“客户端特征配置”:

特征描述符值是位字段。当某个位置1时,该动作应被使能,否则将不被使用。

和第4.10节

可以使用客户端特征配置描述符来配置通知(请参见第3.3.3.3节)。

明确指出如果客户端要从服务器接收通知(或指示,需要响应),则应将“ Notification”位写入1(否则将“ Indication”位也写入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;
}'

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


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

第三步

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

1

这个正在为我工​​作:

通知主设备某些特性已更改,请在您的外围设备上调用此函数:

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

0

我还有一个理由想补充一下,这使我整日变得疯狂:

在我的Samsung Note 3上,当相同的代码在我测试过的任何其他设备上都可以使用时,我没有收到更改值的通知。

重新启动设备可以解决所有问题。显而易见,但是当您遇到问题时,您会忘记考虑。


-1

我也遇到了Android上的BLE通知问题。但是,有一个可以正常工作的演示,其中包括蓝牙包装BluetoothAdapter。包装程序将被调用,BleWrapper并随附于Application Accelerator软件包中包含的名为BLEDemo的演示应用程序。在此处下载:https : //developer.bluetooth.org/Pages/Bluetooth-Android-Developers.aspx。下载前,您需要在右上角注册您的电子邮件地址。该项目的许可证允许免费使用,代码修改和发布。

以我的经验,Android演示应用程序可以很好地处理BLE通知订阅。我还没有深入研究代码,以了解包装器实际上是如何包装的。

Play商店中有一个Android应用程序,它是Application Accelerator演示的自定义项。由于用户界面看起来几乎相同,因此我猜想它也使用BleWrapper。在此处下载该应用程序:https : //play.google.com/store/apps/details?id=com.macdom.ble.blescanner

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.