Kasir/resources/js/composables/useNiimbotPrinter.js
Baghaztra 8665584567 [Update] print label
Library niimblue tidak digunakan, namun potongan kode tetap disimpan
2025-10-15 14:33:47 +07:00

299 lines
7.8 KiB
JavaScript

/*
* Menggunakan library@mmote/niimbluelib
*/
import { ref, computed } from 'vue';
import {
NiimbotBluetoothClient,
NiimbotSerialClient,
Utils,
ImageEncoder,
LabelType
} from '@mmote/niimbluelib';
export function useNiimbotPrinter() {
// State
const printerClient = ref(null);
const connectionState = ref('disconnected'); // 'disconnected' | 'connecting' | 'connected'
const connectedPrinterName = ref('');
const printerInfo = ref(null);
const printerMeta = ref(null);
const heartbeatData = ref(null);
const connectionType = ref('bluetooth'); // 'bluetooth' | 'serial'
const printProgress = ref(0);
const isPrinting = ref(false);
// Computed
const isConnected = computed(() => connectionState.value === 'connected');
const isDisconnected = computed(() => connectionState.value === 'disconnected');
const featureSupport = computed(() => Utils.getAvailableTransports());
/**
* Inisialisasi printer client
*/
const initClient = (type = 'bluetooth') => {
connectionType.value = type;
// Disconnect existing client
if (printerClient.value) {
printerClient.value.disconnect();
}
// Create new client based on type
if (type === 'bluetooth') {
printerClient.value = new NiimbotBluetoothClient();
} else if (type === 'serial') {
printerClient.value = new NiimbotSerialClient();
}
// Setup event listeners
printerClient.value.on('connect', (e) => {
console.log('Printer connected:', e);
connectionState.value = 'connected';
connectedPrinterName.value = e.info.deviceName || 'Niimbot Printer';
});
printerClient.value.on('connect', (e) => {
console.log('Printer connected (Serial):', e);
console.log('Device details:', e.info);
connectionState.value = 'connected';
connectedPrinterName.value = e.info.deviceName || 'Niimbot Printer';
});
printerClient.value.on('packetreceived', (e) => {
console.log('<< Packet received (Serial):', Utils.bufToHex(e.packet.toBytes()), e);
});
printerClient.value.on('disconnect', () => {
console.log('Printer disconnected');
connectionState.value = 'disconnected';
connectedPrinterName.value = '';
printerInfo.value = null;
printerMeta.value = null;
});
printerClient.value.on('printerinfofetched', (e) => {
console.log('Printer info fetched:', e.info);
printerInfo.value = e.info;
printerMeta.value = printerClient.value.getModelMetadata();
});
printerClient.value.on('heartbeat', (e) => {
heartbeatData.value = e.data;
});
printerClient.value.on('printprogress', (e) => {
printProgress.value = Math.floor(
(e.page / e.totalPages) * ((e.pagePrintProgress + e.pageFeedProgress) / 2)
);
});
// Log packets for debugging
printerClient.value.on('packetsent', (e) => {
console.log('>> Packet sent:', Utils.bufToHex(e.packet.toBytes()));
});
// printerClient.value.on('packetreceived', (e) => {
// console.log('<< Packet received:', Utils.bufToHex(e.packet.toBytes()));
// });
};
/**
* Connect to printer
*/
// const connect = async () => {
// if (!printerClient.value) {
// initClient(connectionType.value);
// }
// connectionState.value = 'connecting';
// try {
// await printerClient.value.connect();
// // Connection state will be updated by 'connect' event
// } catch (error) {
// console.error('Failed to connect to printer:', error);
// connectionState.value = 'disconnected';
// throw error;
// }
// };
const connect = async () => {
if (!printerClient.value) {
initClient(connectionType.value);
}
connectionState.value = 'connecting';
try {
const port = await printerClient.value.connect();
console.log('Selected serial port:', port);
} catch (error) {
console.error('Failed to connect to printer (Serial):', error);
connectionState.value = 'disconnected';
throw error;
}
};
/**
* Disconnect from printer
*/
const disconnect = () => {
if (printerClient.value) {
printerClient.value.disconnect();
}
};
/**
* Print QR Code image
* @param {string} imageDataUrl - Data URL of QR code image
* @param {object} options - Print options
*/
const printQRCode = async (imageDataUrl, options = {}) => {
if (!printerClient.value || !isConnected.value) {
throw new Error('Printer not connected. Please connect to printer first.');
}
const {
density = 3,
quantity = 1,
labelType = LabelType.WithGaps,
printTaskName = 'D110'
} = options;
isPrinting.value = true;
printProgress.value = 0;
try {
// Stop heartbeat during print
printerClient.value.stopHeartbeat();
// Create print task
const printTask = printerClient.value.abstraction.newPrintTask(printTaskName, {
totalPages: quantity,
density,
labelType,
statusPollIntervalMs: 100,
statusTimeoutMs: 8000,
});
// Load image into canvas
const canvas = await loadImageToCanvas(imageDataUrl);
// Encode canvas to printer format
const encoded = ImageEncoder.encodeCanvas(canvas, 'top'); // 'top' = print direction
// Initialize print
await printTask.printInit();
// Send print data
await printTask.printPage(encoded, quantity);
// Wait for print to finish
await printTask.waitForFinished();
// End print
await printTask.printEnd();
printProgress.value = 100;
// Restart heartbeat
printerClient.value.startHeartbeat();
return { success: true, message: 'QR Code printed successfully' };
} catch (error) {
console.error('Print failed:', error);
// Try to end print gracefully
try {
await printerClient.value.abstraction.printEnd();
printerClient.value.startHeartbeat();
} catch (endError) {
console.error('Failed to end print:', endError);
}
throw error;
} finally {
isPrinting.value = false;
printProgress.value = 0;
}
};
/**
* Load image data URL into canvas
* @param {string} dataUrl - Image data URL
* @returns {HTMLCanvasElement}
*/
const loadImageToCanvas = (dataUrl) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
resolve(canvas);
};
img.onerror = () => {
reject(new Error('Failed to load image'));
};
img.src = dataUrl;
});
};
/**
* Apply threshold to canvas for better print quality
* @param {HTMLCanvasElement} canvas
* @param {number} threshold - 0-255
*/
const applyThreshold = (canvas, threshold = 140) => {
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
const val = avg < threshold ? 0 : 255;
data[i] = val; // red
data[i + 1] = val; // green
data[i + 2] = val; // blue
}
ctx.putImageData(imageData, 0, 0);
return canvas;
};
return {
// State
printerClient,
connectionState,
connectedPrinterName,
printerInfo,
printerMeta,
heartbeatData,
connectionType,
printProgress,
isPrinting,
// Computed
isConnected,
isDisconnected,
featureSupport,
// Methods
initClient,
connect,
disconnect,
printQRCode,
loadImageToCanvas,
applyThreshold,
};
}