react native如何发送蓝牙命令

使用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发给蓝牙设备

相关推荐
博主花神2 小时前
【TypeScript】梳理
javascript·ubuntu·typescript
淡笑沐白2 小时前
ECharts入门指南:数据可视化实战
前端·javascript·echarts
光影少年2 小时前
RN中如何处理权限申请(相机、相册、定位、存储)?使用第三方库还是原生封装?
react native·react.js·掘金·金石计划
非科班Java出身GISer2 小时前
ArcGIS JS 基础教程(1):地图初始化(含AMD/ESM两种引入方式)
javascript·arcgis·arcgis js·arcgis js 初始化·arcgis js 地图初始化
A923A3 小时前
【从零开始学 React | 第四章】useEffect和自定义Hook
前端·react.js·fetch·useeffect
前端摸鱼匠3 小时前
Vue 3 的defineProps编译器宏:详解<script setup>中defineProps的使用
前端·javascript·vue.js·前端框架·ecmascript
天外天-亮3 小时前
Vue2.0 + jsmind:开发思维导图
javascript·vue.js·jsmind
LIO3 小时前
React 零基础入门,一篇搞懂核心用法(适合新手)
前端·react.js
蜡台3 小时前
JavaScript async和awiat 使用
开发语言·前端·javascript·async·await