lx-printer
v0.5.0
Published
Print library for LX series thermal printers
Maintainers
Readme
lx-printer/lx-d02
A TypeScript library for controlling the LX-D02 thermal printer via the Web Bluetooth API. This library allows you to print images and raw binary data directly from your browser.
Features
- Web Bluetooth GATT Communication: Connects directly to LX-D02 devices.
- Automatic Image Processing:
- Effortlessly resize images to 384px width.
- Convert to grayscale using luminance correction (Y = 0.299R + 0.587G + 0.114B).
- Apply Floyd-Steinberg dithering for high-quality monochrome output.
- Raw Data Printing: Send pre-formatted 384px binary data directly using
Uint8Array. - Status Monitoring: Real-time updates for battery level, voltage, charging state, and error flags (out of paper, overheat, etc.).
- Retransmission Support: Automatically handles retransmission requests from the printer for stable communication.
- Browser Only: Explicit environment checks to prevent accidental use in non-browser environments (Node.js).
- Typed Errors: All library-thrown errors are instances of
LXPrinterErrorwith stablecodeidentifiers for reliable handling. See Error Handling.
Installation
npm install lx-printerUsage
Basic Image Printing
import { LXD02Printer, PrintData } from 'lx-printer/lx-d02';
const printer = new LXD02Printer({
onStatusChange: (status) => {
if (status.isConnected) {
if (status.battery !== undefined) {
console.log(`Battery: ${status.battery}%`);
} else {
console.log('Battery: checking...');
}
} else {
console.log('Printer disconnected');
}
if (status.isOutOfPaper) {
console.error('Out of paper!');
}
},
});
// Request device and connect
await printer.connect();
// Print an HTML image or canvas element (with optional density 1-7)
const img = document.getElementById('my-image') as HTMLImageElement;
const printData = PrintData.fromImage(img, { algorithm: 'dither' });
await printer.print(printData, { density: 7 });
// Disconnect when done
printer.disconnect();Printing Raw Binary Data
If you have pre-dithered 384px wide data (48 bytes per line), you can send it directly.
const rawData = new Uint8Array(48 * 100); // 100 lines
// ... fill data ...
const printData = PrintData.fromRaw(rawData);
await printer.print(printData);API Reference
LXD02Printer
constructor(options?: { onStatusChange?: (status: PrinterStatus) => void })
onStatusChange: Callback triggered when the printer notifies its status.
connect(): Promise<void>
Requests a Bluetooth device matching the LX prefix and establishes a connection. Performs challenge-response authentication automatically.
print(data: PrintData, options?: { density?: number }): Promise<void>
Sends print data to the printer.
datamust be an instance ofPrintData.options.density: Optional density setting from1(lightest) to7(darkest). If provided, it automatically sends a density configuration command before printing. It intelligently skips sending the command if the printer is already set to the desired density.
[!WARNING] The printer does not support concurrent print jobs. If
print()is called while another print job is in progress, it will immediately throw an error (Printer is already printing). You can check the current printing status viaPrinterStatus.isPrinting.
PrintData
Represents print data ready to be sent to the printer.
static fromImage(data: HTMLImageElement | HTMLCanvasElement, options?: ImagePrintOptions): PrintData
Creates print data from an image or canvas element. It is automatically resized to 384px width.
Options (options):
algorithm: The image processing algorithm to use.'dither'(default): Applies Floyd-Steinberg dithering. Use this when you want to print general images or photos beautifully.'threshold': Applies simple black/white thresholding. Use this when you want to print images that have already been optimized for thermal printers (like pixel art or pre-processed line drawings).
threshold: The threshold value (from0to255) used whenalgorithmis'threshold'. Pixels with grayscale luminance below this value become black, while pixels above become white. (default:128). Must be a finite integer.
Throws:
ENV_UNSUPPORTED: If called outside of a browser environment (e.g., SSR or Node.js).INVALID_IMAGE: If the image dimensions are zero or if the canvas 2D context cannot be created.INVALID_ALGORITHM: If an unsupported algorithm name is passed.INVALID_THRESHOLD: If thethresholdvalue is not a finite integer between0and255.
static fromRaw(data: Uint8Array): PrintData
Creates print data from raw 1-bit-per-pixel binary data (must be a multiple of 48 bytes, representing 384px width).
setDensity(density: number): Promise<void>
Directly changes the print density. density must be a number between 1 and 7.
disconnect(): void
Disconnects the current GATT connection.
PrinterStatus
isConnected: boolean (Indicates whether the printer is currently connected via Bluetooth)isPrinting: boolean (Indicates whether a print job is currently in progress)battery?: number (0-100)voltage?: number (mV)isCharging?: booleanisOutOfPaper?: booleanisOverheat?: booleanisLowBattery?: booleandensity?: number (1-7) representing the current hardware density level
Error Handling
All library-defined errors thrown by this library are instances of LXPrinterError, which extends the standard Error class with a stable code property. Browser- or platform-originated errors from underlying APIs may still be propagated unwrapped. Always branch on code rather than matching against message — messages may be reworded between releases, but codes are part of the public API.
import { LXD02Printer, PrintData, LXPrinterError } from 'lx-printer/lx-d02';
const printer = new LXD02Printer();
try {
await printer.connect();
const img = document.getElementById('my-image') as HTMLImageElement;
const printData = PrintData.fromImage(img);
await printer.print(printData, { density: 5 });
} catch (err) {
if (err instanceof LXPrinterError) {
switch (err.code) {
case 'BLUETOOTH_UNSUPPORTED':
alert('This browser does not support Web Bluetooth.');
break;
case 'DISCONNECTED':
// Re-attempt connection
break;
case 'PRINT_TIMEOUT':
case 'DISCOVERY_TIMEOUT':
// Retry with backoff
break;
case 'INVALID_DENSITY':
// Show validation message in UI
break;
default:
console.error(`[${err.code}] ${err.message}`);
}
} else {
// Non-library errors (e.g. user cancelled the device picker)
throw err;
}
}A type guard isLXPrinterError(value) is also exported for callers that prefer not to import the class.
Error Codes
| Category | Code | Thrown when |
| -------------- | ----------------------- | ------------------------------------------------------------------------------- |
| Environment | ENV_UNSUPPORTED | Library is loaded outside a browser (e.g. Node.js). |
| | BLUETOOTH_UNSUPPORTED | The Web Bluetooth API is not available in the current browser. |
| Connection | GATT_CONNECT_FAILED | The GATT server returned no connection. |
| | DISCOVERY_FAILED | The required GATT service or characteristics could not be retrieved. |
| | DISCOVERY_TIMEOUT | GATT service discovery hung past the internal timeout (5 s). |
| | NOT_CONNECTED | An operation was attempted before connect() succeeded. |
| | DISCONNECTED | The printer disconnected while an operation (auth/density/print) was in flight. |
| Authentication | AUTH_FAILED | The printer rejected the challenge-response handshake. |
| | AUTH_TIMEOUT | Authentication did not complete within 10 s. |
| Density | DENSITY_IN_PROGRESS | setDensity() was called while a previous call was still pending. |
| | DENSITY_TIMEOUT | The density-set ACK did not arrive within 5 s. |
| | DENSITY_FAILED | The printer reported failure for the density-set request. |
| | INVALID_DENSITY | The density argument was not an integer in [1, 7]. |
| Printing | ALREADY_PRINTING | print() was called while another print job was running. |
| | PRINT_TIMEOUT | The print job did not complete within 30 s. |
| Validation | INVALID_RAW_DATA | A raw Uint8Array length is not a multiple of 48 bytes. |
| | INVALID_IMAGE | The provided image/canvas has zero dimensions, no 2D context, or invalid width. |
| | INVALID_ALGORITHM | An unknown algorithm is passed to fromImage. |
| | INVALID_THRESHOLD | The threshold parameter is not a finite integer in [0, 255]. |
| | INVALID_AUTH_BYTES | (Internal) Auth challenge bytes are not exactly 10 bytes. |
| | INVALID_MAC_ADDRESS | (Internal) MAC address is not exactly 6 bytes. |
[!NOTE] Errors originating from the browser itself (e.g. the user cancelling the device picker, which throws a
DOMExceptionwith nameNotFoundError, or a transientNetworkErrorfromwriteValueWithoutResponse) are propagated unwrapped. Useinstanceof LXPrinterErrorto distinguish library errors from these.
Tested Devices
Acknowledgments
The protocol implementation in this library is based on the research and reference implementation provided by paradon/lxprint.
License
MIT
