import { clearPrinter, getBluetoothConnection, getCurrentPrinterDriverKey, getPrinterType, sendToPrinter, setBluetoothConnection, setBuiltinPrinter, } from '../printerConnection' import classicBluetooth from '../bluetoothTool.js' import { rasterizeImageData, rasterizeImageForPrinter } from '../imageRaster' import { buildEscPosImageData, buildEscPosTemplateData } from '../protocols/escPosBuilder' import { buildTscImageData, buildTscTemplateData } from '../protocols/tscProtocol' import { connectNativeFastPrinter as connectNativeFastPrinterPlugin, disconnectNativeFastPrinter as disconnectNativeFastPrinterPlugin, isNativeFastPrinterAvailable, printNativeFastTemplate as printNativeFastTemplatePlugin, } from '../nativeFastPrinter' import { adaptSystemLabelTemplate } from '../systemTemplateAdapter' import { TEST_PRINT_SYSTEM_TEMPLATE, TEST_PRINT_TEMPLATE_DATA } from '../templates/testPrintTemplate' import { describePrinterCandidate, getPrinterDriverByKey, resolvePrinterDriver } from './driverRegistry' import type { CurrentPrinterSummary, LabelPrintPayload, LabelTemplateData, RawImageDataSource, PrintImageOptions, PrinterCandidate, PrinterDriver, StructuredLabelTemplate, SystemLabelTemplate, } from '../types/printer' function getPrinterTypeDisplayName (type: '' | 'bluetooth' | 'builtin'): string { if (type === 'bluetooth') return 'Bluetooth' if (type === 'builtin') return 'Built-in' return '' } function connectClassicBluetooth (device: PrinterCandidate, driver: PrinterDriver): Promise { return new Promise((resolve, reject) => { // #ifdef APP-PLUS if (isNativeFastPrinterAvailable()) { connectNativeFastPrinterPlugin({ deviceId: device.deviceId, deviceName: device.name || 'Bluetooth Printer', }).then(() => { setBluetoothConnection({ deviceId: device.deviceId, deviceName: device.name || 'Bluetooth Printer', deviceType: 'classic', driverKey: driver.key, mtu: driver.preferredBleMtu || 20, }) resolve() }).catch((error: any) => { reject(error instanceof Error ? error : new Error(String(error || 'Classic Bluetooth connection failed.'))) }) return } reject(new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND. Please rebuild the custom base with native-fast-printer.')) // #endif // #ifndef APP-PLUS reject(new Error('Classic Bluetooth requires the app.')) // #endif }) } function findBleWriteCharacteristic (deviceId: string): Promise<{ serviceId: string; characteristicId: string } | null> { return new Promise((resolve) => { uni.getBLEDeviceServices({ deviceId, success: (serviceRes) => { const services = serviceRes.services || [] const next = (index: number) => { if (index >= services.length) { resolve(null) return } const serviceId = services[index].uuid uni.getBLEDeviceCharacteristics({ deviceId, serviceId, success: (charRes) => { const target = (charRes.characteristics || []).find((item: any) => item.properties && item.properties.write) if (target) { resolve({ serviceId, characteristicId: target.uuid, }) return } next(index + 1) }, fail: () => next(index + 1), }) } next(0) }, fail: () => resolve(null), }) }) } function connectBlePrinter (device: PrinterCandidate, driver: PrinterDriver): Promise { const finalizeExistingBleConnection = async () => { const write = await findBleWriteCharacteristic(device.deviceId) if (!write) { throw new Error('No writable characteristic found. This device may not support printing.') } setBluetoothConnection({ deviceId: device.deviceId, deviceName: device.name || 'Bluetooth Printer', serviceId: write.serviceId, characteristicId: write.characteristicId, deviceType: 'ble', mtu: driver.preferredBleMtu || 20, driverKey: driver.key, }) } return new Promise((resolve, reject) => { uni.createBLEConnection({ deviceId: device.deviceId, timeout: 10000, success: async () => { try { await finalizeExistingBleConnection() resolve() } catch (e: any) { reject(e) } }, fail: (err: any) => { if (err?.errCode === -1) { finalizeExistingBleConnection().then(() => resolve()).catch(reject) } else { reject(new Error(err?.errMsg || 'BLE connection failed.')) } }, }) }) } export async function connectBluetoothPrinter (device: PrinterCandidate): Promise { const driver = resolvePrinterDriver(device) const resolvedType = driver.resolveConnectionType(device) if (resolvedType === 'classic') { await connectClassicBluetooth(device, driver) } else { await connectBlePrinter(device, driver) } return driver } export function useBuiltinPrinter (driverKey = 'generic-tsc') { setBuiltinPrinter(driverKey) } export function getCurrentPrinterDriver (): PrinterDriver { const type = getPrinterType() const storedKey = getCurrentPrinterDriverKey() if (storedKey) return getPrinterDriverByKey(storedKey) if (type === 'bluetooth') { const connection = getBluetoothConnection() if (connection) { return resolvePrinterDriver({ deviceId: connection.deviceId, name: connection.deviceName, type: connection.deviceType, }) } } return getPrinterDriverByKey('generic-tsc') } export function getCurrentPrinterSummary (): CurrentPrinterSummary { const type = getPrinterType() const driver = getCurrentPrinterDriver() if (type === 'builtin') { return { type, displayName: getPrinterTypeDisplayName(type), deviceId: 'builtin', driverKey: driver.key, driverName: driver.displayName, protocol: driver.protocol, deviceType: '', } } if (type === 'bluetooth') { const connection = getBluetoothConnection() if (connection) { return { type, displayName: getPrinterTypeDisplayName(type), deviceId: connection.deviceId, driverKey: driver.key, driverName: driver.displayName, protocol: driver.protocol, deviceType: connection.deviceType, } } } return { type: '', displayName: '', deviceId: '', driverKey: driver.key, driverName: driver.displayName, protocol: driver.protocol, deviceType: '', } } function canUseNativeFastTemplatePrint (driver: PrinterDriver): boolean { const connection = getBluetoothConnection() return driver.protocol === 'tsc' && connection?.deviceType === 'classic' && isNativeFastPrinterAvailable() } function getNativeClassicConnection () { const connection = getBluetoothConnection() if (!connection || connection.deviceType !== 'classic') return null return connection } export async function testPrintCurrentPrinter (onProgress?: (percent: number) => void): Promise { const driver = getCurrentPrinterDriver() const connection = getBluetoothConnection() if (driver.protocol === 'tsc' && connection?.deviceType === 'classic' && !isNativeFastPrinterAvailable()) { throw new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND. Please rebuild the custom base with native-fast-printer.') } if (canUseNativeFastTemplatePrint(driver)) { const nativeConnection = getNativeClassicConnection() if (nativeConnection) { await printNativeFastTemplatePlugin({ deviceId: nativeConnection.deviceId, deviceName: nativeConnection.deviceName, template: TEST_PRINT_SYSTEM_TEMPLATE, data: TEST_PRINT_TEMPLATE_DATA, dpi: driver.imageDpi || 203, printQty: 1, }) if (onProgress) onProgress(100) return driver } } await sendToPrinter(driver.buildTestPrintData(), onProgress) return driver } export async function printLabelForCurrentPrinter ( payload: LabelPrintPayload, onProgress?: (percent: number) => void ): Promise { const driver = getCurrentPrinterDriver() await sendToPrinter(driver.buildLabelData(payload), onProgress) return driver } export async function printImageForCurrentPrinter ( imageSource: string, options: PrintImageOptions = {}, onProgress?: (percent: number) => void ): Promise { const driver = getCurrentPrinterDriver() const raster = await rasterizeImageForPrinter(imageSource, driver, options) if (onProgress) onProgress(5) let data: number[] = [] if (driver.protocol === 'esc') { data = buildEscPosImageData(raster, options) } else { data = buildTscImageData(raster, options, driver.imageDpi || 203) } await sendToPrinter(data, onProgress) return driver } export async function printImageDataForCurrentPrinter ( imageData: RawImageDataSource, options: PrintImageOptions = {}, onProgress?: (percent: number) => void ): Promise { const driver = getCurrentPrinterDriver() const raster = rasterizeImageData(imageData, options) if (onProgress) onProgress(5) const data = driver.protocol === 'esc' ? buildEscPosImageData(raster, options) : buildTscImageData(raster, options, driver.imageDpi || 203) await sendToPrinter(data, onProgress) return driver } export async function printTemplateForCurrentPrinter ( template: StructuredLabelTemplate, data: LabelTemplateData = {}, onProgress?: (percent: number) => void ): Promise { const driver = getCurrentPrinterDriver() const bytes = driver.protocol === 'esc' ? buildEscPosTemplateData(template, data) : buildTscTemplateData(template, data) await sendToPrinter(bytes, onProgress) return driver } export async function printSystemTemplateForCurrentPrinter ( template: SystemLabelTemplate, data: LabelTemplateData = {}, options: { printQty?: number } = {}, onProgress?: (percent: number) => void ): Promise { const driver = getCurrentPrinterDriver() const connection = getBluetoothConnection() if (driver.protocol === 'tsc' && connection?.deviceType === 'classic' && !isNativeFastPrinterAvailable()) { throw new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND. Please rebuild the custom base with native-fast-printer.') } if (canUseNativeFastTemplatePrint(driver)) { const nativeConnection = getNativeClassicConnection() if (nativeConnection) { await printNativeFastTemplatePlugin({ deviceId: nativeConnection.deviceId, deviceName: nativeConnection.deviceName, template, data, dpi: driver.imageDpi || 203, printQty: options.printQty || 1, }) if (onProgress) onProgress(100) return driver } } const structuredTemplate = adaptSystemLabelTemplate(template, data, { dpi: driver.imageDpi || 203, printQty: options.printQty || 1, }) const bytes = driver.protocol === 'esc' ? buildEscPosTemplateData(structuredTemplate) : buildTscTemplateData(structuredTemplate) await sendToPrinter(bytes, onProgress) return driver } export function describeDiscoveredPrinter (device: PrinterCandidate) { return describePrinterCandidate(device) } export function disconnectCurrentPrinter (): Promise { return new Promise((resolve) => { const type = getPrinterType() const connection = getBluetoothConnection() if (type === 'bluetooth' && connection?.deviceType === 'classic') { // #ifdef APP-PLUS if (isNativeFastPrinterAvailable()) { disconnectNativeFastPrinterPlugin().catch((e: any) => { console.error('Disconnect native fast printer failed', e) }).finally(() => { clearPrinter() resolve() }) return } try { const classic = classicBluetooth if (classic && classic.disConnDevice) classic.disConnDevice() } catch (e) { console.error('Disconnect classic bluetooth failed', e) } // #endif clearPrinter() resolve() return } clearPrinter() if (type === 'bluetooth' && connection?.deviceId) { uni.closeBLEConnection({ deviceId: connection.deviceId, complete: () => resolve(), }) return } resolve() }) }