// receiptinterface.js - Thermal receipt printer interface implementation import log4js from "log4js"; import { loadConfig } from "../../config.js"; import { ThermalPrinter, PrinterTypes } from "node-thermal-printer"; import { convertPDFToImage } from "../../utils.js"; const config = loadConfig(); const logger = log4js.getLogger("Receipt Printer Interface"); logger.level = config.logLevel; export default class ReceiptInterface { constructor(documentPrinterClient) { this.documentPrinterClient = documentPrinterClient; this.host = documentPrinterClient.connection.host; this.name = documentPrinterClient.documentPrinter.name; this.port = documentPrinterClient.connection.port || 9100; this.interface = documentPrinterClient.connection.interface || "epsonReceipt"; this.protocol = documentPrinterClient.connection.protocol; this.isConnected = false; this.receiptPrinter = null; this.retrieveStatusInterval = null; this.images = new Map(); } buildPrinterUrl() { switch (this.protocol) { case "tcp": return `tcp://${this.host}:${this.port}`; case "system": return `printer:${this.host}`; case "serial": return this.host; default: logger.warn(`Unknown protocol ${this.protocol}, defaulting to tcp.`); return `tcp://${this.host}:${this.port}`; } } async connect() { try { // Determine printer type enum let type; switch (this.interface) { case "epsonReceipt": type = PrinterTypes.EPSON; break; case "starReceipt": type = PrinterTypes.STAR; break; default: type = PrinterTypes.EPSON; logger.warn( `Unknown interface ${this.interface}, defaulting to EPSON` ); } // Determine interface based on connection type const interfaceStr = this.buildPrinterUrl(); logger.info( `Connecting to receipt printer ${this.name} (${interfaceStr})` ); // Initialize thermal printer this.receiptPrinter = new ThermalPrinter({ type: type, interface: interfaceStr, options: { timeout: 10000, }, }); // Test connection const isConnected = await this.receiptPrinter.isPrinterConnected(); if (!isConnected) { logger.error("Printer is not connected or not reachable"); return { error: "Printer is not connected or not reachable." }; } this.isConnected = true; logger.info(`Successfully connected to receipt printer ${this.name}`); return true; } catch (error) { logger.error(`Failed to connect to receipt printer ${this.name}:`, error); this.isConnected = false; return { error: "Failed to connect to receipt printer. " + error.message, }; } } async disconnect() { logger.info(`Disconnecting from receipt printer ${this.name}`); this.isConnected = false; this.receiptPrinter = null; return { success: true }; } async initialize() { logger.info(`Initializing receipt printer ${this.name}`); // Thermal printers typically don't need special initialization // but we can test the connection if (this.receiptPrinter) { try { const isConnected = await this.receiptPrinter.isPrinterConnected(); if (!isConnected) { logger.error( `Printer not connected during initialization for receipt printer ${this.name}` ); return { error: "Printer not connected during initialization" }; } logger.info(`Receipt printer ${this.name} initialized successfully`); } catch (error) { logger.error( `Failed to initialize receipt printer ${this.name}:`, error ); } } return true; } async print(jobId) { logger.info(`Printing job ${jobId} to receipt printer ${this.name}`); if (!this.isConnected || !this.receiptPrinter) { throw new Error("Printer is not connected"); } try { // Clear the printer buffer to prevent previous print jobs from being included this.receiptPrinter.clear(); const images = this.images.get(jobId); if (!images || (Array.isArray(images) && images.length === 0)) { throw new Error("Image not found"); } // convertPDFToImage returns an array of buffers, so handle multiple pages const imageArray = Array.isArray(images) ? images : [images]; for (const image of imageArray) { // Ensure the image is a proper Buffer and create a fresh copy // This prevents issues with pngjs reading from consumed streams // pdf-to-img returns PNG buffers, but we create a copy to avoid any stream issues const imageBuffer = Buffer.isBuffer(image) ? Buffer.from(image) // Create a fresh copy : Buffer.from(image); // Convert if needed this.receiptPrinter.printImageBuffer(imageBuffer); this.receiptPrinter.cut(); } // Execute the print job await this.receiptPrinter.execute({ waitForResponse: true }); logger.info( `Successfully printed job ${jobId} to receipt printer ${this.name}` ); return { success: true }; } catch (error) { logger.error( `Failed to print job ${jobId} to receipt printer ${this.name}:`, error ); return { success: false, error: error.message }; } } async deploy(documentJob, pdf) { logger.info( `Deploying job ${documentJob._id} to receipt printer ${this.name}` ); const images = await convertPDFToImage(pdf, { width: 512 }); // Store the array of image buffers this.images.set(documentJob._id, images); return { success: true }; } async retrieveStatus() { logger.debug(`Getting status of receipt printer ${this.name}`); if (this.isOnline == false) { logger.error("Printer is not connected or not reachable"); return { error: "Printer is not connected or not reachable." }; } try { const status = await this.receiptPrinter.raw( Buffer.from([0x10, 0x04, 0x04]) ); logger.info(`Printer status: ${status}`); } catch (error) { logger.error(`Failed to execute printer status:`, error); } logger.debug(`Receipt printer ${this.name} is connected.`); return true; } }