@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 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