jc-printer-sdk-ts 0.1.1 → 0.1.2
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/README.md +8 -0
- package/dist/client.d.ts +3 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +82 -0
- package/dist/client.js.map +1 -1
- package/dist/imagePrint.d.ts.map +1 -1
- package/dist/imagePrint.js +30 -21
- package/dist/imagePrint.js.map +1 -1
- package/dist/transport.d.ts +5 -1
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +58 -0
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +28 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/webBluetoothTransport.d.ts +18 -1
- package/dist/webBluetoothTransport.d.ts.map +1 -1
- package/dist/webBluetoothTransport.js +246 -24
- package/dist/webBluetoothTransport.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +100 -0
- package/src/imagePrint.ts +39 -19
- package/src/transport.ts +66 -0
- package/src/types.ts +44 -0
- package/src/webBluetoothTransport.ts +318 -23
|
@@ -7,8 +7,11 @@ import type {
|
|
|
7
7
|
PrintDocument,
|
|
8
8
|
PrinterDevice,
|
|
9
9
|
PrinterInfo,
|
|
10
|
+
PrinterReadiness,
|
|
11
|
+
PrinterReadinessWarningCode,
|
|
10
12
|
PrinterSdkCallback,
|
|
11
13
|
PrinterState,
|
|
14
|
+
PrinterStatusSnapshot,
|
|
12
15
|
ScanCallback,
|
|
13
16
|
} from "./types.js";
|
|
14
17
|
import { JCPrintApi } from "./client.js";
|
|
@@ -33,6 +36,8 @@ export interface WebBluetoothPrinterTransportOptions {
|
|
|
33
36
|
scanMode?: "all" | "name";
|
|
34
37
|
optionalServices?: string[];
|
|
35
38
|
chunkSize?: number;
|
|
39
|
+
preflightStatus?: boolean;
|
|
40
|
+
statusQueryTimeoutMs?: number;
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
interface ResolvedWebBluetoothPrinterTransportOptions {
|
|
@@ -42,6 +47,8 @@ interface ResolvedWebBluetoothPrinterTransportOptions {
|
|
|
42
47
|
scanMode: "all" | "name";
|
|
43
48
|
optionalServices: string[];
|
|
44
49
|
chunkSize: number;
|
|
50
|
+
preflightStatus: boolean;
|
|
51
|
+
statusQueryTimeoutMs: number;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
export const NIIMBOT_WEB_BLUETOOTH_SERVICE_UUID = "e7810a71-73ae-499d-8c15-faa9aef0c3f2";
|
|
@@ -54,6 +61,8 @@ export const DEFAULT_WEB_BLUETOOTH_PRINTER_TRANSPORT_OPTIONS: ResolvedWebBluetoo
|
|
|
54
61
|
scanMode: "all",
|
|
55
62
|
optionalServices: [],
|
|
56
63
|
chunkSize: 180,
|
|
64
|
+
preflightStatus: true,
|
|
65
|
+
statusQueryTimeoutMs: 700,
|
|
57
66
|
};
|
|
58
67
|
|
|
59
68
|
interface PrintJobContext {
|
|
@@ -90,6 +99,13 @@ interface PendingResponse {
|
|
|
90
99
|
optional: boolean;
|
|
91
100
|
}
|
|
92
101
|
|
|
102
|
+
const DEFAULT_GATT_BUSY_RETRY_COUNT = 8;
|
|
103
|
+
const DEFAULT_GATT_BUSY_RETRY_DELAY_MS = 25;
|
|
104
|
+
const HEARTBEAT_ADVANCED_1 = 0x01;
|
|
105
|
+
const HEARTBEAT_ADVANCED_2 = 0x04;
|
|
106
|
+
const HEARTBEAT_RESPONSE_COMMANDS = [0xd9, 0xdd, 0xde, 0xdf];
|
|
107
|
+
const INVERTED_LID_MODEL_IDS = new Set([512, 514, 513, 2304, 1792, 3584, 5120, 2560, 3840, 4352, 272, 273, 274]);
|
|
108
|
+
|
|
93
109
|
function clone<T>(value: T): T {
|
|
94
110
|
if (typeof globalThis.structuredClone === "function") {
|
|
95
111
|
return globalThis.structuredClone(value);
|
|
@@ -105,6 +121,13 @@ function uniqueStrings(values: Array<string | undefined>): string[] {
|
|
|
105
121
|
return Array.from(new Set(values.map((value) => value?.trim()).filter((value): value is string => !!value)));
|
|
106
122
|
}
|
|
107
123
|
|
|
124
|
+
function isGattOperationBusyError(error: unknown): boolean {
|
|
125
|
+
if (!(error instanceof Error)) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return /GATT operation already in progress/i.test(error.message);
|
|
129
|
+
}
|
|
130
|
+
|
|
108
131
|
function resolveOptions(
|
|
109
132
|
options: Partial<WebBluetoothPrinterTransportOptions> = {},
|
|
110
133
|
): ResolvedWebBluetoothPrinterTransportOptions {
|
|
@@ -119,9 +142,24 @@ function resolveOptions(
|
|
|
119
142
|
scanMode: options.scanMode ?? DEFAULT_WEB_BLUETOOTH_PRINTER_TRANSPORT_OPTIONS.scanMode,
|
|
120
143
|
optionalServices: [...(options.optionalServices ?? DEFAULT_WEB_BLUETOOTH_PRINTER_TRANSPORT_OPTIONS.optionalServices)],
|
|
121
144
|
chunkSize: Math.max(20, chunkSize),
|
|
145
|
+
preflightStatus: options.preflightStatus ?? DEFAULT_WEB_BLUETOOTH_PRINTER_TRANSPORT_OPTIONS.preflightStatus,
|
|
146
|
+
statusQueryTimeoutMs: Math.max(
|
|
147
|
+
100,
|
|
148
|
+
Math.trunc(Number(options.statusQueryTimeoutMs ?? DEFAULT_WEB_BLUETOOTH_PRINTER_TRANSPORT_OPTIONS.statusQueryTimeoutMs)),
|
|
149
|
+
),
|
|
122
150
|
};
|
|
123
151
|
}
|
|
124
152
|
|
|
153
|
+
function signedByte(value: number): number {
|
|
154
|
+
const byte = value & 0xff;
|
|
155
|
+
return byte > 0x7f ? byte - 0x100 : byte;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function signedWord(high: number, low: number): number {
|
|
159
|
+
const value = ((high & 0xff) << 8) | (low & 0xff);
|
|
160
|
+
return value > 0x7fff ? value - 0x10000 : value;
|
|
161
|
+
}
|
|
162
|
+
|
|
125
163
|
function defaultPrinterInfo(): PrinterInfo {
|
|
126
164
|
return {
|
|
127
165
|
deviceType: 2,
|
|
@@ -175,6 +213,7 @@ export class WebBluetoothPrinterTransport implements PrinterTransport {
|
|
|
175
213
|
private b1HandshakeDone = false;
|
|
176
214
|
private responseQueue: ParsedResponse[] = [];
|
|
177
215
|
private pendingResponse: PendingResponse | undefined;
|
|
216
|
+
private gattOperationQueue: Promise<void> = Promise.resolve();
|
|
178
217
|
|
|
179
218
|
constructor(options: Partial<WebBluetoothPrinterTransportOptions> = {}) {
|
|
180
219
|
this.options = resolveOptions(options);
|
|
@@ -230,13 +269,13 @@ export class WebBluetoothPrinterTransport implements PrinterTransport {
|
|
|
230
269
|
};
|
|
231
270
|
|
|
232
271
|
const device = await navigator.bluetooth.requestDevice(requestOptions);
|
|
233
|
-
const server = await device.gatt
|
|
272
|
+
const server = await this.runGattOperation(() => device.gatt!.connect());
|
|
234
273
|
if (!server) {
|
|
235
274
|
throw new Error("Failed to open the selected Bluetooth device.");
|
|
236
275
|
}
|
|
237
276
|
|
|
238
|
-
const service = await server.getPrimaryService(serviceUuid);
|
|
239
|
-
const characteristic = await service.getCharacteristic(characteristicUuid);
|
|
277
|
+
const service = await this.runGattOperation(() => server.getPrimaryService(serviceUuid));
|
|
278
|
+
const characteristic = await this.runGattOperation(() => service.getCharacteristic(characteristicUuid));
|
|
240
279
|
|
|
241
280
|
this.device = device;
|
|
242
281
|
this.server = server;
|
|
@@ -255,7 +294,7 @@ export class WebBluetoothPrinterTransport implements PrinterTransport {
|
|
|
255
294
|
|
|
256
295
|
device.addEventListener("gattserverdisconnected", this.handleDisconnect);
|
|
257
296
|
characteristic.addEventListener("characteristicvaluechanged", this.handleCharacteristicChanged);
|
|
258
|
-
await characteristic.startNotifications();
|
|
297
|
+
await this.runGattOperation(() => characteristic.startNotifications());
|
|
259
298
|
await this.handshakeAndIdentify();
|
|
260
299
|
this.sdkCallback?.onConnectSuccess(this.lastAddress, 20);
|
|
261
300
|
return 0;
|
|
@@ -303,14 +342,33 @@ export class WebBluetoothPrinterTransport implements PrinterTransport {
|
|
|
303
342
|
printMode: number,
|
|
304
343
|
callback: PrintCallback,
|
|
305
344
|
): Promise<void> {
|
|
345
|
+
if (this.currentJob && (this.printerState === "Printing" || this.printerState === "Working")) {
|
|
346
|
+
throw new Error("A print job is already in progress on this Web Bluetooth printer.");
|
|
347
|
+
}
|
|
348
|
+
|
|
306
349
|
this.currentJob = { density, paperType, printMode, callback };
|
|
307
|
-
this.printerState = "
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
350
|
+
this.printerState = "Working";
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
if (this.options.preflightStatus) {
|
|
354
|
+
const readiness = await this.evaluatePrintReadiness(this.options.statusQueryTimeoutMs, true);
|
|
355
|
+
if (!readiness.canPrint) {
|
|
356
|
+
throw new Error(`Printer is not ready to print: ${readiness.reason}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
this.printerState = "Printing";
|
|
361
|
+
callback.onProgress(0, 0, {
|
|
362
|
+
phase: "start",
|
|
363
|
+
density,
|
|
364
|
+
paperType,
|
|
365
|
+
printMode,
|
|
366
|
+
});
|
|
367
|
+
} catch (error) {
|
|
368
|
+
this.currentJob = undefined;
|
|
369
|
+
this.printerState = this.server?.connected ? "Connected" : "Disconnected";
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
314
372
|
}
|
|
315
373
|
|
|
316
374
|
async commitData(jsonPages: string[], infoPages: string[]): Promise<void> {
|
|
@@ -379,13 +437,15 @@ export class WebBluetoothPrinterTransport implements PrinterTransport {
|
|
|
379
437
|
|
|
380
438
|
this.reconnecting = true;
|
|
381
439
|
try {
|
|
382
|
-
const server = await this.device
|
|
440
|
+
const server = await this.runGattOperation(() => this.device!.gatt!.connect());
|
|
383
441
|
this.server = server;
|
|
384
|
-
const service = await server.getPrimaryService(normalizeUuid(this.options.serviceUuid));
|
|
385
|
-
const characteristic = await
|
|
442
|
+
const service = await this.runGattOperation(() => server.getPrimaryService(normalizeUuid(this.options.serviceUuid)));
|
|
443
|
+
const characteristic = await this.runGattOperation(() =>
|
|
444
|
+
service.getCharacteristic(normalizeUuid(this.options.characteristicUuid)),
|
|
445
|
+
);
|
|
386
446
|
this.characteristic = characteristic;
|
|
387
447
|
characteristic.addEventListener("characteristicvaluechanged", this.handleCharacteristicChanged);
|
|
388
|
-
await characteristic.startNotifications();
|
|
448
|
+
await this.runGattOperation(() => characteristic.startNotifications());
|
|
389
449
|
await this.handshakeAndIdentify();
|
|
390
450
|
this.printerState = "Connected";
|
|
391
451
|
this.sdkCallback?.onConnectSuccess(this.lastAddress, 20);
|
|
@@ -409,7 +469,7 @@ export class WebBluetoothPrinterTransport implements PrinterTransport {
|
|
|
409
469
|
if (this.characteristic) {
|
|
410
470
|
this.characteristic.removeEventListener("characteristicvaluechanged", this.handleCharacteristicChanged);
|
|
411
471
|
try {
|
|
412
|
-
await this.characteristic
|
|
472
|
+
await this.runGattOperation(() => this.characteristic!.stopNotifications());
|
|
413
473
|
} catch {
|
|
414
474
|
// Some browsers reject when the device is already gone.
|
|
415
475
|
}
|
|
@@ -466,6 +526,22 @@ export class WebBluetoothPrinterTransport implements PrinterTransport {
|
|
|
466
526
|
return clone(this.printerInfo);
|
|
467
527
|
}
|
|
468
528
|
|
|
529
|
+
async queryPrinterStatus(timeoutMs = this.options.statusQueryTimeoutMs): Promise<PrinterStatusSnapshot> {
|
|
530
|
+
if (!this.isDeviceConnected()) {
|
|
531
|
+
return this.createLocalStatusSnapshot(false);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (this.currentJob && (this.printerState === "Printing" || this.printerState === "Working")) {
|
|
535
|
+
return this.createLocalStatusSnapshot(true);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return this.queryPhysicalPrinterStatus(timeoutMs);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async canStartPrintJob(timeoutMs = this.options.statusQueryTimeoutMs): Promise<PrinterReadiness> {
|
|
542
|
+
return this.evaluatePrintReadiness(timeoutMs, false);
|
|
543
|
+
}
|
|
544
|
+
|
|
469
545
|
async getPrinterState(): Promise<PrinterState> {
|
|
470
546
|
return this.printerState;
|
|
471
547
|
}
|
|
@@ -549,6 +625,196 @@ export class WebBluetoothPrinterTransport implements PrinterTransport {
|
|
|
549
625
|
this.currentJob?.callback?.onCancelJob(success);
|
|
550
626
|
}
|
|
551
627
|
|
|
628
|
+
private isDeviceConnected(): boolean {
|
|
629
|
+
return !!this.server?.connected && !!this.characteristic;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
private createLocalStatusSnapshot(connected = this.isDeviceConnected()): PrinterStatusSnapshot {
|
|
633
|
+
return {
|
|
634
|
+
connected,
|
|
635
|
+
state: this.printerState,
|
|
636
|
+
statusAvailable: false,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
private async evaluatePrintReadiness(
|
|
641
|
+
timeoutMs: number,
|
|
642
|
+
ignoreCurrentJob: boolean,
|
|
643
|
+
): Promise<PrinterReadiness> {
|
|
644
|
+
if (!this.isDeviceConnected()) {
|
|
645
|
+
return this.createReadiness("not_connected", "Printer is not connected.", false, this.createLocalStatusSnapshot(false));
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (!ignoreCurrentJob && this.currentJob && this.printerState === "Printing") {
|
|
649
|
+
return this.createReadiness(
|
|
650
|
+
"sdk_printing",
|
|
651
|
+
"A print job is already in progress on this Web Bluetooth printer.",
|
|
652
|
+
false,
|
|
653
|
+
this.createLocalStatusSnapshot(true),
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (!ignoreCurrentJob && this.currentJob && this.printerState === "Working") {
|
|
658
|
+
return this.createReadiness(
|
|
659
|
+
"printer_working",
|
|
660
|
+
"Printer is preparing another job.",
|
|
661
|
+
false,
|
|
662
|
+
this.createLocalStatusSnapshot(true),
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const status = await this.queryPhysicalPrinterStatus(timeoutMs);
|
|
667
|
+
const warnings = this.collectReadinessWarnings(status);
|
|
668
|
+
|
|
669
|
+
if (!status.statusAvailable) {
|
|
670
|
+
return this.createReadiness(
|
|
671
|
+
"status_unavailable",
|
|
672
|
+
"Printer is connected, but live hardware status did not respond before the timeout.",
|
|
673
|
+
true,
|
|
674
|
+
status,
|
|
675
|
+
warnings,
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (status.lidClosed === false) {
|
|
680
|
+
return this.createReadiness("cover_open", "Printer cover is open.", false, status, warnings);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (status.paperInserted === false) {
|
|
684
|
+
return this.createReadiness("paper_missing", "Printer has no paper inserted.", false, status, warnings);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (status.voltageState !== undefined && status.voltageState !== 0) {
|
|
688
|
+
return this.createReadiness("voltage_error", "Printer voltage state is abnormal.", false, status, warnings);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
return this.createReadiness("ready", "Printer is ready to receive a print job.", true, status, warnings);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
private createReadiness(
|
|
695
|
+
code: PrinterReadiness["code"],
|
|
696
|
+
reason: string,
|
|
697
|
+
canPrint: boolean,
|
|
698
|
+
status: PrinterStatusSnapshot,
|
|
699
|
+
warnings: PrinterReadinessWarningCode[] = [],
|
|
700
|
+
): PrinterReadiness {
|
|
701
|
+
return {
|
|
702
|
+
canPrint,
|
|
703
|
+
code,
|
|
704
|
+
reason,
|
|
705
|
+
warnings,
|
|
706
|
+
state: status.state,
|
|
707
|
+
connected: status.connected,
|
|
708
|
+
status,
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
private collectReadinessWarnings(status: PrinterStatusSnapshot): PrinterReadinessWarningCode[] {
|
|
713
|
+
const warnings: PrinterReadinessWarningCode[] = [];
|
|
714
|
+
if (status.paperRfidSuccess === false) {
|
|
715
|
+
warnings.push("paper_rfid_failed");
|
|
716
|
+
}
|
|
717
|
+
if (status.ribbonInserted === false) {
|
|
718
|
+
warnings.push("ribbon_missing");
|
|
719
|
+
}
|
|
720
|
+
if (status.ribbonRfidSuccess === false) {
|
|
721
|
+
warnings.push("ribbon_rfid_failed");
|
|
722
|
+
}
|
|
723
|
+
if (status.lightingErrorCode !== undefined && status.lightingErrorCode !== 0) {
|
|
724
|
+
warnings.push("lighting_error");
|
|
725
|
+
}
|
|
726
|
+
return warnings;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
private async queryPhysicalPrinterStatus(timeoutMs: number): Promise<PrinterStatusSnapshot> {
|
|
730
|
+
if (!this.isDeviceConnected()) {
|
|
731
|
+
return this.createLocalStatusSnapshot(false);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const heartbeatType = this.connectedProfile?.protocolVersion !== undefined && this.connectedProfile.protocolVersion < 3
|
|
735
|
+
? HEARTBEAT_ADVANCED_1
|
|
736
|
+
: HEARTBEAT_ADVANCED_2;
|
|
737
|
+
let response = await this.sendCommand(0xdc, [heartbeatType], HEARTBEAT_RESPONSE_COMMANDS, timeoutMs, true);
|
|
738
|
+
if (!response && heartbeatType !== HEARTBEAT_ADVANCED_1) {
|
|
739
|
+
response = await this.sendCommand(0xdc, [HEARTBEAT_ADVANCED_1], HEARTBEAT_RESPONSE_COMMANDS, timeoutMs, true);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if (!response) {
|
|
743
|
+
return this.createLocalStatusSnapshot(true);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
return this.parseHeartbeatStatus(response);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
private parseHeartbeatStatus(response: ParsedResponse): PrinterStatusSnapshot {
|
|
750
|
+
const status: PrinterStatusSnapshot = {
|
|
751
|
+
connected: this.isDeviceConnected(),
|
|
752
|
+
state: this.printerState,
|
|
753
|
+
statusAvailable: false,
|
|
754
|
+
responseCommand: response.cmd,
|
|
755
|
+
rawData: [...response.data],
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
if (response.cmd === 0xd9) {
|
|
759
|
+
this.applyAdvanced2Heartbeat(status, response.data);
|
|
760
|
+
} else if (response.cmd === 0xdd) {
|
|
761
|
+
this.applyAdvanced1Heartbeat(status, response.data);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return status;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
private applyAdvanced2Heartbeat(status: PrinterStatusSnapshot, data: number[]): void {
|
|
768
|
+
if (data.length < 9) {
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
status.chargeLevel = signedByte(data[2]);
|
|
773
|
+
status.temperature = signedByte(data[3]);
|
|
774
|
+
status.lidClosed = signedByte(data[4]) === 0;
|
|
775
|
+
status.paperInserted = signedByte(data[5]) === 0;
|
|
776
|
+
status.paperRfidSuccess = signedByte(data[6]) !== 0;
|
|
777
|
+
status.ribbonRfidSuccess = signedByte(data[7]) !== 0;
|
|
778
|
+
status.ribbonInserted = signedByte(data[8]) === 0;
|
|
779
|
+
if (data.length >= 11) {
|
|
780
|
+
status.wifiRssi = signedWord(data[9], data[10]);
|
|
781
|
+
}
|
|
782
|
+
if (data.length >= 13) {
|
|
783
|
+
status.lightingErrorCode = signedByte(data[12]);
|
|
784
|
+
}
|
|
785
|
+
if (data.length >= 14) {
|
|
786
|
+
status.voltageState = signedByte(data[13]);
|
|
787
|
+
}
|
|
788
|
+
status.statusAvailable = true;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
private applyAdvanced1Heartbeat(status: PrinterStatusSnapshot, data: number[]): void {
|
|
792
|
+
if (data.length === 10) {
|
|
793
|
+
status.lidClosed = signedByte(data[8]) === 0;
|
|
794
|
+
status.chargeLevel = signedByte(data[9]);
|
|
795
|
+
} else if (data.length === 13) {
|
|
796
|
+
status.lidClosed = signedByte(data[9]) === 0;
|
|
797
|
+
status.chargeLevel = signedByte(data[10]);
|
|
798
|
+
status.paperInserted = signedByte(data[11]) === 0;
|
|
799
|
+
status.paperRfidSuccess = signedByte(data[12]) !== 0;
|
|
800
|
+
} else if (data.length === 19) {
|
|
801
|
+
status.lidClosed = signedByte(data[15]) === 0;
|
|
802
|
+
status.chargeLevel = signedByte(data[16]);
|
|
803
|
+
status.paperInserted = signedByte(data[17]) === 0;
|
|
804
|
+
status.paperRfidSuccess = signedByte(data[18]) !== 0;
|
|
805
|
+
} else if (data.length === 20) {
|
|
806
|
+
status.paperInserted = signedByte(data[18]) === 0;
|
|
807
|
+
status.paperRfidSuccess = signedByte(data[19]) !== 0;
|
|
808
|
+
} else {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (status.lidClosed !== undefined && INVERTED_LID_MODEL_IDS.has(this.printerInfo.deviceType)) {
|
|
813
|
+
status.lidClosed = !status.lidClosed;
|
|
814
|
+
}
|
|
815
|
+
status.statusAvailable = true;
|
|
816
|
+
}
|
|
817
|
+
|
|
552
818
|
private handleDisconnect = (): void => {
|
|
553
819
|
this.printerState = "Disconnected";
|
|
554
820
|
this.server = undefined;
|
|
@@ -787,9 +1053,8 @@ export class WebBluetoothPrinterTransport implements PrinterTransport {
|
|
|
787
1053
|
timeoutMs = 2000,
|
|
788
1054
|
optional = false,
|
|
789
1055
|
): Promise<ParsedResponse | null> {
|
|
790
|
-
const wait = expected?.length ? this.waitForCommand(expected, timeoutMs, optional) : Promise.resolve(null);
|
|
791
1056
|
await this.writeBytes(createFrame(cmd, data));
|
|
792
|
-
return
|
|
1057
|
+
return expected?.length ? this.waitForCommand(expected, timeoutMs, optional) : null;
|
|
793
1058
|
}
|
|
794
1059
|
|
|
795
1060
|
private async waitForCommand(
|
|
@@ -835,17 +1100,47 @@ export class WebBluetoothPrinterTransport implements PrinterTransport {
|
|
|
835
1100
|
const payload = new ArrayBuffer(bytes.byteLength);
|
|
836
1101
|
new Uint8Array(payload).set(bytes);
|
|
837
1102
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1103
|
+
await this.runGattOperation(async () => {
|
|
1104
|
+
if (canWriteWithoutResponse) {
|
|
1105
|
+
await characteristic.writeValueWithoutResponse(payload);
|
|
1106
|
+
} else {
|
|
1107
|
+
await characteristic.writeValue(payload);
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
843
1110
|
|
|
844
1111
|
if (paceMs > 0) {
|
|
845
1112
|
await delay(paceMs);
|
|
846
1113
|
}
|
|
847
1114
|
}
|
|
848
1115
|
|
|
1116
|
+
private async runGattOperation<T>(operation: () => Promise<T>): Promise<T> {
|
|
1117
|
+
const run = async (): Promise<T> => {
|
|
1118
|
+
let lastError: unknown;
|
|
1119
|
+
|
|
1120
|
+
for (let attempt = 0; attempt <= DEFAULT_GATT_BUSY_RETRY_COUNT; attempt += 1) {
|
|
1121
|
+
try {
|
|
1122
|
+
return await operation();
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
lastError = error;
|
|
1125
|
+
if (!isGattOperationBusyError(error) || attempt >= DEFAULT_GATT_BUSY_RETRY_COUNT) {
|
|
1126
|
+
throw error;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
await delay(DEFAULT_GATT_BUSY_RETRY_DELAY_MS * (attempt + 1));
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
throw lastError;
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1136
|
+
const queued = this.gattOperationQueue.then(run, run);
|
|
1137
|
+
this.gattOperationQueue = queued.then(
|
|
1138
|
+
() => undefined,
|
|
1139
|
+
() => undefined,
|
|
1140
|
+
);
|
|
1141
|
+
return queued;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
849
1144
|
private normalizeDensity(value: number, task: "v4" | "b1"): number {
|
|
850
1145
|
if (task === "b1") {
|
|
851
1146
|
return Math.max(1, Math.min(5, Math.round(value)));
|