小程序蓝牙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 格式,需要根据硬件设备的协议进行精确的序列化和反序列化。
相关推荐
我的xiaodoujiao1 天前
从 0 到 1 搭建 Python 语言 Web UI自动化测试学习系列 9--基础知识 5--常用函数 3
前端·python·测试工具·ui
李鸿耀1 天前
Flex 布局下文字省略不生效?原因其实很简单
前端
皮蛋瘦肉粥_1211 天前
pink老师html5+css3day06
前端·css3·html5
华仔啊1 天前
前端必看!12个JS神级简写技巧,代码效率直接飙升80%,告别加班!
前端·javascript
excel1 天前
dep.ts 逐行解读
前端·javascript·vue.js
爱上妖精的尾巴1 天前
5-20 WPS JS宏 every与some数组的[与或]迭代(数组的逻辑判断)
开发语言·前端·javascript·wps·js宏·jsa
excel1 天前
Vue3 响应式核心源码全解析:Dep、Link 与 track/trigger 完整执行机制详解
前端
前端大卫1 天前
一个关于时区的线上问题
前端·javascript·vue.js
whltaoin1 天前
中秋赏月互动页面:用前端技术演绎传统节日之美
前端·javascript·html·css3·中秋主题前端
IT派同学1 天前
TableWiz诞生记:一个被表格合并逼疯的程序员如何自救
前端·vue.js