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