iobroker.zwavews 0.0.3

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/main.js ADDED
@@ -0,0 +1,407 @@
1
+ "use strict";
2
+
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
+
8
+ const adapterInfo = require("./lib/messages").adapterInfo;
9
+ const StatesController = require("./lib/statesController").StatesController;
10
+ const WebsocketController = require('./lib/websocketController').WebsocketController;
11
+ const Helper = require("./lib/helper").Helper;
12
+
13
+ const MqttServerController = require("./lib/mqttServerController").MqttServerController;
14
+
15
+ let mqttClient;
16
+ let deviceCache = {};
17
+ let nodeCache = {};
18
+ const logCustomizations = { debugDevices: "", logfilter: [] };
19
+
20
+ let websocketController;
21
+ let mqttServerController;
22
+ let statesController;
23
+ let helper;
24
+ let messageParseMutex = Promise.resolve();
25
+ let options = {};
26
+ let startListening = false;
27
+
28
+ let driver;
29
+ let controller;
30
+ let allNodes;
31
+ let eventTyp;
32
+
33
+
34
+ class zwavews extends core.Adapter {
35
+ constructor(options) {
36
+ super({
37
+ ...options,
38
+ name: "zwavews",
39
+ });
40
+ this.on("ready", this.onReady.bind(this));
41
+ this.on("stateChange", this.onStateChange.bind(this));
42
+ this.on("unload", this.onUnload.bind(this));
43
+ }
44
+
45
+ async onReady() {
46
+ statesController = new StatesController(this);
47
+
48
+ // Initialize your adapter here
49
+ adapterInfo(this.config, this.log);
50
+
51
+ this.setState("info.connection", false, true);
52
+ await statesController.setAllAvailableToFalse();
53
+
54
+ helper = new Helper(this, deviceCache);
55
+
56
+ const debugDevicesState = await this.getStateAsync("info.debugId");
57
+ if (debugDevicesState && debugDevicesState.val) {
58
+ logCustomizations.debugDevices = String(
59
+ debugDevicesState.val.toLowerCase(),
60
+ );
61
+ }
62
+ this.setState("info.debugmessages", "", true);
63
+
64
+ // MQTT
65
+ if (["exmqtt", "intmqtt"].includes(this.config.connectionType)) {
66
+ // External MQTT-Server
67
+ if (this.config.connectionType == "exmqtt") {
68
+ if (this.config.externalMqttServerIP == "") {
69
+ this.log.warn(
70
+ "Please configure the External MQTT-Server connection!",
71
+ );
72
+ return;
73
+ }
74
+
75
+ // MQTT connection settings
76
+ const mqttClientOptions = {
77
+ clientId: `ioBroker.zwavews_${Math.random().toString(16).slice(2, 8)}`,
78
+ clean: false,
79
+ protocolVersion: 4,
80
+ reconnectPeriod: 5000,
81
+ connectTimeout: 30000, // 30s
82
+ keepalive: 30,
83
+ resubscribe: true,
84
+ };
85
+
86
+ // Set external mqtt credentials
87
+ if (this.config.externalMqttServerCredentials == true) {
88
+ mqttClientOptions.username = this.config.externalMqttServerUsername;
89
+ mqttClientOptions.password = this.config.externalMqttServerPassword;
90
+ }
91
+
92
+ // Init connection
93
+ mqttClient = mqtt.connect(
94
+ `mqtt://${this.config.externalMqttServerIP}:${this.config.externalMqttServerPort}`,
95
+ mqttClientOptions,
96
+ );
97
+ } else {
98
+ // Internal MQTT-Server
99
+ mqttServerController = new MqttServerController(this);
100
+ await mqttServerController.createMQTTServer();
101
+ await this.delay(1500);
102
+ mqttClient = mqtt.connect(
103
+ `mqtt://${this.config.mqttServerIPBind}:${this.config.mqttServerPort}`,
104
+ {
105
+ clientId: `ioBroker.zwavews_${Math.random().toString(16).slice(2, 8)}`,
106
+ clean: true,
107
+ reconnectPeriod: 500,
108
+ },
109
+ );
110
+ }
111
+
112
+ // MQTT Client
113
+ mqttClient.on("connect", () => {
114
+ this.log.info(`Connect to zwavews over ${this.config.connectionType == "exmqtt" ? "external mqtt" : "internal mqtt"} connection.`);
115
+ this.setState("info.connection", true, true);
116
+ });
117
+
118
+ mqttClient.subscribe(`${this.config.baseTopic}/#`);
119
+
120
+ mqttClient.on("message", (topic, payload) => {
121
+ const newMessage = `{"payload":${payload.toString() == "" ? '"null"' : payload.toString()},"topic":"${topic.slice(topic.search("/") + 1)}"}`;
122
+ this.messageParse(newMessage);
123
+ });
124
+ } else if (this.config.connectionType == 'ws') {
125
+ // Websocket
126
+ if (this.config.wsServerIP == '') {
127
+ this.log.warn('Please configure the Websoket connection!');
128
+ return;
129
+ }
130
+
131
+ // Dummy MQTT-Server
132
+ if (this.config.dummyMqtt == true) {
133
+ mqttServerController = new MqttServerController(this);
134
+ await mqttServerController.createDummyMQTTServer();
135
+ this.setState("info.connection", true, true);
136
+ await this.delay(1500);
137
+ }
138
+
139
+ this.startWebsocket();
140
+ }
141
+ }
142
+
143
+ startWebsocket() {
144
+ websocketController = new WebsocketController(this);
145
+ const wsClient = websocketController.initWsClient();
146
+
147
+ if (wsClient) {
148
+ wsClient.on('open', () => {
149
+ this.log.info('Connect to Zigbee2MQTT over websocket connection.');
150
+ startListening = true;
151
+ websocketController.send(JSON.stringify({command: "start_listening"}));
152
+ });
153
+
154
+ wsClient.on('message', (message) => {
155
+ this.messageParse(message);
156
+ });
157
+
158
+ wsClient.on('close', async () => {
159
+ this.setStateChanged('info.connection', false, true);
160
+ await statesController.setAllAvailableToFalse();
161
+ startListening = false;
162
+ deviceCache = [];
163
+ nodeCache = [];
164
+ this.log.info('Websocket connection closed. Attempting to reconnect...');
165
+ });
166
+ }
167
+ }
168
+
169
+ async messageParse(message) {
170
+ // Mutex lock: queue up calls to messageParse
171
+ let release;
172
+ const lock = new Promise((resolve) => (release = resolve));
173
+ const prev = messageParseMutex;
174
+ messageParseMutex = lock;
175
+ await prev;
176
+
177
+ try {
178
+ const messageObj = JSON.parse(message);
179
+ const type = messageObj?.type;
180
+
181
+ if (this.config.connectionType === 'ws') {
182
+ switch (type) {
183
+ case 'version': { // say hello
184
+ this.setStateChanged('info.connection', true, true);
185
+ this.setStateChanged('info.zwave_gateway_version', messageObj.driverVersion, true);
186
+ this.setStateChanged('info.zwave_gateway_status', 'online', true);
187
+ break;
188
+ }
189
+ case 'result': {
190
+ if (messageObj.result?.success === true) {
191
+ this.setStateChanged('info.debugmessages', JSON.stringify(messageObj), true);
192
+ break;
193
+ }
194
+
195
+ driver = messageObj.result.state.driver;
196
+ controller = messageObj.result.state.controller;
197
+ allNodes = messageObj.result.state.nodes;
198
+
199
+ for (const nodeData of allNodes) {
200
+ const nodeId = utils.formatNodeId(nodeData.nodeId);
201
+ if (!nodeCache[nodeId]) {
202
+ if (this.config.showNodeInfoMessage) {
203
+ this.log.info(`Node Info Update for ${nodeId}`);
204
+ }
205
+ nodeCache[nodeId] = {nodeId: nodeId};
206
+ }
207
+ await helper.createNode(`${nodeId}`, nodeData, options);
208
+ }
209
+
210
+ if (startListening) {
211
+ websocketController.send(JSON.stringify({command: "start_listening"}));
212
+ startListening = false;
213
+ }
214
+ break;
215
+ }
216
+ case 'event':
217
+ eventTyp = messageObj.event;
218
+
219
+ switch (eventTyp.event) {
220
+ case 'value updated': {
221
+ const nodeArg = eventTyp.args;
222
+ const nodeId = utils.formatNodeId(eventTyp.nodeId);
223
+
224
+ let parsePath = `${nodeId}.${nodeArg.commandClassName}.${nodeArg.propertyName
225
+ .replace(/[^\p{L}\p{N}\s]/gu, "")
226
+ .replace(/\s+/g, " ")
227
+ .trim()}`;
228
+ if (nodeArg?.propertyKeyName) {
229
+ parsePath = `${parsePath}.${nodeArg.propertyKeyName
230
+ .replace(/[^\p{L}\p{N}\s]/gu, "")
231
+ .replace(/\s+/g, " ")
232
+ .trim()}`;
233
+
234
+ if (constant.RGB.includes(nodeArg.propertyKeyName)) {
235
+ parsePath = utils.replaceLastDot(parsePath);
236
+ }
237
+ }
238
+
239
+ if (nodeArg.commandClass === 119) { // sonderlocke für node naming
240
+ switch (nodeArg.property) {
241
+ case 'name':
242
+ await helper.updateDevice(nodeId, nodeArg);
243
+ parsePath = `${nodeId}.info.${nodeArg.property}`;
244
+ break;
245
+ case 'location':
246
+
247
+ break;
248
+ default:
249
+ parsePath = `${nodeId}.info.${nodeArg.property}`;
250
+ break;
251
+ }
252
+ }
253
+
254
+ this.log.debug(`${parsePath} ->> ${nodeArg.newValue}`);
255
+
256
+ if (parsePath.includes('firmwareVersions')) { // noderlocke damit array werte gespeichert werden
257
+ parsePath = `${parsePath }_value`;
258
+ }
259
+
260
+ await helper.parse(`${parsePath}`, nodeArg.newValue, options);
261
+
262
+ break;
263
+ }
264
+
265
+ case 'firmware update progress': {
266
+ const total = Number(eventTyp.totalFragments) || 0;
267
+ const sent = Number(eventTyp.sentFragments) || 0;
268
+
269
+ const progress = total > 0 ? Math.min(100, Math.max(0, (sent / total) * 100)) : 0;
270
+
271
+ this.log.info(
272
+ `Firmware update progress for ${utils.formatNodeId(eventTyp.nodeId)} ->> ` + `send Fragments ${sent} -- total ${total}
273
+ (${progress.toFixed(1)}%)`);
274
+ break;
275
+ }
276
+ case 'firmware update finished': {
277
+ this.log.info(`${utils.formatNodeId(eventTyp.nodeId)} --> ${eventTyp.event}`);
278
+ break;
279
+ }
280
+ case 'ready':
281
+ case 'sleep':
282
+ case 'wake up':
283
+ case 'alive':
284
+ case 'dead': {
285
+ if (this.config.wakeUpInfo) {
286
+ this.log.info(`${utils.formatNodeId(eventTyp.nodeId)} --> ${eventTyp.event}`);
287
+ }
288
+ break;
289
+ }
290
+
291
+ case 'node removed': {
292
+ const nodeId = utils.formatNodeId(eventTyp.nodeId);
293
+ const nodeArg = {name : 'Node is Deleted'};
294
+ await helper.updateDevice(nodeId, nodeArg);
295
+ this.log.error(`Delete ${utils.formatNodeId(eventTyp.nodeId)}`);
296
+ break;
297
+ }
298
+
299
+ case 'statistics updated':
300
+ case 'metadata updated':
301
+ case 'value added':
302
+ case 'node info received':
303
+ case 'interview started':
304
+ case 'interview stage completed':
305
+ case 'interview failed':
306
+ break;
307
+ default:
308
+ if (this.config.newTypeEvent) {
309
+ this.log.warn(`New type event ->> ${eventTyp.event}`);
310
+ this.log.warn(JSON.stringify(messageObj));
311
+ }
312
+ break;
313
+ }
314
+
315
+ break;
316
+ default:
317
+ break;
318
+ }
319
+ }
320
+ } catch (err) {
321
+ this.log.error(err);
322
+ this.log.error(`<zwavews> error message -->> ${message}`);
323
+ } finally {
324
+ release();
325
+ }
326
+ }
327
+
328
+ async onUnload(callback) {
329
+ // Close MQTT connections
330
+ if (["exmqtt", "intmqtt"].includes(this.config.connectionType)) {
331
+ if (mqttClient && !mqttClient.closed) {
332
+ try {
333
+ if (mqttClient) {
334
+ mqttClient.end();
335
+ }
336
+ } catch (e) {
337
+ this.log.error(e);
338
+ }
339
+ }
340
+ }
341
+ // Internal or Dummy MQTT-Server
342
+ if (this.config.connectionType == "intmqtt" || this.config.dummyMqtt == true) {
343
+ try {
344
+ if (mqttServerController) {
345
+ mqttServerController.closeServer();
346
+ }
347
+ } catch (e) {
348
+ this.log.error(e);
349
+ }
350
+ }
351
+ // Set all device available states of false
352
+ try {
353
+ if (statesController) {
354
+ await statesController.setAllAvailableToFalse();
355
+ }
356
+ } catch (e) {
357
+ this.log.error(e);
358
+ }
359
+
360
+ this.setState("info.connection", false, true);
361
+
362
+ callback();
363
+ }
364
+
365
+ async onStateChange(id, state) {
366
+ if (state && state.ack == false) {
367
+ if (id.endsWith("info.debugId")) {
368
+ logCustomizations.debugDevices = state.val.toLowerCase();
369
+ this.setState(id, state.val, true);
370
+ return;
371
+ }
372
+
373
+ let message;
374
+ const obj = await this.getObjectAsync(id);
375
+ if (obj) {
376
+ const nativeObj= obj.native || {};
377
+
378
+ const m = id.match(/nodeID_0*(\d+)/i);
379
+ const nodeId = m ? Number(m[1]) : null;
380
+
381
+ message = {
382
+ messageId: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
383
+ command: "node.set_value",
384
+ nodeId: nodeId,
385
+ valueId: nativeObj.valueId,
386
+ value: state.val
387
+ }
388
+ }
389
+ this.setStateChanged('info.debugmessages', JSON.stringify(message), true);
390
+
391
+ this.log.debug(`<zwavews> error message ${message}`);
392
+
393
+ websocketController.send(JSON.stringify(message));
394
+ }
395
+ }
396
+ }
397
+
398
+ if (require.main !== module) {
399
+ // Export the constructor in compact mode
400
+ /**
401
+ * @param {Partial<core.AdapterOptions>} [options]
402
+ */
403
+ module.exports = (options) => new zwavews(options);
404
+ } else {
405
+ // otherwise start the instance directly
406
+ new zwavews();
407
+ }
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "iobroker.zwavews",
3
+ "version": "0.0.3",
4
+ "description": "zwavews adapter for ioBroker",
5
+ "author": {
6
+ "name": "Dennis Rathjen and Arthur Rupp",
7
+ "email": "arteck@outlook.com"
8
+ },
9
+ "homepage": "https://github.com/arteck/ioBroker.zwavews",
10
+ "license": "MIT",
11
+ "keywords": [
12
+ "zwavews",
13
+ "zwave",
14
+ "zwave-js-ui",
15
+ "ioBroker",
16
+ "template",
17
+ "Smart Home",
18
+ "home automation"
19
+ ],
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/arteck/ioBroker.zwavews.git"
23
+ },
24
+ "engines": {
25
+ "node": ">= 20"
26
+ },
27
+ "dependencies": {
28
+ "@iobroker/adapter-core": "^3.3.2",
29
+ "@iobroker/dm-utils": "^1.0.16",
30
+ "aedes": "^0.51.3",
31
+ "aedes-persistence-nedb": "^2.0.3",
32
+ "mqtt": "^5.14.1",
33
+ "net": "^1.0.2",
34
+ "node-schedule": "^2.1.1",
35
+ "sharp": "^0.34.5",
36
+ "ws": "^8.19.0"
37
+ },
38
+ "devDependencies": {
39
+ "@alcalzone/release-script": "^5.0.0",
40
+ "@alcalzone/release-script-plugin-iobroker": "^4.0.0",
41
+ "@alcalzone/release-script-plugin-license": "^4.0.0",
42
+ "@alcalzone/release-script-plugin-manual-review": "^4.0.0",
43
+ "@iobroker/adapter-dev": "^1.5.0",
44
+ "@iobroker/testing": "^5.2.2",
45
+ "@iobroker/eslint-config": "^2.1.0",
46
+ "@tsconfig/node14": "^14.1.8",
47
+ "@types/node": "^25.0.3",
48
+ "@types/node-schedule": "^2.1.8",
49
+ "typescript": "~5.9.2"
50
+ },
51
+ "main": "main.js",
52
+ "files": [
53
+ "admin{,/!(src)/**}/!(tsconfig|tsconfig.*|.eslintrc).json",
54
+ "admin{,/!(src)/**}/*.{html,css,png,svg,jpg,js}",
55
+ "lib/",
56
+ "www/",
57
+ "io-package.json",
58
+ "LICENSE",
59
+ "main.js"
60
+ ],
61
+ "scripts": {
62
+ "test:js": "mocha --config test/mocharc.custom.json \"{!(node_modules|test)/**/*.test.js,*.test.js,test/**/test!(PackageFiles|Startup).js}\"",
63
+ "test:package": "mocha test/package --exit",
64
+ "test:integration": "mocha test/integration --exit",
65
+ "test": "npm run test:js && npm run test:package",
66
+ "check": "tsc --noEmit -p tsconfig.check.json",
67
+ "lint": "eslint .",
68
+ "translate": "translate-adapter",
69
+ "release": "release-script"
70
+ },
71
+ "bugs": {
72
+ "url": "https://github.com/arteck/ioBroker.zwavews/issues"
73
+ },
74
+ "readmeFilename": "README.md"
75
+ }