@sincpro/printer-expo 1.0.4 → 1.0.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/README.md
CHANGED
|
@@ -8,10 +8,10 @@ A powerful React Native module for controlling thermal printers in Expo applicat
|
|
|
8
8
|
|
|
9
9
|
## ✨ Features
|
|
10
10
|
|
|
11
|
-
- 🔗 **
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
11
|
+
- 🔗 **Multiple Connectivity**: Bluetooth, WiFi, and USB support
|
|
12
|
+
- 🖨️ **Advanced Printing**: Text, QR codes, barcodes, images, and PDFs
|
|
13
|
+
- 📋 **Structured Receipts**: Header/body/footer with flexible line types
|
|
14
|
+
- ⚙️ **Configurable**: Margins, density, speed, orientation, auto-cutter
|
|
15
15
|
- 🏗️ **Clean Architecture**: SOLID principles, testable, swappable adapters
|
|
16
16
|
- 📚 **Official SDK**: Integration with Bixolon SDK (extensible to other brands)
|
|
17
17
|
- 📝 **TypeScript**: 100% type-safe API with comprehensive definitions
|
|
@@ -26,6 +26,12 @@ npm install @sincpro/printer-expo
|
|
|
26
26
|
yarn add @sincpro/printer-expo
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
### Requirements
|
|
30
|
+
|
|
31
|
+
- **Expo SDK**: `>=52.0.0`
|
|
32
|
+
- **React Native**: Compatible with Expo SDK
|
|
33
|
+
- **Platform**: Android (iOS not currently supported)
|
|
34
|
+
|
|
29
35
|
### Post-Installation
|
|
30
36
|
|
|
31
37
|
```bash
|
|
@@ -59,84 +65,79 @@ The module requires Bluetooth permissions. Add to your `app.json`:
|
|
|
59
65
|
}
|
|
60
66
|
```
|
|
61
67
|
|
|
62
|
-
**Note**:
|
|
63
|
-
|
|
64
|
-
````
|
|
68
|
+
**Note**: These permissions are required for Bluetooth device discovery and connection on Android 12+.
|
|
65
69
|
|
|
66
70
|
## 🚀 Quick Start
|
|
67
71
|
|
|
68
72
|
### Basic Example
|
|
69
73
|
|
|
70
74
|
```typescript
|
|
71
|
-
import
|
|
72
|
-
|
|
73
|
-
// 1. Check Bluetooth status
|
|
74
|
-
const isEnabled = await Printer.bluetooth.isEnabled();
|
|
75
|
-
const isSupported = await Printer.bluetooth.isSupported();
|
|
75
|
+
import { bluetooth, connection, config, print } from '@sincpro/printer-expo';
|
|
76
76
|
|
|
77
|
-
//
|
|
78
|
-
const
|
|
79
|
-
if (!permStatus.allGranted) {
|
|
80
|
-
console.log('Missing permissions:', permStatus.deniedPermissions);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// 3. Get paired devices
|
|
84
|
-
const devices = await Printer.bluetooth.getPairedDevices();
|
|
77
|
+
// 1. Get paired devices
|
|
78
|
+
const devices = bluetooth.getPairedDevices();
|
|
85
79
|
const printers = devices.filter(d => d.isPrinter);
|
|
86
80
|
|
|
87
|
-
//
|
|
88
|
-
await
|
|
81
|
+
// 2. Connect to printer via Bluetooth
|
|
82
|
+
await connection.connectBluetooth(printers[0].address, 30000); // 30s timeout
|
|
83
|
+
|
|
84
|
+
// 3. Optional: Configure printer
|
|
85
|
+
await config.set({
|
|
86
|
+
marginLeft: 10,
|
|
87
|
+
marginTop: 5,
|
|
88
|
+
density: 'dark',
|
|
89
|
+
speed: 'medium',
|
|
90
|
+
orientation: 'top_to_bottom',
|
|
91
|
+
autoCutter: { enabled: true, fullCut: true }
|
|
92
|
+
});
|
|
89
93
|
|
|
90
|
-
//
|
|
91
|
-
await
|
|
94
|
+
// 4. Print a simple text
|
|
95
|
+
await print.text('Hello World!', {
|
|
96
|
+
fontSize: 'large',
|
|
97
|
+
alignment: 'center',
|
|
98
|
+
bold: true,
|
|
99
|
+
media: { preset: 'continuous80mm' }
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// 5. Print a complete receipt
|
|
103
|
+
await print.receipt({
|
|
92
104
|
header: [
|
|
93
105
|
{ type: 'text', content: 'MY STORE', fontSize: 'large', alignment: 'center', bold: true },
|
|
106
|
+
{ type: 'text', content: '123 Main Street', alignment: 'center' },
|
|
94
107
|
{ type: 'separator' },
|
|
95
108
|
],
|
|
96
|
-
|
|
97
|
-
{ type: 'keyValue', key: 'Product', value: '$10.00' },
|
|
98
|
-
{ type: 'keyValue', key: '
|
|
109
|
+
body: [
|
|
110
|
+
{ type: 'keyValue', key: 'Product 1', value: '$10.00' },
|
|
111
|
+
{ type: 'keyValue', key: 'Product 2', value: '$15.00' },
|
|
112
|
+
{ type: 'separator' },
|
|
113
|
+
{ type: 'keyValue', key: 'TOTAL', value: '$25.00', bold: true },
|
|
99
114
|
],
|
|
100
115
|
footer: [
|
|
101
|
-
{ type: '
|
|
102
|
-
{ type: '
|
|
116
|
+
{ type: 'qr', data: 'https://mystore.com/receipt/123', alignment: 'center' },
|
|
117
|
+
{ type: 'text', content: 'Thank you!', alignment: 'center' },
|
|
103
118
|
{ type: 'space', lines: 2 },
|
|
104
|
-
]
|
|
105
|
-
});
|
|
106
|
-
|
|
119
|
+
]
|
|
120
|
+
}, { media: { preset: 'continuous80mm' } });
|
|
121
|
+
|
|
122
|
+
// 6. Disconnect
|
|
123
|
+
await connection.disconnect();
|
|
124
|
+
```
|
|
107
125
|
|
|
108
126
|
### Complete React Component
|
|
109
127
|
|
|
110
128
|
```typescript
|
|
111
|
-
import React, { useState
|
|
129
|
+
import React, { useState } from 'react';
|
|
112
130
|
import { View, Button, FlatList, Text, Alert } from 'react-native';
|
|
113
|
-
import
|
|
131
|
+
import { bluetooth, connection, print } from '@sincpro/printer-expo';
|
|
132
|
+
import type { BluetoothDevice } from '@sincpro/printer-expo';
|
|
114
133
|
|
|
115
134
|
export default function PrinterScreen() {
|
|
116
135
|
const [devices, setDevices] = useState<BluetoothDevice[]>([]);
|
|
117
136
|
const [connected, setConnected] = useState(false);
|
|
118
137
|
|
|
119
|
-
useEffect(() => {
|
|
120
|
-
checkPermissions();
|
|
121
|
-
}, []);
|
|
122
|
-
|
|
123
|
-
const checkPermissions = () => {
|
|
124
|
-
const status = Printer.permission.getStatus();
|
|
125
|
-
|
|
126
|
-
if (!status.allGranted) {
|
|
127
|
-
Alert.alert('Permissions Required', `Missing: ${status.deniedPermissions.join(', ')}`);
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
|
|
131
138
|
const scanDevices = async () => {
|
|
132
139
|
try {
|
|
133
|
-
const
|
|
134
|
-
if (!isEnabled) {
|
|
135
|
-
Alert.alert('Error', 'Please enable Bluetooth');
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const foundDevices = await Printer.bluetooth.getPairedDevices();
|
|
140
|
+
const foundDevices = bluetooth.getPairedDevices();
|
|
140
141
|
setDevices(foundDevices.filter((d) => d.isPrinter));
|
|
141
142
|
} catch (error) {
|
|
142
143
|
Alert.alert('Error', error.message);
|
|
@@ -145,7 +146,7 @@ export default function PrinterScreen() {
|
|
|
145
146
|
|
|
146
147
|
const connectDevice = async (device: BluetoothDevice) => {
|
|
147
148
|
try {
|
|
148
|
-
await
|
|
149
|
+
await connection.connectBluetooth(device.address, 30000);
|
|
149
150
|
setConnected(true);
|
|
150
151
|
Alert.alert('Success', `Connected to ${device.name}`);
|
|
151
152
|
} catch (error) {
|
|
@@ -155,12 +156,12 @@ export default function PrinterScreen() {
|
|
|
155
156
|
|
|
156
157
|
const printTest = async () => {
|
|
157
158
|
try {
|
|
158
|
-
await
|
|
159
|
+
await print.receipt({
|
|
159
160
|
header: [
|
|
160
161
|
{ type: 'text', content: 'Test Receipt', fontSize: 'large', alignment: 'center' },
|
|
161
162
|
{ type: 'separator' },
|
|
162
163
|
],
|
|
163
|
-
|
|
164
|
+
body: [{ type: 'text', content: 'This is a test print' }],
|
|
164
165
|
footer: [{ type: 'space', lines: 2 }],
|
|
165
166
|
});
|
|
166
167
|
Alert.alert('Success', 'Receipt printed');
|
|
@@ -186,461 +187,313 @@ export default function PrinterScreen() {
|
|
|
186
187
|
}
|
|
187
188
|
```
|
|
188
189
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
## 📱 Detailed Usage
|
|
192
|
-
|
|
193
|
-
### Bluetooth Device Discovery
|
|
194
|
-
|
|
195
|
-
```typescript
|
|
196
|
-
// Check Bluetooth permissions
|
|
197
|
-
const checkPermissions = async () => {
|
|
198
|
-
const permissions = await BixolonPrinter.checkBluetoothPermissions();
|
|
199
|
-
console.log('Permissions:', permissions);
|
|
200
|
-
|
|
201
|
-
// Check if all required permissions are granted
|
|
202
|
-
const allGranted = Object.values(permissions).every((granted) => granted);
|
|
203
|
-
if (!allGranted) {
|
|
204
|
-
await requestPermissions();
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
// Request permissions if needed
|
|
209
|
-
const requestPermissions = async () => {
|
|
210
|
-
try {
|
|
211
|
-
const granted = await BixolonPrinter.requestBluetoothPermissions();
|
|
212
|
-
if (granted) {
|
|
213
|
-
console.log('✅ All permissions granted');
|
|
214
|
-
} else {
|
|
215
|
-
console.log('❌ Some permissions were denied');
|
|
216
|
-
}
|
|
217
|
-
} catch (error) {
|
|
218
|
-
console.error('Permission request failed:', error);
|
|
219
|
-
}
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
// Discover Bluetooth devices
|
|
223
|
-
const discoverDevices = async () => {
|
|
224
|
-
try {
|
|
225
|
-
// Check if Bluetooth is enabled
|
|
226
|
-
const isEnabled = await BixolonPrinter.isBluetoothEnabled();
|
|
227
|
-
if (!isEnabled) {
|
|
228
|
-
Alert.alert('Error', 'Please enable Bluetooth first');
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
190
|
+
---
|
|
231
191
|
|
|
232
|
-
|
|
233
|
-
await BixolonPrinter.startBluetoothDiscovery();
|
|
192
|
+
## 📚 API Reference
|
|
234
193
|
|
|
235
|
-
|
|
236
|
-
const devices = await BixolonPrinter.discoverBluetoothDevices();
|
|
237
|
-
console.log('Found devices:', devices);
|
|
194
|
+
### Connectivity API
|
|
238
195
|
|
|
239
|
-
|
|
240
|
-
const printers = devices.filter((device) => device.isPrinter);
|
|
241
|
-
console.log('Printer devices:', printers);
|
|
242
|
-
} catch (error) {
|
|
243
|
-
console.error('Device discovery failed:', error);
|
|
244
|
-
} finally {
|
|
245
|
-
// Stop discovery
|
|
246
|
-
await BixolonPrinter.stopBluetoothDiscovery();
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
````
|
|
196
|
+
#### `bluetooth.getPairedDevices(): BluetoothDevice[]`
|
|
250
197
|
|
|
251
|
-
|
|
198
|
+
Get all paired/bonded Bluetooth devices (synchronous).
|
|
252
199
|
|
|
253
200
|
```typescript
|
|
254
|
-
|
|
255
|
-
const connectToDevice = async (device: BluetoothDevice) => {
|
|
256
|
-
try {
|
|
257
|
-
console.log(`Connecting to ${device.name} (${device.address})...`);
|
|
258
|
-
|
|
259
|
-
const success = await BixolonPrinter.connectPrinter(
|
|
260
|
-
'BLUETOOTH', // Interface type
|
|
261
|
-
device.address, // Device MAC address
|
|
262
|
-
1 // Port (usually 1 for Bluetooth)
|
|
263
|
-
);
|
|
201
|
+
import { bluetooth } from '@sincpro/printer-expo';
|
|
264
202
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
setIsConnected(true);
|
|
268
|
-
} else {
|
|
269
|
-
console.log('❌ Connection failed');
|
|
270
|
-
}
|
|
271
|
-
} catch (error) {
|
|
272
|
-
console.error('Connection error:', error);
|
|
273
|
-
Alert.alert('Connection Failed', error.message);
|
|
274
|
-
}
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
// Connect via WiFi (if supported)
|
|
278
|
-
const connectViaWiFi = async (ipAddress: string, port: number = 9100) => {
|
|
279
|
-
try {
|
|
280
|
-
const success = await BixolonPrinter.connectPrinter('WIFI', ipAddress, port);
|
|
281
|
-
|
|
282
|
-
if (success) {
|
|
283
|
-
console.log('✅ Connected via WiFi');
|
|
284
|
-
}
|
|
285
|
-
} catch (error) {
|
|
286
|
-
console.error('WiFi connection failed:', error);
|
|
287
|
-
}
|
|
288
|
-
};
|
|
203
|
+
const devices = bluetooth.getPairedDevices();
|
|
204
|
+
const printers = devices.filter(d => d.isPrinter);
|
|
289
205
|
```
|
|
290
206
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
```typescript
|
|
294
|
-
// Print formatted text with custom formatting
|
|
295
|
-
const printFormattedText = async () => {
|
|
296
|
-
try {
|
|
297
|
-
const text = `COMPANY NAME
|
|
298
|
-
123 Main Street
|
|
299
|
-
City, State 12345
|
|
300
|
-
|
|
301
|
-
Invoice #: INV-001
|
|
302
|
-
Date: ${new Date().toLocaleDateString()}
|
|
303
|
-
Customer: John Doe
|
|
207
|
+
#### `bluetooth.getPairedPrinters(): PairedPrinter[]`
|
|
304
208
|
|
|
305
|
-
|
|
306
|
-
- Product A $10.00
|
|
307
|
-
- Product B $15.00
|
|
308
|
-
- Product C $25.00
|
|
209
|
+
Get only paired devices that are printers (synchronous).
|
|
309
210
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const success = await BixolonPrinter.printFormattedText(text, 10);
|
|
315
|
-
if (success) {
|
|
316
|
-
console.log('✅ Formatted text printed');
|
|
317
|
-
}
|
|
318
|
-
} catch (error) {
|
|
319
|
-
console.error('Print error:', error);
|
|
320
|
-
}
|
|
321
|
-
};
|
|
211
|
+
```typescript
|
|
212
|
+
const printers = bluetooth.getPairedPrinters();
|
|
213
|
+
console.log(printers); // [{ name: 'SPP-R200III', address: '00:11:22:AA:BB:CC' }]
|
|
214
|
+
```
|
|
322
215
|
|
|
323
|
-
|
|
324
|
-
const printLongText = async () => {
|
|
325
|
-
try {
|
|
326
|
-
const longText = `This is a very long text that will be split into multiple pages for better printing on thermal printers...`;
|
|
216
|
+
---
|
|
327
217
|
|
|
328
|
-
|
|
329
|
-
if (success) {
|
|
330
|
-
console.log('✅ Long text printed in pages');
|
|
331
|
-
}
|
|
332
|
-
} catch (error) {
|
|
333
|
-
console.error('Page printing error:', error);
|
|
334
|
-
}
|
|
335
|
-
};
|
|
218
|
+
#### `connection.connectBluetooth(address: string, timeoutMs?: number): Promise<void>`
|
|
336
219
|
|
|
337
|
-
|
|
338
|
-
const printInvoice = async () => {
|
|
339
|
-
try {
|
|
340
|
-
const items = [
|
|
341
|
-
{ description: 'Premium Coffee', quantity: 2, price: 4.5 },
|
|
342
|
-
{ description: 'Chocolate Croissant', quantity: 1, price: 3.25 },
|
|
343
|
-
{ description: 'Fresh Orange Juice', quantity: 1, price: 2.75 },
|
|
344
|
-
];
|
|
345
|
-
|
|
346
|
-
const success = await BixolonPrinter.printInvoice(
|
|
347
|
-
'Jane Smith', // Customer name
|
|
348
|
-
items, // Items array
|
|
349
|
-
15.0 // Total amount
|
|
350
|
-
);
|
|
351
|
-
|
|
352
|
-
if (success) {
|
|
353
|
-
console.log('✅ Invoice printed successfully');
|
|
354
|
-
}
|
|
355
|
-
} catch (error) {
|
|
356
|
-
console.error('Invoice printing error:', error);
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
|
-
```
|
|
220
|
+
Connect to a printer via Bluetooth.
|
|
360
221
|
|
|
361
|
-
|
|
222
|
+
**Parameters:**
|
|
223
|
+
- `address`: MAC address of the printer (e.g., `"00:11:22:AA:BB:CC"`)
|
|
224
|
+
- `timeoutMs`: Connection timeout in milliseconds (default: `10000`)
|
|
362
225
|
|
|
363
226
|
```typescript
|
|
364
|
-
|
|
365
|
-
const disconnect = async () => {
|
|
366
|
-
try {
|
|
367
|
-
const success = await BixolonPrinter.disconnectPrinter();
|
|
368
|
-
if (success) {
|
|
369
|
-
console.log('✅ Disconnected from printer');
|
|
370
|
-
setIsConnected(false);
|
|
371
|
-
}
|
|
372
|
-
} catch (error) {
|
|
373
|
-
console.error('Disconnection error:', error);
|
|
374
|
-
}
|
|
375
|
-
};
|
|
227
|
+
import { connection } from '@sincpro/printer-expo';
|
|
376
228
|
|
|
377
|
-
|
|
378
|
-
useEffect(() => {
|
|
379
|
-
return () => {
|
|
380
|
-
if (isConnected) {
|
|
381
|
-
disconnect();
|
|
382
|
-
}
|
|
383
|
-
};
|
|
384
|
-
}, [isConnected]);
|
|
229
|
+
await connection.connectBluetooth('00:11:22:AA:BB:CC', 30000);
|
|
385
230
|
```
|
|
386
231
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
### Creating a Local Package
|
|
232
|
+
#### `connection.connectWifi(ip: string, port?: number, timeoutMs?: number): Promise<void>`
|
|
390
233
|
|
|
391
|
-
|
|
234
|
+
Connect to a printer via WiFi.
|
|
392
235
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
# 2. Create a tarball
|
|
399
|
-
npm pack
|
|
236
|
+
**Parameters:**
|
|
237
|
+
- `ip`: IP address (e.g., `"192.168.1.100"`)
|
|
238
|
+
- `port`: TCP port (default: `9100`)
|
|
239
|
+
- `timeoutMs`: Connection timeout in milliseconds (default: `10000`)
|
|
400
240
|
|
|
401
|
-
|
|
241
|
+
```typescript
|
|
242
|
+
await connection.connectWifi('192.168.1.100', 9100, 30000);
|
|
402
243
|
```
|
|
403
244
|
|
|
404
|
-
|
|
245
|
+
#### `connection.connectUsb(): Promise<void>`
|
|
405
246
|
|
|
406
|
-
|
|
407
|
-
# Method 1: Direct tarball installation
|
|
408
|
-
cd /path/to/your-app
|
|
409
|
-
npm install /path/to/expo-bixolon-0.1.0.tgz
|
|
410
|
-
|
|
411
|
-
# Method 2: Using file path
|
|
412
|
-
npm install /absolute/path/to/expo-bixolon
|
|
247
|
+
Connect to a printer via USB.
|
|
413
248
|
|
|
414
|
-
|
|
415
|
-
|
|
249
|
+
```typescript
|
|
250
|
+
await connection.connectUsb();
|
|
416
251
|
```
|
|
417
252
|
|
|
418
|
-
|
|
253
|
+
#### `connection.disconnect(): Promise<void>`
|
|
419
254
|
|
|
420
|
-
|
|
421
|
-
# Install native dependencies
|
|
422
|
-
npx expo install
|
|
423
|
-
|
|
424
|
-
# Generate native code
|
|
425
|
-
npx expo prebuild
|
|
255
|
+
Disconnect from the current printer.
|
|
426
256
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
# or
|
|
430
|
-
npx expo run:ios
|
|
257
|
+
```typescript
|
|
258
|
+
await connection.disconnect();
|
|
431
259
|
```
|
|
432
260
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
### Namespaces
|
|
261
|
+
#### `connection.isConnected(): boolean`
|
|
436
262
|
|
|
437
|
-
|
|
263
|
+
Check if currently connected (synchronous).
|
|
438
264
|
|
|
439
265
|
```typescript
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
Printer.bluetooth; // Bluetooth operations
|
|
443
|
-
Printer.permission; // Permission management
|
|
444
|
-
Printer.connection; // Connection control
|
|
445
|
-
Printer.print; // Printing operations
|
|
266
|
+
const connected = connection.isConnected();
|
|
446
267
|
```
|
|
447
268
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
### Bluetooth API
|
|
269
|
+
#### `connection.getStatus(): Promise<PrinterStatus>`
|
|
451
270
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
Check if Bluetooth is enabled on the device.
|
|
271
|
+
Get current printer status (paper, cover, errors).
|
|
455
272
|
|
|
456
273
|
```typescript
|
|
457
|
-
const
|
|
274
|
+
const status = await connection.getStatus();
|
|
275
|
+
console.log('Connection:', status.connectionState); // 'CONNECTED' | 'CONNECTING' | 'DISCONNECTED' | 'ERROR'
|
|
276
|
+
console.log('Has paper:', status.hasPaper);
|
|
277
|
+
console.log('Cover open:', status.isCoverOpen);
|
|
278
|
+
console.log('Error:', status.errorMessage);
|
|
458
279
|
```
|
|
459
280
|
|
|
460
|
-
#### `
|
|
281
|
+
#### `connection.getInfo(): Promise<PrinterInfo>`
|
|
461
282
|
|
|
462
|
-
|
|
283
|
+
Get printer information (model, firmware, serial).
|
|
463
284
|
|
|
464
285
|
```typescript
|
|
465
|
-
const
|
|
286
|
+
const info = await connection.getInfo();
|
|
287
|
+
console.log('Model:', info.model);
|
|
288
|
+
console.log('Firmware:', info.firmware);
|
|
289
|
+
console.log('Serial:', info.serial);
|
|
290
|
+
console.log('DPI:', info.dpi);
|
|
466
291
|
```
|
|
467
292
|
|
|
468
|
-
#### `
|
|
293
|
+
#### `connection.getDpi(): number`
|
|
469
294
|
|
|
470
|
-
Get
|
|
295
|
+
Get printer DPI (synchronous).
|
|
471
296
|
|
|
472
297
|
```typescript
|
|
473
|
-
const
|
|
474
|
-
const printers = devices.filter((d) => d.isPrinter);
|
|
298
|
+
const dpi = connection.getDpi(); // e.g., 203 or 300
|
|
475
299
|
```
|
|
476
300
|
|
|
477
|
-
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
### Configuration API
|
|
478
304
|
|
|
479
|
-
|
|
305
|
+
#### `config.set(printerConfig: PrinterConfig): Promise<void>`
|
|
306
|
+
|
|
307
|
+
Set printer configuration (margins, density, speed, orientation, cutter). This sets the default config and applies it immediately if connected.
|
|
480
308
|
|
|
481
309
|
```typescript
|
|
482
|
-
|
|
310
|
+
import { config } from '@sincpro/printer-expo';
|
|
311
|
+
|
|
312
|
+
await config.set({
|
|
313
|
+
marginLeft: 10, // Left margin in dots
|
|
314
|
+
marginTop: 5, // Top margin in dots
|
|
315
|
+
density: 'dark', // 'light' | 'medium' | 'dark' | 'extra_dark'
|
|
316
|
+
speed: 'medium', // 'slow' | 'medium' | 'fast' | 'extra_fast'
|
|
317
|
+
orientation: 'top_to_bottom', // 'top_to_bottom' | 'bottom_to_top'
|
|
318
|
+
autoCutter: {
|
|
319
|
+
enabled: true,
|
|
320
|
+
fullCut: true // true = full cut, false = partial cut
|
|
321
|
+
}
|
|
322
|
+
});
|
|
483
323
|
```
|
|
484
324
|
|
|
485
|
-
#### `
|
|
325
|
+
#### `config.get(): PrinterConfig`
|
|
486
326
|
|
|
487
|
-
|
|
327
|
+
Get current printer configuration (synchronous).
|
|
488
328
|
|
|
489
329
|
```typescript
|
|
490
|
-
|
|
330
|
+
const currentConfig = config.get();
|
|
331
|
+
console.log('Density:', currentConfig.density);
|
|
332
|
+
console.log('Speed:', currentConfig.speed);
|
|
491
333
|
```
|
|
492
334
|
|
|
493
335
|
---
|
|
494
336
|
|
|
495
|
-
###
|
|
337
|
+
### Print API
|
|
338
|
+
|
|
339
|
+
#### `print.text(text: string, options?: PrintTextOptions): Promise<void>`
|
|
496
340
|
|
|
497
|
-
|
|
341
|
+
Print a single line of text.
|
|
498
342
|
|
|
499
|
-
|
|
343
|
+
**Options:**
|
|
344
|
+
- `fontSize`: `'small'` | `'medium'` | `'large'` | `'xlarge'`
|
|
345
|
+
- `alignment`: `'left'` | `'center'` | `'right'`
|
|
346
|
+
- `bold`: `boolean`
|
|
347
|
+
- `media`: `MediaConfig`
|
|
500
348
|
|
|
501
349
|
```typescript
|
|
502
|
-
|
|
350
|
+
import { print } from '@sincpro/printer-expo';
|
|
351
|
+
|
|
352
|
+
await print.text('Hello World!', {
|
|
353
|
+
fontSize: 'large',
|
|
354
|
+
alignment: 'center',
|
|
355
|
+
bold: true,
|
|
356
|
+
media: { preset: 'continuous80mm' }
|
|
357
|
+
});
|
|
503
358
|
```
|
|
504
359
|
|
|
505
|
-
#### `
|
|
360
|
+
#### `print.texts(texts: string[], options?: PrintTextsOptions): Promise<void>`
|
|
506
361
|
|
|
507
|
-
|
|
362
|
+
Print multiple lines of text.
|
|
508
363
|
|
|
509
364
|
```typescript
|
|
510
|
-
|
|
511
|
-
|
|
365
|
+
await print.texts(
|
|
366
|
+
['Line 1', 'Line 2', 'Line 3'],
|
|
367
|
+
{ fontSize: 'medium', media: { preset: 'continuous80mm' } }
|
|
368
|
+
);
|
|
512
369
|
```
|
|
513
370
|
|
|
514
|
-
#### `
|
|
371
|
+
#### `print.qr(data: string, options?: PrintQROptions): Promise<void>`
|
|
372
|
+
|
|
373
|
+
Print a QR code.
|
|
515
374
|
|
|
516
|
-
|
|
375
|
+
**Options:**
|
|
376
|
+
- `size`: QR size 1-10 (default: `5`)
|
|
377
|
+
- `alignment`: `'left'` | `'center'` | `'right'`
|
|
378
|
+
- `media`: `MediaConfig`
|
|
517
379
|
|
|
518
380
|
```typescript
|
|
519
|
-
|
|
520
|
-
|
|
381
|
+
await print.qr('https://sincpro.com', {
|
|
382
|
+
size: 8,
|
|
383
|
+
alignment: 'center',
|
|
384
|
+
media: { preset: 'continuous80mm' }
|
|
385
|
+
});
|
|
521
386
|
```
|
|
522
387
|
|
|
523
|
-
#### `
|
|
388
|
+
#### `print.barcode(data: string, options?: PrintBarcodeOptions): Promise<void>`
|
|
524
389
|
|
|
525
|
-
|
|
390
|
+
Print a barcode.
|
|
391
|
+
|
|
392
|
+
**Options:**
|
|
393
|
+
- `type`: `'CODE128'` | `'CODE39'` | `'EAN13'` | `'EAN8'` | `'UPCA'` | `'UPCE'` | `'CODE93'` | `'CODABAR'`
|
|
394
|
+
- `height`: Barcode height in dots
|
|
395
|
+
- `alignment`: `'left'` | `'center'` | `'right'`
|
|
396
|
+
- `media`: `MediaConfig`
|
|
526
397
|
|
|
527
398
|
```typescript
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
399
|
+
await print.barcode('123456789012', {
|
|
400
|
+
type: 'CODE128',
|
|
401
|
+
height: 80,
|
|
402
|
+
alignment: 'center',
|
|
403
|
+
media: { preset: 'continuous80mm' }
|
|
404
|
+
});
|
|
533
405
|
```
|
|
534
406
|
|
|
535
|
-
|
|
407
|
+
#### `print.imageBase64(base64Data: string, options?: PrintImageOptions): Promise<void>`
|
|
536
408
|
|
|
537
|
-
|
|
409
|
+
Print an image from base64 data.
|
|
538
410
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
**Parameters:**
|
|
544
|
-
|
|
545
|
-
- `address`: MAC address of the printer (e.g., `"00:11:22:AA:BB:CC"`)
|
|
546
|
-
- `port`: TCP port (default: `9100`)
|
|
411
|
+
**Options:**
|
|
412
|
+
- `alignment`: `'left'` | `'center'` | `'right'`
|
|
413
|
+
- `media`: `MediaConfig`
|
|
547
414
|
|
|
548
415
|
```typescript
|
|
549
|
-
await
|
|
416
|
+
await print.imageBase64(base64ImageData, {
|
|
417
|
+
alignment: 'center',
|
|
418
|
+
media: { preset: 'continuous80mm' }
|
|
419
|
+
});
|
|
550
420
|
```
|
|
551
421
|
|
|
552
|
-
#### `
|
|
422
|
+
#### `print.pdfBase64(base64Data: string, options?: PrintPdfOptions): Promise<void>`
|
|
553
423
|
|
|
554
|
-
|
|
424
|
+
Print a PDF page from base64 data.
|
|
425
|
+
|
|
426
|
+
**Options:**
|
|
427
|
+
- `page`: Page number to print (default: `0`)
|
|
428
|
+
- `alignment`: `'left'` | `'center'` | `'right'`
|
|
429
|
+
- `media`: `MediaConfig`
|
|
555
430
|
|
|
556
431
|
```typescript
|
|
557
|
-
await
|
|
432
|
+
await print.pdfBase64(base64PdfData, {
|
|
433
|
+
page: 0,
|
|
434
|
+
alignment: 'center',
|
|
435
|
+
media: { preset: 'continuous80mm' }
|
|
436
|
+
});
|
|
558
437
|
```
|
|
559
438
|
|
|
560
|
-
#### `
|
|
439
|
+
#### `print.getPdfPageCount(base64Data: string): number`
|
|
561
440
|
|
|
562
|
-
Get
|
|
441
|
+
Get page count from a PDF (synchronous).
|
|
563
442
|
|
|
564
443
|
```typescript
|
|
565
|
-
const
|
|
566
|
-
console.log('Address:', info.address);
|
|
567
|
-
console.log('Type:', info.type); // 'BLUETOOTH' | 'WIFI' | 'USB'
|
|
568
|
-
console.log('Status:', info.status); // 'CONNECTED' | 'CONNECTING' | 'DISCONNECTED' | 'ERROR'
|
|
444
|
+
const pageCount = print.getPdfPageCount(base64PdfData);
|
|
569
445
|
```
|
|
570
446
|
|
|
571
|
-
#### `
|
|
447
|
+
#### `print.keyValue(key: string, value: string, options?: PrintKeyValueOptions): Promise<void>`
|
|
572
448
|
|
|
573
|
-
|
|
449
|
+
Print a key-value pair (two columns).
|
|
574
450
|
|
|
575
451
|
```typescript
|
|
576
|
-
|
|
452
|
+
await print.keyValue('Total', '$25.00', {
|
|
453
|
+
fontSize: 'large',
|
|
454
|
+
bold: true,
|
|
455
|
+
media: { preset: 'continuous80mm' }
|
|
456
|
+
});
|
|
577
457
|
```
|
|
578
458
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
### Print API
|
|
582
|
-
|
|
583
|
-
#### `print.receipt(receipt: Receipt): Promise<void>`
|
|
459
|
+
#### `print.receipt(receipt: Receipt, options?: PrintReceiptOptions): Promise<void>`
|
|
584
460
|
|
|
585
|
-
Print a complete receipt with header,
|
|
461
|
+
Print a complete structured receipt with header, body, and footer sections.
|
|
586
462
|
|
|
587
|
-
**
|
|
588
|
-
|
|
589
|
-
- `
|
|
463
|
+
**Options:**
|
|
464
|
+
- `media`: `MediaConfig`
|
|
465
|
+
- `copies`: Number of copies to print (default: `1`)
|
|
590
466
|
|
|
591
467
|
```typescript
|
|
592
|
-
await
|
|
468
|
+
await print.receipt({
|
|
593
469
|
header: [
|
|
594
|
-
{ type: 'text', content: 'STORE
|
|
595
|
-
{ type: 'text', content: '123 Main
|
|
470
|
+
{ type: 'text', content: 'MY STORE', fontSize: 'large', alignment: 'center', bold: true },
|
|
471
|
+
{ type: 'text', content: '123 Main Street', alignment: 'center' },
|
|
596
472
|
{ type: 'separator' },
|
|
597
473
|
],
|
|
598
|
-
|
|
599
|
-
{ type: 'keyValue', key: 'Product
|
|
600
|
-
{ type: 'keyValue', key: 'Product
|
|
601
|
-
{ type: '
|
|
474
|
+
body: [
|
|
475
|
+
{ type: 'keyValue', key: 'Product 1', value: '$10.00' },
|
|
476
|
+
{ type: 'keyValue', key: 'Product 2', value: '$15.00' },
|
|
477
|
+
{ type: 'separator' },
|
|
478
|
+
{ type: 'keyValue', key: 'Tax', value: '$2.50' },
|
|
602
479
|
],
|
|
603
480
|
footer: [
|
|
604
481
|
{ type: 'separator' },
|
|
605
482
|
{ type: 'keyValue', key: 'TOTAL', value: '$27.50', bold: true, fontSize: 'large' },
|
|
606
|
-
{ type: '
|
|
483
|
+
{ type: 'qr', data: 'https://mystore.com/receipt/123', alignment: 'center', size: 6 },
|
|
484
|
+
{ type: 'text', content: 'Thank you for your purchase!', alignment: 'center' },
|
|
607
485
|
{ type: 'space', lines: 2 },
|
|
608
|
-
]
|
|
609
|
-
|
|
610
|
-
});
|
|
611
|
-
```
|
|
612
|
-
|
|
613
|
-
#### `print.lines(lines: ReceiptLine[]): Promise<void>`
|
|
614
|
-
|
|
615
|
-
Print a list of receipt lines without sections.
|
|
616
|
-
|
|
617
|
-
```typescript
|
|
618
|
-
await Printer.print.lines([
|
|
619
|
-
{ type: 'text', content: 'Quick Receipt', alignment: 'center' },
|
|
620
|
-
{ type: 'separator' },
|
|
621
|
-
{ type: 'keyValue', key: 'Item', value: '$5.00' },
|
|
622
|
-
{ type: 'space', lines: 1 },
|
|
623
|
-
]);
|
|
624
|
-
```
|
|
625
|
-
|
|
626
|
-
#### `print.qrCode(data: string, size?: number): Promise<void>`
|
|
627
|
-
|
|
628
|
-
Print a standalone QR code.
|
|
629
|
-
|
|
630
|
-
**Parameters:**
|
|
631
|
-
|
|
632
|
-
- `data`: QR code content (URL, text, etc.)
|
|
633
|
-
- `size`: QR code size 1-10 (default: `5`)
|
|
634
|
-
|
|
635
|
-
```typescript
|
|
636
|
-
await Printer.print.qrCode('https://example.com', 7);
|
|
486
|
+
]
|
|
487
|
+
}, { media: { preset: 'continuous80mm' }, copies: 1 });
|
|
637
488
|
```
|
|
638
489
|
|
|
639
490
|
---
|
|
640
491
|
|
|
641
|
-
|
|
492
|
+
### Receipt Line Types
|
|
642
493
|
|
|
643
|
-
|
|
494
|
+
Receipt lines are the building blocks of structured receipts. Each line type has specific properties.
|
|
495
|
+
|
|
496
|
+
#### `TextLine`
|
|
644
497
|
|
|
645
498
|
Print formatted text with customizable style.
|
|
646
499
|
|
|
@@ -655,87 +508,203 @@ Print formatted text with customizable style.
|
|
|
655
508
|
```
|
|
656
509
|
|
|
657
510
|
**Example:**
|
|
658
|
-
|
|
659
511
|
```typescript
|
|
660
512
|
{ type: 'text', content: 'INVOICE', fontSize: 'xlarge', alignment: 'center', bold: true }
|
|
661
513
|
```
|
|
662
514
|
|
|
663
|
-
|
|
515
|
+
#### `KeyValueLine`
|
|
664
516
|
|
|
665
|
-
Print key-value pairs (common in receipts).
|
|
517
|
+
Print key-value pairs in two columns (common in receipts).
|
|
666
518
|
|
|
667
519
|
```typescript
|
|
668
520
|
{
|
|
669
521
|
type: 'keyValue',
|
|
670
522
|
key: string,
|
|
671
523
|
value: string,
|
|
672
|
-
fontSize?:
|
|
524
|
+
fontSize?: 'small' | 'medium' | 'large' | 'xlarge',
|
|
673
525
|
bold?: boolean
|
|
674
526
|
}
|
|
675
527
|
```
|
|
676
528
|
|
|
677
529
|
**Example:**
|
|
678
|
-
|
|
679
530
|
```typescript
|
|
680
531
|
{ type: 'keyValue', key: 'Subtotal', value: '$25.00' }
|
|
681
|
-
{ type: 'keyValue', key: 'TOTAL', value: '$27.50', bold: true }
|
|
532
|
+
{ type: 'keyValue', key: 'TOTAL', value: '$27.50', bold: true, fontSize: 'large' }
|
|
682
533
|
```
|
|
683
534
|
|
|
684
|
-
|
|
535
|
+
#### `QRLine`
|
|
685
536
|
|
|
686
537
|
Embed QR codes in receipts.
|
|
687
538
|
|
|
688
539
|
```typescript
|
|
689
540
|
{
|
|
690
|
-
type: '
|
|
541
|
+
type: 'qr',
|
|
542
|
+
data: string,
|
|
543
|
+
size?: number, // 1-10, default: 5
|
|
544
|
+
alignment?: 'left' | 'center' | 'right'
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
**Example:**
|
|
549
|
+
```typescript
|
|
550
|
+
{ type: 'qr', data: 'https://store.com/receipt/12345', size: 6, alignment: 'center' }
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
#### `BarcodeLine`
|
|
554
|
+
|
|
555
|
+
Embed barcodes in receipts.
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
{
|
|
559
|
+
type: 'barcode',
|
|
691
560
|
data: string,
|
|
692
|
-
|
|
693
|
-
|
|
561
|
+
barcodeType?: 'CODE128' | 'CODE39' | 'EAN13' | 'EAN8' | 'UPCA' | 'UPCE' | 'CODE93' | 'CODABAR',
|
|
562
|
+
height?: number,
|
|
563
|
+
alignment?: 'left' | 'center' | 'right'
|
|
694
564
|
}
|
|
695
565
|
```
|
|
696
566
|
|
|
697
567
|
**Example:**
|
|
568
|
+
```typescript
|
|
569
|
+
{ type: 'barcode', data: '123456789012', barcodeType: 'EAN13', height: 80, alignment: 'center' }
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
#### `ImageLine`
|
|
698
573
|
|
|
574
|
+
Embed images (base64) in receipts.
|
|
575
|
+
|
|
576
|
+
```typescript
|
|
577
|
+
{
|
|
578
|
+
type: 'image',
|
|
579
|
+
base64: string,
|
|
580
|
+
alignment?: 'left' | 'center' | 'right'
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
**Example:**
|
|
699
585
|
```typescript
|
|
700
|
-
{ type: '
|
|
586
|
+
{ type: 'image', base64: 'iVBORw0KGgoAAAANS...', alignment: 'center' }
|
|
701
587
|
```
|
|
702
588
|
|
|
703
|
-
|
|
589
|
+
#### `SeparatorLine`
|
|
704
590
|
|
|
705
591
|
Print horizontal separator lines.
|
|
706
592
|
|
|
707
593
|
```typescript
|
|
708
594
|
{
|
|
709
595
|
type: 'separator',
|
|
710
|
-
char?: string,
|
|
711
|
-
length?: number
|
|
596
|
+
char?: string, // Character to repeat, default: '-'
|
|
597
|
+
length?: number // Line length in characters, default: 48
|
|
712
598
|
}
|
|
713
599
|
```
|
|
714
600
|
|
|
715
|
-
**
|
|
716
|
-
|
|
601
|
+
**Examples:**
|
|
717
602
|
```typescript
|
|
718
603
|
{ type: 'separator' }
|
|
719
604
|
{ type: 'separator', char: '=', length: 32 }
|
|
720
605
|
```
|
|
721
606
|
|
|
722
|
-
|
|
607
|
+
#### `SpaceLine`
|
|
723
608
|
|
|
724
609
|
Add blank lines for spacing.
|
|
725
610
|
|
|
726
611
|
```typescript
|
|
727
612
|
{
|
|
728
613
|
type: 'space',
|
|
729
|
-
lines?: number
|
|
614
|
+
lines?: number // Number of blank lines, default: 1
|
|
730
615
|
}
|
|
731
616
|
```
|
|
732
617
|
|
|
733
618
|
**Example:**
|
|
734
|
-
|
|
735
619
|
```typescript
|
|
736
620
|
{ type: 'space', lines: 2 }
|
|
737
621
|
```
|
|
738
622
|
|
|
623
|
+
#### `ColumnsLine`
|
|
624
|
+
|
|
625
|
+
Print multiple columns in one row.
|
|
626
|
+
|
|
627
|
+
```typescript
|
|
628
|
+
{
|
|
629
|
+
type: 'columns',
|
|
630
|
+
columns: Array<{
|
|
631
|
+
text: string,
|
|
632
|
+
widthRatio?: number,
|
|
633
|
+
alignment?: 'left' | 'center' | 'right'
|
|
634
|
+
}>,
|
|
635
|
+
fontSize?: 'small' | 'medium' | 'large' | 'xlarge',
|
|
636
|
+
bold?: boolean
|
|
637
|
+
}
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
**Example:**
|
|
641
|
+
```typescript
|
|
642
|
+
{
|
|
643
|
+
type: 'columns',
|
|
644
|
+
columns: [
|
|
645
|
+
{ text: 'Item', widthRatio: 2, alignment: 'left' },
|
|
646
|
+
{ text: 'Qty', widthRatio: 1, alignment: 'center' },
|
|
647
|
+
{ text: 'Price', widthRatio: 1, alignment: 'right' }
|
|
648
|
+
],
|
|
649
|
+
bold: true
|
|
650
|
+
}
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
### MediaConfig - Paper Configuration
|
|
656
|
+
|
|
657
|
+
Configure paper/label dimensions for printing. You can use presets or custom configurations.
|
|
658
|
+
|
|
659
|
+
#### Using Presets
|
|
660
|
+
|
|
661
|
+
```typescript
|
|
662
|
+
// Continuous paper presets
|
|
663
|
+
{ preset: 'continuous58mm' } // 58mm continuous paper
|
|
664
|
+
{ preset: 'continuous72mm' } // 72mm continuous paper
|
|
665
|
+
{ preset: 'continuous80mm' } // 80mm continuous paper (most common)
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
#### Custom Configuration (Millimeters)
|
|
669
|
+
|
|
670
|
+
```typescript
|
|
671
|
+
// Custom continuous paper
|
|
672
|
+
{
|
|
673
|
+
widthMm: 72,
|
|
674
|
+
type: 'continuous'
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Labels with gap
|
|
678
|
+
{
|
|
679
|
+
widthMm: 50,
|
|
680
|
+
heightMm: 30,
|
|
681
|
+
gapMm: 3,
|
|
682
|
+
type: 'gap'
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Labels with black mark
|
|
686
|
+
{
|
|
687
|
+
widthMm: 60,
|
|
688
|
+
heightMm: 40,
|
|
689
|
+
type: 'black_mark'
|
|
690
|
+
}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
#### Custom Configuration (Dots)
|
|
694
|
+
|
|
695
|
+
If you need precise control, you can specify dimensions in dots (based on printer DPI).
|
|
696
|
+
|
|
697
|
+
```typescript
|
|
698
|
+
{
|
|
699
|
+
widthDots: 576, // 72mm at 203 DPI
|
|
700
|
+
heightDots: 240, // 30mm at 203 DPI
|
|
701
|
+
gapDots: 24, // 3mm at 203 DPI
|
|
702
|
+
type: 'gap'
|
|
703
|
+
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
**Note:** The module automatically converts millimeters to dots based on the printer's DPI. Using millimeters is recommended for easier configuration.
|
|
707
|
+
|
|
739
708
|
---
|
|
740
709
|
|
|
741
710
|
## 📦 TypeScript Types
|
|
@@ -744,44 +713,58 @@ Add blank lines for spacing.
|
|
|
744
713
|
|
|
745
714
|
```typescript
|
|
746
715
|
interface BluetoothDevice {
|
|
747
|
-
name: string;
|
|
748
|
-
address: string;
|
|
749
|
-
|
|
750
|
-
isPrinter: boolean;
|
|
716
|
+
name: string; // Device name
|
|
717
|
+
address: string; // MAC address (e.g., "00:11:22:AA:BB:CC")
|
|
718
|
+
isPrinter: boolean; // True if device is identified as a printer
|
|
751
719
|
}
|
|
752
720
|
```
|
|
753
721
|
|
|
754
|
-
###
|
|
722
|
+
### PairedPrinter
|
|
755
723
|
|
|
756
724
|
```typescript
|
|
757
|
-
interface
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
deniedPermissions: string[];
|
|
761
|
-
androidVersion: number;
|
|
725
|
+
interface PairedPrinter {
|
|
726
|
+
name: string; // Printer name
|
|
727
|
+
address: string; // MAC address
|
|
762
728
|
}
|
|
763
729
|
```
|
|
764
730
|
|
|
765
|
-
###
|
|
731
|
+
### PrinterStatus
|
|
766
732
|
|
|
767
733
|
```typescript
|
|
768
|
-
interface
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
734
|
+
interface PrinterStatus {
|
|
735
|
+
connectionState: 'DISCONNECTED' | 'CONNECTING' | 'CONNECTED' | 'ERROR';
|
|
736
|
+
hasPaper: boolean; // True if paper is available
|
|
737
|
+
isCoverOpen: boolean; // True if printer cover is open
|
|
738
|
+
isOverheated: boolean; // True if printer is overheated
|
|
739
|
+
hasError: boolean; // True if printer has an error
|
|
740
|
+
errorMessage: string | null; // Error message if hasError is true
|
|
773
741
|
}
|
|
774
742
|
```
|
|
775
743
|
|
|
776
|
-
###
|
|
744
|
+
### PrinterInfo
|
|
777
745
|
|
|
778
746
|
```typescript
|
|
779
|
-
interface
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
747
|
+
interface PrinterInfo {
|
|
748
|
+
model: string; // Printer model (e.g., "SPP-R200III")
|
|
749
|
+
firmware: string; // Firmware version
|
|
750
|
+
serial: string; // Serial number
|
|
751
|
+
dpi: number; // Printer DPI (e.g., 203 or 300)
|
|
752
|
+
}
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
### PrinterConfig
|
|
756
|
+
|
|
757
|
+
```typescript
|
|
758
|
+
interface PrinterConfig {
|
|
759
|
+
marginLeft?: number; // Left margin in dots
|
|
760
|
+
marginTop?: number; // Top margin in dots
|
|
761
|
+
density?: 'light' | 'medium' | 'dark' | 'extra_dark';
|
|
762
|
+
speed?: 'slow' | 'medium' | 'fast' | 'extra_fast';
|
|
763
|
+
orientation?: 'top_to_bottom' | 'bottom_to_top';
|
|
764
|
+
autoCutter?: {
|
|
765
|
+
enabled: boolean; // Enable auto cutter
|
|
766
|
+
fullCut?: boolean; // Full cut (true) or partial cut (false)
|
|
767
|
+
};
|
|
785
768
|
}
|
|
786
769
|
```
|
|
787
770
|
|
|
@@ -789,128 +772,283 @@ interface Receipt {
|
|
|
789
772
|
|
|
790
773
|
```typescript
|
|
791
774
|
interface MediaConfig {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
775
|
+
// Use preset for common paper sizes
|
|
776
|
+
preset?: 'continuous58mm' | 'continuous72mm' | 'continuous80mm';
|
|
777
|
+
|
|
778
|
+
// Or custom configuration in millimeters
|
|
779
|
+
widthMm?: number; // Paper width in mm
|
|
780
|
+
heightMm?: number; // Label height in mm (for labels)
|
|
781
|
+
gapMm?: number; // Gap size in mm (for labels)
|
|
782
|
+
|
|
783
|
+
// Or custom configuration in dots
|
|
784
|
+
widthDots?: number; // Paper width in dots
|
|
785
|
+
heightDots?: number; // Label height in dots
|
|
786
|
+
gapDots?: number; // Gap size in dots
|
|
787
|
+
|
|
788
|
+
// Media type
|
|
789
|
+
type?: 'continuous' | 'gap' | 'label' | 'black_mark';
|
|
790
|
+
}
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
### Receipt
|
|
794
|
+
|
|
795
|
+
```typescript
|
|
796
|
+
interface Receipt {
|
|
797
|
+
header?: ReceiptLine[]; // Header section (logo, store info)
|
|
798
|
+
body?: ReceiptLine[]; // Body section (items, details)
|
|
799
|
+
footer?: ReceiptLine[]; // Footer section (totals, QR, thanks)
|
|
797
800
|
}
|
|
798
801
|
```
|
|
799
802
|
|
|
803
|
+
### ReceiptLine Types
|
|
804
|
+
|
|
805
|
+
```typescript
|
|
806
|
+
type ReceiptLine =
|
|
807
|
+
| TextLine
|
|
808
|
+
| KeyValueLine
|
|
809
|
+
| QRLine
|
|
810
|
+
| BarcodeLine
|
|
811
|
+
| ImageLine
|
|
812
|
+
| SeparatorLine
|
|
813
|
+
| SpaceLine
|
|
814
|
+
| ColumnsLine;
|
|
815
|
+
```
|
|
816
|
+
|
|
800
817
|
---
|
|
801
818
|
|
|
802
|
-
## 🎯
|
|
819
|
+
## 🎯 Usage Examples
|
|
803
820
|
|
|
804
|
-
###
|
|
821
|
+
### Example 1: Basic Connection and Text Printing
|
|
805
822
|
|
|
806
823
|
```typescript
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
824
|
+
import { bluetooth, connection, print } from '@sincpro/printer-expo';
|
|
825
|
+
|
|
826
|
+
async function basicPrint() {
|
|
827
|
+
try {
|
|
828
|
+
// 1. Get paired printers
|
|
829
|
+
const printers = bluetooth.getPairedPrinters();
|
|
830
|
+
if (printers.length === 0) {
|
|
831
|
+
throw new Error('No paired printers found');
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// 2. Connect to first printer
|
|
835
|
+
await connection.connectBluetooth(printers[0].address, 30000);
|
|
836
|
+
console.log('Connected to', printers[0].name);
|
|
837
|
+
|
|
838
|
+
// 3. Print simple text
|
|
839
|
+
await print.text('Hello from Expo!', {
|
|
840
|
+
fontSize: 'large',
|
|
813
841
|
alignment: 'center',
|
|
814
842
|
bold: true,
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
843
|
+
media: { preset: 'continuous80mm' }
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
// 4. Disconnect
|
|
847
|
+
await connection.disconnect();
|
|
848
|
+
console.log('Print complete!');
|
|
849
|
+
} catch (error) {
|
|
850
|
+
console.error('Print failed:', error);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
### Example 2: Sales Receipt
|
|
856
|
+
|
|
857
|
+
```typescript
|
|
858
|
+
import { connection, print } from '@sincpro/printer-expo';
|
|
859
|
+
|
|
860
|
+
async function printSalesReceipt(items: Array<{ name: string; qty: number; price: number }>) {
|
|
861
|
+
const subtotal = items.reduce((sum, item) => sum + (item.qty * item.price), 0);
|
|
862
|
+
const tax = subtotal * 0.08; // 8% tax
|
|
863
|
+
const total = subtotal + tax;
|
|
864
|
+
|
|
865
|
+
await print.receipt({
|
|
866
|
+
header: [
|
|
867
|
+
{ type: 'text', content: '🏪 MY RETAIL STORE', fontSize: 'xlarge', alignment: 'center', bold: true },
|
|
868
|
+
{ type: 'text', content: '123 Commerce Street', alignment: 'center' },
|
|
869
|
+
{ type: 'text', content: 'Phone: (555) 123-4567', alignment: 'center' },
|
|
870
|
+
{ type: 'separator', char: '=' },
|
|
871
|
+
{ type: 'text', content: 'SALES RECEIPT', fontSize: 'large', alignment: 'center' },
|
|
872
|
+
{ type: 'separator', char: '=' },
|
|
873
|
+
{ type: 'space' },
|
|
874
|
+
{ type: 'keyValue', key: 'Date', value: new Date().toLocaleDateString() },
|
|
875
|
+
{ type: 'keyValue', key: 'Receipt #', value: 'RCP-' + Date.now() },
|
|
876
|
+
{ type: 'space' },
|
|
877
|
+
{ type: 'separator' },
|
|
878
|
+
],
|
|
879
|
+
body: [
|
|
880
|
+
{ type: 'text', content: 'ITEMS', bold: true },
|
|
881
|
+
{ type: 'separator' },
|
|
882
|
+
...items.map(item => ({
|
|
883
|
+
type: 'keyValue' as const,
|
|
884
|
+
key: `${item.name} (x${item.qty})`,
|
|
885
|
+
value: `$${(item.qty * item.price).toFixed(2)}`
|
|
886
|
+
})),
|
|
887
|
+
{ type: 'space' },
|
|
888
|
+
{ type: 'separator' },
|
|
889
|
+
{ type: 'keyValue', key: 'Subtotal', value: `$${subtotal.toFixed(2)}` },
|
|
890
|
+
{ type: 'keyValue', key: 'Tax (8%)', value: `$${tax.toFixed(2)}` },
|
|
891
|
+
],
|
|
892
|
+
footer: [
|
|
893
|
+
{ type: 'separator', char: '=' },
|
|
894
|
+
{ type: 'keyValue', key: 'TOTAL', value: `$${total.toFixed(2)}`, fontSize: 'large', bold: true },
|
|
895
|
+
{ type: 'separator', char: '=' },
|
|
896
|
+
{ type: 'space', lines: 2 },
|
|
897
|
+
{ type: 'text', content: 'Thank you for your purchase!', alignment: 'center' },
|
|
898
|
+
{ type: 'text', content: 'Visit us at www.mystore.com', alignment: 'center' },
|
|
899
|
+
{ type: 'space' },
|
|
900
|
+
{ type: 'qr', data: `https://mystore.com/receipt/${Date.now()}`, size: 6, alignment: 'center' },
|
|
901
|
+
{ type: 'space', lines: 3 },
|
|
902
|
+
]
|
|
903
|
+
}, { media: { preset: 'continuous80mm' } });
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// Usage
|
|
907
|
+
printSalesReceipt([
|
|
908
|
+
{ name: 'Coffee', qty: 2, price: 4.50 },
|
|
909
|
+
{ name: 'Croissant', qty: 1, price: 3.25 },
|
|
910
|
+
{ name: 'Orange Juice', qty: 1, price: 2.75 }
|
|
911
|
+
]);
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
### Example 3: Product Label Printing
|
|
852
915
|
|
|
853
|
-
|
|
916
|
+
```typescript
|
|
917
|
+
import { config, print } from '@sincpro/printer-expo';
|
|
918
|
+
|
|
919
|
+
async function printProductLabel(product: { name: string; sku: string; price: number }) {
|
|
920
|
+
// Configure for label printing
|
|
921
|
+
await config.set({
|
|
922
|
+
marginLeft: 5,
|
|
923
|
+
marginTop: 5,
|
|
924
|
+
density: 'dark',
|
|
925
|
+
speed: 'medium'
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
await print.receipt({
|
|
929
|
+
header: [
|
|
930
|
+
{ type: 'text', content: product.name, fontSize: 'large', alignment: 'center', bold: true },
|
|
931
|
+
{ type: 'space' },
|
|
932
|
+
],
|
|
933
|
+
body: [
|
|
934
|
+
{ type: 'barcode', data: product.sku, barcodeType: 'CODE128', height: 60, alignment: 'center' },
|
|
935
|
+
{ type: 'space' },
|
|
936
|
+
{ type: 'text', content: `SKU: ${product.sku}`, alignment: 'center' },
|
|
937
|
+
],
|
|
938
|
+
footer: [
|
|
939
|
+
{ type: 'space' },
|
|
940
|
+
{ type: 'text', content: `$${product.price.toFixed(2)}`, fontSize: 'xlarge', alignment: 'center', bold: true },
|
|
941
|
+
]
|
|
942
|
+
}, {
|
|
943
|
+
media: {
|
|
944
|
+
widthMm: 50,
|
|
945
|
+
heightMm: 30,
|
|
946
|
+
gapMm: 3,
|
|
947
|
+
type: 'gap'
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// Usage
|
|
953
|
+
printProductLabel({
|
|
954
|
+
name: 'Premium Coffee Beans',
|
|
955
|
+
sku: '1234567890',
|
|
956
|
+
price: 12.99
|
|
957
|
+
});
|
|
854
958
|
```
|
|
855
959
|
|
|
856
|
-
### Error Handling Pattern
|
|
960
|
+
### Example 4: Error Handling Pattern
|
|
857
961
|
|
|
858
962
|
```typescript
|
|
859
|
-
|
|
963
|
+
import { connection, print } from '@sincpro/printer-expo';
|
|
964
|
+
import type { Receipt } from '@sincpro/printer-expo';
|
|
965
|
+
|
|
966
|
+
async function safePrint(receipt: Receipt) {
|
|
860
967
|
try {
|
|
861
968
|
// Check connection
|
|
862
|
-
if (!
|
|
969
|
+
if (!connection.isConnected()) {
|
|
863
970
|
throw new Error('Printer not connected');
|
|
864
971
|
}
|
|
865
972
|
|
|
866
|
-
// Check status
|
|
867
|
-
const status = await
|
|
868
|
-
|
|
869
|
-
|
|
973
|
+
// Check printer status
|
|
974
|
+
const status = await connection.getStatus();
|
|
975
|
+
|
|
976
|
+
if (status.connectionState !== 'CONNECTED') {
|
|
977
|
+
throw new Error(`Printer is ${status.connectionState.toLowerCase()}`);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
if (!status.hasPaper) {
|
|
981
|
+
throw new Error('Printer is out of paper');
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
if (status.isCoverOpen) {
|
|
985
|
+
throw new Error('Printer cover is open');
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
if (status.isOverheated) {
|
|
989
|
+
throw new Error('Printer is overheated');
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
if (status.hasError) {
|
|
993
|
+
throw new Error(`Printer error: ${status.errorMessage}`);
|
|
870
994
|
}
|
|
871
995
|
|
|
872
|
-
// Print
|
|
873
|
-
await
|
|
996
|
+
// Print receipt
|
|
997
|
+
await print.receipt(receipt, { media: { preset: 'continuous80mm' } });
|
|
998
|
+
|
|
874
999
|
console.log('✅ Print successful');
|
|
1000
|
+
return { success: true };
|
|
875
1001
|
} catch (error) {
|
|
876
|
-
console.error('❌ Print failed:', error
|
|
877
|
-
|
|
878
|
-
//
|
|
879
|
-
|
|
1002
|
+
console.error('❌ Print failed:', error);
|
|
1003
|
+
|
|
1004
|
+
// Return user-friendly error
|
|
1005
|
+
return {
|
|
1006
|
+
success: false,
|
|
1007
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
1008
|
+
};
|
|
880
1009
|
}
|
|
881
|
-
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// Usage in React component
|
|
1013
|
+
function PrintButton() {
|
|
1014
|
+
const handlePrint = async () => {
|
|
1015
|
+
const result = await safePrint(myReceipt);
|
|
1016
|
+
|
|
1017
|
+
if (result.success) {
|
|
1018
|
+
Alert.alert('Success', 'Receipt printed successfully');
|
|
1019
|
+
} else {
|
|
1020
|
+
Alert.alert('Print Error', result.error);
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
return <Button title="Print" onPress={handlePrint} />;
|
|
1025
|
+
}
|
|
882
1026
|
```
|
|
883
1027
|
|
|
884
|
-
### Custom Hook for Printer Management
|
|
1028
|
+
### Example 5: Custom Hook for Printer Management
|
|
885
1029
|
|
|
886
1030
|
```typescript
|
|
887
1031
|
import { useState, useEffect } from 'react';
|
|
888
|
-
import
|
|
1032
|
+
import { bluetooth, connection, print } from '@sincpro/printer-expo';
|
|
1033
|
+
import type { BluetoothDevice, PrinterStatus, Receipt } from '@sincpro/printer-expo';
|
|
889
1034
|
|
|
890
1035
|
export function usePrinter() {
|
|
891
1036
|
const [devices, setDevices] = useState<BluetoothDevice[]>([]);
|
|
892
1037
|
const [connected, setConnected] = useState(false);
|
|
893
|
-
const [
|
|
1038
|
+
const [status, setStatus] = useState<PrinterStatus | null>(null);
|
|
894
1039
|
|
|
895
1040
|
useEffect(() => {
|
|
896
1041
|
checkConnection();
|
|
897
1042
|
}, []);
|
|
898
1043
|
|
|
899
1044
|
const checkConnection = () => {
|
|
900
|
-
|
|
901
|
-
setConnected(isConnected);
|
|
1045
|
+
setConnected(connection.isConnected());
|
|
902
1046
|
};
|
|
903
1047
|
|
|
904
|
-
const scanDevices =
|
|
1048
|
+
const scanDevices = () => {
|
|
905
1049
|
try {
|
|
906
|
-
const
|
|
907
|
-
|
|
908
|
-
throw new Error('Bluetooth is disabled');
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
const foundDevices = await Printer.bluetooth.getPairedDevices();
|
|
912
|
-
setDevices(foundDevices.filter((d) => d.isPrinter));
|
|
913
|
-
|
|
1050
|
+
const foundDevices = bluetooth.getPairedDevices();
|
|
1051
|
+
setDevices(foundDevices.filter(d => d.isPrinter));
|
|
914
1052
|
return foundDevices;
|
|
915
1053
|
} catch (error) {
|
|
916
1054
|
console.error('Scan failed:', error);
|
|
@@ -918,14 +1056,14 @@ export function usePrinter() {
|
|
|
918
1056
|
}
|
|
919
1057
|
};
|
|
920
1058
|
|
|
921
|
-
const connect = async (address: string) => {
|
|
1059
|
+
const connect = async (address: string, timeoutMs = 30000) => {
|
|
922
1060
|
try {
|
|
923
|
-
await
|
|
1061
|
+
await connection.connectBluetooth(address, timeoutMs);
|
|
924
1062
|
setConnected(true);
|
|
925
|
-
|
|
926
|
-
const
|
|
927
|
-
|
|
928
|
-
|
|
1063
|
+
|
|
1064
|
+
const printerStatus = await connection.getStatus();
|
|
1065
|
+
setStatus(printerStatus);
|
|
1066
|
+
|
|
929
1067
|
return true;
|
|
930
1068
|
} catch (error) {
|
|
931
1069
|
console.error('Connection failed:', error);
|
|
@@ -935,9 +1073,9 @@ export function usePrinter() {
|
|
|
935
1073
|
|
|
936
1074
|
const disconnect = async () => {
|
|
937
1075
|
try {
|
|
938
|
-
await
|
|
1076
|
+
await connection.disconnect();
|
|
939
1077
|
setConnected(false);
|
|
940
|
-
|
|
1078
|
+
setStatus(null);
|
|
941
1079
|
return true;
|
|
942
1080
|
} catch (error) {
|
|
943
1081
|
console.error('Disconnection failed:', error);
|
|
@@ -950,32 +1088,45 @@ export function usePrinter() {
|
|
|
950
1088
|
throw new Error('Not connected to printer');
|
|
951
1089
|
}
|
|
952
1090
|
|
|
953
|
-
await
|
|
1091
|
+
await print.receipt(receipt, { media: { preset: 'continuous80mm' } });
|
|
1092
|
+
};
|
|
1093
|
+
|
|
1094
|
+
const refreshStatus = async () => {
|
|
1095
|
+
if (connected) {
|
|
1096
|
+
const printerStatus = await connection.getStatus();
|
|
1097
|
+
setStatus(printerStatus);
|
|
1098
|
+
return printerStatus;
|
|
1099
|
+
}
|
|
1100
|
+
return null;
|
|
954
1101
|
};
|
|
955
1102
|
|
|
956
1103
|
return {
|
|
957
1104
|
devices,
|
|
958
1105
|
connected,
|
|
959
|
-
|
|
1106
|
+
status,
|
|
960
1107
|
scanDevices,
|
|
961
1108
|
connect,
|
|
962
1109
|
disconnect,
|
|
963
1110
|
printReceipt,
|
|
1111
|
+
refreshStatus,
|
|
964
1112
|
};
|
|
965
1113
|
}
|
|
966
1114
|
|
|
967
|
-
// Usage
|
|
968
|
-
function
|
|
1115
|
+
// Usage in component
|
|
1116
|
+
function MyPrinterComponent() {
|
|
969
1117
|
const printer = usePrinter();
|
|
970
1118
|
|
|
971
1119
|
return (
|
|
972
1120
|
<View>
|
|
973
|
-
<Button title="Scan" onPress={printer.scanDevices} />
|
|
974
|
-
<Button
|
|
975
|
-
title="Print"
|
|
1121
|
+
<Button title="Scan Printers" onPress={printer.scanDevices} />
|
|
1122
|
+
<Button
|
|
1123
|
+
title="Print Receipt"
|
|
976
1124
|
onPress={() => printer.printReceipt(myReceipt)}
|
|
977
1125
|
disabled={!printer.connected}
|
|
978
1126
|
/>
|
|
1127
|
+
{printer.status && (
|
|
1128
|
+
<Text>Status: {printer.status.connectionState}</Text>
|
|
1129
|
+
)}
|
|
979
1130
|
</View>
|
|
980
1131
|
);
|
|
981
1132
|
}
|
|
@@ -985,106 +1136,276 @@ function MyComponent() {
|
|
|
985
1136
|
|
|
986
1137
|
## 🏗️ Architecture
|
|
987
1138
|
|
|
988
|
-
This module follows **Clean Architecture** with **Hexagonal Architecture** (Ports & Adapters) principles
|
|
1139
|
+
This module follows **Clean Architecture** with **Hexagonal Architecture** (Ports & Adapters) principles for maximum maintainability and extensibility.
|
|
1140
|
+
|
|
1141
|
+
### Architecture Layers
|
|
989
1142
|
|
|
990
1143
|
```
|
|
1144
|
+
┌─────────────────────────────────────────────────────────┐
|
|
1145
|
+
│ Dependencies ALWAYS point inward (toward Domain) │
|
|
1146
|
+
└─────────────────────────────────────────────────────────┘
|
|
1147
|
+
|
|
991
1148
|
TypeScript (React Native)
|
|
992
1149
|
↓
|
|
993
|
-
ENTRYPOINT
|
|
994
|
-
↓
|
|
995
|
-
SERVICE - Use cases & orchestration
|
|
996
|
-
↓
|
|
997
|
-
DOMAIN - Business entities & rules
|
|
1150
|
+
ENTRYPOINT ← Expo Modules API bridge
|
|
998
1151
|
↓
|
|
999
|
-
|
|
1152
|
+
SERVICE ← Use cases & orchestration
|
|
1000
1153
|
↓
|
|
1001
|
-
|
|
1154
|
+
DOMAIN ← Business entities & rules (interfaces)
|
|
1155
|
+
↑
|
|
1156
|
+
┌─────┴─────┐
|
|
1157
|
+
ADAPTER INFRASTRUCTURE
|
|
1158
|
+
(Vendors) (Platform APIs)
|
|
1002
1159
|
```
|
|
1003
1160
|
|
|
1004
|
-
|
|
1161
|
+
### Layer Responsibilities
|
|
1005
1162
|
|
|
1006
|
-
|
|
1163
|
+
| Layer | Purpose | Examples |
|
|
1164
|
+
|-------|---------|----------|
|
|
1165
|
+
| **TypeScript** | React Native API | `bluetooth`, `connection`, `config`, `print` |
|
|
1166
|
+
| **Entrypoint** | Expo ↔ Kotlin bridge | `PrinterModule.kt` |
|
|
1167
|
+
| **Service** | Business logic | `ConnectivityService`, `PrintService` |
|
|
1168
|
+
| **Domain** | Contracts & entities | `IPrinter`, `Receipt`, `MediaConfig` |
|
|
1169
|
+
| **Adapter** | Vendor SDKs | `BixolonPrinterAdapter` |
|
|
1170
|
+
| **Infrastructure** | Platform utilities | `AndroidBluetoothProvider`, `BinaryConverter` |
|
|
1171
|
+
|
|
1172
|
+
### Benefits
|
|
1173
|
+
|
|
1174
|
+
- ✅ **Testable**: Mock adapters and services independently
|
|
1007
1175
|
- ✅ **Maintainable**: Clear separation of concerns
|
|
1008
|
-
- ✅ **Extensible**: Easy to add new printer brands
|
|
1176
|
+
- ✅ **Extensible**: Easy to add new printer brands (Zebra, Epson, Star, etc.)
|
|
1009
1177
|
- ✅ **Swappable**: Change implementations without affecting business logic
|
|
1178
|
+
- ✅ **Framework-independent**: Domain layer has no Android/iOS dependencies
|
|
1179
|
+
|
|
1180
|
+
### Adding New Printer Brands
|
|
1010
1181
|
|
|
1011
|
-
|
|
1182
|
+
The architecture makes it easy to support additional printer brands:
|
|
1183
|
+
|
|
1184
|
+
1. **Create Adapter**: Implement `IPrinter` interface for new vendor SDK
|
|
1185
|
+
2. **Register in SDK**: Add to `SincproPrinterSdk` entry point
|
|
1186
|
+
3. **No changes needed**: Business logic and API remain unchanged
|
|
1187
|
+
|
|
1188
|
+
See [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed architecture documentation.
|
|
1012
1189
|
|
|
1013
1190
|
---
|
|
1014
1191
|
|
|
1015
|
-
##
|
|
1192
|
+
## 🖨️ Supported Printers
|
|
1016
1193
|
|
|
1017
|
-
###
|
|
1194
|
+
### Bixolon Printers
|
|
1018
1195
|
|
|
1019
|
-
**
|
|
1196
|
+
This module currently supports **Bixolon thermal printers** via the official Bixolon SDK.
|
|
1020
1197
|
|
|
1021
|
-
|
|
1198
|
+
#### Tested Models
|
|
1022
1199
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1200
|
+
- ✅ **SPP-R200III** - 2" mobile printer (58mm)
|
|
1201
|
+
- ✅ **SPP-R300** - 3" mobile printer (80mm)
|
|
1202
|
+
- ✅ **SPP-R400** - 4" mobile printer (112mm)
|
|
1203
|
+
- ✅ **SRP-275III** - 3" desktop printer (80mm)
|
|
1204
|
+
- ✅ **SRP-350III** - 3" desktop printer (80mm)
|
|
1205
|
+
- ✅ **SRP-352III** - 3" desktop printer (80mm)
|
|
1206
|
+
|
|
1207
|
+
#### Compatible Models
|
|
1208
|
+
|
|
1209
|
+
The following Bixolon models should work but have not been tested:
|
|
1210
|
+
|
|
1211
|
+
- SPP-R210, SPP-R220, SPP-R310, SPP-R410
|
|
1212
|
+
- SRP-330II, SRP-350plusIII, SRP-380
|
|
1213
|
+
- XD3-40d, XD5-40d
|
|
1214
|
+
- XT5-40, XT5-43
|
|
1215
|
+
|
|
1216
|
+
#### Connectivity Support
|
|
1217
|
+
|
|
1218
|
+
| Connection Type | Status | Notes |
|
|
1219
|
+
|----------------|--------|-------|
|
|
1220
|
+
| **Bluetooth** | ✅ Fully supported | Most common for mobile printers |
|
|
1221
|
+
| **WiFi** | ✅ Supported | For network-connected printers |
|
|
1222
|
+
| **USB** | ⚠️ Limited | Requires USB OTG on Android |
|
|
1223
|
+
|
|
1224
|
+
### Adding Support for Other Brands
|
|
1225
|
+
|
|
1226
|
+
The architecture supports adding other printer brands. To add support:
|
|
1227
|
+
|
|
1228
|
+
1. Implement the `IPrinter` interface in a new adapter
|
|
1229
|
+
2. Integrate the vendor's SDK (Zebra, Epson, Star, etc.)
|
|
1230
|
+
3. Register the adapter in the SDK entry point
|
|
1231
|
+
|
|
1232
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for development guidelines.
|
|
1233
|
+
|
|
1234
|
+
---
|
|
1235
|
+
|
|
1236
|
+
## 🛠️ Troubleshooting
|
|
1030
1237
|
|
|
1031
1238
|
### Connection Issues
|
|
1032
1239
|
|
|
1033
1240
|
**Problem**: Connection fails or times out
|
|
1034
1241
|
|
|
1035
1242
|
**Solutions**:
|
|
1243
|
+
1. Verify Bluetooth is enabled on the device
|
|
1244
|
+
2. Check device is paired: `bluetooth.getPairedDevices()`
|
|
1245
|
+
3. Ensure printer is powered on and in range (< 10 meters)
|
|
1246
|
+
4. Try increasing timeout: `connectBluetooth(address, 60000)` (60 seconds)
|
|
1247
|
+
5. Restart printer and retry connection
|
|
1036
1248
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1249
|
+
**Example:**
|
|
1250
|
+
```typescript
|
|
1251
|
+
try {
|
|
1252
|
+
await connection.connectBluetooth(address, 60000);
|
|
1253
|
+
} catch (error) {
|
|
1254
|
+
console.error('Connection failed:', error);
|
|
1255
|
+
// Try restarting printer
|
|
1256
|
+
}
|
|
1257
|
+
```
|
|
1041
1258
|
|
|
1042
1259
|
### Print Failures
|
|
1043
1260
|
|
|
1044
1261
|
**Problem**: Print command succeeds but nothing prints
|
|
1045
1262
|
|
|
1046
1263
|
**Solutions**:
|
|
1264
|
+
1. Check printer status:
|
|
1265
|
+
```typescript
|
|
1266
|
+
const status = await connection.getStatus();
|
|
1267
|
+
if (!status.hasPaper) console.error('No paper!');
|
|
1268
|
+
if (status.isCoverOpen) console.error('Cover open!');
|
|
1269
|
+
```
|
|
1270
|
+
2. Verify connection: `connection.isConnected()`
|
|
1271
|
+
3. Check media configuration matches paper type
|
|
1272
|
+
4. Ensure printer is not in an error state
|
|
1273
|
+
|
|
1274
|
+
### Paper Not Feeding
|
|
1275
|
+
|
|
1276
|
+
**Problem**: Paper doesn't feed after printing
|
|
1277
|
+
|
|
1278
|
+
**Solutions**:
|
|
1279
|
+
1. Add space lines at the end of receipt:
|
|
1280
|
+
```typescript
|
|
1281
|
+
footer: [
|
|
1282
|
+
// ... other lines
|
|
1283
|
+
{ type: 'space', lines: 3 } // Add extra space
|
|
1284
|
+
]
|
|
1285
|
+
```
|
|
1286
|
+
2. Configure auto-cutter:
|
|
1287
|
+
```typescript
|
|
1288
|
+
await config.set({
|
|
1289
|
+
autoCutter: { enabled: true, fullCut: true }
|
|
1290
|
+
});
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1293
|
+
### Bluetooth Permissions Denied
|
|
1294
|
+
|
|
1295
|
+
**Problem**: "Permission denied" errors
|
|
1296
|
+
|
|
1297
|
+
**Solution**: Ensure permissions are declared in `app.json` and granted at runtime. Android 12+ requires runtime permission grants.
|
|
1298
|
+
|
|
1299
|
+
```json
|
|
1300
|
+
{
|
|
1301
|
+
"expo": {
|
|
1302
|
+
"android": {
|
|
1303
|
+
"permissions": [
|
|
1304
|
+
"android.permission.BLUETOOTH",
|
|
1305
|
+
"android.permission.BLUETOOTH_ADMIN",
|
|
1306
|
+
"android.permission.BLUETOOTH_SCAN",
|
|
1307
|
+
"android.permission.BLUETOOTH_CONNECT"
|
|
1308
|
+
]
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
```
|
|
1313
|
+
|
|
1314
|
+
### Image/QR Not Printing
|
|
1315
|
+
|
|
1316
|
+
**Problem**: Images or QR codes don't appear
|
|
1317
|
+
|
|
1318
|
+
**Solutions**:
|
|
1319
|
+
1. Check base64 data is valid
|
|
1320
|
+
2. Ensure alignment is correct
|
|
1321
|
+
3. Try different QR size (1-10)
|
|
1322
|
+
4. Verify printer supports graphics
|
|
1323
|
+
|
|
1324
|
+
### Module Not Found Error
|
|
1325
|
+
|
|
1326
|
+
**Problem**: `Module "SincproPrinter" not found`
|
|
1047
1327
|
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1328
|
+
**Solutions**:
|
|
1329
|
+
1. Rebuild native modules:
|
|
1330
|
+
```bash
|
|
1331
|
+
npx expo prebuild --clean
|
|
1332
|
+
npx expo run:android
|
|
1333
|
+
```
|
|
1334
|
+
2. Clear cache:
|
|
1335
|
+
```bash
|
|
1336
|
+
npm start -- --clear
|
|
1337
|
+
```
|
|
1052
1338
|
|
|
1053
1339
|
---
|
|
1054
1340
|
|
|
1055
1341
|
## 📖 Resources
|
|
1056
1342
|
|
|
1057
|
-
- **
|
|
1058
|
-
- **
|
|
1059
|
-
- **
|
|
1343
|
+
- **Package**: [NPM Package](https://www.npmjs.com/package/@sincpro/printer-expo)
|
|
1344
|
+
- **Repository**: [GitHub](https://github.com/Sincpro-SRL/sincpro_printer_expo)
|
|
1345
|
+
- **Architecture**: [ARCHITECTURE.md](./ARCHITECTURE.md) - Detailed architecture guide
|
|
1346
|
+
- **Contributing**: [CONTRIBUTING.md](./CONTRIBUTING.md) - Development guidelines
|
|
1060
1347
|
- **Expo Modules**: [Official Documentation](https://docs.expo.dev/modules/overview/)
|
|
1061
|
-
- **Bixolon
|
|
1348
|
+
- **Bixolon**: [Official Website](https://www.bixolon.com/)
|
|
1062
1349
|
|
|
1063
1350
|
---
|
|
1064
1351
|
|
|
1065
1352
|
## 🤝 Contributing
|
|
1066
1353
|
|
|
1067
|
-
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for:
|
|
1354
|
+
We welcome contributions! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for:
|
|
1355
|
+
|
|
1356
|
+
- Development setup and environment
|
|
1357
|
+
- Code standards (ktlint, Prettier, ESLint)
|
|
1358
|
+
- Architecture guidelines and patterns
|
|
1359
|
+
- Git workflow and branch naming
|
|
1360
|
+
- Pull request process and review
|
|
1361
|
+
- Testing requirements
|
|
1362
|
+
|
|
1363
|
+
### Quick Start for Contributors
|
|
1068
1364
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1365
|
+
```bash
|
|
1366
|
+
# Clone the repository
|
|
1367
|
+
git clone https://github.com/Sincpro-SRL/sincpro_printer_expo.git
|
|
1368
|
+
cd sincpro_printer_expo
|
|
1369
|
+
|
|
1370
|
+
# Install dependencies
|
|
1371
|
+
npm install
|
|
1372
|
+
|
|
1373
|
+
# Build TypeScript
|
|
1374
|
+
npm run build
|
|
1375
|
+
|
|
1376
|
+
# Format code
|
|
1377
|
+
npm run format
|
|
1378
|
+
npm run format:kotlin
|
|
1379
|
+
|
|
1380
|
+
# Lint code
|
|
1381
|
+
npm run lint
|
|
1382
|
+
npm run lint:kotlin
|
|
1383
|
+
```
|
|
1074
1384
|
|
|
1075
1385
|
---
|
|
1076
1386
|
|
|
1077
1387
|
## 📄 License
|
|
1078
1388
|
|
|
1079
|
-
MIT License - see [LICENSE](LICENSE) file for details.
|
|
1389
|
+
MIT License - see [LICENSE](./LICENSE) file for details.
|
|
1390
|
+
|
|
1391
|
+
Copyright (c) 2024 Sincpro SRL
|
|
1080
1392
|
|
|
1081
1393
|
---
|
|
1082
1394
|
|
|
1083
1395
|
## 🙏 Acknowledgments
|
|
1084
1396
|
|
|
1085
|
-
- Bixolon for the official printer SDK
|
|
1086
|
-
- Expo team for the Modules API
|
|
1087
|
-
- Contributors and testers
|
|
1397
|
+
- **Bixolon** for the official printer SDK
|
|
1398
|
+
- **Expo team** for the Modules API
|
|
1399
|
+
- **Contributors** and testers who helped improve this module
|
|
1400
|
+
- **Open source community** for inspiration and support
|
|
1401
|
+
|
|
1402
|
+
---
|
|
1403
|
+
|
|
1404
|
+
## 📞 Support
|
|
1405
|
+
|
|
1406
|
+
- **Issues**: [GitHub Issues](https://github.com/Sincpro-SRL/sincpro_printer_expo/issues)
|
|
1407
|
+
- **Discussions**: [GitHub Discussions](https://github.com/Sincpro-SRL/sincpro_printer_expo/discussions)
|
|
1408
|
+
- **Email**: support@sincpro.com
|
|
1088
1409
|
|
|
1089
1410
|
---
|
|
1090
1411
|
|