iobroker.sonoff 3.1.2 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/server.js CHANGED
@@ -7,18 +7,18 @@ const types = require('./datapoints');
7
7
  // const heapdump = require('heapdump');
8
8
  const hueCalc = true;
9
9
  // var heapTest = 0;
10
- const FORBIDDEN_CHARS = /[\]\[*,;'"`<>\\?]/g;
10
+ const FORBIDDEN_CHARS = /[\]\\[*,;'"`<>\\?]/g;
11
11
  const mappingClients = {};
12
12
 
13
13
  /*
14
- * HSV to RGB color conversion
15
- *
16
- * H runs from 0 to 360 degrees
17
- * S and V run from 0 to 100
18
- *
19
- * Ported from the excellent java algorithm by Eugene Vishnevsky at:
20
- * http://www.cs.rit.edu/~ncs/color/t_convert.html
21
- */
14
+ * HSV to RGB color conversion
15
+ *
16
+ * H runs from 0 to 360 degrees
17
+ * S and V run from 0 to 100
18
+ *
19
+ * Ported from the excellent java algorithm by Eugene Vishnevsky at:
20
+ * http://www.cs.rit.edu/~ncs/color/t_convert.html
21
+ */
22
22
  function hsvToRgb(h, s, v) {
23
23
  let r, g, b;
24
24
  let i;
@@ -39,11 +39,7 @@ function hsvToRgb(h, s, v) {
39
39
  if (s === 0) {
40
40
  // Achromatic (grey)
41
41
  r = g = b = v;
42
- return [
43
- Math.round(r * 255),
44
- Math.round(g * 255),
45
- Math.round(b * 255)
46
- ];
42
+ return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
47
43
  }
48
44
 
49
45
  h /= 60; // sector 0 to 5
@@ -90,11 +86,7 @@ function hsvToRgb(h, s, v) {
90
86
  b = q;
91
87
  }
92
88
 
93
- return [
94
- Math.round(r * 255),
95
- Math.round(g * 255),
96
- Math.round(b * 255)
97
- ];
89
+ return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
98
90
  }
99
91
 
100
92
  function componentToHex(c) {
@@ -109,10 +101,17 @@ function toPaddedHexString(num, len) {
109
101
  }
110
102
  }
111
103
  const str = num.toString(16);
112
- if (str.length >= len) return str;
104
+ if (str.length >= len) {
105
+ return str;
106
+ }
113
107
  return '0'.repeat(len - str.length) + str;
114
108
  }
115
109
 
110
+ /**
111
+ * MQTT Server constructor
112
+ *
113
+ * @param {object} adapter - The ioBroker adapter instance
114
+ */
116
115
  function MQTTServer(adapter) {
117
116
  if (!(this instanceof MQTTServer)) {
118
117
  return new MQTTServer(adapter);
@@ -126,7 +125,7 @@ function MQTTServer(adapter) {
126
125
  let taskCallbacks = [];
127
126
  let messageId = 1;
128
127
  let persistentSessions = {};
129
- let resending = false;
128
+ let resending = false;
130
129
  let resendTimer = null;
131
130
 
132
131
  const cacheAddedObjects = {};
@@ -162,7 +161,7 @@ function MQTTServer(adapter) {
162
161
  }
163
162
  };
164
163
 
165
- /*
164
+ /*
166
165
  memwatch.on('leak', (info) => {
167
166
  //console.error('Memory leak detected:\n', info);
168
167
  adapter.log.info('Memory leak detected:\n', info);
@@ -175,7 +174,7 @@ function MQTTServer(adapter) {
175
174
  });
176
175
  });
177
176
  */
178
- /*
177
+ /*
179
178
  memwatch.on('stats', function(stats) {
180
179
  adapter.log.info("stats:",stats);
181
180
  });
@@ -185,9 +184,21 @@ function MQTTServer(adapter) {
185
184
  //adapter.log.info('color write: '+ val);
186
185
  const stateId = 'Color';
187
186
  if (clients[channelId] && clients[channelId]._map && clients[channelId]._map[stateId]) {
188
- setImmediate(sendState2Client, clients[channelId], clients[channelId]._map[stateId] || 'cmnd/sonoff/Color', val, adapter.config.defaultQoS);
187
+ setImmediate(
188
+ sendState2Client,
189
+ clients[channelId],
190
+ clients[channelId]._map[stateId] || 'cmnd/sonoff/Color',
191
+ val,
192
+ adapter.config.defaultQoS,
193
+ );
189
194
  } else if (clients[channelId] && clients[channelId]._fallBackName) {
190
- setImmediate(sendState2Client, clients[channelId], `cmnd/${clients[channelId]._fallBackName}/${stateId}`, val, adapter.config.defaultQoS);
195
+ setImmediate(
196
+ sendState2Client,
197
+ clients[channelId],
198
+ `cmnd/${clients[channelId]._fallBackName}/${stateId}`,
199
+ val,
200
+ adapter.config.defaultQoS,
201
+ );
191
202
  } else {
192
203
  adapter.log.warn(`Unknown mapping for "${stateId}"`);
193
204
  }
@@ -199,19 +210,86 @@ function MQTTServer(adapter) {
199
210
  return adapter.log.debug('Empty power was ignored');
200
211
  }
201
212
  if (clients[channelId]._map && clients[channelId]._map[stateId]) {
202
- setImmediate(sendState2Client, clients[channelId], clients[channelId]._map[stateId] || 'cmnd/sonoff/POWER', val ? 'ON' : 'OFF', adapter.config.defaultQoS);
213
+ setImmediate(
214
+ sendState2Client,
215
+ clients[channelId],
216
+ clients[channelId]._map[stateId] || 'cmnd/sonoff/POWER',
217
+ val ? 'ON' : 'OFF',
218
+ adapter.config.defaultQoS,
219
+ );
203
220
  } else if (clients[channelId]._fallBackName) {
204
- setImmediate(sendState2Client, clients[channelId], `cmnd/${clients[channelId]._fallBackName}/${stateId}`, val, adapter.config.defaultQoS);
221
+ setImmediate(
222
+ sendState2Client,
223
+ clients[channelId],
224
+ `cmnd/${clients[channelId]._fallBackName}/${stateId}`,
225
+ val,
226
+ adapter.config.defaultQoS,
227
+ );
205
228
  } else {
206
229
  adapter.log.warn(`Unknown mapping for "${stateId}"`);
207
230
  }
208
231
  }
209
232
 
233
+ function setZbSendCommand(channelId, deviceId, attribute, val) {
234
+ // Send Zigbee command via ZbSend
235
+ const zbSendCommand = JSON.stringify({
236
+ device: deviceId,
237
+ send: {
238
+ [attribute]: val,
239
+ },
240
+ });
241
+
242
+ if (clients[channelId] && clients[channelId]._fallBackName) {
243
+ setImmediate(
244
+ sendState2Client,
245
+ clients[channelId],
246
+ `cmnd/${clients[channelId]._fallBackName}/ZbSend`,
247
+ zbSendCommand,
248
+ adapter.config.defaultQoS,
249
+ );
250
+ } else {
251
+ adapter.log.warn(`Cannot send ZbSend command for device ${deviceId}: client not found`);
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Transform shutter state names to correct Tasmota command format
257
+ * e.g., "Shutter1_Position" -> "ShutterPosition1"
258
+ * e.g., "Shutter1_Tilt" -> "ShutterTilt1"
259
+ *
260
+ * @param {string} stateId - The original state ID to transform
261
+ * @returns {string} The transformed state ID for Tasmota commands
262
+ */
263
+ function transformShutterStateId(stateId) {
264
+ const shutterMatch = stateId.match(/^Shutter(\d+)_(Position|Direction|Target|Tilt)$/);
265
+ if (shutterMatch) {
266
+ const shutterNumber = shutterMatch[1];
267
+ const command = shutterMatch[2];
268
+ return `Shutter${command}${shutterNumber}`;
269
+ }
270
+ return stateId;
271
+ }
272
+
210
273
  function setStateImmediate(channelId, stateId, val) {
274
+ // Transform shutter state names to correct Tasmota command format
275
+ const transformedStateId = transformShutterStateId(stateId);
276
+
211
277
  if (clients[channelId] && clients[channelId]._map && clients[channelId]._map[stateId]) {
212
- setImmediate(sendState2Client, clients[channelId], clients[channelId]._map[stateId] || (`cmnd/sonoff/${stateId}`), val, adapter.config.defaultQoS);
278
+ setImmediate(
279
+ sendState2Client,
280
+ clients[channelId],
281
+ clients[channelId]._map[stateId] || `cmnd/sonoff/${transformedStateId}`,
282
+ val,
283
+ adapter.config.defaultQoS,
284
+ );
213
285
  } else if (clients[channelId] && clients[channelId]._fallBackName) {
214
- setImmediate(sendState2Client, clients[channelId], `cmnd/${clients[channelId]._fallBackName}/${stateId}`, val, adapter.config.defaultQoS);
286
+ setImmediate(
287
+ sendState2Client,
288
+ clients[channelId],
289
+ `cmnd/${clients[channelId]._fallBackName}/${transformedStateId}`,
290
+ val,
291
+ adapter.config.defaultQoS,
292
+ );
215
293
  } else {
216
294
  adapter.log.warn(`Unknown mapping for "${stateId}"`);
217
295
  }
@@ -228,7 +306,7 @@ function MQTTServer(adapter) {
228
306
  val = undefined;
229
307
  }
230
308
  if (val !== undefined) {
231
- task.cb = _setState;
309
+ task.cb = _setState;
232
310
  task.cbArg = val;
233
311
  }
234
312
  tasks.push(task);
@@ -240,19 +318,37 @@ function MQTTServer(adapter) {
240
318
  if (tasks.length === 1) {
241
319
  processTasks();
242
320
  }
243
- }
321
+ }
244
322
 
245
- const specVars = ['Red', 'Green', 'Blue', 'WW', 'CW', 'Color', 'RGB_POWER', 'WW_POWER', 'CW_POWER', 'Hue', 'Saturation'];
323
+ const specVars = [
324
+ 'Red',
325
+ 'Green',
326
+ 'Blue',
327
+ 'WW',
328
+ 'CW',
329
+ 'Color',
330
+ 'RGB_POWER',
331
+ 'WW_POWER',
332
+ 'CW_POWER',
333
+ 'Hue',
334
+ 'Saturation',
335
+ ];
246
336
  function onStateChangedColors(id, state, channelId, stateId) {
247
337
  if (!channelId) {
248
- let parts = id.split('.');
249
- stateId = parts.pop();
250
-
251
- if (stateId === 'level' || stateId === 'state' || stateId === 'red' || stateId === 'blue' || stateId === 'green') {
338
+ let parts = id.split('.');
339
+ stateId = parts.pop();
340
+
341
+ if (
342
+ stateId === 'level' ||
343
+ stateId === 'state' ||
344
+ stateId === 'red' ||
345
+ stateId === 'blue' ||
346
+ stateId === 'green'
347
+ ) {
252
348
  stateId = `${parts.pop()}.${stateId}`;
253
349
  }
254
350
 
255
- channelId = parts.splice(2, parts.length).join('.');
351
+ channelId = parts.splice(2, parts.length).join('.');
256
352
  }
257
353
  const ledModeIdExor = `${adapter.namespace}.${channelId}.modeLedExor`;
258
354
  if (cachedModeExor[ledModeIdExor] === undefined) {
@@ -278,7 +374,9 @@ function MQTTServer(adapter) {
278
374
  if (!obj) {
279
375
  adapter.log.warn(`Invalid rgbww obj for ${id}`);
280
376
  } else if (typeof state.val !== 'string') {
281
- adapter.log.warn(`Invalid rgbww state value for ${id} : ${JSON.stringify(state.val)} needs to be a string`);
377
+ adapter.log.warn(
378
+ `Invalid rgbww state value for ${id} : ${JSON.stringify(state.val)} needs to be a string`,
379
+ );
282
380
  } else {
283
381
  const role = obj.common.role;
284
382
  let color;
@@ -311,13 +409,13 @@ function MQTTServer(adapter) {
311
409
  const hidE = id.split('.');
312
410
  const deviceDesc = `${hidE[0]}.${hidE[1]}.${hidE[2]}`;
313
411
  let idAlive = `${deviceDesc}.Red`;
314
- adapter.setState(idAlive, 100 * parseInt(color.substring(0, 2), 16) / 255, true);
412
+ adapter.setState(idAlive, (100 * parseInt(color.substring(0, 2), 16)) / 255, true);
315
413
 
316
414
  idAlive = `${deviceDesc}.Green`;
317
- adapter.setState(idAlive, 100 * parseInt(color.substring(2, 4), 16) / 255, true);
415
+ adapter.setState(idAlive, (100 * parseInt(color.substring(2, 4), 16)) / 255, true);
318
416
 
319
417
  idAlive = `${deviceDesc}.Blue`;
320
- adapter.setState(idAlive, 100 * parseInt(color.substring(4, 6), 16) / 255, true);
418
+ adapter.setState(idAlive, (100 * parseInt(color.substring(4, 6), 16)) / 255, true);
321
419
  }
322
420
  });
323
421
  } else {
@@ -325,8 +423,10 @@ function MQTTServer(adapter) {
325
423
  const deviceDesc = `${hidE[0]}.${hidE[1]}.${hidE[2]}`;
326
424
  if (stateId.match(/Red\d?/)) {
327
425
  // set red component
328
- if (state.val > 100) state.val = 100;
329
- const red = toPaddedHexString(Math.floor(255 * state.val / 100), 2);
426
+ if (state.val > 100) {
427
+ state.val = 100;
428
+ }
429
+ const red = toPaddedHexString(Math.floor((255 * state.val) / 100), 2);
330
430
  const idAlive = `${deviceDesc}.Color`;
331
431
  adapter.getForeignState(idAlive, (err, state) => {
332
432
  if (!state) {
@@ -339,11 +439,12 @@ function MQTTServer(adapter) {
339
439
  adapter.setState(idAlive, `#${out}`, false);
340
440
  setColor(channelId, out);
341
441
  });
342
- } else
343
- if (stateId.match(/Green\d?/)) {
442
+ } else if (stateId.match(/Green\d?/)) {
344
443
  // set green component
345
- if (state.val > 100) state.val = 100;
346
- const green = toPaddedHexString(Math.floor(255 * state.val / 100), 2);
444
+ if (state.val > 100) {
445
+ state.val = 100;
446
+ }
447
+ const green = toPaddedHexString(Math.floor((255 * state.val) / 100), 2);
347
448
  const idAlive = `${deviceDesc}.Color`;
348
449
  adapter.getForeignState(idAlive, (err, state) => {
349
450
  if (!state) {
@@ -356,11 +457,12 @@ function MQTTServer(adapter) {
356
457
  adapter.setState(idAlive, `#${out}`, false);
357
458
  setColor(channelId, out);
358
459
  });
359
- } else
360
- if (stateId.match(/Blue\d?/)) {
460
+ } else if (stateId.match(/Blue\d?/)) {
361
461
  // set blue component
362
- if (state.val > 100) state.val = 100;
363
- const blue = toPaddedHexString(Math.floor(255 * state.val / 100), 2);
462
+ if (state.val > 100) {
463
+ state.val = 100;
464
+ }
465
+ const blue = toPaddedHexString(Math.floor((255 * state.val) / 100), 2);
364
466
  const idAlive = `${deviceDesc}.Color`;
365
467
  adapter.getForeignState(idAlive, (err, state) => {
366
468
  if (!state) {
@@ -373,8 +475,7 @@ function MQTTServer(adapter) {
373
475
  adapter.setState(idAlive, `#${out}`, false);
374
476
  setColor(channelId, out);
375
477
  });
376
- } else
377
- if (stateId.match(/RGB_POWER\d?/)) {
478
+ } else if (stateId.match(/RGB_POWER\d?/)) {
378
479
  // set ww component
379
480
  const rgbpow = state.val === 'true' || state.val === true || state.val === 1 || state.val === '1';
380
481
  const idAlive = `${deviceDesc}.Color`;
@@ -407,16 +508,16 @@ function MQTTServer(adapter) {
407
508
  setColor(channelId, out);
408
509
  adapter.setState(idAlive, `#${out}`, false);
409
510
  if (rgbpow) {
410
- setPower(channelId, true)
511
+ setPower(channelId, true);
411
512
  }
412
513
  // if led_mode&1, exor white leds
413
-
414
514
  });
415
- } else
416
- // calc hue + saturation params to rgb
417
- if (hueCalc && stateId.match(/Hue\d?/)) {
515
+ } else if (hueCalc && stateId.match(/Hue\d?/)) {
516
+ // calc hue + saturation params to rgb
418
517
  let hue = state.val;
419
- if (hue > 359) hue = 359;
518
+ if (hue > 359) {
519
+ hue = 359;
520
+ }
420
521
  // recalc color by hue
421
522
  const idAlive = `${deviceDesc}.Dimmer`;
422
523
  adapter.getForeignState(idAlive, (err, state) => {
@@ -441,10 +542,11 @@ function MQTTServer(adapter) {
441
542
  });
442
543
  }
443
544
  });
444
- } else
445
- if (hueCalc && stateId.match(/Saturation\d?/)) {
545
+ } else if (hueCalc && stateId.match(/Saturation\d?/)) {
446
546
  let sat = state.val;
447
- if (sat > 100) sat = 100;
547
+ if (sat > 100) {
548
+ sat = 100;
549
+ }
448
550
  // recalc color by saturation
449
551
  const idAlive = `${deviceDesc}.Dimmer`;
450
552
  adapter.getForeignState(idAlive, (err, state) => {
@@ -488,7 +590,8 @@ function MQTTServer(adapter) {
488
590
 
489
591
  if (stateId.match(/WW_POWER\d?/)) {
490
592
  // set ww component
491
- const wwpow = state.val === 'true' || state.val === true || state.val === 1 || state.val === '1';
593
+ const wwpow =
594
+ state.val === 'true' || state.val === true || state.val === 1 || state.val === '1';
492
595
  const idAlive = `${deviceDesc}.Color`;
493
596
  adapter.getForeignState(idAlive, (err, state) => {
494
597
  if (!state) {
@@ -506,24 +609,24 @@ function MQTTServer(adapter) {
506
609
  //adapter.log.info('reset white');
507
610
  out = `000000${ww}`;
508
611
  let idAlive = `${deviceDesc}.RGB_POWER`;
509
- adapter.setState(idAlive,false, false);
612
+ adapter.setState(idAlive, false, false);
510
613
  }
511
614
 
512
615
  let idAlive = `${deviceDesc}.Color`;
513
- adapter.setState(idAlive,`#${out}`, false);
616
+ adapter.setState(idAlive, `#${out}`, false);
514
617
  setColor(channelId, out);
515
618
 
516
619
  // set ww channel
517
620
  idAlive = `${deviceDesc}.WW`;
518
- adapter.setState(idAlive, 100 * parseInt(out.substring(6, 8), 16) / 255, true);
621
+ adapter.setState(idAlive, (100 * parseInt(out.substring(6, 8), 16)) / 255, true);
519
622
 
520
623
  // in case POWER is off, switch it on
521
- wwpow && setPower(channelId, true)
624
+ wwpow && setPower(channelId, true);
522
625
  });
523
- } else
524
- if (stateId.match(/CW_POWER\d?/)) {
626
+ } else if (stateId.match(/CW_POWER\d?/)) {
525
627
  // set ww component
526
- const cwpow = state.val === 'true' || state.val === true || state.val === 1 || state.val === '1';
628
+ const cwpow =
629
+ state.val === 'true' || state.val === true || state.val === 1 || state.val === '1';
527
630
  const idAlive = `${deviceDesc}.Color`;
528
631
  adapter.getForeignState(idAlive, (err, state) => {
529
632
  if (!state) {
@@ -550,7 +653,7 @@ function MQTTServer(adapter) {
550
653
 
551
654
  // set cw channel
552
655
  idAlive = `${deviceDesc}.CW`;
553
- adapter.setState(idAlive, 100 * parseInt(out.substring(6, 8), 16) / 255, true);
656
+ adapter.setState(idAlive, (100 * parseInt(out.substring(6, 8), 16)) / 255, true);
554
657
 
555
658
  // in case POWER is off, switch it on
556
659
  if (cwpow) {
@@ -558,10 +661,9 @@ function MQTTServer(adapter) {
558
661
  adapter.setState(idAlive, true, false);
559
662
  }
560
663
  });
561
- } else
562
- if (stateId.match(/WW\d?/)) {
664
+ } else if (stateId.match(/WW\d?/)) {
563
665
  // set ww component
564
- const ww = toPaddedHexString(Math.floor(255 * state.val / 100), 2);
666
+ const ww = toPaddedHexString(Math.floor((255 * state.val) / 100), 2);
565
667
  const idAlive = `${deviceDesc}.Color`;
566
668
  adapter.getForeignState(idAlive, (err, state) => {
567
669
  if (!state) {
@@ -573,10 +675,9 @@ function MQTTServer(adapter) {
573
675
  const out = color.substring(0, wwindex) + ww;
574
676
  setColor(channelId, out);
575
677
  });
576
- } else
577
- if (stateId.match(/CW\d?/)) {
678
+ } else if (stateId.match(/CW\d?/)) {
578
679
  // set ww component
579
- const cw = toPaddedHexString(Math.floor(255 * state.val / 100), 2);
680
+ const cw = toPaddedHexString(Math.floor((255 * state.val) / 100), 2);
580
681
  const idAlive = `${deviceDesc}.Color`;
581
682
  adapter.getForeignState(idAlive, (err, state) => {
582
683
  if (!state) {
@@ -599,9 +700,30 @@ function MQTTServer(adapter) {
599
700
  adapter.log.debug(`onStateChange ${id}: ${JSON.stringify(state)}`);
600
701
  if (server && state && !state.ack) {
601
702
  // find client.id
602
- let parts = id.split('.');
603
- const stateId = parts.pop();
703
+ let parts = id.split('.');
704
+ const stateId = parts.pop();
604
705
  const channelId = parts.splice(2, parts.length).join('.');
706
+
707
+ // Check if this is a Zigbee device state change
708
+ // Pattern: ZbReceived_DEVICEID_ATTRIBUTE (e.g., ZbReceived_0x0856_Power)
709
+ const zbMatch = stateId.match(/^ZbReceived_([^_]+)_(Power|Dimmer)$/);
710
+ if (zbMatch && clients[mappingClients[channelId]]) {
711
+ const deviceId = zbMatch[1];
712
+ const attribute = zbMatch[2];
713
+ let zbValue;
714
+
715
+ // Convert values for Zigbee commands
716
+ if (attribute === 'Power') {
717
+ zbValue = state.val ? '1' : '0';
718
+ } else if (attribute === 'Dimmer') {
719
+ zbValue = state.val;
720
+ }
721
+
722
+ adapter.log.debug(`Sending ZbSend command for device ${deviceId}: ${attribute}=${zbValue}`);
723
+ setZbSendCommand(mappingClients[channelId], deviceId, attribute, zbValue);
724
+ return;
725
+ }
726
+
605
727
  if (clients[mappingClients[channelId]]) {
606
728
  // check for special led-stripe vars
607
729
  if (!specVars.includes(stateId)) {
@@ -616,10 +738,18 @@ function MQTTServer(adapter) {
616
738
  setStateImmediate(mappingClients[channelId], stateId, state.val ? 'ON' : 'OFF');
617
739
  break;
618
740
  case 'number':
619
- setStateImmediate(mappingClients[channelId], stateId, (state.val === null) ? '' : state.val.toString());
741
+ setStateImmediate(
742
+ mappingClients[channelId],
743
+ stateId,
744
+ state.val === null ? '' : state.val.toString(),
745
+ );
620
746
  break;
621
747
  case 'string':
622
- setStateImmediate(mappingClients[channelId], stateId, (state.val === null) ? '' : state.val.toString());
748
+ setStateImmediate(
749
+ mappingClients[channelId],
750
+ stateId,
751
+ state.val === null ? '' : state.val.toString(),
752
+ );
623
753
  break;
624
754
  }
625
755
  }
@@ -634,7 +764,9 @@ function MQTTServer(adapter) {
634
764
  // or state = false
635
765
  // or state.ack = true
636
766
  // or clients[channelId] = false
637
- adapter.log.warn(`Client "${channelId}" not connected`);
767
+ if (!adapter.config.ignoreNotConnectedWarnings) {
768
+ adapter.log.info(`Client "${channelId}" not connected`);
769
+ }
638
770
 
639
771
  /*
640
772
  if (!clients[channelId]) {
@@ -679,9 +811,9 @@ function MQTTServer(adapter) {
679
811
  task.data = JSON.parse(JSON.stringify(task.data)); // copy
680
812
  delete task.data.common.storeMap;
681
813
  }
682
- adapter.setForeignObject(task.id, task.data, (err) => {
814
+ adapter.setForeignObject(task.id, task.data, err => {
683
815
  if (err) {
684
- adapter.log.warn(`New object creation error: ${err.message}`)
816
+ adapter.log.warn(`New object creation error: ${err.message}`);
685
817
  } else {
686
818
  adapter.log.info(`New object created: ${task.id}`);
687
819
  }
@@ -702,7 +834,7 @@ function MQTTServer(adapter) {
702
834
  }
703
835
  adapter.setForeignObject(task.id, obj, err => {
704
836
  if (err) {
705
- adapter.log.warn(`Object update error: ${err.message}`)
837
+ adapter.log.warn(`Object update error: ${err.message}`);
706
838
  } else {
707
839
  adapter.log.info(`Object updated: ${task.id}`);
708
840
  }
@@ -787,14 +919,14 @@ function MQTTServer(adapter) {
787
919
  _id: id,
788
920
  common: {
789
921
  name: client.id,
790
- desc: ''
922
+ desc: '',
791
923
  },
792
924
  native: {
793
- clientId: client.id
925
+ clientId: client.id,
794
926
  },
795
- type: 'channel'
927
+ type: 'channel',
796
928
  };
797
- tasks.push({type: 'addObject', id: obj._id, data: obj});
929
+ tasks.push({ type: 'addObject', id: obj._id, data: obj });
798
930
 
799
931
  obj = {
800
932
  _id: `${id}.alive`,
@@ -803,11 +935,11 @@ function MQTTServer(adapter) {
803
935
  role: 'indicator.reachable',
804
936
  read: true,
805
937
  write: false,
806
- name: `${client.id} alive`
938
+ name: `${client.id} alive`,
807
939
  },
808
- type: 'state'
940
+ type: 'state',
809
941
  };
810
- tasks.push({type: 'addObject', id: obj._id, data: obj});
942
+ tasks.push({ type: 'addObject', id: obj._id, data: obj });
811
943
  if (isStart) {
812
944
  processTasks(callback);
813
945
  } else {
@@ -815,14 +947,56 @@ function MQTTServer(adapter) {
815
947
  }
816
948
  }
817
949
 
818
- function updateClients() {
950
+ async function updateClients() {
951
+ const clientIds = [];
952
+ if (clients) {
953
+ for (const id in clients) {
954
+ const oid = `info.clients.${id.replace(/[.\s]+/g, '_').replace(FORBIDDEN_CHARS, '_')}`;
955
+ clientIds.push(oid);
956
+ const clientObj = await adapter.getObjectAsync(oid);
957
+ if (!clientObj?.native) {
958
+ await adapter.setObjectAsync(oid, {
959
+ type: 'state',
960
+ common: {
961
+ name: id,
962
+ role: 'indicator.reachable',
963
+ type: 'boolean',
964
+ read: true,
965
+ write: false,
966
+ },
967
+ native: {
968
+ ip: clients[id].stream.remoteAddress,
969
+ port: clients[id].stream.remotePort,
970
+ },
971
+ });
972
+ } else {
973
+ if (
974
+ clients[id] &&
975
+ (clientObj.native.port !== clients[id].stream.remotePort ||
976
+ clientObj.native.ip !== clients[id].stream.remoteAddress)
977
+ ) {
978
+ clientObj.native.port = clients[id].stream.remotePort;
979
+ clientObj.native.ip = clients[id].stream.remoteAddress;
980
+ adapter.setObject(clientObj._id, clientObj);
981
+ }
982
+ }
983
+ await adapter.setStateAsync(oid, true, true);
984
+ }
985
+ }
986
+ // read all other states and set alive to false
987
+ const allStates = await adapter.getStatesAsync('info.clients.*');
988
+ for (const id in allStates) {
989
+ if (!clientIds.includes(id.replace(`${adapter.namespace}.`, ''))) {
990
+ await adapter.setStateAsync(id, { val: false, ack: true });
991
+ }
992
+ }
993
+
819
994
  let text = '';
820
995
  if (clients) {
821
996
  for (let id in clients) {
822
997
  text += `${text ? ',' : ''}${id}`;
823
998
  }
824
999
  }
825
-
826
1000
  adapter.setState('info.connection', text, true);
827
1001
  }
828
1002
 
@@ -846,8 +1020,8 @@ function MQTTServer(adapter) {
846
1020
  retain = undefined;
847
1021
  }
848
1022
  adapter.log.debug(`Send to "${client.id}": ${topic} = ${state}`);
849
- client.publish({topic: topic, payload: state, qos: qos, retain: retain, messageId: messageId++}, cb);
850
- messageId &= 0xFFFFFFFF;
1023
+ client.publish({ topic: topic, payload: state, qos: qos, retain: retain, messageId: messageId++ }, cb);
1024
+ messageId &= 0xffffffff;
851
1025
  }
852
1026
 
853
1027
  function resendMessages2Client(client, messages, i) {
@@ -856,7 +1030,9 @@ function MQTTServer(adapter) {
856
1030
  try {
857
1031
  messages[i].ts = Date.now();
858
1032
  messages[i].count++;
859
- adapter.log.debug(`Client [${client.id}] resend messages on connect: ${messages[i].topic} = ${messages[i].payload}`);
1033
+ adapter.log.debug(
1034
+ `Client [${client.id}] resend messages on connect: ${messages[i].topic} = ${messages[i].payload}`,
1035
+ );
860
1036
  if (messages[i].cmd === 'publish') {
861
1037
  client.publish(messages[i]);
862
1038
  }
@@ -874,18 +1050,20 @@ function MQTTServer(adapter) {
874
1050
  }
875
1051
  }
876
1052
 
877
- function addObject(attr, client, prefix, path) {
878
- let replaceAttr = types[attr].replace || attr;
1053
+ function addObject(typeKey, client, prefix, path) {
1054
+ // Extract the actual attribute name for the state ID construction
1055
+ const attr = typeKey.includes('_') && path.length > 0 ? typeKey.split('_').pop() : typeKey;
1056
+ let replaceAttr = types[typeKey].replace || attr;
879
1057
  let id = `${adapter.namespace}.${client.iobId}.${prefix ? `${prefix}.` : ''}${path.length ? `${path.join('_')}_` : ''}${replaceAttr.replace(FORBIDDEN_CHARS, '_')}`;
880
1058
  let obj = {
881
1059
  type: 'addObject',
882
1060
  id: id,
883
1061
  data: {
884
1062
  _id: id,
885
- common: Object.assign({}, types[attr]),
1063
+ common: Object.assign({}, types[typeKey]),
886
1064
  native: {},
887
- type: 'state'
888
- }
1065
+ type: 'state',
1066
+ },
889
1067
  };
890
1068
  obj.data.common.name = `${client.id} ${prefix ? `${prefix} ` : ''}${path.length ? `${path.join(' ')} ` : ''} ${replaceAttr}`;
891
1069
  return obj;
@@ -905,26 +1083,35 @@ function MQTTServer(adapter) {
905
1083
  });
906
1084
  }
907
1085
 
908
- path = path || [];
1086
+ path = path || [];
909
1087
  prefix = prefix || '';
910
1088
 
1089
+ // Extract pressure and temperature units if available at this level for nested data
1090
+ const pressureUnit = data.PressureUnit || (unit && typeof unit === 'object' ? unit.pressureUnit : undefined);
1091
+ const tempUnit = data.TempUnit || (unit && typeof unit === 'object' ? unit.tempUnit : undefined);
1092
+
911
1093
  for (let attr in data) {
912
- if (!data.hasOwnProperty(attr)) {
1094
+ if (!Object.prototype.hasOwnProperty.call(data, attr)) {
913
1095
  adapter.log.warn(`[${client.id}] attr error: ${attr}${data[attr]}`);
914
1096
  continue;
915
1097
  }
916
1098
 
1099
+ // Skip unit fields - they are used for dynamic unit override, not as states
1100
+ if (attr === 'TempUnit' || attr === 'PressureUnit') {
1101
+ continue;
1102
+ }
1103
+
917
1104
  if (typeof data[attr] === 'object') {
918
1105
  // check for arrays
919
1106
  if (types[attr]) {
920
1107
  if (types[attr].type === 'array') {
921
- // transform to array of attributes
1108
+ // transform to an array of attributes
922
1109
  for (let i = 1; i <= 10; i++) {
923
1110
  let val = data[attr][i - 1];
924
1111
  if (typeof val === 'undefined') {
925
1112
  break;
926
1113
  }
927
- // define new object
1114
+ // define a new object
928
1115
  let replaceAttr = attr.replace(FORBIDDEN_CHARS, '_') + i.toString();
929
1116
  let id = `${adapter.namespace}.${client.iobId}.${prefix ? `${prefix}.` : ''}${path.length ? `${path.join('_')}_` : ''}${replaceAttr}`;
930
1117
  let obj = {
@@ -934,8 +1121,8 @@ function MQTTServer(adapter) {
934
1121
  _id: id,
935
1122
  common: Object.assign({}, types[attr]),
936
1123
  native: {},
937
- type: 'state'
938
- }
1124
+ type: 'state',
1125
+ },
939
1126
  };
940
1127
  obj.data.common.name = `${client.id} ${prefix ? `${prefix} ` : ''}${path.length ? `${path.join(' ')} ` : ''} ${replaceAttr}`;
941
1128
  obj.data.common.type = typeof val;
@@ -947,29 +1134,45 @@ function MQTTServer(adapter) {
947
1134
  } else {
948
1135
  let nPath = Object.assign([], path);
949
1136
  nPath.push(attr.replace(FORBIDDEN_CHARS, '_'));
950
- checkData(client, topic, prefix, data[attr], unit, nPath);
1137
+ const nestedUnits = pressureUnit || tempUnit ? { pressureUnit, tempUnit } : unit;
1138
+ checkData(client, topic, prefix, data[attr], nestedUnits, nPath);
951
1139
  nPath = undefined;
952
1140
  }
953
1141
  } else {
954
1142
  let nPath = Object.assign([], path);
955
1143
  nPath.push(attr.replace(FORBIDDEN_CHARS, '_'));
1144
+ const nestedUnits = pressureUnit || tempUnit ? { pressureUnit, tempUnit } : unit;
956
1145
  if (adapter.config.OBJ_TREE) {
957
- checkData(client, topic, `${prefix}.${nPath}`, data[attr], unit);
1146
+ checkData(client, topic, `${prefix}.${nPath}`, data[attr], nestedUnits);
958
1147
  } else {
959
- checkData(client, topic, prefix, data[attr], unit, nPath);
1148
+ checkData(client, topic, prefix, data[attr], nestedUnits, nPath);
960
1149
  }
961
1150
  nPath = undefined;
962
1151
  }
963
- } else if (types[attr]) {
1152
+ } else if (types[attr] || (path.length > 0 && types[`${path.join('_')}_${attr}`])) {
964
1153
  let allowReadColors;
965
1154
 
1155
+ // Check for path-based type definition first, then fallback to simple attr type
1156
+ const typeKey =
1157
+ path.length > 0 && types[`${path.join('_')}_${attr}`] ? `${path.join('_')}_${attr}` : attr;
1158
+
966
1159
  // create object
967
- const obj = addObject(attr, client, prefix, path);
968
- let replaceAttr = types[attr].replace || attr;
1160
+ const obj = addObject(typeKey, client, prefix, path);
1161
+ let replaceAttr = types[typeKey].replace || attr;
969
1162
 
970
1163
  if (obj.data.common.storeMap) {
971
1164
  delete obj.data.common.storeMap;
972
- client._map[replaceAttr] = topic.replace(/$\w+\//, 'cmnd/').replace(/\/\w+$/, `/${replaceAttr}`);
1165
+ client._map[replaceAttr] = topic.replace(/^\w+\//, 'cmnd/').replace(/\/\w+$/, `/${replaceAttr}`);
1166
+ }
1167
+
1168
+ // handle dynamic pressure unit override
1169
+ if (attr === 'Pressure' && pressureUnit && typeof pressureUnit === 'string') {
1170
+ obj.data.common.unit = pressureUnit;
1171
+ }
1172
+
1173
+ // handle dynamic temperature unit override
1174
+ if (attr === 'Temperature' && tempUnit && typeof tempUnit === 'string') {
1175
+ obj.data.common.unit = tempUnit;
973
1176
  }
974
1177
 
975
1178
  // adaptions for magichome tasmota
@@ -979,7 +1182,7 @@ function MQTTServer(adapter) {
979
1182
 
980
1183
  // if ledFlags bit 2, read color from tasmota, else ignore
981
1184
  if (data[attr].length === 10) {
982
- obj.data.common.role = 'level.color.rgbcwww'; // RGB + cold white + white???
1185
+ obj.data.common.role = 'level.color.rgbcwww'; // RGB + cold white + white???
983
1186
  } else if (data[attr].length === 8) {
984
1187
  obj.data.common.role = 'level.color.rgbww'; // RGB + White
985
1188
  } else {
@@ -1002,15 +1205,24 @@ function MQTTServer(adapter) {
1002
1205
 
1003
1206
  xObj = addObject('Red', client, prefix, path);
1004
1207
  xObj.data.common.read = allowReadColors;
1005
- updateState(xObj, allowReadColors ? 100 * parseInt(data[attr].substring(0, 2), 16) / 255 : undefined);
1208
+ updateState(
1209
+ xObj,
1210
+ allowReadColors ? (100 * parseInt(data[attr].substring(0, 2), 16)) / 255 : undefined,
1211
+ );
1006
1212
 
1007
1213
  xObj = addObject('Green', client, prefix, path);
1008
1214
  xObj.data.common.read = allowReadColors;
1009
- updateState(xObj, allowReadColors ? 100 * parseInt(data[attr].substring(2, 4), 16) / 255 : undefined);
1215
+ updateState(
1216
+ xObj,
1217
+ allowReadColors ? (100 * parseInt(data[attr].substring(2, 4), 16)) / 255 : undefined,
1218
+ );
1010
1219
 
1011
1220
  xObj = addObject('Blue', client, prefix, path);
1012
1221
  xObj.data.common.read = allowReadColors;
1013
- updateState(xObj, allowReadColors ? 100 * parseInt(data[attr].substring(4, 6), 16) / 255 : undefined);
1222
+ updateState(
1223
+ xObj,
1224
+ allowReadColors ? (100 * parseInt(data[attr].substring(4, 6), 16)) / 255 : undefined,
1225
+ );
1014
1226
 
1015
1227
  xObj = addObject('RGB_POWER', client, prefix, path);
1016
1228
  xObj.data.common.read = allowReadColors;
@@ -1021,30 +1233,38 @@ function MQTTServer(adapter) {
1021
1233
  // rgbww
1022
1234
  xObj = addObject('WW', client, prefix, path);
1023
1235
  xObj.data.common.read = allowReadColors;
1024
- updateState(xObj, allowReadColors ? 100 * parseInt(data[attr].substring(6, 8), 16) / 255 : undefined);
1236
+ updateState(
1237
+ xObj,
1238
+ allowReadColors ? (100 * parseInt(data[attr].substring(6, 8), 16)) / 255 : undefined,
1239
+ );
1025
1240
 
1026
1241
  xObj = addObject('WW_POWER', client, prefix, path);
1027
1242
  xObj.data.common.read = allowReadColors;
1028
- updateState(xObj, allowReadColors ? (val > 0) : undefined);
1029
- } else
1030
- if (obj.data.common.role === 'level.color.rgbcwww') {
1243
+ updateState(xObj, allowReadColors ? val > 0 : undefined);
1244
+ } else if (obj.data.common.role === 'level.color.rgbcwww') {
1031
1245
  // rgbcwww
1032
1246
  xObj = addObject('CW', client, prefix, path);
1033
1247
  xObj.data.common.read = allowReadColors;
1034
- updateState(xObj, allowReadColors ? 100 * parseInt(data[attr].substring(6, 8), 16) / 255 : undefined);
1248
+ updateState(
1249
+ xObj,
1250
+ allowReadColors ? (100 * parseInt(data[attr].substring(6, 8), 16)) / 255 : undefined,
1251
+ );
1035
1252
 
1036
1253
  xObj = addObject('CW_POWER', client, prefix, path);
1037
1254
  xObj.data.common.read = allowReadColors;
1038
- updateState(xObj, allowReadColors ? (val > 0) : undefined);
1255
+ updateState(xObj, allowReadColors ? val > 0 : undefined);
1039
1256
 
1040
1257
  xObj = addObject('WW', client, prefix, path);
1041
1258
  xObj.data.common.read = allowReadColors;
1042
- updateState(xObj, allowReadColors ? 100 * parseInt(data[attr].substring(8, 10), 16) / 255 : undefined);
1259
+ updateState(
1260
+ xObj,
1261
+ allowReadColors ? (100 * parseInt(data[attr].substring(8, 10), 16)) / 255 : undefined,
1262
+ );
1043
1263
 
1044
1264
  xObj = addObject('WW_POWER', client, prefix, path);
1045
1265
  xObj.data.common.read = allowReadColors;
1046
1266
  val = parseInt(data[attr].substring(8, 10), 16);
1047
- updateState(xObj, allowReadColors ? (val > 0) : undefined);
1267
+ updateState(xObj, allowReadColors ? val > 0 : undefined);
1048
1268
  }
1049
1269
  }
1050
1270
  }
@@ -1075,6 +1295,42 @@ function MQTTServer(adapter) {
1075
1295
 
1076
1296
  updateState(obj, val);
1077
1297
 
1298
+ // Special handling for Zigbee devices: create writable control states for Power and Dimmer
1299
+ if (
1300
+ (attr === 'Power' || attr === 'Dimmer') &&
1301
+ prefix === 'SENSOR' &&
1302
+ path.length >= 2 &&
1303
+ path[0] === 'ZbReceived'
1304
+ ) {
1305
+ // Extract device ID from path (e.g., ZbReceived_0x0856_Power -> 0x0856)
1306
+ const deviceId = path[1];
1307
+
1308
+ // Create a writable control state for Zigbee devices
1309
+ let controlObj = Object.assign({}, obj);
1310
+
1311
+ // Modify object properties for control state
1312
+ if (attr === 'Power') {
1313
+ // For Zigbee Power: boolean switch, not power consumption in watts
1314
+ controlObj.data.common = {
1315
+ type: 'boolean',
1316
+ role: 'switch',
1317
+ read: true,
1318
+ write: true,
1319
+ name: `${client.id} ${prefix ? `${prefix} ` : ''}${path.join(' ')} ${attr} Control`,
1320
+ };
1321
+ // Convert numeric power value (0/1) to boolean for control state
1322
+ const controlVal = Boolean(val);
1323
+ updateState(controlObj, controlVal);
1324
+ } else if (attr === 'Dimmer') {
1325
+ // For Zigbee Dimmer: ensure it's writable
1326
+ controlObj.data.common.write = true;
1327
+ controlObj.data.common.name = `${client.id} ${prefix ? `${prefix} ` : ''}${path.join(' ')} ${attr} Control`;
1328
+ updateState(controlObj, val);
1329
+ }
1330
+
1331
+ adapter.log.debug(`Created Zigbee control state for device ${deviceId}: ${controlObj.id}`);
1332
+ }
1333
+
1078
1334
  if (powerStateFound) {
1079
1335
  adapter.log.debug(`Got .STATE.POWER update, auto syncing .POWER (src: ${obj.id})`);
1080
1336
  let powerObj = Object.assign({}, obj);
@@ -1089,22 +1345,23 @@ function MQTTServer(adapter) {
1089
1345
  // tele/sonoff/SENSOR tele/sonoff/STATE => read only
1090
1346
  // stat/sonoff/RESULT => read,write
1091
1347
 
1092
- let parts = topic.split('/');
1093
- // auto generate objects
1094
- if ((parts[0] === 'tele' && (
1095
- (adapter.config.TELE_SENSOR && parts[2] === 'SENSOR') ||
1096
- (adapter.config.STAT_RESULT && parts[2] === 'RESULT') ||
1097
- (adapter.config.TELE_STATE && parts[2] === 'STATE'))) ||
1098
- (parts[0] === 'stat' &&
1099
- (adapter.config.STAT_RESULT && parts[2] === 'RESULT'))
1100
- ) {
1101
- let val = data[attr];
1102
- let attributes = {role: 'value', read: true, write: false};
1103
- if (parts[2] === 'RESULT') {
1104
- // control
1348
+ let parts = topic.split('/');
1349
+ // auto generate objects
1350
+ if (
1351
+ (parts[0] === 'tele' &&
1352
+ ((adapter.config.TELE_SENSOR && parts[2] === 'SENSOR') ||
1353
+ (adapter.config.STAT_RESULT && parts[2] === 'RESULT') ||
1354
+ (adapter.config.TELE_STATE && parts[2] === 'STATE') ||
1355
+ (adapter.config.TELE_MARGINS && parts[2] === 'MARGINS'))) ||
1356
+ (parts[0] === 'stat' && adapter.config.STAT_RESULT && parts[2] === 'RESULT')
1357
+ ) {
1358
+ let val = data[attr];
1359
+ let attributes = { role: 'value', read: true, write: false };
1360
+ if (parts[2] === 'RESULT') {
1361
+ // control
1105
1362
  attributes.write = true;
1106
1363
  attributes.role = 'level';
1107
- }
1364
+ }
1108
1365
 
1109
1366
  attributes.type = typeof val;
1110
1367
  if (!['number', 'string', 'boolean'].includes(attributes.type)) {
@@ -1112,19 +1369,19 @@ function MQTTServer(adapter) {
1112
1369
  val = (val || '').toString();
1113
1370
  }
1114
1371
 
1115
- let replaceAttr = attr ;
1116
- let id = `${adapter.namespace}.${client.iobId}.${prefix ? `${prefix}.` : ''}${path.length ? `${path.join('_')}_` : ''}${replaceAttr}`;
1117
- let obj = {
1118
- type: 'addObject',
1119
- id: id,
1120
- data: {
1121
- _id: id,
1122
- common: attributes,
1123
- native: {},
1124
- type: 'state'
1125
- }
1126
- };
1127
- obj.data.common.name = `${client.id} ${prefix ? `${prefix} ` : ''}${path.length ? `${path.join(' ')} ` : ''} ${replaceAttr}`;
1372
+ let replaceAttr = attr;
1373
+ let id = `${adapter.namespace}.${client.iobId}.${prefix ? `${prefix}.` : ''}${path.length ? `${path.join('_')}_` : ''}${replaceAttr}`;
1374
+ let obj = {
1375
+ type: 'addObject',
1376
+ id: id,
1377
+ data: {
1378
+ _id: id,
1379
+ common: attributes,
1380
+ native: {},
1381
+ type: 'state',
1382
+ },
1383
+ };
1384
+ obj.data.common.name = `${client.id} ${prefix ? `${prefix} ` : ''}${path.length ? `${path.join(' ')} ` : ''} ${replaceAttr}`;
1128
1385
  // due to some (for ex. NSPanel) versions do not automatically send .POWER switch data
1129
1386
  // but this is generally a default for all devices (state cannot exist without switch) we now add
1130
1387
  // POWER switches automatically if we get POWER STATE
@@ -1144,7 +1401,7 @@ function MQTTServer(adapter) {
1144
1401
  powerObj.data.common.name = powerName;
1145
1402
  updateState(powerObj, val);
1146
1403
  }
1147
- }
1404
+ }
1148
1405
  }
1149
1406
  }
1150
1407
  }
@@ -1158,7 +1415,7 @@ function MQTTServer(adapter) {
1158
1415
  client.states[packet.topic] = {
1159
1416
  message: packet.payload,
1160
1417
  retain: packet.retain,
1161
- qos: packet.qos
1418
+ qos: packet.qos,
1162
1419
  };
1163
1420
 
1164
1421
  // update alive state
@@ -1235,8 +1492,12 @@ function MQTTServer(adapter) {
1235
1492
  // ignore: stat/Sonoff/RESULT = {"POWER":"ON"}
1236
1493
  // testserver.js reports error, so reject above cmd
1237
1494
  const str = val.replace(/\s+/g, '');
1238
- if (str.startsWith('{"POWER":"ON"}')) return;
1239
- if (str.startsWith('{"POWER":"OFF"}')) return;
1495
+ if (str.startsWith('{"POWER":"ON"}')) {
1496
+ return;
1497
+ }
1498
+ if (str.startsWith('{"POWER":"OFF"}')) {
1499
+ return;
1500
+ }
1240
1501
 
1241
1502
  if (parts[0] === 'stat' || parts[0] === 'tele') {
1242
1503
  try {
@@ -1266,7 +1527,7 @@ function MQTTServer(adapter) {
1266
1527
  } else if (stateId.match(/^(WAKEUP)\d?$/)) {
1267
1528
  checkData(client, packet.topic, 'WAKEUP', JSON.parse(val));
1268
1529
  } else if (stateId.match(/^(RESULT)\d?$/)) {
1269
- checkData(client, packet.topic, 'WAKEUP', JSON.parse(val));
1530
+ checkData(client, packet.topic, 'RESULT', JSON.parse(val));
1270
1531
  }
1271
1532
  } else {
1272
1533
  checkData(client, packet.topic, NO_PREFIX, JSON.parse(val));
@@ -1291,6 +1552,13 @@ function MQTTServer(adapter) {
1291
1552
  } catch (e) {
1292
1553
  adapter.log.warn(`Client [${client.id}] cannot parse data"${stateId}": _${val}_ - ${e}`);
1293
1554
  }
1555
+ } else if (parts[0] === 'tele' && stateId.match(/^(MARGINS)\d?$/)) {
1556
+ // tele/sonoffPOW/MARGINS = {"Time":"2020-04-23T10:15:00","PowerLow":100,"PowerHigh":2000,"PowerDelta":50}
1557
+ try {
1558
+ checkData(client, packet.topic, 'MARGINS', JSON.parse(val));
1559
+ } catch (e) {
1560
+ adapter.log.warn(`Client [${client.id}] cannot parse data"${stateId}": _${val}_ - ${e}`);
1561
+ }
1294
1562
  } else if (types[stateId]) {
1295
1563
  // /ESP_BOX/BM280/Pressure = 1010.09
1296
1564
  // /ESP_BOX/BM280/Humidity = 42.39
@@ -1301,7 +1569,7 @@ function MQTTServer(adapter) {
1301
1569
  // stat/sonoff/POWER
1302
1570
 
1303
1571
  if (types[stateId]) {
1304
- let id = `${adapter.namespace}.${client.iobId}.${stateId.replace(/[-.+\s]+/g, '_')}`;
1572
+ let id = `${adapter.namespace}.${client.iobId}.${stateId.replace(/[-.+\s]+/g, '_').replace(FORBIDDEN_CHARS, '_')}`;
1305
1573
  let obj = {
1306
1574
  type: 'addObject',
1307
1575
  id: id,
@@ -1309,15 +1577,14 @@ function MQTTServer(adapter) {
1309
1577
  _id: id,
1310
1578
  common: JSON.parse(JSON.stringify(types[stateId])),
1311
1579
  native: {},
1312
- type: 'state'
1313
- }
1580
+ type: 'state',
1581
+ },
1314
1582
  };
1315
1583
  obj.data.common.name = `${client.id} ${stateId}`;
1316
1584
 
1317
1585
  // push only new objects
1318
1586
  updateState(obj, () => {
1319
1587
  if (parts[0] === 'cmnd') {
1320
-
1321
1588
  // Set Object fix
1322
1589
  if (obj.data.common.type === 'number') {
1323
1590
  adapter.setState(id, parseFloat(val), true);
@@ -1354,7 +1621,9 @@ function MQTTServer(adapter) {
1354
1621
  }
1355
1622
 
1356
1623
  function clientClose(client, reason) {
1357
- if (!client) return;
1624
+ if (!client) {
1625
+ return;
1626
+ }
1358
1627
 
1359
1628
  if (persistentSessions[client.id]) {
1360
1629
  persistentSessions[client.id].connected = false;
@@ -1366,7 +1635,7 @@ function MQTTServer(adapter) {
1366
1635
  }
1367
1636
 
1368
1637
  try {
1369
- if (clients[client.id] && (client.__secret === clients[client.id].__secret)) {
1638
+ if (clients[client.id] && client.__secret === clients[client.id].__secret) {
1370
1639
  adapter.log.info(`Client [${client.id}] connection closed: ${reason}`);
1371
1640
  updateAlive(client, false);
1372
1641
  delete clients[client.id];
@@ -1388,12 +1657,18 @@ function MQTTServer(adapter) {
1388
1657
  const now = Date.now();
1389
1658
  resending = true;
1390
1659
  for (const clientId in clients) {
1391
- if (clients.hasOwnProperty(clientId) && clients[clientId] && clients[clientId]._messages) {
1660
+ if (
1661
+ Object.prototype.hasOwnProperty.call(clients, clientId) &&
1662
+ clients[clientId] &&
1663
+ clients[clientId]._messages
1664
+ ) {
1392
1665
  for (let m = clients[clientId]._messages.length - 1; m >= 0; m--) {
1393
1666
  const message = clients[clientId]._messages[m];
1394
1667
  if (now - message.ts >= adapter.config.retransmitInterval) {
1395
1668
  if (message.count > adapter.config.retransmitCount) {
1396
- adapter.log.warn(`Client [${clientId}] message ${message.messageId} deleted after ${message.count} retries`);
1669
+ adapter.log.warn(
1670
+ `Client [${clientId}] message ${message.messageId} deleted after ${message.count} retries`,
1671
+ );
1397
1672
  clients[clientId]._messages.splice(m, 1);
1398
1673
  continue;
1399
1674
  }
@@ -1402,7 +1677,9 @@ function MQTTServer(adapter) {
1402
1677
  message.count++;
1403
1678
  message.ts = now;
1404
1679
  try {
1405
- adapter.log.debug(`Client [${clientId}] resend message topic: ${message.topic}, payload: ${message.payload}`);
1680
+ adapter.log.debug(
1681
+ `Client [${clientId}] resend message topic: ${message.topic}, payload: ${message.payload}`,
1682
+ );
1406
1683
  if (message.cmd === 'publish') {
1407
1684
  clients[clientId].publish(message);
1408
1685
  }
@@ -1424,7 +1701,7 @@ function MQTTServer(adapter) {
1424
1701
  // delete old sessions
1425
1702
  if (adapter.config.storeClientsTime !== -1) {
1426
1703
  for (const id in persistentSessions) {
1427
- if (persistentSessions.hasOwnProperty(id)) {
1704
+ if (Object.prototype.hasOwnProperty.call(persistentSessions, id)) {
1428
1705
  if (now - persistentSessions[id].lastSeen > adapter.config.storeClientsTime * 60000) {
1429
1706
  delete persistentSessions[id];
1430
1707
  }
@@ -1449,22 +1726,21 @@ function MQTTServer(adapter) {
1449
1726
 
1450
1727
  // client connected
1451
1728
  client.on('connect', options => {
1452
- // acknowledge the connect packet
1729
+ // acknowledge the "connect" packet
1453
1730
  client.id = options.clientId;
1454
1731
  client.iobId = client.id.replace(FORBIDDEN_CHARS, '_');
1455
1732
  mappingClients[client.iobId] = client.id;
1456
1733
 
1457
- // get possible old client
1734
+ // get possible an old client
1458
1735
  let oldClient = clients[client.id];
1459
1736
 
1460
1737
  if (config.user) {
1461
1738
  if (options.password) {
1462
1739
  options.password = options.password.toString();
1463
1740
  }
1464
- if (config.user !== options.username ||
1465
- config.password !== options.password) {
1741
+ if (config.user !== options.username || config.password !== options.password) {
1466
1742
  adapter.log.warn(`Client [${client.id}] has invalid password or username`);
1467
- client.connack({returnCode: 4});
1743
+ client.connack({ returnCode: 4 });
1468
1744
  if (oldClient) {
1469
1745
  // delete existing client
1470
1746
  delete clients[client.id];
@@ -1478,12 +1754,13 @@ function MQTTServer(adapter) {
1478
1754
  }
1479
1755
 
1480
1756
  if (oldClient) {
1481
- adapter.log.info(`Client [${client.id}] reconnected. Old secret ${clients[client.id].__secret} ==> New secret ${client.__secret}`);
1757
+ adapter.log.info(
1758
+ `Client [${client.id}] reconnected. Old secret ${clients[client.id].__secret} ==> New secret ${client.__secret}`,
1759
+ );
1482
1760
  // need to destroy the old client
1483
1761
 
1484
1762
  if (client.__secret !== clients[client.id].__secret) {
1485
1763
  // it is another socket!!
1486
-
1487
1764
  // It was following situation:
1488
1765
  // - old connection was active
1489
1766
  // - new connection is on the same TCP
@@ -1505,7 +1782,7 @@ function MQTTServer(adapter) {
1505
1782
  _subsID: {},
1506
1783
  _subs: {},
1507
1784
  messages: [],
1508
- lastSeen: Date.now()
1785
+ lastSeen: Date.now(),
1509
1786
  };
1510
1787
  }
1511
1788
  client._messages = persistentSessions[client.id].messages;
@@ -1515,7 +1792,7 @@ function MQTTServer(adapter) {
1515
1792
  }
1516
1793
  client._messages = client._messages || [];
1517
1794
 
1518
- client.connack({returnCode: 0, sessionPresent});
1795
+ client.connack({ returnCode: 0, sessionPresent });
1519
1796
  clients[client.id] = client;
1520
1797
  updateClients();
1521
1798
 
@@ -1526,10 +1803,14 @@ function MQTTServer(adapter) {
1526
1803
  client._subs = persistentSessions[client.id]._subs;
1527
1804
  if (persistentSessions[client.id].messages.length) {
1528
1805
  // give to the client a little bit time
1529
- client._resendonStart = setTimeout(clientId => {
1530
- client._resendonStart = null;
1531
- resendMessages2Client(client, persistentSessions[clientId].messages);
1532
- }, 100, client.id);
1806
+ client._resendonStart = setTimeout(
1807
+ clientId => {
1808
+ client._resendonStart = null;
1809
+ resendMessages2Client(client, persistentSessions[clientId].messages);
1810
+ },
1811
+ 100,
1812
+ client.id,
1813
+ );
1533
1814
  }
1534
1815
  }
1535
1816
  });
@@ -1549,7 +1830,10 @@ function MQTTServer(adapter) {
1549
1830
 
1550
1831
  client.on('publish', packet => {
1551
1832
  if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
1552
- !adapter.config.ignorePings && adapter.log.warn(`Old client ${client.id} with secret ${client.__secret} sends publish. Ignore! Actual secret is ${clients[client.id].__secret}`);
1833
+ !adapter.config.ignorePings &&
1834
+ adapter.log.warn(
1835
+ `Old client ${client.id} with secret ${client.__secret} sends publish. Ignore! Actual secret is ${clients[client.id].__secret}`,
1836
+ );
1553
1837
  return;
1554
1838
  }
1555
1839
 
@@ -1558,33 +1842,36 @@ function MQTTServer(adapter) {
1558
1842
  }
1559
1843
 
1560
1844
  if (packet.qos === 1) {
1561
- // send PUBACK to client
1562
- client.puback({messageId: packet.messageId});
1845
+ // send PUBACK to a client
1846
+ client.puback({ messageId: packet.messageId });
1563
1847
  } else if (packet.qos === 2) {
1564
1848
  const pack = client._messages && client._messages.find(e => e.messageId === packet.messageId);
1565
1849
  if (pack) {
1566
1850
  // duplicate message => ignore
1567
- adapter.log.warn(`Client [${client.id}] ignored duplicate message with ID: ${packet.messageId}`);
1568
- return;
1569
- } else {
1570
- packet.ts = Date.now();
1571
- packet.cmd = 'pubrel';
1572
- packet.count = 0;
1573
- client._messages.push(packet);
1574
-
1575
- client.pubrec({messageId: packet.messageId});
1851
+ adapter.log.warn(
1852
+ `Client [${client.id}] ignored duplicate message with ID: ${packet.messageId}`,
1853
+ );
1576
1854
  return;
1577
1855
  }
1578
- }
1856
+ packet.ts = Date.now();
1857
+ packet.cmd = 'pubrel';
1858
+ packet.count = 0;
1859
+ client._messages.push(packet);
1579
1860
 
1861
+ client.pubrec({ messageId: packet.messageId });
1862
+ return;
1863
+ }
1580
1864
 
1581
- receivedTopic(packet, client)
1865
+ receivedTopic(packet, client);
1582
1866
  });
1583
1867
 
1584
1868
  // response for QoS2
1585
1869
  client.on('pubrec', packet => {
1586
1870
  if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
1587
- !adapter.config.ignorePings && adapter.log.warn(`Old client ${client.id} with secret ${client.__secret} sends pubrec. Ignore! Actual secret is ${clients[client.id].__secret}`);
1871
+ !adapter.config.ignorePings &&
1872
+ adapter.log.warn(
1873
+ `Old client ${client.id} with secret ${client.__secret} sends pubrec. Ignore! Actual secret is ${clients[client.id].__secret}`,
1874
+ );
1588
1875
  return;
1589
1876
  }
1590
1877
 
@@ -1594,23 +1881,29 @@ function MQTTServer(adapter) {
1594
1881
 
1595
1882
  let pos = null;
1596
1883
  // remove this message from queue
1597
- client._messages && client._messages.forEach((e, i) => {
1598
- if (e.messageId === packet.messageId) {
1599
- pos = i;
1600
- return false;
1601
- }
1602
- });
1884
+ client._messages &&
1885
+ client._messages.forEach((e, i) => {
1886
+ if (e.messageId === packet.messageId) {
1887
+ pos = i;
1888
+ return false;
1889
+ }
1890
+ });
1603
1891
  if (pos !== null) {
1604
- client.pubrel({messageId: packet.messageId});
1892
+ client.pubrel({ messageId: packet.messageId });
1605
1893
  } else {
1606
- adapter.log.warn(`Client [${client.id}] received pubrec on ${client.id} for unknown message ID: ${packet.messageId}`);
1894
+ adapter.log.warn(
1895
+ `Client [${client.id}] received pubrec on ${client.id} for unknown message ID: ${packet.messageId}`,
1896
+ );
1607
1897
  }
1608
1898
  });
1609
1899
 
1610
1900
  // response for QoS2
1611
1901
  client.on('pubcomp', packet => {
1612
1902
  if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
1613
- !adapter.config.ignorePings && adapter.log.warn(`Old client ${client.id} with secret ${client.__secret} sends pubcomp. Ignore! Actual secret is ${clients[client.id].__secret}`);
1903
+ !adapter.config.ignorePings &&
1904
+ adapter.log.warn(
1905
+ `Old client ${client.id} with secret ${client.__secret} sends pubcomp. Ignore! Actual secret is ${clients[client.id].__secret}`,
1906
+ );
1614
1907
  return;
1615
1908
  }
1616
1909
 
@@ -1620,16 +1913,19 @@ function MQTTServer(adapter) {
1620
1913
 
1621
1914
  let pos = null;
1622
1915
  // remove this message from queue
1623
- client._messages && client._messages.forEach((e, i) => {
1624
- if (e.messageId === packet.messageId) {
1625
- pos = i;
1626
- return false;
1627
- }
1628
- });
1916
+ client._messages &&
1917
+ client._messages.forEach((e, i) => {
1918
+ if (e.messageId === packet.messageId) {
1919
+ pos = i;
1920
+ return false;
1921
+ }
1922
+ });
1629
1923
  if (pos !== null) {
1630
1924
  client._messages.splice(pos, 1);
1631
1925
  } else {
1632
- adapter.log.warn(`Client [${client.id}] received pubcomp for unknown message ID: ${packet.messageId}`);
1926
+ adapter.log.warn(
1927
+ `Client [${client.id}] received pubcomp for unknown message ID: ${packet.messageId}`,
1928
+ );
1633
1929
  }
1634
1930
  });
1635
1931
 
@@ -1639,7 +1935,10 @@ function MQTTServer(adapter) {
1639
1935
  return;
1640
1936
  }
1641
1937
  if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
1642
- !adapter.config.ignorePings && adapter.log.warn(`Old client ${client.id} with secret ${client.__secret} sends pubrel. Ignore! Actual secret is ${clients[client.id].__secret}`);
1938
+ !adapter.config.ignorePings &&
1939
+ adapter.log.warn(
1940
+ `Old client ${client.id} with secret ${client.__secret} sends pubrel. Ignore! Actual secret is ${clients[client.id].__secret}`,
1941
+ );
1643
1942
  return;
1644
1943
  }
1645
1944
 
@@ -1649,27 +1948,33 @@ function MQTTServer(adapter) {
1649
1948
 
1650
1949
  let pos = null;
1651
1950
  // remove this message from queue
1652
- client._messages && client._messages.forEach((e, i) => {
1653
- if (e.messageId === packet.messageId) {
1654
- pos = i;
1655
- return false;
1656
- }
1657
- });
1951
+ client._messages &&
1952
+ client._messages.forEach((e, i) => {
1953
+ if (e.messageId === packet.messageId) {
1954
+ pos = i;
1955
+ return false;
1956
+ }
1957
+ });
1658
1958
 
1659
1959
  if (pos !== null) {
1660
- client.pubcomp({messageId: packet.messageId});
1960
+ client.pubcomp({ messageId: packet.messageId });
1661
1961
 
1662
1962
  receivedTopic(client._messages[pos], client);
1663
1963
  client._messages && client._messages.splice(pos, 1);
1664
1964
  } else {
1665
- adapter.log.warn(`Client [${client.id}] received pubrel on ${client.id} for unknown messageId ${packet.messageId}`);
1965
+ adapter.log.warn(
1966
+ `Client [${client.id}] received pubrel on ${client.id} for unknown messageId ${packet.messageId}`,
1967
+ );
1666
1968
  }
1667
1969
  });
1668
1970
 
1669
1971
  // response for QoS1
1670
1972
  client.on('puback', packet => {
1671
1973
  if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
1672
- !adapter.config.ignorePings && adapter.log.warn(`Old client ${client.id} with secret ${client.__secret} sends puback. Ignore! Actual secret is ${clients[client.id].__secret}`);
1974
+ !adapter.config.ignorePings &&
1975
+ adapter.log.warn(
1976
+ `Old client ${client.id} with secret ${client.__secret} sends puback. Ignore! Actual secret is ${clients[client.id].__secret}`,
1977
+ );
1673
1978
  return;
1674
1979
  }
1675
1980
 
@@ -1680,24 +1985,32 @@ function MQTTServer(adapter) {
1680
1985
  // remove this message from queue
1681
1986
  let pos = null;
1682
1987
  // remove this message from queue
1683
- client._messages && client._messages.forEach((e, i) => {
1684
- if (e.messageId === packet.messageId) {
1685
- pos = i;
1686
- return false;
1687
- }
1688
- });
1988
+ client._messages &&
1989
+ client._messages.forEach((e, i) => {
1990
+ if (e.messageId === packet.messageId) {
1991
+ pos = i;
1992
+ return false;
1993
+ }
1994
+ });
1689
1995
 
1690
1996
  if (pos !== null) {
1691
- adapter.log.debug(`Client [${client.id}] received puback for ${client.id} message ID: ${packet.messageId}`);
1997
+ adapter.log.debug(
1998
+ `Client [${client.id}] received puback for ${client.id} message ID: ${packet.messageId}`,
1999
+ );
1692
2000
  client._messages && client._messages.splice(pos, 1);
1693
2001
  } else {
1694
- adapter.log.warn(`Client [${client.id}] received puback for unknown message ID: ${packet.messageId}`);
2002
+ adapter.log.warn(
2003
+ `Client [${client.id}] received puback for unknown message ID: ${packet.messageId}`,
2004
+ );
1695
2005
  }
1696
2006
  });
1697
2007
 
1698
2008
  client.on('unsubscribe', packet => {
1699
2009
  if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
1700
- !adapter.config.ignorePings && adapter.log.warn(`Old client ${client.id} with secret ${client.__secret} sends unsubscribe. Ignore! Actual secret is ${clients[client.id].__secret}`);
2010
+ !adapter.config.ignorePings &&
2011
+ adapter.log.warn(
2012
+ `Old client ${client.id} with secret ${client.__secret} sends unsubscribe. Ignore! Actual secret is ${clients[client.id].__secret}`,
2013
+ );
1701
2014
  return;
1702
2015
  }
1703
2016
 
@@ -1705,12 +2018,15 @@ function MQTTServer(adapter) {
1705
2018
  persistentSessions[client.id].lastSeen = Date.now();
1706
2019
  }
1707
2020
 
1708
- client.unsuback({messageId: packet.messageId});
2021
+ client.unsuback({ messageId: packet.messageId });
1709
2022
  });
1710
2023
 
1711
2024
  client.on('subscribe', packet => {
1712
2025
  if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
1713
- !adapter.config.ignorePings && adapter.log.warn(`Old client ${client.id} with secret ${client.__secret} sends subscribe. Ignore! Actual secret is ${clients[client.id].__secret}`);
2026
+ !adapter.config.ignorePings &&
2027
+ adapter.log.warn(
2028
+ `Old client ${client.id} with secret ${client.__secret} sends subscribe. Ignore! Actual secret is ${clients[client.id].__secret}`,
2029
+ );
1714
2030
  return;
1715
2031
  }
1716
2032
 
@@ -1722,12 +2038,15 @@ function MQTTServer(adapter) {
1722
2038
  // we expect subscribe for 'cmnd.sonoff.#'
1723
2039
  let granted = packet.subscriptions.map(subs => subs.qos);
1724
2040
 
1725
- client.suback({granted: granted, messageId: packet.messageId});
2041
+ client.suback({ granted: granted, messageId: packet.messageId });
1726
2042
  });
1727
2043
 
1728
2044
  client.on('pingreq', (/*packet*/) => {
1729
2045
  if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
1730
- !adapter.config.ignorePings && adapter.log.warn(`Old client ${client.id} with secret ${client.__secret} sends pingreq. Ignore! Actual secret is ${clients[client.id].__secret}`);
2046
+ !adapter.config.ignorePings &&
2047
+ adapter.log.warn(
2048
+ `Old client ${client.id} with secret ${client.__secret} sends pingreq. Ignore! Actual secret is ${clients[client.id].__secret}`,
2049
+ );
1731
2050
  return;
1732
2051
  }
1733
2052
 
@@ -1740,15 +2059,15 @@ function MQTTServer(adapter) {
1740
2059
  });
1741
2060
  });
1742
2061
 
1743
- server.on('error', err =>
1744
- adapter.log.error(`Can not start Server ${err}`));
2062
+ server.on('error', err => adapter.log.error(`Can not start Server ${err}`));
1745
2063
 
1746
2064
  config.port = parseInt(config.port, 10) || 1883;
1747
2065
 
1748
2066
  config.retransmitInterval = config.retransmitInterval || 2000;
1749
- config.retransmitCount = config.retransmitCount || 10;
1750
- config.storeClientsTime = config.storeClientsTime === undefined ? 1440 : parseInt(config.storeClientsTime, 10) || 0;
1751
- config.defaultQoS = parseInt(config.defaultQoS, 10) || 0;
2067
+ config.retransmitCount = config.retransmitCount || 10;
2068
+ config.storeClientsTime =
2069
+ config.storeClientsTime === undefined ? 1440 : parseInt(config.storeClientsTime, 10) || 0;
2070
+ config.defaultQoS = parseInt(config.defaultQoS, 10) || 0;
1752
2071
 
1753
2072
  // Update connection state
1754
2073
  updateClients();
@@ -1757,9 +2076,7 @@ function MQTTServer(adapter) {
1757
2076
  server.listen(config.port, config.bind, () => {
1758
2077
  adapter.log.info(`Starting MQTT ${config.user ? 'authenticated ' : ''} server on port ${config.port}`);
1759
2078
 
1760
- resendTimer = setInterval(() =>
1761
- !resending && checkResends(),
1762
- adapter.config.retransmitInterval || 2000);
2079
+ resendTimer = setInterval(() => !resending && checkResends(), adapter.config.retransmitInterval || 2000);
1763
2080
  });
1764
2081
  })(adapter.config);
1765
2082