You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

379 lines
13 KiB

6 years ago
/**
*
* 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 cant 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});
}