我项目需要用到蓝牙模块,蓝牙扫描到设备并且获取到电量显示到页面上,因此我做了如下demo,使用了react-native-ble-plx这个插件 点击进入官方文档官方文档
1.安卓环境配置(ios暂定,还没做ios,不过下面的方法是兼容的,自行配置ios权限)
android/app/src/main/AndroidManifest.xml加入以下权限代码
<uses-permission android:name="android.permission.BLUETOOTH" /><uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /><uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
2.封装蓝牙相关模块工具包
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
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><Buttontitle="点击获取蓝牙权限"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}><Viewstyle={{flexDirection: 'row',justifyContent: 'space-around',alignItems: 'center',height: 50,}}><Text>信号强度为:{item.xinhao}</Text><Text>{item.name}</Text><TouchableOpacityonPress={() => {connectDevice(index, item);}}style={{flexDirection: 'row',flexDirection: 'row',width: 80,height: 35,borderRadius: 10,justifyContent: 'space-around',alignItems: 'center',borderWidth: 1,}}><Textstyle={{fontSize: 15,}}>获取当前电量完成后断开蓝牙</Text></TouchableOpacity></View><View style={{borderWidth: 0.2, borderColor: '#CCCCCC'}}></View></TouchableOpacity>);})}</View>);
}
效果图