普通视图

发现新文章,点击刷新页面。
昨天 — 2025年7月8日首页

作为一个新手,如果让你去用【微信小程序通过BLE实现与设备通讯】,你会怎么做,

2025年7月8日 11:47

背景

作为一个程序员,基本上公司的需求就是你的技能。最近公司让我做一个与设备进行蓝牙通信的微信小程序。起初我一脸懵,但经过摸索,还是成功打通了 BLE 通信的完整流程。

适合第一次接触 BLE 的小程序开发者,本文将完整讲解连接、读写、监听的基础用法。

一、目标拆解

我们的目标是:
🔧 通过微信小程序连接蓝牙设备,并实现读写数据通信。

实现步骤大致如下:

  1. 初始化蓝牙模块
  2. 监听蓝牙模块状态
  3. 获取本机蓝牙状态
  4. 开启扫描设备
  5. 监听发现设备
  6. 停止扫描
  7. 连接设备
  8. 获取服务列表
  9. 获取特征值并监听通知
  10. 读写数据
  11. 断开连接&关闭蓝牙

二、准备工作

环境要求

  • 微信开发者工具(建议新版本)
  • 安卓或支持 BLE 的 iOS 手机(真机调试必须)
  • 一台支持蓝牙 BLE 的设备(或模拟器)

权限配置(app.json

"permission": {
  "scope.userLocation": {
    "desc": "用于蓝牙设备搜索"
  }
},
"requiredBackgroundModes": ["bluetooth"]

很重要,如果你没有配置,你会发现上线后,你会搜索不到设备或者直接使用不了蓝牙。

蓝牙权限

在真机中需要开启蓝牙和定位,安卓手机必须开启定位权限才能搜索到设备。

1、初始化蓝牙模块

目标:激活微信小程序蓝牙模块。

注意:安卓首次需授权蓝牙权限,IOS如果为开启蓝牙,将直接报错。

wx.openBluetoothAdapter({
  success() {
    console.log("蓝牙初始化成功");
    // 可继续 startBluetoothDevicesDiscovery
  },
  fail(err) {
    console.error("蓝牙初始化失败", err);
  }
});
2、监听蓝牙模块状态

目的:检查蓝牙是否被用户关闭,或临时断开

wx.onBluetoothAdapterStateChange((res) => {
  console.log("蓝牙状态变化:", res);
  if (!res.available) {
    wx.showToast({ title: '蓝牙不可用', icon: 'none' });
  }
});
3、获取本机蓝牙状态

目的:初始化时判断蓝牙是否可用。避免用户未开启蓝牙就触发后续流程。

wx.getBluetoothAdapterState({
  success(res) {
    if (!res.available) {
      wx.showToast({ title: '请开启蓝牙', icon: 'none' });
    }
  }
});
4、开始扫描设备

目的:搜索附近蓝牙设备,通常需设置allowDuplicatesKey: false,避免重复。

wx.startBluetoothDevicesDiscovery({
allowDuplicatesKey: falsesuccess() {
    console.log("开始搜索设备");
  }
});
5、监听发现设备

目的:拿到设备列表,根据设备名或广播字段筛选出目标设备。

wx.onBluetoothDeviceFound(function (res) {
  const devices = res.devices;
  devices.forEach(device => {
    console.log('发现设备:', device);
    if (device.name.includes('MyDevice')) {
      // 记录下来供点击连接
    }
  });
});
6、停止扫描

目的:找到设备后停止扫描,可以节省资源,防止冲突。

wx.stopBluetoothDevicesDiscovery();

7、连接蓝牙设备

目的:建立BLE连接,注意处理连接失败重试。

wx.createBLEConnection({
  deviceId: deviceId,
  success() {
    console.log("连接成功");
  },
  fail(err) {
    console.log("连接失败", err);
  }
});
8、获取服务列表

目的:获取设备的服务(service),筛选出主服务(一般不是isPrimary:false的跳过)

wx.getBLEDeviceServices({
  deviceId,
  success(res) {
    const primaryService = res.services.find(s => s.isPrimary);
    console.log('主服务', primaryService);
  }
});

9、获取特征值并监听通知

目的:获取通知特征值,用于接收设备传回的数据。

wx.getBLEDeviceCharacteristics({
  deviceId,
  serviceId,
  success(res) {
    const notifyChar = res.characteristics.find(item => item.properties.notify);
    wx.notifyBLECharacteristicValueChange({
      deviceId,
      serviceId,
      characteristicId: notifyChar.uuid,
      state: true
    });
  }
});

10、读写数据

目的:收发数据,完成通信。

// 写入数据
wx.writeBLECharacteristicValue({
  deviceId,
  serviceId,
  characteristicId,
  value: new Uint8Array([0x01, 0xA0]).buffer
});

// 监听数据返回
wx.onBLECharacteristicValueChange(function (res) {
  const buffer = res.value;
  const data = new Uint8Array(buffer);
  console.log('收到数据:', data);
});

11、断开连接&关闭蓝牙
wx.closeBLEConnection({
  deviceId,
  success() {
    console.log("🔌 连接断开");
  }
});

wx.closeBluetoothAdapter();

三、踩坑小结(新手必看)

问题 解决方式
安卓搜索不到设备 一定要打开位置权限
ios设备搜索不到服务 ios设备服务较少,需特定服务
发现设备列表 设备一定要去重,不然会出现几百条数据
设备MTU IOS一般都是512,但安卓一般的都是23,并且不支持修改,所以你发送的数据包字节数大于20的时候,是需要分包发送的,ios则是509
数据格式 每个设备的接受的数据格式是不一样的,这个你需要和嵌入式那边去定好协议
监听不到设备数据 必须先 notifyBLECharacteristicValueChange 成功后设备才上报数据
写入数据错误 数据类型必须是ArrayBuffer

四、总结

BLE开发并不复杂,难的是:不知从何下手,以及细节调试一堆。 这篇文章希望给初学者一个完整的思路,如果你也在学习BLE项目,欢迎留言交流。

下一篇:实现蓝牙列表点击连接,扫码连接,以及自动回连

五 代码实例(可直接用)

// 文件结构:
// ├── pages
// │   └── ble-demo
// │       └── index.js / index.json / index.wxml / index.wxss
// └── utils
//     └── ble.js

// ========== utils/ble.js ==========
const ble = {
    deviceId: null,
    serviceId: null,
    writeCharId: null,
    notifyCharId: null,
    /**
     * 打开蓝牙适配器
     */
    openAdapter() {
        return new Promise((resolve, reject) => {
            wx.openBluetoothAdapter({
                success: resolve,
                fail: reject
            });
        });
    },
    /**
     * 开始搜索蓝牙设备
     */
    startDiscovery() {
        return new Promise((resolve, reject) => {
            wx.startBluetoothDevicesDiscovery({
                allowDuplicatesKey: false,
                success: resolve,
                fail: reject
            });
        });
    },
    /**
     * 监听设备发现事件,并通过回调返回设备信息
     * @param {*} callback 
     */
    onDeviceFound(callback) {
        wx.onBluetoothDeviceFound(callback);
    },
    /**
     * 停止搜索设备
     */
    stopDiscovery() {
        return wx.stopBluetoothDevicesDiscovery();
    },
    /**
     * 创建蓝牙连接
     * @param {*} deviceId 
     */
    createConnection(deviceId) {
        this.deviceId = deviceId;
        return new Promise((resolve, reject) => {
            wx.createBLEConnection({
                deviceId,
                success: resolve,
                fail: reject
            });
        });
    },
    /**
     * 获取设备主服务
     */
    getPrimaryService() {
        return new Promise((resolve, reject) => {
            wx.getBLEDeviceServices({
                deviceId: this.deviceId,
                success(res) {
                    const primary = res.services.find(s => s.isPrimary);
                    ble.serviceId = primary.uuid;
                    resolve(primary);
                },
                fail: reject
            });
        });
    },
    /**
     * 获取设备特征值
     */
    getCharacteristics() {
        const that=this;
        return new Promise((resolve, reject) => {
            wx.getBLEDeviceCharacteristics({
                deviceId: this.deviceId,
                serviceId: this.serviceId,
                success(res) {
                    for (let char of res.characteristics) {
                        if (char.properties.write) ble.writeCharId = char.uuid;
                        if (char.properties.notify) ble.notifyCharId = char.uuid;
                        if (char.properties.notify || char.properties.indicate) {
                            that.enableNotify(char.uuid); // 启用特征值变化监听
                        }
                    }
                    resolve(res.characteristics);
                },
                fail: reject
            });
        });
    },
    /**
     * 开启特征值通知(监听蓝牙数据)
     * @param {*} characteristicId 
     */
    enableNotify(callback) {
        wx.notifyBLECharacteristicValueChange({
            deviceId: this.deviceId,
            serviceId: this.serviceId,
            characteristicId: this.notifyCharId,
            state: true,
            success: () => {
            },
            fail:()=>{

            }
        });
        wx.onBLECharacteristicValueChange(callback)
        
        // wx.onBLECharacteristicValueChange((characteristic) => {
        //     console.log(characteristic)
        //     const value = characteristic.value;
        //     const data = new Uint8Array(value);
        //     console.log(this.ab2hex(data))
        // })
    },
    // ArrayBuffer转16进度字符串示例
    ab2hex(buffer) {
        var hexArr = Array.prototype.map.call(
            new Uint8Array(buffer),
            function (bit) {
                return ('00' + bit.toString(16)).slice(-2)
            }
        )
        return hexArr.join('');
    },
    /**
     * 向设备写入数据(valueArray 是一个字节数组)
     * @param {*} valueArray 
     */
    write(valueArray) {
        let buffer = new Uint8Array(valueArray).buffer;
        return new Promise((resolve, reject) => {
            wx.writeBLECharacteristicValue({
                deviceId: this.deviceId,
                serviceId: this.serviceId,
                characteristicId: this.writeCharId,
                value: buffer,
                success: resolve,
                fail: reject
            });
        });
    },
    /**
     * 获取设备MTU大小
     * @param {*} mtu 
     */
    requestMTU() {
        return new Promise((resolve, reject) => {
            wx.getBLEMTU({
                deviceId: this.deviceId,
                success: (res) => {
                    let mtu = res.mtu;
                    let maxWriteSize = Math.min(mtu - 3, 509); // **最大单次可写入的数据**
                    console.log(`✅ 设备 MTU: ${mtu}, 单次最大可写: ${maxWriteSize}`);
                    resolve({
                        mtu,
                        maxWriteSize
                    })
                },
                fail: (err) => {
                    reject(err)
                }
            });
        });
    },
    /**
     * 断开蓝牙连接
     */
    closeConnection() {
        return new Promise((resolve, reject) => {
            wx.closeBLEConnection({
                deviceId: this.deviceId,
                success: resolve,
                fail: reject
            });
        });
    },
    /**
     * 工具方法:将十六进制字符串转换为字节数组
     * @param {*} hexStr 
     */
    hexStringToBytes(hexStr) {
        if (!hexStr || typeof hexStr !== 'string') return [];
        return hexStr.match(/.{1,2}/g).map(byte => parseInt(byte, 16));
    },
};

module.exports = ble;
// ========== pages/ble-demo/index/index.js ==========
const ble = require('../../../utils/ble');
Page({

    /**
     * 页面的初始数据
     */
    data: {
        log: '',
        foundDevice: null
    },
    log(msg) {
        this.setData({
            log: this.data.log + `\n` + msg
        });
    },
    /**
     * 初始化 BLE 并扫描设备
     */
    async startBLE() {
        try {
            this.log('开始初始化蓝牙');
            await ble.openAdapter();
            this.log('蓝牙模块已打开');
            await ble.startDiscovery();
            this.log('正在扫描设备...');
            this.discoveredIds = new Set();
            ble.onDeviceFound(res => {
                 /**
                 * 增加筛选条件,不然会出现很多设备
                 * 可以是名字,也可以是UUID
                 * 还可以是值搜索有广播的数据
                 * 添加去重
                 */
                const devices = res.devices || [res.device];
                devices.forEach(device => {
                    if (!device.name || !device.name.includes('TPS-22C1')) return;
                    if (this.discoveredIds.has(device.deviceId)) return; // 跳过重复设备

                    this.discoveredIds.add(device.deviceId); // 添加到已发现集合
                    this.setData({ foundDevice: device });
                    ble.stopDiscovery();
                    this.log(`发现设备: ${device.name} 设备id:(${device.deviceId})`);
                });
               
            });

        } catch (e) {
            this.log('初始化失败: ' + e.errMsg);
        }
    },
    /**
     * 与设备建立连接
     */
    async connectBLE() {
        try {
            const {
                foundDevice
            } = this.data;
            if (!foundDevice) return;

            this.log('正在连接设备...');
            await ble.createConnection(foundDevice.deviceId);
            await ble.getPrimaryService();
            await ble.getCharacteristics();

            // 协商 MTU 提高数据传输效率
            const mtuInfo = await ble.requestMTU();
            this.log(`MTU: ${mtuInfo.mtu}, 单次最大可写: ${mtuInfo.maxWriteSize}`);
            ble.enableNotify(res => {
              
                console.log(res)
                const value = res.value;
                const data = new Uint8Array(value);
                this.log('监听设备返回数据: ' + ble.ab2hex(data));
                console.log(ble.ab2hex(data))
            });
            this.log('设备连接成功');

        } catch (e) {
            this.log('连接失败: ' + e.errMsg);
        }
    },
    /**
     * 发送测试数据(0x01, 0xA0)
     */
    sendTestData() {
        const hex = '5AA5060000';
        const data = ble.hexStringToBytes(hex);
        ble.write(data).then(() => {
            this.log('指令已发送: 5AA5060000');
        }).catch(err => {
            this.log('指令发送失败: ' + err.errMsg);
        });
    },
    /**
     * 断开设备连接
     */
    disconnectBLE() {
        ble.closeConnection().then(() => {
            this.log('已断开连接');
        }).catch(err => {
            this.log('断开失败: ' + err.errMsg);
        });
    },
    
})

// ========== pages/ble-demo/index/index.wxml ==========
<view class="container">
    <button bindtap="startBLE">初始化并扫描</button>
    <button bindtap="connectBLE">连接设备</button>
    <button bindtap="sendTestData">发送测试数据</button>
    <button bindtap="disconnectBLE">断开连接</button>
    <view class="log-area">{{log}}</view>
</view>

// ========== pages/ble-demo/index/index.wxss ==========
.container {
    padding: 20rpx;
    width: 96%;
}

.log-area {
    margin-top: 30rpx;
    white-space: pre-wrap;
    font-size: 28rpx;
    background: #333333;
    padding: 20rpx;
    border-radius: 12rpx;
    max-height: 600rpx;
    overflow-y: scroll;
    color: #ffffff;
}
❌
❌