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>
  );
}

效果图

相关推荐
熊的猫35 分钟前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
瑶琴AI前端1 小时前
uniapp组件实现省市区三级联动选择
java·前端·uni-app
会发光的猪。1 小时前
如何在vscode中安装git详细新手教程
前端·ide·git·vscode
我要洋人死2 小时前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人2 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人2 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR3 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香3 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596933 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai3 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书