iobroker.zwavews 0.1.4 → 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 +3 -0
- package/io-package.json +14 -14
- package/lib/devicemgmt.js +199 -12
- package/lib/helper.js +76 -35
- 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 +106 -66
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,9 @@ 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
|
+
|
|
38
41
|
### 0.1.4 (2026-04-16)
|
|
39
42
|
* (arteck) Dependencies have been updated
|
|
40
43
|
* (arteck) add vscode folder
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
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
|
+
},
|
|
6
19
|
"0.1.4": {
|
|
7
20
|
"en": "Dependencies have been updated\nadd vscode folder",
|
|
8
21
|
"de": "Abhängigkeiten wurden aktualisiert\nvscode ordner hinzufügen",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
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",
|
|
81
94
|
"uk": "додайте інформацію.sendMessageВсього об'єкту, щоб дозволити надсилати повідомлення на zwave-ui-js\nдодати нову прапорець, щоб встановити інформацію.sendMessage Допускається відразу після запуску адаптера",
|
|
82
95
|
"zh-cn": "添加信息. sendMessage Allowed 对象允许将消息发送到 zwave- ui- js\n添加新复选框以设置信息. sendMessage 启动适配器后立即允许"
|
|
83
|
-
},
|
|
84
|
-
"0.0.17": {
|
|
85
|
-
"en": "fix adapter start\nDependencies have been updated",
|
|
86
|
-
"de": "befestigungsadapter start\nAbhängigkeiten wurden aktualisiert",
|
|
87
|
-
"ru": "запуск адаптера\nЗависимости были обновлены",
|
|
88
|
-
"pt": "corrigir o início do adaptador\nAs dependências foram atualizadas",
|
|
89
|
-
"nl": "fix adapter start\nAfhankelijkheden zijn bijgewerkt",
|
|
90
|
-
"fr": "fixer le démarrage de l'adaptateur\nLes dépendances ont été actualisées",
|
|
91
|
-
"it": "avvio dell'adattatore\nLe dipendenze sono state aggiornate",
|
|
92
|
-
"es": "adaptador de fijación\nSe han actualizado las dependencias",
|
|
93
|
-
"pl": "uruchomić adapter\nZaktualizowano zależności",
|
|
94
|
-
"uk": "запуск адаптера\nЗалежність було оновлено",
|
|
95
|
-
"zh-cn": "固定适配器启动\n依赖关系已更新"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|
package/lib/devicemgmt.js
CHANGED
|
@@ -17,17 +17,26 @@ 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
|
|
34
|
+
const cacheEntry = this.adapter.nodeCache[nodeId];
|
|
35
|
+
if (!cacheEntry) {
|
|
36
|
+
this.adapter.log.warn(`listDevices: nodeCache miss for ${nodeId}, skipping.`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const device = cacheEntry.nodeData;
|
|
31
40
|
|
|
32
41
|
status.connection = device.ready ? 'connected' : 'disconnected';
|
|
33
42
|
|
|
@@ -58,12 +67,53 @@ class dmZwave extends dmUtils.DeviceManagement {
|
|
|
58
67
|
devStatus = 'unknown';
|
|
59
68
|
}
|
|
60
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
|
+
|
|
61
111
|
const res = {
|
|
62
112
|
id: nodeId,
|
|
63
113
|
name: device.name || device.label,
|
|
64
114
|
icon: devStatus,
|
|
65
|
-
manufacturer: device.deviceConfig
|
|
66
|
-
model: `${device.deviceConfig
|
|
115
|
+
manufacturer: device.deviceConfig?.manufacturer ?? '',
|
|
116
|
+
model: `${device.deviceConfig?.label ?? ''} ${device.deviceConfig?.description ?? ''}`.trim(),
|
|
67
117
|
status: status,
|
|
68
118
|
hasDetails: true,
|
|
69
119
|
actions: [
|
|
@@ -76,17 +126,122 @@ class dmZwave extends dmUtils.DeviceManagement {
|
|
|
76
126
|
],
|
|
77
127
|
};
|
|
78
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))];
|
|
79
160
|
|
|
80
|
-
|
|
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';
|
|
169
|
+
|
|
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);
|
|
81
240
|
}
|
|
82
241
|
|
|
83
242
|
// nach id sortieren (z.B. nodeID_2 vor nodeID_10)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
sensitivity: 'base',
|
|
87
|
-
}));
|
|
88
|
-
|
|
89
|
-
return arrDevices;
|
|
243
|
+
// Note: sorting is informational only; context already sent devices
|
|
244
|
+
context.complete();
|
|
90
245
|
}
|
|
91
246
|
|
|
92
247
|
/**
|
|
@@ -267,6 +422,10 @@ class dmZwave extends dmUtils.DeviceManagement {
|
|
|
267
422
|
devStatus = 'unknown';
|
|
268
423
|
}
|
|
269
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
|
+
|
|
270
429
|
return {
|
|
271
430
|
id: String(device.nodeId),
|
|
272
431
|
schema: {
|
|
@@ -336,6 +495,34 @@ class dmZwave extends dmUtils.DeviceManagement {
|
|
|
336
495
|
label: 'Max Baud Rate',
|
|
337
496
|
readOnly: true,
|
|
338
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
|
+
} : {}),
|
|
339
526
|
},
|
|
340
527
|
},
|
|
341
528
|
_tab_Details: {
|
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";
|
|
@@ -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,38 +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
|
-
|
|
422
|
+
if (mqttServerController) {
|
|
423
|
+
mqttServerController.closeServer();
|
|
424
|
+
}
|
|
391
425
|
} catch (e) {
|
|
392
|
-
|
|
426
|
+
this.log.error(e);
|
|
393
427
|
}
|
|
394
428
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
|
398
439
|
try {
|
|
399
|
-
if (
|
|
400
|
-
|
|
440
|
+
if (statesController) {
|
|
441
|
+
await statesController.setAllAvailableToFalse();
|
|
401
442
|
}
|
|
402
443
|
} catch (e) {
|
|
403
444
|
this.log.error(e);
|
|
404
445
|
}
|
|
405
|
-
}
|
|
406
|
-
// Set all device available states of false
|
|
407
|
-
try {
|
|
408
|
-
if (statesController) {
|
|
409
|
-
await statesController.setAllAvailableToFalse();
|
|
410
|
-
}
|
|
411
|
-
} catch (e) {
|
|
412
|
-
this.log.error(e);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
this.setStateChanged("info.connection", false, true);
|
|
416
446
|
|
|
417
|
-
|
|
447
|
+
this.setStateChanged("info.connection", false, true);
|
|
448
|
+
} finally {
|
|
449
|
+
callback();
|
|
450
|
+
}
|
|
418
451
|
}
|
|
419
452
|
|
|
420
453
|
async onStateChange(id, state) {
|
|
@@ -422,21 +455,24 @@ class zwavews extends core.Adapter {
|
|
|
422
455
|
return;
|
|
423
456
|
}
|
|
424
457
|
|
|
425
|
-
if (state && state.ack
|
|
458
|
+
if (state && state.ack === false) {
|
|
426
459
|
if (id.endsWith("info.debugId")) {
|
|
427
460
|
this.setStateChanged(id, state.val, true);
|
|
428
461
|
return;
|
|
429
462
|
}
|
|
430
463
|
|
|
431
|
-
let message;
|
|
432
464
|
const obj = await this.getObjectAsync(id);
|
|
433
465
|
if (obj) {
|
|
434
|
-
const nativeObj= obj.native || {};
|
|
466
|
+
const nativeObj = obj.native || {};
|
|
435
467
|
|
|
436
468
|
const m = id.match(/nodeID_0*(\d+)/i);
|
|
437
|
-
|
|
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]);
|
|
438
474
|
|
|
439
|
-
message = {
|
|
475
|
+
const message = {
|
|
440
476
|
messageId: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
441
477
|
command: "node.set_value",
|
|
442
478
|
nodeId: nodeId,
|
|
@@ -446,13 +482,17 @@ class zwavews extends core.Adapter {
|
|
|
446
482
|
|
|
447
483
|
const sendMessageAllowed = await this.getStateAsync("info.sendMessageAllowed");
|
|
448
484
|
|
|
449
|
-
if (sendMessageAllowed.val) {
|
|
450
|
-
|
|
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
|
+
}
|
|
451
491
|
}
|
|
452
492
|
|
|
453
|
-
this.setStateChanged('info.debugmessages', JSON.stringify(message), true);
|
|
454
|
-
this.log.debug(`<zwavews> message onStateChange ${message}`);
|
|
455
|
-
}
|
|
493
|
+
this.setStateChanged('info.debugmessages', JSON.stringify(message), true);
|
|
494
|
+
this.log.debug(`<zwavews> message onStateChange ${JSON.stringify(message)}`);
|
|
495
|
+
}
|
|
456
496
|
}
|
|
457
497
|
}
|
|
458
498
|
}
|