kenjdavidson / react-native-bluetooth-classic

⚛ Bluetooth classic Android(Bluetooth)/IOS(ExternalAccessory) module for serial communication
https://kenjdavidson.github.io/react-native-bluetooth-classic
MIT License
250 stars 94 forks source link

[help] cannot read sent data when you use connectedDevice.read() or connectedDevice.onDataReceived() #333

Closed brianwachira closed 4 months ago

brianwachira commented 4 months ago

Mobile Device Environment Provide a list of operating systems on which this issue is relevant.

Application Environment Provide information about your development environment:

Describe the bug Hi there, I have an offline-first mobile app that should be able to share api response data from one mobile device to another device that has no access to internet. I have managed to connect devices through the app with the device.connect() but when I send data ( stringified json ) it is never received. Data is always null image

A sample of the data I am sending to the other device :

{
  "gps":{
     "lat":-1.3729792,
     "lng":36.9360896
   },
  "survey_id":"9c74c0be-e671-466b-ab9d-725c9003cbe3",
  "question1":false,
  "question2":true
}

Expected behavior A clear and concise description of what you expected to happen.

Additional context

useBluetooth hook ( some functions )

function useBluetooth(){

    const scanForDevices = async () => {
        try {
            const devices = await RNBluetoothClassic.startDiscovery();
            console.log(devices);
            setAllDevices(devices);

            const paired = await RNBluetoothClassic.getBondedDevices();
            console.log(paired);
            setPairedDevices(paired);
        } catch (error) {
            console.error("Failed to scan for devices:", error);
        }
    };

    const connectToDevice = async (device: BluetoothDevice) => {
        try {
            const connected = await device.connect({ delimiter: '\n',DEVICE_CHARSET: Platform.OS === "ios" ? 1536 : "utf-8",READ_SIZE: 800000});
            if (connected) {
                setConnectedDevice(device);
            }
        } catch (error) {
            console.error("Failed to connect to device:", error);
        }
    };

    const acceptConnections = async () => {
        try {
            const device = await RNBluetoothClassic.accept({});
            if (device) {
                setConnectedDevice(device);
            }
        } catch (error) {
            console.error("Failed to connect to device:", error);
        }
    };

    const sendData = async (data: string) => {
        //console.log(data)
        if (connectedDevice) {
            try {
                const senddata= await connectedDevice.write(data);
                console.log("send data",senddata)
            } catch (error) {
                console.error("Failed to send data:", error);
            }
        } else {
            console.log("No device connected to send data");
        }
    };
}

export default useBLE;

How I detect data change on imports screen

image

This useEffect logs null image

manhdev0901 commented 4 months ago

I have same issue.

brianwachira commented 4 months ago

I have same issue.

Could you try to share your implementation we see whether we can compare notes?

kenjdavidson commented 4 months ago

Chances are this is a delimiter setting, for example:

I forget to be honest. This should be a fairly straight forward thing to debug in the connection and parsing of data on the native side. I suggest you debug through Android Studio and check out:

  1. whether the data is being received
  2. whether the data is being chopped into messages based on delimiter

This will give you the best path to finding out what's wrong.

If you're using the manualmethod of device.read() you should probably be using the device.available() to see whether there are messages available.

viratishah commented 4 months ago

I have the same issue. Here's my code. Not sure if I am doing something wrong (as I am pretty new to this). onDataReceived() doesn't trigger and read sent data.

export const BluetoothConnection = props => {
  const [blueToothAvailable, setBluetoothAvailable] = useState(false);
  const [deviceConnectionStatus, setDeviceConnectionStatus] = useState(false);
  const [connectedDevice, setConnectedDevice] = useState(null);
  const [data, setData] = useState('');
  const [logs, setLogs] = useState([]);

  const log = message => {
    setLogs(prevLogs => [
      ...prevLogs,
      `${new Date().toLocaleTimeString()}: ${message}`,
    ]);
  };

  const isBluetoothAvailable = async () => {
    try {
      const available = await RNBluetoothClassic.isBluetoothAvailable();
      const enabled = await RNBluetoothClassic.isBluetoothEnabled();
      if (enabled && available) {
        setBluetoothAvailable(true);
        log('Bluetooth is available and enabled');
        const granted = await checkAndRequestPermission(
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
          'This app needs access to Bluetooth to connect to devices.',
        );
        if (granted) {
          log('Bluetooth permission granted');
          // checkConnection();
        } else {
          setConnectedDevice(null);
          setDeviceConnectionStatus(false);
          log('Bluetooth permission denied');
          Alert.alert(
            'Permission Denied',
            'This app requires Bluetooth permission to function properly. Please grant the permission in settings.',
            [{text: 'OK'}],
          );
        }
      }
    } catch (err) {
      log(`Error checking Bluetooth availability: ${err.message}`);
    }
  };

  const sendData = async message => {
    try {
      const response = await RNBluetoothClassic.writeToDevice(
        deviceAddress,
        message,
      );
      log(`Data sent: ${message}`);
      log(`Response: ${response}`);
    } catch (err) {
      log(`Error sending data: ${err.message}`);
    }
  };

  const onReceivedData = d => {
    log(`Data received: ${d}`);
    setData(data);
  };

  const checkConnection = async () => {
    try {
      const device = await RNBluetoothClassic.connectToDevice(deviceAddress, {
        CONNECTOR_TYPE: 'rfcomm',
        DELIMITER: '\n',
        DEVICE_CHARSET: Platform.OS === 'ios' ? 1536 : 'utf-8',
        SECURE_SOCKET: false,
      });
      const isConnected = await RNBluetoothClassic.isDeviceConnected(
        deviceAddress,
      );
      if (isConnected) {
        setDeviceConnectionStatus(true);
        log('Device connected');
        setConnectedDevice(device);
        const subscription = device.onDataReceived(onReceivedData);
        return () => {
          subscription.remove();
        };
      } else {
        setConnectedDevice(null);
        setDeviceConnectionStatus(false);
        log('Device connection failed');
      }
    } catch (err) {
      log(`Error connecting to device: ${err.message}`);
    }
  };

  useEffect(() => {
    isBluetoothAvailable();
  }, []);

  useEffect(() => {
    if (blueToothAvailable) {
      log('Bluetooth is available');
    }
  }, [blueToothAvailable]);

  useEffect(() => {
    if (deviceConnectionStatus) {
      sendData('03F0028B2F0D0A');
    }
  }, [deviceConnectionStatus]);

  return (
    <SafeAreaView>
      <ScrollView contentContainerStyle={styles.container}>
        <Text variant="displayMedium">Bluetooth Connection</Text>
        <Button
          onPress={checkConnection}
          mode="contained"
          style={styles.button}>
          Connect to Device
        </Button>
        <Text style={styles.title}>Logs:</Text>
        {logs.map((log, index) => (
          <Text key={index} style={styles.logText}>
            {log}
          </Text>
        ))}
      </ScrollView>
    </SafeAreaView>
  );
};
kenjdavidson commented 4 months ago

Again, you need to debug it yourself to see what's going on, I can't debug your application and the Bluetooth functionality is very specific to your devices. I'm always happy to help, but just posting code (that usually looks good to the eye) doesn't help me much.

viratishah commented 4 months ago

Again, you need to debug it yourself to see what's going on, I can't debug your application and the Bluetooth functionality is very specific to your devices. I'm always happy to help, but just posting code (that usually looks good to the eye) doesn't help me much.

Hi, Thank you, I was able to solve this issue. Adding CONNECTION_TYPE = 'delimited' and using encoding='hex' while calling writeToDevice helped.

I get the value now, but it is some unicode string. My data is supposed to be in Hex (using HC-05 fyi). I tried changing the receivedData function (as mentioned in doc and #50 ), but it led to the function not returning any value whatsoever. Any help?

brianwachira commented 4 months ago

Again, you need to debug it yourself to see what's going on, I can't debug your application and the Bluetooth functionality is very specific to your devices. I'm always happy to help, but just posting code (that usually looks good to the eye) doesn't help me much.

Hi, Thank you, I was able to solve this issue. Adding CONNECTION_TYPE = 'delimited' and using encoding='hex' while calling writeToDevice helped.

I get the value now, but it is some unicode string. My data is supposed to be in Hex (using HC-05 fyi). I tried changing the receivedData function (as mentioned in doc and #50 ), but it led to the function not returning any value whatsoever. Any help?

Wow Could you share small code snippet on how you did yours? where do I add the CONNECTION_TYPE = 'delimited' and using encoding='hex'? It may help with mine

kenjdavidson commented 4 months ago

If you're using hex/binary data, you may want to look at the following:

They may assist in the data conversion or reading.

brianwachira commented 4 months ago

Chances are this is a delimiter setting, for example:

  • on connection you're using \n
  • on accept, you're not setting delimiter so it's using the default, which may be \r\n?

I forget to be honest. This should be a fairly straight forward thing to debug in the connection and parsing of data on the native side. I suggest you debug through Android Studio and check out:

  1. whether the data is being received
  2. whether the data is being chopped into messages based on delimiter

This will give you the best path to finding out what's wrong.

If you're using the manualmethod of device.read() you should probably be using the device.available() to see whether there are messages available.

It finally worked! I set delimiter on accept.

here

image

But I must mention @kenjdavidson , the docs doesn't mention about adding connector options on accept image I think this is something that has to be made clear?

kenjdavidson commented 4 months ago

Glad it's working.

With regards to your comment, I hear what you're saying, but if you look at the documentation (at least htis was my logic):

Delimiter is a connection parameter and is documented on that page https://kenjdavidson.com/react-native-bluetooth-classic/android/device-connection/

I guess it's only cause I'm used to it that I know that the connection arguments are passed through the connector or accepter.

Feel free to write up whatever you think makes more sense and open a PR.

viratishah commented 4 months ago

If you're using hex/binary data, you may want to look at the following:

They may assist in the data conversion or reading.

Okay, finally its resolved. Just changed CONNECTION_TYPE to binary and converted the output into buffer object.

receivedData = Buffer.from(event.data, 'base64');
/* this line in case you need a hex string
const hexString = receivedData.toString('hex'); */

Thanks @kenjdavidson for this amazing library and the links as well.