Files
AI_Printer/APP_BLE打印控制实现说明.md
2026-02-10 17:11:06 +08:00

10 KiB
Raw Blame History

lyfPrinter APP BLE 打印控制实现说明

本文档基于当前项目源码,说明 Android 上位机如何通过 BLE 控制热敏打印机完成打印。

适用代码目录:app/src/main/java/com/example/electronicScale


1. 总体架构

APP 的控制链路可分为 4 层:

  1. 页面层(各类预览页)
    • 负责生成待打印位图(图片/文本/二维码/小票/标签)。
  2. 业务协议层(BlueHandler
    • 负责打印协议封装、状态解析、分包发送、打印参数下发。
  3. BLE 传输层(ECBLE
    • 负责扫描、连接、订阅通知、写特征、MTU 协商。
  4. 打印机固件
    • 解析自定义指令执行打印、走纸、标签定位、OTA 等动作。

核心类:

  • MainActivity:权限、扫描、连接入口。
  • ECBLEBLE 扫描/连接/读写底层。
  • BlueHandler:打印协议与数据处理核心。
  • PrinterControlActivity:打印机状态面板与功能入口页。
  • *PreviewActivity:生成位图并调用统一打印入口。

2. 连接与初始化流程

2.1 权限与系统开关

MainActivity 在进入时先做权限检查,再打开蓝牙:

  • 位置权限:ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION
  • Android 12+ 蓝牙权限:BLUETOOTH_SCANBLUETOOTH_ADVERTISEBLUETOOTH_CONNECT

参考:

  • MainActivity.java:264
  • AndroidManifest.xml

ECBLE.openBluetoothAdapter() 会继续检查:

  • 是否支持蓝牙
  • 蓝牙开关是否开启
  • 定位开关是否开启GPS 或 Network

参考:

  • ECBLE.java:61

2.2 扫描与设备过滤

扫描使用 BluetoothAdapter.startLeScan(),回调设备名与 MAC。

APP 仅展示设备名为 lyfPrinter(忽略大小写、去空格)的设备。

参考:

  • ECBLE.java:132
  • MainActivity.java:238
  • MainActivity.java:244

2.3 建链与 GATT 特征绑定

用户点击设备后,调用 ECBLE.createBLEConnection()

连接策略:

  • 失败自动重试,最多 5 次(初次 + 4 次重试)。
  • 每次重试间隔约 300ms。

参考:

  • MainActivity.java:176
  • ECBLE.java:336

服务发现后,绑定两个关键特征:

  • Notify0000fff1-0000-1000-8000-00805f9b34fb
  • Write0000fff2-0000-1000-8000-00805f9b34fb

并执行:

  • 开启通知(setCharacteristicNotification + CCCD
  • 请求 MTU = 247
  • 写入类型 WRITE_TYPE_NO_RESPONSE

参考:

  • ECBLE.java:180
  • ECBLE.java:181
  • ECBLE.java:219
  • ECBLE.java:291
  • ECBLE.java:303
  • ECBLE.java:378

3. BlueHandler统一打印控制核心

BlueHandler 是单例,负责:

  1. 注册 BLE 回调并解析回包。
  2. 定时查询打印机状态(每 5 秒)。
  3. 校验打印条件(缺纸/低电/高温)。
  4. 将位图转为热敏点阵数据。
  5. 按打印协议分帧发送并处理进度。

参考:

  • BlueHandler.java:70
  • BlueHandler.java:91
  • BlueHandler.java:232
  • BlueHandler.java:296
  • BlueHandler.java:438

4. 自定义通信协议(打印指令)

4.1 帧格式

常规帧由 printer_sendParam() 组包:

  1. slaveAddr1字节默认 0x01
  2. function1字节
  3. lengthH1字节
  4. lengthL1字节
  5. payload(可变)
  6. checkSum可选1字节

校验规则(若启用):

  • 从地址到 payload 所有字节求和,取低 8 位。

参考:

  • BlueHandler.java:590

4.2 指令码

打印控制相关:

指令 功能
0x00 电源开关(PRINT_POWER_CMD
0x01 获取状态(PRINT_GET_STATUS_CMD
0x02 走纸距离(PRINT_SET_DISTANCE_CMD
0x03 打印参数(PRINT_SET_PARAM_CMD
0x04 发送打印数据(PRINT_SEND_DATA_CMD
0x05 标签间隙定位(PRINT_GAP_MOVE_CMD
0x06 查询标签偏移(PRINT_GET_LABEL_OFFSET_CMD
0x07 设置标签偏移(PRINT_SET_LABEL_OFFSET_CMD

OTA 相关:

指令 功能
0xA0 跳转 Boot
0xA1 擦页
0xA2 写固件数据
0xA3 跳转 App
0xA4 查询版本

参考:

  • BlueHandler.java:49
  • BlueHandler.java:59

4.3 回包解析

BlueHandler.anasysBlueData() 解析 hexBuf[1](功能码):

  • 0x01(状态)
    • hexBuf[4]:纸张状态
    • hexBuf[5]:电量
    • hexBuf[6]:温度符号(0x2D 表示负)
    • hexBuf[7..8]温度数值0.1℃)
  • 0x04(打印数据应答)
    • printerIsIdle = 0,允许发送下一包
  • 0x05(标签定位完成)
    • printerPaperPosition = 1

参考:

  • BlueHandler.java:296

5. 从页面到打印的通用流程

无论是图片、文字、二维码、小票、标签,最终都走统一入口:

mBlueHandler.printImageToPrinter(bitmap, density)

参考:

  • ImagePreviewActivity.java:369
  • TextPreviewActivity.java:352
  • QrPreviewMainActivity.java:367
  • receiptPreviewActivity.java:412
  • labelPapaerPreviewActivity.java:927
  • factoryTestPreviewActivity.java:308

通用步骤:

  1. 页面先调用 printrt_errorDetect() 做可打印校验。
  2. 各页面把预览内容渲染成 384 宽位图。
  3. 调用 printImageToPrinter() 进入统一发送流程。

6. 位图到热敏数据的转换

BlueHandler.printImageToPrinter() 内部流程:

  1. convertBitmapToThermalData(bitmap, 384)
  2. scaleBitmapToPrinterWidth():缩放到 384 像素宽
  3. convertToBlackWhite():阈值二值化(灰度 < 128 记黑)
  4. convertBitmapToRawData()按行取模8 像素打包 1 字节MSB first
  5. 得到连续点阵字节流后进行分包发送

参考:

  • BlueHandler.java:438
  • BlueHandler.java:379
  • BlueHandler.java:401
  • DataConvertTool.java:117
  • DataConvertTool.java:128

7. 分包发送与打印执行

7.1 分包策略

APP 设置单帧上限为 244 字节,其中:

  • 协议头 4 字节
  • 数据体最多 240 字节

这样每帧可发送 5 行384点宽 => 48字节/行5行=240字节

参考:

  • BlueHandler.java:442
  • BlueHandler.java:476

7.2 打印前参数下发

实际发送前会执行:

  1. 开启打印电源:printer_setVhSwitch(true)0x00
  2. 设置打印参数:printer_setPrintParam(1, 2, hotTime)
  3. hotTime 来自浓度映射:
    • 较淡=1000
    • 中等=1500
    • 较浓=2000
    • 最深=3000

参考:

  • BlueHandler.java:505
  • BlueHandler.java:509
  • BlueHandler.java:450

7.3 数据发送节奏

发送循环中每个分包都走 PRINT_SEND_DATA_CMD(0x04),但 isSendCheck=false(打印数据帧不附加校验字节)。

流控机制:

  • 发送后置 printerIsIdle = 0x04
  • 收到 0x04 应答时在解析函数中将其清零
  • 第 1 包后只短延时
  • 从第 2 包开始等待空闲再发下一包(超时 30s

参考:

  • BlueHandler.java:520
  • BlueHandler.java:334
  • BlueHandler.java:551

7.4 收尾动作

发送结束后统一执行:

  1. 关闭打印电源:printer_setVhSwitch(false)
  2. 走纸留白:printer_setMoveDistance(12.5f)(约 12.5mm
  3. 恢复状态轮询
  4. 弹窗提示“打印完成”

参考:

  • BlueHandler.java:566
  • BlueHandler.java:574

8. 各打印页面的实现差异

8.1 图片打印(ImagePreviewActivity

  • 用户选图后预处理成 384 宽位图。
  • 预览和打印使用误差扩散Floyd-Steinberg/Atkinson提升灰阶视觉效果。
  • 最终把二值结果送入统一打印入口。

参考:

  • ImagePreviewActivity.java:184
  • ImagePreviewActivity.java:319
  • ImagePreviewActivity.java:354

8.2 文本打印(TextPreviewActivity

  • TextView 按 384 固定宽重新测量布局。
  • 绘制到白底 Bitmap 后打印。

参考:

  • TextPreviewActivity.java:328
  • TextPreviewActivity.java:356

8.3 二维码打印(QrPreviewMainActivity

  • 先生成二维码,再按打印宽度 384 等比缩放后打印。

参考:

  • QrPreviewMainActivity.java:340
  • QrPreviewMainActivity.java:367

8.4 小票与工厂测试页

  • 都是将固定模板文本绘制为 Bitmap再统一打印。

参考:

  • receiptPreviewActivity.java:387
  • factoryTestPreviewActivity.java:286

8.5 标签纸打印(labelPapaerPreviewActivity

标签场景在打印前多了“找缝 + 偏移”动作:

  1. 查询/设置标签偏移(0x06 / 0x07)。
  2. 打印前发送 0x05 做 gap 定位,等待 printerPaperPosition 标志。
  3. 再走纸到 currentOffset 位置后开始打印。

参考:

  • labelPapaerPreviewActivity.java:117
  • labelPapaerPreviewActivity.java:234
  • labelPapaerPreviewActivity.java:886
  • labelPapaerPreviewActivity.java:909

9. 打印状态与防呆

状态评估来自 detectJundge()

  • 缺纸 -> 禁止打印
  • 电量 <= 40% -> 禁止打印
  • 温度 >= 60℃ -> 禁止打印

页面点击打印时会先调用 printrt_errorDetect(),失败就弹窗并中止。

参考:

  • DataConvertTool.java:12
  • BlueHandler.java:787

10. 一次打印的时序(简化)

用户点击打印
  -> PreviewActivity 生成 Bitmap(384宽)
  -> BlueHandler.printrt_errorDetect()
  -> BlueHandler.printImageToPrinter()
      -> 位图转点阵字节流
      -> printer_setVhSwitch(true)
      -> printer_setPrintParam(...)
      -> 循环发送 0x04 数据帧
          -> 等待 0x04 应答解锁下一帧
      -> printer_setVhSwitch(false)
      -> printer_setMoveDistance(12.5)
      -> 弹窗: 打印完成

11. 代码实现中的关键注意点

  1. ECBLEBlueHandler 的回调是静态单实例风格,多个页面共享同一通道,页面切换时需正确注册/移除监听器。
  2. 打印分包等待采用忙等循环(无休眠),高负载时可能有 CPU 占用峰值。
  3. 打印数据帧(0x04)当前不带校验字节,链路异常时主要依赖 BLE 层可靠性与应答超时控制。
  4. 扫描采用 startLeScan(旧 API如后续适配新系统可迁移到 BluetoothLeScanner
  5. 状态解析里 connectState 目前写死为 1BlueHandler.java:305),如果后续需要严格依赖设备回包连接位,建议改为按协议字段解析。

如果需要,我可以继续补一份《协议字段示例帧文档》(把 0x00~0x07 每个命令的数据字段按字节位展开,并给出示例十六进制报文)。