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

396 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`:权限、扫描、连接入口。
- `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: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`
服务发现后,绑定两个关键特征:
- Notify`0000fff1-0000-1000-8000-00805f9b34fb`
- Write`0000fff2-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. `slaveAddr`1字节默认 `0x01`
2. `function`1字节
3. `lengthH`1字节
4. `lengthL`1字节
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. 一次打印的时序(简化)
```text
用户点击打印
-> PreviewActivity 生成 Bitmap(384宽)
-> BlueHandler.printrt_errorDetect()
-> BlueHandler.printImageToPrinter()
-> 位图转点阵字节流
-> printer_setVhSwitch(true)
-> printer_setPrintParam(...)
-> 循环发送 0x04 数据帧
-> 等待 0x04 应答解锁下一帧
-> printer_setVhSwitch(false)
-> printer_setMoveDistance(12.5)
-> 弹窗: 打印完成
```
---
## 11. 代码实现中的关键注意点
1. `ECBLE``BlueHandler` 的回调是静态单实例风格,多个页面共享同一通道,页面切换时需正确注册/移除监听器。
2. 打印分包等待采用忙等循环(无休眠),高负载时可能有 CPU 占用峰值。
3. 打印数据帧(`0x04`)当前不带校验字节,链路异常时主要依赖 BLE 层可靠性与应答超时控制。
4. 扫描采用 `startLeScan`(旧 API如后续适配新系统可迁移到 `BluetoothLeScanner`
5. 状态解析里 `connectState` 目前写死为 `1``BlueHandler.java:305`),如果后续需要严格依赖设备回包连接位,建议改为按协议字段解析。
---
如果需要,我可以继续补一份《协议字段示例帧文档》(把 `0x00~0x07` 每个命令的数据字段按字节位展开,并给出示例十六进制报文)。