Hoy vamos a aprender a imprimir a través de la impresora térmica Bluetooth en Flutter. Para lograr esto, debemos conectarnos a una impresora a través de Bluetooth y enviar datos imprimibles como texto, imagen o código QR. Usaremos estos complementos:
flutter_blue: ^0.8.0
esc_pos_utils: ^1.1.0
Usaremos este complemento para conectarnos al dispositivo Bluetooth y enviar los datos. No es solo para impresoras térmicas, sino que también se puede usar para cualquier dispositivo Bluetooth. Elegí este complemento porque es compatible con iOS, Android y macOS.
https://pub.dev/packages/flutter_blue
Este complemento ayudará a generar comandos ESC/POS para enviarlos a través de flutter_blue. Admite imágenes, códigos de barras, estilo de texto, tablas, corte de papel, alimentación de papel y mucho más.
https://pub.dev/packages/esc_pos_utils
Es posible que necesitemos configuraciones de permisos, así que vaya a la página flutter_blue y realice la configuración de acuerdo con las instrucciones.
Podemos escanear y obtener dispositivos disponibles como este:
FlutterBlue flutterBlue = FlutterBlue.instance; // Start scanning flutterBlue.startScan(timeout: Duration(seconds: 4)); // Listen to scan results var subscription = flutterBlue.scanResults.listen((results) { // do something with scan results for (ScanResult r in results) { print('${r.device.name} found! rssi: ${r.rssi}'); } }); // Stop scanning flutterBlue.stopScan();
Pero vamos a crear un widget donde
import 'package:blue_thermal_printing/blue_print.dart'; import 'package:esc_pos_utils/esc_pos_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_blue/flutter_blue.dart'; FlutterBlue flutterBlue = FlutterBlue.instance; class PrintingWidget extends StatefulWidget { const PrintingWidget({Key? key}) : super(key: key); @override _PrintingWidgetState createState() => _PrintingWidgetState(); } class _PrintingWidgetState extends State<PrintingWidget> { List<ScanResult>? scanResult; @override void initState() { super.initState(); findDevices(); } void findDevices() { flutterBlue.startScan(timeout: const Duration(seconds: 4)); flutterBlue.scanResults.listen((results) { setState(() { scanResult = results; }); }); flutterBlue.stopScan(); } void printWithDevice(BluetoothDevice device) async { await device.connect(); final gen = Generator(PaperSize.mm58, await CapabilityProfile.load()); final printer = BluePrint(); printer.add(gen.qrcode('https://altospos.com')); printer.add(gen.text('Hello')); printer.add(gen.text('World', styles: const PosStyles(bold: true))); printer.add(gen.feed(1)); await printer.printData(device); device.disconnect(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Bluetooth devices')), body: ListView.separated( itemBuilder: (context, index) { return ListTile( title: Text(scanResult![index].device.name), subtitle: Text(scanResult![index].device.id.id), onTap: () => printWithDevice(scanResult![index].device), ); }, separatorBuilder: (context, index) => const Divider(), itemCount: scanResult?.length ?? 0, ), ); } }
Veamos el método printWithDevice
.
Generator
es una clase del complemento esc_pos_utils
. Con la ayuda de esta clase, podemos crear comandos ESC/POS como este: generator.text('Hello')
.BluePrint
(crearemos esta clase más adelante).Ahora veamos la clase BluePrint. En resumen, recopila comandos ESC/POS y envía estos datos a una impresora determinada.
void add(List<int> data)
- Para agregar comandos.List<List<int>> getChunks()
: si necesitamos imprimir datos muy grandes como una imagen, podemos encontrar problemas porque algunos dispositivos fallarán debido al gran tamaño del paquete. Así que tenemos que enviar los datos en trozos.printData(BluetoothDevice device)
: envía datos fragmentados al dispositivo a través de BluetoothCharacteristic.
import 'dart:math'; import 'package:flutter_blue/flutter_blue.dart'; 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 charac, List<List<int>> data, ) async { for (var i = 0; i < data.length; i++) { try { await charac.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; } }
Necesitaba agregar una función de impresora térmica Bluetooth cuando estaba trabajando en el proyecto POS de Alto . Fue mi primera experiencia con Bluetooth con Flutter y, debido al poco material que contenía, tuve que probar muchas cosas antes de que funcionara correctamente en todos los dispositivos. Así que decidí escribir este artículo con la esperanza de que sea útil para otros.
El proyecto GitHub está disponible aquí: https://github.com/usenbekov/blue-thermal-printing