@seiton/printer
Version:
Driver para las impresoras Seiton, incluye todas las funciones basicas para poder imprimir tickets
769 lines (768 loc) • 35.1 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Seiton = void 0;
const fs = __importStar(require("fs"));
const net = __importStar(require("net"));
const sharp_1 = __importDefault(require("sharp"));
const usb_1 = require("usb");
const commands_enum_1 = require("./enum/commands.enum");
const image_alignment_enum_1 = require("./enum/image-alignment.enum");
class Seiton {
config;
ticket = Buffer.alloc(0);
lastOperation = Promise.resolve();
/**
* Inicializa una nueva instancia de la clase Seiton para control de impresoras termicas.
*
* @param config - Configuración para la conexion con la impresora
* @throws {Error} Si la configuracion es invalida o incompleta
*
* @example
* // Configuracion para conexión USB en Windows
* const seiton = new Seiton({
* usb: {
* windows: {
* vendorId: SEITON_VID.V2,
* productId: SEITON_PID.V2
* }
* }
* });
*
* @example
* // Configuracion para conexion USB en Linux
* const seiton = new Seiton({
* usb: {
* linux: {
* devicePath: '/dev/usb/lp0'
* }
* }
* });
*
* @example
* // Configuracion para conexion Ethernet
* const seiton = new Seiton({
* ethernet: {
* ip: '192.168.1.100',
* port: 9100
* }
* });
*
*/
constructor(config) {
this.config = config;
this.validarConfiguracion();
this.setDefaultStyles();
}
validarConfiguracion() {
// Validamos que exista la configuración
if (!this.config) {
throw new Error('La configuración es requerida para instanciar la clase Seiton');
}
// Validamos que se proporcione al menos una configuración (USB o Ethernet)
if (!this.config.usb && !this.config.ethernet) {
throw new Error('Debe proporcionar la configuración para USB o Ethernet');
}
// Si hay configuración USB, validamos sus propiedades
if (this.config.usb) {
// Validamos que solo se configure un sistema operativo
if (this.config.usb.windows && this.config.usb.linux) {
throw new Error('Solo puede configurar un sistema operativo a la vez (Windows o Linux)');
}
// Validamos que se configure al menos un sistema operativo
if (!this.config.usb.windows && !this.config.usb.linux) {
throw new Error('Debe configurar al menos un sistema operativo para la conexión USB');
}
// Validaciones específicas para Windows
if (this.config.usb.windows) {
if (typeof this.config.usb.windows !== 'object') {
throw new Error('La configuración de Windows debe ser un objeto');
}
if (!this.config.usb.windows.vendorId) {
throw new Error('Para la configuración de Windows, el atributo vendorId es requerido');
}
if (!this.config.usb.windows.productId) {
throw new Error('Para la configuración de Windows, el atributo productId es requerido');
}
// Validamos que el vendorId y productId sean números válidos en el rango de USB
if (typeof this.config.usb.windows.vendorId !== 'number' || this.config.usb.windows.vendorId < 0x0000 || this.config.usb.windows.vendorId > 0xffff) {
throw new Error('El vendorId debe ser un número hexadecimal válido entre 0x0000 y 0xFFFF');
}
if (typeof this.config.usb.windows.productId !== 'number' || this.config.usb.windows.productId < 0x0000 || this.config.usb.windows.productId > 0xffff) {
throw new Error('El productId debe ser un número hexadecimal válido entre 0x0000 y 0xFFFF');
}
}
// Validaciones específicas para Linux
if (this.config.usb.linux) {
if (typeof this.config.usb.linux !== 'object') {
throw new Error('La configuración de Linux debe ser un objeto');
}
if (!this.config.usb.linux.devicePath) {
throw new Error('Para la configuración de Linux, el atributo devicePath es requerido');
}
if (typeof this.config.usb.linux.devicePath !== 'string') {
throw new Error('El atributo devicePath debe ser una cadena de texto');
}
if (this.config.usb.linux.devicePath.trim() === '') {
throw new Error('El atributo devicePath no puede estar vacío');
}
}
}
// Si hay configuración Ethernet, validamos sus propiedades
if (this.config.ethernet) {
if (typeof this.config.ethernet !== 'object') {
throw new Error('La configuración de Ethernet debe ser un objeto');
}
if (!this.config.ethernet.ip) {
throw new Error('Para la configuración de Ethernet, el atributo ip es requerido');
}
if (typeof this.config.ethernet.ip !== 'string' || this.config.ethernet.ip.trim() === '') {
throw new Error('La dirección IP debe ser una cadena de texto válida');
}
if (!this.config.ethernet.port) {
throw new Error('Para la configuración de Ethernet, el atributo port es requerido');
}
if (typeof this.config.ethernet.port !== 'number' || this.config.ethernet.port < 1 || this.config.ethernet.port > 65535) {
throw new Error('El puerto debe ser un número entre 1 y 65535');
}
}
}
/**
* Agrega un texto al ticket actual, agregando automaticamente el salto de linea al final.
*
* @param text - El texto a agregar al ticket.
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
addText(text) {
return this.chain(() => {
const mappedText = this.mapSpecialCharacters(text);
const textBuffer = Buffer.from(`${mappedText}\n`, 'binary');
this.ticket = Buffer.concat([this.ticket, textBuffer]);
});
}
/**
* Restablece los estilos predeterminados de la impresora a los valores por defecto.
*
* Cualquier texto/imagen/codigo de barras/codigo QR agregado despues de esta llamada se verá afectado por los estilos predeterminados.
*
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
setDefaultStyles() {
return this.chain(() => {
const setDefaultStylesCommand = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.INICIALIZAR_IMPRESORA, 'binary');
this.ticket = Buffer.concat([this.ticket, setDefaultStylesCommand]);
});
}
/**
* Alinea todo el contenido a ser insertado **despues** de la llamada a esta funcion a la derecha.
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
alignToRight() {
return this.chain(() => {
const alignRightCommand = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.ALINEACION_DERECHA, 'binary');
this.ticket = Buffer.concat([this.ticket, alignRightCommand]);
});
}
/**
* Centra todo el contenido a ser insertado **despues** de la llamada a esta funcion.
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
alignToCenter() {
return this.chain(() => {
const alignCenterCommand = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.ALINEACION_CENTRADA, 'binary');
this.ticket = Buffer.concat([this.ticket, alignCenterCommand]);
});
}
/**
* Avanza el papel una línea.
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
feedLine() {
return this.chain(() => {
const lineFeedCommand = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.AVANCE_DE_LINEA, 'binary');
this.ticket = Buffer.concat([this.ticket, lineFeedCommand]);
});
}
/**
* Avanza el papel un número específico de líneas.
* @param {number} lines Número de líneas a avanzar.
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
feedLines(lines) {
return this.chain(() => {
const feedLinesCommand = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.AVANCE_DE_LINEAS + String.fromCharCode(lines));
this.ticket = Buffer.concat([this.ticket, feedLinesCommand]);
});
}
/**
* Activa la cuchilla de la impresora para cortar el papel.
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
cutPaper() {
return this.chain(() => {
const cutCommand = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.CORTAR_PAPEL, 'binary');
this.ticket = Buffer.concat([this.ticket, cutCommand]);
});
}
/**
* Avanza el papel una linea y luego lo corta.
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
feedAndCutPaper() {
return this.chain(() => {
const feedAndCutPaperCommand = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.AVANCE_Y_CORTE_PAPEL, 'binary');
this.ticket = Buffer.concat([this.ticket, feedAndCutPaperCommand]);
});
}
/**
* Aplica el formato de texto doble alto y doble ancho.
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
doubleTallDoubleWide() {
return this.chain(() => {
const doubleTallDoubleWideCommand = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.TAMANO_DOBLE_ANCHO_Y_DOBLE_ALTURA, 'binary');
this.ticket = Buffer.concat([this.ticket, doubleTallDoubleWideCommand]);
});
}
/**
* Aplica el formato de texto doble alto.
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
doubleTall() {
return this.chain(() => {
const doubleTallCommand = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.TAMANO_DOBLE_ALTURA, 'binary');
this.ticket = Buffer.concat([this.ticket, doubleTallCommand]);
});
}
/**
* Aplica el formato de texto doble ancho.
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
doubleWide() {
return this.chain(() => {
const doubleWideCommand = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.TAMANO_DOBLE_ANCHO, 'binary');
this.ticket = Buffer.concat([this.ticket, doubleWideCommand]);
});
}
/**
* Aplica el formato de texto doble alto y negrita.
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
doubleTallBold() {
return this.chain(() => {
const doubleTallBoldCommand = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.DOBLE_ALTO_NEGRITA, 'binary');
this.ticket = Buffer.concat([this.ticket, doubleTallBoldCommand]);
});
}
/**
* Activa el buzzer de la impresora y hace que emita un número especificado de pitidos
* con una duración determinada para cada uno.
*
* @param {number} beepCount Número de pitidos a emitir (1-8).
* @param {number} beepMillis Duración de cada pitido (1-10).
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
posBeep(beepCount, beepMillis) {
return this.chain(() => {
if (beepCount < 1 || beepCount > 8) {
this.inicializarBuffer();
throw new Error('La cantidad de pitidos debe estar entre 1 y 8.');
}
if (beepMillis < 1 || beepMillis > 10) {
this.inicializarBuffer();
throw new Error('La duración del pitido debe estar entre 1 y 10.');
}
const beepCommand = Buffer.from([...Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.POS_BEEP), beepCount, beepMillis]);
this.ticket = Buffer.concat([this.ticket, beepCommand]);
});
}
/**
* Genera un código QR de tamaño y contenido configurable.
*
* @param {string} content - El contenido que se almacenará en el código QR.
* @param {number} size - Tamaño del módulo del QR (valor entre 1 y 16).
* @throws {Error} Si el tamaño está fuera del rango permitido (1-16).
* @returns {Seiton} La instancia actual de Seiton para continuar armando el ticket
*
* @example
* // Generar un código QR de tamaño 4 con el contenido "Hola Mundo"
* addQRCode("Hola Mundo", 4);
*/
addQRCode(content, size) {
return this.chain(() => {
if (size < 1 || size > 16) {
this.inicializarBuffer();
throw new Error('El tamaño del codigo QR debe estar entre 1 y 16.');
}
const sizeCommand = Buffer.from([
...Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.QR_INICIO, 'ascii'),
...Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.QR_PARAM_LONGITUD_MODULO, 'ascii'),
...Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.QR_CONFIG_MODULO, 'ascii'),
size,
]);
const storeCommand = Buffer.concat([
Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.QR_INICIO, 'ascii'),
// Calcula la longitud total del contenido (datos QR) más los 3 bytes adicionales requeridos por el comando QR.
// La longitud se divide en dos bytes: el primer byte contiene los 8 bits menos significativos (& 0xff),
// y el segundo byte contiene los 8 bits más significativos (>> 8) para representar el valor como un entero de 16 bits.
Buffer.from([(content.length + 3) & 0xff, ((content.length + 3) >> 8) & 0xff]),
Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.QR_ALMACENAR_DATOS, 'ascii'),
Buffer.from(content, 'utf8'),
]);
const printCommand = Buffer.from([
...Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.QR_INICIO, 'ascii'),
...Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.QR_PARAM_LONGITUD_MODULO, 'ascii'),
...Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.QR_IMPRIMIR, 'ascii'),
]);
const qrCommand = Buffer.concat([sizeCommand, storeCommand, printCommand]);
this.ticket = Buffer.concat([this.ticket, qrCommand]);
});
}
/**
* Genera un código de barras CODE128.
* Permite ajustar el tamaño del código de barras (ancho y altura), así como el ancho de las barras.
* @param barcodeData - El contenido del código de barras en formato ASCII.
* @param width - El ancho del código de barras (1 a 6).
* @param height - La altura del código de barras (1 a 255).
* @returns La instancia actual de Seiton para continuar armando el ticket.
*/
addBarcode(barcodeData, width = 3, height = 100) {
return this.chain(() => {
if (!barcodeData) {
this.inicializarBuffer();
throw new Error('El contenido del código de barras no puede estar vacío.');
}
if (width < 1 || width > 6) {
this.inicializarBuffer();
throw new Error('El ancho del código de barras debe estar entre 1 y 6.');
}
if (height < 1 || height > 255) {
this.inicializarBuffer();
throw new Error('La altura del código de barras debe estar entre 1 y 255.');
}
// Configurar ancho
this.ticket = Buffer.concat([this.ticket, Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.ANCHO_CODIGO_BARRAS), Buffer.from([width])]);
// Configurar altura
this.ticket = Buffer.concat([this.ticket, Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.ALTO_CODIGO_BARRAS), Buffer.from([height])]);
// Configurar fuente HRI
this.ticket = Buffer.concat([this.ticket, Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.FUENTE_TEXTO_BARCODE)]);
// Imprimir código de barras CODE39
const barcodeBytes = Buffer.from(barcodeData.toUpperCase(), 'ascii');
this.ticket = Buffer.concat([
this.ticket,
Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.INICIA_CODIGO_BARRAS_CODE39),
barcodeBytes,
Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.FIN_CODIGO_BARRAS),
]);
});
}
/**
* Método para imprimir la página de autodiagnóstico de la impresora.
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
printSelfTest() {
return this.chain(() => {
const selfTestCommand = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.PRINT_SELF_TEST, 'binary');
this.ticket = Buffer.concat([this.ticket, selfTestCommand]);
});
}
/**
* Agrega una imagen al ticket. La imagen puede ser redimensionada según los parámetros especificados.
* @param imagePath - Ruta al archivo de imagen
* @param width - Ancho deseado de la imagen (opcional, por defecto usa el ancho de la impresora 384)
* @param height - Alto deseado de la imagen (opcional, mantiene la proporción si no se especifica)
* @param alignment - Alineación de la imagen (opcional, por defecto es CENTER = 2, LEFT = 0, RIGHT = 1)
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
addImage(imagePath, width, height, alignment = image_alignment_enum_1.IMAGE_ALIGNMENT.CENTER) {
return this.chain(async () => {
try {
const printerWidth = 384; // Ancho común en impresoras térmicas
const resizeWidth = width || printerWidth;
const resizeHeight = height || null; // null mantiene el aspect ratio
const buffer = await (0, sharp_1.default)(imagePath).resize(resizeWidth, resizeHeight).grayscale().png().toBuffer();
const { PNG } = require('pngjs');
const png = PNG.sync.read(buffer);
const printerBuffer = this.printImageBuffer(png.width, png.height, png.data);
const alignmentCommand = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.ALINEACION + String.fromCharCode(alignment));
this.ticket = Buffer.concat([
this.ticket,
Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.INICIALIZAR_IMPRESORA, 'binary'),
alignmentCommand,
printerBuffer,
Buffer.from('\n', 'binary'),
]);
}
catch (error) {
console.error('Error detallado:', error);
this.inicializarBuffer();
throw new Error(`Error al procesar la imagen: ${error}`);
}
});
}
/**
* Mapea los caracteres especiales a sus códigos de impresora
* @param text Texto a mapear
* @returns texto con caracteres especiales mapeados
*/
mapSpecialCharacters(text) {
const specialChars = {
á: '\xA0',
é: '\x82',
í: '\xA1',
ó: '\xA2',
ú: '\xA3',
ñ: '\xA4',
Ñ: '\xA5',
};
return text
.split('')
.map((char) => specialChars[char] || char)
.join('');
}
/**
* Abre el cajón de dinero conectado a la impresora
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
openCashDrawer() {
return this.chain(() => {
this.ticket = Buffer.concat([this.ticket, Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.ABRIR_CAJON)]);
});
}
/**
* Aplica el formato de texto en negrita.
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
enableBold() {
return this.chain(() => {
const boldCommand = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.HABILITAR_NEGRITA, 'binary');
this.ticket = Buffer.concat([this.ticket, boldCommand]);
});
}
/**
* Desactiva el formato de texto en negrita.
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
disableBold() {
return this.chain(() => {
const boldOffCommand = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.DESHABILITAR_NEGRITA, 'binary');
this.ticket = Buffer.concat([this.ticket, boldOffCommand]);
});
}
/**
* Aplica el formato de texto subrayado.
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
enableUnderline() {
return this.chain(() => {
const underlineCommand = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.HABILITAR_SUBRAYADO, 'binary');
this.ticket = Buffer.concat([this.ticket, underlineCommand]);
});
}
/**
* Desactiva el formato de texto subrayado.
* @returns La instancia actual de Seiton para continuar armando el ticket
*/
disableUnderline() {
return this.chain(() => {
const underlineOffCommand = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.DESHABILITAR_SUBRAYADO, 'binary');
this.ticket = Buffer.concat([this.ticket, underlineOffCommand]);
});
}
printImageBuffer(width, height, data) {
let outputBuffer = Buffer.from([0x1d, 0x76, 0x30, 48]);
// Get pixel rgba in 2D array
const pixels = [];
for (let i = 0; i < height; i++) {
const line = [];
for (let j = 0; j < width; j++) {
const idx = (width * i + j) << 2;
line.push({
r: data[idx],
g: data[idx + 1],
b: data[idx + 2],
a: data[idx + 3],
});
}
pixels.push(line);
}
const imageBufferArray = [];
for (let i = 0; i < height; i++) {
for (let j = 0; j < Math.ceil(width / 8); j++) {
let byte = 0x0;
for (let k = 0; k < 8; k++) {
let pixel = pixels[i][j * 8 + k];
// Image overflow
if (pixel === undefined) {
pixel = {
a: 0,
r: 0,
g: 0,
b: 0,
};
}
if (pixel.a > 126) {
// checking transparency
const grayscale = Number(0.2126 * pixel.r + 0.7152 * pixel.g + 0.0722 * pixel.b);
if (grayscale < 128) {
// checking color
const mask = 1 << (7 - k); // setting bitwise mask
byte |= mask; // setting the correct bit to 1
}
}
}
imageBufferArray.push(byte);
}
}
const imageBuffer = Buffer.from(imageBufferArray);
// Check if width/8 is decimal
if (width % 8 != 0) {
width += 8;
}
outputBuffer = Buffer.concat([outputBuffer, Buffer.from([(width >> 3) & 0xff])]);
outputBuffer = Buffer.concat([outputBuffer, Buffer.from([0x00])]);
outputBuffer = Buffer.concat([outputBuffer, Buffer.from([height & 0xff])]);
outputBuffer = Buffer.concat([outputBuffer, Buffer.from([(height >> 8) & 0xff])]);
// append data
outputBuffer = Buffer.concat([outputBuffer, imageBuffer]);
return outputBuffer;
}
/**
* Verifica si la impresora está conectada por USB o Red en base a la configuracion de la clase Seiton.
* @returns {boolean} true si la impresora está conectada, false si no lo está.
*/
async isPrinterConnected() {
try {
if (this.config.usb) {
this.ticket = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.STATUS_REQUEST, 'binary');
this.enviarComandosAImpresora();
return true;
}
if (this.config.ethernet) {
return this.isHostConnected(this.config.ethernet.ip, this.config.ethernet.port);
}
return false;
}
catch (error) {
return false;
}
}
/**
* Configura la impresora para obtener una dirección IP dinámica mediante DHCP.
* @returns {Seiton} La instancia actual de Seiton para permitir el encadenamiento de métodos.
*/
setDHCP() {
return this.chain(() => {
const command = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.CONFIGURAR_IP_DINAMICA, 'binary');
this.ticket = Buffer.concat([this.ticket, command]);
});
}
/**
* Configura la impresora con una dirección IP estática.
* Este método envía el comando necesario para establecer una IP fija, máscara de subred,
* puerta de enlace y servidores DNS en la impresora.
*
* @param {string} ip - La dirección IP estática a asignar.
* @param {string} mask - La máscara de subred asociada a la IP.
* @param {string} gateway - La dirección de la puerta de enlace.
* @param {string} dns1 - La dirección del primer servidor DNS.
* @param {string} dns2 - La dirección del segundo servidor DNS.
* @returns {Seiton} La instancia actual de Seiton para permitir el encadenamiento de métodos.
* @throws {Error} Si alguna de las direcciones no tiene 4 octetos.
*/
setStaticIP(ip, mask, gateway, dns1, dns2) {
return this.chain(() => {
// Parsear y validar las direcciones IP
const ipParts = this.parseAndValidateIP(ip);
const maskParts = this.parseAndValidateIP(mask);
const gatewayParts = this.parseAndValidateIP(gateway);
const dns1Parts = this.parseAndValidateIP(dns1);
const dns2Parts = this.parseAndValidateIP(dns2);
const header = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.CABECERA_IP_FIJA, 'binary');
const config = Buffer.from(commands_enum_1.PRINTER_COMMANDS_ESC_POS.CONFIGURAR_IP_FIJA, 'binary');
const allParts = [...ipParts, ...maskParts, ...gatewayParts, ...dns1Parts, ...dns2Parts];
const checksum = this.calculateChecksum([...config, ...allParts]);
const command = Buffer.concat([header, config, Buffer.from(allParts), Buffer.from([checksum])]);
this.ticket = Buffer.concat([this.ticket, command]);
});
}
parseAndValidateIP(ip) {
const parts = ip.split('.').map((part) => parseInt(part));
if (parts.length !== 4) {
throw new Error('Todas las direcciones deben tener 4 octetos');
}
return parts;
}
calculateChecksum(parts) {
return parts.reduce((xor, part) => xor ^ part, 0x1f ^ 0x49);
}
/**
* Imprime el ticket en el puerto USB especificado en el constructor de la clase.
* @throws Lanzará un error si el ticket no se puede imprimir.
*/
async print() {
try {
// Esperamos a que se complete la última operación
await this.lastOperation;
// Enviamos los comandos a la impresora
this.enviarComandosAImpresora();
}
catch (error) {
console.error(`Ocurrió un error al intentar imprimir el ticket: ${error}`);
this.inicializarBuffer();
throw error;
}
}
chain(operation) {
this.lastOperation = this.lastOperation.then(async () => {
await operation();
});
return this;
}
inicializarBuffer() {
this.ticket = Buffer.alloc(0);
this.lastOperation = Promise.resolve();
}
enviarComandosAImpresora() {
if (this.config.usb) {
if (this.config.usb.linux) {
try {
fs.writeFileSync(this.config.usb.linux.devicePath, this.ticket, { encoding: 'binary' });
this.inicializarBuffer();
return;
}
catch (error) {
console.error('No se pudo encontrar la impresora en Linux');
this.inicializarBuffer();
throw error;
}
}
if (this.config.usb.windows) {
try {
let outEndpoint;
let counter = 0;
const device = (0, usb_1.findByIds)(this.config.usb.windows.vendorId, this.config.usb.windows.productId);
if (!device) {
this.inicializarBuffer();
throw new Error(`No se encontró la impresora con Vendor ID ${this.config.usb.windows.vendorId} y Product ID ${this.config.usb.windows.productId} conectada por USB`);
}
device.open();
if (!device.interfaces) {
this.inicializarBuffer();
throw new Error(`No se encontraron interfaces en la impresora con Vendor ID ${this.config.usb.windows.vendorId} y Product ID ${this.config.usb.windows.productId} conectada por USB`);
}
for (const usbInterface of device.interfaces) {
try {
usbInterface.claim();
outEndpoint = usbInterface.endpoints.find((endpoint) => endpoint.direction === 'out');
if (++counter === device.interfaces.length && !outEndpoint) {
this.inicializarBuffer();
throw new Error(`No se encontro el endpoint para imprimir en la impresora con Vendor ID ${this.config.usb.windows.vendorId} y Product ID ${this.config.usb.windows.productId}`);
}
}
catch (error) {
this.inicializarBuffer();
throw new Error(`No se pudo obtener acceso a la impresora con Vendor ID ${this.config.usb.windows.vendorId} y Product ID ${this.config.usb.windows.productId}, puede que este ocupada por otro proceso`);
}
}
outEndpoint.transfer(this.ticket);
this.inicializarBuffer();
}
catch (error) {
console.error(`No se pudo encontrar la impresora en Windows con Vendor ID ${this.config.usb.windows.vendorId} y Product ID ${this.config.usb.windows.productId}`);
this.inicializarBuffer();
throw error;
}
return;
}
}
if (this.config.ethernet) {
const impresora = new net.Socket();
const desconectarImpresora = () => {
try {
impresora.destroy();
}
catch (err) {
// Se ignora cualquier error en caso de que la conexion ya este cerrada.
}
};
impresora.on('error', (error) => {
desconectarImpresora();
console.error(`Error de conexión con la impresora: ${error.message}`);
});
impresora.on('timeout', () => {
desconectarImpresora();
console.error('Tiempo de espera agotado al conectar con la impresora');
});
impresora.on('connect', () => {
try {
impresora.write(this.ticket);
this.inicializarBuffer();
impresora.end();
}
catch (error) {
desconectarImpresora();
console.error(`Error al enviar comando de impresion a la impresora: ${JSON.stringify(error)}`);
}
});
impresora.setTimeout(5000); // Timeout de 5 segundos
impresora.connect({ host: this.config.ethernet.ip, port: this.config.ethernet.port });
}
}
async isHostConnected(host, port) {
const timeout = 2000;
const socket = new net.Socket();
return new Promise((resolve) => {
socket.on('connect', () => {
socket.destroy();
resolve(true);
});
socket.on('error', () => {
socket.destroy();
resolve(false);
});
socket.setTimeout(timeout);
socket.on('timeout', () => {
socket.destroy();
resolve(false);
});
socket.connect(port, host);
});
}
}
exports.Seiton = Seiton;