@ecobridge.xyz/devicemanager 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitea/workflows/default_nottags.yaml +66 -0
- package/.gitea/workflows/default_tags.yaml +124 -0
- package/.vscode/launch.json +11 -0
- package/.vscode/settings.json +26 -0
- package/changelog.md +91 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/device/device.classes.device.d.ts +205 -0
- package/dist_ts/device/device.classes.device.js +344 -0
- package/dist_ts/devicemanager.classes.devicemanager.d.ts +210 -0
- package/dist_ts/devicemanager.classes.devicemanager.js +835 -0
- package/dist_ts/discovery/discovery.classes.homeassistant.d.ts +101 -0
- package/dist_ts/discovery/discovery.classes.homeassistant.js +313 -0
- package/dist_ts/discovery/discovery.classes.mdns.d.ts +90 -0
- package/dist_ts/discovery/discovery.classes.mdns.js +291 -0
- package/dist_ts/discovery/discovery.classes.networkscanner.d.ts +64 -0
- package/dist_ts/discovery/discovery.classes.networkscanner.js +535 -0
- package/dist_ts/discovery/discovery.classes.ssdp.d.ts +112 -0
- package/dist_ts/discovery/discovery.classes.ssdp.js +255 -0
- package/dist_ts/factories/index.d.ts +190 -0
- package/dist_ts/factories/index.js +350 -0
- package/dist_ts/features/feature.abstract.d.ts +118 -0
- package/dist_ts/features/feature.abstract.js +172 -0
- package/dist_ts/features/feature.camera.d.ts +95 -0
- package/dist_ts/features/feature.camera.js +162 -0
- package/dist_ts/features/feature.climate.d.ts +174 -0
- package/dist_ts/features/feature.climate.js +328 -0
- package/dist_ts/features/feature.cover.d.ts +121 -0
- package/dist_ts/features/feature.cover.js +217 -0
- package/dist_ts/features/feature.fan.d.ts +127 -0
- package/dist_ts/features/feature.fan.js +235 -0
- package/dist_ts/features/feature.light.d.ts +156 -0
- package/dist_ts/features/feature.light.js +296 -0
- package/dist_ts/features/feature.lock.d.ts +103 -0
- package/dist_ts/features/feature.lock.js +170 -0
- package/dist_ts/features/feature.playback.d.ts +98 -0
- package/dist_ts/features/feature.playback.js +191 -0
- package/dist_ts/features/feature.power.d.ts +79 -0
- package/dist_ts/features/feature.power.js +173 -0
- package/dist_ts/features/feature.print.d.ts +78 -0
- package/dist_ts/features/feature.print.js +226 -0
- package/dist_ts/features/feature.scan.d.ts +79 -0
- package/dist_ts/features/feature.scan.js +315 -0
- package/dist_ts/features/feature.sensor.d.ts +95 -0
- package/dist_ts/features/feature.sensor.js +147 -0
- package/dist_ts/features/feature.snmp.d.ts +88 -0
- package/dist_ts/features/feature.snmp.js +175 -0
- package/dist_ts/features/feature.switch.d.ts +75 -0
- package/dist_ts/features/feature.switch.js +126 -0
- package/dist_ts/features/feature.volume.d.ts +104 -0
- package/dist_ts/features/feature.volume.js +186 -0
- package/dist_ts/features/index.d.ts +19 -0
- package/dist_ts/features/index.js +23 -0
- package/dist_ts/helpers/helpers.iprange.d.ts +35 -0
- package/dist_ts/helpers/helpers.iprange.js +151 -0
- package/dist_ts/helpers/helpers.retry.d.ts +25 -0
- package/dist_ts/helpers/helpers.retry.js +69 -0
- package/dist_ts/index.d.ts +15 -0
- package/dist_ts/index.js +40 -0
- package/dist_ts/interfaces/feature.interfaces.d.ts +255 -0
- package/dist_ts/interfaces/feature.interfaces.js +6 -0
- package/dist_ts/interfaces/homeassistant.interfaces.d.ts +550 -0
- package/dist_ts/interfaces/homeassistant.interfaces.js +111 -0
- package/dist_ts/interfaces/index.d.ts +330 -0
- package/dist_ts/interfaces/index.js +16 -0
- package/dist_ts/interfaces/smarthome.interfaces.d.ts +282 -0
- package/dist_ts/interfaces/smarthome.interfaces.js +7 -0
- package/dist_ts/paths.d.ts +1 -0
- package/dist_ts/paths.js +3 -0
- package/dist_ts/plugins.d.ts +23 -0
- package/dist_ts/plugins.js +30 -0
- package/dist_ts/protocols/index.d.ts +12 -0
- package/dist_ts/protocols/index.js +21 -0
- package/dist_ts/protocols/protocol.escl.d.ts +58 -0
- package/dist_ts/protocols/protocol.escl.js +364 -0
- package/dist_ts/protocols/protocol.homeassistant.d.ts +248 -0
- package/dist_ts/protocols/protocol.homeassistant.js +611 -0
- package/dist_ts/protocols/protocol.ipp.d.ts +45 -0
- package/dist_ts/protocols/protocol.ipp.js +284 -0
- package/dist_ts/protocols/protocol.nut.d.ts +160 -0
- package/dist_ts/protocols/protocol.nut.js +364 -0
- package/dist_ts/protocols/protocol.sane.d.ts +104 -0
- package/dist_ts/protocols/protocol.sane.js +597 -0
- package/dist_ts/protocols/protocol.snmp.d.ts +133 -0
- package/dist_ts/protocols/protocol.snmp.js +333 -0
- package/dist_ts/protocols/protocol.upnp.d.ts +207 -0
- package/dist_ts/protocols/protocol.upnp.js +442 -0
- package/dist_ts/protocols/protocol.upssnmp.d.ts +160 -0
- package/dist_ts/protocols/protocol.upssnmp.js +263 -0
- package/npmextra.json +24 -0
- package/package.json +39 -0
- package/readme.hints.md +115 -0
- package/readme.md +624 -0
- package/test/test.ts +261 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/device/device.classes.device.ts +454 -0
- package/ts/devicemanager.classes.devicemanager.ts +1219 -0
- package/ts/discovery/discovery.classes.homeassistant.ts +376 -0
- package/ts/discovery/discovery.classes.mdns.ts +347 -0
- package/ts/discovery/discovery.classes.networkscanner.ts +648 -0
- package/ts/discovery/discovery.classes.ssdp.ts +357 -0
- package/ts/factories/index.ts +696 -0
- package/ts/features/feature.abstract.ts +251 -0
- package/ts/features/feature.camera.ts +214 -0
- package/ts/features/feature.climate.ts +407 -0
- package/ts/features/feature.cover.ts +278 -0
- package/ts/features/feature.fan.ts +296 -0
- package/ts/features/feature.light.ts +369 -0
- package/ts/features/feature.lock.ts +223 -0
- package/ts/features/feature.playback.ts +246 -0
- package/ts/features/feature.power.ts +231 -0
- package/ts/features/feature.print.ts +297 -0
- package/ts/features/feature.scan.ts +370 -0
- package/ts/features/feature.sensor.ts +202 -0
- package/ts/features/feature.snmp.ts +235 -0
- package/ts/features/feature.switch.ts +170 -0
- package/ts/features/feature.volume.ts +256 -0
- package/ts/features/index.ts +25 -0
- package/ts/helpers/helpers.iprange.ts +172 -0
- package/ts/helpers/helpers.retry.ts +100 -0
- package/ts/index.ts +165 -0
- package/ts/interfaces/feature.interfaces.ts +359 -0
- package/ts/interfaces/homeassistant.interfaces.ts +666 -0
- package/ts/interfaces/index.ts +421 -0
- package/ts/interfaces/smarthome.interfaces.ts +421 -0
- package/ts/paths.ts +5 -0
- package/ts/plugins.ts +41 -0
- package/ts/protocols/index.ts +59 -0
- package/ts/protocols/protocol.escl.ts +439 -0
- package/ts/protocols/protocol.homeassistant.ts +737 -0
- package/ts/protocols/protocol.ipp.ts +329 -0
- package/ts/protocols/protocol.nut.ts +471 -0
- package/ts/protocols/protocol.sane.ts +694 -0
- package/ts/protocols/protocol.snmp.ts +439 -0
- package/ts/protocols/protocol.upnp.ts +627 -0
- package/ts/protocols/protocol.upssnmp.ts +377 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// native scope
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as net from 'net';
|
|
4
|
+
import * as dgram from 'dgram';
|
|
5
|
+
import * as events from 'events';
|
|
6
|
+
export { path, net, dgram, events };
|
|
7
|
+
// @push.rocks scope
|
|
8
|
+
import * as smartpath from '@push.rocks/smartpath';
|
|
9
|
+
import * as smartpromise from '@push.rocks/smartpromise';
|
|
10
|
+
import * as smartdelay from '@push.rocks/smartdelay';
|
|
11
|
+
import * as smartnetwork from '@push.rocks/smartnetwork';
|
|
12
|
+
// Re-export SmartRequest for HTTP
|
|
13
|
+
import { SmartRequest } from '@push.rocks/smartrequest';
|
|
14
|
+
export { SmartRequest };
|
|
15
|
+
export { smartpath, smartpromise, smartdelay, smartnetwork, };
|
|
16
|
+
// third party
|
|
17
|
+
import * as bonjourService from 'bonjour-service';
|
|
18
|
+
import ipp from 'ipp';
|
|
19
|
+
import nodeSsdpModule from 'node-ssdp';
|
|
20
|
+
import * as netSnmp from 'net-snmp';
|
|
21
|
+
import * as sonos from 'sonos';
|
|
22
|
+
import * as castv2Client from 'castv2-client';
|
|
23
|
+
import WebSocket from 'ws';
|
|
24
|
+
// node-ssdp exports Client/Server under default in ESM
|
|
25
|
+
const nodeSsdp = {
|
|
26
|
+
Client: nodeSsdpModule.Client,
|
|
27
|
+
Server: nodeSsdpModule.Server,
|
|
28
|
+
};
|
|
29
|
+
export { bonjourService, ipp, nodeSsdp, netSnmp, sonos, castv2Client, WebSocket };
|
|
30
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsZUFBZTtBQUNmLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBQzNCLE9BQU8sS0FBSyxLQUFLLE1BQU0sT0FBTyxDQUFDO0FBQy9CLE9BQU8sS0FBSyxNQUFNLE1BQU0sUUFBUSxDQUFDO0FBRWpDLE9BQU8sRUFBRSxJQUFJLEVBQUUsR0FBRyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsQ0FBQztBQUVwQyxvQkFBb0I7QUFDcEIsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQUNuRCxPQUFPLEtBQUssWUFBWSxNQUFNLDBCQUEwQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxVQUFVLE1BQU0sd0JBQXdCLENBQUM7QUFDckQsT0FBTyxLQUFLLFlBQVksTUFBTSwwQkFBMEIsQ0FBQztBQUV6RCxrQ0FBa0M7QUFDbEMsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ3hELE9BQU8sRUFBRSxZQUFZLEVBQUUsQ0FBQztBQUV4QixPQUFPLEVBQ0wsU0FBUyxFQUNULFlBQVksRUFDWixVQUFVLEVBQ1YsWUFBWSxHQUNiLENBQUM7QUFFRixjQUFjO0FBQ2QsT0FBTyxLQUFLLGNBQWMsTUFBTSxpQkFBaUIsQ0FBQztBQUNsRCxPQUFPLEdBQUcsTUFBTSxLQUFLLENBQUM7QUFDdEIsT0FBTyxjQUFjLE1BQU0sV0FBVyxDQUFDO0FBQ3ZDLE9BQU8sS0FBSyxPQUFPLE1BQU0sVUFBVSxDQUFDO0FBQ3BDLE9BQU8sS0FBSyxLQUFLLE1BQU0sT0FBTyxDQUFDO0FBQy9CLE9BQU8sS0FBSyxZQUFZLE1BQU0sZUFBZSxDQUFDO0FBQzlDLE9BQU8sU0FBUyxNQUFNLElBQUksQ0FBQztBQUUzQix1REFBdUQ7QUFDdkQsTUFBTSxRQUFRLEdBQUc7SUFDZixNQUFNLEVBQUUsY0FBYyxDQUFDLE1BQU07SUFDN0IsTUFBTSxFQUFFLGNBQWMsQ0FBQyxNQUFNO0NBQzlCLENBQUM7QUFFRixPQUFPLEVBQUUsY0FBYyxFQUFFLEdBQUcsRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxZQUFZLEVBQUUsU0FBUyxFQUFFLENBQUMifQ==
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol implementations
|
|
3
|
+
* All network communication protocols for device interaction
|
|
4
|
+
*/
|
|
5
|
+
export { EsclProtocol } from './protocol.escl.js';
|
|
6
|
+
export { SaneProtocol } from './protocol.sane.js';
|
|
7
|
+
export { IppProtocol } from './protocol.ipp.js';
|
|
8
|
+
export { SnmpProtocol, SNMP_OIDS, type TSnmpValueType, type ISnmpVarbind, type ISnmpOptions, } from './protocol.snmp.js';
|
|
9
|
+
export { NutProtocol, NUT_VARIABLES, NUT_COMMANDS, type TNutStatusFlag, type INutUpsInfo, type INutVariable, } from './protocol.nut.js';
|
|
10
|
+
export { UpnpSoapClient, UPNP_SERVICE_TYPES, UPNP_DEVICE_TYPES, type TDlnaTransportState, type TDlnaTransportStatus, type IDlnaPositionInfo, type IDlnaTransportInfo, type IDlnaMediaInfo, type IDlnaContentItem, type IDlnaBrowseResult, } from './protocol.upnp.js';
|
|
11
|
+
export { UpsSnmpHandler, UPS_SNMP_OIDS, type TUpsBatteryStatus, type TUpsOutputSource, type TUpsTestResult, type IUpsSnmpStatus, } from './protocol.upssnmp.js';
|
|
12
|
+
export { HomeAssistantProtocol } from './protocol.homeassistant.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol implementations
|
|
3
|
+
* All network communication protocols for device interaction
|
|
4
|
+
*/
|
|
5
|
+
// eSCL/AirScan scanner protocol
|
|
6
|
+
export { EsclProtocol } from './protocol.escl.js';
|
|
7
|
+
// SANE network scanner protocol
|
|
8
|
+
export { SaneProtocol } from './protocol.sane.js';
|
|
9
|
+
// IPP printer protocol
|
|
10
|
+
export { IppProtocol } from './protocol.ipp.js';
|
|
11
|
+
// SNMP query protocol
|
|
12
|
+
export { SnmpProtocol, SNMP_OIDS, } from './protocol.snmp.js';
|
|
13
|
+
// Network UPS Tools protocol
|
|
14
|
+
export { NutProtocol, NUT_VARIABLES, NUT_COMMANDS, } from './protocol.nut.js';
|
|
15
|
+
// UPnP/DLNA SOAP protocol
|
|
16
|
+
export { UpnpSoapClient, UPNP_SERVICE_TYPES, UPNP_DEVICE_TYPES, } from './protocol.upnp.js';
|
|
17
|
+
// UPS SNMP (UPS-MIB RFC 1628)
|
|
18
|
+
export { UpsSnmpHandler, UPS_SNMP_OIDS, } from './protocol.upssnmp.js';
|
|
19
|
+
// Home Assistant WebSocket protocol
|
|
20
|
+
export { HomeAssistantProtocol } from './protocol.homeassistant.js';
|
|
21
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9wcm90b2NvbHMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztHQUdHO0FBRUgsZ0NBQWdDO0FBQ2hDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUVsRCxnQ0FBZ0M7QUFDaEMsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBRWxELHVCQUF1QjtBQUN2QixPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFFaEQsc0JBQXNCO0FBQ3RCLE9BQU8sRUFDTCxZQUFZLEVBQ1osU0FBUyxHQUlWLE1BQU0sb0JBQW9CLENBQUM7QUFFNUIsNkJBQTZCO0FBQzdCLE9BQU8sRUFDTCxXQUFXLEVBQ1gsYUFBYSxFQUNiLFlBQVksR0FJYixNQUFNLG1CQUFtQixDQUFDO0FBRTNCLDBCQUEwQjtBQUMxQixPQUFPLEVBQ0wsY0FBYyxFQUNkLGtCQUFrQixFQUNsQixpQkFBaUIsR0FRbEIsTUFBTSxvQkFBb0IsQ0FBQztBQUU1Qiw4QkFBOEI7QUFDOUIsT0FBTyxFQUNMLGNBQWMsRUFDZCxhQUFhLEdBS2QsTUFBTSx1QkFBdUIsQ0FBQztBQUUvQixvQ0FBb0M7QUFDcEMsT0FBTyxFQUFFLHFCQUFxQixFQUFFLE1BQU0sNkJBQTZCLENBQUMifQ==
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { IEsclCapabilities, IEsclScanStatus, IScanOptions, IScanResult } from '../interfaces/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* eSCL/AirScan protocol client for network scanners
|
|
4
|
+
*/
|
|
5
|
+
export declare class EsclProtocol {
|
|
6
|
+
private baseUrl;
|
|
7
|
+
private capabilities;
|
|
8
|
+
constructor(address: string, port: number, secure?: boolean);
|
|
9
|
+
/**
|
|
10
|
+
* Get scanner capabilities
|
|
11
|
+
*/
|
|
12
|
+
getCapabilities(): Promise<IEsclCapabilities>;
|
|
13
|
+
/**
|
|
14
|
+
* Get scanner status
|
|
15
|
+
*/
|
|
16
|
+
getStatus(): Promise<IEsclScanStatus>;
|
|
17
|
+
/**
|
|
18
|
+
* Submit a scan job
|
|
19
|
+
* Returns the job URI for tracking
|
|
20
|
+
*/
|
|
21
|
+
submitScanJob(options: IScanOptions): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Wait for scan job to complete and download the result
|
|
24
|
+
*
|
|
25
|
+
* Note: Many scanners (including Brother) don't start scanning until
|
|
26
|
+
* NextDocument is requested. The request triggers the scan and blocks
|
|
27
|
+
* until complete. We try direct download first, then fall back to polling.
|
|
28
|
+
*/
|
|
29
|
+
waitForScanComplete(jobUri: string, options: IScanOptions, pollInterval?: number): Promise<IScanResult>;
|
|
30
|
+
/**
|
|
31
|
+
* Download scanned document
|
|
32
|
+
*/
|
|
33
|
+
downloadScan(jobUri: string, options: IScanOptions): Promise<IScanResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Cancel a scan job
|
|
36
|
+
*/
|
|
37
|
+
cancelJob(jobUri: string): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Build XML scan settings
|
|
40
|
+
*/
|
|
41
|
+
private buildScanSettings;
|
|
42
|
+
/**
|
|
43
|
+
* Map source to eSCL format
|
|
44
|
+
*/
|
|
45
|
+
private mapSource;
|
|
46
|
+
/**
|
|
47
|
+
* Parse capabilities XML response
|
|
48
|
+
*/
|
|
49
|
+
private parseCapabilities;
|
|
50
|
+
/**
|
|
51
|
+
* Parse status XML response
|
|
52
|
+
*/
|
|
53
|
+
private parseStatus;
|
|
54
|
+
/**
|
|
55
|
+
* Perform a complete scan operation
|
|
56
|
+
*/
|
|
57
|
+
scan(options: IScanOptions): Promise<IScanResult>;
|
|
58
|
+
}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js'; // Used for smartdelay
|
|
2
|
+
/**
|
|
3
|
+
* eSCL XML namespaces
|
|
4
|
+
*/
|
|
5
|
+
const NAMESPACES = {
|
|
6
|
+
scan: 'http://schemas.hp.com/imaging/escl/2011/05/03',
|
|
7
|
+
pwg: 'http://www.pwg.org/schemas/2010/12/sm',
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Color mode mappings
|
|
11
|
+
*/
|
|
12
|
+
const COLOR_MODE_MAP = {
|
|
13
|
+
color: 'RGB24',
|
|
14
|
+
grayscale: 'Grayscale8',
|
|
15
|
+
blackwhite: 'BlackAndWhite1',
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Format MIME type mappings
|
|
19
|
+
*/
|
|
20
|
+
const FORMAT_MIME_MAP = {
|
|
21
|
+
jpeg: 'image/jpeg',
|
|
22
|
+
png: 'image/png',
|
|
23
|
+
pdf: 'application/pdf',
|
|
24
|
+
tiff: 'image/tiff',
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Helper to make HTTP requests using native fetch (available in Node.js 18+)
|
|
28
|
+
*/
|
|
29
|
+
async function httpRequest(url, method, body) {
|
|
30
|
+
const options = { method };
|
|
31
|
+
if (body) {
|
|
32
|
+
options.headers = { 'Content-Type': 'text/xml; charset=utf-8' };
|
|
33
|
+
options.body = body;
|
|
34
|
+
}
|
|
35
|
+
const response = await fetch(url, options);
|
|
36
|
+
const responseBody = await response.text();
|
|
37
|
+
const headers = {};
|
|
38
|
+
response.headers.forEach((value, key) => {
|
|
39
|
+
headers[key.toLowerCase()] = value;
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
status: response.status,
|
|
43
|
+
headers,
|
|
44
|
+
body: responseBody,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* eSCL/AirScan protocol client for network scanners
|
|
49
|
+
*/
|
|
50
|
+
export class EsclProtocol {
|
|
51
|
+
baseUrl;
|
|
52
|
+
capabilities = null;
|
|
53
|
+
constructor(address, port, secure = false) {
|
|
54
|
+
const protocol = secure ? 'https' : 'http';
|
|
55
|
+
this.baseUrl = `${protocol}://${address}:${port}/eSCL`;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get scanner capabilities
|
|
59
|
+
*/
|
|
60
|
+
async getCapabilities() {
|
|
61
|
+
const response = await httpRequest(`${this.baseUrl}/ScannerCapabilities`, 'GET');
|
|
62
|
+
if (response.status !== 200) {
|
|
63
|
+
throw new Error(`Failed to get capabilities: HTTP ${response.status}`);
|
|
64
|
+
}
|
|
65
|
+
this.capabilities = this.parseCapabilities(response.body);
|
|
66
|
+
return this.capabilities;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get scanner status
|
|
70
|
+
*/
|
|
71
|
+
async getStatus() {
|
|
72
|
+
const response = await httpRequest(`${this.baseUrl}/ScannerStatus`, 'GET');
|
|
73
|
+
if (response.status !== 200) {
|
|
74
|
+
throw new Error(`Failed to get status: HTTP ${response.status}`);
|
|
75
|
+
}
|
|
76
|
+
return this.parseStatus(response.body);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Submit a scan job
|
|
80
|
+
* Returns the job URI for tracking
|
|
81
|
+
*/
|
|
82
|
+
async submitScanJob(options) {
|
|
83
|
+
const scanSettings = this.buildScanSettings(options);
|
|
84
|
+
// Use fetch for POST with body since SmartRequest API may not support raw body
|
|
85
|
+
const response = await fetch(`${this.baseUrl}/ScanJobs`, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: {
|
|
88
|
+
'Content-Type': 'text/xml; charset=utf-8',
|
|
89
|
+
},
|
|
90
|
+
body: scanSettings,
|
|
91
|
+
});
|
|
92
|
+
if (response.status !== 201) {
|
|
93
|
+
throw new Error(`Failed to submit scan job: HTTP ${response.status}`);
|
|
94
|
+
}
|
|
95
|
+
// Get job URI from Location header
|
|
96
|
+
const location = response.headers.get('location');
|
|
97
|
+
if (!location) {
|
|
98
|
+
throw new Error('No job location returned from scanner');
|
|
99
|
+
}
|
|
100
|
+
return location;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Wait for scan job to complete and download the result
|
|
104
|
+
*
|
|
105
|
+
* Note: Many scanners (including Brother) don't start scanning until
|
|
106
|
+
* NextDocument is requested. The request triggers the scan and blocks
|
|
107
|
+
* until complete. We try direct download first, then fall back to polling.
|
|
108
|
+
*/
|
|
109
|
+
async waitForScanComplete(jobUri, options, pollInterval = 500) {
|
|
110
|
+
// Try direct download first - this triggers the scan on many scanners
|
|
111
|
+
// (including Brother) and blocks until the scan completes
|
|
112
|
+
try {
|
|
113
|
+
const result = await this.downloadScan(jobUri, options);
|
|
114
|
+
if (result.data.length > 0) {
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
// Direct download failed, fall back to polling
|
|
120
|
+
}
|
|
121
|
+
// Fall back to polling for scanners that need it
|
|
122
|
+
let attempts = 0;
|
|
123
|
+
const maxAttempts = 120; // 60 seconds max
|
|
124
|
+
while (attempts < maxAttempts) {
|
|
125
|
+
const status = await this.getStatus();
|
|
126
|
+
const job = status.jobs?.find((j) => jobUri.includes(j.jobUuid) || j.jobUri === jobUri);
|
|
127
|
+
if (job) {
|
|
128
|
+
if (job.jobState === 'Completed') {
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
else if (job.jobState === 'Canceled' || job.jobState === 'Aborted') {
|
|
132
|
+
throw new Error(`Scan job ${job.jobState}: ${job.jobStateReason || 'Unknown reason'}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
await plugins.smartdelay.delayFor(pollInterval);
|
|
136
|
+
attempts++;
|
|
137
|
+
}
|
|
138
|
+
if (attempts >= maxAttempts) {
|
|
139
|
+
throw new Error('Scan job timed out');
|
|
140
|
+
}
|
|
141
|
+
// Download the scanned document
|
|
142
|
+
return this.downloadScan(jobUri, options);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Download scanned document
|
|
146
|
+
*/
|
|
147
|
+
async downloadScan(jobUri, options) {
|
|
148
|
+
const downloadUrl = `${jobUri}/NextDocument`;
|
|
149
|
+
const response = await fetch(downloadUrl, { method: 'GET' });
|
|
150
|
+
if (response.status !== 200) {
|
|
151
|
+
throw new Error(`Failed to download scan: HTTP ${response.status}`);
|
|
152
|
+
}
|
|
153
|
+
const format = options.format ?? 'jpeg';
|
|
154
|
+
const contentType = response.headers.get('content-type') ?? FORMAT_MIME_MAP[format];
|
|
155
|
+
// Get image dimensions from headers if available
|
|
156
|
+
const width = parseInt(response.headers.get('x-image-width') || '0') || 0;
|
|
157
|
+
const height = parseInt(response.headers.get('x-image-height') || '0') || 0;
|
|
158
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
159
|
+
const data = Buffer.from(arrayBuffer);
|
|
160
|
+
return {
|
|
161
|
+
data: data,
|
|
162
|
+
format: format,
|
|
163
|
+
width: width,
|
|
164
|
+
height: height,
|
|
165
|
+
resolution: options.resolution ?? 300,
|
|
166
|
+
colorMode: options.colorMode ?? 'color',
|
|
167
|
+
mimeType: contentType,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Cancel a scan job
|
|
172
|
+
*/
|
|
173
|
+
async cancelJob(jobUri) {
|
|
174
|
+
const response = await fetch(jobUri, { method: 'DELETE' });
|
|
175
|
+
// 204 No Content or 200 OK are both acceptable
|
|
176
|
+
if (response.status !== 200 && response.status !== 204) {
|
|
177
|
+
throw new Error(`Failed to cancel job: HTTP ${response.status}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Build XML scan settings
|
|
182
|
+
*/
|
|
183
|
+
buildScanSettings(options) {
|
|
184
|
+
const resolution = options.resolution ?? 300;
|
|
185
|
+
const colorMode = COLOR_MODE_MAP[options.colorMode ?? 'color'];
|
|
186
|
+
const format = FORMAT_MIME_MAP[options.format ?? 'jpeg'];
|
|
187
|
+
const source = this.mapSource(options.source ?? 'flatbed');
|
|
188
|
+
const intent = options.intent ?? 'TextAndPhoto';
|
|
189
|
+
let xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
190
|
+
<scan:ScanSettings xmlns:scan="${NAMESPACES.scan}" xmlns:pwg="${NAMESPACES.pwg}">
|
|
191
|
+
<pwg:Version>2.0</pwg:Version>
|
|
192
|
+
<scan:Intent>${intent}</scan:Intent>
|
|
193
|
+
<pwg:ScanRegions>
|
|
194
|
+
<pwg:ScanRegion>`;
|
|
195
|
+
if (options.area) {
|
|
196
|
+
// Convert mm to 300ths of an inch (eSCL uses 300dpi as base unit)
|
|
197
|
+
const toUnits = (mm) => Math.round((mm / 25.4) * 300);
|
|
198
|
+
xml += `
|
|
199
|
+
<pwg:XOffset>${toUnits(options.area.x)}</pwg:XOffset>
|
|
200
|
+
<pwg:YOffset>${toUnits(options.area.y)}</pwg:YOffset>
|
|
201
|
+
<pwg:Width>${toUnits(options.area.width)}</pwg:Width>
|
|
202
|
+
<pwg:Height>${toUnits(options.area.height)}</pwg:Height>`;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
// Full page (A4 default: 210x297mm)
|
|
206
|
+
xml += `
|
|
207
|
+
<pwg:XOffset>0</pwg:XOffset>
|
|
208
|
+
<pwg:YOffset>0</pwg:YOffset>
|
|
209
|
+
<pwg:Width>2480</pwg:Width>
|
|
210
|
+
<pwg:Height>3508</pwg:Height>`;
|
|
211
|
+
}
|
|
212
|
+
xml += `
|
|
213
|
+
<pwg:ContentRegionUnits>escl:ThreeHundredthsOfInches</pwg:ContentRegionUnits>
|
|
214
|
+
</pwg:ScanRegion>
|
|
215
|
+
</pwg:ScanRegions>
|
|
216
|
+
<scan:DocumentFormatExt>${format}</scan:DocumentFormatExt>
|
|
217
|
+
<scan:XResolution>${resolution}</scan:XResolution>
|
|
218
|
+
<scan:YResolution>${resolution}</scan:YResolution>
|
|
219
|
+
<scan:ColorMode>${colorMode}</scan:ColorMode>
|
|
220
|
+
<scan:InputSource>${source}</scan:InputSource>`;
|
|
221
|
+
if (options.format === 'jpeg' && options.quality) {
|
|
222
|
+
xml += `
|
|
223
|
+
<scan:CompressionFactor>${100 - options.quality}</scan:CompressionFactor>`;
|
|
224
|
+
}
|
|
225
|
+
xml += `
|
|
226
|
+
</scan:ScanSettings>`;
|
|
227
|
+
return xml;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Map source to eSCL format
|
|
231
|
+
*/
|
|
232
|
+
mapSource(source) {
|
|
233
|
+
switch (source) {
|
|
234
|
+
case 'flatbed':
|
|
235
|
+
return 'Platen';
|
|
236
|
+
case 'adf':
|
|
237
|
+
return 'Feeder';
|
|
238
|
+
case 'adf-duplex':
|
|
239
|
+
return 'Duplex';
|
|
240
|
+
default:
|
|
241
|
+
return 'Platen';
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Parse capabilities XML response
|
|
246
|
+
*/
|
|
247
|
+
parseCapabilities(body) {
|
|
248
|
+
const xml = body;
|
|
249
|
+
// Simple XML parsing without full XML parser
|
|
250
|
+
const getTagContent = (tag) => {
|
|
251
|
+
const regex = new RegExp(`<[^:]*:?${tag}[^>]*>([^<]*)<`, 'i');
|
|
252
|
+
const match = xml.match(regex);
|
|
253
|
+
return match?.[1]?.trim() ?? '';
|
|
254
|
+
};
|
|
255
|
+
const getAllTagContents = (tag) => {
|
|
256
|
+
const regex = new RegExp(`<[^:]*:?${tag}[^>]*>([^<]*)<`, 'gi');
|
|
257
|
+
const matches = [];
|
|
258
|
+
let match;
|
|
259
|
+
while ((match = regex.exec(xml)) !== null) {
|
|
260
|
+
if (match[1]?.trim()) {
|
|
261
|
+
matches.push(match[1].trim());
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return matches;
|
|
265
|
+
};
|
|
266
|
+
const parseResolutions = () => {
|
|
267
|
+
const resolutions = getAllTagContents('XResolution');
|
|
268
|
+
return [...new Set(resolutions.map((r) => parseInt(r)).filter((r) => !isNaN(r)))];
|
|
269
|
+
};
|
|
270
|
+
return {
|
|
271
|
+
version: getTagContent('Version') || '2.0',
|
|
272
|
+
makeAndModel: getTagContent('MakeAndModel') || getTagContent('Make') || 'Unknown',
|
|
273
|
+
serialNumber: getTagContent('SerialNumber') || undefined,
|
|
274
|
+
uuid: getTagContent('UUID') || undefined,
|
|
275
|
+
adminUri: getTagContent('AdminURI') || undefined,
|
|
276
|
+
iconUri: getTagContent('IconURI') || undefined,
|
|
277
|
+
platen: xml.includes('Platen')
|
|
278
|
+
? {
|
|
279
|
+
minWidth: parseInt(getTagContent('MinWidth')) || 0,
|
|
280
|
+
maxWidth: parseInt(getTagContent('MaxWidth')) || 2550,
|
|
281
|
+
minHeight: parseInt(getTagContent('MinHeight')) || 0,
|
|
282
|
+
maxHeight: parseInt(getTagContent('MaxHeight')) || 3508,
|
|
283
|
+
maxScanRegions: parseInt(getTagContent('MaxScanRegions')) || 1,
|
|
284
|
+
supportedResolutions: parseResolutions(),
|
|
285
|
+
colorModes: getAllTagContents('ColorMode'),
|
|
286
|
+
documentFormats: getAllTagContents('DocumentFormatExt'),
|
|
287
|
+
}
|
|
288
|
+
: undefined,
|
|
289
|
+
adf: xml.includes('Adf') || xml.includes('ADF') || xml.includes('Feeder')
|
|
290
|
+
? {
|
|
291
|
+
minWidth: 0,
|
|
292
|
+
maxWidth: 2550,
|
|
293
|
+
minHeight: 0,
|
|
294
|
+
maxHeight: 4200,
|
|
295
|
+
maxScanRegions: 1,
|
|
296
|
+
supportedResolutions: parseResolutions(),
|
|
297
|
+
colorModes: getAllTagContents('ColorMode'),
|
|
298
|
+
documentFormats: getAllTagContents('DocumentFormatExt'),
|
|
299
|
+
}
|
|
300
|
+
: undefined,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Parse status XML response
|
|
305
|
+
*/
|
|
306
|
+
parseStatus(body) {
|
|
307
|
+
const xml = body;
|
|
308
|
+
const getTagContent = (tag) => {
|
|
309
|
+
const regex = new RegExp(`<[^:]*:?${tag}[^>]*>([^<]*)<`, 'i');
|
|
310
|
+
const match = xml.match(regex);
|
|
311
|
+
return match?.[1]?.trim() ?? '';
|
|
312
|
+
};
|
|
313
|
+
const state = getTagContent('State') || getTagContent('ScannerState') || 'Idle';
|
|
314
|
+
const adfState = getTagContent('AdfState') || undefined;
|
|
315
|
+
// Parse jobs if present
|
|
316
|
+
const jobs = [];
|
|
317
|
+
const jobMatches = xml.match(/<[^:]*:?JobInfo[^>]*>[\s\S]*?<\/[^:]*:?JobInfo>/gi);
|
|
318
|
+
if (jobMatches) {
|
|
319
|
+
for (const jobXml of jobMatches) {
|
|
320
|
+
const getJobTag = (tag) => {
|
|
321
|
+
const regex = new RegExp(`<[^:]*:?${tag}[^>]*>([^<]*)<`, 'i');
|
|
322
|
+
const match = jobXml.match(regex);
|
|
323
|
+
return match?.[1]?.trim() ?? '';
|
|
324
|
+
};
|
|
325
|
+
jobs.push({
|
|
326
|
+
jobUri: getJobTag('JobUri') || getJobTag('JobURI') || '',
|
|
327
|
+
jobUuid: getJobTag('JobUuid') || getJobTag('JobUUID') || '',
|
|
328
|
+
age: parseInt(getJobTag('Age')) || 0,
|
|
329
|
+
imagesCompleted: parseInt(getJobTag('ImagesCompleted')) || 0,
|
|
330
|
+
imagesToTransfer: parseInt(getJobTag('ImagesToTransfer')) || 0,
|
|
331
|
+
jobState: getJobTag('JobState') || 'Pending',
|
|
332
|
+
jobStateReason: getJobTag('JobStateReason') || undefined,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
state: state,
|
|
338
|
+
adfState: adfState,
|
|
339
|
+
jobs: jobs.length > 0 ? jobs : undefined,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Perform a complete scan operation
|
|
344
|
+
*/
|
|
345
|
+
async scan(options) {
|
|
346
|
+
// Submit the job
|
|
347
|
+
const jobUri = await this.submitScanJob(options);
|
|
348
|
+
try {
|
|
349
|
+
// Wait for completion and download
|
|
350
|
+
return await this.waitForScanComplete(jobUri, options);
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
// Try to cancel the job on error
|
|
354
|
+
try {
|
|
355
|
+
await this.cancelJob(jobUri);
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
// Ignore cancel errors
|
|
359
|
+
}
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvdG9jb2wuZXNjbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3Byb3RvY29scy9wcm90b2NvbC5lc2NsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sZUFBZSxDQUFDLENBQUMsc0JBQXNCO0FBV2hFOztHQUVHO0FBQ0gsTUFBTSxVQUFVLEdBQUc7SUFDakIsSUFBSSxFQUFFLCtDQUErQztJQUNyRCxHQUFHLEVBQUUsdUNBQXVDO0NBQzdDLENBQUM7QUFFRjs7R0FFRztBQUNILE1BQU0sY0FBYyxHQUErQjtJQUNqRCxLQUFLLEVBQUUsT0FBTztJQUNkLFNBQVMsRUFBRSxZQUFZO0lBQ3ZCLFVBQVUsRUFBRSxnQkFBZ0I7Q0FDN0IsQ0FBQztBQUVGOztHQUVHO0FBQ0gsTUFBTSxlQUFlLEdBQWdDO0lBQ25ELElBQUksRUFBRSxZQUFZO0lBQ2xCLEdBQUcsRUFBRSxXQUFXO0lBQ2hCLEdBQUcsRUFBRSxpQkFBaUI7SUFDdEIsSUFBSSxFQUFFLFlBQVk7Q0FDbkIsQ0FBQztBQUVGOztHQUVHO0FBQ0gsS0FBSyxVQUFVLFdBQVcsQ0FDeEIsR0FBVyxFQUNYLE1BQWlDLEVBQ2pDLElBQWE7SUFFYixNQUFNLE9BQU8sR0FBZ0IsRUFBRSxNQUFNLEVBQUUsQ0FBQztJQUV4QyxJQUFJLElBQUksRUFBRSxDQUFDO1FBQ1QsT0FBTyxDQUFDLE9BQU8sR0FBRyxFQUFFLGNBQWMsRUFBRSx5QkFBeUIsRUFBRSxDQUFDO1FBQ2hFLE9BQU8sQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO0lBQ3RCLENBQUM7SUFFRCxNQUFNLFFBQVEsR0FBRyxNQUFNLEtBQUssQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDM0MsTUFBTSxZQUFZLEdBQUcsTUFBTSxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUM7SUFFM0MsTUFBTSxPQUFPLEdBQTJCLEVBQUUsQ0FBQztJQUMzQyxRQUFRLENBQUMsT0FBTyxD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
|