uniapp微信小程序蓝牙开发

566 阅读7分钟
  1. manifest.json配置蓝牙相关用户授权 scope.userLocation(兼容部分手机需要位置授权的情况) scope.bluetooth 详见developers.weixin.qq.com/miniprogram…
    developers.weixin.qq.com/community/d…
  2. 判断手机型号
        const systemInfo = wx.getSystemInfoSync();
        if (/iPhone/.test(systemInfo.model)) {
            this.isIos = true;
        }
  1. 用户授权信息判断
       getSetting() {
            let _that = this
            try {
                wx.getSetting({
                    withSubscriptions: true,
                    success(res) {
                    //校验蓝牙是否已经授权
                        if (res.authSetting['scope.bluetooth']) {
                            _that.initBlueTooth()//初始化蓝牙
                        } else {
                        //关闭蓝牙提示相关....
                            if (_that.$refs["setBlueRef"]) _that.$refs["setBlueRef"].hide();
                             toast('您拒绝了蓝牙授权')
                            _that.closeBlueTooth()
                        }
                    }
                })
            } catch (error) {
                console.log(error, 'getSetting');
                throw error
            }
        }

  1. 打开蓝牙适配器 也可以先执行这一步
   async initBlueTooth() {
            let _that = this;
        //可以添加《隐私保护指引》等逻辑
                wx.openBluetoothAdapter({
                    mode: "central",
                    success: function (res) {
                        // 获取蓝牙适配器状态
                        _that.isOpenBluebooth = true
                        _that.getBluetoothAdapter();
                    },
                    fail: function (res) {
                        //蓝牙未打开 
                        if (res.errCode == 10001) {
                            uni.hideToast()
                            if (_that.$refs["setBlueRef"]) return _that.$refs["setBlueRef"].show();//打开蓝牙提示弹窗 后面用onShow加个倒计时监听页面返回执行下面一步
                        }
                        _that.isOpenBluebooth = false
                        _that.getSetting()
                    },
                });
        },

跳转系统微信授权管理页

        setBlueTooth() {
            //ios特殊处理
            if (this.isIos) {
                    wx.openAppAuthorizeSetting({
                        success: (res) => {
                            this.isOpenBluebooth = true
                            // console.log('您拒绝了蓝牙授权', res);
                            if (this.$refs["setBlueRef"]) this.$refs["setBlueRef"].hide();
                        },
                        fail: (res) => {
                            this.isOpenBluebooth = false
                        },
                    });
            } else {
                //安卓手机打开蓝牙设置页面
                    wx.openSystemBluetoothSetting({
                        success: (data) => {
                            this.isOpenBluebooth = true
                            if (this.$refs["setBlueRef"]) this.$refs["setBlueRef"].hide();
                        },
                        fail: (err) => {
                            this.isOpenBluebooth = false
                        },
                    });
            }
        }
  1. 获取本机蓝牙适配器状态
    async getBluetoothAdapter() {
            let _that = this;
            try {
                //获取本机蓝牙适配器状态
                wx.getBluetoothAdapterState({
                    success: function (res2) {
                        console.log('getBluetoothAdapterState', res2);
                        //跳转到手机系统蓝牙设置页后回到此页面监听蓝牙是否打开
                        if (!res2.available && _that.isOpenSetting) {
                            _that.isOpenSetting = false
                            _that.bleAdapterState = false
                            toast('蓝牙未打开')
                            return
                        }
                        if (res2.errMsg == "getBluetoothAdapterState:ok") {
                            //搜寻附近的蓝牙设备
                            uni.showLoading({
                                title: "连接设备蓝牙中",
                                mask: true,
                            });
                            _that.startBluetoothDevices();
                            _that.isOpenSetting = false
                            _that.bleAdapterState = true
                        }
                    },
                    fail: function (res2) {
                        if (_that.isOpenSetting)
                            uni.hideLoading();
                    },
                });
            } catch (error) {
                throw error
            }
        }

6.开始搜寻附近的蓝牙外围设备

        startBluetoothDevices() {
            let _that = this;
            try {
                wx.startBluetoothDevicesDiscovery({
                    success: function (res) {
                        if (res.errCode == 0) {
                            // 监听寻找到新设备的事件
                            _that.onBluetoothDevice();
                        }
                        console.log('startBluetoothDevicesDiscovery', res);
                    },
                    fail: function (res) {
                    //可能未开启 小程序/微信app 定位相关授权
                        if (res.errCode < 0 && res.errMsg.includes('location')) {
                            uni.hideToast()
                            uni.hideLoading()
                            toast('请开启微信定位授权')
                            return
                        }
                        /**
                        或者在前面用处理
                          wx.authorize({
                          scope: 'scope.userLocation',
                          success() {
                            // 用户同意授权
                          },
                          fail() {
                           // 用户拒绝授权
                          }
                          });
                        */
                        console.log(res, 'startBluetoothDevicesDiscovery');
                        if (res.errCode > 1) toast("连接失败");
                    },
                });
            } catch (error) {
                console.log(error, "startBluetoothDevicesDiscovery");
                throw error

            }
        },

7.监听寻找到新设备的事件

     onBluetoothDevice() {
            let _that = this;
            try {
                wx.onBluetoothDeviceFound((res) => {
                    res.devices.length &&
                        res.devices.forEach((device) => {
                            //https://developers.weixin.qq.com/miniprogram/dev/api/device/bluetooth/wx.onBluetoothDeviceFound.html
                            //这里要求外围设备发送ManufacturerData 数据段 (advertisData ArrayBuffer数据类型)
                            if (device.deviceId && device.advertisData && device.localName) {
                                let deviceId = _that.hex2ab(device.advertisData);
                                // 确保蓝牙设备中的advertisData字段有数据
                                if (deviceId) {
                                    console.log(deviceId, _that.sim, device.localName);
                         //这里与硬件方沟通好根据什么标识判断 进行连接
                           if (deviceId.endsWith(_that.xx)) {
                                  _that.stopBluetooth()
                                  setTimeout(() => {
                                     _that.createBLEConnectionFn(device.deviceId);
                                 }, 400)
                            }
                        });
                });
            } catch (error) {
                uni.hideLoading();
                _that.stopBluetooth();
                throw error
            }
        },
  1. 连接蓝牙
        async createBLEConnectionFn() {
            let _that = this;
            try {
                //连接蓝牙前先关闭已连接蓝牙
                wx.createBLEConnection({
                    deviceId: _that.deviceId,
                    success: function (res) {
                        //获取连接设备的service服务
                        _that.getBLEDeviceServicesFn(_that.deviceId);
                        uni.hideLoading();
                        uni.showToast({
                            title: "连接设备蓝牙成功",
                            icon: "success",
                            mask: true,
                        });
                        _that.connectSuccess = true
                        //监听蓝牙关闭
                        wx.onBLEConnectionStateChange(function (res) {
                            // 该方法回调中可以用于处理连接意外断开等异常情况
                            if (!res.connected) {
                            //............断开的业务逻辑
                            }
                        });
                    },
                    fail: function (res) {
                        _that.stopBluetooth();
                        uni.hideLoading();
                        uni.showToast({
                            title: "连接蓝牙失败",
                            icon: "error",
                            mask: true,
                        });
                    },
                });
            } catch (error) {
                throw error
            }
        },

9.获取连接设备的service服务

       getBLEDeviceServicesFn(deviceId) {
            let _that = this;
                wx.getBLEDeviceServices({
                    deviceId: _that.deviceId,
                    success: function (res) {
                        console.log(res, 'getBLEDeviceServices');
                        // 遍历服务列表,找到设备支持的主服务 可能是多个主服务 循环取对应的功能的uuid和serviceid
                        for (let i = 0; i < res.services.length; i++) {
                            if (
                                res.services[i].isPrimary
                            ) {
                                                                                                                      _that.getBLEDevice(res.services[i].uuid);

                                //获取连接设备的所有特征值
                            }
                        }
                   
                    },
                    fail: function (res) {
                        console.log("获取蓝牙失败", res);
                    },
                });
        }
  1. 获取连接设备的所有特征值
           getBLEDevice() {
            let _that = this;
         
                wx.getBLEDeviceCharacteristics({
                    deviceId: _that.deviceId,
                    serviceId: _that.serviceId,
                    success: function (res) {
                        if (res.errCode == 0) {
                            console.log(res, 'getBLEDeviceCharacteristics');
                            let characteristicId = res.characteristics;
                            for (var i = 0; i < res.characteristics.length; i++) {
                                let item = res.characteristics[i].properties
                             if (item.notify && !_that.notifyUuid) {
                             //启用蓝牙notify功能uuid
                                    _that.notifyUuid = characteristicId[i]['uuid']
                                    //  notifyServiceId: "", //启用蓝牙notify功能serviceId
                                    _that.notifyServiceId = serviceId
                          
                                }
                              
           if (
                                    item.write && !_that.writeUuid
                                ) {
                                //这里可以开始给设备发送数据了
                                //启用蓝牙write功能serviceId
                                    _that.writeServiceId = serviceId
                                    // //启用蓝牙write功能uuid
                                    _that.writeUuid = characteristicId[i]["uuid"];
                                    let str = ""
                                    _that.writeBLECharacteristic(str);
                                }
                                //确保设备能够写入数据
                            }
                        } else {
                            console.log("获取特征值失败");
                        }
                    },
                });
        },
  1. 启用低功耗蓝牙设备特征值变化时的 notify 功能
        notifyBLECharacteristic() {
            let _that = this;
            wx.notifyBLECharacteristicValueChange({
               deviceId: _that.deviceId,
                serviceId: _that.notifyServiceId,
                characteristicId: _that.notifyUuid,
                state: true,
                success: function (res) {
                    console.log("启用低功耗蓝牙设备特征值变化时的 notify 功能", res);
                    /*用来监听手机蓝牙设备的数据变化*/
                    _that.onBLECharacteristic();
                    _that.watchBleConnectionState()//启动监听连接状态
                },
                fail: function (res) {
                     toast('启用notify失败')
                    console.log(res, '启用notify失败', _that.notifyUuid);
                },
            });
        }
  1. 监听接受到的数据解析/处理业务逻辑
      onBLECharacteristic() {
            let _that = this;
            try {
                wx.onBLECharacteristicValueChange(function (res) {
                    let flag = _that.hex2ab(res.value);
                    //........................业务代码 拼接数据等很多逻辑
                    //_that+=flag
                });
            } catch (error) {
                // console.log(error, 'onBLECharacteristic');
                uni.hideLoading()
                 toast('监听notify失败')
                throw error
            }
        },

离开页面记得释放蓝牙资源

        handlerCloeseBle() {
            //关闭蓝牙模块。
            wx.closeBluetoothAdapter({
                success: (res) => {
                },
                fail: (err) => {
                    console.log('closeBluetoothAdapter', err);
                }
            });
        }
        //...........其他业务代码

工具函数

        string2ArrayBuffer(str) {
            try {
                var bytes = new Array();
                var len, c;
                len = str.length;
                for (var i = 0; i < len; i++) {
                    c = str.charCodeAt(i);
                    if (c >= 0x010000 && c <= 0x10ffff) {
                        bytes.push(((c >> 18) & 0x07) | 0xf0);
                        bytes.push(((c >> 12) & 0x3f) | 0x80);
                        bytes.push(((c >> 6) & 0x3f) | 0x80);
                        bytes.push((c & 0x3f) | 0x80);
                    } else if (c >= 0x000800 && c <= 0x00ffff) {
                        bytes.push(((c >> 12) & 0x0f) | 0xe0);
                        bytes.push(((c >> 6) & 0x3f) | 0x80);
                        bytes.push((c & 0x3f) | 0x80);
                    } else if (c >= 0x000080 && c <= 0x0007ff) {
                        bytes.push(((c >> 6) & 0x1f) | 0xc0);
                        bytes.push((c & 0x3f) | 0x80);
                    } else {
                        bytes.push(c & 0xff);
                    }
                }
                var array = new Int8Array(bytes.length);
                for (var i in bytes) {
                    array[i] = bytes[i];
                }
                return array.buffer;
            } catch (error) {
                uni.hideLoading()
            toast('解析ArrayBuffer失败~')
                console.log(error, 'string2ArrayBuffer');
                throw error
            }
        },
                //ArrayBuffer 转字符串
        str2ab(arrayBuffer) {
            try {
                let unit8Arr = new Uint8Array(arrayBuffer);
                let encodedString = String.fromCharCode.apply(null, unit8Arr);
                let decodedString = decodeURIComponent(escape(encodedString)); //没有这一步中文会乱码
                return decodedString;
            } catch (error) {
                console.log(error, 'str2ab');
                uni.hideLoading()
                toast('解析数据失败~str2ab')
                return false;
            }
        },
        hex2ab(str) {
            try {
                return String.fromCharCode.apply(null, new Uint8Array(str));
            } catch (error) {
                uni.hideLoading()
              toast('解析数据失败~hex2ab')
                console.log(error, 'hex2ab');
                throw error
            }
        },    
        // 16进制(byte格式)转字符串
        hexToString(str) {
            try {
                let res = str.replace(/\\x/g, "%");
                let result = decodeURIComponent(res, "utf-8");
                return result;
            } catch (error) {
                uni.hideLoading()
                throw error
            }
        },
        //监听蓝牙连接状态
        watchBleConnectionState() {
            wx.onBLEConnectionStateChange((res) => {
                console.log('watchBleConnectionState', res);
                if (!res?.connected) {
                toast('设备已断开蓝牙连接')
                }
            })
        },
        
       async stopBluetooth() {
            let _that = this
            wx.stopBluetoothDevicesDiscovery({
                success: (result) => {
                    console.log("停止搜寻附近的蓝牙外围设备成功");
                   //处理逻辑......
                },
                fail: (error) => {
                    console.log("停止搜寻附近的蓝牙外围设备失败", error);
                },
            });
        },
        

尝试解析拼接数据 视硬件方发送数据而定

transStrToArr(){
 //decodeURIComponent(str)
 //hexToString(str)
 //str.split("/n")
}

写入数据操作writeBLECharacteristic 不要并行写入操作!!! developers.weixin.qq.com/miniprogram… 查看‘注意’

    async writeBLECharacteristic(val, index) {
            return new Promise((resolve, reject) => {
                let _that = this;
                let buffer = _that.string2ArrayBuffer(val);
                wx.writeBLECharacteristicValue({
                 deviceId: _that.deviceId,
                    serviceId: _that.writeServiceId,
                    characteristicId: _that.writeUuid,
                    value: buffer,
                    success: function (res) {
                             //业务代码..........
                             //判断是否写入了完整的数据  _that.chunkCount-1===index 
                        resolve(res);
                    },
                    fail: function (err) {
                    toast("写入数据失败");
                        reject(err);
                    },
                });
            });
        }

接收数据 要与硬件方沟通好, 数据传输用哪种结构 ,因为数据发过来一般只有20字节(可能不是完整的中文/encodeURIComponent 编码,可能有换行符、特殊字符等), 我们要将他进行拼接处理再解析(decodeURIComponent处理 、处理特殊字符) ,如果有排序去重等建议让硬件方去处理 发送数据 在发送的数据的时候可能硬件也只能收到20字节的数据, 我们也需要去处理(确保每次发送数据不超过20字节大小)

    async handlerWrite() {
        let position = 0;
        let chunks = [];
        // 计算字符串的实际字节长度
        const getByteLength = (str) => {
          let len = 0;
          for (let i = 0; i < str.length; i++) {
            const charCode = str.charCodeAt(i);
            if (charCode <= 0x007f) {
              // ASCII字符占1字节
              len += 1;
            } else if (charCode <= 0x07ff) {
              len += 2; // 部分Unicode字符占2字节
            } else {
              len += 3; // 中文等字符占3字节
            }
          }
          return len;
        // 获取下一个安全的切割点
        const getNextChunkEnd = (str, start, maxBytes) => {
          let byteCount = 0;
          let i = start;
          while (i < str.length) {
            const charCode = str.charCodeAt(i);
            const charByteLength =
              charCode <= 0x007f ? 1 : charCode <= 0x07ff ? 2 : 3;
            // 如果当前字符会导致超出20字节,就在这里截断
            if (byteCount + charByteLength > maxBytes) {
              // 如果已经到了字符串末尾,就不再添加
              if (i === str.length - 1) {
                return i;
              }
              return i;
            }
            byteCount += charByteLength;
            i++;
          }
          return i;
        };
        // 分割字符串,确保不会截断中文字符
        while (position < str.length) {
          const end = getNextChunkEnd(str, position, 20); // 使用20字节作为上限
          const chunk = str.substring(position, end);
          chunks.push(chunk);
          position = end;
        }
        // 记录总块数,用于进度跟踪
        this.chunkCount = chunks.length;
        // 按顺序发送所有数据块
        for (let i = 0; i < chunks.length; i++) {
          await this.writeBLECharacteristic(chunks[i], i);
        }
      }
    //其他业务代码
    },

看社区有人说用wx.setBLEMTU循环设置蓝牙低功耗的最大传输单元 但是有人说不行 但是这api不支持ios!

微信截图_20241223172911.png 可以参考blog.csdn.net/weixin_4498… 如有错误疑问 请评论指出