uni-app 蓝牙打印:实现数据分片传输机制

一. 技术背景

在蓝牙 4.0 协议规范中,低功耗蓝牙(BLE)的 MTU(Maximum Transmission Unit)默认值为 23 字节。实际传输中有效载荷通常为 20 字节,超过该限制会导致写入错误,并行传输也会写入失败,除此之外,Android 平台和 iOS 平台还有以下区别:

  1. Android 平台 :连续调用 writeBLECharacteristicValue 时触发 10008 系统错误(特征值操作频繁)
  2. iOS 平台:大数据包写入时可能丢失回调事件(包括成功/失败回调)

如下图所示:

所以可以建立分片传输机制,通过将数据包拆解为 20 字节的原子单元,同时在发送数据时考虑时序控制,避免并行写入,要确保前序数据包传输完成后再发送后续分片。

本篇文章,我们主要实现 uni-app 蓝牙打印场景下,因低功耗蓝牙(BLE)MTU 默认值限制,通过移动端分片传输机制来解决数据传输问题。

二. 分片算法

由于蓝牙设备的传输限制,我们需要将打印数据进行分片,要控制在 20 个字节以内,以确保数据能够完整地传输到打印机。

因此我们写一个函数,该函数用于将一个 ArrayBuffer 类型的缓冲区数据按照指定的最大块大小进行分片。它会将输入的缓冲区数据分割成多个小块,每个小块的大小不超过指定的最大块大小(默认为 20 字节)。

2.1 初步实现

主要思路如下:

  1. 初始化一个空数组 result 用于存储分片结果
  2. 初始化一个变量 start 用于记录当前分片的起始位置,初始值为 0
  3. 使用循环进行分片操作,直到处理完整个缓冲区
  4. 在每次循环中,计算当前分片的结束位置,确保不超过缓冲区的总长度
  5. 使用 slice 方法从缓冲区中提取当前分片,并将其添加到结果数组 result
  6. 更新起始位置 start,准备下一次分片
  7. 循环结束后,返回包含所有分片的数组 result
js 复制代码
function getSliceBufferList(buffer, maxChunk = 20) {
  // 用于存储分片结果的数组
  const result = []
  // 记录当前分片的起始位置
  let start = 0

  // 循环进行分片操作,直到处理完整个缓冲区
  while (start < buffer.byteLength) {
    // 计算当前分片的结束位置,确保不超过缓冲区的总长度
    const end = Math.min(start + maxChunk, buffer.byteLength)
    // 将当前分片添加到结果数组中
    result.push(buffer.slice(start, end))
    // 更新起始位置,准备下一次分片
    start = end
  }
  return result
}

2.2 优化后

使用 Tare AI 优化提示后,新增了以下优化点:

  1. 输入验证:检查输入缓冲区是否有效
  2. 边界处理:处理最后一个不完整分片

优化后的代码如下:

js 复制代码
/**
 * 获取分片数组
 * @param {ArrayBuffer} buffer - 要分片的二进制数据缓冲区
 * @param {number} [maxChunk=20] - 每包控制大小,默认为 20 字节
 * @returns {Array<ArrayBuffer>} - 分片后的缓冲区数组
 * @throws {Error} - 当输入缓冲区无效时抛出异常
 */
function getSliceBufferList(buffer, maxChunk = 20) {
  // 输入验证
  if (!(buffer instanceof ArrayBuffer)) {
    throw new Error('Invalid input: expected ArrayBuffer')
  }
  if (buffer.byteLength === 0) {
    return []
  }
  const result = []
  let start = 0
  // 循环分片
  while (start < buffer.byteLength) {
    // 边界处理:确保不超过缓冲区长度
    const end = Math.min(start + maxChunk, buffer.byteLength)
    // 处理最后一个不完整分片
    if (end - start > 0) {
      result.push(buffer.slice(start, end))
    }
    start = end
  }

  return result
}

在优化后的代码中,首先进行了输入验证,确保输入的 buffer 是一个有效的 ArrayBuffer 类型。然后,对输入缓冲区进行了边界处理,确保不会超出缓冲区的长度。

通过以上的优化,确保输入缓冲区的有效性,并处理边界情况,从而得到正确的分片结果。

三. 写入数据

通过上述的分片算法,我们可以将一个 ArrayBuffer 类型的缓冲区数据按照指定的最大块大小进行分片,得到一个分片后的 buffers 数组。

接下来,我们需要将每个分片数据写入到蓝牙设备中。但在实际写入数据过程中,我们除了要确保每次写入数据不能超过 20 个字节外,也要避免过快的写入操作,避免并行操作,不然会出现系统错误。

  • 分片数据大小不能超过 20 个字节
  • 分片数据按照队列以此执行
  • 通过 setTimeout 来控制写入数据的时间间隔,

在 uni-app 中,使用 uni.writeBLECharacteristicValue 方法来写入数据。

js 复制代码
  async writeBLEValue(buffer, delayTime = 100) {
    let buffers = this.getSliceBufferList(buffer);
    for (const buffer of buffers) {
      await new Promise((resolve) => {
        uni.writeBLECharacteristicValue({
          deviceId: this.deviceId,
          serviceId: this.serviceId,
          characteristicId: this.characteristicId,
          value: buffer,
          success: () => {
            console.log('写入数据成功');
            // 使用 setTimeout 控制时间间隔
            setTimeout(() => {
              resolve();
            }, delayTime);
          },
          fail: (err) => {
            console.error('写入数据失败:', err);
            // 使用 setTimeout 控制时间间隔
            setTimeout(() => {
              resolve();
            }, delayTime);
          }
        });
      });
    }
  }

writeBLEValue 这个方法主要会执行以下流程:

  1. 输入参数 :接收一个 buffer ,该 buffer 是需要写入蓝牙设备的总的二进制数据。
  2. 分片处理 :通过 getSliceBufferList 方法将输入的 buffer 进行分片处理,得到一个分片后的 buffers 数组。
  3. 遍历分片 :循环遍历 buffers 数组,对每个分片进行处理。
  4. 写入数据 :针对每个分片,创建一个 Promise 对象,并调用 uni.writeBLECharacteristicValue 方法将分片数据写入蓝牙设备。
    • 成功处理 :若写入操作成功,并调用 resolve 方法来解决 Promise
    • 失败处理 :若写入操作失败,同样调用 resolve 方法来解决 Promise
    • 超时处理 :使用 setTimeout 来控制写入数据的时间间隔,避免过快的写入操作,避免系统错误。
  5. 等待完成 :使用 await 等待当前分片的 Promise 解决,确保前序数据包传输完成后再处理后续分片。
  6. 结束方法:当所有分片都处理完毕后,方法执行结束。

关于以上的写入数据的操作,我们还可以继续进行优化和完善,比如:

  1. 异常处理 :在 Promise 失败的情况下,我们可以添加重试逻辑或用户提示。
  2. 错误隔离:针对单个分片的写入失败,我们可以实现错误隔离,避免影响整体任务。
  3. 重试策略:针对写入失败的分片,我们可以实现指数退避重试策略,以避免频繁重试导致系统负载过高。

后续我们可以实现一个打印队列管理,来实现时序控制、错误隔离和重试策略,以及中断后恢复传输机制,来更完美的确保打印数据完整、稳定地传输到打印机。

四. 总结

通过本篇文章,我们简单实现了分片算法和写入策略来解决低功耗蓝牙(BLE)MTU 默认值限制的问题。通过合理的分片策略和控制写入数据的时间间隔,我们可以确保数据能够完整地传输到打印机,避免系统错误和数据丢失。

在实际应用中,我们可以根据具体的需求和打印机的支持情况,进一步优化和完善分片算法和写入数据的逻辑,以提高打印数据的传输效率和稳定性。

参考文档:uni-app writeblecharacteristicvalue

相关推荐
云端看世界几秒前
ECMAScript 运算符怪谈 上
前端·javascript·ecmascript 6
前端涂涂几秒前
express的介绍,简单使用
前端
卡西法kl2 分钟前
AI,让我们从最简单的to-do-list开始
trae
正在脱发中2 分钟前
vue-cropper 遇到的坑 Failed to execute 'getComputedStyle' on 'Window': parameter
前端·vue.js
黑暗之神4 分钟前
灵通义码应用课题——基于制作海报demo
前端
前端涂涂6 分钟前
express的中间件,全局中间件,路由中间件,静态资源中间件以及使用注意事项 , 获取请求体数据
前端·后端
新时代农民工--小明7 分钟前
从0开始搭建一套工具函数库,发布npm,支持commonjs模块es模块和script引入使用
前端·javascript·typescript·npm·node.js
云端看世界8 分钟前
ECMAScript 标识符绑定关系和作用域
前端·javascript
云端看世界8 分钟前
ECMAScript 闭包
前端·javascript·ecmascript 6
云端看世界10 分钟前
ECMAScript 作用域链
前端·javascript