10 KiB
lyfPrinter APP BLE 打印控制实现说明
本文档基于当前项目源码,说明 Android 上位机如何通过 BLE 控制热敏打印机完成打印。
适用代码目录:app/src/main/java/com/example/electronicScale
1. 总体架构
APP 的控制链路可分为 4 层:
- 页面层(各类预览页)
- 负责生成待打印位图(图片/文本/二维码/小票/标签)。
- 业务协议层(
BlueHandler)- 负责打印协议封装、状态解析、分包发送、打印参数下发。
- BLE 传输层(
ECBLE)- 负责扫描、连接、订阅通知、写特征、MTU 协商。
- 打印机固件
- 解析自定义指令,执行打印、走纸、标签定位、OTA 等动作。
核心类:
MainActivity:权限、扫描、连接入口。ECBLE:BLE 扫描/连接/读写底层。BlueHandler:打印协议与数据处理核心。PrinterControlActivity:打印机状态面板与功能入口页。- 各
*PreviewActivity:生成位图并调用统一打印入口。
2. 连接与初始化流程
2.1 权限与系统开关
MainActivity 在进入时先做权限检查,再打开蓝牙:
- 位置权限:
ACCESS_FINE_LOCATION、ACCESS_COARSE_LOCATION - Android 12+ 蓝牙权限:
BLUETOOTH_SCAN、BLUETOOTH_ADVERTISE、BLUETOOTH_CONNECT
参考:
MainActivity.java:264AndroidManifest.xml
ECBLE.openBluetoothAdapter() 会继续检查:
- 是否支持蓝牙
- 蓝牙开关是否开启
- 定位开关是否开启(GPS 或 Network)
参考:
ECBLE.java:61
2.2 扫描与设备过滤
扫描使用 BluetoothAdapter.startLeScan(),回调设备名与 MAC。
APP 仅展示设备名为 lyfPrinter(忽略大小写、去空格)的设备。
参考:
ECBLE.java:132MainActivity.java:238MainActivity.java:244
2.3 建链与 GATT 特征绑定
用户点击设备后,调用 ECBLE.createBLEConnection()。
连接策略:
- 失败自动重试,最多 5 次(初次 + 4 次重试)。
- 每次重试间隔约 300ms。
参考:
MainActivity.java:176ECBLE.java:336
服务发现后,绑定两个关键特征:
- Notify:
0000fff1-0000-1000-8000-00805f9b34fb - Write:
0000fff2-0000-1000-8000-00805f9b34fb
并执行:
- 开启通知(
setCharacteristicNotification + CCCD) - 请求 MTU = 247
- 写入类型
WRITE_TYPE_NO_RESPONSE
参考:
ECBLE.java:180ECBLE.java:181ECBLE.java:219ECBLE.java:291ECBLE.java:303ECBLE.java:378
3. BlueHandler:统一打印控制核心
BlueHandler 是单例,负责:
- 注册 BLE 回调并解析回包。
- 定时查询打印机状态(每 5 秒)。
- 校验打印条件(缺纸/低电/高温)。
- 将位图转为热敏点阵数据。
- 按打印协议分帧发送并处理进度。
参考:
BlueHandler.java:70BlueHandler.java:91BlueHandler.java:232BlueHandler.java:296BlueHandler.java:438
4. 自定义通信协议(打印指令)
4.1 帧格式
常规帧由 printer_sendParam() 组包:
slaveAddr(1字节,默认0x01)function(1字节)lengthH(1字节)lengthL(1字节)payload(可变)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:49BlueHandler.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:369TextPreviewActivity.java:352QrPreviewMainActivity.java:367receiptPreviewActivity.java:412labelPapaerPreviewActivity.java:927factoryTestPreviewActivity.java:308
通用步骤:
- 页面先调用
printrt_errorDetect()做可打印校验。 - 各页面把预览内容渲染成 384 宽位图。
- 调用
printImageToPrinter()进入统一发送流程。
6. 位图到热敏数据的转换
BlueHandler.printImageToPrinter() 内部流程:
convertBitmapToThermalData(bitmap, 384)scaleBitmapToPrinterWidth():缩放到 384 像素宽convertToBlackWhite():阈值二值化(灰度 < 128 记黑)convertBitmapToRawData():按行取模,8 像素打包 1 字节(MSB first)- 得到连续点阵字节流后进行分包发送
参考:
BlueHandler.java:438BlueHandler.java:379BlueHandler.java:401DataConvertTool.java:117DataConvertTool.java:128
7. 分包发送与打印执行
7.1 分包策略
APP 设置单帧上限为 244 字节,其中:
- 协议头 4 字节
- 数据体最多 240 字节
这样每帧可发送 5 行(384点宽 => 48字节/行,5行=240字节)。
参考:
BlueHandler.java:442BlueHandler.java:476
7.2 打印前参数下发
实际发送前会执行:
- 开启打印电源:
printer_setVhSwitch(true)(0x00) - 设置打印参数:
printer_setPrintParam(1, 2, hotTime) hotTime来自浓度映射:- 较淡=1000
- 中等=1500
- 较浓=2000
- 最深=3000
参考:
BlueHandler.java:505BlueHandler.java:509BlueHandler.java:450
7.3 数据发送节奏
发送循环中每个分包都走 PRINT_SEND_DATA_CMD(0x04),但 isSendCheck=false(打印数据帧不附加校验字节)。
流控机制:
- 发送后置
printerIsIdle = 0x04 - 收到
0x04应答时在解析函数中将其清零 - 第 1 包后只短延时
- 从第 2 包开始等待空闲再发下一包(超时 30s)
参考:
BlueHandler.java:520BlueHandler.java:334BlueHandler.java:551
7.4 收尾动作
发送结束后统一执行:
- 关闭打印电源:
printer_setVhSwitch(false) - 走纸留白:
printer_setMoveDistance(12.5f)(约 12.5mm) - 恢复状态轮询
- 弹窗提示“打印完成”
参考:
BlueHandler.java:566BlueHandler.java:574
8. 各打印页面的实现差异
8.1 图片打印(ImagePreviewActivity)
- 用户选图后预处理成 384 宽位图。
- 预览和打印使用误差扩散(Floyd-Steinberg/Atkinson)提升灰阶视觉效果。
- 最终把二值结果送入统一打印入口。
参考:
ImagePreviewActivity.java:184ImagePreviewActivity.java:319ImagePreviewActivity.java:354
8.2 文本打印(TextPreviewActivity)
- 把
TextView按 384 固定宽重新测量布局。 - 绘制到白底 Bitmap 后打印。
参考:
TextPreviewActivity.java:328TextPreviewActivity.java:356
8.3 二维码打印(QrPreviewMainActivity)
- 先生成二维码,再按打印宽度 384 等比缩放后打印。
参考:
QrPreviewMainActivity.java:340QrPreviewMainActivity.java:367
8.4 小票与工厂测试页
- 都是将固定模板文本绘制为 Bitmap,再统一打印。
参考:
receiptPreviewActivity.java:387factoryTestPreviewActivity.java:286
8.5 标签纸打印(labelPapaerPreviewActivity)
标签场景在打印前多了“找缝 + 偏移”动作:
- 查询/设置标签偏移(
0x06/0x07)。 - 打印前发送
0x05做 gap 定位,等待printerPaperPosition标志。 - 再走纸到
currentOffset位置后开始打印。
参考:
labelPapaerPreviewActivity.java:117labelPapaerPreviewActivity.java:234labelPapaerPreviewActivity.java:886labelPapaerPreviewActivity.java:909
9. 打印状态与防呆
状态评估来自 detectJundge():
- 缺纸 -> 禁止打印
- 电量
<= 40%-> 禁止打印 - 温度
>= 60℃-> 禁止打印
页面点击打印时会先调用 printrt_errorDetect(),失败就弹窗并中止。
参考:
DataConvertTool.java:12BlueHandler.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. 代码实现中的关键注意点
ECBLE与BlueHandler的回调是静态单实例风格,多个页面共享同一通道,页面切换时需正确注册/移除监听器。- 打印分包等待采用忙等循环(无休眠),高负载时可能有 CPU 占用峰值。
- 打印数据帧(
0x04)当前不带校验字节,链路异常时主要依赖 BLE 层可靠性与应答超时控制。 - 扫描采用
startLeScan(旧 API),如后续适配新系统可迁移到BluetoothLeScanner。 - 状态解析里
connectState目前写死为1(BlueHandler.java:305),如果后续需要严格依赖设备回包连接位,建议改为按协议字段解析。
如果需要,我可以继续补一份《协议字段示例帧文档》(把 0x00~0x07 每个命令的数据字段按字节位展开,并给出示例十六进制报文)。