@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,329 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import type {
|
|
3
|
+
IPrinterCapabilities,
|
|
4
|
+
IPrintOptions,
|
|
5
|
+
IPrintJob,
|
|
6
|
+
} from '../interfaces/index.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* IPP protocol wrapper using the ipp npm package
|
|
10
|
+
*/
|
|
11
|
+
export class IppProtocol {
|
|
12
|
+
private printerUrl: string;
|
|
13
|
+
private printer: ReturnType<typeof plugins.ipp.Printer>;
|
|
14
|
+
|
|
15
|
+
constructor(address: string, port: number, path: string = '/ipp/print') {
|
|
16
|
+
this.printerUrl = `ipp://${address}:${port}${path}`;
|
|
17
|
+
this.printer = plugins.ipp.Printer(this.printerUrl);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get printer attributes/capabilities
|
|
22
|
+
*/
|
|
23
|
+
public async getAttributes(): Promise<IPrinterCapabilities> {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
this.printer.execute(
|
|
26
|
+
'Get-Printer-Attributes',
|
|
27
|
+
null,
|
|
28
|
+
(err: Error | null, res: Record<string, unknown>) => {
|
|
29
|
+
if (err) {
|
|
30
|
+
reject(err);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const attrs = res['printer-attributes-tag'] as Record<string, unknown> || {};
|
|
36
|
+
resolve(this.parseCapabilities(attrs));
|
|
37
|
+
} catch (parseErr) {
|
|
38
|
+
reject(parseErr);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Print a document
|
|
47
|
+
*/
|
|
48
|
+
public async print(data: Buffer, options?: IPrintOptions): Promise<IPrintJob> {
|
|
49
|
+
const msg = this.buildPrintMessage(options);
|
|
50
|
+
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
this.printer.execute(
|
|
53
|
+
'Print-Job',
|
|
54
|
+
{ ...msg, data },
|
|
55
|
+
(err: Error | null, res: Record<string, unknown>) => {
|
|
56
|
+
if (err) {
|
|
57
|
+
reject(err);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const jobAttrs = res['job-attributes-tag'] as Record<string, unknown> || {};
|
|
63
|
+
resolve(this.parseJobInfo(jobAttrs));
|
|
64
|
+
} catch (parseErr) {
|
|
65
|
+
reject(parseErr);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get all jobs
|
|
74
|
+
*/
|
|
75
|
+
public async getJobs(): Promise<IPrintJob[]> {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
this.printer.execute(
|
|
78
|
+
'Get-Jobs',
|
|
79
|
+
{
|
|
80
|
+
'operation-attributes-tag': {
|
|
81
|
+
'requesting-user-name': 'devicemanager',
|
|
82
|
+
'which-jobs': 'not-completed',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
(err: Error | null, res: Record<string, unknown>) => {
|
|
86
|
+
if (err) {
|
|
87
|
+
reject(err);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const jobs: IPrintJob[] = [];
|
|
93
|
+
const jobTags = res['job-attributes-tag'];
|
|
94
|
+
|
|
95
|
+
if (Array.isArray(jobTags)) {
|
|
96
|
+
for (const jobAttrs of jobTags) {
|
|
97
|
+
jobs.push(this.parseJobInfo(jobAttrs as Record<string, unknown>));
|
|
98
|
+
}
|
|
99
|
+
} else if (jobTags && typeof jobTags === 'object') {
|
|
100
|
+
jobs.push(this.parseJobInfo(jobTags as Record<string, unknown>));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
resolve(jobs);
|
|
104
|
+
} catch (parseErr) {
|
|
105
|
+
reject(parseErr);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get specific job info
|
|
114
|
+
*/
|
|
115
|
+
public async getJobInfo(jobId: number): Promise<IPrintJob> {
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
this.printer.execute(
|
|
118
|
+
'Get-Job-Attributes',
|
|
119
|
+
{
|
|
120
|
+
'operation-attributes-tag': {
|
|
121
|
+
'job-id': jobId,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
(err: Error | null, res: Record<string, unknown>) => {
|
|
125
|
+
if (err) {
|
|
126
|
+
reject(err);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const jobAttrs = res['job-attributes-tag'] as Record<string, unknown> || {};
|
|
132
|
+
resolve(this.parseJobInfo(jobAttrs));
|
|
133
|
+
} catch (parseErr) {
|
|
134
|
+
reject(parseErr);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Cancel a job
|
|
143
|
+
*/
|
|
144
|
+
public async cancelJob(jobId: number): Promise<void> {
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
146
|
+
this.printer.execute(
|
|
147
|
+
'Cancel-Job',
|
|
148
|
+
{
|
|
149
|
+
'operation-attributes-tag': {
|
|
150
|
+
'job-id': jobId,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
(err: Error | null, _res: Record<string, unknown>) => {
|
|
154
|
+
if (err) {
|
|
155
|
+
reject(err);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
resolve();
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Check if printer is available
|
|
166
|
+
*/
|
|
167
|
+
public async checkAvailability(): Promise<boolean> {
|
|
168
|
+
try {
|
|
169
|
+
await this.getAttributes();
|
|
170
|
+
return true;
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Build IPP print message from options
|
|
178
|
+
*/
|
|
179
|
+
private buildPrintMessage(options?: IPrintOptions): Record<string, unknown> {
|
|
180
|
+
const operationAttrs: Record<string, unknown> = {
|
|
181
|
+
'requesting-user-name': 'devicemanager',
|
|
182
|
+
'job-name': options?.jobName ?? 'Print Job',
|
|
183
|
+
'document-format': 'application/octet-stream',
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const jobAttrs: Record<string, unknown> = {};
|
|
187
|
+
|
|
188
|
+
if (options?.copies && options.copies > 1) {
|
|
189
|
+
jobAttrs['copies'] = options.copies;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (options?.mediaSize) {
|
|
193
|
+
jobAttrs['media'] = options.mediaSize;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (options?.mediaType) {
|
|
197
|
+
jobAttrs['media-type'] = options.mediaType;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (options?.sides) {
|
|
201
|
+
jobAttrs['sides'] = options.sides;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (options?.quality) {
|
|
205
|
+
const qualityMap: Record<string, number> = {
|
|
206
|
+
draft: 3,
|
|
207
|
+
normal: 4,
|
|
208
|
+
high: 5,
|
|
209
|
+
};
|
|
210
|
+
jobAttrs['print-quality'] = qualityMap[options.quality] ?? 4;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (options?.colorMode) {
|
|
214
|
+
jobAttrs['print-color-mode'] = options.colorMode;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const msg: Record<string, unknown> = {
|
|
218
|
+
'operation-attributes-tag': operationAttrs,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
if (Object.keys(jobAttrs).length > 0) {
|
|
222
|
+
msg['job-attributes-tag'] = jobAttrs;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return msg;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Parse printer capabilities from attributes
|
|
230
|
+
*/
|
|
231
|
+
private parseCapabilities(attrs: Record<string, unknown>): IPrinterCapabilities {
|
|
232
|
+
const getArray = (key: string): string[] => {
|
|
233
|
+
const value = attrs[key];
|
|
234
|
+
if (Array.isArray(value)) return value.map(String);
|
|
235
|
+
if (value !== undefined) return [String(value)];
|
|
236
|
+
return [];
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const getNumber = (key: string, defaultVal: number): number => {
|
|
240
|
+
const value = attrs[key];
|
|
241
|
+
if (typeof value === 'number') return value;
|
|
242
|
+
if (typeof value === 'string') return parseInt(value) || defaultVal;
|
|
243
|
+
return defaultVal;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const getBool = (key: string, defaultVal: boolean): boolean => {
|
|
247
|
+
const value = attrs[key];
|
|
248
|
+
if (typeof value === 'boolean') return value;
|
|
249
|
+
if (value === 'true' || value === 1) return true;
|
|
250
|
+
if (value === 'false' || value === 0) return false;
|
|
251
|
+
return defaultVal;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// Parse resolutions
|
|
255
|
+
const resolutions: number[] = [];
|
|
256
|
+
const resSupported = attrs['printer-resolution-supported'];
|
|
257
|
+
if (Array.isArray(resSupported)) {
|
|
258
|
+
for (const res of resSupported) {
|
|
259
|
+
if (typeof res === 'object' && res !== null && 'x' in res) {
|
|
260
|
+
resolutions.push((res as { x: number }).x);
|
|
261
|
+
} else if (typeof res === 'number') {
|
|
262
|
+
resolutions.push(res);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (resolutions.length === 0) {
|
|
267
|
+
resolutions.push(300, 600);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
colorSupported: getBool('color-supported', false),
|
|
272
|
+
duplexSupported:
|
|
273
|
+
getArray('sides-supported').some((s) =>
|
|
274
|
+
s.includes('two-sided')
|
|
275
|
+
),
|
|
276
|
+
mediaSizes: getArray('media-supported'),
|
|
277
|
+
mediaTypes: getArray('media-type-supported'),
|
|
278
|
+
resolutions: [...new Set(resolutions)],
|
|
279
|
+
maxCopies: getNumber('copies-supported', 99),
|
|
280
|
+
sidesSupported: getArray('sides-supported'),
|
|
281
|
+
qualitySupported: getArray('print-quality-supported').map(String),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Parse job info from attributes
|
|
287
|
+
*/
|
|
288
|
+
private parseJobInfo(attrs: Record<string, unknown>): IPrintJob {
|
|
289
|
+
const getString = (key: string, defaultVal: string): string => {
|
|
290
|
+
const value = attrs[key];
|
|
291
|
+
if (typeof value === 'string') return value;
|
|
292
|
+
if (value !== undefined) return String(value);
|
|
293
|
+
return defaultVal;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const getNumber = (key: string, defaultVal: number): number => {
|
|
297
|
+
const value = attrs[key];
|
|
298
|
+
if (typeof value === 'number') return value;
|
|
299
|
+
if (typeof value === 'string') return parseInt(value) || defaultVal;
|
|
300
|
+
return defaultVal;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// Map IPP job state to our state
|
|
304
|
+
const ippState = getNumber('job-state', 3);
|
|
305
|
+
const stateMap: Record<number, IPrintJob['state']> = {
|
|
306
|
+
3: 'pending', // pending
|
|
307
|
+
4: 'pending', // pending-held
|
|
308
|
+
5: 'processing', // processing
|
|
309
|
+
6: 'processing', // processing-stopped
|
|
310
|
+
7: 'canceled', // canceled
|
|
311
|
+
8: 'aborted', // aborted
|
|
312
|
+
9: 'completed', // completed
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const createdTime = attrs['time-at-creation'];
|
|
316
|
+
const completedTime = attrs['time-at-completed'];
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
id: getNumber('job-id', 0),
|
|
320
|
+
name: getString('job-name', 'Unknown Job'),
|
|
321
|
+
state: stateMap[ippState] ?? 'pending',
|
|
322
|
+
stateReason: getString('job-state-reasons', undefined),
|
|
323
|
+
createdAt: createdTime ? new Date(createdTime as number * 1000) : new Date(),
|
|
324
|
+
completedAt: completedTime ? new Date(completedTime as number * 1000) : undefined,
|
|
325
|
+
pagesPrinted: getNumber('job-media-sheets-completed', undefined),
|
|
326
|
+
pagesTotal: getNumber('job-media-sheets', undefined),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|