小程序蓝牙API能力探索 3——如何开发一个蓝牙小程序?

本文将提供一个从零开始、端到端的微信小程序BLE开发流程,包含完整的代码示例和注意事项。

1. 准备工作:权限配置

在调用任何蓝牙API之前,必须在小程序的全局配置文件 app.json 中声明所需权限。

json 复制代码
{
  "permission": {
    "scope.bluetooth": {
      "desc": "需要使用蓝牙功能连接您的设备"
    },
    "scope.userLocation": {
      "desc": "需要您的位置信息以搜索蓝牙设备"
    }
  }
}

注意 :在Android系统上,搜索蓝牙设备需要用户授权地理位置信息,因此建议同时声明 scope.userLocation 权限。

2. 核心开发流程与代码示例

以下是标准的BLE通信流程,建议将相关逻辑封装在独立的模块中以便于管理和复用。

第1步:初始化蓝牙模块

这是所有操作的起点。调用 wx.openBluetoothAdapter 来初始化小程序与手机蓝牙模块的会话。

ts 复制代码
// bluetooth.js
function initBluetooth() {
  wx.openBluetoothAdapter({
    success: (res) => {
      console.log('初始化蓝牙模块成功', res);
      // 成功后,可以开始搜索设备
      startDeviceDiscovery();
    },
    fail: (res) => {
      console.error('初始化蓝牙模块失败', res);
      if (res.errCode === 10001) { // 常见错误:蓝牙未开启
        wx.showToast({
          title: '请开启手机蓝牙',
          icon: 'none',
          duration: 2000
        });
        // 可以监听蓝牙适配器状态,在用户开启后自动重试
        wx.onBluetoothAdapterStateChange(function(res) {
          if (res.available) {
            initBluetooth();
          }
        });
      }
    }
  });
}

第2步:开始搜索周边设备

初始化成功后,调用 wx.startBluetoothDevicesDiscovery 开始扫描。

ts 复制代码
function startDeviceDiscovery() {
  wx.showLoading({ title: '正在搜索设备...' });
  wx.startBluetoothDevicesDiscovery({
    // services: ['FEE7'], // 可选:根据主服务UUID过滤,可提升效率
    allowDuplicatesKey: false, // 不允许重复上报同一个设备
    success: (res) => {
      console.log('开始搜索成功', res);
      // 监听新发现的设备
      onDeviceFound();
    },
    fail: (err) => {
      console.error('搜索设备失败', err);
    }
  });
}

最佳实践 :搜索操作非常耗电。一旦找到目标设备并开始连接,应立即调用 wx.stopBluetoothDevicesDiscovery() 停止搜索。

第3步:监听并获取发现的设备

通过 wx.onBluetoothDeviceFound 监听扫描到的新设备,并维护一个设备列表。

ts 复制代码
let foundDevices = []; // 用于存储和去重

function onDeviceFound() {
  wx.onBluetoothDeviceFound((res) => {
    res.devices.forEach(device => {
      // 过滤掉无名称或信号弱的设备
      if (!device.name && !device.localName) return;
      
      const isExist = foundDevices.some(item => item.deviceId === device.deviceId);
      if (!isExist) {
        foundDevices.push(device);
        // 更新UI,展示设备列表给用户选择
        // getApp().globalData.devices = foundDevices; 
        console.log('发现新设备:', device.name || device.localName, device.deviceId);
      }
    });
  });
}

第4步:连接选定的设备

用户选择设备后,使用其 deviceId 调用 wx.createBLEConnection 发起连接。

ts 复制代码
function connectToDevice(deviceId) {
  wx.showLoading({ title: '正在连接...' });
  // 连接前务必停止搜索
  wx.stopBluetoothDevicesDiscovery();

  wx.createBLEConnection({
    deviceId: deviceId,
    timeout: 10000, // 超时时间10秒
    success: (res) => {
      console.log('设备连接成功', res);
      wx.hideLoading();
      // 连接成功,下一步是获取设备的服务
      getDeviceServices(deviceId);
    },
    fail: (err) => {
      console.error('设备连接失败', err);
      wx.hideLoading();
      wx.showToast({ title: '连接失败,请重试', icon: 'none' });
    }
  });
}

第5步:获取设备的所有服务(Service)

连接成功后,必须先获取设备提供的服务列表,才能进行后续操作。

ts 复制代码
function getDeviceServices(deviceId) {
  wx.getBLEDeviceServices({
    deviceId: deviceId,
    success: (res) => {
      console.log('获取设备服务成功:', res.services);
      // 通常需要根据业务协议,找到特定的服务UUID
      const targetService = res.services.find(service => service.uuid.includes('YOUR_SERVICE_UUID'));
      if (targetService) {
        getDeviceCharacteristics(deviceId, targetService.uuid);
      } else {
        wx.showToast({ title: '未找到目标服务', icon: 'none' });
      }
    },
    fail: (err) => {
      console.error('获取设备服务失败', err);
    }
  });
}

第6步:获取服务下的特征值(Characteristic)

获取到服务后,再获取该服务下的所有特征值,这些特征值是数据读写的真正端点。

ts 复制代码
let writeCharacteristicId = ''; // 用于写入的特征值ID
let notifyCharacteristicId = ''; // 用于接收通知的特征值ID

function getDeviceCharacteristics(deviceId, serviceId) {
  wx.getBLEDeviceCharacteristics({
    deviceId: deviceId,
    serviceId: serviceId,
    success: (res) => {
      console.log('获取特征值成功:', res.characteristics);
      for (let char of res.characteristics) {
        // 根据特征值的属性(properties)区分其功能
        if (char.properties.write) {
          writeCharacteristicId = char.uuid;
          console.log('找到写特征值:', writeCharacteristicId);
        }
        if (char.properties.notify || char.properties.indicate) {
          notifyCharacteristicId = char.uuid;
          console.log('找到通知特征值:', notifyCharacteristicId);
          // 关键一步:启用通知
          enableNotifications(deviceId, serviceId, notifyCharacteristicId);
        }
      }
    },
    fail: (err) => {
      console.error('获取特征值失败', err);
    }
  });
}

第7步:数据通信

a. 启用特征值通知(接收数据准备)

要接收设备主动推送的数据,必须先订阅该特征值的通知。

ts 复制代码
function enableNotifications(deviceId, serviceId, characteristicId) {
  wx.notifyBLECharacteristicValueChange({
    state: true, // true为启用
    deviceId: deviceId,
    serviceId: serviceId,
    characteristicId: characteristicId,
    success: (res) => {
      console.log('启用通知成功', res);
      // 监听数据回调
      onCharacteristicValueChange();
    },
    fail: (err) => {
      console.error('启用通知失败', err);
    }
  });
}

b. 监听特征值变化(接收数据)

启用通知后,通过 wx.onBLECharacteristicValueChange 即可接收到设备上报的数据。

ts 复制代码
function onCharacteristicValueChange() {
  wx.onBLECharacteristicValueChange((res) => {
    // res.value 是一个 ArrayBuffer,需要根据协议解析
    const buffer = res.value;
    const dataView = new DataView(buffer);
    // 示例:读取第一个字节为无符号8位整数
    // const value = dataView.getUint8(0); 
    console.log('收到设备数据:', buffer);
  });
}

c. 向设备写入数据

使用 wx.writeBLECharacteristicValue 发送指令或数据。注意,发送的数据必须是 ArrayBuffer 格式。

ts 复制代码
function writeData(deviceId, serviceId, characteristicId, commandHex) {
  // 将16进制字符串命令转换为ArrayBuffer
  const buffer = new ArrayBuffer(commandHex.length / 2);
  const dataView = new DataView(buffer);
  for (let i = 0; i < commandHex.length / 2; i++) {
    dataView.setUint8(i, parseInt(commandHex.substring(i * 2, i * 2 + 2), 16));
  }

  wx.writeBLECharacteristicValue({
    deviceId: deviceId,
    serviceId: serviceId,
    characteristicId: characteristicId,
    value: buffer,
    success: (res) => {
      console.log('写入数据成功', res);
    },
    fail: (err) => {
      console.error('写入数据失败', err);
    }
  });
}

第8步:断开连接与释放资源

当不再需要与设备通信时(例如退出页面),应主动断开连接并关闭蓝牙适配器,以释放系统资源。

ts 复制代码
function disconnectAndClose(deviceId) {
  wx.closeBLEConnection({
    deviceId: deviceId,
    success: (res) => {
      console.log('断开连接成功', res);
    }
  });

  wx.closeBluetoothAdapter({
    success: (res) => {
      console.log('关闭蓝牙模块成功', res);
    }
  });
}

Tips

  1. 及时停止搜索startBluetoothDevicesDiscovery 是一个高耗能操作,在发起连接前或页面隐藏时,务必调用 stopBluetoothDevicesDiscovery 停止搜索。
  2. 资源释放 :在页面 onUnloadonHide生命周期中,调用 closeBLEConnectioncloseBluetoothAdapter 来断开连接和释放资源,避免内存泄漏和不必要的电量消耗。
  3. 数据格式 :所有写入和收到的数据都是 ArrayBuffer 格式,需要根据硬件设备的协议进行精确的序列化和反序列化。
相关推荐
寅时码35 分钟前
我开源了一款 Canvas “瑞士军刀”,十几种“特效与工具”开箱即用
前端·开源·canvas
CF14年老兵37 分钟前
🚀 React 面试 20 题精选:基础 + 实战 + 代码解析
前端·react.js·redux
CF14年老兵38 分钟前
2025 年每个开发人员都应该知道的 6 个 VS Code AI 工具
前端·后端·trae
十五_在努力41 分钟前
参透 JavaScript —— 彻底理解 new 操作符及手写实现
前端·javascript
拾光拾趣录1 小时前
🔥99%人答不全的安全链!第5问必翻车?💥
前端·面试
IH_LZH1 小时前
kotlin小记(1)
android·java·前端·kotlin
lwlcode1 小时前
前端大数据渲染性能优化 - 分时函数的封装
前端·javascript
Java技术小馆1 小时前
MCP是怎么和大模型交互
前端·面试·架构
玲小珑1 小时前
Next.js 教程系列(二十二)代码分割与打包优化
前端·next.js
coding随想1 小时前
HTML5插入标记的秘密:如何高效操控DOM而不踩坑?
前端·html