logo

iOS蓝牙打印小票:发票二维码指令全解析

作者:问题终结者2025.09.19 13:00浏览量:1

简介:本文深入解析iOS设备通过蓝牙打印小票时,如何嵌入发票二维码的指令实现方法。涵盖蓝牙打印机连接、二维码生成、指令封装及异常处理等核心环节,为开发者提供完整的解决方案。

一、蓝牙打印技术基础与设备适配

1.1 CoreBluetooth框架原理

iOS系统通过CoreBluetooth框架实现与蓝牙设备的通信,该框架采用GATT(Generic Attribute Profile)协议栈,支持BLE 4.0及以上设备。开发者需掌握CBPeripheralManager(服务端)和CBCentralManager(客户端)的协作机制,其中打印设备通常作为服务端提供特征值(Characteristic)供iOS端写入数据。

1.2 打印机协议解析

主流蓝牙小票打印机(如佳博、汉印)采用ESC/POS指令集,其数据传输格式包含:

  • 初始化指令:\x1B\x40(重置打印机状态)
  • 文字对齐:\x1B\x61\x00(左对齐)/\x01(居中)/\x02(右对齐)
  • 二维码模式:\x1D\x28\x6B\x03\x00\x31\x50\x30(设置QR码模型1,版本2)
  • 打印指令:\x0A(换行)/\x0C(走纸)

1.3 设备发现与连接管理

  1. import CoreBluetooth
  2. class BluetoothPrinterManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
  3. private var centralManager: CBCentralManager!
  4. private var targetPeripheral: CBPeripheral?
  5. private var writeCharacteristic: CBCharacteristic?
  6. override init() {
  7. super.init()
  8. centralManager = CBCentralManager(delegate: self, queue: nil)
  9. }
  10. func centralManagerDidUpdateState(_ central: CBCentralManager) {
  11. if central.state == .poweredOn {
  12. central.scanForPeripherals(withServices: [CBUUID(string: "FFE0")], options: nil)
  13. }
  14. }
  15. func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
  16. if peripheral.name?.contains("Printer") == true {
  17. targetPeripheral = peripheral
  18. central.stopScan()
  19. central.connect(peripheral, options: nil)
  20. }
  21. }
  22. func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
  23. peripheral.delegate = self
  24. peripheral.discoverServices([CBUUID(string: "FFE0")])
  25. }
  26. func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
  27. guard let services = peripheral.services else { return }
  28. for service in services {
  29. peripheral.discoverCharacteristics([CBUUID(string: "FFE1")], for: service)
  30. }
  31. }
  32. func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
  33. guard let characteristics = service.characteristics else { return }
  34. for characteristic in characteristics {
  35. if characteristic.uuid.uuidString == "FFE1" {
  36. writeCharacteristic = characteristic
  37. print("打印机准备就绪")
  38. }
  39. }
  40. }
  41. }

二、发票二维码生成与指令封装

2.1 二维码数据编码规范

根据《增值税发票税控码规范》,二维码需包含:

  • 发票代码(10-12位数字)
  • 发票号码(8位数字)
  • 开票日期(YYYYMMDD)
  • 校验码(6-20位字符)
  • 金额(含税合计,单位元)

示例数据结构:

  1. {
  2. "code": "123456789012",
  3. "number": "87654321",
  4. "date": "20230815",
  5. "checkCode": "ABC123",
  6. "amount": "100.50"
  7. }

2.2 二维码指令序列构建

完整打印指令需包含:

  1. 初始化序列
  2. 文字信息(商户名称、交易时间等)
  3. 二维码指令块
  4. 切割指令(如需)
  5. 走纸指令
  1. struct PrintCommandBuilder {
  2. static func buildInvoiceQRCommand(invoiceData: InvoiceData) -> Data {
  3. var commands = Data()
  4. // 1. 初始化打印机
  5. commands.append("\x1B\x40".data(using: .ascii)!)
  6. // 2. 设置中文编码(如需)
  7. commands.append("\x1B\x74\x01".data(using: .ascii)!) // GBK编码
  8. // 3. 添加标题
  9. let title = "电子发票".centerAligned(width: 32)
  10. commands.append(title.data(using: .ascii)!)
  11. commands.append("\x0A".data(using: .ascii)!)
  12. // 4. 构建二维码数据
  13. let qrContent = """
  14. FPCODE:\(invoiceData.code)
  15. NUMBER:\(invoiceData.number)
  16. DATE:\(invoiceData.date)
  17. CHECK:\(invoiceData.checkCode)
  18. AMOUNT:\(invoiceData.amount)
  19. """
  20. // 5. 二维码指令(模型2,版本5,纠错L)
  21. let qrCommand = "\x1D\x28\x6B\x04\x00\x31\x50\x30\x05" +
  22. String(format: "%02X", qrContent.count) +
  23. qrContent +
  24. "\x1D\x28\x6B\x03\x00\x31\x51\x30"
  25. commands.append(qrCommand.data(using: .ascii)!)
  26. // 6. 走纸
  27. commands.append("\x0A\x0A\x0C".data(using: .ascii)!)
  28. return commands
  29. }
  30. }
  31. extension String {
  32. func centerAligned(width: Int) -> String {
  33. let padding = (width - count) / 2
  34. return String(repeating: " ", count: padding) + self
  35. }
  36. }

三、完整打印流程实现

3.1 打印状态管理

  1. class PrinterSession {
  2. private let manager = BluetoothPrinterManager()
  3. private var isPrinting = false
  4. func printInvoice(invoice: InvoiceData, completion: @escaping (Bool, Error?) -> Void) {
  5. guard !isPrinting else {
  6. completion(false, PrinterError.busy)
  7. return
  8. }
  9. isPrinting = true
  10. let command = PrintCommandBuilder.buildInvoiceQRCommand(invoiceData: invoice)
  11. DispatchQueue.global().async {
  12. let semaphore = DispatchSemaphore(value: 0)
  13. self.manager.writeData(command) { success, error in
  14. self.isPrinting = false
  15. completion(success, error)
  16. semaphore.signal()
  17. }
  18. semaphore.wait()
  19. }
  20. }
  21. }
  22. extension BluetoothPrinterManager {
  23. func writeData(_ data: Data, completion: @escaping (Bool, Error?) -> Void) {
  24. guard let characteristic = writeCharacteristic else {
  25. completion(false, PrinterError.notConnected)
  26. return
  27. }
  28. targetPeripheral?.writeValue(data, for: characteristic, type: .withResponse) { error in
  29. if let error = error {
  30. completion(false, error)
  31. } else {
  32. completion(true, nil)
  33. }
  34. }
  35. }
  36. }

3.2 异常处理机制

需重点处理的异常场景:

  1. 连接超时:设置10秒超时重试机制
  2. 指令格式错误:验证打印机支持的二维码版本
  3. 数据截断:检查特征值MTU(通常20字节)
  4. 打印卡纸:监听打印机状态特征值
  1. enum PrinterError: Error {
  2. case notConnected
  3. case busy
  4. case timeout
  5. case invalidData
  6. case unknown(Error)
  7. }
  8. class RetryPolicy {
  9. static func withRetry<T>(maxAttempts: Int = 3,
  10. delay: TimeInterval = 2,
  11. operation: @escaping () -> (T?, Error?)) -> (T?, Error?) {
  12. var lastError: Error?
  13. for attempt in 1...maxAttempts {
  14. let result = operation()
  15. if let value = result.0 {
  16. return (value, nil)
  17. } else if let error = result.1 {
  18. lastError = error
  19. if attempt < maxAttempts {
  20. Thread.sleep(forTimeInterval: delay)
  21. }
  22. }
  23. }
  24. return (nil, lastError ?? PrinterError.unknown(NSError(domain: "", code: 0, userInfo: nil)))
  25. }
  26. }

四、性能优化与兼容性处理

4.1 指令分包传输

当打印数据超过特征值MTU时,需实现分包逻辑:

  1. extension Data {
  2. func chunked(for characteristic: CBCharacteristic) -> [Data] {
  3. let mtu = characteristic.maximumWriteValueLength - 3 // 减去ATT头开销
  4. var chunks = [Data]()
  5. var offset = 0
  6. while offset < count {
  7. let endIndex = min(offset + mtu, count)
  8. let chunk = subdata(in: offset..<endIndex)
  9. chunks.append(chunk)
  10. offset = endIndex
  11. }
  12. return chunks
  13. }
  14. }

4.2 打印机型号适配

不同厂商指令差异处理:

  1. protocol PrinterProtocol {
  2. func initialize() -> Data
  3. func setQRModel(version: Int, errorCorrection: Int) -> Data
  4. func printQR(data: String) -> Data
  5. }
  6. class GenericPrinter: PrinterProtocol {
  7. func initialize() -> Data { "\x1B\x40".data(using: .ascii)! }
  8. // ...其他默认实现
  9. }
  10. class HanxinPrinter: GenericPrinter {
  11. override func setQRModel(version: Int, errorCorrection: Int) -> Data {
  12. // 汉印特殊指令格式
  13. return "\x1D\x28\x6B\x03\x00\x31\x52\x30".data(using: .ascii)!
  14. }
  15. }

五、实际应用建议

  1. 打印预览:在发送指令前,使用Core Graphics生成预览图
  2. 日志记录:保存每次打印的指令和结果,便于故障排查
  3. 固件更新:检查打印机是否支持动态二维码版本调整
  4. 能耗优化:在非打印时段断开蓝牙连接

通过上述技术实现,开发者可以构建稳定可靠的iOS蓝牙打印系统,满足电子发票二维码的合规打印需求。实际开发中需结合具体打印机型号的文档进行指令微调,并通过大量测试验证兼容性。

相关文章推荐

发表评论