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/LICENSE +22 -22
- package/README.md +203 -199
- package/admin/i18n/de/translations.json +52 -0
- package/admin/i18n/en/translations.json +52 -0
- package/admin/i18n/es/translations.json +52 -0
- package/admin/i18n/fr/translations.json +52 -0
- package/admin/i18n/it/translations.json +52 -0
- package/admin/i18n/nl/translations.json +52 -0
- package/admin/i18n/pl/translations.json +52 -0
- package/admin/i18n/pt/translations.json +52 -0
- package/admin/i18n/ru/translations.json +52 -0
- package/admin/i18n/zh-cn/translations.json +52 -0
- package/admin/index.html +177 -177
- package/admin/index_m.html +218 -218
- package/admin/words.js +54 -54
- package/io-package.json +14 -13
- package/lib/datapoints.js +193 -193
- package/lib/server.js +1765 -1732
- package/lib/tools.js +99 -99
- package/main.js +93 -93
- package/package.json +20 -9
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -37
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -24
- package/.github/auto-merge.yml +0 -17
- package/.github/dependabot.yml +0 -16
- package/.github/stale.yml +0 -78
- package/.github/workflows/dependabot-automerge.yml +0 -22
- package/.github/workflows/test-and-release.yml +0 -156
- package/.mocharc.json +0 -5
- package/.releaseconfig.json +0 -3
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
|
-
|
|
1065
|
-
|
|
1066
|
-
//
|
|
1067
|
-
//
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
let
|
|
1098
|
-
let
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
//
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
if (
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
if (
|
|
1424
|
-
if (
|
|
1425
|
-
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
persistentSessions[client.id]
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
if (
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
return;
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
}
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
if (
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
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;
|