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 设备发现与连接管理
import CoreBluetooth
class BluetoothPrinterManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
private var centralManager: CBCentralManager!
private var targetPeripheral: CBPeripheral?
private var writeCharacteristic: CBCharacteristic?
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
central.scanForPeripherals(withServices: [CBUUID(string: "FFE0")], options: nil)
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if peripheral.name?.contains("Printer") == true {
targetPeripheral = peripheral
central.stopScan()
central.connect(peripheral, options: nil)
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices([CBUUID(string: "FFE0")])
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
for service in services {
peripheral.discoverCharacteristics([CBUUID(string: "FFE1")], for: service)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
if characteristic.uuid.uuidString == "FFE1" {
writeCharacteristic = characteristic
print("打印机准备就绪")
}
}
}
}
二、发票二维码生成与指令封装
2.1 二维码数据编码规范
根据《增值税发票税控码规范》,二维码需包含:
- 发票代码(10-12位数字)
- 发票号码(8位数字)
- 开票日期(YYYYMMDD)
- 校验码(6-20位字符)
- 金额(含税合计,单位元)
示例数据结构:
{
"code": "123456789012",
"number": "87654321",
"date": "20230815",
"checkCode": "ABC123",
"amount": "100.50"
}
2.2 二维码指令序列构建
完整打印指令需包含:
- 初始化序列
- 文字信息(商户名称、交易时间等)
- 二维码指令块
- 切割指令(如需)
- 走纸指令
struct PrintCommandBuilder {
static func buildInvoiceQRCommand(invoiceData: InvoiceData) -> Data {
var commands = Data()
// 1. 初始化打印机
commands.append("\x1B\x40".data(using: .ascii)!)
// 2. 设置中文编码(如需)
commands.append("\x1B\x74\x01".data(using: .ascii)!) // GBK编码
// 3. 添加标题
let title = "电子发票".centerAligned(width: 32)
commands.append(title.data(using: .ascii)!)
commands.append("\x0A".data(using: .ascii)!)
// 4. 构建二维码数据
let qrContent = """
FPCODE:\(invoiceData.code)
NUMBER:\(invoiceData.number)
DATE:\(invoiceData.date)
CHECK:\(invoiceData.checkCode)
AMOUNT:\(invoiceData.amount)
"""
// 5. 二维码指令(模型2,版本5,纠错L)
let qrCommand = "\x1D\x28\x6B\x04\x00\x31\x50\x30\x05" +
String(format: "%02X", qrContent.count) +
qrContent +
"\x1D\x28\x6B\x03\x00\x31\x51\x30"
commands.append(qrCommand.data(using: .ascii)!)
// 6. 走纸
commands.append("\x0A\x0A\x0C".data(using: .ascii)!)
return commands
}
}
extension String {
func centerAligned(width: Int) -> String {
let padding = (width - count) / 2
return String(repeating: " ", count: padding) + self
}
}
三、完整打印流程实现
3.1 打印状态管理
class PrinterSession {
private let manager = BluetoothPrinterManager()
private var isPrinting = false
func printInvoice(invoice: InvoiceData, completion: @escaping (Bool, Error?) -> Void) {
guard !isPrinting else {
completion(false, PrinterError.busy)
return
}
isPrinting = true
let command = PrintCommandBuilder.buildInvoiceQRCommand(invoiceData: invoice)
DispatchQueue.global().async {
let semaphore = DispatchSemaphore(value: 0)
self.manager.writeData(command) { success, error in
self.isPrinting = false
completion(success, error)
semaphore.signal()
}
semaphore.wait()
}
}
}
extension BluetoothPrinterManager {
func writeData(_ data: Data, completion: @escaping (Bool, Error?) -> Void) {
guard let characteristic = writeCharacteristic else {
completion(false, PrinterError.notConnected)
return
}
targetPeripheral?.writeValue(data, for: characteristic, type: .withResponse) { error in
if let error = error {
completion(false, error)
} else {
completion(true, nil)
}
}
}
}
3.2 异常处理机制
需重点处理的异常场景:
- 连接超时:设置10秒超时重试机制
- 指令格式错误:验证打印机支持的二维码版本
- 数据截断:检查特征值MTU(通常20字节)
- 打印卡纸:监听打印机状态特征值
enum PrinterError: Error {
case notConnected
case busy
case timeout
case invalidData
case unknown(Error)
}
class RetryPolicy {
static func withRetry<T>(maxAttempts: Int = 3,
delay: TimeInterval = 2,
operation: @escaping () -> (T?, Error?)) -> (T?, Error?) {
var lastError: Error?
for attempt in 1...maxAttempts {
let result = operation()
if let value = result.0 {
return (value, nil)
} else if let error = result.1 {
lastError = error
if attempt < maxAttempts {
Thread.sleep(forTimeInterval: delay)
}
}
}
return (nil, lastError ?? PrinterError.unknown(NSError(domain: "", code: 0, userInfo: nil)))
}
}
四、性能优化与兼容性处理
4.1 指令分包传输
当打印数据超过特征值MTU时,需实现分包逻辑:
extension Data {
func chunked(for characteristic: CBCharacteristic) -> [Data] {
let mtu = characteristic.maximumWriteValueLength - 3 // 减去ATT头开销
var chunks = [Data]()
var offset = 0
while offset < count {
let endIndex = min(offset + mtu, count)
let chunk = subdata(in: offset..<endIndex)
chunks.append(chunk)
offset = endIndex
}
return chunks
}
}
4.2 打印机型号适配
不同厂商指令差异处理:
protocol PrinterProtocol {
func initialize() -> Data
func setQRModel(version: Int, errorCorrection: Int) -> Data
func printQR(data: String) -> Data
}
class GenericPrinter: PrinterProtocol {
func initialize() -> Data { "\x1B\x40".data(using: .ascii)! }
// ...其他默认实现
}
class HanxinPrinter: GenericPrinter {
override func setQRModel(version: Int, errorCorrection: Int) -> Data {
// 汉印特殊指令格式
return "\x1D\x28\x6B\x03\x00\x31\x52\x30".data(using: .ascii)!
}
}
五、实际应用建议
- 打印预览:在发送指令前,使用Core Graphics生成预览图
- 日志记录:保存每次打印的指令和结果,便于故障排查
- 固件更新:检查打印机是否支持动态二维码版本调整
- 能耗优化:在非打印时段断开蓝牙连接
通过上述技术实现,开发者可以构建稳定可靠的iOS蓝牙打印系统,满足电子发票二维码的合规打印需求。实际开发中需结合具体打印机型号的文档进行指令微调,并通过大量测试验证兼容性。
发表评论
登录后可评论,请前往 登录 或 注册