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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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