/** * * doorgate adapter * * * file io-package.json comments: * * { * "common": { * "name": "doorgate", // name has to be set and has to be equal to adapters folder name and main file name excluding extension * "version": "0.0.0", // use "Semantic Versioning"! see http://semver.org/ * "title": "Node.js doorgate Adapter", // Adapter title shown in User Interfaces * "authors": [ // Array of authord * "name " * ] * "desc": "doorgate adapter", // Adapter description shown in User Interfaces. Can be a language object {de:"...",ru:"..."} or a string * "platform": "Javascript/Node.js", // possible values "javascript", "javascript/Node.js" - more coming * "mode": "daemon", // possible values "daemon", "schedule", "subscribe" * "materialize": true, // support of admin3 * "schedule": "0 0 * * *" // cron-style schedule. Only needed if mode=schedule * "loglevel": "info" // Adapters Log Level * }, * "native": { // the native object is available via adapter.config in your adapters code - use it for configuration * "test1": true, * "test2": 42, * "mySelect": "auto" * } * } * */ /* jshint -W097 */// jshint strict:false /*jslint node: true */ 'use strict'; // you have to require the utils module and call adapter function const utils = require(__dirname + '/lib/utils'); // Get common adapter utils // load additional libraries var express = require('express'); // call express var request = null; // will be initialized later if polling enabled var LE = require(utils.controllerDir + '/lib/letsencrypt.js'); // REST server var webServer = null; var app = null; var router = null; var timer = null; // you have to call the adapter function and pass a options object // name has to be set and has to be equal to adapters folder name and main file name excluding extension // adapter will be restarted automatically every time as the configuration changed, e.g system.adapter.doorgate.0 const adapter = new utils.Adapter('doorgate'); /*Variable declaration, since ES6 there are let to declare variables. Let has a more clearer definition where it is available then var.The variable is available inside a block and it's childs, but not outside. You can define the same variable name inside a child without produce a conflict with the variable of the parent block.*/ let variable = 1234; let objects = {}; let enums = {}; // is called when adapter shuts down - callback has to be called under any circumstances! adapter.on('unload', function (callback) { try { adapter.log.info('cleaned everything up...'); callback(); } catch (e) { callback(); } }); // is called if a subscribed object changes adapter.on('objectChange', function (id, obj) { // Warning, obj can be null if it was deleted adapter.log.info('objectChange ' + id + ' ' + JSON.stringify(obj)); }); // is called if a subscribed state changes adapter.on('stateChange', function (id, state) { // Warning, state can be null if it was deleted adapter.log.info('stateChange ' + id + ' ' + JSON.stringify(state)); // you can use the ack flag to detect if it is status (true) or command (false) if (state && !state.ack) { adapter.log.info('ack is not set!'); } }); // Some message was sent to adapter instance over message box. Used by email, pushover, text2speech, ... adapter.on('message', function (obj) { if (typeof obj === 'object' && obj.message) { if (obj.command === 'send') { // e.g. send email or pushover or whatever console.log('send command'); // Send response in callback if required if (obj.callback) adapter.sendTo(obj.from, obj.command, 'Message received', obj.callback); } } }); // is called when databases are connected and adapter received configuration. // start here! adapter.on('ready', function () { main(); }); function addRoutes(_router) { // test route to make sure everything is working (accessed at GET http://localhost:9090/api) _router.get('/', function (req, res) { res.json({message: 'Welcome to our JSON REST api!'}); }); _router.get('/plain/', function(req, res) { res.send('Welcome to our text REST api!'); }); // on routes that end in /plain/:id // ---------------------------------------------------- _router.route('/plain/:id') // get the bear with that id (accessed at GET http://localhost:8080/api/plain/:id) .get(function (req, res) { adapter.getForeignState(req.params.id, {user: req.user}, function (err, state) { if (err) { res.status(500).send(err); } else if (!state) { res.status(500).send('not found'); } else { res.send('Value: ' + state.val); } }); })// create a handler for post (accessed at POST http://localhost:8080/api/bears) .post(function (req, res) { adapter.setForeignState(req.params.id, req.body, {user: req.user}, function (err, state) { if (err) { res.status(500).send(err); } else if (!state) { res.status(500).send('not found'); } else { res.send('Value used'); } }); }); // handler for get over JSON _router.route('/:id') // get the bear with that id (accessed at GET http://localhost:8080/api/plain/:id) .get(function (req, res) { adapter.getForeignState(req.params.id, {user: req.user}, function (err, state) { if (err) { res.status(500).send({error: err}); } else { res.json(state); } }); }); } function initWebServer(settings) { app = express(); router = express.Router(); // install authentication app.get('/', function (req, res) { if (settings.auth) { var b64auth = (req.headers.authorization || '').split(' ')[1] || ''; var loginPass = new Buffer(b64auth, 'base64').toString().split(':'); var login = loginPass[0]; var password = loginPass[1]; // Check in yunkong2 user and password adapter.checkPassword(login, password, function (result) { if (!result) { adapter.log.error('Wrong user or password: ' + login); res.set('WWW-Authenticate', 'Basic realm="nope"'); res.status(401).send('You shall not pass.'); } else { req.user = login; } }); } else { req.user = settings.defaultUser; } }); // add route cases addRoutes(router); // REGISTER OUR ROUTES ------------------------------- // all of our routes will be prefixed with /api app.use('/api', router); adapter.log.info('config port: ' + settings.port); if (settings.port) { if (settings.secure) { if (!adapter.config.certificates) { adapter.log.error('certificates missing'); return null; } } webServer = LE.createServer(app, adapter.config, adapter.config.certificates, adapter.config.leConfig, adapter.log); adapter.getPort(settings.port, function (port) { if (port != settings.port && !adapter.config.findNextPort) { adapter.log.error('port ' + settings.port + ' already in use'); process.exit(1); } webServer.listen(port, settings.bind, function() { adapter.log.info('Server listening on http' + (settings.secure ? 's' : '') + '://' + settings.bind + ':' + port); }); }); } else { adapter.log.error('port missing'); process.exit(1); } } function pollData() { // you can read about module "request" here: https://www.npmjs.com/package/request request = request || require('request'); // load library request(adapter.config.pollURL, function (error, response, body) { if (error || response.statusCode !== 200) { adapter.log.error(error || response.statusCode); } else { // try to parse answer try { var data = JSON.parse(body); // do something with data adapter.log.info(JSON.parse(data)); } catch (e) { adapter.log.error('Cannot parse answer'); } } }); } function addToEnum(enumName, id, callback) { adapter.getForeignObject(enumName, function (err, obj) { if (!err && obj) { let pos = obj.common.members.indexOf(id); if (pos === -1) { obj.common.members.push(id); adapter.setForeignObject(obj._id, obj, function (err) { if (callback) callback(err); }); } else { if (callback) callback(err); } } else { if (callback) callback(err); } }); } function removeFromEnum(enumName, id, callback) { adapter.getForeignObject(enumName, function (err, obj) { if (!err && obj) { let pos = obj.common.members.indexOf(id); if (pos !== -1) { obj.common.members.splice(pos, 1); adapter.setForeignObject(obj._id, obj, function (err) { if (callback) callback(err); }); } else { if (callback) callback(err); } } else { if (callback) callback(err); } }); } function syncEnums(enumGroup, id, newEnumName, callback) { if (!enums[enumGroup]) { adapter.getEnum(enumGroup, function (err, _enums) { enums[enumGroup] = _enums; syncEnums(enumGroup, id, newEnumName, callback); }); return; } // try to find this id in enums let found = false; let count = 0; for (let e in enums[enumGroup]) { if (enums[enumGroup].hasOwnProperty(e) && enums[enumGroup][e].common && enums[enumGroup][e].common.members && enums[enumGroup][e].common.members.indexOf(id) !== -1) { if (enums[enumGroup][e]._id !== newEnumName) { count++; removeFromEnum(enums[enumGroup][e]._id, id, function () { if (!--count && typeof callback === 'function') callback(); }); } else { found = true; } } } if (!found && newEnumName) { count++; addToEnum(newEnumName, id, function () { if (!--count&& typeof callback === 'function') callback(); }); } if (!count && typeof callback === 'function') callback(); } function createExtendObject(id, objData, callback) { adapter.getObject(id, function (err, oldObj) { if (!err && oldObj) { adapter.extendObject(id, objData, callback); } else { adapter.setObjectNotExists(id, objData, callback); } }); } function processTasks(tasks, callback) { if (!tasks || !tasks.length) { if (typeof callback === 'function') callback(); return; } let task = tasks.shift(); if (task.name === 'add') { createExtendObject(task.id, task.obj, function () { setTimeout(processTasks, 0, tasks, callback); }); } else if (task.name === 'del') { adapter.delObject(task.id, function () { setTimeout(processTasks, 0, tasks, callback); }); } else if (task.name === 'syncEnums') { syncEnums('rooms', task.id, task.obj, function () { setTimeout(processTasks, 0, tasks, callback); }); } else { throw 'Unknown task'; } } function checkDeviceIds(options, config, deviceIds) { for (let i = config.length - 1; i >= 0; i--) { config[i].deviceId = !options.config.multiDeviceId ? options.config.defaultDeviceId : (config[i].deviceId !== undefined ? parseInt(config[i].deviceId, 10) : options.config.defaultDeviceId); if (isNaN(config[i].deviceId)) config[i].deviceId = options.config.defaultDeviceId; if (deviceIds.indexOf(config[i].deviceId) === -1) { deviceIds.push(config[i].deviceId); } } } function prepareConfig(config) { let params = config.params; let options = { config: { type: 'tcp', round: 0, timeout: 5000, defaultDeviceId: 0, }, devices: {} }; let deviceIds = []; checkDeviceIds(options, config.disInputs, deviceIds); deviceIds.sort(); options.objects = objects; return options; } function parseConfig(callback) { adapter.log.info('初始化: ' + adapter.config.door); let options = prepareConfig(adapter.config); const params = adapter.config.params; adapter.getForeignObjects(adapter.namespace + '.*', (err, list) => { let oldObjects = list; let newObjects = []; // adapter.config.door.sort(sortByAddress); let tasks = []; checkObjects(adapter.config, 'door', 'door', 'Door', tasks, newObjects); tasks.push({ id: 'info', name: 'add', obj: { type: 'channel', common: { name: 'info' }, native: {} } }); adapter.getObject('info.connection', function (err, obj) { if (!obj) { obj = { type: 'state', common: { name: 'Number of connected partners', role: 'indicator.connected', write: false, read: true, type: 'boolean' }, native: {} }; adapter.setObjectNotExists('info.connection', obj); } }); newObjects.push(adapter.namespace + '.info.connection'); // clear unused states for (let id_ in oldObjects) { if (oldObjects.hasOwnProperty(id_) && newObjects.indexOf(id_) === -1) { tasks.push({ id: id_, name: 'del' }); } } processTasks(tasks, function () { oldObjects = []; newObjects = []; adapter.subscribeStates('*'); callback(options); }); }); } function checkObjects(options, regType, regName, regFullName, tasks, newObjects) { let regs = options[regType]; for (let i = 0; regs.length > i; i++) { const id = adapter.namespace + '.' + regs[i].id; adapter.log.info("id=" + id); regs[i].fullId = id; objects[id] = { _id: regs[i].id, type: 'state', common: { name: regs[i].description, role: regs[i].role, type: 'string', read: true, write: true, def: "" }, native: { regType: regType, address: regs[i].address, ip: regs[i].ip } }; tasks.push({ id: regs[i].deviceId, name: 'add', obj: objects[id] }); newObjects.push(id); } if (regs.length) { tasks.push({ id: regName, name: 'add', obj: { type: 'channel', common: { name: regFullName }, native: {} } }); } } function sortByAddress(a, b) { let ad = parseFloat(a.deviceId); let bd = parseFloat(b.deviceId); return ((ad < bd) ? -1 : ((ad > bd) ? 1 : 0)); } function main() { // The adapters config (in the instance object everything under the attribute "native") is accessible via // adapter.config: adapter.log.info('config test1: ' + adapter.config.test1); adapter.log.info('config test1: ' + adapter.config.test2); adapter.log.info('config mySelect: ' + adapter.config.mySelect); parseConfig(options => { }); /** * * For every state in the system there has to be also an object of type state * * Here a simple doorgate for a boolean variable named "testVariable" * * Because every adapter instance uses its own unique namespace variable names can't collide with other adapters variables * */ adapter.setObject('testVariable', { type: 'state', common: { name: 'testVariable', type: 'boolean', role: 'indicator' }, native: {} }); // in this doorgate all states changes inside the adapters namespace are subscribed adapter.subscribeStates('*'); /** * setState examples * * you will notice that each setState will cause the stateChange event to fire (because of above subscribeStates cmd) * */ // the variable testVariable is set to true as command (ack=false) adapter.setState('testVariable', true); // same thing, but the value is flagged "ack" // ack should be always set to true if the value is received from or acknowledged from the target system adapter.setState('testVariable', {val: true, ack: true}); // same thing, but the state is deleted after 30s (getState will return null afterwards) adapter.setState('testVariable', {val: true, ack: true, expire: 30}); // examples for the checkPassword/checkGroup functions adapter.checkPassword('admin', 'yunkong2', function (res) { console.log('check user admin pw ioboker: ' + res); }); adapter.checkGroup('admin', 'admin', function (res) { console.log('check group user admin group admin: ' + res); }); // try to load certificates if (adapter.config.secure) { // Load certificates // Load certificates adapter.getCertificates(function (err, certificates, leConfig) { adapter.config.certificates = certificates; adapter.config.leConfig = leConfig; initWebServer(adapter.config); }); } else { initWebServer(adapter.config); } /* // Convert port to number adapter.config.interval = parseInt(adapter.config.interval, 10); // If interval and URl are set => poll it every X milliseconds if (adapter.config.interval && adapter.config.pollURL) { // initial poll pollData(); // poll every x milliseconds timer = setInterval(pollData, adapter.config.interval); } */ }