蓝牙能力与场景

智能秤、手环、血压计、门锁、打印机,这些硬件设备通过低功耗蓝牙和小程序连上,用户不用装 App 就能用,是小程序一个很有想象力的能力边界。

小程序蓝牙主要走 BLE 协议:手机做主机,外设做从机。一次完整交互通常是扫描、连接、发现服务、订阅通知或写数据、断开,五步走完一轮。

蓝牙调试很依赖真机,模拟器基本测不出连接行为。开发时准备一台安卓一台 iOS,两个系统在权限弹窗、扫描结果上的表现差异不小。下面按连接流程逐步讲。

本文核心知识点
  • 扫描adapter 初始化与设备发现。
  • 连接BLE 连接与服务特征。
  • 收发写特征值与订阅通知。
  • 收尾断开、释放与异常恢复。
小程序蓝牙与设备交互配图:蓝调渐变背景上手机与多个外设之间以无线信号弧线相连的扁平科技插画
手机当主机,设备当从机,一次连接一轮数据交换。

扫描与发现

开始之前先初始化蓝牙适配器,检查系统蓝牙是否打开。适配器没初始化就调用后面的接口,会直接报错,所以每一步都要判断当前状态。

wx.openBluetoothAdapter({
  success: function () {
    wx.startBluetoothDevicesDiscovery({
      allowDuplicatesKey: false,
      success: function () {
        wx.onBluetoothDeviceFound(function (res) {
          // res.devices 里是发现的设备,按名称过滤
          const list = res.devices.filter(function (d) {
            return d.name && d.name.indexOf('JCY') === 0;
          });
          renderDeviceList(list);
        });
      }
    });
  },
  fail: function () {
    showToast('请打开手机蓝牙后重试');
  }
});

发现的设备对象里,name 是设备广播名,deviceId 是设备唯一标识,RSSI 是信号强度。按名称前缀过滤能筛掉一堆无关设备,列表里显示信号强度,用户也好选。

扫描不要一直开着,找到目标设备就 stopBluetoothDevicesDiscovery 停下来,省电也避免接口冲突。

连接与服务

选中设备后用 createBLEConnection 建立连接。连接成功之后,先取设备的服务列表,再取每个服务下的特征值列表,找到要读写的那几个特征值 UUID。

wx.createBLEConnection({
  deviceId: deviceId,
  success: function () {
    wx.getBLEDeviceServices({
      deviceId: deviceId,
      success: function (res) {
        const service = res.services[0];
        wx.getBLEDeviceCharacteristics({
          deviceId: deviceId,
          serviceId: service.uuid,
          success: function (r) {
            // r.characteristics 里挑读写特征值
            const writeChar = pickWrite(r.characteristics);
            const notifyChar = pickNotify(r.characteristics);
            readyToUse(writeChar, notifyChar);
          }
        });
      }
    });
  }
});

服务和特征值的 UUID 由设备厂商提供,一般写在硬件文档里。有些设备 UUID 是 16 位短格式,连接时要把它们补成 128 位标准格式再匹配,不然对不上。

连接要带超时判断:超过几秒没成功就提示失败,并清理这次连接的资源,避免半连接状态卡住用户。

数据收发

向设备发数据用 writeBLECharacteristicValue,参数要转成 ArrayBuffer。设备回传数据一般通过通知,先启用 notify 监听,再在 onBLECharacteristicValueChange 里收。

function sendCommand(buffer) {
  wx.writeBLECharacteristicValue({
    deviceId: deviceId,
    serviceId: serviceId,
    characteristicId: writeCharId,
    value: buffer,
    fail: function () {
      showToast('指令发送失败');
    }
  });
}

wx.notifyBLECharacteristicValueChange({
  deviceId: deviceId,
  serviceId: serviceId,
  characteristicId: notifyCharId,
  state: true
});
wx.onBLECharacteristicValueChange(function (res) {
  const data = arrayBufferToHex(res.value);
  handleDeviceData(data);
});

十六进制字符串和 ArrayBuffer 互转要写成工具函数,数据解析按设备协议来:帧头、长度、指令、数据、校验位,逐字节解析,协议字段要跟厂商核对清楚。

数据会一帧一帧来,注意粘包问题。收到的数据按协议切帧,不够一帧先攒着,凑满再处理,别丢中间帧。

指令发送频率也要控制,设备处理能力有限,短时间发太多设备会丢指令。发一条等一条的应答,或者按固定间隔排队发,比一股脑全丢出去稳。

状态与断开

蓝牙适配器和设备连接是全局状态,页面切走、小程序切后台、用户关蓝牙,都可能让连接失效。onBLEConnectionStateChange 可以监听连接状态变化,断线后及时提示并走重连流程。

事件处理
页面卸载断开连接、关闭适配器
连接意外断开提示用户,提供重连按钮
系统蓝牙关闭提示打开蓝牙,恢复后重试
扫描未停止离开页面时停止扫描

断开用 closeBLEConnection 关连接、closeBluetoothAdapter 关适配器。页面 onUnload 里把监听器清理干净,避免回调泄漏导致重复处理数据。

重连要克制:失败就停,让用户手动点重试,别在后台无限循环重连,既耗电又可能触发系统拦截。

最后,蓝牙相关逻辑建议单独抽成一个蓝牙管理模块,页面只负责 UI 和调用,连接状态、数据解析都在模块里维护。多个页面共用一套蓝牙能力时,模块化能少踩很多重复的坑。