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