Tom Butcher 7dbe7da4ee
Some checks reported errors
farmcontrol/farmcontrol-server/pipeline/head Something is wrong with the build of this commit
Overhauled server code.
2025-12-28 21:46:58 +00:00

201 lines
6.3 KiB
JavaScript

// 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;
}
}