iobroker.zwavews 0.1.4 → 0.1.6
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 +13 -0
- package/io-package.json +27 -27
- package/lib/devicemgmt.js +199 -12
- package/lib/helper.js +78 -37
- package/lib/messages.js +4 -4
- package/lib/mqttServerController.js +3 -3
- package/lib/statesController.js +3 -2
- package/lib/utils.js +9 -7
- package/lib/websocketController.js +53 -45
- package/main.js +439 -416
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,13 @@
|
|
|
17
17
|
|
|
18
18
|
The `zwavews` adapter connects a [`zwave-js-ui`](https://zwave-js.github.io/zwave-js-ui/#/) to ioBroker and creates corresponding data points for devices, values, and statuses. This allows Z-Wave devices to be conveniently used in visualizations, logic, and automations.
|
|
19
19
|
|
|
20
|
+
### Features
|
|
21
|
+
* **Real-time communication**: Instantly receives updates of device values and statuses via WebSocket or MQTT.
|
|
22
|
+
* **Auto-Discovery**: Automatically creates and updates the device and state structure in ioBroker from the `zwave-js-ui` nodes.
|
|
23
|
+
* **Device Management**: View battery levels, connection status, and detailed device metrics right from the ioBroker interface.
|
|
24
|
+
* **Firmware Updates**: Observe firmware update progress directly via the adapter's logs and states.
|
|
25
|
+
* **State Control**: Send commands and update values natively through the ioBroker object tree.
|
|
26
|
+
* **Support for multiple protocols**: You can connect to `zwave-js-ui` using WebSocket, External MQTT, or an Internal Dummy MQTT server.
|
|
20
27
|
|
|
21
28
|
## Adapter Documentation
|
|
22
29
|
|
|
@@ -35,6 +42,12 @@ Activate WS Server Settings in `zwave-js-ui` we use the Home Assistant Settings
|
|
|
35
42
|
|
|
36
43
|
|
|
37
44
|
## Changelog
|
|
45
|
+
### 0.1.6 (2026-04-23)
|
|
46
|
+
* (arteck) add test
|
|
47
|
+
|
|
48
|
+
### 0.1.5 (2026-04-21)
|
|
49
|
+
* (arteck) upd devicemanager
|
|
50
|
+
|
|
38
51
|
### 0.1.4 (2026-04-16)
|
|
39
52
|
* (arteck) Dependencies have been updated
|
|
40
53
|
* (arteck) add vscode folder
|
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.6",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.1.6": {
|
|
7
|
+
"en": "add test",
|
|
8
|
+
"de": "test hinzufügen",
|
|
9
|
+
"ru": "добавить тест",
|
|
10
|
+
"pt": "adicionar teste",
|
|
11
|
+
"nl": "test toevoegen",
|
|
12
|
+
"fr": "ajouter un essai",
|
|
13
|
+
"it": "aggiungere test",
|
|
14
|
+
"es": "agregar la prueba",
|
|
15
|
+
"pl": "dodać test",
|
|
16
|
+
"uk": "додати тест",
|
|
17
|
+
"zh-cn": "添加测试"
|
|
18
|
+
},
|
|
19
|
+
"0.1.5": {
|
|
20
|
+
"en": "upd devicemanager",
|
|
21
|
+
"de": "gerätemanager und -manager",
|
|
22
|
+
"ru": "улучшенный devicemanager",
|
|
23
|
+
"pt": "gerenciador de dispositivos upd",
|
|
24
|
+
"nl": "upd apparaatbeheerder",
|
|
25
|
+
"fr": "gestionnaire d'appareil",
|
|
26
|
+
"it": "upd devicemanager",
|
|
27
|
+
"es": "upd devicemanager",
|
|
28
|
+
"pl": "upd devicemaniger",
|
|
29
|
+
"uk": "напляскване",
|
|
30
|
+
"zh-cn": "上调设备管理器"
|
|
31
|
+
},
|
|
6
32
|
"0.1.4": {
|
|
7
33
|
"en": "Dependencies have been updated\nadd vscode folder",
|
|
8
34
|
"de": "Abhängigkeiten wurden aktualisiert\nvscode ordner hinzufügen",
|
|
@@ -67,32 +93,6 @@
|
|
|
67
93
|
"pl": "ZMIANA BREAKING - nazwa dp jest teraz z podkreśleniem\ndodaj urządzenie Kierownik\nfix dp 's z przestrzenią\nfix dp 's ze specjalnymi znakami",
|
|
68
94
|
"uk": "BREAKING CHANGE - ім'я dp тепер з онлайн\nдодати пристрій Менеджер\nзакріпити dp's з космосом\nфіксувати dp з особливими chars",
|
|
69
95
|
"zh-cn": "断裂变换 - dp 名称现在是下划线\n添加设备 经理\n修补 dp 的空格\n修复 dp 与特殊字符"
|
|
70
|
-
},
|
|
71
|
-
"0.0.18": {
|
|
72
|
-
"en": "add info.sendMessageAllowed object to allow sending the message to zwave-ui-js\nadd new checkbox to set info.sendMessageAllowed immediately after starting the adapter",
|
|
73
|
-
"de": "add info.sendMessageAllowed object to let send the message to zwave-ui-js\nneue Checkbox hinzufügen, um info.sendMessage einzustellen Sofort nach dem Start des Adapters zugelassen",
|
|
74
|
-
"ru": "добавить info.sendMessageРазрешенный объект для отправки сообщения на zwave-ui-js\nдобавить новый флажок для установки info.sendMessage Допускается сразу после запуска адаптера",
|
|
75
|
-
"pt": "add info.sendMessagePermitido objeto para permitir o envio da mensagem para zwave-ui-js\nadicionar nova caixa de seleção para definir info.sendMessage Permitido imediatamente após iniciar o adaptador",
|
|
76
|
-
"nl": "info.sendMessageToegewezen object om het bericht naar zwave-ui-js te versturen\nnieuwe selectievakje toevoegen om info.sendMessage in te stellen Onmiddellijk na het starten van de adapter toegestaan",
|
|
77
|
-
"fr": "ajouter info.sendMessageObjet autorisé pour permettre l'envoi du message à zwave-ui-js\najouter une nouvelle case à cocher pour définir info.sendMessage Autorisé immédiatement après le démarrage de l'adaptateur",
|
|
78
|
-
"it": "aggiungere info.sendMessageAllowed oggetto per consentire l'invio del messaggio a zwave-ui-js\naggiungere nuova casella di controllo per impostare info.sendMessage Consentito subito dopo l'avvio dell'adattatore",
|
|
79
|
-
"es": "añadir info.sendMessagePropósito permitido enviar el mensaje a zwave-ui-js\nañadir nueva casilla para configurar información.sendMessage Permitido inmediatamente después de iniciar el adaptador",
|
|
80
|
-
"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
|
-
"uk": "додайте інформацію.sendMessageВсього об'єкту, щоб дозволити надсилати повідомлення на zwave-ui-js\nдодати нову прапорець, щоб встановити інформацію.sendMessage Допускається відразу після запуску адаптера",
|
|
82
|
-
"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);
|
|
@@ -278,8 +316,8 @@ class Helper {
|
|
|
278
316
|
|
|
279
317
|
if (isObj) {
|
|
280
318
|
if (Object.keys(valDP).length > 0) {
|
|
281
|
-
options
|
|
282
|
-
await this.parse(fullPath, valDP, options);
|
|
319
|
+
// FIX: options-Objekt nicht mutieren – Spread-Kopie verwenden
|
|
320
|
+
await this.parse(fullPath, valDP, { ...options, write: false });
|
|
283
321
|
}
|
|
284
322
|
continue;
|
|
285
323
|
}
|
|
@@ -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
|
}
|
|
@@ -33,7 +33,7 @@ class MqttServerController {
|
|
|
33
33
|
this.adapter.config.mqttServerIPBind,
|
|
34
34
|
() => {
|
|
35
35
|
this.adapter.log.info(
|
|
36
|
-
`
|
|
36
|
+
`Starting MQTT-Server on IP ${this.adapter.config.mqttServerIPBind} and Port ${this.adapter.config.mqttServerPort}`,
|
|
37
37
|
);
|
|
38
38
|
},
|
|
39
39
|
);
|
|
@@ -54,7 +54,7 @@ class MqttServerController {
|
|
|
54
54
|
this.adapter.config.mqttServerIPBind,
|
|
55
55
|
() => {
|
|
56
56
|
this.adapter.log.info(
|
|
57
|
-
`
|
|
57
|
+
`Starting DummyMQTT-Server on IP ${this.adapter.config.mqttServerIPBind} and Port ${this.adapter.config.mqttServerPort}`,
|
|
58
58
|
);
|
|
59
59
|
},
|
|
60
60
|
);
|
|
@@ -67,7 +67,7 @@ class MqttServerController {
|
|
|
67
67
|
*
|
|
68
68
|
*/
|
|
69
69
|
closeServer() {
|
|
70
|
-
if (mqttServer &&
|
|
70
|
+
if (mqttServer && mqttServer.listening) {
|
|
71
71
|
mqttServer.close();
|
|
72
72
|
}
|
|
73
73
|
}
|
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) {
|