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