From ffbb79af6e8cac7f7d0af93bb5922562e7f1033b Mon Sep 17 00:00:00 2001 From: Nicola Peditto Date: Fri, 2 Nov 2018 10:26:42 +0100 Subject: [PATCH] Release v2.1.2 --- Dockerfile | 2 +- board-management.js | 508 +++- device/lyt_android.js | 8 +- device/lyt_arduino_yun.js | 48 +- device/lyt_orange_pi.js | 99 + device/lyt_raspberry_pi.js | 6 - device/lyt_server.js | 6 - docs/arduinoyun.md | 3 +- docs/bundling.md | 38 - docs/raspberrypi2.md | 7 +- docs/raspberrypi3.md | 7 +- docs/ubuntu1404.md | 9 +- docs/ubuntu1604.md | 8 +- etc/cron.d/root_openwrt | 1 + etc/cron.d/root_yun | 3 +- etc/init.d/s4t-lightning-rod_openwrt | 2 +- etc/init.d/s4t-lightning-rod_raspberry | 2 +- etc/init.d/s4t-lightning-rod_ubu14 | 2 +- etc/init.d/s4t-lightning-rod_yun | 2 +- etc/logrotate.d/lightning-rod.log | 2 +- etc/systemd/system/s4t-lightning-rod.service | 1 + lightning-rod.js | 323 +-- modules/drivers-manager/manage-drivers.js | 11 +- modules/gpio-manager/manage-gpio.js | 14 +- modules/nodered-manager/manage-nodered.js | 253 ++ modules/plugins-manager/manage-plugins.js | 2199 +++++++++++++---- .../async-wrapper.js} | 5 +- .../{ => nodejs}/plugin-apis.js | 22 +- .../sync-wrapper.js} | 11 +- .../plugins.examples/call_bar.js | 68 - .../plugins.examples/call_gas.js | 97 - .../plugins.examples/call_hello.js | 25 - .../plugins.examples/call_hum.js | 82 - .../plugins.examples/call_lux.js | 63 - .../plugins.examples/call_temp.js | 69 - .../plugins.examples/gps_plugin.js | 147 -- .../plugins.examples/hello_plugin.js | 26 - .../plugins.examples/hum_plugin.js | 86 - .../plugins.examples/lux_plugin.js | 65 - .../plugins.examples/nodejs/driver_app.js | 63 + .../plugins.examples/nodejs/echo.js | 13 + .../plugins.examples/nodejs/hello.js | 16 + .../plugins.examples/python/py_async.py | 33 + .../plugins.examples/python/py_sync.py | 35 + .../plugins.examples/temp_plugin.js | 73 - .../__pycache__/plugin_apis.cpython-35.pyc | Bin 0 -> 969 bytes .../plugins-manager/python/async-wrapper.py | 127 + modules/plugins-manager/python/plugin_apis.py | 8 + .../plugins-manager/python/sync-wrapper.py | 124 + modules/services-manager/manage-services.js | 174 +- modules/vfs-manager/manage-fs.js | 25 +- modules/vnets-manager/manage-networks.js | 14 +- modules/vnets-manager/network-wrapper.js | 2 +- package.json | 12 +- scripts/lr_configure.sh | 1 + scripts/postinst | 27 +- settings.example.json | 43 +- utils/docker/rpi/Dockerfile | 2 +- utils/docker/x86_64/Dockerfile | 2 +- .../install/arancino/configure_LR_arancino.sh | 10 +- utils/install/arancino/install_LR_arancino.sh | 10 +- utils/install/arancino/moveVar_LR_arancino.sh | 27 +- utils/install/install_LR_ubuntu16.04.sh | 53 - 63 files changed, 3435 insertions(+), 1789 deletions(-) create mode 100644 device/lyt_orange_pi.js delete mode 100644 docs/bundling.md mode change 100755 => 100644 etc/init.d/s4t-lightning-rod_raspberry mode change 100755 => 100644 etc/init.d/s4t-lightning-rod_yun create mode 100644 modules/nodered-manager/manage-nodered.js rename modules/plugins-manager/{plugin-wrapper.js => nodejs/async-wrapper.js} (92%) rename modules/plugins-manager/{ => nodejs}/plugin-apis.js (80%) rename modules/plugins-manager/{call-wrapper.js => nodejs/sync-wrapper.js} (88%) delete mode 100644 modules/plugins-manager/plugins.examples/call_bar.js delete mode 100644 modules/plugins-manager/plugins.examples/call_gas.js delete mode 100644 modules/plugins-manager/plugins.examples/call_hello.js delete mode 100644 modules/plugins-manager/plugins.examples/call_hum.js delete mode 100644 modules/plugins-manager/plugins.examples/call_lux.js delete mode 100644 modules/plugins-manager/plugins.examples/call_temp.js delete mode 100644 modules/plugins-manager/plugins.examples/gps_plugin.js delete mode 100644 modules/plugins-manager/plugins.examples/hello_plugin.js delete mode 100644 modules/plugins-manager/plugins.examples/hum_plugin.js delete mode 100644 modules/plugins-manager/plugins.examples/lux_plugin.js create mode 100644 modules/plugins-manager/plugins.examples/nodejs/driver_app.js create mode 100644 modules/plugins-manager/plugins.examples/nodejs/echo.js create mode 100644 modules/plugins-manager/plugins.examples/nodejs/hello.js create mode 100755 modules/plugins-manager/plugins.examples/python/py_async.py create mode 100755 modules/plugins-manager/plugins.examples/python/py_sync.py delete mode 100644 modules/plugins-manager/plugins.examples/temp_plugin.js create mode 100644 modules/plugins-manager/python/__pycache__/plugin_apis.cpython-35.pyc create mode 100644 modules/plugins-manager/python/async-wrapper.py create mode 100644 modules/plugins-manager/python/plugin_apis.py create mode 100644 modules/plugins-manager/python/sync-wrapper.py delete mode 100644 utils/install/install_LR_ubuntu16.04.sh diff --git a/Dockerfile b/Dockerfile index 707ba46..1b8bc66 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y \ && rm -rf /var/lib/apt/lists/* -RUN npm install -g --unsafe gyp autobahn nconf @mdslab/wstun fuse-bindings requestify is-running connection-tester log4js@1.1.1 q fs-access mknod jsonfile \ +RUN npm install -g --unsafe gyp autobahn nconf @mdslab/wstun fuse-bindings requestify is-running connection-tester log4js@1.1.1 q fs-access mknod jsonfile md5 python-shell net node-red \ && npm install -g --unsafe https://github.com/PlayNetwork/node-statvfs/tarball/v3.0.0 \ && npm cache --force clean diff --git a/board-management.js b/board-management.js index 8a7a7a7..cbb63df 100644 --- a/board-management.js +++ b/board-management.js @@ -38,13 +38,23 @@ function manage_WAMP_connection(session) { logger.info('[CONFIGURATION] - Board configuration starting...'); //EXPORTING NETWORK COMMANDS - var manageNetworks = require(LIGHTNINGROD_HOME + '/modules/vnets-manager/manage-networks'); - manageNetworks.exportNetworkCommands(session); + checkModEnabled("vnets_manager").then( + + function (enabled) { + + if(enabled){ + var networksManager = require(LIGHTNINGROD_HOME + '/modules/vnets-manager/manage-networks'); + networksManager.Init(session); + } + } + ); + //Topic on which the board can send a message to be registered var connectionTopic = 'board.connection'; session.subscribe(connectionTopic, onTopicConnection); + //Registering the board to the Cloud by sending a message to the connection topic logger.info("[WAMP] - Sending board ID '" + boardCode + "' to topic " + connectionTopic + " to register the board"); session.publish(connectionTopic, [boardCode, 'connection', session._id]); @@ -56,34 +66,212 @@ function onTopicConnection(args) { var message = args[0]; if (message == 'IoTronic-connected') logger.info("Message on board.connection: " + args[0]) + +} +function checkModEnabled(module_name) { + + var d = Q.defer(); + + var configFile = JSON.parse(fs.readFileSync(SETTINGS, 'utf8')); + + var modules = configFile.config["board"]["modules"]; //console.log(module_name, modules[module_name]); + + d.resolve(modules[module_name]); + + return d.promise; } // This function loads the Lightning-rod modules function moduleLoader (session, device) { + + logger.info("[SYSTEM] - Modules loading:"); + + var configFile = JSON.parse(fs.readFileSync(SETTINGS, 'utf8')); + + var modules = configFile.config["board"]["modules"]; + + var modules_keys = Object.keys( modules ); + + for (var i = 0; i < modules_keys.length; i++) { + + (function (i) { + + var module_name = modules_keys[i]; + var enabled = modules[module_name]["enabled"]; + + + if(enabled) + + switch (module_name) { + + case 'plugins_manager': + logger.info("[SYSTEM] --> " + module_name + ": " + enabled); + var pluginsManager = require(LIGHTNINGROD_HOME + '/modules/plugins-manager/manage-plugins'); + pluginsManager.Init(session); + break; + + case 'services_manager': + logger.info("[SYSTEM] --> " + module_name + ": " + enabled); + var servicesManager = require(LIGHTNINGROD_HOME + '/modules/services-manager/manage-services'); + servicesManager.Init(session); + + break; + + case 'nodered_manager': + logger.info("[SYSTEM] --> " + module_name + ": " + enabled); + var nodeRedManager = require(LIGHTNINGROD_HOME + '/modules/nodered-manager/manage-nodered'); + nodeRedManager.Init(session); + break; + + /* + case 'vnets_manager': + break; + */ + + case 'gpio_manager': + logger.info("[SYSTEM] --> " + module_name + ": " + enabled); + var gpioManager = require(LIGHTNINGROD_HOME + '/modules/gpio-manager/manage-gpio'); + gpioManager.Init(session, lyt_device); + break; + + case 'drivers_manager': + logger.info("[SYSTEM] --> " + module_name + ": " + enabled); + var driversManager = require(LIGHTNINGROD_HOME + "/modules/drivers-manager/manage-drivers"); + driversManager.Init(session); + driversManager.restartDrivers(); + break; + + case 'vfs_manager': + logger.info("[SYSTEM] --> " + module_name + ": " + enabled); + var fsManager = require(LIGHTNINGROD_HOME + "/modules/vfs-manager/manage-fs"); + fsManager.Init(session); + var fsLibsManager = require(LIGHTNINGROD_HOME + "/modules/vfs-manager/manage-fs-libs"); + fsLibsManager.exportFSLibs(session); + break; + + default: + + //logger.error("[SYSTEM] --> Wrong module: " + module_name) + + break; + + + + } + + + })(i); + + } + + /* // MODULES LOADING-------------------------------------------------------------------------------------------------- - var manageGpio = require(LIGHTNINGROD_HOME + '/modules/gpio-manager/manage-gpio'); - manageGpio.exportPins(session, lyt_device); + var gpioManager = require(LIGHTNINGROD_HOME + '/modules/gpio-manager/manage-gpio'); + gpioManager.Init(session, lyt_device); - var managePlugins = require(LIGHTNINGROD_HOME + '/modules/plugins-manager/manage-plugins'); - managePlugins.exportPluginCommands(session); + var pluginsManager = require(LIGHTNINGROD_HOME + '/modules/plugins-manager/manage-plugins'); + pluginsManager.Init(session); var driversManager = require(LIGHTNINGROD_HOME + "/modules/drivers-manager/manage-drivers"); - driversManager.exportDriverCommands(session); + driversManager.Init(session); driversManager.restartDrivers(); var fsManager = require(LIGHTNINGROD_HOME + "/modules/vfs-manager/manage-fs"); - fsManager.exportFSCommands(session); + fsManager.Init(session); var fsLibsManager = require(LIGHTNINGROD_HOME + "/modules/vfs-manager/manage-fs-libs"); fsLibsManager.exportFSLibs(session); - var manageServices = require(LIGHTNINGROD_HOME + '/modules/services-manager/manage-services'); - manageServices.exportServiceCommands(session); + var servicesManager = require(LIGHTNINGROD_HOME + '/modules/services-manager/manage-services'); + servicesManager.Init(session); + + var nodeRedManager = require(LIGHTNINGROD_HOME + '/modules/nodered-manager/manage-nodered'); + nodeRedManager.Init(session); //------------------------------------------------------------------------------------------------------------------ + */ + } +// This function loads at boot the Lightning-rod modules +exports.moduleLoaderOnBoot = function() { + + logger.info("[SYSTEM] - Modules loading:"); + + var configFile = JSON.parse(fs.readFileSync(SETTINGS, 'utf8')); + + var modules = configFile.config["board"]["modules"]; //console.log(modules); + + var modules_keys = Object.keys( modules ); //console.log(modules_keys); + + for (var i = 0; i < modules_keys.length; i++) { + + (function (i) { + + var module_name = modules_keys[i]; + var enabled = modules[module_name]["boot"]; + + if(enabled) + + switch (module_name) { + + case 'plugins_manager': + logger.info("[SYSTEM] --> " + module_name + ": " + enabled); + var pluginsManager = require(LIGHTNINGROD_HOME + '/modules/plugins-manager/manage-plugins'); + pluginsManager.Boot(); + break; + + case 'services_manager': + logger.info("[SYSTEM] --> " + module_name + ": " + enabled); + var servicesManager = require(LIGHTNINGROD_HOME + '/modules/services-manager/manage-services'); + servicesManager.Boot(); + break; + + case 'nodered_manager': + logger.info("[SYSTEM] --> " + module_name + ": " + enabled); + var nodeRedManager = require(LIGHTNINGROD_HOME + '/modules/nodered-manager/manage-nodered'); + nodeRedManager.Boot(); + break; + + + case 'vnets_manager': + logger.info("[SYSTEM] --> " + module_name + ": " + enabled); + break; + + + case 'gpio_manager': + logger.info("[SYSTEM] --> " + module_name + ": " + enabled); + break; + + case 'drivers_manager': + logger.info("[SYSTEM] --> " + module_name + ": " + enabled); + break; + + case 'vfs_manager': + logger.info("[SYSTEM] --> " + module_name + ": " + enabled); + + default: + + logger.warn("[SYSTEM] --> Wrong module: " + module_name); + + break; + + + + } + + + })(i); + + } + + +} + + + + // Init() LR function in order to control the correct LR configuration: // - logging setup // - settings control @@ -197,7 +385,7 @@ exports.checkSettings = function (callback) { } else { check_response = true; } - + //REVERSE CONF url_reverse = nconf.get('config:reverse:server:url_reverse'); port_reverse = nconf.get('config:reverse:server:port_reverse'); @@ -234,23 +422,13 @@ exports.checkSettings = function (callback) { } reg_status = nconf.get('config:board:status'); - if (reg_status == undefined || reg_status == "") { - logger.warn('[SYSTEM] - Registration status undefined or not specified!'); - process.exit(); - - } else if (reg_status != "registered" && reg_status != "new") { - logger.warn('[SYSTEM] - Wrong registration status: ' + reg_status); - logger.warn(' - The registration status can be "registered" or "new".'); - process.exit(); - - } else { - check_response = true; - } + + boardLabel = nconf.get('config:board:label'); var board_position = nconf.get('config:board:position'); if ( (board_position == undefined || Object.keys(board_position).length === 0 ) && (reg_status == "registered") ) { - logger.warn('[SYSTEM] - Wrong board coordinates! Set status to "new" to retrive these information.'); + logger.warn('[SYSTEM] - Wrong board coordinates!'); logger.debug('- Coordinates: ' + JSON.stringify(board_position)); process.exit(); @@ -260,7 +438,7 @@ exports.checkSettings = function (callback) { var socat_port = nconf.get('config:socat:client:port'); if (socat_port == undefined || socat_port == "") { - logger.warn('[SYSTEM] - socat_port not specified or undefined: if the board is network enabled specify this parameter!'); + logger.warn("[SYSTEM] - 'socat_port' not specified or 'undefined': if the board is network enabled specify this parameter!"); } callback(check_response); @@ -313,134 +491,208 @@ exports.setBoardPosition = function (args) { }; -/* -// This function sets the coordinates of the device: called by IoTronic via RPC + +// This function create the settings.json file of the board injected by IoTronic exports.updateConf = function (args) { - var remote_conf = args[0]; - var board_position = args[1]; - logger.info("[SYSTEM] - Set board configuration: " + JSON.stringify(args)); + var d = Q.defer(); - var configFile = JSON.parse(fs.readFileSync(SETTINGS, 'utf8')); - var board_config = configFile.config["board"]; - - logger.info("[SYSTEM] --> BOARD CONFIGURATION " + JSON.stringify(board_config)); + var response = { + message: '', + result: '' + }; + + // activate listener on-exit event after LR exit on-update-conf + logger.debug("[SYSTEM] - Listener on process 'exit' event activated:"); + logger.debug("[SYSTEM] --> Lightning-rod PID: " + process.pid); + process.on("exit", function () { + require("child_process").spawn(process.argv.shift(), process.argv, { + cwd: process.cwd(), + detached : true, + stdio: "inherit" + }); + }); - board_config["remote_conf"] = remote_conf; - board_config["position"] = board_position; - logger.info("[SYSTEM] --> BOARD CONF UPDATED: " + JSON.stringify(board_config)); + + var remote_conf = args[0].message; + + logger.info("[SYSTEM] - Board configuration injected: " + JSON.stringify(remote_conf, null, "\t")); + + logger.info("[SYSTEM] --> BOARD CONF UPDATED: " + JSON.stringify(remote_conf)); //Updates the settings.json file - fs.writeFile(SETTINGS, JSON.stringify(configFile, null, 4), function (err) { + fs.writeFile(SETTINGS, JSON.stringify(remote_conf, null, "\t"), function (err) { if (err) { - logger.error('[SYSTEM] --> Error writing settings.json file: ' + err); + + response.message = 'Error writing settings.json file: ' + err; + response.result = "ERROR"; + logger.error('[SYSTEM] --> ' +response.message); + d.resolve(response); + } else { + logger.debug("[SYSTEM] --> settings.json configuration file saved to " + SETTINGS); + response.message = "Board '"+boardCode+"' configuration updated!"; + response.result = "SUCCESS"; + d.resolve(response); + + //Restarting LR + setTimeout(function () { + process.exit(); + }, 1000) + + } }); - return "Board configuration file updated!"; + return d.promise; + +}; + +// This function update the board configuration +exports.setConf = function (args) { + + var remote_conf = args[0].message; + + logger.info("[SYSTEM] - Board configuration injected: " + JSON.stringify(remote_conf, null, "\t")); + + var d = Q.defer(); + + var response = { + message: '', + result: '' + }; + + // activate listener on-exit event after LR exit on-update-conf + logger.debug("[SYSTEM] --> Listener on process 'exit' event activated:"); + logger.debug("[SYSTEM] --> Lightning-rod PID: " + process.pid); + process.on("exit", function () { + require("child_process").spawn(process.argv.shift(), process.argv, { + cwd: process.cwd(), + detached : true, + stdio: "inherit" + }); + }); + + + logger.info("[SYSTEM] --> Updating board configuration: " + JSON.stringify(remote_conf)); + + //Updates the settings.json file + fs.writeFile(SETTINGS, JSON.stringify(remote_conf, null, "\t"), function (err) { + if (err) { + + response.message = 'Error writing settings.json file: ' + err; + response.result = "ERROR"; + logger.error('[SYSTEM] --> ' +response.message); + d.resolve(response); + + } else { + + logger.debug("[SYSTEM] --> settings.json configuration file saved to " + SETTINGS); + response.message = "Board '"+boardCode+"' configuration updated!"; + response.result = "SUCCESS"; + d.resolve(response); + + + + + } + }); + + return d.promise; }; -*/ + // This function manages the registration status of the board exports.checkRegistrationStatus = function(args){ - var response = args[0]; - - if(response.result == "SUCCESS"){ - - logger.info("[SYSTEM] - Connection to Iotronic "+response.result+" - "+response.message); + var regStatus = args[0]; - // CONNECTION TO IoTronic after access granted. - var configFile = JSON.parse(fs.readFileSync(SETTINGS, 'utf8')); - var board_config = configFile.config["board"]; + var response = { + message: '', + result: '' + }; - logger.info("[CONFIGURATION] - Board configuration parameters: " + JSON.stringify(board_config)); + var d = Q.defer(); - //PROVISIONING: Iotronic sends coordinates to this device when its status is "new" - if(reg_status === "new"){ - logger.info('[CONFIGURATION] - NEW BOARD CONFIGURATION STARTED... '); - - board_session.call("s4t.board.provisioning", [boardCode]).then( + if(regStatus.result == "SUCCESS"){ - function(result){ + logger.info("[SYSTEM] - Connection to Iotronic "+regStatus.result+":\n"+JSON.stringify(regStatus.message, null, "\t")); - logger.info("\n\nPROVISIONING "+boardCode+" RECEIVED: " + JSON.stringify(result) + "\n\n"); + exports.exportManagementCommands(board_session); - board_position = result.message.info.coordinates; - board_config["position"] = board_position; - board_config["status"] = "registered"; - logger.info("\n[CONFIGURATION] - BOARD POSITION UPDATED: " + JSON.stringify(board_config["position"])); + if(regStatus.message['state'] == "new"){ - //Updates the settings.json file - fs.writeFile(SETTINGS, JSON.stringify(configFile, null, 4), function(err) { - if(err) { - logger.error('Error writing settings.json file: ' + err); + logger.info('[CONFIGURATION] - NEW BOARD CONFIGURATION STARTED... '); - } else { - logger.info("settings.json configuration file saved to " + SETTINGS); - //Calling the manage_WAMP_connection function that contains the logic that has to be performed if the device is connected to the WAMP server - //exports.manage_WAMP_connection(board_session, details); - moduleLoader(board_session, lyt_device); - } - }); + var confBundle = { + message: '' + }; - //Create a backup file of settings.json - fs.writeFile(SETTINGS + ".BKP", JSON.stringify(configFile, null, 4), function(err) { - if(err) { - logger.error('Error writing settings.json.BKP file: ' + err); + regStatus.message['conf']['config']['board']['status'] = "registered"; + confBundle.message = regStatus.message['conf']; - } else { - logger.info("settings.json.BKP configuration file saved to " + SETTINGS + ".BKP"); - } - }); + exports.setConf([confBundle]).then( + function (msg) { - }, - function (error){ - logger.error('[WAMP] - Error board provisioning: ' + JSON.stringify(error)); - logger.info("Bye!"); - process.exit(1); - } - - ); + console.log(msg); + d.resolve(msg); + + //Restarting LR + setTimeout(function () { + process.exit(); + }, 2000) - } else if(reg_status === "registered"){ - - moduleLoader(board_session, lyt_device); - } else{ + } - logger.error('[CONFIGURATION] - WRONG BOARD STATUS: status allowed "new" or "registerd"!'); - process.exit(); + ) - } + } + else if(regStatus.message['state'] == "registered"){ + logger.info("[SYSTEM] - Board registered - Start module loading... "); + moduleLoader(board_session, lyt_device); + response.message = "Board '"+boardCode+"' is loading modules."; + response.result = "SUCCESS"; + d.resolve(response); + + } else{ + d.resolve("Board "+boardCode+" in wrong status!"); + logger.error('[CONFIGURATION] - WRONG BOARD STATUS: status allowed "new" or "registerd"!'); + process.exit(); + } + - }else { + } + else { + // IF access to IoTronic was rejected - logger.error("[SYSTEM] - Connection to Iotronic "+response.result+" - "+response.message); + logger.error("[SYSTEM] - Connection to Iotronic " + regStatus.result + " - " + regStatus.message); logger.info("[SYSTEM] - Bye"); + d.resolve("Board " + boardCode + " disconnection..."); + process.exit(); + } - - + + return d.promise; }; @@ -453,6 +705,10 @@ exports.execAction = function(args){ var action = args[0]; var params = args[1]; + + logger.info("[SYSTEM] - execAction on board RPC called: '" + action + "' action..."); + + var response = { message: '', result: '' @@ -526,6 +782,41 @@ exports.execAction = function(args){ }); break; + + case 'restart_lr': + + params=JSON.parse(params); + + logger.info("[SYSTEM] --> parameters:\n" + JSON.stringify(params, null, "\t")); + + // activate listener on-exit event after LR exit on-update-conf + logger.debug("[SYSTEM] --> Listener on process 'exit' event activated:"); + logger.debug("[SYSTEM] --> Lightning-rod PID: " + process.pid); + process.on("exit", function () { + + require("child_process").spawn(process.argv.shift(), process.argv, { + cwd: process.cwd(), + detached : true, + stdio: "inherit" + }); + + }); + + logger.info('[SYSTEM] - Restarting Lightning-rod'); + response.message = "Restarting Lightning-rod on board "+ boardCode; + response.result = "SUCCESS"; + d.resolve(response); + + //Restarting LR + setTimeout(function () { + + process.exit(); + + }, params["time"]); + + + break; + default: response.message = "Board operation '" + action + "' not supported!"; @@ -547,18 +838,27 @@ exports.execAction = function(args){ -exports.exportManagementCommands = function (session) { + + +exports.IotronicLogin = function (session) { board_session = session; + session.register('s4t.' + session._id + '.board.checkRegistrationStatus', exports.checkRegistrationStatus); + + manage_WAMP_connection(session) + +}; + + + +exports.exportManagementCommands = function (session, callback) { + //Register all the module functions as WAMP RPCs logger.info('[WAMP-EXPORTS] Management commands exported to the cloud!'); session.register('s4t.' + boardCode + '.board.setBoardPosition', exports.setBoardPosition); - session.register('s4t.' + boardCode + '.board.checkRegistrationStatus', exports.checkRegistrationStatus); session.register('s4t.' + boardCode + '.board.execAction', exports.execAction); - //session.register('s4t.' + boardCode + '.board.updateConf', exports.updateConf); + session.register('s4t.' + boardCode + '.board.updateConf', exports.updateConf); - manage_WAMP_connection(session) - }; diff --git a/device/lyt_android.js b/device/lyt_android.js index e737d76..220b148 100644 --- a/device/lyt_android.js +++ b/device/lyt_android.js @@ -43,13 +43,7 @@ AndroidDevice.prototype.Main = function (wampConnection, logger) { logger.info('[WAMP] - Opening connection to WAMP server...'); wampConnection.open(); //----------------------------------------------------------------------------------------------------- - - // PLUGINS RESTART ALL -------------------------------------------------------------------------------- - //This procedure restarts all plugins in "ON" status - var managePlugins = require('../modules/plugins-manager/manage-plugins'); - managePlugins.pluginsLoader(); - //----------------------------------------------------------------------------------------------------- - + }; diff --git a/device/lyt_arduino_yun.js b/device/lyt_arduino_yun.js index 3dd97f3..852bb45 100644 --- a/device/lyt_arduino_yun.js +++ b/device/lyt_arduino_yun.js @@ -22,29 +22,29 @@ var util = require('util'); var Device = require('./Device'); function ArduinoYunDevice(name) { - + Device.call(this); this.name = name; /* - Digital PIN mapping on lininoIO - - ------------------------- - GPIO n. PIN - 104 D8 - 105 D9 - 106 D10 - 107 D11 - 114 D5 - 115 D13 - 116 D3 - 117 D2 - 120 D4 - 122 D12 - 123 D6 - - */ + Digital PIN mapping on lininoIO + + ------------------------- + GPIO n. PIN + 104 D8 + 105 D9 + 106 D10 + 107 D11 + 114 D5 + 115 D13 + 116 D3 + 117 D2 + 120 D4 + 122 D12 + 123 D6 + + */ } @@ -64,20 +64,14 @@ ArduinoYunDevice.prototype.Main = function (wampConnection, logger){ //Given the way linino lib is designed we first need to connect to the board and only then we can do anything else board.connect(function() { - + // CONNECTION TO WAMP SERVER -------------------------------------------------------------------------- logger.info('[WAMP] - Opening connection to WAMP server...'); wampConnection.open(); //----------------------------------------------------------------------------------------------------- - // PLUGINS RESTART ALL -------------------------------------------------------------------------------- - //This procedure restarts all plugins in "ON" status - var managePlugins = require('../modules/plugins-manager/manage-plugins'); - managePlugins.pluginsLoader(); - //----------------------------------------------------------------------------------------------------- - }); - + }; @@ -97,7 +91,7 @@ ArduinoYunDevice.prototype.readDigital = function(args, callback) { logger.error("[GPIO] - readDigital: " + response.message); callback(response); } - + }; diff --git a/device/lyt_orange_pi.js b/device/lyt_orange_pi.js new file mode 100644 index 0000000..50c9d41 --- /dev/null +++ b/device/lyt_orange_pi.js @@ -0,0 +1,99 @@ +//############################################################################################ +//## +//# Copyright (C) 2017 Nicola Peditto +//## +//# Licensed under the Apache License, Version 2.0 (the "License"); +//# you may not use this file except in compliance with the License. +//# You may obtain a copy of the License at +//## +//# http://www.apache.org/licenses/LICENSE-2.0 +//## +//# Unless required by applicable law or agreed to in writing, software +//# distributed under the License is distributed on an "AS IS" BASIS, +//# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//# See the License for the specific language governing permissions and +//# limitations under the License. +//## +//############################################################################################ + + +var util = require('util'); + +var Device = require('./Device'); + +function OrangePiDevice(name) { + + Device.call(this); + + this.name = name; + +} + +util.inherits(OrangePiDevice, Device); + + +var response = { + message: '', + result: '' +}; + +OrangePiDevice.prototype.Main = function (wampConnection, logger) { + + // CONNECTION TO WAMP SERVER -------------------------------------------------------------------------- + logger.info('[WAMP] - Opening connection to WAMP server...'); + wampConnection.open(); + //----------------------------------------------------------------------------------------------------- + +}; + + + + + + +OrangePiDevice.prototype.readDigital = function(args, callback) { + response.message = "Not implemented"; + response.result = "ERROR"; + logger.info("[GPIO] - readDigital: " + response.message); + callback(response); + +}; + + +OrangePiDevice.prototype.readAnalog = function(args, callback) { + response.message = "Not implemented"; + response.result = "ERROR"; + logger.info("[GPIO] - readAnalog: " + response.message); + callback(response); + +}; + + +OrangePiDevice.prototype.writeAnalog = function(args, callback) { + response.message = "Not implemented"; + response.result = "ERROR"; + logger.info("[GPIO] - writeAnalog: " + response.message); + callback(response); + +}; + + +OrangePiDevice.prototype.writeDigital = function(args, callback) { + response.message = "Not implemented"; + response.result = "ERROR"; + logger.info("[GPIO] - writeDigital: " + response.message); + callback(response); + +}; + + +OrangePiDevice.prototype.setMode = function(args, callback) { + response.message = "Not implemented"; + response.result = "ERROR"; + logger.info("[GPIO] - setMode: " + response.message); + callback(response); + +}; + + +module.exports = OrangePiDevice; \ No newline at end of file diff --git a/device/lyt_raspberry_pi.js b/device/lyt_raspberry_pi.js index 5f6526d..064d362 100644 --- a/device/lyt_raspberry_pi.js +++ b/device/lyt_raspberry_pi.js @@ -44,12 +44,6 @@ RaspberryPiDevice.prototype.Main = function (wampConnection, logger) { wampConnection.open(); //----------------------------------------------------------------------------------------------------- - // PLUGINS RESTART ALL -------------------------------------------------------------------------------- - //This procedure restarts all plugins in "ON" status - var managePlugins = require('../modules/plugins-manager/manage-plugins'); - managePlugins.pluginsLoader(); - //----------------------------------------------------------------------------------------------------- - }; diff --git a/device/lyt_server.js b/device/lyt_server.js index 49fde12..aa91d4f 100644 --- a/device/lyt_server.js +++ b/device/lyt_server.js @@ -44,12 +44,6 @@ ServerDevice.prototype.Main = function (wampConnection, logger) { wampConnection.open(); //----------------------------------------------------------------------------------------------------- - // PLUGINS RESTART ALL -------------------------------------------------------------------------------- - //This procedure restarts all plugins in "ON" status - var managePlugins = require('../modules/plugins-manager/manage-plugins'); - managePlugins.pluginsLoader(); - //----------------------------------------------------------------------------------------------------- - }; diff --git a/docs/arduinoyun.md b/docs/arduinoyun.md index 4708928..44a0ed9 100644 --- a/docs/arduinoyun.md +++ b/docs/arduinoyun.md @@ -20,6 +20,7 @@ opkg install kmod-gre kmod-ip6-tunnel kmod-iptunnel4 kmod-iptunnel6 kmod-ipv6 km ## Install from NPM ``` npm install -g --unsafe @mdslab/wstun + npm install -g --unsafe @mdslab/iotronic-lightning-rod ``` If you have some problems during npm dependencies installation phase we suggest you to follow the "Install from source-code" procedure. @@ -30,7 +31,7 @@ If you have some problems during npm dependencies installation phase we suggest ##### Install required NodeJS modules via npm ``` -npm install -g requestify is-running connection-tester@0.1.2 log4js@1.1.1 q fs-access util +npm install -g requestify is-running connection-tester@0.1.2 log4js@1.1.1 q fs-access util md5 python-shell net ``` ##### Install the Lightning-rod diff --git a/docs/bundling.md b/docs/bundling.md deleted file mode 100644 index 7a86df7..0000000 --- a/docs/bundling.md +++ /dev/null @@ -1,38 +0,0 @@ -# Bundled distribution - -## Introduction - -That is a single JavaScript file which contains LR source code and the most of `node_modules` -dependencies. - -It is intended to deploy those bundles instead of full-blown source code and `node_modules` to -the boards. This provides some benefits, like faster bootstraping (no need to do many filesystem -lookups for loading CommonJS modules), less `node_modules` directory size on a board (which is -significant on slow boards). - -## Maintenance - -However, some npm packages cannot be bundled (see the `build:index.js` script in the -`/package.json`), such as: - -- `log4js` - loads appenders from a filesystem -- `nconf` - loads stores from a filesystem -- `wstun` - is used by LR as a program, not as a CommonJS module. -- `ideino-linino-lib` - Arduino Yun specific - not needed on other boards. Must be installed -manually. -- `fuse-bindings` - Platform-specific, contains binary objects. Not needed on Android. Must be -installed manually. - -So these non-bundled dependencies are still required to be installed on a board. For convenience, -they're listed in the `/package.dist.json` file. It it important to keep it in sync with the -`/package.json`. - -## Usage - -- `npm install` - Installs all the required dependencies from the `/package.json` locally. -- `npm run build` - Builds a bundled distribution to the `/dist` folder with a bundle (`index.js`) -and a `package.json` with all the non-bundled dependencies. -- Transfer the `/dist` directory to a board. -- `cd` to that directory on a board and run `npm install` to install non-bundled dependencies -locally. -- Use `npm start` to launch the LR, or simply `node index.js`. diff --git a/docs/raspberrypi2.md b/docs/raspberrypi2.md index 08a2b8f..51bf602 100644 --- a/docs/raspberrypi2.md +++ b/docs/raspberrypi2.md @@ -45,7 +45,10 @@ reboot ## Install from NPM ``` npm install -g --unsafe @mdslab/wstun + npm install -g --unsafe @mdslab/iotronic-lightning-rod + +reboot ``` @@ -54,7 +57,7 @@ npm install -g --unsafe @mdslab/iotronic-lightning-rod ##### Install required NodeJS modules via npm: ``` -npm install -g --unsafe gyp autobahn nconf @mdslab/wstun fuse-bindings requestify is-running connection-tester log4js@1.1.1 q fs-access mknod jsonfile +npm install -g --unsafe gyp autobahn nconf @mdslab/wstun fuse-bindings requestify is-running connection-tester log4js@1.1.1 q fs-access mknod jsonfile md5 python-shell net node-red npm install -g --unsafe https://github.com/PlayNetwork/node-statvfs/tarball/v3.0.0 ``` @@ -82,6 +85,8 @@ source /etc/environment > /dev/null cp $NODE_PATH/@mdslab/iotronic-lightning-rod/settings.example.json /var/lib/iotronic/settings.json cp $NODE_PATH/@mdslab/iotronic-lightning-rod/modules/plugins-manager/plugins.example.json /var/lib/iotronic/plugins/plugins.json cp $NODE_PATH/@mdslab/iotronic-lightning-rod/modules/drivers-manager/drivers.example.json /var/lib/iotronic/drivers/drivers.json + +reboot ``` diff --git a/docs/raspberrypi3.md b/docs/raspberrypi3.md index 2cfb2bc..cce5283 100644 --- a/docs/raspberrypi3.md +++ b/docs/raspberrypi3.md @@ -45,7 +45,10 @@ reboot ## Install from NPM ``` npm install -g --unsafe @mdslab/wstun + npm install -g --unsafe @mdslab/iotronic-lightning-rod + +reboot ``` @@ -54,7 +57,7 @@ npm install -g --unsafe @mdslab/iotronic-lightning-rod ##### Install required NodeJS modules via npm: ``` -npm install -g --unsafe gyp autobahn nconf @mdslab/wstun fuse-bindings requestify is-running connection-tester log4js@1.1.1 q fs-access mknod jsonfile +npm install -g --unsafe gyp autobahn nconf @mdslab/wstun fuse-bindings requestify is-running connection-tester log4js@1.1.1 q fs-access mknod jsonfile md5 python-shell net node-red npm install -g --unsafe https://github.com/PlayNetwork/node-statvfs/tarball/v3.0.0 ``` @@ -82,6 +85,8 @@ source /etc/environment > /dev/null cp $NODE_PATH/@mdslab/iotronic-lightning-rod/settings.example.json /var/lib/iotronic/settings.json cp $NODE_PATH/@mdslab/iotronic-lightning-rod/modules/plugins-manager/plugins.example.json /var/lib/iotronic/plugins/plugins.json cp $NODE_PATH/@mdslab/iotronic-lightning-rod/modules/drivers-manager/drivers.example.json /var/lib/iotronic/drivers/drivers.json + +reboot ``` diff --git a/docs/ubuntu1404.md b/docs/ubuntu1404.md index 5225d60..6b8a7d1 100644 --- a/docs/ubuntu1404.md +++ b/docs/ubuntu1404.md @@ -40,8 +40,11 @@ reboot ## Install from NPM ``` npm install -g --unsafe @mdslab/wstun + npm install -g --unsafe @mdslab/iotronic-lightning-rod +reboot + ``` @@ -49,7 +52,7 @@ npm install -g --unsafe @mdslab/iotronic-lightning-rod ##### Install required NodeJS modules via npm: ``` -npm install -g --unsafe gyp autobahn nconf @mdslab/wstun fuse-bindings requestify is-running connection-tester log4js@1.1.1 q fs-access mknod jsonfile +npm install -g --unsafe gyp autobahn nconf @mdslab/wstun fuse-bindings requestify is-running connection-tester log4js@1.1.1 q fs-access mknod jsonfile md5 python-shell net node-red npm install -g --unsafe https://github.com/PlayNetwork/node-statvfs/tarball/v3.0.0 ``` @@ -78,6 +81,8 @@ source /etc/environment > /dev/null cp /var/lib/iotronic/iotronic-lightning-rod/settings.example.json /var/lib/iotronic/settings.json cp /var/lib/iotronic/iotronic-lightning-rod/plugins.example.json /var/lib/iotronic/plugins/plugins.json cp /var/lib/iotronic/iotronic-lightning-rod/drivers.example.json /var/lib/iotronic/drivers/drivers.json + +reboot ``` @@ -115,4 +120,4 @@ nano /etc/logrotate.d/lightning-rod.log /etc/init.d/lightning-rod start tail -f /var/log/iotronic/lightning-rod.log -``` +``` \ No newline at end of file diff --git a/docs/ubuntu1604.md b/docs/ubuntu1604.md index b1419eb..290e36b 100644 --- a/docs/ubuntu1604.md +++ b/docs/ubuntu1604.md @@ -39,11 +39,13 @@ echo $NODE_PATH reboot ``` - ## Install from NPM ``` npm install -g --unsafe @mdslab/wstun + npm install -g --unsafe @mdslab/iotronic-lightning-rod + +reboot ``` @@ -52,7 +54,7 @@ npm install -g --unsafe @mdslab/iotronic-lightning-rod ##### Install required NodeJS modules via npm: ``` -npm install -g --unsafe gyp autobahn nconf @mdslab/wstun fuse-bindings requestify is-running connection-tester log4js@1.1.1 q fs-access mknod jsonfile +npm install -g --unsafe gyp autobahn nconf @mdslab/wstun fuse-bindings requestify is-running connection-tester log4js@1.1.1 q fs-access mknod jsonfile md5 python-shell net node-red npm install -g --unsafe https://github.com/PlayNetwork/node-statvfs/tarball/v3.0.0 ``` @@ -80,6 +82,8 @@ source /etc/environment > /dev/null cp $NODE_PATH/@mdslab/iotronic-lightning-rod/settings.example.json /var/lib/iotronic/settings.json cp $NODE_PATH/@mdslab/iotronic-lightning-rod/modules/plugins-manager/plugins.example.json /var/lib/iotronic/plugins/plugins.json cp $NODE_PATH/@mdslab/iotronic-lightning-rod/modules/drivers-manager/drivers.example.json /var/lib/iotronic/drivers/drivers.json + +reboot ``` diff --git a/etc/cron.d/root_openwrt b/etc/cron.d/root_openwrt index f21d1ef..934374e 100644 --- a/etc/cron.d/root_openwrt +++ b/etc/cron.d/root_openwrt @@ -1,2 +1,3 @@ * * * * * /etc/init.d/lightning-rod start +0 0 * * 0 /usr/sbin/logrotate /etc/logrotate.d/lightning-rod.log */10 * * * * ntpdate 0.pool.ntp.org \ No newline at end of file diff --git a/etc/cron.d/root_yun b/etc/cron.d/root_yun index f21d1ef..e0b8b90 100644 --- a/etc/cron.d/root_yun +++ b/etc/cron.d/root_yun @@ -1,2 +1,3 @@ * * * * * /etc/init.d/lightning-rod start -*/10 * * * * ntpdate 0.pool.ntp.org \ No newline at end of file +0 0 * * 0 /usr/sbin/logrotate /etc/logrotate.d/lightning-rod.log +*/10 * * * * ntpdate 0.pool.ntp.org diff --git a/etc/init.d/s4t-lightning-rod_openwrt b/etc/init.d/s4t-lightning-rod_openwrt index 849681f..4a1c752 100644 --- a/etc/init.d/s4t-lightning-rod_openwrt +++ b/etc/init.d/s4t-lightning-rod_openwrt @@ -12,7 +12,7 @@ export NODEJS=/usr/bin/node export LD_LIBRARY_PATH=/usr/lib export NPM=/usr/bin/npm export NODE_PATH=/usr/lib/node_modules:/usr/lib/node_modules:$NODE_PATH - +export NODE_TLS_REJECT_UNAUTHORIZED=0 export IOTRONIC_HOME=/var/lib/iotronic diff --git a/etc/init.d/s4t-lightning-rod_raspberry b/etc/init.d/s4t-lightning-rod_raspberry old mode 100755 new mode 100644 index 4a222e9..e30e229 --- a/etc/init.d/s4t-lightning-rod_raspberry +++ b/etc/init.d/s4t-lightning-rod_raspberry @@ -8,7 +8,7 @@ # Description: The\\\ IoTronic\\\ board-side\\\ probe ### END INIT INFO -SCRIPT="node /usr/lib/node_modules/iotronic-lightning-rod/lightning-rod.js" +SCRIPT="node $NODE_PATH/iotronic-lightning-rod/lightning-rod.js" RUNAS=root NAME=lightning-rod diff --git a/etc/init.d/s4t-lightning-rod_ubu14 b/etc/init.d/s4t-lightning-rod_ubu14 index ea0fdba..1e459ca 100644 --- a/etc/init.d/s4t-lightning-rod_ubu14 +++ b/etc/init.d/s4t-lightning-rod_ubu14 @@ -3,7 +3,7 @@ export NODE_PATH=/usr/lib/node_modules:$NODE_PATH NAME=lightning-rod -FOLDER=/usr/lib/node_modules/@mdslab/iotronic-lightning-rod +FOLDER=$NODE_PATH/@mdslab/iotronic-lightning-rod DAEMON=$FOLDER/lr-server.js DESC=lightning-rod LOGFILE="/var/log/iotronic/lightning-rod.log" diff --git a/etc/init.d/s4t-lightning-rod_yun b/etc/init.d/s4t-lightning-rod_yun old mode 100755 new mode 100644 index 849681f..4a1c752 --- a/etc/init.d/s4t-lightning-rod_yun +++ b/etc/init.d/s4t-lightning-rod_yun @@ -12,7 +12,7 @@ export NODEJS=/usr/bin/node export LD_LIBRARY_PATH=/usr/lib export NPM=/usr/bin/npm export NODE_PATH=/usr/lib/node_modules:/usr/lib/node_modules:$NODE_PATH - +export NODE_TLS_REJECT_UNAUTHORIZED=0 export IOTRONIC_HOME=/var/lib/iotronic diff --git a/etc/logrotate.d/lightning-rod.log b/etc/logrotate.d/lightning-rod.log index 57aa153..cd67b1f 100644 --- a/etc/logrotate.d/lightning-rod.log +++ b/etc/logrotate.d/lightning-rod.log @@ -1,4 +1,4 @@ -/var/log/iotronic/lightning-rod.log { +/var/log/iotronic/lightning-rod.log /var/log/iotronic/plugins/*.log { copytruncate create missingok diff --git a/etc/systemd/system/s4t-lightning-rod.service b/etc/systemd/system/s4t-lightning-rod.service index f0db455..b07f902 100644 --- a/etc/systemd/system/s4t-lightning-rod.service +++ b/etc/systemd/system/s4t-lightning-rod.service @@ -11,6 +11,7 @@ StandardOutput=journal StandardError=journal WorkingDirectory=${LIGHTNINGROD_HOME} Environment="NODE_PATH=/usr/lib/node_modules" +Environment="NODE_TLS_REJECT_UNAUTHORIZED=0" Environment="IOTRONIC_HOME=/var/lib/iotronic" Environment="LIGHTNINGROD_HOME=" ExecStart=/usr/bin/node ${LIGHTNINGROD_HOME}/lightning-rod.js diff --git a/lightning-rod.js b/lightning-rod.js index 8b62dff..0cfccde 100644 --- a/lightning-rod.js +++ b/lightning-rod.js @@ -35,28 +35,40 @@ logger = log4js.getLogger('main'); // Device settings boardCode = null; //valued in checkSettings -board_position = null; //valued by Iotronic RPC funcion (provisioning) +boardLabel = null; //valued in checkSettings +board_position = null; //valued by Iotronic (via updateConf, setConf or setBoardPosition RPCs) reg_status = null; //valued in checkSettings device = null; //valued in checkSettings lyt_device = null; //valued here in main - +net_backend=''; +wampIP = null; // To test the connection status -online = true; // We use this flag during the process of connection recovery reconnected = false; // We use this flag to identify the connection status of reconnected after a connection fault -keepWampAlive = null; // It is a timer related to the function that every "X" seconds/minutes checks the connection status -var tcpkill_pid = null; // PID of tcpkill process spawned to manage the connection recovery process -wamp_check = null; // "false" = we need to restore the WAMP connection (with tcpkill). "true" = the WAMP connection is enstablished or the standard reconnection procedure was triggered by the WAMP client and managed by "onclose" precedure. +//WIFI HACK +wifi_force_reconnect = nconf.get('config:wamp:wifi_force_reconnect'); //hack = true; -// LR s4t libraries -var manageBoard = require('./board-management'); +if(wifi_force_reconnect == true || wifi_force_reconnect == "true"){ + wifi_force_reconnect_time = undefined;//nconf.get('config:wamp:wifi_force_reconnect_time'); + if (isNaN(wifi_force_reconnect_time)) + wifi_force_reconnect_time = 60; //set default value -net_backend=''; + keepWampAlive = null; // It is a timer related to the function that every "X" seconds/minutes checks the connection status + online = true; // We use this flag during the process of connection recovery + wamp_check = null; // "false" = we need to restore the WAMP connection (with tcpkill). "true" = the WAMP connection is enstablished or the standard reconnection procedure was triggered by the WAMP client and managed by "onclose" precedure. + var tcpkill_pid = null; // PID of tcpkill process spawned to manage the connection recovery process +} + + +// LR s4t libraries +var manageBoard = require('./board-management'); +var LIGHTNINGROD_HOME = process.env.LIGHTNINGROD_HOME; + //Init_Ligthning_Rod(function (check) { manageBoard.Init_Ligthning_Rod(function (check) { @@ -75,29 +87,31 @@ manageBoard.Init_Ligthning_Rod(function (check) { }else{ // Configuration file is correctly configured... Starting LR... - - - var wampUrl = nconf.get('config:wamp:url_wamp')+":"+nconf.get('config:wamp:port_wamp')+"/ws"; - var wampIP = wampUrl.split("//")[1].split(":")[0]; - logger.info("[SYSTEM] - Iotronic server IP: "+wampIP); logger.info('[SYSTEM] - DEVICE: ' + device); //---------------------------------------- // 1. Set WAMP connection configuration //---------------------------------------- var wampUrl = nconf.get('config:wamp:url_wamp')+":"+nconf.get('config:wamp:port_wamp')+"/ws"; + wampIP = wampUrl.split("//")[1].split(":")[0]; var wampRealm = nconf.get('config:wamp:realm'); + + logger.info("[SYSTEM] - Iotronic server IP: "+wampIP); + + var wampConnection = new autobahn.Connection({ url: wampUrl, realm: wampRealm, max_retries: -1 }); - var wampIP = wampUrl.split("//")[1].split(":")[0]; - logger.info("[SYSTEM] - WAMP server IP: "+wampIP); + logger.info("[SYSTEM] - WAMP server IP: " + wampIP); + + logger.info("[SYSTEM] - Node ID: " + boardCode); + + logger.debug("[SYSTEM] - Wifi force reconnection: " + wifi_force_reconnect); - logger.info("[SYSTEM] - Node ID: "+boardCode); //---------------------------------------------------------------------------------------- @@ -110,7 +124,7 @@ manageBoard.Init_Ligthning_Rod(function (check) { logger.info('[WAMP] |--> Realm: '+ wampRealm); logger.info('[WAMP] |--> Session ID: '+ session._id); //logger.debug('[WAMP] |--> Connection details:\n'+ JSON.stringify(details)); - + logger.info('[WAMP] |--> WIFI-HACK: '+ wifi_force_reconnect + " - Timing: " + wifi_force_reconnect_time + " seconds"); // Test if IoTronic is connected to the realm session.call("s4t.iotronic.isAlive", [boardCode]).then( @@ -119,214 +133,222 @@ manageBoard.Init_Ligthning_Rod(function (check) { logger.info("[SYSTEM] - " + response.message ); - if (keepWampAlive != null){ + // RPC registration of Board Management Commands + //manageBoard.exportManagementCommands(session); + manageBoard.IotronicLogin(session); - //We trigger this event after a connection recovery: we are deleting the previous timer for the function that checks the connection status - clearInterval( keepWampAlive ); - logger.info('[WAMP-RECOVERY] - WAMP CONNECTION RECOVERED!'); - logger.debug('[WAMP-RECOVERY] - Old timer to keep alive WAMP connection cleared!'); - reconnected = true; - } + if(wifi_force_reconnect == true || wifi_force_reconnect == "true"){ - // RPC registration of Board Management Commands - manageBoard.exportManagementCommands(session); + //---------------------------------------------------------------------------------------------------- + // THIS IS AN HACK TO FORCE RECONNECTION AFTER A BREAK OF INTERNET CONNECTION + //---------------------------------------------------------------------------------------------------- - //---------------------------------------------------------------------------------------------------- - // THIS IS AN HACK TO FORCE RECONNECTION AFTER A BREAK OF INTERNET CONNECTION - //---------------------------------------------------------------------------------------------------- - // The function managed by setInterval checks the connection status every "X" TIME - keepWampAlive = setInterval(function(){ + if (keepWampAlive != null){ - // connectionTester: library used to check the reachability of Iotronic-Server/WAMP-Server - var connectionTester = require('connection-tester'); - connectionTester.test(wampIP, port_wamp, 1000, function (err, output) { + //We trigger this event after a connection recovery: we are deleting the previous timer for the function that checks the connection status + clearInterval( keepWampAlive ); + logger.info('[WAMP-RECOVERY] - WAMP CONNECTION RECOVERED!'); + logger.debug('[WAMP-RECOVERY] - Old timer to keep alive WAMP connection cleared!'); + reconnected = true; - //logger.debug("[WAMP] - CONNECTION STATUS: "+JSON.stringify(output)); + } - var reachable = output.success; - var error_test = output.error; - //logger.debug("[WAMP] - CONNECTION STATUS: "+reachable); + // The function managed by setInterval checks the connection status every "X" TIME + keepWampAlive = setInterval(function(){ - if(!reachable){ + // connectionTester: library used to check the reachability of Iotronic-Server/WAMP-Server + var connectionTester = require('connection-tester'); + connectionTester.test(wampIP, port_wamp, 1000, function (err, output) { - //CONNECTION STATUS: FALSE - logger.warn("[CONNECTION-RECOVERY] - INTERNET CONNECTION STATUS: " + reachable + " - ERROR: " + error_test); - wamp_check = false; - online = false; + var reachable = output.success; + var error_test = output.error; - } else { + if(!reachable){ - //CONNECTION STATUS: TRUE - try{ + //CONNECTION STATUS: FALSE + logger.warn("[CONNECTION-RECOVERY] - INTERNET CONNECTION STATUS: " + reachable + " - ERROR: " + error_test); + wamp_check = false; + online = false; - if(!online){ + } else { - // In the previous checks the "online" flag was set to FALSE. - // The connection is come back ("online" is TRUE) + //CONNECTION STATUS: TRUE + try{ - logger.info("[CONNECTION-RECOVERY] - INTERNET CONNECTION STATUS: " + reachable); - logger.info("[CONNECTION-RECOVERY] ---> INTERNET CONNECTION RECOVERED!"); - logger.info("[WAMP-RECOVERY] - WAMP connection checks started..."); + if(!online){ - // Test if IoTronic is connected to the realm - session.call("s4t.iotronic.isAlive", [boardCode]).then( + // In the previous checks the "online" flag was set to FALSE. + // The connection is come back ("online" is TRUE) - function(response){ + logger.info("[CONNECTION-RECOVERY] - INTERNET CONNECTION STATUS: " + reachable); + logger.info("[CONNECTION-RECOVERY] ---> INTERNET CONNECTION RECOVERED!"); + logger.info("[WAMP-RECOVERY] - WAMP connection checks started..."); - // WAMP connection is established and the previous connection fault didn't compromise the WAMP socket - // so we don't need to restore the WAMP connection and we set the connection status ("online") to TRUE. - wamp_check = true; - logger.info("[WAMP-RECOVERY] - WAMP CONNECTION STATUS: " + response.message); - online = true; + // Test if IoTronic is connected to the realm + session.call("s4t.iotronic.isAlive", [boardCode]).then( - }, - function (err) { + function(response){ - // IoTronic is not connected to the realm yet so LR need to try to reconnect later - wamp_check = false; - logger.debug("[WAMP-RECOVERY] - WAMP CONNECTION STATUS: " + JSON.stringify(err)); + // WAMP connection is established and the previous connection fault didn't compromise the WAMP socket + // so we don't need to restore the WAMP connection and we set the connection status ("online") to TRUE. + wamp_check = true; + logger.info("[WAMP-RECOVERY] - WAMP CONNECTION STATUS: " + response.message); + online = true; - setTimeout(function(){ + }, + function (err) { - // WAMP CONNECTION IS NOT ESTABLISHED: if after a connection fault the WAMP connection recovery procedure - // didn't start automatically we need to KILL the WAMP socket through the TCPKILL tool - // (problem noticed in WIFI connection with DSL internet connection). + // IoTronic is not connected to the realm yet so LR need to try to reconnect later + wamp_check = false; + logger.debug("[WAMP-RECOVERY] - WAMP CONNECTION STATUS: " + JSON.stringify(err)); - logger.warn("[WAMP-RECOVERY] - WAMP CONNECTION STATUS: " + wamp_check); + setTimeout(function(){ - // Check if the tcpkill process was killed after a previous connection recovery through this check we will avoid to start another tcpkill process - var tcpkill_status = running(tcpkill_pid); + // WAMP CONNECTION IS NOT ESTABLISHED: if after a connection fault the WAMP connection recovery procedure + // didn't start automatically we need to KILL the WAMP socket through the TCPKILL tool + // (problem noticed in WIFI connection with DSL internet connection). - logger.warn("[WAMP-RECOVERY] - TCPKILL STATUS: " + tcpkill_status + " - PID: " + tcpkill_pid); + logger.warn("[WAMP-RECOVERY] - WAMP CONNECTION STATUS: " + wamp_check); - // at LR startup "tcpkill_pid" is NULL and in this condition "is-running" module return "true" that is a WRONG result! - if (tcpkill_status === false || tcpkill_pid == null){ + // Check if the tcpkill process was killed after a previous connection recovery through this check we will avoid to start another tcpkill process + var tcpkill_status = running(tcpkill_pid); - logger.warn("[WAMP-RECOVERY] - Cleaning WAMP socket..."); - var tcpkill_kill_count = 0; + logger.warn("[WAMP-RECOVERY] - TCPKILL STATUS: " + tcpkill_status + " - PID: " + tcpkill_pid); - //tcpkill -9 port 8181 - var tcpkill = spawn('tcpkill',['-9','port','8181']); - tcpkill_pid = tcpkill.pid; + // at LR startup "tcpkill_pid" is NULL and in this condition "is-running" module return "true" that is a WRONG result! + if (tcpkill_status === false || tcpkill_pid == null){ - tcpkill.stdout.on('data', function (data) { - logger.debug('[WAMP-RECOVERY] ... tcpkill stdout: ' + data); - }); + logger.warn("[WAMP-RECOVERY] - Cleaning WAMP socket..."); + var tcpkill_kill_count = 0; - tcpkill.stderr.on('data', function (data) { + //tcpkill -9 port 8181 + var tcpkill = spawn('tcpkill',['-9','port','8181']); + tcpkill_pid = tcpkill.pid; - logger.debug('[WAMP-RECOVERY] ... tcpkill stderr:\n' + data); + tcpkill.stdout.on('data', function (data) { + logger.debug('[WAMP-RECOVERY] ... tcpkill stdout: ' + data); + }); - //it will check if tcpkill is in listening state on the port 8181 - if(data.toString().indexOf("listening") > -1){ + tcpkill.stderr.on('data', function (data) { - // LISTENING: to manage the starting of tcpkill (listening on port 8181) + logger.debug('[WAMP-RECOVERY] ... tcpkill stderr:\n' + data); - }else if (data.toString().indexOf("win 0") > -1){ + //it will check if tcpkill is in listening state on the port 8181 + if(data.toString().indexOf("listening") > -1){ - // TCPKILL DETECTED WAMP ACTIVITY (WAMP reconnection attempts) - // This is the stage triggered when the WAMP socket was killed by tcpkill and WAMP reconnection process automaticcally started: - // in this phase we need to kill tcpkill to allow WAMP reconnection. - try{ + // LISTENING: to manage the starting of tcpkill (listening on port 8181) - logger.debug('[WAMP-RECOVERY] ... killing tcpkill process with PID: ' + tcpkill_pid); - process.kill(tcpkill_pid); + }else if (data.toString().indexOf("win 0") > -1){ - //double check: It will test after a while if the tcpkill process has been killed - setTimeout(function(){ + // TCPKILL DETECTED WAMP ACTIVITY (WAMP reconnection attempts) + // This is the stage triggered when the WAMP socket was killed by tcpkill and WAMP reconnection process automaticcally started: + // in this phase we need to kill tcpkill to allow WAMP reconnection. + try{ - if ( running(tcpkill_pid) || tcpkill_pid == null){ + logger.debug('[WAMP-RECOVERY] ... killing tcpkill process with PID: ' + tcpkill_pid); + process.kill(tcpkill_pid); - tcpkill_kill_count = tcpkill_kill_count + 1; + //double check: It will test after a while if the tcpkill process has been killed + setTimeout(function(){ - logger.warn("[WAMP-RECOVERY] ... tcpkill still running!!! PID ["+tcpkill_pid+"]"); - logger.debug('[WAMP-RECOVERY] ... tcpkill killing retry_count '+ tcpkill_kill_count); + if ( running(tcpkill_pid) || tcpkill_pid == null){ - tcpkill.kill('SIGINT'); + tcpkill_kill_count = tcpkill_kill_count + 1; - } + logger.warn("[WAMP-RECOVERY] ... tcpkill still running!!! PID ["+tcpkill_pid+"]"); + logger.debug('[WAMP-RECOVERY] ... tcpkill killing retry_count '+ tcpkill_kill_count); - }, 3000); + tcpkill.kill('SIGINT'); + } - }catch (e) { + }, 3000); - logger.error('[WAMP-RECOVERY] ... tcpkill killing error: ', e); - } + }catch (e) { - tcpkill.kill('SIGINT'); + logger.error('[WAMP-RECOVERY] ... tcpkill killing error: ', e); - //double check: It will test after a while if the tcpkill process has been killed - setTimeout(function(){ + } - if ( running(tcpkill_pid) || tcpkill_pid == null){ + tcpkill.kill('SIGINT'); - tcpkill_kill_count = tcpkill_kill_count + 1; + //double check: It will test after a while if the tcpkill process has been killed + setTimeout(function(){ - logger.warn("[WAMP-RECOVERY] ... tcpkill still running!!! PID ["+tcpkill_pid+"]"); - logger.debug('[WAMP-RECOVERY] ... tcpkill killing retry_count '+ tcpkill_kill_count); + if ( running(tcpkill_pid) || tcpkill_pid == null){ - tcpkill.kill('SIGINT'); + tcpkill_kill_count = tcpkill_kill_count + 1; - } + logger.warn("[WAMP-RECOVERY] ... tcpkill still running!!! PID ["+tcpkill_pid+"]"); + logger.debug('[WAMP-RECOVERY] ... tcpkill killing retry_count '+ tcpkill_kill_count); - }, 3000); + tcpkill.kill('SIGINT'); - } + } + }, 3000); - }); + } - tcpkill.on('close', function (code) { - logger.debug('[WAMP-RECOVERY] ... tcpkill killed!'); - logger.info("[WAMP-RECOVERY] - WAMP socket cleaned!"); + }); - //The previous WAMP socket was KILLED and the automatic WAMP recovery process will start - //so the connection recovery is completed and "online" flag is set again to TRUE - online = true; + tcpkill.on('close', function (code) { - }); + logger.debug('[WAMP-RECOVERY] ... tcpkill killed!'); + logger.info("[WAMP-RECOVERY] - WAMP socket cleaned!"); - }else{ + //The previous WAMP socket was KILLED and the automatic WAMP recovery process will start + //so the connection recovery is completed and "online" flag is set again to TRUE + online = true; - logger.warn('[WAMP-RECOVERY] ...tcpkill already started!'); + }); - } + }else{ + logger.warn('[WAMP-RECOVERY] ...tcpkill already started!'); - }, 2 * 1000); + } - } + }, 2 * 1000); - ); + } - } + ); + } + + + } + catch(err){ + logger.warn('[CONNECTION-RECOVERY] - Error keeping alive wamp connection: '+ err); + } + } - catch(err){ - logger.warn('[CONNECTION-RECOVERY] - Error keeping alive wamp connection: '+ err); - } - } + }); + - }); + }, wifi_force_reconnect_time * 1000); + logger.debug('[WAMP] - TIMER to keep alive WAMP connection set up!'); - }, 10 * 1000); + //---------------------------------------------------------------------------------------------------- - logger.debug('[WAMP] - TIMER to keep alive WAMP connection set up!'); + + } }, function (err) { + // IoTronic is not connected to the realm yet so LR need to try to reconnect later logger.error("[SYSTEM] - IoTronic is not online: " + JSON.stringify(err) ); //if (err.error !== 'wamp.error.no_such_procedure') {} @@ -340,12 +362,10 @@ manageBoard.Init_Ligthning_Rod(function (check) { } - ); - + ); - //---------------------------------------------------------------------------------------------------- }; @@ -359,7 +379,9 @@ manageBoard.Init_Ligthning_Rod(function (check) { try{ - wamp_check = true; // IMPORTANT: for ethernet connections this flag avoid to start recovery procedure (tcpkill will not start!) + //WIFI HACK + if(wifi_force_reconnect == true || wifi_force_reconnect == "true") + wamp_check = true; // IMPORTANT: for ethernet connections this flag avoid to start recovery procedure (tcpkill will not start!) logger.error('[WAMP] - Error in connecting to WAMP server!'); logger.error('- Reason: ' + reason); @@ -388,7 +410,7 @@ manageBoard.Init_Ligthning_Rod(function (check) { // 2. The selected device will connect to Iotronic WAMP server //-------------------------------------------------------------- try{ - + IoT_Device = require('./device/lyt_'+device); lyt_device = new IoT_Device(device); logger.info("[SYSTEM] - Lightning-rod "+ lyt_device.name +" starting..."); @@ -405,6 +427,15 @@ manageBoard.Init_Ligthning_Rod(function (check) { } + //-------------------------------------------------------------- + // 5. Load Plugin Manager + //-------------------------------------------------------------- + + manageBoard.moduleLoaderOnBoot(); + + + + } diff --git a/modules/drivers-manager/manage-drivers.js b/modules/drivers-manager/manage-drivers.js index 7bb9632..a13fa8c 100644 --- a/modules/drivers-manager/manage-drivers.js +++ b/modules/drivers-manager/manage-drivers.js @@ -1290,7 +1290,7 @@ function LoadDriver(driver_name, mountpoint, remote, mirror_board){ function MountpointCreation(driver_name, mountpoint, remote, mirror_board, d){ - fs.mkdir(mountpoint, 0755, function() { + fs.mkdir(mountpoint, "0755", function() { logger.debug("[DRIVER] - "+driver_name+" ----> folder "+mountpoint+" CREATED!"); @@ -1488,7 +1488,7 @@ function HumanMaskConversion(mode_b10){ //This function exports all the functions in the module as WAMP remote procedure calls -exports.exportDriverCommands = function (session){ +exports.Init = function (session){ session_drivers = session; @@ -1503,3 +1503,10 @@ exports.exportDriverCommands = function (session){ }; + +//This function executes procedures at boot time (no Iotronic dependent) +exports.Boot = function (){ + + logger.info('[BOOT] - Driver Manager booting procedures not defined.'); + +}; \ No newline at end of file diff --git a/modules/gpio-manager/manage-gpio.js b/modules/gpio-manager/manage-gpio.js index 16014c0..d3fd175 100644 --- a/modules/gpio-manager/manage-gpio.js +++ b/modules/gpio-manager/manage-gpio.js @@ -17,8 +17,8 @@ //## //############################################################################################ -//service logging configuration: "manageGpio" -var logger = log4js.getLogger('manageGpio'); +//service logging configuration: "gpioManager" +var logger = log4js.getLogger('gpioManager'); logger.setLevel(loglevel); var lyt_device = null; @@ -93,7 +93,7 @@ function setMode(args) { } //This function exports all the functions in the module as WAMP remote procedure calls -exports.exportPins = function (session, device) { +exports.Init = function (session, device) { lyt_device = device; @@ -106,4 +106,12 @@ exports.exportPins = function (session, device) { logger.info('[WAMP-EXPORTS] Pins exported to the cloud!'); +}; + + +//This function executes procedures at boot time (no Iotronic dependent) +exports.Boot = function (){ + + logger.info('[BOOT] - GPIO Manager booting procedures not defined.'); + }; \ No newline at end of file diff --git a/modules/nodered-manager/manage-nodered.js b/modules/nodered-manager/manage-nodered.js new file mode 100644 index 0000000..861667e --- /dev/null +++ b/modules/nodered-manager/manage-nodered.js @@ -0,0 +1,253 @@ +//############################################################################################ +//## +//# Copyright (C) 2018 Nicola Peditto +//## +//# Licensed under the Apache License, Version 2.0 (the "License"); +//# you may not use this file except in compliance with the License. +//# You may obtain a copy of the License at +//## +//# http://www.apache.org/licenses/LICENSE-2.0 +//## +//# Unless required by applicable law or agreed to in writing, software +//# distributed under the License is distributed on an "AS IS" BASIS, +//# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//# See the License for the specific language governing permissions and +//# limitations under the License. +//## +//############################################################################################ + + +var logger = log4js.getLogger('noderedManager'); +logger.setLevel(loglevel); + +var requestify = require('requestify'); + +RED = require("node-red"); +var Q = require("q"); +var fs = require("fs"); + +server = null; + +var NODE_RED_HOME = process.env.IOTRONIC_HOME + '/.node-red'; + + +exports.init = function (settings){ + + var d = Q.defer(); + + logger.info("[NODE-RED] - Init..."); + + if(settings.disableEditor){ + + d.resolve( RED.init(false, settings) ) + + } + else{ + + var http = require('http'); + var express = require("express"); + + // Create an Express app + var app = express(); + + // Add a simple route for static content served from 'public' + app.use("/",express.static("public")); + + // Create a server + server = http.createServer(app); + + // Initialise the runtime with a server and settings + RED.init(server, settings); + + // Serve the editor UI from /red + app.use(settings.httpAdminRoot, RED.httpAdmin); + + // Serve the http nodes UI from /api + app.use(settings.httpNodeRoot, RED.httpNode); + + d.resolve(server.listen(8000)); + + } + + return d.promise; + + +}; + + +exports.start = function (){ + + logger.info("[NODE-RED] - Starting..."); + + var d = Q.defer(); + + var response = { + message: '', + result: '' + }; + + // Create the settings object - see default settings.js file for other options + var settings = { + + httpAdminRoot: "/red", + httpNodeRoot: "/", + disableEditor: false, + userDir: NODE_RED_HOME, //"/root/.node-red/", + nodesDir: NODE_RED_HOME+"/nodes/", //"/root/.node-red/nodes/", + //uiHost:"0.0.0.0", + //uiPort:"1880", + //functionGlobalContext: { }, // enables global context + /**/ + adminAuth: { + type: "credentials", + users: [{ + username: "admin", + password: "$2a$08$0tcdIPRESSKHCZqlIEjer.zDl7lJiGxeQmsmkbpcHmmMAg5yx0PEm", + permissions: "*" + }] + } + + }; + + exports.init(settings).then( + function(){ + + logger.info("[NODE-RED] - Init completed."); + // Start the runtime + RED.start().then( + + function () { + + logger.info("[NODE-RED] - Started."); + response.message = "Node-RED: init and started!"; + response.result = "SUCCESS"; + d.resolve(response); + + } + ) + + + } + ); + + + + + + return d.promise; + +}; + + +exports.stop = function (){ + + logger.info("[NODE-RED] - Stopping"); + + var d = Q.defer(); + + var response = { + message: '', + result: '' + }; + + // Stop the runtime + RED.stop().then( + function () { + + if(server != null) + server.close(); //close the server + + logger.info("[NODE-RED] - Stopped"); + + response.message = "Node-RED stopped!"; + response.result = "SUCCESS"; + d.resolve(response); + } + + ); + + return d.promise; + +}; + + +exports.getFlows = function (){ + + logger.info("[NODE-RED] - Getting Node-RED flows"); + + var d = Q.defer(); + + var response = { + message: '', + result: '' + }; + + requestify.get('http://localhost:1880/flows').then( function(response) { + + var flows = response.getBody(); + + response.message = flows; + response.result = "SUCCESS"; + + d.resolve(response); + + }); + + + return d.promise; + +}; + + +exports.getFlowInfo = function (args){ + + logger.info("[NODE-RED] - Getting Node-RED flow info"); + + var flow_id = String(args[0]); + + var d = Q.defer(); + + var response = { + message: '', + result: '' + }; + + requestify.get('http://localhost:1880/flow/'+flow_id).then( function(response) { + + var flow = response.getBody(); + + response.message = flow; + response.result = "SUCCESS"; + + d.resolve(response); + + }); + + + return d.promise; + +}; + + +//This function Inits the Node-RED Manager +exports.Init = function (session){ + + //Register all the module functions as WAMP RPCs + session.register('s4t.'+ boardCode+'.nodered.start', exports.start); + session.register('s4t.'+ boardCode+'.nodered.stop', exports.stop); + + session.register('s4t.'+ boardCode+'.nodered.getFlows', exports.getFlows); + session.register('s4t.'+ boardCode+'.nodered.getFlowInfo', exports.getFlowInfo); + + + logger.info('[WAMP-EXPORTS] Node-RED methods exported to the cloud!'); + +}; + + +//This function executes procedures at boot time (no Iotronic dependent) +exports.Boot = function (){ + + logger.info('[BOOT] - Node-RED Manager booting procedures not defined.'); + +}; \ No newline at end of file diff --git a/modules/plugins-manager/manage-plugins.js b/modules/plugins-manager/manage-plugins.js index 158a9c0..d36c608 100644 --- a/modules/plugins-manager/manage-plugins.js +++ b/modules/plugins-manager/manage-plugins.js @@ -17,14 +17,20 @@ //############################################################################################ -//service logging configuration: "managePlugins" -var logger = log4js.getLogger('managePlugins'); +//service logging configuration: "pluginsManager" +var logger = log4js.getLogger('pluginsManager'); logger.setLevel(loglevel); var fs = require("fs"); var Q = require("q"); -var cp = require('child_process'); //In order to create a plugin-wrapper process for each active plugin. +var cp = require('child_process'); //In order to create a wrapper process for each active plugin. var running = require('is-running'); //In order to verify if a plugin is alive or not. +var md5 = require('md5'); + +var PythonShell = require('python-shell'); +var net = require('net'); + +var session_plugins = null; var plugins = {}; // This data structure collects all status information of all plugins started in this LR session @@ -33,21 +39,35 @@ var PLUGINS_STORE = process.env.IOTRONIC_HOME + '/plugins/'; var LIGHTNINGROD_HOME = process.env.LIGHTNINGROD_HOME; +SETTINGS = process.env.IOTRONIC_HOME+'/settings.json'; +nconf = require('nconf'); +nconf.file ({file: SETTINGS}); + +alive_timer = nconf.get('config:board:modules:plugins_manager:alive_timer'); +if (isNaN(alive_timer)) + alive_timer = 60; //set default value + +var PLUGIN_MODULE_LOADED = false; + +CHECKSUMS_PLUGINS_LIST = []; + // This function checks if the plugin process is still alive otherwise starts it -function pluginStarter(plugin_name, timer, plugin_json_name, skip) { +function pluginStarter(plugin_name, timer, plugin_json_name, skip, plugin_checksum) { try{ - + // Get the plugin's configuration. - var pluginsConf = JSON.parse(fs.readFileSync(PLUGINS_SETTING, 'utf8')); - - var status = pluginsConf.plugins[plugin_name].status; - var pid = pluginsConf.plugins[plugin_name].pid; - var autostart = pluginsConf.plugins[plugin_name].autostart; - - // The board restarts all the plugins with status "on" (this status happens after a crash of L-R/board) or with autostart parameter set at true (because some plugins need to start at boot time). - if (status == "on" || autostart == "true"){ + var pluginsConf = JSON.parse(fs.readFileSync(PLUGINS_SETTING, 'utf8')); + + var status = pluginsConf.plugins[plugin_name].status; + var pid = pluginsConf.plugins[plugin_name].pid; + var autostart = pluginsConf.plugins[plugin_name].autostart; + var plugin_type = pluginsConf.plugins[plugin_name].type; + var plugin_version = pluginsConf.plugins[plugin_name].version; + + // The board restarts all the plugins with status "on" (this status happens after a crash of L-R/board) or with autostart parameter set at true (because some plugins need to start at boot time). + if (status == "on" || autostart == "true"){ // if the pid of plugin is empty (wrong status) if (pid == '') { @@ -58,21 +78,21 @@ function pluginStarter(plugin_name, timer, plugin_json_name, skip) { alive: false, timer: timer } - + }else if( pid == null){ - - // if the plugin was just injected it does not have the "pid" field in the plugins.json conf file - skip = "true"; - + + // if the plugin was just injected it does not have the "pid" field in the plugins.json conf file + skip = "true"; + plugins[plugin_name]={ child: "", pid: pid, alive: null, timer: timer } - + }else{ - + // if the pid is specified and the device is in the after reboot status of the device/LR or after a crash of the plugin process plugins[plugin_name]={ child: "", @@ -80,12 +100,69 @@ function pluginStarter(plugin_name, timer, plugin_json_name, skip) { alive: running(pid), timer: timer } - + } if( plugins[plugin_name].alive === true ){ - // the plugin is normally running - logger.debug('[PLUGIN] - PluginChecker - '+ plugin_name + ' with PID: ' + plugins[plugin_name].pid + ' alive: '+ plugins[plugin_name].alive ); + + if(CHECKSUMS_PLUGINS_LIST.length == 0){ + // the plugin is normally running + console.log('[PLUGIN] - PluginChecker - '+ plugin_name + ' with PID: ' + plugins[plugin_name].pid + ' alive: '+ plugins[plugin_name].alive ); + + }else{ + + if(plugin_type == "nodejs") + var ext = '.js'; + else if(plugin_type == "python") + var ext ='.py'; + + var checksum = md5( fs.readFileSync(PLUGINS_STORE + plugin_name + "/"+plugin_name+ext, 'utf8')); + + var plugin_checksum = CHECKSUMS_PLUGINS_LIST[plugin_name]; + + //logger.warn(CHECKSUMS_PLUGINS_LIST, plugin_checksum); + + if(checksum != plugin_checksum){ + + process.kill(plugins[plugin_name].pid); + + // the plugin is not alive and its checksum mismatches! + logger.warn( '[PLUGIN] - PluginChecker - '+ plugin_name + ' - The plugin was modified: checksum mismatches!'); + clearPluginTimer(plugin_name); + + session_plugins.call('s4t.iotronic.plugin.invalidPlugin', [boardCode, plugin_name, plugin_version]).then( + + function (rpc_response) { + + if (rpc_response.result == "ERROR") { + + logger.error("[PLUGIN] --> Error notification plugin checksum mismatch for '" + plugin_name + "' plugin: " + rpc_response.message); + + } + else { + + logger.debug("[PLUGIN] - Invalidation plugin response: " + rpc_response.message); + + } + + } + ); + + + + }else{ + // the plugin is normally running + console.log('[PLUGIN] - PluginChecker - '+ plugin_name + ' with PID: ' + plugins[plugin_name].pid + ' alive: '+ plugins[plugin_name].alive ); + + } + + + } + + + + + } else if( skip === "true") { @@ -97,122 +174,192 @@ function pluginStarter(plugin_name, timer, plugin_json_name, skip) { } else if( plugins[plugin_name].alive === false || skip === "false") { - // the plugin is not alive: we are in the state after a reboot of the device/LR or after a crash of the plugin process - logger.warn( '[PLUGIN] - PluginChecker - '+ plugin_name + ' - No such process found with PID '+plugins[plugin_name].pid+'!'+ ' - alive: '+ plugins[plugin_name].alive +' - Restarting...'); + if(plugin_type == "nodejs") + var ext = '.js'; + else if(plugin_type == "python") + var ext ='.py'; - // If the schema json file exists the board will create a child_process to restart the plugin and update the status and the PID value - if (fs.existsSync(plugin_json_name) === true){ + var checksum = md5( fs.readFileSync(PLUGINS_STORE + plugin_name + "/"+plugin_name+ext, 'utf8')); - //Create a new process that has plugin-wrapper as code - try{ + if(CHECKSUMS_PLUGINS_LIST.length != 0) + var plugin_checksum = CHECKSUMS_PLUGINS_LIST[plugin_name]; - plugins[plugin_name].child = cp.fork(LIGHTNINGROD_HOME + '/modules/plugins-manager/plugin-wrapper'); + //logger.warn(CHECKSUMS_PLUGINS_LIST); - var plugin_json_schema = JSON.parse(fs.readFileSync(plugin_json_name)); - var input_message = { - "plugin_name": plugin_name, - "plugin_json": plugin_json_schema - }; + if( (checksum === plugin_checksum) || (CHECKSUMS_PLUGINS_LIST.length == 0) ){ - logger.info("[PLUGIN] --> "+ plugin_name + " - Input parameters: "+ fs.readFileSync(plugin_json_name)); + // the plugin is not alive: we are in the state after a reboot of the device/LR or after a crash of the plugin process + logger.warn( '[PLUGIN] - PluginChecker - '+ plugin_name + ' - No such process found with PID '+plugins[plugin_name].pid+'!'+ ' - alive: '+ plugins[plugin_name].alive +' - Checksum accepted ('+checksum+') - Restarting...'); - pluginsConf.plugins[plugin_name].pid = plugins[plugin_name].child.pid; - pluginsConf.plugins[plugin_name].status = "on"; + // If the schema json file exists the board will create a child_process to restart the plugin and update the status and the PID value + if (fs.existsSync(plugin_json_name) === true){ - plugins[plugin_name].child.send(input_message); + // Check the plugin type: "nodejs" or "python" + switch (plugin_type) { - plugins[plugin_name].child.on('message', function(msg) { + case 'nodejs': - if(msg.name != undefined){ + //Create a new process that has wrapper that manages the plugin execution + try{ - if (msg.status === "alive"){ + plugins[plugin_name].child = cp.fork(LIGHTNINGROD_HOME + '/modules/plugins-manager/nodejs/async-wrapper'); - //updates the JSON file plugins.json - try{ + var plugin_json_schema = JSON.parse(fs.readFileSync(plugin_json_name)); + var input_message = { + "plugin_name": plugin_name, + "plugin_json": plugin_json_schema + }; - fs.writeFile(PLUGINS_SETTING, JSON.stringify(pluginsConf, null, 4), function(err) { - if(err) { - logger.error('[PLUGIN] --> '+ plugin_name + ' - Error writing JSON file ' + PLUGINS_SETTING + ': ' + err); - } else { - logger.debug("[PLUGIN] --> "+ plugin_name + " - JSON file " + PLUGINS_SETTING + " updated!"); - } - }); + logger.info("[PLUGIN] --> "+ plugin_name + " - Input parameters: "+ fs.readFileSync(plugin_json_name)); - } - catch(err){ - logger.error('[PLUGIN] --> '+ plugin_name + ' - Error updating JSON file ' + PLUGINS_SETTING + ': ' + err); - } + pluginsConf.plugins[plugin_name].pid = plugins[plugin_name].child.pid; + pluginsConf.plugins[plugin_name].status = "on"; - logger.info("[PLUGIN] --> "+ msg.name + " - " + msg.status + " - Plugin initialization completed: PID = " + pluginsConf.plugins[plugin_name].pid +" - Status = " + pluginsConf.plugins[plugin_name].status); + plugins[plugin_name].child.send(input_message); - } else if(msg.level === "error") { - logger.error("[PLUGIN] - "+ msg.name + " - " + msg.logmsg); + plugins[plugin_name].child.on('message', function(msg) { - } else if(msg.level === "warn") { + if(msg.name != undefined){ - logger.warn("[PLUGIN] --> "+ msg.name + " - " + msg.logmsg); + if (msg.status === "alive"){ - } else{ + //updates the JSON file plugins.json + try{ + + fs.writeFile(PLUGINS_SETTING, JSON.stringify(pluginsConf, null, 4), function(err) { + if(err) { + logger.error('[PLUGIN] --> '+ plugin_name + ' - Error writing JSON file ' + PLUGINS_SETTING + ': ' + err); + } else { + logger.debug("[PLUGIN] --> "+ plugin_name + " - JSON file " + PLUGINS_SETTING + " updated!"); + } + }); + + } + catch(err){ + logger.error('[PLUGIN] --> '+ plugin_name + ' - Error updating JSON file ' + PLUGINS_SETTING + ': ' + err); + } - logger.info("[PLUGIN] --> "+ msg.name + " - " + msg.logmsg); + logger.info("[PLUGIN] --> "+ msg.name + " - " + msg.status + " - Plugin initialization completed: PID = " + pluginsConf.plugins[plugin_name].pid +" - Status = " + pluginsConf.plugins[plugin_name].status); + + } else if(msg.level === "error") { + + logger.error("[PLUGIN] - "+ msg.name + " - " + msg.logmsg); + + } else if(msg.level === "warn") { + + logger.warn("[PLUGIN] --> "+ msg.name + " - " + msg.logmsg); + + } else{ + + logger.info("[PLUGIN] --> "+ msg.name + " - " + msg.logmsg); + + } + + + } else{ + //used to manage the first message coming from the child process + logger.info("[PLUGIN] --> "+ msg); + } + + }); + + + } + catch(err){ + logger.error('[PLUGIN] --> Error starting '+plugin_name+' plugin: ' + err); } - } else{ - //used to manage the first message coming from the child process - logger.info("[PLUGIN] --> "+ msg); - } + break; + + case 'python': + + var plugin_json = fs.readFileSync(plugin_json_name); + + pyAsyncStarter(plugin_name, plugin_json, plugin_checksum, "restart"); + + break; + + + default: + logger.warn('[PLUGIN] - "' + plugin_name + '": wrong plugin type: ' + plugin_type); + break; + + } + - }); } - catch(err){ - logger.error('[PLUGIN] --> Error starting '+plugin_name+' plugin: ' + err); + else{ + + //If the schema json file doesn't exist the related plugin will be not restarted and the value of its PID will be cleaned. + + //updates the plugins.json JSON file + try{ + + logger.warn('[PLUGIN] --> '+ plugin_name + ' - I can not restart plugin!!! JSON file '+ plugin_json_name +' does not exist!'); + + pluginsConf.plugins[plugin_name].pid = ""; + + fs.writeFile(PLUGINS_SETTING, JSON.stringify(pluginsConf, null, 4), function(err) { + if(err) { + logger.error('[PLUGIN] --> '+ plugin_name + ' - Error writing JSON file ' + PLUGINS_SETTING + ': ' + err); + } else { + logger.info('[PLUGIN] --> '+ plugin_name + ' - JSON file ' + PLUGINS_SETTING + ' updated: PID value cleaned!'); + } + }); + + logger.warn('[PLUGIN] --> '+ plugin_name + ' - Please call the RUN command again for this plugin!'); + + } + catch(err){ + logger.error('[PLUGIN] --> '+ plugin_name + ' - Error updating JSON file ' + PLUGINS_SETTING + ': ' + err); + } + } }else{ - //If the schema json file doesn't exist the related plugin will be not restarted and the value of its PID will be cleaned. + // the plugin is not alive and its checksum mismatches! + logger.warn( '[PLUGIN] - PluginChecker - '+ plugin_name + ' - The plugin is not alive and it will not be restarted: checksum mismatches!'); + clearPluginTimer(plugin_name); - //updates the plugins.json JSON file - try{ + session_plugins.call('s4t.iotronic.plugin.invalidPlugin', [boardCode, plugin_name, plugin_version]).then( + + function (rpc_response) { - logger.warn('[PLUGIN] --> '+ plugin_name + ' - I can not restart plugin!!! JSON file '+ plugin_json_name +' does not exist!'); + if (rpc_response.result == "ERROR") { - pluginsConf.plugins[plugin_name].pid = ""; + logger.error("[PLUGIN] --> Error notification plugin checksum mismatch for '" + plugin_name + "' plugin: " + rpc_response.message); - fs.writeFile(PLUGINS_SETTING, JSON.stringify(pluginsConf, null, 4), function(err) { - if(err) { - logger.error('[PLUGIN] --> '+ plugin_name + ' - Error writing JSON file ' + PLUGINS_SETTING + ': ' + err); - } else { - logger.info('[PLUGIN] --> '+ plugin_name + ' - JSON file ' + PLUGINS_SETTING + ' updated: PID value cleaned!'); } - }); + else { - logger.warn('[PLUGIN] --> '+ plugin_name + ' - Please call the RUN command again for this plugin!'); + logger.debug("[PLUGIN] - Invalidation plugin response: " + rpc_response.message); - } - catch(err){ - logger.error('[PLUGIN] --> '+ plugin_name + ' - Error updating JSON file ' + PLUGINS_SETTING + ': ' + err); - } + } + + } + ); } + } - + } - - } - catch(err){ - logger.error('[PLUGIN] --> '+ plugin_name + ' - Error loading plugin: ' + err); - } + + } + catch(err){ + logger.error('[PLUGIN] --> '+ plugin_name + ' - Error loading plugin: ' + err); + } } @@ -323,201 +470,771 @@ function cleanPluginData(plugin_name){ } +function pyAsyncStarter(plugin_name, plugin_json, plugin_checksum, action) { + var d = Q.defer(); -// RPC to execute a syncronous plugin ("call" as the exection of a command that returns a value to the "caller"): it is called by Iotronic via RPC -exports.call = function (args, details){ + var response = { + message: '', + result: '' + }; - //Parsing the input arguments - var plugin_name = String(args[0]); - var plugin_json = String(args[1]); + var PY_PID = null; + var s_server = null; + var socketPath = '/tmp/plugin-'+plugin_name; - var d = Q.defer(); + // Callback for socket + var handler = function(socket){ - // The autostart parameter at RUN stage is OPTIONAL. It is used at this stage if the user needs to change the boot execution configuration of the plugin after the INJECTION stage. - var plugin_autostart = ""; + // Listen for data from client + socket.on('data',function(bytes){ + var data = bytes.toString(); // Decode byte string + var data_parsed = JSON.parse(data); // Parse JSON response - logger.info('[PLUGIN] - Execution request for \"'+ plugin_name +'\" plugin with parameter json: '+plugin_json); + if(data_parsed.result == "ERROR"){ - try{ - //Reading the plugins.json configuration file - var pluginsConf = JSON.parse(fs.readFileSync(PLUGINS_SETTING, 'utf8')); - } - catch(err){ - logger.error('[PLUGIN] --> Error parsing JSON file plugins.json'); - } + response.result = "ERROR"; + response.message = data_parsed.payload; + logger.info('[PLUGIN] - Error in '+plugin_name + ':\n'+JSON.stringify(response.message, null, "\t")); + d.resolve(response); - //If the plugin exists - if(pluginsConf["plugins"].hasOwnProperty(plugin_name)){ + }else{ - logger.info("[PLUGIN] --> Plugin successfully loaded!"); + response.result = "SUCCESS"; + response.message = data_parsed.payload; + logger.info('[PLUGIN] - '+plugin_name + ': '+ JSON.stringify(response.message, null, "\t")); + d.resolve(response); - //Check the plugin status - var status = pluginsConf.plugins[plugin_name].status; + } - if (status == "off" || status == "injected"){ + /* + response.result = "SUCCESS"; + response.message = bytes.toString(); // Decode byte strin + logger.info('[PLUGIN] - '+plugin_name + ' - '+response.message); + d.resolve(response); + */ - logger.info("[PLUGIN] --> Plugin " + plugin_name + " being started"); + }); - //Create a new process that has plugin-wrapper as code - var child = cp.fork(LIGHTNINGROD_HOME + '/modules/plugins-manager/call-wrapper'); + // On client close + socket.on('end', function() { + logger.debug('[PLUGIN-SOCKET] - Socket disconnected'); + s_server.close(function(){ + logger.debug('[PLUGIN-SOCKET] - Server socket closed'); + }); - //Prepare the message I will send to the process with name of the plugin to start and json file as argument - var input_message = { - "plugin_name": plugin_name, - "plugin_json": JSON.parse(plugin_json) - }; + }); - child.on('message', function(msg) { - if(msg.name != undefined){ + }; - if (msg.status === "alive"){ + // Remove an existing socket + fs.unlink(socketPath, function(){ + // Create the server, give it our callback handler and listen at the path - //Creating the plugin json schema - var plugin_folder = PLUGINS_STORE + plugin_name; - var schema_outputFilename = plugin_folder + "/" + plugin_name + '.json'; + s_server = net.createServer(handler).listen(socketPath, function(){ + logger.debug('[PLUGIN-SOCKET] - Socket in listening...'); + logger.debug('[PLUGIN-SOCKET] --> socket: '+socketPath); + }) - fs.writeFile(schema_outputFilename, plugin_json, function(err) { + } + ); - if(err) { + var options = { + mode: 'text', + pythonPath: '/usr/bin/python3', + pythonOptions: ['-u'], + scriptPath: __dirname, + args: [plugin_name, plugin_json] + }; - logger.error('[PLUGIN] --> Error parsing '+plugin_name+'.json file: ' + err); + var pyshell = new PythonShell('./python/async-wrapper.py', options); + PY_PID = pyshell.childProcess.pid; + logger.debug("[PLUGIN-SHELL] - PID wrapper: "+ PY_PID); - } else { + //Creating the plugin json schema + var plugin_folder = PLUGINS_STORE + plugin_name; + var schema_outputFilename = plugin_folder + "/" + plugin_name + '.json'; - logger.info('[PLUGIN] --> Plugin JSON schema saved to ' + schema_outputFilename); + // Reading the plugins.json configuration file + try{ - // - change the plugin status from "off" to "on" and update the PID value - pluginsConf.plugins[plugin_name].status = "on"; - pluginsConf.plugins[plugin_name].pid = child.pid; + var pluginsConf = JSON.parse(fs.readFileSync(PLUGINS_SETTING, 'utf8')); + var pluginsSchemaConf = JSON.parse(fs.readFileSync(schema_outputFilename, 'utf8')); - fs.writeFile(PLUGINS_SETTING, JSON.stringify(pluginsConf, null, 4), function(err) { + //Get the autostart parameter from the schema just uploaded + plugin_autostart = pluginsSchemaConf.autostart; - if(err) { - logger.error('[PLUGIN] --> Error writing plugins.json file: ' + err); - } else { - logger.info("[PLUGIN] --> JSON file plugins.json updated -> " + plugin_name + ': status < '+ pluginsConf.plugins[plugin_name].status + ' > ' + pluginsConf.plugins[plugin_name].pid); - } - }); + } + catch(err){ - } + response.result = "ERROR"; + response.message = 'Error parsing plugins.json configuration file: ' + err; + logger.error('[PLUGIN] - '+plugin_name + ' - '+response.message); - }); + d.resolve(response); + } - } else if(msg.status === "finish") { + if(action == "start") { - logger.info("[PLUGIN] --> RESULT: ", msg.logmsg); - d.resolve(msg.logmsg); + fs.writeFile(schema_outputFilename, plugin_json, function(err) { - } else if(msg.status === "fault") { + if(err) { + response.result = "ERROR"; + response.message = 'Error opening '+plugin_name+'.json file: ' + err; + logger.error('[PLUGIN] - "'+plugin_name + '" - '+response.message); + d.resolve(response); - logger.info("[PLUGIN] --> RESULT: ", msg.logmsg); - d.resolve(msg.logmsg); + } else { - } else if(msg.level === "error") { + logger.info('[PLUGIN] - '+ plugin_name + ' - Plugin JSON schema saved to ' + schema_outputFilename); - logger.error("[PLUGIN] --> "+ msg.name + ": " + msg.logmsg); - } else if(msg.level === "warn") { - logger.warn("[PLUGIN] --> "+ msg.name + ": " + msg.logmsg); + // Updating the plugins.json file: + // - check if the user changed the autostart parameter at this stage + if(plugin_autostart != undefined){ - } - else{ - logger.info("[PLUGIN] --> "+ msg.name + ": " + msg.logmsg); + pluginsConf.plugins[plugin_name].autostart = plugin_autostart; + logger.info('[PLUGIN] - '+ plugin_name + ' - Autostart parameter set by user to ' + plugin_autostart); - } + } else { + + logger.info('[PLUGIN] - '+ plugin_name + ' - Autostart parameter not changed!'); - } - else{ - //serve per gestire il primo messaggio alla creazione del child - logger.info("[PLUGIN] --> " + msg); } + // - change the plugin status from "off" to "on" and update the PID value + pluginsConf.plugins[plugin_name].status = "on"; + pluginsConf.plugins[plugin_name].pid = PY_PID; - }); + fs.writeFile(PLUGINS_SETTING, JSON.stringify(pluginsConf, null, 4), function(err) { + if(err) { + logger.error('[PLUGIN] - '+ plugin_name + ' - Error opening '+plugin_name+'.json file: ' + err); + } else { + logger.info('[PLUGIN] - '+ plugin_name + ' - plugins.json updated -> autostart < ' + pluginsConf.plugins[plugin_name].autostart + ' > - status < '+ pluginsConf.plugins[plugin_name].status + ' > ' + pluginsConf.plugins[plugin_name].pid); - //I send the input to the wrapper so that it can launch the proper plugin with the proper json file as argument - child.send(input_message); + // Start a timer to check every X minutes if the plugin is still alive! + exports.pluginKeepAlive(plugin_name, plugin_checksum); - return d.promise; + } - } - else{ - logger.warn("[PLUGIN] --> Call already started!"); - return 'Call already started on this board!'; - } + }); + + } + + }); } - else{ - // Here the plugin does not exist - logger.warn("[PLUGIN] --> Call \"" + plugin_name + "\" does not exist on this board!"); - return 'Call does not exist on this board!'; - } + else if(action == "restart"){ -}; + // - change the plugin status from "off" to "on" and update the PID value + pluginsConf.plugins[plugin_name].status = "on"; + pluginsConf.plugins[plugin_name].pid = PY_PID; -// RPC to check if the plugin has to be restarted -exports.pluginKeepAlive = function (plugin_name){ - - try{ - - // Get the plugin's configuration. - var pluginsConf = JSON.parse(fs.readFileSync(PLUGINS_SETTING, 'utf8')); - var status = pluginsConf.plugins[plugin_name].status; - var autostart = pluginsConf.plugins[plugin_name].autostart; + fs.writeFile(PLUGINS_SETTING, JSON.stringify(pluginsConf, null, 4), function(err) { - var plugin_folder = PLUGINS_STORE + plugin_name; - var plugin_json_name = plugin_folder + "/" + plugin_name + '.json'; + if(err) { + logger.error('[PLUGIN] - '+ plugin_name + ' - Error opening '+plugin_name+'.json file: ' + err); + } else { + logger.info('[PLUGIN] - '+ plugin_name + ' - plugins.json updated -> autostart < ' + pluginsConf.plugins[plugin_name].autostart + ' > - status < '+ pluginsConf.plugins[plugin_name].status + ' > ' + pluginsConf.plugins[plugin_name].pid); - var skip = "false"; + } - // We have to restart only the plugins: - // - that the "autostart" flag is TRUE (boot enabled plugin) - // - that were in status "on" (it means that the device it was rebooted or LR crashed) even if "auotstart" is FALSE - if (status == "on" || autostart == "true"){ + }); - // We associate to each plugin that has to be restarted (no injected ones) a timer to check during LR execution if the plugin is still alive - if(status != "injected"){ - /* - We have to verify this "injected" status condition because of when a plugin is just injected, with "autostart" set at true, has the following configuration: - "PLUGIN": { - "status": "injected", - "autostart": "true" - } + } - */ - // BUT we call NOW "pluginStarter" in order to start immediately the plugins that have to be, with "timer" parameter set to null, - // so in this way we don't wait for the timer expiration - pluginStarter(plugin_name, null, plugin_json_name, skip); - - var timer = setInterval(function() { - - pluginStarter(plugin_name, timer, plugin_json_name, skip); + //if(logger.level.levelStr == 'DEBUG') + // listening 'print' output + pyshell.on('message', function (message) { + // received a message sent from the Python script (a simple "print" statement) + console.log("[PLUGIN-WRAPPER] - PYTHON: "+message); + }); - }, 300000); + // end the input stream and allow the process to exit + pyshell.end(function (err, code, signal) { - plugins[plugin_name]={ - child: "", - pid: "", - alive: "", - timer: timer - } - - } - - } + if (err){ + response.result = "ERROR"; + response.message = err; + d.resolve(response); + }else{ + logger.debug('[PLUGIN-SHELL] - Python shell of "'+plugin_name+'" plugin terminated: {signal: '+ signal+', code: '+code+'}'); - + if(signal == null && code == 0){ + + logger.warn("[PLUGIN-SHELL] --> unexpected '"+plugin_name+"' plugin termination!"); + + pluginsConf.plugins[plugin_name].status = "off"; + pluginsConf.plugins[plugin_name].pid = ""; + + // updates the JSON file + fs.writeFile(PLUGINS_SETTING, JSON.stringify(pluginsConf, null, 4), function(err) { + + if(err) { + + response.result = "ERROR"; + response.message = 'Error writing plugins.json: '+ err; + logger.error('[PLUGIN] - stop plugin '+plugin_name + ' error: '+response.message); + d.resolve(response); + + } else { + logger.debug("[PLUGIN] --> " + PLUGINS_SETTING + " updated!"); + clearPluginTimer(plugin_name); + response.result = "SUCCESS"; + response.message = 'Plugin environment cleaned!'; + logger.info('[PLUGIN] - plugin '+plugin_name + ': '+response.message); + d.resolve(response); + } + + }); + }else{ + logger.debug("[PLUGIN-SHELL] --> Python plugin '"+plugin_name+"' terminated!") + } + + } + + + + }); + + + return d.promise; + +} + + +function pySyncStarter(plugin_name, plugin_json) { + + var d = Q.defer(); + + var response = { + message: '', + result: '' + }; + + var s_server = null; + var socketPath = '/tmp/plugin-'+plugin_name; + + var pyshell = null; + + // Callback for socket + var handler = function(socket){ + + // Listen for data from client + socket.on('data', function(bytes){ + + var data = bytes.toString(); // Decode byte string + var data_parsed = JSON.parse(data); // Parse JSON response + + if(data_parsed.result == "ERROR"){ + + response.result = "ERROR"; + response.message = data_parsed.payload; + logger.info('[PLUGIN] - Error in '+plugin_name + ':\n'+JSON.stringify(response.message, null, "\t")); + d.resolve(response); + + }else{ + + + try{ + + response.result = "SUCCESS"; + response.message = data_parsed.payload; + logger.info('[PLUGIN] - '+plugin_name + ': '+ JSON.stringify(response.message, null, "\t")); + d.resolve(response); + + } + catch(err){ + response.result = "ERROR"; + response.message = JSON.stringify(err); + logger.error('Error parsing '+plugin_name + ' plugin response: '+ response.message); + d.resolve(response); + } + + + } + + + + + }); + + // On client close + socket.on('end', function() { + + logger.debug('[PLUGIN-SOCKET] - Socket disconnected'); + + s_server.close(function(){ + + logger.debug('[PLUGIN-SOCKET] - Server socket closed'); + + }); + + }); + + }; + + // Remove an existing plugin socket + fs.unlink(socketPath, function(){ + + var plugin_folder = PLUGINS_STORE + plugin_name; + var schema_outputFilename = plugin_folder + "/" + plugin_name + '.json'; + + // Create the server, give it our callback handler and listen at the path + s_server = net.createServer(handler).listen(socketPath, function() { + logger.debug('[PLUGIN-SOCKET] - Socket in listening...'); + logger.debug('[PLUGIN-SOCKET] --> socket: '+socketPath); + + + // after socket creation we will start the plugin wrapper + var options = { + mode: 'text', + pythonPath: '/usr/bin/python3', + pythonOptions: ['-u'], + scriptPath: __dirname, + args: [plugin_name, plugin_json] + }; + + pyshell = new PythonShell('./python/sync-wrapper.py', options); + // it will create a python instance like this: + // python -u /opt/stack4things/lightning-rod/modules/plugins-manager/python/sync-wrapper.py py_sync {"name":"S4T"} + + logger.debug("[PLUGIN-SHELL] - PID wrapper: "+pyshell.childProcess.pid); + + if(logger.level.levelStr == 'DEBUG') + // listening 'print' output + pyshell.on('message', function (message) { + // received a message sent from the Python script (a simple "print" statement) + console.log("[PLUGIN-WRAPPER] - PYTHON: "+message); + }); + + + // end the input stream and allow the process to exit + pyshell.end(function (err, code, signal) { + + if (err){ + + response.result = "ERROR"; + response.message = err; + d.resolve(response); + + }else{ + logger.debug('[PLUGIN-SHELL] - Python shell terminated: {signal: '+ signal+', code: '+code+'}'); + } + + }); + + + //update parameters and plugins.json conf file + fs.writeFile(schema_outputFilename, plugin_json, function(err) { + + if(err) { + + logger.error('[PLUGIN] --> Error parsing '+plugin_name+'.json file: ' + err); + + } else { + + logger.debug('[PLUGIN] --> Plugin JSON schema saved to ' + schema_outputFilename); + + try{ + + //Reading the plugin configuration file + var pluginsConf = JSON.parse(fs.readFileSync(PLUGINS_SETTING, 'utf8')); + + // - change the plugin status from "off" to "on" and update the PID value + pluginsConf.plugins[plugin_name].status = "on"; + pluginsConf.plugins[plugin_name].pid = pyshell.childProcess.pid; + + //updates the JSON file + fs.writeFile(PLUGINS_SETTING, JSON.stringify(pluginsConf, null, 4), function(err) { + + if(err) { + logger.error('[PLUGIN] --> Error writing plugins.json file: ' + err); + } else { + logger.debug("[PLUGIN] --> JSON file plugins.json updated -> " + plugin_name + ': status < '+ pluginsConf.plugins[plugin_name].status + ' > ' + pluginsConf.plugins[plugin_name].pid); + } + + }); + + } + catch(err){ + logger.error('Error updating JSON file plugins.json: '+ JSON.stringify(err)); + } + + + + } + + }); + + + }) + + + } + + ); + + + + + return d.promise; + +} + + +// RPC to execute a syncronous plugin ("call" as the exection of a command that returns a value to the "caller"): it is called by Iotronic via RPC +exports.call = function (args){ + + //Parsing the input arguments + var plugin_name = String(args[0]); + var plugin_json = String(args[1]); + var plugin_checksum = String(args[2]); + + var d = Q.defer(); + + var response = { + message: '', + result: '' + }; + + logger.info('[PLUGIN] - Sync plugin RPC called for plugin "'+ plugin_name +'" plugin...'); + logger.info("[PLUGIN] --> Input parameters:\n"+ plugin_json); + + try{ + //Reading the plugin configuration file + var pluginsConf = JSON.parse(fs.readFileSync(PLUGINS_SETTING, 'utf8')); + + var status = pluginsConf.plugins[plugin_name].status; + var plugin_type = pluginsConf.plugins[plugin_name].type; + + } + catch(err){ + response.result = "ERROR"; + response.message = 'Error parsing plugins.json!'; + logger.error('[PLUGIN] - "' + plugin_name + '" plugin execution error: '+response.message); + d.resolve(response); + } + + + if(plugin_type == "nodejs") + var ext = '.js'; + else if(plugin_type == "python") + var ext ='.py'; + + + var checksum = md5( fs.readFileSync(PLUGINS_STORE + plugin_name + "/"+plugin_name+ext, 'utf8')); + + if(checksum === plugin_checksum){ + + // The autostart parameter at RUN stage is OPTIONAL. It is used at this stage if the user needs to change the boot execution configuration of the plugin after the INJECTION stage. + var plugin_autostart = ""; + + logger.info('[PLUGIN] - Execution request for \"'+ plugin_name +'\" plugin with parameter json: '+plugin_json); + + //If the plugin exists + if(pluginsConf["plugins"].hasOwnProperty(plugin_name)){ + + logger.info("[PLUGIN] --> Plugin successfully loaded!"); + + //Check the plugin status + var status = pluginsConf.plugins[plugin_name].status; + + if (status == "off" || status == "injected"){ + + logger.info("[PLUGIN] --> Plugin " + plugin_name + " being started"); + + + // Check the plugin type: "nodejs" or "python" + + switch (plugin_type) { + + case 'nodejs': + + logger.info("[PLUGIN] --> plugin type: " + plugin_type); + + //Create a new process that has wrapper that manages the plugin execution + var child = cp.fork(LIGHTNINGROD_HOME + '/modules/plugins-manager/nodejs/sync-wrapper'); + + //Prepare the message I will send to the process with name of the plugin to start and json file as argument + var input_message = { + "plugin_name": plugin_name, + "plugin_json": JSON.parse(plugin_json) + }; + + child.on('message', function(msg) { + + if(msg.name != undefined){ + + if (msg.status === "alive"){ + + //Creating the plugin json schema + var plugin_folder = PLUGINS_STORE + plugin_name; + var schema_outputFilename = plugin_folder + "/" + plugin_name + '.json'; + + //update parameters and plugins.json conf file + fs.writeFile(schema_outputFilename, plugin_json, function(err) { + + if(err) { + + logger.error('[PLUGIN] --> Error parsing '+plugin_name+'.json file: ' + err); + + } else { + + logger.info('[PLUGIN] --> Plugin JSON schema saved to ' + schema_outputFilename); + + // - change the plugin status from "off" to "on" and update the PID value + pluginsConf.plugins[plugin_name].status = "on"; + pluginsConf.plugins[plugin_name].pid = child.pid; + + fs.writeFile(PLUGINS_SETTING, JSON.stringify(pluginsConf, null, 4), function(err) { + + if(err) { + logger.error('[PLUGIN] --> Error writing plugins.json file: ' + err); + } else { + logger.info("[PLUGIN] --> JSON file plugins.json updated -> " + plugin_name + ': status < '+ pluginsConf.plugins[plugin_name].status + ' > ' + pluginsConf.plugins[plugin_name].pid); + } + + }); + + } + + }); + + + } else if(msg.status === "finish") { + + logger.info("[PLUGIN] --> RESULT "+msg.name+": ", msg.logmsg); + d.resolve(msg.logmsg); + + } else if(msg.status === "fault") { + + logger.warn("[PLUGIN] --> FAULT "+msg.name+": ", msg.logmsg); + d.resolve(msg.logmsg); + + } else if(msg.level === "error") { + + logger.error("[PLUGIN] --> ERROR "+ msg.name + ": " + msg.logmsg); + + } else if(msg.level === "warn") { + + logger.warn("[PLUGIN] --> WARNING "+ msg.name + ": " + msg.logmsg); + + } + else{ + logger.info("[PLUGIN] --> "+ msg.name + ": " + msg.logmsg); + + } + + } + else{ + //serve per gestire il primo messaggio alla creazione del child + logger.info("[PLUGIN] --> " + msg); + } + + + }); + + + //I send the input to the wrapper so that it can launch the proper plugin with the proper json file as argument + child.send(input_message); + + + break; + + + + case 'python': + + pySyncStarter(plugin_name, plugin_json).then( + + function (execRes) { + + if(execRes.result == "ERROR"){ + + logger.error("[PLUGIN] - '" + plugin_name + "' plugin execution error: "+JSON.stringify(execRes, null, "\t")); + + d.resolve(execRes); + + + } + else if (execRes.result == "SUCCESS") + d.resolve(execRes.message); + + + //update parameters and plugins.json conf file + try { + + var plugin_folder = PLUGINS_STORE + plugin_name; + var schema_outputFilename = plugin_folder + "/" + plugin_name + '.json'; + + fs.writeFile(schema_outputFilename, plugin_json, function(err) { + + if(err) { + + logger.error('[PLUGIN] --> Error parsing '+plugin_name+'.json file: ' + err); + + } else { + + logger.debug('[PLUGIN] --> Plugin JSON schema saved to ' + schema_outputFilename); + + try{ + + //Reading the plugin configuration file + var pluginsConf = JSON.parse(fs.readFileSync(PLUGINS_SETTING, 'utf8')); + + // - change the plugin status from "off" to "on" and update the PID value + pluginsConf.plugins[plugin_name].status = "off"; + pluginsConf.plugins[plugin_name].pid = ""; + + //updates the JSON file + fs.writeFile(PLUGINS_SETTING, JSON.stringify(pluginsConf, null, 4), function(err) { + + if(err) { + logger.error('[PLUGIN] --> Error writing plugins.json file: ' + err); + } else { + logger.debug("[PLUGIN] --> JSON file plugins.json updated -> " + plugin_name + ': status < '+ pluginsConf.plugins[plugin_name].status + ' > ' + pluginsConf.plugins[plugin_name].pid); + } + + }); + + } + catch(err){ + logger.error('Error updating JSON file plugins.json: '+ JSON.stringify(err)); + } + + + + } + + }); + + + } + catch(err){ + logger.error('Error updating JSON file plugins.json: '+ JSON.stringify(err)); + } + + + + + } + + ); + + + break; + + default: + + response.result = "ERROR"; + response.message = 'Wrong plugin type: ' + plugin_type; + logger.warn("[PLUGIN] - '" + plugin_name + "' plugin execution error: "+response.message); + d.resolve(response); + + break; + + } + + + + + + + + } + else{ + + response.result = "ERROR"; + response.message = "Sync plugin '" + plugin_name + "' already started on board '"+boardCode+"'!"; + logger.warn("[PLUGIN] --> " + response.message); + d.resolve(response); + } + + } + else{ + // Here the plugin does not exist + response.result = "ERROR"; + response.message = "Sync plugin '" + plugin_name + "' does not exist on board '"+boardCode+"'!"; + logger.error("[PLUGIN] --> " + response.message); + d.resolve(response); + } + + }else{ + response.result = "ERROR"; + response.message = 'Checksum plugin error!'; + logger.error("[PLUGIN] - '" + plugin_name + "' plugin execution error on board '"+boardCode+"': "+response.message); + d.resolve(response); + } + + return d.promise; +}; + + +// RPC to check if the plugin has to be restarted +exports.pluginKeepAlive = function (plugin_name, plugin_checksum){ + + try{ + + // Get the plugin's configuration. + var pluginsConf = JSON.parse(fs.readFileSync(PLUGINS_SETTING, 'utf8')); + var status = pluginsConf.plugins[plugin_name].status; + var autostart = pluginsConf.plugins[plugin_name].autostart; + + var plugin_folder = PLUGINS_STORE + plugin_name; + var plugin_json_name = plugin_folder + "/" + plugin_name + '.json'; + + var skip = "false"; + + // We have to restart only the plugins: + // - that the "autostart" flag is TRUE (boot enabled plugin) + // - that were in status "on" (it means that the device it was rebooted or LR crashed) even if "auotstart" is FALSE + if (status == "on" || autostart == "true"){ + + // We associate to each plugin that has to be restarted (no injected ones) a timer to check during LR execution if the plugin is still alive + if(status != "injected"){ + + /* + We have to verify this "injected" status condition because of when a plugin is just injected, with "autostart" set at true, has the following configuration: + "PLUGIN": { + "status": "injected", + "autostart": "true" + } + + */ + + // BUT we call NOW "pluginStarter" in order to start immediately the plugins that have to be, with "timer" parameter set to null, + // so in this way we don't wait for the timer expiration + pluginStarter(plugin_name, null, plugin_json_name, skip, plugin_checksum); + + var timer = setInterval(function() { + + pluginStarter(plugin_name, timer, plugin_json_name, skip, plugin_checksum); + + }, alive_timer * 1000); //LR checks if the plugin is alive + + plugins[plugin_name]={ + child: "", + pid: "", + alive: "", + timer: timer + } + + } + + } + + } catch(err){ logger.error('Error in keeping alive the plugin '+plugin_name+': '+ err); @@ -527,26 +1244,28 @@ exports.pluginKeepAlive = function (plugin_name){ }; -// RPC to restart all enabled plugins at LR startup...moreover associates a timer with each plugin to check if the plugin process is alive -exports.pluginsLoader = function (){ +// RPC to restart all enabled plugins at LR boot +exports.pluginsBootLoader = function (){ logger.info('[PLUGIN] - Plugins loader is running!'); - try{ - + PLUGIN_MODULE_LOADED = true; + + try{ + // Get the plugin's configuration. - var pluginsConf = JSON.parse(fs.readFileSync(PLUGINS_SETTING, 'utf8')); - - // Get the plugin json object list - var plugins_keys = Object.keys( pluginsConf["plugins"] ); + var pluginsConf = JSON.parse(fs.readFileSync(PLUGINS_SETTING, 'utf8')); + + // Get the plugin json object list + var plugins_keys = Object.keys( pluginsConf["plugins"] ); - // Get the number of plugins in the list "plugins_keys" in order to use it in the next loop - var plugin_num = plugins_keys.length; - logger.debug('[PLUGIN] - Number of installed plugins: '+ plugin_num); + // Get the number of plugins in the list "plugins_keys" in order to use it in the next loop + var plugin_num = plugins_keys.length; + logger.debug('[PLUGIN] - Number of installed plugins: '+ plugin_num); if(plugin_num > 0) { - logger.info('[PLUGIN] |- Restarting enabled plugins on the device: '); + var enabledPlugins = { "plugins":{}}; for (var i = 0; i < plugin_num; i++) { @@ -556,26 +1275,245 @@ exports.pluginsLoader = function (){ var status = pluginsConf.plugins[plugin_name].status; var autostart = pluginsConf.plugins[plugin_name].autostart; - logger.info('[PLUGIN] |--> ' + plugin_name + ' - status: ' + status + ' - autostart: ' + autostart); + // We have to restart only the plugins: + // - that the "autostart" flag is TRUE (boot enabled plugin) + // - that were in status "on" (it means that the device it was rebooted or LR crashed) even if "auotstart" is FALSE + if (status == "on" || autostart == "true"){ + + enabledPlugins.plugins[plugin_name] = pluginsConf.plugins[plugin_name] + + } + + if(i==plugin_num-1){ + + var enabled_keys = Object.keys( enabledPlugins["plugins"] ); + var enabled_num = enabled_keys.length; + + logger.debug('[PLUGIN] - Number of enabled plugins: '+ enabled_num); + + if(enabled_num > 0) { + + logger.info('[PLUGIN] |- Restarting enabled plugins on the device: '); + + for (var i = 0; i < enabled_num; i++) { + + (function (i) { + + var plugin_name = enabled_keys[i]; + var status = enabledPlugins.plugins[plugin_name].status; + var autostart = enabledPlugins.plugins[plugin_name].autostart; + var plugin_type = enabledPlugins.plugins[plugin_name].type; + + if(plugin_type == "nodejs") + var ext = '.js'; + else if(plugin_type == "python") + var ext ='.py'; + + + logger.info('[PLUGIN] |--> ' + plugin_name + ' - status: ' + status + ' - autostart: ' + autostart); + + + + setTimeout(function () { + + //var plugin_checksum = md5( fs.readFileSync(PLUGINS_STORE + plugin_name + "/"+plugin_name+ext, 'utf8')); + var plugin_checksum = CHECKSUMS_PLUGINS_LIST[plugin_name]; //if LR will start without connection to Iotronic this value will be "undefined" + + exports.pluginKeepAlive(plugin_name, plugin_checksum); + + }, 7000 * i); - setTimeout(function () { - exports.pluginKeepAlive(plugin_name); - }, 7000 * i); + + + })(i); + + } + + } + + + } + + })(i); } - + + }else{ logger.info('[PLUGIN] - No enabled plugins to be restarted!'); } + - } - catch(err){ + } + catch(err){ logger.warn('[PLUGIN] - Error parsing plugins.json: '+ err); - } + } + + +}; + + +// RPC to restart all enabled plugins at LR startup...moreover associates a timer with each plugin to check if the plugin process is alive +exports.pluginsLoader = function (){ + + logger.info('[PLUGIN] - Plugins loader is running!'); + try{ + + // Get plugins checksum from Iotronic + session_plugins.call('s4t.iotronic.plugin.checksum', [boardCode]).then( + + function (rpc_response) { + + if (rpc_response.result == "ERROR") { + + logger.error("[PLUGIN] --> Getting plugin checksum list failed: " + rpc_response.message); + + } + else { + + CHECKSUMS_PLUGINS_LIST = rpc_response.message; + + try{ + + // Get the plugin's configuration. + var pluginsConf = JSON.parse(fs.readFileSync(PLUGINS_SETTING, 'utf8')); + + // Get the plugin json object list + var plugins_keys = Object.keys( pluginsConf["plugins"] ); + + // Get the number of plugins in the list "plugins_keys" in order to use it in the next loop + var plugin_num = plugins_keys.length; + logger.debug('[PLUGIN] - Number of installed plugins: '+ plugin_num); + + if(plugin_num > 0) { + + var enabledPlugins = { "plugins":{}}; + + for (var i = 0; i < plugin_num; i++) { + + (function (i) { + + var plugin_name = plugins_keys[i]; + var status = pluginsConf.plugins[plugin_name].status; + var autostart = pluginsConf.plugins[plugin_name].autostart; + + // We have to restart only the plugins: + // - that the "autostart" flag is TRUE (boot enabled plugin) + // - that were in status "on" (it means that the device it was rebooted or LR crashed) even if "auotstart" is FALSE + if (status == "on" || autostart == "true"){ + + enabledPlugins.plugins[plugin_name] = pluginsConf.plugins[plugin_name] + + } + + if(i==plugin_num-1){ + + var enabled_keys = Object.keys( enabledPlugins["plugins"] ); + var enabled_num = enabled_keys.length; + + logger.debug('[PLUGIN] - Number of enabled plugins: '+ enabled_num); + + if(enabled_num > 0) { + + logger.info('[PLUGIN] |- Restarting enabled plugins on the device: '); + //console.log(enabledPlugins); + + for (var i = 0; i < enabled_num; i++) { + + (function (i) { + + var plugin_name = enabled_keys[i]; + var status = enabledPlugins.plugins[plugin_name].status; + var autostart = enabledPlugins.plugins[plugin_name].autostart; + var plugin_type = enabledPlugins.plugins[plugin_name].type; + var plugin_version = enabledPlugins.plugins[plugin_name].version; + + + if(plugin_type == "nodejs") + var ext = '.js'; + else if(plugin_type == "python") + var ext ='.py'; + + var checksum = md5( fs.readFileSync(PLUGINS_STORE + plugin_name + "/"+plugin_name+ext, 'utf8')); + var plugin_checksum = CHECKSUMS_PLUGINS_LIST[plugin_name]; + + if(plugin_checksum == checksum){ + + logger.info('[PLUGIN] |--> ' + plugin_name + ' - status: ' + status + ' - autostart: ' + autostart); + + setTimeout(function () { + + exports.pluginKeepAlive(plugin_name, plugin_checksum); + + }, 7000 * i); + + }else{ + + logger.warn('[PLUGIN] |--> ' + plugin_name + ' - checksums mismatch: ' + checksum + ' - correct checksum: ' + plugin_checksum); + + session_plugins.call('s4t.iotronic.plugin.invalidPlugin', [boardCode, plugin_name, plugin_version]).then( + + function (rpc_response) { + + if (rpc_response.result == "ERROR") { + + logger.error("[PLUGIN] --> Error notification plugin checksum mismatch for '" + plugin_name + "' plugin: " + rpc_response.message); + + } + else { + + logger.debug("[PLUGIN] - Invalidation plugin response: " + rpc_response.message); + + } + + } + ); + + } + + })(i); + + } + + } + + + } + + + + })(i); + + } + + + } + else{ + logger.info('[PLUGIN] - No enabled plugins to be restarted!'); + } + + } + catch(err){ + logger.warn('[PLUGIN] - Error parsing plugins.json: '+ err); + } + + + + } + } + + ); + + + } + catch(err){ + logger.warn('[PLUGIN-CONNECTION-RECOVERY] - Error calling "s4t.iotronic.isAlive"'); + } + }; @@ -586,182 +1524,248 @@ exports.run = function (args){ //Parsing the input arguments var plugin_name = String(args[0]); var plugin_json = String(args[1]); - + var plugin_checksum = String(args[2]); + // The autostart parameter at RUN stage is OPTIONAL. It is used at this stage if the user needs to change the boot execution configuration of the plugin after the INJECTION stage. var plugin_autostart = ""; - var d = Q.defer(); + var d = Q.defer(); + + var response = { + message: '', + result: '' + }; + + logger.info('[PLUGIN] - Async plugin RPC called for plugin "'+ plugin_name +'" plugin...'); + logger.info("[PLUGIN] --> Input parameters:\n"+ plugin_json); + + + try{ + //Reading the plugin configuration file + var pluginsConf = JSON.parse(fs.readFileSync(PLUGINS_SETTING, 'utf8')); + + var status = pluginsConf.plugins[plugin_name].status; + var plugin_type = pluginsConf.plugins[plugin_name].type; + + } + catch(err){ + response.result = "ERROR"; + response.message = 'Error parsing plugins.json!'; + logger.error('[PLUGIN] - "' + plugin_name + '" plugin execution error: '+response.message); + d.resolve(response); + } + + + if(plugin_type == "nodejs") + var ext = '.js'; + else if(plugin_type == "python") + var ext ='.py'; + + + var checksum = md5( fs.readFileSync(PLUGINS_STORE + plugin_name + "/"+plugin_name+ext, 'utf8')); + + if(checksum === plugin_checksum){ + + //If the plugin exists + if(pluginsConf["plugins"].hasOwnProperty(plugin_name)){ + + logger.debug('[PLUGIN] - '+ plugin_name + ' - Plugin configuration successfully loaded!'); + + //Check the status + var status = pluginsConf.plugins[plugin_name].status; + + if (status == "off" || status == "injected"){ + + logger.info('[PLUGIN] - '+ plugin_name + ' - Plugin starting...'); + + + // Check the plugin type: "nodejs" or "python" + + switch (plugin_type) { + + case 'nodejs': + + //Create a new process that has wrapper that manages the plugin execution + var child = cp.fork(LIGHTNINGROD_HOME + '/modules/plugins-manager/nodejs/async-wrapper'); + + //Prepare the message I will send to the process with name of the plugin to start and json file as argument + var input_message = { + "plugin_name": plugin_name, + "plugin_json": JSON.parse(plugin_json) + }; + + child.on('message', function(msg) { + + if(msg.name != undefined){ + + if (msg.status === "alive"){ + + //Creating the plugin json schema + var plugin_folder = PLUGINS_STORE + plugin_name; + var schema_outputFilename = plugin_folder + "/" + plugin_name + '.json'; + + fs.writeFile(schema_outputFilename, plugin_json, function(err) { + + if(err) { + response.result = "ERROR"; + response.message = 'Error opening '+plugin_name+'.json file: ' + err; + logger.error('[PLUGIN] - "'+plugin_name + '" - '+response.message); + d.resolve(response); + + } else { + + logger.info('[PLUGIN] - '+ plugin_name + ' - Plugin JSON schema saved to ' + schema_outputFilename); + + // Reading the plugins.json configuration file + try{ + + var pluginsConf = JSON.parse(fs.readFileSync(PLUGINS_SETTING, 'utf8')); + var pluginsSchemaConf = JSON.parse(fs.readFileSync(schema_outputFilename, 'utf8')); + + //Get the autostart parameter from the schema just uploaded + plugin_autostart = pluginsSchemaConf.autostart; + + + } + catch(err){ + + response.result = "ERROR"; + response.message = 'Error parsing plugins.json configuration file: ' + err; + logger.error('[PLUGIN] - '+plugin_name + ' - '+response.message); + + d.resolve(response); + + } + + // Updating the plugins.json file: + // - check if the user changed the autostart parameter at this stage + if(plugin_autostart != undefined){ + + pluginsConf.plugins[plugin_name].autostart = plugin_autostart; + logger.info('[PLUGIN] - '+ plugin_name + ' - Autostart parameter set by user to ' + plugin_autostart); + + } else { + + logger.info('[PLUGIN] - '+ plugin_name + ' - Autostart parameter not changed!'); + + } + + // - change the plugin status from "off" to "on" and update the PID value + pluginsConf.plugins[plugin_name].status = "on"; + pluginsConf.plugins[plugin_name].pid = child.pid; + + fs.writeFile(PLUGINS_SETTING, JSON.stringify(pluginsConf, null, 4), function(err) { + + if(err) { + logger.error('[PLUGIN] - '+ plugin_name + ' - Error opening '+plugin_name+'.json file: ' + err); + } else { + logger.info('[PLUGIN] - '+ plugin_name + ' - plugins.json updated -> autostart < ' + pluginsConf.plugins[plugin_name].autostart + ' > - status < '+ pluginsConf.plugins[plugin_name].status + ' > ' + pluginsConf.plugins[plugin_name].pid); + + // Start a timer to check every X minutes if the plugin is still alive! + exports.pluginKeepAlive(plugin_name, plugin_checksum); + + } + + }); + + } + + }); + + + } else if(msg.level === "error") { + + logger.error("[PLUGIN] - "+ msg.name + " - " + msg.logmsg); + + } else if(msg.level === "warn") { + + logger.warn("[PLUGIN] - "+ msg.name + " - " + msg.logmsg); + + } else{ + + logger.info("[PLUGIN] - "+ msg.name + " - " + msg.logmsg); + + } + + } else{ + //serve per gestire il primo messaggio alla creazione del child + logger.info("[PLUGIN] --> "+ msg); + } + + + }); + + //I send the input to the wrapper so that it can launch the proper plugin with the proper json file as argument + child.send(input_message); + + response.result = "SUCCESS"; + response.message = 'Plugin is running!'; + logger.info('[PLUGIN] - '+plugin_name + ' - '+response.message); + d.resolve(response); + + break; + + + + case 'python': + + pyAsyncStarter(plugin_name, plugin_json, plugin_checksum, "start").then( + + function (execRes) { + d.resolve(execRes); + } + + ); + + break; + + + default: + + response.result = "ERROR"; + response.message = 'Wrong plugin type: ' + plugin_type; + logger.warn('[PLUGIN] - "' + plugin_name + '" plugin execution error: '+response.message); + d.resolve(response); - var response = { - message: '', - result: '' - }; - - logger.info('[PLUGIN] - Run plugin RPC called for plugin "'+ plugin_name +'" plugin...'); - logger.info("[PLUGIN] --> Input parameters:\n"+ plugin_json); - - try{ - //Reading the plugin configuration file - var pluginsConf = JSON.parse(fs.readFileSync(PLUGINS_SETTING, 'utf8')); - } - catch(err){ - response.result = "ERROR"; - response.message = 'Error parsing plugins.json!'; - logger.error('[PLUGIN] - "' + plugin_name + '" plugin execution error: '+response.message); - d.resolve(response); - } - - //If the plugin exists - if(pluginsConf["plugins"].hasOwnProperty(plugin_name)){ - - logger.debug('[PLUGIN] - '+ plugin_name + ' - Plugin configuration successfully loaded!'); - - //Check the status - var status = pluginsConf.plugins[plugin_name].status; - - if (status == "off" || status == "injected"){ - - logger.info('[PLUGIN] - '+ plugin_name + ' - Plugin starting...'); - - //Create a new process that has plugin-wrapper as code - var child = cp.fork(LIGHTNINGROD_HOME + '/modules/plugins-manager/plugin-wrapper'); - - //Prepare the message I will send to the process with name of the plugin to start and json file as argument - var input_message = { - "plugin_name": plugin_name, - "plugin_json": JSON.parse(plugin_json) - }; - - child.on('message', function(msg) { - - if(msg.name != undefined){ - - if (msg.status === "alive"){ - - //Creating the plugin json schema - var plugin_folder = PLUGINS_STORE + plugin_name; - var schema_outputFilename = plugin_folder + "/" + plugin_name + '.json'; - - fs.writeFile(schema_outputFilename, plugin_json, function(err) { - - if(err) { - response.result = "ERROR"; - response.message = 'Error opening '+plugin_name+'.json file: ' + err; - logger.error('[PLUGIN] - "'+plugin_name + '" - '+response.message); - d.resolve(response); - - } else { - - logger.info('[PLUGIN] - '+ plugin_name + ' - Plugin JSON schema saved to ' + schema_outputFilename); - - // Reading the plugins.json configuration file - try{ - - var pluginsConf = JSON.parse(fs.readFileSync(PLUGINS_SETTING, 'utf8')); - var pluginsSchemaConf = JSON.parse(fs.readFileSync(schema_outputFilename, 'utf8')); - - //Get the autostart parameter from the schema just uploaded - plugin_autostart = pluginsSchemaConf.autostart; - - - } - catch(err){ + break; - response.result = "ERROR"; - response.message = 'Error parsing plugins.json configuration file: ' + err; - logger.error('[PLUGIN] - '+plugin_name + ' - '+response.message); - d.resolve(response); - } - - // Updating the plugins.json file: - // - check if the user changed the autostart parameter at this stage - if(plugin_autostart != undefined){ - - pluginsConf.plugins[plugin_name].autostart = plugin_autostart; - logger.info('[PLUGIN] - '+ plugin_name + ' - Autostart parameter set by user to ' + plugin_autostart); - - } else { - - logger.info('[PLUGIN] - '+ plugin_name + ' - Autostart parameter not changed!'); - - } - - // - change the plugin status from "off" to "on" and update the PID value - pluginsConf.plugins[plugin_name].status = "on"; - pluginsConf.plugins[plugin_name].pid = child.pid; - - fs.writeFile(PLUGINS_SETTING, JSON.stringify(pluginsConf, null, 4), function(err) { - - if(err) { - logger.error('[PLUGIN] - '+ plugin_name + ' - Error opening '+plugin_name+'.json file: ' + err); - } else { - logger.info('[PLUGIN] - '+ plugin_name + ' - plugins.json updated -> autostart < ' + pluginsConf.plugins[plugin_name].autostart + ' > - status < '+ pluginsConf.plugins[plugin_name].status + ' > ' + pluginsConf.plugins[plugin_name].pid); - - // Start a timer to check every X minutes if the plugin is still alive! - exports.pluginKeepAlive(plugin_name); - - } - - }); - - } - - }); - - - } else if(msg.level === "error") { - - logger.error("[PLUGIN] - "+ msg.name + " - " + msg.logmsg); - - } else if(msg.level === "warn") { - - logger.warn("[PLUGIN] - "+ msg.name + " - " + msg.logmsg); - - } else{ + } - logger.info("[PLUGIN] - "+ msg.name + " - " + msg.logmsg); - } - - } else{ - //serve per gestire il primo messaggio alla creazione del child - logger.info("[PLUGIN] --> "+ msg); - } - - }); - //I send the input to the wrapper so that it can launch the proper plugin with the proper json file as argument - child.send(input_message); + } + else{ - response.result = "SUCCESS"; - response.message = 'Plugin is running!'; - logger.info('[PLUGIN] - '+plugin_name + ' - '+response.message); - d.resolve(response); + response.result = "WARNING"; + response.message = 'Plugin already started on this board!'; + logger.warn('[PLUGIN] - '+plugin_name+' - '+response.message); + d.resolve(response); + } - } - else{ + } + else{ + // Here the plugin does not exist - response.result = "WARNING"; - response.message = 'Plugin already started on this board!'; - logger.warn('[PLUGIN] - '+plugin_name+' - '+response.message); + response.result = "ERROR"; + response.message = "Plugin '" + plugin_name + "' does not exist on this board!"; + logger.warn('[PLUGIN] - '+plugin_name + ' - '+response.message); d.resolve(response); - } - - } - else{ - // Here the plugin does not exist - + } + + }else{ response.result = "ERROR"; - response.message = "Plugin '" + plugin_name + "' does not exist on this board!"; - logger.warn('[PLUGIN] - '+plugin_name + ' - '+response.message); + response.message = 'Checksum plugin error!'; + logger.error('[PLUGIN] - "' + plugin_name + '" plugin execution error: '+response.message); d.resolve(response); + } + - } + return d.promise; @@ -799,33 +1803,45 @@ exports.kill = function (args){ logger.info('[PLUGIN] --> '+ plugin_name + ' - Plugin (with PID='+pid+') being stopped!'); //PLUGIN KILLING - process.kill(pid); - - pluginsConf.plugins[plugin_name].status = "off"; - pluginsConf.plugins[plugin_name].pid = ""; + try{ - // updates the JSON file - fs.writeFile(PLUGINS_SETTING, JSON.stringify(pluginsConf, null, 4), function(err) { - - if(err) { + process.kill(pid); - response.result = "ERROR"; - response.message = 'Error writing plugins.json: '+ err; - logger.error('[PLUGIN] - stop plugin '+plugin_name + ' error: '+response.message); - d.resolve(response); + } + catch(err){ - } else { - logger.debug("[PLUGIN] --> " + PLUGINS_SETTING + " updated!"); - clearPluginTimer(plugin_name); - response.result = "SUCCESS"; - response.message = 'Plugin killed!'; - logger.info('[PLUGIN] - stop plugin '+plugin_name + ': '+response.message); - d.resolve(response); - } + response.result = "ERROR"; + response.message = 'Error killing plugin: '+ err; + logger.error('[PLUGIN] - stop plugin "'+plugin_name + '" error: '+response.message); + d.resolve(response); + + }finally { + + pluginsConf.plugins[plugin_name].status = "off"; + pluginsConf.plugins[plugin_name].pid = ""; + + // updates the JSON file + fs.writeFile(PLUGINS_SETTING, JSON.stringify(pluginsConf, null, 4), function(err) { + + if(err) { + + response.result = "ERROR"; + response.message = 'Error writing plugins.json: '+ err; + logger.error('[PLUGIN] - stop plugin '+plugin_name + ' error: '+response.message); + d.resolve(response); - }); + } else { + logger.debug("[PLUGIN] --> " + PLUGINS_SETTING + " updated!"); + clearPluginTimer(plugin_name); + response.result = "SUCCESS"; + response.message = 'Plugin killed!'; + logger.info('[PLUGIN] - stop plugin '+plugin_name + ': '+response.message); + d.resolve(response); + } + }); + } } else{ @@ -862,15 +1878,9 @@ exports.kill = function (args){ exports.injectPlugin = function(args){ // Parsing the input arguments - plugin_name = String(args[0]); - plugin_code = String(args[1]); - - // The autostart parameter is used to set the boot execution configuration of the plugin. - autostart = String(args[2]); - - logger.info("[PLUGIN] - Injecting plugin RPC called for "+plugin_name+" plugin..."); - logger.debug("[PLUGIN] --> Parameters injected: { plugin_name : " + plugin_name + ", autostart : " + autostart + " }"); - logger.debug("[PLUGIN] --> plugin code:\n\n" + JSON.stringify(plugin_code) + "\n\n"); + var plugin_bundle = JSON.parse(args[0]); + var autostart = String(args[1]); // The autostart parameter is used to set the boot execution configuration of the plugin. + var force = String(args[2]); // If specified -> overwrite the plugin previously injected var response = { message: '', @@ -879,28 +1889,41 @@ exports.injectPlugin = function(args){ var d = Q.defer(); + var plugin_code = plugin_bundle.code; + var plugin_name = plugin_bundle.name; + + + logger.info("[PLUGIN] - Injecting plugin RPC called for '"+plugin_name+"' plugin..."); + logger.debug("[PLUGIN] --> Parameters injected: { plugin_name : " + plugin_name + ", autostart : " + autostart + ", force : " + force + " }"); + //logger.debug("[PLUGIN] --> plugin code:\n\n" + plugin_code + "\n\n"); + logger.debug(JSON.stringify(plugin_bundle, null, "\t")) + var plugin_folder = PLUGINS_STORE + plugin_name; - var fileName = plugin_folder + "/" + plugin_name + '.js'; - + + if(plugin_bundle.type == "nodejs") + var fileName = plugin_folder + "/" + plugin_name + '.js'; + else if(plugin_bundle.type == "python") + var fileName = plugin_folder + "/" + plugin_name + '.py'; + cleanPluginData(plugin_name).then( function (clean_res) { - if (clean_res.result == "SUCCESS"){ + if (clean_res.result == "SUCCESS") { clean_res.message = "plugin '" + plugin_name + "' environment is clean!"; logger.debug("[PLUGIN] ----> " + clean_res.message); // plugin folder creation - fs.mkdir(plugin_folder, function() { + fs.mkdir(plugin_folder, function () { // Writing the file - fs.writeFile(fileName, plugin_code, function(err) { + fs.writeFile(fileName, plugin_code, function (err) { - if(err) { + if (err) { response.result = "ERROR"; - response.message = 'Error writing '+ fileName +' file: ' + err; + response.message = 'Error writing ' + fileName + ' file: ' + err; logger.error('[PLUGIN] --> ' + response.message); d.resolve(response); @@ -913,15 +1936,18 @@ exports.injectPlugin = function(args){ pluginsConf.plugins[plugin_name] = {}; pluginsConf.plugins[plugin_name]['status'] = "injected"; - if(autostart != undefined){ + pluginsConf.plugins[plugin_name]['version'] = plugin_bundle.version; + pluginsConf.plugins[plugin_name]['type'] = plugin_bundle.type; + + if (autostart != undefined) pluginsConf.plugins[plugin_name]['autostart'] = autostart; - } else { + else pluginsConf.plugins[plugin_name]['autostart'] = false; - } + //Update plugins.json config file - fs.writeFile(PLUGINS_SETTING, JSON.stringify(pluginsConf, null, 4), function(err) { - if(err) { + fs.writeFile(PLUGINS_SETTING, JSON.stringify(pluginsConf, null, 4), function (err) { + if (err) { response.result = "ERROR"; response.message = 'Error writing plugins.json file: ' + err; @@ -930,33 +1956,62 @@ exports.injectPlugin = function(args){ } else { + logger.debug("[PLUGIN] --> Configuration in plugins.json updated!"); - response.result = "SUCCESS"; - response.message = "Plugin '"+ plugin_name +"' injected successfully!"; - logger.info('[PLUGIN] --> ' + response.message); - d.resolve(response); + + // Write default parameters for the plugin + + var plugin_folder = PLUGINS_STORE + plugin_name; + var pluginsParamsFilename = plugin_folder + "/" + plugin_name + '.json'; + + //Reading the plugins configuration file + var pluginsParams = plugin_bundle.defaults; + + fs.writeFile(pluginsParamsFilename, JSON.stringify(JSON.parse(pluginsParams), null, 4), function (err) { + if (err) { + + response.result = "ERROR"; + response.message = 'Error writing default parameters: ' + err; + logger.error('[PLUGIN] --> ' + response.message); + d.resolve(response); + + } else { + logger.debug("[PLUGIN] --> Default parameters written!"); + + response.result = "SUCCESS"; + response.message = "Plugin '" + plugin_name + "' injected successfully!"; + logger.info('[PLUGIN] --> ' + response.message); + d.resolve(response); + + } + }); } }); + } + }); + }); - }else{ + } else { logger.error("[PLUGIN] --> " + clean_res.message); d.resolve(clean_res.message); } - - } ); + + return d.promise; + + }; @@ -966,8 +2021,8 @@ exports.removePlugin = function(args){ // Parsing the input arguments var plugin_name = String(args[0]); - - logger.info("[PLUGIN] - Removing plugin RPC called for " + plugin_name +" plugin..."); + + logger.info("[PLUGIN] - Removing plugin RPC called for '" + plugin_name + "' plugin..."); var d = Q.defer(); @@ -1037,8 +2092,7 @@ exports.removePlugin = function(args){ exports.restartPlugin = function(args){ var plugin_name = String(args[0]); - - + var plugin_checksum = String(args[1]); logger.info('[PLUGIN] - Restart plugin RPC called for plugin "'+ plugin_name +'" plugin...'); @@ -1051,9 +2105,30 @@ exports.restartPlugin = function(args){ // Get the plugin's configuration. try{ - + //Reading the plugins.json configuration file var pluginsConf = JSON.parse(fs.readFileSync(PLUGINS_SETTING, 'utf8')); + var plugin_type = pluginsConf.plugins[plugin_name].type; + + + } + catch(err){ + response.result = "ERROR"; + response.message = 'Error parsing plugins.json!'; + logger.error('[PLUGIN] - "' + plugin_name + '" plugin execution error: '+response.message); + d.resolve(response); + } + + if(plugin_type == "nodejs") + var ext = '.js'; + else if(plugin_type == "python") + var ext ='.py'; + + + var checksum = md5( fs.readFileSync(PLUGINS_STORE + plugin_name + "/"+plugin_name+ext, 'utf8')); + + if(checksum === plugin_checksum){ + //If the plugin exists if(pluginsConf["plugins"].hasOwnProperty(plugin_name)){ @@ -1065,9 +2140,9 @@ exports.restartPlugin = function(args){ if(response.result == "SUCCESS" || response.code == "NO-RUN"){ var plugin_json_name = PLUGINS_STORE + plugin_name + "/" + plugin_name + '.json'; - var plugin_json_schema = JSON.parse(fs.readFileSync(plugin_json_name, 'utf8')); + var plugin_json_schema = fs.readFileSync(plugin_json_name, 'utf8'); - exports.run([plugin_name, JSON.stringify(plugin_json_schema)]).then( + exports.run([plugin_name, plugin_json_schema, plugin_checksum]).then( function (response) { @@ -1114,11 +2189,13 @@ exports.restartPlugin = function(args){ } - catch(err){ - logger.error('[PLUGIN] --> Error parsing JSON file plugins.json'); + else{ + response.result = "ERROR"; + response.message = 'Checksum plugin error!'; + logger.error('[PLUGIN] - "' + plugin_name + '" plugin execution error: '+response.message); + d.resolve(response); } - return d.promise; @@ -1126,9 +2203,58 @@ exports.restartPlugin = function(args){ -//This function exports all the functions in the module as WAMP remote procedure calls -exports.exportPluginCommands = function (session){ + +// RPC to manage the removal of a plugin from the device: it is called by Iotronic via RPC +exports.getPluginLogs = function(args){ + + // Parsing the input arguments + var plugin_name = String(args[0]); + var rows = String(args[1]); + + logger.info("[PLUGIN] - Getting plugin logs RPC called for '" + plugin_name + "' plugin..."); + + var d = Q.defer(); + + var response = { + message: '', + result: '' + }; + + + fs.readFile('/var/log/iotronic/plugins/'+plugin_name+'.log', 'utf-8', function(err, data) { + + if(err != null){ + response.message = 'Error retrieving plugin logs: '+err; + response.result = "ERROR"; + logger.warn("[PLUGIN] --> " + response.message); + d.resolve(response); + } + else{ + var lines = data.trim().split('\n'); + var lastLine = lines.slice(-rows); + console.log(lastLine); + + response.message = lastLine; //"Plugin logs for '" + plugin_name + "' successfully retrieved!"; + response.result = "SUCCESS"; + logger.info("[PLUGIN] --> Plugin logs for '" + plugin_name + "' successfully retrieved!"); + d.resolve(response); + } + + + }); + + return d.promise; + +}; + + + +//This function exports all the functions in the module as WAMP remote procedure calls +exports.Init = function (session){ + + session_plugins = session; + //Register all the module functions as WAMP RPCs session.register('s4t.'+ boardCode+'.plugin.run', exports.run); session.register('s4t.'+ boardCode+'.plugin.kill', exports.kill); @@ -1136,8 +2262,165 @@ exports.exportPluginCommands = function (session){ session.register('s4t.'+ boardCode+'.plugin.call', exports.call); session.register('s4t.'+ boardCode+'.plugin.remove', exports.removePlugin); session.register('s4t.'+ boardCode+'.plugin.restart', exports.restartPlugin); + session.register('s4t.'+ boardCode+'.plugin.logs', exports.getPluginLogs); + + logger.info('[WAMP-EXPORTS] Plugin commands exported to the cloud!'); + + + // PLUGINS RESTART ALL -------------------------------------------------------------------------------- + //This procedure restarts all plugins in "ON" status + //if(PLUGIN_MODULE_LOADED == false) + // exports.pluginsLoader(); + //----------------------------------------------------------------------------------------------------- + + }; + +//This function executes procedures at boot time (no Iotronic dependent) +exports.Boot = function (){ + + logger.info('[BOOT] - Plugin Manager booting'); + logger.debug('[BOOT] --> plugin alive check timer: ' + alive_timer + ' seconds'); + + // connectionTester: library used to check the reachability of Iotronic-Server/WAMP-Server + var connectionTester = require('connection-tester'); + + setTimeout(function(){ + + connectionTester.test(wampIP, port_wamp, 1000, function (err, output) { + + var reachable = output.success; + var error_test = output.error; + + if (!reachable) { + + //CONNECTION STATUS: FALSE + logger.warn("[CONNECTION-RECOVERY] - INTERNET CONNECTION STATUS: " + reachable + " - ERROR: " + error_test); + + exports.pluginsBootLoader(); + + checkIotronicWampConnection = setInterval(function(){ + + logger.warn("[PLUGIN-CONNECTION-RECOVERY] - RETRY..."); + + connectionTester.test(wampIP, port_wamp, 1000, function (err, output) { + + var reachable = output.success; + var error_test = output.error; + + if (!reachable) { + + //CONNECTION STATUS: FALSE + logger.warn("[PLUGIN-CONNECTION-RECOVERY] - INTERNET CONNECTION STATUS: " + reachable + " - ERROR: " + error_test); + + }else{ + + try { + + // Test if IoTronic is connected to the realm + session_plugins.call("s4t.iotronic.isAlive", [boardCode]).then( + + function(response){ + + // Get plugins checksum from Iotronic + session_plugins.call('s4t.iotronic.plugin.checksum', [boardCode]).then( + + function (rpc_response) { + + if (rpc_response.result == "ERROR") { + + logger.error("[PLUGIN-CONNECTION-RECOVERY] --> Getting plugin checksum list failed: " + rpc_response.message); + + } + else { + + CHECKSUMS_PLUGINS_LIST = rpc_response.message; + + logger.debug("[PLUGIN-CONNECTION-RECOVERY] --> Plugins checksums list recovered: ", CHECKSUMS_PLUGINS_LIST); + + clearInterval( checkIotronicWampConnection ); + + } + + } + + ); + + }, + function(err){ + + logger.warn("NO WAMP CONNECTION YET!") + + } + + ); + + } + catch(err){ + logger.warn('[PLUGIN-CONNECTION-RECOVERY] - Error calling "s4t.iotronic.isAlive"'); + } + + + } + + }); + + + }, alive_timer * 1000); + + + } + else{ + + + checkIotronicWampConnection = setInterval(function(){ + + try { + + // Test if IoTronic is connected to the realm + session_plugins.call("s4t.iotronic.isAlive", [boardCode]).then( + + function(response){ + + exports.pluginsLoader(); + + clearInterval( checkIotronicWampConnection ); + + + }, + function(err){ + + logger.warn("NO WAMP CONNECTION YET!") + + } + + ); + + } + catch(err){ + logger.warn('[PLUGIN-CONNECTION-RECOVERY] - Internet connection available BUT wamp session not established!'); + if(PLUGIN_MODULE_LOADED == false) + exports.pluginsBootLoader(); + + } + + + + }, alive_timer * 1000); + + + + + } + + }); + + }, 5000); + + + +}; \ No newline at end of file diff --git a/modules/plugins-manager/plugin-wrapper.js b/modules/plugins-manager/nodejs/async-wrapper.js similarity index 92% rename from modules/plugins-manager/plugin-wrapper.js rename to modules/plugins-manager/nodejs/async-wrapper.js index c86adae..317bb7f 100644 --- a/modules/plugins-manager/plugin-wrapper.js +++ b/modules/plugins-manager/nodejs/async-wrapper.js @@ -30,6 +30,9 @@ process.once('message', function(message) { plugin_name = message.plugin_name; plugin_json = message.plugin_json; + var LIGHTNINGROD_HOME = process.env.LIGHTNINGROD_HOME; + var api = require(LIGHTNINGROD_HOME + '/modules/plugins-manager/nodejs/plugin-apis'); + var plugin_folder = PLUGINS_STORE + plugin_name; var fileName = plugin_folder + "/" + plugin_name + '.js'; @@ -43,7 +46,7 @@ process.once('message', function(message) { process.send({ name: plugin_name, status: "alive"}); - plugin.main(plugin_json); + plugin.main(plugin_name, plugin_json, api); } else{ diff --git a/modules/plugins-manager/plugin-apis.js b/modules/plugins-manager/nodejs/plugin-apis.js similarity index 80% rename from modules/plugins-manager/plugin-apis.js rename to modules/plugins-manager/nodejs/plugin-apis.js index 2b4045d..405e44c 100644 --- a/modules/plugins-manager/plugin-apis.js +++ b/modules/plugins-manager/nodejs/plugin-apis.js @@ -21,21 +21,25 @@ SETTINGS = process.env.IOTRONIC_HOME+'/settings.json'; nconf = require('nconf'); nconf.file ({file: SETTINGS}); -log4js = require('log4js'); -log4js.loadAppender('file'); +var Q = require("q"); -logfile = nconf.get('config:log:logfile'); -loglevel = nconf.get('config:log:loglevel'); -log4js.addAppender(log4js.appenders.file(logfile)); -var logger = log4js.getLogger('plugin-apis'); -logger.setLevel(loglevel); +exports.getLogger = function (plugin_name, loglevel){ -var Q = require("q"); + log4js = require('log4js'); + log4js.loadAppender('file'); + + //logfile = nconf.get('config:log:logfile'); + //loglevel = nconf.get('config:log:loglevel'); + logfile = '/var/log/iotronic/plugins/'+plugin_name+'.log'; -exports.getLogger = function (){ + log4js.addAppender(log4js.appenders.file(logfile)); + var logger = log4js.getLogger(plugin_name); + logger.setLevel(loglevel); + return logger; + }; exports.getPosition = function (){ diff --git a/modules/plugins-manager/call-wrapper.js b/modules/plugins-manager/nodejs/sync-wrapper.js similarity index 88% rename from modules/plugins-manager/call-wrapper.js rename to modules/plugins-manager/nodejs/sync-wrapper.js index 233a6cc..6e29d84 100644 --- a/modules/plugins-manager/call-wrapper.js +++ b/modules/plugins-manager/nodejs/sync-wrapper.js @@ -34,16 +34,19 @@ process.once('message', function(message) { if (fs.existsSync(fileName) === true){ var plugin = require(plugin_folder + "/" + plugin_name); + + var LIGHTNINGROD_HOME = process.env.LIGHTNINGROD_HOME; + var api = require(LIGHTNINGROD_HOME + '/modules/plugins-manager/nodejs/plugin-apis'); process.send({ name: plugin_name, status: true , logmsg: "I'm alive!"}); process.send({ name: plugin_name, level: "info" , logmsg: "starting..."}); process.send({ name: plugin_name, status: "alive"}); - plugin.main(plugin_json, function(err, result){ - - process.send({ name: plugin_name, status: "finish", logmsg: result}); + plugin.main(plugin_name, plugin_json, api, function(err, result){ - }); + process.send({ name: plugin_name, status: "finish", logmsg: result}); + + }); } else{ diff --git a/modules/plugins-manager/plugins.examples/call_bar.js b/modules/plugins-manager/plugins.examples/call_bar.js deleted file mode 100644 index 5995809..0000000 --- a/modules/plugins-manager/plugins.examples/call_bar.js +++ /dev/null @@ -1,68 +0,0 @@ -//############################################################################################ -//## -//# Copyright (C) 2015-2016 Nicola Peditto -//## -//# Licensed under the Apache License, Version 2.0 (the "License"); -//# you may not use this file except in compliance with the License. -//# You may obtain a copy of the License at -//## -//# http://www.apache.org/licenses/LICENSE-2.0 -//## -//# Unless required by applicable law or agreed to in writing, software -//# distributed under the License is distributed on an "AS IS" BASIS, -//# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -//# See the License for the specific language governing permissions and -//# limitations under the License. -//## -//############################################################################################ - -exports.main = function (arguments, callback){ - - /* {"m_authid" : "", "m_resourceid" : "", "autostart":"false"} */ - - var m_authid = arguments.m_authid; - var m_resourceid = arguments.m_resourceid; - - var api = require('../modules/plugins-manager/plugin-apis'); - var position = api.getPosition(); - - var linino = require('ideino-linino-lib'); - var board = new linino.Board(); - - board.addI2c('BAR', 'mpl3115', '0x60', 0); - - - board.connect(function() { - - var record = []; - - var in_pressure_raw = board.i2cRead('BAR', 'in_pressure_raw'); - //var in_pressure_scale = board.i2cRead('BAR', 'in_pressure_scale'); - var pressure = in_pressure_raw*0.00025*10; - - - record.push({ - Date: new Date().toISOString(), - Pressure: pressure, - Altitude: position.altitude, - Latitude: position.latitude, - Longitude: position.longitude - }); - - api.sendToCKAN(m_authid, m_resourceid, record, function(payloadJSON){ - - results="Pressure: " + pressure + " hPa"; - console.log("PAYLOAD:\n" + payloadJSON); - console.log(results); - callback("OK", results); - }); - - - - }); - - - - - -} \ No newline at end of file diff --git a/modules/plugins-manager/plugins.examples/call_gas.js b/modules/plugins-manager/plugins.examples/call_gas.js deleted file mode 100644 index 62ccf6c..0000000 --- a/modules/plugins-manager/plugins.examples/call_gas.js +++ /dev/null @@ -1,97 +0,0 @@ -//############################################################################################ -//## -//# Copyright (C) 2015-2016 Nicola Peditto -//## -//# Licensed under the Apache License, Version 2.0 (the "License"); -//# you may not use this file except in compliance with the License. -//# You may obtain a copy of the License at -//## -//# http://www.apache.org/licenses/LICENSE-2.0 -//## -//# Unless required by applicable law or agreed to in writing, software -//# distributed under the License is distributed on an "AS IS" BASIS, -//# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -//# See the License for the specific language governing permissions and -//# limitations under the License. -//## -//############################################################################################ - -exports.main = function (arguments, callback){ - - /* {"m_authid" : "", "m_resourceid" : "", "pin" : "A3", "autostart":"false"} */ - - var pin = arguments.pin; - var m_authid = arguments.m_authid; - var m_resourceid = arguments.m_resourceid; - - var api = require('../modules/plugins-manager/plugin-apis'); - var position = api.getPosition(); - - var linino = require('ideino-linino-lib'); - board = new linino.Board(); - - - board.connect(function() { - - var record = []; - - - var sensor_volt; - var RS_air; /* Get the value of RS via in a clear air */ - var R0; /* Get the value of R0 via in LPG */ - var sensorValue = 0; - var supplyVolt = 4.64; - - - /* NOTE: uncomment this part only to get an average of R0 among 100 samples */ - /*--------------------------------------------------------------------*/ - /* - for(var x = 0 ; x < 100 ; x++) - sensorValue = sensorValue + board.analogRead(pin); - - sensorValue = sensorValue/100.0; - - sensor_volt = sensorValue/1024*supplyVolt; - RS_air = (supplyVolt-sensor_volt)/sensor_volt; - R0 = RS_air/9.9; - - results="R0: " + R0 + " ohm"; - console.log("R0: " + R0 + " ohm"); - */ - /*--------------------------------------------------------------------*/ - - - /* NOTE: uncomment this part ONLY AFTER got the averaged R0 */ - /*--------------------------------------------------------------------*/ - R0 = 98.4078884078884; - - sensorValue = sensorValue + board.analogRead(pin); - sensor_volt = sensorValue/1024*supplyVolt; - RS_gas = (supplyVolt-sensor_volt)/sensor_volt; - ratio = RS_gas/R0; - /*--------------------------------------------------------------------*/ - - - - - record.push({ - Date: new Date().toISOString(), - Gas: ratio, - Altitude: position.altitude, - Latitude: position.latitude, - Longitude: position.longitude - }); - - api.sendToCKAN(m_authid, m_resourceid, record, function(payloadJSON){ - - results="Concentration: " + ratio + " ppm"; - console.log("PAYLOAD:\n" + payloadJSON); - console.log(results); - callback("OK", results); - - }); - - - }); - -} \ No newline at end of file diff --git a/modules/plugins-manager/plugins.examples/call_hello.js b/modules/plugins-manager/plugins.examples/call_hello.js deleted file mode 100644 index ed973c3..0000000 --- a/modules/plugins-manager/plugins.examples/call_hello.js +++ /dev/null @@ -1,25 +0,0 @@ -//############################################################################################ -//## -//# Copyright (C) 2015 Nicola Peditto -//## -//# Licensed under the Apache License, Version 2.0 (the "License"); -//# you may not use this file except in compliance with the License. -//# You may obtain a copy of the License at -//## -//# http://www.apache.org/licenses/LICENSE-2.0 -//## -//# Unless required by applicable law or agreed to in writing, software -//# distributed under the License is distributed on an "AS IS" BASIS, -//# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -//# See the License for the specific language governing permissions and -//# limitations under the License. -//## -//############################################################################################ - -exports.main = function (arguments, callback){ - - var result = 'PLUGIN ALIVE!'; - console.log(result); - callback("OK", result); - -} \ No newline at end of file diff --git a/modules/plugins-manager/plugins.examples/call_hum.js b/modules/plugins-manager/plugins.examples/call_hum.js deleted file mode 100644 index 0fb2272..0000000 --- a/modules/plugins-manager/plugins.examples/call_hum.js +++ /dev/null @@ -1,82 +0,0 @@ -//############################################################################################ -//## -//# Copyright (C) 2015-2016 Nicola Peditto -//## -//# Licensed under the Apache License, Version 2.0 (the "License"); -//# you may not use this file except in compliance with the License. -//# You may obtain a copy of the License at -//## -//# http://www.apache.org/licenses/LICENSE-2.0 -//## -//# Unless required by applicable law or agreed to in writing, software -//# distributed under the License is distributed on an "AS IS" BASIS, -//# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -//# See the License for the specific language governing permissions and -//# limitations under the License. -//## -//############################################################################################ - -exports.main = function (arguments, callback){ - - /* {"m_authid" : "", "m_resourceid" : "", "pin_temp" : "A0", "pin_hum" : "A2", "timer" : "5000", "autostart":"false"} */ - - var pin_temp = arguments.pin_temp; - var pin_hum = arguments.pin_hum; - var m_authid = arguments.m_authid; - var m_resourceid = arguments.m_resourceid; - - var api = require('../modules/plugins-manager/plugin-apis'); - var position = api.getPosition(); - - var linino = require('ideino-linino-lib'); - board = new linino.Board(); - - - board.connect(function() { - - var record = []; - - /*FOR TEMP SENSOR*/ - var ADCres = 1023.0; - var Beta = 3950; - var Kelvin = 273.15; - var Rb = 10000; - var Ginf = 120.6685; - var temp_volt = board.analogRead(pin_temp); - var Rthermistor = Rb * (ADCres / temp_volt - 1); - var _temperatureC = Beta / (Math.log( Rthermistor * Ginf )) ; - var temp = _temperatureC - Kelvin; - - - /*FOR HUM SENSOR*/ - var degreesCelsius = temp; - var supplyVolt = 4.64; - var HIH4030_Value = board.analogRead(pin_hum); - var voltage = HIH4030_Value/1023. * supplyVolt; - var sensorRH = 161.0 * voltage / supplyVolt - 25.8; - var relativeHumidity = sensorRH / (1.0546 - 0.0026 * degreesCelsius); - - - - record.push({ - Date: new Date().toISOString(), - Humidity: relativeHumidity, - Altitude: position.altitude, - Latitude: position.latitude, - Longitude: position.longitude - }); - - - api.sendToCKAN(m_authid, m_resourceid, record, function(payloadJSON){ - - results="Humidity " + relativeHumidity + " percent (with "+temp+" °C) sent to CKAN"; - console.log("PAYLOAD:\n" + payloadJSON); - console.log(results); - callback("OK", results); - - }); - - - }); - -} \ No newline at end of file diff --git a/modules/plugins-manager/plugins.examples/call_lux.js b/modules/plugins-manager/plugins.examples/call_lux.js deleted file mode 100644 index acd530f..0000000 --- a/modules/plugins-manager/plugins.examples/call_lux.js +++ /dev/null @@ -1,63 +0,0 @@ -//############################################################################################ -//## -//# Copyright (C) 2015-2016 Nicola Peditto -//## -//# Licensed under the Apache License, Version 2.0 (the "License"); -//# you may not use this file except in compliance with the License. -//# You may obtain a copy of the License at -//## -//# http://www.apache.org/licenses/LICENSE-2.0 -//## -//# Unless required by applicable law or agreed to in writing, software -//# distributed under the License is distributed on an "AS IS" BASIS, -//# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -//# See the License for the specific language governing permissions and -//# limitations under the License. -//## -//############################################################################################ - -exports.main = function (arguments, callback){ - - /* {"m_authid" : "", "m_resourceid" : "", "pin" : "A1", "autostart":"false"} */ - - var pin = arguments.pin; - var m_authid = arguments.m_authid; - var m_resourceid = arguments.m_resourceid; - - var api = require('../modules/plugins-manager/plugin-apis'); - var position = api.getPosition(); - - var linino = require('ideino-linino-lib'); - var board = new linino.Board(); - - - board.connect(function() { - - var record = []; - var voltage = board.analogRead(pin); - var ldr = (2500/(5-voltage*0.004887)-500)/3.3; - - - record.push({ - Date: new Date().toISOString(), - Brightness: ldr, - Altitude: position.altitude, - Latitude: position.latitude, - Longitude: position.longitude - }); - - api.sendToCKAN(m_authid, m_resourceid, record, function(payloadJSON){ - - results="Brightness: " + ldr + " (lux) sent to CKAN"; - console.log("PAYLOAD:\n" + payloadJSON); - console.log(results); - callback("OK", results); - }); - - - - }); - - - -} \ No newline at end of file diff --git a/modules/plugins-manager/plugins.examples/call_temp.js b/modules/plugins-manager/plugins.examples/call_temp.js deleted file mode 100644 index fffdf7c..0000000 --- a/modules/plugins-manager/plugins.examples/call_temp.js +++ /dev/null @@ -1,69 +0,0 @@ -//############################################################################################ -//## -//# Copyright (C) 2015-2016 Nicola Peditto -//## -//# Licensed under the Apache License, Version 2.0 (the "License"); -//# you may not use this file except in compliance with the License. -//# You may obtain a copy of the License at -//## -//# http://www.apache.org/licenses/LICENSE-2.0 -//## -//# Unless required by applicable law or agreed to in writing, software -//# distributed under the License is distributed on an "AS IS" BASIS, -//# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -//# See the License for the specific language governing permissions and -//# limitations under the License. -//## -//############################################################################################ - -exports.main = function (arguments, callback){ - - /* {"m_authid" : "", "m_resourceid" : "", "pin" : "A0", "autostart":"false"} */ - - var pin = arguments.pin; - var m_authid = arguments.m_authid; - var m_resourceid = arguments.m_resourceid; - - var api = require('../modules/plugins-manager/plugin-apis'); - var position = api.getPosition(); - - var linino = require('ideino-linino-lib'); - board = new linino.Board(); - - - board.connect(function() { - - var ADCres = 1023.0; - var Beta = 3950; - var Kelvin = 273.15; - var Rb = 10000; - var Ginf = 120.6685; - - var record = []; - var sensor = board.analogRead(pin); - var Rthermistor = Rb * (ADCres / sensor - 1); - var _temperatureC = Beta / (Math.log( Rthermistor * Ginf )) ; - var temp = _temperatureC - Kelvin; - - - record.push({ - Date: new Date().toISOString(), - Temperature: temp, - Altitude: position.altitude, - Latitude: position.latitude, - Longitude: position.longitude - }); - - api.sendToCKAN(m_authid, m_resourceid, record, function(payloadJSON){ - - results="Temperature " + temp + " °C sent to CKAN\n\n"; - console.log("PAYLOAD:\n" + payloadJSON); - console.log(results); - callback("OK", results); - - }); - - - }); - -} \ No newline at end of file diff --git a/modules/plugins-manager/plugins.examples/gps_plugin.js b/modules/plugins-manager/plugins.examples/gps_plugin.js deleted file mode 100644 index 4ee9741..0000000 --- a/modules/plugins-manager/plugins.examples/gps_plugin.js +++ /dev/null @@ -1,147 +0,0 @@ -//############################################################################################ -//## -//# Copyright (C) 2015-2016 Francesco Longo, Giovanni Merlino -//## -//# Licensed under the Apache License, Version 2.0 (the "License"); -//# you may not use this file except in compliance with the License. -//# You may obtain a copy of the License at -//## -//# http://www.apache.org/licenses/LICENSE-2.0 -//## -//# Unless required by applicable law or agreed to in writing, software -//# distributed under the License is distributed on an "AS IS" BASIS, -//# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -//# See the License for the specific language governing permissions and -//# limitations under the License. -//## -//############################################################################################ - -//JSON to send -// { -// 'm_authid': '', -// 'm_resourceid': '', -// 'gps_device_command': '', -// 'gps_device_data': '', -// 'ckan_host': '', -// 'ckan_port': '', -// 'ckan_path': '' -// } - - -exports.main = function (arguments){ - var m_authid = arguments.m_authid; - var m_resourceid = arguments.m_resourceid; - var gps_device_command = arguments.gps_device_command; - var gps_device_data = arguments.gps_device_data; - var ckan_host = arguments.ckan_host; - var ckan_port = arguments.ckan_port; - var ckan_path = arguments.ckan_path; - - var http = require('http'); - var gpsd = require('node-gpsd'); - var modem = require('modem').Modem(); - var exec = require('child_process').exec; - - modem.open(gps_device_command, function() { - console.log('Connected to ' + gps_device_command); - var job = modem.execute('AT+CGPS?', function(escape_char, response){ - if(response == 'OK'){ - - console.log('It is working'); - modem.close(); - - exec('/root/dialup.sh', function(error, stdout, stderr){ - console.log('Starting PPP connection: ' + stdout); - }); - - var daemon = new gpsd.Daemon({ - program: 'gpsd', - device: gps_device_data, - port: 2947, - pid: '/tmp/gpsd.pid' - }); - - daemon.start(function(err, result) { - console.log('Deamon started'); - - var listener = new gpsd.Listener({ - parse: true, - parsejson: true - }); - - listener.connect(function() { - console.log('Connected'); - - listener.watch(); - - listener.on('TPV', function(data){ - if(data.tag == 'RMC'){ - delete data.class; - delete data.tag; - delete data.device; - delete data.mode; - - var record = []; - - var header = { - 'Content-Type': "application/json", - 'Authorization' : m_authid - }; - - var options = { - host: ckan_host, - port: ckan_port, - path: ckan_path, - method: 'POST', - headers: header - }; - - record.push(data); - - var payload = { - resource_id : m_resourceid, - method: 'insert', - records : record - }; - - var payloadJSON = JSON.stringify(payload); - - var req = http.request(options, function(res) { - - res.setEncoding('utf-8'); - - var responseString = ''; - - }); - - req.on('error', function(e) { - console.log('On Error:'+e); - }); - - req.write(payloadJSON); - - req.end(); - - console.log("sent to CKAN, JSON: %j", data); - }; - }); - }); - }); - } - else{ - console.log('I need to reboot'); - modem.close(); - exec('reboot', function(error, stdout, stderr){ - console.log('Rebooting now: ' + stdout); - }); - } - }, true, 4000) - job.on('timeout', function(data){ - console.log('Timed out on test command'); - modem.close(); - exec('reboot', function(error, stdout, stderr){ - console.log('Rebooting now: ' + stdout); - }); - }); - }); -} diff --git a/modules/plugins-manager/plugins.examples/hello_plugin.js b/modules/plugins-manager/plugins.examples/hello_plugin.js deleted file mode 100644 index 95dc778..0000000 --- a/modules/plugins-manager/plugins.examples/hello_plugin.js +++ /dev/null @@ -1,26 +0,0 @@ -//############################################################################################ -//## -//# Copyright (C) 2015 Nicola Peditto -//## -//# Licensed under the Apache License, Version 2.0 (the "License"); -//# you may not use this file except in compliance with the License. -//# You may obtain a copy of the License at -//## -//# http://www.apache.org/licenses/LICENSE-2.0 -//## -//# Unless required by applicable law or agreed to in writing, software -//# distributed under the License is distributed on an "AS IS" BASIS, -//# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -//# See the License for the specific language governing permissions and -//# limitations under the License. -//## -//############################################################################################ - -exports.main = function (arguments){ - - setInterval(function(){ - console.log('PLUGIN ALIVE!'); - - }, 3000); - -} diff --git a/modules/plugins-manager/plugins.examples/hum_plugin.js b/modules/plugins-manager/plugins.examples/hum_plugin.js deleted file mode 100644 index 6d3bd93..0000000 --- a/modules/plugins-manager/plugins.examples/hum_plugin.js +++ /dev/null @@ -1,86 +0,0 @@ -//############################################################################################ -//## -//# Copyright (C) 2015-2016 Nicola Peditto -//## -//# Licensed under the Apache License, Version 2.0 (the "License"); -//# you may not use this file except in compliance with the License. -//# You may obtain a copy of the License at -//## -//# http://www.apache.org/licenses/LICENSE-2.0 -//## -//# Unless required by applicable law or agreed to in writing, software -//# distributed under the License is distributed on an "AS IS" BASIS, -//# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -//# See the License for the specific language governing permissions and -//# limitations under the License. -//## -//############################################################################################ - -exports.main = function (arguments){ - - /* {"m_authid" : "", "m_resourceid" : "", "pin_temp" : "A0", "pin_hum" : "A2", "timer" : "5000", "autostart":"false"} */ - - - var pin_temp = arguments.pin_temp; - var pin_hum = arguments.pin_hum; - var timer = arguments.timer; - var m_authid = arguments.m_authid; - var m_resourceid = arguments.m_resourceid; - - var api = require('../modules/plugins-manager/plugin-apis'); - var position = api.getPosition(); - - var linino = require('ideino-linino-lib'); - board = new linino.Board(); - - - board.connect(function() { - - setInterval(function(){ - - var record = []; - - /*FOR TEMP SENSOR*/ - var ADCres = 1023.0; - var Beta = 3950; - var Kelvin = 273.15; - var Rb = 10000; - var Ginf = 120.6685; - var temp_volt = board.analogRead(pin_temp); - var Rthermistor = Rb * (ADCres / temp_volt - 1); - var _temperatureC = Beta / (Math.log( Rthermistor * Ginf )) ; - var temp = _temperatureC - Kelvin; - - - /*FOR HUM SENSOR*/ - var degreesCelsius = temp; - var supplyVolt = 4.64; - var HIH4030_Value = board.analogRead(pin_hum); - var voltage = HIH4030_Value/1023. * supplyVolt; - var sensorRH = 161.0 * voltage / supplyVolt - 25.8; - var relativeHumidity = sensorRH / (1.0546 - 0.0026 * degreesCelsius); - - - - record.push({ - Date: new Date().toISOString(), - Humidity: relativeHumidity, - Altitude: position.altitude, - Latitude: position.latitude, - Longitude: position.longitude - }); - - - api.sendToCKAN(m_authid, m_resourceid, record, function(payloadJSON){ - - console.log("PAYLOAD:\n" + payloadJSON); - console.log("Humidity " + relativeHumidity + " percent (with "+temp+" °C) sent to CKAN"); - - }); - - - },timer); - - }); - -} \ No newline at end of file diff --git a/modules/plugins-manager/plugins.examples/lux_plugin.js b/modules/plugins-manager/plugins.examples/lux_plugin.js deleted file mode 100644 index f0fdc56..0000000 --- a/modules/plugins-manager/plugins.examples/lux_plugin.js +++ /dev/null @@ -1,65 +0,0 @@ -//############################################################################################ -//## -//# Copyright (C) 2015-2016 Nicola Peditto -//## -//# Licensed under the Apache License, Version 2.0 (the "License"); -//# you may not use this file except in compliance with the License. -//# You may obtain a copy of the License at -//## -//# http://www.apache.org/licenses/LICENSE-2.0 -//## -//# Unless required by applicable law or agreed to in writing, software -//# distributed under the License is distributed on an "AS IS" BASIS, -//# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -//# See the License for the specific language governing permissions and -//# limitations under the License. -//## -//############################################################################################ - -exports.main = function (arguments){ - - /* {"m_authid" : "", "m_resourceid" : "", "pin" : "A1", "timer" : "5000", "autostart":"false"} */ - - var pin = arguments.pin; - var timer = arguments.timer; - var m_authid = arguments.m_authid; - var m_resourceid = arguments.m_resourceid; - - var api = require('../modules/plugins-manager/plugin-apis'); - var position = api.getPosition(); - - var linino = require('ideino-linino-lib'); - var board = new linino.Board(); - - - board.connect(function() { - - setInterval(function(){ - - var record = []; - var voltage = board.analogRead(pin); - var ldr = (2500/(5-voltage*0.004887)-500)/3.3; - - - record.push({ - Date: new Date().toISOString(), - Brightness: ldr, - Altitude: position.altitude, - Latitude: position.latitude, - Longitude: position.longitude - }); - - api.sendToCKAN(m_authid, m_resourceid, record, function(payloadJSON){ - - console.log("PAYLOAD:\n" + payloadJSON); - console.log("Brightness: " + ldr + " (lux) sent to CKAN\n\n"); - - }); - - - },timer); - - }); - - -} \ No newline at end of file diff --git a/modules/plugins-manager/plugins.examples/nodejs/driver_app.js b/modules/plugins-manager/plugins.examples/nodejs/driver_app.js new file mode 100644 index 0000000..c9d2bba --- /dev/null +++ b/modules/plugins-manager/plugins.examples/nodejs/driver_app.js @@ -0,0 +1,63 @@ +exports.main = function (arguments){ + + /* THIS PLUGIN IS FOR ARDUINO YUN */ + /* THIS PLUGIN REQUIRES THE DRIVER "led_driver" MOUNTED */ + + var fs = require('fs'); + + var LIGHTNINGROD_HOME = process.env.LIGHTNINGROD_HOME; + api = require(LIGHTNINGROD_HOME + '/modules/plugins-manager/nodejs/plugin-apis'); + + var logger = api.getLogger(); + logger.info("DRIVER-APP - Plugin driver application starting..."); + + var path = '/opt/stack4things/drivers/led_driver/led'; + + setInterval(function(){ + + fs.open(path, 'a', function(err, data) { + if (err) { + logger.error("ERROR: " + err); + } else { + + fs.write(data, '1', 0, 'utf8', function(err) { + if (err) + logger.error("ERROR: " + err); + fs.close(data, function() { + logger.info("TURN ON"); + }) + }); + + } + }); + + + setTimeout(function(){ + + + + fs.open(path, 'a', function(err, data) { + if (err) { + logger.error("ERROR: " + err); + } else { + + fs.write(data, '0', 0, 'utf8', function(err) { + if (err) + logger.error("ERROR: " + err); + fs.close(data, function() { + logger.info("TURN OFF"); + }) + }); + + } + }); + + }, 500); + + + }, 2000); + + + +} + diff --git a/modules/plugins-manager/plugins.examples/nodejs/echo.js b/modules/plugins-manager/plugins.examples/nodejs/echo.js new file mode 100644 index 0000000..205ddbc --- /dev/null +++ b/modules/plugins-manager/plugins.examples/nodejs/echo.js @@ -0,0 +1,13 @@ +//pluginjsonschema = {"says": "BlaBlaBla..."} + +exports.main = function (plugin_name, arguments, api, callback){ + + var says = arguments.says; + + logger = api.getLogger(plugin_name, 'debug'); + + logger.info(says); + + callback("OK", says); + +}; \ No newline at end of file diff --git a/modules/plugins-manager/plugins.examples/nodejs/hello.js b/modules/plugins-manager/plugins.examples/nodejs/hello.js new file mode 100644 index 0000000..6a361e7 --- /dev/null +++ b/modules/plugins-manager/plugins.examples/nodejs/hello.js @@ -0,0 +1,16 @@ +//pluginjsonschema = {"name": "IoTronic"} + +exports.main = function (plugin_name, arguments, api){ + + var name = arguments.name; + + logger = api.getLogger(plugin_name, 'debug'); + + logger.info("Hello plugin starting..."); + + setInterval(function(){ + logger.info('Hello '+name+'!'); + + }, 3000); + +}; diff --git a/modules/plugins-manager/plugins.examples/python/py_async.py b/modules/plugins-manager/plugins.examples/python/py_async.py new file mode 100755 index 0000000..f298209 --- /dev/null +++ b/modules/plugins-manager/plugins.examples/python/py_async.py @@ -0,0 +1,33 @@ +############################################################################################# +### +## Copyright (C) 2018 Nicola Peditto +### +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +### +## http://www.apache.org/licenses/LICENSE-2.0 +### +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +### +############################################################################################# + +# User imports +import time +from datetime import datetime + +#PARAMS: {"name": "S4T"} + +def main(plugin_name, params, api): + + logging = api.getLogger(plugin_name); + + while(True): + now = datetime.now().strftime( "%-d %b %Y %H:%M:%S.%f" ) + #print("I'm "+str(params['name'])+" @ "+now) + logging.info("I'm "+str(params['name'])+" @ "+now) + time.sleep(1) diff --git a/modules/plugins-manager/plugins.examples/python/py_sync.py b/modules/plugins-manager/plugins.examples/python/py_sync.py new file mode 100755 index 0000000..0e4692a --- /dev/null +++ b/modules/plugins-manager/plugins.examples/python/py_sync.py @@ -0,0 +1,35 @@ +############################################################################################# +### +## Copyright (C) 2018 Nicola Peditto +### +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +### +## http://www.apache.org/licenses/LICENSE-2.0 +### +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +### +############################################################################################# + +# User imports +from datetime import datetime + +#PARAMS: {"name": "S4T"} + +def main(plugin_name, params, api): + + logging = api.getLogger(plugin_name); + + now = datetime.now().strftime( "%d %b %Y %H:%M:%S.%f" ) + + result = { 'time':now, 'name':str(params['name']) } + + logging.info(result) + + + return result \ No newline at end of file diff --git a/modules/plugins-manager/plugins.examples/temp_plugin.js b/modules/plugins-manager/plugins.examples/temp_plugin.js deleted file mode 100644 index e5b45d5..0000000 --- a/modules/plugins-manager/plugins.examples/temp_plugin.js +++ /dev/null @@ -1,73 +0,0 @@ -//############################################################################################ -//## -//# Copyright (C) 2015-2016 Nicola Peditto -//## -//# Licensed under the Apache License, Version 2.0 (the "License"); -//# you may not use this file except in compliance with the License. -//# You may obtain a copy of the License at -//## -//# http://www.apache.org/licenses/LICENSE-2.0 -//## -//# Unless required by applicable law or agreed to in writing, software -//# distributed under the License is distributed on an "AS IS" BASIS, -//# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -//# See the License for the specific language governing permissions and -//# limitations under the License. -//## -//############################################################################################ - -exports.main = function (arguments){ - - /* {"m_authid" : "", "m_resourceid" : "", "pin" : "A0", "timer" : "5000", "autostart":"false"} */ - - var pin = arguments.pin; - var timer = arguments.timer; - var m_authid = arguments.m_authid; - var m_resourceid = arguments.m_resourceid; - - var api = require('../modules/plugins-manager/plugin-apis'); - var position = api.getPosition(); - - var linino = require('ideino-linino-lib'); - board = new linino.Board(); - - - board.connect(function() { - - var ADCres = 1023.0; - var Beta = 3950; - var Kelvin = 273.15; - var Rb = 10000; - var Ginf = 120.6685; - - setInterval(function(){ - - var record = []; - var sensor = board.analogRead(pin); - var Rthermistor = Rb * (ADCres / sensor - 1); - var _temperatureC = Beta / (Math.log( Rthermistor * Ginf )) ; - var temp = _temperatureC - Kelvin; - - - record.push({ - Date: new Date().toISOString(), - Temperature: temp, - Altitude: position.altitude, - Latitude: position.latitude, - Longitude: position.longitude - }); - - api.sendToCKAN(m_authid, m_resourceid, record, function(payloadJSON){ - - console.log("PAYLOAD:\n" + payloadJSON); - console.log("Temperature " + temp + " °C sent to CKAN\n\n"); - - }); - - - - },timer); - - }); - -} \ No newline at end of file diff --git a/modules/plugins-manager/python/__pycache__/plugin_apis.cpython-35.pyc b/modules/plugins-manager/python/__pycache__/plugin_apis.cpython-35.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19c4c530fb83bb9d8bf3aac0a1b44d416b476a40 GIT binary patch literal 969 zcmY*YOK;Oa5T0E-ary{_9vT%15e`*rq157v5Kv0gMuPN_ptRu-x%S3&aJ*)B`-&1I zRIZ5M!CzX56DR%xCuW_dD(juunQzB4E;shthkMP& zc5ip@eSLJ#lT4{NjpXJ)@-!gE3YR$@q-`Ym6qy1TV0_#Z#sC!)a~cpsnM(@SU=fsB zuG7?|ew?t>AF|Oy@7xz&!Xq!{O7Jueyev73;#7JgdlPY8>~k^n)yQ4j@MWOlA#-K5 zR$bdj*f~p#lN)`=r1T@ybRoZTE$yHK^c8g%PJ9^$+dS>ZkuJQgzxlA^61^}D)iX|h zf|$b(`P=tS`H*=R?Bs$ceXqk#S_%``7kt11B|WKXx0xn!bgEMHUlTm^hCDn=n4INb z8~Q1xBfRWNo$~Ys?)h0PH?u1}7cun?vtz;(QrgIXtlMG%&%h{V8MK0w*ZC_7SU^SK zn!t|~CIDAYK;?bLnj<(NFwVQMYWhB*&`Do`t!(%-58^YBi3Mcz7_d2br*I5xP(>J9 z=G_Vs=^UHU0zktr796Dnj>Z&P9fS@JkC3w;fCHyXJZjBz*h2Wx^`9fkj zs{c*R^Q!A;n`bQ5784?G+?355Y!ORaH>ow!KEH>EypIg%61C_OS+ PARAMS:" + str(self.params)) + + result = "Plugin " +plugin_name+" is running!" + + response = { + "message": str(result), + "result": "SUCCESS" + } + + self.q_result.put(json.dumps(response)) + + plugin.main(self.name, self.params, api) + + + except Exception as err: + response = { + "message": str(err), + "result": "ERROR" + } + + self.q_result.put(json.dumps(response)) + + + + + +# WRAPPER MAIN +if __name__ == '__main__': + + api = imp.load_source("api", os.environ['LIGHTNINGROD_HOME'] + "/modules/plugins-manager/python/plugin_apis.py") + + logging = api.getLogger(plugin_name) + logging.info(plugin_name + " started with these parameters: " + str(plugin_params) ) + + worker = Plugin( + plugin_params, + q_result, + api + ) + + # 1. thread plugin starting + worker.start() + + # 2. waiting for plugin result injected in the queue + while q_result.empty(): + pass + + # 3. Get data from plugin queue and parsing + data = q_result.get() + data_parsed = json.loads(data) + + # 4. Create package for Plugin Manager + msg = json.dumps({ + "plugin": plugin_name, + "payload": str(data_parsed["message"]), + "result": str(data_parsed["result"]) + }) + + # 5. Connect to the unix local socket to send the plugin package + client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + client.connect(socket_path) + client.send(msg.encode('utf-8')) + client.close() diff --git a/modules/plugins-manager/python/plugin_apis.py b/modules/plugins-manager/python/plugin_apis.py new file mode 100644 index 0000000..4ed5bd3 --- /dev/null +++ b/modules/plugins-manager/python/plugin_apis.py @@ -0,0 +1,8 @@ +import logging + +def getLogger(plugin_name): + logging.basicConfig(filename='/var/log/iotronic/plugins/'+plugin_name+'.log', format='%(asctime)s - %(levelname)s - %(message)s', level=logging.DEBUG) + #logging.debug('This message should go to the log file') + #logging.info('So should this') + #logging.warning('And this, too') + return logging \ No newline at end of file diff --git a/modules/plugins-manager/python/sync-wrapper.py b/modules/plugins-manager/python/sync-wrapper.py new file mode 100644 index 0000000..8c556a5 --- /dev/null +++ b/modules/plugins-manager/python/sync-wrapper.py @@ -0,0 +1,124 @@ +############################################################################################# +### +## Copyright (C) 2018 Nicola Peditto +### +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +### +## http://www.apache.org/licenses/LICENSE-2.0 +### +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +### +############################################################################################# + +import sys +import imp +import socket +from datetime import datetime +import time +import os +import threading +import json + + +# Inputs +plugin_name = sys.argv[1] +plugin_params = sys.argv[2] + + +# Globals +socket_path = '/tmp/plugin-'+plugin_name +plugin_path = os.environ.get('IOTRONIC_HOME')+"/plugins/"+plugin_name+"/"+plugin_name+".py" +plugin = imp.load_source("plugin", plugin_path) + + +if sys.version_info[0] < 3: + import Queue + q_result = Queue.Queue() +else: + import queue + q_result = queue.Queue() + + +# Thread to run user's plugin logic +class Plugin(threading.Thread): + + def __init__(self, params, q_result, api): + threading.Thread.__init__(self) + self.setName(plugin_name) + self.name = plugin_name + self.params = json.loads(params) + self.q_result = q_result + self.api = api + + def run(self): + + try: + + print("Plugin Thread starting...") + print("--> PARAMS:" + str(self.params)) + + result = plugin.main(self.name, self.params, api) + + response = { + "message": str(result), + "result": "SUCCESS" + } + + self.q_result.put(json.dumps(response)) + + + except Exception as err: + + response = { + "message": str(err), + "result": "ERROR" + } + + self.q_result.put(json.dumps(response)) + + +# WRAPPER MAIN +if __name__ == '__main__': + + api = imp.load_source("api", os.environ['LIGHTNINGROD_HOME'] + "/modules/plugins-manager/python/plugin_apis.py") + + logging = api.getLogger(plugin_name) + logging.info(plugin_name + " started with these parameters: " + str(plugin_params) ) + + worker = Plugin( + plugin_params, + q_result, + api + ) + + # 1. thread plugin starting + worker.start() + + # 2. waiting for plugin result injected in the queue + while q_result.empty(): + pass + + # 3. Get data from plugin queue and parsing + data = q_result.get() + data_parsed = json.loads(data) + + # 4. Create package for Plugin Manager + + msg = json.dumps({ + "plugin": plugin_name, + "payload": str(data_parsed["message"]), + "result": str(data_parsed["result"]) + }) + + + # 5. Connect to the unix local socket to send the plugin package + client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + client.connect(socket_path) + client.send(msg.encode('utf-8')) + client.close() diff --git a/modules/services-manager/manage-services.js b/modules/services-manager/manage-services.js index 82c9e83..7740da9 100644 --- a/modules/services-manager/manage-services.js +++ b/modules/services-manager/manage-services.js @@ -18,13 +18,15 @@ //service logging configuration: "manageCommands" -var logger = log4js.getLogger('manageServices'); +var logger = log4js.getLogger('servicesManager'); logger.setLevel(loglevel); //Services list: it is used to store the services process data that are started in the current LR session servicesProcess = []; var Q = require("q"); +var running = require('is-running'); //In order to verify if a tunnel-process is alive or not. + // This function expose a service @@ -36,6 +38,8 @@ exports.enableService = function(args){ var publicPort = String(args[2]); var restore = String(args[3]); + var db_tunnel_pid = String(args[4]); + var response = { message: '', result: '' @@ -46,54 +50,90 @@ exports.enableService = function(args){ logger.debug("[SERVICE] - RPC enableService called: " + args); - //Looking for the process in the array - var i = findValue(servicesProcess, serviceName, 'key'); - if ( i != -1) { + if(running(db_tunnel_pid)){ - if(restore == "true"){ + // Call when Restore tunnel API is called and after injection LR conf is called - logger.info("[SERVICE] - Restoring tunnel for '"+ serviceName + "' service..."); + //kill tunnel process + process.kill(db_tunnel_pid); - //Killing the process - logger.debug('[SERVICE] --> killing '+serviceName+' process [ PID ' + servicesProcess[i].process.pid + " ]"); - servicesProcess[i].process.kill('SIGINT'); - servicesProcess.splice(i,1); + //clean data structure + var i = findValue(servicesProcess, serviceName, 'key'); + servicesProcess.splice(i,1); - createTunnel(serviceName, localPort, publicPort, function (newTunnel) { + logger.warn("[SERVICE] --> Tunnel process of service "+ serviceName +" still active: killed!"); - response.result = "SUCCESS"; - response.message = "Service "+ serviceName +" successfully restored on port " + publicPort + " - [PID "+newTunnel.process.pid+"]"; - logger.info('[SERVICE] --> ' + response.message); - d.resolve(response); + createTunnel(serviceName, localPort, publicPort, function (newTunnel) { - }); + response.result = "SUCCESS"; + response.pid = newTunnel.process.pid; + response.message = "Service "+ serviceName +" successfully restored on port " + publicPort + " - [PID "+newTunnel.process.pid+"]"; + logger.info('[SERVICE] --> ' + response.message); + d.resolve(response); + }); + + + }else{ + + //Looking for the process in the array + var i = findValue(servicesProcess, serviceName, 'key'); + + if ( i != -1) { + + // if service is active and stored in servicesProcess array + + if(restore == "true"){ + + // Call when self-Restore tunnel procedure is called: when connection is recovered! + logger.info("[SERVICE] - Restoring tunnel for '"+ serviceName + "' service..."); + + //Killing the process + logger.debug('[SERVICE] --> killing '+serviceName+' process [ PID ' + servicesProcess[i].process.pid + " ]"); + servicesProcess[i].process.kill('SIGINT'); + servicesProcess.splice(i,1); + + createTunnel(serviceName, localPort, publicPort, function (newTunnel) { + + response.result = "SUCCESS"; + response.pid = newTunnel.process.pid; + response.message = "Service "+ serviceName +" successfully restored (after reconnection) on port " + publicPort + " - [PID "+newTunnel.process.pid+"]"; + logger.info('[SERVICE] --> ' + response.message); + d.resolve(response); + + }); + + + }else{ + + response.result = "WARNING"; + response.message = "Service '"+ serviceName +"' already active!"; + logger.info('[SERVICE] - ' + response.message); + d.resolve(response); + + } - }else{ - logger.warn("Service already active!"); - response.result = "WARNING"; - response.message = "Service "+ serviceName +" already active!"; - logger.info('[SERVICE] - ' + response.message); - d.resolve(response); } - + else{ + // Call when LR restore tunnels at boot time and when enable-service API is called - }else{ + logger.info('[SERVICE] - Exposing new tunnel for '+ serviceName + ' service...'); + createTunnel(serviceName, localPort, publicPort, function (newTunnel) { + response.result = "SUCCESS"; + response.pid = newTunnel.process.pid; + response.message = "Service "+ serviceName +" successfully exposed on port " + publicPort + " - [PID "+newTunnel.process.pid+"]"; + logger.info('[SERVICE] --> ' + response.message); + d.resolve(response); - logger.info('[SERVICE] - Exposing new tunnel for '+ serviceName + ' service...'); - createTunnel(serviceName, localPort, publicPort, function (newTunnel) { + }); - response.result = "SUCCESS"; - response.message = "Service "+ serviceName +" successfully exposed on port " + publicPort + " - [PID "+newTunnel.process.pid+"]"; - logger.info('[SERVICE] --> ' + response.message); - d.resolve(response); + } - }); } @@ -102,6 +142,7 @@ exports.enableService = function(args){ + return d.promise; @@ -170,6 +211,8 @@ exports.checkService = function(args){ var serviceName = args[0]; + var serviceDbPID = args[1]; + var response = { message: '', result: '' @@ -185,34 +228,51 @@ exports.checkService = function(args){ logger.info('[SERVICE] - Checking ' + serviceName + " service..."); var d = Q.defer(); - - //Looking for the process in the array - var i = findValue(servicesProcess, serviceName, 'key'); - - if(i == -1){ - - response.result = "SUCCESS"; - service.message = serviceName +" service is inactive!"; - service.status = "INACTIVE"; - service.pid = null; - service.name = serviceName; - response.message = service; - logger.warn('[SERVICE] --> ' + JSON.stringify(response.message)); - d.resolve(response); - - }else{ + + if(running(serviceDbPID)){ response.result = "WARNING"; - service.message = serviceName +" service is active!"; + service.message = serviceName +" service is still active!"; service.status = "ACTIVE"; - service.pid = servicesProcess[i].process.pid; + service.pid = serviceDbPID; service.name = serviceName; response.message = service; logger.info('[SERVICE] --> ' + JSON.stringify(response.message)); - d.resolve(response); - + d.resolve(response); + + }else{ + + //Looking for the process in the array + var i = findValue(servicesProcess, serviceName, 'key'); + + if(i == -1){ + + response.result = "SUCCESS"; + service.message = serviceName +" service is inactive!"; + service.status = "INACTIVE"; + service.pid = null; + service.name = serviceName; + response.message = service; + logger.warn('[SERVICE] --> ' + JSON.stringify(response.message)); + d.resolve(response); + + }else{ + + response.result = "WARNING"; + service.message = serviceName +" service is active!"; + service.status = "ACTIVE"; + service.pid = servicesProcess[i].process.pid; + service.name = serviceName; + response.message = service; + logger.info('[SERVICE] --> ' + JSON.stringify(response.message)); + d.resolve(response); + + } + } + + return d.promise; }; @@ -269,10 +329,8 @@ function findValue(myArray, value, property) { - - //This function exports all the functions in the module as WAMP remote procedure calls -exports.exportServiceCommands = function (session){ +exports.Init = function (session){ //Register all the module functions as WAMP RPCs session.register('s4t.'+boardCode+'.service.enable', exports.enableService); @@ -294,3 +352,11 @@ exports.exportServiceCommands = function (session){ + +//This function executes procedures at boot time (no Iotronic dependent) +exports.Boot = function (){ + + logger.info('[BOOT] - Services Manager booting procedures not defined.'); + +}; + diff --git a/modules/vfs-manager/manage-fs.js b/modules/vfs-manager/manage-fs.js index de3652a..8250172 100644 --- a/modules/vfs-manager/manage-fs.js +++ b/modules/vfs-manager/manage-fs.js @@ -847,23 +847,8 @@ function wrap_write_function(mirrored_board, path_org, fs_function){ - - - - - - - - - - - - - - - //This function exports all the functions in the module as WAMP remote procedure calls -exports.exportFSCommands = function (session){ +exports.Init = function (session){ session_fs = session; @@ -877,3 +862,11 @@ exports.exportFSCommands = function (session){ }; + + +//This function executes procedures at boot time (no Iotronic dependent) +exports.Boot = function (){ + + logger.info('[BOOT] - VFS Manager booting procedures not defined.'); + +}; \ No newline at end of file diff --git a/modules/vnets-manager/manage-networks.js b/modules/vnets-manager/manage-networks.js index 2043a11..454f173 100644 --- a/modules/vnets-manager/manage-networks.js +++ b/modules/vnets-manager/manage-networks.js @@ -19,8 +19,8 @@ -//service logging configuration: "manageNetworks" -var logger = log4js.getLogger('manageNetworks'); +//service logging configuration: "networksManager" +var logger = log4js.getLogger('networksManager'); logger.setLevel(loglevel); var Q = require("q"); @@ -408,7 +408,7 @@ exports.removeFromNetwork = function (args) { // This function exports all the functions in the module as WAMP remote procedure calls -exports.exportNetworkCommands = function (session) { +exports.Init = function (session) { session_wamp = session; @@ -419,4 +419,12 @@ exports.exportNetworkCommands = function (session) { logger.info('[WAMP-EXPORTS] Network commands exported to the cloud!'); +}; + + +//This function executes procedures at boot time (no Iotronic dependent) +exports.Boot = function (){ + + logger.info('[BOOT] - VNET Manager booting procedures not defined.'); + }; \ No newline at end of file diff --git a/modules/vnets-manager/network-wrapper.js b/modules/vnets-manager/network-wrapper.js index aedad60..0967e67 100644 --- a/modules/vnets-manager/network-wrapper.js +++ b/modules/vnets-manager/network-wrapper.js @@ -166,7 +166,7 @@ process.once('message', function (message) { logger.info('[VNET] --> SOCAT - process exited with code ' + code); //logger.warn(boardCode+" needs to be restored!"); - //manageNetworks.setSocatOnBoard([socatServer_ip, socatServer_port, socatBoard_ip, net_backend, boardCode, true] ); //TO RESTORE + //networksManager.setSocatOnBoard([socatServer_ip, socatServer_port, socatBoard_ip, net_backend, boardCode, true] ); //TO RESTORE }); diff --git a/package.json b/package.json index 3647c47..ad409f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mdslab/iotronic-lightning-rod", - "version": "2.0.6", + "version": "2.1.2", "description": "Implementation of the Lightning-rod, the Stack4Things node-side probe (this version works with the standalone version of IoTronic) http://stack4things.unime.it/", "main": "lightning-rod.js", "directories": { @@ -13,15 +13,19 @@ }, "dependencies": { "autobahn": ">=0.9.9", + "ws":"4.1.0", "connection-tester": ">=0.1.1", "fs-access": ">=1.0.1", - "fuse-bindings": ">=2.8.1", + "fuse-bindings": "2.8.1", "is-running": ">=2.0.0", - "log4js": "<=1.1.1", + "log4js": "1.1.1", "mknod": ">=1.1.0", "nconf": ">=0.8.4", "q": ">=0.9.7", - "requestify": ">=0.1.17" + "requestify": ">=0.1.17", + "md5":"<=2.2.1", + "python-shell":"0.5.0", + "net":"1.0.2" }, "repository": { "type": "git", diff --git a/scripts/lr_configure.sh b/scripts/lr_configure.sh index b39ebd0..4257fb6 100755 --- a/scripts/lr_configure.sh +++ b/scripts/lr_configure.sh @@ -107,3 +107,4 @@ else fi + diff --git a/scripts/postinst b/scripts/postinst index bf49347..3251e69 100755 --- a/scripts/postinst +++ b/scripts/postinst @@ -25,13 +25,13 @@ if [ "$rm_check" = "no" ] || [ ! -d "$IOTRONIC_HOME" ]; then mkdir /var/lib/iotronic/ cd /var/lib/iotronic/ && mkdir plugins && mkdir drivers - cp /usr/lib/node_modules/@mdslab/iotronic-lightning-rod/settings.example.json /var/lib/iotronic/settings.json - cp /usr/lib/node_modules/@mdslab/iotronic-lightning-rod/modules/plugins-manager/plugins.example.json /var/lib/iotronic/plugins/plugins.json - cp /usr/lib/node_modules/@mdslab/iotronic-lightning-rod/modules/drivers-manager/drivers.example.json /var/lib/iotronic/drivers/drivers.json + cp $NODE_PATH/@mdslab/iotronic-lightning-rod/settings.example.json /var/lib/iotronic/settings.json + cp $NODE_PATH/@mdslab/iotronic-lightning-rod/modules/plugins-manager/plugins.example.json /var/lib/iotronic/plugins/plugins.json + cp $NODE_PATH/@mdslab/iotronic-lightning-rod/modules/drivers-manager/drivers.example.json /var/lib/iotronic/drivers/drivers.json - chmod +x /usr/lib/node_modules/@mdslab/iotronic-lightning-rod/lr-server.js + chmod +x $NODE_PATH/@mdslab/iotronic-lightning-rod/lr-server.js - mkdir /var/log/iotronic/ + mkdir -p /var/log/iotronic/plugins touch /var/log/iotronic/lightning-rod.log fi @@ -58,13 +58,24 @@ then else # if not found echo "Setting LIGHTNINGROD_HOME env var." - echo "LIGHTNINGROD_HOME=/usr/lib/node_modules/@mdslab/iotronic-lightning-rod" >> /etc/environment + echo "LIGHTNINGROD_HOME=$NODE_PATH/@mdslab/iotronic-lightning-rod" >> /etc/environment . /etc/environment fi +if grep -Fq "NODE_TLS_REJECT_UNAUTHORIZED" /etc/environment +then + # if found + echo "NODE_TLS_REJECT_UNAUTHORIZED env var already set." +else + # if not found + echo "Setting NODE_TLS_REJECT_UNAUTHORIZED env var." + echo "NODE_TLS_REJECT_UNAUTHORIZED=0" >> /etc/environment + . /etc/environment +fi + echo "After the installation execute lr_configure.sh script in order to complete the Lightning-rod configuration!" -echo "--> /usr/lib/node_modules/@mdslab/iotronic-lightning-rod/scripts/lr_configure.sh" -chmod +x /usr/lib/node_modules/@mdslab/iotronic-lightning-rod/scripts/lr_configure.sh +echo "--> $NODE_PATH/@mdslab/iotronic-lightning-rod/scripts/lr_configure.sh" +chmod +x $NODE_PATH/@mdslab/iotronic-lightning-rod/scripts/lr_configure.sh echo "Bye!" diff --git a/settings.example.json b/settings.example.json index ae70b1d..eca9439 100644 --- a/settings.example.json +++ b/settings.example.json @@ -10,7 +10,9 @@ { "url_wamp": "", "port_wamp": "8181", - "realm": "s4t" + "realm": "s4t", + "wifi_force_reconnect": "true", + "wifi_force_reconnect_time": 60 }, "reverse": { @@ -32,12 +34,45 @@ "board": { "code":"", + "label":"", "status": "new", "position":{ - "altitude":0, - "longitude":0, - "latitude":0 + "altitude":"", + "longitude":"", + "latitude":"", + "timestamp": "" + }, + "modules": { + "plugins_manager": { + "enabled": true, + "boot": true, + "alive_timer": 60 + }, + "services_manager": { + "enabled": true, + "boot": false + }, + "nodered_manager": { + "enabled": true, + "boot": true + }, + "vnets_manager": { + "enabled": false, + "boot": false + }, + "gpio_manager": { + "enabled": false, + "boot": false + }, + "drivers_manager": { + "enabled": false, + "boot": false + }, + "vfs_manager": { + "enabled": false, + "boot": false + } } } diff --git a/utils/docker/rpi/Dockerfile b/utils/docker/rpi/Dockerfile index 48f6d92..12ba08b 100644 --- a/utils/docker/rpi/Dockerfile +++ b/utils/docker/rpi/Dockerfile @@ -4,7 +4,7 @@ RUN apt-get update && apt-get install -y \ socat dsniff fuse libfuse-dev git ntpdate python pkg-config build-essential \ && rm -rf /var/lib/apt/lists/* -RUN npm install -g --unsafe gyp autobahn nconf @mdslab/wstun fuse-bindings requestify is-running connection-tester log4js@1.1.1 q fs-access mknod jsonfile \ +RUN npm install -g --unsafe gyp autobahn nconf @mdslab/wstun fuse-bindings requestify is-running connection-tester log4js@1.1.1 q fs-access mknod jsonfile md5 python-shell net node-red \ && npm install -g --unsafe https://github.com/PlayNetwork/node-statvfs/tarball/v3.0.0 \ && npm cache --force clean diff --git a/utils/docker/x86_64/Dockerfile b/utils/docker/x86_64/Dockerfile index 707ba46..1b8bc66 100644 --- a/utils/docker/x86_64/Dockerfile +++ b/utils/docker/x86_64/Dockerfile @@ -5,7 +5,7 @@ RUN apt-get update && apt-get install -y \ && rm -rf /var/lib/apt/lists/* -RUN npm install -g --unsafe gyp autobahn nconf @mdslab/wstun fuse-bindings requestify is-running connection-tester log4js@1.1.1 q fs-access mknod jsonfile \ +RUN npm install -g --unsafe gyp autobahn nconf @mdslab/wstun fuse-bindings requestify is-running connection-tester log4js@1.1.1 q fs-access mknod jsonfile md5 python-shell net node-red \ && npm install -g --unsafe https://github.com/PlayNetwork/node-statvfs/tarball/v3.0.0 \ && npm cache --force clean diff --git a/utils/install/arancino/configure_LR_arancino.sh b/utils/install/arancino/configure_LR_arancino.sh index bd33781..0d88455 100644 --- a/utils/install/arancino/configure_LR_arancino.sh +++ b/utils/install/arancino/configure_LR_arancino.sh @@ -13,9 +13,11 @@ echo -e "\n" sed -i "s/\"device\":.*\"\"/\"device\": \"raspberry_pi\"/g" /var/lib/iotronic/settings.json sed -i "s/\"code\":.*\"\"/\"code\": \"$1\"/g" /var/lib/iotronic/settings.json -sed -i "s,\"url_wamp\":.*,\"url_wamp\": \"$2\"\,,g" /var/lib/iotronic/settings.json -sed -i "s,\"url_reverse\":.*,\"url_reverse\": \"$2\"\,,g" /var/lib/iotronic/settings.json -sed -i "s/\"bin\":.*\"\"/\"bin\": \"\/usr\/lib\/node_modules\/@mdslab\/wstun\/bin\/wstun.js\"/g" /var/lib/iotronic/settings.json +sed -i "s,\"url_wamp\":.*,\"url_wamp\": \"wss://crossbar.$2\"\,,g" /var/lib/iotronic/settings.json +sed -i "s,\"port_wamp\":.*,\"port_wamp\": \"443\"\,,g" /var/lib/iotronic/settings.json +sed -i "s,\"url_reverse\":.*,\"url_reverse\": \"wss://wstun.$2\"\,,g" /var/lib/iotronic/settings.json +sed -i "s,\"port_reverse\":.*,\"port_reverse\": \"443\",g" /var/lib/iotronic/settings.json +sed -i "s/\"bin\":.*\"wstun\"/\"bin\":\"\/usr\/lib\/node_modules\/@mdslab\/wstun\/bin\/wstun.js\"/g" /var/lib/iotronic/settings.json echo " - settings.json file configured." @@ -24,6 +26,6 @@ echo " - Lightning-rod enabled at boot." echo -e "\nLightning-rod starting..." -/etc/init.d/lightning-rod start +/etc/init.d/lightning-rod restart diff --git a/utils/install/arancino/install_LR_arancino.sh b/utils/install/arancino/install_LR_arancino.sh index bcd8320..58632b8 100644 --- a/utils/install/arancino/install_LR_arancino.sh +++ b/utils/install/arancino/install_LR_arancino.sh @@ -4,7 +4,7 @@ echo -e "Installing Stack4Things Lightning-rod.\n" echo "Configure IoTronic ENV:" -mkdir /var/lib/iotronic/ +mkdir -p /var/lib/iotronic/ cd /var/lib/iotronic/ && mkdir plugins && mkdir drivers cp /usr/lib/node_modules/@mdslab/iotronic-lightning-rod/settings.example.json /var/lib/iotronic/settings.json cp /usr/lib/node_modules/@mdslab/iotronic-lightning-rod/modules/plugins-manager/plugins.example.json /var/lib/iotronic/plugins/plugins.json @@ -12,7 +12,7 @@ cp /usr/lib/node_modules/@mdslab/iotronic-lightning-rod/modules/drivers-manager/ echo " - IoTronic Home created and configured." -mkdir /var/log/iotronic/ +mkdir -p /var/log/iotronic/plugins touch /var/log/iotronic/lightning-rod.log cp /usr/lib/node_modules/@mdslab/iotronic-lightning-rod/etc/logrotate.d/lightning-rod.log /etc/logrotate.d/lightning-rod.log echo " - logging configured." @@ -21,9 +21,11 @@ echo " - logging configured." echo "export NODE_PATH=/usr/lib/node_modules" >> /etc/profile echo "export IOTRONIC_HOME=/var/lib/iotronic" >> /etc/profile echo "export LIGHTNINGROD_HOME=/usr/lib/node_modules/@mdslab/iotronic-lightning-rod" >> /etc/profile +echo "export NODE_TLS_REJECT_UNAUTHORIZED=0" >> /etc/profile source /etc/profile echo " - environment configured:" echo " --> NODE_PATH: "$NODE_PATH +echo " --> NODE_TLS_REJECT_UNAUTHORIZED "$NODE_TLS_REJECT_UNAUTHORIZED echo " --> IOTRONIC_HOME: "$IOTRONIC_HOME echo " --> LIGHTNINGROD_HOME: "$LIGHTNINGROD_HOME @@ -38,7 +40,7 @@ cp /usr/lib/node_modules/@mdslab/iotronic-lightning-rod/etc/cron.d/root_openwrt echo " - crond configuration installed." -echo "Rebooting..." -reboot +#echo "Rebooting..." +#reboot diff --git a/utils/install/arancino/moveVar_LR_arancino.sh b/utils/install/arancino/moveVar_LR_arancino.sh index 616935e..1c09641 100644 --- a/utils/install/arancino/moveVar_LR_arancino.sh +++ b/utils/install/arancino/moveVar_LR_arancino.sh @@ -1,15 +1,22 @@ #!/bin/ash -echo "Moving /var/ folder..." +#echo "Moving /var/ folder..." +#mkdir /var +#mv /tmp/etc /var +#mv /tmp/lib /var +#mv /tmp/log /var +#mv /tmp/run /var +#mv /tmp/lock /var +#mv /tmp/state /var +#mv /tmp/tmp /var +#cd / +mkdir /var_tmp +cp -a /tmp/etc/ /var_tmp/ +cp -a /tmp/lib/ /var_tmp/ +cp -a /tmp/log/ /var_tmp/ +cp -a /tmp/run/ /var_tmp/ rm /var -cd / -mkdir var -cp -a /tmp/etc/ /var/ -cp -a /tmp/lib/ /var/ -cp -a /tmp/log/ /var/ -cp -a /tmp/run/ /var/ -echo " - /var/ folder moved." - - +mv /var_tmp /var +echo " - /var/ folder moved." \ No newline at end of file diff --git a/utils/install/install_LR_ubuntu16.04.sh b/utils/install/install_LR_ubuntu16.04.sh deleted file mode 100644 index 26d65c8..0000000 --- a/utils/install/install_LR_ubuntu16.04.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash - -echo Number of parameters: $# - -if [ "$#" -ne 4 ]; then - echo "Usage: ./install_LR_ubuntu16.04.sh " - exit -fi - -echo Installing Stack4Things Lightning-rod with the following parameters: -echo Branch: $1 -echo Node UUID: $2 -echo WAMP Router IP: $3 -echo WS Server IP: $4 - -apt -y install unzip socat dsniff fuse libfuse-dev pkg-config python - -curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash - -apt-get install -y nodejs -node -v - -npm install -g npm -echo Python found at `which python2.7` -npm config set python `which python2.7` -npm -v - -npm install -g gyp autobahn jsonfile nconf wstun tty.js fuse-bindings requestify is-running connection-tester log4js q secure-keys fs-access mknod -npm install -g https://github.com/PlayNetwork/node-statvfs/tarball/v3.0.0 - -echo "NODE_PATH=/usr/lib/node_modules" | sudo tee -a /etc/environment -source /etc/environment > /dev/null -echo $NODE_PATH - -mkdir /opt/stack4things/ && cd /opt/stack4things/ -wget https://github.com/MDSLab/s4t-lightning-rod/archive/$1.zip --no-check-certificate -unzip $1.zip && rm -f $1.zip -mv s4t-lightning-rod-$1 lightning-rod -cd lightning-rod && mkdir plugins && mkdir plugin_conf && mkdir drivers -cp /opt/stack4things/lightning-rod/plugins.example.json /opt/stack4things/lightning-rod/plugins.json -cp /opt/stack4things/lightning-rod/drivers.example.json /opt/stack4things/lightning-rod/drivers.json -cp /opt/stack4things/lightning-rod/settings.example.json /opt/stack4things/lightning-rod/settings.json -cp /opt/stack4things/lightning-rod/etc/systemd/system/s4t-lightning-rod.service /etc/systemd/system/s4t-lightning-rod.service -chmod +x /etc/systemd/system/s4t-lightning-rod.service -systemctl daemon-reload -systemctl enable s4t-lightning-rod.service -touch /var/log/s4t-lightning-rod.log - -sed -i "s/\"device\":.*\"\"/\"device\": \"laptop\"/g" /opt/stack4things/lightning-rod/settings.json -sed -i "s/\"code\":.*\"\"/\"code\": \""$2"\"/g" /opt/stack4things/lightning-rod/settings.json -sed -i "s/\"bin\":.*\"\"/\"bin\": \"\/usr\/lib\/node_modules\/wstun\/bin\/wstun.js\"/g" /opt/stack4things/lightning-rod/settings.json -sed -i "s/\"url_wamp\":.*\"\"/\"url_wamp\": \"ws:\/\/"$3"\"/g" /opt/stack4things/lightning-rod/settings.json -sed -i "s/\"url_reverse\":.*\"\"/\"url_reverse\": \"ws:\/\/"$4"\"/g" /opt/stack4things/lightning-rod/settings.json -systemctl start s4t-lightning-rod