ReactNative ble manager is not reading data from peripheral on IOS

2.9k Views Asked by At

I am building a ReactNative application for both IOS and Android platforms. My app needs to read data from another device via BLE communication. I am using this package for implementing BLE communication, https://github.com/innoveit/react-native-ble-manager. I am having a problem with receiving characteristic data on IOS even though it is working as expected on the Android platform.

I have added the following to the info.plist file:

Privacy - Bluetooth Always Usage Description: App needs to use Bluetooth to receive data from WaterRower machine

I have a component that scan and list down the BLE devices as follow. It is called, BleDeviceList.js

import React, {
  useState,
  useEffect,
} from 'react';
import {
  SafeAreaView,
  StyleSheet,
  ScrollView,
  View,
  Text,
  StatusBar,
  NativeModules,
  NativeEventEmitter,
  Button,
  Platform,
  PermissionsAndroid,
  FlatList,
  TouchableHighlight,
} from 'react-native';

import {
  Colors,
} from 'react-native/Libraries/NewAppScreen';

import BleManager from 'react-native-ble-manager';
const BleManagerModule = NativeModules.BleManager;
const bleManagerEmitter = new NativeEventEmitter(BleManagerModule);

const BleDeviceList = (props) => {
  const [isScanning, setIsScanning] = useState(false);
  const peripherals = new Map();
  const [list, setList] = useState([]);
  const [ connectedDevices, setConnectedDevices ] = useState([ ]);
  const [ permissionsAllowed, setPermissionsAllowed ] = useState(false)


  const startScan = () => {
    if (!isScanning) {
      BleManager.scan([], 3, true).then((results) => {
        console.log('Scanning...');
        setIsScanning(true);
      }).catch(err => {
        console.error(err);
      });
    }
  }

  const handleStopScan = () => {
    console.log('Scan is stopped');
    setIsScanning(false);
  }

  const handleDisconnectedPeripheral = (data) => {
    let peripheral = peripherals.get(data.peripheral);
    if (peripheral) {
      peripheral.connected = false;
      peripherals.set(peripheral.id, peripheral);
      setList(Array.from(peripherals.values()));
    }
    console.log('Disconnected from ' + data.peripheral);
  }

  const handleUpdateValueForCharacteristic = (data) => {
    console.log('Received data from ' + data.peripheral + ' characteristic ' + data.characteristic, data.value);
  }

  const retrieveConnected = () => {
    BleManager.getConnectedPeripherals([]).then((results) => {
      if (results.length == 0) {
        console.log('No connected peripherals')
      }
      console.log(results);
      for (var i = 0; i < results.length; i++) {
        var peripheral = results[i];
        peripheral.connected = true;
        peripherals.set(peripheral.id, peripheral);
        setList(Array.from(peripherals.values()));
      }
    });
  }

  const handleDiscoverPeripheral = (peripheral) => {
    console.log('Got ble peripheral', peripheral);
    if (!peripheral.name) {
      peripheral.name = 'NO NAME';
    }
    peripherals.set(peripheral.id, peripheral);
    setList(Array.from(peripherals.values()));
  }

  const isConnected = (peripheral) => {
    return connectedDevices.filter(cd => cd.id == peripheral.id).length > 0;
  }

  const toggleConnectPeripheral = (peripheral) => {
    if (peripheral){
      if (isConnected(peripheral)){
        BleManager.disconnect(peripheral.id);
        setConnectedDevices(connectedDevices.filter(cd => cd.id != peripheral.id))
      }else{
        BleManager.connect(peripheral.id).then(() => {
          let tempConnnectedDevices = [ ...connectedDevices ]
          tempConnnectedDevices.push(peripheral);
          setConnectedDevices(tempConnnectedDevices);
          props.navigation.push('BleRowingSession', { peripheral: peripheral });

          let p = peripherals.get(peripheral.id);
          if (p) {
            p.connected = true;
            peripherals.set(peripheral.id, p);
            setList(Array.from(peripherals.values()));

            props.navigation.push('BleDeviceServiceList', { peripheral: peripheral });
          }
          console.log('Connected to ' + peripheral.id);
        }).catch((error) => {
          console.log('Connection error', error);
        });
      }
    }

  }

  useEffect(() => {
    BleManager.start({showAlert: false});

    bleManagerEmitter.addListener('BleManagerDiscoverPeripheral', handleDiscoverPeripheral);
    bleManagerEmitter.addListener('BleManagerStopScan', handleStopScan );
    bleManagerEmitter.addListener('BleManagerDisconnectPeripheral', handleDisconnectedPeripheral );
    bleManagerEmitter.addListener('BleManagerDidUpdateValueForCharacteristic', handleUpdateValueForCharacteristic );

    if (Platform.OS === 'android' && Platform.Version >= 23) {
      PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION).then((result) => {
          if (result) {
            console.log("Permission is OK");
            setPermissionsAllowed(true);
          } else {
            PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION).then((result) => {
              if (result) {
                console.log("User accept");
                setPermissionsAllowed(true);
              } else {
                console.log("User refuse");
                setPermissionsAllowed(false);
              }
            });
          }
      });
    } else {
      setPermissionsAllowed(true)
    }

    return (() => {
      console.log('unmount');
      bleManagerEmitter.removeListener('BleManagerDiscoverPeripheral', handleDiscoverPeripheral);
      bleManagerEmitter.removeListener('BleManagerStopScan', handleStopScan );
      bleManagerEmitter.removeListener('BleManagerDisconnectPeripheral', handleDisconnectedPeripheral );
      bleManagerEmitter.removeListener('BleManagerDidUpdateValueForCharacteristic', handleUpdateValueForCharacteristic );
    })
  }, []);

  const renderConnectButton = (item) => {
    if (isConnected(item)) {
      return null
    }

    return (
      <Button
      title="Connect"
      onPress={() => {
          toggleConnectPeripheral(item)
      }}
      />
    )
  }

  const renderDisconnectButton = (item) => {
    if (! isConnected(item)) {
      return null
    }

    return (
      <Button
      title="Disconnect"
      onPress={() => {
        toggleConnectPeripheral(item)
      }}
      />
    )
  }

  const renderItem = (item) => {
    const color = item.connected ? 'green' : '#fff';
    return (
      <TouchableHighlight>
        <View style={[styles.row, {backgroundColor: color}]}>
          <Text style={{fontSize: 12, textAlign: 'center', color: '#333333', padding: 10}}>{item.name}</Text>
          <Text style={{fontSize: 10, textAlign: 'center', color: '#333333', padding: 2}}>RSSI: {item.rssi}</Text>
          <Text style={{fontSize: 8, textAlign: 'center', color: '#333333', padding: 2, paddingBottom: 20}}>{item.id}</Text>
          {renderConnectButton(item)}
          {renderDisconnectButton(item)}
        </View>
      </TouchableHighlight>
    );
  }

  const renderContent = () => {
    if (! permissionsAllowed) {
      return <Text>Bluetooth and locations permissions are required.</Text>
    }

    return (
        <>
          <StatusBar barStyle="dark-content" />
          <SafeAreaView>
            <ScrollView
                contentInsetAdjustmentBehavior="automatic"
                style={styles.scrollView}>
              {global.HermesInternal == null ? null : (
                  <View style={styles.engine}>
                    <Text style={styles.footer}>Engine: Hermes</Text>
                  </View>
              )}
              <View style={styles.body}>

                <View style={{margin: 10}}>
                  <Button
                      title={'Scan Bluetooth (' + (isScanning ? 'on' : 'off') + ')'}
                      onPress={() => startScan() }
                  />
                </View>

                <View style={{margin: 10}}>
                  <Button title="Retrieve connected peripherals" onPress={() => retrieveConnected() } />
                </View>

                {(list.length == 0) &&
                <View style={{flex:1, margin: 20}}>
                  <Text style={{textAlign: 'center'}}>No peripherals</Text>
                </View>
                }

              </View>
            </ScrollView>
            <FlatList
                data={list}
                renderItem={({ item }) => renderItem(item) }
                keyExtractor={item => item.id}
            />
          </SafeAreaView>
        </>
    )
  }

  return (
    renderContent()
  );
};

const styles = StyleSheet.create({
  scrollView: {
    backgroundColor: Colors.lighter,
  },
  engine: {
    position: 'absolute',
    right: 0,
  },
  body: {
    backgroundColor: Colors.white,
  },
  sectionContainer: {
    marginTop: 32,
    paddingHorizontal: 24,
  },
  sectionTitle: {
    fontSize: 24,
    fontWeight: '600',
    color: Colors.black,
  },
  sectionDescription: {
    marginTop: 8,
    fontSize: 18,
    fontWeight: '400',
    color: Colors.dark,
  },
  highlight: {
    fontWeight: '700',
  },
  footer: {
    color: Colors.dark,
    fontSize: 12,
    fontWeight: '600',
    padding: 4,
    paddingRight: 12,
    textAlign: 'right',
  },
});

export default BleDeviceList;

As you can see in the code, when the Connect button is clicked, it will redirect the user to another component that reads data from another device. The following is the BleRowingSession.js that reads the data from another device.

import React, { useEffect, useState } from 'react';
import { Text, View, StyleSheet, NativeModules, NativeEventEmitter, ScrollView } from 'react-native';
import BleManager from 'react-native-ble-manager';
const BleManagerModule = NativeModules.BleManager;
const bleManagerEmitter = new NativeEventEmitter(BleManagerModule);

const serviceId = "00001826-0000-1000-8000-00805F9B34FB";
const characteristicId = "00002AD1-0000-1000-8000-00805F9B34FB";

let readDataCache = "";

const BleRowingSession = (props) => {
  let peripheral = props.route.params.peripheral;
  const [ readData, setReadData ] = useState("");

  const setUpBleNotification = () => {
    BleManager.retrieveServices(peripheral.id).then((peripheralData) => {
              console.log('Retrieved peripheral services', peripheralData);

              setTimeout(() => {
                BleManager.startNotification(peripheral.id, serviceId, characteristicId).then(() => {
                  console.log('Started notification on ' + peripheral.id);
                  bleManagerEmitter.addListener('BleManagerDidUpdateValueForCharacteristic', (data) => {
                    readDataCache = readDataCache + "\n" + data.value.toString()
                    setReadData(readDataCache);
                  });
                  setTimeout(() => {

                  }, 500);
                }).catch((error) => {
                  console.log('Notification error', error);
                });
              }, 500)
            });
  }

  useEffect(() => {
    setUpBleNotification()
  }, [ ])

  return (
    <View style={styles.container}>
      <ScrollView>
        <Text>Ble Rowing Session</Text>
        <Text>{readData}</Text>
      </ScrollView>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center"
  }
})

export default BleRowingSession;

For now, the service id and characteristic id are hardcoded. Of course, I had to choose the right device on the first page that displays the list of BLE devices.

When I run the code, it is working as expected on the Android device, it is receiving the characteristic value. When I run the code on the actual IOS device, it is not receiving any data. But it can scan the devices and connect to them. What is wrong with my code and how can I fix it?

0

There are 0 best solutions below