RN使用蓝牙扫描

我项目需要用到蓝牙模块,蓝牙扫描到设备并且获取到电量显示到页面上,因此我做了如下demo,使用了react-native-ble-plx这个插件 点击进入官方文档官方文档

1.安卓环境配置(ios暂定,还没做ios,不过下面的方法是兼容的,自行配置ios权限) android/app/src/main/AndroidManifest.xml加入以下权限代码

java 复制代码
  <uses-permission android:name="android.permission.BLUETOOTH" />
  <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
  <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

2.封装蓝牙相关模块工具包

js 复制代码
import {BleManager} from 'react-native-ble-plx'; // 引入蓝牙模块
import {PermissionsAndroid, Platform} from 'react-native';
import {Buffer} from 'buffer';
// 下面这两个常量为了获取设备的电量信息(每个人设备不一样,这里仅作参考)
const batteryServiceUUID = '0000180f-0000-1000-8000-00805f9b34fb'; // 电量服务的UUID
const batteryLevelCharacteristicUUID = '00002a19-0000-1000-8000-00805f9b34fb'; // 电量特征的UUID

// 授权(位置,蓝牙扫描,蓝牙连接三个权限,但是我这目前有一个问题,蓝牙连接和蓝牙扫描权限会重复弹出两次框,这个bug后期再改)
const bleManager = new BleManager();
// 位置信息和蓝牙权限信息(请求权限)
export const requestPermissions = async callback => {
  if (Platform.OS === 'android') {
    try {
      let grantedLocation = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
      );
      let grantedBluetoothScan = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
        {
          title: '请求蓝牙扫描权限',
          message: '我们需要您的蓝牙扫描权限来扫描设备。',
          buttonNeutral: '稍后询问',
          buttonNegative: '拒绝',
          buttonPositive: '同意',
        },
      );
      let grantedBluetoothConnect = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
        {
          title: '请求蓝牙连接权限',
          message: '我们需要您的蓝牙连接权限来扫描设备。',
          buttonNeutral: '稍后询问',
          buttonNegative: '拒绝',
          buttonPositive: '同意',
        },
      );

      if (
        grantedLocation === PermissionsAndroid.RESULTS.GRANTED &&
        grantedBluetoothConnect === PermissionsAndroid.RESULTS.GRANTED
      ) {
        console.log('已有权限');
        callback(true);
      } else {
        console.log('某些权限被拒绝');
        callback(false);
      }
    } catch (error) {
      console.error('权限请求错误:', error);
      callback(false);
    }
  } else {
    callback(false);
  }
};

// 扫描蓝牙设备
export const scanDevice = callback => {
  // 参数1可以为null不做限制,也可以为服务的uuid,例如['180D']:心率服务的uuid,这样就只扫描过滤出关于心率服务的设备的uuid,如下
  // bleManager.startDeviceScan(['180D'], null, (error, scannedDevice)
  bleManager.startDeviceScan(null, null, (error, scannedDevice) => {
    if (error) {
      console.error('扫描错误:', error);
      callback(ture, error);
      return;
    }
    console.log('scannedDevice', scannedDevice);
    callback(false, scannedDevice);
  });
};

// 返回信号强度(传入参数为上面的scannedDevice.rssi),每个数字都代表一个信号,我这就是0-5六个信号
export const BluetoothBatteryLevel = level => {
  let signal = 0;
  if (level < 0) {
    signal = Math.abs(level) / 10;
  } else {
    signal = 0;
  }
  if (signal > 0 && signal <= 3) {
    return 5;
  } else if (signal > 3 && signal <= 6) {
    return 4;
  } else if (signal > 6 && signal <= 9) {
    return 3;
  } else if (signal > 9 && signal <= 12) {
    return 2;
  } else if (signal > 12 && signal <= 15) {
    return 1;
  } else {
    return 0;
  }
};

// 断开某个连接的设备,传入的是scannedDevice.id设备编码,如E6:13:27:12:BF:92
export const disconnectAllDevices = async a => {
  try {
    if (bleManager) {
      await bleManager.cancelDeviceConnection(a);
    }
  } catch (error) {
    console.log('断开连接时出错:', error);
  }
};

// 获取蓝牙设备电量(传入的是整个scannedDevice)
export const connectAndDiscoverServices = async (device, callback) => {
  try {
    if (device) {
      bleManager.stopDeviceScan(); // 停止扫描
      device
        .connect() // 连接设备
        .then(device => {
          console.log('设备已连接:', device.id);
          return device.discoverAllServicesAndCharacteristics();
        })
        .then(device => {
          return device.services();
        })
        .then(services => {
          // 下面这堆逻辑是处理蓝牙设备电量的
          services.forEach(service => {
            if (service.uuid === batteryServiceUUID) {
              service.characteristics().then(characteristics => {
                characteristics.forEach(characteristic => {
                  if (characteristic.uuid === batteryLevelCharacteristicUUID) {
                    characteristic
                      .read()
                      .then(readData => {
                        console.log('电量:', readData.value);
                        // Step 1: 使用 base64 解码
                        // 这里的Buffer自行安装 yarn add buffer 导入方法如上所示
                        const decodedBytes = Buffer.from(
                          readData.value,
                          'base64',
                        );
                        // Step 2: 将字节数组转换为十六进制字符串
                        const hexString = decodedBytes.toString('hex');
                        // Step 3: 将十六进制字符串解析为十进制数值
                        const decimalValue = parseInt(hexString, 16);
                        console.log('解析后的十进制数值:', decimalValue);
                        callback(true, decimalValue, device); // 将电量信息返回
                        // 这里的 readData.value 可能需要根据设备的具体协议进行解析
                        // 断开连接
                        disconnectAllDevices(device.id);
                      })
                      .catch(error => {
                        console.error('读取电量信息出错:', error);
                        // 断开连接
                        disconnectAllDevices(device.id);
                        callback(false, error);
                      });
                  }
                });
              });
            }
          });
        })
        .catch(error => {
          console.error('连接设备出错:', error);
        });
    }
  } catch (error) {
    console.error('连接设备时出错:', error);
    return null;
  }
};

// 停止扫描
export const stopScan = () => {
  bleManager.stopDeviceScan();
};

使用demo

js 复制代码
import {Button, View, TouchableOpacity, Text} from 'react-native';
import {
  requestPermissions, // 请求设备权限
  scanDevice, // 开始扫描设备
  BluetoothBatteryLevel, // 返回信号强度
  connectAndDiscoverServices, // 连接设备获取设备电量,然后断开连接(断开连接逻辑在工具包里面)
  stopScan, // 停止扫描
} from './uitls';
import {useState} from 'react';

export default function App() {
  const [dataArr, setDataArr] = useState([]); // 设备列表
  const [dianliang, setDianLiang] = useState(0); // 电量

  // 更新设备数据(已经到扫描到的不会重复添加)
  function devicesArr(str) {
    let newData = str;
    let found = false;
    setDataArr(arr => {
      arr = arr.map(item => {
        if (item.name === newData.name) {
          found = true;
          return {...item, xinhao: newData.xinhao};
        }
        return item;
      });
      if (!found) {
        arr.push(newData);
      }
      return arr;
    });
  }

  // 扫描设备
  const scanDevices = async () => {
    // 请求权限
    await requestPermissions(res => {
      if (!res) {
        console.log('未获得必要权限');
        return;
      }
      console.log('开始扫描设备');
      scanDevice((error, scannedDevice) => {
        if (error) {
          console.error('扫描错误:', error);
          return;
        }
        devicesArr({
          name: scannedDevice.name,
          xinhao: BluetoothBatteryLevel(scannedDevice.rssi),
          devices: scannedDevice,
        });
      });
    });
  };

  // 连接设备获取电量,然后断开设备
  async function connectDevice(index, item) {
    await connectAndDiscoverServices(
      item.devices,
      (flag, dianliang, device) => {
        // dianliang 是设备电量 , flag代表扫描状态, device代表当前设备信息
        // 断开设备的逻辑在工具文件内
        if (flag) {
          console.log('flag,dianliang,device', flag, dianliang, device);
          setDianLiang(dianliang);
        }
      },
    );
  }

  return (
    <View>
      <Button
        title="点击获取蓝牙权限"
        onPress={() => {
          requestPermissions(res => {
            console.log('是否获取到了权限', res);
          });
        }}></Button>
      <Button title="开始扫描" onPress={scanDevices}></Button>
      <Button title="停止扫描" onPress={stopScan}></Button>
      <Text>选中的设备电量为:{dianliang}</Text>
      {dataArr.map((item, index) => {
        return (
          <TouchableOpacity key={index}>
            <View
              style={{
                flexDirection: 'row',
                justifyContent: 'space-around',
                alignItems: 'center',
                height: 50,
              }}>
              <Text>信号强度为:{item.xinhao}</Text>
              <Text>{item.name}</Text>
              <TouchableOpacity
                onPress={() => {
                  connectDevice(index, item);
                }}
                style={{
                  flexDirection: 'row',
                  flexDirection: 'row',
                  width: 80,
                  height: 35,
                  borderRadius: 10,
                  justifyContent: 'space-around',
                  alignItems: 'center',
                  borderWidth: 1,
                }}>
                <Text
                  style={{
                    fontSize: 15,
                  }}>
                  获取当前电量完成后断开蓝牙
                </Text>
              </TouchableOpacity>
            </View>
            <View style={{borderWidth: 0.2, borderColor: '#CCCCCC'}}></View>
          </TouchableOpacity>
        );
      })}
    </View>
  );
}

效果图

相关推荐
前端小小王29 分钟前
React Hooks
前端·javascript·react.js
迷途小码农零零发38 分钟前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
娃哈哈哈哈呀1 小时前
vue中的css深度选择器v-deep 配合!important
前端·css·vue.js
旭东怪1 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
ekskef_sef3 小时前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6413 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻4 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云4 小时前
npm淘宝镜像
前端·npm·node.js
dz88i84 小时前
修改npm镜像源
前端·npm·node.js
Jiaberrr4 小时前
解锁 GitBook 的奥秘:从入门到精通之旅
前端·gitbook