PrintMaster BLE

ko-fi

A robust, brand-agnostic Flutter library for Zhuhai Quin based thermal printers.

Formerly known as marklife_label_printer, this library communicates directly with the firmware protocol used by millions of portable thermal printers sold under different brands.

Brand Models Status
Marklife P50, P50S ✅ Verified
Marklife P11, P12, P15 ⚠️ Compatible (To be verified)
Phomemo D30, D35, Q30, M02 ⚠️ Compatible (To be verified)
Vretti HP3, HP4 ⚠️ Compatible (To be verified)
Niimbot D11, B21 Incompatible (DRM/RFID)

Help Wanted: If you verify this library on "To be verified" models, please email mickeyanthonygudiel@gmail.com so we can update this table!

Features

  • Genuine Protocol implementation: Uses the native 0x1F command set.
  • High-Quality Image Processing:
    • Floyd-Steinberg Dithering: Converts color/grayscale images to 1-bit monochrome using error diffusion for superior print quality.
    • Luminance Conversion: ITU-R BT.601 standard formula (0.299R + 0.587G + 0.114B) for accurate grayscale representation.
    • Error Diffusion: Distributes quantization error to 4 neighboring pixels (7/16 right, 3/16 bottom-left, 5/16 bottom, 1/16 bottom-right).
    • Bit Packing: Optimized bitmap encoding (White=0, Black=1) for thermal printer heat control.
  • Multi-Platform Support:
    • Android: Fully tested and verified
    • macOS: Fully tested and verified
    • ⚠️ Web: Experimental (connects but printing not yet working)
    • 🔄 iOS: Expected to work (not yet tested)
  • Fast: Optimized for BLE packet chunking and flow control (200 bytes/chunk).
  • Smart Compression:
    • ZLIB (RFC 1950) with manual header construction for 1KB window compatibility.
    • Level 0 (Stored) compression to reduce printer CPU load.
    • Adler-32 checksum validation.
  • Smart Discovery: Handles Service UUID obfuscation on macOS and Web.

Example in Action

Here's the example app in action, printing a label:

Example App Printing

Known Issues

  • Long Images: Very tall images (> 500-1000px height) may fail to print or print partially due to printer buffer limitations.
    • Recommendation: Split long content into multiple smaller print jobs or wait for the upcoming "Image Slicing" feature.

BLE Protocol Reference

This library uses standard Bluetooth Low Energy (BLE) to communicate with Zhuhai Quin thermal printers. Understanding the protocol can help with troubleshooting and implementing custom integrations.

Service & Characteristic UUIDs

The library provides PrinterBleProtocol class with documented constants for all UUIDs:

import 'package:print_master_ble/print_master_ble.dart';

// Use helper methods for UUID matching
if (PrinterBleProtocol.isServiceUuid(uuid)) {
  // This is a printer service
}

// Or use constants directly
final serviceId = PrinterBleProtocol.serviceUuid;

Primary Service UUID: 0x FF00

Full UUID: 0000ff00-0000-1000-8000-00805f9b34fb

This is the main BLE service used by Marklife, Phomemo, and Vretti printers. The library automatically discovers this service during connection.

Alternative UUIDs (Platform-specific obfuscation):

  • macOS/iOS: e7810a71-73ae-499d-8c15-faa9aef0c3f2
  • Some variants: 49535343-fe7d-4ae5-8fa9-9fafd205e455

Write Characteristic: 0x FF02

Full UUID: 0000ff02-0000-1000-8000-00805f9b34fb

Used to send commands and image data to the printer. Supports both:

  • WriteWithResponse (default, more reliable)
  • WriteWithoutResponse (faster, used on Web)

Flow Control Characteristic: 0x FF03

Full UUID: 0000ff03-0000-1000-8000-00805f9b34fb

Sends notifications to manage data transmission flow using Protocol 0x01 (credit-based flow control). This characteristic is optional - if not available, the library uses a fallback mode with simulated credits.

Protocol Identifiers

Protocol Hex Description
Flow Control 0x01 Credit-based transmission control
Format: [0x01, Credits, ...]
Battery Status 0x02 Battery level information
Format: [0x02, RawValue (0-255), ...]
Image Data 0x1F Compressed image payload
Contains ZLIB-compressed bitmap data

Flow Control Mechanism

The printer implements credit-based flow control to prevent buffer overflow:

  1. Initial state: 0 credits
  2. Printer sends: [0x01, N] where N = number of credits (usually 4)
  3. Library sends: One 200-byte chunk, decrements credit
  4. Repeat: When credits reach 0, library waits for more credits

Fallback mode: If flow control isn't available, library uses 1000 simulated credits with adaptive delays.

Implementation Tips

When implementing PrinterDevice for a new BLE library:

// ✅ Good - Use protocol constants
import 'package:print_master_ble/print_master_ble.dart';

if (PrinterBleProtocol.isServiceUuid(service.uuid)) {
  // Found printer service
}

// ❌ Avoid - Hardcoded strings
if (uuid.contains('ff00')) { ... }

For complete implementation examples, see:

  • example/lib/logic/flutter_blue_device.dart - flutter_blue_plus wrapper
  • lib/src/core/ble_protocol.dart - Full protocol documentation

Library Architecture

The library is organized into clearly separated modules:

lib/src/
├── core/                       # Core components & interfaces
│   ├── ble_protocol.dart       # BLE UUIDs and protocol constants
│   ├── printer_device.dart     # PrinterDevice interface
│   └── print_master_log.dart   # Logging utility
├── commands/                   # Printer command implementations
│   └── printer_commands.dart   # Low-level printer commands
├── utils/                      # Helper functions
│   └── image_processing.dart   # Dithering and bitmap encoding
├── print_master_handler.dart   # High-level API (recommended)
└── print_port.dart             # Low-level API (advanced)

Design Principles:

  • BLE Agnostic: Zero BLE dependencies in core library
  • Single Export Point: All public APIs via print_master_ble.dart
  • Clear Separation: Core, commands, and utils are distinct
  • Backward Compatible: Existing PrintPort API unchanged

For detailed architecture documentation, see ARCHITECTURE.md.

Installation

Add this to your pubspec.yaml:

dependencies:
  print_master_ble: ^0.1.0  # Check pub.dev for latest version
  flutter_blue_plus: ^2.0.2
  image: ^4.6.0

The library provides a high-level API that manages BLE communication, flow control, and progress tracking automatically.

1. Implement PrinterDevice for Your BLE Library

Create a wrapper for your BLE library. Here's an example using flutter_blue_plus:

import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:print_master_ble/print_master_ble.dart';

// 1. Create a wrapper for your BLE library (e.g. flutter_blue_plus)
class FlutterBluePrinterDevice implements PrinterDevice {
  final BluetoothDevice _device;
  BluetoothCharacteristic? _writeChar;
  BluetoothCharacteristic? _flowControlChar;

  FlutterBluePrinterDevice(this._device);

  /// Call this after connecting
  Future<void> initialize() async {
    final services = await _device.discoverServices();
    
    // Find printer service (UUID: 0xFF00 or alternatives)
    // You can use PrinterBleProtocol.isServiceUuid(uuid) for robust matching
    final printerService = services.firstWhere(
      (s) => PrinterBleProtocol.isServiceUuid(s.uuid.toString()),
    );

    // Find characteristics
    for (final c in printerService.characteristics) {
      final uuid = c.uuid.toString();
      if (PrinterBleProtocol.isWriteCharacteristic(uuid)) {
        _writeChar = c; 
      } else if (PrinterBleProtocol.isFlowControlCharacteristic(uuid)) {
        _flowControlChar = c; 
        await c.setNotifyValue(true);
      }
    }
  }

  @override
  String get deviceName => _device.platformName;

  @override
  bool get isConnected => _device.isConnected;

  @override
  BleWriteCallback get write => (data, withoutResponse) async {
    await _writeChar!.write(data, withoutResponse: withoutResponse);
    return true;
  };

  @override
  BleNotifyCallback? get notify {
    if (_flowControlChar == null) return null;
    return () => _flowControlChar!.lastValueStream;
  }

  @override
  Future<void> disconnect() => _device.disconnect();
}

2. Print with PrintMasterHandler

import 'package:image/image.dart' as img;
import 'package:print_master_ble/print_master_ble.dart';

// 1. Connect to your printer (using your BLE library)
await bleDevice.connect();

// 2. Create and initialize printer device wrapper
final device = FlutterBluePrinterDevice(bleDevice);
await device.initialize();

// 3. Create handler
final handler = PrintMasterHandler(device);

// 4. Listen to progress and status
handler.progress.listen((p) => print('Progress: ${(p * 100).toInt()}%'));
handler.status.listen((s) => print('Status: $s'));

// 5. Print your image - that's it!
await handler.printImage(
  myImage,  // img.Image
  printerWidth: 384,
  alignment: PrintAlignment.center,
);

// 6. Clean up when done
handler.dispose();

Benefits

Simple - No BLE protocol knowledge required
BLE-Agnostic - Works with any BLE library
Automatic - Flow control, chunking, and retries handled for you
Observable - Progress and status streams for UI updates


Usage (Low-Level API - Advanced)

For advanced users who need full control over the printing process.

1. Device Discovery & Connection

import 'package:flutter_blue_plus/flutter_blue_plus.dart';

// Start scanning for devices
await FlutterBluePlus.startScan(timeout: Duration(seconds: 4));

// Listen to scan results
FlutterBluePlus.scanResults.listen((results) {
  for (ScanResult r in results) {
      // Filter by UUID (More robust than name)
      if (PrinterBleProtocol.isServiceUuid(r.advertisementData.serviceUuids.firstOrNull ?? '')) {
         print('Found Printer: ${r.device.platformName}');
      }
  }
});

// Stop scanning
await FlutterBluePlus.stopScan();

2. Connect and Send Data

You'll need to implement a Bluetooth handler to manage the connection and data transmission. See the complete example in example/lib/logic/bluetooth_handler.dart which includes:

  • Service and characteristic discovery
  • Flow control management (Protocol 0x01)
  • Data chunking (200 bytes)
  • Progress tracking
import 'package:flutter_blue_plus/flutter_blue_plus.dart';

// Connect to device
await device.connect(license: License.free);

// Discover services
List<BluetoothService> services = await device.discoverServices();

// Find printer service (UUID: 0xFF00)
// Use helper for robust matching across platforms (macOS/Android)
BluetoothService? printerService = services.firstWhere(
  (s) => PrinterBleProtocol.isServiceUuid(s.uuid.toString()),
);

// Find write characteristic (UUID: 0xFF02)
BluetoothCharacteristic? writeChar = printerService.characteristics.firstWhere(
  (c) => PrinterBleProtocol.isWriteCharacteristic(c.uuid.toString()),
);

3. Print an Image

import 'dart:typed_data';
import 'package:image/image.dart' as img;
import 'package:print_master_ble/print_master_ble.dart';

// Load and decode your image
Uint8List imageBytes = ...; // From file, network, etc.
img.Image? image = img.decodeImage(imageBytes);

if (image != null) {
  // Generate complete print payload (includes dithering + compression)
  Uint8List payload = await PrintPort.generatePrintPayload(
    image,
    printerWidth: 384,  // P50S width
    alignment: PrintAlignment.center,
  );
  
  // Send to printer via your Bluetooth handler
  // (See example/lib/logic/bluetooth_handler.dart for complete implementation)
  await bluetoothHandler.sendDataSerial(payload);
}

4. Two-Phase Printing (Advanced)

For better control, you can split the print job into header+image and footer:

// Phase 1: Send header + image data
Uint8List headerAndImage = await PrintPort.generatePrintHeaderAndImage(
  image,
  printerWidth: 384,
  alignment: PrintAlignment.center,
);
await bluetoothHandler.sendDataSerial(headerAndImage);

// Phase 2: Wait for printer, then send footer (stop + feed)
await Future.delayed(Duration(seconds: 2));
Uint8List footer = PrintPort.generatePrintFooter();
await bluetoothHandler.sendDataSerial(footer);

5. Complete Example

For a full working example with UI, see the example/ directory. It includes:

  • Bluetooth device scanning and filtering
  • Connection management with flow control
  • Image picker integration
  • Real-time print progress tracking
  • Terminal-style logging UI

Run the example:

cd example
fvm flutter run

Compatibility Note

This library is designed for printers that communicate using the Print Master or Phomemo app protocols. If your printer requires the "Niimbot" app, this library will NOT work due to RFID encryption.

Web & Node.js Version

This library is based on the research and implementation from marklife-label-printer-web-kit, a JavaScript library for Node.js and the Browser (Web Bluetooth).

Note: The JavaScript version currently supports printing very long images (slicing) which is a planned feature for this Flutter library. If you need to print long labels from a web environment today, check out the JS version.

Author

MickeyGR - mickeygr.atokatl.dev

License

MIT

Libraries