You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter蓝牙热敏打印机打印QR码/图片失败问题排查

Flutter蓝牙热敏打印机无法打印QR码/图片的解决方案

问题背景

使用flutter_blue和flutter_esc_pos_utils实现蓝牙热敏打印,文本打印正常,但以下两种方式均无法打印QR码:

  1. 调用gen.qrcode()直接生成QR码
  2. 加载本地图片转成img.Image后调用gen.image()打印
    调试发现打印图片时数据全为0,尝试Base64转图片仅打印字符串,还出现过大量问号的异常。

相关代码片段

打印核心函数

void printWithDevice(BluetoothDevice device) async {
    final profile = await CapabilityProfile.load();
    final gen = Generator(PaperSize.mm58, profile);

    final printer = BluePrint();
    final ByteData data = await rootBundle.load('assets/images/qr.png');
    final Uint8List bytes = data.buffer.asUint8List();
    final img.Image image = img.decodeImage(bytes)!;
    printer.add(gen.image(image));               // 无效
    printer.add(gen.qrcode('www.google.com'));  // 无效
    printer.add(gen.text('hello'));              // 有效
    await printer.printData(device);
}

BluePrint类实现

class BluePrint {
  BluePrint({this.chunkLen = 512});

  final int chunkLen;
  final _data = List<int>.empty(growable: true);

  void add(List<int> data) {
    _data.addAll(data);
  }

  List<List<int>> getChunks() {
    final chunks = List<List<int>>.empty(growable: true);
    for (var i = 0; i < _data.length; i += chunkLen) {
      chunks.add(_data.sublist(i, min(i + chunkLen, _data.length)));
    }
    return chunks;
  }

  Future<void> printData(BluetoothDevice device) async {
    final data = getChunks();
    final characs = await _getCharacteristics(device);
    for (var i = 0; i < characs.length; i++) {
      if (await _tryPrint(characs[i], data)) {
        break;
      }
    }
  }

  Future<bool> _tryPrint(
    BluetoothCharacteristic charc,
    List<List<int>> data,
  ) async {
    for (var i = 0; i < data.length; i++) {
      try {
        await charc.write(data[i]);
      } catch (e) {
        return false;
      }
    }
    return true;
  }

  Future<List<BluetoothCharacteristic>> _getCharacteristics(
    BluetoothDevice device,
  ) async {
    final services = await device.discoverServices();
    final res = List<BluetoothCharacteristic>.empty(growable: true);
    for (var i = 0; i < services.length; i++) {
      res.addAll(services[i].characteristics);
    }
    return res;
  }
}

尝试的QR码生成方式(无效)

String qrData = "google.com";
const double qrSize = 200;
try {
  final uiImg = await QrPainter(
    data: qrData,
    version: QrVersions.auto,
    gapless: false,
  ).toImageData(qrSize);
  final dir = await getTemporaryDirectory();
  final pathName = '${dir.path}/qr_tmp.png';
  final qrFile = File(pathName);
  final imgFile = await qrFile.writeAsBytes(uiImg.buffer.asUint8List());
  final img = decodeImage(imgFile.readAsBytesSync());

  generator.image(img);
} catch (e) {
  print(e);
}

修复方案

1. 过滤有效蓝牙特征值

当前代码遍历所有特征值,但只有支持写入的特征值才能传输打印命令。修改_getCharacteristics方法:

Future<List<BluetoothCharacteristic>> _getCharacteristics(BluetoothDevice device) async {
  final services = await device.discoverServices();
  final res = List<BluetoothCharacteristic>.empty(growable: true);
  for (var service in services) {
    // 只保留可写入的特征值
    res.addAll(service.characteristics.where((c) => 
      c.properties.write || c.properties.writeWithoutResponse
    ));
  }
  return res;
}

2. 修复图片解码与QR码生成逻辑

  • 避免强制解包可能为null的图片对象,添加空判断:
final ByteData data = await rootBundle.load('assets/images/qr.png');
final Uint8List bytes = data.buffer.asUint8List();
final img.Image? image = img.decodeImage(bytes);
if(image != null){
  printer.add(gen.image(image));
} else {
  print("图片解码失败,请检查图片格式");
}
  • 直接使用gen.qrcode()生成,添加刷新缓冲区命令:
// 控制QR码大小(3-5为常规范围),添加换行/切纸确保输出
printer.add(gen.qrcode('www.google.com', size: 4));
printer.add(gen.newline());
printer.add(gen.cut());

3. 调整蓝牙写入的Chunk大小

默认512的Chunk可能超过打印机MTU,改为更小的数值:

BluePrint({this.chunkLen = 64}); // 先尝试64,若仍有问题可改为20

或动态获取设备MTU:

Future<void> printData(BluetoothDevice device) async {
  final mtu = await device.mtu;
  final chunkLen = mtu - 3; // 预留蓝牙协议头空间
  final data = _getChunks(chunkLen); // 需修改getChunks方法接收参数
  // ... 后续逻辑
}

4. 添加打印机初始化命令

打印前发送重置命令,确保打印机处于就绪状态:

final gen = Generator(PaperSize.mm58, profile);
printer.add(gen.reset()); // 初始化打印机

5. 调试建议

  • 打印gen.image()或gen.qrcode()返回的List长度,若为0则说明数据生成失败,重点排查图片解码逻辑。
  • 在_tryPrint中打印每个Chunk的内容,确认是否为有效数据而非全0。

内容的提问来源于stack exchange,提问作者pranshul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 08:56:05