NordicSemiconductor / Android-BLE-Library

A library that makes working with Bluetooth LE on Android a pleasure. Seriously.
BSD 3-Clause "New" or "Revised" License
1.99k stars 414 forks source link

Lose notifications for some reasons #492

Closed eotin closed 2 days ago

eotin commented 1 year ago

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 commented 1 year ago

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

DmytroTols commented 1 year ago

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

philips77 commented 11 months ago

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