更新 main.js
This commit is contained in:
892
main.js
892
main.js
@@ -1,443 +1,449 @@
|
||||
/**
|
||||
*
|
||||
* yunkong2 node-red Adapter
|
||||
*
|
||||
* (c) 2014 bluefox<bluefox@ccu.io>
|
||||
*
|
||||
* Apache 2.0 License
|
||||
*
|
||||
*/
|
||||
/* jshint -W097 */// jshint strict:false
|
||||
/*jslint node: true */
|
||||
'use strict';
|
||||
|
||||
const utils = require(__dirname + '/lib/utils'); // Get common adapter utils
|
||||
const adapter = utils.Adapter({
|
||||
name: 'node-red',
|
||||
systemConfig: true, // get the system configuration as systemConfig parameter of adapter
|
||||
unload: unloadRed
|
||||
});
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const spawn = require('child_process').spawn;
|
||||
const Notify = require('fs.notify');
|
||||
const attempts = {};
|
||||
const additional = [];
|
||||
let secret;
|
||||
|
||||
let userdataDir = __dirname + '/userdata/';
|
||||
|
||||
adapter.on('message', function (obj) {
|
||||
if (obj) processMessage(obj);
|
||||
processMessages();
|
||||
});
|
||||
|
||||
adapter.on('ready', function () {
|
||||
installLibraries(main);
|
||||
});
|
||||
|
||||
function installNpm(npmLib, callback) {
|
||||
const path = __dirname;
|
||||
if (typeof npmLib === 'function') {
|
||||
callback = npmLib;
|
||||
npmLib = undefined;
|
||||
}
|
||||
|
||||
const cmd = 'npm install ' + npmLib + ' --production --prefix "' + path + '" --save';
|
||||
adapter.log.info(cmd + ' (System call)');
|
||||
// Install node modules as system call
|
||||
|
||||
// System call used for update of js-controller itself,
|
||||
// because during installation npm packet will be deleted too, but some files must be loaded even during the install process.
|
||||
const exec = require('child_process').exec;
|
||||
const child = exec(cmd);
|
||||
child.stdout.on('data', function(buf) {
|
||||
adapter.log.info(buf.toString('utf8'));
|
||||
});
|
||||
child.stderr.on('data', function(buf) {
|
||||
adapter.log.error(buf.toString('utf8'));
|
||||
});
|
||||
|
||||
child.on('exit', function (code, signal) {
|
||||
if (code) {
|
||||
adapter.log.error('Cannot install ' + npmLib + ': ' + code);
|
||||
}
|
||||
// command succeeded
|
||||
if (callback) callback(npmLib);
|
||||
});
|
||||
}
|
||||
|
||||
function installLibraries(callback) {
|
||||
let allInstalled = true;
|
||||
if (typeof adapter.common.npmLibs === 'string') {
|
||||
adapter.common.npmLibs = adapter.common.npmLibs.split(/[,;\s]+/);
|
||||
}
|
||||
|
||||
if (adapter.common && adapter.common.npmLibs) {
|
||||
for (let lib = 0; lib < adapter.common.npmLibs.length; lib++) {
|
||||
if (adapter.common.npmLibs[lib] && adapter.common.npmLibs[lib].trim()) {
|
||||
adapter.common.npmLibs[lib] = adapter.common.npmLibs[lib].trim();
|
||||
if (!fs.existsSync(__dirname + '/node_modules/' + adapter.common.npmLibs[lib] + '/package.json')) {
|
||||
|
||||
if (!attempts[adapter.common.npmLibs[lib]]) {
|
||||
attempts[adapter.common.npmLibs[lib]] = 1;
|
||||
} else {
|
||||
attempts[adapter.common.npmLibs[lib]]++;
|
||||
}
|
||||
if (attempts[adapter.common.npmLibs[lib]] > 3) {
|
||||
adapter.log.error('Cannot install npm packet: ' + adapter.common.npmLibs[lib]);
|
||||
continue;
|
||||
}
|
||||
|
||||
installNpm(adapter.common.npmLibs[lib], function () {
|
||||
installLibraries(callback);
|
||||
});
|
||||
allInstalled = false;
|
||||
break;
|
||||
} else {
|
||||
if (additional.indexOf(adapter.common.npmLibs[lib]) === -1) additional.push(adapter.common.npmLibs[lib]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (allInstalled) callback();
|
||||
}
|
||||
|
||||
// is called if a subscribed state changes
|
||||
//adapter.on('stateChange', function (id, state) {
|
||||
//});
|
||||
function unloadRed (callback) {
|
||||
// Stop node-red
|
||||
stopping = true;
|
||||
if (redProcess) {
|
||||
adapter.log.info("kill node-red task");
|
||||
redProcess.kill();
|
||||
redProcess = null;
|
||||
}
|
||||
if (notificationsCreds) notificationsCreds.close();
|
||||
if (notificationsFlows) notificationsFlows.close();
|
||||
|
||||
if (callback) callback();
|
||||
}
|
||||
|
||||
function processMessage(obj) {
|
||||
if (!obj || !obj.command) return;
|
||||
switch (obj.command) {
|
||||
case 'update':
|
||||
writeStateList(error => {
|
||||
if (typeof obj.callback === 'function') adapter.sendTo(obj.from, obj.command, error, obj.callback);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'stopInstance':
|
||||
unloadRed();
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function processMessages() {
|
||||
adapter.getMessage((err, obj) => {
|
||||
if (obj) {
|
||||
processMessage(obj.command, obj.message);
|
||||
processMessages();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getNodeRedPath() {
|
||||
let nodeRed = __dirname + '/node_modules/node-red';
|
||||
if (!fs.existsSync(nodeRed)) {
|
||||
nodeRed = path.normalize(__dirname + '/../node-red');
|
||||
if (!fs.existsSync(nodeRed)) {
|
||||
nodeRed = path.normalize(__dirname + '/../node_modules/node-red');
|
||||
if (!fs.existsSync(nodeRed)) {
|
||||
adapter && adapter.log && adapter.log.error('Cannot find node-red packet!');
|
||||
throw new Error('Cannot find node-red packet!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nodeRed;
|
||||
}
|
||||
|
||||
let redProcess;
|
||||
let stopping;
|
||||
let notificationsFlows;
|
||||
let notificationsCreds;
|
||||
let saveTimer;
|
||||
const nodePath = getNodeRedPath();
|
||||
|
||||
function startNodeRed() {
|
||||
adapter.config.maxMemory = parseInt(adapter.config.maxMemory, 10) || 128;
|
||||
const args = ['--max-old-space-size=' + adapter.config.maxMemory, nodePath + '/red.js', '-v', '--settings', userdataDir + 'settings.js'];
|
||||
adapter.log.info('Starting node-red: ' + args.join(' '));
|
||||
|
||||
redProcess = spawn('node', args);
|
||||
|
||||
redProcess.on('error', function (err) {
|
||||
adapter.log.error('catched exception from node-red:' + JSON.stringify(err));
|
||||
});
|
||||
|
||||
redProcess.stdout.on('data', function (data) {
|
||||
if (!data) return;
|
||||
data = data.toString();
|
||||
if (data[data.length - 2] === '\r' && data[data.length - 1] === '\n') data = data.substring(0, data.length - 2);
|
||||
if (data[data.length - 2] === '\n' && data[data.length - 1] === '\r') data = data.substring(0, data.length - 2);
|
||||
if (data[data.length - 1] === '\r') data = data.substring(0, data.length - 1);
|
||||
|
||||
if (data.indexOf('[err') !== -1) {
|
||||
adapter.log.error(data);
|
||||
} else if (data.indexOf('[warn]') !== -1) {
|
||||
adapter.log.warn(data);
|
||||
} else {
|
||||
adapter.log.debug(data);
|
||||
}
|
||||
});
|
||||
redProcess.stderr.on('data', function (data) {
|
||||
if (!data) return;
|
||||
if (data[0]) {
|
||||
let text = '';
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
text += String.fromCharCode(data[i]);
|
||||
}
|
||||
data = text;
|
||||
}
|
||||
if (data.indexOf && data.indexOf('[warn]') === -1) {
|
||||
adapter.log.warn(data);
|
||||
} else {
|
||||
adapter.log.error(JSON.stringify(data));
|
||||
}
|
||||
});
|
||||
|
||||
redProcess.on('exit', function (exitCode) {
|
||||
adapter.log.info('node-red exited with ' + exitCode);
|
||||
redProcess = null;
|
||||
if (!stopping) {
|
||||
setTimeout(startNodeRed, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setOption(line, option, value) {
|
||||
const toFind = "'%%" + option + "%%'";
|
||||
const pos = line.indexOf(toFind);
|
||||
if (pos !== -1) {
|
||||
return line.substring(0, pos) + ((value !== undefined) ? value : (adapter.config[option] === null || adapter.config[option] === undefined) ? '' : adapter.config[option]) + line.substring(pos + toFind.length);
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
function writeSettings() {
|
||||
const config = JSON.stringify(adapter.systemConfig);
|
||||
const text = fs.readFileSync(__dirname + '/settings.js').toString();
|
||||
const lines = text.split('\n');
|
||||
let npms = '\r\n';
|
||||
const dir = __dirname.replace(/\\/g, '/') + '/node_modules/';
|
||||
const nodesDir = '"' + __dirname.replace(/\\/g, '/') + '/nodes/"';
|
||||
|
||||
const bind = '"' + (adapter.config.bind || '0.0.0.0') + '"';
|
||||
const auth = adapter.config.user && adapter.config.pass ? JSON.stringify({user: adapter.config.user, pass: adapter.config.pass}) : '""';
|
||||
const pass = '"' + adapter.config.pass + '"';
|
||||
|
||||
for (let a = 0; a < additional.length; a++) {
|
||||
if (additional[a].match(/^node-red-/)) continue;
|
||||
npms += ' "' + additional[a] + '": require("' + dir + additional[a] + '")';
|
||||
if (a !== additional.length - 1) {
|
||||
npms += ', \r\n';
|
||||
}
|
||||
}
|
||||
|
||||
// update from 1.0.1 (new convert-option)
|
||||
if (adapter.config.valueConvert === null ||
|
||||
adapter.config.valueConvert === undefined ||
|
||||
adapter.config.valueConvert === '' ||
|
||||
adapter.config.valueConvert === 'true' ||
|
||||
adapter.config.valueConvert === '1' ||
|
||||
adapter.config.valueConvert === 1) {
|
||||
adapter.config.valueConvert = true;
|
||||
}
|
||||
if (adapter.config.valueConvert === 0 ||
|
||||
adapter.config.valueConvert === '0' ||
|
||||
adapter.config.valueConvert === 'false') {
|
||||
adapter.config.valueConvert = false;
|
||||
}
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
lines[i] = setOption(lines[i], 'port');
|
||||
lines[i] = setOption(lines[i], 'auth', auth);
|
||||
lines[i] = setOption(lines[i], 'pass', pass);
|
||||
lines[i] = setOption(lines[i], 'bind', bind);
|
||||
lines[i] = setOption(lines[i], 'port');
|
||||
lines[i] = setOption(lines[i], 'instance', adapter.instance);
|
||||
lines[i] = setOption(lines[i], 'config', config);
|
||||
lines[i] = setOption(lines[i], 'functionGlobalContext', npms);
|
||||
lines[i] = setOption(lines[i], 'nodesdir', nodesDir);
|
||||
lines[i] = setOption(lines[i], 'httpRoot');
|
||||
lines[i] = setOption(lines[i], 'credentialSecret', secret);
|
||||
lines[i] = setOption(lines[i], 'valueConvert');
|
||||
}
|
||||
fs.writeFileSync(userdataDir + 'settings.js', lines.join('\n'));
|
||||
}
|
||||
|
||||
function writeStateList(callback) {
|
||||
adapter.getForeignObjects('*', 'state', ['rooms', 'functions'], function (err, obj) {
|
||||
// remove native information
|
||||
for (const i in obj) {
|
||||
if (obj.hasOwnProperty(i) && obj[i].native) {
|
||||
delete obj[i].native;
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(nodePath + '/public/yunkong2.json', JSON.stringify(obj));
|
||||
if (callback) callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
function saveObjects() {
|
||||
if (saveTimer) {
|
||||
clearTimeout(saveTimer);
|
||||
saveTimer = null;
|
||||
}
|
||||
let cred = undefined;
|
||||
let flows = undefined;
|
||||
|
||||
try {
|
||||
if (fs.existsSync(userdataDir + 'flows_cred.json')) {
|
||||
cred = JSON.parse(fs.readFileSync(userdataDir + 'flows_cred.json'));
|
||||
}
|
||||
} catch(e) {
|
||||
adapter.log.error('Cannot save ' + userdataDir + 'flows_cred.json');
|
||||
}
|
||||
try {
|
||||
if (fs.existsSync(userdataDir + 'flows.json')) {
|
||||
flows = JSON.parse(fs.readFileSync(userdataDir + 'flows.json'));
|
||||
}
|
||||
} catch(e) {
|
||||
adapter.log.error('Cannot save ' + userdataDir + 'flows.json');
|
||||
}
|
||||
//upload it to config
|
||||
adapter.setObject('flows', {
|
||||
common: {
|
||||
name: 'Flows for node-red'
|
||||
},
|
||||
native: {
|
||||
cred: cred,
|
||||
flows: flows
|
||||
},
|
||||
type: 'config'
|
||||
}, function () {
|
||||
adapter.log.info('Save ' + userdataDir + 'flows.json');
|
||||
});
|
||||
}
|
||||
|
||||
function syncPublic(path) {
|
||||
if (!path) path = '/public';
|
||||
|
||||
const dir = fs.readdirSync(__dirname + path);
|
||||
|
||||
if (!fs.existsSync(nodePath + path)) {
|
||||
fs.mkdirSync(nodePath + path);
|
||||
}
|
||||
|
||||
for (let i = 0; i < dir.length; i++) {
|
||||
const stat = fs.statSync(__dirname + path + '/' + dir[i]);
|
||||
if (stat.isDirectory()) {
|
||||
syncPublic(path + '/' + dir[i]);
|
||||
} else {
|
||||
if (!fs.existsSync(nodePath + path + '/' + dir[i])) {
|
||||
fs.createReadStream(__dirname + path + '/' + dir[i]).pipe(fs.createWriteStream(nodePath + path + '/' + dir[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function installNotifierFlows(isFirst) {
|
||||
if (!notificationsFlows) {
|
||||
if (fs.existsSync(userdataDir + 'flows.json')) {
|
||||
if (!isFirst) saveObjects();
|
||||
// monitor project file
|
||||
notificationsFlows = new Notify([userdataDir + 'flows.json']);
|
||||
notificationsFlows.on('change', function () {
|
||||
if (saveTimer) clearTimeout(saveTimer);
|
||||
saveTimer = setTimeout(saveObjects, 500);
|
||||
});
|
||||
} else {
|
||||
// Try to install notifier every 10 seconds till the file will be created
|
||||
setTimeout(function () {
|
||||
installNotifierFlows();
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function installNotifierCreds(isFirst) {
|
||||
if (!notificationsCreds) {
|
||||
if (fs.existsSync(userdataDir + 'flows_cred.json')) {
|
||||
if (!isFirst) saveObjects();
|
||||
// monitor project file
|
||||
notificationsCreds = new Notify([userdataDir + 'flows_cred.json']);
|
||||
notificationsCreds.on('change', function () {
|
||||
if (saveTimer) clearTimeout(saveTimer);
|
||||
saveTimer = setTimeout(saveObjects, 500);
|
||||
});
|
||||
} else {
|
||||
// Try to install notifier every 10 seconds till the file will be created
|
||||
setTimeout(function () {
|
||||
installNotifierCreds();
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
// Find userdata directory
|
||||
|
||||
// normally /opt/yunkong2/node_modules/yunkong2.js-controller
|
||||
// but can be /example/yunkong2.js-controller
|
||||
const controllerDir = utils.controllerDir;
|
||||
const parts = controllerDir.split('/');
|
||||
if (parts.length > 1 && parts[parts.length - 2] === 'node_modules') {
|
||||
parts.splice(parts.length - 2, 2);
|
||||
userdataDir = parts.join('/');
|
||||
userdataDir += '/yunkong2-data/node-red/';
|
||||
}
|
||||
|
||||
// create userdata directory
|
||||
if (!fs.existsSync(userdataDir)) {
|
||||
fs.mkdirSync(userdataDir);
|
||||
}
|
||||
|
||||
syncPublic();
|
||||
|
||||
// Read configuration
|
||||
adapter.getObject('flows', function (err, obj) {
|
||||
if (obj && obj.native && obj.native.cred) {
|
||||
const c = JSON.stringify(obj.native.cred);
|
||||
// If really not empty
|
||||
if (c !== '{}' && c !== '[]') {
|
||||
fs.writeFileSync(userdataDir + 'flows_cred.json', JSON.stringify(obj.native.cred));
|
||||
}
|
||||
}
|
||||
if (obj && obj.native && obj.native.flows) {
|
||||
const f = JSON.stringify(obj.native.flows);
|
||||
// If really not empty
|
||||
if (f !== '{}' && f !== '[]') {
|
||||
fs.writeFileSync(userdataDir + 'flows.json', JSON.stringify(obj.native.flows));
|
||||
}
|
||||
}
|
||||
|
||||
installNotifierFlows(true);
|
||||
installNotifierCreds(true);
|
||||
|
||||
adapter.getForeignObject('system.config', (err, obj) => {
|
||||
if (obj && obj.native && obj.native.secret) {
|
||||
//noinspection JSUnresolvedVariable
|
||||
secret = obj.native.secret;
|
||||
}
|
||||
// Create settings for node-red
|
||||
writeSettings();
|
||||
writeStateList(() => startNodeRed());
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
*
|
||||
* yunkong2 node-red Adapter
|
||||
*
|
||||
* (c) 2014 bluefox<bluefox@ccu.io>
|
||||
*
|
||||
* Apache 2.0 License
|
||||
*
|
||||
*/
|
||||
/* jshint -W097 */// jshint strict:false
|
||||
/*jslint node: true */
|
||||
'use strict';
|
||||
|
||||
const utils = require(__dirname + '/lib/utils'); // Get common adapter utils
|
||||
const adapter = utils.Adapter({
|
||||
name: 'node-red',
|
||||
systemConfig: true, // get the system configuration as systemConfig parameter of adapter
|
||||
unload: unloadRed
|
||||
});
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const spawn = require('child_process').spawn;
|
||||
const Notify = require('fs.notify');
|
||||
const attempts = {};
|
||||
const additional = [];
|
||||
let secret;
|
||||
|
||||
let userdataDir = __dirname + '/userdata/';
|
||||
|
||||
adapter.on('message', function (obj) {
|
||||
if (obj) processMessage(obj);
|
||||
processMessages();
|
||||
});
|
||||
|
||||
adapter.on('ready', function () {
|
||||
installLibraries(main);
|
||||
});
|
||||
|
||||
function installNpm(npmLib, callback) {
|
||||
const path = __dirname;
|
||||
if (typeof npmLib === 'function') {
|
||||
callback = npmLib;
|
||||
npmLib = undefined;
|
||||
}
|
||||
|
||||
const cmd = 'npm install ' + npmLib + ' --production --prefix "' + path + '" --save';
|
||||
adapter.log.info(cmd + ' (System call)');
|
||||
// Install node modules as system call
|
||||
|
||||
// System call used for update of js-controller itself,
|
||||
// because during installation npm packet will be deleted too, but some files must be loaded even during the install process.
|
||||
const exec = require('child_process').exec;
|
||||
const child = exec(cmd);
|
||||
child.stdout.on('data', function(buf) {
|
||||
adapter.log.info(buf.toString('utf8'));
|
||||
});
|
||||
child.stderr.on('data', function(buf) {
|
||||
adapter.log.error(buf.toString('utf8'));
|
||||
});
|
||||
|
||||
child.on('exit', function (code, signal) {
|
||||
if (code) {
|
||||
adapter.log.error('Cannot install ' + npmLib + ': ' + code);
|
||||
}
|
||||
// command succeeded
|
||||
if (callback) callback(npmLib);
|
||||
});
|
||||
}
|
||||
|
||||
function installLibraries(callback) {
|
||||
let allInstalled = true;
|
||||
if (typeof adapter.common.npmLibs === 'string') {
|
||||
adapter.common.npmLibs = adapter.common.npmLibs.split(/[,;\s]+/);
|
||||
}
|
||||
|
||||
if (adapter.common && adapter.common.npmLibs) {
|
||||
for (let lib = 0; lib < adapter.common.npmLibs.length; lib++) {
|
||||
if (adapter.common.npmLibs[lib] && adapter.common.npmLibs[lib].trim()) {
|
||||
adapter.common.npmLibs[lib] = adapter.common.npmLibs[lib].trim();
|
||||
if (!fs.existsSync(__dirname + '/node_modules/' + adapter.common.npmLibs[lib] + '/package.json')) {
|
||||
|
||||
if (!attempts[adapter.common.npmLibs[lib]]) {
|
||||
attempts[adapter.common.npmLibs[lib]] = 1;
|
||||
} else {
|
||||
attempts[adapter.common.npmLibs[lib]]++;
|
||||
}
|
||||
if (attempts[adapter.common.npmLibs[lib]] > 3) {
|
||||
adapter.log.error('Cannot install npm packet: ' + adapter.common.npmLibs[lib]);
|
||||
continue;
|
||||
}
|
||||
|
||||
installNpm(adapter.common.npmLibs[lib], function () {
|
||||
installLibraries(callback);
|
||||
});
|
||||
allInstalled = false;
|
||||
break;
|
||||
} else {
|
||||
if (additional.indexOf(adapter.common.npmLibs[lib]) === -1) additional.push(adapter.common.npmLibs[lib]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (allInstalled) callback();
|
||||
}
|
||||
|
||||
// is called if a subscribed state changes
|
||||
//adapter.on('stateChange', function (id, state) {
|
||||
//});
|
||||
function unloadRed (callback) {
|
||||
// Stop node-red
|
||||
stopping = true;
|
||||
if (redProcess) {
|
||||
adapter.log.info("kill node-red task");
|
||||
redProcess.kill();
|
||||
redProcess = null;
|
||||
}
|
||||
if (notificationsCreds) notificationsCreds.close();
|
||||
if (notificationsFlows) notificationsFlows.close();
|
||||
|
||||
if (callback) callback();
|
||||
}
|
||||
|
||||
function processMessage(obj) {
|
||||
if (!obj || !obj.command) return;
|
||||
switch (obj.command) {
|
||||
case 'update':
|
||||
writeStateList(error => {
|
||||
if (typeof obj.callback === 'function') adapter.sendTo(obj.from, obj.command, error, obj.callback);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'stopInstance':
|
||||
unloadRed();
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function processMessages() {
|
||||
adapter.getMessage((err, obj) => {
|
||||
if (obj) {
|
||||
processMessage(obj.command, obj.message);
|
||||
processMessages();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getNodeRedPath() {
|
||||
let nodeRed = __dirname + '/node_modules/node-red';
|
||||
if (!fs.existsSync(nodeRed)) {
|
||||
nodeRed = path.normalize(__dirname + '/../node-red');
|
||||
if (!fs.existsSync(nodeRed)) {
|
||||
nodeRed = path.normalize(__dirname + '/../node_modules/node-red');
|
||||
if (!fs.existsSync(nodeRed)) {
|
||||
adapter && adapter.log && adapter.log.error('Cannot find node-red packet!');
|
||||
throw new Error('Cannot find node-red packet!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nodeRed;
|
||||
}
|
||||
|
||||
let redProcess;
|
||||
let stopping;
|
||||
let notificationsFlows;
|
||||
let notificationsCreds;
|
||||
let saveTimer;
|
||||
const nodePath = getNodeRedPath();
|
||||
|
||||
function startNodeRed() {
|
||||
adapter.config.maxMemory = parseInt(adapter.config.maxMemory, 10) || 128;
|
||||
const args = ['--max-old-space-size=' + adapter.config.maxMemory, nodePath + '/red.js', '-v', '--settings', userdataDir + 'settings.js'];
|
||||
adapter.log.info('Starting node-red: ' + args.join(' '));
|
||||
|
||||
redProcess = spawn('node', args);
|
||||
|
||||
redProcess.on('error', function (err) {
|
||||
adapter.log.error('catched exception from node-red:' + JSON.stringify(err));
|
||||
});
|
||||
|
||||
redProcess.stdout.on('data', function (data) {
|
||||
if (!data) return;
|
||||
data = data.toString();
|
||||
if (data[data.length - 2] === '\r' && data[data.length - 1] === '\n') data = data.substring(0, data.length - 2);
|
||||
if (data[data.length - 2] === '\n' && data[data.length - 1] === '\r') data = data.substring(0, data.length - 2);
|
||||
if (data[data.length - 1] === '\r') data = data.substring(0, data.length - 1);
|
||||
|
||||
if (data.indexOf('[err') !== -1) {
|
||||
adapter.log.error(data);
|
||||
} else if (data.indexOf('[warn]') !== -1) {
|
||||
adapter.log.warn(data);
|
||||
} else {
|
||||
adapter.log.debug(data);
|
||||
}
|
||||
});
|
||||
redProcess.stderr.on('data', function (data) {
|
||||
if (!data) return;
|
||||
if (data[0]) {
|
||||
let text = '';
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
text += String.fromCharCode(data[i]);
|
||||
}
|
||||
data = text;
|
||||
}
|
||||
if (data.indexOf && data.indexOf('[warn]') === -1) {
|
||||
adapter.log.warn(data);
|
||||
} else {
|
||||
adapter.log.error(JSON.stringify(data));
|
||||
}
|
||||
});
|
||||
|
||||
redProcess.on('exit', function (exitCode) {
|
||||
adapter.log.info('node-red exited with ' + exitCode);
|
||||
redProcess = null;
|
||||
if (!stopping) {
|
||||
setTimeout(startNodeRed, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setOption(line, option, value) {
|
||||
const toFind = "'%%" + option + "%%'";
|
||||
const pos = line.indexOf(toFind);
|
||||
if (pos !== -1) {
|
||||
return line.substring(0, pos) + ((value !== undefined) ? value : (adapter.config[option] === null || adapter.config[option] === undefined) ? '' : adapter.config[option]) + line.substring(pos + toFind.length);
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
function writeSettings() {
|
||||
const config = JSON.stringify(adapter.systemConfig);
|
||||
const text = fs.readFileSync(__dirname + '/settings.js').toString();
|
||||
const lines = text.split('\n');
|
||||
let npms = '\r\n';
|
||||
const dir = __dirname.replace(/\\/g, '/') + '/node_modules/';
|
||||
const nodesDir = '"' + __dirname.replace(/\\/g, '/') + '/nodes/"';
|
||||
|
||||
const bind = '"' + (adapter.config.bind || '0.0.0.0') + '"';
|
||||
const auth = adapter.config.user && adapter.config.pass ? JSON.stringify({user: adapter.config.user, pass: adapter.config.pass}) : '""';
|
||||
const pass = '"' + adapter.config.pass + '"';
|
||||
|
||||
const ui_auth = adapter.config.ui_user && adapter.config.ui_pass ? JSON.stringify({ui_user: adapter.config.ui_user, ui_pass: adapter.config.ui_pass}) : '""';
|
||||
const ui_pass = '"' + adapter.config.ui_pass + '"';
|
||||
|
||||
|
||||
for (let a = 0; a < additional.length; a++) {
|
||||
if (additional[a].match(/^node-red-/)) continue;
|
||||
npms += ' "' + additional[a] + '": require("' + dir + additional[a] + '")';
|
||||
if (a !== additional.length - 1) {
|
||||
npms += ', \r\n';
|
||||
}
|
||||
}
|
||||
|
||||
// update from 1.0.1 (new convert-option)
|
||||
if (adapter.config.valueConvert === null ||
|
||||
adapter.config.valueConvert === undefined ||
|
||||
adapter.config.valueConvert === '' ||
|
||||
adapter.config.valueConvert === 'true' ||
|
||||
adapter.config.valueConvert === '1' ||
|
||||
adapter.config.valueConvert === 1) {
|
||||
adapter.config.valueConvert = true;
|
||||
}
|
||||
if (adapter.config.valueConvert === 0 ||
|
||||
adapter.config.valueConvert === '0' ||
|
||||
adapter.config.valueConvert === 'false') {
|
||||
adapter.config.valueConvert = false;
|
||||
}
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
lines[i] = setOption(lines[i], 'port');
|
||||
lines[i] = setOption(lines[i], 'auth', auth);
|
||||
lines[i] = setOption(lines[i], 'pass', pass);
|
||||
lines[i] = setOption(lines[i], 'bind', bind);
|
||||
lines[i] = setOption(lines[i], 'port');
|
||||
lines[i] = setOption(lines[i], 'ui_auth', ui_auth);
|
||||
lines[i] = setOption(lines[i], 'ui_pass', ui_pass);
|
||||
lines[i] = setOption(lines[i], 'instance', adapter.instance);
|
||||
lines[i] = setOption(lines[i], 'config', config);
|
||||
lines[i] = setOption(lines[i], 'functionGlobalContext', npms);
|
||||
lines[i] = setOption(lines[i], 'nodesdir', nodesDir);
|
||||
lines[i] = setOption(lines[i], 'httpRoot');
|
||||
lines[i] = setOption(lines[i], 'credentialSecret', secret);
|
||||
lines[i] = setOption(lines[i], 'valueConvert');
|
||||
}
|
||||
fs.writeFileSync(userdataDir + 'settings.js', lines.join('\n'));
|
||||
}
|
||||
|
||||
function writeStateList(callback) {
|
||||
adapter.getForeignObjects('*', 'state', ['rooms', 'functions'], function (err, obj) {
|
||||
// remove native information
|
||||
for (const i in obj) {
|
||||
if (obj.hasOwnProperty(i) && obj[i].native) {
|
||||
delete obj[i].native;
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(nodePath + '/public/yunkong2.json', JSON.stringify(obj));
|
||||
if (callback) callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
function saveObjects() {
|
||||
if (saveTimer) {
|
||||
clearTimeout(saveTimer);
|
||||
saveTimer = null;
|
||||
}
|
||||
let cred = undefined;
|
||||
let flows = undefined;
|
||||
|
||||
try {
|
||||
if (fs.existsSync(userdataDir + 'flows_cred.json')) {
|
||||
cred = JSON.parse(fs.readFileSync(userdataDir + 'flows_cred.json'));
|
||||
}
|
||||
} catch(e) {
|
||||
adapter.log.error('Cannot save ' + userdataDir + 'flows_cred.json');
|
||||
}
|
||||
try {
|
||||
if (fs.existsSync(userdataDir + 'flows.json')) {
|
||||
flows = JSON.parse(fs.readFileSync(userdataDir + 'flows.json'));
|
||||
}
|
||||
} catch(e) {
|
||||
adapter.log.error('Cannot save ' + userdataDir + 'flows.json');
|
||||
}
|
||||
//upload it to config
|
||||
adapter.setObject('flows', {
|
||||
common: {
|
||||
name: 'Flows for node-red'
|
||||
},
|
||||
native: {
|
||||
cred: cred,
|
||||
flows: flows
|
||||
},
|
||||
type: 'config'
|
||||
}, function () {
|
||||
adapter.log.info('Save ' + userdataDir + 'flows.json');
|
||||
});
|
||||
}
|
||||
|
||||
function syncPublic(path) {
|
||||
if (!path) path = '/public';
|
||||
|
||||
const dir = fs.readdirSync(__dirname + path);
|
||||
|
||||
if (!fs.existsSync(nodePath + path)) {
|
||||
fs.mkdirSync(nodePath + path);
|
||||
}
|
||||
|
||||
for (let i = 0; i < dir.length; i++) {
|
||||
const stat = fs.statSync(__dirname + path + '/' + dir[i]);
|
||||
if (stat.isDirectory()) {
|
||||
syncPublic(path + '/' + dir[i]);
|
||||
} else {
|
||||
if (!fs.existsSync(nodePath + path + '/' + dir[i])) {
|
||||
fs.createReadStream(__dirname + path + '/' + dir[i]).pipe(fs.createWriteStream(nodePath + path + '/' + dir[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function installNotifierFlows(isFirst) {
|
||||
if (!notificationsFlows) {
|
||||
if (fs.existsSync(userdataDir + 'flows.json')) {
|
||||
if (!isFirst) saveObjects();
|
||||
// monitor project file
|
||||
notificationsFlows = new Notify([userdataDir + 'flows.json']);
|
||||
notificationsFlows.on('change', function () {
|
||||
if (saveTimer) clearTimeout(saveTimer);
|
||||
saveTimer = setTimeout(saveObjects, 500);
|
||||
});
|
||||
} else {
|
||||
// Try to install notifier every 10 seconds till the file will be created
|
||||
setTimeout(function () {
|
||||
installNotifierFlows();
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function installNotifierCreds(isFirst) {
|
||||
if (!notificationsCreds) {
|
||||
if (fs.existsSync(userdataDir + 'flows_cred.json')) {
|
||||
if (!isFirst) saveObjects();
|
||||
// monitor project file
|
||||
notificationsCreds = new Notify([userdataDir + 'flows_cred.json']);
|
||||
notificationsCreds.on('change', function () {
|
||||
if (saveTimer) clearTimeout(saveTimer);
|
||||
saveTimer = setTimeout(saveObjects, 500);
|
||||
});
|
||||
} else {
|
||||
// Try to install notifier every 10 seconds till the file will be created
|
||||
setTimeout(function () {
|
||||
installNotifierCreds();
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
// Find userdata directory
|
||||
|
||||
// normally /opt/yunkong2/node_modules/yunkong2.js-controller
|
||||
// but can be /example/yunkong2.js-controller
|
||||
const controllerDir = utils.controllerDir;
|
||||
const parts = controllerDir.split('/');
|
||||
if (parts.length > 1 && parts[parts.length - 2] === 'node_modules') {
|
||||
parts.splice(parts.length - 2, 2);
|
||||
userdataDir = parts.join('/');
|
||||
userdataDir += '/yunkong2-data/node-red/';
|
||||
}
|
||||
|
||||
// create userdata directory
|
||||
if (!fs.existsSync(userdataDir)) {
|
||||
fs.mkdirSync(userdataDir);
|
||||
}
|
||||
|
||||
syncPublic();
|
||||
|
||||
// Read configuration
|
||||
adapter.getObject('flows', function (err, obj) {
|
||||
if (obj && obj.native && obj.native.cred) {
|
||||
const c = JSON.stringify(obj.native.cred);
|
||||
// If really not empty
|
||||
if (c !== '{}' && c !== '[]') {
|
||||
fs.writeFileSync(userdataDir + 'flows_cred.json', JSON.stringify(obj.native.cred));
|
||||
}
|
||||
}
|
||||
if (obj && obj.native && obj.native.flows) {
|
||||
const f = JSON.stringify(obj.native.flows);
|
||||
// If really not empty
|
||||
if (f !== '{}' && f !== '[]') {
|
||||
fs.writeFileSync(userdataDir + 'flows.json', JSON.stringify(obj.native.flows));
|
||||
}
|
||||
}
|
||||
|
||||
installNotifierFlows(true);
|
||||
installNotifierCreds(true);
|
||||
|
||||
adapter.getForeignObject('system.config', (err, obj) => {
|
||||
if (obj && obj.native && obj.native.secret) {
|
||||
//noinspection JSUnresolvedVariable
|
||||
secret = obj.native.secret;
|
||||
}
|
||||
// Create settings for node-red
|
||||
writeSettings();
|
||||
writeStateList(() => startNodeRed());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user