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

相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax