iobroker.zwavews 0.1.3 → 0.1.5
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 +7 -1
- package/io-package.json +28 -28
- package/lib/devicemgmt.js +212 -29
- package/lib/helper.js +77 -36
- package/lib/messages.js +4 -4
- package/lib/mqttServerController.js +1 -1
- package/lib/statesController.js +3 -2
- package/lib/utils.js +9 -7
- package/lib/websocketController.js +3 -3
- package/main.js +104 -66
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -35,8 +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.5 (2026-04-21)
|
|
39
|
+
* (arteck) upd devicemanager
|
|
40
|
+
|
|
41
|
+
### 0.1.4 (2026-04-16)
|
|
42
|
+
* (arteck) Dependencies have been updated
|
|
43
|
+
* (arteck) add vscode folder
|
|
44
|
+
|
|
38
45
|
### 0.1.3 (2026-04-03)
|
|
39
|
-
* (arteck) fix unknown state from scene
|
|
40
46
|
* (arteck) del last dot from DP
|
|
41
47
|
* (arteck) fix scene
|
|
42
48
|
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "zwavews",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.5",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.1.5": {
|
|
7
|
+
"en": "upd devicemanager",
|
|
8
|
+
"de": "gerätemanager und -manager",
|
|
9
|
+
"ru": "улучшенный devicemanager",
|
|
10
|
+
"pt": "gerenciador de dispositivos upd",
|
|
11
|
+
"nl": "upd apparaatbeheerder",
|
|
12
|
+
"fr": "gestionnaire d'appareil",
|
|
13
|
+
"it": "upd devicemanager",
|
|
14
|
+
"es": "upd devicemanager",
|
|
15
|
+
"pl": "upd devicemaniger",
|
|
16
|
+
"uk": "напляскване",
|
|
17
|
+
"zh-cn": "上调设备管理器"
|
|
18
|
+
},
|
|
19
|
+
"0.1.4": {
|
|
20
|
+
"en": "Dependencies have been updated\nadd vscode folder",
|
|
21
|
+
"de": "Abhängigkeiten wurden aktualisiert\nvscode ordner hinzufügen",
|
|
22
|
+
"ru": "Зависимости были обновлены\nдобавить vscode папку",
|
|
23
|
+
"pt": "As dependências foram atualizadas\nadicionar pasta vscode",
|
|
24
|
+
"nl": "Afhankelijkheden zijn bijgewerkt\nvscode map toevoegen",
|
|
25
|
+
"fr": "Les dépendances ont été actualisées\najouter un dossier vscode",
|
|
26
|
+
"it": "Le dipendenze sono state aggiornate\naggiungere cartella vscode",
|
|
27
|
+
"es": "Se han actualizado las dependencias\nañadir carpeta vscode",
|
|
28
|
+
"pl": "Zaktualizowano zależności\ndodaj folder vscode",
|
|
29
|
+
"uk": "Залежність було оновлено\nдодати папку проти коду",
|
|
30
|
+
"zh-cn": "依赖关系已更新\n添加 vscode 文件夹"
|
|
31
|
+
},
|
|
6
32
|
"0.1.3": {
|
|
7
33
|
"en": "fix unknown state from scene\ndel last dot from DP\nfix scene",
|
|
8
34
|
"de": "unbekannter zustand von szene\nder letzte Punkt von DP\nfixe szene",
|
|
@@ -67,32 +93,6 @@
|
|
|
67
93
|
"pl": "dodaj info.sendMessageDopuszczalny obiekt, aby umożliwić wysyłanie wiadomości do zwave- ui- js\ndodaj nową opcję do ustawienia info.sendMessage Dozwolone bezpośrednio po uruchomieniu adaptera",
|
|
68
94
|
"uk": "додайте інформацію.sendMessageВсього об'єкту, щоб дозволити надсилати повідомлення на zwave-ui-js\nдодати нову прапорець, щоб встановити інформацію.sendMessage Допускається відразу після запуску адаптера",
|
|
69
95
|
"zh-cn": "添加信息. sendMessage Allowed 对象允许将消息发送到 zwave- ui- js\n添加新复选框以设置信息. sendMessage 启动适配器后立即允许"
|
|
70
|
-
},
|
|
71
|
-
"0.0.17": {
|
|
72
|
-
"en": "fix adapter start\nDependencies have been updated",
|
|
73
|
-
"de": "befestigungsadapter start\nAbhängigkeiten wurden aktualisiert",
|
|
74
|
-
"ru": "запуск адаптера\nЗависимости были обновлены",
|
|
75
|
-
"pt": "corrigir o início do adaptador\nAs dependências foram atualizadas",
|
|
76
|
-
"nl": "fix adapter start\nAfhankelijkheden zijn bijgewerkt",
|
|
77
|
-
"fr": "fixer le démarrage de l'adaptateur\nLes dépendances ont été actualisées",
|
|
78
|
-
"it": "avvio dell'adattatore\nLe dipendenze sono state aggiornate",
|
|
79
|
-
"es": "adaptador de fijación\nSe han actualizado las dependencias",
|
|
80
|
-
"pl": "uruchomić adapter\nZaktualizowano zależności",
|
|
81
|
-
"uk": "запуск адаптера\nЗалежність було оновлено",
|
|
82
|
-
"zh-cn": "固定适配器启动\n依赖关系已更新"
|
|
83
|
-
},
|
|
84
|
-
"0.0.16": {
|
|
85
|
-
"en": "fix warning message",
|
|
86
|
-
"de": "warnmeldung aktivieren",
|
|
87
|
-
"ru": "исправить предупреждающее сообщение",
|
|
88
|
-
"pt": "corrigir a mensagem de aviso",
|
|
89
|
-
"nl": "waarschuwingsbericht herstellen",
|
|
90
|
-
"fr": "corriger le message d'avertissement",
|
|
91
|
-
"it": "correzione del messaggio di avviso",
|
|
92
|
-
"es": "mensaje de advertencia",
|
|
93
|
-
"pl": "naprawić komunikat ostrzegawczy",
|
|
94
|
-
"uk": "фіксувати повідомлення про попередження",
|
|
95
|
-
"zh-cn": "修补警告消息"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|
|
@@ -180,7 +180,7 @@
|
|
|
180
180
|
],
|
|
181
181
|
"globalDependencies": [
|
|
182
182
|
{
|
|
183
|
-
"admin": ">=7.6.
|
|
183
|
+
"admin": ">=7.6.20"
|
|
184
184
|
}
|
|
185
185
|
],
|
|
186
186
|
"plugins": {
|
package/lib/devicemgmt.js
CHANGED
|
@@ -17,21 +17,28 @@ class dmZwave extends dmUtils.DeviceManagement {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
+
* Loads all ZWave devices and reports them to the device manager context.
|
|
21
|
+
* Called by the dm-utils framework in response to a 'dm:loadDevices' message.
|
|
20
22
|
*
|
|
23
|
+
* @param {object} context - The DeviceLoadContext (addDevice / setTotalDevices / complete).
|
|
21
24
|
*/
|
|
22
|
-
async
|
|
25
|
+
async loadDevices(context) {
|
|
23
26
|
const devices = await this.adapter.getDevicesAsync();
|
|
24
|
-
|
|
27
|
+
context.setTotalDevices(devices.length);
|
|
28
|
+
|
|
25
29
|
for (const i in devices) {
|
|
26
30
|
const status = {};
|
|
27
31
|
|
|
28
32
|
const nodeId = this.stripIobPrefix(devices[i]._id);
|
|
29
33
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
const cacheEntry = this.adapter.nodeCache[nodeId];
|
|
35
|
+
if (!cacheEntry) {
|
|
36
|
+
this.adapter.log.warn(`listDevices: nodeCache miss for ${nodeId}, skipping.`);
|
|
37
|
+
continue;
|
|
34
38
|
}
|
|
39
|
+
const device = cacheEntry.nodeData;
|
|
40
|
+
|
|
41
|
+
status.connection = device.ready ? 'connected' : 'disconnected';
|
|
35
42
|
|
|
36
43
|
//const link_quality = await this.adapter.getStateAsync(`${theDevice._id}.status`);
|
|
37
44
|
//status.rssi = link_quality.val == 'alive' ? '100' : '0';
|
|
@@ -60,12 +67,53 @@ class dmZwave extends dmUtils.DeviceManagement {
|
|
|
60
67
|
devStatus = 'unknown';
|
|
61
68
|
}
|
|
62
69
|
|
|
70
|
+
// Sensordaten aus Multilevel_Sensor laden
|
|
71
|
+
const sensorCustomInfo = {
|
|
72
|
+
id: nodeId,
|
|
73
|
+
schema: {
|
|
74
|
+
type: 'panel',
|
|
75
|
+
items: {},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const sensorObjects = await this.adapter.getObjectViewAsync('system', 'state', {
|
|
81
|
+
startkey: `${devices[i]._id}.Multilevel_Sensor.`,
|
|
82
|
+
endkey: `${devices[i]._id}.Multilevel_Sensor.\u9999`,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (sensorObjects && sensorObjects.rows && sensorObjects.rows.length > 0) {
|
|
86
|
+
for (const row of sensorObjects.rows) {
|
|
87
|
+
const obj = row.value;
|
|
88
|
+
if (!obj) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const stateId = obj._id;
|
|
92
|
+
const sensorKey = stateId.replace(/\./g, '_');
|
|
93
|
+
const labelParts = stateId.split('.');
|
|
94
|
+
const sensorLabel = labelParts[labelParts.length - 1];
|
|
95
|
+
const unit = obj.common?.unit ? ` (${obj.common.unit})` : '';
|
|
96
|
+
|
|
97
|
+
sensorCustomInfo.schema.items[sensorKey] = {
|
|
98
|
+
type: 'state',
|
|
99
|
+
oid: stateId,
|
|
100
|
+
foreign: true,
|
|
101
|
+
label: `${sensorLabel}${unit}`,
|
|
102
|
+
newLine: true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} catch (e) {
|
|
107
|
+
this.adapter.log.warn(`listDevices: Fehler beim Laden der Multilevel_Sensor-Daten für ${nodeId}: ${e.message}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
63
111
|
const res = {
|
|
64
112
|
id: nodeId,
|
|
65
113
|
name: device.name || device.label,
|
|
66
114
|
icon: devStatus,
|
|
67
|
-
manufacturer: device.deviceConfig
|
|
68
|
-
model: `${device.deviceConfig
|
|
115
|
+
manufacturer: device.deviceConfig?.manufacturer ?? '',
|
|
116
|
+
model: `${device.deviceConfig?.label ?? ''} ${device.deviceConfig?.description ?? ''}`.trim(),
|
|
69
117
|
status: status,
|
|
70
118
|
hasDetails: true,
|
|
71
119
|
actions: [
|
|
@@ -78,17 +126,122 @@ class dmZwave extends dmUtils.DeviceManagement {
|
|
|
78
126
|
],
|
|
79
127
|
};
|
|
80
128
|
|
|
129
|
+
// Schalter aus Multilevel_Switch laden
|
|
130
|
+
try {
|
|
131
|
+
const switchObjects = await this.adapter.getObjectViewAsync('system', 'state', {
|
|
132
|
+
startkey: `${devices[i]._id}.Multilevel_Switch.`,
|
|
133
|
+
endkey: `${devices[i]._id}.Multilevel_Switch.\u9999`,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (switchObjects && switchObjects.rows && switchObjects.rows.length > 0) {
|
|
137
|
+
// Trennlinie einfügen, wenn bereits Sensordaten vorhanden
|
|
138
|
+
if (Object.keys(sensorCustomInfo.schema.items).length > 0) {
|
|
139
|
+
sensorCustomInfo.schema.items['_divider_switch'] = {
|
|
140
|
+
type: 'divider',
|
|
141
|
+
color: 'primary',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Alle Rows in eine Map sammeln (rawName → {obj, stateId})
|
|
146
|
+
const switchMap = {};
|
|
147
|
+
for (const row of switchObjects.rows) {
|
|
148
|
+
const obj = row.value;
|
|
149
|
+
if (!obj) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const parts = obj._id.split('.');
|
|
153
|
+
const rawName = parts[parts.length - 1];
|
|
154
|
+
switchMap[rawName] = { obj, stateId: obj._id };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Gewünschte Reihenfolge, unbekannte States werden danach angehängt
|
|
158
|
+
const order = ['open', 'close', 'currentValue', 'targetValue', 'restorePrevious', 'duration'];
|
|
159
|
+
const allKeys = [...order, ...Object.keys(switchMap).filter(k => !order.includes(k))];
|
|
160
|
+
|
|
161
|
+
allKeys.forEach((rawName, index) => {
|
|
162
|
+
if (!switchMap[rawName]) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const { obj, stateId } = switchMap[rawName];
|
|
166
|
+
// Nummerierten Prefix damit Admin-UI die Elemente in der richtigen Reihenfolge anzeigt
|
|
167
|
+
const switchKey = `_sw${String(index + 1).padStart(2, '0')}_${rawName}`;
|
|
168
|
+
const isBoolean = obj.common?.type === 'boolean';
|
|
81
169
|
|
|
82
|
-
|
|
170
|
+
// Anzeigenamen umbenennen
|
|
171
|
+
let switchLabel;
|
|
172
|
+
if (rawName === 'targetValue') {
|
|
173
|
+
switchLabel = 'Target';
|
|
174
|
+
} else if (rawName === 'currentValue') {
|
|
175
|
+
switchLabel = 'Current';
|
|
176
|
+
} else {
|
|
177
|
+
switchLabel = rawName;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// currentValue: nur lesebarer numerischer Wert mit %-Einheit
|
|
181
|
+
// duration: nur lesebarer numerischer Wert
|
|
182
|
+
if (rawName === 'currentValue' && !isBoolean) {
|
|
183
|
+
sensorCustomInfo.schema.items[switchKey] = {
|
|
184
|
+
type: 'state',
|
|
185
|
+
oid: stateId,
|
|
186
|
+
foreign: true,
|
|
187
|
+
label: switchLabel,
|
|
188
|
+
readOnly: true,
|
|
189
|
+
unit: '%',
|
|
190
|
+
newLine: true,
|
|
191
|
+
};
|
|
192
|
+
} else if (rawName === 'duration' && !isBoolean) {
|
|
193
|
+
sensorCustomInfo.schema.items[switchKey] = {
|
|
194
|
+
type: 'state',
|
|
195
|
+
oid: stateId,
|
|
196
|
+
foreign: true,
|
|
197
|
+
label: switchLabel,
|
|
198
|
+
readOnly: true,
|
|
199
|
+
newLine: true,
|
|
200
|
+
};
|
|
201
|
+
} else if (isBoolean) {
|
|
202
|
+
sensorCustomInfo.schema.items[switchKey] = {
|
|
203
|
+
type: 'state',
|
|
204
|
+
oid: stateId,
|
|
205
|
+
foreign: true,
|
|
206
|
+
label: switchLabel,
|
|
207
|
+
control: 'switch',
|
|
208
|
+
trueTextStyle: { color: 'green' },
|
|
209
|
+
falseTextStyle: { color: 'red' },
|
|
210
|
+
trueText: 'ON',
|
|
211
|
+
falseText: 'OFF',
|
|
212
|
+
newLine: true,
|
|
213
|
+
};
|
|
214
|
+
} else {
|
|
215
|
+
sensorCustomInfo.schema.items[switchKey] = {
|
|
216
|
+
type: 'state',
|
|
217
|
+
oid: stateId,
|
|
218
|
+
foreign: true,
|
|
219
|
+
label: switchLabel,
|
|
220
|
+
control: 'slider',
|
|
221
|
+
min: obj.common?.min ?? 0,
|
|
222
|
+
max: obj.common?.max ?? 99,
|
|
223
|
+
newLine: true,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
} catch (e) {
|
|
229
|
+
this.adapter.log.warn(`listDevices: Fehler beim Laden der Multilevel_Switch-Daten für ${nodeId}: ${e.message}`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
// Nur customInfo anhängen, wenn Sensor- oder Schalterdaten vorhanden sind
|
|
234
|
+
if (Object.keys(sensorCustomInfo.schema.items).length > 0) {
|
|
235
|
+
res.customInfo = sensorCustomInfo;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
context.addDevice(res);
|
|
83
240
|
}
|
|
84
241
|
|
|
85
242
|
// nach id sortieren (z.B. nodeID_2 vor nodeID_10)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
sensitivity: 'base',
|
|
89
|
-
}));
|
|
90
|
-
|
|
91
|
-
return arrDevices;
|
|
243
|
+
// Note: sorting is informational only; context already sent devices
|
|
244
|
+
context.complete();
|
|
92
245
|
}
|
|
93
246
|
|
|
94
247
|
/**
|
|
@@ -145,10 +298,10 @@ class dmZwave extends dmUtils.DeviceManagement {
|
|
|
145
298
|
* Returns the detail schema and data for a specific device.
|
|
146
299
|
*
|
|
147
300
|
* @param {string} id - The node ID of the device.
|
|
148
|
-
* @param {object}
|
|
149
|
-
* @param {object}
|
|
301
|
+
* @param {object} _action - The action object passed by the device management framework.
|
|
302
|
+
* @param {object} _context - The device management context.
|
|
150
303
|
*/
|
|
151
|
-
async getDeviceDetails(id,
|
|
304
|
+
async getDeviceDetails(id, _action, _context) {
|
|
152
305
|
this.adapter.log.debug('getDeviceDetails');
|
|
153
306
|
|
|
154
307
|
const device = this.adapter.nodeCache[id]?.nodeData;
|
|
@@ -269,6 +422,10 @@ class dmZwave extends dmUtils.DeviceManagement {
|
|
|
269
422
|
devStatus = 'unknown';
|
|
270
423
|
}
|
|
271
424
|
|
|
425
|
+
// Kalibrierungsknopf prüfen
|
|
426
|
+
const calibStateId = `${this.adapter.namespace}.${id}.Configuration.Forced_Roller_Shutter_Calibration`;
|
|
427
|
+
const calibObj = await this.adapter.getObjectAsync(calibStateId).catch(() => null);
|
|
428
|
+
|
|
272
429
|
return {
|
|
273
430
|
id: String(device.nodeId),
|
|
274
431
|
schema: {
|
|
@@ -338,6 +495,34 @@ class dmZwave extends dmUtils.DeviceManagement {
|
|
|
338
495
|
label: 'Max Baud Rate',
|
|
339
496
|
readOnly: true,
|
|
340
497
|
},
|
|
498
|
+
...(calibObj ? {
|
|
499
|
+
_divider_calib: {
|
|
500
|
+
type: 'divider',
|
|
501
|
+
color: 'primary',
|
|
502
|
+
},
|
|
503
|
+
_calib_spacer: {
|
|
504
|
+
type: 'staticText',
|
|
505
|
+
text: '',
|
|
506
|
+
newLine: true,
|
|
507
|
+
xs: 8,
|
|
508
|
+
sm: 9,
|
|
509
|
+
md: 10,
|
|
510
|
+
lg: 10,
|
|
511
|
+
xl: 10,
|
|
512
|
+
},
|
|
513
|
+
_calib_button: {
|
|
514
|
+
type: 'state',
|
|
515
|
+
oid: calibStateId,
|
|
516
|
+
foreign: true,
|
|
517
|
+
label: 'Kalibrierung starten',
|
|
518
|
+
control: 'button',
|
|
519
|
+
xs: 4,
|
|
520
|
+
sm: 3,
|
|
521
|
+
md: 2,
|
|
522
|
+
lg: 2,
|
|
523
|
+
xl: 2,
|
|
524
|
+
},
|
|
525
|
+
} : {}),
|
|
341
526
|
},
|
|
342
527
|
},
|
|
343
528
|
_tab_Details: {
|
|
@@ -370,18 +555,16 @@ class dmZwave extends dmUtils.DeviceManagement {
|
|
|
370
555
|
* @param {number} time - The timestamp in milliseconds (epoch).
|
|
371
556
|
* @param {'ISO_8601'|'ISO_8601_local'|'epoch'|'relative'} type - The desired output format.
|
|
372
557
|
*/
|
|
373
|
-
|
|
558
|
+
formatDate(time, type) { //'ISO_8601' | 'ISO_8601_local' | 'epoch' | 'relative'
|
|
374
559
|
if (type === 'ISO_8601') {
|
|
375
|
-
return new Date(time).toISOString();
|
|
376
|
-
} else if (type === 'ISO_8601_local') {
|
|
377
|
-
return this.toLocalISOString(new Date(time));
|
|
378
|
-
} else if (type === 'epoch') {
|
|
379
|
-
return time;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
return ago;
|
|
384
|
-
|
|
560
|
+
return new Date(time).toISOString();
|
|
561
|
+
} else if (type === 'ISO_8601_local') {
|
|
562
|
+
return this.toLocalISOString(new Date(time));
|
|
563
|
+
} else if (type === 'epoch') {
|
|
564
|
+
return time;
|
|
565
|
+
}
|
|
566
|
+
// relative
|
|
567
|
+
return `${humanizeDuration(Date.now() - time, { language: 'en', largest: 2, round: true })} ago`;
|
|
385
568
|
}
|
|
386
569
|
|
|
387
570
|
/**
|
package/lib/helper.js
CHANGED
|
@@ -26,6 +26,33 @@ class Helper {
|
|
|
26
26
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Normalises any value to a valid ioBroker common.type string.
|
|
31
|
+
* Valid types: "number" | "string" | "boolean" | "array" | "object" | "mixed" | "file"
|
|
32
|
+
*
|
|
33
|
+
* @param {*} value - The raw value whose type should be determined.
|
|
34
|
+
* @param {string} [hint] - An optional type hint (e.g. from metadata.type).
|
|
35
|
+
* @returns {string} A valid ioBroker type string.
|
|
36
|
+
*/
|
|
37
|
+
normalizeType(value, hint) {
|
|
38
|
+
const VALID = new Set(["number", "string", "boolean", "array", "object", "mixed", "file"]);
|
|
39
|
+
if (hint && VALID.has(hint)) {
|
|
40
|
+
return hint;
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(value)) {
|
|
43
|
+
return "array";
|
|
44
|
+
}
|
|
45
|
+
const t = typeof value;
|
|
46
|
+
if (t === "number") {
|
|
47
|
+
return "number";
|
|
48
|
+
}
|
|
49
|
+
if (t === "boolean") {
|
|
50
|
+
return "boolean";
|
|
51
|
+
}
|
|
52
|
+
// strings are stored as "mixed" to allow numeric/bool changes later
|
|
53
|
+
return "mixed";
|
|
54
|
+
}
|
|
55
|
+
|
|
29
56
|
/**
|
|
30
57
|
* Creates a ZWave node device and all its value states in ioBroker.
|
|
31
58
|
*
|
|
@@ -55,9 +82,9 @@ class Helper {
|
|
|
55
82
|
await this.createReadyStatus(nodeId);
|
|
56
83
|
|
|
57
84
|
const valuesOnly = element.values ?? null;
|
|
58
|
-
|
|
85
|
+
const { values: _values, ...elementWithoutValues } = element;
|
|
59
86
|
|
|
60
|
-
await this.parse(`${nodeId}.info`,
|
|
87
|
+
await this.parse(`${nodeId}.info`, elementWithoutValues);
|
|
61
88
|
|
|
62
89
|
if (valuesOnly != null && typeof valuesOnly === "object" && valuesOnly.length > 0) {
|
|
63
90
|
for (const v of valuesOnly) {
|
|
@@ -112,7 +139,8 @@ class Helper {
|
|
|
112
139
|
metadata.value = v.value; // add value for resolution
|
|
113
140
|
const valDp = this.resolveCommandClassValue(metadata) ?? 0;
|
|
114
141
|
|
|
115
|
-
|
|
142
|
+
const rawType = metadata.type === "timeout" ? "number" : metadata.type;
|
|
143
|
+
let typeDp = this.normalizeType(valDp, rawType);
|
|
116
144
|
|
|
117
145
|
if (constant.mixedType.includes(nam_id)) {
|
|
118
146
|
typeDp = "mixed";
|
|
@@ -177,7 +205,7 @@ class Helper {
|
|
|
177
205
|
async parse(path, element, options = { write: false },change = false) {
|
|
178
206
|
let parsePath = utils.deleteLastDot(utils.formatObject(path));
|
|
179
207
|
|
|
180
|
-
if (element === undefined) {
|
|
208
|
+
if (element === undefined || element === null) {
|
|
181
209
|
this.adapter.log.error(`Skip undefined value for ${parsePath}`);
|
|
182
210
|
return;
|
|
183
211
|
}
|
|
@@ -189,8 +217,19 @@ class Helper {
|
|
|
189
217
|
|
|
190
218
|
if (!this.alreadyCreatedObjects[parsePath]) {
|
|
191
219
|
try {
|
|
192
|
-
let common
|
|
193
|
-
if (typeof element === "
|
|
220
|
+
let common;
|
|
221
|
+
if (typeof element === "boolean") {
|
|
222
|
+
common = {
|
|
223
|
+
id: parsePath,
|
|
224
|
+
name: parsePath,
|
|
225
|
+
role: "switch",
|
|
226
|
+
type: "boolean",
|
|
227
|
+
write: options.write,
|
|
228
|
+
read: true,
|
|
229
|
+
def: false,
|
|
230
|
+
};
|
|
231
|
+
} else {
|
|
232
|
+
// string or number
|
|
194
233
|
common = {
|
|
195
234
|
id: parsePath,
|
|
196
235
|
name: parsePath,
|
|
@@ -222,20 +261,19 @@ class Helper {
|
|
|
222
261
|
return;
|
|
223
262
|
}
|
|
224
263
|
|
|
225
|
-
|
|
264
|
+
const channelName = utils.getLastSegment(parsePath);
|
|
226
265
|
|
|
227
266
|
if (!this.alreadyCreatedObjects[parsePath]) {
|
|
228
267
|
try {
|
|
229
268
|
await this.adapter.setObjectNotExistsAsync(parsePath, {
|
|
230
269
|
type: "channel",
|
|
231
270
|
common: {
|
|
232
|
-
name:
|
|
271
|
+
name: channelName || ""
|
|
233
272
|
},
|
|
234
273
|
native: {},
|
|
235
274
|
});
|
|
236
275
|
|
|
237
276
|
this.alreadyCreatedObjects[parsePath] = { };
|
|
238
|
-
delete options.channelName;
|
|
239
277
|
} catch (error) {
|
|
240
278
|
this.adapter.log.error(`parse error ${ parsePath}`);
|
|
241
279
|
this.adapter.log.error(error);
|
|
@@ -300,7 +338,7 @@ class Helper {
|
|
|
300
338
|
|
|
301
339
|
if (!this.alreadyCreatedObjects[fullPath]) {
|
|
302
340
|
const objectName = options.descriptions?.[key] || key;
|
|
303
|
-
let typeDp =
|
|
341
|
+
let typeDp = this.normalizeType(valDP);
|
|
304
342
|
|
|
305
343
|
if (constant.mixedType.includes(key)) {
|
|
306
344
|
typeDp = "mixed";
|
|
@@ -335,10 +373,9 @@ class Helper {
|
|
|
335
373
|
await this.changeState(fullPath, valDP, change);
|
|
336
374
|
|
|
337
375
|
if (valDP !== undefined) {
|
|
338
|
-
if (fullPath.endsWith('ready')
|
|
339
|
-
|
|
340
|
-
if (utils.isNumeric(
|
|
341
|
-
fullPath = fullPath.replace(".status", ".ready");
|
|
376
|
+
if (fullPath.endsWith('ready')) {
|
|
377
|
+
const statusVal = element['status'];
|
|
378
|
+
if (utils.isNumeric(statusVal) && statusVal === 3) {
|
|
342
379
|
await this.changeState(fullPath, false);
|
|
343
380
|
}
|
|
344
381
|
}
|
|
@@ -375,15 +412,11 @@ class Helper {
|
|
|
375
412
|
|
|
376
413
|
for (let i = 0; i < array.length; i++) {
|
|
377
414
|
const arrayElement = array[i];
|
|
378
|
-
// const index = (i + 1).toString().padStart(2, "0");
|
|
379
415
|
|
|
380
416
|
if (typeof arrayElement === "string") {
|
|
381
|
-
|
|
382
|
-
key = arrayElement;
|
|
383
|
-
}
|
|
384
|
-
|
|
417
|
+
const segKey = (key === undefined || key === "") ? arrayElement : key;
|
|
385
418
|
await this.parse(
|
|
386
|
-
`${path}.${
|
|
419
|
+
`${path}.${segKey}`,
|
|
387
420
|
arrayElement,
|
|
388
421
|
options,
|
|
389
422
|
);
|
|
@@ -443,6 +476,10 @@ class Helper {
|
|
|
443
476
|
resolveCommandClassValue(element) {
|
|
444
477
|
const type = element.type;
|
|
445
478
|
|
|
479
|
+
if (!type) {
|
|
480
|
+
return element.value ?? 0;
|
|
481
|
+
}
|
|
482
|
+
|
|
446
483
|
if (type === "any" || type === "color") {
|
|
447
484
|
element.type = "mixed";
|
|
448
485
|
return typeof element.value === "object"
|
|
@@ -487,10 +524,10 @@ class Helper {
|
|
|
487
524
|
}
|
|
488
525
|
|
|
489
526
|
if (type === "number") {
|
|
490
|
-
if (element
|
|
527
|
+
if (element.value != null) {
|
|
491
528
|
return utils.isNumeric(element.value) ? element.value : 0;
|
|
492
529
|
}
|
|
493
|
-
return element.
|
|
530
|
+
return element.min ?? 0;
|
|
494
531
|
}
|
|
495
532
|
|
|
496
533
|
return element.readable === false
|
|
@@ -545,18 +582,22 @@ class Helper {
|
|
|
545
582
|
*/
|
|
546
583
|
async updateDevice(nodeId, element, nameChange = true) {
|
|
547
584
|
const obj = await this.adapter.getObjectAsync(nodeId);
|
|
548
|
-
if (obj) {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
585
|
+
if (!obj) {
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
obj.common = obj.common ?? {};
|
|
590
|
+
|
|
591
|
+
if (nameChange) {
|
|
592
|
+
const newName = element.name || element.productLabel || element.manufacturer || element.newValue;
|
|
593
|
+
if (newName !== undefined && obj.common.name !== newName) {
|
|
594
|
+
obj.common.name = newName;
|
|
595
|
+
await this.adapter.setObjectAsync(nodeId, obj);
|
|
596
|
+
}
|
|
597
|
+
} else {
|
|
598
|
+
const newDesc = element.desc;
|
|
599
|
+
if (newDesc !== undefined) {
|
|
600
|
+
obj.common.desc = newDesc;
|
|
560
601
|
await this.adapter.setObjectAsync(nodeId, obj);
|
|
561
602
|
}
|
|
562
603
|
}
|
|
@@ -571,9 +612,9 @@ class Helper {
|
|
|
571
612
|
*/
|
|
572
613
|
async changeState(path, value, change = false) {
|
|
573
614
|
if (change) {
|
|
574
|
-
this.adapter.
|
|
615
|
+
await this.adapter.setStateAsync(path, value, true);
|
|
575
616
|
} else {
|
|
576
|
-
this.adapter.
|
|
617
|
+
await this.adapter.setStateChangedAsync(path, value, true);
|
|
577
618
|
}
|
|
578
619
|
}
|
|
579
620
|
|
package/lib/messages.js
CHANGED
|
@@ -11,7 +11,7 @@ async function adapterInfo(config, log) {
|
|
|
11
11
|
log.info(`|| zwaveWS Frontend Server: ${config.webUIServer}`);
|
|
12
12
|
log.info(`|| zwaveWS Frontend Port: ${config.webUIPort}`);
|
|
13
13
|
log.info(`|| zwaveWS Connection Type: ${config.connectionType}`);
|
|
14
|
-
if (config.connectionType
|
|
14
|
+
if (config.connectionType === "ws") {
|
|
15
15
|
log.info(`|| zwaveWS Websocket Scheme: ${config.wsScheme}`);
|
|
16
16
|
log.info(`|| zwaveWS Websocket Server: ${config.wsServerIP}`);
|
|
17
17
|
log.info(`|| zwaveWS Websocket Port: ${config.wsServerPort}`);
|
|
@@ -21,11 +21,11 @@ async function adapterInfo(config, log) {
|
|
|
21
21
|
log.info(
|
|
22
22
|
`|| zwaveWS Websocket Dummy MQTT-Server: ${config.dummyMqtt ? "activated" : "deactivated"}`,
|
|
23
23
|
);
|
|
24
|
-
if (config.dummyMqtt
|
|
24
|
+
if (config.dummyMqtt === true) {
|
|
25
25
|
log.info(`|| zwaveWS Dummy MQTT IP-Bind: ${config.mqttServerIPBind}`);
|
|
26
26
|
log.info(`|| zwaveWS Dummy MQTT Port: ${config.mqttServerPort}`);
|
|
27
27
|
}
|
|
28
|
-
} else if (config.connectionType
|
|
28
|
+
} else if (config.connectionType === "exmqtt") {
|
|
29
29
|
log.info(
|
|
30
30
|
`|| zwaveWS Externanl MQTT Server: ${config.externalMqttServerIP}`,
|
|
31
31
|
);
|
|
@@ -35,7 +35,7 @@ async function adapterInfo(config, log) {
|
|
|
35
35
|
log.info(
|
|
36
36
|
`|| zwaveWS Externanl MQTT Credentials: ${config.externalMqttServerCredentials ? "use" : "unused"}`,
|
|
37
37
|
);
|
|
38
|
-
} else if (config.connectionType
|
|
38
|
+
} else if (config.connectionType === "intmqtt") {
|
|
39
39
|
log.info(`|| zwaveWS Internal MQTT IP-Bind: ${config.mqttServerIPBind}`);
|
|
40
40
|
log.info(`|| zwaveWS Internal MQTT Port: ${config.mqttServerPort}`);
|
|
41
41
|
}
|
package/lib/statesController.js
CHANGED
|
@@ -45,9 +45,10 @@ class StatesController {
|
|
|
45
45
|
async subscribeAllWritableExistsStates() {
|
|
46
46
|
const writableStates = {};
|
|
47
47
|
|
|
48
|
+
const ns = `${this.adapter.namespace}.`;
|
|
48
49
|
const res = await this.adapter.getObjectViewAsync("system", "state", {
|
|
49
|
-
startkey:
|
|
50
|
-
endkey:
|
|
50
|
+
startkey: ns,
|
|
51
|
+
endkey: `${ns}\u9999`,
|
|
51
52
|
});
|
|
52
53
|
|
|
53
54
|
for (const row of res.rows) {
|
package/lib/utils.js
CHANGED
|
@@ -41,12 +41,8 @@ function miredKelvinConversion(t) {
|
|
|
41
41
|
* @param {number} decimal - The decimal number to convert.
|
|
42
42
|
* @param {number} padding - The minimum length of the resulting hex string.
|
|
43
43
|
*/
|
|
44
|
-
function decimalToHex(decimal, padding) {
|
|
44
|
+
function decimalToHex(decimal, padding = 2) {
|
|
45
45
|
let hex = Number(decimal).toString(16);
|
|
46
|
-
padding =
|
|
47
|
-
typeof padding === "undefined" || padding === null
|
|
48
|
-
? (padding = 2)
|
|
49
|
-
: padding;
|
|
50
46
|
|
|
51
47
|
while (hex.length < padding) {
|
|
52
48
|
hex = `0${hex}`;
|
|
@@ -145,6 +141,9 @@ function isNumeric(value) {
|
|
|
145
141
|
* @param {string} str - The string to process.
|
|
146
142
|
*/
|
|
147
143
|
function replaceLastDot(str) {
|
|
144
|
+
if (typeof str !== "string") {
|
|
145
|
+
return "";
|
|
146
|
+
}
|
|
148
147
|
const idx = str.lastIndexOf(".");
|
|
149
148
|
return idx >= 0 ? `${str.slice(0, idx)}_${str.slice(idx + 1)}` : str;
|
|
150
149
|
}
|
|
@@ -155,6 +154,9 @@ function replaceLastDot(str) {
|
|
|
155
154
|
* @param {string|undefined} str - The string to process.
|
|
156
155
|
*/
|
|
157
156
|
function deleteLastDot(str) {
|
|
157
|
+
if (typeof str !== "string") {
|
|
158
|
+
return "";
|
|
159
|
+
}
|
|
158
160
|
return str.endsWith(".") ? str.slice(0, -1) : str;
|
|
159
161
|
}
|
|
160
162
|
|
|
@@ -217,8 +219,8 @@ function getStatusText(status) {
|
|
|
217
219
|
function formatNodeId(nodeIdOriginal) {
|
|
218
220
|
let nodeId = nodeIdOriginal;
|
|
219
221
|
|
|
220
|
-
if (
|
|
221
|
-
nodeId =
|
|
222
|
+
if (isNumeric(nodeIdOriginal)) {
|
|
223
|
+
nodeId = padNodeId(`nodeID_${nodeIdOriginal}`);
|
|
222
224
|
}
|
|
223
225
|
return nodeId;
|
|
224
226
|
}
|
|
@@ -28,7 +28,7 @@ class WebsocketController {
|
|
|
28
28
|
try {
|
|
29
29
|
let wsURL = `${this.adapter.config.wsScheme}://${this.adapter.config.wsServerIP}:${this.adapter.config.wsServerPort}/api`;
|
|
30
30
|
|
|
31
|
-
if (this.adapter.config.wsTokenEnabled
|
|
31
|
+
if (this.adapter.config.wsTokenEnabled === true) {
|
|
32
32
|
wsURL += `?token=${this.adapter.config.wsToken}`;
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -104,7 +104,7 @@ class WebsocketController {
|
|
|
104
104
|
/**
|
|
105
105
|
* Schedules an automatic reconnect attempt after the configured restart timeout.
|
|
106
106
|
*/
|
|
107
|
-
|
|
107
|
+
autoRestart() {
|
|
108
108
|
this.adapter.log.warn(`Start try again in ${restartTimeout / 1000} seconds...`);
|
|
109
109
|
autoRestartTimeout = setTimeout(() => {
|
|
110
110
|
this.adapter.startWebsocket();
|
|
@@ -123,7 +123,7 @@ class WebsocketController {
|
|
|
123
123
|
/**
|
|
124
124
|
* Clears all active timers (ping, pingTimeout, autoRestartTimeout).
|
|
125
125
|
*/
|
|
126
|
-
|
|
126
|
+
allTimerClear() {
|
|
127
127
|
clearTimeout(pingTimeout);
|
|
128
128
|
clearTimeout(ping);
|
|
129
129
|
clearTimeout(autoRestartTimeout);
|
package/main.js
CHANGED
|
@@ -65,8 +65,8 @@ class zwavews extends core.Adapter {
|
|
|
65
65
|
// MQTT
|
|
66
66
|
if (["exmqtt", "intmqtt"].includes(this.config.connectionType)) {
|
|
67
67
|
// External MQTT-Server
|
|
68
|
-
if (this.config.connectionType
|
|
69
|
-
if (this.config.externalMqttServerIP
|
|
68
|
+
if (this.config.connectionType === "exmqtt") {
|
|
69
|
+
if (this.config.externalMqttServerIP === "") {
|
|
70
70
|
this.log.warn(
|
|
71
71
|
"Please configure the External MQTT-Server connection!",
|
|
72
72
|
);
|
|
@@ -85,7 +85,7 @@ class zwavews extends core.Adapter {
|
|
|
85
85
|
};
|
|
86
86
|
|
|
87
87
|
// Set external mqtt credentials
|
|
88
|
-
if (this.config.externalMqttServerCredentials
|
|
88
|
+
if (this.config.externalMqttServerCredentials === true) {
|
|
89
89
|
mqttClientOptions.username = this.config.externalMqttServerUsername;
|
|
90
90
|
mqttClientOptions.password = this.config.externalMqttServerPassword;
|
|
91
91
|
}
|
|
@@ -112,25 +112,39 @@ class zwavews extends core.Adapter {
|
|
|
112
112
|
|
|
113
113
|
// MQTT Client
|
|
114
114
|
mqttClient.on("connect", () => {
|
|
115
|
-
this.log.info(`Connect to zwavews over ${this.config.connectionType
|
|
115
|
+
this.log.info(`Connect to zwavews over ${this.config.connectionType === "exmqtt" ? "external mqtt" : "internal mqtt"} connection.`);
|
|
116
116
|
this.setStateChanged("info.connection", true, true);
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
-
mqttClient.subscribe(`${this.config.baseTopic}
|
|
119
|
+
mqttClient.subscribe(`${this.config.baseTopic}/#`, (err) => {
|
|
120
|
+
if (err) {
|
|
121
|
+
this.log.error(`<zwavews> MQTT subscribe error: ${err.message}`);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
120
124
|
|
|
121
125
|
mqttClient.on("message", (topic, payload) => {
|
|
122
|
-
const
|
|
126
|
+
const rawPayload = payload.toString();
|
|
127
|
+
let parsedPayload;
|
|
128
|
+
try {
|
|
129
|
+
parsedPayload = rawPayload === "" ? null : JSON.parse(rawPayload);
|
|
130
|
+
} catch {
|
|
131
|
+
parsedPayload = rawPayload;
|
|
132
|
+
}
|
|
133
|
+
const newMessage = JSON.stringify({
|
|
134
|
+
payload: parsedPayload,
|
|
135
|
+
topic: topic.slice(topic.indexOf("/") + 1),
|
|
136
|
+
});
|
|
123
137
|
this.messageParse(newMessage);
|
|
124
138
|
});
|
|
125
|
-
} else if (this.config.connectionType
|
|
139
|
+
} else if (this.config.connectionType === 'ws') {
|
|
126
140
|
// Websocket
|
|
127
|
-
if (this.config.wsServerIP
|
|
141
|
+
if (this.config.wsServerIP === '') {
|
|
128
142
|
this.log.warn('Please configure the Websoket connection!');
|
|
129
143
|
return;
|
|
130
144
|
}
|
|
131
145
|
|
|
132
146
|
// Dummy MQTT-Server
|
|
133
|
-
if (this.config.dummyMqtt
|
|
147
|
+
if (this.config.dummyMqtt === true) {
|
|
134
148
|
mqttServerController = new MqttServerController(this);
|
|
135
149
|
await mqttServerController.createDummyMQTTServer();
|
|
136
150
|
this.setStateChanged("info.connection", true, true);
|
|
@@ -145,27 +159,30 @@ class zwavews extends core.Adapter {
|
|
|
145
159
|
websocketController = new WebsocketController(this);
|
|
146
160
|
const wsClient = websocketController.initWsClient();
|
|
147
161
|
|
|
148
|
-
if (wsClient) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
startListening = true;
|
|
152
|
-
websocketController.send(JSON.stringify({command: "start_listening"}));
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
wsClient.on('message', (message) => {
|
|
156
|
-
this.messageParse(message);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
wsClient.on('close', async () => {
|
|
160
|
-
this.setStateChanged('info.connection', false, true);
|
|
161
|
-
await statesController.setAllAvailableToFalse();
|
|
162
|
-
startListening = false;
|
|
163
|
-
allNodesCreated = false;
|
|
164
|
-
deviceCache = [];
|
|
165
|
-
this.nodeCache = [];
|
|
166
|
-
this.log.info('Websocket connection closed. Attempting to reconnect...');
|
|
167
|
-
});
|
|
162
|
+
if (!wsClient) {
|
|
163
|
+
this.log.error('<zwavews> initWsClient returned null — websocket not started.');
|
|
164
|
+
return;
|
|
168
165
|
}
|
|
166
|
+
|
|
167
|
+
wsClient.on('open', () => {
|
|
168
|
+
this.log.info('Connect to zwave-js-ui over websocket connection.');
|
|
169
|
+
startListening = true;
|
|
170
|
+
websocketController.send(JSON.stringify({command: "start_listening"}));
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
wsClient.on('message', (message) => {
|
|
174
|
+
this.messageParse(message);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
wsClient.on('close', async () => {
|
|
178
|
+
this.setStateChanged('info.connection', false, true);
|
|
179
|
+
await statesController.setAllAvailableToFalse();
|
|
180
|
+
startListening = false;
|
|
181
|
+
allNodesCreated = false;
|
|
182
|
+
deviceCache = {};
|
|
183
|
+
this.nodeCache = {};
|
|
184
|
+
this.log.info('Websocket connection closed. Attempting to reconnect...');
|
|
185
|
+
});
|
|
169
186
|
}
|
|
170
187
|
|
|
171
188
|
async messageParse(message) {
|
|
@@ -203,14 +220,19 @@ class zwavews extends core.Adapter {
|
|
|
203
220
|
break;
|
|
204
221
|
}
|
|
205
222
|
|
|
206
|
-
|
|
223
|
+
if (!messageObj.result?.state || !Array.isArray(messageObj.result.state.nodes)) {
|
|
224
|
+
this.log.warn('<zwavews> Invalid result.state structure received, skipping.');
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
driver = messageObj.result.state.driver;
|
|
207
229
|
controller = messageObj.result.state.controller;
|
|
208
|
-
allNodes
|
|
230
|
+
allNodes = messageObj.result.state.nodes;
|
|
209
231
|
|
|
210
232
|
for (const nodeData of allNodes) {
|
|
211
233
|
const nodeId = utils.formatNodeId(nodeData.nodeId);
|
|
212
234
|
|
|
213
|
-
if (debugDevicesState && debugDevicesState.val.includes(nodeId)) {
|
|
235
|
+
if (debugDevicesState && debugDevicesState.val && String(debugDevicesState.val).includes(nodeId)) {
|
|
214
236
|
this.log.warn(`--->>> fromZ2W_RAW2-> ${JSON.stringify(nodeData)}` );
|
|
215
237
|
}
|
|
216
238
|
|
|
@@ -244,7 +266,7 @@ class zwavews extends core.Adapter {
|
|
|
244
266
|
const nodeArg = eventTyp.args;
|
|
245
267
|
const nodeId = utils.formatNodeId(eventTyp.nodeId);
|
|
246
268
|
|
|
247
|
-
if (debugDevicesState && debugDevicesState.val.includes(nodeId)) {
|
|
269
|
+
if (debugDevicesState && debugDevicesState.val && String(debugDevicesState.val).includes(nodeId)) {
|
|
248
270
|
this.log.warn(`--->>> fromZ2W_RAW2-> ${JSON.stringify(eventTyp)}` );
|
|
249
271
|
}
|
|
250
272
|
|
|
@@ -273,7 +295,7 @@ class zwavews extends core.Adapter {
|
|
|
273
295
|
parsePath = `${nodeId}.info.${nodeArg.property}`;
|
|
274
296
|
break;
|
|
275
297
|
case 'location':
|
|
276
|
-
|
|
298
|
+
// intentionally ignored
|
|
277
299
|
break;
|
|
278
300
|
default:
|
|
279
301
|
parsePath = `${nodeId}.info.${nodeArg.property}`;
|
|
@@ -383,40 +405,49 @@ class zwavews extends core.Adapter {
|
|
|
383
405
|
}
|
|
384
406
|
|
|
385
407
|
async onUnload(callback) {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
if (
|
|
408
|
+
try {
|
|
409
|
+
// Close MQTT connections
|
|
410
|
+
if (["exmqtt", "intmqtt"].includes(this.config.connectionType)) {
|
|
411
|
+
if (mqttClient && !mqttClient.closed) {
|
|
412
|
+
try {
|
|
413
|
+
mqttClient.end();
|
|
414
|
+
} catch (e) {
|
|
415
|
+
this.log.error(e);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
// Internal or Dummy MQTT-Server
|
|
420
|
+
if (this.config.connectionType === "intmqtt" || this.config.dummyMqtt === true) {
|
|
389
421
|
try {
|
|
390
|
-
if (
|
|
391
|
-
|
|
422
|
+
if (mqttServerController) {
|
|
423
|
+
mqttServerController.closeServer();
|
|
392
424
|
}
|
|
393
425
|
} catch (e) {
|
|
394
426
|
this.log.error(e);
|
|
395
427
|
}
|
|
396
428
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
429
|
+
// WebSocket cleanup
|
|
430
|
+
if (websocketController) {
|
|
431
|
+
try {
|
|
432
|
+
await websocketController.allTimerClear();
|
|
433
|
+
websocketController.closeConnection();
|
|
434
|
+
} catch (e) {
|
|
435
|
+
this.log.error(e);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
// Set all device available states to false
|
|
400
439
|
try {
|
|
401
|
-
if (
|
|
402
|
-
|
|
440
|
+
if (statesController) {
|
|
441
|
+
await statesController.setAllAvailableToFalse();
|
|
403
442
|
}
|
|
404
443
|
} catch (e) {
|
|
405
444
|
this.log.error(e);
|
|
406
445
|
}
|
|
407
|
-
}
|
|
408
|
-
// Set all device available states of false
|
|
409
|
-
try {
|
|
410
|
-
if (statesController) {
|
|
411
|
-
await statesController.setAllAvailableToFalse();
|
|
412
|
-
}
|
|
413
|
-
} catch (e) {
|
|
414
|
-
this.log.error(e);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
this.setStateChanged("info.connection", false, true);
|
|
418
446
|
|
|
419
|
-
|
|
447
|
+
this.setStateChanged("info.connection", false, true);
|
|
448
|
+
} finally {
|
|
449
|
+
callback();
|
|
450
|
+
}
|
|
420
451
|
}
|
|
421
452
|
|
|
422
453
|
async onStateChange(id, state) {
|
|
@@ -424,21 +455,24 @@ class zwavews extends core.Adapter {
|
|
|
424
455
|
return;
|
|
425
456
|
}
|
|
426
457
|
|
|
427
|
-
if (state && state.ack
|
|
458
|
+
if (state && state.ack === false) {
|
|
428
459
|
if (id.endsWith("info.debugId")) {
|
|
429
460
|
this.setStateChanged(id, state.val, true);
|
|
430
461
|
return;
|
|
431
462
|
}
|
|
432
463
|
|
|
433
|
-
let message;
|
|
434
464
|
const obj = await this.getObjectAsync(id);
|
|
435
465
|
if (obj) {
|
|
436
|
-
const nativeObj= obj.native || {};
|
|
466
|
+
const nativeObj = obj.native || {};
|
|
437
467
|
|
|
438
468
|
const m = id.match(/nodeID_0*(\d+)/i);
|
|
439
|
-
|
|
469
|
+
if (!m) {
|
|
470
|
+
this.log.warn(`<zwavews> Could not extract nodeId from state id: ${id}`);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const nodeId = Number(m[1]);
|
|
440
474
|
|
|
441
|
-
message = {
|
|
475
|
+
const message = {
|
|
442
476
|
messageId: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
443
477
|
command: "node.set_value",
|
|
444
478
|
nodeId: nodeId,
|
|
@@ -448,13 +482,17 @@ class zwavews extends core.Adapter {
|
|
|
448
482
|
|
|
449
483
|
const sendMessageAllowed = await this.getStateAsync("info.sendMessageAllowed");
|
|
450
484
|
|
|
451
|
-
if (sendMessageAllowed.val) {
|
|
452
|
-
|
|
485
|
+
if (sendMessageAllowed && sendMessageAllowed.val === true) {
|
|
486
|
+
if (websocketController) {
|
|
487
|
+
websocketController.send(JSON.stringify(message));
|
|
488
|
+
} else {
|
|
489
|
+
this.log.warn('<zwavews> websocketController not initialised, cannot send message.');
|
|
490
|
+
}
|
|
453
491
|
}
|
|
454
492
|
|
|
455
|
-
this.setStateChanged('info.debugmessages', JSON.stringify(message), true);
|
|
456
|
-
this.log.debug(`<zwavews> message onStateChange ${message}`);
|
|
457
|
-
}
|
|
493
|
+
this.setStateChanged('info.debugmessages', JSON.stringify(message), true);
|
|
494
|
+
this.log.debug(`<zwavews> message onStateChange ${JSON.stringify(message)}`);
|
|
495
|
+
}
|
|
458
496
|
}
|
|
459
497
|
}
|
|
460
498
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.zwavews",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "zwavews adapter for ioBroker",
|
|
5
5
|
"author": {
|
|
6
|
-
"name": "
|
|
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": "^3.0.
|
|
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.
|
|
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.
|
|
37
|
+
"ws": "^8.20.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@alcalzone/release-script": "^5.1.1",
|
|
41
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": "^
|
|
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
48
|
"@types/node": "^25.5.0",
|
|
49
49
|
"@types/node-schedule": "^2.1.8",
|
|
50
|
-
"typescript": "~
|
|
50
|
+
"typescript": "~6.0.2"
|
|
51
51
|
},
|
|
52
52
|
"main": "main.js",
|
|
53
53
|
"files": [
|