From e3af2bd74a39fbfe26898207d38a458d299fe03e Mon Sep 17 00:00:00 2001
From: zhongjin
+ | page_title |
+
+
+
+
+ s
+
+
+
ip_info
+ +port_info
+ +name_info
+ +update_interval_info
+ +trouble_info
+ +Gc+WElAWa9!m)BG>Q!-Eez z_xt+5H@-f1bVVW265dTAJ{dZ7EcW?a+K-A^F`iAJE-7`0J+jj&F^}HZE{OLqoJY z6!^#2UOW7|RX0o{SnNAzU}YQW^2;Z2=bD-LGu+7xKQ5>mH9})!KRrERgn(ObvtVUP z$!r*s6N6E{`K{#?6lNro-K+nxn?nule`;#)x HIeJ8KHyu02I zjw{audsg5ht*!Mae*DvicQP_!+Hm&PhD>Sp-zR(G xsX+I)_ 8(Uf?|4Gf#YOHKiO3%@=8Z|;{QcE$=*k)bt=osX&_3w`K z^bKCp)Y`e_lfuF|JeYQ*we!uvf!H^`|NZxf$K@DFC$gnECay;(8DqSA7TDAY$awAW z;U3mK_?LKJ?}?S?3VTk#0F7-O>+0%yo_ykoU1O7$R2J;Xaz1SmI~8_JEj})^V)w2V z9{Jfja(E $n}rZR%F0U*9`CXK2TfH~KJ(@k zGkbOsB@;5oR9&Qij+sK963eH;Dh0#gIDdNW02? 3)ZQ;1rD`fP4anzL$pK2=ruOq*6nC}izVodSD` zm&d~P^@Z8Ct(gsP)-f~`?GdoHv9;s5PXl^Rz(NR78VqdC5Be8fch>@%kMywVmHnLP z4?nM&tUc1%*;!tmokiSpjgsm*1eXBL48NPH)AE@zC67R%kAeapdHFuvZpRqVk#L;8 zz6iVbw6b$Y3rCOkq7;UJ7j=`hpVq7A3=A+~Lg6IM)P4r)QlJB+zI?Q;^TqK#TV9@> zrJ}AY5ZXde7XmYY Hint: If you want to connect to an UPS connected to a Synology diskstation the name is simply 'ups'.", + "de":"Name der USV, wie in den NUT EInstellungen definiert.Hinweis: Für eine USV, die an eine Synology Diskstation angeschlossen ist, lautet der Name 'ups'.", + "ru":"Name of the UPS as defined in the NUT configuration of the NUT server.Hint: If you want to connect to an UPS connected to a Synology diskstation the name is simply 'ups'." + }, + "update_interval_info": { + "en":"Interval in Seconds to update the data.", + "de":"Intervall in Sekunden in dem die Daten aktualisiert werden.", + "ru":"Interval in Seconds to update the data." + }, + "trouble_info": { + "en":"When you turn the adapter into debug then you can see all created states and their data in the logfile. If you have problems and the adapter do not deliver the data you can use the two scripts in directory 'test' of the adapter installation (so normally in node_modules/iobroker.nut/test relative to your iobroker installation directory) to try it out on the commandline. Call the scripts using 'node filename.js' to see the awaited parameters. ", + "de":"Wenn der Adapter im Debug Modus gestartet wird, werden im Logfile alle erzeugten States und deren Daten aufgelistet. Wenn der Adapter keine Daten liefert können für direklte Tests auch die beiden Skripte im Verzeichnis 'test' der Adapter-Installation (normalerweise unter node_modules/iobroker.nut/test relativ zur iobroker-Installation) an der Kommandozeile aufgerufen werden. Die Skripte können mit 'node filename.js' aufgerufen werden um die benötigten Parameter zu sehen.
- test_upslist.js: Connects to the NUT server and returns a list of available UPS names
- test_upsvars.js: Connects to the NUT server for a defined UPS and returns a list of available UPS variables
", + "ru":"When you turn the adapter into debug then you can see all created states and their data in the logfile. If you have problems and the adapter do not deliver the data you can use the two scripts in directory 'test' of the adapter installation (so normally in node_modules/iobroker.nut/test relative to your iobroker installation directory) to try it out on the commandline. Call the scripts using 'node filename.js' to see the awaited parameters.
- test_upslist.js: Verbindet sich zu einem NUT Server und gibt die Namen der verbundenen USVs aus
- test_upsvars.js: Verbindet sich zu einem NUT Server für eine definierte USV und gibt die verfügbaren UPS Varialen aus
" + } +}; diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..84ca0a9 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,25 @@ +version: 'test-{build}' +environment: + matrix: + - nodejs_version: '4' + - nodejs_version: '6' + - nodejs_version: '8' + - nodejs_version: '10' +platform: + - x86 + - x64 +clone_folder: 'c:\projects\%APPVEYOR_PROJECT_NAME%' +install: + - ps: 'Install-Product node $env:nodejs_version $env:platform' + - ps: '$NpmVersion = (npm -v).Substring(0,1)' + - ps: 'if($NpmVersion -eq 5) { npm install -g npm@5 }' + - ps: npm --version + - npm install + - npm install winston@2.3.1 + - 'npm install https://github.com/ioBroker/ioBroker.js-controller/tarball/master --production' +test_script: + - echo %cd% + - node --version + - npm --version + - npm test +build: 'off' diff --git a/io-package.json b/io-package.json new file mode 100644 index 0000000..03a8669 --- /dev/null +++ b/io-package.json @@ -0,0 +1,74 @@ +{ + "common": { + "name": "nut", + "version": "1.1.3", + "news": { + "1.1.3": { + "en": "Fix Admin", + "de": "Fixe Admin", + "ru": "Fix Admin", + "pt": "Corrigir Admin", + "nl": "Admin oplossen", + "fr": "Fix Admin", + "it": "Correggi Admin", + "es": "Fix Admin", + "pl": "Napraw administratora" + }, + "1.1.2": { + "en": "fix status parsing", + "de": "Statuserkennung verbessert", + "ru": "fix status parsing" + }, + "1.1.1": { + "en": "enhance error handling", + "de": "Fehlerbehandlung verbessert", + "ru": "enhance error handling" + }, + "1.1.0": { + "en": "Add possibility to call commands on the UPS", + "de": "Möglichkeit hinzugefügt Kommandos auf der USV auszuführen", + "ru": "Add possibility to call commands on the UPS" + }, + "1.0.0": { + "en": "change mode from schedule to deamon, implement message support to receive messages from upsmon, add status.severity", + "de": "Adapter arbeitet nun als Deamon, Notify-Support per upsmon hinzugefügt, status.severity hinzugefügt", + "ru": "change mode from schedule to deamon, implement message support to receive messages from upsmon, add status.severity" + }, + "0.3.1": { + "en": "add better usable status states under 'status' channel", + "de": "Besser nutzbare Stati States unter 'status' eingefügt", + "ru": "add better usable status states under 'status' channel" + } + }, + "authors": [ + "Apollon77
- test_upslist.js: Connects to the NUT server and returns a list of available UPS names
- test_upsvars.js: Connects to the NUT server for a defined UPS and returns a list of available UPS variables
" + ], + "title": "Network UPS Adapter", + "desc": "Read all data from your UPS/USV via nut protocol", + "platform": "Javascript/Node.js", + "mode": "daemon", + "messagebox": true, + "subscribe": "messagebox", + "stopBeforeUpdate": true, + "icon": "nut.png", + "extIcon": "https://raw.githubusercontent.com/Apollon77/ioBroker.nut/master/admin/nut.png", + "readme": "https://github.com/Apollon77/ioBroker.nut/blob/master/README.md", + "license": "MIT", + "npmLibs": [], + "type": "hardware", + "keywords": ["iobroker", "nut", "ups", "usv"], + "loglevel": "info", + "enabled": false + + }, + "native": { + "host_ip": "127.0.0.1", + "host_port": "3493", + "ups_name": "nutName", + "update_interval": 300, + "username": "", + "password": "" + }, + "objects": [], + "instanceObjects": [] +} diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..c8a0eb7 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,83 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +let controllerDir; +let appName; + +/** + * returns application name + * + * The name of the application can be different and this function finds it out. + * + * @returns {string} + */ + function getAppName() { + const parts = __dirname.replace(/\\/g, '/').split('/'); + return parts[parts.length - 2].split('.')[0]; +} + +/** + * looks for js-controller home folder + * + * @param {boolean} isInstall + * @returns {string} + */ +function getControllerDir(isInstall) { + // Find the js-controller location + const possibilities = [ + 'iobroker.js-controller', + 'ioBroker.js-controller', + ]; + /** @type {string} */ + let controllerPath; + for (const pkg of possibilities) { + try { + const possiblePath = require.resolve(pkg); + if (fs.existsSync(possiblePath)) { + controllerPath = possiblePath; + break; + } + } catch (e) { /* not found */ } + } + if (controllerPath == null) { + if (!isInstall) { + console.log('Cannot find js-controller'); + process.exit(10); + } else { + process.exit(); + } + } + // we found the controller + return path.dirname(controllerPath); +} + +/** + * reads controller base settings + * + * @alias getConfig + * @returns {object} + */ + function getConfig() { + let configPath; + if (fs.existsSync( + configPath = path.join(controllerDir, 'conf', appName + '.json') + )) { + return JSON.parse(fs.readFileSync(configPath, 'utf8')); + } else if (fs.existsSync( + configPath = path.join(controllerDir, 'conf', + appName.toLowerCase() + '.json') + )) { + return JSON.parse(fs.readFileSync(configPath, 'utf8')); + } else { + throw new Error('Cannot find ' + controllerDir + '/conf/' + appName + '.json'); + } +} +appName = getAppName(); +controllerDir = getControllerDir(typeof process !== 'undefined' && process.argv && process.argv.indexOf('--install') !== -1); +const adapter = require(path.join(controllerDir, 'lib/adapter.js')); + +exports.controllerDir = controllerDir; +exports.getConfig = getConfig; +exports.Adapter = adapter; +exports.appName = appName; diff --git a/nut.js b/nut.js new file mode 100644 index 0000000..9785671 --- /dev/null +++ b/nut.js @@ -0,0 +1,378 @@ +/** + * + * NUT adapter + * + * Adapter loading NUT data from an UPS + * + */ + /* jshint -W097 */ + // jshint strict:true + /*jslint node: true */ + /*jslint esversion: 6 */ +'use strict'; + +var path = require('path'); +var utils = require(path.join(__dirname,'lib','utils')); // Get common adapter utils +var Nut = require('node-nut'); + +var nutTimeout; + +var nutCommands = null; + +var adapter = utils.Adapter('nut'); + +adapter.on('ready', function (obj) { + main(); +}); + +adapter.on('message', function (msg) { + processMessage(msg); +}); + +adapter.on('stateChange', function (id, state) { + adapter.log.debug('stateChange ' + id + ' ' + JSON.stringify(state)); + var realNamespace = adapter.namespace + '.commands.'; + var stateId = id.substring(realNamespace.length); + if (!state || state.ack || id.indexOf(realNamespace) !== 0) return; + + var command = stateId.replace(/-/g,'.'); + initNutConnection(function(oNut) { + if (adapter.config.username && adapter.config.password) { + adapter.log.info('send username for command ' + command); + oNut.SetUsername(adapter.config.username, function (err) { + if (err) { + adapter.log.error('Err while sending username: '+ err); + } + else { + adapter.log.info('send password for command ' + command); + oNut.SetPassword(adapter.config.password, function (err) { + if (err) { + adapter.log.error('Err while sending password: '+ err); + } + else { + adapter.log.info('send command ' + command); + oNut.RunUPSCommand(adapter.config.ups_name, command, function (err) { + if (err) { + adapter.log.error('Err while sending command ' + command + ': '+ err); + } + getCurrentNutValues(oNut, true); + }); + } + }); + } + }); + } + else { + adapter.log.info('send command ' + command + ' without username and password'); + oNut.RunUPSCommand(adapter.config.ups_name, command, function (err) { + if (err) { + adapter.log.error('Err while sending command ' + command + ': '+ err); + } + getCurrentNutValues(oNut, true); + }); + } + + adapter.setState(id, {ack: true, val: false}); + }); +}); + +adapter.on('unload', function (callback) { + if (nutTimeout) clearTimeout(nutTimeout); +}); + +process.on('SIGINT', function () { + if (nutTimeout) clearTimeout(nutTimeout); +}); + +process.on('uncaughtException', function (err) { + if (adapter && adapter.log) { + adapter.log.warn('Exception: ' + err); + } + if (nutTimeout) clearTimeout(nutTimeout); +}); + +function main() { + adapter.getForeignObject('system.adapter.' + adapter.namespace, function (err, obj) { + if (!err && obj && (obj.common.mode !== 'daemon')) { + obj.common.mode = 'daemon'; + if (obj.common.schedule) delete(obj.common.schedule); + adapter.setForeignObject(obj._id, obj); + } + }); + adapter.setObjectNotExists('status.last_notify', { + type: 'state', + common: { + name: 'status.last_notify', + type: 'string', + read: true, + write: false + }, + native: {id: 'status.last_notify'} + }); + adapter.getState('status.last_notify', function (err, state) { + if (!err && !state) { + adapter.setState('status.last_notify', {ack: true, val: ''}); + } + initNutConnection(function(oNut) { + oNut.GetUPSCommands(adapter.config.ups_name, function(cmdlist, err) { + if (err) { + adapter.log.error('Err while getting all commands: '+ err); + } + else { + adapter.log.debug('Got commands, create and subscribe command states'); + initNutCommands(cmdlist); + } + + getCurrentNutValues(oNut, true); + + var update_interval = parseInt(adapter.config.update_interval,10) || 60; + nutTimeout = setTimeout(updateNutData, update_interval*1000); + }); + }); + }); +} + +function initNutCommands(cmdlist) { + adapter.log.debug('Create Channel commands'); + adapter.setObjectNotExists('commands', { + type: 'channel', + common: {name: 'commands'}, + native: {} + }); + + if (! cmdlist) return; + nutCommands = cmdlist; + for (var i = 0; i < cmdlist.length; i++) { + var cmdName = cmdlist[i].replace(/\./g,'-'); + adapter.log.debug('Create State commands.' + cmdName); + adapter.setObjectNotExists('commands.' + cmdName, { + type: 'state', + common: { + name: 'commands.' + cmdName, + role: 'button', + type: 'boolean', + read: true, + write: true, + def: false + }, + native: {id: 'commands.' + cmdName} + }); + adapter.setState('commands.' + cmdName, {ack: true, val: false}); + } + adapter.subscribeStates('commands.*'); +} + +/* +Command Datapoint to be used with "NOIFY EVENTS" and upsmon +ONLINE : The UPS is back on line. +ONBATT : The UPS is on battery. +LOWBATT : The UPS battery is low (as determined by the driver). +FSD : The UPS has been commanded into the "forced shutdown" mode. +COMMOK : Communication with the UPS has been established. +COMMBAD : Communication with the UPS was just lost. +SHUTDOWN : The local system is being shut down. +REPLBATT : The UPS needs to have its battery replaced. +NOCOMM : The UPS can’t be contacted for monitoring. +*/ +function processMessage(message) { + if (!message) return; + + adapter.log.info('Message received = ' + JSON.stringify(message)); + + var updateNut = false; + if (message.command === 'notify' && message.message) { + adapter.log.info('got Notify ' + message.message.notifytype + ' for: ' + message.message.upsname); + var ownName = adapter.config.ups_name + '@' + adapter.config.host_ip; + adapter.log.info('ownName=' + ownName + ' --> ' + (ownName === message.message.upsname)); + if (ownName === message.message.upsname) { + updateNut = true; + adapter.setState('status.last_notify', {ack: true, val: message.message.notifytype}); + if (message.message.notifytype==='COMMBAD' || message.message.notifytype==='NOCOMM') parseAndSetSeverity("OFF"); + } + } + else updateNut = true; + + if (updateNut) { + if (nutTimeout) clearTimeout(nutTimeout); + updateNutData(); + } +} + +function initNutConnection(callback) { + var oNut = new Nut(adapter.config.host_port, adapter.config.host_ip); + + oNut.on('error', function(err) { + adapter.log.error('Error happend: ' + err); + adapter.getState('status.last_notify', function (err, state) { + if (!err && !state || (state && state.val!=='COMMBAD' && state.val!=='SHUTDOWN' && state.val!=='NOCOMM')) { + adapter.setState('status.last_notify', {ack: true, val: 'ERROR'}); + } + if (!err) parseAndSetSeverity(""); + }); + }); + + oNut.on('close', function() { + adapter.log.debug('NUT Connection closed. Done.'); + }); + + oNut.on('ready', function() { + adapter.log.debug('NUT Connection ready'); + callback(oNut); + }); + + oNut.start(); +} + +function updateNutData() { + adapter.log.info('Start NUT update'); + + initNutConnection(function(oNut) { + getCurrentNutValues(oNut, true); + }); + + var update_interval = parseInt(adapter.config.update_interval,10) || 60; + nutTimeout = setTimeout(updateNutData, update_interval*1000); +} + +function getCurrentNutValues(oNut, closeConnection) { + oNut.GetUPSVars(adapter.config.ups_name, function(varlist, err) { + if (err) { + adapter.log.error('Err while getting NUT values: '+ err); + } + else { + adapter.log.debug('Got values, start setting them'); + storeNutData(varlist); + } + if (closeConnection) oNut.close(); + }); +} + +function storeNutData(varlist) { + var last=''; + var current=''; + var index=0; + var stateName=''; + + for (var key in varlist) { + if (!varlist.hasOwnProperty(key)) continue; + + index=key.indexOf('.'); + if (index > 0) { + current=key.substring(0,index); + } + else { + current=''; + last=''; + index=-1; + } + if (((last==='') || (last!==current)) && (current!=='')) { + adapter.log.debug('Create Channel '+current); + adapter.setObjectNotExists(current, { + type: 'channel', + common: {name: current}, + native: {} + }); + } + stateName=current+'.'+key.substring(index+1).replace(/\./g,'-'); + adapter.log.debug('Create State '+stateName); + if (stateName === 'battery.charge') { + adapter.setObjectNotExists(stateName, { + type: 'state', + common: {name: stateName, type: 'number', role: 'value.battery', read: true, write: false}, + native: {id: stateName} + }); + } + else { + adapter.setObjectNotExists(stateName, { + type: 'state', + common: {name: stateName, type: 'string', read: true, write: false}, + native: {id: stateName} + }); + } + adapter.log.debug('Set State '+stateName+' = '+varlist[key]); + adapter.setState(stateName, {ack: true, val: varlist[key]}); + last=current; + } + + adapter.log.debug('Create Channel status'); + adapter.setObjectNotExists('status', { + type: 'channel', + common: {name: 'status'}, + native: {} + }); + adapter.setObjectNotExists('status.severity', { + type: 'state', + common: { + name: 'status.severity', + role: 'indicator', + type: 'number', + read: true, + write: false, + def:4, + states: '0:idle;1:operating;2:operating_critical;3:action_needed;4:unknown' + }, + native: {id: 'status.severity'} + }); + if (varlist['ups.status']) { + parseAndSetSeverity(varlist['ups.status']); + } + else parseAndSetSeverity(""); + + adapter.log.info('All Nut values set'); +} + +function parseAndSetSeverity(ups_status) { + var statusMap = { + 'OL':{name:'online',severity:'idle'}, + 'OB':{name:'onbattery',severity:'operating'}, + 'LB':{name:'lowbattery',severity:'operating_critical'}, + 'HB':{name:'highbattery',severity:'operating_critical'}, + 'RB':{name:'replacebattery',severity:'action_needed'}, + 'CHRG':{name:'charging',severity:'idle'}, + 'DISCHRG':{name:'discharging',severity:'operating'}, + 'BYPASS':{name:'bypass',severity:'action_needed'}, + 'CAL':{name:'calibration',severity:'operating'}, + 'OFF':{name:'offline',severity:'action_needed'}, + 'OVER':{name:'overload',severity:'action_needed'}, + 'TRIM':{name:'trimming',severity:'operating'}, + 'BOOST':{name:'boosting',severity:'operating'}, + 'FSD':{name:'shutdown',severity:'operating_critical'} + }; + var severity = { + 'idle':false, + 'operating':false, + 'operating_critical':false, + 'action_needed':false + }; + if (ups_status.indexOf('FSD') !== -1) { + ups_status += ' OB LB'; + } + var checker=' '+ups_status+' '; + var stateName=""; + for (var idx in statusMap) { + if (statusMap.hasOwnProperty(idx)) { + var found=(checker.indexOf(' ' + idx)>-1); + stateName='status.'+statusMap[idx].name; + adapter.log.debug('Create State '+stateName); + adapter.setObjectNotExists(stateName, { + type: 'state', + common: {name: stateName, type: 'boolean', read: true, write: false}, + native: {id: stateName} + }); + adapter.log.debug('Set State '+stateName+' = '+found); + adapter.setState(stateName, {ack: true, val: found}); + if (found) { + severity[statusMap[idx].severity]=true; + adapter.log.debug('Severity Flag '+statusMap[idx].severity+'=true'); + } + } + } + var severityVal = 4; + if (severity.operating_critical) severityVal=2; + else if (severity.action_needed) severityVal=3; + else if (severity.operating) severityVal=1; + else if (severity.idle) severityVal=0; + + adapter.log.debug('Set State status.severity = '+severityVal); + adapter.setState('status.severity', {ack: true, val: severityVal}); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1faa9a2 --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "iobroker.nut", + "version": "1.1.3", + "description": "Network UPS Adapter", + "author": "Ingo Fischer ", + "contributors": [], + "homepage": "", + "license": "MIT", + "keywords": [ + "iobroker", + "nut", + "ups", + "usv" + ], + "repository": { + "type": "git", + "url": "https://github.com/Apollon77/ioBroker.nut" + }, + "dependencies": { + "node-nut": "^1.0.0" + }, + "devDependencies": { + "mocha": "^5.0.0", + "chai": "^4.1.2", + "nyc": "*" + }, + "bugs": { + "url": "https://github.com/Apollon77/ioBroker.nut/issues" + }, + "main": "nut.js", + "scripts": { + "test": "nyc --reporter=lcov node_modules/mocha/bin/mocha test/testAdapter.js --exit && node node_modules/mocha/bin/mocha test/testPackageFiles.js" + }, + "nyc": { + "exclude": ["!**/node_modules/"], + "include": [ + "**/tmp/node_modules/iobroker.nut/*.js" + ], + "produce-source-map": true + } +} diff --git a/scripts/nut-notify.sh b/scripts/nut-notify.sh new file mode 100644 index 0000000..5359347 --- /dev/null +++ b/scripts/nut-notify.sh @@ -0,0 +1,5 @@ +#! /bin/sh +# NUT adapter notify script. + +logger -t nut-notify "Notify iobroker $UPSNAME -> $NOTIFYTYPE" +/opt/iobroker/iobroker message nut notify "{\"upsname\":\"$UPSNAME\",\"notifytype\":\"$NOTIFYTYPE\"}" diff --git a/test/lib/setup.js b/test/lib/setup.js new file mode 100644 index 0000000..16857ed --- /dev/null +++ b/test/lib/setup.js @@ -0,0 +1,728 @@ +/* jshint -W097 */// jshint strict:false +/*jslint node: true */ +// check if tmp directory exists +var fs = require('fs'); +var path = require('path'); +var child_process = require('child_process'); +var rootDir = path.normalize(__dirname + '/../../'); +var pkg = require(rootDir + 'package.json'); +var debug = typeof v8debug === 'object'; +pkg.main = pkg.main || 'main.js'; + +var adapterName = path.normalize(rootDir).replace(/\\/g, '/').split('/'); +adapterName = adapterName[adapterName.length - 2]; +var adapterStarted = false; + +function getAppName() { + var parts = __dirname.replace(/\\/g, '/').split('/'); + return parts[parts.length - 3].split('.')[0]; +} + +var appName = getAppName().toLowerCase(); + +var objects; +var states; + +var pid = null; + +function copyFileSync(source, target) { + + var targetFile = target; + + //if target is a directory a new file with the same name will be created + if (fs.existsSync(target)) { + if ( fs.lstatSync( target ).isDirectory() ) { + targetFile = path.join(target, path.basename(source)); + } + } + + try { + fs.writeFileSync(targetFile, fs.readFileSync(source)); + } + catch (err) { + console.log("file copy error: " +source +" -> " + targetFile + " (error ignored)"); + } +} + +function copyFolderRecursiveSync(source, target, ignore) { + var files = []; + + var base = path.basename(source); + if (base === adapterName) { + base = pkg.name; + } + //check if folder needs to be created or integrated + var targetFolder = path.join(target, base); + if (!fs.existsSync(targetFolder)) { + fs.mkdirSync(targetFolder); + } + + //copy + if (fs.lstatSync(source).isDirectory()) { + files = fs.readdirSync(source); + files.forEach(function (file) { + if (ignore && ignore.indexOf(file) !== -1) { + return; + } + + var curSource = path.join(source, file); + var curTarget = path.join(targetFolder, file); + if (fs.lstatSync(curSource).isDirectory()) { + // ignore grunt files + if (file.indexOf('grunt') !== -1) return; + if (file === 'chai') return; + if (file === 'mocha') return; + copyFolderRecursiveSync(curSource, targetFolder, ignore); + } else { + copyFileSync(curSource, curTarget); + } + }); + } +} + +if (!fs.existsSync(rootDir + 'tmp')) { + fs.mkdirSync(rootDir + 'tmp'); +} + +function storeOriginalFiles() { + console.log('Store original files...'); + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + + var f = fs.readFileSync(dataDir + 'objects.json'); + var objects = JSON.parse(f.toString()); + if (objects['system.adapter.admin.0'] && objects['system.adapter.admin.0'].common) { + objects['system.adapter.admin.0'].common.enabled = false; + } + if (objects['system.adapter.admin.1'] && objects['system.adapter.admin.1'].common) { + objects['system.adapter.admin.1'].common.enabled = false; + } + + fs.writeFileSync(dataDir + 'objects.json.original', JSON.stringify(objects)); + try { + f = fs.readFileSync(dataDir + 'states.json'); + fs.writeFileSync(dataDir + 'states.json.original', f); + } + catch (err) { + console.log('no states.json found - ignore'); + } +} + +function restoreOriginalFiles() { + console.log('restoreOriginalFiles...'); + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + + var f = fs.readFileSync(dataDir + 'objects.json.original'); + fs.writeFileSync(dataDir + 'objects.json', f); + try { + f = fs.readFileSync(dataDir + 'states.json.original'); + fs.writeFileSync(dataDir + 'states.json', f); + } + catch (err) { + console.log('no states.json.original found - ignore'); + } + +} + +function checkIsAdapterInstalled(cb, counter, customName) { + customName = customName || pkg.name.split('.').pop(); + counter = counter || 0; + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + console.log('checkIsAdapterInstalled...'); + + try { + var f = fs.readFileSync(dataDir + 'objects.json'); + var objects = JSON.parse(f.toString()); + if (objects['system.adapter.' + customName + '.0']) { + console.log('checkIsAdapterInstalled: ready!'); + setTimeout(function () { + if (cb) cb(); + }, 100); + return; + } else { + console.warn('checkIsAdapterInstalled: still not ready'); + } + } catch (err) { + + } + + if (counter > 20) { + console.error('checkIsAdapterInstalled: Cannot install!'); + if (cb) cb('Cannot install'); + } else { + console.log('checkIsAdapterInstalled: wait...'); + setTimeout(function() { + checkIsAdapterInstalled(cb, counter + 1); + }, 1000); + } +} + +function checkIsControllerInstalled(cb, counter) { + counter = counter || 0; + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + + console.log('checkIsControllerInstalled...'); + try { + var f = fs.readFileSync(dataDir + 'objects.json'); + var objects = JSON.parse(f.toString()); + if (objects['system.adapter.admin.0']) { + console.log('checkIsControllerInstalled: installed!'); + setTimeout(function () { + if (cb) cb(); + }, 100); + return; + } + } catch (err) { + + } + + if (counter > 20) { + console.log('checkIsControllerInstalled: Cannot install!'); + if (cb) cb('Cannot install'); + } else { + console.log('checkIsControllerInstalled: wait...'); + setTimeout(function() { + checkIsControllerInstalled(cb, counter + 1); + }, 1000); + } +} + +function installAdapter(customName, cb) { + if (typeof customName === 'function') { + cb = customName; + customName = null; + } + customName = customName || pkg.name.split('.').pop(); + console.log('Install adapter...'); + var startFile = 'node_modules/' + appName + '.js-controller/' + appName + '.js'; + // make first install + if (debug) { + child_process.execSync('node ' + startFile + ' add ' + customName + ' --enabled false', { + cwd: rootDir + 'tmp', + stdio: [0, 1, 2] + }); + checkIsAdapterInstalled(function (error) { + if (error) console.error(error); + console.log('Adapter installed.'); + if (cb) cb(); + }); + } else { + // add controller + var _pid = child_process.fork(startFile, ['add', customName, '--enabled', 'false'], { + cwd: rootDir + 'tmp', + stdio: [0, 1, 2, 'ipc'] + }); + + waitForEnd(_pid, function () { + checkIsAdapterInstalled(function (error) { + if (error) console.error(error); + console.log('Adapter installed.'); + if (cb) cb(); + }); + }); + } +} + +function waitForEnd(_pid, cb) { + if (!_pid) { + cb(-1, -1); + return; + } + _pid.on('exit', function (code, signal) { + if (_pid) { + _pid = null; + cb(code, signal); + } + }); + _pid.on('close', function (code, signal) { + if (_pid) { + _pid = null; + cb(code, signal); + } + }); +} + +function installJsController(cb) { + console.log('installJsController...'); + if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller') || + !fs.existsSync(rootDir + 'tmp/' + appName + '-data')) { + // try to detect appName.js-controller in node_modules/appName.js-controller + // travis CI installs js-controller into node_modules + if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller')) { + console.log('installJsController: no js-controller => copy it from "' + rootDir + 'node_modules/' + appName + '.js-controller"'); + // copy all + // stop controller + console.log('Stop controller if running...'); + var _pid; + if (debug) { + // start controller + _pid = child_process.exec('node ' + appName + '.js stop', { + cwd: rootDir + 'node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2] + }); + } else { + _pid = child_process.fork(appName + '.js', ['stop'], { + cwd: rootDir + 'node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'] + }); + } + + waitForEnd(_pid, function () { + // copy all files into + if (!fs.existsSync(rootDir + 'tmp')) fs.mkdirSync(rootDir + 'tmp'); + if (!fs.existsSync(rootDir + 'tmp/node_modules')) fs.mkdirSync(rootDir + 'tmp/node_modules'); + + if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')){ + console.log('Copy js-controller...'); + copyFolderRecursiveSync(rootDir + 'node_modules/' + appName + '.js-controller', rootDir + 'tmp/node_modules/'); + } + + console.log('Setup js-controller...'); + var __pid; + if (debug) { + // start controller + _pid = child_process.exec('node ' + appName + '.js setup first --console', { + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2] + }); + } else { + __pid = child_process.fork(appName + '.js', ['setup', 'first', '--console'], { + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'] + }); + } + waitForEnd(__pid, function () { + checkIsControllerInstalled(function () { + // change ports for object and state DBs + var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json'); + config.objects.port = 19001; + config.states.port = 19000; + fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2)); + console.log('Setup finished.'); + + copyAdapterToController(); + + installAdapter(function () { + storeOriginalFiles(); + if (cb) cb(true); + }); + }); + }); + }); + } else { + // check if port 9000 is free, else admin adapter will be added to running instance + var client = new require('net').Socket(); + client.connect(9000, '127.0.0.1', function() { + console.error('Cannot initiate fisrt run of test, because one instance of application is running on this PC. Stop it and repeat.'); + process.exit(0); + }); + + setTimeout(function () { + client.destroy(); + if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')) { + console.log('installJsController: no js-controller => install from git'); + + child_process.execSync('npm install https://github.com/' + appName + '/' + appName + '.js-controller/tarball/master --prefix ./ --production', { + cwd: rootDir + 'tmp/', + stdio: [0, 1, 2] + }); + } else { + console.log('Setup js-controller...'); + var __pid; + if (debug) { + // start controller + child_process.exec('node ' + appName + '.js setup first', { + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2] + }); + } else { + child_process.fork(appName + '.js', ['setup', 'first'], { + cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'] + }); + } + } + + // let npm install admin and run setup + checkIsControllerInstalled(function () { + var _pid; + + if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller/' + appName + '.js')) { + _pid = child_process.fork(appName + '.js', ['stop'], { + cwd: rootDir + 'node_modules/' + appName + '.js-controller', + stdio: [0, 1, 2, 'ipc'] + }); + } + + waitForEnd(_pid, function () { + // change ports for object and state DBs + var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json'); + config.objects.port = 19001; + config.states.port = 19000; + fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2)); + + copyAdapterToController(); + + installAdapter(function () { + storeOriginalFiles(); + if (cb) cb(true); + }); + }); + }); + }, 1000); + } + } else { + setTimeout(function () { + console.log('installJsController: js-controller installed'); + if (cb) cb(false); + }, 0); + } +} + +function copyAdapterToController() { + console.log('Copy adapter...'); + // Copy adapter to tmp/node_modules/appName.adapter + copyFolderRecursiveSync(rootDir, rootDir + 'tmp/node_modules/', ['.idea', 'test', 'tmp', '.git', appName + '.js-controller']); + console.log('Adapter copied.'); +} + +function clearControllerLog() { + var dirPath = rootDir + 'tmp/log'; + var files; + try { + if (fs.existsSync(dirPath)) { + console.log('Clear controller log...'); + files = fs.readdirSync(dirPath); + } else { + console.log('Create controller log directory...'); + files = []; + fs.mkdirSync(dirPath); + } + } catch(e) { + console.error('Cannot read "' + dirPath + '"'); + return; + } + if (files.length > 0) { + try { + for (var i = 0; i < files.length; i++) { + var filePath = dirPath + '/' + files[i]; + fs.unlinkSync(filePath); + } + console.log('Controller log cleared'); + } catch (err) { + console.error('cannot clear log: ' + err); + } + } +} + +function clearDB() { + var dirPath = rootDir + 'tmp/iobroker-data/sqlite'; + var files; + try { + if (fs.existsSync(dirPath)) { + console.log('Clear sqlite DB...'); + files = fs.readdirSync(dirPath); + } else { + console.log('Create controller log directory...'); + files = []; + fs.mkdirSync(dirPath); + } + } catch(e) { + console.error('Cannot read "' + dirPath + '"'); + return; + } + if (files.length > 0) { + try { + for (var i = 0; i < files.length; i++) { + var filePath = dirPath + '/' + files[i]; + fs.unlinkSync(filePath); + } + console.log('Clear sqlite DB'); + } catch (err) { + console.error('cannot clear DB: ' + err); + } + } +} + +function setupController(cb) { + installJsController(function (isInited) { + clearControllerLog(); + clearDB(); + + if (!isInited) { + restoreOriginalFiles(); + copyAdapterToController(); + } + // read system.config object + var dataDir = rootDir + 'tmp/' + appName + '-data/'; + + var objs; + try { + objs = fs.readFileSync(dataDir + 'objects.json'); + objs = JSON.parse(objs); + } + catch (e) { + console.log('ERROR reading/parsing system configuration. Ignore'); + objs = {'system.config': {}}; + } + if (!objs || !objs['system.config']) { + objs = {'system.config': {}}; + } + + if (cb) cb(objs['system.config']); + }); +} + +function startAdapter(objects, states, callback) { + if (adapterStarted) { + console.log('Adapter already started ...'); + if (callback) callback(objects, states); + return; + } + adapterStarted = true; + console.log('startAdapter...'); + if (fs.existsSync(rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main)) { + try { + if (debug) { + // start controller + pid = child_process.exec('node node_modules/' + pkg.name + '/' + pkg.main + ' --console silly', { + cwd: rootDir + 'tmp', + stdio: [0, 1, 2] + }); + } else { + // start controller + pid = child_process.fork('node_modules/' + pkg.name + '/' + pkg.main, ['--console', 'silly'], { + cwd: rootDir + 'tmp', + stdio: [0, 1, 2, 'ipc'] + }); + } + } catch (error) { + console.error(JSON.stringify(error)); + } + } else { + console.error('Cannot find: ' + rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main); + } + if (callback) callback(objects, states); +} + +function startController(isStartAdapter, onObjectChange, onStateChange, callback) { + if (typeof isStartAdapter === 'function') { + callback = onStateChange; + onStateChange = onObjectChange; + onObjectChange = isStartAdapter; + isStartAdapter = true; + } + + if (onStateChange === undefined) { + callback = onObjectChange; + onObjectChange = undefined; + } + + if (pid) { + console.error('Controller is already started!'); + } else { + console.log('startController...'); + adapterStarted = false; + var isObjectConnected; + var isStatesConnected; + + var Objects = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/objects/objectsInMemServer'); + objects = new Objects({ + connection: { + "type" : "file", + "host" : "127.0.0.1", + "port" : 19001, + "user" : "", + "pass" : "", + "noFileCache": false, + "connectTimeout": 2000 + }, + logger: { + silly: function (msg) { + console.log(msg); + }, + debug: function (msg) { + console.log(msg); + }, + info: function (msg) { + console.log(msg); + }, + warn: function (msg) { + console.warn(msg); + }, + error: function (msg) { + console.error(msg); + } + }, + connected: function () { + isObjectConnected = true; + if (isStatesConnected) { + console.log('startController: started!'); + if (isStartAdapter) { + startAdapter(objects, states, callback); + } else { + if (callback) { + callback(objects, states); + callback = null; + } + } + } + }, + change: onObjectChange + }); + + // Just open in memory DB itself + var States = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/states/statesInMemServer'); + states = new States({ + connection: { + type: 'file', + host: '127.0.0.1', + port: 19000, + options: { + auth_pass: null, + retry_max_delay: 15000 + } + }, + logger: { + silly: function (msg) { + console.log(msg); + }, + debug: function (msg) { + console.log(msg); + }, + info: function (msg) { + console.log(msg); + }, + warn: function (msg) { + console.log(msg); + }, + error: function (msg) { + console.log(msg); + } + }, + connected: function () { + isStatesConnected = true; + if (isObjectConnected) { + console.log('startController: started!!'); + if (isStartAdapter) { + startAdapter(objects, states, callback); + } else { + if (callback) { + callback(objects, states); + callback = null; + } + } + } + }, + change: onStateChange + }); + } +} + +function stopAdapter(cb) { + if (!pid) { + console.error('Controller is not running!'); + if (cb) { + setTimeout(function () { + cb(false); + }, 0); + } + } else { + adapterStarted = false; + pid.on('exit', function (code, signal) { + if (pid) { + console.log('child process terminated due to receipt of signal ' + signal); + if (cb) cb(); + pid = null; + } + }); + + pid.on('close', function (code, signal) { + if (pid) { + if (cb) cb(); + pid = null; + } + }); + + pid.kill('SIGTERM'); + } +} + +function _stopController() { + if (objects) { + objects.destroy(); + objects = null; + } + if (states) { + states.destroy(); + states = null; + } +} + +function stopController(cb) { + var timeout; + if (objects) { + console.log('Set system.adapter.' + pkg.name + '.0'); + objects.setObject('system.adapter.' + pkg.name + '.0', { + common:{ + enabled: false + } + }); + } + + stopAdapter(function () { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + + _stopController(); + + if (cb) { + cb(true); + cb = null; + } + }); + + timeout = setTimeout(function () { + timeout = null; + console.log('child process NOT terminated'); + + _stopController(); + + if (cb) { + cb(false); + cb = null; + } + pid = null; + }, 5000); +} + +// Setup the adapter +function setAdapterConfig(common, native, instance) { + var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString()); + var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0); + if (common) objects[id].common = common; + if (native) objects[id].native = native; + fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/objects.json', JSON.stringify(objects)); +} + +// Read config of the adapter +function getAdapterConfig(instance) { + var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString()); + var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0); + return objects[id]; +} + +if (typeof module !== undefined && module.parent) { + module.exports.getAdapterConfig = getAdapterConfig; + module.exports.setAdapterConfig = setAdapterConfig; + module.exports.startController = startController; + module.exports.stopController = stopController; + module.exports.setupController = setupController; + module.exports.stopAdapter = stopAdapter; + module.exports.startAdapter = startAdapter; + module.exports.installAdapter = installAdapter; + module.exports.appName = appName; + module.exports.adapterName = adapterName; + module.exports.adapterStarted = adapterStarted; +} diff --git a/test/testAdapter.js b/test/testAdapter.js new file mode 100644 index 0000000..8d7cba6 --- /dev/null +++ b/test/testAdapter.js @@ -0,0 +1,195 @@ +/* jshint -W097 */// jshint strict:false +/*jslint node: true */ +/*jshint expr: true*/ +var expect = require('chai').expect; +var setup = require(__dirname + '/lib/setup'); + +var objects = null; +var states = null; +var onStateChanged = null; +var onObjectChanged = null; +var sendToID = 1; + +var adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.')+1); + +function checkConnectionOfAdapter(cb, counter) { + counter = counter || 0; + console.log('Try check #' + counter); + if (counter > 30) { + if (cb) cb('Cannot check connection'); + return; + } + + states.getState('system.adapter.' + adapterShortName + '.0.alive', function (err, state) { + if (err) console.error(err); + if (state && state.val) { + if (cb) cb(); + } else { + setTimeout(function () { + checkConnectionOfAdapter(cb, counter + 1); + }, 1000); + } + }); +} + +function checkValueOfState(id, value, cb, counter) { + counter = counter || 0; + if (counter > 20) { + if (cb) cb('Cannot check value Of State ' + id); + return; + } + + states.getState(id, function (err, state) { + if (err) console.error(err); + if (value === null && !state) { + if (cb) cb(); + } else + if (state && (value === undefined || state.val === value)) { + if (cb) cb(); + } else { + setTimeout(function () { + checkValueOfState(id, value, cb, counter + 1); + }, 500); + } + }); +} + +function sendTo(target, command, message, callback) { + onStateChanged = function (id, state) { + if (id === 'messagebox.system.adapter.test.0') { + callback(state.message); + } + }; + + states.pushMessage('system.adapter.' + target, { + command: command, + message: message, + from: 'system.adapter.test.0', + callback: { + message: message, + id: sendToID++, + ack: false, + time: (new Date()).getTime() + } + }); +} + +describe('Test ' + adapterShortName + ' adapter', function() { + before('Test ' + adapterShortName + ' adapter: Start js-controller', function (_done) { + this.timeout(600000); // because of first install from npm + + setup.setupController(function () { + var config = setup.getAdapterConfig(); + // enable adapter + config.common.enabled = true; + config.common.loglevel = 'debug'; + + //config.native.dbtype = 'sqlite'; + + setup.setAdapterConfig(config.common, config.native); + + setup.startController(true, function(id, obj) {}, function (id, state) { + if (onStateChanged) onStateChanged(id, state); + }, + function (_objects, _states) { + objects = _objects; + states = _states; + _done(); + }); + }); + }); + + it('Test ' + adapterShortName + ' adapter: Check if adapter started', function (done) { + this.timeout(60000); + checkConnectionOfAdapter(function (res) { + if (res) console.log(res); + expect(res).not.to.be.equal('Cannot check connection'); + objects.setObject('system.adapter.test.0', { + common: { + + }, + type: 'instance' + }, + function () { + states.subscribeMessage('system.adapter.test.0'); + done(); + }); + }); + }); + + // We expect ERROR as last Notify necause no nut is running there + it('Test ' + adapterShortName + ' adapter: test initial state as ERROR', function (done) { + this.timeout(25000); + + setTimeout(function() { + states.getState('nut.0.status.last_notify', function (err, state) { + if (err) console.error(err); + expect(state).to.exist; + if (!state) { + console.error('state "status.last_notify" not set'); + } + else { + console.log('check status.last_notify ... ' + state.val); + expect(state.val).to.exist; + expect(state.val).to.be.equal('ERROR'); + } + states.getState('nut.0.status.severity', function (err, state) { + if (err) console.error(err); + expect(state).to.exist; + if (!state) { + console.error('state "status.severity" not set'); + } + else { + console.log('check status.severity ... ' + state.val); + } + expect(state.val).to.exist; + expect(state.val).to.be.equal(4); + done(); + }); + }); + }, 10000); + }); + + it('Test ' + adapterShortName + ' adapter: send notify Message and receive answer', function (done) { + this.timeout(25000); + var now = new Date().getTime(); + + console.log('send notify with "COMMBAD" to adapter ...'); + sendTo('nut.0', 'notify', {notifytype: 'COMMBAD', upsname: 'nutName@127.0.0.1'}); + setTimeout(function() { + states.getState('nut.0.status.last_notify', function (err, state) { + if (err) console.error(err); + expect(state).to.exist; + if (!state) { + console.error('state "status.last_notify" not set'); + } + else { + console.log('check status.last_notify ... ' + state.val); + } + expect(state.val).to.be.equal('COMMBAD'); + states.getState('nut.0.status.severity', function (err, state) { + if (err) console.error(err); + expect(state).to.exist; + if (!state) { + console.error('state "status.severity" not set'); + } + else { + console.log('check status.severity ... ' + state.val); + } + expect(state.val).to.exist; + expect(state.val).to.be.equal(4); + done(); + }); + }); + }, 2000); + }); + + after('Test ' + adapterShortName + ' adapter: Stop js-controller', function (done) { + this.timeout(10000); + + setup.stopController(function (normalTerminated) { + console.log('Adapter normal terminated: ' + normalTerminated); + done(); + }); + }); +}); diff --git a/test/testPackageFiles.js b/test/testPackageFiles.js new file mode 100644 index 0000000..c600a60 --- /dev/null +++ b/test/testPackageFiles.js @@ -0,0 +1,91 @@ +/* jshint -W097 */ +/* jshint strict:false */ +/* jslint node: true */ +/* jshint expr: true */ +var expect = require('chai').expect; +var fs = require('fs'); + +describe('Test package.json and io-package.json', function() { + it('Test package files', function (done) { + console.log(); + + var fileContentIOPackage = fs.readFileSync(__dirname + '/../io-package.json', 'utf8'); + var ioPackage = JSON.parse(fileContentIOPackage); + + var fileContentNPMPackage = fs.readFileSync(__dirname + '/../package.json', 'utf8'); + var npmPackage = JSON.parse(fileContentNPMPackage); + + expect(ioPackage).to.be.an('object'); + expect(npmPackage).to.be.an('object'); + + expect(ioPackage.common.version, 'ERROR: Version number in io-package.json needs to exist').to.exist; + expect(npmPackage.version, 'ERROR: Version number in package.json needs to exist').to.exist; + + expect(ioPackage.common.version, 'ERROR: Version numbers in package.json and io-package.json needs to match').to.be.equal(npmPackage.version); + + if (!ioPackage.common.news || !ioPackage.common.news[ioPackage.common.version]) { + console.log('WARNING: No news entry for current version exists in io-package.json, no rollback in Admin possible!'); + console.log(); + } + + expect(npmPackage.author, 'ERROR: Author in package.json needs to exist').to.exist; + expect(ioPackage.common.authors, 'ERROR: Authors in io-package.json needs to exist').to.exist; + + if (ioPackage.common.name.indexOf('template') !== 0) { + if (Array.isArray(ioPackage.common.authors)) { + expect(ioPackage.common.authors.length, 'ERROR: Author in io-package.json needs to be set').to.not.be.equal(0); + if (ioPackage.common.authors.length === 1) { + expect(ioPackage.common.authors[0], 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name '); + } + } + else { + expect(ioPackage.common.authors, 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name '); + } + } + else { + console.log('WARNING: Testing for set authors field in io-package skipped because template adapter'); + console.log(); + } + expect(fs.existsSync(__dirname + '/../README.md'), 'ERROR: README.md needs to exist! Please create one with description, detail information and changelog. English is mandatory.').to.be.true; + if (!ioPackage.common.titleLang || typeof ioPackage.common.titleLang !== 'object') { + console.log('WARNING: titleLang is not existing in io-package.json. Please add'); + console.log(); + } + if ( + ioPackage.common.title.indexOf('iobroker') !== -1 || + ioPackage.common.title.indexOf('ioBroker') !== -1 || + ioPackage.common.title.indexOf('adapter') !== -1 || + ioPackage.common.title.indexOf('Adapter') !== -1 + ) { + console.log('WARNING: title contains Adapter or ioBroker. It is clear anyway, that it is adapter for ioBroker.'); + console.log(); + } + + if (ioPackage.common.name.indexOf('vis-') !== 0) { + if (!ioPackage.common.materialize || !fs.existsSync(__dirname + '/../admin/index_m.html') || !fs.existsSync(__dirname + '/../gulpfile.js')) { + console.log('WARNING: Admin3 support is missing! Please add it'); + console.log(); + } + if (ioPackage.common.materialize) { + expect(fs.existsSync(__dirname + '/../admin/index_m.html'), 'Admin3 support is enabled in io-package.json, but index_m.html is missing!').to.be.true; + } + } + + var licenseFileExists = fs.existsSync(__dirname + '/../LICENSE'); + var fileContentReadme = fs.readFileSync(__dirname + '/../README.md', 'utf8'); + if (fileContentReadme.indexOf('## Changelog') === -1) { + console.log('Warning: The README.md should have a section ## Changelog'); + console.log(); + } + expect((licenseFileExists || fileContentReadme.indexOf('## License') !== -1), 'A LICENSE must exist as LICENSE file or as part of the README.md').to.be.true; + if (!licenseFileExists) { + console.log('Warning: The License should also exist as LICENSE file'); + console.log(); + } + if (fileContentReadme.indexOf('## License') === -1) { + console.log('Warning: The README.md should also have a section ## License to be shown in Admin3'); + console.log(); + } + done(); + }); +}); diff --git a/test/test_upslist.js b/test/test_upslist.js new file mode 100644 index 0000000..d5f3f8e --- /dev/null +++ b/test/test_upslist.js @@ -0,0 +1,27 @@ +if (process.argv.length<3) { + console.log('Call: test_upsvars.js '); + process.exit(); +} + +var Nut = require('node-nut'); + +//oNut = new Nut(3493, 'localhost'); +oNut = new Nut(process.argv[3], process.argv[2]); + +oNut.on('error', function(err) { + console.log('There was an error: ' + err); +}); + +oNut.on('close', function() { + console.log('Connection closed.'); +}); + +oNut.on('ready', function() { + self = this; + this.GetUPSList(function(upslist) { + console.log(upslist); + self.close(); + }); +}); + +oNut.start(); diff --git a/test/test_upsvars.js b/test/test_upsvars.js new file mode 100644 index 0000000..2bd9def --- /dev/null +++ b/test/test_upsvars.js @@ -0,0 +1,27 @@ +if (process.argv.length<3) { + console.log('Call: test_upsvars.js '); + process.exit(); +} + +var Nut = require('node-nut'); + +//oNut = new Nut(3493, 'localhost'); +oNut = new Nut(process.argv[3], process.argv[2]); + +oNut.on('error', function(err) { + console.log('There was an error: ' + err); +}); + +oNut.on('close', function() { + console.log('Connection closed.'); +}); + +oNut.on('ready', function() { + self = this; + this.GetUPSVars(process.argv[4],function(varlist) { + console.log(varlist); + self.close(); + }); +}); + +oNut.start();