You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native集成RFID阅读器与标签的技术实现问题咨询

React Native RFID阅读器集成技术支持

我正在React Native中开发RFID阅读器、标签与设备的集成功能,需求如下:

  • 完成RFID阅读器与设备的蓝牙连接
  • 当RFID标签进入感应范围时,读取标签数据并在UI展示
  • UI包含Start、Stop、Reset按钮,点击Start时先检测范围内是否有RFID标签,再启动扫描并展示数据

当前代码已实现基础连接,点击Start按钮后可与阅读器集成并读取标签数据,但需要进一步的技术优化支持。

实现代码

import React, {useState, useEffect} from 'react';
import {
  Text,
  View,
  ScrollView,
  TouchableOpacity,
  Modal,
  FlatList,
  SafeAreaView,
  PermissionsAndroid,
  NativeModules,
  NativeEventEmitter,
  useColorScheme,
  StyleSheet,
  Platform,
  Alert,
} from 'react-native';
import BleManager from 'react-native-ble-manager';
import {Colors} from 'react-native/Libraries/NewAppScreen';
import {Buffer} from 'buffer';
import encodeUtf8 from 'encode-utf8';

const BleManagerModule = NativeModules.BleManager;
const BleManagerEmitter = new NativeEventEmitter(BleManagerModule);

const BleConnectDevice = () => {
  const [isScanning, setIsScanning] = useState(false);
  const [devices, setDevices] = useState([]);
  const [connectedDevices, setConnectedDevices] = useState([]);
  const [modalVisible, setModalVisible] = useState(false);
  const [readData, setReadData] = useState('');
  const isDarkMode = useColorScheme() === 'dark';

  const characteristic = 'ffe1';
  const service = 'ffe0';

  useEffect(() => {
    BleManager.enableBluetooth()
      .then(() => {
        console.log('Bluetooth is turned on!');
      })
      .catch(error => {
        console.error('Bluetooth enable error:', error);
      });

    if (Platform.OS === 'android') {
      requestPermissions();
    }
  }, []);

  const requestPermissions = async () => {
    try {
      const granted = await PermissionsAndroid.requestMultiple([
        PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
        PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
        PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
        PermissionsAndroid.PERMISSIONS.BLUETOOTH_ADVERTISE,
      ]);

      if (
        granted['android.permission.ACCESS_FINE_LOCATION'] ===
          PermissionsAndroid.RESULTS.GRANTED &&
        granted['android.permission.BLUETOOTH_SCAN'] ===
          PermissionsAndroid.RESULTS.GRANTED &&
        granted['android.permission.BLUETOOTH_CONNECT'] ===
          PermissionsAndroid.RESULTS.GRANTED &&
        granted['android.permission.BLUETOOTH_ADVERTISE'] ===
          PermissionsAndroid.RESULTS.GRANTED
      ) {
        console.log('All permissions granted');
      } else {
        console.log('Permissions denied');
      }
    } catch (err) {
      console.warn('Permission request error:', err);
    }
  };

  useEffect(() => {
    BleManager.start({showAlert: false}).then(() => {
      console.log('BleManager initialized');
    });

    const handleDiscoverPeripheral = peripheral => {
      if (!peripheral.name) {
        peripheral.name = 'NO NAME';
      }
      setDevices(prevDevices => {
        const deviceExists = prevDevices.find(
          device => device.id === peripheral.id,
        );
        if (deviceExists) {
          return prevDevices;
        } else {
          return [...prevDevices, peripheral];
        }
      });
    };

    BleManagerEmitter.addListener(
      'BleManagerDiscoverPeripheral',
      handleDiscoverPeripheral,
    );

    return () => {
      BleManagerEmitter.removeListener(
        'BleManagerDiscoverPeripheral',
        handleDiscoverPeripheral,
      );
    };
  }, []);

  useEffect(() => {
    const stopListener = BleManagerEmitter.addListener(
      'BleManagerStopScan',
      () => {
        setIsScanning(false);
        console.log('Scan is stopped');
      },
    );

    return () => {
      stopListener.remove();
    };
  }, []);

  const startScan = () => {
    if (!isScanning) {
      BleManager.scan([], 5, true)
        .then(() => {
          console.log('Scanning...');
          setIsScanning(true);
          setModalVisible(true);
        })
        .catch(error => {
          console.error('Scan error:', error);
        });
    }
  };

  const stopScan = () => {
    if (isScanning) {
      BleManager.stopScan()
        .then(() => {
          console.log('Stopped scanning');
          setIsScanning(false);
        })
        .catch(error => {
          console.error('Stop scan error:', error);
        });
    }
  };

  const resetScan = () => {
    setDevices([]);
    setConnectedDevices([]);
    setReadData('');
    console.log('Reset complete');
  };

  const connectToDevice = device => {
    BleManager.connect(device.id)
      .then(() => {
        return BleManager.retrieveServices(device.id);
      })
      .then(peripheralInfo => {
        console.log('Services and characteristics:', peripheralInfo);
        setModalVisible(false);
        setConnectedDevices(prevDevices => [...prevDevices, device]);
      })
      .then(() => {
        return BleManager.startNotification(device.id, service, characteristic);
      })

      .then(() => {
        BleManagerEmitter.addListener(
          'BleManagerDidUpdateValueForCharacteristic',
          handleUpdateValueForCharacteristic,
        );
      })
      .catch(error => {
        console.log('Error connecting to device:', error);
      });
  };

  const handleUpdateValueForCharacteristic = data => {
    console.log('Received data from characteristic', data);
    const buffer = Buffer.from(data.value);
    const sensorData = buffer.toString('ascii');
    console.log('Received data:', sensorData);
    setReadData(sensorData);
    Alert.alert('Received data: ' + sensorData);
  };

  const disconnectFromDevice = device => {
    BleManager.disconnect(device.id)
      .then(() => {
        console.log('Disconnected from ' + device.name);
        setConnectedDevices(prevDevices =>
          prevDevices.filter(d => d.id !== device.id),
        );
      })
      .catch(error => {
        console.log('Disconnection error', error);
      });
  };

  const readDataFromDevice = (deviceId, serviceUUID, characteristicUUID) => {
    BleManager.startNotification(deviceId, serviceUUID, characteristicUUID);
    BleManager.read(deviceId, serviceUUID, characteristicUUID)
      .then(() => {
        console.log('Started notifications.');
      })
      .then(readData => {
        console.log(readData, 'readdddd');
        // const buffer = Buffer.from(readData);
        // console.log(buffer, 'bufferrr');
        // const sensorData = buffer.toString('ascii');
        // console.log(sensorData, 'sensorrr');
        // setReadData(sensorData);
      })
      .catch(error => {
        console.error('Notification error:', error);
      });
    BleManager.addListener(
      'BleManagerDidUpdateValueForCharacteristic',
      handleUpdateValueForCharacteristic,
      // data => {
      //   console.log('Received2333 data:', decodeData(data.value));
      //   // Update UI with received data
      // },
    );
  };
  const decodeData = encodedData => {
    // Convert received data to its original format
    return encodedData;
  };

  const sendCom = async peripheral => {
    const data = encodeUtf8('1'); // convert data to bytes

    BleManager.retrieveServices(peripheral)
      .then(() => {
        BleManager.startNotification(peripheral, service, characteristic);
      })
      .then(() => {
        BleManager.writeWithoutResponse(
          peripheral.id,
          service,
          characteristic,
          data,
        );
        console.log(`Sent: ${data}`);
      })
      .catch(error => {
        console.log(`Error writing ${characteristic}: ${error}`);
      });
  };

  return (
    <SafeAreaView style={styles.container}>
      <ScrollView contentContainerStyle={{flex: 1}}>
        <Text style={styles.title}>React Native BLE Manager</Text>

        <View style={styles.deviceList}>
          {connectedDevices.length > 0 ? (
            connectedDevices.map(device => (
              <View key={device.id} style={styles.connectedDevice}>
                <View>
                  <Text style={styles.deviceName}>{device.name}</Text>
                  <Text style={styles.deviceId}>{device.id}</Text>
                </View>
                <TouchableOpacity
                  activeOpacity={0.5}
                  onPress={() => disconnectFromDevice(device)}
                  style={styles.disconnectButton}>
                  <Text style={styles.buttonText}>Disconnect</Text>
                </TouchableOpacity>
              </View>
            ))
          ) : (
            <TouchableOpacity
              activeOpacity={0.5}
              onPress={startScan}
              style={styles.scanButton}>
              <Text style={styles.buttonText}>Scan</Text>
            </TouchableOpacity>
          )}
        </View>

        {connectedDevices.length > 0 && (
          <View style={styles.buttonWrap}>
            <TouchableOpacity
              activeOpacity={0.5}
              onPress={readDataFromDevice(
                connectedDevices[0]?.id,
                '0000ffe0-0000-1000-8000-00805f9b34fb',
                '0000ffe1-0000-1000-8000-00805f9b34fb',
              )}
              style={[styles.resetButton, {backgroundColor: '#0a84ff'}]}>
              <Text style={styles.buttonText}>Start</Text>
            </TouchableOpacity>
            <TouchableOpacity
              activeOpacity={0.5}
              onPress={stopScan}
              style={styles.stopButton}>
              <Text style={styles.buttonText}>Stop</Text>
            </TouchableOpacity>
            <TouchableOpacity
              activeOpacity={0.5}
              onPress={resetScan}
              style={styles.resetButton}>
              <Text style={styles.buttonText}>Reset</Text>
            </TouchableOpacity>
          </View>
        )}

        <Modal visible={modalVisible} animationType="slide">
          <SafeAreaView style={styles.modalContainer}>
            <FlatList
              data={devices}
              renderItem={({item}) => (
                <TouchableOpacity
                  style={styles.deviceContainer}
                  onPress={() => connectToDevice(item)}>
                  <Text style={styles.deviceName}>{item.name}</Text>
                  <Text style={styles.deviceId}>{item.id}</Text>
                </TouchableOpacity>
              )}
              keyExtractor={item => item.id}
            />
            <TouchableOpacity
              activeOpacity={0.5}
              onPress={() => setModalVisible(false)}
              style={styles.scanButton}>
              <Text style={styles.buttonText}>Close</Text>
            </TouchableOpacity>
          </SafeAreaView>
        </Modal>
        <View style={styles.dataContainer}>
          <Text style={styles.dataText}>Received Data:</Text>
          <Text style={styles.dataText}>{readData}</Text>
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 24,
    backgroundColor: Colors.lighter,
  },
  title: {
    fontSize: 24,
    fontWeight: '600',
    color: Colors.black,
    textAlign: 'center',
    marginBottom: 24,
  },
  deviceList: {
    marginBottom: 24,
  },
  connectedDevice: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: 16,
    backgroundColor: '#f0f0f0',
    borderRadius: 8,
    marginBottom: 16,
  },
  deviceContainer: {
    padding: 16,
    backgroundColor: '#f0f0f0',
    borderRadius: 8,
    marginBottom: 16,
  },
  deviceName: {
    fontSize: 18,
    fontWeight: '600',
    color: Colors.black,
  },
  deviceId: {
    fontSize: 12,
    color: Colors.dark,
  },
  scanButton: {
    padding: 16,
    backgroundColor: '#007aff',
    borderRadius: 8,
    alignItems: 'center',
  },
  stopButton: {
    padding: 16,
    backgroundColor: '#ff3b30',
    borderRadius: 8,
    alignItems: 'center',
  },
  resetButton: {
    padding: 16,
    backgroundColor: '#ffcc00',
    borderRadius: 8,
    alignItems: 'center',
  },
  buttonText: {
    color: '#fff',
    fontWeight: '600',
  },
  buttonWrap: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    marginBottom: 24,
  },
  disconnectButton: {
    padding: 8,
    backgroundColor: '#ff3b30',
    borderRadius: 8,
  },
  modalContainer: {
    flex: 1,
    padding: 24,
    backgroundColor: Colors.lighter,
  },
  dataContainer: {
    marginTop: 16,
    padding: 16,
    backgroundColor: '#f0f0f0',
    borderRadius: 8,
  },
  dataText: {
    fontSize: 16,
    color: Colors.black,
  },
});

export default BleConnectDevice;

针对性优化方案

1. Start按钮触发逻辑修复

当前代码中Start按钮的onPress直接执行readDataFromDevice,会导致组件挂载时自动触发该方法,而非点击时触发。需改为箭头函数包裹:

<TouchableOpacity
  activeOpacity={0.5}
  onPress={() => readDataFromDevice(
    connectedDevices[0]?.id,
    '0000ffe0-0000-1000-8000-00805f9b34fb',
    '0000ffe1-0000-1000-8000-00805f9b34fb',
  )}
  style={[styles.resetButton, {backgroundColor: '#0a84ff'}]}>
  <Text style={styles.buttonText}>Start</Text>
</TouchableOpacity>

2. 标签存在检测逻辑补充

按照需求,Start按钮点击时需先检测范围内是否有RFID标签。可通过向阅读器发送查询指令(需匹配阅读器协议),监听响应判断是否存在标签:

const checkForTags = async (deviceId) => {
  // 替换为阅读器支持的查询标签指令
  const queryCommand = encodeUtf8('CHECK_TAGS'); 
  await BleManager.writeWithoutResponse(deviceId, service, characteristic, queryCommand);
  // 后续通过handleUpdateValueForCharacteristic回调判断是否有标签返回
};

// 修改Start按钮触发逻辑
const handleStart = async () => {
  const deviceId = connectedDevices[0]?.id;
  if (!deviceId) return;
  await checkForTags(deviceId);
  // 若检测到标签,启动数据读取
  readDataFromDevice(deviceId, '0000ffe0-0000-1000-8000-00805f9b34fb', '0000ffe1-0000-1000-8000-00805f9b34fb');
};

3. 重复监听问题修复

connectToDevice和readDataFromDevice中重复添加了特征值更新监听,会导致回调多次触发。建议在组件挂载时统一添加监听,卸载时移除:

useEffect(() => {
  const valueUpdateListener = BleManagerEmitter.addListener(
    'BleManagerDidUpdateValueForCharacteristic',
    handleUpdateValueForCharacteristic,
  );

  return () => {
    valueUpdateListener.remove();
  };
}, []);

4. 数据解析优化

RFID标签数据多为十六进制或特定编码,当前ASCII解析可能导致乱码,需根据阅读器协议调整解析逻辑:

const handleUpdateValueForCharacteristic = data => {
  console.log('Received raw data:', data.value);
  // 十六进制转字符串示例
  const hexString = Buffer.from(data.value).toString('hex');
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 00:09:56