1

我正在为 IOS 和 Android 平台构建一个 ReactNative 应用程序。我的应用需要通过 BLE 通信从另一台设备读取数据。我正在使用这个包来实现 BLE 通信,https://github.com/innoveit/react-native-ble-manager。我在 IOS 上接收特征数据时遇到问题,即使它在 Android 平台上按预期工作。

我在 info.plist 文件中添加了以下内容:

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

我有一个组件可以扫描并列出 BLE 设备,如下所示。它被称为 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;

正如您在代码中看到的,当单击“连接”按钮时,它将用户重定向到另一个组件,该组件从另一个设备读取数据。以下是从其他设备读取数据的 BleRowingSession.js。

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;

目前,服务 id 和特征 id 是硬编码的。当然,我必须在显示 BLE 设备列表的第一页上选择正确的设备。

当我运行代码时,它在 Android 设备上按预期工作,它正在接收特征值。当我在实际的 IOS 设备上运行代码时,它没有收到任何数据。但它可以扫描设备并连接到它们。我的代码有什么问题,我该如何解决?

4

0 回答 0