yunkong2.sonoff/lib/server.js
2019-01-16 09:09:54 +08:00

1644 lines
68 KiB
JavaScript

/* jshint -W097 */
/* jshint strict:true */
/* jslint node: true */
/* jslint esversion: 6 */
'use strict';
const mqtt = require('mqtt-connection');
const net = require('net');
const types = require(__dirname + '/datapoints');
//const memwatch = require('memwatch-next');
//const heapdump = require('heapdump');
const hueCalc = true;
//var heapTest = 0;
const FORBIDDEN_CHARS = /[\]\[*,;'"`<>\\?]/g;
const mappingClients = {};
/*
* HSV to RGB color conversion
*
* H runs from 0 to 360 degrees
* S and V run from 0 to 100
*
* Ported from the excellent java algorithm by Eugene Vishnevsky at:
* http://www.cs.rit.edu/~ncs/color/t_convert.html
*/
function hsvToRgb(h, s, v) {
let r, g, b;
let i;
let f, p, q, t;
// Make sure our arguments stay in-range
h = Math.max(0, Math.min(360, h));
s = Math.max(0, Math.min(100, s));
v = Math.max(0, Math.min(100, v));
// We accept saturation and value arguments from 0 to 100 because that's
// how Photoshop represents those values. Internally, however, the
// saturation and value are calculated from a range of 0 to 1. We make
// That conversion here.
s /= 100;
v /= 100;
if (s === 0) {
// Achromatic (grey)
r = g = b = v;
return [
Math.round(r * 255),
Math.round(g * 255),
Math.round(b * 255)
];
}
h /= 60; // sector 0 to 5
i = Math.floor(h);
f = h - i; // factorial part of h
p = v * (1 - s);
q = v * (1 - s * f);
t = v * (1 - s * (1 - f));
switch (i) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
default: // case 5:
r = v;
g = p;
b = q;
}
return [
Math.round(r * 255),
Math.round(g * 255),
Math.round(b * 255)
];
}
function componentToHex(c) {
const hex = c.toString(16);
return hex.length === 1 ? '0' + hex : hex;
}
function toPaddedHexString(num, len) {
if (len === 2) {
if (num > 255) {
num = 255;
}
}
const str = num.toString(16);
return '0'.repeat(len - str.length) + str;
}
function MQTTServer(adapter) {
if (!(this instanceof MQTTServer)) return new MQTTServer(adapter);
const NO_PREFIX = '';
let server = new net.Server();
const clients = {};
const tasks = [];
let messageId = 1;
let persistentSessions = {};
let resending = false;
let resendTimer = null;
const cacheAddedObjects = {};
const cachedModeExor = {};
const cachedReadColors = {};
this.destroy = cb => {
if (resendTimer) {
clearInterval(resendTimer);
resendTimer = null;
}
if (server) {
let cnt = 0;
for (let id in clients) {
if (clients.hasOwnProperty(id)) {
cnt++;
adapter.setForeignState(adapter.namespace + '.' + clients[id].iobId + '.alive', false, true, () => {
if (!--cnt) {
// to release all resources
server.close(() => cb && cb());
server = null;
}
});
}
}
if (!cnt) {
// to release all resources
server.close(() => cb && cb());
server = null;
}
}
};
/*
memwatch.on('leak', (info) => {
//console.error('Memory leak detected:\n', info);
adapter.log.info('Memory leak detected:\n', info);
var filename='/opt/yunkong2/dumps/' + Date.now() + '.heapsnapshot';
heapdump.writeSnapshot((err,filename) => {
if (err) {
console.error(err);
}
else console.error('Wrote snapshot: ' + filename);
});
});
*/
/*
memwatch.on('stats', function(stats) {
adapter.log.info("stats:",stats);
});
*/
function setColor(channelId, val) {
//adapter.log.info('color write: '+ val);
const stateId = 'Color';
if (clients[channelId]._map && clients[channelId]._map[stateId]) {
setImmediate(sendState2Client, clients[channelId], clients[channelId]._map[stateId] || 'cmnd/sonoff/Color', val, adapter.config.defaultQoS);
} else if (clients[channelId]._fallBackName) {
setImmediate(sendState2Client, clients[channelId], 'cmnd/' + clients[channelId]._fallBackName + '/' + stateId, val, adapter.config.defaultQoS);
} else {
adapter.log.warn('Unknown mapping for "' + stateId + '"');
}
}
function setPower(channelId, val) {
const stateId = 'POWER';
if (clients[channelId]._map && clients[channelId]._map[stateId]) {
setImmediate(sendState2Client, clients[channelId], clients[channelId]._map[stateId] || 'cmnd/sonoff/POWER', val ? 'ON' : 'OFF', adapter.config.defaultQoS);
} else if (clients[channelId]._fallBackName) {
setImmediate(sendState2Client, clients[channelId], 'cmnd/' + clients[channelId]._fallBackName + '/' + stateId, val, adapter.config.defaultQoS);
} else {
adapter.log.warn('Unknown mapping for "' + stateId + '"');
}
}
function setStateImmediate(channelId,stateId,val) {
if (clients[channelId]._map && clients[channelId]._map[stateId]) {
setImmediate(sendState2Client, clients[channelId], clients[channelId]._map[stateId] || ('cmnd/sonoff/' + stateId), val, adapter.config.defaultQoS);
} else if (clients[channelId]._fallBackName) {
setImmediate(sendState2Client, clients[channelId], 'cmnd/' + clients[channelId]._fallBackName + '/' + stateId, val, adapter.config.defaultQoS);
} else {
adapter.log.warn('Unknown mapping for "' + stateId + '"');
}
}
function _setState(id, val) {
adapter.setForeignState(id, val, true, () => {
setImmediate(processTasks);
});
}
function updateState(task, val) {
if (val !== undefined) {
task.cb = _setState;
task.cbArg = val;
}
tasks.push(task);
if (tasks.length === 1) {
setImmediate(processTasks);
}
}
const specVars = ['Red', 'Green', 'Blue', 'WW', 'CW', 'Color', 'RGB_POWER', 'WW_POWER', 'CW_POWER', 'Hue', 'Saturation'];
function onStateChangedColors(id, state, channelId, stateId) {
if (!channelId) {
let parts = id.split('.');
stateId = parts.pop();
if (stateId === 'level' || stateId === 'state' || stateId === 'red' || stateId === 'blue' || stateId === 'green') {
stateId = parts.pop() + '.' + stateId;
}
channelId = parts.splice(2, parts.length).join('.');
}
const ledModeIdExor = adapter.namespace + '.' + channelId + '.modeLedExor';
if (cachedModeExor[ledModeIdExor] === undefined) {
return adapter.getForeignState(ledModeIdExor, (err, _state) => {
cachedModeExor[ledModeIdExor] = _state ? _state.val || false : true;
setImmediate(() => onStateChangedColors(id, state, channelId, stateId));
});
}
// ledstripe objects
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)
// now evaluate ledstripe vars
// adaptions for magichome tasmota
if (stateId.match(/Color\d?/)) {
//adapter.log.info('sending color');
// id = sonoff.0.DVES_96ABFA.Color
// statid=Color
// state = {"val":"#faadcf","ack":false,"ts":1520146102580,"q":0,"from":"system.adapter.web.0","lc":1520146102580}
// set white to rgb or rgbww
adapter.getObject(id, (err, obj) => {
if (!obj) {
adapter.log.warn('ill rgbww obj');
} else {
const role = obj.common.role;
let color;
//adapter.log.info(state.val);
if (role === 'level.color.rgbww') {
// rgbww
if (state.val.toUpperCase() === '#FFFFFF') {
// transform white to WW
//color='000000FF';
color = state.val.substring(1) + '00';
} else {
// strip # char and add ww
color = state.val.substring(1) + '00';
}
} else if (role === 'level.color.rgbcwww') {
color = state.val.substring(1) + '0000';
} else {
// rgb, strip # char
color = state.val.substring(1);
}
//adapter.log.info('color :' + color + ' : ' + role);
// strip # char
//color=state.val.substring(1);
setColor(channelId, color);
// set rgb too
const hidE = id.split('.');
const deviceDesc = hidE[0] + '.' + hidE[1] + '.' + hidE[2];
let idAlive = deviceDesc + '.Red';
adapter.setState(idAlive, 100 * parseInt(color.substring(0, 2), 16) / 255, true);
idAlive = deviceDesc + '.Green';
adapter.setState(idAlive, 100 * parseInt(color.substring(2, 4), 16) / 255, true);
idAlive = deviceDesc + '.Blue';
adapter.setState(idAlive, 100 * parseInt(color.substring(4, 6), 16) / 255, true);
}
});
} else {
const hidE = id.split('.');
const deviceDesc = hidE[0] + '.' + hidE[1] + '.' + hidE[2];
if (stateId.match(/Red\d?/)) {
// set red component
if (state.val > 100) state.val = 100;
const red = toPaddedHexString(Math.floor(255 * state.val / 100), 2);
const idAlive = deviceDesc + '.Color';
adapter.getForeignState(idAlive, (err, state) => {
if (!state) {
adapter.setState(idAlive, '#000000', false);
return;
}
const color = state.val.substring(1);
// replace red component
const out = red + color.substring(2, 10);
adapter.setState(idAlive, '#' + out, false);
setColor(channelId, out);
});
} else
if (stateId.match(/Green\d?/)) {
// set green component
if (state.val > 100) state.val = 100;
const green = toPaddedHexString(Math.floor(255 * state.val / 100), 2);
const idAlive = deviceDesc + '.Color';
adapter.getForeignState(idAlive, (err, state) => {
if (!state) {
adapter.setState(idAlive, '#000000', false);
return;
}
const color = state.val.substring(1);
// replace green component
const out = color.substring(0, 2) + green + color.substring(4, 10);
adapter.setState(idAlive, '#' + out, false);
setColor(channelId, out);
});
} else
if (stateId.match(/Blue\d?/)) {
// set blue component
if (state.val > 100) state.val = 100;
const blue = toPaddedHexString(Math.floor(255 * state.val / 100), 2);
const idAlive = deviceDesc + '.Color';
adapter.getForeignState(idAlive, (err, state) => {
if (!state) {
adapter.setState(idAlive, '#000000', false);
return;
}
const color = state.val.substring(1);
// replace blue component
const out = color.substring(0, 4) + blue + color.substring(6, 10);
adapter.setState(idAlive, '#' + out, false);
setColor(channelId, out);
});
} else
if (stateId.match(/RGB_POWER\d?/)) {
// set ww component
const rgbpow = state.val === 'true' || state.val === true || state.val === 1 || state.val === '1';
const idAlive = deviceDesc + '.Color';
adapter.getForeignState(idAlive, (err, state) => {
if (!state) {
//adapter.log.warn('ill state Color');
adapter.setState(idAlive, '#000000', false);
return;
}
const color = state.val.substring(1);
let rgb = '000000';
if (rgbpow === true) {
rgb = 'FFFFFF';
}
// replace rgb component
let out = rgb + color.substring(6, 10);
if (rgbpow && exorWhiteLeds) {
//adapter.log.info('reset white');
out = rgb + '0000';
let idAlive = deviceDesc + '.WW_POWER';
adapter.setState(idAlive, false, false);
idAlive = deviceDesc + '.WW';
adapter.setState(idAlive, 0, false);
idAlive = deviceDesc + '.CW_POWER';
adapter.setState(idAlive, false, false);
idAlive = deviceDesc + '.CW';
adapter.setState(idAlive, 0, false);
}
setColor(channelId, out);
adapter.setState(idAlive, '#' + out, false);
if (rgbpow) {
setPower(channelId, true)
}
// if led_mode&1, exor white leds
});
} else
// calc hue + saturation params to rgb
if (hueCalc && stateId.match(/Hue\d?/)) {
let hue = state.val;
if (hue > 359) hue = 359;
// recalc color by hue
const idAlive = deviceDesc + '.Dimmer';
adapter.getForeignState(idAlive, (err, state) => {
if (!state) {
const dim = 100;
adapter.setState(idAlive, dim, true);
//adapter.log.warn('ill state Dimmer');
} else {
const dim = state.val;
let idAlive = deviceDesc + '.Saturation';
adapter.getForeignState(idAlive, (err, state) => {
if (!state) {
const sat = 100;
adapter.setState(idAlive, sat, true);
} else {
const sat = state.val;
const rgb = hsvToRgb(hue, sat, dim);
const hexval = componentToHex(rgb[0]) + componentToHex(rgb[1]) + componentToHex(rgb[2]);
let idAlive = deviceDesc + '.Color';
adapter.setState(idAlive, '#' + hexval, false);
}
});
}
});
} else
if (hueCalc && stateId.match(/Saturation\d?/)) {
let sat = state.val;
if (sat > 100) sat = 100;
// recalc color by saturation
const idAlive = deviceDesc + '.Dimmer';
adapter.getForeignState(idAlive, (err, state) => {
if (!state) {
const dim = 100;
adapter.setState(idAlive, dim, true);
//adapter.log.warn('ill state Dimmer');
} else {
const dim = state.val;
const idAlive = deviceDesc + '.Hue';
adapter.getForeignState(idAlive, (err, state) => {
if (!state) {
const hue = 100;
adapter.setState(idAlive, hue, true);
} else {
const hue = state.val;
const rgb = hsvToRgb(hue, sat, dim);
const hexval = componentToHex(rgb[0]) + componentToHex(rgb[1]) + componentToHex(rgb[2]);
const idAlive = deviceDesc + '.Color';
adapter.setState(idAlive, '#' + hexval, false);
}
});
}
});
} else {
// get color attributes to check other ledstripe vars
const idAlive = deviceDesc + '.Color';
adapter.getForeignObject(idAlive, (err, obj) => {
if (!obj) {
// no color object
adapter.log.warn(`unknown object: ${id}: ${state}`);
} else {
const role = obj.common.role;
//if (role='level.color.rgb') return;
let wwindex;
if (role === 'level.color.rgbww') {
wwindex = 6;
} else {
wwindex = 8;
}
if (stateId.match(/WW_POWER\d?/)) {
// set ww component
const wwpow = state.val === 'true' || state.val === true || state.val === 1 || state.val === '1';
const idAlive = deviceDesc + '.Color';
adapter.getForeignState(idAlive, (err, state) => {
if (!state) {
adapter.log.warn('ill state Color');
return;
}
const color = state.val.substring(1);
let ww = '00';
if (wwpow) {
ww = 'FF';
}
// replace ww component
let out = color.substring(0, wwindex) + ww;
if (wwpow && exorWhiteLeds) {
//adapter.log.info('reset white');
out = '000000' + ww;
let idAlive = deviceDesc + '.RGB_POWER';
adapter.setState(idAlive,false, false);
}
let idAlive = deviceDesc + '.Color';
adapter.setState(idAlive,'#' + out, false);
setColor(channelId, out);
// set ww channel
idAlive = deviceDesc + '.WW';
adapter.setState(idAlive, 100 * parseInt(out.substring(6, 8), 16) / 255, true);
// in case POWER is off, switch it on
wwpow && setPower(channelId, true)
});
} else
if (stateId.match(/CW_POWER\d?/)) {
// set ww component
const cwpow = state.val === 'true' || state.val === true || state.val === 1 || state.val === '1';
const idAlive = deviceDesc + '.Color';
adapter.getForeignState(idAlive, (err, state) => {
if (!state) {
adapter.log.warn('ill state Color');
return;
}
const color = state.val.substring(1);
let cw = '00';
if (cwpow) {
cw = 'FF';
}
// replace cw component
let out = color.substring(0, 6) + cw + color.substring(8, 10);
if (cwpow && exorWhiteLeds) {
//adapter.log.info('reset white');
out = '000000' + cw + color.substring(8, 10);
let idAlive = deviceDesc + '.RGB_POWER';
adapter.setState(idAlive, false, false);
}
let idAlive = deviceDesc + '.Color';
adapter.setState(idAlive, '#' + out, false);
setColor(channelId, out);
// set cw channel
idAlive = deviceDesc + '.CW';
adapter.setState(idAlive, 100 * parseInt(out.substring(6, 8), 16) / 255, true);
// in case POWER is off, switch it on
if (cwpow) {
let idAlive = deviceDesc + '.POWER';
adapter.setState(idAlive, true, false);
}
});
} else
if (stateId.match(/WW\d?/)) {
// set ww component
const ww = toPaddedHexString(Math.floor(255 * state.val / 100), 2);
const idAlive = deviceDesc + '.Color';
adapter.getForeignState(idAlive, (err, state) => {
if (!state) {
adapter.setState(idAlive, '#000000', false);
return;
}
const color = state.val.substring(1);
// replace ww component
const out = color.substring(0, wwindex) + ww;
setColor(channelId, out);
});
} else
if (stateId.match(/CW\d?/)) {
// set ww component
const cw = toPaddedHexString(Math.floor(255 * state.val / 100), 2);
const idAlive = deviceDesc + '.Color';
adapter.getForeignState(idAlive, (err, state) => {
if (!state) {
adapter.setState(idAlive, '#000000', false);
return;
}
const color = state.val.substring(1);
// replace cw component
const out = color.substring(0, 6) + cw + color.substring(8, 10);
setColor(channelId, out);
});
}
}
});
}
}
}
this.onStateChange = (id, state) => {
adapter.log.debug('onStateChange ' + id + ': ' + JSON.stringify(state));
if (server && state && !state.ack) {
// find client.id
let parts = id.split('.');
const stateId = parts.pop();
const channelId = parts.splice(2, parts.length).join('.');
if (clients[mappingClients[channelId]]) {
// check for special ledstripe vars
if (specVars.indexOf(stateId) === -1) {
// other objects
adapter.getObject(id, (err, obj) => {
if (!obj) {
adapter.log.warn(`invalid obj ${id}`);
} else {
const type = obj.common.type;
switch (type) {
case 'boolean':
setStateImmediate(mappingClients[channelId], stateId, state.val ? 'ON' : 'OFF');
break;
case 'number':
setStateImmediate(mappingClients[channelId], stateId, state.val.toString());
break;
case 'string':
setStateImmediate(mappingClients[channelId], stateId, state.val);
break;
}
}
});
} else {
onStateChangedColors(id, state, channelId, stateId);
}
} else {
//Client:"DVES_96ABFA : MagicHome" not connected => State: sonoff.0.myState - Value: 0, ack: false, time stamp: 1520369614189, last changed: 1520369614189
// if (server && state && !state.ack) {
// server = false
// or state = false
// or state.ack = true
// or clients[channelId] = false
adapter.log.warn(`Client "${channelId}" not connected`);
/*
if (!clients[channelId]) {
var idAlive='sonoff.0.'+channelId+'.INFO.IPAddress';
adapter.getForeignState(idAlive, function (err, state) {
if (!state) {
adapter.log.warn('Client "' + channelId + '" could not get ip adress');
} else {
var ip=state.val;
adapter.log.warn('Clients ip "' + ip);
request('http://'+ip+'/cm?cmnd=Restart 1', function(error, response, body) {
if (error || response.statusCode !== 200) {
log('Fehler beim Neustart von Sonoff: ' + channelId + ' (StatusCode = ' + response.statusCode + ')');
}
});
}
});
}*/
}
}
};
function processTasks() {
if (tasks && tasks.length) {
let task = tasks[0];
if (task.type === 'addObject') {
if (!cacheAddedObjects[task.id]) {
cacheAddedObjects[task.id] = true;
adapter.getForeignObject(task.id, (err, obj) => {
if (!obj) {
adapter.setForeignObject(task.id, task.data, (/* err */) => {
adapter.log.info('new object created: ' + task.id);
tasks.shift();
if (task.cb) {
task.cb(task.id, task.cbArg);
} else {
setImmediate(processTasks);
}
});
} else {
tasks.shift();
if (task.cb) {
task.cb(task.id, task.cbArg);
} else {
setImmediate(processTasks);
}
}
});
} else {
tasks.shift();
if (task.cb) {
task.cb(task.id, task.cbArg);
} else {
setImmediate(processTasks);
}
}
} else if (task.type === 'extendObject') {
adapter.extendObject(task.id, task.data, (/* err */) => {
tasks.shift();
if (task.cb) {
task.cb(task.id, task.cbArg);
} else {
setImmediate(processTasks);
}
});
} else if (task.type === 'deleteState') {
adapter.deleteState('', '', task.id, (/* err */) => {
tasks.shift();
if (task.cb) {
task.cb(task.id, task.cbArg);
} else {
setImmediate(processTasks);
}
});
} else {
adapter.log.error('Unknown task name: ' + JSON.stringify(task));
tasks.shift();
if (task.cb) {
task.cb(task.id, task.cbArg);
} else {
setImmediate(processTasks);
}
}
}
}
function createClient(client) {
// mqtt.0.cmnd.sonoff.POWER
// mqtt.0.stat.sonoff.POWER
let isStart = !tasks.length;
let id = adapter.namespace + '.' + client.iobId;
let obj = {
_id: id,
common: {
name: client.id,
desc: ''
},
native: {
clientId: client.id
},
type: 'channel'
};
tasks.push({type: 'addObject', id: obj._id, data: obj});
obj = {
_id: id + '.alive',
common: {
type: 'boolean',
role: 'indicator.connected',
read: true,
write: false,
name: client.id + ' alive'
},
type: 'state'
};
tasks.push({type: 'addObject', id: obj._id, data: obj});
if (isStart) {
processTasks(tasks);
}
}
function updateClients() {
let text = '';
if (clients) {
for (let id in clients) {
text += (text ? ',' : '') + id;
}
}
adapter.setState('info.connection', {val: text, ack: true});
}
function updateAlive(client, alive) {
let idAlive = adapter.namespace + '.' + client.iobId + '.alive';
adapter.getForeignState(idAlive, (err, state) => {
if (!state || state.val !== alive) {
adapter.setForeignState(idAlive, alive, true);
}
});
}
function sendState2Client(client, topic, state, qos, retain, cb) {
if (typeof qos === 'function') {
cb = qos;
qos = undefined;
}
if (typeof retain === 'function') {
cb = retain;
retain = undefined;
}
adapter.log.debug('Send to "' + client.id + '": ' + topic + ' = ' + state);
client.publish({topic: topic, payload: state, qos: qos, retain: retain, messageId: messageId++}, cb);
messageId &= 0xFFFFFFFF;
}
function resendMessages2Client(client, messages, i) {
i = i || 0;
if (messages && i < messages.length) {
try {
messages[i].ts = Date.now();
messages[i].count++;
adapter.log.debug(`Client [${client.id}] Resend messages on connect: ${messages[i].topic} = ${messages[i].payload}`);
if (messages[i].cmd === 'publish') {
client.publish(messages[i]);
}
} catch (e) {
adapter.log.warn(`Client [${client.id}] Cannot resend message: ${e}`);
}
if (adapter.config.sendInterval) {
setTimeout(() => resendMessages2Client(client, messages, i + 1), adapter.config.sendInterval);
} else {
setImmediate(() => resendMessages2Client(client, messages, i + 1));
}
} else {
//return;
}
}
function addObject(attr, client, prefix, path) {
let replaceAttr = types[attr].replace || attr;
let id = adapter.namespace + '.' + client.iobId + '.' + (prefix ? prefix + '.' : '') + (path.length ? path.join('_') + '_' : '') + replaceAttr.replace(FORBIDDEN_CHARS, '_');
let obj = {
type: 'addObject',
id: id,
data: {
_id: id,
common: Object.assign({}, types[attr]),
native: {},
type: 'state'
}
};
obj.data.common.name = client.id + ' ' + (prefix ? prefix + ' ' : '') + (path.length ? path.join(' ') + ' ' : '') + ' ' + replaceAttr;
return obj;
}
function checkData(client, topic, prefix, data, unit, path) {
if (!data || typeof data !== 'object') return;
const ledModeReadColorsID = adapter.namespace + '.' + client.iobId + '.modeReadColors';
if (cachedReadColors[ledModeReadColorsID] === undefined) {
return adapter.getForeignState(ledModeReadColorsID, (err, state) => {
cachedReadColors[ledModeReadColorsID] = (state && state.val) || false;
setImmediate(() => checkData(client, topic, prefix, data, unit, path));
});
}
path = path || [];
prefix = prefix || '';
// first get the units
if (data.TempUnit) {
unit = data.TempUnit;
if (unit.indexOf('°') !== 0) {
unit = '°' + unit.replace('°');
}
}
for (let attr in data) {
if (!data.hasOwnProperty(attr)) {
adapter.log.warn('[' + client.id + '] attr error: ' + attr + '' + data[attr]);
continue;
}
if (typeof data[attr] === 'object') {
// check for arrays
if (types[attr]) {
if (types[attr].type === 'array') {
// transform to array of attributes
for (let i = 1; i <= 10; i++) {
let val = data[attr][i - 1];
if (typeof val === 'undefined') break;
// define new object
let replaceAttr = attr.replace(FORBIDDEN_CHARS, '_') + i.toString();
let id = adapter.namespace + '.' + client.iobId + '.' + (prefix ? prefix + '.' : '') + (path.length ? path.join('_') + '_' : '') + replaceAttr;
let obj = {
type: 'addObject',
id: id,
data: {
_id: id,
common: Object.assign({}, types[attr]),
native: {},
type: 'state'
}
};
obj.data.common.name = client.id + ' ' + (prefix ? prefix + ' ' : '') + (path.length ? path.join(' ') + ' ' : '') + ' ' + replaceAttr;
obj.data.common.type = 'number';
updateState(obj, val);
}
} else {
let nPath = Object.assign([], path);
nPath.push(attr.replace(FORBIDDEN_CHARS, '_'));
checkData(client, topic, prefix, data[attr], unit, nPath);
nPath = undefined;
}
} else {
let nPath = Object.assign([], path);
nPath.push(attr.replace(FORBIDDEN_CHARS, '_'));
checkData(client, topic, prefix, data[attr], unit, nPath);
nPath = undefined;
}
} else if (types[attr]) {
const allowReadColors = cachedReadColors[ledModeReadColorsID]; // allow for color read from MQTT (default off)
// create object
const obj = addObject(attr, client, prefix, path);
let replaceAttr = types[attr].replace || attr;
if (attr === 'Temperature') {
obj.data.common.unit = unit || obj.data.common.unit || '°C';
}
if (attr === 'Humidity') {
obj.data.common.unit = unit || obj.data.common.unit || '%';
}
if (obj.data.common.storeMap) {
delete obj.data.common.storeMap;
client._map[replaceAttr] = topic.replace(/$\w+\//, 'cmnd/').replace(/\/\w+$/, '/' + replaceAttr);
}
console.log(attr);
// adaptions for magichome tasmota
if (attr === 'Color') {
// read vars
// if ledFlags bit 2, read color from tasmota, else ignore
if (data[attr].length === 10) {
obj.data.common.role = 'level.color.rgbcwww'; // RGB + cold white + white???
} else if (data[attr].length === 8) {
obj.data.common.role = 'level.color.rgbww'; // RGB + White
} else {
obj.data.common.role = 'level.color.rgb';
}
if (hueCalc) {
// Create LEDs modes if required
let xObj = addObject('modeReadColors', client, prefix, path);
updateState(xObj);
xObj = addObject('modeLedExor', client, prefix, path);
updateState(xObj);
xObj = addObject('Hue', client, prefix, path);
updateState(xObj);
xObj = addObject('Saturation', client, prefix, path);
updateState(xObj);
xObj = addObject('Red', client, prefix, path);
xObj.data.common.read = allowReadColors;
updateState(xObj, allowReadColors ? 100 * parseInt(data[attr].substring(0, 2), 16) / 255 : undefined);
xObj = addObject('Green', client, prefix, path);
xObj.data.common.read = allowReadColors;
updateState(xObj, allowReadColors ? 100 * parseInt(data[attr].substring(2, 4), 16) / 255 : undefined);
xObj = addObject('Blue', client, prefix, path);
xObj.data.common.read = allowReadColors;
updateState(xObj, allowReadColors ? 100 * parseInt(data[attr].substring(4, 6), 16) / 255 : undefined);
xObj = addObject('RGB_POWER', client, prefix, path);
xObj.data.common.read = allowReadColors;
let val = parseInt(data[attr].substring(0, 6), 16);
updateState(xObj, allowReadColors ? (val > 0) : undefined);
if (obj.data.common.role === 'level.color.rgbww') {
// rgbww
xObj = addObject('WW', client, prefix, path);
xObj.data.common.read = allowReadColors;
updateState(xObj, allowReadColors ? 100 * parseInt(data[attr].substring(6, 8), 16) / 255 : undefined);
xObj = addObject('WW_POWER', client, prefix, path);
xObj.data.common.read = allowReadColors;
updateState(xObj, allowReadColors ? (val > 0) : undefined);
} else
if (obj.data.common.role === 'level.color.rgbcwww') {
// rgbcwww
xObj = addObject('CW', client, prefix, path);
xObj.data.common.read = allowReadColors;
updateState(xObj, allowReadColors ? 100 * parseInt(data[attr].substring(6, 8), 16) / 255 : undefined);
xObj = addObject('CW_POWER', client, prefix, path);
xObj.data.common.read = allowReadColors;
updateState(xObj, allowReadColors ? (val > 0) : undefined);
xObj = addObject('WW', client, prefix, path);
xObj.data.common.read = allowReadColors;
updateState(xObj, allowReadColors ? 100 * parseInt(data[attr].substring(8, 10), 16) / 255 : undefined);
xObj = addObject('WW_POWER', client, prefix, path);
xObj.data.common.read = allowReadColors;
val = parseInt(data[attr].substring(8, 10), 16);
updateState(xObj, allowReadColors ? (val > 0) : undefined);
}
}
}
let val;
if (obj.data.common.type === 'number') {
val = parseFloat(data[attr]);
} else if (obj.data.common.type === 'boolean') {
val = (data[attr] || '').toUpperCase() === 'ON';
} else {
if (attr === 'Color') {
// add # char
if (allowReadColors) {
val = '#' + data[attr];
}
} else {
val = data[attr];
}
}
updateState(obj, val);
} else {
// not in list, auto insert
//if (client.id=='DVES_008ADB') {
// adapter.log.warn('[' + client.id + '] Received attr not in list: ' + attr + '' + data[attr]);
//}
// tele/sonoff/SENSOR tele/sonoff/STATE => read only
// stat/sonoff/RESULT => read,write
let parts = topic.split('/');
// auto generate objects
if ((parts[0] === 'tele' && (
(adapter.config.TELE_SENSOR && parts[2] === 'SENSOR') ||
(adapter.config.TELE_STATE && parts[2] === 'STATE'))) ||
(parts[0] === 'stat' &&
(adapter.config.STAT_RESULT && parts[2] === 'RESULT'))
) {
//adapter.log.info('[' + client.id + '] auto insert object: ' + attr + ' ' + data[attr] + ' flags: ' + autoFlags);
//if (data[attr]) {
const xdata = data[attr];
//adapter.log.info('[' + client.id + '] auto insert object: ' + attr + ' ' + data[attr]);
let attributes;
if (parts[2] === 'RESULT') {
if (xdata.isNaN) {
// string
attributes = {type: 'string', role:'value', read: true, write: true};
} else {
// number
attributes = {type: 'number', role:'value', read: true, write: true};
}
} else {
if (xdata.isNaN) {
// string
attributes = {type: 'string', role: 'value', read: true, write: false};
} else {
// number
attributes = {type: 'number', role: 'value', read: true, write: false};
}
}
let replaceAttr = attr ;
let id = adapter.namespace + '.' + client.iobId + '.' + (prefix ? prefix + '.' : '') + (path.length ? path.join('_') + '_' : '') + replaceAttr;
let obj = {
type: 'addObject',
id: id,
data: {
_id: id,
common: Object.assign({}, attributes),
native: {},
type: 'state'
}
};
obj.data.common.name = client.id + ' ' + (prefix ? prefix + ' ' : '') + (path.length ? path.join(' ') + ' ' : '') + ' ' + replaceAttr;
updateState(obj, xdata);
}
}
}
}
function receivedTopic(packet, client) {
client.states = client.states || {};
client.states[packet.topic] = {
message: packet.payload,
retain: packet.retain,
qos: packet.qos
};
// update alive state
updateAlive(client, true);
if (client._will && client._will.topic && packet.topic === client._will.topic) {
client._will.payload = packet.payload;
return;
}
let val = packet.payload.toString('utf8');
adapter.log.debug('[' + client.id + '] Received: ' + packet.topic + ' = ' + val);
/*
adapter.getForeignState('system.adapter.sonoff.0.memRss', (err, state) => {
adapter.log.info('mem: ' + state.val + ' MB');
if (heapTest==0 || (heapTest==1 && state.val>37.5)) {
heapTest+=1;
heapdump.writeSnapshot('/opt/yunkong2/' + Date.now() + '.heapsnapshot');
adapter.log.info('Wrote snapshot: ');
var filename='/opt/yunkong2/' + Date.now() + '.heapsnapshot';
heapdump.writeSnapshot((err,filename) => {
if (err) {
adapter.log.info('heap: ' + err);
} else {
adapter.log.info('Wrote snapshot: ' + filename);
}
});
}
});
*/
// [DVES_BD3B4D] Received: tele/sonoff2/STATE = {
// "Time":"2017-10-01T12:37:18",
// "Uptime":0,
// "Vcc":3.224,
// "POWER":"ON",
// "POWER1":"OFF",
// "POWER2":"ON"
// "Wifi":{
// "AP":1,
// "SSId":"FuckOff",
// "RSSI":62,
// "APMac":"E0:28:6D:EC:21:EA"
// }
// }
// [DVES_BD3B4D] Received: tele/sonoff2/SENSOR = {
// "Time":"2017-10-01T12:37:18",
// "Switch1":"ON",
// "DS18B20":{"Temperature":20.6},
// "TempUnit":"C"
// }
client._map = client._map || {};
const parts = packet.topic.split('/');
if (!client._fallBackName) {
client._fallBackName = parts[1];
}
const stateId = parts.pop();
if (stateId === 'LWT') {
return;
}
if (stateId === 'RESULT') {
// ignore: stat/Sonoff/RESULT = {"POWER":"ON"}
// testserver.js reports error, so reject above cmd
const str = val.replace(/\s+/g, '');
if (str.startsWith('{"POWER":"ON"}')) return;
if (str.startsWith('{"POWER":"OFF"}')) return;
if (parts[0] === 'stat') {
try {
checkData(client, packet.topic, NO_PREFIX, JSON.parse(val));
} catch (e) {
adapter.log.warn('Cannot parse data "' + stateId + '": _' + val + '_ - ' + e);
}
return;
}
if (parts[0] === 'tele') {
try {
checkData(client, packet.topic, NO_PREFIX, JSON.parse(val));
} catch (e) {
adapter.log.warn('Cannot parse data "' + stateId + '": _' + val + '_ - ' + e);
}
}
return;
}
// 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}}
// tele/sonoff/SENSOR = {"Time":"2017-10-05T17:43:19", "DS18x20":{"DS1":{"Type":"DS18B20", "Address":"28FF9A9876815022A", "Temperature":12.2}}, "TempUnit":"C"}
// tele/sonoff5/SENSOR = {"Time":"2017-10-03T14:02:25", "AM2301-14":{"Temperature":21.6, "Humidity":54.7}, "TempUnit":"C"}
// tele/sonoff/SENSOR = {"Time":"2018-02-23T17:36:59", "Analog0":298}
if (parts[0] === 'tele' && stateId.match(/^(STATE|SENSOR|WAKEUP)\d?$/)) {
try {
checkData(client, packet.topic, NO_PREFIX, JSON.parse(val));
//adapter.log.warn('log sensor parse"' + stateId + '": _' + val);
} catch (e) {
adapter.log.warn('Cannot parse data "' + stateId + '": _' + val + '_ - ' + e);
}
} else if (parts[0] === 'tele' && stateId.match(/^INFO\d?$/)) {
// tele/SonoffPOW/INFO1 = {"Module":"Sonoff Pow", "Version":"5.8.0", "FallbackTopic":"SonoffPOW", "GroupTopic":"sonoffs"}
// tele/SonoffPOW/INFO2 = {"WebServerMode":"Admin", "Hostname":"Sonoffpow", "IPAddress":"192.168.2.182"}
// tele/SonoffPOW/INFO3 = {"RestartReason":"Software/System restart"}
try {
checkData(client, packet.topic, 'INFO', JSON.parse(val));
} catch (e) {
adapter.log.warn('Cannot parse data"' + stateId + '": _' + val + '_ - ' + e);
}
} else if (parts[0] === 'tele' && stateId.match(/^(ENERGY)\d?$/)) {
// 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}
try {
checkData(client, packet.topic, 'ENERGY', JSON.parse(val));
} catch (e) {
adapter.log.warn('Cannot parse data"' + stateId + '": _' + val + '_ - ' + e);
}
} else if (types[stateId]) {
// /ESP_BOX/BM280/Pressure = 1010.09
// /ESP_BOX/BM280/Humidity = 42.39
// /ESP_BOX/BM280/Temperature = 25.86
// /ESP_BOX/BM280/Approx. Altitude = 24
// cmnd/sonoff/POWER
// stat/sonoff/POWER
if (types[stateId]) {
let id = adapter.namespace + '.' + client.iobId + '.' + stateId.replace(/[-.+\s]+/g, '_');
let obj = {
type: 'addObject',
id: id,
data: {
_id: id,
common: JSON.parse(JSON.stringify(types[stateId])),
native: {},
type: 'state'
}
};
obj.data.common.name = client.id + ' ' + stateId;
// push only new objects
updateState(obj);
if (parts[0] === 'cmnd') {
// Set Object fix
if (obj.data.common.type === 'number') {
adapter.setState(id, parseFloat(val), true);
} else if (obj.data.common.type === 'boolean') {
adapter.setState(id, val === 'ON' || val === '1' || val === 'true' || val === 'on', true);
} else {
adapter.setState(id, val, true);
}
// remember POWER topic
client._map[stateId] = packet.topic;
} else {
if (obj.data.common.type === 'number') {
adapter.setState(id, parseFloat(val), true);
} else if (obj.data.common.type === 'boolean') {
adapter.setState(id, val === 'ON' || val === '1' || val === 'true' || val === 'on', true);
} else {
adapter.setState(id, val, true);
}
}
} else {
adapter.log.debug('Cannot process: ' + packet.topic);
}
}
}
function clientClose(client, reason) {
if (!client) return;
if (persistentSessions[client.id]) {
persistentSessions[client.id].connected = false;
}
if (client._resendonStart) {
clearTimeout(client._resendonStart);
client._resendonStart = null;
}
try {
if (clients[client.id] && (client.__secret === clients[client.id].__secret)) {
adapter.log.info(`Client [${client.id}] connection closed: ${reason}`);
delete clients[client.id];
updateClients();
if (client._will) {
receivedTopic(client._will, client, () => client.destroy());
} else {
client.destroy();
}
} else {
client.destroy();
}
} catch (e) {
adapter.log.warn(`Client [${client.id}] Cannot close client: ${e}`);
}
}
function checkResends() {
const now = Date.now();
resending = true;
for (const clientId in clients) {
if (clients.hasOwnProperty(clientId) && clients[clientId] && clients[clientId]._messages) {
for (let m = clients[clientId]._messages.length - 1; m >= 0; m--) {
const message = clients[clientId]._messages[m];
if (now - message.ts >= adapter.config.retransmitInterval) {
if (message.count > adapter.config.retransmitCount) {
adapter.log.warn(`Client [${clientId}] Message ${message.messageId} deleted after ${message.count} retries`);
clients[clientId]._messages.splice(m, 1);
continue;
}
// resend this message
message.count++;
message.ts = now;
try {
adapter.log.debug(`Client [${clientId}] Resend message topic: ${message.topic}, payload: ${message.payload}`);
if (message.cmd === 'publish') {
clients[clientId].publish(message);
}
} catch (e) {
adapter.log.warn(`Client [${clientId}] Cannot publish message: ${e}`);
}
if (adapter.config.sendInterval) {
setTimeout(checkResends, adapter.config.sendInterval);
} else {
setImmediate(checkResends);
}
return;
}
}
}
}
// delete old sessions
if (adapter.config.storeClientsTime !== -1) {
for (const id in persistentSessions) {
if (persistentSessions.hasOwnProperty(id)) {
if (now - persistentSessions[id].lastSeen > adapter.config.storeClientsTime * 60000) {
delete persistentSessions[id];
}
}
}
}
resending = false;
}
(function _constructor(config) {
if (config.timeout === undefined) {
config.timeout = 300;
} else {
config.timeout = parseInt(config.timeout, 10);
}
server.on('connection', stream => {
let client = mqtt(stream);
// Store unique connection identifier
client.__secret = Date.now() + '_' + Math.round(Math.random() * 10000);
// client connected
client.on('connect', options => {
// acknowledge the connect packet
client.id = options.clientId;
client.iobId = client.id.replace(FORBIDDEN_CHARS, '_');
mappingClients[client.iobId] = client.id;
// get possible old client
let oldClient = clients[client.id];
if (config.user) {
if (config.user !== options.username ||
config.pass !== options.password.toString()) {
adapter.log.warn(`Client [${client.id}] has invalid password(${options.password}) or username(${options.username})`);
client.connack({returnCode: 4});
if (oldClient) {
// delete existing client
delete clients[client.id];
updateAlive(oldClient, false);
updateClients();
oldClient.destroy();
}
client.destroy();
return;
}
}
if (oldClient) {
adapter.log.info(`Client [${client.id}] reconnected. Old secret ${clients[client.id].__secret}. New secret ${client.__secret}`);
// need to destroy the old client
if (client.__secret !== clients[client.id].__secret) {
// it is another socket!!
// It was following situation:
// - old connection was active
// - new connection is on the same TCP
// Just forget him
// oldClient.destroy();
}
} else {
adapter.log.info(`Client [${client.id}] connected with secret ${client.__secret}`);
}
let sessionPresent = false;
if (!client.cleanSession && adapter.config.storeClientsTime !== 0) {
if (persistentSessions[client.id]) {
sessionPresent = true;
persistentSessions[client.id].lastSeen = Date.now();
} else {
persistentSessions[client.id] = {
_subsID: {},
_subs: {},
messages: [],
lastSeen: Date.now()
};
}
client._messages = persistentSessions[client.id].messages;
persistentSessions[client.id].connected = true;
} else if (client.cleanSession && persistentSessions[client.id]) {
delete persistentSessions[client.id];
}
client._messages = client._messages || [];
client.connack({returnCode: 0, sessionPresent});
clients[client.id] = client;
updateClients();
client._will = options.will;
createClient(client);
if (persistentSessions[client.id]) {
client._subsID = persistentSessions[client.id]._subsID;
client._subs = persistentSessions[client.id]._subs;
if (persistentSessions[client.id].messages.length) {
// give to the client a little bit time
client._resendonStart = setTimeout(clientId => {
client._resendonStart = null;
resendMessages2Client(client, persistentSessions[clientId].messages);
}, 100, client.id);
}
}
});
// timeout idle streams after 5 minutes
if (config.timeout) {
stream.setTimeout(config.timeout * 1000);
}
// connection error handling
client.on('close', had_error => clientClose(client, had_error ? 'closed because of error' : 'closed'));
client.on('error', e => clientClose(client, e));
client.on('disconnect', () => clientClose(client, 'disconnected'));
// stream timeout
stream.on('timeout', () => clientClose(client, 'timeout'));
client.on('publish', packet => {
if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
return adapter.log.warn(`Old client ${client.id} with secret ${client.__secret} sends publish. Ignore! Actual secret is ${clients[client.id].__secret}`);
}
if (persistentSessions[client.id]) {
persistentSessions[client.id].lastSeen = Date.now();
}
if (packet.qos === 1) {
// send PUBACK to client
client.puback({
messageId: packet.messageId
});
} else if (packet.qos === 2) {
const pack = client._messages.find(e => {
return e.messageId === packet.messageId;
});
if (pack) {
// duplicate message => ignore
adapter.log.warn(`Client [${client.id}] Ignored duplicate message with ID: ${packet.messageId}`);
return;
} else {
packet.ts = Date.now();
packet.cmd = 'pubrel';
packet.count = 0;
client._messages.push(packet);
client.pubrec({messageId: packet.messageId});
return;
}
}
receivedTopic(packet, client)
});
// response for QoS2
client.on('pubrec', packet => {
if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
return adapter.log.warn(`Old client ${client.id} with secret ${client.__secret} sends pubrec. Ignore! Actual secret is ${clients[client.id].__secret}`);
}
if (persistentSessions[client.id]) {
persistentSessions[client.id].lastSeen = Date.now();
}
let pos = null;
// remove this message from queue
client._messages.forEach((e, i) => {
if (e.messageId === packet.messageId) {
pos = i;
return false;
}
});
if (pos !== -1) {
client.pubrel({
messageId: packet.messageId
});
} else {
adapter.log.warn(`Client [${client.id}] Received pubrec on ${client.id} for unknown messageId ${packet.messageId}`);
}
});
// response for QoS2
client.on('pubcomp', packet => {
if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
return adapter.log.warn(`Old client ${client.id} with secret ${client.__secret} sends pubcomp. Ignore! Actual secret is ${clients[client.id].__secret}`);
}
if (persistentSessions[client.id]) {
persistentSessions[client.id].lastSeen = Date.now();
}
let pos = null;
// remove this message from queue
client._messages.forEach((e, i) => {
if (e.messageId === packet.messageId) {
pos = i;
return false;
}
});
if (pos !== null) {
client._messages.splice(pos, 1);
} else {
adapter.log.warn(`Client [${client.id}] Received pubcomp for unknown message ID: ${packet.messageId}`);
}
});
// response for QoS2
client.on('pubrel', packet => {
if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
return adapter.log.warn(`Old client ${client.id} with secret ${client.__secret} sends pubrel. Ignore! Actual secret is ${clients[client.id].__secret}`);
}
if (persistentSessions[client.id]) {
persistentSessions[client.id].lastSeen = Date.now();
}
let pos = null;
// remove this message from queue
client._messages.forEach((e, i) => {
if (e.messageId === packet.messageId) {
pos = i;
return false;
}
});
if (pos !== -1) {
client.pubcomp({
messageId: packet.messageId
});
receivedTopic(client._messages[pos], client);
} else {
adapter.log.warn(`Client [${client.id}] Received pubrel on ${client.id} for unknown messageId ${packet.messageId}`);
}
});
// response for QoS1
client.on('puback', packet => {
if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
return adapter.log.warn(`Old client ${client.id} with secret ${client.__secret} sends puback. Ignore! Actual secret is ${clients[client.id].__secret}`);
}
if (persistentSessions[client.id]) {
persistentSessions[client.id].lastSeen = Date.now();
}
// remove this message from queue
let pos = null;
// remove this message from queue
client._messages.forEach((e, i) => {
if (e.messageId === packet.messageId) {
pos = i;
return false;
}
});
if (pos !== null) {
adapter.log.debug(`Client [${client.id}] Received puback for ${client.id} message ID: ${packet.messageId}`);
client._messages.splice(pos, 1);
} else {
adapter.log.warn(`Client [${client.id}] Received puback for unknown message ID: ${packet.messageId}`);
}
});
client.on('unsubscribe', packet => {
if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
return adapter.log.warn(`Old client ${client.id} with secret ${client.__secret} sends unsubscribe. Ignore! Actual secret is ${clients[client.id].__secret}`);
}
if (persistentSessions[client.id]) {
persistentSessions[client.id].lastSeen = Date.now();
}
client.unsuback({messageId: packet.messageId});
});
client.on('subscribe', packet => {
if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
return adapter.log.warn(`Old client ${client.id} with secret ${client.__secret} sends unsubscribe. Ignore! Actual secret is ${clients[client.id].__secret}`);
}
if (persistentSessions[client.id]) {
persistentSessions[client.id].lastSeen = Date.now();
}
let granted = [];
// just confirm the request.
// we expect subscribe for 'cmnd.sonoff.#'
for (let i = 0; i < packet.subscriptions.length; i++) {
granted.push(packet.subscriptions[i].qos);
}
client.suback({granted: granted, messageId: packet.messageId});
});
client.on('pingreq', (/*packet*/) => {
if (clients[client.id] && client.__secret !== clients[client.id].__secret) {
return adapter.log.warn(`Old client ${client.id} with secret ${client.__secret} sends pingreq. Ignore! Actual secret is ${clients[client.id].__secret}`);
}
if (persistentSessions[client.id]) {
persistentSessions[client.id].lastSeen = Date.now();
}
adapter.log.debug(`Client [${client.id}] pingreq`);
client.pingresp();
});
});
config.port = parseInt(config.port, 10) || 1883;
config.retransmitInterval = config.retransmitInterval || 2000;
config.retransmitCount = config.retransmitCount || 10;
if (config.storeClientsTime === undefined) {
config.storeClientsTime = 1440;
} else {
config.storeClientsTime = parseInt(config.storeClientsTime, 10) || 0;
}
config.defaultQoS = parseInt(config.defaultQoS, 10) || 0;
// Update connection state
updateClients();
// to start
server.listen(config.port, config.bind, () => {
adapter.log.info('Starting MQTT ' + (config.user ? 'authenticated ' : '') + ' server on port ' + config.port);
resendTimer = setInterval(() => {
if (!resending) {
checkResends();
}
}, adapter.config.retransmitInterval || 2000);
});
})(adapter.config);
return this;
}
module.exports = MQTTServer;