uniapp小程序连接蓝牙设备

发布于:2024-07-11 ⋅ 阅读:(21) ⋅ 点赞:(0)

一、初始化蓝牙模块

这一步是必须的,在开发项目过程中,初始化蓝牙模块之后,紧接着就要开启一些监听的api,供后续设备读写数据时监听变化。

initBLE(callback) {
      //提前定义变量,直接在uni的api中使用this是不可以的
      var self = this;
      // `第一步:初始化蓝牙模块`
      uni.openBluetoothAdapter({
        success: function (e) {
          utils.toast("初始化蓝牙适配器成功");
        },
        fail: function (e) {
          utils.toast("初始化蓝牙适配器失败 : " + JSON.stringify(e));
        },
      });

      // `检查蓝牙适配器状态变化`
      // `这一步主要是为了在连接成功蓝牙之后关闭蓝牙搜索,蓝牙搜索非常耗费性能`
      uni.onBluetoothAdapterStateChange(function (e) {
        self.bluetooth.available = e.available;
        if (!e.available) {
          utils.toast("蓝牙适配器不可用");

          if (self.bluetooth.startDiscovery) {
            self.bluetooth.startDiscovery = false;
            self.stopBluetoothDevicesDiscovery();
          }
        }
      });
      // `监听搜索到设备`
      uni.onBluetoothDeviceFound(function (e) {
     // `当搜索到设备后,可以在e.devices中获取到设备信息列表`
        if (!e.devices) return;
        for (var i = 0; i < e.devices.length; i++) {
          var device = e.devices[i];

          for (var j = 0; j < self.discoveryList.length; j++) {
            var item = self.discoveryList[j];

            // 去重
            if (item.deviceId === device.deviceId) {
              return;
            }
          }
          self.discoveryList.push(device);
        }
      });
      // `监听蓝牙设备连接变化`
      uni.onBLEConnectionStateChange(function (e) {
        // 该方法回调中可以用于处理连接意外断开等异常情况
        self.currentDeviceStatus = e.connected ? 1 : 2;

        // 在连续盘点过程中是不允许断开的,万一因为其他什么原因断开了,则尝试停止连续盘点
        if (!e.connected && self.inventoryLabelForm.looping) {
          self.sendStopLoopInventoryCmd(); //停止盘点
        }
      });

      // `读数据`
      uni.onBLECharacteristicValueChange(function (e) {
        if (e.deviceId != self.currentDevice.deviceId) return;
        var value = self.buffer2Hex(e.value);
        console.log(e); //查看是否有R20固定返回值
        utils.toast("读数据178:" + JSON.stringify(e));
        self.lastRcvData += value;
        self.doRcvData();
      });

      self.historyList = utils.getHistoryList();
      // 如果有cb回调函数,则需要停止下拉刷新
      if (callback) {
        // setTimeout这里等待一下上面的异步初始化
        setTimeout(() => {
          callback();
        }, 1000);
      }
    },

二、开始搜索

以上只是蓝牙的初始化操作,要想真正实现蓝牙连接从第二步开始

// 开启蓝牙搜索服务
    startBluetoothDevicesDiscovery() {
      var self = this;
      // 小程序环境需要等待一下,不然会报错
      setTimeout(() => {
        // 开始搜寻附近的蓝牙外围设备
        uni.startBluetoothDevicesDiscovery({
          allowDuplicatesKey: false,
          success: function (e) {
            self.bluetooth.discoverying = true;
            //开启搜索成功,此时初始化中定义的onBluetoothDeviceFound会自动执行
          },
          fail: function (e) {
            utils.toast("开始搜索蓝牙设备失败 : " + JSON.stringify(e));
          },
        });
      }, 1000);
    },

三、连接蓝牙

目前为止我们已经搜索到蓝牙设备了,下面要做的就是在蓝牙列表中选择对应的蓝牙设备进行连接

// 连接设备
    onConnectDevice(device) {
    // `device是选择的设备对象,在这可以取到设备的deviceId供后续使用`
      var self = this;
      this.onCloseDiscoveryDialog();
      this.onCloseHistoryDialog();
      this.currentDevice = device;
      this.currentDeviceStatus = 3;
      this.lastRcvData = "";
      // 创建一个BLE连接
      uni.createBLEConnection({
        deviceId: device.deviceId,//这里使用设备id来创建连接
        success: function (e) {
          // 创建完成后获取服务(这在ios中是必须的,否则会导致后面读取数据失败)
          uni.getBLEDeviceServices({
            // 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
            deviceId: device.deviceId,
            success(res) {
              // 获取特征值(这在ios中是必须的,否则会导致后面读取数据失败)
              uni.getBLEDeviceCharacteristics({
                // 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
                deviceId: device.deviceId,
                // 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
                serviceId: RX_SERVICE_UUID, //因为这里使用的是固定服务所以事先定义过常量了,这里直接使用
                success(res) {
                  // 创建连接-获取服务-获取特征值之后就可以开启nofity功能了
                  self.enableBleNotify(device);
                },
                fail() {},
              });
            },
          });
          // 开启nofity功能
          self.enableBleNotify(device);
        },
        fail: function (e) {
          utils.toast("连接蓝牙设备失败 : " + JSON.stringify(e));
          self.currentDeviceStatus = 2;
        },
      });

      // 保存设备到历史记录里
      var flag = true;

      for (var i = 0; i < this.historyList.length; i++) {
        var item = this.historyList[i];

        if (item.deviceId === device.deviceId) {
          this.historyList[i] = device;
          flag = false;
        }
      }

      if (flag) {
        this.historyList.push(device);
      }

      utils.saveHistoryList(this.historyList);
    },

四、监听特征值变化

 enableBleNotify(device) {
      // 这里监听时,ios测试需要有点延迟,安卓的话,只在测试机上测试过,不需要延迟
      // 这里延迟1秒兼容,反正差距不大
      setTimeout(function () {
        uni.notifyBLECharacteristicValueChange({
          state: true,
          deviceId: device.deviceId,
          serviceId: RX_SERVICE_UUID,
          characteristicId: R_UUID,
          success: function (e) {
          //到了此时蓝牙才算真正的连接成功
			},
          fail: function (e) {},
        });
      }, 1000);
    },

五、调用示例

这里以单次盘点为例

 // 单步盘点标签
   async  onOneStepInventory() {
      var self = this;
      if (!this.checkDeviceConnect()) return;
      this.inventoryLabelForm.startTime = utils.currentTimeMillis(); //获取盘点时间的
      // 单步盘点关闭enableRssi
      this.inventoryLabelForm.enableRssi = false;
      // 发送固定值
      await this.sendR20Hex(); //这里是定制需要没有可以省去
      var cmd = this.generateUHFProtocl(0x80, utils.number2Hex(5000, 2));
      self.sendData(cmd);
    },
    //发送固定值
   async sendR20Hex() {
      await this.sendData(
        "A55A0029F011D75238EE46C3ECFFCE3B9AFC093ACC13F711A6ADF3FF76ACE59A8DF1BA704E22EC0D0A"
      );
    },
    //写入操作(发送数据,连续存盘指令自行实现了,不调用该方法)
    async sendData(hexStr, serviceId, characteristicId) {
      if (utils.isBlank(hexStr)) return;
      serviceId = serviceId || RX_SERVICE_UUID;
      characteristicId = characteristicId || W_UUID;

      var self = this;
      var sendData;
      // ble发送数据时,需要分包,最多20字节
      if (hexStr.length > 40) {
        sendData = hexStr.substr(0, 40);
        hexStr = hexStr.substr(40, hexStr.length - 40);
      } else {
        sendData = hexStr;
        hexStr = null;
      }
      this.logSend(sendData);

      var buffer = new ArrayBuffer(sendData.length / 2);
      var bufView = new Uint8Array(buffer);

      for (var i = 0; i < sendData.length; i += 2) {
        bufView[i / 2] = parseInt(sendData.substr(i, 2), 16);
      }

    //这里写了一个promise是为了在执行正常操作之前,先执行固定值操作
      return new Promise((resolve, reject) => {
          uni.writeBLECharacteristicValue({
            deviceId: self.currentDevice.deviceId,
            serviceId: serviceId,
            characteristicId: characteristicId,
            value: buffer,
            success: async function (e) {
               if (hexStr) {
               //超过20字节 递归循环写入
                await self.sendData(hexStr);
               }
              console.log("写入数据成功391" + sendData);
              // 由于写字节限制,如果还有未发送完的数据,接着继续发送
              resolve();
            },
            fail: function (e) {},
          });
      });
    },
    

utils.js文件

这里主要存放一些转换方法,上面用到的utils.xx方法可以在此处查看

export default {
  alert: function (content, title = "提示") {
    uni.showModal({
      title,
      content,
      showCancel: false,
      success: function (res) {},
    });
  },
  confirm: function (content, title = "提示") {
    uni.showModal({
      title,
      content,
      success: function (res) {
        if (res.confirm) {
          console.log("用户点击确定");
        } else if (res.cancel) {
          console.log("用户点击取消");
        }
      },
    });
  },
  toast: function (title, duration = 1000) {
    uni.showToast({
      title,
      icon: "none",
      duration,
    });
  },
  showWaiting: function (title = "加载中") {
    uni.showLoading({
      title,
    });
  },
  closeWaiting: function () {
    uni.hideLoading();
  },
  actionSheet: function (itemList) {
    uni.showActionSheet({
      itemList,
      success: function (res) {
        console.log("选中了第" + (res.tapIndex + 1) + "个按钮");
      },
      fail: function (res) {
        console.log(res.errMsg);
      },
    });
  },
  saveHistoryList: function (list) {
    try {
      uni.setStorageSync("history_list", JSON.stringify(list));
    } catch (e) {
      // error
    }
  },
  getHistoryList: function () {
    try {
      const value = uni.getStorageSync("history_list");
      if (value) {
        return JSON.parse(value);
      } else {
        return [];
      }
    } catch (e) {
      // error
    }
  },
  clearHistoryList: function () {
    try {
      uni.removeStorageSync("history_list");
    } catch (e) {
      // error
    }
  },
  leftPad: function (value, length, fill) {
    while (length - value.length > 0) {
      value = fill + value;
    }
    return value;
  },
  number2Hex: function (value, byteLength) {
    value = parseInt(value);
    var hex = value.toString(16).toUpperCase();
    byteLength = byteLength || hex.length / 2 + (hex.length % 2);

    return this.leftPad(hex, byteLength * 2, "0");
  },
  bin2Hex: function (value, byteLength) {
    byteLength = byteLength || 1;
    byteLength = Math.max(
      value.length / 8 + (value.length % 8 > 0 ? 1 : 0),
      byteLength
    );
    value = this.leftPad(value, byteLength * 8, "0");
    var hex = "";

    for (var i = 0; i < value.length; i += 8) {
      hex += this.number2Hex(parseInt(value.substr(i, 8), 2), 1);
    }

    return hex;
  },
  isNull: function (value) {
    if (value == null) return true;

    if ("undefined" == typeof value) return true;

    return false;
  },
  isString: function (value) {
    return "[object String]" === Object.prototype.toString.call(value);
  },
  isBlank: function (value) {
    if (this.isNull(value)) return true;

    if (!this.isString(value)) return true;

    if (value.trim().length == 0) return true;

    return false;
  },
  str2Hex: function (str) {
    var hex = "";

    for (var i = 0; i < str.length; i++) {
      hex += this.number2Hex(str.charCodeAt(i), 1);
    }

    return hex;
  },
  currentTimeMillis: function () {
    return new Date().getTime();
  },
  hex2ByteArray: function (hex) {
    var result = [];

    for (var i = 0; i < hex.length; i += 2) {
      var value = parseInt(hex.substr(i, 2), 16);

      if (value > 127) {
        value -= 0x100;
      }

      result.push(value);
    }

    return result;
  },
  /**
   * hex转字符串
   * @param {Object} hex
   * @param {Object} charset	编码格式,默认为utf-8
   */
  hex2Str: function (hex, charset = "utf-8") {
    var bytesArray = this.hex2ByteArray(hex);

    try {
      var str;
      // 创建一个TextDecoder对象,指定所需的编码格式
      const decoder = new TextDecoder(charset);
      // 将ByteArray转换为字符串
      str = decoder.decode(bytesArray);
      return str;
    } catch (e) {
      this.toast("错误的编码格式");
    }
  },
};