first commit

This commit is contained in:
2026-02-10 17:11:06 +08:00
commit 12f4fb81b8
32 changed files with 2958 additions and 0 deletions

View 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` 每个命令的数据字段按字节位展开,并给出示例十六进制报文)。