iobroker.zwavews 0.1.5 → 0.2.0
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/helper.js +3 -3
- package/lib/mqttServerController.js +2 -2
- package/lib/statesController.js +1 -1
- package/lib/websocketController.js +50 -42
- package/main.js +438 -455
- package/package.json +3 -3
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.2.0 (2026-04-26)
|
|
46
|
+
* (arteck) del deprectated setStateAsync
|
|
47
|
+
|
|
48
|
+
### 0.1.6 (2026-04-23)
|
|
49
|
+
* (arteck) add test
|
|
50
|
+
|
|
38
51
|
### 0.1.5 (2026-04-21)
|
|
39
52
|
* (arteck) upd devicemanager
|
|
40
53
|
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "zwavews",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.2.0": {
|
|
7
|
+
"en": "del deprectated setStateAsync",
|
|
8
|
+
"de": "del deprected setStateAsync",
|
|
9
|
+
"ru": "обсуждение StateAsync",
|
|
10
|
+
"pt": "del deprectated setStateAsync",
|
|
11
|
+
"nl": "del deprectated setStateAsync",
|
|
12
|
+
"fr": "set dépréciéStateAsync",
|
|
13
|
+
"it": "del set deprettatoStateAsync",
|
|
14
|
+
"es": "del deprectated setStateAsync",
|
|
15
|
+
"pl": "del deprectated setStateAsync",
|
|
16
|
+
"uk": "deprectated setStateAsync",
|
|
17
|
+
"zh-cn": "已贬值的设置"
|
|
18
|
+
},
|
|
19
|
+
"0.1.6": {
|
|
20
|
+
"en": "add test",
|
|
21
|
+
"de": "test hinzufügen",
|
|
22
|
+
"ru": "добавить тест",
|
|
23
|
+
"pt": "adicionar teste",
|
|
24
|
+
"nl": "test toevoegen",
|
|
25
|
+
"fr": "ajouter un essai",
|
|
26
|
+
"it": "aggiungere test",
|
|
27
|
+
"es": "agregar la prueba",
|
|
28
|
+
"pl": "dodać test",
|
|
29
|
+
"uk": "додати тест",
|
|
30
|
+
"zh-cn": "添加测试"
|
|
31
|
+
},
|
|
6
32
|
"0.1.5": {
|
|
7
33
|
"en": "upd devicemanager",
|
|
8
34
|
"de": "gerätemanager und -manager",
|
|
@@ -67,32 +93,6 @@
|
|
|
67
93
|
"pl": "dodaj informacje o debugowaniu",
|
|
68
94
|
"uk": "додати інформацію debug",
|
|
69
95
|
"zh-cn": "添加调试信息"
|
|
70
|
-
},
|
|
71
|
-
"0.1.0": {
|
|
72
|
-
"en": "BREAKING CHANGE - dp name is now with underline\nadd deviceManager\nfix dp's with a space\nfix dp's with special chars",
|
|
73
|
-
"de": "BREAKING CHANGE - dp name ist jetzt mit unterstreichen\nadd-Gerät Manager\ndp's mit einem raum reparieren\nfix dp's with special chars",
|
|
74
|
-
"ru": "BREAKING CHANGE - название dp теперь с подчеркиванием\nдобавить устройство Менеджер\nустановить dp в пространстве\nобсуждение dp's with special chars",
|
|
75
|
-
"pt": "MUDANÇA DE INÍCIO - nome dp está agora com sublinhado\nadicionar dispositivo Gestor\ncorrigir dp's com um espaço\ncorrigir dp's com caracteres especiais",
|
|
76
|
-
"nl": "BREAKING VERANDERING - dp naam is nu met benadrukken\napparaat toevoegen Beheerder\nfix dp's met een spatie\nfix dp's met speciale tekens",
|
|
77
|
-
"fr": "CHANGEMENT DE BREAKING - DP nom est maintenant avec le soulignement\najouter un périphérique Gestionnaire\nréparer dp's avec un espace\nfixer dp's avec des caractères spéciaux",
|
|
78
|
-
"it": "BREAKING CHANGE - il nome dp è ora in linea\naggiungere dispositivo Manager\nfix dp's con uno spazio\nsistemare dp's con beneficenza speciali",
|
|
79
|
-
"es": "CAMBIO DE BREAKING - nombre dp ahora es con subrayado\nañadir dispositivo Manager\narreglar dp con un espacio\narreglar dp con chars especiales",
|
|
80
|
-
"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",
|
|
81
|
-
"uk": "BREAKING CHANGE - ім'я dp тепер з онлайн\nдодати пристрій Менеджер\nзакріпити dp's з космосом\nфіксувати dp з особливими chars",
|
|
82
|
-
"zh-cn": "断裂变换 - dp 名称现在是下划线\n添加设备 经理\n修补 dp 的空格\n修复 dp 与特殊字符"
|
|
83
|
-
},
|
|
84
|
-
"0.0.18": {
|
|
85
|
-
"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",
|
|
86
|
-
"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",
|
|
87
|
-
"ru": "добавить info.sendMessageРазрешенный объект для отправки сообщения на zwave-ui-js\nдобавить новый флажок для установки info.sendMessage Допускается сразу после запуска адаптера",
|
|
88
|
-
"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",
|
|
89
|
-
"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",
|
|
90
|
-
"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",
|
|
91
|
-
"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",
|
|
92
|
-
"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",
|
|
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",
|
|
94
|
-
"uk": "додайте інформацію.sendMessageВсього об'єкту, щоб дозволити надсилати повідомлення на zwave-ui-js\nдодати нову прапорець, щоб встановити інформацію.sendMessage Допускається відразу після запуску адаптера",
|
|
95
|
-
"zh-cn": "添加信息. sendMessage Allowed 对象允许将消息发送到 zwave- ui- js\n添加新复选框以设置信息. sendMessage 启动适配器后立即允许"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|
package/lib/helper.js
CHANGED
|
@@ -316,8 +316,8 @@ return "boolean";
|
|
|
316
316
|
|
|
317
317
|
if (isObj) {
|
|
318
318
|
if (Object.keys(valDP).length > 0) {
|
|
319
|
-
options
|
|
320
|
-
await this.parse(fullPath, valDP, options);
|
|
319
|
+
// FIX: options-Objekt nicht mutieren – Spread-Kopie verwenden
|
|
320
|
+
await this.parse(fullPath, valDP, { ...options, write: false });
|
|
321
321
|
}
|
|
322
322
|
continue;
|
|
323
323
|
}
|
|
@@ -612,7 +612,7 @@ return;
|
|
|
612
612
|
*/
|
|
613
613
|
async changeState(path, value, change = false) {
|
|
614
614
|
if (change) {
|
|
615
|
-
|
|
615
|
+
this.adapter.setState(path, value, true);
|
|
616
616
|
} else {
|
|
617
617
|
await this.adapter.setStateChangedAsync(path, value, true);
|
|
618
618
|
}
|
|
@@ -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
|
);
|
package/lib/statesController.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
const WebSocket = require('ws');
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
let ping;
|
|
6
|
-
let pingTimeout;
|
|
7
|
-
let autoRestartTimeout;
|
|
4
|
+
|
|
5
|
+
const WS_HEARTBEAT_INTERVAL = 5000;
|
|
6
|
+
const WS_RESTART_TIMEOUT = 1000;
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* Manages the WebSocket connection to the zwave-js-ui server.
|
|
@@ -17,12 +16,18 @@ class WebsocketController {
|
|
|
17
16
|
*/
|
|
18
17
|
constructor(adapter) {
|
|
19
18
|
this.adapter = adapter;
|
|
19
|
+
|
|
20
|
+
// FIX: Instanz-Properties statt Modul-globaler Variablen
|
|
21
|
+
this.wsClient = null;
|
|
22
|
+
this.ping = null;
|
|
23
|
+
this.pingTimeout = null;
|
|
24
|
+
this.autoRestartTimeout = null;
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
/**
|
|
23
28
|
* Initialises and connects the WebSocket client to the zwave-js-ui server.
|
|
24
29
|
*
|
|
25
|
-
* @returns {WebSocket} The created WebSocket client instance.
|
|
30
|
+
* @returns {WebSocket|null} The created WebSocket client instance, or null on error.
|
|
26
31
|
*/
|
|
27
32
|
initWsClient() {
|
|
28
33
|
try {
|
|
@@ -32,37 +37,35 @@ class WebsocketController {
|
|
|
32
37
|
wsURL += `?token=${this.adapter.config.wsToken}`;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
wsClient = new WebSocket(wsURL, { rejectUnauthorized: false });
|
|
40
|
+
this.wsClient = new WebSocket(wsURL, { rejectUnauthorized: false });
|
|
36
41
|
|
|
37
|
-
wsClient.on('open', () => {
|
|
38
|
-
// Send ping to server
|
|
42
|
+
this.wsClient.on('open', () => {
|
|
39
43
|
this.sendPingToServer();
|
|
40
|
-
// Start Heartbeat
|
|
41
44
|
this.wsHeartbeat();
|
|
42
45
|
});
|
|
43
46
|
|
|
44
|
-
wsClient.on('pong', () => {
|
|
47
|
+
this.wsClient.on('pong', () => {
|
|
45
48
|
this.wsHeartbeat();
|
|
46
49
|
});
|
|
47
50
|
|
|
48
|
-
wsClient.on('close',
|
|
49
|
-
clearTimeout(pingTimeout);
|
|
50
|
-
clearTimeout(ping);
|
|
51
|
+
this.wsClient.on('close', () => {
|
|
52
|
+
clearTimeout(this.pingTimeout);
|
|
53
|
+
clearTimeout(this.ping);
|
|
51
54
|
|
|
52
|
-
if (wsClient.readyState === WebSocket.CLOSED) {
|
|
55
|
+
if (this.wsClient.readyState === WebSocket.CLOSED) {
|
|
53
56
|
this.autoRestart();
|
|
54
57
|
}
|
|
55
58
|
});
|
|
56
59
|
|
|
57
|
-
wsClient.on('
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
this.adapter.log.debug(err);
|
|
60
|
+
this.wsClient.on('error', (err) => {
|
|
61
|
+
// FIX: err.message statt komplettes Objekt loggen
|
|
62
|
+
this.adapter.log.warn(`<zwavews> WebSocket error: ${err.message}`);
|
|
61
63
|
});
|
|
62
64
|
|
|
63
|
-
return wsClient;
|
|
65
|
+
return this.wsClient;
|
|
64
66
|
} catch (err) {
|
|
65
|
-
this.adapter.log.error(err);
|
|
67
|
+
this.adapter.log.error(`<zwavews> initWsClient failed: ${err.message}`);
|
|
68
|
+
return null;
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
71
|
|
|
@@ -72,51 +75,56 @@ class WebsocketController {
|
|
|
72
75
|
* @param {string} message - The message payload to send.
|
|
73
76
|
*/
|
|
74
77
|
send(message) {
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
// FIX: Null-Check für wsClient
|
|
79
|
+
if (!this.wsClient || this.wsClient.readyState !== WebSocket.OPEN) {
|
|
80
|
+
this.adapter.log.warn('<zwavews> Cannot send message, no open websocket connection to zwave-js-ui.');
|
|
77
81
|
return;
|
|
78
82
|
}
|
|
79
|
-
wsClient.send(message);
|
|
83
|
+
this.wsClient.send(message);
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
/**
|
|
83
87
|
* Sends a WebSocket ping to the server and schedules the next ping.
|
|
84
88
|
*/
|
|
85
89
|
sendPingToServer() {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
if (!this.wsClient || this.wsClient.readyState !== WebSocket.OPEN) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
this.wsClient.ping();
|
|
94
|
+
this.ping = setTimeout(() => {
|
|
89
95
|
this.sendPingToServer();
|
|
90
|
-
},
|
|
96
|
+
}, WS_HEARTBEAT_INTERVAL);
|
|
91
97
|
}
|
|
92
98
|
|
|
93
99
|
/**
|
|
94
100
|
* Resets the heartbeat timeout; terminates the connection if no pong is received in time.
|
|
95
101
|
*/
|
|
96
102
|
wsHeartbeat() {
|
|
97
|
-
clearTimeout(pingTimeout);
|
|
98
|
-
pingTimeout = setTimeout(() => {
|
|
99
|
-
this.adapter.log.warn('
|
|
100
|
-
wsClient
|
|
101
|
-
|
|
103
|
+
clearTimeout(this.pingTimeout);
|
|
104
|
+
this.pingTimeout = setTimeout(() => {
|
|
105
|
+
this.adapter.log.warn('<zwavews> WebSocket connection timed out, terminating.');
|
|
106
|
+
if (this.wsClient) {
|
|
107
|
+
this.wsClient.terminate();
|
|
108
|
+
}
|
|
109
|
+
}, WS_HEARTBEAT_INTERVAL + 3000);
|
|
102
110
|
}
|
|
103
111
|
|
|
104
112
|
/**
|
|
105
113
|
* Schedules an automatic reconnect attempt after the configured restart timeout.
|
|
106
114
|
*/
|
|
107
115
|
autoRestart() {
|
|
108
|
-
this.adapter.log.warn(
|
|
109
|
-
autoRestartTimeout = setTimeout(() => {
|
|
116
|
+
this.adapter.log.warn(`<zwavews> WebSocket closed, reconnecting in ${WS_RESTART_TIMEOUT / 1000}s...`);
|
|
117
|
+
this.autoRestartTimeout = setTimeout(() => {
|
|
110
118
|
this.adapter.startWebsocket();
|
|
111
|
-
},
|
|
119
|
+
}, WS_RESTART_TIMEOUT);
|
|
112
120
|
}
|
|
113
121
|
|
|
114
122
|
/**
|
|
115
123
|
* Closes the WebSocket connection if it is currently open.
|
|
116
124
|
*/
|
|
117
125
|
closeConnection() {
|
|
118
|
-
if (wsClient && wsClient.readyState !== WebSocket.CLOSED) {
|
|
119
|
-
wsClient.close();
|
|
126
|
+
if (this.wsClient && this.wsClient.readyState !== WebSocket.CLOSED) {
|
|
127
|
+
this.wsClient.close();
|
|
120
128
|
}
|
|
121
129
|
}
|
|
122
130
|
|
|
@@ -124,9 +132,9 @@ class WebsocketController {
|
|
|
124
132
|
* Clears all active timers (ping, pingTimeout, autoRestartTimeout).
|
|
125
133
|
*/
|
|
126
134
|
allTimerClear() {
|
|
127
|
-
clearTimeout(pingTimeout);
|
|
128
|
-
clearTimeout(ping);
|
|
129
|
-
clearTimeout(autoRestartTimeout);
|
|
135
|
+
clearTimeout(this.pingTimeout);
|
|
136
|
+
clearTimeout(this.ping);
|
|
137
|
+
clearTimeout(this.autoRestartTimeout);
|
|
130
138
|
}
|
|
131
139
|
}
|
|
132
140
|
|
package/main.js
CHANGED
|
@@ -1,509 +1,492 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const core = require(
|
|
4
|
-
const mqtt = require(
|
|
5
|
-
const utils = require(
|
|
6
|
-
const constant = require(
|
|
7
|
-
const dmZwave
|
|
8
|
-
|
|
9
|
-
const adapterInfo = require(
|
|
10
|
-
const StatesController = require(
|
|
11
|
-
const WebsocketController = require('./lib/websocketController')
|
|
12
|
-
const Helper = require(
|
|
13
|
-
|
|
14
|
-
const MqttServerController = require("./lib/mqttServerController").MqttServerController;
|
|
15
|
-
|
|
16
|
-
let mqttClient;
|
|
17
|
-
let deviceCache = {};
|
|
18
|
-
let websocketController;
|
|
19
|
-
let mqttServerController;
|
|
20
|
-
let statesController;
|
|
21
|
-
let helper;
|
|
22
|
-
let messageParseMutex = Promise.resolve();
|
|
23
|
-
let options = {};
|
|
24
|
-
let startListening = false;
|
|
25
|
-
let allNodesCreated = false;
|
|
26
|
-
|
|
27
|
-
let driver;
|
|
28
|
-
let controller;
|
|
29
|
-
let allNodes;
|
|
30
|
-
let eventTyp;
|
|
31
|
-
|
|
3
|
+
const core = require('@iobroker/adapter-core');
|
|
4
|
+
const mqtt = require('mqtt');
|
|
5
|
+
const utils = require('./lib/utils');
|
|
6
|
+
const constant = require('./lib/constants');
|
|
7
|
+
const dmZwave = require('./lib/devicemgmt.js');
|
|
8
|
+
|
|
9
|
+
const { adapterInfo } = require('./lib/messages');
|
|
10
|
+
const { StatesController } = require('./lib/statesController');
|
|
11
|
+
const { WebsocketController } = require('./lib/websocketController');
|
|
12
|
+
const { Helper } = require('./lib/helper');
|
|
13
|
+
const { MqttServerController } = require('./lib/mqttServerController');
|
|
32
14
|
|
|
33
15
|
class zwavews extends core.Adapter {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
this.on("ready", this.onReady.bind(this));
|
|
40
|
-
this.on("stateChange", this.onStateChange.bind(this));
|
|
41
|
-
this.on("unload", this.onUnload.bind(this));
|
|
42
|
-
}
|
|
16
|
+
constructor(options) {
|
|
17
|
+
super({
|
|
18
|
+
...options,
|
|
19
|
+
name: 'zwavews',
|
|
20
|
+
});
|
|
43
21
|
|
|
44
|
-
|
|
45
|
-
|
|
22
|
+
// Instanz-State statt Modul-globale Variablen
|
|
23
|
+
this.mqttClient = null;
|
|
24
|
+
this.deviceCache = {};
|
|
25
|
+
this.websocketController = null;
|
|
26
|
+
this.mqttServerController = null;
|
|
27
|
+
this.statesController = null;
|
|
28
|
+
this.helper = null;
|
|
29
|
+
this.messageParseMutex = Promise.resolve();
|
|
30
|
+
this.parseOptions = {};
|
|
31
|
+
this.startListening = false;
|
|
32
|
+
this.allNodesCreated = false;
|
|
33
|
+
this.nodeCache = {};
|
|
34
|
+
|
|
35
|
+
this.on('ready', this.onReady.bind(this));
|
|
36
|
+
this.on('stateChange', this.onStateChange.bind(this));
|
|
37
|
+
this.on('unload', this.onUnload.bind(this));
|
|
38
|
+
}
|
|
46
39
|
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
async onReady() {
|
|
41
|
+
this.statesController = new StatesController(this);
|
|
49
42
|
|
|
50
|
-
|
|
51
|
-
await statesController.setAllAvailableToFalse();
|
|
43
|
+
adapterInfo(this.config, this.log);
|
|
52
44
|
|
|
53
|
-
|
|
45
|
+
this.setStateChanged('info.connection', false, true);
|
|
46
|
+
await this.statesController.setAllAvailableToFalse();
|
|
54
47
|
|
|
55
|
-
|
|
48
|
+
this.helper = new Helper(this, this.deviceCache);
|
|
49
|
+
this.deviceManagement = new dmZwave(this);
|
|
56
50
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
if (this.config.wsOnStart) {
|
|
52
|
+
this.setStateChanged('info.sendMessageAllowed', true, true);
|
|
53
|
+
}
|
|
60
54
|
|
|
61
|
-
|
|
62
|
-
this.setStateChanged("info.debugmessages", "", true);
|
|
55
|
+
this.setStateChanged('info.debugmessages', '', true);
|
|
63
56
|
|
|
57
|
+
// MQTT-Verbindungstypen
|
|
58
|
+
if (['exmqtt', 'intmqtt'].includes(this.config.connectionType)) {
|
|
59
|
+
if (this.config.connectionType === 'exmqtt') {
|
|
60
|
+
if (!this.config.externalMqttServerIP) {
|
|
61
|
+
this.log.warn('Please configure the External MQTT-Server connection!');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
65
|
+
const mqttClientOptions = {
|
|
66
|
+
clientId: `ioBroker.zwavews_${Math.random().toString(16).slice(2, 8)}`,
|
|
67
|
+
clean: false,
|
|
68
|
+
protocolVersion: 4,
|
|
69
|
+
reconnectPeriod: 5000,
|
|
70
|
+
connectTimeout: 30000,
|
|
71
|
+
keepalive: 30,
|
|
72
|
+
resubscribe: true,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
if (this.config.externalMqttServerCredentials === true) {
|
|
76
|
+
mqttClientOptions.username = this.config.externalMqttServerUsername;
|
|
77
|
+
mqttClientOptions.password = this.config.externalMqttServerPassword;
|
|
78
|
+
}
|
|
75
79
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
80
|
+
this.mqttClient = mqtt.connect(
|
|
81
|
+
`mqtt://${this.config.externalMqttServerIP}:${this.config.externalMqttServerPort}`,
|
|
82
|
+
mqttClientOptions,
|
|
83
|
+
);
|
|
84
|
+
} else {
|
|
85
|
+
// Interner MQTT-Server
|
|
86
|
+
this.mqttServerController = new MqttServerController(this);
|
|
87
|
+
await this.mqttServerController.createMQTTServer();
|
|
88
|
+
await this.delay(1500);
|
|
89
|
+
this.mqttClient = mqtt.connect(
|
|
90
|
+
`mqtt://${this.config.mqttServerIPBind}:${this.config.mqttServerPort}`,
|
|
91
|
+
{
|
|
92
|
+
clientId: `ioBroker.zwavews_${Math.random().toString(16).slice(2, 8)}`,
|
|
93
|
+
clean: true,
|
|
94
|
+
reconnectPeriod: 500,
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
}
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
} else {
|
|
99
|
-
// Internal MQTT-Server
|
|
100
|
-
mqttServerController = new MqttServerController(this);
|
|
101
|
-
await mqttServerController.createMQTTServer();
|
|
102
|
-
await this.delay(1500);
|
|
103
|
-
mqttClient = mqtt.connect(
|
|
104
|
-
`mqtt://${this.config.mqttServerIPBind}:${this.config.mqttServerPort}`,
|
|
105
|
-
{
|
|
106
|
-
clientId: `ioBroker.zwavews_${Math.random().toString(16).slice(2, 8)}`,
|
|
107
|
-
clean: true,
|
|
108
|
-
reconnectPeriod: 500,
|
|
109
|
-
},
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// MQTT Client
|
|
114
|
-
mqttClient.on("connect", () => {
|
|
115
|
-
this.log.info(`Connect to zwavews over ${this.config.connectionType === "exmqtt" ? "external mqtt" : "internal mqtt"} connection.`);
|
|
116
|
-
this.setStateChanged("info.connection", true, true);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
mqttClient.subscribe(`${this.config.baseTopic}/#`, (err) => {
|
|
120
|
-
if (err) {
|
|
121
|
-
this.log.error(`<zwavews> MQTT subscribe error: ${err.message}`);
|
|
122
|
-
}
|
|
123
|
-
});
|
|
99
|
+
// FIX: subscribe innerhalb des connect-Events, nicht außerhalb
|
|
100
|
+
this.mqttClient.on('connect', () => {
|
|
101
|
+
const connType = this.config.connectionType === 'exmqtt' ? 'external mqtt' : 'internal mqtt';
|
|
102
|
+
this.log.info(`Connect to zwavews over ${connType} connection.`);
|
|
103
|
+
this.setStateChanged('info.connection', true, true);
|
|
124
104
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
105
|
+
this.mqttClient.subscribe(`${this.config.baseTopic}/#`, (err) => {
|
|
106
|
+
if (err) {
|
|
107
|
+
this.log.error(`<zwavews> MQTT subscribe error: ${err.message}`);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
this.mqttClient.on('error', (err) => {
|
|
113
|
+
this.log.error(`<zwavews> MQTT client error: ${err.message}`);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
this.mqttClient.on('offline', () => {
|
|
117
|
+
this.log.warn('<zwavews> MQTT client offline.');
|
|
118
|
+
this.setStateChanged('info.connection', false, true);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
this.mqttClient.on('message', (topic, payload) => {
|
|
122
|
+
const rawPayload = payload.toString();
|
|
123
|
+
let parsedPayload;
|
|
124
|
+
try {
|
|
125
|
+
parsedPayload = rawPayload === '' ? null : JSON.parse(rawPayload);
|
|
126
|
+
} catch {
|
|
127
|
+
parsedPayload = rawPayload;
|
|
128
|
+
}
|
|
129
|
+
const newMessage = JSON.stringify({
|
|
130
|
+
payload: parsedPayload,
|
|
131
|
+
topic: topic.slice(topic.indexOf('/') + 1),
|
|
132
|
+
});
|
|
133
|
+
this.messageParse(newMessage);
|
|
134
|
+
});
|
|
135
|
+
} else if (this.config.connectionType === 'ws') {
|
|
136
|
+
if (!this.config.wsServerIP) {
|
|
137
|
+
this.log.warn('Please configure the Websocket connection!');
|
|
143
138
|
return;
|
|
144
139
|
}
|
|
145
140
|
|
|
146
|
-
// Dummy MQTT-Server
|
|
147
141
|
if (this.config.dummyMqtt === true) {
|
|
148
|
-
mqttServerController = new MqttServerController(this);
|
|
149
|
-
await mqttServerController.createDummyMQTTServer();
|
|
150
|
-
this.setStateChanged(
|
|
142
|
+
this.mqttServerController = new MqttServerController(this);
|
|
143
|
+
await this.mqttServerController.createDummyMQTTServer();
|
|
144
|
+
this.setStateChanged('info.connection', true, true);
|
|
151
145
|
await this.delay(1500);
|
|
152
146
|
}
|
|
153
147
|
|
|
154
148
|
this.startWebsocket();
|
|
155
149
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
startWebsocket() {
|
|
159
|
-
websocketController = new WebsocketController(this);
|
|
160
|
-
const wsClient = websocketController.initWsClient();
|
|
161
|
-
|
|
162
|
-
if (!wsClient) {
|
|
163
|
-
this.log.error('<zwavews> initWsClient returned null — websocket not started.');
|
|
164
|
-
return;
|
|
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
|
-
});
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
async messageParse(message) {
|
|
189
|
-
// Mutex lock: queue up calls to messageParse
|
|
190
|
-
let release;
|
|
191
|
-
const lock = new Promise((resolve) => (release = resolve));
|
|
192
|
-
const prev = messageParseMutex;
|
|
193
|
-
messageParseMutex = lock;
|
|
194
|
-
await prev;
|
|
195
|
-
|
|
196
|
-
try {
|
|
197
|
-
const messageObj = JSON.parse(message);
|
|
198
|
-
|
|
199
|
-
const debugDevicesState = await this.getStateAsync("info.debugId");
|
|
200
|
-
|
|
201
|
-
this.log.debug(`--->>> fromZ2W_RAW1 -> ${JSON.stringify(messageObj)}`);
|
|
202
|
-
|
|
203
|
-
const type = messageObj?.type;
|
|
204
|
-
|
|
205
|
-
if (this.config.connectionType === 'ws') {
|
|
206
|
-
switch (type) {
|
|
207
|
-
case 'version': { // say hello
|
|
208
|
-
this.setStateChanged('info.connection', true, true);
|
|
209
|
-
this.setStateChanged('info.zwave_gateway_version', messageObj.driverVersion, true);
|
|
210
|
-
this.setStateChanged('info.zwave_gateway_status', 'online', true);
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
case 'result': {
|
|
214
|
-
if (messageObj.result?.success === true) {
|
|
215
|
-
this.setStateChanged('info.debugmessages', JSON.stringify(messageObj), true);
|
|
216
|
-
break;
|
|
217
|
-
}
|
|
150
|
+
}
|
|
218
151
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
152
|
+
startWebsocket() {
|
|
153
|
+
this.websocketController = new WebsocketController(this);
|
|
154
|
+
const wsClient = this.websocketController.initWsClient();
|
|
222
155
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
156
|
+
if (!wsClient) {
|
|
157
|
+
this.log.error('<zwavews> initWsClient returned null — websocket not started.');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
227
160
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
161
|
+
wsClient.on('open', () => {
|
|
162
|
+
this.log.info('Connect to zwave-js-ui over websocket connection.');
|
|
163
|
+
this.startListening = true;
|
|
164
|
+
this.websocketController.send(JSON.stringify({ command: 'start_listening' }));
|
|
165
|
+
});
|
|
231
166
|
|
|
232
|
-
|
|
233
|
-
|
|
167
|
+
wsClient.on('message', (message) => {
|
|
168
|
+
this.messageParse(message);
|
|
169
|
+
});
|
|
234
170
|
|
|
235
|
-
|
|
236
|
-
|
|
171
|
+
wsClient.on('close', async () => {
|
|
172
|
+
this.setStateChanged('info.connection', false, true);
|
|
173
|
+
await this.statesController.setAllAvailableToFalse();
|
|
174
|
+
this.startListening = false;
|
|
175
|
+
this.allNodesCreated = false;
|
|
176
|
+
this.deviceCache = {};
|
|
177
|
+
this.nodeCache = {};
|
|
178
|
+
this.log.info('Websocket connection closed. Attempting to reconnect...');
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async messageParse(message) {
|
|
183
|
+
// FIX: release mit let deklarieren (war implizite globale Variable)
|
|
184
|
+
let release;
|
|
185
|
+
const lock = new Promise((resolve) => (release = resolve));
|
|
186
|
+
const prev = this.messageParseMutex;
|
|
187
|
+
this.messageParseMutex = lock;
|
|
188
|
+
await prev;
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const messageObj = JSON.parse(message);
|
|
192
|
+
const debugDevicesState = await this.getStateAsync('info.debugId');
|
|
193
|
+
|
|
194
|
+
this.log.debug(`--->>> fromZ2W_RAW1 -> ${JSON.stringify(messageObj)}`);
|
|
195
|
+
|
|
196
|
+
const type = messageObj?.type;
|
|
197
|
+
|
|
198
|
+
if (this.config.connectionType === 'ws') {
|
|
199
|
+
switch (type) {
|
|
200
|
+
case 'version': {
|
|
201
|
+
this.setStateChanged('info.connection', true, true);
|
|
202
|
+
this.setStateChanged('info.zwave_gateway_version', messageObj.driverVersion, true);
|
|
203
|
+
this.setStateChanged('info.zwave_gateway_status', 'online', true);
|
|
204
|
+
break;
|
|
237
205
|
}
|
|
206
|
+
case 'result': {
|
|
207
|
+
if (messageObj.result?.success === true) {
|
|
208
|
+
this.setStateChanged('info.debugmessages', JSON.stringify(messageObj), true);
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (this.allNodesCreated) {
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!messageObj.result?.state || !Array.isArray(messageObj.result.state.nodes)) {
|
|
217
|
+
this.log.warn('<zwavews> Invalid result.state structure received, skipping.');
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const { nodes: allNodes } = messageObj.result.state;
|
|
222
|
+
|
|
223
|
+
for (const nodeData of allNodes) {
|
|
224
|
+
const nodeId = utils.formatNodeId(nodeData.nodeId);
|
|
225
|
+
|
|
226
|
+
if (debugDevicesState?.val && String(debugDevicesState.val).includes(nodeId)) {
|
|
227
|
+
this.log.warn(`--->>> fromZ2W_RAW2-> ${JSON.stringify(nodeData)}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!this.nodeCache[nodeId]) {
|
|
231
|
+
if (this.config.showNodeInfoMessage) {
|
|
232
|
+
this.log.info(`Node Info Update for ${nodeId}`);
|
|
233
|
+
}
|
|
234
|
+
this.nodeCache[nodeId] = { nodeData };
|
|
235
|
+
}
|
|
236
|
+
await this.helper.createNode(nodeId, nodeData, this.parseOptions);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
this.allNodesCreated = true;
|
|
238
240
|
|
|
239
|
-
if (!this.nodeCache[nodeId]) {
|
|
240
241
|
if (this.config.showNodeInfoMessage) {
|
|
241
|
-
this.log.info(
|
|
242
|
+
this.log.info('all Nodes are ready');
|
|
243
|
+
}
|
|
244
|
+
if (this.startListening) {
|
|
245
|
+
this.websocketController.send(JSON.stringify({ command: 'start_listening' }));
|
|
246
|
+
this.startListening = false;
|
|
242
247
|
}
|
|
243
|
-
|
|
248
|
+
break;
|
|
244
249
|
}
|
|
245
|
-
|
|
250
|
+
case 'event': {
|
|
251
|
+
const eventTyp = messageObj.event;
|
|
252
|
+
|
|
253
|
+
switch (eventTyp.event) {
|
|
254
|
+
case 'value updated':
|
|
255
|
+
case 'value added':
|
|
256
|
+
case 'value notification': {
|
|
257
|
+
const nodeArg = eventTyp.args;
|
|
258
|
+
const nodeId = utils.formatNodeId(eventTyp.nodeId);
|
|
259
|
+
|
|
260
|
+
if (debugDevicesState?.val && String(debugDevicesState.val).includes(nodeId)) {
|
|
261
|
+
this.log.warn(`--->>> fromZ2W_RAW2-> ${JSON.stringify(eventTyp)}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let parsePath = `${nodeId}.${nodeArg.commandClassName}.${nodeArg.propertyName
|
|
265
|
+
.replace(/[^\p{L}\p{N}\s]/gu, '')
|
|
266
|
+
.replace(/\s+/g, ' ')
|
|
267
|
+
.trim()}`;
|
|
268
|
+
|
|
269
|
+
if (nodeArg?.propertyKeyName) {
|
|
270
|
+
parsePath = `${parsePath}.${nodeArg.propertyKeyName
|
|
271
|
+
.replace(/[^\p{L}\p{N}\s]/gu, '')
|
|
272
|
+
.replace(/\s+/g, ' ')
|
|
273
|
+
.trim()}`;
|
|
274
|
+
|
|
275
|
+
if (constant.RGB.includes(nodeArg.propertyKeyName)) {
|
|
276
|
+
parsePath = utils.replaceLastDot(parsePath);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
parsePath = utils.deleteLastDot(utils.formatObject(parsePath));
|
|
281
|
+
|
|
282
|
+
if (nodeArg.commandClass === 119) {
|
|
283
|
+
switch (nodeArg.property) {
|
|
284
|
+
case 'name':
|
|
285
|
+
await this.helper.updateDevice(nodeId, nodeArg);
|
|
286
|
+
parsePath = `${nodeId}.info.${nodeArg.property}`;
|
|
287
|
+
break;
|
|
288
|
+
case 'location':
|
|
289
|
+
break;
|
|
290
|
+
default:
|
|
291
|
+
parsePath = `${nodeId}.info.${nodeArg.property}`;
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
this.log.debug(`${parsePath} ->> ${nodeArg.newValue}`);
|
|
297
|
+
|
|
298
|
+
if (parsePath.includes('firmwareVersions')) {
|
|
299
|
+
parsePath = `${parsePath}_value`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (nodeArg.endpoint != null && nodeArg.endpoint > 0) {
|
|
303
|
+
parsePath = `${parsePath}_${nodeArg.endpoint}`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
parsePath = utils.deleteLastDot(parsePath);
|
|
307
|
+
|
|
308
|
+
if (eventTyp.event === 'value notification') {
|
|
309
|
+
await this.helper.parse(parsePath, nodeArg.newValue, this.parseOptions, true);
|
|
310
|
+
} else {
|
|
311
|
+
await this.helper.parse(parsePath, nodeArg.newValue, this.parseOptions, false);
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
case 'firmware update progress': {
|
|
317
|
+
const total = Number(eventTyp.totalFragments) || 0;
|
|
318
|
+
const sent = Number(eventTyp.sentFragments) || 0;
|
|
319
|
+
const progress = total > 0 ? Math.min(100, Math.max(0, (sent / total) * 100)) : 0;
|
|
320
|
+
this.log.info(
|
|
321
|
+
`Firmware update progress for ${utils.formatNodeId(eventTyp.nodeId)} ->> ` +
|
|
322
|
+
`send Fragments ${sent} -- total ${total} (${progress.toFixed(1)}%)`,
|
|
323
|
+
);
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
case 'firmware update finished': {
|
|
328
|
+
this.log.info(`${utils.formatNodeId(eventTyp.nodeId)} --> ${eventTyp.event}`);
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
case 'ready':
|
|
333
|
+
case 'sleep':
|
|
334
|
+
case 'wake up':
|
|
335
|
+
case 'alive':
|
|
336
|
+
case 'dead': {
|
|
337
|
+
const nodeId = utils.formatNodeId(eventTyp.nodeId);
|
|
338
|
+
await this.helper.parse(`${nodeId}.status`, eventTyp.event.toLowerCase(), this.parseOptions);
|
|
339
|
+
|
|
340
|
+
if (eventTyp.event === 'dead') {
|
|
341
|
+
await this.helper.parse(`${nodeId}.ready`, false, this.parseOptions);
|
|
342
|
+
} else {
|
|
343
|
+
await this.helper.parse(`${nodeId}.ready`, true, this.parseOptions);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (this.config.wakeUpInfo) {
|
|
347
|
+
this.log.info(`${utils.formatNodeId(eventTyp.nodeId)} --> ${eventTyp.event}`);
|
|
348
|
+
}
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
case 'node removed': {
|
|
353
|
+
const nodeId = utils.formatNodeId(eventTyp.nodeId);
|
|
354
|
+
if (this.config.useEventInDesc) {
|
|
355
|
+
await this.helper.updateDevice(nodeId, { desc: 'Node is Deleted' }, false);
|
|
356
|
+
} else {
|
|
357
|
+
await this.helper.updateDevice(nodeId, { name: 'Node is Deleted' }, true);
|
|
358
|
+
}
|
|
359
|
+
this.log.error(`Delete ${utils.formatNodeId(eventTyp.nodeId)}`);
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
case 'interview started':
|
|
364
|
+
case 'interview stage completed':
|
|
365
|
+
case 'interview failed':
|
|
366
|
+
case 'interview completed':
|
|
367
|
+
this.log.info(`${utils.formatNodeId(eventTyp.nodeId)} --> ${eventTyp.event}`);
|
|
368
|
+
break;
|
|
369
|
+
|
|
370
|
+
case 'statistics updated':
|
|
371
|
+
case 'metadata updated':
|
|
372
|
+
case 'node info received':
|
|
373
|
+
break;
|
|
374
|
+
|
|
375
|
+
default:
|
|
376
|
+
if (this.config.newTypeEvent) {
|
|
377
|
+
this.log.warn(`New type event ->> ${eventTyp.event}`);
|
|
378
|
+
this.log.warn(JSON.stringify(messageObj));
|
|
379
|
+
}
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
default:
|
|
385
|
+
break;
|
|
246
386
|
}
|
|
247
|
-
|
|
248
|
-
|
|
387
|
+
}
|
|
388
|
+
} catch (err) {
|
|
389
|
+
this.log.error(err);
|
|
390
|
+
this.log.error(`<zwavews> error message -->> ${message}`);
|
|
391
|
+
} finally {
|
|
392
|
+
release();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
249
395
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (
|
|
254
|
-
|
|
255
|
-
|
|
396
|
+
async onUnload(callback) {
|
|
397
|
+
try {
|
|
398
|
+
if (['exmqtt', 'intmqtt'].includes(this.config.connectionType)) {
|
|
399
|
+
if (this.mqttClient && !this.mqttClient.closed) {
|
|
400
|
+
try {
|
|
401
|
+
this.mqttClient.end();
|
|
402
|
+
} catch (e) {
|
|
403
|
+
this.log.error(e);
|
|
404
|
+
}
|
|
256
405
|
}
|
|
257
|
-
break;
|
|
258
406
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
case 'value added':
|
|
265
|
-
case 'value notification': {
|
|
266
|
-
const nodeArg = eventTyp.args;
|
|
267
|
-
const nodeId = utils.formatNodeId(eventTyp.nodeId);
|
|
268
|
-
|
|
269
|
-
if (debugDevicesState && debugDevicesState.val && String(debugDevicesState.val).includes(nodeId)) {
|
|
270
|
-
this.log.warn(`--->>> fromZ2W_RAW2-> ${JSON.stringify(eventTyp)}` );
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
let parsePath = `${nodeId}.${nodeArg.commandClassName}.${nodeArg.propertyName
|
|
274
|
-
.replace(/[^\p{L}\p{N}\s]/gu, "")
|
|
275
|
-
.replace(/\s+/g, " ")
|
|
276
|
-
.trim()}`;
|
|
277
|
-
|
|
278
|
-
if (nodeArg?.propertyKeyName) {
|
|
279
|
-
parsePath = `${parsePath}.${nodeArg.propertyKeyName
|
|
280
|
-
.replace(/[^\p{L}\p{N}\s]/gu, "")
|
|
281
|
-
.replace(/\s+/g, " ")
|
|
282
|
-
.trim()}`;
|
|
283
|
-
|
|
284
|
-
if (constant.RGB.includes(nodeArg.propertyKeyName)) {
|
|
285
|
-
parsePath = utils.replaceLastDot(parsePath);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
parsePath = utils.deleteLastDot(utils.formatObject(parsePath));
|
|
290
|
-
|
|
291
|
-
if (nodeArg.commandClass === 119) { // sonderlocke für node naming
|
|
292
|
-
switch (nodeArg.property) {
|
|
293
|
-
case 'name':
|
|
294
|
-
await helper.updateDevice(nodeId, nodeArg);
|
|
295
|
-
parsePath = `${nodeId}.info.${nodeArg.property}`;
|
|
296
|
-
break;
|
|
297
|
-
case 'location':
|
|
298
|
-
// intentionally ignored
|
|
299
|
-
break;
|
|
300
|
-
default:
|
|
301
|
-
parsePath = `${nodeId}.info.${nodeArg.property}`;
|
|
302
|
-
break;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
this.log.debug(`${parsePath} ->> ${nodeArg.newValue}`);
|
|
307
|
-
|
|
308
|
-
if (parsePath.includes('firmwareVersions')) { // damit array werte gespeichert werden
|
|
309
|
-
parsePath = `${parsePath}_value`;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// mehr als 1 endpoint behandeln
|
|
313
|
-
if (nodeArg.endpoint != null && nodeArg.endpoint > 0) {
|
|
314
|
-
parsePath = `${parsePath}_${nodeArg.endpoint}`;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
parsePath = utils.deleteLastDot(parsePath); // check again
|
|
318
|
-
|
|
319
|
-
if (eventTyp.event === 'value notification') {
|
|
320
|
-
await helper.parse(`${parsePath}`, nodeArg.newValue, options, true);
|
|
321
|
-
} else {
|
|
322
|
-
await helper.parse(`${parsePath}`, nodeArg.newValue, options, false);
|
|
323
|
-
}
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
case 'firmware update progress': {
|
|
328
|
-
const total = Number(eventTyp.totalFragments) || 0;
|
|
329
|
-
const sent = Number(eventTyp.sentFragments) || 0;
|
|
330
|
-
|
|
331
|
-
const progress = total > 0 ? Math.min(100, Math.max(0, (sent / total) * 100)) : 0;
|
|
332
|
-
|
|
333
|
-
this.log.info(
|
|
334
|
-
`Firmware update progress for ${utils.formatNodeId(eventTyp.nodeId)} ->> ` + `send Fragments ${sent} -- total ${total}
|
|
335
|
-
(${progress.toFixed(1)}%)`);
|
|
336
|
-
break;
|
|
337
|
-
}
|
|
338
|
-
case 'firmware update finished': {
|
|
339
|
-
this.log.info(`${utils.formatNodeId(eventTyp.nodeId)} --> ${eventTyp.event}`);
|
|
340
|
-
break;
|
|
341
|
-
}
|
|
342
|
-
case 'ready':
|
|
343
|
-
case 'sleep':
|
|
344
|
-
case 'wake up':
|
|
345
|
-
case 'alive':
|
|
346
|
-
case 'dead': {
|
|
347
|
-
const nodeId = utils.formatNodeId(eventTyp.nodeId);
|
|
348
|
-
await helper.parse(`${nodeId}.status`, eventTyp.event.toLowerCase(), options);
|
|
349
|
-
|
|
350
|
-
if (eventTyp.event === 'dead') {
|
|
351
|
-
await helper.parse(`${nodeId}.ready`, false, options);
|
|
352
|
-
} else {
|
|
353
|
-
await helper.parse(`${nodeId}.ready`, true, options);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (this.config.wakeUpInfo) {
|
|
357
|
-
this.log.info(`${utils.formatNodeId(eventTyp.nodeId)} --> ${eventTyp.event}`);
|
|
358
|
-
}
|
|
359
|
-
break;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
case 'node removed': {
|
|
363
|
-
const nodeId = utils.formatNodeId(eventTyp.nodeId);
|
|
364
|
-
|
|
365
|
-
if (this.config.useEventInDesc) {
|
|
366
|
-
const nodeArg = {desc: "Node is Deleted"};
|
|
367
|
-
await helper.updateDevice(nodeId, nodeArg, false);
|
|
368
|
-
} else {
|
|
369
|
-
const nodeArg = {name : 'Node is Deleted'};
|
|
370
|
-
await helper.updateDevice(nodeId, nodeArg, true);
|
|
371
|
-
}
|
|
372
|
-
this.log.error(`Delete ${utils.formatNodeId(eventTyp.nodeId)}`);
|
|
373
|
-
break;
|
|
374
|
-
}
|
|
375
|
-
case 'interview started':
|
|
376
|
-
case 'interview stage completed':
|
|
377
|
-
case 'interview failed':
|
|
378
|
-
case 'interview completed':
|
|
379
|
-
this.log.info(`${utils.formatNodeId(eventTyp.nodeId)} --> ${eventTyp.event}`);
|
|
380
|
-
break;
|
|
381
|
-
|
|
382
|
-
case 'statistics updated':
|
|
383
|
-
case 'metadata updated':
|
|
384
|
-
case 'node info received':
|
|
385
|
-
break;
|
|
386
|
-
default:
|
|
387
|
-
if (this.config.newTypeEvent) {
|
|
388
|
-
this.log.warn(`New type event ->> ${eventTyp.event}`);
|
|
389
|
-
this.log.warn(JSON.stringify(messageObj));
|
|
407
|
+
|
|
408
|
+
if (this.config.connectionType === 'intmqtt' || this.config.dummyMqtt === true) {
|
|
409
|
+
try {
|
|
410
|
+
if (this.mqttServerController) {
|
|
411
|
+
this.mqttServerController.closeServer();
|
|
390
412
|
}
|
|
391
|
-
|
|
413
|
+
} catch (e) {
|
|
414
|
+
this.log.error(e);
|
|
415
|
+
}
|
|
392
416
|
}
|
|
393
417
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
418
|
+
if (this.websocketController) {
|
|
419
|
+
try {
|
|
420
|
+
await this.websocketController.allTimerClear();
|
|
421
|
+
this.websocketController.closeConnection();
|
|
422
|
+
} catch (e) {
|
|
423
|
+
this.log.error(e);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
if (this.statesController) {
|
|
429
|
+
await this.statesController.setAllAvailableToFalse();
|
|
430
|
+
}
|
|
431
|
+
} catch (e) {
|
|
432
|
+
this.log.error(e);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
this.setStateChanged('info.connection', false, true);
|
|
436
|
+
} finally {
|
|
437
|
+
callback();
|
|
397
438
|
}
|
|
398
|
-
}
|
|
399
|
-
} catch (err) {
|
|
400
|
-
this.log.error(err);
|
|
401
|
-
this.log.error(`<zwavews> error message -->> ${message}`);
|
|
402
|
-
} finally {
|
|
403
|
-
release();
|
|
404
439
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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) {
|
|
421
|
-
try {
|
|
422
|
-
if (mqttServerController) {
|
|
423
|
-
mqttServerController.closeServer();
|
|
424
|
-
}
|
|
425
|
-
} catch (e) {
|
|
426
|
-
this.log.error(e);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
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
|
|
439
|
-
try {
|
|
440
|
-
if (statesController) {
|
|
441
|
-
await statesController.setAllAvailableToFalse();
|
|
440
|
+
|
|
441
|
+
async onStateChange(id, state) {
|
|
442
|
+
if (!this.allNodesCreated) {
|
|
443
|
+
return;
|
|
442
444
|
}
|
|
443
|
-
} catch (e) {
|
|
444
|
-
this.log.error(e);
|
|
445
|
-
}
|
|
446
445
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
446
|
+
if (state && state.ack === false) {
|
|
447
|
+
if (id.endsWith('info.debugId')) {
|
|
448
|
+
this.setStateChanged(id, state.val, true);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
452
451
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
452
|
+
const obj = await this.getObjectAsync(id);
|
|
453
|
+
if (obj) {
|
|
454
|
+
const nativeObj = obj.native || {};
|
|
455
|
+
|
|
456
|
+
const m = id.match(/nodeID_0*(\d+)/i);
|
|
457
|
+
if (!m) {
|
|
458
|
+
this.log.warn(`<zwavews> Could not extract nodeId from state id: ${id}`);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const nodeId = Number(m[1]);
|
|
462
|
+
|
|
463
|
+
const message = {
|
|
464
|
+
messageId: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
465
|
+
command: 'node.set_value',
|
|
466
|
+
nodeId,
|
|
467
|
+
valueId: nativeObj.valueId,
|
|
468
|
+
value: state.val,
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
const sendMessageAllowed = await this.getStateAsync('info.sendMessageAllowed');
|
|
472
|
+
|
|
473
|
+
if (sendMessageAllowed && sendMessageAllowed.val === true) {
|
|
474
|
+
if (this.websocketController) {
|
|
475
|
+
this.websocketController.send(JSON.stringify(message));
|
|
476
|
+
} else {
|
|
477
|
+
this.log.warn('<zwavews> websocketController not initialised, cannot send message.');
|
|
478
|
+
}
|
|
479
|
+
}
|
|
457
480
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
const obj = await this.getObjectAsync(id);
|
|
465
|
-
if (obj) {
|
|
466
|
-
const nativeObj = obj.native || {};
|
|
467
|
-
|
|
468
|
-
const m = id.match(/nodeID_0*(\d+)/i);
|
|
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]);
|
|
474
|
-
|
|
475
|
-
const message = {
|
|
476
|
-
messageId: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
477
|
-
command: "node.set_value",
|
|
478
|
-
nodeId: nodeId,
|
|
479
|
-
valueId: nativeObj.valueId,
|
|
480
|
-
value: state.val
|
|
481
|
-
};
|
|
482
|
-
|
|
483
|
-
const sendMessageAllowed = await this.getStateAsync("info.sendMessageAllowed");
|
|
484
|
-
|
|
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
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
this.setStateChanged('info.debugmessages', JSON.stringify(message), true);
|
|
494
|
-
this.log.debug(`<zwavews> message onStateChange ${JSON.stringify(message)}`);
|
|
495
|
-
}
|
|
481
|
+
this.setStateChanged('info.debugmessages', JSON.stringify(message), true);
|
|
482
|
+
this.log.debug(`<zwavews> message onStateChange ${JSON.stringify(message)}`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
496
485
|
}
|
|
497
|
-
}
|
|
498
486
|
}
|
|
499
487
|
|
|
500
488
|
if (require.main !== module) {
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* @param {Partial<core.AdapterOptions>} [options]
|
|
504
|
-
*/
|
|
505
|
-
module.exports = (options) => new zwavews(options);
|
|
489
|
+
module.exports = (options) => new zwavews(options);
|
|
506
490
|
} else {
|
|
507
|
-
|
|
508
|
-
new zwavews();
|
|
491
|
+
new zwavews();
|
|
509
492
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.zwavews",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "zwavews adapter for ioBroker",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Arthur Rupp",
|
|
@@ -26,7 +26,7 @@
|
|
|
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.19",
|
|
30
30
|
"humanize-duration": "^3.33.2",
|
|
31
31
|
"aedes": "^0.51.3",
|
|
32
32
|
"aedes-persistence-nedb": "^2.0.3",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@iobroker/testing": "^5.2.2",
|
|
46
46
|
"@iobroker/eslint-config": "^2.2.0",
|
|
47
47
|
"@tsconfig/node14": "^14.1.8",
|
|
48
|
-
"@types/node": "^25.
|
|
48
|
+
"@types/node": "^25.6.0",
|
|
49
49
|
"@types/node-schedule": "^2.1.8",
|
|
50
50
|
"typescript": "~6.0.2"
|
|
51
51
|
},
|