2018-09-25 11:22:24 +08:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* 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 <mail@doorgate.com>"
|
|
|
|
* ]
|
|
|
|
* "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
|
|
|
|
|
2018-09-25 11:31:05 +08:00
|
|
|
// load additional libraries
|
|
|
|
var express = require('express'); // call express
|
|
|
|
var request = null; // will be initialized later if polling enabled
|
|
|
|
|
2018-09-25 11:49:31 +08:00
|
|
|
var LE = require(utils.controllerDir + '/lib/letsencrypt.js');
|
|
|
|
|
|
|
|
|
2018-09-25 11:31:05 +08:00
|
|
|
// REST server
|
|
|
|
var webServer = null;
|
|
|
|
var app = null;
|
|
|
|
var router = null;
|
|
|
|
var timer = null;
|
|
|
|
|
|
|
|
|
2018-09-25 11:22:24 +08:00
|
|
|
// 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.*/
|
2018-09-26 13:39:59 +08:00
|
|
|
|
2018-09-25 11:22:24 +08:00
|
|
|
let variable = 1234;
|
2018-09-26 13:39:59 +08:00
|
|
|
let objects = {};
|
|
|
|
|
2018-09-25 11:22:24 +08:00
|
|
|
|
|
|
|
// 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();
|
|
|
|
});
|
|
|
|
|
2018-09-25 11:31:05 +08:00
|
|
|
|
|
|
|
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();
|
|
|
|
|
2018-09-25 11:53:23 +08:00
|
|
|
|
2018-09-25 11:31:05 +08:00
|
|
|
// 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);
|
|
|
|
|
2018-09-25 11:55:46 +08:00
|
|
|
|
|
|
|
adapter.log.info('config port: ' + settings.port);
|
2018-09-25 12:16:05 +08:00
|
|
|
|
2018-09-25 11:31:05 +08:00
|
|
|
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);
|
|
|
|
}
|
2018-09-25 12:22:18 +08:00
|
|
|
|
2018-09-25 11:31:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-09-26 13:39:59 +08:00
|
|
|
|
2018-09-26 13:57:50 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-26 13:39:59 +08:00
|
|
|
function prepareConfig(config) {
|
|
|
|
let params = config.params;
|
|
|
|
|
|
|
|
let options = {
|
|
|
|
config: {
|
2018-09-26 13:40:56 +08:00
|
|
|
type: 'tcp',
|
2018-09-26 13:42:24 +08:00
|
|
|
round: 0,
|
2018-09-26 13:57:50 +08:00
|
|
|
timeout: 5000,
|
|
|
|
defaultDeviceId: 0,
|
2018-09-26 13:39:59 +08:00
|
|
|
},
|
|
|
|
devices: {}
|
|
|
|
};
|
|
|
|
|
2018-09-26 13:57:50 +08:00
|
|
|
let deviceIds = [];
|
|
|
|
checkDeviceIds(options, config.disInputs, deviceIds);
|
|
|
|
|
|
|
|
deviceIds.sort();
|
|
|
|
|
|
|
|
|
2018-09-26 13:39:59 +08:00
|
|
|
options.objects = objects;
|
|
|
|
|
|
|
|
return options;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-09-26 13:21:18 +08:00
|
|
|
function parseConfig(callback) {
|
|
|
|
|
|
|
|
|
|
|
|
adapter.log.info('初始化: ' + adapter.config.door);
|
2018-09-26 13:39:59 +08:00
|
|
|
|
2018-09-26 13:57:50 +08:00
|
|
|
// let options = prepareConfig(adapter.config);
|
2018-09-26 13:39:59 +08:00
|
|
|
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);
|
|
|
|
|
|
|
|
|
2018-09-26 14:13:14 +08:00
|
|
|
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);
|
|
|
|
});
|
2018-09-26 13:39:59 +08:00
|
|
|
|
|
|
|
});
|
2018-09-26 13:21:18 +08:00
|
|
|
|
|
|
|
}
|
2018-09-25 11:31:05 +08:00
|
|
|
|
2018-09-26 13:39:59 +08:00
|
|
|
function checkObjects(options, regType, regName, regFullName, tasks, newObjects) {
|
|
|
|
|
|
|
|
|
2018-09-26 13:45:24 +08:00
|
|
|
let regs = options[regType];
|
|
|
|
|
|
|
|
for (let i = 0; regs.length > i; i++) {
|
2018-09-26 13:57:50 +08:00
|
|
|
const id = adapter.namespace + '.' + regs[i].deviceId;
|
2018-09-26 13:45:24 +08:00
|
|
|
|
|
|
|
adapter.log.info("id=" + id);
|
|
|
|
|
2018-09-26 14:04:11 +08:00
|
|
|
regs[i].fullId = id;
|
|
|
|
objects[id] = {
|
|
|
|
_id: regs[i].deviceId,
|
|
|
|
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]
|
|
|
|
});
|
2018-09-26 14:06:51 +08:00
|
|
|
|
2018-09-26 14:04:11 +08:00
|
|
|
newObjects.push(id);
|
2018-09-26 13:45:24 +08:00
|
|
|
}
|
2018-09-26 14:06:51 +08:00
|
|
|
|
|
|
|
if (regs.length) {
|
|
|
|
tasks.push({
|
|
|
|
id: regName,
|
|
|
|
name: 'add',
|
|
|
|
obj: {
|
|
|
|
type: 'channel',
|
|
|
|
common: {
|
|
|
|
name: regFullName
|
|
|
|
},
|
|
|
|
native: {}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-09-26 13:39:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function sortByAddress(a, b) {
|
|
|
|
let ad = parseFloat(a.deviceId);
|
|
|
|
let bd = parseFloat(b.deviceId);
|
|
|
|
return ((ad < bd) ? -1 : ((ad > bd) ? 1 : 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-09-25 11:22:24 +08:00
|
|
|
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);
|
|
|
|
|
|
|
|
|
2018-09-26 13:21:18 +08:00
|
|
|
parseConfig(options => {
|
|
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2018-09-25 11:22:24 +08:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* 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);
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2018-09-25 11:33:04 +08:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2018-09-25 11:50:14 +08:00
|
|
|
/*
|
|
|
|
|
2018-09-25 11:33:04 +08:00
|
|
|
// 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);
|
|
|
|
}
|
2018-09-25 11:40:57 +08:00
|
|
|
*/
|
2018-09-25 11:22:24 +08:00
|
|
|
}
|