@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.
Files changed (137) hide show
  1. package/.gitea/workflows/default_nottags.yaml +66 -0
  2. package/.gitea/workflows/default_tags.yaml +124 -0
  3. package/.vscode/launch.json +11 -0
  4. package/.vscode/settings.json +26 -0
  5. package/changelog.md +91 -0
  6. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  7. package/dist_ts/00_commitinfo_data.js +9 -0
  8. package/dist_ts/device/device.classes.device.d.ts +205 -0
  9. package/dist_ts/device/device.classes.device.js +344 -0
  10. package/dist_ts/devicemanager.classes.devicemanager.d.ts +210 -0
  11. package/dist_ts/devicemanager.classes.devicemanager.js +835 -0
  12. package/dist_ts/discovery/discovery.classes.homeassistant.d.ts +101 -0
  13. package/dist_ts/discovery/discovery.classes.homeassistant.js +313 -0
  14. package/dist_ts/discovery/discovery.classes.mdns.d.ts +90 -0
  15. package/dist_ts/discovery/discovery.classes.mdns.js +291 -0
  16. package/dist_ts/discovery/discovery.classes.networkscanner.d.ts +64 -0
  17. package/dist_ts/discovery/discovery.classes.networkscanner.js +535 -0
  18. package/dist_ts/discovery/discovery.classes.ssdp.d.ts +112 -0
  19. package/dist_ts/discovery/discovery.classes.ssdp.js +255 -0
  20. package/dist_ts/factories/index.d.ts +190 -0
  21. package/dist_ts/factories/index.js +350 -0
  22. package/dist_ts/features/feature.abstract.d.ts +118 -0
  23. package/dist_ts/features/feature.abstract.js +172 -0
  24. package/dist_ts/features/feature.camera.d.ts +95 -0
  25. package/dist_ts/features/feature.camera.js +162 -0
  26. package/dist_ts/features/feature.climate.d.ts +174 -0
  27. package/dist_ts/features/feature.climate.js +328 -0
  28. package/dist_ts/features/feature.cover.d.ts +121 -0
  29. package/dist_ts/features/feature.cover.js +217 -0
  30. package/dist_ts/features/feature.fan.d.ts +127 -0
  31. package/dist_ts/features/feature.fan.js +235 -0
  32. package/dist_ts/features/feature.light.d.ts +156 -0
  33. package/dist_ts/features/feature.light.js +296 -0
  34. package/dist_ts/features/feature.lock.d.ts +103 -0
  35. package/dist_ts/features/feature.lock.js +170 -0
  36. package/dist_ts/features/feature.playback.d.ts +98 -0
  37. package/dist_ts/features/feature.playback.js +191 -0
  38. package/dist_ts/features/feature.power.d.ts +79 -0
  39. package/dist_ts/features/feature.power.js +173 -0
  40. package/dist_ts/features/feature.print.d.ts +78 -0
  41. package/dist_ts/features/feature.print.js +226 -0
  42. package/dist_ts/features/feature.scan.d.ts +79 -0
  43. package/dist_ts/features/feature.scan.js +315 -0
  44. package/dist_ts/features/feature.sensor.d.ts +95 -0
  45. package/dist_ts/features/feature.sensor.js +147 -0
  46. package/dist_ts/features/feature.snmp.d.ts +88 -0
  47. package/dist_ts/features/feature.snmp.js +175 -0
  48. package/dist_ts/features/feature.switch.d.ts +75 -0
  49. package/dist_ts/features/feature.switch.js +126 -0
  50. package/dist_ts/features/feature.volume.d.ts +104 -0
  51. package/dist_ts/features/feature.volume.js +186 -0
  52. package/dist_ts/features/index.d.ts +19 -0
  53. package/dist_ts/features/index.js +23 -0
  54. package/dist_ts/helpers/helpers.iprange.d.ts +35 -0
  55. package/dist_ts/helpers/helpers.iprange.js +151 -0
  56. package/dist_ts/helpers/helpers.retry.d.ts +25 -0
  57. package/dist_ts/helpers/helpers.retry.js +69 -0
  58. package/dist_ts/index.d.ts +15 -0
  59. package/dist_ts/index.js +40 -0
  60. package/dist_ts/interfaces/feature.interfaces.d.ts +255 -0
  61. package/dist_ts/interfaces/feature.interfaces.js +6 -0
  62. package/dist_ts/interfaces/homeassistant.interfaces.d.ts +550 -0
  63. package/dist_ts/interfaces/homeassistant.interfaces.js +111 -0
  64. package/dist_ts/interfaces/index.d.ts +330 -0
  65. package/dist_ts/interfaces/index.js +16 -0
  66. package/dist_ts/interfaces/smarthome.interfaces.d.ts +282 -0
  67. package/dist_ts/interfaces/smarthome.interfaces.js +7 -0
  68. package/dist_ts/paths.d.ts +1 -0
  69. package/dist_ts/paths.js +3 -0
  70. package/dist_ts/plugins.d.ts +23 -0
  71. package/dist_ts/plugins.js +30 -0
  72. package/dist_ts/protocols/index.d.ts +12 -0
  73. package/dist_ts/protocols/index.js +21 -0
  74. package/dist_ts/protocols/protocol.escl.d.ts +58 -0
  75. package/dist_ts/protocols/protocol.escl.js +364 -0
  76. package/dist_ts/protocols/protocol.homeassistant.d.ts +248 -0
  77. package/dist_ts/protocols/protocol.homeassistant.js +611 -0
  78. package/dist_ts/protocols/protocol.ipp.d.ts +45 -0
  79. package/dist_ts/protocols/protocol.ipp.js +284 -0
  80. package/dist_ts/protocols/protocol.nut.d.ts +160 -0
  81. package/dist_ts/protocols/protocol.nut.js +364 -0
  82. package/dist_ts/protocols/protocol.sane.d.ts +104 -0
  83. package/dist_ts/protocols/protocol.sane.js +597 -0
  84. package/dist_ts/protocols/protocol.snmp.d.ts +133 -0
  85. package/dist_ts/protocols/protocol.snmp.js +333 -0
  86. package/dist_ts/protocols/protocol.upnp.d.ts +207 -0
  87. package/dist_ts/protocols/protocol.upnp.js +442 -0
  88. package/dist_ts/protocols/protocol.upssnmp.d.ts +160 -0
  89. package/dist_ts/protocols/protocol.upssnmp.js +263 -0
  90. package/npmextra.json +24 -0
  91. package/package.json +39 -0
  92. package/readme.hints.md +115 -0
  93. package/readme.md +624 -0
  94. package/test/test.ts +261 -0
  95. package/ts/00_commitinfo_data.ts +8 -0
  96. package/ts/device/device.classes.device.ts +454 -0
  97. package/ts/devicemanager.classes.devicemanager.ts +1219 -0
  98. package/ts/discovery/discovery.classes.homeassistant.ts +376 -0
  99. package/ts/discovery/discovery.classes.mdns.ts +347 -0
  100. package/ts/discovery/discovery.classes.networkscanner.ts +648 -0
  101. package/ts/discovery/discovery.classes.ssdp.ts +357 -0
  102. package/ts/factories/index.ts +696 -0
  103. package/ts/features/feature.abstract.ts +251 -0
  104. package/ts/features/feature.camera.ts +214 -0
  105. package/ts/features/feature.climate.ts +407 -0
  106. package/ts/features/feature.cover.ts +278 -0
  107. package/ts/features/feature.fan.ts +296 -0
  108. package/ts/features/feature.light.ts +369 -0
  109. package/ts/features/feature.lock.ts +223 -0
  110. package/ts/features/feature.playback.ts +246 -0
  111. package/ts/features/feature.power.ts +231 -0
  112. package/ts/features/feature.print.ts +297 -0
  113. package/ts/features/feature.scan.ts +370 -0
  114. package/ts/features/feature.sensor.ts +202 -0
  115. package/ts/features/feature.snmp.ts +235 -0
  116. package/ts/features/feature.switch.ts +170 -0
  117. package/ts/features/feature.volume.ts +256 -0
  118. package/ts/features/index.ts +25 -0
  119. package/ts/helpers/helpers.iprange.ts +172 -0
  120. package/ts/helpers/helpers.retry.ts +100 -0
  121. package/ts/index.ts +165 -0
  122. package/ts/interfaces/feature.interfaces.ts +359 -0
  123. package/ts/interfaces/homeassistant.interfaces.ts +666 -0
  124. package/ts/interfaces/index.ts +421 -0
  125. package/ts/interfaces/smarthome.interfaces.ts +421 -0
  126. package/ts/paths.ts +5 -0
  127. package/ts/plugins.ts +41 -0
  128. package/ts/protocols/index.ts +59 -0
  129. package/ts/protocols/protocol.escl.ts +439 -0
  130. package/ts/protocols/protocol.homeassistant.ts +737 -0
  131. package/ts/protocols/protocol.ipp.ts +329 -0
  132. package/ts/protocols/protocol.nut.ts +471 -0
  133. package/ts/protocols/protocol.sane.ts +694 -0
  134. package/ts/protocols/protocol.snmp.ts +439 -0
  135. package/ts/protocols/protocol.upnp.ts +627 -0
  136. package/ts/protocols/protocol.upssnmp.ts +377 -0
  137. 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
+ }