在物联网快速发展的今天,微信小程序与蓝牙设备的交互已成为智能硬件开发的重要场景。本文将基于官方蓝牙 API,详细讲解小程序连接蓝牙设备的完整流程,涵盖从适配器初始化到数据收发的全链路操作。

一、蓝牙开发基础认知

蓝牙连接是小程序与硬件设备通信的重要方式,主要应用于:

  • 智能家居设备控制(智能门锁、灯光)
  • 穿戴设备数据同步(手环、健康监测设备)
  • 物联网传感器数据采集

核心优势

  • 无需云开发即可实现设备直连
  • 低功耗设计适合移动设备
  • 微信生态内用户触达便捷

二、蓝牙连接全流程实现

1. 初始化蓝牙适配器

首先需要获取设备的蓝牙权限并初始化适配器:

wx.openBluetoothAdapter({
  // 打开蓝牙适配器成功回调
  success(res) {
    console.log(res)
    console.log("初始化成功!")
  },
  // 打开蓝牙适配器失败回调
  fail(res) {
    console.log(res)
    console.log("初始化失败!")
  },
  // 无论成功失败都会执行的回调
  complete(res) {
    console.log(res)
    console.log("初始化完成!")
  },
})

关键说明

  • 首次使用时会弹出权限申请弹窗
  • 安卓设备需确保系统蓝牙已开启
  • 失败时可能需要引导用户检查蓝牙状态

2. 获取蓝牙适配器状态

初始化后需确认适配器当前状态:

wx.getBluetoothAdapterState({
  // 成功获取状态回调
  success(res) {
    console.log(res)
    console.log("得到蓝牙适配器状态成功!")
  },
  // 失败获取状态回调
  fail(res) {
    console.log(res)
    console.log("得到蓝牙适配器状态失败!")
  },
  // 状态获取完成回调
  complete(res) {
    console.log(res)
    console.log("得到蓝牙适配器状态完成!")
  },
})

关键字段

  • res.available:蓝牙是否可用
  • res.discovering:是否正在搜索设备
  • res.platform:设备平台(iOS/Android)

3. 搜索附近蓝牙设备

搜索设备是连接的前提,需注意资源消耗问题:

javascript

wx.startBluetoothDevicesDiscovery({
  // services参数可选,指定UUID可过滤设备
  // services: ['FEE7'], 
  success(res) {
    console.log(res, '搜索成功')
  },
  fail(res) {
    console.log(res, '搜索失败')
  },
  complete(res) {
    console.log(res, '搜索操作完成')
  }
})

重要注意事项

  • 安卓 6.0 + 设备需开启定位权限才能搜索
  • 搜索会消耗较多电量,找到设备后需及时停止
  • 8.0.16 以上版本安卓设备无定位权限会直接报错

4. 获取搜索到的设备列表

实时获取搜索到的设备信息:

javascript

// 工具函数:ArrayBuffer转16进制字符串
function ab2hex(buffer) {
  var hexArr = Array.prototype.map.call(
    new Uint8Array(buffer),
    function(bit) {
      return ('00' + bit.toString(16)).slice(-2)
    }
  )
  return hexArr.join('');
}

// 获取蓝牙设备列表
getBluetoothDevices: function() {
  wx.getBluetoothDevices({
    success: function(res) {
      console.log(res) // 打印所有设备信息
      if (res.devices[0]) {
        console.log(ab2hex(res.devices[0].advertisData)) // 打印第一个设备的广播数据
      }
    }
  })
}

设备数据解析

  • name:设备名称(可能为空)
  • deviceId:设备唯一标识
  • RSSI:信号强度(-120~0dBm,值越大信号越好)
  • advertisData:广播数据(可解析设备信息)

5. 连接指定蓝牙设备

通过 deviceId 建立与目标设备的连接:

javascript

wx.createBLEConnection({
  deviceId: deviceId, // 从搜索结果中获取的设备ID
  success(res) {
    console.log(res, '连接成功')
    // 连接成功后立即停止搜索以节省资源
    wx.stopBluetoothDevicesDiscovery({
      success(res) {
        console.log(res)
      }
    })
  },
  fail(res) {
    console.log(res, '连接失败')
  },
  complete(res) {
    console.log(res, '连接操作完成')
  }
})

连接管理要点

  • 连接成功后必须调用 stopBluetoothDevicesDiscovery
  • 连接失败可能原因:设备距离过远、设备未开启可连接模式
  • 每个设备同时只能有一个连接

6. 停止设备搜索

当找到目标设备后务必停止搜索:

wx.stopBluetoothDevicesDiscovery({
  success(res) {
    console.log(res)
  },
  fail(res) {
    console.log(res, '停止搜索失败')
  },
  complete(res) {
    console.log(res, '停止搜索完成')
  }
})

7. 获取设备服务列表

连接后需要获取设备支持的服务:

wx.getBLEDeviceServices({
  deviceId, // 已连接的设备ID
  success(res) {
    console.log(res)
    // 从res.services中获取serviceId
  },
  fail(res) {
    console.log(res, '获取服务失败')
  },
  complete(res) {
    console.log(res, '获取服务完成')
  }
})

服务数据结构

  • uuid:服务唯一标识
  • isPrimary:是否为主服务(主服务是设备核心功能)

8. 获取服务特征值

每个服务包含多个特征值,这是数据交互的关键:

wx.getBLEDeviceCharacteristics({
  deviceId, // 已连接设备ID
  serviceId, // 从服务列表中获取的serviceId
  success(res) {
    console.log('设备特征值列表:', res.characteristics)
    if (res.characteristics.properties) {
      // 从res.characteristics中获取characteristicId
      return
    }
  }
})

特征值属性解析

  • read:是否支持读取
  • write:是否支持写入
  • notify:是否支持通知(设备主动推送数据)
  • indicate:是否支持指示(比通知更可靠的推送)

9. 订阅特征值变化通知

开启设备主动推送数据的功能:

wx.notifyBLECharacteristicValueChange({
  state: true, // true为启用notify功能
  deviceId,
  serviceId,
  characteristicId, // 从特征值列表中获取的characteristicId
  success(res) {
    console.log('订阅成功', res.errMsg)
  }
})

订阅注意事项

  • 必须确保特征值支持 notify/indicate
  • 订阅后设备主动更新数据才会触发回调
  • 安卓部分机型订阅后立即写入可能报错(10008 错误)

10. 监听特征值变化事件

接收设备主动推送的数据:

// 工具函数:ArrayBuffer转16进制字符串
function ab2hex(buffer) {
  let hexArr = Array.prototype.map.call(
    new Uint8Array(buffer),
    function(bit) {
      return ('00' + bit.toString(16)).slice(-2)
    }
  )
  return hexArr.join('');
}

// 注册监听事件
wx.onBLECharacteristicValueChange(function(res) {
  console.log(`特征值 ${res.characteristicId} 已更新,当前值:${res.value}`)
  console.log(ab2hex(res.value)) // 打印16进制数据
})

数据处理要点

  • 数据以 ArrayBuffer 形式传输
  • 需根据设备协议解析数据(如温度、湿度等)
  • 建议在页面卸载时取消监听(wx.offBLECharacteristicValueChange

11. 向设备写入数据

与设备交互的最后一步,发送控制指令:

senddata: function() {
  // 准备写入数据(示例:发送0x00)
  let buffer = new ArrayBuffer(1)
  let dataView = new DataView(buffer)
  dataView.setUint8(0, 0) // 设置第一个字节为0
  
  wx.writeBLECharacteristicValue({
    deviceId,
    serviceId,
    characteristicId,
    value: buffer, // 必须为ArrayBuffer类型
    success(res) {
      console.log('写入成功', res.errMsg)
    }
  })
}

写入操作限制

  • 单次写入建议不超过 20 字节
  • 并行多次写入可能失败
  • iOS 设备写入过长数据可能无回调
  • 安卓订阅后立即写入可能报错

三、实战开发最佳实践

  1. 设备状态管理

    • 维护全局设备连接状态变量
    • 页面卸载时断开连接(onUnload中调用wx.closeBLEConnection
  2. 错误处理优化

    • 封装蓝牙操作函数并统一处理错误
    • 对常见错误(如 10008)提供用户友好提示
  3. 性能优化

    • 严格控制搜索时间(建议不超过 10 秒)
    • 批量处理特征值读写操作
  4. 跨平台适配

    • 针对 iOS/Android 做差异化处理
    • 安卓设备优先检查定位权限

四、常见问题与解决方案

  1. 安卓设备无法搜索到设备

    • 检查是否开启定位权限(安卓 6.0 + 强制要求)
    • 确认蓝牙适配器状态是否可用
  2. 连接成功但无法通信

    • 检查 serviceId/characteristicId 是否正确
    • 确认特征值是否支持对应操作(read/write/notify)
  3. 数据传输异常

    • 确保数据格式为 ArrayBuffer
    • 按设备协议规范解析数据(如大端 / 小端模式)

通过以上步骤,我们可以完成微信小程序与蓝牙设备的全流程交互。实际开发中需结合具体硬件协议调整数据处理逻辑,并做好异常情况的处理,以提供稳定的用户体验。

下面是写的一些简易的代码,有错误的地方希望大家指出

WXML

<view class="page-body">
    <view class="btn-area" id="buttonContainer">
        <wux-toptips class="wux-light--bg" id="wux-toptips" />
        <button type="primary" bindtap='openbluetooth'>打开蓝牙适配器</button>
        <button type="primary" bindtap='getbluetoothstatus'>获取蓝牙适配器状态</button>
        <button type="primary" bindtap='startsearchbluetooth'>开始搜索附近的蓝牙设备</button>
        <button type="primary" bindtap='getbluetooths'>获取搜到的蓝牙设备</button>
        <view wx:for="{{getbluetoothlist}}" wx:key="index" class="device-card">
            <view class="device-row">
                <text class="device-label">设备名:</text>
                <text class="device-value">{{item.name || '未知设备'}}</text>
            </view>
            <view class="device-row">
                <text class="device-label">设备ID:</text>
                <text class="device-value">{{item.deviceId}}</text>
            </view>
            <view class="device-row">
                <text class="device-label">信号强度:</text>
                <text class="device-value">{{item.RSSI}}</text>
            </view>
            <view class="device-row">
                <text class="device-label">支持连接:</text>
                <text class="device-value">{{item.connectable ? '是' : '否'}}</text>
            </view>
            <button class="mini-btn" type="primary" size="mini" bindtap='contentdev' data-devid="{{item.deviceId}}">连接</button>
        </view>
        <button type="primary" bindtap='getservice'>获取已连接蓝牙的服务</button>
        <button type="primary" bindtap='getserviceeigenvalues'>获取蓝牙设备某服务的所有特征值</button>
        <view wx:for="{{genvalueslist}}" wx:key="index" class="device-card">
            <view class="device-row">
                <text class="device-label">特征值:</text>
                <text class="device-value">{{item.uuid}}</text>
            </view>
            <view class="device-row">
                <text class="device-label">是否可读:</text>
                <text class="device-value">{{item.properties.read?'是':'否'}}</text>
            </view>
            <view class="device-row">
                <text class="device-label">是否可写:</text>
                <text class="device-value">{{item.properties.write?'是':'否'}}</text>
            </view>
            <view class="device-row">
                <text class="device-label">是否支持notify操作:</text>
                <text class="device-value">{{item.properties.notify?'是':'否'}}</text>
            </view>
            <view class="device-row">
                <text class="device-label">是否支持indicate操作:</text>
                <text class="device-value">{{item.properties.indicate?'是':'否'}}</text>
            </view>
            <view class="device-row">
                <text class="device-label">是否支持无回复写操作:</text>
                <text class="device-value">{{item.properties.writeNoResponse?'是':'否'}}</text>
            </view>
            <view class="device-row">
                <text class="device-label">是否支持有回复写操作:</text>
                <text class="device-value">{{item.properties.writeDefault?'是':'否'}}</text>
            </view>
        </view>
        <button type="primary" bindtap='startmonitornotify'>启用特征值变化时的 notify 功能</button>
        <button type="primary" bindtao='monitoreventchanges'>监听低功耗蓝牙设备的特征值变化事件</button>
        <button type="primary" bindtap='writecontent'>向低功耗蓝牙设备特征值中写入二进制数据</button>

    </view>
</view>

js


Page({
    /**
     * 页面的初始数据
     */
    data: {
        getbluetoothlist: [], //获取到的蓝牙设备列表,
        genvalueslist: [], //设备特征值列表
        deviceId: '', //蓝牙设备ID
        serviceId: '', //蓝牙设备服务ID
        genvalueId: '', //特征值ID
    },
    // 打开蓝牙适配器
    openbluetooth() {
        const that = this;
        console.log(1111)
        wx.showLoading({
            title: '蓝牙初始化...',
        })
        wx.openBluetoothAdapter({
            //打开蓝牙适配器成功
            success(res) {
                wx.hideLoading()
                wx.showToast({
                    title: '初始化成功',
                    icon: 'success',
                    duration: 2000
                })

            },
            //打开蓝牙适配器失败
            fail(res) {
                console.log(res)
                console.log("初始化失败!")
                wx.hideLoading()
                wx.showToast({
                    title: '初始化失败',
                    icon: 'error',
                    duration: 2000
                })
                const toptips = that.selectComponent('#wux-toptips')
                toptips && toptips.warn({
                    icon: 'cancel',
                    hidden: false,
                    text: '请检查蓝牙是否开启',
                    duration: 3000,
                })
            },

        })
    },
    // 获取蓝牙适配器状态
    getbluetoothstatus() {
        wx.showLoading({
            title: '获取蓝牙适配器状态...',
        })
        wx.getBluetoothAdapterState({

            success(res) {
                console.log("获取蓝牙适配器状态成功!")
                wx.hideLoading()
                wx.showToast({
                    title: '获取适配器状态成功',
                    icon: 'success',
                    duration: 2000
                })
            },

            fail(res) {
                console.log("得到蓝牙适配器状态失败!")
                wx.hideLoading()
                wx.showToast({
                    title: '获取状态失败',
                    icon: 'error',
                    duration: 2000
                })
            }

        })
    },
    //开始搜索蓝牙
    startsearchbluetooth() {
        wx.showLoading({
            title: '搜索蓝牙中...',
        })
        wx.startBluetoothDevicesDiscovery({
            // services: ['FEE7'],  services额可以不填,则搜索的是所有的蓝颜设备,如果填写则搜索广播包有对应 UUID 的主服务的蓝牙设备
            success(res) {
                console.log(res, '成功')
                wx.hideLoading()
            },
            fail(res) {
                console.log(res, '失败')

            }
        })

    },
    //获取搜索到的蓝牙
    getbluetooths() {
        const that = this;
        // ArrayBuffer转16进制字符串示例
        function ab2hex(buffer) {

            var hexArr = Array.prototype.map.call(
                new Uint8Array(buffer),
                function (bit) {
                    return ('00' + bit.toString(16)).slice(-2)
                }
            )
            return hexArr.join('');
        }
      
        wx.getBluetoothDevices({
            success: function (res) {
                console.log(res) //打印蓝牙设备信息日志
                that.setData({
                    getbluetoothlist: [...res.devices]
                })
                if (that.data.getbluetoothlist) {
                    console.log(22222, that.data.getbluetoothlist)
                }



            }
        })
    },
    //连接蓝牙设备
    contentdev(event) {
        const that = this
        that.setData({
            deviceId: event.currentTarget.dataset.devid
        })
        wx.createBLEConnection({
            deviceId: that.data.deviceId, //(deviceId)
            success(res) {

                console.log(res, '连接成功')
                wx.showToast({
                    title: '连接成功',
                    icon: 'success',
                    duration: 2000
                })
                //连接成功后最好要释放资源,不然会消耗手机资源
                wx.stopBluetoothDevicesDiscovery({
                    success(res) {
                        console.log(res)
                    }
                })

            },
            fail(res) {
                console.log(res, '失败')

            }
        })

    },
    //获取蓝牙设备的服务
    getservice() {
        const that = this
        wx.getBLEDeviceServices({
            deviceId: that.data.deviceId, //deviceId 需要已经通过createBLEConnection与对应设备建立连接
            success(res) {
                console.log(res)
                //res.services.uuid服务id的获取
                res.services.forEach((item) => {
                    if (item.isPrimary) {
                        that.setData({
                            serviceId: item.uuid
                        })
                        return
                    }
                })
            },
            fail(res) {
                console.log(res, '失败')

            }
        })
    },
    //获取蓝牙设备某服务所有特征值
    getserviceeigenvalues() {
        const that = this
        wx.getBLEDeviceCharacteristics({
            deviceId: that.data.deviceId,
            serviceId: that.data.serviceId,
            success(res) {
                console.log('device getBLEDeviceCharacteristics:', res.characteristics)
                that.setData({
                    genvalueslist: [...res.characteristics]
                })
                res.characteristics.forEach((item) => {
                    if (item.properties.notify) {
                        that.setData({
                            genvalueId: item.uuid
                        })
                        return
                    }
                })

            }
        })
    },
    //启用特征值变化的motify功能
    startmonitornotify() {
        const that = this
        wx.notifyBLECharacteristicValueChange({
            state: true, // 启用 notify 功能
            deviceId: that.data.deviceId,
            serviceId: that.data.serviceId,
            characteristicId: that.data.genvalueId,
            success(res) {
                console.log('notifyBLECharacteristicValueChange success', res.errMsg)
            }
        })
    },
    //监听变化事件
    monitoreventchanges() {
        function ab2hex(buffer) {
            let hexArr = Array.prototype.map.call(
                new Uint8Array(buffer),
                function (bit) {
                    return ('00' + bit.toString(16)).slice(-2)
                }
            )
            return hexArr.join('');
        }
        wx.onBLECharacteristicValueChange(function (res) {
            console.log(`characteristic ${res.characteristicId} has changed, now is ${res.value}`)
            console.log(ab2hex(res.value))
        })
    },
    //向蓝牙设备写入二进制数据
    writecontent() {
        const that=this
        // 向蓝牙设备发送一个0x00的16进制数据
        let buffer = new ArrayBuffer(1)
        // let buffer = new ArrayBuffer(写入内容)
        let dataView = new DataView(buffer)
        dataView.setUint8(0, 0)
        // dataView.setUint8(数组下标, 值)

        wx.writeBLECharacteristicValue({
            deviceId:that.data.deviceId,
            serviceId:that.data.serviceId,
            characteristicId:that.data.genvalueId,
            value: buffer,
            success(res) {
                console.log('writeBLECharacteristicValue success', res.errMsg)
            }
        })
    },
    

})

wxss

/* pages/bluetooth/bluetooth.wxss */
button {
    margin-top: 30rpx;
    margin-bottom: 30rpx;
    width: 90% !important;
}

.button-sp-area {
    margin: 0 auto;
    width: 100%;
}

.mini-btn {
    margin-right: 10rpx;
}

.device-card {
    background: #fff;
    border-radius: 12rpx;
    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
    margin: 24rpx 16rpx;
    padding: 20rpx 24rpx;
    border: 1rpx solid #f0f0f0;
}

.device-row {
    display: flex;
    align-items: center;
    margin-bottom: 12rpx;
}

.device-label {
    color: #888;
    width: 160rpx;
    font-size: 28rpx;
}

.device-value {
    color: #222;
    font-size: 28rpx;
    word-break: break-all;
}

.device-row:last-child {
    margin-bottom: 0;
}

Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐