Android BLE API: GATT Notification not received

前端 未结 10 1389
执笔经年
执笔经年 2020-11-29 16:31

Device used for testing: Nexus 4, Android 4.3

Connection is working fine but the onCharacteristicChangedMethod of my callback is never called. However I

相关标签:
10条回答
  • 2020-11-29 17:09

    Well, this API name surely lead some confusions to app developer if he/she was not the Bluetooth background programmer.

    From Bluetooth core specification perspective, quote from core spec 4.2 Vol 3, Part G section 3.3.3.3 "Client Characteristic Configuration" :

    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

    Notifications can be configured using the Client Characteristic Configuration descriptor (See Section 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).

    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; unfortunately this API only set the local bit to allow the notification sent to apps in case of remote notification comes. See code from 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.

    0 讨论(0)
  • 2020-11-29 17:13

    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.

    Rebooting the device solved all the problems. Obvious, but when you are in the problem, you forget to think of.

    0 讨论(0)
  • 2020-11-29 17:14

    It seems like you forgot to write the Descriptor which tells your BLE device to go in this mode. See the code lines that deal with descriptor at 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. 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? 
    }
    
    0 讨论(0)
  • 2020-11-29 17:15

    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:

    @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) :

    @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 讨论(0)
  • 2020-11-29 17:21

    Here's a simple way to do it, but let me know if you see any drawbacks.

    Step 1 Declare boolean variables

    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:

    @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

    Subscribe to any others after the onCharacteristicChanged callback fires

    @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;
        }
    }
    
    0 讨论(0)
  • 2020-11-29 17:21

    I've experienced the problems with notifications for BLE on Android as well. However there's a fully working demo that includes a bluetooth wrapper around BluetoothAdapter. The wrapper is called BleWrapper and ships with the demo application called BLEDemo contained in the Application Accelerator package. Download here: 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. 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. As the user interface looks nearly the same I suppose that it also uses BleWrapper. Download the app here: https://play.google.com/store/apps/details?id=com.macdom.ble.blescanner

    0 讨论(0)
提交回复
热议问题