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

效果图

相关推荐
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte5 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc
NEXT065 小时前
前端算法:从 O(n²) 到 O(n),列表转树的极致优化
前端·数据结构·算法
剪刀石头布啊5 小时前
生成随机数,Math.random的使用
前端