/* jshint -W097 */ /* jshint strict: false */ /* jslint node: true */ 'use strict'; // This is file, that makes all communication with controller. All options are optional except name. // following options are available: // name: name of the adapter. Must be exactly the same as directory name. // dirname: adapter directory name // instance: instance number of adapter // objects: true or false, if desired to have oObjects. This is a list with all states, channels and devices of this adapter and it will be updated automatically. // states: true or false, if desired to have oStates. This is a list with all states values and it will be updated automatically. // systemConfig: if required system configuration. Store it in systemConfig attribute // objectChange: callback function (id, obj) that will be called if object changed // stateChange: callback function (id, obj) that will be called if state changed // message: callback to inform about new message the adapter // unload: callback to stop the adapter // config: configuration of the connection to controller // noNamespace: return short names of objects and states in objectChange and in stateChange var net = require('net'); var fs = require('fs'); var extend = require('node.extend'); var util = require('util'); var EventEmitter = require('events').EventEmitter; var tools = require(__dirname + '/tools'); var getConfigFileName = tools.getConfigFileName; var schedule; var password = require(__dirname + '/password'); var config = null; var that; var defaultObjs; if (fs.existsSync(getConfigFileName())) { config = JSON.parse(fs.readFileSync(getConfigFileName(), 'utf8')); if (!config.states) config.states = {type: 'file'}; if (!config.objects) config.objects = {type: 'file'}; } else { throw 'Cannot find ' + getConfigFileName(); } /** * Adapter class * * @class * @param {string|object} options object like {name: "adapterName", systemConfig: true} or just "adapterName" * @return {object} object instance */ function Adapter(options) { if (!(this instanceof Adapter)) return new Adapter(options); if (!options || (!config && !options.config)) throw 'Configuration not set!'; if (options.config && !options.config.log) options.config.log = config.log; config = options.config || config; var regUser = /^system\.user\./; var regGroup = /^system\.group\./; that = this; that.logList = []; // possible arguments // 0,1,.. - instance // info, debug, warn, error - log level // --force // --logs // --silent // --install if (process.argv) { for (var a = 1; a < process.argv.length; a++) { if (process.argv[a] === 'info' || process.argv[a] === 'debug' || process.argv[a] === 'error' || process.argv[a] === 'warn' || process.argv[a] === 'silly') { config.log.level = process.argv[a]; } else if (process.argv[a] === '--silent') { config.isInstall = true; process.argv[a] = '--install'; } else if (process.argv[a] === '--install') { config.isInstall = true; } else if (process.argv[a] === '--logs') { config.consoleOutput = true; } else if (process.argv[a] === '--force') { config.forceIfDisabled = true; } else if (parseInt(process.argv[a], 10).toString() === process.argv[a]) { config.instance = parseInt(process.argv[a], 10); } } } config.log.level = config.log.level || 'info'; if (config.log.noStdout && process.argv && process.argv.indexOf('--console') !== -1) { config.log.noStdout = false; } var logger = require(__dirname + '/logger.js')(config.log); // compatibility if (!logger.silly) { logger.silly = logger.debug; } // enable "var adapter = require(__dirname + '/../../lib/adapter.js')('adapterName');" call if (typeof options === 'string') options = {name: options}; if (!options) throw 'Empty options!'; if (!options.name) throw 'No name of adapter!'; // If installed as npm module if (options.dirname) { this.adapterDir = options.dirname.replace(/\\/g, '/'); } else { this.adapterDir = __dirname.replace(/\\/g, '/').split('/'); // it can be .../node_modules/appName.js-controller/node_modules/appName.adapter // .../appName.js-controller/node_modules/appName.adapter // .../appName.js-controller/adapter/adapter // remove "lib" this.adapterDir.pop(); var jsc = this.adapterDir.pop(); if ((jsc === tools.appName + '.js-controller' || jsc === tools.appName.toLowerCase() + '.js-controller') && this.adapterDir.pop() === 'node_modules') { // js-controller is installed as npm var appName = tools.appName.toLowerCase(); this.adapterDir = this.adapterDir.join('/'); if (fs.existsSync(this.adapterDir + '/node_modules/' + appName + '.' + options.name)) { this.adapterDir += '/node_modules/' + appName + '.' + options.name; } else if (fs.existsSync(this.adapterDir + '/node_modules/' + appName + '.js-controller/node_modules/' + appName + '.' + options.name)) { this.adapterDir += '/node_modules/' + appName + '.js-controller/node_modules/' + appName + '.' + options.name; } else if (fs.existsSync(this.adapterDir + '/node_modules/' + appName + '.js-controller/adapter/' + options.name)) { this.adapterDir += '/node_modules/' + appName + '.js-controller/adapter/' + options.name; } else if (fs.existsSync(this.adapterDir + '/node_modules/' + tools.appName + '.js-controller/node_modules/' + appName + '.' + options.name)) { this.adapterDir += '/node_modules/' + tools.appName + '.js-controller/node_modules/' + appName + '.' + options.name; } else { logger.error('Cannot find directory of adapter ' + options.name); process.exit(10); } } else { this.adapterDir = __dirname.replace(/\\/g, '/'); // remove "/lib" this.adapterDir = this.adapterDir.substring(0, this.adapterDir.length - 4); if (fs.existsSync(this.adapterDir + '/node_modules/' + tools.appName + '.' + options.name)) { this.adapterDir += '/node_modules/' + tools.appName + '.' + options.name; } else if (fs.existsSync(this.adapterDir + '/../node_modules/' + tools.appName + '.' + options.name)) { var parts = this.adapterDir.split('/'); parts.pop(); this.adapterDir = parts.join('/') + '/node_modules/' + tools.appName + '.' + options.name; } else { logger.error('Cannot find directory of adapter ' + options.name); process.exit(10); } } } if (fs.existsSync(this.adapterDir + '/package.json')) { this.pack = JSON.parse(fs.readFileSync(this.adapterDir + '/package.json', 'utf8')); } else { logger.info('Non npm module. No package.json'); } if (!this.pack || !this.pack.io) { if (fs.existsSync(this.adapterDir + '/io-package.json')) { this.ioPack = JSON.parse(fs.readFileSync(this.adapterDir + '/io-package.json', 'utf8')); } else { logger.error('Cannot find: ' + this.adapterDir + '/io-package.json'); process.exit(10); } } else { this.ioPack = this.pack.io; } // If required system configuration. Store it in systemConfig attribute if (options.systemConfig) that.systemConfig = config; var States; if (config.states && config.states.type) { if (config.states.type === 'file') { States = require(__dirname + '/states/statesInMemClient'); } else if (config.states.type === 'redis') { States = require(__dirname + '/states/statesInRedis'); } else { throw 'Unknown objects type: ' + config.states.type; } } else { States = require(__dirname + '/states'); } var Objects; if (config.objects && config.objects.type) { if (config.objects.type === 'file') { Objects = require(__dirname + '/objects/objectsInMemClient'); } else if (config.objects.type === 'redis') { Objects = require(__dirname + '/objects/objectsInRedis'); } else if (config.objects.type === 'couch') { Objects= require(__dirname + '/objects/objectsInCouch'); } else { throw 'Unknown objects type: ' + config.objects.type; } } else { Objects = require(__dirname + '/objects'); } var os = require('os'); var ifaces = os.networkInterfaces(); var ipArr = []; for (var dev in ifaces) { if (!ifaces.hasOwnProperty(dev)) continue; /*jshint loopfunc:true */ ifaces[dev].forEach(function (details) { if (!details.internal) ipArr.push(details.address); }); } var instance = (options.instance !== undefined) ? options.instance : (config.instance || 0); that.name = options.name; that.namespace = options.name + '.' + instance; that.users = []; // cache of user groups that.defaultHistory = null; that.autoSubscribe = null; // array of instances, that support auto subscribe that.inputCount = 0; that.outputCount = 0; var reportInterval; var callbackId = 1; that.getPortRunning = null; /** * Helper function to find next free port * * Looks for first free TCP port starting with given one: *

     *     adapter.getPort(8081, function (port) {
     *         adapter.log.debug('Followinf port is free: ' + port);
     *     });
     * 
* * @alias getPort * @memberof Adapter * @param {number} port port number to start the search for free port * @param {function} callback return result *
function (port) {}
*/ that.getPort = function adapterGetPort(port, callback) { if (!port) throw 'adapterGetPort: no port'; port = parseInt(port, 10); that.getPortRunning = {port: port, callback: callback}; var server = net.createServer(); try { server.listen(port, function (/* err */) { server.once('close', function () { if (typeof callback === 'function') { //that.getPortRunning = null; callback(port); } }); server.close(); }); server.on('error', function (/* err */) { setTimeout(function () { that.getPort(port + 1, callback); }, 100); }); } catch (e) { setImmediate(function () { that.getPort(port + 1, callback); }); } }; /** * Promise-version of Adapter.getPort */ that.getPortAsync = tools.promisifyNoError(that.getPort, that); /** * validates user and password * * * @alias checkPassword * @memberof Adapter * @param {string} user user name as text * @param {string} pw password as text * @param {object} options optional user context * @param {function} callback return result *

     *            function (result) {
     *              adapter.log.debug('User is valid');
     *            }
     *        
*/ that.checkPassword = function checkPassword(user, pw, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (!callback) throw 'checkPassword: no callback'; if (user && !regUser.test(user)) { user = 'system.user.' + user; } that.getForeignObject(user, options, function (err, obj) { if (err || !obj || !obj.common || (!obj.common.enabled && user !== 'system.user.admin')) { callback(false); return; } password(pw).check(obj.common.password, function (err, res) { callback(res); }); }); }; /** * Promise-version of Adapter.checkPassword */ that.checkPasswordAsync = tools.promisifyNoError(that.checkPassword, that); /** * sets the user's password * * @alias setPassword * @memberof Adapter * @param {string} user user name as text * @param {string} pw password as text * @param {object} options optional user context * @param {function} callback return result *

     *            function (err) {
     *              if (err) adapter.log.error('Cannot set password: ' + err);
     *            }
     *        
*/ that.setPassword = function setPassword(user, pw, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (user && !regUser.test(user)) { user = 'system.user.' + user; } that.getForeignObject(user, options, function (err, obj) { if (err || !obj) { if (typeof callback === 'function') callback('User does not exist'); return; } password(pw).hash(null, null, function (err, res) { if (err) { if (typeof callback === 'function') callback(err); return; } that.extendForeignObject(user, { common: { password: res } }, options, function () { if (typeof callback === 'function') callback(null); }); }); }); }; /** * Promise-version of Adapter.setPassword */ that.setPasswordAsync = tools.promisify(that.setPassword, that); /** * returns if user exists and is in the group * * This function used mostly internally and the adapter developer do not require it. * * @alias checkGroup * @memberof Adapter * @param {string} user user name as text * @param {string} group group name * @param {object} options optional user context * @param {function} callback return result *

     *            function (result) {
     *              if (result) adapter.log.debug('User exists and in the group');
     *            }
     *        
*/ that.checkGroup = function checkGroup(user, group, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (user && !regUser.test(user)) { user = 'system.user.' + user; } if (group && !regGroup.test(group)) { group = 'system.group.' + group; } that.getForeignObject(user, options, function (err, obj) { if (err || !obj) { callback(false); return; } that.getForeignObject(group, options, function (err, obj) { if (err || !obj) { callback(false); return; } if (obj.common.members.indexOf(user) !== -1) { callback(true); } else { callback(false); } }); }); }; /** * Promise-version of Adapter.checkGroup */ that.checkGroupAsync = tools.promisifyNoError(that.checkGroup, that); /** @typedef {{[permission: string]: {type: 'object' | 'state' | '' | 'other' | 'file', operation: string}}} CommandsPermissions */ /** * get the user permissions * * This function used mostly internally and the adapter developer do not require it. * The function reads permissions of user's groups (it can be more than one) and merge permissions together * * @alias calculatePermissions * @memberof Adapter * @param {string} user user name as text * @param {object} commandsPermissions object that describes the access rights like *

     *         // static information
     *         var commandsPermissions = {
     *            getObject:          {type: 'object',    operation: 'read'},
     *            getObjects:         {type: 'object',    operation: 'list'},
     *            getObjectView:      {type: 'object',    operation: 'list'},
     *            setObject:          {type: 'object',    operation: 'write'},
     *            subscribeObjects:   {type: 'object',    operation: 'read'},
     *            unsubscribeObjects: {type: 'object',    operation: 'read'},
     *
     *            getStates:          {type: 'state',     operation: 'list'},
     *            getState:           {type: 'state',     operation: 'read'},
     *            setState:           {type: 'state',     operation: 'write'},
     *            getStateHistory:    {type: 'state',     operation: 'read'},
     *            subscribe:          {type: 'state',     operation: 'read'},
     *            unsubscribe:        {type: 'state',     operation: 'read'},
     *            getVersion:         {type: '',          operation: ''},
     *
     *            httpGet:            {type: 'other',     operation: 'http'},
     *            sendTo:             {type: 'other',     operation: 'sendto'},
     *            sendToHost:         {type: 'other',     operation: 'sendto'},
     *
     *            readFile:           {type: 'file',      operation: 'read'},
     *            readFile64:         {type: 'file',      operation: 'read'},
     *            writeFile:          {type: 'file',      operation: 'write'},
     *            writeFile64:        {type: 'file',      operation: 'write'},
     *            unlink:             {type: 'file',      operation: 'delete'},
     *            rename:             {type: 'file',      operation: 'write'},
     *            mkdir:              {type: 'file',      operation: 'write'},
     *            readDir:            {type: 'file',      operation: 'list'},
     *            chmodFile:          {type: 'file',      operation: 'write'},
     *
     *            authEnabled:        {type: '',          operation: ''},
     *            disconnect:         {type: '',          operation: ''},
     *            listPermissions:    {type: '',          operation: ''},
     *            getUserPermissions: {type: 'object',    operation: 'read'}
     *         };
     *        
* @param {object} options optional user context * @param {function} callback return result *

     *            function (acl) {
     *              // Access control object for admin looks like:
     *              // {
     *              //    file: {
     *              //         read:       true,
     *              //         write:      true,
     *              //         'delete':   true,
     *              //         create:     true,
     *              //         list:       true
     *              //     },
     *              //     object: {
     *              //         read:       true,
     *              //         write:      true,
     *              //         'delete':   true,
     *              //         list:       true
     *              //     },
     *              //     state: {
     *              //         read:       true,
     *              //         write:      true,
     *              //         'delete':   true,
     *              //         create:     true,
     *              //         list:       true
     *              //     },
     *              //     user: 'admin',
     *              //     users:  {
     *              //         read:       true,
     *              //         write:      true,
     *              //         create:     true,
     *              //         'delete':   true,
     *              //         list:       true
     *              //     },
     *              //     other: {
     *              //         execute:    true,
     *              //         http:       true,
     *              //         sendto:     true
     *              //     },
     *              //     groups: ['administrator'] // can be more than one
     *              // }
     *            }
     *        
*/ that.calculatePermissions = function (user, commandsPermissions, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (!regUser.test(user)) { user = 'system.user.' + user; } // read all groups var acl = {user: user}; if (user === 'system.user.admin') { acl.groups = ['system.group.administrator']; for (var c in commandsPermissions) { if (!commandsPermissions.hasOwnProperty(c) || !commandsPermissions[c].type) continue; acl[commandsPermissions[c].type] = acl[commandsPermissions[c].type] || {}; acl[commandsPermissions[c].type][commandsPermissions[c].operation] = true; } if (callback) callback(acl); return; } acl.groups = []; that.getForeignObjects('*', 'group', options, function (err, groups) { // aggregate all groups permissions, where this user is if (groups) { for (var g in groups) { if (!groups.hasOwnProperty(g)) continue; if (groups[g] && groups[g].common && groups[g].common.members && groups[g].common.members.indexOf(user) !== -1) { acl.groups.push(groups[g]._id); if (groups[g]._id === 'system.group.administrator') { acl = { file: { read: true, write: true, 'delete': true, create: true, list: true }, object: { read: true, write: true, 'delete': true, list: true }, state: { read: true, write: true, 'delete': true, create: true, list: true }, user: user, users: { read: true, write: true, create: true, 'delete': true, list: true }, other: { execute: true, http: true, sendto: true }, groups: acl.groups }; break; } var gAcl = groups[g].common.acl; try { for (var type in gAcl) { if (!gAcl.hasOwnProperty(type)) continue; // fix bug. Some version have user instead of users. if (type === 'user') { acl.users = acl.users || {}; } else { acl[type] = acl[type] || {}; } for (var op in gAcl[type]) { if (gAcl[type].hasOwnProperty(op)) { // fix error if (type === 'user') { acl.users[op] = acl.users[op] || gAcl.user[op]; } else { acl[type][op] = acl[type][op] || gAcl[type][op]; } } } } } catch (e) { that.log.error('Cannot set acl: ' + e); that.log.error('Cannot set acl: ' + JSON.stringify(gAcl)); that.log.error('Cannot set acl: ' + JSON.stringify(acl)); } } } } if (callback) callback(acl); }); }; /** * Promise-version of Adapter.calculatePermissions */ that.calculatePermissionsAsync = tools.promisifyNoError(that.calculatePermissions, that); function readFileCertificate(cert) { if (typeof cert === 'string') { try { if (cert.length < 1024 && fs.existsSync(cert)) { cert = fs.readFileSync(cert).toString(); // start watcher of this file fs.watch(cert, function (eventType, filename) { that.log.warn('New certificate "' + filename + '" detected. Restart adapter'); setTimeout(stop, 2000, false, true); }); } } catch (e) { // ignore } } return cert; } /** * returns SSL certificates by name * * This function returns SSL certificates (private key, public cert and chained certificate). * Names are defined in the system's configuration in admin, e.g. "defaultPrivate", "defaultPublic". * The result can be directly used for creation of https server. * * @alias getCertificates * @memberof Adapter * @param {string} publicName public certificate name * @param {string} privateName private certificate name * @param {string} chainedName optional chained certificate name * @param {function} callback return result *

     *            function (err, certs) {
     *              adapter.log.debug('private key: ' + certs.key);
     *              adapter.log.debug('public cert: ' + certs.cert);
     *              adapter.log.debug('chained cert: ' + certs.ca);
     *            }
     *        
*/ that.getCertificates = function (publicName, privateName, chainedName, callback) { if (typeof publicName === 'function') { callback = publicName; publicName = null; } if (typeof privateName === 'function') { callback = privateName; privateName = null; } if (typeof chainedName === 'function') { callback = chainedName; chainedName = null; } publicName = publicName || that.config.certPublic; privateName = privateName || that.config.certPrivate; chainedName = chainedName || that.config.certChained; // Load certificates that.getForeignObject('system.certificates', function (err, obj) { if (err || !obj || !obj.native.certificates || !publicName || !privateName || !obj.native.certificates[publicName] || !obj.native.certificates[privateName] || (chainedName && !obj.native.certificates[chainedName]) ) { that.log.error('Cannot enable secure web server, because no certificates found: ' + publicName + ', ' + privateName + ', ' + chainedName); if (callback) callback('Not found'); } else { var ca; if (chainedName) { var chained = readFileCertificate(obj.native.certificates[chainedName]).split('-----END CERTIFICATE-----\r\n'); ca = []; for (var c = 0; c < chained.length; c++) { if (chained[c].replace(/[\r\n|\r|\n]+/, '').trim()) { ca.push(chained[c] + '-----END CERTIFICATE-----\r\n'); } } } if (callback) { callback(null, { key: readFileCertificate(obj.native.certificates[privateName]), cert: readFileCertificate(obj.native.certificates[publicName]), ca: ca }, obj.native.letsEncrypt); } } }); }; /** * Promise-version of Adapter.getCertificates */ that.getCertificatesAsync = tools.promisify(that.getCertificates, that); // Can be later deleted if no more appears that.inited = false; initObjects(function () { if (that.inited) { if (that.log) that.log.warn('Reconnection to DB.'); return; } that.inited = true; // auto oObjects if (options.objects) { that.getAdapterObjects(function (objs) { that.oObjects = objs; that.subscribeObjects('*'); initStates(prepareInitAdapter); }); } else { initStates(prepareInitAdapter); } }); function createInstancesObjects(callback, objs) { if (!objs) { objs = that.ioPack.instanceObjects; } if (!objs || !objs.length) { callback(); } else { var obj = objs.shift(); that.getObject(obj._id, function (err, _obj) { if (!_obj) { if (obj.common) { if (obj.common.name) { obj.common.name = obj.common.name.replace('%INSTANCE%', instance); } if (obj.common.desc) { obj.common.desc = obj.common.desc.replace('%INSTANCE%', instance); } } that.setObject(obj._id, obj, function (err) { if (err && that.log) that.log.error('Cannot setObject: ' + err); setImmediate(createInstancesObjects, callback, objs); }); } else { setImmediate(createInstancesObjects, callback, objs); } }); } } function prepareInitAdapter() { that.getForeignState('system.adapter.' + that.namespace + '.alive', function (err, res) { if (options.instance !== undefined) { initAdapter(options); } else if (!config.isInstall && res && res.val === true) { logger.error(options.name + '.' + instance + ' already running'); process.exit(7); } else { that.getForeignObject('system.adapter.' + that.namespace, function (err, res) { if ((err || !res) && !config.isInstall) { logger.error(options.name + '.' + instance + ' invalid config'); process.exit(2); } else { createInstancesObjects(function () { initAdapter(res); }); } }); } }); } function autoSubscribeOn(cb) { if (!that.autoSubscribe) { // collect all that.objects.getObjectView('system', 'instance', {startkey: 'system.adapter.', endkey: 'system.adapter.\u9999'}, options, function (err, res) { if (res && res.rows) { that.autoSubscribe = []; for (var c = res.rows.length - 1; c >= 0; c--) { if (res.rows[c].value.common.subscribable) { var _id = res.rows[c].id.substring(15); if (that.autoSubscribe.indexOf(_id) === -1) that.autoSubscribe.push(_id); } } } if (typeof cb === 'function') cb(); }); // because of autoSubscribe that.objects.subscribe('system.adapter.*'); } else if (typeof cb === 'function') { cb(); } } function initObjects(cb) { that.objects = new Objects({ namespace: that.namespace, connection: config.objects, logger: logger, connected: function () { that.connected = true; // Read dateformat if using of formatDate is announced if (options.useFormatDate) { that.getForeignObject('system.config', function (err, data) { if (data && data.common) { that.dateFormat = data.common.dateFormat; that.isFloatComma = data.common.isFloatComma; } if (typeof cb === 'function') cb(); }); } else if (typeof cb === 'function') { cb(); } }, disconnected: function () { that.connected = false; }, change: function (id, obj) { if (obj === 'null') obj = null; if (!id) { logger.error(that.namespace + ' change ID is empty: ' + JSON.stringify(obj)); return; } // If desired, that adapter must be terminated if (id === 'system.adapter.' + that.namespace && obj && obj.common && obj.common.enabled === false) { that.log.info('Adapter is disabled => stop'); if (!obj.common.enabled) { stop(); setTimeout(function () { process.exit(); }, 4000); } } // update oObjects structure if desired if (that.oObjects) { if (obj) { that.oObjects[id] = obj; } else { delete that.oObjects[id]; } } // process autosubscribe adapters if (id.match(/^system\.adapter\./)) { if (obj && obj.common.subscribable) { var _id = id.substring(15); // 'system.adapter.'.length if (obj.common.enabled) { if (that.autoSubscribe.indexOf(_id) === -1) that.autoSubscribe.push(_id); } else { var pos = that.autoSubscribe.indexOf(_id); if (pos !== -1) that.autoSubscribe.splice(pos, 1); } } } // It was an error in the calculation if ((options.noNamespace || config.noNamespace) && that._namespaceRegExp.test(id)) { // emit 'objectChange' event instantly setImmediate(function () { if (typeof options.objectChange === 'function') options.objectChange(id.substring(that.namespace.length + 1), obj); that.emit('objectChange', id.substring(that.namespace.length + 1), obj); }); } else { setImmediate(function () { if (typeof options.objectChange === 'function') options.objectChange(id, obj); // emit 'objectChange' event instantly that.emit('objectChange', id, obj); }); } }, connectTimeout: function (/* err */) { if (config.isInstall) { if (logger) logger.warn(that.namespace + ' no connection to objects DB'); process.exit(0); } else { if (logger) logger.error(that.namespace + ' no connection to objects DB'); } } }); that._namespaceRegExp = new RegExp('^' + that.namespace); // chache the regex object 'adapter.0' that._fixId = function _fixId(id, isPattern/* , type */) { var result = ''; // If id is an object if (typeof id === 'object') { // Add namespace + device + channel result = that.namespace + '.' + (id.device ? id.device + '.' : '') + (id.channel ? id.channel + '.' : '') + (id.state ? id.state : ''); } else { result = id; if (!that._namespaceRegExp.test(id)) { if (!isPattern) { result = that.namespace + (id ? '.' + id : ''); } else { result = that.namespace + '.' + (id ? id : ''); } } } return result; }; /** * Creates or overwrites object in objectDB. * * This function can create or overwrite objects in objectDB for this adapter. * Only Ids that belong to this adapter can be modified. So the function automatically adds "adapter.X." to ID. * common, native and type attributes are mandatory and it will be checked. * Additionally type "state" requires role, type and name, e.g.: *
{
         *     common: {
         *          name: 'object name',
         *          type: 'number', // string, boolean, object, mixed, array
         *          role: 'value'   // see https://github.com/yunkong2/yunkong2/blob/master/doc/SCHEMA.md#state-commonrole
         *     },
         *     native: {},
         *     type: 'state' // channel, device
         * }
* * @alias setObject * @memberof Adapter * @param {string} id object ID, that must be overwritten or created. * @param {object} obj new object * @param {object} options optional user context * @param {function} callback return result *

         *            function (err, obj) {
         *              // obj is {id: id}
         *              if (err) adapter.log.error('Cannot write object: ' + err);
         *            }
         *        
*/ that.setObject = function setObject(id, obj, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (!defaultObjs) { defaultObjs = require(__dirname + '/defaultObjs.js')('de', '°C', 'EUR'); } if (!id) { logger.error(that.namespace + ' setObject id missing!!'); if (typeof callback === 'function') callback('id missing!'); return; } if (!obj) { logger.error(that.namespace + ' setObject ' + id + ' object missing!'); if (typeof callback === 'function') callback('object missing!'); return; } if (obj.hasOwnProperty('type')) { if (!obj.hasOwnProperty('native')) { logger.warn(that.namespace + ' setObject ' + id + ' (type=' + obj.type + ') property native missing!'); obj.native = {}; } // Check property 'common' if (!obj.hasOwnProperty('common')) { logger.warn(that.namespace + ' setObject ' + id + ' (type=' + obj.type + ') property common missing!'); obj.common = {}; } else if (obj.type === 'state') { // Try to extend the model for type='state' // Check property 'role' by 'state' if (obj.common.hasOwnProperty('role') && defaultObjs[obj.common.role]) { obj.common = extend(true, defaultObjs[obj.common.role], obj.common); } else if (!obj.common.hasOwnProperty('role')) { logger.warn(that.namespace + ' setObject ' + id + ' (type=' + obj.type + ') property common.role missing!'); } if (!obj.common.hasOwnProperty('type')) { logger.warn(that.namespace + ' setObject ' + id + ' (type=' + obj.type + ') property common.type missing!'); } } if (!obj.common.hasOwnProperty('name')) { obj.common.name = id; logger.debug(that.namespace + ' setObject ' + id + ' (type=' + obj.type + ') property common.name missing, using id as name'); } id = that._fixId(id, false, obj.type); if (obj.children || obj.parent) { logger.warn(that.namespace + ' Do not use parent or children for ' + id); } if (!obj.from) obj.from = 'system.adapter.' + that.namespace; if (!obj.ts) obj.ts = new Date().getTime(); that.objects.setObject(id, obj, options, callback); } else { logger.error(that.namespace + ' setObject ' + id + ' mandatory property type missing!'); if (typeof callback === 'function') callback('mandatory property type missing!'); } }; /** * Promise-version of Adapter.setObject */ that.setObjectAsync = tools.promisify(that.setObject, that); /** * Get all states, channels and devices of this adapter. * * @alias getAdapterObjects * @memberof Adapter * @param {function} callback return result *

         *            function (objects) {
         *                for (var id in objects) {
         *                    adapter.log.debug(id);
         *                }
         *            }
         *        
*/ that.getAdapterObjects = function (callback) { var objects = {}; that.objects.getObjectView('system', 'state', {startkey: that.namespace + '.', endkey: that.namespace + '.\u9999', include_docs: true}, function (err, _states) { that.objects.getObjectView('system', 'channel', {startkey: that.namespace + '.', endkey: that.namespace + '.\u9999', include_docs: true}, function (err, _channels) { that.objects.getObjectView('system', 'device', {startkey: that.namespace + '.', endkey: that.namespace + '.\u9999', include_docs: true}, function (err, _devices) { if (_channels) { for (var c = _channels.rows.length - 1; c >= 0; c--) { objects[_channels.rows[c].id] = _channels.rows[c].value; } } if (_devices) { for (var d = _devices.rows.length - 1; d >= 0; d--) { objects[_devices.rows[d].id] = _devices.rows[d].value; } } if (_states) { if (options.states) that.oStates = {}; for (var s = _states.rows.length - 1; s >= 0; s--) { objects[_states.rows[s].id] = _states.rows[s].value; if (that.oStates) { that.oStates[_states.rows[s].id] = null; } } } if (typeof callback === 'function') callback(objects); }); }); }); }; /** * Promise-version of Adapter.getAdapterObjects */ that.getAdapterObjectsAsync = tools.promisifyNoError(that.getAdapterObjects, that); /** * Extend some object and create it if it does not exist * * You can change or extend some object. E.g existing object is: *

         *     {
         *          common: {
         *              name: 'Adapter name',
         *              desc: 'Description'
         *          },
         *          type: 'state',
         *          native: {
         *              unused: 'text'
         *          }
         *     }
         * 
* * If following object will be passed as argument * *

         *     {
         *          common: {
         *              desc: 'New description',
         *              min: 0,
         *              max: 100
         *          },
         *          native: {
         *              unused: null
         *          }
         *     }
         * 
* * We will get as output: *

         *     {
         *          common: {
         *              desc: 'New description',
         *              min: 0,
         *              max: 100
         *          },
         *          type: 'state',
         *          native: {
         *          }
         *     }
         * 
* * * @alias extendObject * @memberof Adapter * @param {string} id object ID, that must be extended * @param {object} obj part that must be extended * @param {object} options optional user context * @param {function} callback return result *

         *            function (err, obj) {
         *                if (err) adapter.log.error(err);
         *                // obj is {"id": id}
         *            }
         *        
*/ that.extendObject = function extendObject(id, obj, options, callback) { if (typeof options === 'function') { callback = options; options = null; } id = that._fixId(id, false, obj.type); if (obj.children || obj.parent) { logger.warn(that.namespace + ' Do not use parent or children for ' + id); } // delete arrays if they should be changed if (obj && ( (obj.common && obj.common.members) || (obj.native && obj.native.repositories) || (obj.native && obj.native.certificates) || (obj.native && obj.native.devices)) ) { // Read whole object that.objects.getObject(id, options, function (err, oldObj) { if (err) { if (typeof callback === 'function') callback(err); return; } if (!oldObj) { logger.error(that.namespace + ' Object ' + id + ' not exist!'); oldObj = {}; } if (obj.native && obj.native.repositories && oldObj.native && oldObj.native.repositories) { oldObj.native.repositories = []; } if (obj.common && obj.common.members && oldObj.common && oldObj.common.members) { oldObj.common.members = []; } if (obj.native && obj.native.certificates && oldObj.native && oldObj.native.certificates) { oldObj.native.certificates = []; } if (obj.native && obj.native.devices && oldObj.native && oldObj.native.devices) { oldObj.native.devices = []; } obj = extend(true, oldObj, obj); if (!obj.from) obj.from = 'system.adapter.' + that.namespace; if (!obj.ts) obj.ts = new Date().getTime(); that.objects.setObject(id, obj, options, callback); }); } else { if (!obj.from) obj.from = 'system.adapter.' + that.namespace; if (!obj.ts) obj.ts = new Date().getTime(); that.objects.extendObject(id, obj, options, callback); } }; /** * Promise-version of Adapter.extendObject */ that.extendObjectAsync = tools.promisify(that.extendObject, that); /** * Same as {@link Adapter.setObject}, but for any object. * * ID must be specified as a full name with adapter namespace. E.g "hm-rpc.0.ABC98989.1.STATE" * * @alias setForeignObject * @memberof Adapter * @param {string} id object ID, that must be overwritten or created. * @param {object} obj new object * @param {object} options optional user context * @param {function} callback return result *

         *            function (err, obj) {
         *              // obj is {id: id}
         *              if (err) adapter.log.error('Cannot write object: ' + err);
         *            }
         *        
*/ that.setForeignObject = function setForeignObject(id, obj, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (!obj.from) obj.from = 'system.adapter.' + that.namespace; if (!obj.ts) obj.ts = new Date().getTime(); that.objects.setObject(id, obj, options, callback); }; /** * Promise-version of Adapter.setForeignObject */ that.setForeignObjectAsync = tools.promisify(that.setForeignObject, that); /** * Same as {@link Adapter.extendObject}, but for any object. * * ID must be specified as a full name with adapter namespace. E.g "hm-rpc.0.ABC98989.1.STATE" * * @alias extendForeignObject * @memberof Adapter * @param {string} id object ID, that must be extended * @param {object} obj part that must be extended * @param {object} options optional user context * @param {function} callback return result *

         *            function (err, obj) {
         *                // obj is {"id": id}
         *                if (err) adapter.log.error(err);
         *            }
         *        
*/ that.extendForeignObject = function extendForeignObject(id, obj, options, callback) { if (typeof options === 'function') { callback = options; options = null; } // delete arrays if they should be changed if (obj && ((obj.native && (obj.native.repositories || obj.native.certificates || obj.native.devices)) || (obj.common && obj.common.members))) { // Read whole object that.objects.getObject(id, options, function (err, oldObj) { if (err) { if (typeof callback === 'function') callback(err); return; } if (!oldObj) { logger.error(that.namespace + ' Object ' + id + ' not exist!'); oldObj = {}; } if (obj.native && obj.native.repositories && oldObj.native && oldObj.native.repositories) { oldObj.native.repositories = []; } if (obj.common && obj.common.members && oldObj.common && oldObj.common.members) { oldObj.common.members = []; } if (obj.native && obj.native.certificates && oldObj.native && oldObj.native.certificates) { oldObj.native.certificates = []; } if (obj.native && obj.native.devices && oldObj.native && oldObj.native.devices) { oldObj.native.devices = []; } obj = extend(true, oldObj, obj); if (!obj.from) obj.from = 'system.adapter.' + that.namespace; if (!obj.ts) obj.ts = new Date().getTime(); that.objects.setObject(id, obj, callback); }); } else { if (!obj.from) obj.from = 'system.adapter.' + that.namespace; if (!obj.ts) obj.ts = new Date().getTime(); that.objects.extendObject(id, obj, options, callback); } }; /** * Promise-version of Adapter.extendForeignObject */ that.extendForeignObjectAsync = tools.promisify(that.extendForeignObject, that); /** * Get object of this instance. * * It is not required, that ID consists namespace. E.g. to get object of "adapterName.X.myObject", only "myObject" is required as ID. * * @alias getObject * @memberof Adapter * @param {string} id exactly object ID (without namespace) * @param {object} options optional user context * @param {function} callback return result *

         *            function (err, obj) {
         *              if (err) adapter.log.error('Cannot get object: ' + err);
         *            }
         *        
*/ that.getObject = function getObject(id, options, callback) { if (typeof options === 'function') { callback = options; options = null; } that.objects.getObject(that._fixId(id), options, callback); }; /** * Promise-version of Adapter.getObject */ that.getObjectAsync = tools.promisify(that.getObject, that); /** * Get the enum tree. * * Get enums of specified tree or all enums if nothing specified as object with values. * If getEnum called with no enum specified, all enums will be returned: *

         *      adapter.getEnums(function (err, enums, requestEnum) {
         *        // All enums
         *        if (err) adapter.log.error('Cannot get object: ' + err);
         *        for (var e in enums) {
         *           adapter.log.debug('Enum "' + e + '" has following members: ' + enums[e].common.members.join(', '));
         *        }
         *      });
         * 
* * @alias getEnum * @memberof Adapter * @param {string} _enum enum name, e.g. 'rooms', 'function' or '' (all enums) * @param {object} options optional user context * @param {function} callback return result *

         *            function (err, enums, requestEnum) {
         *              // requestEnum is _enum
         *              if (err) adapter.log.error('Cannot get object: ' + err);
         *              for (var e in enums) {
         *                 adapter.log.debug('Enum "' + e + '" has following members: ' + enums[e].common.members.join(', '));
         *              }
         *            }
         *        
*/ that.getEnum = function getEnum(_enum, options, callback) { if (typeof _enum === 'function') { callback = _enum; options = null; _enum = ''; } if (typeof options === 'function') { callback = options; options = null; } if (!_enum.match('^enum.')) _enum = 'enum.' + _enum; var result = {}; that.objects.getObjectView('system', 'enum', {startkey: _enum + '.', endkey: _enum + '.\u9999'}, options, function (err, res) { if (err) { if (typeof callback === 'function') callback(err); return; } for (var t = 0; t < res.rows.length; t++) { result[res.rows[t].id] = res.rows[t].value; } if (typeof callback === 'function') callback(err, result, _enum); }); }; /** * Promise-version of Adapter.getEnum */ that.getEnumAsync = tools.promisify(that.getEnum, that, ["result", "requestEnum"]); /** * Read the members of given enums. * * Get enums of specified tree or all enums if nothing specified as object with values. * * @alias getEnums * @memberof Adapter * @param {string|array} _enumList enum name or names, e.g. ['rooms', 'function'] * @param {object} options optional user context * @param {function} callback return result *

         *            function (err, enums) {
         *              // requestEnum is _enum
         *              if (err) adapter.log.error('Cannot get object: ' + err);
         *              // Result is like
         *              // {
         *              //    "enum.rooms": {
         *              //       "enum.rooms.livingroom": {
         *              //           common: {
         *              //              members: ['ID1', 'ID2']
         *              //           }
         *              //       },
         *              //       "enum.rooms.sleepingroom": {
         *              //           common: {
         *              //              members: ['ID3', 'ID4']
         *              //           }
         *              //       }
         *              //    },
         *              //    "enum.functions": {
         *              //       "enum.rooms.light": {
         *              //           common: {
         *              //              members: ['ID1', 'ID6']
         *              //           }
         *              //       },
         *              //       "enum.rooms.weather": {
         *              //           common: {
         *              //              members: ['ID4', 'ID7']
         *              //           }
         *              //       }
         *              //    }
         *              // }
         *            }
         *        
*/ that.getEnums = function getEnums(_enumList, options, callback) { if (typeof _enumList === 'function') { callback = _enumList; _enumList = null; } if (typeof options === 'function') { callback = options; options = null; } var _enums = {}; if (_enumList) { if (typeof _enumList === 'string') _enumList = [_enumList]; var count = 0; for (var t = 0; t < _enumList.length; t++) { count++; that.getEnum(_enumList[t], options, function (err, list, _enum) { if (list) _enums[_enum] = list; if (!--count && callback) callback(err, _enums); }); } } else { // Read all enums that.objects.getObjectView('system', 'enum', {startkey: 'enum.', endkey: 'enum.\u9999'}, options, function (err, res) { if (err) { callback(err); return; } var result = {}; if (res && res.rows) { for (var i = 0; i < res.rows.length; i++) { var parts = res.rows[i].id.split('.', 3); if (!parts[2]) continue; if (!result[parts[0] + '.' + parts[1]]) result[parts[0] + '.' + parts[1]] = {}; result[parts[0] + '.' + parts[1]][res.rows[i].id] = res.rows[i].value; } } if (callback) callback(err, result); }); } }; /** * Promise-version of Adapter.getEnums */ that.getEnumsAsync = tools.promisify(that.getEnums, that); /** * Get objects by pattern, by specific type and resolve their enums. * * Get all objects in the system of specified type. E.g.: * *

         *            adapter.getForeignObjects('hm-rega.0.*', 'state', ['rooms', 'functions'], function (err, objs) {
         *              if (err) adapter.log.error('Cannot get object: ' + err);
         *              // objs look like:
         *              // {
         *              //    "hm-rega.0.ABC0000.1.STATE": {
         *              //        common: {...},
         *              //        native: {},
         *              //        type: 'state',
         *              //        enums: {
         *              //           'enums.rooms.livingroom': 'Living room',
         *              //           'enums.functions.light': 'Light'
         *              //       }
         *              //    },
         *              //    "hm-rega.0.ABC0000.2.STATE": {
         *              //        common: {...},
         *              //        native: {},
         *              //        type: 'state',
         *              //        enums: {
         *              //           'enums.rooms.sleepingroom': 'Sleeping room',
         *              //           'enums.functions.window': 'Windows'
         *              //       }
         *              //    }
         *            }
         *        
* * @alias getForeignObjects * @memberof Adapter * @param {string} pattern object ID/wildchars * @param {string} type type of object: 'state', 'channel' or 'device'. Default - 'state' * @param {string|string[]} enums object ID, that must be overwritten or created. * @param {object} options optional user context * @param {function} callback return result *

         *            function (err, obj) {
         *              if (err) adapter.log.error('Cannot get object: ' + err);
         *            }
         *        
*/ that.getForeignObjects = function getForeignObjects(pattern, type, enums, options, callback) { if (typeof options === 'function') { callback = options; options = null; } var params = {}; if (pattern && pattern !== '*') { params = { startkey: pattern.replace('*', ''), endkey: pattern.replace('*', '\u9999') }; } if (typeof enums === 'function') { callback = enums; enums = null; } if (typeof type === 'function') { callback = type; type = null; } if (typeof type === 'object') { options = type; type = null; } if (typeof enums === 'object' && !(enums instanceof Array)) { options = enums; enums = null; } that.objects.getObjectView('system', type || 'state', params, options, function (err, res) { if (err) { callback(err); return; } that.getEnums(enums, function (err, _enums) { var list = {}; for (var i = 0; i < res.rows.length; i++) { var id = res.rows[i].id; if (typeof id !== 'string') { that.log.debug('Invalid id returned from getEnums: ' + JSON.stringify(id)); continue; } list[id] = res.rows[i].value; if (_enums && id) { // get device or channel of this state and check it too var parts = id.split('.'); parts.splice(parts.length - 1, 1); var channel = parts.join('.'); parts.splice(parts.length - 1, 1); var device = parts.join('.'); list[id].enums = {}; for (var es in _enums) { if (!_enums.hasOwnProperty(es)) continue; for (var e in _enums[es]) { if (!_enums[es].hasOwnProperty(e)) continue; if (!_enums[es][e] || !_enums[es][e].common || !_enums[es][e].common.members) continue; if (_enums[es][e].common.members.indexOf(id) !== -1 || _enums[es][e].common.members.indexOf(channel) !== -1 || _enums[es][e].common.members.indexOf(device) !== -1) { list[id].enums[e] = _enums[es][e].common.name; } } } } } callback(null, list); }); }); }; /** * Promise-version of Adapter.getForeignObjects */ that.getForeignObjectsAsync = tools.promisify(that.getForeignObjects, that); /** * Find any object by name or ID. * * Find object by the exact name or ID. * * @alias findForeignObject * @memberof Adapter * @param {string} id exactly object ID (without namespace) * @param {string} type optional common.type of state: 'number', 'string', 'boolean', 'file', ... * @param {object} options optional user context * @param {function} callback return result *

         *            adapter.findForeignObject('Some name', function (err, id, name) {
         *              if (err) adapter.log.error('Cannot get object: ' + err);
         *              adapter.log.debug('ID of object with name "' + name + '" is "' + id + '"');
         *            }
         *        
*/ that.findForeignObject = function findForeignObject(id, type, options, callback) { if (typeof options === 'function') { callback = options; options = null; } that.objects.findObject(id, type, options, callback); }; /** * Promise-version of Adapter.findForeignObject */ that.findForeignObjectAsync = tools.promisify(that.findForeignObject, that, ["id", "name"]); /** * Get any object. * * ID must be specified with namespace. * * @alias getForeignObject * @memberof Adapter * @param {string} id exactly object ID (with namespace) * @param {object} options optional user context * @param {function} callback return result *

         *            function (err, obj) {
         *              if (err) adapter.log.error('Cannot get object: ' + err);
         *            }
         *        
*/ that.getForeignObject = function getForeignObject(id, options, callback) { if (typeof options === 'function') { callback = options; options = null; } that.objects.getObject(id, options, callback); }; /** * Promise-version of Adapter.getForeignObject */ that.getForeignObjectAsync = tools.promisify(that.getForeignObject, that); /** * Delete object of this instance. * * It is not required, that ID consists namespace. E.g. to get object of "adapterName.X.myObject", only "myObject" is required as ID. * * @alias delObject * @memberof Adapter * @param {string} id exactly object ID (without namespace) * @param {object} options optional user context * @param {function} callback return result *

         *            function (err) {
         *              if (err) adapter.log.error('Cannot delete object: ' + err);
         *            }
         *        
*/ that.delObject = function delObject(id, options, callback) { if (typeof options === 'function') { callback = options; options = null; } that.objects.delObject(that._fixId(id), options, callback); }; /** * Promise-version of Adapter.delObject */ that.delObjectAsync = tools.promisify(that.delObject, that); /** * Delete any object. * * ID must be specified with namespace. * * @alias delForeignObject * @memberof Adapter * @param {string} id exactly object ID (with namespace) * @param {object} options optional user context * @param {function} callback return result *

         *            function (err) {
         *              if (err) adapter.log.error('Cannot delete object: ' + err);
         *            }
         *        
*/ that.delForeignObject = function delForeignObject(id, options, callback) { if (typeof options === 'function') { callback = options; options = null; } that.objects.delObject(id, options, callback); }; /** * Promise-version of Adapter.delForeignObject */ that.delForeignObjectAsync = tools.promisify(that.delForeignObject, that); /** * Subscribe for the changes of objects in this instance. ** * @alias subscribeObjects * @memberof Adapter * @param {string} pattern pattern like 'channel.*' or '*' (all objects of this adapter) - without namespaces * @param {object} options optional user context */ that.subscribeObjects = function subscribeObjects(pattern, options) { if (pattern === '*') { that.objects.subscribe(that.namespace + '.*'); } else { pattern = that._fixId(pattern, true); that.objects.subscribe(pattern, options); } }; /** * Unsubscribe on the changes of objects in this instance. * * @alias unsubscribeObjects * @memberof Adapter * @param {string} pattern pattern like 'channel.*' or '*' (all objects) - without namespaces * @param {object} options optional user context */ that.unsubscribeObjects = function unsubscribeObjects(pattern, options) { if (pattern === '*') { that.objects.unsubscribe(that.namespace + '.*', options); } else { pattern = that._fixId(pattern, true); that.objects.unsubscribe(pattern); } }; /** * Subscribe for the changes of objects in any instance. * * @alias subscribeForeignObjects * @memberof Adapter * @param {string} pattern pattern like 'channel.*' or '*' (all objects) - without namespaces * @param {object} options optional user context */ that.subscribeForeignObjects = function subscribeObjects(pattern, options) { that.objects.subscribe(pattern, options); }; /** * Unsubscribe for the patterns on all objects. * * @alias unsubscribeForeignObjects * @memberof Adapter * @param {string} pattern pattern like 'channel.*' or '*' (all objects) - without namespaces * @param {object} options optional user context */ that.unsubscribeForeignObjects = function unsubscribeForeignObjects(pattern, options) { if (!pattern) pattern = '*'; that.objects.unsubscribe(pattern, options); }; that.setObjectNotExists = function setObjectNotExists(id, object, options, callback) { if (typeof options === 'function') { callback = options; options = null; } id = that._fixId(id); if (object.children || object.parent) { logger.warn(that.namespace + ' Do not use parent or children for ' + id); } that.objects.getObject(id, options, function (err, obj) { if (!obj) { if (!object.from) object.from = 'system.adapter.' + that.namespace; if (!object.ts) object.ts = new Date().getTime(); that.objects.setObject(id, object, callback); } else { if (typeof callback === 'function') callback(null); } }); }; /** * Promise-version of Adapter.setObjectNotExists */ that.setObjectNotExistsAsync = tools.promisify(that.setObjectNotExists, that); that.setForeignObjectNotExists = function setForeignObjectNotExists(id, obj, options, callback) { if (typeof options === 'function') { callback = options; options = null; } that.objects.getObject(id, options, function (err, _obj) { if (!_obj) { if (!obj.from) obj.from = 'system.adapter.' + that.namespace; if (!obj.ts) obj.ts = new Date().getTime(); that.objects.setObject(id, obj, callback); } else { if (typeof callback === 'function') callback(null); } }); }; /** * Promise-version of Adapter.setForeignObjectNotExists */ that.setForeignObjectNotExistsAsync = tools.promisify(that.setForeignObjectNotExists, that); that._DCS2ID = function (device, channel, stateOrPoint) { var id = ''; if (device) id += device; if (channel) id += ((id) ? '.' : '') + channel; if (stateOrPoint !== true && stateOrPoint !== false) { if (stateOrPoint) id += ((id) ? '.' : '') + stateOrPoint; } else if (stateOrPoint === true) { if (id) id += '.'; } return id; }; that.createDevice = function createDevice(deviceName, common, _native, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (!deviceName) { that.log.error('Try to create device with empty name!'); return; } if (typeof _native === 'function') { callback = _native; _native = {}; } if (typeof common === 'function') { callback = common; common = {}; } common = common || {}; common.name = common.name || deviceName; deviceName = deviceName.replace(/[.\s]+/g, '_'); _native = _native || {}; that.setObjectNotExists(deviceName, { type: 'device', common: common, native: _native }, options, callback); }; /** * Promise-version of Adapter.createDevice */ that.createDeviceAsync = tools.promisify(that.createDevice, that); // name of channel must be in format "channel" that.createChannel = function createChannel(parentDevice, channelName, roleOrCommon, _native, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (!channelName) throw 'Try to create channel without name!'; if (typeof _native === 'function') { callback = _native; _native = {}; } if (typeof roleOrCommon === 'function') { callback = roleOrCommon; roleOrCommon = undefined; } var common = {}; if (typeof roleOrCommon === 'string') { common = { role: roleOrCommon }; } else if (typeof roleOrCommon === 'object') { common = roleOrCommon; } common.name = common.name || channelName; if (parentDevice) parentDevice = parentDevice.replace(/[.\s]+/g, '_'); channelName = channelName.replace(/[.\s]+/g, '_'); channelName = that._DCS2ID(parentDevice, channelName); _native = _native || {}; var obj = { type: 'channel', common: common, native: _native }; that.setObjectNotExists(channelName, obj, options, callback); }; /** * Promise-version of Adapter.createChannel */ that.createChannelAsync = tools.promisify(that.createChannel, that); that.createState = function createState(parentDevice, parentChannel, stateName, roleOrCommon, _native, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (!stateName) throw 'Empty name is not allowed!'; if (typeof _native === 'function') { callback = _native; _native = {}; } if (typeof roleOrCommon === 'function') { callback = roleOrCommon; roleOrCommon = undefined; } var common = {}; if (typeof roleOrCommon === 'string') { common = { role: roleOrCommon }; } else if (typeof roleOrCommon === 'object') { common = roleOrCommon; } common.name = common.name || stateName; _native = _native || {}; common.read = (common.read === undefined) ? true : common.read; common.write = (common.write === undefined) ? false : common.write; if (!common.role) { logger.error(that.namespace + ' Try to create state ' + (parentDevice ? (parentDevice + '.') : '') + parentChannel + '.' + stateName + ' without role'); return; } if (parentDevice) parentDevice = parentDevice.replace(/[.\s]+/g, '_'); if (parentChannel) parentChannel = parentChannel.replace(/[.\s]+/g, '_'); stateName = stateName.replace(/[.\s]+/g, '_'); var id = that._fixId({device: parentDevice, channel: parentChannel, state: stateName}); // Check min, max and def values for number if (common.type !== undefined && common.type === 'number') { var min = 0; var max = 0; var def = 0; var err; if (common.min !== undefined) { min = common.min; if (typeof min !== 'number') { min = parseFloat(min); if (isNaN(min)) { err = 'Wrong type of ' + id + '.common.min'; logger.error(that.namespace + ' ' + err); if (callback) callback(err); return; } else { common.min = min; } } } if (common.max !== undefined) { max = common.max; if (typeof max !== 'number') { max = parseFloat(max); if (isNaN(max)) { err = 'Wrong type of ' + id + '.common.max'; logger.error(that.namespace + ' ' + err); if (callback) callback(err); return; } else { common.max = max; } } } if (common.def !== undefined) { def = common.def; if (typeof def !== 'number') { def = parseFloat(def); if (isNaN(def)) { err = 'Wrong type of ' + id + '.common.def'; logger.error(that.namespace + ' ' + err); if (callback) callback(err); return; } else { common.def = def; } } } if (common.min !== undefined && common.max !== undefined && min > max) { common.max = min; common.min = max; } if (common.def !== undefined && common.min !== undefined && def < min) common.def = min; if (common.def !== undefined && common.max !== undefined && def > max) common.def = max; } that.setObjectNotExists(id, { type: 'state', common: common, native: _native }, options, callback); if (common.def !== undefined) { that.setState(id, common.def, options); } else { that.setState(id, null, true, options); } }; /** * Promise-version of Adapter.createState */ that.createStateAsync = tools.promisify(that.createState, that); that.deleteDevice = function deleteDevice(deviceName, options, callback) { if (typeof options === 'function') { callback = options; options = null; } deviceName = deviceName.replace(/[.\s]+/g, '_'); if (!that._namespaceRegExp.test(deviceName)) deviceName = that.namespace + '.' + deviceName; that.objects.getObjectView('system', 'device', {startkey: deviceName, endkey: deviceName}, options, function (err, res) { if (err || !res || !res.rows) { if (typeof callback === 'function') callback(err); callback = null; return; } var cnt = 0; if (res.rows.length > 1) that.log.warn('Found more than one device ' + deviceName); for (var t = 0; t < res.rows.length; t++) { cnt++; that.delObject(res.rows[t].id, options, function (err) { if (err) { if (typeof callback === 'function') callback(err); callback = null; return; } if (!--cnt) { that.objects.getObjectView('system', 'channel', {startkey: deviceName + '.', endkey: deviceName + '.\u9999'}, options, function (err, res) { if (err) { if (typeof callback === 'function') callback(err); return; } var _cnt = 0; for (var k = 0; k < res.rows.length; k++) { _cnt++; that.deleteChannel(deviceName, res.rows[k].id, options, function (err) { if (!(--_cnt) && callback) { callback(err); } else { if (err) { if (typeof callback === 'function') callback(err); callback = null; } } }); } if (!_cnt && callback) callback(); }); } }); } if (!cnt && callback) callback(); }); }; /** * Promise-version of Adapter.deleteDevice */ that.deleteDeviceAsync = tools.promisify(that.deleteDevice, that); that.addChannelToEnum = function addChannelToEnum(enumName, addTo, parentDevice, channelName, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (parentDevice) { if (that._namespaceRegExp.test(parentDevice)) { parentDevice = parentDevice.substring(that.namespace.length + 1); } parentDevice = parentDevice.replace(/[.\s]+/g, '_'); } if (that._namespaceRegExp.test(channelName)) { channelName = channelName.substring(that.namespace.length + 1); } if (parentDevice && channelName.substring(0, parentDevice.length) === parentDevice) { channelName = channelName.substring(parentDevice.length + 1); } channelName = channelName.replace(/[.\s]+/g, '_'); var objId = that.namespace + '.' + that._DCS2ID(parentDevice, channelName); if (addTo.match(/^enum\./)) { that.objects.getObject(addTo, options, function (err, obj) { if (err) { if (typeof callback === 'function') callback(err); return; } if (!err && obj) { var pos = obj.common.members.indexOf(objId); if (pos === -1) { obj.common.members.push(objId); obj.from = 'system.adapter.' + that.namespace; obj.ts = new Date().getTime(); that.objects.setObject(obj._id, obj, options, function (err) { if (callback) callback(err); }); } } }); } else { if (enumName.match(/^enum\./)) enumName = enumName.substring(5); that.objects.getObject('enum.' + enumName + '.' + addTo, options, function (err, obj) { if (err) { if (typeof callback === 'function') callback(err); return; } if (obj) { var pos = obj.common.members.indexOf(objId); if (pos === -1) { obj.common.members.push(objId); obj.from = 'system.adapter.' + that.namespace; obj.ts = new Date().getTime(); that.objects.setObject(obj._id, obj, options, callback); } else { if (callback) callback(); } } else { // Create enum that.objects.setObject('enum.' + enumName + '.' + addTo, { common: { name: addTo, members: [objId] }, from: 'system.adapter.' + that.namespace, ts: new Date().getTime(), type: 'enum' }, options, callback); } }); } }; /** * Promise-version of Adapter.addChannelToEnum */ that.addChannelToEnumAsync = tools.promisify(that.addChannelToEnum, that); that.deleteChannelFromEnum = function deleteChannelFromEnum(enumName, parentDevice, channelName, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (parentDevice) { if (parentDevice.substring(0, that.namespace.length) === that.namespace) { parentDevice = parentDevice.substring(that.namespace.length + 1); } parentDevice = parentDevice.replace(/[.\s]+/g, '_'); } if (channelName && channelName.substring(0, that.namespace.length) === that.namespace) { channelName = channelName.substring(that.namespace.length + 1); } if (parentDevice && channelName && channelName.substring(0, parentDevice.length) === parentDevice) { channelName = channelName.substring(parentDevice.length + 1); } channelName = channelName || ''; channelName = channelName.replace(/[.\s]+/g, '_'); var objId = that.namespace + '.' + that._DCS2ID(parentDevice, channelName); if (enumName) { enumName = 'enum.' + enumName + '.'; } else { enumName = 'enum.'; } that.objects.getObjectView('system', 'enum', {startkey: enumName, endkey: enumName + '\u9999'}, options, function (err, res) { if (err) { if (typeof callback === 'function') callback(err); return; } if (res) { var count = 0; for (var i = 0; i < res.rows.length; i++) { count++; that.objects.getObject(res.rows[i].id, options, function (err, obj) { if (err) { if (typeof callback === 'function') callback(err); callback = null; return; } if (!err && obj && obj.common && obj.common.members) { var pos = obj.common.members.indexOf(objId); if (pos !== -1) { obj.common.members.splice(pos, 1); count++; obj.from = 'system.adapter.' + that.namespace; obj.ts = new Date().getTime(); that.objects.setObject(obj._id, obj, options, function (err) { if (!--count && callback) { callback(err); } else { if (err) { if (typeof callback === 'function') callback(err); callback = null; } } }); } } if (!--count && callback) callback(err); }); } } else if (callback) { callback (err); } }); }; /** * Promise-version of Adapter.deleteChannelFromEnum */ that.deleteChannelFromEnumAsync = tools.promisify(that.deleteChannelFromEnum, that); that.deleteChannel = function deleteChannel(parentDevice, channelName, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (typeof channelName === 'function') { callback = channelName; channelName = parentDevice; parentDevice = ''; } if (parentDevice && !channelName) { channelName = parentDevice; parentDevice = ''; } else if (parentDevice && typeof channelName === 'function') { callback = channelName; channelName = parentDevice; parentDevice = ''; } if (!parentDevice) parentDevice = ''; that.deleteChannelFromEnum('', parentDevice, channelName); var _parentDevice = parentDevice; var _channelName = channelName; if (parentDevice) { if (that._namespaceRegExp.test(parentDevice)) { parentDevice = parentDevice.substring(that.namespace.length + 1); } parentDevice = parentDevice.replace(/[.\s]+/g, '_'); } if (channelName && that._namespaceRegExp.test(channelName)) { channelName = channelName.substring(that.namespace.length + 1); } if (parentDevice && channelName && channelName.substring(0, parentDevice.length) === parentDevice) { channelName = channelName.substring(parentDevice.length + 1); } channelName = channelName || ''; channelName = channelName.replace(/[.\s]+/g, '_'); channelName = that.namespace + '.' + that._DCS2ID(parentDevice, channelName); logger.info(that.namespace + ' Delete channel ' + channelName); that.objects.getObjectView('system', 'channel', {startkey: channelName, endkey: channelName}, options, function (err, res) { if (err || !res || !res.rows) { if (typeof callback === 'function') callback(err); callback = null; return; } var cnt = 0; if (res.rows.length > 1) that.log.warn('Found more than one channel ' + channelName); for (var t = 0; t < res.rows.length; t++) { cnt++; that.delObject(res.rows[t].id, options, function (err) { if (err) { if (typeof callback === 'function') callback(err); callback = null; return; } if (!--cnt) { that.objects.getObjectView('system', 'state', {startkey: channelName + '.', endkey: channelName + '.\u9999'}, options, function (err, res) { if (err || !res || !res.rows) { if (typeof callback === 'function') callback(err); callback = null; return; } var _cnt = 0; for (var k = 0; k < res.rows.length; k++) { _cnt++; that.deleteState(_parentDevice, _channelName, res.rows[k].id, options, function (err) { if (!--_cnt && callback) { callback(err); } else { if (err) { if (typeof callback === 'function') callback(err); callback = null; } } }); } if (!_cnt && callback) callback(); }); } }); } if (!cnt && callback) callback(); }); }; /** * Promise-version of Adapter.deleteChannel */ that.deleteChannelAsync = tools.promisify(that.deleteChannel, that); that.deleteState = function deleteState(parentDevice, parentChannel, stateName, options, callback) { if (typeof parentChannel === 'function' && stateName === undefined) { stateName = parentDevice; callback = parentChannel; parentChannel = ''; parentDevice = ''; } else if (parentChannel === undefined && stateName === undefined) { stateName = parentDevice; parentDevice = ''; parentChannel = ''; } else { if (typeof options === 'function') { callback = options; options = null; } if (typeof stateName === 'function') { callback = stateName; stateName = parentChannel; parentChannel = parentDevice; parentDevice = ''; } if (typeof parentChannel === 'function') { callback = parentChannel; stateName = parentDevice; parentChannel = ''; parentDevice = ''; } if (typeof parentChannel === 'function') { callback = parentChannel; stateName = parentDevice; parentChannel = ''; parentDevice = ''; } } that.deleteStateFromEnum('', parentDevice, parentChannel, stateName, options); if (parentDevice) { if (that._namespaceRegExp.test(parentDevice)) { parentDevice = parentDevice.substring(that.namespace.length + 1); } parentDevice = parentDevice.replace(/[.\s]+/g, '_'); } if (parentChannel) { if (that._namespaceRegExp.test(parentChannel)) { parentChannel = parentChannel.substring(that.namespace.length + 1); } if (parentDevice && parentChannel.substring(0, parentDevice.length) === parentDevice) { parentChannel = parentChannel.substring(parentDevice.length + 1); } parentChannel = parentChannel.replace(/[.\s]+/g, '_'); } if (that._namespaceRegExp.test(stateName)) { stateName = stateName.substring(that.namespace.length + 1); } if (parentDevice && stateName.substring(0, parentDevice.length) === parentDevice) { stateName = stateName.substring(parentDevice.length + 1); } if (parentChannel && stateName.substring(0, parentChannel.length) === parentChannel) { stateName = stateName.substring(parentChannel.length + 1); } stateName = stateName || ''; stateName = stateName.replace(/[.\s]+/g, '_'); var _name = that._DCS2ID(parentDevice, parentChannel, stateName); that.delState(_name, options, function () { that.delObject(_name, options, callback); }); }; /** * Promise-version of Adapter.deleteState */ that.deleteStateAsync = tools.promisify(that.deleteState, that); that.getDevices = function getDevices(options, callback) { if (typeof options === 'function' && typeof callback === 'object') { var tmp = callback; callback = options; options = tmp; } if (typeof options === 'function') { callback = options; options = null; } that.objects.getObjectView('system', 'device', {startkey: that.namespace + '.', endkey: that.namespace + '.\u9999'}, options, function (err, obj) { if (callback) { if (obj.rows.length) { var res = []; for (var i = 0; i < obj.rows.length; i++) { res.push(obj.rows[i].value); } callback(null, res); } else { callback(err, []); } } }); }; /** * Promise-version of Adapter.getDevices */ that.getDevicesAsync = tools.promisify(that.getDevices, that); that.getChannelsOf = function getChannelsOf(parentDevice, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (typeof parentDevice === 'function') { callback = parentDevice; parentDevice = null; } if (!parentDevice) parentDevice = ''; if (parentDevice && that._namespaceRegExp.test(parentDevice)) { parentDevice = parentDevice.substring(that.namespace.length + 1); } parentDevice = parentDevice.replace(/[.\s]+/g, '_'); parentDevice = that.namespace + (parentDevice ? ('.' + parentDevice) : ''); that.objects.getObjectView('system', 'channel', {startkey: parentDevice + '.', endkey: parentDevice + '.\u9999'}, options, function (err, obj) { if (callback) { if (obj.rows.length) { var res = []; for (var i = 0; i < obj.rows.length; i++) { res.push(obj.rows[i].value); } callback(null, res); } else { callback(err, []); } } }); }; /** * Promise-version of Adapter.getChannelsOf */ that.getChannelsOfAsync = tools.promisify(that.getChannelsOf, that); that.getChannels = that.getChannelsOf; that.getStatesOf = function getStatesOf(parentDevice, parentChannel, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (typeof parentDevice === 'function') { callback = parentDevice; parentDevice = null; parentChannel = null; } if (typeof parentChannel === 'function') { callback = parentChannel; parentChannel = null; } if (!parentDevice) { parentDevice = ''; } else { if (that._namespaceRegExp.test(parentDevice)) { parentDevice = parentDevice.substring(that.namespace.length + 1); } parentDevice = parentDevice.replace(/[.\s]+/g, '_'); } if (!parentChannel) { parentChannel = ''; } else if (that._namespaceRegExp.test(parentChannel)) { parentChannel = parentChannel.substring(that.namespace.length + 1); } if (parentDevice && parentChannel && parentChannel.substring(0, parentDevice.length) === parentDevice) { parentChannel = parentChannel.substring(parentDevice.length + 1); } parentChannel = parentChannel.replace(/[.\s]+/g, '_'); var id = that.namespace + '.' + that._DCS2ID(parentDevice, parentChannel, true); that.objects.getObjectView('system', 'state', {startkey: id, endkey: id + '\u9999'}, options, function (err, obj) { if (callback) { var res = []; if (obj.rows.length) { var read = 0; for (var i = 0; i < obj.rows.length; i++) { read++; that.objects.getObject(obj.rows[i].id, function (err, subObj) { if (subObj) res.push(subObj); if (!--read) callback(null, res); }); } } else { callback(null, res); } } }); }; /** * Promise-version of Adapter.getStatesOf */ that.getStatesOfAsync = tools.promisify(that.getStatesOf, that); that.addStateToEnum = function addStateToEnum(enumName, addTo, parentDevice, parentChannel, stateName, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (parentDevice) { if (that._namespaceRegExp.test(parentDevice)) { parentDevice = parentDevice.substring(that.namespace.length + 1); } parentDevice = parentDevice.replace(/[.\s]+/g, '_'); } if (parentChannel) { if (that._namespaceRegExp.test(parentChannel)) { parentChannel = parentChannel.substring(that.namespace.length + 1); } if (parentDevice && parentChannel.substring(0, parentDevice.length) === parentDevice) { parentChannel = parentChannel.substring(parentDevice.length + 1); } parentChannel = parentChannel.replace(/[.\s]+/g, '_'); } if (that._namespaceRegExp.test(stateName)) { stateName = stateName.substring(that.namespace.length + 1); } if (parentDevice && stateName.substring(0, parentDevice.length) === parentDevice) { stateName = stateName.substring(parentDevice.length + 1); } if (parentChannel && stateName.substring(0, parentChannel.length) === parentChannel) { stateName = stateName.substring(parentChannel.length + 1); } stateName = stateName.replace(/[.\s]+/g, '_'); var objId = that._fixId({device: parentDevice, channel: parentChannel, state: stateName}); if (addTo.match(/^enum\./)) { that.objects.getObject(addTo, options, function (err, obj) { if (!err && obj) { var pos = obj.common.members.indexOf(objId); if (pos === -1) { obj.common.members.push(objId); obj.from = 'system.adapter.' + that.namespace; obj.ts = new Date().getTime(); that.objects.setObject(obj._id, obj, options, callback); } else if (callback) { callback(); } } else { if (callback) callback(err || 'object not found'); } }); } else { if (enumName.match(/^enum\./)) enumName = enumName.substring(5); that.objects.getObject('enum.' + enumName + '.' + addTo, options, function (err, obj) { if (!err && obj) { var pos = obj.common.members.indexOf(objId); if (pos === -1) { obj.common.members.push(objId); obj.from = 'system.adapter.' + that.namespace; obj.ts = new Date().getTime(); that.objects.setObject(obj._id, obj, callback); } else if (callback) { callback(); } } else { if (err) { if (typeof callback === 'function') callback(err); return; } // Create enum that.objects.setObject('enum.' + enumName + '.' + addTo, { common: { name: addTo, members: [objId] }, from: 'system.adapter.' + that.namespace, ts: new Date().getTime(), type: 'enum' }, options, callback); } }); } }; /** * Promise-version of Adapter.addStateToEnum */ that.addStateToEnumAsync = tools.promisify(that.addStateToEnum, that); that.deleteStateFromEnum = function deleteStateFromEnum(enumName, parentDevice, parentChannel, stateName, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (parentDevice) { if (that._namespaceRegExp.test(parentDevice)) { parentDevice = parentDevice.substring(that.namespace.length + 1); } parentDevice = parentDevice.replace(/[.\s]+/g, '_'); } if (parentChannel) { if (that._namespaceRegExp.test(parentChannel)) { parentChannel = parentChannel.substring(that.namespace.length + 1); } if (parentDevice && parentChannel.substring(0, parentDevice.length) === parentDevice) { parentChannel = parentChannel.substring(parentDevice.length + 1); } parentChannel = parentChannel.replace(/[.\s]+/g, '_'); } if (that._namespaceRegExp.test(stateName)) { stateName = stateName.substring(that.namespace.length + 1); } if (parentDevice && stateName.substring(0, parentDevice.length) === parentDevice) { stateName = stateName.substring(parentDevice.length + 1); } if (parentChannel && stateName.substring(0, parentChannel.length) === parentChannel) { stateName = stateName.substring(parentChannel.length + 1); } stateName = stateName.replace(/[.\s]+/g, '_'); var objId = that._fixId({device: parentDevice, channel: parentChannel, state: stateName}, false, 'state'); if (enumName) { enumName = 'enum.' + enumName + '.'; } else { enumName = 'enum.'; } that.objects.getObjectView('system', 'enum', {startkey: enumName, endkey: enumName + '\u9999'}, options, function (err, res) { if (!err && res) { var count = 0; for (var i = 0; i < res.rows.length; i++) { count++; that.objects.getObject(res.rows[i].id, options, function (err, obj) { if (err) { if (callback) { callback(err); callback = null; } return; } if (!err && obj && obj.common && obj.common.members) { var pos = obj.common.members.indexOf(objId); if (pos !== -1) { obj.common.members.splice(pos, 1); count++; obj.from = 'system.adapter.' + that.namespace; obj.ts = new Date().getTime(); that.objects.setObject(obj._id, obj, function (err) { if (!--count && callback) { callback(err); callback = null; } }); } } if (!--count && callback) { callback(err); callback = null; } }); } if (!count && callback) { callback(); callback = null; } } else if (callback) { callback(err); callback = null; } }); }; /** * Promise-version of Adapter.deleteStateFromEnum */ that.deleteStateFromEnumAsync = tools.promisify(that.deleteStateFromEnum, that); that.chmodFile = function chmodFile(_adapter, path, options, callback) { if (_adapter === null) _adapter = that.name; if (typeof options === 'function') { callback = options; options = null; } that.objects.chmodFile(_adapter, path, options, callback); }; /** * Promise-version of Adapter.chmodFile */ that.chmodFileAsync = tools.promisify(that.chmodFile, that, ["entries", "id"]); /** * Read directory from DB. * * This function reads the content of directory from DB for given adapter and path. * If getEnum called with no enum specified, all enums will be returned: *

         *      adapter.readDir('vis.0', '/main/', function (err, filesOrDirs) {
         *        // All enums
         *        if (err) adapter.log.error('Cannot read directory: ' + err);
         *        if (filesOrDirs) {
         *           for (var f = 0; f < filesOrDirs.length; f++) {
         *              adapter.log.debug('Directory main has following files and dirs: ' + filesOrDirs[f].file + '[dir - ' + filesOrDirs[f].isDir + ']');
         *           }
          *       }
         *      });
         * 
* * @alias readDir * @memberof Adapter * @param {string} _adapter adapter name. If adapter name is null, so the name (not instance) of current adapter will be taken. * @param {string} path path to direcory without adapter name. E.g. If you want to read "/vis.0/main/views.json", here must be "/main/views.json" and _adapter must be equal to "vis.0". * @param {object} options optional user context * @param {function} callback return result *

         *            function (err, filesOrDirs) {
         *                // filesOrDirs is array with elements like
         *                // {
         *                //      file:       'views.json,
         *                //      stats:      node.js stats object like https://nodejs.org/api/fs.html#fs_class_fs_stats ,
         *                //      isDir:      true/false,
         *                //      acl:        access control list object,
         *                //      modifiedAt: time when modified,
         *                //      createdAt:  time when created
         *                // }
         *            }
         *        
*/ that.readDir = function readDir(_adapter, path, options, callback) { if (_adapter === null) _adapter = that.name; if (typeof options === 'function') { callback = options; options = null; } that.objects.readDir(_adapter, path, options, callback); }; /** * Promise-version of Adapter.readDir */ that.readDirAsync = tools.promisify(that.readDir, that); that.unlink = function unlink(_adapter, name, options, callback) { if (_adapter === null) _adapter = that.name; if (typeof options === 'function') { callback = options; options = null; } that.objects.unlink(_adapter, name, options, callback); }; /** * Promise-version of Adapter.unlink */ that.unlinkAsync = tools.promisify(that.unlink, that); that.delFile = that.unlink; that.delFileAsync = that.unlinkAsync; that.rename = function rename(_adapter, oldName, newName, options, callback) { if (_adapter === null) _adapter = that.name; if (typeof options === 'function') { callback = options; options = null; } that.objects.rename(_adapter, oldName, newName, options, callback); }; /** * Promise-version of Adapter.rename */ that.renameAsync = tools.promisify(that.rename, that); that.mkdir = function mkdir(_adapter, dirname, options, callback) { if (_adapter === null) _adapter = that.name; if (typeof options === 'function') { callback = options; options = null; } that.objects.mkdir(_adapter, dirname, options, callback); }; /** * Promise-version of Adapter.mkdir */ that.mkdirAsync = tools.promisify(that.mkdir, that); /** * Read file from DB. * * This function reads the content of one file from DB for given adapter and file name. *

         *      adapter.readFile('vis.0', '/main/vis-views.json', function (err, data) {
         *        // All enums
         *        if (err) adapter.log.error('Cannot read file: ' + err);
         *        console.log('Content of file is: ' + data);
         *      });
         * 
* * @alias readFile * @memberof Adapter * @param {string} _adapter adapter name. If adapter name is null, so the name (not instance) of current adapter will be taken. * @param {string} filename path to file without adapter name. E.g. If you want to read "/vis.0/main/views.json", here must be "/main/views.json" and _adapter must be equal to "vis.0". * @param {object} options optional user context * @param {function} callback return result *

         *            function (err, data) {
         *                // data is utf8 or binary Buffer depends on the file extension.
         *            }
         *        
*/ that.readFile = function readFile(_adapter, filename, options, callback) { if (_adapter === null) _adapter = that.name; if (typeof options === 'function') { callback = options; options = null; } that.objects.readFile(_adapter, filename, options, callback); }; /** * Promise-version of Adapter.readFile */ that.readFileAsync = tools.promisify(that.readFile, that, ["file", "mimeType"]); /** * Write file to DB. * * This function writes the content of one file into DB for given adapter and file name. *

         *      adapter.writeFile('vis.0', '/main/vis-views.json', function (err, data) {
         *        // All enums
         *        if (err) adapter.log.error('Cannot read file: ' + err);
         *        console.log('Content of file is: ' + data);
         *      });
         * 
* * @alias readFile * @memberof Adapter * @param {string} _adapter adapter name. If adapter name is null, so the name (not instance) of current adapter will be taken. * @param {string} filename path to file without adapter name. E.g. If you want to read "/vis.0/main/views.json", here must be "/main/views.json" and _adapter must be equal to "vis.0". * @param {object} data data as UTF8 string or buffer depends on the file extension. * @param {object} options optional user context * @param {function} callback return result *

         *            function (err) {
         *
         *            }
         *        
*/ that.writeFile = function writeFile(_adapter, filename, data, options, callback) { if (_adapter === null) _adapter = that.name; if (typeof options === 'function') { callback = options; options = null; } that.objects.writeFile(_adapter, filename, data, options, callback); }; /** * Promise-version of Adapter.writeFile */ that.writeFileAsync = tools.promisify(that.writeFile, that); that.formatValue = function (value, decimals, _format) { if (typeof decimals !== 'number') { _format = decimals; decimals = 2; } var format = (!_format || _format.length !== 2) ? ((that.isFloatComma === undefined) ? '.,' : ((that.isFloatComma) ? '.,' : ',.')) : _format; if (typeof value !== 'number') value = parseFloat(value); return isNaN(value) ? '' : value.toFixed(decimals).replace(format[0], format[1]).replace(/\B(?=(\d{3})+(?!\d))/g, format[0]); }; that.formatDate = function formatDate(dateObj, isDuration, _format) { if ((typeof isDuration === 'string' && isDuration.toLowerCase() === 'duration') || isDuration === true) { isDuration = true; } if (typeof isDuration !== 'boolean') { _format = isDuration; isDuration = false; } if (!dateObj) return ''; var type = typeof dateObj; if (type === 'string') dateObj = new Date(dateObj); if (type !== 'object') { var j = parseInt(dateObj, 10); if (j == dateObj) { // may this is interval if (j < 946681200) { isDuration = true; dateObj = new Date(dateObj); } else { // if less 2000.01.01 00:00:00 dateObj = (j < 946681200000) ? new Date(j * 1000) : new Date(j); } } else { dateObj = new Date(dateObj); } } var format = _format || that.dateFormat || 'DD.MM.YYYY'; if (isDuration) dateObj.setMilliseconds(dateObj.getMilliseconds() + dateObj.getTimezoneOffset() * 60 * 1000); var validFormatChars = 'YJГMМDTДhSчmмsс'; var s = ''; var result = ''; function put(s) { /** @type {number | string} */ var v = ''; switch (s) { case 'YYYY': case 'JJJJ': case 'ГГГГ': case 'YY': case 'JJ': case 'ГГ': v = /** @type {Date} */(dateObj).getFullYear(); if (s.length === 2) v %= 100; if (v <= 9) v = '0' + v; break; case 'MM': case 'M': case 'ММ': case 'М': v = dateObj.getMonth() + 1; if ((v < 10) && (s.length === 2)) v = '0' + v; break; case 'DD': case 'TT': case 'D': case 'T': case 'ДД': case 'Д': v = dateObj.getDate(); if ((v < 10) && (s.length === 2)) v = '0' + v; break; case 'hh': case 'SS': case 'h': case 'S': case 'чч': case 'ч': v = dateObj.getHours(); if ((v < 10) && (s.length === 2)) v = '0' + v; break; case 'mm': case 'm': case 'мм': case 'м': v = dateObj.getMinutes(); if ((v < 10) && (s.length === 2)) v = '0' + v; break; case 'ss': case 's': case 'cc': case 'c': v = dateObj.getSeconds(); if ((v < 10) && (s.length === 2)) v = '0' + v; v = v.toString(); break; case 'sss': case 'ссс': v = dateObj.getMilliseconds(); if (v < 10) { v = '00' + v; } else if (v < 100) { v = '0' + v; } v = v.toString(); } return result += v; } for (var i = 0; i < format.length; i++) { if (validFormatChars.indexOf(format[i]) >= 0) s += format[i]; else { put(s); s = ''; result += format[i]; } } put(s); return result; }; } // TODO: clear somehow the cache by changing of user permissions function getUserGroups(options, callback) { if (that.users[options.user]) { options.groups = that.users[options.user]; return callback(options); } options.groups = []; that.getForeignObjects('*', 'group', function (err, groups) { // aggregate all groups permissions, where this user is if (groups) { for (var g in groups) { if (groups[g] && groups[g].common && groups[g].common.members && groups[g].common.members.indexOf(options.user) !== -1) { options.groups.push(groups[g]._id); } } } that.users[options.user] = options.groups; callback(options); }); } function checkStates(ids, options, command, callback) { if (!options.groups) { return getUserGroups(options, function () { checkStates(ids, options, command, callback); }); } if (ids instanceof Array) { var errors = []; var count = ids.length; if (count === 0) { callback(null, ids); return; } for (var i = 0; i < ids.length; i++) { checkStates(ids[i], options, command, function (err, obj) { if (err && obj) { errors.push(obj._id); } if (!--count) { if (errors.length) { for (var j = ids.length - 1; j >= 0; j--) { if (errors.indexOf(ids[j]) !== -1) { ids.splice(j, 1); } } } callback(null, ids); } }); } } else { var originalChecked = undefined; if (options.checked !== undefined) originalChecked = options.checked; options.checked = true; that.objects.getObject(ids, options, function (err, obj) { if (originalChecked !== undefined) { options.checked = originalChecked; } else { options.checked = undefined; } if (err) { callback(err, {_id: ids}); return; } else { var limitToOwnerRights = options.limitToOwnerRights === true; if (obj && obj.acl) { if (obj.acl.state === undefined) obj.acl.state = obj.acl.object; if (obj.acl.state !== undefined) { // If user is owner if (options.user === obj.acl.owner) { if (command === 'setState' || command === 'delState') { if (!(obj.acl.state & (2 << 8))/*write*/) { that.log.warn('Permission error for user "' + options.user + '": ' + command); callback('permissionError', {_id: ids}); return; } } else if (command === 'getState') { if (!(obj.acl.state & (4 << 8))/*read*/) { that.log.warn('Permission error for user "' + options.user + '": ' + command); callback('permissionError', {_id: ids}); return; } } else { that.log.warn('Called unknown command:' + command); } } else if (options.groups.indexOf(obj.acl.ownerGroup) !== -1 && !limitToOwnerRights) { if (command === 'setState' || command === 'delState') { if (!(obj.acl.state & (2 << 4))/*write*/) { that.log.warn('Permission error for user "' + options.user + '": ' + command); callback('permissionError', {_id: ids}); return; } } else if (command === 'getState') { if (!(obj.acl.state & (4 << 4))/*read*/) { that.log.warn('Permission error for user "' + options.user + '": ' + command); callback('permissionError', {_id: ids}); return; } } else { that.log.warn('Called unknown command:' + command); } } else if (!limitToOwnerRights) { if (command === 'setState' || command === 'delState') { if (!(obj.acl.state & 2)/*write*/) { that.log.warn('Permission error for user "' + options.user + '": ' + command); callback('permissionError', {_id: ids}); return; } } else if (command === 'getState') { if (!(obj.acl.state & 4)/*read*/) { that.log.warn('Permission error for user "' + options.user + '": ' + command); callback('permissionError', {_id: ids}); return; } } else { that.log.warn('Called unknown command:' + command); callback('permissionError', {_id: ids}); return; } } else { that.log.warn('Permissions limited to Owner rights'); callback('permissionError', {_id: ids}); return; } } else if (limitToOwnerRights) { that.log.warn('Permissions limited to Owner rights'); callback('permissionError', {_id: ids}); return; } } else if (limitToOwnerRights){ that.log.warn('Permissions limited to Owner rights'); callback('permissionError', {_id: ids}); return; } } callback(); }); } } // find out default history instance function getDefaultHistory(callback) { if (!that.defaultHistory) { // read default history instance from system.config return that.getForeignObject('system.config', function (err, data) { if (data && data.common) that.defaultHistory = data.common.defaultHistory; // if no default history set if (!that.defaultHistory) { // read all adapters that.objects.getObjectView('system', 'instance', {startkey: '', endkey: '\u9999'}, function (err, _obj) { if (_obj) { for (var i = 0; i < _obj.rows.length; i++) { if (_obj.rows[i].value.common && _obj.rows[i].value.common.type === 'storage') { that.defaultHistory = _obj.rows[i].id.substring('system.adapter.'.length); break; } } } if (!that.defaultHistory) that.defaultHistory = 'history.0'; if (callback) callback(); }); } else { if (callback) callback(); } }); } else { if (callback) callback(); } } function pattern2RegEx(pattern) { if (pattern !== '*') { if (pattern[0] === '*' && pattern[pattern.length - 1] !== '*') pattern += '$'; if (pattern[0] !== '*' && pattern[pattern.length - 1] === '*') pattern = '^' + pattern; } pattern = (pattern || '').toString().replace(/\./g, '\\.'); pattern = pattern.replace(/\*/g, '.*'); return pattern; } function _setStateChangedHelper (id, state, callback) { that.getForeignState(id, function (err, oldState) { if (err) { if (typeof callback === 'function') callback(err); } else { var differ = false; if (!oldState) { differ = true; } else if (state.val !== oldState.val) { differ = true; } else if (state.ack !== undefined && state.ack !== oldState.ack) { differ = true; } else if (state.q !== undefined && state.q !== oldState.q) { differ = true; } else if (state.ts !== undefined && state.ts !== oldState.ts) { differ = true; } else if (state.c !== undefined && state.c !== oldState.c) { differ = true; } else if (state.expire !== undefined && state.expire !== oldState.expire) { differ = true; } else if (state.from !== undefined && state.from !== oldState.from) { differ = true; } if (differ) { that.outputCount++; that.states.setState(id, state, function (/* err */) { if (typeof callback === 'function') callback(null, id, false); }); } else { if (typeof callback === 'function') callback(null, id, true); } } }); } // initStates is called from initAdapter function initStates(cb) { logger.debug(that.namespace + ' objectDB connected'); config.states.maxQueue = config.states.maxQueue || 1000; // Internal object, but some special adapters want to access it anyway. that.states = new States({ namespace: that.namespace, connection: config.states, connected: function () { logger.debug(that.namespace + ' statesDB connected'); if (options.subscribable) { that.states.subscribe('system.adapter.' + that.namespace + '.subscribes'); that.states.getState('system.adapter.' + that.namespace + '.subscribes', function (err, state) { if (!state || !state.val) { that.patterns = {}; } else { try { that.patterns = JSON.parse(state.val); for (var p in that.patterns) { that.patterns[p].regex = pattern2RegEx(p); } } catch (e) { that.patterns = {}; } } if (typeof cb === 'function') cb(); }); } else if (typeof cb === 'function') { cb(); } }, logger: logger, change: function (id, state) { that.inputCount++; if (state === 'null') state = null; if (!id || typeof id !== 'string') { console.log('Something is wrong! ' + JSON.stringify(id)); return; } // todo remove it as an error with log will be found if (id === 'system.adapter.' + that.namespace + '.checkLogging') { checkLogging(); return; } // someone subscribes or unsubscribes from adapter if (options.subscribable && id === 'system.adapter.' + that.namespace + '.subscribes') { var subs; try { subs = JSON.parse(state.val || '{}'); } catch (e) { subs = {}; } for (var p in subs) { subs[p].regex = pattern2RegEx(p); } that.patterns = subs; if (typeof options.subscribesChange === 'function') { options.subscribesChange(state); } else { that.emit('subscribesChange', state); } 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$/)) { var instance = id.substring(0, id.length - '.logging'.length); if (logger) logger.debug(that.namespace + ' ' + instance + ': logging ' + (state ? state.val : false)); that.logRedirect(state ? state.val : false, instance); } 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); var 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 var now = (new Date()).getTime(); for (var _id in that.callbacks) { if (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 (that.oStates) { if (!state) { delete that.oStates[id]; } else { that.oStates[id] = state; } } // It was an error in the calculation if ((options.noNamespace || config.noNamespace) && that._namespaceRegExp.test(id)) { if (typeof options.stateChange === 'function') { options.stateChange(id.substring(that.namespace.length + 1), state); } else { // emit 'stateChange' event instantly setImmediate(function () { that.emit('stateChange', id.slice(that.namespace.length + 1), state); }); } } else { if (typeof options.stateChange === 'function') { options.stateChange(id, state); } else { // emit 'stateChange' event instantly setImmediate(function () { that.emit('stateChange', id, state); }); } } } }, connectTimeout: function (error) { if (config.isInstall) { if (logger) logger.warn(that.namespace + ' no connection to states DB'); process.exit(0); } else { if (logger) logger.error(that.namespace + ' no connection to states DB: ' + (error || '')); } } }); /** * Send message to other adapter instance or all instances of adapter. * * This function sends a message to specific instance or all instances of some specific adapter. * If no instance given (e.g. "pushover"), the callback argument will be ignored. Because normally many responses will come. * * @alias sendTo * @memberof Adapter * @param {string} instanceName name of the instance where the message must be send to. E.g. "pushover.0" or "system.adapter.pushover.0". * @param {string} command command name, like "send", "browse", "list". Command is depend on target adapter implementation. * @param {object} message object that will be given as argument for request * @param {function} callback optional return result *

         *            function (result) {
         *              // result is target adapter specific and can vary from adapter to adapter
         *              if (!result) adapter.log.error('No response received');
         *            }
         *        
*/ that.sendTo = function sendTo(instanceName, command, message, callback) { if ((typeof message === 'function') && (typeof callback === 'undefined')) { callback = message; message = undefined; } if (typeof message === 'undefined') { message = command; command = 'send'; } var obj = {command: command, message: message, from: 'system.adapter.' + that.namespace}; if (!instanceName.match(/^system\.adapter\./)) instanceName = 'system.adapter.' + instanceName; if (typeof message !== 'object') { that.log.debug('sendTo "' + command + '" to ' + instanceName + ' from system.adapter.' + that.namespace + ': ' + message); } else { that.log.debug('sendTo "' + command + '" to ' + instanceName + ' from system.adapter.' + that.namespace); } // If not specific instance if (!instanceName.match(/\.[0-9]+$/)) { // Send to all instances of adapter that.objects.getObjectView('system', 'instance', {startkey: instanceName + '.', endkey: instanceName + '.\u9999'}, function (err, _obj) { if (_obj) { for (var 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: (new Date()).getTime() }; if (callbackId >= 0xFFFFFFFF) callbackId = 1; if (!that.callbacks) that.callbacks = {}; that.callbacks['_' + obj.callback.id] = {cb: callback}; // delete too old callbacks IDs var now = (new Date()).getTime(); for (var _id in that.callbacks) { if (now - that.callbacks[_id].time > 3600000) delete that.callbacks[_id]; } } else { obj.callback = callback; obj.callback.ack = true; } } that.states.pushMessage(instanceName, obj); } }; /** * Promise-version of Adapter.sendTo */ that.sendToAsync = tools.promisifyNoError(that.sendTo, that); /** * Send message to specific host or to all hosts. * * This function sends a message to specific host or all hosts. * If no host name given (e.g. null), the callback argument will be ignored. Because normally many responses will come. * * @alias sendToHost * @memberof Adapter * @param {string} hostName name of the host where the message must be send to. E.g. "myPC" or "system.host.myPC". If argument is empty, the message will be sent to all hosts. * @param {string} command command name. One of: "cmdExec", "getRepository", "getInstalled", "getVersion", "getDiagData", "getLocationOnDisk", "getDevList", "getLogs", "delLogs", "readDirAsZip", "writeDirAsZip", "readObjectsAsZip", "writeObjectsAsZip", "checkLogging". Commands can be checked in controller.js (function processMessage) * @param {object} message object that will be given as argument for request * @param {function} callback optional return result *

         *            function (result) {
         *              // result is target adapter specific and can vary from command to command
         *              if (!result) adapter.log.error('No response received');
         *            }
         *        
*/ that.sendToHost = function sendToHost(hostName, command, message, callback) { if (typeof message === 'undefined') { message = command; command = 'send'; } var obj = {command: command, message: message, from: 'system.adapter.' + that.namespace}; if (hostName && !hostName.match(/^system\.host\./)) hostName = 'system.host.' + hostName; if (!hostName) { // Send to all hosts that.objects.getObjectList({startkey: 'system.host.', endkey: 'system.host.' + '\u9999'}, null, function (err, res) { if (!err && res.rows.length) { for (var i = 0; i < res.rows.length; i++) { var 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: (new Date()).getTime() }; 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(hostName, obj); } }; /** * Promise-version of Adapter.sendToHost */ that.sendToHostAsync = tools.promisifyNoError(that.sendToHost, that); /** * Writes value into states DB. * * This function can write values into states DB for this adapter. * Only Ids that belong to this adapter can be modified. So the function automatically adds "adapter.X." to ID. * ack, options and callback are optional * * @alias setState * @memberof Adapter * @param {string} id object ID of the state. * @param {object|string|number|boolean} state simple value or object with attribues. * If state is object and ack exists too as function argument, function argument has priority. *

         *      {
         *          val:    value,
         *          ack:    true|false,       // default - false; is command(false) or status(true)
         *          ts:     timestampMS,      // default - now
         *          q:      qualityAsNumber,  // default - 0 (ok)
         *          from:   origin,           // default - this adapter
         *          c:      comment,          // default - empty
         *          expire: expireInSeconds   // default - 0
         *      }
         *  
* @param {boolean} ack optional is command(false) or status(true) * @param {object} options optional user context * @param {function} callback optional return error and id *

         *            function (err, id) {
         *              if (err) adapter.log.error('Cannot set value for "' + id + '": ' + err);
         *            }
         *        
*/ 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, false, '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', function (err) { if (err) { if (typeof callback === 'function') callback(err); } else { that.outputCount++; that.states.setState(id, state, callback); } }); } else { that.outputCount++; that.states.setState(id, state, callback); } }; /** * Promise-version of Adapter.setState */ that.setStateAsync = tools.promisify(that.setState, that); /** * Writes value into states DB only if the value really changed. * * This function can write values into states DB for this adapter. * Only Ids that belong to this adapter can be modified. So the function automatically adds "adapter.X." to ID. * ack, options and callback are optional * * @alias setStateChanged * @memberof Adapter * @param {string} id object ID of the state. * @param {object|string|number|boolean} state simple value or object with attribues. * @param {boolean} ack optional is command(false) or status(true) * @param {object} options optional user context * @param {function} callback optional return error and id *

         *            function (err, id, notChanged) {
         *              if (err) adapter.log.error('Cannot set value for "' + id + '": ' + err);
         *              if (!notChanged) adapter.log.debug('Value was chnaged');
         *            }
         *        
*/ that.setStateChanged = function setStateChanged(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, false, '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', function (err) { if (err) { if (typeof callback === 'function') callback(err); } else { _setStateChangedHelper(id, state, callback); } }); } else { _setStateChangedHelper(id, state, callback); } }; /** * Promise-version of Adapter.setStateChanged */ that.setStateChangedAsync = tools.promisify(that.setStateChanged, that, ["id", "notChanged"]); /** * Writes value into states DB for any instance. * * This function can write values into states DB for all instances and system states too. * ack, options and callback are optional * * @alias setForeignState * @memberof Adapter * @param {string} id object ID of the state. * @param {object|string|number|boolean} state simple value or object with attribues. * If state is object, so the ack will be ignored and must be included into object. *

         *      {
         *          val:    value,
         *          ack:    true|false,       // default - false; is command(false) or status(true)
         *          ts:     timestampMS,      // default - now
         *          q:      qualityAsNumber,  // default - 0 (ok)
         *          from:   origin,           // default - this adapter
         *          c:      comment,          // default - empty
         *          expire: expireInSeconds   // default - 0
         *      }
         *  
* @param {boolean} ack optional is command(false) or status(true) * @param {object} options optional user context * @param {function} callback optional return error and id *

         *            function (err, id) {
         *              if (err) adapter.log.error('Cannot set value for "' + id + '": ' + err);
         *            }
         *        
*/ 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', function (err) { if (err) { if (typeof callback === 'function') callback(err); } else { that.outputCount++; that.states.setState(id, state, callback); } }); } else { that.outputCount++; that.states.setState(id, state, callback); } }; /** * Promise-version of Adapter.setForeignState */ that.setForeignStateAsync = tools.promisify(that.setForeignState, that); /** * Writes value into states DB for any instance, but only if state changed. * * This function can write values into states DB for all instances and system states too. * ack, options and callback are optional * * @alias setForeignStateChanged * @memberof Adapter * @param {string} id object ID of the state. * @param {object|string|number|boolean} state simple value or object with attribues. * If state is object and ack exists too as function argument, function argument has priority. *

         *      {
         *          val:    value,
         *          ack:    true|false,       // default - false; is command(false) or status(true)
         *          ts:     timestampMS,      // default - now
         *          q:      qualityAsNumber,  // default - 0 (ok)
         *          from:   origin,           // default - this adapter
         *          c:      comment,          // default - empty
         *          expire: expireInSeconds   // default - 0
         *      }
         *  
* @param {boolean} ack optional is command(false) or status(true) * @param {object} options optional user context * @param {function} callback optional return error and id *

         *            function (err, id) {
         *              if (err) adapter.log.error('Cannot set value for "' + id + '": ' + err);
         *            }
         *        
*/ that.setForeignStateChanged = function setForeignStateChanged(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', function (err) { if (err) { if (typeof callback === 'function') callback(err); } else { _setStateChangedHelper(id, state, callback); } }); } else { _setStateChangedHelper(id, state, callback); } }; /** * Promise-version of Adapter.setForeignStateChanged */ that.setForeignStateChangedAsync = tools.promisify(that.setForeignStateChanged, that); /** * Read value from states DB. * * This function can read values from states DB for this adapter. * Only Ids that belong to this adapter can be read. So the function automatically adds "adapter.X." to ID. * * @alias getState * @memberof Adapter * @param {string} id object ID of the state. * @param {object} options optional user context * @param {function} callback return result *

         *            function (err, state) {
         *              if (err) adapter.log.error('Cannot read value: ' + err);
         *            }
         *        
* * See possible attributes of the state in @setState explanation */ that.getState = function getState(id, options, callback) { if (typeof options === 'function') { callback = options; options = {}; } id = that._fixId(id, false, 'state'); if (options && options.user && options.user !== 'system.user.admin') { checkStates(id, options, 'getState', function (err) { if (err) { if (typeof callback === 'function') callback(err); } else { if (that.oStates && that.oStates[id]) { if (typeof callback === 'function') callback(null, that.oStates[id]); } else { that.states.getState(id, callback); } } }); } else { if (that.oStates && that.oStates[id]) { if (typeof callback === 'function') callback(null, that.oStates[id]); } else { that.states.getState(id, callback); } } }; /** * Promise-version of Adapter.getState */ that.getStateAsync = tools.promisify(that.getState, that); /** * Read value from states DB for any instance and system state. * * This function can read values from states DB for all instances and adapters. It expects the full path of object ID. * * @alias getForeignState * @memberof Adapter * @param {string} id object ID of the state. * @param {object} options optional user context * @param {function} callback return result *

         *            function (err, state) {
         *              if (err) adapter.log.error('Cannot read value: ' + err);
         *            }
         *        
* * See possible attributes of the state in @setState explanation */ 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', function (err) { if (err) { if (typeof callback === 'function') callback(err); } else { if (that.oStates && that.oStates[id]) { if (typeof callback === 'function') callback(null, that.oStates[id]); } else { that.states.getState(id, callback); } } }); } else { if (that.oStates && that.oStates[id]) { if (typeof callback === 'function') callback(null, that.oStates[id]); } else { that.states.getState(id, callback); } } }; /** * Promise-version of Adapter.getForeignState */ that.getForeignStateAsync = tools.promisify(that.getForeignState, that); /** * Read historian data for states of any instance or system state. * * This function can read values from history adapters like: history, sql, influxdb. It expects the full path of object ID. * Normally only foreign history has interest, so there is no getHistory and getForeignHistory * * Possible options: * * - instance - (optional) name of instance, where to read the historian data, e.g. 'history.0', 'sql.1'. By default will be taken from system settings. * - start - (optional) time in ms - new Date().getTime()', by default is (now - 1 week) * - end - (optional) time in ms - new Date().getTime()', by default is (now + 5000 seconds) * - step - (optional) used in aggregate (m4, max, min, average, total) step in ms of intervals * - count - number of values if aggregate is 'onchange' or number of intervals if other aggregate method. Count will be ignored if step is set. * - from - if from field should be included in answer * - ack - if ack field should be included in answer * - q - if q field should be included in answer * - addId - if id field should be included in answer * - limit - do not return more entries than limit * - ignoreNull - if null values should be include (false), replaced by last not null value (true) or replaced with 0 (0) * - sessionId - (optional) identifier of request, will be returned back in the answer * - aggregate - aggregate method: * - minmax - used special algorithm. Splice the whole time range in small intervals and find for every interval max, min, start and end values. * - max - Splice the whole time range in small intervals and find for every interval max value and use it for this interval (nulls will be ignored). * - min - Same as max, but take minimal value. * - average - Same as max, but take average value. * - total - Same as max, but calculate total value. * - count - Same as max, but calculate number of values (nulls will be calculated). * - none - No aggregation at all. Only raw values in given period. * * @alias getHistory * @memberof Adapter * @param {string} id object ID of the state. * @param {object} options see function description * @param {function} callback return result *

         *            function (error, result, step, sessionId) {
         *              if (error) adapter.log.error('Cannot read value: ' + err);
         *            }
         *        
* * See possible attributes of the state in @setState explanation */ that.getHistory = function getHistory(id, options, callback) { options = options || {}; options.end = options.end || (new Date()).getTime() + 5000000; if (!options.count && !options.start) { options.start = options.start || (new Date()).getTime() - 604800000; // - 1 week } if (!options.instance) { if (!that.defaultHistory) { // read default history instance from system.config return getDefaultHistory(function () { that.getHistory(id, options, callback); }); } else { options.instance = that.defaultHistory; } } that.sendTo(options.instance || 'history.0', 'getHistory', {id: id, options: options}, function (res) { setImmediate(function () { callback(res.error, res.result, res.step, res.sessionId); }); }); }; /** * Promise-version of Adapter.getHistory */ that.getHistoryAsync = tools.promisify(that.getHistory, that, ["result", "step", "sessionId"]); /** * Convert ID into object with device's, channel's and state's name. * * Convert "adapter.instance.D.C.S" in object {device: D, channel: C, state: S} * Convert ID to {device: D, channel: C, state: S} * * @alias idToDCS * @memberof Adapter * @param {string} id short or long string of ID like "stateID" or "adapterName.0.stateID". * @return {object} parsed ID as an object */ that.idToDCS = function idToDCS(id) { if (!id) return null; var 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]}; }; /** * Delete one state of this adapter. * * Deletes the state. If State does not exist, no error will be returned. * Do not forget do delete the object for the state too (with delObject) *

         *     adapter.delState('stateID', function (err) {
         *         console.log('adapterName.0.stateID is deleted');
         *     });
         * 
* * @alias delState * @memberof Adapter * @param {string} id short or long string of ID like "stateID" or "adapterName.0.stateID". * @param {object} options optional argument to describe the user context * @param {function} callback return result function (err) {} */ that.delState = function delState(id, options, callback) { if (typeof options === 'function') { callback = options; options = {}; } id = that._fixId(id, false, 'state'); if (options && options.user && options.user !== 'system.user.admin') { checkStates(id, options, 'delState', function (err) { if (err) { if (typeof callback === 'function') callback(err); } else { that.states.delState(id, callback); } }); } else { that.states.delState(id, callback); } }; /** * Promise-version of Adapter.delState */ that.delStateAsync = tools.promisify(that.delState, that); /** * Delete one state of any adapter. * * Deletes the state. If State does not exist, no error will be returned. * Do not forget do delete the object for the state too (with delObject) *

         *     adapter.delState('adapterName.0.stateID', function (err) {
         *         console.log('adapterName.0.stateID is deleted');
         *     });
         * 
* * @alias delForeignState * @memberof Adapter * @param {string} id long string for ID like "adapterName.0.stateID". * @param {object} options optional argument to describe the user context * @param {function} callback return result function (err) {} */ 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', function (err) { if (err) { if (typeof callback === 'function') callback(err); } else { that.states.delState(id, callback); } }); } else { that.states.delState(id, callback); } }; /** * Promise-version of Adapter.delForeignState */ that.delForeignStateAsync = tools.promisify(that.delForeignState, that); /** * Read all states of this adapter, that pass the pattern * * Allows to read all states of current adapter according to pattern. To read all states of current adapter use: *

         *     adapter.getStates('*', function (err, states) {
         *         for (var id in states) {
         *              adapter.log.debug('"' + id + '" = "' + states[id].val);
         *         }
         *     });
         * 
* * @alias getStates * @memberof Adapter * @param {string} pattern string in form 'adapter.0.*' or like this. It can be array of IDs too. * @param {object} options optional argument to describe the user context * @param {function} callback return result function (err, states) {}, where states is an object like {"ID1": {"val": 1, "ack": true}, "ID2": {"val": 2, "ack": false}, ...} */ that.getStates = function getStates(pattern, options, callback) { if (typeof options === 'function') { callback = options; options = {}; } pattern = that._fixId(pattern, true, 'state'); that.getForeignStates(pattern, options, callback); }; /** * Promise-version of Adapter.getStates */ that.getStatesAsync = tools.promisify(that.getStates, that); /** * Read all states of all adapters (and system states), that pass the pattern * * Allows to read all states of current adapter according to pattern. To read all states of current adapter use: *

         *     adapter.getStates('*', function (err, states) {
         *         for (var id in states) {
         *              adapter.log.debug('"' + id + '" = "' + states[id].val);
         *         }
         *     });
         * 
* * @alias getForeignStates * @memberof Adapter * @param {string} pattern string in form 'adapter.0.*' or like this. It can be array of IDs too. * @param {object} options optional argument to describe the user context * @param {function} callback return result function (err, states) {}, where states is an object like {"ID1": {"val": 1, "ack": true}, "ID2": {"val": 2, "ack": false}, ...} */ that.getForeignStates = function getForeignStates(pattern, options, callback) { if (typeof options === 'function') { callback = options; options = {}; } var list = {}; if (typeof pattern === 'function') { callback = pattern; pattern = '*'; } if (typeof callback !== 'function') { logger.error(that.namespace + ' getForeignStates invalid callback for ' + pattern); return; } if (typeof pattern === 'object') { if (options && options.user && options.user !== 'system.user.admin') { checkStates(pattern, options, 'getState', function (err, keys) { if (err) { if (typeof callback === 'function') callback(err); return; } that.states.getStates(keys, function (err, arr) { if (err) { callback(err); return; } for (var i = 0; i < keys.length; i++) { if (typeof arr[i] === 'string') arr[i] = JSON.parse(arr[i]); list[keys[i]] = arr[i] || {}; } callback(null, list); }); }); } else { that.states.getStates(pattern, function (err, arr) { if (err) { callback(err); return; } for (var 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; } var keys = []; var params = {}; if (pattern && pattern !== '*') { params = { startkey: pattern.replace('*', ''), endkey: pattern.replace('*', '\u9999') }; } var originalChecked = undefined; if (options.checked !== undefined) originalChecked = options.checked; options.checked = true; that.objects.getObjectView('system', 'state', params, options, function (err, res) { if (originalChecked !== undefined) { options.checked = originalChecked; } else { options.checked = undefined; } if (err) { if (typeof callback === 'function') callback(err); return; } // filter out var regEx; // process patterns like "*.someValue". The patterns "someValue.*" will be processed by getObjectView if (pattern && pattern !== '*' && pattern[pattern.length - 1] !== '*') { regEx = new RegExp(pattern2RegEx(pattern)); } for (var i = 0; i < res.rows.length; i++) { if (!regEx || regEx.test(res.rows[i].id)) { keys.push(res.rows[i].id); } } if (options && options.user && options.user !== 'system.user.admin') { checkStates(keys, options, 'getState', function (err, keys) { if (err) { if (typeof callback === 'function') callback(err); return; } that.states.getStates(keys, function (err, arr) { if (err) { callback(err); return; } for (var i = 0; i < res.rows.length; i++) { if (typeof arr[i] === 'string') arr[i] = JSON.parse(arr[i]); list[keys[i]] = arr[i] || null; } if (typeof callback === 'function') callback(null, list); }); }); } else { that.states.getStates(keys, function (err, arr) { if (err) { callback(err); return; } for (var i = 0; i < res.rows.length; i++) { if (typeof arr[i] === 'string') { try { arr[i] = JSON.parse(arr[i]); } catch (e) { logger.error(that.namespace + ' Cannot parse state"' + keys[i] + ': ' + arr[i]); } } list[keys[i]] = arr[i] || null; } if (typeof callback === 'function') callback(null, list); }); } }); }; /** * Promise-version of Adapter.getForeignStates */ that.getForeignStatesAsync = tools.promisify(that.getForeignStates, that); /** * Subscribe for changes on all states of all adapters (and system states), that pass the pattern * * Allows to Subscribe on changes all states of all instances according to pattern. E.g. to read all states of 'adapterName.X' instance use: *

         *     adapter.subscribeForeignStates('adapterName.X.*');
         * 
* * @alias subscribeForeignStates * @memberof Adapter * @param {string} pattern string in form 'adapter.0.*' or like this. It can be array of IDs too. * @param {object} options optional argument to describe the user context * @param {function} callback return result function (err) {} */ that.subscribeForeignStates = function subscribeForeignStates(pattern, options, callback) { if (!pattern) pattern = '*'; if (typeof options === 'function') { callback = options; options = null; } // Todo check rights for options autoSubscribeOn(function () { // compare if this pattern for one of autosubscribe adapters for (var s = 0; s < that.autoSubscribe.length; s++) { if (pattern === '*' || pattern.substring(0, that.autoSubscribe[s].length + 1) === that.autoSubscribe[s] + '.') { // put this pattern into adapter list that.states.getState('system.adapter.' + that.autoSubscribe[s] + '.subscribes', function (err, state) { state = {}; state.val = state.val || '{}'; var subs; try { subs = JSON.parse(state.val); } catch (e) { that.log.error('Cannot parse subscribes for "' + that.autoSubscribe[s] + '.subscribes"'); } subs[pattern] = subs[pattern] || {}; subs[pattern][that.namespace] = subs[pattern][that.namespace] || 0; subs[pattern][that.namespace]++; that.outputCount++; that.states.setState('system.adapter.' + that.autoSubscribe[s] + '.subscribes', subs); }); } } that.states.subscribe(pattern, callback); }); }; /** * Promise-version of Adapter.subscribeForeignStates */ that.subscribeForeignStatesAsync = tools.promisify(that.subscribeForeignStates, that); /** * Unsubscribe for changes for given pattern * * This function allows to unsubsrcibe from changes. The pattern must be equal to requested one. * *

         *     adapter.subscribeForeignStates('adapterName.X.*');
         *     adapter.unsubscribeForeignStates('adapterName.X.abc*'); // This will not work
         *     adapter.unsubscribeForeignStates('adapterName.X.*'); // Valid unsubscribe
         * 
* * @alias unsubscribeForeignStates * @memberof Adapter * @param {string} pattern string in form 'adapter.0.*'. Must be the same as subscribe. * @param {object} options optional argument to describe the user context * @param {function} callback return result function (err) {} */ that.unsubscribeForeignStates = function unsubscribeForeignStates(pattern, options, callback) { if (!pattern) pattern = '*'; // Todo check rights for options if (typeof options === 'function') { callback = options; options = null; } if (that.autoSubscribe) { for (var s = 0; s < that.autoSubscribe.length; s++) { if (pattern === '*' || pattern.substring(0, that.autoSubscribe[s].length + 1) === that.autoSubscribe[s] + '.') { // remove this pattern from adapter list that.states.getState('system.adapter.' + that.autoSubscribe[s] + '.subscribes', function (err, state) { if (!state || !state.val) return; var subs; try { subs = JSON.parse(state.val); } catch (e) { that.log.error('Cannot parse subscribes for "' + that.autoSubscribe[s] + '.subscribes"'); return; } if (!subs[pattern]) return; if (subs[pattern][that.namespace] === undefined) return; subs[pattern][that.namespace]--; if (subs[pattern][that.namespace] <= 0) delete subs[pattern][that.namespace]; var found = false; // if any other subs are there for (var id in subs[pattern]) { if (subs[pattern].hasOwnProperty(id)) { found = true; break; } } if (!found) delete subs[pattern]; that.outputCount++; that.states.setState('system.adapter.' + that.autoSubscribe[s] + '.subscribes', subs); }); } } } that.states.unsubscribe(pattern, callback); }; /** * Promise-version of Adapter.unsubscribeForeignStates */ that.unsubscribeForeignStatesAsync = tools.promisify(that.unsubscribeForeignStates, that); /** * Subscribe for changes on all states of this instance, that pass the pattern * * Allows to Subscribe on changes all states of current adapter according to pattern. To read all states of current adapter use: *

         *     adapter.subscribeStates('*'); // subscribe for all states of this adapter
         * 
* * @alias subscribeStates * @memberof Adapter * @param {string} pattern string in form 'adapter.0.*' or like this. It can be array of IDs too. * @param {object} options optional argument to describe the user context * @param {function} callback return result function (err) {} */ that.subscribeStates = function subscribeStates(pattern, options, callback) { // Todo check rights for options if (typeof options === 'function') { callback = options; options = null; } // Exception. Threat the '*' case automatically if (!pattern || pattern === '*') { that.states.subscribe(that.namespace + '.*', callback); } else { pattern = that._fixId(pattern, true, 'state'); that.states.subscribe(pattern, callback); } }; /** * Promise-version of Adapter.subscribeStates */ that.subscribeStatesAsync = tools.promisify(that.subscribeStates, that); /** * Unsubscribe for changes for given pattern for own states. * * This function allows to unsubsrcibe from changes. The pattern must be equal to requested one. * *

         *     adapter.subscribeForeignStates('*');
         *     adapter.unsubscribeForeignStates('abc*'); // This will not work
         *     adapter.unsubscribeForeignStates('*');    // Valid unsubscribe
         * 
* * @alias unsubscribeStates * @memberof Adapter * @param {string} pattern string in form 'adapter.0.*'. Must be the same as subscribe. * @param {object} options optional argument to describe the user context * @param {function} callback return result function (err) {} */ that.unsubscribeStates = function unsubscribeStates(pattern, options, callback) { // Todo check rights for options if (typeof options === 'function') { callback = options; options = null; } if (!pattern || pattern === '*') { that.states.unsubscribe(that.namespace + '.*', callback); } else { pattern = that._fixId(pattern, true, 'state'); that.states.unsubscribe(pattern, callback); } }; /** * Promise-version of Adapter.unsubscribeStates */ that.unsubscribeStatesAsync = tools.promisify(that.unsubscribeStates, that); 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 = {}; } if (options && options.user && options.user !== 'system.user.admin') { checkStates(id, options, 'setState', function (err) { if (err) { if (typeof callback === 'function') callback(err); } else { that.states.setBinaryState(id, binary, callback); } }); } else { that.states.setBinaryState(id, binary, callback); } }; /** * Promise-version of Adapter.setBinaryState */ that.setBinaryStateAsync = tools.promisify(that.setBinaryState, that); // Read binary block from redis, e.g. image that.getBinaryState = function getBinaryState(id, options, callback) { if (typeof options === 'function') { callback = options; options = {}; } if (options && options.user && options.user !== 'system.user.admin') { checkStates(id, options, 'getState', function (err) { if (err) { if (typeof callback === 'function') callback(err); } else { that.states.getBinaryState(id, callback); } }); } else { that.states.getBinaryState(id, callback); } }; /** * Promise-version of Adapter.getBinaryState */ that.getBinaryStateAsync = tools.promisify(that.getBinaryState, that); } // read all logs prepared for this adapter at start function readLogs(callback) { // read all stored messages that.states.getLog('system.adapter.' + that.namespace, function (err, msg) { if (msg) { if (msg) that.emit('log', msg); setImmediate(function () { readLogs(callback); }); } else if (callback) { callback(); } }); } function printLog(logs, id, callback) { that.states.lenLog(id, function (err, len) { logs.push('Subscriber - ' + id + ' (queued ' + len + ') ' + (err || '')); if (callback) callback(); }); } // debug function to find error with stop logging function checkLogging() { var logs = []; var count = 0; // LogList logs.push('Actual Loglist - ' + JSON.stringify(that.logList)); // Read current state of all log subscribers that.states.getKeys('*.logging', function (err, keys) { if (keys && keys.length) { that.states.getStates(keys, function (err, obj) { if (obj) { for (var i = 0; i < keys.length; i++) { // We can JSON.parse, but index is 16x faster if (obj[i]) { var id = keys[i].substring(0, keys[i].length - '.logging'.length).replace(/^io\./, ''); if ((typeof obj[i] === 'string' && (obj[i].indexOf('"val":true') !== -1 || obj[i].indexOf('"val":"true"') !== -1)) || (typeof obj[i] === 'object' && (obj[i].val === true || obj[i].val === 'true'))) { count++; printLog(logs, id, function () { if (!--count) { for (var m = 0; m < logs.length; m++) { that.log.error('LOGINFO: ' + logs[m]); } logs = null; } }); } else { if (logs) { logs.push('Subscriber - ' + id + ' (disabled)'); } else { that.log.error('LOGINFO: Subscriber - ' + id + ' (disabled)'); } } } } } if (!count && logs) { for (var m = 0; m < logs.length; m++) { that.log.error('LOGINFO: ' + logs[m]); } logs = null; } }); } }); } function initLogging() { // temporary log buffer var messages = []; // Read current state of all log subscriber that.states.getKeys('*.logging', function (err, keys) { if (keys && keys.length) { that.states.getStates(keys, function (err, obj) { if (obj) { for (var i = 0; i < keys.length; i++) { // We can JSON.parse, but index is 16x faster if (!obj[i]) continue; var id = keys[i].substring(0, keys[i].length - '.logging'.length); if (typeof obj[i] === 'string' && (obj[i].indexOf('"val":true') !== -1 || obj[i].indexOf('"val":"true"') !== -1)) { that.logRedirect(true, id); } else if (typeof obj[i] === 'object' && (obj[i].val === true || obj[i].val === 'true')) { that.logRedirect(true, id); } } if (that.logList.length && messages && messages.length) { for (var m = 0; m < messages.length; m++) { for (var k = 0; k < that.logList.length; k++) { that.states.pushLog(that.logList[k], messages[m]); } } } } // clear log buffer messages = null; }); } else { // disable log buffer messages = null; } }); that.logRedirect = function (isActive, id) { // ignore itself if (id === 'system.adapter.' + that.namespace) return; if (isActive) { if (that.logList.indexOf(id) === -1) that.logList.push(id); } else { var pos = that.logList.indexOf(id); if (pos !== -1) that.logList.splice(pos, 1); } }; // If some message from logger logger.on('logging', function (transport, level, msg /* , meta */) { if (transport.name !== tools.appName) return; var message = {message: msg, severity: level, from: that.namespace, ts: (new Date()).getTime()}; if (options.logTransporter) { that.emit('log', message); } if (!that.logList.length) { // if log buffer still active if (messages && !options.logTransporter) { messages.push(message); // do not let messages to grow without limit if (messages.length > config.states.maxQueue) { messages.splice(0, messages.length - config.states.maxQueue); } } } else { // Send to all adapter, that required logs for (var i = 0; i < that.logList.length; i++) { that.states.pushLog(that.logList[i], message); } } }); options.logTransporter = options.logTransporter || that.ioPack.common.logTransporter; if (options.logTransporter) { that.requireLog = function (isActive) { if (that.states) { if (that.logRequired !== isActive) { that.logRequired = isActive; // remember state if (!isActive) { if (that.logOffTimer) { clearTimeout(that.logOffTimer); } // disable log receiving after 10 seconds that.logOffTimer = setTimeout(function () { that.logOffTimer = null; that.log.debug('Change log subscriber state: FALSE'); that.outputCount++; that.states.setState('system.adapter.' + that.namespace + '.logging', {val: false, ack: true, from: 'system.adapter.' + that.namespace}); }, 10000); } else { if (that.logOffTimer) { clearTimeout(that.logOffTimer); that.logOffTimer = null; } else { that.log.debug('Change log subscriber state: true'); that.outputCount++; that.states.setState('system.adapter.' + that.namespace + '.logging', {val: true, ack: true, from: 'system.adapter.' + that.namespace}); } } } } }; that.processLog = function (msg) { if (msg) that.emit('log', msg); that.states.delLog('system.adapter.' + that.namespace, msg._id); }; readLogs(); that.states.subscribeLog('system.adapter.' + that.namespace); } } function initAdapter(adapterConfig) { initLogging(); if (options.instance === undefined) { if (!adapterConfig || !adapterConfig.common || !adapterConfig.common.enabled) { if (adapterConfig && adapterConfig.common && adapterConfig.common.enabled !== undefined) { if (!config.isInstall) logger.error(that.namespace + ' adapter disabled'); } else { if (!config.isInstall) logger.error(that.namespace + ' no config found for adapter'); } if (!config.isInstall && (!process.argv || !config.forceIfDisabled)) { var id = 'system.adapter.' + that.namespace; that.outputCount += 2; that.states.setState(id + '.alive', {val: true, ack: true, expire: 30, from: id}); that.states.setState(id + '.connected', {val: true, ack: true, expire: 30, from: id}, function () { process.exit(3); }); setTimeout(function () { process.exit(3); }, 1000); return; } } if (!config.isInstall && !adapterConfig._id) { logger.error(that.namespace + ' invalid config: no _id found'); process.exit(4); return; } var name; var instance; if (!config.isInstall) { var tmp = adapterConfig._id.match(/^system\.adapter\.([a-zA-Z0-9-_]+)\.([0-9]+)$/); if (!tmp) { logger.error(that.namespace + ' invalid config'); process.exit(5); return; } name = tmp[1]; instance = parseInt(tmp[2]) || 0; } else { name = options.name; instance = 0; adapterConfig = adapterConfig || {common: {mode: 'once', name: name}, native: {}}; } for (var tp in logger.transports) { if (logger.transports.hasOwnProperty(tp)) { logger.transports[tp].level = adapterConfig.common.logLevel || 'info'; } } that.name = adapterConfig.common.name; that.instance = instance; that.namespace = name + '.' + instance; process.title = 'io.' + that.namespace; that.config = adapterConfig.native; that.host = adapterConfig.common.host; that.common = adapterConfig.common; if (adapterConfig.common.mode === 'subscribe' || adapterConfig.common.mode === 'schedule' || adapterConfig.common.mode === 'once') { that.stop = function () { stop(true); }; } // Monitor logging state that.states.subscribe('*.logging'); if (typeof options.message === 'function' && !adapterConfig.common.messagebox) { logger.error(that.namespace + ' : message handler implemented, but messagebox not enabled. Define common.messagebox in io-package.json for adapter or delete message handler.'); } else if (/*typeof options.message === 'function' && */adapterConfig.common.messagebox) { that.mboxSubscribed = true; that.states.subscribeMessage('system.adapter.' + that.namespace); } // set configured in DB log level if (adapterConfig.common.loglevel) { for (var trans in logger.transports) { if (logger.transports.hasOwnProperty(trans)) { logger.transports[trans].level = adapterConfig.common.loglevel; } } } } else { that.name = adapterConfig.name || options.name; that.instance = adapterConfig.instance || 0; that.namespace = that.name + '.' + that.instance; that.config = adapterConfig.native || {}; that.common = adapterConfig.common || {}; that.host = that.common.host || tools.getHostName() || require('os').hostname(); } var Log = function () {}; if (config.consoleOutput) { Log.prototype.silly = function (msg) { console.log(msg); logger.silly(that.namespace + ' ' + msg); }; Log.prototype.debug = function (msg) { console.log(msg); logger.debug(that.namespace + ' ' + msg); }; Log.prototype.info = function (msg) { console.log(msg); logger.info(that.namespace + ' ' + msg); }; Log.prototype.error = function (msg) { console.error(msg); logger.error(that.namespace + ' ' + msg); }; Log.prototype.warn = function (msg) { console.warn(msg); logger.warn(that.namespace + ' ' + msg); }; } else { Log.prototype.silly = function (msg) { logger.silly(that.namespace + ' ' + msg); }; Log.prototype.debug = function (msg) { logger.debug(that.namespace + ' ' + msg); }; Log.prototype.info = function (msg) { logger.info(that.namespace + ' ' + msg); }; Log.prototype.error = function (msg) { logger.error(that.namespace + ' ' + msg); }; Log.prototype.warn = function (msg) { logger.warn(that.namespace + ' ' + msg); }; } that.log = new Log(); that.log.level = config.log.level; if (options.instance === undefined) { that.version = (that.pack && that.pack.version) ? that.pack.version : ((that.ioPack && that.ioPack.common) ? that.ioPack.common.version : 'unknown'); that.log.info('starting. Version ' + that.version + ' in ' + that.adapterDir + ', node: ' + process.version); config.system = config.system || {}; config.system.statisticsInterval = parseInt(config.system.statisticsInterval, 10) || 15000; reportInterval = setInterval(reportStatus, config.system.statisticsInterval); reportStatus(); } if (adapterConfig && adapterConfig.common && adapterConfig.common.restartSchedule) { try { schedule = require('node-schedule'); } catch (e) { that.log.error('Cannot load node-schedule. Scheduled restart is disabled'); } if (schedule) { that.log.debug('Schedule restart: ' + adapterConfig.common.restartSchedule); schedule.scheduleJob(adapterConfig.common.restartSchedule, function () { that.log.info('Scheduled restart.'); stop(false, true); }); } } // auto oStates if (options.states) { that.getStates('*', function (err, _states) { that.oStates = _states; that.subscribeStates('*'); if (typeof options.ready === 'function') options.ready(); that.emit('ready'); }); } else { if (typeof options.ready === 'function') options.ready(); that.emit('ready'); // todo remove it later, when the error is fixed that.subscribeStates('checkLogging'); } } function reportStatus() { var id = 'system.adapter.' + that.namespace; that.outputCount += 7; that.states.setState(id + '.alive', {val: true, ack: true, expire: Math.floor(config.system.statisticsInterval / 1000) + 10, from: id}); if (that.connected) { that.states.setState(id + '.connected', {val: true, ack: true, expire: 30, from: id}); that.outputCount++; } //RSS is the resident set size, the portion of the process's memory held in RAM (as opposed to the swap space or the part held in the filesystem). var mem = process.memoryUsage(); that.states.setState(id + '.memRss', {val: parseFloat((mem.rss / 1048576/* 1MB */).toFixed(2)), ack: true, from: id}); that.states.setState(id + '.memHeapTotal', {val: parseFloat((mem.heapTotal / 1048576/* 1MB */).toFixed(2)), ack: true, from: id}); that.states.setState(id + '.memHeapUsed', {val: parseFloat((mem.heapUsed / 1048576/* 1MB */).toFixed(2)), ack: true, from: id}); // Under windows toFixed returns string ? that.states.setState(id + '.uptime', {val: parseInt(process.uptime().toFixed(), 10), ack: true, from: id}); that.states.setState(id + '.inputCount', {val: that.inputCount, ack: true, from: id}); that.states.setState(id + '.outputCount', {val: that.outputCount, ack: true, from: id}); that.inputCount = 0; that.outputCount = 0; } function stop(isPause, isScheduled) { clearInterval(reportInterval); var id = 'system.adapter.' + that.namespace; if (typeof options.unload === 'function') { options.unload(function () { if (that.states) { that.outputCount++; that.states.setState(id + '.alive', {val: false, ack: true, from: id}, function () { if (!isPause && that.log) that.log.info('terminating'); process.exit(isScheduled ? -100: 0); }); } }); } else { that.emit('unload', function () { if (that.states) { that.outputCount++; that.states.setState(id + '.alive', {val: false, ack: true, from: id}, function () { if (!isPause && that.log) that.log.info('terminating'); process.exit(isScheduled ? -100: 0); }); } }); // Make delay to let event 'unload' to be processed setTimeout(function () { if (that.states) { that.outputCount++; that.states.setState(id + '.alive', {val: false, ack: true, from: id}, function () { if (!isPause && that.log) that.log.info('terminating'); process.exit(isScheduled ? -100: 0); }); // Give 2 seconds to write the value setTimeout(function () { if (!isPause && that.log) that.log.info('terminating with timeout'); process.exit(isScheduled ? -100: 0); }, 1000); } else { if (!isPause && that.log) that.log.info('terminating'); process.exit(isScheduled ? -100: 0); } }, that.common ? that.common.stopTimeout || 500 : 500); } } process.once('SIGINT', stop); process.once('SIGTERM', stop); // And the exit event shuts down the child. process.once('exit', stop); process.on('uncaughtException', function (err) { console.error(err); // catch it on windows if (that.getPortRunning && err.message === 'listen EADDRINUSE') { logger.warn(that.namespace + ' Port ' + that.getPortRunning.port + ' is in use. Get next'); setImmediate(function () { that.getPort(that.getPortRunning.port + 1, that.getPortRunning.callback); }); return; } logger.error(that.namespace + ' uncaught exception: ' + (err.message || err)); if (err.stack) logger.error(that.namespace + ' ' + err.stack); try { stop(); setTimeout(function () { process.exit(6); }, 1000); } catch (err) { logger.error(that.namespace + ' exception by stop: ' + (err.message || err)); } }); return this; } // extend the EventEmitter class using our class util.inherits(Adapter, EventEmitter); module.exports = Adapter;