taro-bluetooth-print 2.9.4 → 2.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -396
- package/README.md +197 -132
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/types/drivers/CpclDriver.d.ts +9 -4
- package/dist/types/drivers/ZplDriver.d.ts +9 -9
- package/dist/types/queue/PrintQueue.d.ts +1 -0
- package/package.json +12 -2
- package/src/adapters/WebBluetoothAdapter.ts +5 -2
- package/src/drivers/CpclDriver.ts +34 -7
- package/src/drivers/ZplDriver.ts +31 -12
- package/src/queue/PrintQueue.ts +9 -1
- package/src/services/ConnectionManager.ts +7 -3
- package/src/services/PrinterStatus.ts +5 -1
|
@@ -264,12 +264,17 @@ export declare class CpclDriver {
|
|
|
264
264
|
*/
|
|
265
265
|
logo(x: number, y: number, logoName: string): this;
|
|
266
266
|
/**
|
|
267
|
-
* Download logo to printer (
|
|
268
|
-
*
|
|
267
|
+
* Download logo to printer memory (macro/form storage).
|
|
268
|
+
* Encodes bitmap using CPCL CG (Compressed Graphic) command.
|
|
269
|
+
* The bitmap should be 1-bit monochrome data.
|
|
269
270
|
* @param logoName - Name to store logo as
|
|
270
|
-
* @param
|
|
271
|
+
* @param bitmap - 1-bit monochrome bitmap data (MSB first, row-major)
|
|
272
|
+
* @param options - Optional width and height (will be inferred from bitmap size if omitted)
|
|
271
273
|
*/
|
|
272
|
-
downloadLogo(logoName: string,
|
|
274
|
+
downloadLogo(logoName: string, bitmap: Uint8Array, options?: {
|
|
275
|
+
width?: number;
|
|
276
|
+
height?: number;
|
|
277
|
+
}): this;
|
|
273
278
|
/**
|
|
274
279
|
* Print stored logo
|
|
275
280
|
* @param logoName - Logo name
|
|
@@ -267,15 +267,15 @@ export declare class ZplDriver {
|
|
|
267
267
|
*/
|
|
268
268
|
ellipse(x: number, y: number, width: number, height: number, borderThickness?: number): this;
|
|
269
269
|
/**
|
|
270
|
-
* Add image from raw bitmap
|
|
271
|
-
*
|
|
272
|
-
* @param
|
|
273
|
-
* @param
|
|
274
|
-
* @param
|
|
275
|
-
* @param
|
|
276
|
-
* @param
|
|
277
|
-
*/
|
|
278
|
-
image(
|
|
270
|
+
* Add image from raw bitmap using ZPL ^GFA (Graphic Field) command.
|
|
271
|
+
* The bitmap should be 1-bit (monochrome) data where each bit represents a pixel.
|
|
272
|
+
* @param x - X position in dots
|
|
273
|
+
* @param y - Y position in dots
|
|
274
|
+
* @param width - Image width in pixels
|
|
275
|
+
* @param height - Image height in pixels
|
|
276
|
+
* @param bitmap - 1-bit monochrome bitmap data (MSB first, row-major)
|
|
277
|
+
*/
|
|
278
|
+
image(x: number, y: number, width: number, height: number, bitmap: Uint8Array): this;
|
|
279
279
|
/**
|
|
280
280
|
* Set darkness/print density
|
|
281
281
|
* @param value - Darkness value (0-30, default: 15)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "taro-bluetooth-print",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.6",
|
|
4
4
|
"description": "Taro 蓝牙打印库 v2.6 - 轻量级、高性能、跨平台支持微信、支付宝、百度、字节跳动小程序及H5 Web Bluetooth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs.js",
|
|
@@ -88,5 +88,15 @@
|
|
|
88
88
|
"vitepress": "^1.6.4",
|
|
89
89
|
"vitest": "^4.0.18"
|
|
90
90
|
},
|
|
91
|
-
"sideEffects": false
|
|
91
|
+
"sideEffects": false,
|
|
92
|
+
"pnpm": {
|
|
93
|
+
"overrides": {
|
|
94
|
+
"esbuild@<=0.24.2": ">=0.25.0",
|
|
95
|
+
"lodash-es@>=4.0.0 <=4.17.22": ">=4.17.23",
|
|
96
|
+
"lodash-es@>=4.0.0 <=4.17.23": ">=4.18.0",
|
|
97
|
+
"lodash-es@<=4.17.23": ">=4.18.0",
|
|
98
|
+
"vite@<=6.4.1": ">=6.4.2",
|
|
99
|
+
"postcss@<8.5.10": ">=8.5.10"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
92
102
|
}
|
|
@@ -218,8 +218,11 @@ export class WebBluetoothAdapter extends BaseAdapter {
|
|
|
218
218
|
// Get RSSI if available (may not be available on all devices)
|
|
219
219
|
let rssi: number | undefined;
|
|
220
220
|
try {
|
|
221
|
-
const deviceWithRssi = characteristic.service.device as BluetoothDeviceWithRssi;
|
|
222
|
-
if (
|
|
221
|
+
const deviceWithRssi = characteristic.service.device as unknown as BluetoothDeviceWithRssi;
|
|
222
|
+
if (
|
|
223
|
+
'readRemoteRssi' in deviceWithRssi &&
|
|
224
|
+
typeof deviceWithRssi.readRemoteRssi === 'function'
|
|
225
|
+
) {
|
|
223
226
|
rssi = await deviceWithRssi.readRemoteRssi();
|
|
224
227
|
}
|
|
225
228
|
} catch {
|
|
@@ -478,16 +478,43 @@ export class CpclDriver {
|
|
|
478
478
|
}
|
|
479
479
|
|
|
480
480
|
/**
|
|
481
|
-
* Download logo to printer (
|
|
482
|
-
*
|
|
481
|
+
* Download logo to printer memory (macro/form storage).
|
|
482
|
+
* Encodes bitmap using CPCL CG (Compressed Graphic) command.
|
|
483
|
+
* The bitmap should be 1-bit monochrome data.
|
|
483
484
|
* @param logoName - Name to store logo as
|
|
484
|
-
* @param
|
|
485
|
-
|
|
486
|
-
|
|
485
|
+
* @param bitmap - 1-bit monochrome bitmap data (MSB first, row-major)
|
|
486
|
+
* @param options - Optional width and height (will be inferred from bitmap size if omitted)
|
|
487
|
+
*/
|
|
488
|
+
downloadLogo(
|
|
489
|
+
logoName: string,
|
|
490
|
+
bitmap: Uint8Array,
|
|
491
|
+
options?: { width?: number; height?: number }
|
|
492
|
+
): this {
|
|
493
|
+
const width = options?.width ?? 100;
|
|
494
|
+
const bytesPerRow = Math.ceil(width / 8);
|
|
495
|
+
const height = options?.height ?? Math.max(1, Math.floor(bitmap.length / bytesPerRow));
|
|
496
|
+
const totalBytes = bytesPerRow * height;
|
|
497
|
+
|
|
498
|
+
// Validate bitmap size
|
|
499
|
+
if (bitmap.length < totalBytes) {
|
|
500
|
+
this.logger.warn(
|
|
501
|
+
`CPCL logo bitmap size mismatch: expected ${totalBytes}, got ${bitmap.length}`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Convert bitmap bytes to hex string (uppercase)
|
|
506
|
+
const hexData: string[] = [];
|
|
507
|
+
const limit = Math.min(bitmap.length, totalBytes);
|
|
508
|
+
for (let i = 0; i < limit; i++) {
|
|
509
|
+
const byte = bitmap[i]!;
|
|
510
|
+
hexData.push(byte.toString(16).padStart(2, '0').toUpperCase());
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// CPCL macro definition with graphic data
|
|
487
514
|
this.commands.push(`! DF ${logoName}`);
|
|
488
|
-
|
|
489
|
-
this.logger.debug('CPCL logo download not fully implemented');
|
|
515
|
+
this.commands.push(`CG ${totalBytes} ${bytesPerRow} ${height} ${hexData.join('')}`);
|
|
490
516
|
this.commands.push(`! DF`);
|
|
517
|
+
this.logger.debug(`CPCL logo downloaded: ${logoName} (${width}x${height})`);
|
|
491
518
|
return this;
|
|
492
519
|
}
|
|
493
520
|
|
package/src/drivers/ZplDriver.ts
CHANGED
|
@@ -428,18 +428,37 @@ export class ZplDriver {
|
|
|
428
428
|
}
|
|
429
429
|
|
|
430
430
|
/**
|
|
431
|
-
* Add image from raw bitmap
|
|
432
|
-
*
|
|
433
|
-
* @param
|
|
434
|
-
* @param
|
|
435
|
-
* @param
|
|
436
|
-
* @param
|
|
437
|
-
* @param
|
|
438
|
-
*/
|
|
439
|
-
image(
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
431
|
+
* Add image from raw bitmap using ZPL ^GFA (Graphic Field) command.
|
|
432
|
+
* The bitmap should be 1-bit (monochrome) data where each bit represents a pixel.
|
|
433
|
+
* @param x - X position in dots
|
|
434
|
+
* @param y - Y position in dots
|
|
435
|
+
* @param width - Image width in pixels
|
|
436
|
+
* @param height - Image height in pixels
|
|
437
|
+
* @param bitmap - 1-bit monochrome bitmap data (MSB first, row-major)
|
|
438
|
+
*/
|
|
439
|
+
image(x: number, y: number, width: number, height: number, bitmap: Uint8Array): this {
|
|
440
|
+
const bytesPerRow = Math.ceil(width / 8);
|
|
441
|
+
const totalBytes = bytesPerRow * height;
|
|
442
|
+
|
|
443
|
+
// Validate bitmap size
|
|
444
|
+
if (bitmap.length < totalBytes) {
|
|
445
|
+
this.logger.warn(
|
|
446
|
+
`ZPL image bitmap size mismatch: expected ${totalBytes}, got ${bitmap.length}`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Convert bitmap bytes to hex string (uppercase)
|
|
451
|
+
const hexData: string[] = [];
|
|
452
|
+
const limit = Math.min(bitmap.length, totalBytes);
|
|
453
|
+
for (let i = 0; i < limit; i++) {
|
|
454
|
+
const byte = bitmap[i]!;
|
|
455
|
+
hexData.push(byte.toString(16).padStart(2, '0').toUpperCase());
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
this.commands.push(`^FO${x},${y}`);
|
|
459
|
+
this.commands.push(`^GFA,${totalBytes},${totalBytes},${bytesPerRow},${hexData.join('')}`);
|
|
460
|
+
this.commands.push('^FS');
|
|
461
|
+
this.logger.debug(`ZPL image encoded: ${width}x${height} at (${x},${y})`);
|
|
443
462
|
return this;
|
|
444
463
|
}
|
|
445
464
|
|
package/src/queue/PrintQueue.ts
CHANGED
|
@@ -149,6 +149,7 @@ export class PrintQueue implements IPrintQueue {
|
|
|
149
149
|
private activeJobs = 0;
|
|
150
150
|
private jobCounter = 0;
|
|
151
151
|
private executor: JobExecutor | null = null;
|
|
152
|
+
private retryTimerId: ReturnType<typeof setTimeout> | null = null;
|
|
152
153
|
|
|
153
154
|
/**
|
|
154
155
|
* Creates a new PrintQueue instance
|
|
@@ -257,6 +258,12 @@ export class PrintQueue implements IPrintQueue {
|
|
|
257
258
|
* Clear all pending jobs
|
|
258
259
|
*/
|
|
259
260
|
clear(): void {
|
|
261
|
+
// Cancel pending retry timer if one is active
|
|
262
|
+
if (this.retryTimerId !== null) {
|
|
263
|
+
clearTimeout(this.retryTimerId);
|
|
264
|
+
this.retryTimerId = null;
|
|
265
|
+
}
|
|
266
|
+
|
|
260
267
|
const pendingJobs = [...this.pendingQueue];
|
|
261
268
|
for (const jobId of pendingJobs) {
|
|
262
269
|
const job = this.jobs.get(jobId);
|
|
@@ -430,13 +437,14 @@ export class PrintQueue implements IPrintQueue {
|
|
|
430
437
|
job.status = PrintJobStatus.PENDING;
|
|
431
438
|
|
|
432
439
|
// Re-add to queue after delay
|
|
433
|
-
setTimeout(() => {
|
|
440
|
+
this.retryTimerId = setTimeout(() => {
|
|
434
441
|
if (job.status === PrintJobStatus.PENDING) {
|
|
435
442
|
this.insertByPriority(job.id, job.priority);
|
|
436
443
|
if (this.config.autoProcess && !this.isPaused) {
|
|
437
444
|
this.processQueue();
|
|
438
445
|
}
|
|
439
446
|
}
|
|
447
|
+
this.retryTimerId = null;
|
|
440
448
|
}, this.config.retryDelay);
|
|
441
449
|
} else {
|
|
442
450
|
job.status = PrintJobStatus.FAILED;
|
|
@@ -144,8 +144,9 @@ export class ConnectionManager
|
|
|
144
144
|
this.emit('state-change', PrinterState.CONNECTING);
|
|
145
145
|
|
|
146
146
|
const connectPromise = this.adapter.connect(deviceId);
|
|
147
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
147
148
|
const timeoutPromise = new Promise<void>((_, reject) => {
|
|
148
|
-
setTimeout(() => {
|
|
149
|
+
timeoutId = setTimeout(() => {
|
|
149
150
|
reject(
|
|
150
151
|
new BluetoothPrintError(
|
|
151
152
|
ErrorCode.CONNECTION_TIMEOUT,
|
|
@@ -156,6 +157,9 @@ export class ConnectionManager
|
|
|
156
157
|
});
|
|
157
158
|
|
|
158
159
|
await Promise.race([connectPromise, timeoutPromise]);
|
|
160
|
+
if (timeoutId !== null) {
|
|
161
|
+
clearTimeout(timeoutId);
|
|
162
|
+
}
|
|
159
163
|
this.state = PrinterState.CONNECTED;
|
|
160
164
|
this.emit('state-change', PrinterState.CONNECTED);
|
|
161
165
|
this.emit('connected', deviceId);
|
|
@@ -304,7 +308,7 @@ export class ConnectionManager
|
|
|
304
308
|
|
|
305
309
|
this.isReconnecting = true;
|
|
306
310
|
this.reconnectAttempts = 0;
|
|
307
|
-
this.attemptReconnect();
|
|
311
|
+
void this.attemptReconnect();
|
|
308
312
|
}
|
|
309
313
|
|
|
310
314
|
/**
|
|
@@ -361,7 +365,7 @@ export class ConnectionManager
|
|
|
361
365
|
this.connLogger.warn(`Reconnect attempt ${this.reconnectAttempts} failed:`, error);
|
|
362
366
|
|
|
363
367
|
this.reconnectTimer = setTimeout(() => {
|
|
364
|
-
this.attemptReconnect();
|
|
368
|
+
void this.attemptReconnect();
|
|
365
369
|
}, this.config.reconnectInterval);
|
|
366
370
|
}
|
|
367
371
|
}
|
|
@@ -107,14 +107,18 @@ export class PrinterStatus {
|
|
|
107
107
|
await writeFunc(queryCmd.buffer);
|
|
108
108
|
|
|
109
109
|
// Set up timeout promise
|
|
110
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
110
111
|
const timeoutPromise = new Promise<ArrayBuffer>((_, reject) => {
|
|
111
|
-
setTimeout(() => {
|
|
112
|
+
timeoutId = setTimeout(() => {
|
|
112
113
|
reject(new BluetoothPrintError(ErrorCode.CONNECTION_TIMEOUT, 'Status query timed out'));
|
|
113
114
|
}, timeout);
|
|
114
115
|
});
|
|
115
116
|
|
|
116
117
|
// Read response with timeout
|
|
117
118
|
const response = await Promise.race([readFunc(), timeoutPromise]);
|
|
119
|
+
if (timeoutId !== null) {
|
|
120
|
+
clearTimeout(timeoutId);
|
|
121
|
+
}
|
|
118
122
|
|
|
119
123
|
return this.parseStatus(new Uint8Array(response), includeRaw);
|
|
120
124
|
} catch (error) {
|