Swift 5使用CoreBluetooth实现iOS向Phomemo M02蓝牙热敏打印机打印图片
Phomemo M02 iOS蓝牙打印故障修复方案
故障根因
- 直接发送JPEG原始数据格式错误:Phomemo M02这类热敏打印机不支持直接解析JPEG格式,仅能识别符合ESC/POS指令集的二进制指令和处理后的二值化位图数据
- 缺少打印控制指令:未发送初始化、打印触发、结束走纸的控制指令,导致打印机持续处于待接收状态,因此会无限出纸
- 图片尺寸未适配打印机参数:M02的有效打印宽度为384像素,超过宽度的图片会被截断或解析错误,导致无图像输出
修复步骤
1. 确认打印机核心参数
Phomemo M02固定参数如下,所有打印逻辑需要适配参数:
- 打印分辨率:203DPI
- 单行有效打印像素:384px
- 指令集:标准ESC/POS指令集
2. 调整打印逻辑
首先修改UIView截图逻辑,把截图缩放到适配打印机的尺寸,再做二值化处理转为打印机可识别的位图,最后按ESC/POS规范封装控制指令和位图数据再发送。
如果数据长度超过蓝牙MTU(默认20字节,协商后最大通常为512字节),需要拆分分包发送,避免丢包。
完整修正代码
extension UIView { // 生成适配打印机宽度的截图 func captureShotForPrinter(targetWidth: CGFloat = 384) -> UIImage { // 生成原始截图 UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, self.layer.contentsScale) drawHierarchy(in: self.bounds, afterScreenUpdates: true) let originalImage = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage() UIGraphicsEndImageContext() // 等比缩放到打印机适配宽度 let scale = targetWidth / originalImage.size.width let targetHeight = originalImage.size.height * scale UIGraphicsBeginImageContext(CGSize(width: targetWidth, height: targetHeight)) originalImage.draw(in: CGRect(x: 0, y: 0, width: targetWidth, height: targetHeight)) let scaledImage = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage() UIGraphicsEndImageContext() return scaledImage } // 转为热敏打印机可识别的带指令打印数据 func convertToThermalPrintData() -> Data { let image = self.captureShotForPrinter() guard let cgImage = image.cgImage else { return Data() } let width = cgImage.width let height = cgImage.height let bytesPerRow = (width + 7) / 8 // 每8个黑白像素占1字节 var bitmapData = Data(count: bytesPerRow * height) // 图片二值化处理 let grayColorSpace = CGColorSpaceCreateDeviceGray() guard let context = CGContext( data: &bitmapData, width: width, height: height, bitsPerComponent: 1, bytesPerRow: bytesPerRow, space: grayColorSpace, bitmapInfo: CGImageAlphaInfo.none.rawValue ) else { return Data() } context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) // 封装ESC/POS打印指令 var printData = Data() // 1. 初始化打印机指令 0x1B 0x40 printData.append(Data([0x1B, 0x40])) // 2. 设置行间距为0 0x1B 0x33 0x00 printData.append(Data([0x1B, 0x33, 0x00])) // 3. 位图打印指令头 0x1D 0x76 0x30 0x00 printData.append(Data([0x1D, 0x76, 0x30, 0x00])) // 4. 写入位图宽高参数(小端序) let w1 = UInt8(bytesPerRow & 0xFF) let w2 = UInt8((bytesPerRow >> 8) & 0xFF) let h1 = UInt8(height & 0xFF) let h2 = UInt8((height >> 8) & 0xFF) printData.append(Data([w1, w2, h1, h2])) // 5. 写入二值化位图数据 printData.append(bitmapData) // 6. 打印并走纸3行(调整最后一个数值可修改走纸长度)0x1B 0x64 0x03 printData.append(Data([0x1B, 0x64, 0x03])) return printData } }
发送打印数据逻辑:
let printData = printView.convertToThermalPrintData() guard !printData.isEmpty else { return } imageView.image = printView.captureShotForPrinter() // 分包发送,适配蓝牙MTU限制 let mtu = globalPeripheral.maximumWriteValueLength(for: .withResponse) var offset = 0 while offset < printData.count { let chunkLen = min(mtu, printData.count - offset) let chunk = printData.subdata(in: offset..<offset+chunkLen) globalPeripheral.writeValue(chunk, for: characteristic, type: .withResponse) offset += chunkLen // 每包间隔10ms避免丢包 Thread.sleep(forTimeInterval: 0.01) }
内容的提问来源于stack exchange,提问作者techyCode1
相关产品推荐
相关产品推荐

