我项目需要用到蓝牙模块,蓝牙扫描到设备并且获取到电量显示到页面上,因此我做了如下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>
);
}
效果图