@crowsgear/escl-protocol-scanner 0.1.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/LICENSE +21 -0
- package/README.md +663 -0
- package/dist/client.d.ts +69 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +320 -0
- package/dist/client.js.map +1 -0
- package/dist/discovery.d.ts +71 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +269 -0
- package/dist/discovery.js.map +1 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +250 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +170 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +63 -0
- package/python/base.py +57 -0
- package/python/escl_backend.py +541 -0
- package/python/escl_main.py +119 -0
- package/scripts/check-python-deps.js +185 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 eSCL Protocol Scanner Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
# @escl-protocol/scanner
|
|
2
|
+
|
|
3
|
+
A comprehensive TypeScript/Node.js library for discovering and communicating with network scanners using the **eSCL (Enhanced Scanner Communication Language)** protocol, which is based on **AirPrint** standards.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔍 **Automatic Scanner Discovery**: Uses mDNS/Bonjour to discover eSCL-compatible scanners on the local network
|
|
8
|
+
- 📡 **HTTP-based Communication**: eSCL protocol built on HTTP for reliable device communication
|
|
9
|
+
- 🎨 **Multiple Color Modes**: Support for Black & White, Grayscale, and Full Color scanning
|
|
10
|
+
- 📊 **Flexible Resolution**: Supports various DPI settings (150, 200, 300, 600 DPI, etc.)
|
|
11
|
+
- 📄 **Multi-source Scanning**: Platen (flatbed) and ADF (Automatic Document Feeder) support
|
|
12
|
+
- 📸 **Image Processing**: Automatic rotation correction and PNG encoding
|
|
13
|
+
- 🔧 **Python Backend**: Uses Python subprocess for reliable mDNS discovery via zeroconf
|
|
14
|
+
- ✨ **Production Ready**: Mature implementation from scanner-net project
|
|
15
|
+
|
|
16
|
+
## Supported Devices
|
|
17
|
+
|
|
18
|
+
Compatible with network scanners from major manufacturers:
|
|
19
|
+
- **Canon**: iR-series MFP devices
|
|
20
|
+
- **HP**: LaserJet MFP devices
|
|
21
|
+
- **Xerox**: WorkCentre series
|
|
22
|
+
- **Ricoh**: MP series
|
|
23
|
+
- **Epson**: WorkForce Pro series
|
|
24
|
+
- **And other AirPrint-compatible MFP devices**
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
### Prerequisites
|
|
29
|
+
- Node.js ≥ 14.0.0
|
|
30
|
+
- Python 3.6+
|
|
31
|
+
- Python packages: `zeroconf`, `pillow`
|
|
32
|
+
|
|
33
|
+
### Step 1: Install Package
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @escl-protocol/scanner
|
|
37
|
+
# or
|
|
38
|
+
yarn add @escl-protocol/scanner
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The postinstall script will:
|
|
42
|
+
1. Check for required Python packages (`zeroconf`, `pillow`)
|
|
43
|
+
2. **Interactively ask** if you want to install missing packages
|
|
44
|
+
3. Install to the Python environment specified by `PYTHON_PATH` (or system python3)
|
|
45
|
+
|
|
46
|
+
### Step 2: Setup Python Environment
|
|
47
|
+
|
|
48
|
+
#### Option A: Using Virtual Environment (Recommended)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# 1. Create virtual environment
|
|
52
|
+
python3 -m venv venv
|
|
53
|
+
|
|
54
|
+
# 2. Get absolute path to Python executable
|
|
55
|
+
# Copy the full path (e.g., /Users/username/project/venv/bin/python3)
|
|
56
|
+
python3 -c "import sys; print(sys.executable)"
|
|
57
|
+
|
|
58
|
+
# 3. Install Python packages
|
|
59
|
+
source venv/bin/activate
|
|
60
|
+
pip install zeroconf pillow
|
|
61
|
+
|
|
62
|
+
# 4. Set PYTHON_PATH using ABSOLUTE path (important!)
|
|
63
|
+
# Use the full path from step 2, e.g.:
|
|
64
|
+
export PYTHON_PATH=/Users/username/project/venv/bin/python3
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
⚠️ **Important**: Always use **absolute path** for `PYTHON_PATH`, not relative paths like `./venv/bin/python3`
|
|
68
|
+
|
|
69
|
+
#### Option B: Using System Python
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Just ensure Python packages are installed
|
|
73
|
+
pip3 install zeroconf pillow
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Runtime Configuration
|
|
77
|
+
|
|
78
|
+
### Using Virtual Environment
|
|
79
|
+
|
|
80
|
+
Set the `PYTHON_PATH` environment variable when running your application:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Option 1: Export before running
|
|
84
|
+
export PYTHON_PATH=./venv/bin/python3
|
|
85
|
+
npm start
|
|
86
|
+
|
|
87
|
+
# Option 2: One-liner
|
|
88
|
+
PYTHON_PATH=./venv/bin/python3 npm start
|
|
89
|
+
|
|
90
|
+
# Option 3: In package.json scripts
|
|
91
|
+
{
|
|
92
|
+
"scripts": {
|
|
93
|
+
"start": "PYTHON_PATH=./venv/bin/python3 electron ."
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Environment-Specific Setup
|
|
99
|
+
|
|
100
|
+
Create separate `.env` files for different environments:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# .env.dev
|
|
104
|
+
PYTHON_PATH=./venv-dev/bin/python3
|
|
105
|
+
|
|
106
|
+
# .env.staging
|
|
107
|
+
PYTHON_PATH=./venv-staging/bin/python3
|
|
108
|
+
|
|
109
|
+
# .env.production
|
|
110
|
+
PYTHON_PATH=/usr/local/bin/python3
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Then activate the appropriate environment:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
source .env.dev
|
|
117
|
+
npm start
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Quick Start
|
|
121
|
+
|
|
122
|
+
### Basic Usage
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { discoverScanners, ESCLClient } from '@escl-protocol/scanner';
|
|
126
|
+
|
|
127
|
+
async function example() {
|
|
128
|
+
// 1. Discover available scanners (5 second discovery window)
|
|
129
|
+
const scanners = await discoverScanners(5000);
|
|
130
|
+
console.log(`Found ${scanners.length} scanners`);
|
|
131
|
+
|
|
132
|
+
if (scanners.length === 0) {
|
|
133
|
+
console.log('No scanners found on network');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 2. Get scanner info
|
|
138
|
+
const scanner = scanners[0];
|
|
139
|
+
console.log(`Scanner: ${scanner.name}`);
|
|
140
|
+
console.log(`Host: ${scanner.host}:${scanner.port}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
example();
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Discover Scanners
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { discoverScanners, ESCLScanner } from '@escl-protocol/scanner';
|
|
150
|
+
|
|
151
|
+
// Quick discovery
|
|
152
|
+
const scanners = await discoverScanners(5000);
|
|
153
|
+
|
|
154
|
+
scanners.forEach(scanner => {
|
|
155
|
+
console.log(`${scanner.name} at ${scanner.host}:${scanner.port}`);
|
|
156
|
+
if (scanner.manufacturer) {
|
|
157
|
+
console.log(` Manufacturer: ${scanner.manufacturer}`);
|
|
158
|
+
}
|
|
159
|
+
if (scanner.model) {
|
|
160
|
+
console.log(` Model: ${scanner.model}`);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Get Scanner Capabilities
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { discoverScanners, ESCLClient } from '@escl-protocol/scanner';
|
|
169
|
+
|
|
170
|
+
const scanners = await discoverScanners(5000);
|
|
171
|
+
const client = new ESCLClient();
|
|
172
|
+
|
|
173
|
+
const capabilities = await client.getCapabilities(scanners[0]);
|
|
174
|
+
if (capabilities) {
|
|
175
|
+
console.log('Supported Resolutions:', capabilities.resolutions);
|
|
176
|
+
console.log('Color Modes:', capabilities.colorModes);
|
|
177
|
+
console.log('Scan Sources:', capabilities.sources);
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Perform a Scan
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { discoverScanners, ESCLClient } from '@escl-protocol/scanner';
|
|
185
|
+
|
|
186
|
+
const scanners = await discoverScanners(5000);
|
|
187
|
+
const client = new ESCLClient();
|
|
188
|
+
|
|
189
|
+
// Create scan job
|
|
190
|
+
const jobId = await client.createScanJob(
|
|
191
|
+
scanners[0],
|
|
192
|
+
300, // DPI (300 DPI)
|
|
193
|
+
'RGB24', // Color mode (Full Color)
|
|
194
|
+
'Platen' // Source (Flatbed)
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
if (!jobId) {
|
|
198
|
+
console.error('Failed to create scan job');
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Poll for completion
|
|
203
|
+
let completed = false;
|
|
204
|
+
let attempts = 0;
|
|
205
|
+
|
|
206
|
+
while (!completed && attempts < 30) {
|
|
207
|
+
const status = await client.getScanJobStatus(scanners[0], jobId);
|
|
208
|
+
|
|
209
|
+
if (status.status === 'Completed') {
|
|
210
|
+
// Download images
|
|
211
|
+
for (const imageUrl of status.images) {
|
|
212
|
+
const imageBuffer = await client.downloadImage(scanners[0], imageUrl);
|
|
213
|
+
if (imageBuffer) {
|
|
214
|
+
console.log('Downloaded image:', imageBuffer.length, 'bytes');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
completed = true;
|
|
218
|
+
} else if (status.status === 'Aborted') {
|
|
219
|
+
console.error('Scan was aborted');
|
|
220
|
+
process.exit(1);
|
|
221
|
+
} else {
|
|
222
|
+
console.log(`Scan progress: ${status.status}`);
|
|
223
|
+
// Wait before next poll
|
|
224
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
225
|
+
attempts++;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!completed) {
|
|
230
|
+
console.error('Scan job timeout');
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Quick Scan Helper
|
|
236
|
+
|
|
237
|
+
For simple one-off scans:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import { discoverScanners, quickScan } from '@escl-protocol/scanner';
|
|
241
|
+
|
|
242
|
+
const scanners = await discoverScanners(5000);
|
|
243
|
+
|
|
244
|
+
const images = await quickScan({
|
|
245
|
+
scanner: scanners[0],
|
|
246
|
+
dpi: 300,
|
|
247
|
+
mode: 'color', // 'bw' | 'gray' | 'color'
|
|
248
|
+
source: 'Platen', // 'Platen' | 'Feeder'
|
|
249
|
+
timeout: 30000
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
if (images) {
|
|
253
|
+
console.log(`Scanned ${images.length} images`);
|
|
254
|
+
// images are base64-encoded PNG data
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Choosing Your Scanning Approach
|
|
259
|
+
|
|
260
|
+
The library provides two different ways to perform scans, each with different use cases:
|
|
261
|
+
|
|
262
|
+
### Low-Level API: `client.createScanJob()`
|
|
263
|
+
|
|
264
|
+
**Use this when you need:**
|
|
265
|
+
- Fine-grained control over the scanning process
|
|
266
|
+
- Custom polling intervals or timeout logic
|
|
267
|
+
- To handle batch scanning with specific error recovery
|
|
268
|
+
- Advanced features like job cancellation
|
|
269
|
+
|
|
270
|
+
**How it works:**
|
|
271
|
+
1. Create scan job with parameters → returns Job ID
|
|
272
|
+
2. Poll `getScanJobStatus()` to check completion
|
|
273
|
+
3. Download each image with `downloadImage()`
|
|
274
|
+
4. Manually manage job lifecycle
|
|
275
|
+
|
|
276
|
+
**Example:**
|
|
277
|
+
```typescript
|
|
278
|
+
// Step 1: Create job
|
|
279
|
+
const jobId = await client.createScanJob(scanner, 300, 'RGB24', 'Platen');
|
|
280
|
+
|
|
281
|
+
// Step 2: Poll for completion with custom logic
|
|
282
|
+
while (!completed) {
|
|
283
|
+
const status = await client.getScanJobStatus(scanner, jobId);
|
|
284
|
+
|
|
285
|
+
if (status.status === 'Completed') {
|
|
286
|
+
// Step 3: Download images
|
|
287
|
+
for (const imageUrl of status.images) {
|
|
288
|
+
const buffer = await client.downloadImage(scanner, imageUrl);
|
|
289
|
+
// Custom processing...
|
|
290
|
+
}
|
|
291
|
+
completed = true;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
await new Promise(r => setTimeout(r, 1000)); // Custom delay
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Pros:**
|
|
299
|
+
- Maximum flexibility and control
|
|
300
|
+
- Custom error handling strategies
|
|
301
|
+
- Can implement custom polling logic
|
|
302
|
+
- Direct access to job status
|
|
303
|
+
|
|
304
|
+
**Cons:**
|
|
305
|
+
- More code to write and maintain
|
|
306
|
+
- More opportunities for bugs
|
|
307
|
+
- Requires manual resource cleanup
|
|
308
|
+
|
|
309
|
+
### High-Level API: `quickScan()`
|
|
310
|
+
|
|
311
|
+
**Use this when you need:**
|
|
312
|
+
- Simple one-shot scanning (most common case)
|
|
313
|
+
- Fast implementation without boilerplate
|
|
314
|
+
- Automatic error handling and cleanup
|
|
315
|
+
- Sensible defaults for typical scanning scenarios
|
|
316
|
+
|
|
317
|
+
**How it works:**
|
|
318
|
+
1. Creates scan job automatically
|
|
319
|
+
2. Waits and polls for completion (~5 second intervals)
|
|
320
|
+
3. Downloads all images
|
|
321
|
+
4. Saves to disk automatically
|
|
322
|
+
5. Cleans up job automatically
|
|
323
|
+
|
|
324
|
+
**Example:**
|
|
325
|
+
```typescript
|
|
326
|
+
const filePaths = await quickScan({
|
|
327
|
+
scanner: scanners[0],
|
|
328
|
+
dpi: 300,
|
|
329
|
+
mode: 'color', // 'bw' | 'gray' | 'color'
|
|
330
|
+
source: 'Platen', // 'Platen' | 'Feeder'
|
|
331
|
+
savePath: './scans' // optional, defaults to cwd()
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
if (filePaths) {
|
|
335
|
+
console.log(`Scanned ${filePaths.length} images`);
|
|
336
|
+
filePaths.forEach(path => console.log(` - ${path}`));
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Pros:**
|
|
341
|
+
- Minimal code required
|
|
342
|
+
- Automatic cleanup on success/failure
|
|
343
|
+
- Returns file paths ready for use
|
|
344
|
+
- Built-in error handling
|
|
345
|
+
- Best for simple scanning tasks
|
|
346
|
+
|
|
347
|
+
**Cons:**
|
|
348
|
+
- Less control over polling
|
|
349
|
+
- Fixed timeout/retry logic
|
|
350
|
+
- Cannot implement custom scanning workflows
|
|
351
|
+
|
|
352
|
+
### Comparison Table
|
|
353
|
+
|
|
354
|
+
| Feature | `createScanJob()` | `quickScan()` |
|
|
355
|
+
|---------|-------------------|---------------|
|
|
356
|
+
| **Typical Use** | Advanced, custom workflows | Simple one-shot scans |
|
|
357
|
+
| **Code Required** | ~30+ lines | ~10 lines |
|
|
358
|
+
| **Control Level** | Full | Limited |
|
|
359
|
+
| **Error Handling** | Manual | Automatic |
|
|
360
|
+
| **Cleanup** | Manual | Automatic |
|
|
361
|
+
| **Polling Logic** | Custom | Built-in (~5s intervals) |
|
|
362
|
+
| **Return Type** | Job ID (string) | File paths (string[]) |
|
|
363
|
+
| **Learning Curve** | Moderate | Easy |
|
|
364
|
+
| **Best For** | Integrations, batch jobs | Desktop apps, CLI tools |
|
|
365
|
+
|
|
366
|
+
### Recommendation
|
|
367
|
+
|
|
368
|
+
**Choose `quickScan()` unless you have specific reasons not to:**
|
|
369
|
+
- It's the recommended approach for most use cases
|
|
370
|
+
- Handles all the complexity automatically
|
|
371
|
+
- Reduces bugs and improves maintainability
|
|
372
|
+
- Perfect for Electron, CLI, and batch applications
|
|
373
|
+
|
|
374
|
+
**Choose `createScanJob()` only if:**
|
|
375
|
+
- You need custom polling behavior
|
|
376
|
+
- Implementing a queue system
|
|
377
|
+
- Building advanced scanning workflows
|
|
378
|
+
- Integrating with custom error handling
|
|
379
|
+
|
|
380
|
+
## API Reference
|
|
381
|
+
|
|
382
|
+
### Types
|
|
383
|
+
|
|
384
|
+
#### `ESCLScanner`
|
|
385
|
+
```typescript
|
|
386
|
+
interface ESCLScanner {
|
|
387
|
+
name: string; // Scanner display name
|
|
388
|
+
host: string; // IP address
|
|
389
|
+
port: number; // HTTP port (usually 80)
|
|
390
|
+
serviceName?: string; // Full mDNS service name
|
|
391
|
+
model?: string; // Device model (if available)
|
|
392
|
+
manufacturer?: string; // Device manufacturer (if available)
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
#### `ESCLCapabilities`
|
|
397
|
+
```typescript
|
|
398
|
+
interface ESCLCapabilities {
|
|
399
|
+
resolutions: number[]; // Available DPI values
|
|
400
|
+
colorModes: ('BlackAndWhite1' | 'Grayscale8' | 'RGB24')[]; // Available color modes
|
|
401
|
+
sources: ('Platen' | 'Adf' | 'Feeder')[]; // Available scan sources
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Classes
|
|
406
|
+
|
|
407
|
+
#### `ESCLClient`
|
|
408
|
+
|
|
409
|
+
Main client for communicating with eSCL scanners.
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
class ESCLClient {
|
|
413
|
+
constructor(timeout?: number);
|
|
414
|
+
|
|
415
|
+
async getCapabilities(scanner: ESCLScanner): Promise<ESCLCapabilities | null>;
|
|
416
|
+
async createScanJob(scanner: ESCLScanner, dpi: number, colorMode: string, source: string): Promise<string | null>;
|
|
417
|
+
async getScanJobStatus(scanner: ESCLScanner, jobId: string): Promise<{ status: string; images: string[] }>;
|
|
418
|
+
async downloadImage(scanner: ESCLScanner, imageUrl: string): Promise<Buffer | null>;
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
#### `ESCLDiscovery`
|
|
423
|
+
|
|
424
|
+
Scanner discovery service using Python subprocess with zeroconf.
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
class ESCLDiscovery {
|
|
428
|
+
constructor(timeout?: number);
|
|
429
|
+
|
|
430
|
+
async startDiscovery(): Promise<ESCLScanner[]>;
|
|
431
|
+
stopDiscovery(): void;
|
|
432
|
+
getScanners(): ESCLScanner[];
|
|
433
|
+
onScannerDiscovered(callback: (scanners: ESCLScanner[]) => void): void;
|
|
434
|
+
offScannerDiscovered(callback: (scanners: ESCLScanner[]) => void): void;
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Functions
|
|
439
|
+
|
|
440
|
+
#### `discoverScanners(timeout: number): Promise<ESCLScanner[]>`
|
|
441
|
+
|
|
442
|
+
Convenience function for quick scanner discovery.
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
const scanners = await discoverScanners(5000); // 5 second discovery
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
#### `quickScan(params): Promise<string[] | null>`
|
|
449
|
+
|
|
450
|
+
Convenience function for simple scan workflow.
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
const images = await quickScan({
|
|
454
|
+
scanner: device,
|
|
455
|
+
dpi: 300,
|
|
456
|
+
mode: 'color',
|
|
457
|
+
source: 'Platen',
|
|
458
|
+
timeout: 30000
|
|
459
|
+
});
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
## Architecture
|
|
463
|
+
|
|
464
|
+
### How It Works
|
|
465
|
+
|
|
466
|
+
1. **Discovery**: JavaScript code spawns a Python subprocess (`escl_main.py`)
|
|
467
|
+
2. **Python Subprocess**: Uses `zeroconf` library to discover eSCL scanners on the network via mDNS
|
|
468
|
+
3. **Communication**: JSON-RPC over stdin/stdout between Node.js and Python
|
|
469
|
+
4. **Image Processing**: Python handles image rotation and PNG encoding
|
|
470
|
+
|
|
471
|
+
### Design Rationale
|
|
472
|
+
|
|
473
|
+
- **Python for Discovery**: `zeroconf` library is more mature and stable than Node.js alternatives
|
|
474
|
+
- **Subprocess Architecture**: Isolates network scanning from main application
|
|
475
|
+
- **JSON-RPC Protocol**: Simple, reliable IPC between Node.js and Python processes
|
|
476
|
+
- **Image Encoding**: Base64 PNG encoding for safe cross-process transmission
|
|
477
|
+
|
|
478
|
+
## Configuration
|
|
479
|
+
|
|
480
|
+
### Environment Variables
|
|
481
|
+
|
|
482
|
+
```bash
|
|
483
|
+
# Python path (if non-standard)
|
|
484
|
+
export PYTHON_PATH=python3
|
|
485
|
+
|
|
486
|
+
# Logging (if implemented)
|
|
487
|
+
export ESCL_DEBUG=1
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Timeouts
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
const client = new ESCLClient(10000); // 10 second HTTP timeout
|
|
494
|
+
const scanners = await discoverScanners(5000); // 5 second discovery window
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
## Troubleshooting
|
|
498
|
+
|
|
499
|
+
### Scanners Not Found
|
|
500
|
+
|
|
501
|
+
1. **Check Python Dependencies**:
|
|
502
|
+
```bash
|
|
503
|
+
pip install zeroconf pillow
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
2. **Verify Network**:
|
|
507
|
+
- Ensure scanner is on same network as computer
|
|
508
|
+
- Check scanner is powered on and connected
|
|
509
|
+
- Verify network is mDNS-enabled (not blocked by firewall)
|
|
510
|
+
|
|
511
|
+
3. **Enable Debug Output**:
|
|
512
|
+
```typescript
|
|
513
|
+
import { ESCLDiscovery } from '@escl-protocol/scanner';
|
|
514
|
+
const discovery = new ESCLDiscovery();
|
|
515
|
+
// Check stderr output for Python errors
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Connection Refused
|
|
519
|
+
|
|
520
|
+
1. Verify scanner IP and port (usually port 80)
|
|
521
|
+
2. Ensure firewall allows HTTP access to scanner
|
|
522
|
+
3. Check scanner supports eSCL protocol (AirPrint-compatible)
|
|
523
|
+
|
|
524
|
+
### Scan Timeouts
|
|
525
|
+
|
|
526
|
+
1. Increase timeout value:
|
|
527
|
+
```typescript
|
|
528
|
+
const client = new ESCLClient(30000); // 30 seconds
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
2. Check scanner network latency
|
|
532
|
+
3. Reduce scan resolution for slower networks
|
|
533
|
+
|
|
534
|
+
### Image Processing Issues
|
|
535
|
+
|
|
536
|
+
1. Verify `pillow` library is installed:
|
|
537
|
+
```bash
|
|
538
|
+
pip install --upgrade pillow
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
2. Check disk space for image temporary files
|
|
542
|
+
3. Verify scanner outputs valid PNG/JPEG images
|
|
543
|
+
|
|
544
|
+
## Error Handling
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
try {
|
|
548
|
+
const scanners = await discoverScanners(5000);
|
|
549
|
+
|
|
550
|
+
if (scanners.length === 0) {
|
|
551
|
+
throw new Error('No scanners found');
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const capabilities = await client.getCapabilities(scanners[0]);
|
|
555
|
+
if (!capabilities) {
|
|
556
|
+
throw new Error('Failed to get capabilities');
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const jobId = await client.createScanJob(scanners[0], 300, 'RGB24', 'Platen');
|
|
560
|
+
if (!jobId) {
|
|
561
|
+
throw new Error('Failed to create scan job');
|
|
562
|
+
}
|
|
563
|
+
} catch (error) {
|
|
564
|
+
console.error('eSCL operation failed:', error.message);
|
|
565
|
+
// Handle error appropriately
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
## Performance Considerations
|
|
570
|
+
|
|
571
|
+
- **Discovery Time**: ~5 seconds for local network scan
|
|
572
|
+
- **Scan Time**: Varies by document size, DPI, and network latency
|
|
573
|
+
- Single page A4 at 300 DPI: typically 5-10 seconds
|
|
574
|
+
- Large batch jobs: minutes depending on document count
|
|
575
|
+
- **Memory Usage**: Python subprocess uses ~50-100MB when idle
|
|
576
|
+
- **Network**: Requires local network access to scanners (no internet required)
|
|
577
|
+
|
|
578
|
+
## Compatibility
|
|
579
|
+
|
|
580
|
+
### Operating Systems
|
|
581
|
+
- ✅ macOS 10.14+
|
|
582
|
+
- ✅ Linux (Ubuntu 18.04+, CentOS 7+, etc.)
|
|
583
|
+
- ✅ Windows 10+ (with Python 3.6+)
|
|
584
|
+
|
|
585
|
+
### Node.js Versions
|
|
586
|
+
- ✅ Node.js 14.x
|
|
587
|
+
- ✅ Node.js 16.x
|
|
588
|
+
- ✅ Node.js 18.x
|
|
589
|
+
- ✅ Node.js 20.x
|
|
590
|
+
|
|
591
|
+
### Scanner Models
|
|
592
|
+
- ✅ Canon: iR-ADV C series, iR 2500/3000 series
|
|
593
|
+
- ✅ HP: LaserJet Pro M series, MFP devices
|
|
594
|
+
- ✅ Xerox: WorkCentre 5000+ series
|
|
595
|
+
- ✅ Ricoh: MP C series
|
|
596
|
+
- ✅ Epson: WorkForce Pro series
|
|
597
|
+
- ✅ Brother: MFC series (eSCL enabled)
|
|
598
|
+
|
|
599
|
+
## Development
|
|
600
|
+
|
|
601
|
+
### Building from Source
|
|
602
|
+
|
|
603
|
+
```bash
|
|
604
|
+
# Install dependencies
|
|
605
|
+
yarn install
|
|
606
|
+
|
|
607
|
+
# Build TypeScript
|
|
608
|
+
yarn build
|
|
609
|
+
|
|
610
|
+
# Clean build artifacts
|
|
611
|
+
yarn clean
|
|
612
|
+
|
|
613
|
+
# Watch mode
|
|
614
|
+
yarn build:watch
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
### Project Structure
|
|
618
|
+
|
|
619
|
+
```
|
|
620
|
+
├── src/
|
|
621
|
+
│ ├── index.ts # Main entry point
|
|
622
|
+
│ ├── types.ts # TypeScript interfaces
|
|
623
|
+
│ ├── client.ts # eSCL HTTP client
|
|
624
|
+
│ ├── discovery.ts # Scanner discovery service
|
|
625
|
+
│ └── [...other files]
|
|
626
|
+
├── python/
|
|
627
|
+
│ ├── escl_main.py # Python subprocess entry point
|
|
628
|
+
│ ├── escl_backend.py # eSCL protocol implementation
|
|
629
|
+
│ └── base.py # Base class for backends
|
|
630
|
+
├── dist/ # Compiled JavaScript (generated)
|
|
631
|
+
├── package.json
|
|
632
|
+
└── README.md
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
## Contributing
|
|
636
|
+
|
|
637
|
+
Contributions are welcome! Please:
|
|
638
|
+
|
|
639
|
+
1. Fork the repository
|
|
640
|
+
2. Create a feature branch
|
|
641
|
+
3. Submit a pull request with description of changes
|
|
642
|
+
4. Ensure tests pass and code follows project conventions
|
|
643
|
+
|
|
644
|
+
## License
|
|
645
|
+
|
|
646
|
+
MIT
|
|
647
|
+
|
|
648
|
+
## Support
|
|
649
|
+
|
|
650
|
+
For issues, questions, or feature requests:
|
|
651
|
+
- GitHub Repository: [escl-protocol-scanner](https://github.com/byeong1/escl-protocol-scanner)
|
|
652
|
+
- GitHub Issues: [Report Issues](https://github.com/byeong1/escl-protocol-scanner/issues)
|
|
653
|
+
- npm Package: [@escl-protocol/scanner](https://www.npmjs.com/package/@escl-protocol/scanner)
|
|
654
|
+
- Email: your-email@example.com
|
|
655
|
+
|
|
656
|
+
## Changelog
|
|
657
|
+
|
|
658
|
+
### Version 1.0.0 (Initial Release)
|
|
659
|
+
- Basic eSCL protocol support
|
|
660
|
+
- Scanner discovery via mDNS
|
|
661
|
+
- Single and batch scanning
|
|
662
|
+
- Image rotation and encoding
|
|
663
|
+
- Support for multiple manufacturers
|