Printing on POS USB printer from dart/flutter

Viewed 1813

On an Android Flutter project I need to print on USB POS printers and for testing I have an Epson TM-T20. Sending raw bytes would be ideal. flutter_usb_write package does not work (write returns false) and its status is a bit concerning (no sound null-safety yet, the homepage is in Croatian and there is no issue tracker).

Are there any other options?

Future<void> printOnPOS(String text) async {
  // text = String.fromCharCodes([27, 64]) + text; // Initialize printer
  text += "\r\n";
  try {
    var flutterUsbWrite = FlutterUsbWrite();
    List<UsbDevice> devices = await flutterUsbWrite.listDevices();
    print("devices: $devices");
    var device = devices[1]; // this picks the printer, checked correct
    var port = await flutterUsbWrite.open(
      vendorId: device.vid,
      productId: device.pid,
    );
    print("port: $port");
    var rw = await flutterUsbWrite.write(Uint8List.fromList(text.codeUnits));
    print("rw: $rw");
    await flutterUsbWrite.close();
  } on PlatformException catch (e) {
    print(e.message);
  }
}

Output:

I/flutter ( 7763): devices: [UsbDevice: e0f-3 VMware Virtual USB Mouse, VMware null, UsbDevice: 4b8-e03 TM-T20, EPSON 405551460005550000]
I/flutter ( 7763): port: UsbDevice: 4b8-e03 TM-T20, EPSON 405551460005550000
I/flutter ( 7763): rw: false
D/UsbDeviceConnectionJNI( 7763): close
2 Answers

after many attempts I found the working solution for printing on POS printers connected to the device (tested on Windows and Android).

First of all, I've used:

  1. https://pub.dev/packages/pdf - for generating pdf's files with receipts;
  2. https://pub.dev/packages/printing - for getting information about available printers and print the receipt on the terminal;

So the logic is next: First of all, you need to fetch information about connected printers, for that you can write something like that:

Future<List<Printer>> findPrinters() async {
   return await Printing.listPrinters();
}

Next, you need a function that will prepare a PDF document, thanks to the developers of the pdf package, because we can use constants for sizing our document (mm, cm, inches, etc), note that 'pw' is 'import 'package:pdf/widgets.dart' as pw;':

 final doc = pw.Document();
 // 58mm width receipt
 const width = 2.28346457 * PdfPageFormat.inch;
 const height = 300.0 * PdfPageFormat.mm;
 const pageFormat = PdfPageFormat(width, height);

Also if you want to make your 'pdf' document take any height, based on its content, you should also use pw.MultiPage(), but lets take a look on the full example:

Future<void> print(Printer? printer) async {
   if (printer == null) return;

   final doc = pw.Document();
   const width = 2.28346457 * PdfPageFormat.inch;
   const height = 300.0 * PdfPageFormat.mm;
   const pageFormat = PdfPageFormat(width, height);

   final textStyle = pw.TextStyle(
      fontSize: 12.0,
      color: PdfColor.fromHex("#000000"),
   );

   doc.addPage(
      pw.MultiPage(
         pageFormat: pageFormat,
         build: (pw.Context context) {
            return [
               pw.Row(
                  children: [
                     pw.Expanded(
                        child: pw.Text(
                           'Testing',
                            style: textStyle,
                        ),
                     ),
                  ],
               ),
            ];
         },
      ),
   );

   final res = await Printing.directPrintPdf(
     printer: printer!,
     // don't use doc.document.save() - it's not working, even with MS pdf printers
     onLayout: (_) => doc.save(),
     format: pageFormat,
     usePrinterSettings: true,
   );

   if (res) {
      Logger().i("Printed!");
   } else {
      Logger().i("Error");
   }
}

I've tested it on rongta rp58, working fine

It turns out that a small change makes flutter_usb_write work.

In FlutterUsbWritePlugin.java we have this in the method write:

      if (this.ep != null && this.mInterface != null && this.m_Connection != null) {
        transferResult = this.m_Connection.bulkTransfer(this.ep, bytes, bytes.length, 0);
      } else {
        if (this.m_Connection.claimInterface(this.mInterface, true)) {
          transferResult = this.m_Connection.bulkTransfer(this.ep, bytes, bytes.length, 0);
        }
      }

The expression in the if evaluates to true and then the call to bulkTransfer fails.

If we force the execution of the else block instead, the bytes are sent to the printer.

My understanding of the documentation of claimInterface is that this method is required before writing to the device.

A fork of the package, migrated to sound null safety, is now here.

Related