/** * * 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}); }