简体   繁体   English

Android BLE API:未收到GATT通知

[英]Android BLE API: GATT Notification not received

Device used for testing: Nexus 4, Android 4.3 用于测试的设备:Nexus 4,Android 4.3

Connection is working fine but the onCharacteristicChanged Method of my callback is never called. 连接工作正常,但我的回调的onCharacteristicChanged方法永远不会被调用。 However I am registering for notifications using setCharacteristicNotification(char, true) inside onServicesDiscovered and that function even returns true. 但是我在onServicesDiscovered setCharacteristicNotification(char, true)注册通知,该函数甚至返回true。

Device log (there are actually no messages at all when notifications should appear / are sent via the Bluetooth device): 设备日志(当通知应该出现/通过蓝牙设备发送时,实际上根本没有消息):

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 Notifications work fine using iOS and the app basically does the same as on Android (registering for notification etc.). GATT通知使用iOS工作正常,应用程序基本上与Android上相同(注册通知等)。

Has anyone else experienced this with a possible solution? 有没有其他人经历过可能的解决方案?

It seems like you forgot to write the Descriptor which tells your BLE device to go in this mode. 好像你忘了写Descriptor来告诉你的BLE设备进入这种模式。 See the code lines that deal with descriptor at http://developer.android.com/guide/topics/connectivity/bluetooth-le.html#notification 请参阅http://developer.android.com/guide/topics/connectivity/bluetooth-le.html#notification中处理描述符的代码行。

Without setting this descriptor, you never receive updates to a characteristic. 如果不设置此描述符,您将永远不会收到特征的更新。 Calling setCharacteristicNotification is not enough. 调用setCharacteristicNotification是不够的。 This is a common mistake. 这是一个常见的错误。

code snipped 代码剪断

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 - I have the same issues. @ Boni2k - 我有同样的问题。 In my case, I have 3 notifying characteristics and a handful of read/write characteristics. 就我而言,我有3个通知特性和少数读/写特性。

What I did find is that there is some dependency between writeGattDescriptor and readCharacteristic . 我发现的是writeGattDescriptorreadCharacteristic之间存在一些依赖关系。 All of the writeGattDescriptors must come first and complete before you issue any readCharacteristic calls. 在发出任何readCharacteristic调用之前, 所有 writeGattDescriptors 必须先完成完成。

Here is my solution using Queues . 这是我使用Queues解决方案。 Now I am getting notifications and everything else works fine: 现在我收到通知,其他一切正常:

Create two Queues like this: 像这样创建两个队列:

private Queue<BluetoothGattDescriptor> descriptorWriteQueue = new LinkedList<BluetoothGattDescriptor>();
private Queue<BluetoothGattCharacteristic> characteristicReadQueue = new LinkedList<BluetoothGattCharacteristic>();

Then write all of your descriptors immediately after discovery with this method: 然后使用此方法在发现后立即编写所有描述符:

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

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

The method for reading a characteristic normally then looks like this: 通常读取特征的方法如下:

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

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

When setting the value to the descriptor instead of putting descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) , put descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE) . 将值设置为描述符而不是放置descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) ,请设置descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE) The callbacks for onCharacteristicChanged are called now. 现在调用onCharacteristicChanged的回调。

Experienced issues in earlier versions of Android receiving notifications (an indication that was registered) and always had a strange disconnect event afterwards. 早期版本的Android接收通知(已注册的指示)中经验丰富的问题​​,之后总是有一个奇怪的断开连接事件。 As it turns out, this was because we registered for notifications on five characteristics. 事实证明,这是因为我们注册了五个特征的通知。

The error discovered in LogCat was: 在LogCat中发现的错误是:

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

Prior to 4.4.2, the number of registrations was capped at 4! 在4.4.2之前,注册数量上限为4! 4.4.2 increased this limit to 7. 4.4.2将此限制增加到7。

By reducing the number of registrations in earlier versions, we were able to step around this limitation. 通过减少早期版本中的注册数量,我们能够绕过这一限制。

I assume (you did not provide your source code) that you did not implement it as Google wanted : 我假设(您没有提供源代码)您没有按照Google的要求实现它:

(1) (1)

mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);

and then 然后

(2) (2)

BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);

I suppose 2 is missing. 我想2失踪了。 In that case I believe on low-level notification will be triggered but they will never be reported to application layer. 在这种情况下,我相信将触发低级别通知,但它们永远不会报告给应用程序层。

Well, this API name surely lead some confusions to app developer if he/she was not the Bluetooth background programmer. 好吧,如果他/她不是蓝牙背景程序员,这个API名称肯定会引起app开发人员的一些困惑。

From Bluetooth core specification perspective, quote from core spec 4.2 Vol 3, Part G section 3.3.3.3 "Client Characteristic Configuration" : 从蓝牙核心规范的角度来看,引用核心规范4.2第3卷,第G部分第3.3.3.3节“客户端特性配置”:

The characteristic descriptor value is a bit field. 特征描述符值是位字段。 When a bit is set, that action shall be enabled, otherwise it will not be used. 设置位时,应启用该操作,否则将不使用该操作。

and section 4.10 和第4.10节

Notifications can be configured using the Client Characteristic Configuration descriptor (See Section 3.3.3.3). 可以使用客户端特性配置描述符配置通知(参见第3.3.3.3节)。

which is clearly states that if client want to receive the notification(or indication,which need response) from server, should write the "Notification" bit to 1("Indication" bit also to 1 otherwise). 这清楚地表明,如果客户端想要从服务器接收通知(或指示,需要响应),则应将“通知”位写入1(否则“指示”位也为1)。

However, the name "setCharacteristicNotification" give us a hint is that if we set the parameters of this API as TURE, the client would got notifications; 但是,名称“setCharacteristicNotification”给我们一个提示是,如果我们将此API的参数设置为TURE,客户端将收到通知; unfortunately this API only set the local bit to allow the notification sent to apps in case of remote notification comes. 遗憾的是,此API仅设置本地位以允许在发出远程通知时发送给应用程序的通知。 See code from Bluedroid: 查看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;
}'

so what matters was the descriptor write action. 所以最重要的是描述符写操作。

Here's a simple way to do it, but let me know if you see any drawbacks. 这是一个简单的方法,但如果您发现任何缺点,请告诉我。

Step 1 Declare boolean variables 步骤1声明布尔变量

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

Step 2 subscribe to the first characteristic in the onServicesDiscovered callback: 第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;
}

Step 3 第3步

Subscribe to any others after the onCharacteristicChanged callback fires 在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;
    }
}

This one is working for me: 这个对我有用:

to notify master device that some characteristic is change, call this function on your pheripheral: 要通知主设备某些特性发生变化,请在您的外围设备上调用此功能:

private BluetoothGattServer server;
//init....

//on BluetoothGattServerCallback...

//call this after change the characteristic
server.notifyCharacteristicChanged(device, characteristic, false);

in your master device: enable setCharacteristicNotification after discover the service: 在主设备中:发现服务后启用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();
                }
            });
        }
   }

now you can check your characteristic value is change, for example onCharacteristicRead function (this also working on onCharacteristicChanged function as well) : 现在你可以检查你的特征值是否有变化,例如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);
}

I had another reason that I would like to add as it drove me crazy the whole day: 我有另一个原因,我想补充一下,因为它让我整天疯狂:

On my Samsung Note 3 I did not receive notifications of changed values while the same code worked on any other device I tested with. 在我的Samsung Note 3上,我没有收到更改值的通知,而相同的代码在我测试过的任何其他设备上都有效。

Rebooting the device solved all the problems. 重启设备解决了所有问题。 Obvious, but when you are in the problem, you forget to think of. 显而易见,但是当你遇到问题时,你会忘记思考。

I've experienced the problems with notifications for BLE on Android as well. 我也遇到过Android上BLE通知的问题。 However there's a fully working demo that includes a bluetooth wrapper around BluetoothAdapter . 然而,这是一个完全有效的演示,包括围绕BluetoothAdapter的蓝牙包装。 The wrapper is called BleWrapper and ships with the demo application called BLEDemo contained in the Application Accelerator package. 包装器称为BleWrapper ,随附在Application Accelerator包中的名为BLEDemo的演示应用程序。 Download here: https://developer.bluetooth.org/Pages/Bluetooth-Android-Developers.aspx . 点击此处下载: https//developer.bluetooth.org/Pages/Bluetooth-Android-Developers.aspx You need to register with your email address at the top right before downloading. 在下载之前,您需要在右上角注册您的电子邮件地址。 The project's license allows for free use, code modification and publication. 该项目的许可证允许免费使用,代码修改和发布。

To my experience the Android demo application handles BLE notification subscriptions very well. 根据我的经验,Android演示应用程序可以很好地处理BLE通知订阅。 I've not yet dived too much into the code to see how the wrapper actually wraps. 我还没有在代码中潜入太多,看看包装器实际上是如何包装的。

There's an Android app available in Play Store that is a customization of the Application accelerator demo. Play商店中有一个Android应用程序,它是应用程序加速器演示的自定义。 As the user interface looks nearly the same I suppose that it also uses BleWrapper . 由于用户界面看起来几乎相同,我认为它也使用BleWrapper Download the app here: https://play.google.com/store/apps/details?id=com.macdom.ble.blescanner 在此处下载应用: https//play.google.com/store/apps/details?id = com.macdom.ble.blescanner

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM