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