taro-bluetooth-print 2.2.0 → 2.3.0
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 +38 -0
- package/README.md +128 -22
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -6995
- package/dist/index.umd.js +1 -1
- package/dist/types/adapters/AdapterFactory.d.ts +0 -1
- package/dist/types/adapters/AlipayAdapter.d.ts +6 -34
- package/dist/types/adapters/BaiduAdapter.d.ts +6 -34
- package/dist/types/adapters/BaseAdapter.d.ts +112 -1
- package/dist/types/adapters/ByteDanceAdapter.d.ts +6 -34
- package/dist/types/adapters/TaroAdapter.d.ts +6 -34
- package/dist/types/adapters/WebBluetoothAdapter.d.ts +0 -1
- package/dist/types/config/PrinterConfig.d.ts +0 -1
- package/dist/types/core/BluetoothPrinter.d.ts +0 -1
- package/dist/types/drivers/EscPos.d.ts +0 -1
- package/dist/types/drivers/TsplDriver.d.ts +251 -0
- package/dist/types/encoding/gbk-data.d.ts +12 -0
- package/dist/types/encoding/gbk-table.d.ts +5 -1
- package/dist/types/index.d.ts +5 -0
- package/dist/types/plugins/PluginManager.d.ts +87 -0
- package/dist/types/plugins/builtin/LoggingPlugin.d.ts +14 -0
- package/dist/types/plugins/builtin/RetryPlugin.d.ts +18 -0
- package/dist/types/plugins/index.d.ts +7 -0
- package/dist/types/plugins/types.d.ts +97 -0
- package/dist/types/services/CommandBuilder.d.ts +6 -1
- package/dist/types/services/ConnectionManager.d.ts +0 -1
- package/dist/types/services/PrintJobManager.d.ts +6 -2
- package/dist/types/services/interfaces/index.d.ts +0 -1
- package/dist/types/template/TemplateEngine.d.ts +0 -1
- package/package.json +16 -18
- package/src/adapters/AlipayAdapter.ts +8 -314
- package/src/adapters/BaiduAdapter.ts +8 -312
- package/src/adapters/BaseAdapter.ts +366 -0
- package/src/adapters/ByteDanceAdapter.ts +8 -316
- package/src/adapters/TaroAdapter.ts +8 -367
- package/src/core/EventEmitter.ts +9 -6
- package/src/drivers/TsplDriver.ts +417 -0
- package/src/encoding/gbk-data.ts +1911 -0
- package/src/encoding/gbk-table.ts +22 -498
- package/src/index.ts +14 -0
- package/src/plugins/PluginManager.ts +193 -0
- package/src/plugins/builtin/LoggingPlugin.ts +97 -0
- package/src/plugins/builtin/RetryPlugin.ts +109 -0
- package/src/plugins/index.ts +10 -0
- package/src/plugins/types.ts +119 -0
- package/src/preview/PreviewRenderer.ts +7 -1
- package/src/queue/PrintQueue.ts +10 -6
- package/src/services/CommandBuilder.ts +30 -0
- package/src/services/PrintJobManager.ts +51 -35
|
@@ -3,75 +3,19 @@
|
|
|
3
3
|
* Implements the IPrinterAdapter interface for Alipay Mini Program
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import { BaseAdapter } from './BaseAdapter';
|
|
8
|
-
import { BluetoothPrintError, ErrorCode } from '@/errors/BluetoothError';
|
|
6
|
+
import { MiniProgramAdapter, MiniProgramBLEApi } from './BaseAdapter';
|
|
9
7
|
|
|
10
8
|
// Declare Alipay global for TypeScript
|
|
11
|
-
interface
|
|
12
|
-
uuid: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface AlipayBLECharacteristicProperties {
|
|
16
|
-
write?: boolean;
|
|
17
|
-
writeWithoutResponse?: boolean;
|
|
18
|
-
read?: boolean;
|
|
19
|
-
notify?: boolean;
|
|
20
|
-
indicate?: boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface AlipayBLECharacteristic {
|
|
24
|
-
uuid: string;
|
|
25
|
-
properties: AlipayBLECharacteristicProperties;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface AlipayBLEDeviceServicesResult {
|
|
29
|
-
services: AlipayBLEService[];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface AlipayBLEDeviceCharacteristicsResult {
|
|
33
|
-
characteristics: AlipayBLECharacteristic[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface AlipayBLEConnectionStateResult {
|
|
37
|
-
connected: boolean;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface AlipayBLEConnectionOptions {
|
|
41
|
-
deviceId: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface AlipayBLEWriteOptions {
|
|
45
|
-
deviceId: string;
|
|
46
|
-
serviceId: string;
|
|
47
|
-
characteristicId: string;
|
|
48
|
-
value: ArrayBuffer;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
interface AlipayBLEConnectionStateChangeCallback {
|
|
52
|
-
(res: { deviceId: string; connected: boolean }): void;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
interface AlipayGlobal {
|
|
56
|
-
createBLEConnection(options: AlipayBLEConnectionOptions): Promise<void>;
|
|
57
|
-
closeBLEConnection(options: AlipayBLEConnectionOptions): Promise<void>;
|
|
58
|
-
getBLEConnectionState(
|
|
59
|
-
options: AlipayBLEConnectionOptions
|
|
60
|
-
): Promise<AlipayBLEConnectionStateResult>;
|
|
61
|
-
writeBLECharacteristicValue(options: AlipayBLEWriteOptions): Promise<void>;
|
|
62
|
-
getBLEDeviceServices(options: AlipayBLEConnectionOptions): Promise<AlipayBLEDeviceServicesResult>;
|
|
63
|
-
getBLEDeviceCharacteristics(options: {
|
|
64
|
-
deviceId: string;
|
|
65
|
-
serviceId: string;
|
|
66
|
-
}): Promise<AlipayBLEDeviceCharacteristicsResult>;
|
|
67
|
-
onBLEConnectionStateChange(callback: AlipayBLEConnectionStateChangeCallback): void;
|
|
68
|
-
}
|
|
9
|
+
interface AlipayGlobal extends MiniProgramBLEApi {}
|
|
69
10
|
|
|
70
11
|
declare const my: AlipayGlobal;
|
|
71
12
|
|
|
72
13
|
/**
|
|
73
14
|
* Alipay Bluetooth Low Energy adapter
|
|
74
15
|
*
|
|
16
|
+
* Uses the Alipay mini-program's BLE APIs (my.xxx).
|
|
17
|
+
* All connection, write, and service discovery logic is inherited from MiniProgramAdapter.
|
|
18
|
+
*
|
|
75
19
|
* @example
|
|
76
20
|
* ```typescript
|
|
77
21
|
* const adapter = new AlipayAdapter();
|
|
@@ -80,258 +24,8 @@ declare const my: AlipayGlobal;
|
|
|
80
24
|
* await adapter.disconnect('device-id-123');
|
|
81
25
|
* ```
|
|
82
26
|
*/
|
|
83
|
-
export class AlipayAdapter extends
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
*
|
|
87
|
-
* @param deviceId - Bluetooth device ID
|
|
88
|
-
* @throws {BluetoothPrintError} When connection fails
|
|
89
|
-
*/
|
|
90
|
-
async connect(deviceId: string): Promise<void> {
|
|
91
|
-
this.validateDeviceId(deviceId);
|
|
92
|
-
|
|
93
|
-
// Check if already connected
|
|
94
|
-
if (this.isDeviceConnected(deviceId)) {
|
|
95
|
-
this.logger.warn('Device already connected:', deviceId);
|
|
96
|
-
this.updateState(PrinterState.CONNECTED);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
this.updateState(PrinterState.CONNECTING);
|
|
101
|
-
this.logger.debug('Connecting to device:', deviceId);
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
// Add connection timeout handling
|
|
105
|
-
let timeoutId: NodeJS.Timeout | undefined;
|
|
106
|
-
const connectionPromise = my.createBLEConnection({
|
|
107
|
-
deviceId,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
111
|
-
timeoutId = setTimeout(() => {
|
|
112
|
-
reject(new Error('Connection timeout after 10 seconds'));
|
|
113
|
-
}, 10000);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
await Promise.race([connectionPromise, timeoutPromise]);
|
|
117
|
-
if (timeoutId) {
|
|
118
|
-
clearTimeout(timeoutId); // Clear timeout after successful connection
|
|
119
|
-
}
|
|
120
|
-
this.logger.info('BLE connection established');
|
|
121
|
-
|
|
122
|
-
// Discover and cache services
|
|
123
|
-
await this.discoverServices(deviceId);
|
|
124
|
-
|
|
125
|
-
this.updateState(PrinterState.CONNECTED);
|
|
126
|
-
this.logger.info('Device connected successfully');
|
|
127
|
-
|
|
128
|
-
// Listen for connection state changes
|
|
129
|
-
my.onBLEConnectionStateChange((res: { deviceId: string; connected: boolean }) => {
|
|
130
|
-
if (res.deviceId === deviceId && !res.connected) {
|
|
131
|
-
this.logger.warn('Device disconnected unexpectedly');
|
|
132
|
-
this.updateState(PrinterState.DISCONNECTED);
|
|
133
|
-
this.cleanupDevice(deviceId);
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
} catch (error) {
|
|
137
|
-
this.updateState(PrinterState.DISCONNECTED);
|
|
138
|
-
this.logger.error('Connection failed:', error);
|
|
139
|
-
|
|
140
|
-
// Return more specific error codes based on error message
|
|
141
|
-
const errorMessage = (error as Error).message || '';
|
|
142
|
-
if (errorMessage.includes('timeout')) {
|
|
143
|
-
throw new BluetoothPrintError(
|
|
144
|
-
ErrorCode.CONNECTION_TIMEOUT,
|
|
145
|
-
`Connection to device ${deviceId} timed out`,
|
|
146
|
-
error as Error
|
|
147
|
-
);
|
|
148
|
-
} else if (errorMessage.includes('not found')) {
|
|
149
|
-
throw new BluetoothPrintError(
|
|
150
|
-
ErrorCode.DEVICE_NOT_FOUND,
|
|
151
|
-
`Device ${deviceId} not found`,
|
|
152
|
-
error as Error
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
throw new BluetoothPrintError(
|
|
157
|
-
ErrorCode.CONNECTION_FAILED,
|
|
158
|
-
`Failed to connect to device ${deviceId}`,
|
|
159
|
-
error as Error
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Disconnects from a Bluetooth device
|
|
166
|
-
*
|
|
167
|
-
* @param deviceId - Bluetooth device ID
|
|
168
|
-
*/
|
|
169
|
-
async disconnect(deviceId: string): Promise<void> {
|
|
170
|
-
this.validateDeviceId(deviceId);
|
|
171
|
-
this.updateState(PrinterState.DISCONNECTING);
|
|
172
|
-
this.logger.debug('Disconnecting from device:', deviceId);
|
|
173
|
-
|
|
174
|
-
try {
|
|
175
|
-
await my.closeBLEConnection({
|
|
176
|
-
deviceId,
|
|
177
|
-
});
|
|
178
|
-
this.cleanupDevice(deviceId);
|
|
179
|
-
this.updateState(PrinterState.DISCONNECTED);
|
|
180
|
-
this.logger.info('Device disconnected successfully');
|
|
181
|
-
} catch (error) {
|
|
182
|
-
// Ignore error on disconnect, but log it
|
|
183
|
-
this.logger.warn('Disconnect error (ignored):', error);
|
|
184
|
-
this.cleanupDevice(deviceId);
|
|
185
|
-
this.updateState(PrinterState.DISCONNECTED);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Writes data to the Bluetooth device in chunks with retry logic
|
|
191
|
-
*
|
|
192
|
-
* @param deviceId - Bluetooth device ID
|
|
193
|
-
* @param buffer - Data to write as ArrayBuffer
|
|
194
|
-
* @param options - Write options (chunk size, delay, retries)
|
|
195
|
-
* @throws {BluetoothPrintError} When write fails after retries
|
|
196
|
-
*/
|
|
197
|
-
async write(deviceId: string, buffer: ArrayBuffer, options?: IAdapterOptions): Promise<void> {
|
|
198
|
-
this.validateDeviceId(deviceId);
|
|
199
|
-
this.validateBuffer(buffer);
|
|
200
|
-
const serviceInfo = this.getServiceInfo(deviceId);
|
|
201
|
-
const validatedOptions = this.validateOptions(options);
|
|
202
|
-
|
|
203
|
-
// Validate device is still connected
|
|
204
|
-
try {
|
|
205
|
-
const state = await my.getBLEConnectionState({
|
|
206
|
-
deviceId,
|
|
207
|
-
});
|
|
208
|
-
if (!state.connected) {
|
|
209
|
-
this.cleanupDevice(deviceId);
|
|
210
|
-
throw new BluetoothPrintError(ErrorCode.DEVICE_DISCONNECTED, 'Device disconnected');
|
|
211
|
-
}
|
|
212
|
-
} catch (error) {
|
|
213
|
-
this.cleanupDevice(deviceId);
|
|
214
|
-
throw new BluetoothPrintError(
|
|
215
|
-
ErrorCode.DEVICE_DISCONNECTED,
|
|
216
|
-
'Device disconnected',
|
|
217
|
-
error as Error
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const { chunkSize, delay, retries } = validatedOptions;
|
|
222
|
-
const data = new Uint8Array(buffer);
|
|
223
|
-
const totalChunks = Math.ceil(data.length / chunkSize);
|
|
224
|
-
|
|
225
|
-
this.logger.debug(`Writing ${data.length} bytes in ${totalChunks} chunks`);
|
|
226
|
-
|
|
227
|
-
// If no data to write, return directly
|
|
228
|
-
if (data.length === 0) {
|
|
229
|
-
this.logger.warn('No data to write');
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
for (let i = 0; i < data.length; i += chunkSize) {
|
|
234
|
-
const chunk = data.slice(i, i + chunkSize);
|
|
235
|
-
const chunkNum = Math.floor(i / chunkSize) + 1;
|
|
236
|
-
|
|
237
|
-
let attempt = 0;
|
|
238
|
-
while (attempt <= retries) {
|
|
239
|
-
try {
|
|
240
|
-
// Add write timeout handling
|
|
241
|
-
const writePromise = my.writeBLECharacteristicValue({
|
|
242
|
-
deviceId,
|
|
243
|
-
serviceId: serviceInfo.serviceId,
|
|
244
|
-
characteristicId: serviceInfo.characteristicId,
|
|
245
|
-
value: chunk.buffer,
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
249
|
-
setTimeout(() => {
|
|
250
|
-
reject(new Error('Write timeout after 5 seconds'));
|
|
251
|
-
}, 5000);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
await Promise.race([writePromise, timeoutPromise]);
|
|
255
|
-
|
|
256
|
-
this.logger.debug(`Chunk ${chunkNum}/${totalChunks} written successfully`);
|
|
257
|
-
break; // Success
|
|
258
|
-
} catch (error) {
|
|
259
|
-
attempt++;
|
|
260
|
-
if (attempt > retries) {
|
|
261
|
-
this.logger.error(`Chunk ${chunkNum} failed after ${retries} retries`);
|
|
262
|
-
throw new BluetoothPrintError(
|
|
263
|
-
ErrorCode.WRITE_FAILED,
|
|
264
|
-
`Failed to write chunk ${chunkNum}/${totalChunks}`,
|
|
265
|
-
error as Error
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
this.logger.warn(`Chunk ${chunkNum} write failed, retry ${attempt}/${retries}`);
|
|
269
|
-
// Wait before retry
|
|
270
|
-
await new Promise(r => setTimeout(r, delay * 2));
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Small delay to prevent congestion
|
|
275
|
-
if (i + chunkSize < data.length) {
|
|
276
|
-
await new Promise(r => setTimeout(r, delay));
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
this.logger.info(`Successfully wrote ${data.length} bytes`);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Discovers services and characteristics for a device
|
|
285
|
-
* Caches the writeable characteristic for future writes
|
|
286
|
-
*
|
|
287
|
-
* @param deviceId - Bluetooth device ID
|
|
288
|
-
* @throws {BluetoothPrintError} When no writeable characteristic is found
|
|
289
|
-
*/
|
|
290
|
-
private async discoverServices(deviceId: string): Promise<void> {
|
|
291
|
-
this.logger.debug('Discovering services for device:', deviceId);
|
|
292
|
-
|
|
293
|
-
try {
|
|
294
|
-
const services = await my.getBLEDeviceServices({
|
|
295
|
-
deviceId,
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
for (const service of services.services) {
|
|
299
|
-
const chars = await my.getBLEDeviceCharacteristics({
|
|
300
|
-
deviceId,
|
|
301
|
-
serviceId: service.uuid,
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
const writeChar = chars.characteristics.find(
|
|
305
|
-
(c: AlipayBLECharacteristic) => c.properties.write || c.properties.writeWithoutResponse
|
|
306
|
-
);
|
|
307
|
-
|
|
308
|
-
if (writeChar) {
|
|
309
|
-
this.serviceCache.set(deviceId, {
|
|
310
|
-
serviceId: service.uuid,
|
|
311
|
-
characteristicId: writeChar.uuid,
|
|
312
|
-
});
|
|
313
|
-
this.logger.info('Found writeable characteristic:', {
|
|
314
|
-
service: service.uuid,
|
|
315
|
-
characteristic: writeChar.uuid,
|
|
316
|
-
});
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// No writeable characteristic found
|
|
322
|
-
throw new BluetoothPrintError(
|
|
323
|
-
ErrorCode.CHARACTERISTIC_NOT_FOUND,
|
|
324
|
-
'No writeable characteristic found. Make sure the device is a supported printer.'
|
|
325
|
-
);
|
|
326
|
-
} catch (error) {
|
|
327
|
-
if (error instanceof BluetoothPrintError) {
|
|
328
|
-
throw error;
|
|
329
|
-
}
|
|
330
|
-
throw new BluetoothPrintError(
|
|
331
|
-
ErrorCode.SERVICE_DISCOVERY_FAILED,
|
|
332
|
-
'Failed to discover device services',
|
|
333
|
-
error as Error
|
|
334
|
-
);
|
|
335
|
-
}
|
|
27
|
+
export class AlipayAdapter extends MiniProgramAdapter {
|
|
28
|
+
protected getApi(): MiniProgramBLEApi {
|
|
29
|
+
return my;
|
|
336
30
|
}
|
|
337
31
|
}
|
|
@@ -3,73 +3,19 @@
|
|
|
3
3
|
* Implements the IPrinterAdapter interface for Baidu Smart Program
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import { BaseAdapter } from './BaseAdapter';
|
|
8
|
-
import { BluetoothPrintError, ErrorCode } from '@/errors/BluetoothError';
|
|
6
|
+
import { MiniProgramAdapter, MiniProgramBLEApi } from './BaseAdapter';
|
|
9
7
|
|
|
10
8
|
// Declare Baidu global for TypeScript
|
|
11
|
-
interface
|
|
12
|
-
uuid: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface BaiduBLECharacteristicProperties {
|
|
16
|
-
write?: boolean;
|
|
17
|
-
writeWithoutResponse?: boolean;
|
|
18
|
-
read?: boolean;
|
|
19
|
-
notify?: boolean;
|
|
20
|
-
indicate?: boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface BaiduBLECharacteristic {
|
|
24
|
-
uuid: string;
|
|
25
|
-
properties: BaiduBLECharacteristicProperties;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface BaiduBLEDeviceServicesResult {
|
|
29
|
-
services: BaiduBLEService[];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface BaiduBLEDeviceCharacteristicsResult {
|
|
33
|
-
characteristics: BaiduBLECharacteristic[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface BaiduBLEConnectionStateResult {
|
|
37
|
-
connected: boolean;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface BaiduBLEConnectionOptions {
|
|
41
|
-
deviceId: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface BaiduBLEWriteOptions {
|
|
45
|
-
deviceId: string;
|
|
46
|
-
serviceId: string;
|
|
47
|
-
characteristicId: string;
|
|
48
|
-
value: ArrayBuffer;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
interface BaiduBLEConnectionStateChangeCallback {
|
|
52
|
-
(res: { deviceId: string; connected: boolean }): void;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
interface BaiduGlobal {
|
|
56
|
-
createBLEConnection(options: BaiduBLEConnectionOptions): Promise<void>;
|
|
57
|
-
closeBLEConnection(options: BaiduBLEConnectionOptions): Promise<void>;
|
|
58
|
-
getBLEConnectionState(options: BaiduBLEConnectionOptions): Promise<BaiduBLEConnectionStateResult>;
|
|
59
|
-
writeBLECharacteristicValue(options: BaiduBLEWriteOptions): Promise<void>;
|
|
60
|
-
getBLEDeviceServices(options: BaiduBLEConnectionOptions): Promise<BaiduBLEDeviceServicesResult>;
|
|
61
|
-
getBLEDeviceCharacteristics(options: {
|
|
62
|
-
deviceId: string;
|
|
63
|
-
serviceId: string;
|
|
64
|
-
}): Promise<BaiduBLEDeviceCharacteristicsResult>;
|
|
65
|
-
onBLEConnectionStateChange(callback: BaiduBLEConnectionStateChangeCallback): void;
|
|
66
|
-
}
|
|
9
|
+
interface BaiduGlobal extends MiniProgramBLEApi {}
|
|
67
10
|
|
|
68
11
|
declare const swan: BaiduGlobal;
|
|
69
12
|
|
|
70
13
|
/**
|
|
71
14
|
* Baidu Bluetooth Low Energy adapter
|
|
72
15
|
*
|
|
16
|
+
* Uses the Baidu smart program's BLE APIs (swan.xxx).
|
|
17
|
+
* All connection, write, and service discovery logic is inherited from MiniProgramAdapter.
|
|
18
|
+
*
|
|
73
19
|
* @example
|
|
74
20
|
* ```typescript
|
|
75
21
|
* const adapter = new BaiduAdapter();
|
|
@@ -78,258 +24,8 @@ declare const swan: BaiduGlobal;
|
|
|
78
24
|
* await adapter.disconnect('device-id-123');
|
|
79
25
|
* ```
|
|
80
26
|
*/
|
|
81
|
-
export class BaiduAdapter extends
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
*
|
|
85
|
-
* @param deviceId - Bluetooth device ID
|
|
86
|
-
* @throws {BluetoothPrintError} When connection fails
|
|
87
|
-
*/
|
|
88
|
-
async connect(deviceId: string): Promise<void> {
|
|
89
|
-
this.validateDeviceId(deviceId);
|
|
90
|
-
|
|
91
|
-
// Check if already connected
|
|
92
|
-
if (this.isDeviceConnected(deviceId)) {
|
|
93
|
-
this.logger.warn('Device already connected:', deviceId);
|
|
94
|
-
this.updateState(PrinterState.CONNECTED);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
this.updateState(PrinterState.CONNECTING);
|
|
99
|
-
this.logger.debug('Connecting to device:', deviceId);
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
// Add connection timeout handling
|
|
103
|
-
let timeoutId: NodeJS.Timeout | undefined;
|
|
104
|
-
const connectionPromise = swan.createBLEConnection({
|
|
105
|
-
deviceId,
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
109
|
-
timeoutId = setTimeout(() => {
|
|
110
|
-
reject(new Error('Connection timeout after 10 seconds'));
|
|
111
|
-
}, 10000);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
await Promise.race([connectionPromise, timeoutPromise]);
|
|
115
|
-
if (timeoutId) {
|
|
116
|
-
clearTimeout(timeoutId); // Clear timeout after successful connection
|
|
117
|
-
}
|
|
118
|
-
this.logger.info('BLE connection established');
|
|
119
|
-
|
|
120
|
-
// Discover and cache services
|
|
121
|
-
await this.discoverServices(deviceId);
|
|
122
|
-
|
|
123
|
-
this.updateState(PrinterState.CONNECTED);
|
|
124
|
-
this.logger.info('Device connected successfully');
|
|
125
|
-
|
|
126
|
-
// Listen for connection state changes
|
|
127
|
-
swan.onBLEConnectionStateChange((res: { deviceId: string; connected: boolean }) => {
|
|
128
|
-
if (res.deviceId === deviceId && !res.connected) {
|
|
129
|
-
this.logger.warn('Device disconnected unexpectedly');
|
|
130
|
-
this.updateState(PrinterState.DISCONNECTED);
|
|
131
|
-
this.cleanupDevice(deviceId);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
} catch (error) {
|
|
135
|
-
this.updateState(PrinterState.DISCONNECTED);
|
|
136
|
-
this.logger.error('Connection failed:', error);
|
|
137
|
-
|
|
138
|
-
// Return more specific error codes based on error message
|
|
139
|
-
const errorMessage = (error as Error).message || '';
|
|
140
|
-
if (errorMessage.includes('timeout')) {
|
|
141
|
-
throw new BluetoothPrintError(
|
|
142
|
-
ErrorCode.CONNECTION_TIMEOUT,
|
|
143
|
-
`Connection to device ${deviceId} timed out`,
|
|
144
|
-
error as Error
|
|
145
|
-
);
|
|
146
|
-
} else if (errorMessage.includes('not found')) {
|
|
147
|
-
throw new BluetoothPrintError(
|
|
148
|
-
ErrorCode.DEVICE_NOT_FOUND,
|
|
149
|
-
`Device ${deviceId} not found`,
|
|
150
|
-
error as Error
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
throw new BluetoothPrintError(
|
|
155
|
-
ErrorCode.CONNECTION_FAILED,
|
|
156
|
-
`Failed to connect to device ${deviceId}`,
|
|
157
|
-
error as Error
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Disconnects from a Bluetooth device
|
|
164
|
-
*
|
|
165
|
-
* @param deviceId - Bluetooth device ID
|
|
166
|
-
*/
|
|
167
|
-
async disconnect(deviceId: string): Promise<void> {
|
|
168
|
-
this.validateDeviceId(deviceId);
|
|
169
|
-
this.updateState(PrinterState.DISCONNECTING);
|
|
170
|
-
this.logger.debug('Disconnecting from device:', deviceId);
|
|
171
|
-
|
|
172
|
-
try {
|
|
173
|
-
await swan.closeBLEConnection({
|
|
174
|
-
deviceId,
|
|
175
|
-
});
|
|
176
|
-
this.cleanupDevice(deviceId);
|
|
177
|
-
this.updateState(PrinterState.DISCONNECTED);
|
|
178
|
-
this.logger.info('Device disconnected successfully');
|
|
179
|
-
} catch (error) {
|
|
180
|
-
// Ignore error on disconnect, but log it
|
|
181
|
-
this.logger.warn('Disconnect error (ignored):', error);
|
|
182
|
-
this.cleanupDevice(deviceId);
|
|
183
|
-
this.updateState(PrinterState.DISCONNECTED);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Writes data to the Bluetooth device in chunks with retry logic
|
|
189
|
-
*
|
|
190
|
-
* @param deviceId - Bluetooth device ID
|
|
191
|
-
* @param buffer - Data to write as ArrayBuffer
|
|
192
|
-
* @param options - Write options (chunk size, delay, retries)
|
|
193
|
-
* @throws {BluetoothPrintError} When write fails after retries
|
|
194
|
-
*/
|
|
195
|
-
async write(deviceId: string, buffer: ArrayBuffer, options?: IAdapterOptions): Promise<void> {
|
|
196
|
-
this.validateDeviceId(deviceId);
|
|
197
|
-
this.validateBuffer(buffer);
|
|
198
|
-
const serviceInfo = this.getServiceInfo(deviceId);
|
|
199
|
-
const validatedOptions = this.validateOptions(options);
|
|
200
|
-
|
|
201
|
-
// Validate device is still connected
|
|
202
|
-
try {
|
|
203
|
-
const state = await swan.getBLEConnectionState({
|
|
204
|
-
deviceId,
|
|
205
|
-
});
|
|
206
|
-
if (!state.connected) {
|
|
207
|
-
this.cleanupDevice(deviceId);
|
|
208
|
-
throw new BluetoothPrintError(ErrorCode.DEVICE_DISCONNECTED, 'Device disconnected');
|
|
209
|
-
}
|
|
210
|
-
} catch (error) {
|
|
211
|
-
this.cleanupDevice(deviceId);
|
|
212
|
-
throw new BluetoothPrintError(
|
|
213
|
-
ErrorCode.DEVICE_DISCONNECTED,
|
|
214
|
-
'Device disconnected',
|
|
215
|
-
error as Error
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const { chunkSize, delay, retries } = validatedOptions;
|
|
220
|
-
const data = new Uint8Array(buffer);
|
|
221
|
-
const totalChunks = Math.ceil(data.length / chunkSize);
|
|
222
|
-
|
|
223
|
-
this.logger.debug(`Writing ${data.length} bytes in ${totalChunks} chunks`);
|
|
224
|
-
|
|
225
|
-
// If no data to write, return directly
|
|
226
|
-
if (data.length === 0) {
|
|
227
|
-
this.logger.warn('No data to write');
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
for (let i = 0; i < data.length; i += chunkSize) {
|
|
232
|
-
const chunk = data.slice(i, i + chunkSize);
|
|
233
|
-
const chunkNum = Math.floor(i / chunkSize) + 1;
|
|
234
|
-
|
|
235
|
-
let attempt = 0;
|
|
236
|
-
while (attempt <= retries) {
|
|
237
|
-
try {
|
|
238
|
-
// Add write timeout handling
|
|
239
|
-
const writePromise = swan.writeBLECharacteristicValue({
|
|
240
|
-
deviceId,
|
|
241
|
-
serviceId: serviceInfo.serviceId,
|
|
242
|
-
characteristicId: serviceInfo.characteristicId,
|
|
243
|
-
value: chunk.buffer,
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
247
|
-
setTimeout(() => {
|
|
248
|
-
reject(new Error('Write timeout after 5 seconds'));
|
|
249
|
-
}, 5000);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
await Promise.race([writePromise, timeoutPromise]);
|
|
253
|
-
|
|
254
|
-
this.logger.debug(`Chunk ${chunkNum}/${totalChunks} written successfully`);
|
|
255
|
-
break; // Success
|
|
256
|
-
} catch (error) {
|
|
257
|
-
attempt++;
|
|
258
|
-
if (attempt > retries) {
|
|
259
|
-
this.logger.error(`Chunk ${chunkNum} failed after ${retries} retries`);
|
|
260
|
-
throw new BluetoothPrintError(
|
|
261
|
-
ErrorCode.WRITE_FAILED,
|
|
262
|
-
`Failed to write chunk ${chunkNum}/${totalChunks}`,
|
|
263
|
-
error as Error
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
this.logger.warn(`Chunk ${chunkNum} write failed, retry ${attempt}/${retries}`);
|
|
267
|
-
// Wait before retry
|
|
268
|
-
await new Promise(r => setTimeout(r, delay * 2));
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Small delay to prevent congestion
|
|
273
|
-
if (i + chunkSize < data.length) {
|
|
274
|
-
await new Promise(r => setTimeout(r, delay));
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
this.logger.info(`Successfully wrote ${data.length} bytes`);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Discovers services and characteristics for a device
|
|
283
|
-
* Caches the writeable characteristic for future writes
|
|
284
|
-
*
|
|
285
|
-
* @param deviceId - Bluetooth device ID
|
|
286
|
-
* @throws {BluetoothPrintError} When no writeable characteristic is found
|
|
287
|
-
*/
|
|
288
|
-
private async discoverServices(deviceId: string): Promise<void> {
|
|
289
|
-
this.logger.debug('Discovering services for device:', deviceId);
|
|
290
|
-
|
|
291
|
-
try {
|
|
292
|
-
const services = await swan.getBLEDeviceServices({
|
|
293
|
-
deviceId,
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
for (const service of services.services) {
|
|
297
|
-
const chars = await swan.getBLEDeviceCharacteristics({
|
|
298
|
-
deviceId,
|
|
299
|
-
serviceId: service.uuid,
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
const writeChar = chars.characteristics.find(
|
|
303
|
-
(c: BaiduBLECharacteristic) => c.properties.write || c.properties.writeWithoutResponse
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
if (writeChar) {
|
|
307
|
-
this.serviceCache.set(deviceId, {
|
|
308
|
-
serviceId: service.uuid,
|
|
309
|
-
characteristicId: writeChar.uuid,
|
|
310
|
-
});
|
|
311
|
-
this.logger.info('Found writeable characteristic:', {
|
|
312
|
-
service: service.uuid,
|
|
313
|
-
characteristic: writeChar.uuid,
|
|
314
|
-
});
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// No writeable characteristic found
|
|
320
|
-
throw new BluetoothPrintError(
|
|
321
|
-
ErrorCode.CHARACTERISTIC_NOT_FOUND,
|
|
322
|
-
'No writeable characteristic found. Make sure the device is a supported printer.'
|
|
323
|
-
);
|
|
324
|
-
} catch (error) {
|
|
325
|
-
if (error instanceof BluetoothPrintError) {
|
|
326
|
-
throw error;
|
|
327
|
-
}
|
|
328
|
-
throw new BluetoothPrintError(
|
|
329
|
-
ErrorCode.SERVICE_DISCOVERY_FAILED,
|
|
330
|
-
'Failed to discover device services',
|
|
331
|
-
error as Error
|
|
332
|
-
);
|
|
333
|
-
}
|
|
27
|
+
export class BaiduAdapter extends MiniProgramAdapter {
|
|
28
|
+
protected getApi(): MiniProgramBLEApi {
|
|
29
|
+
return swan;
|
|
334
30
|
}
|
|
335
31
|
}
|