innoveit / react-native-ble-manager

React Native BLE communication module
http://innoveit.github.io/react-native-ble-manager/
Apache License 2.0
2.1k stars 756 forks source link

request MTU weird behaviour #1212

Open akiannillo opened 4 months ago

akiannillo commented 4 months ago

I saw there are some closed issues about this but they have been closed before an actual solution.

Bug Description

To Reproduce Code used to reproduce the error

on either Android 13 or 14

        if (this.discoveredDevicesIDs.size === 0) {
            throw new Error('No devices discovered');
        }
        const deviceID = Array.from(this.discoveredDevicesIDs)[0];
        await BleManager.connect(deviceID);
        console.log('Connected to', deviceID);
        await BleManager.requestMTU(deviceID, 232);
        console.log('MTU requested');
        Alert.alert('MTU requested');

on Android 13

        if (this.discoveredDevicesIDs.size === 0) {
            throw new Error('No devices discovered');
        }
        const deviceID = Array.from(this.discoveredDevicesIDs)[0];
        await BleManager.connect(deviceID);
        console.log('Connected to', deviceID);
        BleManager.requestMTU(deviceID, 232)
            .then((mtuSize) => {
                console.log('MTU size changed to', mtuSize);
                Alert.alert('MTU size changed to', mtuSize.toString());
            })
            .catch((error) => {
                console.error('Error requesting MTU', error);
                Alert.alert('Error requesting MTU', error);
            });

Expected behavior on either Android with await: 'MTU requested' printed on console on Android 13 with then: either 'MTU size changed to' or 'Error requesting MTU' printed on console

Smartphone (please complete the following information):

lucaswitch commented 4 months ago

Weird, i see you're using version ^11.3.2 which can be any version newer than that, can you make sure to install version react-native-ble-manager version: "11.5.2" and do a ./android/gradlew clean for a full library refresh and check if the issue is still happening?

akiannillo commented 4 months ago

Done, same results. However, I read a bit of logs and code (even if I did not completely understand it) and I tried this:

        if (this.discoveredDevicesIDs.size === 0) {
            throw new Error('No devices discovered');
        }
        const deviceID = Array.from(this.discoveredDevicesIDs)[0];
        await BleManager.connect(deviceID);
        console.log('Connected to', deviceID);
        await new Promise(resolve => setTimeout(resolve, 2000));
        await BleManager.requestMTU(deviceID, 232);
        console.log('MTU requested');
        Alert.alert('MTU requested');

and it works like a charm. It looks like there is some problem with the "command" management or something similar. This workaround made me think that maybe it's not about the version but about the "speed" of the tablet. The Android 14 tablet is 2024, while the Android 13 is much older.

rahulp9538 commented 3 months ago

Issue Description:

When I run my app on devices with Android 13 or lower, I receive the full data from my Bluetooth device. However, when running the same app on an Android 14 device, the data received from the Bluetooth device is truncated. Despite seeing "MTU size changed to 517 bytes" in the logs on both Android 13 and Android 14, the issue persists only on Android 14. I'm using "react-native-ble-manager": "^11.5.3". Please provide a solution urgently.

Code Example:

import BleManager from 'react-native-ble-manager'; import { Alert } from 'react-native';

const connectPeripheral = async (peripheral, connect, save) => { try { let ShowAlert = false;

if (peripheral) {
  if (connect) {
    console.log('Peripheral exists in the list.');
  } else {
    console.log('Peripheral does not exist in the list.');
    addOrUpdatePeripheral(peripheral.id, { ...peripheral, connecting: true });
  }

  console.log('Before connecting to peripheral...');

  // Create a promise for the connection
  const connectPromise = BleManager.connect(peripheral.id);

  // Create a timeout promise
  const timeoutPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('Connection timeout'));
    }, 20000); // Adjust the timeout duration as needed
  });

  // Use Promise.race to wait for the first promise that resolves
  try {
    await Promise.race([connectPromise, timeoutPromise]);
    console.debug(`[connectPeripheral][${peripheral.id}] connected.`);

    console.log('After connecting to peripheral...');

    if (connect) {
      console.log('Peripheral exists in the list.');
    } else {
      console.log('Peripheral does not exist in the list.');
      addOrUpdatePeripheral(peripheral.id, { 
        ...peripheral, 
        connecting: false, 
        connected: true 
      });
    }

    // Allow some time for bonding and connection to finish
    await new Promise(resolve => setTimeout(resolve, 2000));

    try {
      const mtu = await BleManager.requestMTU(peripheral.id, 185);
      console.log(`MTU size changed to ${mtu} bytes`);
    } catch (error) {
      console.error(`Failed to change MTU size: ${error}`);
    }

    // Retrieve services
    const peripheralData = await BleManager.retrieveServices(peripheral.id);
    console.debug(`[connectPeripheral][${peripheral.id}] retrieved peripheral services`, JSON.stringify(peripheralData));

    storeUniqueObject('userData', peripheral, false);

    if (!movefrwd) {
      setisLoading(false);
      ShowAlert = true;
      navigation.navigate('BTStatusScreen', {
        peripheralObj: peripheral,
        BleManagerObj: BleManager
      });
      console.log(`ShowAlert: ${ShowAlert}`);
    }
  } catch (error) {
    setisLoading(false);
    await BleManager.disconnect(peripheral.id);
    if (error.message === 'Connection timeout') {
      console.log('Connection to peripheral timed out. Device may be off or not in discovery mode.');
    } else {
      console.error(`Error connecting to peripheral: ${error}`);
      if (!movefrwd) {
        // Handle other connection errors here
      }
    }
  }
}

} catch (error) { console.error([connectPeripheral][${peripheral.id}] connectPeripheral error, error); } };

varun761 commented 3 months ago

@akiannillo There is some performance issue in this module. It cannot work as expected. I think you should create your own module. I am stuck at a point too. Where nrf connect app performs well and is able to receive data in 100ms. Whereas, in this module, it sends disconnects. This module total **** me up.

lucaswitch commented 3 months ago

Issue Description:

When I run my app on devices with Android 13 or lower, I receive the full data from my Bluetooth device. However, when running the same app on an Android 14 device, the data received from the Bluetooth device is truncated. Despite seeing "MTU size changed to 517 bytes" in the logs on both Android 13 and Android 14, the issue persists only on Android 14. I'm using "react-native-ble-manager": "^11.5.3". Please provide a solution urgently.

Code Example:

import BleManager from 'react-native-ble-manager'; import { Alert } from 'react-native';

const connectPeripheral = async (peripheral, connect, save) => { try { let ShowAlert = false;

if (peripheral) {
  if (connect) {
    console.log('Peripheral exists in the list.');
  } else {
    console.log('Peripheral does not exist in the list.');
    addOrUpdatePeripheral(peripheral.id, { ...peripheral, connecting: true });
  }

  console.log('Before connecting to peripheral...');

  // Create a promise for the connection
  const connectPromise = BleManager.connect(peripheral.id);

  // Create a timeout promise
  const timeoutPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('Connection timeout'));
    }, 20000); // Adjust the timeout duration as needed
  });

  // Use Promise.race to wait for the first promise that resolves
  try {
    await Promise.race([connectPromise, timeoutPromise]);
    console.debug(`[connectPeripheral][${peripheral.id}] connected.`);

    console.log('After connecting to peripheral...');

    if (connect) {
      console.log('Peripheral exists in the list.');
    } else {
      console.log('Peripheral does not exist in the list.');
      addOrUpdatePeripheral(peripheral.id, { 
        ...peripheral, 
        connecting: false, 
        connected: true 
      });
    }

    // Allow some time for bonding and connection to finish
    await new Promise(resolve => setTimeout(resolve, 2000));

    try {
      const mtu = await BleManager.requestMTU(peripheral.id, 185);
      console.log(`MTU size changed to ${mtu} bytes`);
    } catch (error) {
      console.error(`Failed to change MTU size: ${error}`);
    }

    // Retrieve services
    const peripheralData = await BleManager.retrieveServices(peripheral.id);
    console.debug(`[connectPeripheral][${peripheral.id}] retrieved peripheral services`, JSON.stringify(peripheralData));

    storeUniqueObject('userData', peripheral, false);

    if (!movefrwd) {
      setisLoading(false);
      ShowAlert = true;
      navigation.navigate('BTStatusScreen', {
        peripheralObj: peripheral,
        BleManagerObj: BleManager
      });
      console.log(`ShowAlert: ${ShowAlert}`);
    }
  } catch (error) {
    setisLoading(false);
    await BleManager.disconnect(peripheral.id);
    if (error.message === 'Connection timeout') {
      console.log('Connection to peripheral timed out. Device may be off or not in discovery mode.');
    } else {
      console.error(`Error connecting to peripheral: ${error}`);
      if (!movefrwd) {
        // Handle other connection errors here
      }
    }
  }
}

} catch (error) { console.error([connectPeripheral][${peripheral.id}] connectPeripheral error, error); } };

Thats a behavior that changed on Android Bluetooth since 14.

Issue Description:

When I run my app on devices with Android 13 or lower, I receive the full data from my Bluetooth device. However, when running the same app on an Android 14 device, the data received from the Bluetooth device is truncated. Despite seeing "MTU size changed to 517 bytes" in the logs on both Android 13 and Android 14, the issue persists only on Android 14. I'm using "react-native-ble-manager": "^11.5.3". Please provide a solution urgently.

Code Example:

import BleManager from 'react-native-ble-manager'; import { Alert } from 'react-native';

const connectPeripheral = async (peripheral, connect, save) => { try { let ShowAlert = false;

if (peripheral) {
  if (connect) {
    console.log('Peripheral exists in the list.');
  } else {
    console.log('Peripheral does not exist in the list.');
    addOrUpdatePeripheral(peripheral.id, { ...peripheral, connecting: true });
  }

  console.log('Before connecting to peripheral...');

  // Create a promise for the connection
  const connectPromise = BleManager.connect(peripheral.id);

  // Create a timeout promise
  const timeoutPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('Connection timeout'));
    }, 20000); // Adjust the timeout duration as needed
  });

  // Use Promise.race to wait for the first promise that resolves
  try {
    await Promise.race([connectPromise, timeoutPromise]);
    console.debug(`[connectPeripheral][${peripheral.id}] connected.`);

    console.log('After connecting to peripheral...');

    if (connect) {
      console.log('Peripheral exists in the list.');
    } else {
      console.log('Peripheral does not exist in the list.');
      addOrUpdatePeripheral(peripheral.id, { 
        ...peripheral, 
        connecting: false, 
        connected: true 
      });
    }

    // Allow some time for bonding and connection to finish
    await new Promise(resolve => setTimeout(resolve, 2000));

    try {
      const mtu = await BleManager.requestMTU(peripheral.id, 185);
      console.log(`MTU size changed to ${mtu} bytes`);
    } catch (error) {
      console.error(`Failed to change MTU size: ${error}`);
    }

    // Retrieve services
    const peripheralData = await BleManager.retrieveServices(peripheral.id);
    console.debug(`[connectPeripheral][${peripheral.id}] retrieved peripheral services`, JSON.stringify(peripheralData));

    storeUniqueObject('userData', peripheral, false);

    if (!movefrwd) {
      setisLoading(false);
      ShowAlert = true;
      navigation.navigate('BTStatusScreen', {
        peripheralObj: peripheral,
        BleManagerObj: BleManager
      });
      console.log(`ShowAlert: ${ShowAlert}`);
    }
  } catch (error) {
    setisLoading(false);
    await BleManager.disconnect(peripheral.id);
    if (error.message === 'Connection timeout') {
      console.log('Connection to peripheral timed out. Device may be off or not in discovery mode.');
    } else {
      console.error(`Error connecting to peripheral: ${error}`);
      if (!movefrwd) {
        // Handle other connection errors here
      }
    }
  }
}

} catch (error) { console.error([connectPeripheral][${peripheral.id}] connectPeripheral error, error); } };

Thats a behavior change in the bluetooth not in the library itself, take a look on https://developer.android.com/about/versions/14/behavior-changes-all for further details.

Something that i had to fix in order to work with Android 14 devices. Before 14: connect -> discover -> listen for characteristics changes. After 14: connect -> discover -> requestMtu(thats the minimum MTU to negotiate with device, probably the highest mtu size will be given to you) -> listen for characteristics changes.

lucaswitch commented 3 months ago

@rahulp9538 that fixes your MTU negotiation size.

Screenshot 2024-06-10 at 15 22 38

lucaswitch commented 3 months ago

Please provide a solution urgently

Can you provide a minimal repro that shows this perfomance issue?

rahulp9538 commented 1 month ago

Please provide a solution urgently

Can you provide a minimal repro that shows this perfomance issue?

const connectPeripheral = async (peripheral: Peripheral, connect: boolean, save: boolean) => { try { ShowAlert = false;

    if (peripheral) {
        if (connect) {
            console.log('Peripheral exists in the list.');
        } else {
            console.log('Peripheral does not exist in the list.');
            addOrUpdatePeripheral(peripheral.id, { ...peripheral, connecting: true });
        }

        console.log('Before connecting to peripheral...');

        // Create a promise that resolves when the connection succeeds
        const connectPromise = BleManager.connect(peripheral.id);

        // Create a promise that resolves after a specified timeout (e.g., 20 seconds)
        const timeoutPromise = new Promise((resolve, reject) => {
            setTimeout(() => {
                reject(new Error('Connection timeout'));
            }, 20000); // Adjust the timeout duration as needed
        });

        // Use Promise.race to wait for the first promise that resolves
        try {
            await Promise.race([connectPromise, timeoutPromise]);
            // Connection succeeded
            console.debug(`[connectPeripheral][${peripheral.id}] connected.`);

            console.log('After connecting to peripheral...');

            if (connect) {
                console.log('Peripheral -----> exists in the list.');
            } else {
                console.log('Peripheral does not exist in the list.');

                addOrUpdatePeripheral(peripheral.id, {
                    ...peripheral,
                    connecting: false,
                    connected: true,
                });
            }

            // Allow time for connection to stabilize before requesting MTU
            await sleep(2000); // 2 seconds delay

            // Request a larger MTU size
            try {
                const mtuSize = 185; // Set the desired MTU size
                const mtu = await BleManager.requestMTU(peripheral.id, mtuSize);
                console.log('&&&&&&&&&&&&&&&>MTU size changed to ' + mtu + ' bytes');

                // Allow time for MTU request to be processed
                await sleep(2000); // 2 seconds delay after MTU request
            } catch (error) {
                console.log('&&&&&&&&&&&&&&&>' + error);
            }

            // Retrieve services and other operations
            const peripheralData = await BleManager.retrieveServices(peripheral.id);
            console.debug(
                `[connectPeripheral][${peripheral.id}] retrieved peripheral services`,
                JSON.stringify(peripheralData),
            );

            storeUniqueObject('userData', peripheral, false);

            if (movefrwd === false) {
                setisLoading(false);
                ShowAlert = true;
                navigation.navigate('BTStatusScreen', {
                    peripheralObj: peripheral,
                    BleManagerObj: BleManager
                });
                console.log("ShowAlert---------> " + ShowAlert);
            }

        } catch (error) {
            setisLoading(false);
            await BleManager.disconnect(peripheral.id);
            // Handle the error and provide user feedback
            if (error.message === 'Connection timeout') {
                console.log('Connection to peripheral timed out. Device may be off or not in discovery mode.');
            } else {
                console.error(`Error connecting to peripheral: ${error}`);
                if (movefrwd == false) {
                    // Handle other connection errors here.
                    // Alert.alert('Bluetooth Connection Error', `${error}`);
                }
            }
        }
    }
} catch (error) {
    console.error(
        `[connectPeripheral][${peripheral.id}] connectPeripheral error`,
        error,
    );
}

};

LOG Peripheral exists in the list. LOG Before connecting to peripheral... DEBUG [connectPeripheral][F8:DC:7A:79:3E:CC] connected. LOG After connecting to peripheral... LOG Peripheral -----> exists in the list. LOG &&&&&&&&&&&&&&&>MTU size changed to 517 bytes DEBUG [connectPeripheral][F8:DC:7A:79:3E:CC] retrieved peripheral services {"characteristics":[{"properties":{"Read":"Read"},"characteristic":"2a00","service":"1800"},{"properties":{"Read":"Read"},"characteristic":"2a01","service":"1800"},{"descriptors":[{"value":null,"uuid":"2902"}],"properties":{"Indicate":"Indicate"},"characteristic":"2a05","service":"1801"},{"descriptors":[{"value":null,"uuid":"b38fab03-72ef-4589-afee-5eeab74b4faa"}],"properties":{"Read":"Read"},"characteristic":"b38fab03-72ef-4589-afee-5eeab74b4fa9","service":"b38fab03-72ef-4589-afee-5eeab74b4fa0"},{"descriptors":[{"value":null,"uuid":"b38fab03-72ef-4589-afee-5eeab74b4fa8"}],"properties":{"Read":"Read"},"characteristic":"b38fab03-72ef-4589-afee-5eeab74b4fa7","service":"b38fab03-72ef-4589-afee-5eeab74b4fa0"},{"descriptors":[{"value":null,"uuid":"b38fab03-72ef-4589-afee-5eeab74b4fa6"}],"properties":{"Read":"Read"},"characteristic":"b38fab03-72ef-4589-afee-5eeab74b4fa3","service":"b38fab03-72ef-4589-afee-5eeab74b4fa0"},{"descriptors":[{"value":null,"uuid":"b38fab03-72ef-4589-afee-5eeab74b4fa5"}],"properties":{"Read":"Read"},"characteristic":"b38fab03-72ef-4589-afee-5eeab74b4fa2","service":"b38fab03-72ef-4589-afee-5eeab74b4fa0"},{"descriptors":[{"value":null,"uuid":"b38fab03-72ef-4589-afee-5eeab74b4fa4"}],"properties":{"WriteWithoutResponse":"WriteWithoutResponse"},"characteristic":"b38fab03-72ef-4589-afee-5eeab74b4fa1","service":"b38fab03-72ef-4589-afee-5eeab74b4fa0"}],"services":[{"uuid":"1800"},{"uuid":"1801"},{"uuid":"b38fab03-72ef-4589-afee-5eeab74b4fa0"}],"advertising":{"rawData":{"bytes":[],"data":"","CDVType":"ArrayBuffer"}},"name":null,"rssi":0,"id":"F8:DC:7A:79:3E:CC"} LOG >>>>>>>>F8:DC:7A:79:3E:CC LOG >>>>>>>>vMo_160203 LOG command sent ----> state 0 LOG ShowAlert---------> true.

I am using "react-native-ble-manager": "^11.5.3" and Google Pixel 5 phone with Android 14

lucaswitch commented 1 month ago

Not sure why you're having this problem, i have a Android 14 device and can safely operate MTU size. But for helping you quickly i'm sharing what i have on production right know for all my devices and having zero bugs with that. Tweak your MTU size cause that will make the bluetooth packages larger. 185 is enough for me but could be not for you.


/**
     * Adds a notification listener.
     * @param value
     * @param peripheral
     * @param characteristic
     * @param service
     */
onNotification(id, serviceId, characteristicId, onNotify, onSuccess, onError) {
    console.info(
      `[BLE] Starting GATT notification ${serviceId}/${characteristicId}`
    );
    serviceId = serviceId.toLowerCase();
    characteristicId = characteristicId.toLowerCase();

    const shortServiceId = serviceId.split("-")[0].substr(-4);
    const shortCharacteristicId = characteristicId.split("-")[0].substr(-4);

    /**
     * When characteristic changes.
     * @param value
     * @param peripheral
     * @param characteristic
     * @param service
     */
    function onCharacteristicChange({
      value,
      peripheral,
      characteristic,
      service,
    }) {
      if (peripheral === id) {
        let isSameCharacteristic = false;
        let isSameService = false;

        // Characteristic
        if (characteristic.length === 4) {
          isSameCharacteristic =
            characteristic.toLowerCase() === shortCharacteristicId;
        } else {
          isSameCharacteristic =
            characteristic.toLowerCase() === characteristicId;
        }

        // Service
        if (service.length === 4) {
          isSameService = service.toLowerCase() === shortServiceId;
        } else {
          isSameService = service.toLowerCase() === serviceId;
        }

        if (isSameService && isSameCharacteristic) {
          onNotify(value);
        }
      }
    }

    if (Platform.OS !== "ios") {
      BleManager.requestMTU(id, 185)
        .then(function (mtu) {
          console.log("MTU size changed to " + mtu + " bytes");
        })
        .catch((error) => {
          console.log(error);
        });
    }

    let listener = BleManagerEmitter.addListener(
      "BleManagerDidUpdateValueForCharacteristic",
      onCharacteristicChange
    );

    BleManager.startNotification(id, serviceId, characteristicId)
      .then(function () {
        onSuccess();
      })
      .catch(function (err) {
        console.error(err);
        onError(err);
      });

    return function () {
      console.info(
        `[BLE] Stopping notification ${serviceId}/${characteristicId}`
      );
      BleManager.stopNotification(id, serviceId, characteristicId);
      listener.remove();
    };
  }

 // When notification needed

const unsubscribe = onMonitor(id, serviceId, characteristicId, onNotify, onSuccess, onError);

// Afterwards
unsubscribe(); // Stops notification safely