first commit
This commit is contained in:
395
APP_BLE打印控制实现说明.md
Normal file
395
APP_BLE打印控制实现说明.md
Normal file
@@ -0,0 +1,395 @@
|
||||
# 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` 每个命令的数据字段按字节位展开,并给出示例十六进制报文)。
|
||||
Reference in New Issue
Block a user