Android-BLE-Library icon indicating copy to clipboard operation
Android-BLE-Library copied to clipboard

Lose notifications for some reasons

Open eotin opened this issue 2 years ago • 3 comments

Hello everybody,

First of all, thanks for your amazing work on this library.

I encounter a strange behavior with notifications and would like to understand what I'm doing wrong. I've 2 android devices, one Gatt Server, one Gatt Client. From the client I write a characteristic to the server to tell him that I want to get a file from him. Then the server send me his file as bytes[] by notifying the client that data is available.

Let's consider this part of the server side that send me data :

 public void sendTracks(BluetoothGattCharacteristic characteristic) throws IOException {
        // Begin here
        byte[] bytes = readFileToBytes(DatabaseSettings.getDatabasePathFileName());

        // First Packet
        byte[] size = ByteBuffer.allocate(4).putInt(bytes.length).array(); // Total size of file
        Log.i(TAG, "sendTracksDataBase SIZE : " + bytes.length);
        Log.i(TAG, "sendTracksDataBase MTU Negotiated : " + this.getMtu());

        int nbPacketInt = (int)Math.ceil((double)bytes.length / this.getMtu()); // Nb packets rounded up

        byte[] nbPackets = ByteBuffer.allocate(4).putInt(nbPacketInt).array();
        Log.i(TAG, "sendTracksDataBase NBPACKETS : " + nbPacketInt);

        Checksum crc = new CRC32(); // CRC
        crc.update(bytes, 0, bytes.length);
        byte[] crcBytes = ByteBuffer.allocate(8).putLong(crc.getValue()).array(); // CRC
        Log.i(TAG, "sendTracksDataBase CRC : " + crc.getValue());

        String ext = FileSystemUtils.getExtension(DatabaseSettings.getDatabasePathFileName());
        byte[] extension = ext.getBytes(StandardCharsets.UTF_8);
        Log.i(TAG, "sendTracksDataBase EXTENSION : " + ext);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        // Send first packet
        outputStream.write(size);
        outputStream.write(nbPackets);
        outputStream.write(crcBytes);
        outputStream.write(extension);

        this.waitUntilNotificationsEnabled(characteristic).enqueue();

        this.sendNotification(characteristic, outputStream.toByteArray(), 0, outputStream.toByteArray().length)
                .then(new AfterCallback() {
                    @Override
                    public void onRequestFinished(BluetoothDevice device) {
                        int t = 1;
                    }
                }).enqueue();

        // Send everything here
        this.sendNotification(characteristic, bytes, 0, bytes.length)
                .split()
                .fail(new FailCallback() {
                    @Override
                    public void onRequestFailed(BluetoothDevice device, int status) {
                        Log.e(TAG, "SendNotification failed with status : " + status);
                    }
                })
                .done(new SuccessCallback() {
                    @Override
                    public void onRequestCompleted(BluetoothDevice device) {
                        Log.i(TAG, "SendNotification success : ");
                    }
                })
                .then(new AfterCallback() {
                    @Override
                    public void onRequestFinished(BluetoothDevice device) {

                    }
                })
                .enqueue();

As you can see, i send a first packet containing meta data of the file, and then i send the notification for the whole array of bytes, with the split function.

On the client side, I've enable the notifications and added a notification callBack to the characteristic the server is notifying.

 setNotificationCallback(this.dataCharacteristic)
                .with(new DataReceivedCallback() {
                    @Override
                    public void onDataReceived(BluetoothDevice device, Data data) {
                        try {
                            Log.i(TAG, "onDataReceived");
                            recomputeDownload(device, data, deviceActionStatusListener);
                            if (deviceActionStatusListener != null) {
                                deviceActionStatusListener.onActionProgress(BleTrackerService.DOWNLOAD_PROGRESS);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });

        enableNotifications(this.dataCharacteristic)
                .fail(new FailCallback() {
                    @Override
                    public void onRequestFailed(BluetoothDevice device, int status) {
                        int t = 1;
                    }
                })
                .done(new SuccessCallback() {
                    @Override
                    public void onRequestCompleted(BluetoothDevice device) {
                        int t = 1 ;
                    }
                })
                .enqueue();

The first time I launch the command, everything is going well as i'm receiving all the notifications. in the background i get the different array of bytes and rebuild my file.

But i can stop this command and relaunch it if i wish. When i stop the command, i disconnect my client from the server. In my clientManager i 'have this piece of code :

@Override
    protected void onServicesInvalidated() {
        // This method is called when the services get invalidated, i.e. when the device
        // disconnects.
        // References to characteristics should be nullified here.
        this.removeNotificationCallback(this.dataCharacteristic);
        this.disableNotifications(this.dataCharacteristic).enqueue();

        this.commandCharacteristic = null;
        this.dataCharacteristic = null;

        this.iotDevice = null;
        this.deviceActionStatusListener = null;

        // Refresh device
        this.refreshDeviceCache().enqueue();
    }

Then i re-launch the command. But this time i only get one notification in my Notification CallBack, but the server did the same and should have send me a lot of other notifications.

I cannot understand why my client is not notified anymore ...

Thanks for your help.

eotin avatar Apr 12 '23 12:04 eotin

Update : If I close and create a new instance of Server Manager that extends BleServerManager between 2 commands, it works well.

eotin avatar Apr 12 '23 12:04 eotin

Have you tried to just reconnect to the server? What ConnectionState are you in when this error happens?

dmytrotols avatar Jul 11 '23 11:07 dmytrotols

Hi, sorry for the late response.

I have 2 hints:

  1. You don't have to do:
    this.removeNotificationCallback(this.dataCharacteristic);
    this.disableNotifications(this.dataCharacteristic).enqueue();
    
    The notification callback is cleared automatically and the dataCharacteristic is no longer valid, as the device has disconnected.
  2. You also don't have to call:
    // Refresh device
    this.refreshDeviceCache().enqueue();
    
    instead you may use extend this method and return true: https://github.com/NordicSemiconductor/Android-BLE-Library/blob/fa970a356454a5a2f164e62219e8c988707e5dfc/ble/src/main/java/no/nordicsemi/android/ble/BleManager.java#L613-L615

philips77 avatar Sep 29 '23 12:09 philips77