iobroker.zwavews 0.0.3

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/lib/helper.js ADDED
@@ -0,0 +1,540 @@
1
+ const utils = require("./utils");
2
+ const constant = require("./constants");
3
+ const {isObject} = require("./utils");
4
+
5
+ /*
6
+ options:
7
+ write //set common write variable to true
8
+ forceIndex //instead of trying to find names for array entries, use the index as the name
9
+ channelName //set name of the root channel
10
+ preferedArrayName //set key to use this as an array entry name
11
+ autoCast (true false) // make JSON.parse to parse numbers correctly
12
+ descriptions: Object of names for state keys
13
+ */
14
+ /**
15
+ *
16
+ */
17
+ class Helper {
18
+ /**
19
+ *
20
+ * @param adapter
21
+ * @param alreadyCreatedObjects
22
+ */
23
+ constructor(adapter, alreadyCreatedObjects = {}) {
24
+ this.adapter = adapter;
25
+ this.alreadyCreatedObjects = alreadyCreatedObjects;
26
+
27
+ }
28
+
29
+ /**
30
+ *
31
+ * @param path
32
+ * @param element
33
+ * @param options
34
+ */
35
+
36
+
37
+ /**
38
+ *
39
+ * @param nodeIdOriginal
40
+ * @param element
41
+ */
42
+ async createNode(nodeIdOriginal, element) {
43
+ try {
44
+ let nodeId = utils.formatNodeId(nodeIdOriginal);
45
+
46
+ if (element == null) {
47
+ this.adapter.log.debug(`Cannot extract NodeId: ${nodeId}`);
48
+ return;
49
+ }
50
+
51
+ await this.adapter.setObjectNotExistsAsync(nodeId, {
52
+ type: "device",
53
+ common: {
54
+ name: element.name ?? element.label,
55
+ statusStates: {
56
+ onlineId: `${nodeId}.ready`,
57
+ },
58
+ },
59
+ native: {},
60
+ });
61
+
62
+ await this.createReadyStatus(nodeId);
63
+
64
+ const valuesOnly = element.values ?? null;
65
+ delete element.values;
66
+ await this.parse(`${nodeId}.info`, element);
67
+
68
+ if (valuesOnly != null && typeof valuesOnly === "object" && valuesOnly.length > 0) {
69
+ for (const v of valuesOnly) {
70
+ let parsePath = `${nodeId}.${v.commandClassName}`;
71
+ let metadata = v.metadata || {};
72
+
73
+ if (constant.noInfoDP.includes(v.commandClassName)) {
74
+ continue;
75
+ }
76
+ if (constant.noInfoDP.includes(v.propertyName)) {
77
+ continue;
78
+ }
79
+
80
+ if (!this.alreadyCreatedObjects[parsePath]) {
81
+ this.alreadyCreatedObjects[parsePath] = {};
82
+
83
+ await this.adapter.setObjectNotExistsAsync(parsePath, {
84
+ type: "channel",
85
+ common: {
86
+ name: metadata.label || "",
87
+ },
88
+ native: {},
89
+ });
90
+ }
91
+
92
+ parsePath = `${nodeId}.${v.commandClassName}.${v.propertyName
93
+ .replace(/[^\p{L}\p{N}\s]/gu, "")
94
+ .replace(/\s+/g, " ")
95
+ .trim()}`;
96
+
97
+ if (v?.propertyKeyName) {
98
+ parsePath = `${parsePath}.${v.propertyKeyName
99
+ .replace(/[^\p{L}\p{N}\s]/gu, "")
100
+ .replace(/\s+/g, " ")
101
+ .trim()}`;
102
+
103
+ if (constant.RGB.includes(v.propertyKeyName)) {
104
+ parsePath = utils.replaceLastDot(parsePath);
105
+ }
106
+ }
107
+
108
+ if (this.isObject(v.value)) { // da gibts ein object mit value
109
+ parsePath = `${parsePath}_value`;
110
+ }
111
+
112
+ const nam_id = v.label ?? v.propertyName;
113
+
114
+ metadata.value = v.value; // add value for resolution
115
+ const val = this.resolveCommandClassValue(metadata) ?? 0;
116
+ let typeDp = metadata.type === "timeout" ? "number" : metadata.type;
117
+
118
+ if (constant.mixedType.includes(nam_id)) {
119
+ typeDp = "mixed";
120
+ }
121
+
122
+ const common = {
123
+ id: nam_id,
124
+ name: nam_id,
125
+ write: metadata.writeable,
126
+ read: metadata.readable,
127
+ desc: metadata.label,
128
+ type: typeDp,
129
+ min: metadata?.min,
130
+ max: metadata?.max,
131
+ def: v.default ?? (typeDp === "boolean" ? false : metadata?.min),
132
+ unit: metadata?.unit ?? "",
133
+ role: this.getRole(val, metadata.writeable, parsePath),
134
+ };
135
+
136
+ if (metadata?.states) {
137
+ common.states = metadata?.states;
138
+ }
139
+ const native = {
140
+ valueId : { commandClass: v.commandClass,
141
+ endpoint: v.endpoint,
142
+ property: v.property
143
+ }
144
+ };
145
+
146
+ if (v?.propertyKey) {
147
+ native.valueId.propertyKey = v.propertyKey;
148
+ }
149
+
150
+ await this.adapter.setObjectNotExistsAsync(parsePath, {
151
+ type: "state",
152
+ common,
153
+ native,
154
+ });
155
+
156
+ if (common.write === true) {
157
+ this.adapter.subscribeStates(parsePath);
158
+ }
159
+
160
+ this.alreadyCreatedObjects[parsePath] = { };
161
+
162
+ this.adapter.setState(parsePath, val, true);
163
+
164
+ }
165
+ }
166
+ } catch (error) {
167
+ this.adapter.log.error(`Cannot create node ${nodeIdOriginal} : ${error}`);
168
+ }
169
+ }
170
+
171
+
172
+
173
+
174
+
175
+ /**
176
+ *
177
+ * @param path
178
+ * @param element
179
+ * @param options
180
+ */
181
+ async parse(path, element, options = { write: false }) {
182
+ let parsePath = path;
183
+
184
+ if (element == null) {
185
+ this.adapter.log.debug(`Cannot extract empty: ${parsePath}`);
186
+ return;
187
+ }
188
+
189
+ if (typeof element === "string" || typeof element === "number" || typeof element === "boolean") {
190
+ let val = element ?? 0;
191
+
192
+ if (!this.alreadyCreatedObjects[parsePath]) {
193
+ try {
194
+ let common = {};
195
+ if (typeof element === "string" || typeof element === "number") {
196
+ common = {
197
+ id: parsePath,
198
+ name: parsePath,
199
+ role: this.getRole(element, options.write),
200
+ type: typeof element,
201
+ write: options.write,
202
+ read: true,
203
+ };
204
+ }
205
+ await this.adapter.setObjectNotExistsAsync(parsePath, {
206
+ type: "state",
207
+ common,
208
+ native: { },
209
+ });
210
+
211
+ if (common.write === true) {
212
+ this.adapter.subscribeStates(parsePath);
213
+ }
214
+
215
+ this.alreadyCreatedObjects[parsePath] = {};
216
+ } catch (error) {
217
+ this.adapter.log.error(error);
218
+ }
219
+ }
220
+
221
+ this.adapter.setState(parsePath, val, true);
222
+ return;
223
+ }
224
+ options.channelName = utils.getLastSegment(parsePath);
225
+
226
+ if (!this.alreadyCreatedObjects[parsePath]) {
227
+ try {
228
+ await this.adapter.setObjectNotExistsAsync(parsePath, {
229
+ type: "channel",
230
+ common: {
231
+ name: options.channelName || ""
232
+ },
233
+ native: {},
234
+ });
235
+
236
+ this.alreadyCreatedObjects[parsePath] = { };
237
+ delete options.channelName;
238
+ } catch (error) {
239
+ this.adapter.log.error(error);
240
+ }
241
+ }
242
+
243
+ if (Array.isArray(element)) {
244
+ await this.extractArray(element, "", parsePath, options);
245
+ return;
246
+ }
247
+
248
+ // info schleife
249
+
250
+ for (const key of Object.keys(element)) {
251
+ let fullPath = `${parsePath}.${key}`;
252
+ let value = element[key];
253
+
254
+ if (Array.isArray(value)) {
255
+ try {
256
+ if (!constant.noInfoDP.includes(key)) {
257
+ await this.extractArray(element, key, parsePath, options);
258
+ }
259
+ } catch (error) {
260
+ this.adapter.log.error(`extractArray ${error}`);
261
+ }
262
+ continue;
263
+ }
264
+
265
+ const isObj = this.isObject(value);
266
+
267
+ if (isObj) {
268
+ if (Object.keys(value).length > 0) {
269
+ options.write = false;
270
+ await this.parse(fullPath, value, options);
271
+ }
272
+ continue;
273
+ }
274
+
275
+ switch (key) {
276
+ case "ready":
277
+ fullPath = fullPath.replace(".info.", ".");
278
+ break;
279
+ case "status":
280
+ fullPath = fullPath.replace(".info.", ".");
281
+ if (utils.isNumeric(value)) {
282
+ value = utils.getStatusText(value);
283
+ }
284
+ break;
285
+ default:
286
+ break;
287
+ }
288
+
289
+ if (!this.alreadyCreatedObjects[fullPath]) {
290
+ const objectName = options.descriptions?.[key] || key;
291
+ let type = typeof value === "string" ? "mixed" : (value != null ? typeof value : "mixed");
292
+
293
+ if (constant.mixedType.includes(key)) {
294
+ type = "mixed";
295
+ }
296
+
297
+ const common = {
298
+ id: objectName,
299
+ name: objectName,
300
+ role: this.getRole(value, options, key),
301
+ type,
302
+ write: options.write,
303
+ read: true,
304
+ };
305
+
306
+ await this.adapter.setObjectNotExistsAsync(fullPath, {
307
+ type: "state",
308
+ common,
309
+ native: { },
310
+ });
311
+
312
+ if (options.write === true) {
313
+ this.adapter.subscribeStates(fullPath);
314
+ }
315
+
316
+ this.alreadyCreatedObjects[fullPath] = { };
317
+ }
318
+
319
+ try {
320
+ if (value !== undefined) {
321
+ this.adapter.setState(fullPath, value, true);
322
+ }
323
+ } catch (err) {
324
+ this.adapter.log.warn(`ERROR ${value} ${JSON.stringify(err)}`);
325
+ }
326
+ }
327
+ }
328
+
329
+
330
+ /**
331
+ *
332
+ * @param value
333
+ */
334
+ isObject(value) {
335
+ return value !== null && typeof value === "object";
336
+ }
337
+
338
+ /**
339
+ *
340
+ * @param element
341
+ * @param key
342
+ * @param path
343
+ * @param options
344
+ */
345
+ async extractArray(element, key, path, options) {
346
+ try {
347
+ const array = key ? element[key] : element;
348
+
349
+ for (let i = 0; i < array.length; i++) {
350
+ const arrayElement = array[i];
351
+ const index = (i + 1).toString().padStart(2, "0");
352
+
353
+ if (typeof arrayElement === "string") {
354
+ if (key == undefined || key === "") {
355
+ key = arrayElement;
356
+ }
357
+
358
+ await this.parse(
359
+ `${path}.${key}.${arrayElement}`,
360
+ arrayElement,
361
+ options,
362
+ );
363
+ continue;
364
+ }
365
+
366
+ await this.parse(`${path}.${key}`, arrayElement, options);
367
+ }
368
+ } catch (error) {
369
+ this.adapter.log.error(`Cannot extract array ${path}`);
370
+ }
371
+ }
372
+
373
+ /**
374
+ *
375
+ * @param element
376
+ * @param options
377
+ * @param dpName
378
+ */
379
+ getRole(element, options, dpName) {
380
+ const write = options.write;
381
+ const hasStates =
382
+ element && typeof element === "object" && element.states !== undefined;
383
+
384
+ if (constant.timeKey.includes(dpName)) {
385
+ // check ob es sich um ein timestamp handelt
386
+ return "value.time";
387
+ }
388
+
389
+ if (typeof element === "boolean" && !write) {
390
+ return "indicator";
391
+ }
392
+
393
+ if (hasStates) {
394
+ if (element.type == "boolean") {
395
+ delete element.states;
396
+ return "button";
397
+ }
398
+ return "switch";
399
+ }
400
+
401
+ if (typeof element === "boolean" && !write) {
402
+ return "indicator";
403
+ }
404
+ if (typeof element === "boolean" && write) {
405
+ return "switch";
406
+ }
407
+ if (typeof element === "number" && !write) {
408
+ return "value";
409
+ }
410
+ if (typeof element === "number" && write) {
411
+ return "level";
412
+ }
413
+ if (typeof element === "string") {
414
+ return "text";
415
+ }
416
+
417
+ return "state";
418
+ }
419
+ /**
420
+ *
421
+ * @param element
422
+ */
423
+ resolveCommandClassValue(element) {
424
+ const type = element.type;
425
+
426
+ if (type === "any" || type === "color") {
427
+ element.type = "mixed";
428
+ return typeof element.value === "object"
429
+ ? JSON.stringify(element.value)
430
+ : element.value;
431
+ }
432
+
433
+ if (type.includes("string")) {
434
+ element.type = "mixed";
435
+ if (element.writeable === false) {
436
+ let v = element.value ?? element.min ?? 0;
437
+ if (Array.isArray(v) && v.length) {
438
+ v = JSON.stringify(v);
439
+ }
440
+ return v;
441
+ }
442
+ return element.value ?? element.min ?? 0;
443
+ }
444
+
445
+ if (type.includes("buffer")) {
446
+ element.type = "mixed";
447
+ if (element.writeable === false) {
448
+ let v = element.value ?? element.min ?? 0;
449
+ if (Array.isArray(v) && v.length) {
450
+ v = v[0];
451
+ }
452
+ return v;
453
+ }
454
+ return element.value ?? element.min ?? 0;
455
+ }
456
+
457
+ if (type === "duration") {
458
+ element.type = "mixed";
459
+ let v = element.value ?? element.min ?? 0;
460
+ if (typeof v === "object") {
461
+ if (v?.unit) {
462
+ element.unit = v.unit;
463
+ }
464
+ v = 0;
465
+ }
466
+ return v;
467
+ }
468
+
469
+ if (type === "number") {
470
+ if (element?.value) {
471
+ return utils.isNumeric(element.value) ? element.value : 0;
472
+ }
473
+ return element.value ?? element.min;
474
+ }
475
+
476
+ return element.readable === false
477
+ ? false
478
+ : (element.value ?? (type === "boolean" ? false : (element.min ?? 0)));
479
+ }
480
+
481
+
482
+ /**
483
+ *
484
+ * @param nodeId
485
+ */
486
+ async createReadyStatus(nodeId) {
487
+ // leg die status direkt auch an
488
+ let common = {
489
+ id: 'ready',
490
+ name: 'ready',
491
+ role: 'indicator',
492
+ type: 'boolean',
493
+ write: false,
494
+ read: true,
495
+ };
496
+ await this.adapter.setObjectNotExistsAsync(`${nodeId}.ready`, {
497
+ type: "state",
498
+ common,
499
+ native: {},
500
+ });
501
+
502
+ common = {
503
+ id: 'status',
504
+ name: 'status',
505
+ role: 'text',
506
+ type: 'mixed',
507
+ write: false,
508
+ read: true,
509
+ };
510
+
511
+ await this.adapter.setObjectNotExistsAsync(`${nodeId}.status`, {
512
+ type: "state",
513
+ common,
514
+ native: {},
515
+ });
516
+ }
517
+ /**
518
+ *
519
+ * @param nodeId
520
+ * @param element
521
+ */
522
+ async updateDevice(nodeId, element) {
523
+ const obj = await this.adapter.getObjectAsync(nodeId);
524
+ if (obj) {
525
+ const newName = element.name || element.productLabel || element.manufacturer || element.newValue;
526
+
527
+ if (obj.common?.name !== newName) {
528
+ obj.common = obj.common ?? {};
529
+ obj.common.name = newName;
530
+
531
+ await this.adapter.setObjectAsync(nodeId, obj);
532
+ }
533
+ }
534
+ }
535
+
536
+ }
537
+
538
+ module.exports = {
539
+ Helper: Helper,
540
+ };
@@ -0,0 +1,49 @@
1
+ /**
2
+ *
3
+ * @param config
4
+ * @param log
5
+ */
6
+ async function adapterInfo(config, log) {
7
+ log.info(
8
+ "================================= Adapter Config =================================",
9
+ );
10
+ log.info(`|| zwaveWS Frontend Scheme: ${config.webUIScheme}`);
11
+ log.info(`|| zwaveWS Frontend Server: ${config.webUIServer}`);
12
+ log.info(`|| zwaveWS Frontend Port: ${config.webUIPort}`);
13
+ log.info(`|| zwaveWS Connection Type: ${config.connectionType}`);
14
+ if (config.connectionType == "ws") {
15
+ log.info(`|| zwaveWS Websocket Scheme: ${config.wsScheme}`);
16
+ log.info(`|| zwaveWS Websocket Server: ${config.wsServerIP}`);
17
+ log.info(`|| zwaveWS Websocket Port: ${config.wsServerPort}`);
18
+ log.info(
19
+ `|| zwaveWS Websocket Auth-Token: ${config.wsTokenEnabled ? "use" : "unused"}`,
20
+ );
21
+ log.info(
22
+ `|| zwaveWS Websocket Dummy MQTT-Server: ${config.dummyMqtt ? "activated" : "deactivated"}`,
23
+ );
24
+ if (config.dummyMqtt == true) {
25
+ log.info(`|| zwaveWS Dummy MQTT IP-Bind: ${config.mqttServerIPBind}`);
26
+ log.info(`|| zwaveWS Dummy MQTT Port: ${config.mqttServerPort}`);
27
+ }
28
+ } else if (config.connectionType == "exmqtt") {
29
+ log.info(
30
+ `|| zwaveWS Externanl MQTT Server: ${config.externalMqttServerIP}`,
31
+ );
32
+ log.info(
33
+ `|| zwaveWS Externanl MQTT Port: ${config.externalMqttServerPort}`,
34
+ );
35
+ log.info(
36
+ `|| zwaveWS Externanl MQTT Credentials: ${config.externalMqttServerCredentials ? "use" : "unused"}`,
37
+ );
38
+ } else if (config.connectionType == "intmqtt") {
39
+ log.info(`|| zwaveWS Internal MQTT IP-Bind: ${config.mqttServerIPBind}`);
40
+ log.info(`|| zwaveWS Internal MQTT Port: ${config.mqttServerPort}`);
41
+ }
42
+ log.info(
43
+ "==================================================================================",
44
+ );
45
+ }
46
+
47
+ module.exports = {
48
+ adapterInfo,
49
+ };
@@ -0,0 +1,78 @@
1
+ const core = require("@iobroker/adapter-core");
2
+ const Aedes = require("aedes");
3
+ const net = require("net");
4
+ let mqttServer;
5
+
6
+ /**
7
+ *
8
+ */
9
+ class MqttServerController {
10
+ /**
11
+ *
12
+ * @param adapter
13
+ */
14
+ constructor(adapter) {
15
+ this.adapter = adapter;
16
+ }
17
+
18
+ /**
19
+ *
20
+ */
21
+ async createMQTTServer() {
22
+ try {
23
+ const NedbPersistence = require("aedes-persistence-nedb");
24
+ const db = new NedbPersistence({
25
+ path: `${core.getAbsoluteInstanceDataDir(this.adapter)}/mqttData`,
26
+ prefix: "",
27
+ });
28
+
29
+ const aedes = Aedes({ persistence: db });
30
+ mqttServer = net.createServer(aedes.handle);
31
+ mqttServer.listen(
32
+ this.adapter.config.mqttServerPort,
33
+ this.adapter.config.mqttServerIPBind,
34
+ () => {
35
+ this.adapter.log.info(
36
+ `Statring MQTT-Server on IP ${this.adapter.config.mqttServerIPBind} and Port ${this.adapter.config.mqttServerPort}`,
37
+ );
38
+ },
39
+ );
40
+ } catch (err) {
41
+ this.adapter.log.error(err);
42
+ }
43
+ }
44
+
45
+ /**
46
+ *
47
+ */
48
+ async createDummyMQTTServer() {
49
+ try {
50
+ const aedes = Aedes();
51
+ mqttServer = net.createServer(aedes.handle);
52
+ mqttServer.listen(
53
+ this.adapter.config.mqttServerPort,
54
+ this.adapter.config.mqttServerIPBind,
55
+ () => {
56
+ this.adapter.log.info(
57
+ `Statring DummyMQTT-Server on IP ${this.adapter.config.mqttServerIPBind} and Port ${this.adapter.config.mqttServerPort}`,
58
+ );
59
+ },
60
+ );
61
+ } catch (err) {
62
+ this.adapter.log.error(err);
63
+ }
64
+ }
65
+
66
+ /**
67
+ *
68
+ */
69
+ closeServer() {
70
+ if (mqttServer && !mqttServer.closed()) {
71
+ mqttServer.close();
72
+ }
73
+ }
74
+ }
75
+
76
+ module.exports = {
77
+ MqttServerController,
78
+ };