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

背景

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

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

一、目标拆解

我们的目标是:

🔧 通过微信小程序连接蓝牙设备,并实现读写数据通信。

实现步骤大致如下:

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

二、准备工作

环境要求

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

权限配置(app.json

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

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

蓝牙权限

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

1、初始化蓝牙模块

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

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

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

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

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

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

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

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

javascript 复制代码
wx.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
  success() {
    console.log("开始搜索设备");
  }
});
5、监听发现设备

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

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

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

ini 复制代码
wx.stopBluetoothDevicesDiscovery();
7、连接蓝牙设备

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

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

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

javascript 复制代码
wx.getBLEDeviceServices({
  deviceId,
  success(res) {
    const primaryService = res.services.find(s => s.isPrimary);
    console.log('主服务', primaryService);
  }
});
9、获取特征值并监听通知

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

ini 复制代码
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、读写数据

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

javascript 复制代码
// 写入数据
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、断开连接&关闭蓝牙
javascript 复制代码
wx.closeBLEConnection({
  deviceId,
  success() {
    console.log("🔌 连接断开");
  }
});

wx.closeBluetoothAdapter();

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

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

四、总结

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

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

五 代码实例(可直接用)

js 复制代码
// 文件结构:
// ├── 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;
}
相关推荐
Qrun5 小时前
Windows11安装nvm管理node多版本
前端·vscode·react.js·ajax·npm·html5
中国lanwp5 小时前
全局 npm config 与多环境配置
前端·npm·node.js
JELEE.6 小时前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
TeleostNaCl8 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫9 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友9 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理11 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻11 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js
wdfk_prog12 小时前
便携式功耗分析仪LuatOS IoT Power vs. Nordic PPK2
物联网
mapbar_front12 小时前
在职场生存中如何做个不好惹的人
前端