iobroker.sonoff 3.3.0 → 4.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.
Files changed (46) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +96 -170
  3. package/admin/i18n/{de/translations.json → de.json} +37 -12
  4. package/admin/i18n/{en/translations.json → en.json} +37 -12
  5. package/admin/i18n/{es/translations.json → es.json} +37 -12
  6. package/admin/i18n/{fr/translations.json → fr.json} +37 -12
  7. package/admin/i18n/{it/translations.json → it.json} +37 -12
  8. package/admin/i18n/{nl/translations.json → nl.json} +37 -12
  9. package/admin/i18n/{pl/translations.json → pl.json} +37 -12
  10. package/admin/i18n/{pt/translations.json → pt.json} +37 -12
  11. package/admin/i18n/{ru/translations.json → ru.json} +37 -12
  12. package/admin/i18n/{uk/translations.json → uk.json} +37 -12
  13. package/admin/i18n/{zh-cn/translations.json → zh-cn.json} +37 -12
  14. package/admin/jsonConfig.json +427 -228
  15. package/build/lib/bridge.js +512 -0
  16. package/build/lib/bridge.js.map +1 -0
  17. package/build/lib/checkStates.js +68 -0
  18. package/build/lib/checkStates.js.map +1 -0
  19. package/{lib → build/lib}/datapoints.js +61 -1
  20. package/build/lib/datapoints.js.map +1 -0
  21. package/build/lib/deviceManager.js +898 -0
  22. package/build/lib/deviceManager.js.map +1 -0
  23. package/build/lib/i18n/de.json +61 -0
  24. package/build/lib/i18n/en.json +61 -0
  25. package/build/lib/i18n/es.json +61 -0
  26. package/build/lib/i18n/fr.json +61 -0
  27. package/build/lib/i18n/it.json +61 -0
  28. package/build/lib/i18n/nl.json +61 -0
  29. package/build/lib/i18n/pl.json +61 -0
  30. package/build/lib/i18n/pt.json +61 -0
  31. package/build/lib/i18n/ru.json +61 -0
  32. package/build/lib/i18n/uk.json +61 -0
  33. package/build/lib/i18n/zh-cn.json +61 -0
  34. package/build/lib/mqttBase.js +1308 -0
  35. package/build/lib/mqttBase.js.map +1 -0
  36. package/build/lib/server.js +485 -0
  37. package/build/lib/server.js.map +1 -0
  38. package/build/main.js +110 -0
  39. package/build/main.js.map +1 -0
  40. package/io-package.json +47 -30
  41. package/package.json +26 -21
  42. package/admin/index.html +0 -177
  43. package/admin/index_m.html +0 -218
  44. package/admin/words.js +0 -75
  45. package/lib/server.js +0 -2086
  46. package/main.js +0 -104
package/lib/server.js DELETED
@@ -1,2086 +0,0 @@
1
- 'use strict';
2
-
3
- const net = require('node:net');
4
- const mqtt = require('mqtt-connection');
5
- const types = require('./datapoints');
6
- // const memwatch = require('memwatch-next');
7
- // const heapdump = require('heapdump');
8
- const hueCalc = true;
9
- // var heapTest = 0;
10
- const FORBIDDEN_CHARS = /[\]\\[*,;'"`<>\\?]/g;
11
- const mappingClients = {};
12
-
13
- /*
14
- * HSV to RGB color conversion
15
- *
16
- * H runs from 0 to 360 degrees
17
- * S and V run from 0 to 100
18
- *
19
- * Ported from the excellent java algorithm by Eugene Vishnevsky at:
20
- * http://www.cs.rit.edu/~ncs/color/t_convert.html
21
- */
22
- function hsvToRgb(h, s, v) {
23
- let r, g, b;
24
- let i;
25
- let f, p, q, t;
26
-
27
- // Make sure our arguments stay in-range
28
- h = Math.max(0, Math.min(360, h));
29
- s = Math.max(0, Math.min(100, s));
30
- v = Math.max(0, Math.min(100, v));
31
-
32
- // We accept saturation and value arguments from 0 to 100 because that's
33
- // how Photoshop represents those values. Internally, however, the
34
- // saturation and value are calculated from a range of 0 to 1. We make
35
- // That conversion here.
36
- s /= 100;
37
- v /= 100;
38
-
39
- if (s === 0) {
40
- // Achromatic (grey)
41
- r = g = b = v;
42
- return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
43
- }
44
-
45
- h /= 60; // sector 0 to 5
46
- i = Math.floor(h);
47
- f = h - i; // factorial part of h
48
- p = v * (1 - s);
49
- q = v * (1 - s * f);
50
- t = v * (1 - s * (1 - f));
51
-
52
- switch (i) {
53
- case 0:
54
- r = v;
55
- g = t;
56
- b = p;
57
- break;
58
-
59
- case 1:
60
- r = q;
61
- g = v;
62
- b = p;
63
- break;
64
-
65
- case 2:
66
- r = p;
67
- g = v;
68
- b = t;
69
- break;
70
-
71
- case 3:
72
- r = p;
73
- g = q;
74
- b = v;
75
- break;
76
-
77
- case 4:
78
- r = t;
79
- g = p;
80
- b = v;
81
- break;
82
-
83
- default: // case 5:
84
- r = v;
85
- g = p;
86
- b = q;
87
- }
88
-
89
- return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
90
- }
91
-
92
- function componentToHex(c) {
93
- const hex = c.toString(16);
94
- return hex.length === 1 ? `0${hex}` : hex;
95
- }
96
-
97
- function toPaddedHexString(num, len) {
98
- if (len === 2) {
99
- if (num > 255) {
100
- num = 255;
101
- }
102
- }
103
- const str = num.toString(16);
104
- if (str.length >= len) {
105
- return str;
106
- }
107
- return '0'.repeat(len - str.length) + str;
108
- }
109
-
110
- /**
111
- * MQTT Server constructor
112
- *
113
- * @param {object} adapter - The ioBroker adapter instance
114
- */
115
- function MQTTServer(adapter) {
116
- if (!(this instanceof MQTTServer)) {
117
- return new MQTTServer(adapter);
118
- }
119
-
120
- const NO_PREFIX = '';
121
-
122
- let server = new net.Server();
123
- const clients = {};
124
- const tasks = [];
125
- let taskCallbacks = [];
126
- let messageId = 1;
127
- let persistentSessions = {};
128
- let resending = false;
129
- let resendTimer = null;
130
-
131
- const cacheAddedObjects = {};
132
- const cachedModeExor = {};
133
- const cachedReadColors = {};
134
-
135
- this.destroy = cb => {
136
- if (resendTimer) {
137
- clearInterval(resendTimer);
138
- resendTimer = null;
139
- }
140
-
141
- if (server) {
142
- let cnt = 0;
143
- Object.keys(clients).forEach(id => {
144
- cnt++;
145
- adapter.setForeignState(`${adapter.namespace}.${clients[id].iobId}.alive`, false, true, () => {
146
- if (!--cnt) {
147
- // to release all resources
148
- server.close(() => cb && cb());
149
- server = null;
150
- }
151
- });
152
- });
153
-
154
- if (!cnt) {
155
- // to release all resources
156
- server.close(() => cb && cb());
157
- server = null;
158
- }
159
- } else {
160
- cb && cb();
161
- }
162
- };
163
-
164
- /*
165
- memwatch.on('leak', (info) => {
166
- //console.error('Memory leak detected:\n', info);
167
- adapter.log.info('Memory leak detected:\n', info);
168
- var filename='/opt/iobroker/dumps/' + Date.now() + '.heapsnapshot';
169
- heapdump.writeSnapshot((err,filename) => {
170
- if (err) {
171
- console.error(err);
172
- }
173
- else console.error('Wrote snapshot: ' + filename);
174
- });
175
- });
176
- */
177
- /*
178
- memwatch.on('stats', function(stats) {
179
- adapter.log.info("stats:",stats);
180
- });
181
- */
182
-
183
- function setColor(channelId, val) {
184
- //adapter.log.info('color write: '+ val);
185
- const stateId = 'Color';
186
- if (clients[channelId] && clients[channelId]._map && clients[channelId]._map[stateId]) {
187
- setImmediate(
188
- sendState2Client,
189
- clients[channelId],
190
- clients[channelId]._map[stateId] || 'cmnd/sonoff/Color',
191
- val,
192
- adapter.config.defaultQoS,
193
- );
194
- } else if (clients[channelId] && clients[channelId]._fallBackName) {
195
- setImmediate(
196
- sendState2Client,
197
- clients[channelId],
198
- `cmnd/${clients[channelId]._fallBackName}/${stateId}`,
199
- val,
200
- adapter.config.defaultQoS,
201
- );
202
- } else {
203
- adapter.log.warn(`Unknown mapping for "${stateId}"`);
204
- }
205
- }
206
-
207
- function setPower(channelId, val) {
208
- const stateId = 'POWER';
209
- if (val === '' || val === null || val === undefined) {
210
- return adapter.log.debug('Empty power was ignored');
211
- }
212
- if (clients[channelId]._map && clients[channelId]._map[stateId]) {
213
- setImmediate(
214
- sendState2Client,
215
- clients[channelId],
216
- clients[channelId]._map[stateId] || 'cmnd/sonoff/POWER',
217
- val ? 'ON' : 'OFF',
218
- adapter.config.defaultQoS,
219
- );
220
- } else if (clients[channelId]._fallBackName) {
221
- setImmediate(
222
- sendState2Client,
223
- clients[channelId],
224
- `cmnd/${clients[channelId]._fallBackName}/${stateId}`,
225
- val,
226
- adapter.config.defaultQoS,
227
- );
228
- } else {
229
- adapter.log.warn(`Unknown mapping for "${stateId}"`);
230
- }
231
- }
232
-
233
- function setZbSendCommand(channelId, deviceId, attribute, val) {
234
- // Send Zigbee command via ZbSend
235
- const zbSendCommand = JSON.stringify({
236
- device: deviceId,
237
- send: {
238
- [attribute]: val,
239
- },
240
- });
241
-
242
- if (clients[channelId] && clients[channelId]._fallBackName) {
243
- setImmediate(
244
- sendState2Client,
245
- clients[channelId],
246
- `cmnd/${clients[channelId]._fallBackName}/ZbSend`,
247
- zbSendCommand,
248
- adapter.config.defaultQoS,
249
- );
250
- } else {
251
- adapter.log.warn(`Cannot send ZbSend command for device ${deviceId}: client not found`);
252
- }
253
- }
254
-
255
- /**
256
- * Transform shutter state names to correct Tasmota command format
257
- * e.g., "Shutter1_Position" -> "ShutterPosition1"
258
- * e.g., "Shutter1_Tilt" -> "ShutterTilt1"
259
- *
260
- * @param {string} stateId - The original state ID to transform
261
- * @returns {string} The transformed state ID for Tasmota commands
262
- */
263
- function transformShutterStateId(stateId) {
264
- const shutterMatch = stateId.match(/^Shutter(\d+)_(Position|Direction|Target|Tilt)$/);
265
- if (shutterMatch) {
266
- const shutterNumber = shutterMatch[1];
267
- const command = shutterMatch[2];
268
- return `Shutter${command}${shutterNumber}`;
269
- }
270
- return stateId;
271
- }
272
-
273
- function setStateImmediate(channelId, stateId, val) {
274
- // Transform shutter state names to correct Tasmota command format
275
- const transformedStateId = transformShutterStateId(stateId);
276
-
277
- if (clients[channelId] && clients[channelId]._map && clients[channelId]._map[stateId]) {
278
- setImmediate(
279
- sendState2Client,
280
- clients[channelId],
281
- clients[channelId]._map[stateId] || `cmnd/sonoff/${transformedStateId}`,
282
- val,
283
- adapter.config.defaultQoS,
284
- );
285
- } else if (clients[channelId] && clients[channelId]._fallBackName) {
286
- setImmediate(
287
- sendState2Client,
288
- clients[channelId],
289
- `cmnd/${clients[channelId]._fallBackName}/${transformedStateId}`,
290
- val,
291
- adapter.config.defaultQoS,
292
- );
293
- } else {
294
- adapter.log.warn(`Unknown mapping for "${stateId}"`);
295
- }
296
- }
297
-
298
- function _setState(id, val, callback) {
299
- adapter.log.debug(`Set state after task: ${id}`);
300
- adapter.setForeignState(id, val, true, callback);
301
- }
302
-
303
- function updateState(task, val, callback) {
304
- if (typeof val === 'function') {
305
- callback = val;
306
- val = undefined;
307
- }
308
- if (val !== undefined) {
309
- task.cb = _setState;
310
- task.cbArg = val;
311
- }
312
- tasks.push(task);
313
-
314
- if (callback) {
315
- taskCallbacks.push(callback);
316
- }
317
- adapter.log.debug(`Update state ${task.id} - ${tasks.length}`);
318
- if (tasks.length === 1) {
319
- processTasks();
320
- }
321
- }
322
-
323
- const specVars = [
324
- 'Red',
325
- 'Green',
326
- 'Blue',
327
- 'WW',
328
- 'CW',
329
- 'Color',
330
- 'RGB_POWER',
331
- 'WW_POWER',
332
- 'CW_POWER',
333
- 'Hue',
334
- 'Saturation',
335
- ];
336
- function onStateChangedColors(id, state, channelId, stateId) {
337
- if (!channelId) {
338
- let parts = id.split('.');
339
- stateId = parts.pop();
340
-
341
- if (
342
- stateId === 'level' ||
343
- stateId === 'state' ||
344
- stateId === 'red' ||
345
- stateId === 'blue' ||
346
- stateId === 'green'
347
- ) {
348
- stateId = `${parts.pop()}.${stateId}`;
349
- }
350
-
351
- channelId = parts.splice(2, parts.length).join('.');
352
- }
353
- const ledModeIdExor = `${adapter.namespace}.${channelId}.modeLedExor`;
354
- if (cachedModeExor[ledModeIdExor] === undefined) {
355
- return adapter.getForeignState(ledModeIdExor, (err, _state) => {
356
- cachedModeExor[ledModeIdExor] = _state ? _state.val || false : true;
357
- setImmediate(() => onStateChangedColors(id, state, channelId, stateId));
358
- });
359
- }
360
-
361
- // ledstripe objects
362
- const exorWhiteLeds = cachedModeExor[ledModeIdExor]; // exor for white leds and color leds => if white leds are switched on, color leds are switched off and vice versa (default on)
363
-
364
- // now evaluate ledstripe vars
365
- // adaptions for magichome tasmota
366
- if (stateId.match(/Color\d?/)) {
367
- //adapter.log.info('sending color');
368
- // id = sonoff.0.DVES_96ABFA.Color
369
- // statid=Color
370
- // state = {"val":"#faadcf","ack":false,"ts":1520146102580,"q":0,"from":"system.adapter.web.0","lc":1520146102580}
371
-
372
- // set white to rgb or rgbww
373
- adapter.getObject(id, (err, obj) => {
374
- if (!obj) {
375
- adapter.log.warn(`Invalid rgbww obj for ${id}`);
376
- } else if (typeof state.val !== 'string') {
377
- adapter.log.warn(
378
- `Invalid rgbww state value for ${id} : ${JSON.stringify(state.val)} needs to be a string`,
379
- );
380
- } else {
381
- const role = obj.common.role;
382
- let color;
383
-
384
- //adapter.log.info(state.val);
385
- if (role === 'level.color.rgbww') {
386
- // rgbww
387
- if (state.val.toUpperCase() === '#FFFFFF') {
388
- // transform white to WW
389
- //color='000000FF';
390
- color = `${state.val.substring(1)}00`;
391
- } else {
392
- // strip # char and add ww
393
- color = `${state.val.substring(1)}00`;
394
- }
395
- } else if (role === 'level.color.rgbcwww') {
396
- color = `${state.val.substring(1)}0000`;
397
- } else {
398
- // rgb, strip # char
399
- color = state.val.substring(1);
400
- }
401
-
402
- //adapter.log.info('color :' + color + ' : ' + role);
403
- // strip # char
404
- //color=state.val.substring(1);
405
-
406
- setColor(channelId, color);
407
-
408
- // set rgb too
409
- const hidE = id.split('.');
410
- const deviceDesc = `${hidE[0]}.${hidE[1]}.${hidE[2]}`;
411
- let idAlive = `${deviceDesc}.Red`;
412
- adapter.setState(idAlive, (100 * parseInt(color.substring(0, 2), 16)) / 255, true);
413
-
414
- idAlive = `${deviceDesc}.Green`;
415
- adapter.setState(idAlive, (100 * parseInt(color.substring(2, 4), 16)) / 255, true);
416
-
417
- idAlive = `${deviceDesc}.Blue`;
418
- adapter.setState(idAlive, (100 * parseInt(color.substring(4, 6), 16)) / 255, true);
419
- }
420
- });
421
- } else {
422
- const hidE = id.split('.');
423
- const deviceDesc = `${hidE[0]}.${hidE[1]}.${hidE[2]}`;
424
- if (stateId.match(/Red\d?/)) {
425
- // set red component
426
- if (state.val > 100) {
427
- state.val = 100;
428
- }
429
- const red = toPaddedHexString(Math.floor((255 * state.val) / 100), 2);
430
- const idAlive = `${deviceDesc}.Color`;
431
- adapter.getForeignState(idAlive, (err, state) => {
432
- if (!state) {
433
- adapter.setState(idAlive, '#000000', false);
434
- return;
435
- }
436
- const color = state.val.substring(1);
437
- // replace red component
438
- const out = red + color.substring(2, 10);
439
- adapter.setState(idAlive, `#${out}`, false);
440
- setColor(channelId, out);
441
- });
442
- } else if (stateId.match(/Green\d?/)) {
443
- // set green component
444
- if (state.val > 100) {
445
- state.val = 100;
446
- }
447
- const green = toPaddedHexString(Math.floor((255 * state.val) / 100), 2);
448
- const idAlive = `${deviceDesc}.Color`;
449
- adapter.getForeignState(idAlive, (err, state) => {
450
- if (!state) {
451
- adapter.setState(idAlive, '#000000', false);
452
- return;
453
- }
454
- const color = state.val.substring(1);
455
- // replace green component
456
- const out = color.substring(0, 2) + green + color.substring(4, 10);
457
- adapter.setState(idAlive, `#${out}`, false);
458
- setColor(channelId, out);
459
- });
460
- } else if (stateId.match(/Blue\d?/)) {
461
- // set blue component
462
- if (state.val > 100) {
463
- state.val = 100;
464
- }
465
- const blue = toPaddedHexString(Math.floor((255 * state.val) / 100), 2);
466
- const idAlive = `${deviceDesc}.Color`;
467
- adapter.getForeignState(idAlive, (err, state) => {
468
- if (!state) {
469
- adapter.setState(idAlive, '#000000', false);
470
- return;
471
- }
472
- const color = state.val.substring(1);
473
- // replace blue component
474
- const out = color.substring(0, 4) + blue + color.substring(6, 10);
475
- adapter.setState(idAlive, `#${out}`, false);
476
- setColor(channelId, out);
477
- });
478
- } else if (stateId.match(/RGB_POWER\d?/)) {
479
- // set ww component
480
- const rgbpow = state.val === 'true' || state.val === true || state.val === 1 || state.val === '1';
481
- const idAlive = `${deviceDesc}.Color`;
482
- adapter.getForeignState(idAlive, (err, state) => {
483
- if (!state || typeof state.val !== 'string') {
484
- adapter.log.info(`Invalid state color for ${idAlive}, correcting value to #000000`);
485
- adapter.setState(idAlive, '#000000', false);
486
- return;
487
- }
488
- const color = state.val.substring(1);
489
- let rgb = '000000';
490
- if (rgbpow === true) {
491
- rgb = 'FFFFFF';
492
- }
493
- // replace rgb component
494
- let out = rgb + color.substring(6, 10);
495
-
496
- if (rgbpow && exorWhiteLeds) {
497
- //adapter.log.info('reset white');
498
- out = `${rgb}0000`;
499
- let idAlive = `${deviceDesc}.WW_POWER`;
500
- adapter.setState(idAlive, false, false);
501
- idAlive = `${deviceDesc}.WW`;
502
- adapter.setState(idAlive, 0, false);
503
- idAlive = `${deviceDesc}.CW_POWER`;
504
- adapter.setState(idAlive, false, false);
505
- idAlive = `${deviceDesc}.CW`;
506
- adapter.setState(idAlive, 0, false);
507
- }
508
- setColor(channelId, out);
509
- adapter.setState(idAlive, `#${out}`, false);
510
- if (rgbpow) {
511
- setPower(channelId, true);
512
- }
513
- // if led_mode&1, exor white leds
514
- });
515
- } else if (hueCalc && stateId.match(/Hue\d?/)) {
516
- // calc hue + saturation params to rgb
517
- let hue = state.val;
518
- if (hue > 359) {
519
- hue = 359;
520
- }
521
- // recalc color by hue
522
- const idAlive = `${deviceDesc}.Dimmer`;
523
- adapter.getForeignState(idAlive, (err, state) => {
524
- if (!state) {
525
- const dim = 100;
526
- adapter.setState(idAlive, dim, true);
527
- //adapter.log.warn('ill state Dimmer');
528
- } else {
529
- const dim = state.val;
530
- let idAlive = `${deviceDesc}.Saturation`;
531
- adapter.getForeignState(idAlive, (err, state) => {
532
- if (!state) {
533
- const sat = 100;
534
- adapter.setState(idAlive, sat, true);
535
- } else {
536
- const sat = state.val;
537
- const rgb = hsvToRgb(hue, sat, dim);
538
- const hexVal = componentToHex(rgb[0]) + componentToHex(rgb[1]) + componentToHex(rgb[2]);
539
- let idAlive = `${deviceDesc}.Color`;
540
- adapter.setState(idAlive, `#${hexVal}`, false);
541
- }
542
- });
543
- }
544
- });
545
- } else if (hueCalc && stateId.match(/Saturation\d?/)) {
546
- let sat = state.val;
547
- if (sat > 100) {
548
- sat = 100;
549
- }
550
- // recalc color by saturation
551
- const idAlive = `${deviceDesc}.Dimmer`;
552
- adapter.getForeignState(idAlive, (err, state) => {
553
- if (!state) {
554
- const dim = 100;
555
- adapter.setState(idAlive, dim, true);
556
- //adapter.log.warn('ill state Dimmer');
557
- } else {
558
- const dim = state.val;
559
- const idAlive = `${deviceDesc}.Hue`;
560
- adapter.getForeignState(idAlive, (err, state) => {
561
- if (!state) {
562
- const hue = 100;
563
- adapter.setState(idAlive, hue, true);
564
- } else {
565
- const hue = state.val;
566
- const rgb = hsvToRgb(hue, sat, dim);
567
- const hexVal = componentToHex(rgb[0]) + componentToHex(rgb[1]) + componentToHex(rgb[2]);
568
- const idAlive = `${deviceDesc}.Color`;
569
- adapter.setState(idAlive, `#${hexVal}`, false);
570
- }
571
- });
572
- }
573
- });
574
- } else {
575
- // get color attributes to check other ledstripe vars
576
- const idAlive = `${deviceDesc}.Color`;
577
- adapter.getForeignObject(idAlive, (err, obj) => {
578
- if (!obj) {
579
- // no color object
580
- adapter.log.warn(`Unknown object: ${id}: ${state}`);
581
- } else {
582
- const role = obj.common.role;
583
- //if (role='level.color.rgb') return;
584
- let wwindex;
585
- if (role === 'level.color.rgbww') {
586
- wwindex = 6;
587
- } else {
588
- wwindex = 8;
589
- }
590
-
591
- if (stateId.match(/WW_POWER\d?/)) {
592
- // set ww component
593
- const wwpow =
594
- state.val === 'true' || state.val === true || state.val === 1 || state.val === '1';
595
- const idAlive = `${deviceDesc}.Color`;
596
- adapter.getForeignState(idAlive, (err, state) => {
597
- if (!state) {
598
- adapter.log.warn('ill state color');
599
- return;
600
- }
601
- const color = state.val.substring(1);
602
- let ww = '00';
603
- if (wwpow) {
604
- ww = 'FF';
605
- }
606
- // replace ww component
607
- let out = color.substring(0, wwindex) + ww;
608
- if (wwpow && exorWhiteLeds) {
609
- //adapter.log.info('reset white');
610
- out = `000000${ww}`;
611
- let idAlive = `${deviceDesc}.RGB_POWER`;
612
- adapter.setState(idAlive, false, false);
613
- }
614
-
615
- let idAlive = `${deviceDesc}.Color`;
616
- adapter.setState(idAlive, `#${out}`, false);
617
- setColor(channelId, out);
618
-
619
- // set ww channel
620
- idAlive = `${deviceDesc}.WW`;
621
- adapter.setState(idAlive, (100 * parseInt(out.substring(6, 8), 16)) / 255, true);
622
-
623
- // in case POWER is off, switch it on
624
- wwpow && setPower(channelId, true);
625
- });
626
- } else if (stateId.match(/CW_POWER\d?/)) {
627
- // set ww component
628
- const cwpow =
629
- state.val === 'true' || state.val === true || state.val === 1 || state.val === '1';
630
- const idAlive = `${deviceDesc}.Color`;
631
- adapter.getForeignState(idAlive, (err, state) => {
632
- if (!state) {
633
- adapter.log.warn('ill state color');
634
- return;
635
- }
636
- const color = state.val.substring(1);
637
- let cw = '00';
638
- if (cwpow) {
639
- cw = 'FF';
640
- }
641
- // replace cw component
642
- let out = color.substring(0, 6) + cw + color.substring(8, 10);
643
- if (cwpow && exorWhiteLeds) {
644
- //adapter.log.info('reset white');
645
- out = `000000${cw}${color.substring(8, 10)}`;
646
- let idAlive = `${deviceDesc}.RGB_POWER`;
647
- adapter.setState(idAlive, false, false);
648
- }
649
-
650
- let idAlive = `${deviceDesc}.Color`;
651
- adapter.setState(idAlive, `#${out}`, false);
652
- setColor(channelId, out);
653
-
654
- // set cw channel
655
- idAlive = `${deviceDesc}.CW`;
656
- adapter.setState(idAlive, (100 * parseInt(out.substring(6, 8), 16)) / 255, true);
657
-
658
- // in case POWER is off, switch it on
659
- if (cwpow) {
660
- let idAlive = `${deviceDesc}.POWER`;
661
- adapter.setState(idAlive, true, false);
662
- }
663
- });
664
- } else if (stateId.match(/WW\d?/)) {
665
- // set ww component
666
- const ww = toPaddedHexString(Math.floor((255 * state.val) / 100), 2);
667
- const idAlive = `${deviceDesc}.Color`;
668
- adapter.getForeignState(idAlive, (err, state) => {
669
- if (!state) {
670
- adapter.setState(idAlive, '#000000', false);
671
- return;
672
- }
673
- const color = state.val.substring(1);
674
- // replace ww component
675
- const out = color.substring(0, wwindex) + ww;
676
- setColor(channelId, out);
677
- });
678
- } else if (stateId.match(/CW\d?/)) {
679
- // set ww component
680
- const cw = toPaddedHexString(Math.floor((255 * state.val) / 100), 2);
681
- const idAlive = `${deviceDesc}.Color`;
682
- adapter.getForeignState(idAlive, (err, state) => {
683
- if (!state) {
684
- adapter.setState(idAlive, '#000000', false);
685
- return;
686
- }
687
- const color = state.val.substring(1);
688
- // replace cw component
689
- const out = color.substring(0, 6) + cw + color.substring(8, 10);
690
- setColor(channelId, out);
691
- });
692
- }
693
- }
694
- });
695
- }
696
- }
697
- }
698
-
699
- this.onStateChange = (id, state) => {
700
- adapter.log.debug(`onStateChange ${id}: ${JSON.stringify(state)}`);
701
- if (server && state && !state.ack) {
702
- // find client.id
703
- let parts = id.split('.');
704
- const stateId = parts.pop();
705
- const channelId = parts.splice(2, parts.length).join('.');
706
-
707
- // Check if this is a Zigbee device state change
708
- // Pattern: ZbReceived_DEVICEID_ATTRIBUTE (e.g., ZbReceived_0x0856_Power)
709
- const zbMatch = stateId.match(/^ZbReceived_([^_]+)_(Power|Dimmer)$/);
710
- if (zbMatch && clients[mappingClients[channelId]]) {
711
- const deviceId = zbMatch[1];
712
- const attribute = zbMatch[2];
713
- let zbValue;
714
-
715
- // Convert values for Zigbee commands
716
- if (attribute === 'Power') {
717
- zbValue = state.val ? '1' : '0';
718
- } else if (attribute === 'Dimmer') {
719
- zbValue = state.val;
720
- }
721
-
722
- adapter.log.debug(`Sending ZbSend command for device ${deviceId}: ${attribute}=${zbValue}`);
723
- setZbSendCommand(mappingClients[channelId], deviceId, attribute, zbValue);
724
- return;
725
- }
726
-
727
- if (clients[mappingClients[channelId]]) {
728
- // check for special led-stripe vars
729
- if (!specVars.includes(stateId)) {
730
- // other objects
731
- adapter.getObject(id, (err, obj) => {
732
- if (!obj) {
733
- adapter.log.warn(`Invalid object ${id}`);
734
- } else {
735
- const type = obj.common.type;
736
- switch (type) {
737
- case 'boolean':
738
- setStateImmediate(mappingClients[channelId], stateId, state.val ? 'ON' : 'OFF');
739
- break;
740
- case 'number':
741
- setStateImmediate(
742
- mappingClients[channelId],
743
- stateId,
744
- state.val === null ? '' : state.val.toString(),
745
- );
746
- break;
747
- case 'string':
748
- setStateImmediate(
749
- mappingClients[channelId],
750
- stateId,
751
- state.val === null ? '' : state.val.toString(),
752
- );
753
- break;
754
- }
755
- }
756
- });
757
- } else if (state.val !== null) {
758
- onStateChangedColors(id, state, channelId, stateId);
759
- }
760
- } else {
761
- //Client:"DVES_96ABFA : MagicHome" not connected => State: sonoff.0.myState - Value: 0, ack: false, time stamp: 1520369614189, last changed: 1520369614189
762
- // if (server && state && !state.ack) {
763
- // server = false
764
- // or state = false
765
- // or state.ack = true
766
- // or clients[channelId] = false
767
- if (!adapter.config.ignoreNotConnectedWarnings) {
768
- adapter.log.info(`Client "${channelId}" not connected`);
769
- }
770
-
771
- /*
772
- if (!clients[channelId]) {
773
- var idAlive='sonoff.0.'+channelId+'.INFO.IPAddress';
774
- adapter.getForeignState(idAlive, function (err, state) {
775
- if (!state) {
776
- adapter.log.warn('Client "' + channelId + '" could not get ip adress');
777
- } else {
778
- var ip=state.val;
779
- adapter.log.warn('Clients ip "' + ip);
780
-
781
- request('http://'+ip+'/cm?cmnd=Restart 1', function(error, response, body) {
782
- if (error || response.statusCode !== 200) {
783
- log('Fehler beim Neustart von Sonoff: ' + channelId + ' (StatusCode = ' + response.statusCode + ')');
784
- }
785
- });
786
- }
787
- });
788
- }*/
789
- }
790
- }
791
- };
792
-
793
- function processTasks(callback) {
794
- if (callback) {
795
- taskCallbacks.push(callback);
796
- }
797
- if (!tasks || !tasks.length) {
798
- const doCallbacks = taskCallbacks;
799
- taskCallbacks = [];
800
- doCallbacks.forEach(cb => typeof cb === 'function' && cb());
801
- return;
802
- }
803
- let task = tasks[0];
804
- adapter.log.debug(`process task: ${JSON.stringify(task)}`);
805
- if (task.type === 'addObject') {
806
- if (!cacheAddedObjects[task.id]) {
807
- cacheAddedObjects[task.id] = true;
808
- adapter.getForeignObject(task.id, (err, obj) => {
809
- if (!obj || !obj.common) {
810
- if (task.data.common.storeMap) {
811
- task.data = JSON.parse(JSON.stringify(task.data)); // copy
812
- delete task.data.common.storeMap;
813
- }
814
- adapter.setForeignObject(task.id, task.data, err => {
815
- if (err) {
816
- adapter.log.warn(`New object creation error: ${err.message}`);
817
- } else {
818
- adapter.log.info(`New object created: ${task.id}`);
819
- }
820
- if (task.cb) {
821
- task.cb(task.id, task.cbArg, () => {
822
- tasks.shift();
823
- setImmediate(processTasks);
824
- });
825
- } else {
826
- tasks.shift();
827
- setImmediate(processTasks);
828
- }
829
- });
830
- } else if (obj.common.type !== task.data.common.type || obj.common.storeMap !== undefined) {
831
- obj.common.type = task.data.common.type;
832
- if (obj.common.storeMap !== undefined) {
833
- delete obj.common.storeMap;
834
- }
835
- adapter.setForeignObject(task.id, obj, err => {
836
- if (err) {
837
- adapter.log.warn(`Object update error: ${err.message}`);
838
- } else {
839
- adapter.log.info(`Object updated: ${task.id}`);
840
- }
841
- if (task.cb) {
842
- task.cb(task.id, task.cbArg, () => {
843
- tasks.shift();
844
- setImmediate(processTasks);
845
- });
846
- } else {
847
- tasks.shift();
848
- setImmediate(processTasks);
849
- }
850
- });
851
- } else {
852
- if (task.cb) {
853
- task.cb(task.id, task.cbArg, () => {
854
- tasks.shift();
855
- setImmediate(processTasks);
856
- });
857
- } else {
858
- tasks.shift();
859
- setImmediate(processTasks);
860
- }
861
- }
862
- });
863
- } else {
864
- if (task.cb) {
865
- task.cb(task.id, task.cbArg, () => {
866
- tasks.shift();
867
- setImmediate(processTasks);
868
- });
869
- } else {
870
- tasks.shift();
871
- setImmediate(processTasks);
872
- }
873
- }
874
- } else if (task.type === 'extendObject') {
875
- adapter.extendObject(task.id, task.data, (/* err */) => {
876
- if (task.cb) {
877
- task.cb(task.id, task.cbArg, () => {
878
- tasks.shift();
879
- setImmediate(processTasks);
880
- });
881
- } else {
882
- tasks.shift();
883
- setImmediate(processTasks);
884
- }
885
- });
886
- } else if (task.type === 'deleteState') {
887
- adapter.deleteState('', '', task.id, (/* err */) => {
888
- if (task.cb) {
889
- task.cb(task.id, task.cbArg, () => {
890
- tasks.shift();
891
- setImmediate(processTasks);
892
- });
893
- } else {
894
- tasks.shift();
895
- setImmediate(processTasks);
896
- }
897
- });
898
- } else {
899
- adapter.log.error(`Unknown task name: ${JSON.stringify(task)}`);
900
- if (task.cb) {
901
- task.cb(task.id, task.cbArg, () => {
902
- tasks.shift();
903
- setImmediate(processTasks);
904
- });
905
- } else {
906
- tasks.shift();
907
- setImmediate(processTasks);
908
- }
909
- }
910
- }
911
-
912
- function createClient(client, callback) {
913
- // mqtt.0.cmnd.sonoff.POWER
914
- // mqtt.0.stat.sonoff.POWER
915
- let isStart = !tasks.length;
916
-
917
- let id = `${adapter.namespace}.${client.iobId}`;
918
- let obj = {
919
- _id: id,
920
- common: {
921
- name: client.id,
922
- desc: '',
923
- },
924
- native: {
925
- clientId: client.id,
926
- },
927
- type: 'channel',
928
- };
929
- tasks.push({ type: 'addObject', id: obj._id, data: obj });
930
-
931
- obj = {
932
- _id: `${id}.alive`,
933
- common: {
934
- type: 'boolean',
935
- role: 'indicator.reachable',
936
- read: true,
937
- write: false,
938
- name: `${client.id} alive`,
939
- },
940
- type: 'state',
941
- };
942
- tasks.push({ type: 'addObject', id: obj._id, data: obj });
943
- if (isStart) {
944
- processTasks(callback);
945
- } else {
946
- typeof callback === 'function' && callback();
947
- }
948
- }
949
-
950
- async function updateClients() {
951
- const clientIds = [];
952
- if (clients) {
953
- for (const id in clients) {
954
- const oid = `info.clients.${id.replace(/[.\s]+/g, '_').replace(FORBIDDEN_CHARS, '_')}`;
955
- clientIds.push(oid);
956
- const clientObj = await adapter.getObjectAsync(oid);
957
- if (!clientObj?.native) {
958
- await adapter.setObjectAsync(oid, {
959
- type: 'state',
960
- common: {
961
- name: id,
962
- role: 'indicator.reachable',
963
- type: 'boolean',
964
- read: true,
965
- write: false,
966
- },
967
- native: {
968
- ip: clients[id].stream.remoteAddress,
969
- port: clients[id].stream.remotePort,
970
- },
971
- });
972
- } else {
973
- if (
974
- clients[id] &&
975
- (clientObj.native.port !== clients[id].stream.remotePort ||
976
- clientObj.native.ip !== clients[id].stream.remoteAddress)
977
- ) {
978
- clientObj.native.port = clients[id].stream.remotePort;
979
- clientObj.native.ip = clients[id].stream.remoteAddress;
980
- adapter.setObject(clientObj._id, clientObj);
981
- }
982
- }
983
- await adapter.setStateAsync(oid, true, true);
984
- }
985
- }
986
- // read all other states and set alive to false
987
- const allStates = await adapter.getStatesAsync('info.clients.*');
988
- for (const id in allStates) {
989
- if (!clientIds.includes(id.replace(`${adapter.namespace}.`, ''))) {
990
- await adapter.setStateAsync(id, { val: false, ack: true });
991
- }
992
- }
993
-
994
- let text = '';
995
- if (clients) {
996
- for (let id in clients) {
997
- text += `${text ? ',' : ''}${id}`;
998
- }
999
- }
1000
- adapter.setState('info.connection', text, true);
1001
- }
1002
-
1003
- function updateAlive(client, alive) {
1004
- let idAlive = `${adapter.namespace}.${client.iobId}.alive`;
1005
-
1006
- adapter.getForeignState(idAlive, (err, state) => {
1007
- if (!state || state.val !== alive) {
1008
- adapter.setForeignState(idAlive, alive, true);
1009
- }
1010
- });
1011
- }
1012
-
1013
- function sendState2Client(client, topic, state, qos, retain, cb) {
1014
- if (typeof qos === 'function') {
1015
- cb = qos;
1016
- qos = undefined;
1017
- }
1018
- if (typeof retain === 'function') {
1019
- cb = retain;
1020
- retain = undefined;
1021
- }
1022
- adapter.log.debug(`Send to "${client.id}": ${topic} = ${state}`);
1023
- client.publish({ topic: topic, payload: state, qos: qos, retain: retain, messageId: messageId++ }, cb);
1024
- messageId &= 0xffffffff;
1025
- }
1026
-
1027
- function resendMessages2Client(client, messages, i) {
1028
- i = i || 0;
1029
- if (messages && i < messages.length) {
1030
- try {
1031
- messages[i].ts = Date.now();
1032
- messages[i].count++;
1033
- adapter.log.debug(
1034
- `Client [${client.id}] resend messages on connect: ${messages[i].topic} = ${messages[i].payload}`,
1035
- );
1036
- if (messages[i].cmd === 'publish') {
1037
- client.publish(messages[i]);
1038
- }
1039
- } catch (e) {
1040
- adapter.log.warn(`Client [${client.id}] cannot resend message: ${e}`);
1041
- }
1042
-
1043
- if (adapter.config.sendInterval) {
1044
- setTimeout(() => resendMessages2Client(client, messages, i + 1), adapter.config.sendInterval);
1045
- } else {
1046
- setImmediate(() => resendMessages2Client(client, messages, i + 1));
1047
- }
1048
- } else {
1049
- //return;
1050
- }
1051
- }
1052
-
1053
- function addObject(typeKey, client, prefix, path) {
1054
- // Extract the actual attribute name for the state ID construction
1055
- const attr = typeKey.includes('_') && path.length > 0 ? typeKey.split('_').pop() : typeKey;
1056
- let replaceAttr = types[typeKey].replace || attr;
1057
- let id = `${adapter.namespace}.${client.iobId}.${prefix ? `${prefix}.` : ''}${path.length ? `${path.join('_')}_` : ''}${replaceAttr.replace(FORBIDDEN_CHARS, '_')}`;
1058
- let obj = {
1059
- type: 'addObject',
1060
- id: id,
1061
- data: {
1062
- _id: id,
1063
- common: Object.assign({}, types[typeKey]),
1064
- native: {},
1065
- type: 'state',
1066
- },
1067
- };
1068
- obj.data.common.name = `${client.id} ${prefix ? `${prefix} ` : ''}${path.length ? `${path.join(' ')} ` : ''} ${replaceAttr}`;
1069
- return obj;
1070
- }
1071
-
1072
- function checkData(client, topic, prefix, data, unit, path) {
1073
- if (!data || typeof data !== 'object') {
1074
- return;
1075
- }
1076
-
1077
- const ledModeReadColorsID = `${adapter.namespace}.${client.iobId}.modeReadColors`;
1078
-
1079
- if (cachedReadColors[ledModeReadColorsID] === undefined) {
1080
- return adapter.getForeignState(ledModeReadColorsID, (err, state) => {
1081
- cachedReadColors[ledModeReadColorsID] = (state && state.val) || false;
1082
- setImmediate(() => checkData(client, topic, prefix, data, unit, path));
1083
- });
1084
- }
1085
-
1086
- path = path || [];
1087
- prefix = prefix || '';
1088
-
1089
- // Extract pressure and temperature units if available at this level for nested data
1090
- const pressureUnit = data.PressureUnit || (unit && typeof unit === 'object' ? unit.pressureUnit : undefined);
1091
- const tempUnit = data.TempUnit || (unit && typeof unit === 'object' ? unit.tempUnit : undefined);
1092
-
1093
- for (let attr in data) {
1094
- if (!Object.prototype.hasOwnProperty.call(data, attr)) {
1095
- adapter.log.warn(`[${client.id}] attr error: ${attr}${data[attr]}`);
1096
- continue;
1097
- }
1098
-
1099
- // Skip unit fields - they are used for dynamic unit override, not as states
1100
- if (attr === 'TempUnit' || attr === 'PressureUnit') {
1101
- continue;
1102
- }
1103
-
1104
- if (typeof data[attr] === 'object') {
1105
- // check for arrays
1106
- if (types[attr]) {
1107
- if (types[attr].type === 'array') {
1108
- // transform to an array of attributes
1109
- for (let i = 1; i <= 10; i++) {
1110
- let val = data[attr][i - 1];
1111
- if (typeof val === 'undefined') {
1112
- break;
1113
- }
1114
- // define a new object
1115
- let replaceAttr = attr.replace(FORBIDDEN_CHARS, '_') + i.toString();
1116
- let id = `${adapter.namespace}.${client.iobId}.${prefix ? `${prefix}.` : ''}${path.length ? `${path.join('_')}_` : ''}${replaceAttr}`;
1117
- let obj = {
1118
- type: 'addObject',
1119
- id: id,
1120
- data: {
1121
- _id: id,
1122
- common: Object.assign({}, types[attr]),
1123
- native: {},
1124
- type: 'state',
1125
- },
1126
- };
1127
- obj.data.common.name = `${client.id} ${prefix ? `${prefix} ` : ''}${path.length ? `${path.join(' ')} ` : ''} ${replaceAttr}`;
1128
- obj.data.common.type = typeof val;
1129
- if (!['number', 'string', 'boolean'].includes(obj.data.common.type)) {
1130
- obj.data.common.type = 'string';
1131
- }
1132
- updateState(obj, val);
1133
- }
1134
- } else {
1135
- let nPath = Object.assign([], path);
1136
- nPath.push(attr.replace(FORBIDDEN_CHARS, '_'));
1137
- const nestedUnits = pressureUnit || tempUnit ? { pressureUnit, tempUnit } : unit;
1138
- checkData(client, topic, prefix, data[attr], nestedUnits, nPath);
1139
- nPath = undefined;
1140
- }
1141
- } else {
1142
- let nPath = Object.assign([], path);
1143
- nPath.push(attr.replace(FORBIDDEN_CHARS, '_'));
1144
- const nestedUnits = pressureUnit || tempUnit ? { pressureUnit, tempUnit } : unit;
1145
- if (adapter.config.OBJ_TREE) {
1146
- checkData(client, topic, `${prefix}.${nPath}`, data[attr], nestedUnits);
1147
- } else {
1148
- checkData(client, topic, prefix, data[attr], nestedUnits, nPath);
1149
- }
1150
- nPath = undefined;
1151
- }
1152
- } else if (types[attr] || (path.length > 0 && types[`${path.join('_')}_${attr}`])) {
1153
- let allowReadColors;
1154
-
1155
- // Check for path-based type definition first, then fallback to simple attr type
1156
- const typeKey =
1157
- path.length > 0 && types[`${path.join('_')}_${attr}`] ? `${path.join('_')}_${attr}` : attr;
1158
-
1159
- // create object
1160
- const obj = addObject(typeKey, client, prefix, path);
1161
- let replaceAttr = types[typeKey].replace || attr;
1162
-
1163
- if (obj.data.common.storeMap) {
1164
- delete obj.data.common.storeMap;
1165
- client._map[replaceAttr] = topic.replace(/^\w+\//, 'cmnd/').replace(/\/\w+$/, `/${replaceAttr}`);
1166
- }
1167
-
1168
- // handle dynamic pressure unit override
1169
- if (attr === 'Pressure' && pressureUnit && typeof pressureUnit === 'string') {
1170
- obj.data.common.unit = pressureUnit;
1171
- }
1172
-
1173
- // handle dynamic temperature unit override
1174
- if (attr === 'Temperature' && tempUnit && typeof tempUnit === 'string') {
1175
- obj.data.common.unit = tempUnit;
1176
- }
1177
-
1178
- // adaptions for magichome tasmota
1179
- if (attr === 'Color') {
1180
- // read vars
1181
- allowReadColors = cachedReadColors[ledModeReadColorsID]; // allow for color read from MQTT (default off)
1182
-
1183
- // if ledFlags bit 2, read color from tasmota, else ignore
1184
- if (data[attr].length === 10) {
1185
- obj.data.common.role = 'level.color.rgbcwww'; // RGB + cold white + white???
1186
- } else if (data[attr].length === 8) {
1187
- obj.data.common.role = 'level.color.rgbww'; // RGB + White
1188
- } else {
1189
- obj.data.common.role = 'level.color.rgb';
1190
- }
1191
-
1192
- if (hueCalc) {
1193
- // Create LEDs modes if required
1194
- let xObj = addObject('modeReadColors', client, prefix, path);
1195
- updateState(xObj);
1196
-
1197
- xObj = addObject('modeLedExor', client, prefix, path);
1198
- updateState(xObj);
1199
-
1200
- xObj = addObject('Hue', client, prefix, path);
1201
- updateState(xObj);
1202
-
1203
- xObj = addObject('Saturation', client, prefix, path);
1204
- updateState(xObj);
1205
-
1206
- xObj = addObject('Red', client, prefix, path);
1207
- xObj.data.common.read = allowReadColors;
1208
- updateState(
1209
- xObj,
1210
- allowReadColors ? (100 * parseInt(data[attr].substring(0, 2), 16)) / 255 : undefined,
1211
- );
1212
-
1213
- xObj = addObject('Green', client, prefix, path);
1214
- xObj.data.common.read = allowReadColors;
1215
- updateState(
1216
- xObj,
1217
- allowReadColors ? (100 * parseInt(data[attr].substring(2, 4), 16)) / 255 : undefined,
1218
- );
1219
-
1220
- xObj = addObject('Blue', client, prefix, path);
1221
- xObj.data.common.read = allowReadColors;
1222
- updateState(
1223
- xObj,
1224
- allowReadColors ? (100 * parseInt(data[attr].substring(4, 6), 16)) / 255 : undefined,
1225
- );
1226
-
1227
- xObj = addObject('RGB_POWER', client, prefix, path);
1228
- xObj.data.common.read = allowReadColors;
1229
- let val = parseInt(data[attr].substring(0, 6), 16);
1230
- updateState(xObj, allowReadColors ? val > 0 : undefined);
1231
-
1232
- if (obj.data.common.role === 'level.color.rgbww') {
1233
- // rgbww
1234
- xObj = addObject('WW', client, prefix, path);
1235
- xObj.data.common.read = allowReadColors;
1236
- updateState(
1237
- xObj,
1238
- allowReadColors ? (100 * parseInt(data[attr].substring(6, 8), 16)) / 255 : undefined,
1239
- );
1240
-
1241
- xObj = addObject('WW_POWER', client, prefix, path);
1242
- xObj.data.common.read = allowReadColors;
1243
- updateState(xObj, allowReadColors ? val > 0 : undefined);
1244
- } else if (obj.data.common.role === 'level.color.rgbcwww') {
1245
- // rgbcwww
1246
- xObj = addObject('CW', client, prefix, path);
1247
- xObj.data.common.read = allowReadColors;
1248
- updateState(
1249
- xObj,
1250
- allowReadColors ? (100 * parseInt(data[attr].substring(6, 8), 16)) / 255 : undefined,
1251
- );
1252
-
1253
- xObj = addObject('CW_POWER', client, prefix, path);
1254
- xObj.data.common.read = allowReadColors;
1255
- updateState(xObj, allowReadColors ? val > 0 : undefined);
1256
-
1257
- xObj = addObject('WW', client, prefix, path);
1258
- xObj.data.common.read = allowReadColors;
1259
- updateState(
1260
- xObj,
1261
- allowReadColors ? (100 * parseInt(data[attr].substring(8, 10), 16)) / 255 : undefined,
1262
- );
1263
-
1264
- xObj = addObject('WW_POWER', client, prefix, path);
1265
- xObj.data.common.read = allowReadColors;
1266
- val = parseInt(data[attr].substring(8, 10), 16);
1267
- updateState(xObj, allowReadColors ? val > 0 : undefined);
1268
- }
1269
- }
1270
- }
1271
-
1272
- let val;
1273
- if (obj.data.common.type === 'number') {
1274
- val = parseFloat(data[attr]);
1275
- } else if (obj.data.common.type === 'boolean') {
1276
- const _value = (data[attr] || '').toUpperCase();
1277
- val = _value === 'ON' || _value === 'TRUE' || _value === '1';
1278
- } else {
1279
- if (attr === 'Color') {
1280
- // add # char
1281
- if (allowReadColors) {
1282
- val = `#${data[attr]}`;
1283
- }
1284
- } else {
1285
- val = data[attr];
1286
- }
1287
- }
1288
- let powerStateFound = false;
1289
- // due to some (for ex. NSPanel) versions do not automatically update .POWER from .STATE.POWER
1290
- // but this is generally a default for all devices (state cannot differ from switch) we now keep
1291
- // STATE changes to POWER in sync with POWER switches
1292
- if (obj.id.includes('.STATE.POWER')) {
1293
- powerStateFound = true;
1294
- }
1295
-
1296
- updateState(obj, val);
1297
-
1298
- // Special handling for Zigbee devices: create writable control states for Power and Dimmer
1299
- if (
1300
- (attr === 'Power' || attr === 'Dimmer') &&
1301
- prefix === 'SENSOR' &&
1302
- path.length >= 2 &&
1303
- path[0] === 'ZbReceived'
1304
- ) {
1305
- // Extract device ID from path (e.g., ZbReceived_0x0856_Power -> 0x0856)
1306
- const deviceId = path[1];
1307
-
1308
- // Create a writable control state for Zigbee devices
1309
- let controlObj = Object.assign({}, obj);
1310
-
1311
- // Modify object properties for control state
1312
- if (attr === 'Power') {
1313
- // For Zigbee Power: boolean switch, not power consumption in watts
1314
- controlObj.data.common = {
1315
- type: 'boolean',
1316
- role: 'switch',
1317
- read: true,
1318
- write: true,
1319
- name: `${client.id} ${prefix ? `${prefix} ` : ''}${path.join(' ')} ${attr} Control`,
1320
- };
1321
- // Convert numeric power value (0/1) to boolean for control state
1322
- const controlVal = Boolean(val);
1323
- updateState(controlObj, controlVal);
1324
- } else if (attr === 'Dimmer') {
1325
- // For Zigbee Dimmer: ensure it's writable
1326
- controlObj.data.common.write = true;
1327
- controlObj.data.common.name = `${client.id} ${prefix ? `${prefix} ` : ''}${path.join(' ')} ${attr} Control`;
1328
- updateState(controlObj, val);
1329
- }
1330
-
1331
- adapter.log.debug(`Created Zigbee control state for device ${deviceId}: ${controlObj.id}`);
1332
- }
1333
-
1334
- if (powerStateFound) {
1335
- adapter.log.debug(`Got .STATE.POWER update, auto syncing .POWER (src: ${obj.id})`);
1336
- let powerObj = Object.assign({}, obj);
1337
- powerObj.id = powerObj.data._id = powerObj.id.replace('.STATE.POWER', '.POWER');
1338
- updateState(powerObj, val);
1339
- }
1340
- } else {
1341
- // not in list, auto insert
1342
- //if (client.id=='DVES_008ADB') {
1343
- // adapter.log.warn('[' + client.id + '] Received attr not in list: ' + attr + '' + data[attr]);
1344
- //}
1345
- // tele/sonoff/SENSOR tele/sonoff/STATE => read only
1346
- // stat/sonoff/RESULT => read,write
1347
-
1348
- let parts = topic.split('/');
1349
- // auto generate objects
1350
- if (
1351
- (parts[0] === 'tele' &&
1352
- ((adapter.config.TELE_SENSOR && parts[2] === 'SENSOR') ||
1353
- (adapter.config.STAT_RESULT && parts[2] === 'RESULT') ||
1354
- (adapter.config.TELE_STATE && parts[2] === 'STATE') ||
1355
- (adapter.config.TELE_MARGINS && parts[2] === 'MARGINS'))) ||
1356
- (parts[0] === 'stat' && adapter.config.STAT_RESULT && parts[2] === 'RESULT')
1357
- ) {
1358
- let val = data[attr];
1359
- let attributes = { role: 'value', read: true, write: false };
1360
- if (parts[2] === 'RESULT') {
1361
- // control
1362
- attributes.write = true;
1363
- attributes.role = 'level';
1364
- }
1365
-
1366
- attributes.type = typeof val;
1367
- if (!['number', 'string', 'boolean'].includes(attributes.type)) {
1368
- attributes.type = 'string';
1369
- val = (val || '').toString();
1370
- }
1371
-
1372
- let replaceAttr = attr;
1373
- let id = `${adapter.namespace}.${client.iobId}.${prefix ? `${prefix}.` : ''}${path.length ? `${path.join('_')}_` : ''}${replaceAttr}`;
1374
- let obj = {
1375
- type: 'addObject',
1376
- id: id,
1377
- data: {
1378
- _id: id,
1379
- common: attributes,
1380
- native: {},
1381
- type: 'state',
1382
- },
1383
- };
1384
- obj.data.common.name = `${client.id} ${prefix ? `${prefix} ` : ''}${path.length ? `${path.join(' ')} ` : ''} ${replaceAttr}`;
1385
- // due to some (for ex. NSPanel) versions do not automatically send .POWER switch data
1386
- // but this is generally a default for all devices (state cannot exist without switch) we now add
1387
- // POWER switches automatically if we get POWER STATE
1388
- let powerStateFound = false;
1389
- if (obj.id.includes('.STATE.POWER')) {
1390
- powerStateFound = true;
1391
- }
1392
-
1393
- updateState(obj, val);
1394
-
1395
- if (powerStateFound) {
1396
- adapter.log.debug(`Got .STATE.POWER value message ==> auto add .POWER object from ${obj.id}`);
1397
- let powerId = obj.id.replace('.STATE.POWER', '.POWER');
1398
- let powerName = obj.data.common.name.replace(' STATE POWER', ' POWER');
1399
- let powerObj = Object.assign({}, obj);
1400
- powerObj.id = powerObj.data._id = powerId;
1401
- powerObj.data.common.name = powerName;
1402
- updateState(powerObj, val);
1403
- }
1404
- }
1405
- }
1406
- }
1407
- }
1408
-
1409
- function receivedTopic(packet, client) {
1410
- if (!packet) {
1411
- return adapter.log.warn(`Empty packet received: ${JSON.stringify(packet)}`);
1412
- }
1413
-
1414
- client.states = client.states || {};
1415
- client.states[packet.topic] = {
1416
- message: packet.payload,
1417
- retain: packet.retain,
1418
- qos: packet.qos,
1419
- };
1420
-
1421
- // update alive state
1422
- updateAlive(client, true);
1423
-
1424
- if (client._will && client._will.topic && packet.topic === client._will.topic) {
1425
- client._will.payload = packet.payload;
1426
- return;
1427
- }
1428
-
1429
- let val = packet.payload.toString('utf8');
1430
- adapter.log.debug(`Client [${client.id}] received: ${packet.topic} = ${val}`);
1431
-
1432
- /*
1433
- adapter.getForeignState('system.adapter.sonoff.0.memRss', (err, state) => {
1434
- adapter.log.info('mem: ' + state.val + ' MB');
1435
- if (heapTest==0 || (heapTest==1 && state.val>37.5)) {
1436
- heapTest+=1;
1437
- heapdump.writeSnapshot('/opt/iobroker/' + Date.now() + '.heapsnapshot');
1438
- adapter.log.info('Wrote snapshot: ');
1439
-
1440
- var filename='/opt/iobroker/' + Date.now() + '.heapsnapshot';
1441
- heapdump.writeSnapshot((err,filename) => {
1442
- if (err) {
1443
- adapter.log.info('heap: ' + err);
1444
- } else {
1445
- adapter.log.info('Wrote snapshot: ' + filename);
1446
- }
1447
- });
1448
-
1449
- }
1450
- });
1451
- */
1452
-
1453
- // [DVES_BD3B4D] Received: tele/sonoff2/STATE = {
1454
- // "Time":"2017-10-01T12:37:18",
1455
- // "Uptime":0,
1456
- // "Vcc":3.224,
1457
- // "POWER":"ON",
1458
- // "POWER1":"OFF",
1459
- // "POWER2":"ON"
1460
- // "Wifi":{
1461
- // "AP":1,
1462
- // "SSId":"FuckOff",
1463
- // "RSSI":62,
1464
- // "APMac":"E0:28:6D:EC:21:EA"
1465
- // }
1466
- // }
1467
- // [DVES_BD3B4D] Received: tele/sonoff2/SENSOR = {
1468
- // "Time":"2017-10-01T12:37:18",
1469
- // "Switch1":"ON",
1470
- // "DS18B20":{"Temperature":20.6},
1471
- // "TempUnit":"C"
1472
- // }
1473
- client._map = client._map || {};
1474
-
1475
- const parts = packet.topic.split('/');
1476
-
1477
- if (!client._fallBackName) {
1478
- client._fallBackName = parts[1];
1479
- }
1480
-
1481
- const stateId = parts.pop();
1482
-
1483
- if (stateId === 'LWT') {
1484
- return;
1485
- }
1486
-
1487
- if (val.includes('nan')) {
1488
- val = val.replace(/:nan,/g, ':"NaN",').replace(/:nan}/g, ':"NaN"}').replace(/:nan]/g, ':"NaN"]');
1489
- }
1490
-
1491
- if (stateId === 'RESULT') {
1492
- // ignore: stat/Sonoff/RESULT = {"POWER":"ON"}
1493
- // testserver.js reports error, so reject above cmd
1494
- const str = val.replace(/\s+/g, '');
1495
- if (str.startsWith('{"POWER":"ON"}')) {
1496
- return;
1497
- }
1498
- if (str.startsWith('{"POWER":"OFF"}')) {
1499
- return;
1500
- }
1501
-
1502
- if (parts[0] === 'stat' || parts[0] === 'tele') {
1503
- try {
1504
- if (adapter.config.OBJ_TREE) {
1505
- checkData(client, packet.topic, 'RESULT', JSON.parse(val));
1506
- } else {
1507
- checkData(client, packet.topic, NO_PREFIX, JSON.parse(val));
1508
- }
1509
- } catch (e) {
1510
- adapter.log.warn(`Client [${client.id}] cannot parse data "${stateId}": _${val}_ - ${e}`);
1511
- }
1512
- }
1513
- return;
1514
- }
1515
-
1516
- // tele/sonoff_4ch/STATE = {"Time":"2017-10-02T19:26:06", "Uptime":0, "Vcc":3.226, "POWER1":"OFF", "POWER2":"OFF", "POWER3":"OFF", "POWER4":"OFF", "Wifi":{"AP":1, "SSId":"AAA", "RSSI": 15}}
1517
- // tele/sonoff/SENSOR = {"Time":"2017-10-05T17:43:19", "DS18x20":{"DS1":{"Type":"DS18B20", "Address":"28FF9A9876815022A", "Temperature":12.2}}, "TempUnit":"C"}
1518
- // tele/sonoff5/SENSOR = {"Time":"2017-10-03T14:02:25", "AM2301-14":{"Temperature":21.6, "Humidity":54.7}, "TempUnit":"C"}
1519
- // tele/sonoff/SENSOR = {"Time":"2018-02-23T17:36:59", "Analog0":298}
1520
- if (parts[0] === 'tele' && stateId.match(/^(RESULT|STATE|SENSOR|WAKEUP)\d?$/)) {
1521
- try {
1522
- if (adapter.config.OBJ_TREE) {
1523
- if (stateId.match(/^(STATE)\d?$/)) {
1524
- checkData(client, packet.topic, 'STATE', JSON.parse(val));
1525
- } else if (stateId.match(/^(SENSOR)\d?$/)) {
1526
- checkData(client, packet.topic, 'SENSOR', JSON.parse(val));
1527
- } else if (stateId.match(/^(WAKEUP)\d?$/)) {
1528
- checkData(client, packet.topic, 'WAKEUP', JSON.parse(val));
1529
- } else if (stateId.match(/^(RESULT)\d?$/)) {
1530
- checkData(client, packet.topic, 'RESULT', JSON.parse(val));
1531
- }
1532
- } else {
1533
- checkData(client, packet.topic, NO_PREFIX, JSON.parse(val));
1534
- //adapter.log.warn('log sensor parse"' + stateId + '": _' + val);
1535
- }
1536
- } catch (e) {
1537
- adapter.log.warn(`Client [${client.id}] cannot parse data"${stateId}": _${val}_ - ${e}`);
1538
- }
1539
- } else if (parts[0] === 'tele' && stateId.match(/^INFO\d?$/)) {
1540
- // tele/SonoffPOW/INFO1 = {"Module":"Sonoff Pow", "Version":"5.8.0", "FallbackTopic":"SonoffPOW", "GroupTopic":"sonoffs"}
1541
- // tele/SonoffPOW/INFO2 = {"WebServerMode":"Admin", "Hostname":"Sonoffpow", "IPAddress":"192.168.2.182"}
1542
- // tele/SonoffPOW/INFO3 = {"RestartReason":"Software/System restart"}
1543
- try {
1544
- checkData(client, packet.topic, 'INFO', JSON.parse(val));
1545
- } catch (e) {
1546
- adapter.log.warn(`Client [${client.id}] cannot parse data"${stateId}": _${val}_ - ${e}`);
1547
- }
1548
- } else if (parts[0] === 'tele' && stateId.match(/^(ENERGY)\d?$/)) {
1549
- // tele/sonoff_4ch/ENERGY = {"Time":"2017-10-02T19:24:32", "Total":1.753, "Yesterday":0.308, "Today":0.205, "Period":0, "Power":3, "Factor":0.12, "Voltage":221, "Current":0.097}
1550
- try {
1551
- checkData(client, packet.topic, 'ENERGY', JSON.parse(val));
1552
- } catch (e) {
1553
- adapter.log.warn(`Client [${client.id}] cannot parse data"${stateId}": _${val}_ - ${e}`);
1554
- }
1555
- } else if (parts[0] === 'tele' && stateId.match(/^(MARGINS)\d?$/)) {
1556
- // tele/sonoffPOW/MARGINS = {"Time":"2020-04-23T10:15:00","PowerLow":100,"PowerHigh":2000,"PowerDelta":50}
1557
- try {
1558
- checkData(client, packet.topic, 'MARGINS', JSON.parse(val));
1559
- } catch (e) {
1560
- adapter.log.warn(`Client [${client.id}] cannot parse data"${stateId}": _${val}_ - ${e}`);
1561
- }
1562
- } else if (types[stateId]) {
1563
- // /ESP_BOX/BM280/Pressure = 1010.09
1564
- // /ESP_BOX/BM280/Humidity = 42.39
1565
- // /ESP_BOX/BM280/Temperature = 25.86
1566
- // /ESP_BOX/BM280/Approx. Altitude = 24
1567
-
1568
- // cmnd/sonoff/POWER
1569
- // stat/sonoff/POWER
1570
-
1571
- if (types[stateId]) {
1572
- let id = `${adapter.namespace}.${client.iobId}.${stateId.replace(/[-.+\s]+/g, '_').replace(FORBIDDEN_CHARS, '_')}`;
1573
- let obj = {
1574
- type: 'addObject',
1575
- id: id,
1576
- data: {
1577
- _id: id,
1578
- common: JSON.parse(JSON.stringify(types[stateId])),
1579
- native: {},
1580
- type: 'state',
1581
- },
1582
- };
1583
- obj.data.common.name = `${client.id} ${stateId}`;
1584
-
1585
- // push only new objects
1586
- updateState(obj, () => {
1587
- if (parts[0] === 'cmnd') {
1588
- // Set Object fix
1589
- if (obj.data.common.type === 'number') {
1590
- adapter.setState(id, parseFloat(val), true);
1591
- } else if (obj.data.common.type === 'boolean') {
1592
- if (val === 'ON' || val === '1' || val === 'true' || val === 'on') {
1593
- adapter.setState(id, true, true);
1594
- } else if (val === 'OFF' || val === '0' || val === 'false' || val === 'off') {
1595
- adapter.setState(id, false, true);
1596
- }
1597
- } else {
1598
- adapter.setState(id, val, true);
1599
- }
1600
-
1601
- // remember POWER topic
1602
- client._map[stateId] = packet.topic;
1603
- } else {
1604
- if (obj.data.common.type === 'number') {
1605
- adapter.setState(id, parseFloat(val), true);
1606
- } else if (obj.data.common.type === 'boolean') {
1607
- if (val === 'ON' || val === '1' || val === 'true' || val === 'on') {
1608
- adapter.setState(id, true, true);
1609
- } else if (val === 'OFF' || val === '0' || val === 'false' || val === 'off') {
1610
- adapter.setState(id, false, true);
1611
- }
1612
- } else {
1613
- adapter.setState(id, val, true);
1614
- }
1615
- }
1616
- });
1617
- } else {
1618
- adapter.log.debug(`Cannot process: ${packet.topic}`);
1619
- }
1620
- }
1621
- }
1622
-
1623
- function clientClose(client, reason) {
1624
- if (!client) {
1625
- return;
1626
- }
1627
-
1628
- if (persistentSessions[client.id]) {
1629
- persistentSessions[client.id].connected = false;
1630
- }
1631
-
1632
- if (client._resendonStart) {
1633
- clearTimeout(client._resendonStart);
1634
- client._resendonStart = null;
1635
- }
1636
-
1637
- try {
1638
- if (clients[client.id] && client.__secret === clients[client.id].__secret) {
1639
- adapter.log.info(`Client [${client.id}] connection closed: ${reason}`);
1640
- updateAlive(client, false);
1641
- delete clients[client.id];
1642
- updateClients();
1643
- if (client._will) {
1644
- receivedTopic(client._will, client, () => client.destroy());
1645
- } else {
1646
- client.destroy();
1647
- }
1648
- } else {
1649
- client.destroy();
1650
- }
1651
- } catch (e) {
1652
- adapter.log.warn(`Client [${client.id}] cannot close client: ${e}`);
1653
- }
1654
- }
1655
-
1656
- function checkResends() {
1657
- const now = Date.now();
1658
- resending = true;
1659
- for (const clientId in clients) {
1660
- if (
1661
- Object.prototype.hasOwnProperty.call(clients, clientId) &&
1662
- clients[clientId] &&
1663
- clients[clientId]._messages
1664
- ) {
1665
- for (let m = clients[clientId]._messages.length - 1; m >= 0; m--) {
1666
- const message = clients[clientId]._messages[m];
1667
- if (now - message.ts >= adapter.config.retransmitInterval) {
1668
- if (message.count > adapter.config.retransmitCount) {
1669
- adapter.log.warn(
1670
- `Client [${clientId}] message ${message.messageId} deleted after ${message.count} retries`,
1671
- );
1672
- clients[clientId]._messages.splice(m, 1);
1673
- continue;
1674
- }
1675
-
1676
- // resend this message
1677
- message.count++;
1678
- message.ts = now;
1679
- try {
1680
- adapter.log.debug(
1681
- `Client [${clientId}] resend message topic: ${message.topic}, payload: ${message.payload}`,
1682
- );
1683
- if (message.cmd === 'publish') {
1684
- clients[clientId].publish(message);
1685
- }
1686
- } catch (e) {
1687
- adapter.log.warn(`Client [${clientId}] cannot publish message: ${e}`);
1688
- }
1689
-
1690
- if (adapter.config.sendInterval) {
1691
- setTimeout(checkResends, adapter.config.sendInterval);
1692
- } else {
1693
- setImmediate(checkResends);
1694
- }
1695
- return;
1696
- }
1697
- }
1698
- }
1699
- }
1700
-
1701
- // delete old sessions
1702
- if (adapter.config.storeClientsTime !== -1) {
1703
- for (const id in persistentSessions) {
1704
- if (Object.prototype.hasOwnProperty.call(persistentSessions, id)) {
1705
- if (now - persistentSessions[id].lastSeen > adapter.config.storeClientsTime * 60000) {
1706
- delete persistentSessions[id];
1707
- }
1708
- }
1709
- }
1710
- }
1711
-
1712
- resending = false;
1713
- }
1714
-
1715
- (function _constructor(config) {
1716
- if (config.timeout === undefined) {
1717
- config.timeout = 300;
1718
- } else {
1719
- config.timeout = parseInt(config.timeout, 10);
1720
- }
1721
-
1722
- server.on('connection', stream => {
1723
- let client = mqtt(stream);
1724
- // Store unique connection identifier
1725
- client.__secret = `${Date.now()}_${Math.round(Math.random() * 10000)}`;
1726
-
1727
- // client connected
1728
- client.on('connect', options => {
1729
- // acknowledge the "connect" packet
1730
- client.id = options.clientId;
1731
- client.iobId = client.id.replace(FORBIDDEN_CHARS, '_');
1732
- mappingClients[client.iobId] = client.id;
1733
-
1734
- // get possible an old client
1735
- let oldClient = clients[client.id];
1736
-
1737
- if (config.user) {
1738
- if (options.password) {
1739
- options.password = options.password.toString();
1740
- }
1741
- if (config.user !== options.username || config.password !== options.password) {
1742
- adapter.log.warn(`Client [${client.id}] has invalid password or username`);
1743
- client.connack({ returnCode: 4 });
1744
- if (oldClient) {
1745
- // delete existing client
1746
- delete clients[client.id];
1747
- updateAlive(oldClient, false);
1748
- updateClients();
1749
- oldClient.destroy();
1750
- }
1751
- client.destroy();
1752
- return;
1753
- }
1754
- }
1755
-
1756
- if (oldClient) {
1757
- adapter.log.info(
1758
- `Client [${client.id}] reconnected. Old secret ${clients[client.id].__secret} ==> New secret ${client.__secret}`,
1759
- );
1760
- // need to destroy the old client
1761
-
1762
- if (client.__secret !== clients[client.id].__secret) {
1763
- // it is another socket!!
1764
- // It was following situation:
1765
- // - old connection was active
1766
- // - new connection is on the same TCP
1767
- // Just forget him
1768
- // oldClient.destroy();
1769
- }
1770
- } else {
1771
- adapter.log.info(`Client [${client.id}] connected with secret ${client.__secret}`);
1772
- }
1773
-
1774
- let sessionPresent = false;
1775
-
1776
- if (!client.cleanSession && adapter.config.storeClientsTime !== 0) {
1777
- if (persistentSessions[client.id]) {
1778
- sessionPresent = true;
1779
- persistentSessions[client.id].lastSeen = Date.now();
1780
- } else {
1781
- persistentSessions[client.id] = {
1782
- _subsID: {},
1783
- _subs: {},
1784
- messages: [],
1785
- lastSeen: Date.now(),
1786
- };
1787
- }
1788
- client._messages = persistentSessions[client.id].messages;
1789
- persistentSessions[client.id].connected = true;
1790
- } else if (client.cleanSession && persistentSessions[client.id]) {
1791
- delete persistentSessions[client.id];
1792
- }
1793
- client._messages = client._messages || [];
1794
-
1795
- client.connack({ returnCode: 0, sessionPresent });
1796
- clients[client.id] = client;
1797
- updateClients();
1798
-
1799
- client._will = options.will;
1800
- createClient(client, () => {
1801
- if (persistentSessions[client.id]) {
1802
- client._subsID = persistentSessions[client.id]._subsID;
1803
- client._subs = persistentSessions[client.id]._subs;
1804
- if (persistentSessions[client.id].messages.length) {
1805
- // give to the client a little bit time
1806
- client._resendonStart = setTimeout(
1807
- clientId => {
1808
- client._resendonStart = null;
1809
- resendMessages2Client(client, persistentSessions[clientId].messages);
1810
- },
1811
- 100,
1812
- client.id,
1813
- );
1814
- }
1815
- }
1816
- });
1817
- });
1818
-
1819
- // timeout idle streams after 5 minutes
1820
- if (config.timeout) {
1821
- stream.setTimeout(config.timeout * 1000);
1822
- }
1823
-
1824
- // connection error handling
1825
- client.on('close', had_error => clientClose(client, had_error ? 'closed because of error' : 'closed'));
1826
- client.on('error', e => clientClose(client, e));
1827
- client.on('disconnect', () => clientClose(client, 'disconnected'));
1828
- // stream timeout
1829
- stream.on('timeout', () => clientClose(client, 'timeout'));
1830
-
1831
- client.on('publish', packet => {
1832
- if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
1833
- !adapter.config.ignorePings &&
1834
- adapter.log.warn(
1835
- `Old client ${client.id} with secret ${client.__secret} sends publish. Ignore! Actual secret is ${clients[client.id].__secret}`,
1836
- );
1837
- return;
1838
- }
1839
-
1840
- if (persistentSessions[client.id]) {
1841
- persistentSessions[client.id].lastSeen = Date.now();
1842
- }
1843
-
1844
- if (packet.qos === 1) {
1845
- // send PUBACK to a client
1846
- client.puback({ messageId: packet.messageId });
1847
- } else if (packet.qos === 2) {
1848
- const pack = client._messages && client._messages.find(e => e.messageId === packet.messageId);
1849
- if (pack) {
1850
- // duplicate message => ignore
1851
- adapter.log.warn(
1852
- `Client [${client.id}] ignored duplicate message with ID: ${packet.messageId}`,
1853
- );
1854
- return;
1855
- }
1856
- packet.ts = Date.now();
1857
- packet.cmd = 'pubrel';
1858
- packet.count = 0;
1859
- client._messages.push(packet);
1860
-
1861
- client.pubrec({ messageId: packet.messageId });
1862
- return;
1863
- }
1864
-
1865
- receivedTopic(packet, client);
1866
- });
1867
-
1868
- // response for QoS2
1869
- client.on('pubrec', packet => {
1870
- if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
1871
- !adapter.config.ignorePings &&
1872
- adapter.log.warn(
1873
- `Old client ${client.id} with secret ${client.__secret} sends pubrec. Ignore! Actual secret is ${clients[client.id].__secret}`,
1874
- );
1875
- return;
1876
- }
1877
-
1878
- if (persistentSessions[client.id]) {
1879
- persistentSessions[client.id].lastSeen = Date.now();
1880
- }
1881
-
1882
- let pos = null;
1883
- // remove this message from queue
1884
- client._messages &&
1885
- client._messages.forEach((e, i) => {
1886
- if (e.messageId === packet.messageId) {
1887
- pos = i;
1888
- return false;
1889
- }
1890
- });
1891
- if (pos !== null) {
1892
- client.pubrel({ messageId: packet.messageId });
1893
- } else {
1894
- adapter.log.warn(
1895
- `Client [${client.id}] received pubrec on ${client.id} for unknown message ID: ${packet.messageId}`,
1896
- );
1897
- }
1898
- });
1899
-
1900
- // response for QoS2
1901
- client.on('pubcomp', packet => {
1902
- if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
1903
- !adapter.config.ignorePings &&
1904
- adapter.log.warn(
1905
- `Old client ${client.id} with secret ${client.__secret} sends pubcomp. Ignore! Actual secret is ${clients[client.id].__secret}`,
1906
- );
1907
- return;
1908
- }
1909
-
1910
- if (persistentSessions[client.id]) {
1911
- persistentSessions[client.id].lastSeen = Date.now();
1912
- }
1913
-
1914
- let pos = null;
1915
- // remove this message from queue
1916
- client._messages &&
1917
- client._messages.forEach((e, i) => {
1918
- if (e.messageId === packet.messageId) {
1919
- pos = i;
1920
- return false;
1921
- }
1922
- });
1923
- if (pos !== null) {
1924
- client._messages.splice(pos, 1);
1925
- } else {
1926
- adapter.log.warn(
1927
- `Client [${client.id}] received pubcomp for unknown message ID: ${packet.messageId}`,
1928
- );
1929
- }
1930
- });
1931
-
1932
- // response for QoS2
1933
- client.on('pubrel', packet => {
1934
- if (!client._messages) {
1935
- return;
1936
- }
1937
- if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
1938
- !adapter.config.ignorePings &&
1939
- adapter.log.warn(
1940
- `Old client ${client.id} with secret ${client.__secret} sends pubrel. Ignore! Actual secret is ${clients[client.id].__secret}`,
1941
- );
1942
- return;
1943
- }
1944
-
1945
- if (persistentSessions[client.id]) {
1946
- persistentSessions[client.id].lastSeen = Date.now();
1947
- }
1948
-
1949
- let pos = null;
1950
- // remove this message from queue
1951
- client._messages &&
1952
- client._messages.forEach((e, i) => {
1953
- if (e.messageId === packet.messageId) {
1954
- pos = i;
1955
- return false;
1956
- }
1957
- });
1958
-
1959
- if (pos !== null) {
1960
- client.pubcomp({ messageId: packet.messageId });
1961
-
1962
- receivedTopic(client._messages[pos], client);
1963
- client._messages && client._messages.splice(pos, 1);
1964
- } else {
1965
- adapter.log.warn(
1966
- `Client [${client.id}] received pubrel on ${client.id} for unknown messageId ${packet.messageId}`,
1967
- );
1968
- }
1969
- });
1970
-
1971
- // response for QoS1
1972
- client.on('puback', packet => {
1973
- if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
1974
- !adapter.config.ignorePings &&
1975
- adapter.log.warn(
1976
- `Old client ${client.id} with secret ${client.__secret} sends puback. Ignore! Actual secret is ${clients[client.id].__secret}`,
1977
- );
1978
- return;
1979
- }
1980
-
1981
- if (persistentSessions[client.id]) {
1982
- persistentSessions[client.id].lastSeen = Date.now();
1983
- }
1984
-
1985
- // remove this message from queue
1986
- let pos = null;
1987
- // remove this message from queue
1988
- client._messages &&
1989
- client._messages.forEach((e, i) => {
1990
- if (e.messageId === packet.messageId) {
1991
- pos = i;
1992
- return false;
1993
- }
1994
- });
1995
-
1996
- if (pos !== null) {
1997
- adapter.log.debug(
1998
- `Client [${client.id}] received puback for ${client.id} message ID: ${packet.messageId}`,
1999
- );
2000
- client._messages && client._messages.splice(pos, 1);
2001
- } else {
2002
- adapter.log.warn(
2003
- `Client [${client.id}] received puback for unknown message ID: ${packet.messageId}`,
2004
- );
2005
- }
2006
- });
2007
-
2008
- client.on('unsubscribe', packet => {
2009
- if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
2010
- !adapter.config.ignorePings &&
2011
- adapter.log.warn(
2012
- `Old client ${client.id} with secret ${client.__secret} sends unsubscribe. Ignore! Actual secret is ${clients[client.id].__secret}`,
2013
- );
2014
- return;
2015
- }
2016
-
2017
- if (persistentSessions[client.id]) {
2018
- persistentSessions[client.id].lastSeen = Date.now();
2019
- }
2020
-
2021
- client.unsuback({ messageId: packet.messageId });
2022
- });
2023
-
2024
- client.on('subscribe', packet => {
2025
- if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
2026
- !adapter.config.ignorePings &&
2027
- adapter.log.warn(
2028
- `Old client ${client.id} with secret ${client.__secret} sends subscribe. Ignore! Actual secret is ${clients[client.id].__secret}`,
2029
- );
2030
- return;
2031
- }
2032
-
2033
- if (persistentSessions[client.id]) {
2034
- persistentSessions[client.id].lastSeen = Date.now();
2035
- }
2036
-
2037
- // just confirm the request.
2038
- // we expect subscribe for 'cmnd.sonoff.#'
2039
- let granted = packet.subscriptions.map(subs => subs.qos);
2040
-
2041
- client.suback({ granted: granted, messageId: packet.messageId });
2042
- });
2043
-
2044
- client.on('pingreq', (/*packet*/) => {
2045
- if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
2046
- !adapter.config.ignorePings &&
2047
- adapter.log.warn(
2048
- `Old client ${client.id} with secret ${client.__secret} sends pingreq. Ignore! Actual secret is ${clients[client.id].__secret}`,
2049
- );
2050
- return;
2051
- }
2052
-
2053
- if (persistentSessions[client.id]) {
2054
- persistentSessions[client.id].lastSeen = Date.now();
2055
- }
2056
-
2057
- adapter.log.debug(`Client [${client.id}] pingreq`);
2058
- client.pingresp();
2059
- });
2060
- });
2061
-
2062
- server.on('error', err => adapter.log.error(`Can not start Server ${err}`));
2063
-
2064
- config.port = parseInt(config.port, 10) || 1883;
2065
-
2066
- config.retransmitInterval = config.retransmitInterval || 2000;
2067
- config.retransmitCount = config.retransmitCount || 10;
2068
- config.storeClientsTime =
2069
- config.storeClientsTime === undefined ? 1440 : parseInt(config.storeClientsTime, 10) || 0;
2070
- config.defaultQoS = parseInt(config.defaultQoS, 10) || 0;
2071
-
2072
- // Update connection state
2073
- updateClients();
2074
-
2075
- // to start
2076
- server.listen(config.port, config.bind, () => {
2077
- adapter.log.info(`Starting MQTT ${config.user ? 'authenticated ' : ''} server on port ${config.port}`);
2078
-
2079
- resendTimer = setInterval(() => !resending && checkResends(), adapter.config.retransmitInterval || 2000);
2080
- });
2081
- })(adapter.config);
2082
-
2083
- return this;
2084
- }
2085
-
2086
- module.exports = MQTTServer;