PrintMaster BLE
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:

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:
- Initial state: 0 credits
- Printer sends:
[0x01, N]where N = number of credits (usually 4) - Library sends: One 200-byte chunk, decrements credit
- 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 wrapperlib/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
PrintPortAPI 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
Usage (High-Level API - Recommended)
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.
Related Projects
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).
- NPM Package: marklife-label-printer-web-kit
- GitLab Repository: Marklife Label Printer Web Kit
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