Initial commit

This commit is contained in:
2019-01-16 09:07:10 +08:00
commit 0cc08f136d
32 changed files with 7021 additions and 0 deletions

94
test/lib/mqttClient.js Normal file
View File

@@ -0,0 +1,94 @@
'use strict';
const mqtt = require('mqtt');
function Client(cbConnected, cbChanged, config) {
let that = this;
if (typeof config === 'string') config = {name: config};
config = config || {};
config.url = config.url || 'localhost';
this.client = mqtt.connect('mqtt://' + (config.user ? (config.user + ':' + config.pass + '@') : '') + config.url + (config.name ? '?clientId=' + config.name : ''), config);
this.client.on('connect', () => {
console.log((new Date()) + ' test client connected to localhost');
/*that.client.publish('mqtt/0/test', 'Roger1');
client.publish('test/out/testMessage1', 'Roger1');
client.publish('test/out/testMessage2', 'Roger2');
client.publish('test/in/testMessage3', 'Roger3');
client.publish('test/in/testMessage4', 'Roger4');*/
/*client.publish('arduino/kitchen/out/temperature', '10.1');
client.publish('arduino/kitchen/out/humidity', '56');
// Current light state
client.publish('arduino/kitchen/in/lightActor', 'false');
client.subscribe('arduino/kitchen/in/#');*/
//client.subscribe('arduino/kitchen/in/updateInterval');
that.client.subscribe('#');
if (cbConnected) cbConnected(true);
});
this.client.on('message', (topic, message, packet) => {
// message is Buffer
if (cbChanged) {
cbChanged(topic, message, packet);
} else {
console.log('Test MQTT Client received "' + topic + '": ' + message.toString());
}
});
this.client.on('close', err => {
if (err) console.error('Connection closed: ' + err);
// message is Buffer
if (cbConnected) {
cbConnected(false);
} else {
console.log('Test MQTT Client closed');
}
});
this.client.on('error', error => {
console.error('Test MQTT Client error: ' + error);
});
this.publish = (topic, message, qos, retain, cb) => {
if (typeof qos === 'function') {
cb = qos;
qos = undefined;
}
if (typeof retain === 'function') {
cb = retain;
retain = undefined;
}
const opts = {
retain: retain || false,
qos: qos || 0
};
that.client.publish(topic, message, opts, cb);
};
this.subscribe = (topic, opts, cb) => {
if (typeof opts === 'function') {
cb = opts;
opts = null;
}
that.client.subscribe(topic, opts, cb);
};
this.unsubscribe = (topic, cb) => {
that.client.unsubscribe(topic, cb);
};
this.destroy = () => {
if (that.client) {
that.client.end();
that.client = null;
}
};
this.stop = this.destroy;
return this;
}
if (typeof module !== 'undefined' && module.parent) {
module.exports = Client;
} else {
new Client();
}

1411
test/lib/objects.js Normal file

File diff suppressed because it is too large Load Diff

718
test/lib/setup.js Normal file
View File

@@ -0,0 +1,718 @@
'use strict';
/* jshint -W097 */// jshint strict:false
/*jslint node: true */
// check if tmp directory exists
const fs = require('fs');
const path = require('path');
const child_process = require('child_process');
const rootDir = path.normalize(__dirname + '/../../');
const pkg = require(rootDir + 'package.json');
const debug = typeof v8debug === 'object';
pkg.main = pkg.main || 'main.js';
let adapterName = path.normalize(rootDir).replace(/\\/g, '/').split('/');
adapterName = adapterName[adapterName.length - 2];
let adapterStarted = false;
function getAppName() {
const parts = __dirname.replace(/\\/g, '/').split('/');
return parts[parts.length - 3].split('.')[0];
}
const appName = getAppName().toLowerCase();
let objects;
let states;
let pid = null;
function copyFileSync(source, target) {
let targetFile = target;
//if target is a directory a new file with the same name will be created
if (fs.existsSync(target)) {
if ( fs.lstatSync( target ).isDirectory() ) {
targetFile = path.join(target, path.basename(source));
}
}
try {
fs.writeFileSync(targetFile, fs.readFileSync(source));
}
catch (err) {
console.log(`file copy error: ${source}" -> "${targetFile}" (error ignored)`);
}
}
function copyFolderRecursiveSync(source, target, ignore) {
let files = [];
let base = path.basename(source);
if (base === adapterName) {
base = pkg.name;
}
//check if folder needs to be created or integrated
const targetFolder = path.join(target, base);
if (!fs.existsSync(targetFolder)) {
fs.mkdirSync(targetFolder);
}
//copy
if (fs.lstatSync(source).isDirectory()) {
files = fs.readdirSync(source);
files.forEach(function (file) {
if (ignore && ignore.indexOf(file) !== -1) {
return;
}
const curSource = path.join(source, file);
const curTarget = path.join(targetFolder, file);
if (fs.lstatSync(curSource).isDirectory()) {
// ignore grunt files
if (file.indexOf('grunt') !== -1) return;
if (file === 'chai') return;
if (file === 'mocha') return;
copyFolderRecursiveSync(curSource, targetFolder, ignore);
} else {
copyFileSync(curSource, curTarget);
}
});
}
}
if (!fs.existsSync(rootDir + 'tmp')) {
fs.mkdirSync(rootDir + 'tmp');
}
function storeOriginalFiles() {
console.log('Store original files...');
const dataDir = rootDir + 'tmp/' + appName + '-data/';
let f = fs.readFileSync(dataDir + 'objects.json');
const objects = JSON.parse(f.toString());
if (objects['system.adapter.admin.0'] && objects['system.adapter.admin.0'].common) {
objects['system.adapter.admin.0'].common.enabled = false;
}
if (objects['system.adapter.admin.1'] && objects['system.adapter.admin.1'].common) {
objects['system.adapter.admin.1'].common.enabled = false;
}
fs.writeFileSync(dataDir + 'objects.json.original', JSON.stringify(objects));
try {
f = fs.readFileSync(dataDir + 'states.json');
fs.writeFileSync(dataDir + 'states.json.original', f);
}
catch (err) {
console.log('no states.json found - ignore');
}
}
function restoreOriginalFiles() {
console.log('restoreOriginalFiles...');
const dataDir = rootDir + 'tmp/' + appName + '-data/';
let f = fs.readFileSync(dataDir + 'objects.json.original');
fs.writeFileSync(dataDir + 'objects.json', f);
try {
f = fs.readFileSync(dataDir + 'states.json.original');
fs.writeFileSync(dataDir + 'states.json', f);
}
catch (err) {
console.log('no states.json.original found - ignore');
}
}
function checkIsAdapterInstalled(cb, counter, customName) {
customName = customName || pkg.name.split('.').pop();
counter = counter || 0;
const dataDir = rootDir + 'tmp/' + appName + '-data/';
console.log('checkIsAdapterInstalled...');
try {
const f = fs.readFileSync(dataDir + 'objects.json');
const objects = JSON.parse(f.toString());
if (objects['system.adapter.' + customName + '.0']) {
console.log('checkIsAdapterInstalled: ready!');
setTimeout(() => cb && cb(), 100);
return;
} else {
console.warn('checkIsAdapterInstalled: still not ready');
}
} catch (err) {
}
if (counter > 20) {
console.error('checkIsAdapterInstalled: Cannot install!');
if (cb) cb('Cannot install');
} else {
console.log('checkIsAdapterInstalled: wait...');
setTimeout(() => checkIsAdapterInstalled(cb, counter + 1), 1000);
}
}
function checkIsControllerInstalled(cb, counter) {
counter = counter || 0;
const dataDir = rootDir + 'tmp/' + appName + '-data/';
console.log('checkIsControllerInstalled...');
try {
const f = fs.readFileSync(dataDir + 'objects.json');
const objects = JSON.parse(f.toString());
if (objects['system.adapter.admin.0']) {
console.log('checkIsControllerInstalled: installed!');
setTimeout(() => cb && cb(), 100);
return;
}
} catch (err) {
}
if (counter > 20) {
console.log('checkIsControllerInstalled: Cannot install!');
if (cb) cb('Cannot install');
} else {
console.log('checkIsControllerInstalled: wait...');
setTimeout(() => checkIsControllerInstalled(cb, counter + 1), 1000);
}
}
function installAdapter(customName, cb) {
if (typeof customName === 'function') {
cb = customName;
customName = null;
}
customName = customName || pkg.name.split('.').pop();
console.log('Install adapter...');
const startFile = 'node_modules/' + appName + '.js-controller/' + appName + '.js';
// make first install
if (debug) {
child_process.execSync('node ' + startFile + ' add ' + customName + ' --enabled false', {
cwd: rootDir + 'tmp',
stdio: [0, 1, 2]
});
checkIsAdapterInstalled(error => {
if (error) console.error(error);
console.log('Adapter installed.');
if (cb) cb();
});
} else {
// add controller
const _pid = child_process.fork(startFile, ['add', customName, '--enabled', 'false'], {
cwd: rootDir + 'tmp',
stdio: [0, 1, 2, 'ipc']
});
waitForEnd(_pid, () => {
checkIsAdapterInstalled(error => {
if (error) console.error(error);
console.log('Adapter installed.');
if (cb) cb();
});
});
}
}
function waitForEnd(_pid, cb) {
if (!_pid) {
cb(-1, -1);
return;
}
_pid.on('exit', (code, signal) => {
if (_pid) {
_pid = null;
cb(code, signal);
}
});
_pid.on('close', (code, signal) => {
if (_pid) {
_pid = null;
cb(code, signal);
}
});
}
function installJsController(cb) {
console.log('installJsController...');
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller') ||
!fs.existsSync(rootDir + 'tmp/' + appName + '-data')) {
// try to detect appName.js-controller in node_modules/appName.js-controller
// travis CI installs js-controller into node_modules
if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller')) {
console.log('installJsController: no js-controller => copy it from "' + rootDir + 'node_modules/' + appName + '.js-controller"');
// copy all
// stop controller
console.log('Stop controller if running...');
let _pid;
if (debug) {
// start controller
_pid = child_process.exec('node ' + appName + '.js stop', {
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2]
});
} else {
_pid = child_process.fork(appName + '.js', ['stop'], {
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2, 'ipc']
});
}
waitForEnd(_pid, () => {
// copy all files into
if (!fs.existsSync(rootDir + 'tmp')) fs.mkdirSync(rootDir + 'tmp');
if (!fs.existsSync(rootDir + 'tmp/node_modules')) fs.mkdirSync(rootDir + 'tmp/node_modules');
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')){
console.log('Copy js-controller...');
copyFolderRecursiveSync(rootDir + 'node_modules/' + appName + '.js-controller', rootDir + 'tmp/node_modules/');
}
console.log('Setup js-controller...');
let __pid;
if (debug) {
// start controller
_pid = child_process.exec('node ' + appName + '.js setup first --console', {
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2]
});
} else {
__pid = child_process.fork(appName + '.js', ['setup', 'first', '--console'], {
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2, 'ipc']
});
}
waitForEnd(__pid, () => {
checkIsControllerInstalled(() => {
// change ports for object and state DBs
const config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
config.objects.port = 19001;
config.states.port = 19000;
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2));
console.log('Setup finished.');
copyAdapterToController();
installAdapter(() => {
storeOriginalFiles();
if (cb) cb(true);
});
});
});
});
} else {
// check if port 9000 is free, else admin adapter will be added to running instance
const client = new require('net').Socket();
client.connect(9000, '127.0.0.1', () => {
console.error('Cannot initiate fisrt run of test, because one instance of application is running on this PC. Stop it and repeat.');
process.exit(0);
});
setTimeout(() => {
client.destroy();
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')) {
console.log('installJsController: no js-controller => install from git');
child_process.execSync('npm install https://github.com/' + appName + '/' + appName + '.js-controller/tarball/master --prefix ./ --production', {
cwd: rootDir + 'tmp/',
stdio: [0, 1, 2]
});
} else {
console.log('Setup js-controller...');
if (debug) {
// start controller
child_process.exec('node ' + appName + '.js setup first', {
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2]
});
} else {
child_process.fork(appName + '.js', ['setup', 'first'], {
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2, 'ipc']
});
}
}
// let npm install admin and run setup
checkIsControllerInstalled(() => {
let _pid;
if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller/' + appName + '.js')) {
_pid = child_process.fork(appName + '.js', ['stop'], {
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2, 'ipc']
});
}
waitForEnd(_pid, () => {
// change ports for object and state DBs
const config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
config.objects.port = 19001;
config.states.port = 19000;
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2));
copyAdapterToController();
installAdapter(() => {
storeOriginalFiles();
if (cb) cb(true);
});
});
});
}, 1000);
}
} else {
setImmediate(() => {
console.log('installJsController: js-controller installed');
if (cb) cb(false);
});
}
}
function copyAdapterToController() {
console.log('Copy adapter...');
// Copy adapter to tmp/node_modules/appName.adapter
copyFolderRecursiveSync(rootDir, rootDir + 'tmp/node_modules/', ['.idea', 'test', 'tmp', '.git', appName + '.js-controller']);
console.log('Adapter copied.');
}
function clearControllerLog() {
const dirPath = rootDir + 'tmp/log';
let files;
try {
if (fs.existsSync(dirPath)) {
console.log('Clear controller log...');
files = fs.readdirSync(dirPath);
} else {
console.log('Create controller log directory...');
files = [];
fs.mkdirSync(dirPath);
}
} catch(e) {
console.error('Cannot read "' + dirPath + '"');
return;
}
if (files.length > 0) {
try {
for (let i = 0; i < files.length; i++) {
const filePath = dirPath + '/' + files[i];
fs.unlinkSync(filePath);
}
console.log('Controller log cleared');
} catch (err) {
console.error('cannot clear log: ' + err);
}
}
}
function clearDB() {
const dirPath = rootDir + 'tmp/iobroker-data/sqlite';
let files;
try {
if (fs.existsSync(dirPath)) {
console.log('Clear sqlite DB...');
files = fs.readdirSync(dirPath);
} else {
console.log('Create controller log directory...');
files = [];
fs.mkdirSync(dirPath);
}
} catch(e) {
console.error('Cannot read "' + dirPath + '"');
return;
}
if (files.length > 0) {
try {
for (let i = 0; i < files.length; i++) {
const filePath = dirPath + '/' + files[i];
fs.unlinkSync(filePath);
}
console.log('Clear sqlite DB');
} catch (err) {
console.error('cannot clear DB: ' + err);
}
}
}
function setupController(cb) {
installJsController(isInited => {
clearControllerLog();
clearDB();
if (!isInited) {
restoreOriginalFiles();
copyAdapterToController();
}
// read system.config object
const dataDir = rootDir + 'tmp/' + appName + '-data/';
let objs;
try {
objs = fs.readFileSync(dataDir + 'objects.json');
objs = JSON.parse(objs);
}
catch (e) {
console.log('ERROR reading/parsing system configuration. Ignore');
objs = {'system.config': {}};
}
if (!objs || !objs['system.config']) {
objs = {'system.config': {}};
}
if (cb) cb(objs['system.config']);
});
}
function startAdapter(objects, states, callback) {
if (adapterStarted) {
console.log('Adapter already started ...');
if (callback) callback(objects, states);
return;
}
adapterStarted = true;
console.log('startAdapter...');
if (fs.existsSync(rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main)) {
try {
if (debug) {
// start controller
pid = child_process.exec('node node_modules/' + pkg.name + '/' + pkg.main + ' --console silly', {
cwd: rootDir + 'tmp',
stdio: [0, 1, 2]
});
} else {
// start controller
pid = child_process.fork('node_modules/' + pkg.name + '/' + pkg.main, ['--console', 'silly'], {
cwd: rootDir + 'tmp',
stdio: [0, 1, 2, 'ipc']
});
}
} catch (error) {
console.error(JSON.stringify(error));
}
} else {
console.error('Cannot find: ' + rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main);
}
if (callback) callback(objects, states);
}
function startController(isStartAdapter, onObjectChange, onStateChange, callback) {
if (typeof isStartAdapter === 'function') {
callback = onStateChange;
onStateChange = onObjectChange;
onObjectChange = isStartAdapter;
isStartAdapter = true;
}
if (onStateChange === undefined) {
callback = onObjectChange;
onObjectChange = undefined;
}
if (pid) {
console.error('Controller is already started!');
} else {
console.log('startController...');
adapterStarted = false;
let isObjectConnected;
let isStatesConnected;
const Objects = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/objects/objectsInMemServer');
objects = new Objects({
connection: {
type : 'file',
host : '127.0.0.1',
port : 19001,
user : '',
pass : '',
noFileCache: false,
connectTimeout: 2000
},
logger: {
silly: function (msg) {
console.log(msg);
},
debug: function (msg) {
console.log(msg);
},
info: function (msg) {
console.log(msg);
},
warn: function (msg) {
console.warn(msg);
},
error: function (msg) {
console.error(msg);
}
},
connected: () => {
isObjectConnected = true;
if (isStatesConnected) {
console.log('startController: started!');
if (isStartAdapter) {
startAdapter(objects, states, callback);
} else {
if (callback) {
callback(objects, states);
callback = null;
}
}
}
},
change: onObjectChange
});
// Just open in memory DB itself
const States = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/states/statesInMemServer');
states = new States({
connection: {
type: 'file',
host: '127.0.0.1',
port: 19000,
options: {
auth_pass: null,
retry_max_delay: 15000
}
},
logger: {
silly: function (msg) {
console.log(msg);
},
debug: function (msg) {
console.log(msg);
},
info: function (msg) {
console.log(msg);
},
warn: function (msg) {
console.log(msg);
},
error: function (msg) {
console.log(msg);
}
},
connected: () => {
isStatesConnected = true;
if (isObjectConnected) {
console.log('startController: started!!');
if (isStartAdapter) {
startAdapter(objects, states, callback);
} else {
if (callback) {
callback(objects, states);
callback = null;
}
}
}
},
change: onStateChange
});
}
}
function stopAdapter(cb) {
if (!pid) {
console.error('Controller is not running!');
if (cb) {
setImmediate(() => cb(false));
}
} else {
adapterStarted = false;
pid.on('exit', (code, signal) => {
if (pid) {
console.log('child process terminated due to receipt of signal ' + signal);
if (cb) cb();
pid = null;
}
});
pid.on('close', (code, signal) => {
if (pid) {
if (cb) cb();
pid = null;
}
});
pid.kill('SIGTERM');
}
}
function _stopController() {
if (objects) {
objects.destroy();
objects = null;
}
if (states) {
states.destroy();
states = null;
}
}
function stopController(cb) {
let timeout;
if (objects) {
console.log('Set system.adapter.' + pkg.name + '.0');
objects.setObject('system.adapter.' + pkg.name + '.0', {
common:{
enabled: false
}
});
}
stopAdapter(() => {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
_stopController();
if (cb) {
cb(true);
cb = null;
}
});
timeout = setTimeout(() => {
timeout = null;
console.log('child process NOT terminated');
_stopController();
if (cb) {
cb(false);
cb = null;
}
pid = null;
}, 5000);
}
// Setup the adapter
function setAdapterConfig(common, native, instance) {
const objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString());
const id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0);
if (common) objects[id].common = common;
if (native) objects[id].native = native;
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/objects.json', JSON.stringify(objects));
}
// Read config of the adapter
function getAdapterConfig(instance) {
const objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString());
const id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0);
return objects[id];
}
if (typeof module !== undefined && module.parent) {
module.exports.getAdapterConfig = getAdapterConfig;
module.exports.setAdapterConfig = setAdapterConfig;
module.exports.startController = startController;
module.exports.stopController = stopController;
module.exports.setupController = setupController;
module.exports.stopAdapter = stopAdapter;
module.exports.startAdapter = startAdapter;
module.exports.installAdapter = installAdapter;
module.exports.appName = appName;
module.exports.adapterName = adapterName;
module.exports.adapterStarted = adapterStarted;
}

708
test/lib/states.js Normal file
View File

@@ -0,0 +1,708 @@
'use strict';
const path = require('path');
const rootDir = path.normalize(__dirname + '/../../');
let adapterName = path.normalize(rootDir).replace(/\\/g, '/').split('/');
adapterName = adapterName[adapterName.length - 2];
const logger = {
info: function (msg) {
console.log(msg);
},
debug: function (msg) {
console.log(msg);
},
warn: function (msg) {
console.warn(msg);
},
error: function (msg) {
console.error(msg);
}
};
function States(cb, stateChange) {
const that = this;
const _States = require(rootDir + 'tmp/node_modules/iobroker.js-controller/lib/states');
let callbackId = 0;
const options = {
stateChange: (id, state) => stateChange && stateChange(id, state)
};
that.namespace = 'test';
that.states = new _States({
connection: {
type : 'file',
host : '127.0.0.1',
port : 19000,
options : {
auth_pass : null,
retry_max_delay : 15000
}
},
logger: logger,
change: (id, state) => {
if (!id || typeof id !== 'string') {
console.log('Something is wrong! ' + JSON.stringify(id));
return;
}
// Clear cache if accidentally got the message about change (Will work for admin and javascript)
if (id.match(/^system\.user\./) || id.match(/^system\.group\./)) {
that.users = [];
}
// If someone want to have log messages
if (that.logList && id.match(/\.logging$/)) {
that.logRedirect(state ? state.val : false, id.substring(0, id.length - '.logging'.length));
} else
if (id === 'log.system.adapter.' + that.namespace) {
that.processLog(state);
} else
// If this is messagebox
if (id === 'messagebox.system.adapter.' + that.namespace && state) {
// Read it from fifo list
that.states.delMessage('system.adapter.' + that.namespace, state._id);
const obj = state;
if (obj) {
// If callback stored for this request
if (obj.callback &&
obj.callback.ack &&
obj.callback.id &&
that.callbacks &&
that.callbacks['_' + obj.callback.id]) {
// Call callback function
if (that.callbacks['_' + obj.callback.id].cb) {
that.callbacks['_' + obj.callback.id].cb(obj.message);
delete that.callbacks['_' + obj.callback.id];
}
// delete too old callbacks IDs, like garbage collector
const now = Date.now();
for (const _id in that.callbacks) {
if (that.callbacks.hasOwnProperty(_id) && now - that.callbacks[_id].time > 3600000) delete that.callbacks[_id];
}
} else {
if (options.message) {
// Else inform about new message the adapter
options.message(obj);
}
that.emit('message', obj);
}
}
} else {
if (id.slice(that.namespace.length) === that.namespace) {
if (typeof options.stateChange === 'function') options.stateChange(id.slice(that.namespace.length + 1), state);
// emit 'stateChange' event instantly
setImmediate(() => that.emit('stateChange', id.slice(that.namespace.length + 1), state));
} else {
if (typeof options.stateChange === 'function') options.stateChange(id, state);
if (id.substring(0, 4) === 'log.') {
console.log("LOG");
}
if (that.emit) {
// emit 'stateChange' event instantly
setImmediate(() => that.emit('stateChange', id, state));
}
}
}
},
connectTimeout: (error) => {
if (logger) logger.error(that.namespace + ' no connection to states DB');
if (cb) cb('Timeout');
}
});
// Send message to other adapter instance or all instances of adapter
that.sendTo = function sendTo(objName, command, message, callback) {
if (typeof message === 'undefined') {
message = command;
command = 'send';
}
const obj = {command: command, message: message, from: 'system.adapter.' + that.namespace};
if (!objName.match(/^system\.adapter\./)) objName = 'system.adapter.' + objName;
that.log.info('sendTo "' + command + '" to ' + objName + ' from system.adapter.' + that.namespace + ': ' + JSON.stringify(message));
// If not specific instance
if (!objName.match(/\.[0-9]+$/)) {
// Send to all instances of adapter
that.objects.getObjectView('system', 'instance', {startkey: objName + '.', endkey: objName + '.\u9999'}, (err, _obj) => {
if (_obj) {
for (let i = 0; i < _obj.rows.length; i++) {
that.states.pushMessage(_obj.rows[i].id, obj);
}
}
});
} else {
if (callback) {
if (typeof callback === 'function') {
// force subscribe even no messagebox enabled
if (!that.common.messagebox && !that.mboxSubscribed) {
that.mboxSubscribed = true;
that.states.subscribeMessage('system.adapter.' + that.namespace);
}
obj.callback = {
message: message,
id: callbackId++,
ack: false,
time: Date.now()
};
if (callbackId >= 0xFFFFFFFF) {
callbackId = 1;
}
if (!that.callbacks) that.callbacks = {};
that.callbacks['_' + obj.callback.id] = {cb: callback};
// delete too old callbacks IDs
const now = Date.now();
for (const _id in that.callbacks) {
if (that.callbacks.hasOwnProperty(_id) && now - that.callbacks[_id].time > 3600000) {
delete that.callbacks[_id];
}
}
} else {
obj.callback = callback;
obj.callback.ack = true;
}
}
that.states.pushMessage(objName, obj);
}
};
// Send message to specific host or to all hosts
that.sendToHost = function sendToHost(objName, command, message, callback) {
if (typeof message === 'undefined') {
message = command;
command = 'send';
}
const obj = {command: command, message: message, from: 'system.adapter.' + that.namespace};
if (objName && objName.substring(0, 'system.host.'.length) !== 'system.host.') objName = 'system.host.' + objName;
if (!objName) {
// Send to all hosts
that.objects.getObjectList({startkey: 'system.host.', endkey: 'system.host.' + '\u9999'}, null, (err, res) => {
if (!err && res.rows.length) {
for (let i = 0; i < res.rows.length; i++) {
const parts = res.rows[i].id.split('.');
// ignore system.host.name.alive and so on
if (parts.length === 3) {
that.states.pushMessage(res.rows[i].id, obj);
}
}
}
});
} else {
if (callback) {
if (typeof callback === 'function') {
// force subscribe even no messagebox enabled
if (!that.common.messagebox && !that.mboxSubscribed) {
that.mboxSubscribed = true;
that.states.subscribeMessage('system.adapter.' + that.namespace);
}
obj.callback = {
message: message,
id: callbackId++,
ack: false,
time: Date.now()
};
if (callbackId >= 0xFFFFFFFF) callbackId = 1;
if (!that.callbacks) that.callbacks = {};
that.callbacks['_' + obj.callback.id] = {cb: callback};
} else {
obj.callback = callback;
obj.callback.ack = true;
}
}
that.states.pushMessage(objName, obj);
}
};
that.setState = function setState(id, state, ack, options, callback) {
if (typeof state === 'object' && typeof ack !== 'boolean') {
callback = options;
options = ack;
ack = undefined;
}
if (typeof options === 'function') {
callback = options;
options = {};
}
id = that._fixId(id, 'state');
if (typeof ack === 'function') {
callback = ack;
ack = undefined;
}
if (typeof state !== 'object' || state === null || state === undefined) state = {val: state};
if (ack !== undefined) {
state.ack = ack;
}
state.from = 'system.adapter.' + that.namespace;
if (options && options.user && options.user !== 'system.user.admin') {
checkStates(id, options, 'setState', err => {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
that.states.setState(id, state, callback);
}
});
} else {
that.states.setState(id, state, callback);
}
};
that.setForeignState = function setForeignState(id, state, ack, options, callback) {
if (typeof state === 'object' && typeof ack !== 'boolean') {
callback = options;
options = ack;
ack = undefined;
}
if (typeof options === 'function') {
callback = options;
options = {};
}
if (typeof ack === 'function') {
callback = ack;
ack = undefined;
}
if (typeof state !== 'object' || state === null || state === undefined) state = {val: state};
if (ack !== undefined) {
state.ack = ack;
}
state.from = 'system.adapter.' + that.namespace;
if (options && options.user && options.user !== 'system.user.admin') {
checkStates(id, options, 'setState', err => {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
that.states.setState(id, state, callback);
}
});
} else {
that.states.setState(id, state, callback);
}
};
that.getState = function getState(id, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
id = that._fixId(id, 'state');
if (options && options.user && options.user !== 'system.user.admin') {
checkStates(id, options, 'getState', err => {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
that.states.getState(id, callback);
}
});
} else {
that.states.getState(id, callback);
}
};
that.getStateHistory = function getStateHistory(id, start, end, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
id = that._fixId(id, 'state');
that.getForeignStateHistory(id, start, end, options, callback);
};
that.getForeignStateHistory = function getForeignStateHistory(id, start, end, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
if (typeof start === 'function') {
callback = start;
start = undefined;
end = undefined;
} else if (typeof end === 'function') {
callback = end;
end = undefined;
}
start = start || Math.round((new Date()).getTime() / 1000) - 31536000; // - 1 year
end = end || Math.round((new Date()).getTime() / 1000) + 5000;
const history = [];
const docs = [];
// get data from states
that.log.debug('get states history ' + id + ' ' + start + ' ' + end);
that.getFifo(id, (err, res) => {
if (!err && res) {
let iProblemCount = 0;
for (let i = 0; i < res.length; i++) {
if (!res[i]) {
iProblemCount++;
continue;
}
if (res[i].ts < start) {
continue;
} else if (res[i].ts > end) {
break;
}
history.push(res[i]);
}
if (iProblemCount) that.log.warn('got null states ' + iProblemCount + ' times for ' + id);
that.log.debug('got ' + res.length + ' datapoints for ' + id);
} else {
if (err !== 'Not exists') {
that.log.error(err);
} else {
that.log.debug('datapoints for ' + id + ' do not yet exist');
}
}
// fetch a history document from objectDB
function getObjectsLog(cid, callback) {
that.log.info('getObjectLog ' + cid);
that.getForeignObject(cid, options, (err, res) => {
if (!err && res.common.data) {
for (let i = 0; i < res.common.data.length; i++) {
if (res.common.data[i].ts < start) {
continue;
} else if (res.common.data[i].ts > end) {
break;
}
history.push(res.common.data[i]);
}
} else {
that.log.warn(cid + ' not found');
}
callback(err);
});
}
// queue objects history documents fetching
function queue(ts) {
if (ts < start) {
callback(null, history);
return;
}
const cid = 'history.' + id + '.' + ts2day(ts);
if (docs.indexOf(cid) !== -1) {
getObjectsLog(cid, err => queue(ts - 86400)); // - 1 day
} else {
queue(ts - 86400); // - 1 day
}
}
// get list of available history documents
that.objects.getObjectList({startkey: 'history.' + id, endkey: 'history.' + id + '\u9999'}, options, (err, res) => {
if (!err && res.rows.length) {
for (let i = 0; i < res.rows.length; i++) {
docs.push(res.rows[i].id);
}
queue(end);
} else {
callback(null, history);
}
});
});
};
// normally only foreign history has interest, so there is no getHistory and getForeignHistory
that.getHistory = function getHistory(id, options, callback) {
options = options || {};
options.end = options.end || Math.round((new Date()).getTime() / 1000) + 5000;
if (!options.count && !options.start) {
options.start = options.start || Math.round((new Date()).getTime() / 1000) - 604800; // - 1 week
}
if (!options.instance) {
if (!that.defaultHistory) {
// read default history instance from system.config
return getDefaultHistory(() => that.getHistory(id, options, callback));
} else {
options.instance = that.defaultHistory;
}
}
that.sendTo(options.instance || 'history.0', 'getHistory', {id: id, options: options}, res => {
setImmediate(() => callback(res.error, res.result, res.step));
});
};
// Convert ID adapter.instance.device.channel.state
// Convert ID to {device: D, channel: C, state: S}
that.idToDCS = function idToDCS(id) {
if (!id) return null;
const parts = id.split('.');
if (parts[0] + '.' + parts[1] !== that.namespace) {
that.log.warn("Try to decode id not from this adapter");
return null;
}
return {device: parts[2], channel: parts[3], state: parts[4]};
};
that.getForeignState = function getForeignState(id, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
if (options && options.user && options.user !== 'system.user.admin') {
checkStates(id, options, 'getState', err => {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
that.states.getState(id, callback);
}
});
} else {
that.states.getState(id, callback);
}
};
that.delForeignState = function delForeignState(id, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
if (options && options.user && options.user !== 'system.user.admin') {
checkStates(id, options, 'delState', err => {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
that.states.delState(id, callback);
}
});
} else {
that.states.delState(id, callback);
}
};
that.delState = function delState(id, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
id = that._fixId(id);
if (options && options.user && options.user !== 'system.user.admin') {
checkStates(id, options, 'delState', err => {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
that.states.delState(id, callback);
}
});
} else {
that.states.delState(id, callback);
}
};
that.getStates = function getStates(pattern, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
pattern = that._fixId(pattern, 'state');
that.getForeignStates(pattern, options, callback);
};
that.getForeignStates = function getForeignStates(pattern, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
const list = {};
if (typeof pattern === 'function') {
callback = pattern;
pattern = '*';
}
if (typeof callback !== 'function') {
logger.error('getForeignStates invalid callback for ' + pattern);
return;
}
if (typeof pattern === 'object') {
that.states.getStates(pattern, (err, arr) => {
if (err) {
callback(err);
return;
}
for (let i = 0; i < pattern.length; i++) {
if (typeof arr[i] === 'string') arr[i] = JSON.parse(arr[i]);
list[pattern[i]] = arr[i] || {};
}
callback(null, list);
});
return;
}
const keys = [];
let params = {};
if (pattern && pattern !== '*') {
params = {
startkey: pattern.replace('*', ''),
endkey: pattern.replace('*', '\u9999')
};
}
that.objects.getObjectView('system', 'state', params, options, (err, res) => {
if (err) {
if (typeof callback === 'function') callback(err);
return;
}
for (let i = 0; i < res.rows.length; i++) {
keys.push(res.rows[i].id);
}
if (options && options.user && options.user !== 'system.user.admin') {
checkStates(keys, options, 'getState', (err, keys) => {
if (err) {
if (typeof callback === 'function') callback(err);
return;
}
that.states.getStates(keys, function (err, arr) {
if (err) {
callback(err);
return;
}
for (let i = 0; i < res.rows.length; i++) {
if (typeof arr[i] === 'string') arr[i] = JSON.parse(arr[i]);
list[keys[i]] = arr[i] || {};
}
if (typeof callback === 'function') callback(null, list);
});
});
} else {
that.states.getStates(keys, function (err, arr) {
if (err) {
callback(err);
return;
}
for (let i = 0; i < res.rows.length; i++) {
if (typeof arr[i] === 'string') arr[i] = JSON.parse(arr[i]);
list[keys[i]] = arr[i] || {};
}
if (typeof callback === 'function') callback(null, list);
});
}
});
};
that.subscribeForeignStates = function subscribeForeignStates(pattern, options) {
if (!pattern) pattern = '*';
that.states.subscribe(pattern, options);
};
that.unsubscribeForeignStates = function unsubscribeForeignStates(pattern, options) {
if (!pattern) pattern = '*';
that.states.unsubscribe(pattern, options);
};
that.subscribeStates = function subscribeStates(pattern, options) {
// Exception. Threat the '*' case automatically
if (!pattern || pattern === '*') {
that.states.subscribe(that.namespace + '.*', options);
} else {
pattern = that._fixId(pattern, 'state');
that.states.subscribe(pattern, options);
}
};
that.unsubscribeStates = function unsubscribeStates(pattern, options) {
if (!pattern || pattern === '*') {
that.states.unsubscribe(that.namespace + '.*', options);
} else {
pattern = that._fixId(pattern, 'state');
that.states.unsubscribe(pattern, options);
}
};
that.pushFifo = function pushFifo(id, state, callback) {
that.states.pushFifo(id, state, callback);
};
that.trimFifo = function trimFifo(id, start, end, callback) {
that.states.trimFifo(id, start, end, callback);
};
that.getFifoRange = function getFifoRange(id, start, end, callback) {
that.states.getFifoRange(id, start, end, callback);
};
that.getFifo = function getFifo(id, callback) {
that.states.getFifo(id, callback);
};
that.lenFifo = function lenFifo(id, callback) {
that.states.lenFifo(id, callback);
};
that.subscribeFifo = function subscribeFifo(pattern) {
that.states.subscribeFifo(pattern);
};
that.getSession = function getSession(id, callback) {
that.states.getSession(id, callback);
};
that.setSession = function setSession(id, ttl, data, callback) {
that.states.setSession(id, ttl, data, callback);
};
that.destroySession = function destroySession(id, callback) {
that.states.destroySession(id, callback);
};
that.getMessage = function getMessage(callback) {
that.states.getMessage('system.adapter.' + that.namespace, callback);
};
that.lenMessage = function lenMessage(callback) {
that.states.lenMessage('system.adapter.' + that.namespace, callback);
};
// Write binary block into redis, e.g image
that.setBinaryState = function setBinaryState(id, binary, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
that.states.setBinaryState(id, binary, callback);
};
// Read binary block fromredis, e.g. image
that.getBinaryState = function getBinaryState(id, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
that.states.getBinaryState(id, callback);
};
logger.debug(that.namespace + ' statesDB connected');
if (typeof cb === 'function') {
setImmediate(() => cb(), 0);
}
return this;
}
module.exports = States;

92
test/testPackageFiles.js Normal file
View File

@@ -0,0 +1,92 @@
/* jshint -W097 */
/* jshint strict:false */
/* jslint node: true */
/* jshint expr: true */
'use strict';
const expect = require('chai').expect;
const fs = require('fs');
describe('Test package.json and io-package.json', () => {
it('Test package files', function (done) {
console.log();
const fileContentIOPackage = fs.readFileSync(__dirname + '/../io-package.json', 'utf8');
const ioPackage = JSON.parse(fileContentIOPackage);
const fileContentNPMPackage = fs.readFileSync(__dirname + '/../package.json', 'utf8');
const npmPackage = JSON.parse(fileContentNPMPackage);
expect(ioPackage).to.be.an('object');
expect(npmPackage).to.be.an('object');
expect(ioPackage.common.version, 'ERROR: Version number in io-package.json needs to exist').to.exist;
expect(npmPackage.version, 'ERROR: Version number in package.json needs to exist').to.exist;
expect(ioPackage.common.version, 'ERROR: Version numbers in package.json and io-package.json needs to match').to.be.equal(npmPackage.version);
if (!ioPackage.common.news || !ioPackage.common.news[ioPackage.common.version]) {
console.log('WARNING: No news entry for current version exists in io-package.json, no rollback in Admin possible!');
console.log();
}
expect(npmPackage.author, 'ERROR: Author in package.json needs to exist').to.exist;
expect(ioPackage.common.authors, 'ERROR: Authors in io-package.json needs to exist').to.exist;
if (ioPackage.common.name.indexOf('template') !== 0) {
if (Array.isArray(ioPackage.common.authors)) {
expect(ioPackage.common.authors.length, 'ERROR: Author in io-package.json needs to be set').to.not.be.equal(0);
if (ioPackage.common.authors.length === 1) {
expect(ioPackage.common.authors[0], 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name <my@email.com>');
}
}
else {
expect(ioPackage.common.authors, 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name <my@email.com>');
}
}
else {
console.log('WARNING: Testing for set authors field in io-package skipped because template adapter');
console.log();
}
expect(fs.existsSync(__dirname + '/../README.md'), 'ERROR: README.md needs to exist! Please create one with description, detail information and changelog. English is mandatory.').to.be.true;
if (!ioPackage.common.titleLang || typeof ioPackage.common.titleLang !== 'object') {
console.log('WARNING: titleLang is not existing in io-package.json. Please add');
console.log();
}
if (
ioPackage.common.title.indexOf('iobroker') !== -1 ||
ioPackage.common.title.indexOf('ioBroker') !== -1 ||
ioPackage.common.title.indexOf('adapter') !== -1 ||
ioPackage.common.title.indexOf('Adapter') !== -1
) {
console.log('WARNING: title contains Adapter or ioBroker. It is clear anyway, that it is adapter for ioBroker.');
console.log();
}
if (ioPackage.common.name.indexOf('vis-') !== 0) {
if (!ioPackage.common.materialize || !fs.existsSync(__dirname + '/../admin/index_m.html') || !fs.existsSync(__dirname + '/../gulpfile.js')) {
console.log('WARNING: Admin3 support is missing! Please add it');
console.log();
}
if (ioPackage.common.materialize) {
expect(fs.existsSync(__dirname + '/../admin/index_m.html'), 'Admin3 support is enabled in io-package.json, but index_m.html is missing!').to.be.true;
}
}
const licenseFileExists = fs.existsSync(__dirname + '/../LICENSE');
const fileContentReadme = fs.readFileSync(__dirname + '/../README.md', 'utf8');
if (fileContentReadme.indexOf('## Changelog') === -1) {
console.log('Warning: The README.md should have a section ## Changelog');
console.log();
}
expect((licenseFileExists || fileContentReadme.indexOf('## License') !== -1), 'A LICENSE must exist as LICENSE file or as part of the README.md').to.be.true;
if (!licenseFileExists) {
console.log('Warning: The License should also exist as LICENSE file');
console.log();
}
if (fileContentReadme.indexOf('## License') === -1) {
console.log('Warning: The README.md should also have a section ## License to be shown in Admin3');
console.log();
}
done();
});
});

264
test/testServer.js Normal file
View File

@@ -0,0 +1,264 @@
/* jshint -W097 */
// jshint strict:true
/*jslint node: true */
/*jslint esversion: 6 */
'use strict';
let expect = require('chai').expect;
let setup = require(__dirname + '/lib/setup');
let objects = null;
let states = null;
let mqttClientEmitter = null;
let mqttClientDetector = null;
let connected = false;
let lastReceivedTopic1;
let lastReceivedMessage1;
let lastReceivedTopic2;
let lastReceivedMessage2;
let clientConnected1 = false;
let clientConnected2 = false;
let brokerStarted = false;
let rules = {
'tele/sonoff_4ch/STATE': {send: '{"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}}', expect: {Vcc: 3.226, Wifi_RSSI: 15}},
'tele/sonoff/SENSOR': {send: '{"Time":"2017-10-05T17:43:19", "DS18x20":{"DS1":{"Type":"DS18B20", "Address":"28FF9A9876815022A", "Temperature":12.2}}, "TempUnit":"C"}', expect: {DS18x20_DS1_Temperature: 12.2}},
'tele/sonoff5/SENSOR': {send: '{"Time":"2017-10-03T14:02:25", "AM2301-14":{"Temperature":21.6, "Humidity":54.7}, "TempUnit":"C"}', expect: {'AM2301-14_Temperature': 21.6, 'AM2301-14_Humidity': 54.7}},
'tele/SonoffPOW/INFO1': {send: '{"Module":"Sonoff Pow", "Version":"5.8.0", "FallbackTopic":"SonoffPOW", "GroupTopic":"sonoffs"}', expect: {'INFO.Module': 'Sonoff Pow', 'INFO.Version': '5.8.0'}},
'tele/SonoffPOW/INFO2': {send: '{"WebServerMode":"Admin", "Hostname":"Sonoffpow", "IPAddress":"192.168.2.182"}', expect: {'INFO.Hostname': 'Sonoffpow', 'INFO.IPAddress': '192.168.2.182'}},
'tele/SonoffPOW/INFO3': {send: '{"RestartReason":"Software/System restart"}', expect: {'INFO.RestartReason': 'Software/System restart'}},
'tele/sonoff_4ch/ENERGY': {send: '{"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}', expect: {'ENERGY.Total': 1.753, 'ENERGY.Current': 0.097}},
'tele/sonoff_4ch/ENERGY1': {send: '"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}', expect: {}},
'tele/sonoff_1ch/STATE': {send: '{"Time":"2017-10-02T19:24:32", "Color": "112233"}', expect: {}},
'tele/sonoff/STATE': {send: '{"Time":"2018-06-19T06:39:33","Uptime":"0T23:47:32","Vcc":3.482,"POWER":"OFF","Dimmer":100,"Color":"000000FF","HSBColor":"0,0,0","Channel":[0,0,0,100],"Scheme":0,"Fade":"OFF","Speed":4,"LedTable":"OFF","Wifi":{"AP":1,"SSId":"WLAN-7490","RSSI":50,"APMac":"34:31:C4:C6:EB:0F"}}',
expect:{}},
'tele/sonoff1/SENSOR': {send: '{"Time":"2018-06-15T10:03:24","DS18B20":{"Temperature":0.0},"TempUnit":"C"}', expect: {'DS18B20_Temperature': 0}},
'/ESP_BOX/BM280/Pressure': {send: '1010.09', expect: {'Pressure': 1010.09}},
'/ESP_BOX/BM280/Humidity': {send: '42.39', expect: {'Humidity': 42.39}},
'/ESP_BOX/BM280/Temperature': {send: '25.86', expect: {'Temperature': 25.86}},
'/ESP_BOX/BM280/Approx. Altitude': {send: '24', expect: {'Approx_Altitude': 24}},
'stat/sonoff/POWER': {send: 'ON', expect: {'POWER': true}},
'cmnd/sonoff/POWER': {send: '', expect: {}},
'stat/sonoff/RESULT': {send: '{"POWER": "ON"}', expect: {'RESULT': null}},
'stat/sonoff/LWT': {send: 'someTopic', expect: {'LWT': null}},
'stat/sonoff/ABC': {send: 'text', expect: {'ABC': null}}
};
function decrypt(key, value) {
let result = '';
for (let i = 0; i < value.length; ++i) {
result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i));
}
return result;
}
function startClients(_done) {
// start mqtt client
const MqttClient = require(__dirname + '/lib/mqttClient.js');
// Start client to emit topics
mqttClientEmitter = new MqttClient(connected => {
// on connected
if (connected) {
console.log('Test MQTT Emitter is connected to MQTT broker');
clientConnected1 = true;
if (_done && brokerStarted && clientConnected1 && clientConnected2) {
_done();
_done = null;
}
}
}, (topic, message) => {
console.log(Date.now() + ' emitter received ' + topic + ': ' + message.toString());
// on receive
lastReceivedTopic1 = topic;
lastReceivedMessage1 = message ? message.toString() : null;
}, {name: 'Emitter*1', user: 'user', pass: 'pass1'});
// Start client to receive topics
mqttClientDetector = new MqttClient(connected => {
// on connected
if (connected) {
console.log('Test MQTT Detector is connected to MQTT broker');
clientConnected2 = true;
if (_done && brokerStarted && clientConnected1 && clientConnected2) {
_done();
_done = null;
}
}
}, (topic, message) => {
console.log(Date.now() + ' detector received ' + topic + ': ' + message.toString());
// on receive
lastReceivedTopic2 = topic;
lastReceivedMessage2 = message ? message.toString() : null;
console.log(JSON.stringify(lastReceivedMessage2));
}, {name: 'Detector-1', user: 'user', pass: 'pass1'});
}
function checkMqtt2Adapter(id, task, _it, _done) {
_it.timeout(1000);
lastReceivedMessage1 = null;
lastReceivedTopic1 = null;
lastReceivedTopic2 = null;
lastReceivedMessage2 = null;
console.log(`[${new Date().toISOString()}] Publish ${id}: ${task.send}`);
mqttClientEmitter.publish(id, task.send, err => {
expect(err).to.be.undefined;
setTimeout(() => {
let count = 0;
for (let e in task.expect) {
if (! task.expect.hasOwnProperty(e)) continue;
count++;
(function (_id, _val) {
objects.getObject('sonoff.0.Emitter_1.' + _id, (err, obj) => {
if (_val !== null) {
if (!obj) console.error('Object sonoff.0.Emitter_1.' + _id + ' not found');
expect(obj).to.be.not.null.and.not.undefined;
expect(obj._id).to.be.equal('sonoff.0.Emitter_1.' + _id);
expect(obj.type).to.be.equal('state');
states.getState(obj._id, function (err, state) {
expect(state).to.be.not.null.and.not.undefined;
expect(state.val).to.be.equal(_val);
expect(state.ack).to.be.true;
if (!--count) _done();
});
} else {
expect(obj).to.be.undefined;
states.getState('sonoff.0.Emitter_1.' + _id, (err, state) => {
expect(state).to.be.undefined;
if (!--count) _done();
});
}
});
})(e, task.expect[e]);
}
if (!count) _done();
}, 200);
});
}
function checkAdapter2Mqtt(id, mqttid, value, _done) {
console.log(new Date().toISOString() + ' Send ' + id + ' with value '+ value);
lastReceivedTopic1 = null;
lastReceivedMessage1 = null;
lastReceivedTopic2 = null;
lastReceivedMessage2 = null;
states.setState(id, {
val: value,
ack: false
}, (err, id) => {
setTimeout(() => {
if (!lastReceivedTopic1) {
setTimeout(() => {
expect(lastReceivedTopic1).to.be.equal(mqttid);
expect(lastReceivedMessage1).to.be.equal(value ? 'ON' : 'OFF');
_done();
}, 200);
} else {
expect(lastReceivedTopic1).to.be.equal(mqttid);
expect(lastReceivedMessage1).to.be.equal(value ? 'ON' : 'OFF');
_done();
}
}, 400);
});
}
function checkConnection(value, done, counter) {
counter = counter || 0;
if (counter > 20) {
done && done('Cannot check ' + value);
return;
}
states.getState('sonoff.0.info.connection', (err, state) => {
if (err) console.error(err);
if (state && typeof state.val === 'string' && ((value && state.val.indexOf(',') !== -1) || (!value && state.val.indexOf(',') === -1))) {
connected = value;
done();
} else {
setTimeout(() => {
checkConnection(value, done, counter + 1);
}, 1000);
}
});
}
describe('Sonoff server: Test mqtt server', () => {
before('Sonoff server: Start js-controller', function (_done) { //
this.timeout(600000); // because of first install from npm
setup.adapterStarted = false;
setup.setupController(systemConfig => {
let config = setup.getAdapterConfig();
// enable adapter
config.common.enabled = true;
config.common.loglevel = 'debug';
config.native.user = 'user';
config.native.pass = decrypt(systemConfig.native.secret, 'pass1');
setup.setAdapterConfig(config.common, config.native);
setup.startController((_objects, _states) => {
objects = _objects;
states = _states;
brokerStarted = true;
if (_done && brokerStarted && clientConnected1 && clientConnected2) {
_done();
_done = null;
}
});
});
startClients(_done);
});
it('Sonoff Server: Check if connected to MQTT broker', done => {
if (!connected) {
checkConnection(true, done);
} else {
done();
}
}).timeout(2000);
for (let r in rules) {
(function(id, task) {
it('Sonoff Server: Check receive ' + id, function (done) { // let FUNCTION here
checkMqtt2Adapter(id, task, this, done);
});
})(r, rules[r]);
}
// give time to client to receive all messages
it('wait', done => {
setTimeout(() => done(), 1000);
}).timeout(3000);
it('Sonoff server: detector must receive cmnd/sonoff/POWER', done => {
checkAdapter2Mqtt('sonoff.0.Emitter_1.POWER', 'cmnd/sonoff/POWER', false, done);
}).timeout(2000);
it('Sonoff Server: check reconnection', done => {
mqttClientEmitter.stop();
mqttClientDetector.stop();
checkConnection(false, error => {
expect(error).to.be.not.ok;
startClients();
checkConnection(true, error => {
expect(error).to.be.not.ok;
done();
});
});
}).timeout(10000);
after('Sonoff Server: Stop js-controller', function (_done) { // let FUNCTION and not => here
this.timeout(5000);
mqttClientEmitter.stop();
mqttClientDetector.stop();
setup.stopController(() => _done());
});
});