使用react-native-ble-plx插件:
import { createContext, useState, useEffect, useContext, useRef } from 'react';
import { BleManager } from 'react-native-ble-plx';
import * as Location from 'expo-location';
import { Platform, PermissionsAndroid, ToastAndroid, Dimensions } from 'react-native';
import { Buffer } from '@craftzdog/react-native-buffer';
// 创建蓝牙上下文
const BlueToothContext = createContext();
// 蓝牙状态枚举
export const BluetoothState = {
UNKNOWN: 'unknown',
POWERED_OFF: 'poweredOff',
POWERED_ON: 'poweredOn',
RESETTING: 'resetting',
UNAUTHORIZED: 'unauthorized',
UNSUPPORTED: 'unsupported',
};
//请求蓝牙权限
const requestBluetoothPermission = async () => {
if (Platform.OS === 'ios') {
return true;
}
if (Platform.OS === 'android' && PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION) {
const apiLevel = parseInt(Platform.Version.toString(), 10);
if (apiLevel < 31) {
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION);
return granted === PermissionsAndroid.RESULTS.GRANTED;
}
if (PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN && PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT) {
const result = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
]);
return (
result['android.permission.BLUETOOTH_CONNECT'] === PermissionsAndroid.RESULTS.GRANTED &&
result['android.permission.BLUETOOTH_SCAN'] === PermissionsAndroid.RESULTS.GRANTED &&
result['android.permission.ACCESS_FINE_LOCATION'] === PermissionsAndroid.RESULTS.GRANTED
);
}
}
return false;
};
// 蓝牙Provider组件
export const BlueToothProvider = ({ children }) => {
// 蓝牙状态
const [bluetoothStatus, setBluetoothStatus] = useState(BluetoothState.UNKNOWN);
// BLE管理器实例
const [manager, setManager] = useState(null);
// 设备列表
const [devices, setDevices] = useState([]);
// 已连接设备
const connectedDeviceRef = useRef(null);
// const [connectedDevice, setConnectedDevice] = useState(null);
//已连接设备的设备ID
const connectedDeviceIDRef = useRef(["F9", "06", "78"]);
// 扫描状态
const [isScanning, setIsScanning] = useState(false);
// 连接状态
const [isConnecting, setIsConnecting] = useState(false);
// 错误信息
const [error, setError] = useState(null);
//可写入
const writableCharacteristic = useRef({});
//可监听
const monitorCharacteristic = useRef({});
// 监听器订阅列表
const monitorSubscriptions = useRef(null);
//指令-响应映射
const instructionResponseMap = useRef(new Map());
//消息提示
const showToast = (message) => {
ToastAndroid.showWithGravityAndOffset(
message,
ToastAndroid.SHORT,
ToastAndroid.TOP,
0,
-`${Dimensions.get('window').height / 2}`
);
};
// 初始化BLE管理器
useEffect(() => {
const bleManager = new BleManager();
setManager(bleManager);
// 监听蓝牙状态变化
const subscription = bleManager.onStateChange((state) => {
let newStatus = BluetoothState.UNKNOWN;
switch (state) {
case 'PoweredOn':
newStatus = BluetoothState.POWERED_ON;
break;
case 'PoweredOff':
newStatus = BluetoothState.POWERED_OFF;
break;
case 'Resetting':
newStatus = BluetoothState.RESETTING;
break;
case 'Unauthorized':
newStatus = BluetoothState.UNAUTHORIZED;
break;
case 'Unsupported':
newStatus = BluetoothState.UNSUPPORTED;
break;
default:
newStatus = BluetoothState.UNKNOWN;
}
setBluetoothStatus(newStatus);
}, true);
// 请求位置权限(Android需要)
const requestLocationPermission = async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
setError('需要位置权限才能使用蓝牙功能');
}
};
requestLocationPermission();
return () => {
subscription.remove();
bleManager.destroy();
};
}, []);
// 开始扫描设备
const startScan = async () => {
if (!manager || isScanning) return;
const hasPermission = await requestBluetoothPermission();
if (!hasPermission) return;
// 检查蓝牙状态
if (bluetoothStatus !== BluetoothState.POWERED_ON) {
const errorMessage = bluetoothStatus === BluetoothState.POWERED_OFF ? '蓝牙未开启,请先开启蓝牙' : '蓝牙状态异常';
setError(errorMessage);
return;
}
try {
// 立即停止之前可能正在进行的扫描
if (isScanning) {
manager.stopDeviceScan();
}
setDevices([
]);
setIsScanning(true);
setError(null);
// 记录本次扫描中发现的设备ID
const discoveredDeviceIds = new Set();
// 开始扫描,过滤指定服务(可选)
manager.startDeviceScan(null, null, (error, device) => {
if (error) {
const errorMessage = error.reason || error.message || '蓝牙扫描失败';
setError(errorMessage);
setIsScanning(false);
return;
}
if (device) {
// 记录设备ID
discoveredDeviceIds.add(device.id);
// 更新设备列表,添加或更新设备
setDevices(prevDevices => {
// 检查是否已存在相同id的设备
const deviceIndex = prevDevices.findIndex(d => d.id === device.id);
if (deviceIndex >= 0) {
// 更新已存在的设备
const updatedDevices = [...prevDevices];
updatedDevices[deviceIndex] = device;
return updatedDevices;
} else {
// 添加新设备
return [...prevDevices, device];
}
});
}
});
// 扫描10秒后停止并清理离线设备
setTimeout(() => {
stopScan();
// 清理离线设备:只保留本次扫描中发现的设备
setDevices(prevDevices => {
return prevDevices.filter(device => discoveredDeviceIds.has(device.id));
});
}, 10000);
} catch (err) {
setError('启动扫描失败');
setIsScanning(false);
}
};
// 停止扫描设备
const stopScan = () => {
if (manager && isScanning) {
manager.stopDeviceScan();
setIsScanning(false);
}
};
//刷新设备列表
const refreshDevices = () => {
stopScan();
startScan();
};
// 连接到设备
const connectToDevice = (deviceId) => {
if (!manager || isConnecting) return;
return new Promise(async (resolve, reject) => {
setIsConnecting(true);
setError(null);
try {
// 1.查找设备
const device = await manager.connectToDevice(deviceId);
//设置设备的mtu
const result = await manager.requestMTUForDevice(deviceId, 512);
// 2.发现服务
const discoveredDevice = await device.discoverAllServicesAndCharacteristics();
// 3. 获取服务列表
const servicesList = await discoveredDevice.services();
// 4. 获取每个服务的特征
const allCharacteristics = [];
for (const service of servicesList) {
const serviceCharacteristics = await discoveredDevice.characteristicsForService(
service.uuid
);
allCharacteristics.push(...serviceCharacteristics);
}
//可写入
let target = allCharacteristics.find(item => item.isWritableWithResponse && item.isWritableWithoutResponse);
writableCharacteristic.current = target;
//可监听
let monitorTarget = allCharacteristics.find(item => item.isNotifiable);
monitorCharacteristic.current = monitorTarget;
// 设置连接监听器
manager.onDeviceDisconnected(deviceId, (error, disconnectedDevice) => {
if (disconnectedDevice?.id === deviceId) {
connectedDeviceRef.current = null;
setError('设备已断开连接');
}
});
connectedDeviceRef.current = device;
//开启监听器
const monitorStarted = monitorDevice(monitorCharacteristic.current.serviceUUID, monitorCharacteristic.current.uuid);
setIsConnecting(false);
resolve(device);
return device;
} catch (err) {
setError(err.message);
setIsConnecting(false);
reject('连接失败');
return null;
}
});
};
//储存已连接设备的设备ID
const setConnectedDeviceID = (deviceId) => {
connectedDeviceIDRef.current = deviceId;
};
// 断开设备连接
const disconnectFromDevice = async () => {
if (!connectedDeviceRef.current) return;
try {
// 取消所有监听器
monitorSubscriptions.current.remove();
monitorSubscriptions.current = null;
await connectedDeviceRef.current.cancelConnection();
connectedDeviceRef.current = null;
// 清空特征引用
writableCharacteristic.current = {};
monitorCharacteristic.current = {};
// 清空已连接设备ID
connectedDeviceIDRef.current = null;
} catch (err) {
setError(err.message);
}
};
// 写入数据到设备, 服务id、特征id、指令
const writeToDevice = (serviceId, characteristicId, data) => {
return new Promise((resolve, reject) => {
if (!connectedDeviceRef.current) {
reject('未连接到任何设备');
showToast('未连接到任何设备');
return false;
}
//数据重构
const buffer = data.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16);
});
const typedArray = new Uint8Array(buffer);
let value = Buffer.from(typedArray).toString('base64');
connectedDeviceRef.current.writeCharacteristicWithResponseForService(
serviceId,
characteristicId,
value
).then((res) => {
resolve(true);
}).catch(err => {
console.log("写入数据失败", err);
reject(err.message || "写入数据失败");
setError(err.message || "写入数据失败");
});
});
};
// 读取设备数据
const readFromDevice = async (serviceId, characteristicId) => {
if (!connectedDeviceRef.current) {
showToast('未连接到任何设备');
return null;
}
try {
const characteristic = await connectedDeviceRef.current.readCharacteristicForService(
serviceId,
characteristicId
);
return characteristic.value;
} catch (err) {
setError(err.message);
return null;
}
};
// 监听设备数据通知
const monitorDevice = (serviceId, characteristicId) => {
if (!connectedDeviceRef.current) {
showToast('未连接到任何设备');
return false;
}
if (!serviceId || !characteristicId) {
setError('服务ID和特征ID不能为空');
return false;
}
try {
// 如果已有订阅,先移除
if (monitorSubscriptions.current) {
monitorSubscriptions.current.remove();
}
const subscription = connectedDeviceRef.current.monitorCharacteristicForService(
serviceId,
characteristicId,
(error, characteristic) => {
if (error) {
const errorMessage = error.reason || error.message || '设备通知监听失败';
setError(errorMessage);
} else {
let result = characteristic.value;
console.log("接收到通知", result);
//找到对应的callback并执行
const key = result.substring(6, 8);
let callback = instructionResponseMap.current.get(key);
if (callback) {
callback(result);
}
}
},
);
// 保存订阅到列表,以便后续管理
monitorSubscriptions.current = subscription;
return true;
} catch (err) {
const errorMessage = err.reason || err.message || '设备通知监听失败';
setError(errorMessage);
return false;
}
};
// 发送指令
const sendInstruction = (data, callback, sendSuccessCallback) => {
// 存储回调函数
//取data下标6-7作为key
const key = data.substring(6, 8);
instructionResponseMap.current.set(key, callback);
console.log('开始发送指令', data);
return new Promise((resolve, reject) => {
writeToDevice(writableCharacteristic.current.serviceUUID, writableCharacteristic.current.uuid, data).then(res => {
sendSuccessCallback && sendSuccessCallback(res);
resolve(res);
}).catch(err => {
reject(err || "发送指令失败");
});
});
};
// 上下文值
const contextValue = {
bluetoothStatus,
manager,
devices,
connectedDeviceRef,
isScanning,
isConnecting,
error,
startScan,
stopScan,
refreshDevices,
connectToDevice,
disconnectFromDevice,
writeToDevice,
readFromDevice,
monitorDevice,
sendInstruction,
setConnectedDeviceID,
connectedDeviceIDRef
};
return (
<BlueToothContext.Provider value={contextValue}>
{children}
</BlueToothContext.Provider>
);
};
// 自定义Hook,用于使用蓝牙上下文
export const useBlueToothContext = () => {
const context = useContext(BlueToothContext);
if (!context) {
throw new Error('useBlueToothContext must be used within a BlueToothProvider');
}
return context;
};
1.关键地方:命令如何转格式发送给蓝牙设备
javascript
//数据重构(data: AA5502560157)
const buffer = data.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16);
});
const typedArray = new Uint8Array(buffer);
let value = Buffer.from(typedArray).toString('base64'); //qlUCVgFX
1.其中原始的data是十六进制数字拼接的字符串
(0xAA、0x55、0x02、0x56、0x01、0x57 -> AA5502560157)
2.先转为十进制的数组:buffer = [170, 85, 2, 86, 1, 87]
3.然后通过new Uint8Array处理二进制数据
4.最后转为base64发给蓝牙设备