@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,835 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Manager
|
|
3
|
+
* Unified device discovery and management using UniversalDevice with Features
|
|
4
|
+
*/
|
|
5
|
+
import * as plugins from './plugins.js';
|
|
6
|
+
import { MdnsDiscovery, SERVICE_TYPES } from './discovery/discovery.classes.mdns.js';
|
|
7
|
+
import { NetworkScanner } from './discovery/discovery.classes.networkscanner.js';
|
|
8
|
+
import { SsdpDiscovery, SSDP_SERVICE_TYPES } from './discovery/discovery.classes.ssdp.js';
|
|
9
|
+
import { UniversalDevice } from './device/device.classes.device.js';
|
|
10
|
+
import { ScanFeature, PrintFeature, PlaybackFeature, VolumeFeature, PowerFeature, SnmpFeature, } from './features/index.js';
|
|
11
|
+
import { createScanner, createPrinter, createSnmpDevice, createUpsDevice, createSpeaker, createDlnaRenderer, } from './factories/index.js';
|
|
12
|
+
/**
|
|
13
|
+
* Default device manager options
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_OPTIONS = {
|
|
16
|
+
autoDiscovery: true,
|
|
17
|
+
discoveryTimeout: 10000,
|
|
18
|
+
enableRetry: true,
|
|
19
|
+
maxRetries: 5,
|
|
20
|
+
retryBaseDelay: 1000,
|
|
21
|
+
};
|
|
22
|
+
const NAME_SOURCE_PRIORITY = {
|
|
23
|
+
generic: 0, // IP-based placeholder names
|
|
24
|
+
manual: 1, // Manually added devices
|
|
25
|
+
airplay: 2, // AirPlay can have generic names
|
|
26
|
+
chromecast: 3, // Chromecast has decent names
|
|
27
|
+
mdns: 4, // mDNS for scanners/printers (usually good)
|
|
28
|
+
dlna: 5, // SSDP/UPnP MediaRenderer (good friendly names)
|
|
29
|
+
sonos: 6, // Sonos SSDP ZonePlayer (most authoritative)
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Check if a name is a "real" name vs a generic placeholder
|
|
33
|
+
*/
|
|
34
|
+
function isRealName(name) {
|
|
35
|
+
if (!name)
|
|
36
|
+
return false;
|
|
37
|
+
const lower = name.toLowerCase();
|
|
38
|
+
// Generic names that indicate no real name was discovered
|
|
39
|
+
return !(lower.includes(' at ') ||
|
|
40
|
+
lower.startsWith('device ') ||
|
|
41
|
+
lower.startsWith('scanner ') ||
|
|
42
|
+
lower.startsWith('printer ') ||
|
|
43
|
+
lower.startsWith('speaker ') ||
|
|
44
|
+
lower.startsWith('ups ') ||
|
|
45
|
+
lower.startsWith('snmp ') ||
|
|
46
|
+
/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/.test(name) // Just an IP address
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Choose the best name based on source priority
|
|
51
|
+
*/
|
|
52
|
+
function shouldUpdateName(existingSource, newSource, existingName, newName) {
|
|
53
|
+
// If new name isn't real, don't update
|
|
54
|
+
if (!isRealName(newName))
|
|
55
|
+
return false;
|
|
56
|
+
// If no existing name or source, use new
|
|
57
|
+
if (!existingName || !existingSource)
|
|
58
|
+
return true;
|
|
59
|
+
// If existing name isn't real but new is, use new
|
|
60
|
+
if (!isRealName(existingName))
|
|
61
|
+
return true;
|
|
62
|
+
// Both are real names - use priority
|
|
63
|
+
const existingPriority = NAME_SOURCE_PRIORITY[existingSource] ?? 0;
|
|
64
|
+
const newPriority = NAME_SOURCE_PRIORITY[newSource] ?? 0;
|
|
65
|
+
return newPriority > existingPriority;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Create a device reference for feature construction
|
|
69
|
+
*/
|
|
70
|
+
function makeDeviceRef(address, port, name) {
|
|
71
|
+
return {
|
|
72
|
+
id: `device:${address}`,
|
|
73
|
+
address,
|
|
74
|
+
port,
|
|
75
|
+
name,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Main Device Manager class
|
|
80
|
+
* Discovers and manages network devices using the UniversalDevice architecture
|
|
81
|
+
*
|
|
82
|
+
* Devices are deduplicated by IP address - if the same device is discovered
|
|
83
|
+
* via multiple methods (mDNS, SSDP, IP scan), features are merged into a
|
|
84
|
+
* single UniversalDevice instance.
|
|
85
|
+
*/
|
|
86
|
+
export class DeviceManager extends plugins.events.EventEmitter {
|
|
87
|
+
mdnsDiscovery;
|
|
88
|
+
ssdpDiscovery;
|
|
89
|
+
_networkScanner = null;
|
|
90
|
+
// Devices keyed by IP address for deduplication
|
|
91
|
+
devicesByIp = new Map();
|
|
92
|
+
// Track name source for priority-based naming
|
|
93
|
+
nameSourceByIp = new Map();
|
|
94
|
+
options;
|
|
95
|
+
retryOptions;
|
|
96
|
+
constructor(options) {
|
|
97
|
+
super();
|
|
98
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
99
|
+
this.retryOptions = {
|
|
100
|
+
maxRetries: this.options.maxRetries,
|
|
101
|
+
baseDelay: this.options.retryBaseDelay,
|
|
102
|
+
maxDelay: 16000,
|
|
103
|
+
multiplier: 2,
|
|
104
|
+
jitter: true,
|
|
105
|
+
};
|
|
106
|
+
this.mdnsDiscovery = new MdnsDiscovery({
|
|
107
|
+
timeout: this.options.discoveryTimeout,
|
|
108
|
+
});
|
|
109
|
+
this.ssdpDiscovery = new SsdpDiscovery();
|
|
110
|
+
this.setupDiscoveryEvents();
|
|
111
|
+
this.setupSsdpDiscoveryEvents();
|
|
112
|
+
}
|
|
113
|
+
// ============================================================================
|
|
114
|
+
// Device Registration (with deduplication by IP)
|
|
115
|
+
// ============================================================================
|
|
116
|
+
/**
|
|
117
|
+
* Register or merge a device by IP address
|
|
118
|
+
* Returns the device (existing or new) and whether it's newly created
|
|
119
|
+
*/
|
|
120
|
+
registerDevice(address, port, name, manufacturer, model, feature, nameSource) {
|
|
121
|
+
const existing = this.devicesByIp.get(address);
|
|
122
|
+
if (existing) {
|
|
123
|
+
// Update name if new source has higher priority
|
|
124
|
+
const existingSource = this.nameSourceByIp.get(address);
|
|
125
|
+
if (shouldUpdateName(existingSource, nameSource, existing.name, name)) {
|
|
126
|
+
existing.name = name;
|
|
127
|
+
this.nameSourceByIp.set(address, nameSource);
|
|
128
|
+
}
|
|
129
|
+
// Update manufacturer/model if we have better info
|
|
130
|
+
if (manufacturer && !existing.manufacturer) {
|
|
131
|
+
existing.manufacturer = manufacturer;
|
|
132
|
+
}
|
|
133
|
+
if (model && !existing.model) {
|
|
134
|
+
existing.model = model;
|
|
135
|
+
}
|
|
136
|
+
// Add feature if not already present
|
|
137
|
+
if (!existing.hasFeature(feature.type)) {
|
|
138
|
+
existing.addFeature(feature);
|
|
139
|
+
return { device: existing, isNew: false, featureAdded: true };
|
|
140
|
+
}
|
|
141
|
+
return { device: existing, isNew: false, featureAdded: false };
|
|
142
|
+
}
|
|
143
|
+
// Create new device
|
|
144
|
+
const device = new UniversalDevice(address, port, {
|
|
145
|
+
name: name || `Device at ${address}`,
|
|
146
|
+
manufacturer,
|
|
147
|
+
model,
|
|
148
|
+
retryOptions: this.options.enableRetry ? this.retryOptions : undefined,
|
|
149
|
+
});
|
|
150
|
+
device.addFeature(feature);
|
|
151
|
+
this.devicesByIp.set(address, device);
|
|
152
|
+
this.nameSourceByIp.set(address, isRealName(name) ? nameSource : 'generic');
|
|
153
|
+
return { device, isNew: true, featureAdded: true };
|
|
154
|
+
}
|
|
155
|
+
// ============================================================================
|
|
156
|
+
// Discovery Event Handlers
|
|
157
|
+
// ============================================================================
|
|
158
|
+
setupDiscoveryEvents() {
|
|
159
|
+
this.mdnsDiscovery.on('device:found', (device) => {
|
|
160
|
+
this.handleMdnsDeviceFound(device);
|
|
161
|
+
});
|
|
162
|
+
this.mdnsDiscovery.on('device:lost', (device) => {
|
|
163
|
+
this.handleDeviceLost(device.address);
|
|
164
|
+
});
|
|
165
|
+
this.mdnsDiscovery.on('started', () => {
|
|
166
|
+
this.emit('discovery:started');
|
|
167
|
+
});
|
|
168
|
+
this.mdnsDiscovery.on('stopped', () => {
|
|
169
|
+
this.emit('discovery:stopped');
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
setupSsdpDiscoveryEvents() {
|
|
173
|
+
this.ssdpDiscovery.on('device:found', (device) => {
|
|
174
|
+
this.handleSsdpDeviceFound(device);
|
|
175
|
+
});
|
|
176
|
+
this.ssdpDiscovery.on('started', () => {
|
|
177
|
+
this.emit('ssdp:started');
|
|
178
|
+
});
|
|
179
|
+
this.ssdpDiscovery.on('stopped', () => {
|
|
180
|
+
this.emit('ssdp:stopped');
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
handleMdnsDeviceFound(discovered) {
|
|
184
|
+
const manufacturer = discovered.txtRecords['usb_MFG'] || discovered.txtRecords['mfg'];
|
|
185
|
+
const model = discovered.txtRecords['usb_MDL'] || discovered.txtRecords['mdl'] || discovered.txtRecords['ty'];
|
|
186
|
+
if (discovered.type === 'scanner') {
|
|
187
|
+
const protocol = discovered.protocol === 'ipp' ? 'escl' : discovered.protocol;
|
|
188
|
+
const isSecure = discovered.txtRecords['TLS'] === '1' || (protocol === 'escl' && discovered.port === 443);
|
|
189
|
+
const feature = new ScanFeature(makeDeviceRef(discovered.address, discovered.port, discovered.name), discovered.port, {
|
|
190
|
+
protocol,
|
|
191
|
+
secure: isSecure,
|
|
192
|
+
supportedFormats: this.parseScanFormats(discovered.txtRecords),
|
|
193
|
+
supportedResolutions: this.parseScanResolutions(discovered.txtRecords),
|
|
194
|
+
supportedColorModes: this.parseScanColorModes(discovered.txtRecords),
|
|
195
|
+
supportedSources: this.parseScanSources(discovered.txtRecords),
|
|
196
|
+
hasAdf: discovered.txtRecords['is']?.includes('adf') || false,
|
|
197
|
+
hasDuplex: discovered.txtRecords['is']?.includes('duplex') || false,
|
|
198
|
+
});
|
|
199
|
+
const { device, isNew, featureAdded } = this.registerDevice(discovered.address, discovered.port, discovered.name, manufacturer, model, feature, 'mdns');
|
|
200
|
+
if (isNew || featureAdded) {
|
|
201
|
+
this.emit('device:found', { device, featureType: 'scan' });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else if (discovered.type === 'printer') {
|
|
205
|
+
const ippPath = discovered.txtRecords['rp'] || discovered.txtRecords['rfo'] || '/ipp/print';
|
|
206
|
+
const uri = `ipp://${discovered.address}:${discovered.port}${ippPath.startsWith('/') ? '' : '/'}${ippPath}`;
|
|
207
|
+
const feature = new PrintFeature(makeDeviceRef(discovered.address, discovered.port, discovered.name), discovered.port, {
|
|
208
|
+
protocol: 'ipp',
|
|
209
|
+
uri,
|
|
210
|
+
supportsColor: discovered.txtRecords['Color'] === 'T' || discovered.txtRecords['color'] === 'true',
|
|
211
|
+
supportsDuplex: discovered.txtRecords['Duplex'] === 'T' || discovered.txtRecords['duplex'] === 'true',
|
|
212
|
+
});
|
|
213
|
+
const { device, isNew, featureAdded } = this.registerDevice(discovered.address, discovered.port, discovered.name, manufacturer, model, feature, 'mdns');
|
|
214
|
+
if (isNew || featureAdded) {
|
|
215
|
+
this.emit('device:found', { device, featureType: 'print' });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else if (discovered.type === 'speaker') {
|
|
219
|
+
const protocol = discovered.protocol;
|
|
220
|
+
const playbackFeature = new PlaybackFeature(makeDeviceRef(discovered.address, discovered.port, discovered.name), discovered.port, {
|
|
221
|
+
protocol,
|
|
222
|
+
supportsQueue: protocol === 'sonos',
|
|
223
|
+
supportsSeek: protocol !== 'airplay',
|
|
224
|
+
});
|
|
225
|
+
const volumeFeature = new VolumeFeature(makeDeviceRef(discovered.address, discovered.port, discovered.name), discovered.port, {
|
|
226
|
+
volumeProtocol: protocol,
|
|
227
|
+
minVolume: 0,
|
|
228
|
+
maxVolume: 100,
|
|
229
|
+
supportsMute: true,
|
|
230
|
+
});
|
|
231
|
+
const modelName = discovered.txtRecords['model'] || discovered.txtRecords['md'];
|
|
232
|
+
// Map protocol to name source - mDNS speaker protocols
|
|
233
|
+
const speakerNameSource = protocol === 'sonos' ? 'sonos' :
|
|
234
|
+
protocol === 'airplay' ? 'airplay' :
|
|
235
|
+
protocol === 'chromecast' ? 'chromecast' :
|
|
236
|
+
protocol === 'dlna' ? 'dlna' : 'mdns';
|
|
237
|
+
const { device, isNew } = this.registerDevice(discovered.address, discovered.port, discovered.name, undefined, modelName, playbackFeature, speakerNameSource);
|
|
238
|
+
// Also add volume feature
|
|
239
|
+
if (!device.hasFeature('volume')) {
|
|
240
|
+
device.addFeature(volumeFeature);
|
|
241
|
+
}
|
|
242
|
+
if (isNew) {
|
|
243
|
+
this.emit('device:found', { device, featureType: 'playback' });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
handleSsdpDeviceFound(ssdpDevice) {
|
|
248
|
+
if (!ssdpDevice.description) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const serviceType = ssdpDevice.serviceType;
|
|
252
|
+
const deviceType = ssdpDevice.description.deviceType;
|
|
253
|
+
const desc = ssdpDevice.description;
|
|
254
|
+
// Check for DLNA Media Renderer
|
|
255
|
+
if (serviceType.includes('MediaRenderer') || deviceType.includes('MediaRenderer')) {
|
|
256
|
+
const port = parseInt(new URL(ssdpDevice.location).port) || 80;
|
|
257
|
+
const playbackFeature = new PlaybackFeature(makeDeviceRef(ssdpDevice.address, port, desc.friendlyName), port, {
|
|
258
|
+
protocol: 'dlna',
|
|
259
|
+
supportsQueue: false,
|
|
260
|
+
supportsSeek: true,
|
|
261
|
+
});
|
|
262
|
+
const volumeFeature = new VolumeFeature(makeDeviceRef(ssdpDevice.address, port, desc.friendlyName), port, {
|
|
263
|
+
volumeProtocol: 'dlna',
|
|
264
|
+
minVolume: 0,
|
|
265
|
+
maxVolume: 100,
|
|
266
|
+
supportsMute: true,
|
|
267
|
+
});
|
|
268
|
+
const { device, isNew } = this.registerDevice(ssdpDevice.address, port, desc.friendlyName, desc.manufacturer, desc.modelName, playbackFeature, 'dlna');
|
|
269
|
+
if (!device.hasFeature('volume')) {
|
|
270
|
+
device.addFeature(volumeFeature);
|
|
271
|
+
}
|
|
272
|
+
if (isNew) {
|
|
273
|
+
this.emit('device:found', { device, featureType: 'playback' });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Check for Sonos ZonePlayer
|
|
277
|
+
if (serviceType.includes('ZonePlayer') || deviceType.includes('ZonePlayer')) {
|
|
278
|
+
const playbackFeature = new PlaybackFeature(makeDeviceRef(ssdpDevice.address, 1400, desc.friendlyName), 1400, {
|
|
279
|
+
protocol: 'sonos',
|
|
280
|
+
supportsQueue: true,
|
|
281
|
+
supportsSeek: true,
|
|
282
|
+
});
|
|
283
|
+
const volumeFeature = new VolumeFeature(makeDeviceRef(ssdpDevice.address, 1400, desc.friendlyName), 1400, {
|
|
284
|
+
volumeProtocol: 'sonos',
|
|
285
|
+
minVolume: 0,
|
|
286
|
+
maxVolume: 100,
|
|
287
|
+
supportsMute: true,
|
|
288
|
+
});
|
|
289
|
+
const { device, isNew } = this.registerDevice(ssdpDevice.address, 1400, desc.friendlyName, desc.manufacturer, desc.modelName, playbackFeature, 'sonos' // Sonos has highest naming priority
|
|
290
|
+
);
|
|
291
|
+
if (!device.hasFeature('volume')) {
|
|
292
|
+
device.addFeature(volumeFeature);
|
|
293
|
+
}
|
|
294
|
+
if (isNew) {
|
|
295
|
+
this.emit('device:found', { device, featureType: 'playback' });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
async handleDeviceLost(address) {
|
|
300
|
+
const device = this.devicesByIp.get(address);
|
|
301
|
+
if (device) {
|
|
302
|
+
try {
|
|
303
|
+
await device.disconnect();
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
// Ignore disconnect errors
|
|
307
|
+
}
|
|
308
|
+
this.devicesByIp.delete(address);
|
|
309
|
+
this.nameSourceByIp.delete(address);
|
|
310
|
+
this.emit('device:lost', address);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// ============================================================================
|
|
314
|
+
// Parsing Helpers
|
|
315
|
+
// ============================================================================
|
|
316
|
+
parseScanFormats(txtRecords) {
|
|
317
|
+
const formats = [];
|
|
318
|
+
const pdl = txtRecords['pdl'] || txtRecords['DocumentFormat'] || '';
|
|
319
|
+
if (pdl.includes('jpeg') || pdl.includes('jpg'))
|
|
320
|
+
formats.push('jpeg');
|
|
321
|
+
if (pdl.includes('png'))
|
|
322
|
+
formats.push('png');
|
|
323
|
+
if (pdl.includes('pdf'))
|
|
324
|
+
formats.push('pdf');
|
|
325
|
+
if (pdl.includes('tiff'))
|
|
326
|
+
formats.push('tiff');
|
|
327
|
+
return formats.length > 0 ? formats : ['jpeg', 'png'];
|
|
328
|
+
}
|
|
329
|
+
parseScanResolutions(txtRecords) {
|
|
330
|
+
const rs = txtRecords['rs'] || '';
|
|
331
|
+
const parts = rs.split(',').map((s) => parseInt(s.trim())).filter((n) => !isNaN(n) && n > 0);
|
|
332
|
+
return parts.length > 0 ? parts : [75, 150, 300, 600];
|
|
333
|
+
}
|
|
334
|
+
parseScanColorModes(txtRecords) {
|
|
335
|
+
const cs = txtRecords['cs'] || txtRecords['ColorSpace'] || '';
|
|
336
|
+
const modes = [];
|
|
337
|
+
if (cs.includes('color') || cs.includes('RGB'))
|
|
338
|
+
modes.push('color');
|
|
339
|
+
if (cs.includes('gray') || cs.includes('grayscale'))
|
|
340
|
+
modes.push('grayscale');
|
|
341
|
+
if (cs.includes('binary') || cs.includes('bw'))
|
|
342
|
+
modes.push('blackwhite');
|
|
343
|
+
return modes.length > 0 ? modes : ['color', 'grayscale'];
|
|
344
|
+
}
|
|
345
|
+
parseScanSources(txtRecords) {
|
|
346
|
+
const is = txtRecords['is'] || txtRecords['InputSource'] || '';
|
|
347
|
+
const sources = [];
|
|
348
|
+
if (is.includes('platen') || is.includes('flatbed') || is === '') {
|
|
349
|
+
sources.push('flatbed');
|
|
350
|
+
}
|
|
351
|
+
if (is.includes('adf') || is.includes('feeder')) {
|
|
352
|
+
sources.push('adf');
|
|
353
|
+
}
|
|
354
|
+
if (is.includes('duplex')) {
|
|
355
|
+
sources.push('adf-duplex');
|
|
356
|
+
}
|
|
357
|
+
return sources.length > 0 ? sources : ['flatbed'];
|
|
358
|
+
}
|
|
359
|
+
// ============================================================================
|
|
360
|
+
// Discovery Control
|
|
361
|
+
// ============================================================================
|
|
362
|
+
async startDiscovery() {
|
|
363
|
+
await Promise.all([
|
|
364
|
+
this.mdnsDiscovery.start(),
|
|
365
|
+
this.ssdpDiscovery.start(),
|
|
366
|
+
]);
|
|
367
|
+
}
|
|
368
|
+
async stopDiscovery() {
|
|
369
|
+
await Promise.all([
|
|
370
|
+
this.mdnsDiscovery.stop(),
|
|
371
|
+
this.ssdpDiscovery.stop(),
|
|
372
|
+
]);
|
|
373
|
+
}
|
|
374
|
+
get isDiscovering() {
|
|
375
|
+
return this.mdnsDiscovery.running || this.ssdpDiscovery.isRunning;
|
|
376
|
+
}
|
|
377
|
+
// ============================================================================
|
|
378
|
+
// Device Access - All Devices
|
|
379
|
+
// ============================================================================
|
|
380
|
+
/**
|
|
381
|
+
* Get devices matching the selector criteria.
|
|
382
|
+
* Returns all devices if no selector provided, or devices matching all criteria.
|
|
383
|
+
* @param selector Optional criteria to filter devices
|
|
384
|
+
* @returns Array of matching devices (empty if no matches)
|
|
385
|
+
*/
|
|
386
|
+
getDevices(selector) {
|
|
387
|
+
const devices = Array.from(this.devicesByIp.values());
|
|
388
|
+
if (!selector) {
|
|
389
|
+
return devices;
|
|
390
|
+
}
|
|
391
|
+
return devices.filter((device) => this.matchesSelector(device, selector));
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Select exactly ONE device matching the criteria.
|
|
395
|
+
* Use this when you expect a specific device and want fail-fast behavior.
|
|
396
|
+
* @param selector Criteria to match the device
|
|
397
|
+
* @returns The matching device
|
|
398
|
+
* @throws Error if no device matches the selector
|
|
399
|
+
*/
|
|
400
|
+
selectDevice(selector) {
|
|
401
|
+
const matches = this.getDevices(selector);
|
|
402
|
+
if (matches.length === 0) {
|
|
403
|
+
throw new Error(`No device found matching: ${JSON.stringify(selector)}`);
|
|
404
|
+
}
|
|
405
|
+
if (matches.length > 1 && !selector.address && !selector.id) {
|
|
406
|
+
// Multiple matches without unique identifier - log warning
|
|
407
|
+
console.warn(`Multiple devices (${matches.length}) match selector, using first: ${matches[0].name}`);
|
|
408
|
+
}
|
|
409
|
+
return matches[0];
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Get device by ID
|
|
413
|
+
* @deprecated Use getDevices({ id }) or selectDevice({ id }) instead
|
|
414
|
+
*/
|
|
415
|
+
getDevice(id) {
|
|
416
|
+
for (const device of this.devicesByIp.values()) {
|
|
417
|
+
if (device.id === id) {
|
|
418
|
+
return device;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return undefined;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Get device by address
|
|
425
|
+
* @deprecated Use getDevices({ address }) or selectDevice({ address }) instead
|
|
426
|
+
*/
|
|
427
|
+
getDeviceByAddress(address) {
|
|
428
|
+
return this.devicesByIp.get(address);
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Check if a device matches the selector criteria
|
|
432
|
+
*/
|
|
433
|
+
matchesSelector(device, selector) {
|
|
434
|
+
// Identity checks (exact match)
|
|
435
|
+
if (selector.id && device.id !== selector.id) {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
if (selector.address && device.address !== selector.address) {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
// Attribute checks (partial match, case-insensitive)
|
|
442
|
+
if (selector.name && !device.name.toLowerCase().includes(selector.name.toLowerCase())) {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
if (selector.model && !device.model?.toLowerCase().includes(selector.model.toLowerCase())) {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
if (selector.manufacturer &&
|
|
449
|
+
!device.manufacturer?.toLowerCase().includes(selector.manufacturer.toLowerCase())) {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
// Capability checks
|
|
453
|
+
if (selector.hasFeature && !device.hasFeature(selector.hasFeature)) {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
if (selector.hasFeatures && !device.hasFeatures(selector.hasFeatures)) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
if (selector.hasAnyFeature && !device.hasAnyFeature(selector.hasAnyFeature)) {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
// ============================================================================
|
|
465
|
+
// Device Access - By Feature Type
|
|
466
|
+
// ============================================================================
|
|
467
|
+
/**
|
|
468
|
+
* Get all devices that have a specific feature
|
|
469
|
+
*/
|
|
470
|
+
getDevicesWithFeature(featureType) {
|
|
471
|
+
return this.getDevices().filter(device => device.hasFeature(featureType));
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Get all devices that have ALL specified features
|
|
475
|
+
*/
|
|
476
|
+
getDevicesWithFeatures(featureTypes) {
|
|
477
|
+
return this.getDevices().filter(device => device.hasFeatures(featureTypes));
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Get all devices that have ANY of the specified features
|
|
481
|
+
*/
|
|
482
|
+
getDevicesWithAnyFeature(featureTypes) {
|
|
483
|
+
return this.getDevices().filter(device => featureTypes.some(type => device.hasFeature(type)));
|
|
484
|
+
}
|
|
485
|
+
// ============================================================================
|
|
486
|
+
// Convenience Accessors (by feature type)
|
|
487
|
+
// ============================================================================
|
|
488
|
+
/**
|
|
489
|
+
* Get all scanner devices
|
|
490
|
+
*/
|
|
491
|
+
getScanners() {
|
|
492
|
+
return this.getDevicesWithFeature('scan');
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Get all printer devices
|
|
496
|
+
*/
|
|
497
|
+
getPrinters() {
|
|
498
|
+
return this.getDevicesWithFeature('print');
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Get all speaker devices (playback feature)
|
|
502
|
+
*/
|
|
503
|
+
getSpeakers() {
|
|
504
|
+
return this.getDevicesWithFeature('playback');
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Get all UPS/power devices
|
|
508
|
+
*/
|
|
509
|
+
getUpsDevices() {
|
|
510
|
+
return this.getDevicesWithFeature('power');
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Get all SNMP devices
|
|
514
|
+
*/
|
|
515
|
+
getSnmpDevices() {
|
|
516
|
+
return this.getDevicesWithFeature('snmp');
|
|
517
|
+
}
|
|
518
|
+
// ============================================================================
|
|
519
|
+
// Manual Device Addition
|
|
520
|
+
// ============================================================================
|
|
521
|
+
/**
|
|
522
|
+
* Add a device
|
|
523
|
+
*/
|
|
524
|
+
addDevice(device) {
|
|
525
|
+
const existing = this.devicesByIp.get(device.address);
|
|
526
|
+
if (existing) {
|
|
527
|
+
// Merge features into existing device
|
|
528
|
+
for (const feature of device.getFeatures()) {
|
|
529
|
+
if (!existing.hasFeature(feature.type)) {
|
|
530
|
+
existing.addFeature(feature);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
// Update name if better using priority system
|
|
534
|
+
const existingSource = this.nameSourceByIp.get(device.address);
|
|
535
|
+
if (shouldUpdateName(existingSource, 'manual', existing.name, device.name)) {
|
|
536
|
+
existing.name = device.name;
|
|
537
|
+
this.nameSourceByIp.set(device.address, 'manual');
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
this.devicesByIp.set(device.address, device);
|
|
542
|
+
this.nameSourceByIp.set(device.address, isRealName(device.name) ? 'manual' : 'generic');
|
|
543
|
+
this.emit('device:added', device);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Add a scanner manually
|
|
548
|
+
*/
|
|
549
|
+
async addScanner(address, port, protocol = 'escl', name) {
|
|
550
|
+
const feature = new ScanFeature(makeDeviceRef(address, port, name || `Scanner at ${address}`), port, { protocol });
|
|
551
|
+
const { device } = this.registerDevice(address, port, name, undefined, undefined, feature, 'manual');
|
|
552
|
+
await device.connect();
|
|
553
|
+
return device;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Add a printer manually
|
|
557
|
+
*/
|
|
558
|
+
async addPrinter(address, port = 631, name, ippPath) {
|
|
559
|
+
const path = ippPath || '/ipp/print';
|
|
560
|
+
const uri = `ipp://${address}:${port}${path.startsWith('/') ? '' : '/'}${path}`;
|
|
561
|
+
const feature = new PrintFeature(makeDeviceRef(address, port, name || `Printer at ${address}`), port, { protocol: 'ipp', uri });
|
|
562
|
+
const { device } = this.registerDevice(address, port, name, undefined, undefined, feature, 'manual');
|
|
563
|
+
await device.connect();
|
|
564
|
+
return device;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Add an SNMP device manually
|
|
568
|
+
*/
|
|
569
|
+
async addSnmpDevice(address, port = 161, options) {
|
|
570
|
+
const feature = new SnmpFeature(makeDeviceRef(address, port, options?.name || `SNMP Device at ${address}`), port, { community: options?.community ?? 'public' });
|
|
571
|
+
const { device } = this.registerDevice(address, port, options?.name, undefined, undefined, feature, 'manual');
|
|
572
|
+
await device.connect();
|
|
573
|
+
return device;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Add a UPS device manually
|
|
577
|
+
*/
|
|
578
|
+
async addUpsDevice(address, protocol, options) {
|
|
579
|
+
const port = options?.port ?? (protocol === 'nut' ? 3493 : 161);
|
|
580
|
+
const feature = new PowerFeature(makeDeviceRef(address, port, options?.name || `UPS at ${address}`), port, {
|
|
581
|
+
protocol,
|
|
582
|
+
upsName: options?.upsName,
|
|
583
|
+
community: options?.community,
|
|
584
|
+
});
|
|
585
|
+
const { device } = this.registerDevice(address, port, options?.name, undefined, undefined, feature, 'manual');
|
|
586
|
+
await device.connect();
|
|
587
|
+
return device;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Add a speaker manually
|
|
591
|
+
*/
|
|
592
|
+
async addSpeaker(address, protocol, options) {
|
|
593
|
+
const defaultPort = protocol === 'sonos' ? 1400 : protocol === 'airplay' ? 7000 : protocol === 'chromecast' ? 8009 : 80;
|
|
594
|
+
const port = options?.port ?? defaultPort;
|
|
595
|
+
const deviceName = options?.name || `${protocol} at ${address}`;
|
|
596
|
+
const playbackFeature = new PlaybackFeature(makeDeviceRef(address, port, deviceName), port, {
|
|
597
|
+
protocol,
|
|
598
|
+
supportsQueue: protocol === 'sonos',
|
|
599
|
+
supportsSeek: protocol !== 'airplay',
|
|
600
|
+
});
|
|
601
|
+
const volumeFeature = new VolumeFeature(makeDeviceRef(address, port, deviceName), port, {
|
|
602
|
+
volumeProtocol: protocol,
|
|
603
|
+
minVolume: 0,
|
|
604
|
+
maxVolume: 100,
|
|
605
|
+
supportsMute: true,
|
|
606
|
+
});
|
|
607
|
+
// Use protocol as name source for speakers
|
|
608
|
+
const speakerNameSource = protocol === 'sonos' ? 'sonos' :
|
|
609
|
+
protocol === 'airplay' ? 'airplay' :
|
|
610
|
+
protocol === 'chromecast' ? 'chromecast' : 'dlna';
|
|
611
|
+
const { device } = this.registerDevice(address, port, options?.name, undefined, undefined, playbackFeature, speakerNameSource);
|
|
612
|
+
if (!device.hasFeature('volume')) {
|
|
613
|
+
device.addFeature(volumeFeature);
|
|
614
|
+
}
|
|
615
|
+
await device.connect();
|
|
616
|
+
return device;
|
|
617
|
+
}
|
|
618
|
+
// ============================================================================
|
|
619
|
+
// Feature Access
|
|
620
|
+
// ============================================================================
|
|
621
|
+
/**
|
|
622
|
+
* Get a feature from a device
|
|
623
|
+
*/
|
|
624
|
+
getFeature(deviceId, featureType) {
|
|
625
|
+
const device = this.getDevice(deviceId);
|
|
626
|
+
return device?.getFeature(featureType);
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Add a feature to a device
|
|
630
|
+
*/
|
|
631
|
+
addFeature(deviceId, feature) {
|
|
632
|
+
const device = this.getDevice(deviceId);
|
|
633
|
+
if (device) {
|
|
634
|
+
device.addFeature(feature);
|
|
635
|
+
this.emit('feature:added', { device, feature });
|
|
636
|
+
return true;
|
|
637
|
+
}
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Remove a feature from a device
|
|
642
|
+
*/
|
|
643
|
+
async removeFeature(deviceId, featureType) {
|
|
644
|
+
const device = this.getDevice(deviceId);
|
|
645
|
+
if (device) {
|
|
646
|
+
const removed = await device.removeFeature(featureType);
|
|
647
|
+
if (removed) {
|
|
648
|
+
this.emit('feature:removed', { device, featureType });
|
|
649
|
+
}
|
|
650
|
+
return removed;
|
|
651
|
+
}
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
// ============================================================================
|
|
655
|
+
// Network Scanning
|
|
656
|
+
// ============================================================================
|
|
657
|
+
get networkScanner() {
|
|
658
|
+
if (!this._networkScanner) {
|
|
659
|
+
this._networkScanner = new NetworkScanner();
|
|
660
|
+
this.setupNetworkScannerEvents();
|
|
661
|
+
}
|
|
662
|
+
return this._networkScanner;
|
|
663
|
+
}
|
|
664
|
+
setupNetworkScannerEvents() {
|
|
665
|
+
if (!this._networkScanner)
|
|
666
|
+
return;
|
|
667
|
+
this._networkScanner.on('device:found', (result) => {
|
|
668
|
+
this.emit('network:device:found', result);
|
|
669
|
+
});
|
|
670
|
+
this._networkScanner.on('progress', (progress) => {
|
|
671
|
+
this.emit('network:progress', progress);
|
|
672
|
+
});
|
|
673
|
+
this._networkScanner.on('complete', (results) => {
|
|
674
|
+
this.emit('network:complete', results);
|
|
675
|
+
});
|
|
676
|
+
this._networkScanner.on('error', (error) => {
|
|
677
|
+
this.emit('error', error);
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
async scanNetwork(options) {
|
|
681
|
+
const results = await this.networkScanner.scan(options);
|
|
682
|
+
const foundDevices = [];
|
|
683
|
+
const seenAddresses = new Set();
|
|
684
|
+
for (const result of results) {
|
|
685
|
+
// Skip if we already processed this IP in this scan
|
|
686
|
+
if (seenAddresses.has(result.address))
|
|
687
|
+
continue;
|
|
688
|
+
seenAddresses.add(result.address);
|
|
689
|
+
for (const deviceInfo of result.devices) {
|
|
690
|
+
try {
|
|
691
|
+
let device = null;
|
|
692
|
+
if (deviceInfo.type === 'scanner') {
|
|
693
|
+
device = await this.addScanner(result.address, deviceInfo.port, deviceInfo.protocol, deviceInfo.name);
|
|
694
|
+
}
|
|
695
|
+
else if (deviceInfo.type === 'printer' && deviceInfo.protocol === 'ipp') {
|
|
696
|
+
device = await this.addPrinter(result.address, deviceInfo.port, deviceInfo.name);
|
|
697
|
+
}
|
|
698
|
+
else if (deviceInfo.type === 'speaker') {
|
|
699
|
+
device = await this.addSpeaker(result.address, deviceInfo.protocol, { name: deviceInfo.name, port: deviceInfo.port });
|
|
700
|
+
}
|
|
701
|
+
if (device && !foundDevices.includes(device)) {
|
|
702
|
+
foundDevices.push(device);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
catch (error) {
|
|
706
|
+
this.emit('error', error instanceof Error ? error : new Error(String(error)));
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return foundDevices;
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Discover scanners in a subnet and add them to the manager.
|
|
714
|
+
* Convenience method that focuses discovery on scanner protocols only (eSCL, SANE).
|
|
715
|
+
*
|
|
716
|
+
* @param subnet CIDR notation subnet (e.g., '192.168.1.0/24')
|
|
717
|
+
* @param options Optional discovery settings
|
|
718
|
+
* @returns Array of discovered scanner devices
|
|
719
|
+
*
|
|
720
|
+
* @example
|
|
721
|
+
* ```typescript
|
|
722
|
+
* const scanners = await manager.discoverScanners('192.168.190.0/24');
|
|
723
|
+
* for (const scanner of scanners) {
|
|
724
|
+
* console.log(`Found: ${scanner.name} at ${scanner.address}`);
|
|
725
|
+
* }
|
|
726
|
+
* ```
|
|
727
|
+
*/
|
|
728
|
+
async discoverScanners(subnet, options) {
|
|
729
|
+
await this.scanNetwork({
|
|
730
|
+
ipRange: subnet,
|
|
731
|
+
probeEscl: true,
|
|
732
|
+
probeSane: true,
|
|
733
|
+
probeIpp: false,
|
|
734
|
+
probeAirplay: false,
|
|
735
|
+
probeSonos: false,
|
|
736
|
+
probeChromecast: false,
|
|
737
|
+
concurrency: options?.concurrency ?? 50,
|
|
738
|
+
timeout: options?.timeout ?? 3000,
|
|
739
|
+
});
|
|
740
|
+
return this.getScanners();
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Discover printers in a subnet and add them to the manager.
|
|
744
|
+
* Convenience method that focuses discovery on printer protocols only (IPP).
|
|
745
|
+
*
|
|
746
|
+
* @param subnet CIDR notation subnet (e.g., '192.168.1.0/24')
|
|
747
|
+
* @param options Optional discovery settings
|
|
748
|
+
* @returns Array of discovered printer devices
|
|
749
|
+
*/
|
|
750
|
+
async discoverPrinters(subnet, options) {
|
|
751
|
+
await this.scanNetwork({
|
|
752
|
+
ipRange: subnet,
|
|
753
|
+
probeEscl: false,
|
|
754
|
+
probeSane: false,
|
|
755
|
+
probeIpp: true,
|
|
756
|
+
probeAirplay: false,
|
|
757
|
+
probeSonos: false,
|
|
758
|
+
probeChromecast: false,
|
|
759
|
+
concurrency: options?.concurrency ?? 50,
|
|
760
|
+
timeout: options?.timeout ?? 3000,
|
|
761
|
+
});
|
|
762
|
+
return this.getPrinters();
|
|
763
|
+
}
|
|
764
|
+
async cancelNetworkScan() {
|
|
765
|
+
if (this._networkScanner) {
|
|
766
|
+
await this._networkScanner.cancel();
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
get isNetworkScanning() {
|
|
770
|
+
return this._networkScanner?.isScanning ?? false;
|
|
771
|
+
}
|
|
772
|
+
// ============================================================================
|
|
773
|
+
// Device Removal and Cleanup
|
|
774
|
+
// ============================================================================
|
|
775
|
+
/**
|
|
776
|
+
* Remove a device by ID
|
|
777
|
+
*/
|
|
778
|
+
async removeDevice(id) {
|
|
779
|
+
const device = this.getDevice(id);
|
|
780
|
+
if (device) {
|
|
781
|
+
await device.disconnect();
|
|
782
|
+
this.devicesByIp.delete(device.address);
|
|
783
|
+
this.nameSourceByIp.delete(device.address);
|
|
784
|
+
this.emit('device:removed', id);
|
|
785
|
+
return true;
|
|
786
|
+
}
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Remove a device by address
|
|
791
|
+
*/
|
|
792
|
+
async removeDeviceByAddress(address) {
|
|
793
|
+
const device = this.devicesByIp.get(address);
|
|
794
|
+
if (device) {
|
|
795
|
+
await device.disconnect();
|
|
796
|
+
this.devicesByIp.delete(address);
|
|
797
|
+
this.nameSourceByIp.delete(address);
|
|
798
|
+
this.emit('device:removed', device.id);
|
|
799
|
+
return true;
|
|
800
|
+
}
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Disconnect all devices
|
|
805
|
+
*/
|
|
806
|
+
async disconnectAll() {
|
|
807
|
+
const promises = [];
|
|
808
|
+
for (const device of this.devicesByIp.values()) {
|
|
809
|
+
promises.push(device.disconnect().catch(() => { }));
|
|
810
|
+
}
|
|
811
|
+
await Promise.all(promises);
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Stop discovery and disconnect all devices
|
|
815
|
+
*/
|
|
816
|
+
async shutdown() {
|
|
817
|
+
await this.stopDiscovery();
|
|
818
|
+
await this.disconnectAll();
|
|
819
|
+
this.devicesByIp.clear();
|
|
820
|
+
this.nameSourceByIp.clear();
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
// ============================================================================
|
|
824
|
+
// Exports
|
|
825
|
+
// ============================================================================
|
|
826
|
+
export {
|
|
827
|
+
// Discovery
|
|
828
|
+
MdnsDiscovery, NetworkScanner, SsdpDiscovery, SERVICE_TYPES, SSDP_SERVICE_TYPES,
|
|
829
|
+
// Universal Device
|
|
830
|
+
UniversalDevice,
|
|
831
|
+
// Features
|
|
832
|
+
ScanFeature, PrintFeature, PlaybackFeature, VolumeFeature, PowerFeature, SnmpFeature,
|
|
833
|
+
// Factories
|
|
834
|
+
createScanner, createPrinter, createSnmpDevice, createUpsDevice, createSpeaker, createDlnaRenderer, };
|
|
835
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGV2aWNlbWFuYWdlci5jbGFzc2VzLmRldmljZW1hbmFnZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9kZXZpY2VtYW5hZ2VyLmNsYXNzZXMuZGV2aWNlbWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFFSCxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEVBQUUsYUFBYSxFQUFFLGFBQWEsRUFBRSxNQUFNLHVDQUF1QyxDQUFDO0FBQ3JGLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxpREFBaUQsQ0FBQztBQUNqRixPQUFPLEVBQUUsYUFBYSxFQUFFLGtCQUFrQixFQUFvQixNQUFNLHVDQUF1QyxDQUFDO0FBQzVHLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUNwRSxPQUFPLEVBQ0wsV0FBVyxFQUNYLFlBQVksRUFDWixlQUFlLEVBQ2YsYUFBYSxFQUNiLFlBQVksRUFDWixXQUFXLEdBQ1osTUFBTSxxQkFBcUIsQ0FBQztBQUU3QixPQUFPLEVBQ0wsYUFBYSxFQUNiLGFBQWEsRUFDYixnQkFBZ0IsRUFDaEIsZUFBZSxFQUNmLGFBQWEsRUFDYixrQkFBa0IsR0FPbkIsTUFBTSxzQkFBc0IsQ0FBQztBQVc5Qjs7R0FFRztBQUNILE1BQU0sZUFBZSxHQUFvQztJQUN2RCxhQUFhLEVBQUUsSUFBSTtJQUNuQixnQkFBZ0IsRUFBRSxLQUFLO0lBQ3ZCLFdBQVcsRUFBRSxJQUFJO0lBQ2pCLFVBQVUsRUFBRSxDQUFDO0lBQ2IsY0FBYyxFQUFFLElBQUk7Q0FDckIsQ0FBQztBQVFGLE1BQU0sb0JBQW9CLEdBQWdDO0lBQ3hELE9BQU8sRUFBRSxDQUFDLEVBQU0sNkJBQTZCO0lBQzdDLE1BQU0sRUFBRSxDQUFDLEVBQU8seUJBQXlCO0lBQ3pDLE9BQU8sRUFBRSxDQUFDLEVBQU0saUNBQWlDO0lBQ2pELFVBQVUsRUFBRSxDQUFDLEVBQUcsOEJBQThCO0lB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
|