Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

request MTU weird behaviour #1212

Open
akiannillo opened this issue Apr 30, 2024 · 10 comments
Open

request MTU weird behaviour #1212

akiannillo opened this issue Apr 30, 2024 · 10 comments
Labels

Comments

@akiannillo
Copy link

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

Bug Description

  • await BleManager.requestMTU("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", 512) never returns.
  • BleManager.requestMTU("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", 512).then(... works on Android 14 but not Android 13.

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

  • Device: Samsung Android Tablets
  • OS: one with Android 13 and one with Android 14
  • react-native-ble-manager version: "^11.3.2"
  • react-native version: "0.72.12"
@lucaswitch
Copy link
Contributor

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
Copy link
Author

akiannillo commented May 2, 2024

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
Copy link

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
Copy link

@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
Copy link
Contributor

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
Copy link
Contributor

@rahulp9538 that fixes your MTU negotiation size.

Screenshot 2024-06-10 at 15 22 38

@lucaswitch
Copy link
Contributor

Please provide a solution urgently

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

@rahulp9538
Copy link

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
Copy link
Contributor

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

@marcosinigaglia
Copy link
Member

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants