iobroker.zwavews 0.1.2 → 0.1.4

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/README.md CHANGED
@@ -35,6 +35,14 @@ Activate WS Server Settings in `zwave-js-ui` we use the Home Assistant Settings
35
35
 
36
36
 
37
37
  ## Changelog
38
+ ### 0.1.4 (2026-04-16)
39
+ * (arteck) Dependencies have been updated
40
+ * (arteck) add vscode folder
41
+
42
+ ### 0.1.3 (2026-04-03)
43
+ * (arteck) del last dot from DP
44
+ * (arteck) fix scene
45
+
38
46
  ### 0.1.2 (2026-03-15)
39
47
  * (arteck) typo
40
48
 
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "zwavews",
4
- "version": "0.1.2",
4
+ "version": "0.1.4",
5
5
  "news": {
6
+ "0.1.4": {
7
+ "en": "Dependencies have been updated\nadd vscode folder",
8
+ "de": "Abhängigkeiten wurden aktualisiert\nvscode ordner hinzufügen",
9
+ "ru": "Зависимости были обновлены\nдобавить vscode папку",
10
+ "pt": "As dependências foram atualizadas\nadicionar pasta vscode",
11
+ "nl": "Afhankelijkheden zijn bijgewerkt\nvscode map toevoegen",
12
+ "fr": "Les dépendances ont été actualisées\najouter un dossier vscode",
13
+ "it": "Le dipendenze sono state aggiornate\naggiungere cartella vscode",
14
+ "es": "Se han actualizado las dependencias\nañadir carpeta vscode",
15
+ "pl": "Zaktualizowano zależności\ndodaj folder vscode",
16
+ "uk": "Залежність було оновлено\nдодати папку проти коду",
17
+ "zh-cn": "依赖关系已更新\n添加 vscode 文件夹"
18
+ },
19
+ "0.1.3": {
20
+ "en": "fix unknown state from scene\ndel last dot from DP\nfix scene",
21
+ "de": "unbekannter zustand von szene\nder letzte Punkt von DP\nfixe szene",
22
+ "ru": "неизвестное состояние с места происшествия\nпоследняя точка от DP\nисправить",
23
+ "pt": "corrigir o estado desconhecido da cena\ndo último ponto do DP\ncorrigir cena",
24
+ "nl": "fix onbekende toestand vanaf locatie\nde laatste stip van DP\nscène herstellen",
25
+ "fr": "réparer l'état inconnu de la scène\ndel dernier point de DP\nréparer la scène",
26
+ "it": "fissare stato sconosciuto dalla scena\ndel ultimo punto da DP\ncorrere la scena",
27
+ "es": "arreglar estado desconocido de la escena\ndel último punto de DP\nescena arreglada",
28
+ "pl": "naprawić nieznany stan z miejsca zbrodni\ndel ostatnia kropka z DP\nmiejsce",
29
+ "uk": "виправити невідомого стану з сцени\ndel last dot від DP\nфіксувати сцена",
30
+ "zh-cn": "从现场修复未知状态\ndP 的最后一个点\n修补场景"
31
+ },
6
32
  "0.1.2": {
7
33
  "en": "typo",
8
34
  "de": "typo",
@@ -67,32 +93,6 @@
67
93
  "pl": "uruchomić adapter\nZaktualizowano zależności",
68
94
  "uk": "запуск адаптера\nЗалежність було оновлено",
69
95
  "zh-cn": "固定适配器启动\n依赖关系已更新"
70
- },
71
- "0.0.16": {
72
- "en": "fix warning message",
73
- "de": "warnmeldung aktivieren",
74
- "ru": "исправить предупреждающее сообщение",
75
- "pt": "corrigir a mensagem de aviso",
76
- "nl": "waarschuwingsbericht herstellen",
77
- "fr": "corriger le message d'avertissement",
78
- "it": "correzione del messaggio di avviso",
79
- "es": "mensaje de advertencia",
80
- "pl": "naprawić komunikat ostrzegawczy",
81
- "uk": "фіксувати повідомлення про попередження",
82
- "zh-cn": "修补警告消息"
83
- },
84
- "0.0.15": {
85
- "en": "typo\nfix ready status if status is dead",
86
- "de": "typo\nbereitstellen des status, wenn der status tot ist",
87
- "ru": "опечатка\nготовый статус, если статус мертв",
88
- "pt": "erro de digitação\ncorrigir o estado pronto se o estado estiver morto",
89
- "nl": "type\nfix ready status als status dood is",
90
- "fr": "typo\nfixer le statut prêt si le statut est mort",
91
- "it": "tipo\nfissare lo stato pronto se lo stato è morto",
92
- "es": "typo\nfijar estado listo si el estado está muerto",
93
- "pl": "typo\nustaw stan gotowy, jeśli stan jest martwy",
94
- "uk": "типи\nвиправити готовий статус, якщо статус мертвий",
95
- "zh-cn": "类型\n如果状态已死亡, 则固定状态"
96
96
  }
97
97
  },
98
98
  "titleLang": {
@@ -180,13 +180,13 @@
180
180
  ],
181
181
  "globalDependencies": [
182
182
  {
183
- "admin": ">=7.6.17"
183
+ "admin": ">=7.6.20"
184
184
  }
185
185
  ],
186
186
  "plugins": {
187
187
  "docker": {
188
188
  "iobDockerComposeFiles": [
189
- "https://github.com/zwave-js/zwave-js-ui/blob/master/docker/docker-compose.yml"
189
+ "docker-compose.yaml"
190
190
  ]
191
191
  }
192
192
  },
package/lib/devicemgmt.js CHANGED
@@ -3,12 +3,13 @@ const dmUtils = require('@iobroker/dm-utils');
3
3
  const humanizeDuration = require('humanize-duration');
4
4
 
5
5
  /**
6
- *
6
+ * Device management class for the ZWave adapter.
7
7
  */
8
8
  class dmZwave extends dmUtils.DeviceManagement {
9
9
  /**
10
+ * Creates a new dmZwave instance.
10
11
  *
11
- * @param adapter
12
+ * @param {object} adapter - The ioBroker adapter instance.
12
13
  */
13
14
  constructor(adapter) {
14
15
  super(adapter);
@@ -28,9 +29,7 @@ class dmZwave extends dmUtils.DeviceManagement {
28
29
 
29
30
  const device = this.adapter.nodeCache[nodeId].nodeData;
30
31
 
31
- if (device.ready) {
32
- status.connection = device.ready ? 'connected' : 'disconnected';
33
- }
32
+ status.connection = device.ready ? 'connected' : 'disconnected';
34
33
 
35
34
  //const link_quality = await this.adapter.getStateAsync(`${theDevice._id}.status`);
36
35
  //status.rssi = link_quality.val == 'alive' ? '100' : '0';
@@ -91,9 +90,10 @@ class dmZwave extends dmUtils.DeviceManagement {
91
90
  }
92
91
 
93
92
  /**
93
+ * Opens the device documentation PDF or link in a form dialog.
94
94
  *
95
- * @param context
96
- * @param device
95
+ * @param {object} context - The device management context used to show the form.
96
+ * @param {object} device - The ZWave device object containing device config and metadata.
97
97
  */
98
98
  async openPDF(context, device) {
99
99
  const manual = device?.deviceConfig?.metadata?.manual;
@@ -140,12 +140,13 @@ class dmZwave extends dmUtils.DeviceManagement {
140
140
  }
141
141
 
142
142
  /**
143
+ * Returns the detail schema and data for a specific device.
143
144
  *
144
- * @param id
145
- * @param action
146
- * @param context
145
+ * @param {string} id - The node ID of the device.
146
+ * @param {object} _action - The action object passed by the device management framework.
147
+ * @param {object} _context - The device management context.
147
148
  */
148
- async getDeviceDetails(id, action, context) {
149
+ async getDeviceDetails(id, _action, _context) {
149
150
  this.adapter.log.debug('getDeviceDetails');
150
151
 
151
152
  const device = this.adapter.nodeCache[id]?.nodeData;
@@ -362,27 +363,27 @@ class dmZwave extends dmUtils.DeviceManagement {
362
363
 
363
364
 
364
365
  /**
366
+ * Formats a timestamp according to the given format type.
365
367
  *
366
- * @param time
367
- * @param type
368
+ * @param {number} time - The timestamp in milliseconds (epoch).
369
+ * @param {'ISO_8601'|'ISO_8601_local'|'epoch'|'relative'} type - The desired output format.
368
370
  */
369
- async formatDate(time, type) { //'ISO_8601' | 'ISO_8601_local' | 'epoch' | 'relative'
371
+ formatDate(time, type) { //'ISO_8601' | 'ISO_8601_local' | 'epoch' | 'relative'
370
372
  if (type === 'ISO_8601') {
371
- return new Date(time).toISOString();
372
- } else if (type === 'ISO_8601_local') {
373
- return this.toLocalISOString(new Date(time));
374
- } else if (type === 'epoch') {
375
- return time;
376
- }
377
- // relative
378
- const ago = `${humanizeDuration(Date.now() - time, {language: 'en', largest: 2, round: true}) } ago`;
379
- return ago;
380
-
373
+ return new Date(time).toISOString();
374
+ } else if (type === 'ISO_8601_local') {
375
+ return this.toLocalISOString(new Date(time));
376
+ } else if (type === 'epoch') {
377
+ return time;
378
+ }
379
+ // relative
380
+ return `${humanizeDuration(Date.now() - time, { language: 'en', largest: 2, round: true })} ago`;
381
381
  }
382
382
 
383
383
  /**
384
+ * Converts a Date object to a local ISO 8601 string.
384
385
  *
385
- * @param d
386
+ * @param {Date} d - The Date object to convert.
386
387
  */
387
388
  toLocalISOString(d) {
388
389
  const off = d.getTimezoneOffset();
@@ -391,8 +392,10 @@ return time;
391
392
  // Entfernt den ioBroker-Prefix am Anfang, z.B.
392
393
  // "zwavews.0.nodeID_1.info.name" -> "nodeID_1.info.name"
393
394
  /**
395
+ * Strips the ioBroker adapter prefix from an object ID.
394
396
  *
395
- * @param id
397
+ * @param {string} id - The full ioBroker object ID (e.g. "zwavews.0.nodeID_1.info.name").
398
+ * @returns {string} The ID without the adapter prefix (e.g. "nodeID_1.info.name").
396
399
  */
397
400
  stripIobPrefix(id) {
398
401
  const s = String(id ?? '');
package/lib/helper.js CHANGED
@@ -1,6 +1,5 @@
1
1
  const utils = require("./utils");
2
2
  const constant = require("./constants");
3
- const {isObject} = require("./utils");
4
3
 
5
4
  /*
6
5
  options:
@@ -12,13 +11,14 @@ autoCast (true false) // make JSON.parse to parse numbers correctly
12
11
  descriptions: Object of names for state keys
13
12
  */
14
13
  /**
15
- *
14
+ * Helper class for creating and managing ioBroker objects and states from ZWave data.
16
15
  */
17
16
  class Helper {
18
17
  /**
18
+ * Creates a new Helper instance.
19
19
  *
20
- * @param adapter
21
- * @param alreadyCreatedObjects
20
+ * @param {object} adapter - The ioBroker adapter instance.
21
+ * @param {object} [alreadyCreatedObjects] - Cache of already created object paths.
22
22
  */
23
23
  constructor(adapter, alreadyCreatedObjects = {}) {
24
24
  this.adapter = adapter;
@@ -27,17 +27,10 @@ class Helper {
27
27
  }
28
28
 
29
29
  /**
30
+ * Creates a ZWave node device and all its value states in ioBroker.
30
31
  *
31
- * @param path
32
- * @param element
33
- * @param options
34
- */
35
-
36
-
37
- /**
38
- *
39
- * @param nodeIdOriginal
40
- * @param element
32
+ * @param {string|number} nodeIdOriginal - The original node ID as received from the ZWave driver.
33
+ * @param {object} element - The node data object containing values, name and device config.
41
34
  */
42
35
  async createNode(nodeIdOriginal, element) {
43
36
  try {
@@ -68,13 +61,10 @@ class Helper {
68
61
 
69
62
  if (valuesOnly != null && typeof valuesOnly === "object" && valuesOnly.length > 0) {
70
63
  for (const v of valuesOnly) {
71
- let parsePath = utils.formatObject(`${nodeId}.${v.commandClassName}`);
64
+ let parsePath = utils.deleteLastDot(utils.formatObject(`${nodeId}.${v.commandClassName}`));
72
65
  let metadata = v.metadata || {};
73
66
 
74
- if (constant.noInfoDP.includes(v.commandClassName)) {
75
- continue;
76
- }
77
- if (constant.noInfoDP.includes(v.propertyName)) {
67
+ if (constant.noInfoDP.includes(v.commandClassName) || constant.noInfoDP.includes(v.propertyName)) {
78
68
  continue;
79
69
  }
80
70
 
@@ -100,10 +90,10 @@ class Helper {
100
90
  .replace(/[^\p{L}\p{N}\s]/gu, "")
101
91
  .replace(/\s+/g, " ")
102
92
  .trim()}`;
93
+ }
103
94
 
104
- if (constant.RGB.includes(v.propertyKeyName)) {
95
+ if (constant.RGB.includes(v.propertyKeyName)) {
105
96
  parsePath = utils.replaceLastDot(parsePath);
106
- }
107
97
  }
108
98
 
109
99
  if (this.isObject(v.value)) { // da gibts ein object mit value
@@ -115,7 +105,7 @@ class Helper {
115
105
  parsePath = `${parsePath}_${v.endpoint}`;
116
106
  }
117
107
 
118
- parsePath = utils.formatObject(parsePath); // entferne sonderzeichen und blanke aus dem namen
108
+ parsePath = utils.deleteLastDot(utils.formatObject(parsePath)); // entferne sonderzeichen und blank aus dem namen und letzten dot
119
109
 
120
110
  const nam_id = v.label ?? v.propertyName;
121
111
 
@@ -127,7 +117,7 @@ class Helper {
127
117
  if (constant.mixedType.includes(nam_id)) {
128
118
  typeDp = "mixed";
129
119
  }
130
-
120
+
131
121
  const common = {
132
122
  id: nam_id,
133
123
  name: nam_id,
@@ -166,7 +156,7 @@ class Helper {
166
156
  this.adapter.subscribeStates(parsePath);
167
157
  }
168
158
 
169
- this.adapter.setStateChanged(parsePath, valDp, true);
159
+ await this.changeState(parsePath, valDp);
170
160
 
171
161
  this.alreadyCreatedObjects[parsePath] = {};
172
162
  }
@@ -177,16 +167,18 @@ class Helper {
177
167
  }
178
168
 
179
169
  /**
170
+ * Recursively parses an element and creates the corresponding ioBroker objects and states.
180
171
  *
181
- * @param path
182
- * @param element
183
- * @param options
172
+ * @param {string} path - The ioBroker object path to write to.
173
+ * @param {*} element - The value or object to parse and persist.
174
+ * @param {object} [options] - Parsing options (e.g. write, channelName, descriptions).
175
+ * @param {boolean} [change] - If true, forces setState instead of setStateChanged.
184
176
  */
185
- async parse(path, element, options = { write: false }) {
186
- let parsePath = utils.formatObject(path);
177
+ async parse(path, element, options = { write: false },change = false) {
178
+ let parsePath = utils.deleteLastDot(utils.formatObject(path));
187
179
 
188
- if (element == null) {
189
- this.adapter.log.debug(`Cannot extract empty: ${parsePath}`);
180
+ if (element === undefined || element === null) {
181
+ this.adapter.log.error(`Skip undefined value for ${parsePath}`);
190
182
  return;
191
183
  }
192
184
 
@@ -220,13 +212,16 @@ class Helper {
220
212
 
221
213
  this.alreadyCreatedObjects[parsePath] = {};
222
214
  } catch (error) {
215
+ this.adapter.log.error(`parse error ${ parsePath}`);
223
216
  this.adapter.log.error(error);
224
217
  }
225
218
  }
226
219
 
227
- this.adapter.setStateChanged(parsePath, valDp, true);
220
+ await this.changeState(parsePath, valDp, change);
221
+
228
222
  return;
229
223
  }
224
+
230
225
  options.channelName = utils.getLastSegment(parsePath);
231
226
 
232
227
  if (!this.alreadyCreatedObjects[parsePath]) {
@@ -242,6 +237,7 @@ class Helper {
242
237
  this.alreadyCreatedObjects[parsePath] = { };
243
238
  delete options.channelName;
244
239
  } catch (error) {
240
+ this.adapter.log.error(`parse error ${ parsePath}`);
245
241
  this.adapter.log.error(error);
246
242
  }
247
243
  }
@@ -310,6 +306,8 @@ class Helper {
310
306
  typeDp = "mixed";
311
307
  }
312
308
 
309
+ fullPath = utils.deleteLastDot(fullPath);
310
+
313
311
  const common = {
314
312
  id: objectName,
315
313
  name: objectName,
@@ -333,14 +331,15 @@ class Helper {
333
331
  }
334
332
 
335
333
  try {
336
- if (valDP !== undefined) {
337
- this.adapter.setStateChanged(fullPath, valDP, true);
338
334
 
335
+ await this.changeState(fullPath, valDP, change);
336
+
337
+ if (valDP !== undefined) {
339
338
  if (fullPath.endsWith('ready') ) {
340
339
  valDP = element['status'];
341
- if (utils.isNumeric(valDP) && valDP == 3) {
340
+ if (utils.isNumeric(valDP) && valDP === 3) {
342
341
  fullPath = fullPath.replace(".status", ".ready");
343
- this.adapter.setStateChanged(fullPath, false, true);
342
+ await this.changeState(fullPath, false);
344
343
  }
345
344
  }
346
345
  }
@@ -353,19 +352,22 @@ class Helper {
353
352
 
354
353
 
355
354
  /**
355
+ * Checks whether a value is a non-null object.
356
356
  *
357
- * @param value
357
+ * @param {*} value - The value to check.
358
+ * @returns {boolean}
358
359
  */
359
360
  isObject(value) {
360
361
  return value !== null && typeof value === "object";
361
362
  }
362
363
 
363
364
  /**
365
+ * Extracts and processes an array from an element, creating ioBroker objects for each entry.
364
366
  *
365
- * @param element
366
- * @param key
367
- * @param path
368
- * @param options
367
+ * @param {object|Array} element - The element containing the array, or the array itself.
368
+ * @param {string} key - The key of the array within the element, or empty string if element is the array.
369
+ * @param {string} path - The ioBroker base path to write to.
370
+ * @param {object} options - Parsing options forwarded to the parse method.
369
371
  */
370
372
  async extractArray(element, key, path, options) {
371
373
  try {
@@ -396,13 +398,15 @@ class Helper {
396
398
  }
397
399
 
398
400
  /**
401
+ * Determines the ioBroker role string for a datapoint based on its value and metadata.
399
402
  *
400
- * @param element
401
- * @param options
402
- * @param dpName
403
+ * @param {*} element - The value or metadata object to derive the role from.
404
+ * @param {object|boolean} options - Parsing options or write flag.
405
+ * @param {string} [dpName] - The datapoint name used to detect time-based roles.
406
+ * @returns {string} The ioBroker role string (e.g. "state", "switch", "text").
403
407
  */
404
408
  getRole(element, options, dpName) {
405
- const write = options.write;
409
+ // const write = options.write;
406
410
  const hasStates = element && typeof element === "object" && element.states !== undefined;
407
411
 
408
412
 
@@ -412,7 +416,7 @@ class Helper {
412
416
  }
413
417
 
414
418
  if (hasStates) {
415
- if (element.type == "boolean") {
419
+ if (element.type === "boolean") {
416
420
  delete element.states;
417
421
  return "button";
418
422
  }
@@ -431,8 +435,10 @@ class Helper {
431
435
  return "state";
432
436
  }
433
437
  /**
438
+ * Resolves and normalises the value from a ZWave command class metadata object.
434
439
  *
435
- * @param element
440
+ * @param {object} element - The metadata object containing type, value, min, writeable and readable fields.
441
+ * @returns {*} The resolved and normalised value ready for use as an ioBroker state value.
436
442
  */
437
443
  resolveCommandClassValue(element) {
438
444
  const type = element.type;
@@ -494,8 +500,9 @@ class Helper {
494
500
 
495
501
 
496
502
  /**
503
+ * Creates the ready and status state objects directly on the node device.
497
504
  *
498
- * @param nodeId
505
+ * @param {string} nodeId - The formatted node ID used as the ioBroker object path prefix.
499
506
  */
500
507
  async createReadyStatus(nodeId) {
501
508
  // leg die status direkt auch an
@@ -529,12 +536,13 @@ class Helper {
529
536
  native: {},
530
537
  });
531
538
  }
532
- /**
533
- *
534
- * @param nodeId
535
- * @param element
536
- * @param nameChange
537
- */
539
+ /**
540
+ * Updates the name or description of an existing ioBroker device object.
541
+ *
542
+ * @param {string} nodeId - The ioBroker object ID of the device to update.
543
+ * @param {object} element - The element containing the new name, productLabel, manufacturer or desc.
544
+ * @param {boolean} [nameChange] - If true, updates the common name; otherwise updates the description.
545
+ */
538
546
  async updateDevice(nodeId, element, nameChange = true) {
539
547
  const obj = await this.adapter.getObjectAsync(nodeId);
540
548
  if (obj) {
@@ -553,6 +561,22 @@ class Helper {
553
561
  }
554
562
  }
555
563
  }
564
+
565
+ /**
566
+ * Sets or conditionally updates an ioBroker state value.
567
+ *
568
+ * @param {string} path - The ioBroker state ID to set.
569
+ * @param {*} value - The value to write to the state.
570
+ * @param {boolean} [change] - If true, uses setState (unconditional); otherwise uses setStateChanged.
571
+ */
572
+ async changeState(path, value, change = false) {
573
+ if (change) {
574
+ this.adapter.setState(path, value, true);
575
+ } else {
576
+ this.adapter.setStateChanged(path, value, true);
577
+ }
578
+ }
579
+
556
580
  }
557
581
 
558
582
  module.exports = {
@@ -1,6 +1,6 @@
1
1
  const core = require("@iobroker/adapter-core");
2
2
  const Aedes = require("aedes");
3
- const net = require("net");
3
+ const net = require("node:net");
4
4
  let mqttServer;
5
5
 
6
6
  /**
@@ -1,20 +1,21 @@
1
1
  /**
2
- *
2
+ * Controls reading and writing of ioBroker states for the ZWave adapter.
3
3
  */
4
4
  class StatesController {
5
5
  /**
6
+ * Creates a new StatesController instance.
6
7
  *
7
- * @param adapter
8
- * @param deviceCache
8
+ * @param {object} adapter - The ioBroker adapter instance.
9
9
  */
10
10
  constructor(adapter) {
11
11
  this.adapter = adapter;
12
12
  }
13
13
 
14
14
  /**
15
+ * Sets a state value unconditionally, skipping null/undefined values.
15
16
  *
16
- * @param stateName
17
- * @param value
17
+ * @param {string} stateName - The ioBroker state ID to set.
18
+ * @param {*} value - The value to write to the state.
18
19
  */
19
20
  async setStateSafelyAsync(stateName, value) {
20
21
  if (value === undefined || value === null) {
@@ -24,9 +25,10 @@ class StatesController {
24
25
  }
25
26
 
26
27
  /**
28
+ * Sets a state value only if it has changed, skipping null/undefined values.
27
29
  *
28
- * @param stateName
29
- * @param value
30
+ * @param {string} stateName - The ioBroker state ID to set.
31
+ * @param {*} value - The value to write to the state.
30
32
  */
31
33
  async setStateChangedSafelyAsync(stateName, value) {
32
34
  if (value === undefined || value === null) {
@@ -36,8 +38,9 @@ class StatesController {
36
38
  }
37
39
 
38
40
  /**
41
+ * Reads all writable ZWave states from ioBroker and returns them as a map keyed by object ID.
39
42
  *
40
- * @param deviceCache
43
+ * @returns {Promise<object>} A map of writable state IDs to their MQTT path and write flag.
41
44
  */
42
45
  async subscribeAllWritableExistsStates() {
43
46
  const writableStates = {};
@@ -62,7 +65,8 @@ class StatesController {
62
65
  }
63
66
 
64
67
  /**
65
- *
68
+ * Sets all node ready-states to false, all status-states to "unknown"
69
+ * and the gateway status to "offline".
66
70
  */
67
71
  async setAllAvailableToFalse() {
68
72
  const readyStates = await this.adapter.getStatesAsync("*.ready");
package/lib/utils.js CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
+ * Converts a byte array to a word array.
2
3
  *
3
- * @param ba
4
+ * @param {number[]} ba - The byte array to convert.
4
5
  */
5
6
  function bytesArrayToWordArray(ba) {
6
7
  const wa = [];
@@ -13,8 +14,9 @@ function bytesArrayToWordArray(ba) {
13
14
  // If the value is greater than 1000, kelvin is assumed.
14
15
  // If smaller, it is assumed to be mired.
15
16
  /**
17
+ * Converts a temperature value to mired.
16
18
  *
17
- * @param t
19
+ * @param {number} t - Temperature value in Kelvin or mired.
18
20
  */
19
21
  function toMired(t) {
20
22
  let miredValue = t;
@@ -25,8 +27,9 @@ function toMired(t) {
25
27
  }
26
28
 
27
29
  /**
30
+ * Converts between mired and Kelvin.
28
31
  *
29
- * @param t
32
+ * @param {number} t - Temperature value to convert.
30
33
  */
31
34
  function miredKelvinConversion(t) {
32
35
  return Math.round(1000000 / t);
@@ -35,8 +38,8 @@ function miredKelvinConversion(t) {
35
38
  /**
36
39
  * Converts a decimal number to a hex string with zero-padding
37
40
  *
38
- * @param decimal
39
- * @param padding
41
+ * @param {number} decimal - The decimal number to convert.
42
+ * @param {number} padding - The minimum length of the resulting hex string.
40
43
  */
41
44
  function decimalToHex(decimal, padding) {
42
45
  let hex = Number(decimal).toString(16);
@@ -53,8 +56,9 @@ function decimalToHex(decimal, padding) {
53
56
  }
54
57
 
55
58
  /**
59
+ * Removes all elements from an array in place.
56
60
  *
57
- * @param array
61
+ * @param {any[]} array - The array to clear.
58
62
  */
59
63
  function clearArray(array) {
60
64
  while (array.length > 0) {
@@ -63,9 +67,10 @@ function clearArray(array) {
63
67
  }
64
68
 
65
69
  /**
70
+ * Moves all elements from source array into target array.
66
71
  *
67
- * @param source
68
- * @param target
72
+ * @param {any[]} source - The source array to move elements from.
73
+ * @param {any[]} target - The target array to move elements into.
69
74
  */
70
75
  function moveArray(source, target) {
71
76
  while (source.length > 0) {
@@ -74,16 +79,18 @@ function moveArray(source, target) {
74
79
  }
75
80
 
76
81
  /**
82
+ * Checks whether a value is a plain object.
77
83
  *
78
- * @param item
84
+ * @param {any} item - The value to check.
79
85
  */
80
86
  function isObject(item) {
81
87
  return typeof item === "object" && !Array.isArray(item) && item !== null;
82
88
  }
83
89
 
84
90
  /**
91
+ * Checks whether a value is valid JSON.
85
92
  *
86
- * @param item
93
+ * @param {any} item - The value to check.
87
94
  */
88
95
  function isJson(item) {
89
96
  let value = typeof item !== "string" ? JSON.stringify(item) : item;
@@ -97,8 +104,9 @@ function isJson(item) {
97
104
  }
98
105
 
99
106
  /**
107
+ * Returns the last segment of a dot- or slash-separated string.
100
108
  *
101
- * @param input
109
+ * @param {string} input - The input string to parse.
102
110
  */
103
111
  function getLastSegment(input) {
104
112
  if (typeof input !== "string") {
@@ -109,7 +117,9 @@ function getLastSegment(input) {
109
117
  }
110
118
 
111
119
  /**
112
- * @param {any} value
120
+ * Checks whether a value is numeric (finite number or numeric string).
121
+ *
122
+ * @param {any} value - The value to check.
113
123
  * @returns {boolean}
114
124
  */
115
125
  function isNumeric(value) {
@@ -130,8 +140,9 @@ function isNumeric(value) {
130
140
  }
131
141
 
132
142
  /**
143
+ * Replaces the last dot in a string with an underscore.
133
144
  *
134
- * @param str
145
+ * @param {string} str - The string to process.
135
146
  */
136
147
  function replaceLastDot(str) {
137
148
  const idx = str.lastIndexOf(".");
@@ -139,8 +150,18 @@ function replaceLastDot(str) {
139
150
  }
140
151
 
141
152
  /**
153
+ * Removes a trailing dot from a string if present.
154
+ *
155
+ * @param {string|undefined} str - The string to process.
156
+ */
157
+ function deleteLastDot(str) {
158
+ return str.endsWith(".") ? str.slice(0, -1) : str;
159
+ }
160
+
161
+ /**
162
+ * Trims and normalises an object name string.
142
163
  *
143
- * @param str
164
+ * @param {string} str - The string to format.
144
165
  */
145
166
  function formatObject(str) {
146
167
  if (typeof str !== "string") {
@@ -150,8 +171,9 @@ function formatObject(str) {
150
171
  }
151
172
 
152
173
  /**
174
+ * Replaces all dots in an MQTT topic with slashes.
153
175
  *
154
- * @param input
176
+ * @param {string} input - The MQTT topic string to format.
155
177
  */
156
178
  function formatMQTT(input) {
157
179
  if (typeof input !== "string") {
@@ -161,17 +183,19 @@ function formatMQTT(input) {
161
183
  }
162
184
 
163
185
  /**
186
+ * Zero-pads the numeric suffix of a node ID string.
164
187
  *
165
- * @param nodeId
166
- * @param width
188
+ * @param {string} nodeId - The node ID string to pad.
189
+ * @param {number} [width] - The desired minimum width of the numeric part.
167
190
  */
168
191
  function padNodeId(nodeId, width = 3) {
169
192
  return nodeId.replace(/(\d+)$/, (m) => m.padStart(width, "0"));
170
193
  }
171
194
 
172
195
  /**
196
+ * Returns a human-readable status text for a given node status code.
173
197
  *
174
- * @param status
198
+ * @param {number} status - The numeric status code.
175
199
  */
176
200
  function getStatusText(status) {
177
201
  const nodeStatus = {
@@ -186,8 +210,9 @@ function getStatusText(status) {
186
210
  }
187
211
 
188
212
  /**
213
+ * Formats a node ID, padding numeric IDs with a prefix.
189
214
  *
190
- * @param nodeIdOriginal
215
+ * @param {string|number} nodeIdOriginal - The original node ID to format.
191
216
  */
192
217
  function formatNodeId(nodeIdOriginal) {
193
218
  let nodeId = nodeIdOriginal;
@@ -215,4 +240,5 @@ module.exports = {
215
240
  padNodeId,
216
241
  getStatusText,
217
242
  formatObject,
243
+ deleteLastDot,
218
244
  };
@@ -7,19 +7,22 @@ let pingTimeout;
7
7
  let autoRestartTimeout;
8
8
 
9
9
  /**
10
- *
10
+ * Manages the WebSocket connection to the zwave-js-ui server.
11
11
  */
12
12
  class WebsocketController {
13
13
  /**
14
+ * Creates a new WebsocketController instance.
14
15
  *
15
- * @param adapter
16
+ * @param {object} adapter - The ioBroker adapter instance.
16
17
  */
17
18
  constructor(adapter) {
18
19
  this.adapter = adapter;
19
20
  }
20
21
 
21
22
  /**
23
+ * Initialises and connects the WebSocket client to the zwave-js-ui server.
22
24
  *
25
+ * @returns {WebSocket} The created WebSocket client instance.
23
26
  */
24
27
  initWsClient() {
25
28
  try {
@@ -64,8 +67,9 @@ class WebsocketController {
64
67
  }
65
68
 
66
69
  /**
70
+ * Sends a message to the zwave-js-ui server via the WebSocket connection.
67
71
  *
68
- * @param message
72
+ * @param {string} message - The message payload to send.
69
73
  */
70
74
  send(message) {
71
75
  if (wsClient.readyState !== WebSocket.OPEN) {
@@ -76,7 +80,7 @@ class WebsocketController {
76
80
  }
77
81
 
78
82
  /**
79
- *
83
+ * Sends a WebSocket ping to the server and schedules the next ping.
80
84
  */
81
85
  sendPingToServer() {
82
86
  //this.logDebug('Send ping to server');
@@ -87,7 +91,7 @@ class WebsocketController {
87
91
  }
88
92
 
89
93
  /**
90
- *
94
+ * Resets the heartbeat timeout; terminates the connection if no pong is received in time.
91
95
  */
92
96
  wsHeartbeat() {
93
97
  clearTimeout(pingTimeout);
@@ -98,7 +102,7 @@ class WebsocketController {
98
102
  }
99
103
 
100
104
  /**
101
- *
105
+ * Schedules an automatic reconnect attempt after the configured restart timeout.
102
106
  */
103
107
  async autoRestart() {
104
108
  this.adapter.log.warn(`Start try again in ${restartTimeout / 1000} seconds...`);
@@ -108,7 +112,7 @@ class WebsocketController {
108
112
  }
109
113
 
110
114
  /**
111
- *
115
+ * Closes the WebSocket connection if it is currently open.
112
116
  */
113
117
  closeConnection() {
114
118
  if (wsClient && wsClient.readyState !== WebSocket.CLOSED) {
@@ -117,7 +121,7 @@ class WebsocketController {
117
121
  }
118
122
 
119
123
  /**
120
- *
124
+ * Clears all active timers (ping, pingTimeout, autoRestartTimeout).
121
125
  */
122
126
  async allTimerClear() {
123
127
  clearTimeout(pingTimeout);
package/main.js CHANGED
@@ -160,6 +160,7 @@ class zwavews extends core.Adapter {
160
160
  this.setStateChanged('info.connection', false, true);
161
161
  await statesController.setAllAvailableToFalse();
162
162
  startListening = false;
163
+ allNodesCreated = false;
163
164
  deviceCache = [];
164
165
  this.nodeCache = [];
165
166
  this.log.info('Websocket connection closed. Attempting to reconnect...');
@@ -263,7 +264,7 @@ class zwavews extends core.Adapter {
263
264
  }
264
265
  }
265
266
 
266
- parsePath = utils.formatObject(parsePath);
267
+ parsePath = utils.deleteLastDot(utils.formatObject(parsePath));
267
268
 
268
269
  if (nodeArg.commandClass === 119) { // sonderlocke für node naming
269
270
  switch (nodeArg.property) {
@@ -291,8 +292,13 @@ class zwavews extends core.Adapter {
291
292
  parsePath = `${parsePath}_${nodeArg.endpoint}`;
292
293
  }
293
294
 
294
- await helper.parse(`${parsePath}`, nodeArg.newValue, options);
295
+ parsePath = utils.deleteLastDot(parsePath); // check again
295
296
 
297
+ if (eventTyp.event === 'value notification') {
298
+ await helper.parse(`${parsePath}`, nodeArg.newValue, options, true);
299
+ } else {
300
+ await helper.parse(`${parsePath}`, nodeArg.newValue, options, false);
301
+ }
296
302
  break;
297
303
  }
298
304
 
@@ -381,11 +387,9 @@ class zwavews extends core.Adapter {
381
387
  if (["exmqtt", "intmqtt"].includes(this.config.connectionType)) {
382
388
  if (mqttClient && !mqttClient.closed) {
383
389
  try {
384
- if (mqttClient) {
385
390
  mqttClient.end();
386
- }
387
391
  } catch (e) {
388
- this.log.error(e);
392
+ this.log.error(e);
389
393
  }
390
394
  }
391
395
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "iobroker.zwavews",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "zwavews adapter for ioBroker",
5
5
  "author": {
6
- "name": "Dennis Rathjen and Arthur Rupp",
6
+ "name": "Arthur Rupp",
7
7
  "email": "arteck@outlook.com"
8
8
  },
9
9
  "homepage": "https://github.com/arteck/ioBroker.zwavews",
@@ -26,28 +26,28 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@iobroker/adapter-core": "^3.3.2",
29
- "@iobroker/dm-utils": "^2.0.1",
29
+ "@iobroker/dm-utils": "^3.0.3",
30
30
  "humanize-duration": "^3.33.2",
31
31
  "aedes": "^0.51.3",
32
32
  "aedes-persistence-nedb": "^2.0.3",
33
- "mqtt": "^5.15.0",
33
+ "mqtt": "^5.15.1",
34
34
  "net": "^1.0.2",
35
35
  "node-schedule": "^2.1.1",
36
36
  "sharp": "^0.34.5",
37
- "ws": "^8.19.0"
37
+ "ws": "^8.20.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@alcalzone/release-script": "^5.1.1",
41
- "@alcalzone/release-script-plugin-iobroker": "^4.0.0",
41
+ "@alcalzone/release-script-plugin-iobroker": "^5.1.2",
42
42
  "@alcalzone/release-script-plugin-license": "^5.1.1",
43
- "@alcalzone/release-script-plugin-manual-review": "^4.0.0",
43
+ "@alcalzone/release-script-plugin-manual-review": "^5.1.1",
44
44
  "@iobroker/adapter-dev": "^1.5.0",
45
45
  "@iobroker/testing": "^5.2.2",
46
46
  "@iobroker/eslint-config": "^2.2.0",
47
47
  "@tsconfig/node14": "^14.1.8",
48
- "@types/node": "^25.2.3",
48
+ "@types/node": "^25.5.0",
49
49
  "@types/node-schedule": "^2.1.8",
50
- "typescript": "~5.9.2"
50
+ "typescript": "~6.0.2"
51
51
  },
52
52
  "main": "main.js",
53
53
  "files": [