thing-it-device-temperature-alert
Version:
[thing-it-node] Device Plugin listening to SNMP traps.
458 lines (399 loc) • 16.3 kB
JavaScript
module.exports = {
metadata: {
family: "measurementUnit",
plugin: "measurementUnit",
label: "temperature@lert © Measurement Unit",
manufacturer: "Temperature@lert",
tangible: false,
discoverable: true,
state: [{
id: "readingDateTime",
label: "Reading Date Time",
type: {id: "string"}
}, {
id: "tempUnits",
label: "Temp Units",
type: {id: "string"}
}, {
id: "form",
label: "Form",
type: {id: "string"}
}],
configuration: [{
id: "host",
label: "Host",
type: {id: "string"}
}, {
id: "deviceName",
label: "Device Name",
type: {id: "string"}
}, {
id: "interval",
label: "Interval",
type: {id: "integer"}
}, {
id: "userDefined01",
label: "User Defined 01",
type: {id: "string"}
}, {
id: "userDefined02",
label: "User Defined 02",
type: {id: "string"}
}],
actorTypes: [],
sensorTypes: [],
services: []
},
create: function () {
return new MeasurementUnit();
},
discovery: function (options) {
var discovery = new MeasurementUnitDiscovery();
discovery.options = options;
return discovery;
}
};
var q = require('q');
var xml2js;
var request;
var https;
var sensorLibrary;
function pollUnitState(host, mac, callback) {
this.logDebug("Polling unit state for unit located at " + host);
var url = "http://" + host + "/xmlfeed.rb";
if (!request) {
request = require('request');
}
request.get({
url: url
}, function (error, response, body) {
if (error) {
this.logError("Error communicating to measurement unit.", error, body);
callback(error, null);
}
else {
if (!xml2js) {
xml2js = require('xml2js');
}
xml2js.parseString(body, function (error, result) {
if (error) {
this.logError("Parser error processing response from measurement unit.", error, result);
callback(error, null);
} else {
readUnitState.call(this, host, mac, result, callback);
}
}.bind(this));
}
}.bind(this));
}
function readUnitState(host, mac, result, callback) {
try {
var unitState = {
host: host,
mac: mac,
deviceName: result.currentConditions.deviceName[0],
readingDateTime: result.currentConditions.readingDateTime[0],
tempUnits: result.currentConditions.tempUnits[0],
form: result.currentConditions.form[0],
ports: []
};
for (var n in result.currentConditions.ports[0].port) {
try {
var port = result.currentConditions.ports[0].port[n];
unitState.ports[parseInt(port.$.number)] = {
number: parseInt(port.$.number),
name: port.$.name,
type: port.condition[0].$.type,
currentReading: parseFloat(port.condition[0].currentReading[0]),
highLimit: parseInt(port.condition[0].highLimit[0]),
lowLimit: parseInt(port.condition[0].lowLimit[0]),
alarmStatus: Boolean(port.condition[0].alarmStatus[0] != "0"),
prevAlarmStatus: Boolean(port.condition[0].prevAlarmStatus[0] != "0"),
};
} catch (e) {
this.logError("Error reading port data for port " + n + ". Continuing reading " +
"remaining ports.", e, unitState, result);
}
}
this.logDebug("unitState", unitState);
callback(null, unitState);
} catch (e) {
this.logError("Error reading data in response from measurement unit.", e, unitState, result);
callback(e, unitState);
}
}
function MeasurementUnitDiscovery() {
var discoveryInterval;
var vendors;
MeasurementUnitDiscovery.prototype.start = function () {
vendors = {};
if (!this.node.isSimulated()) {
this.logLevel = "info";
this.scanForUnits();
discoveryInterval = setInterval(function () {
this.scanForUnits();
}.bind(this), 60000);
}
}
MeasurementUnitDiscovery.prototype.stop = function () {
if (discoveryInterval !== undefined && discoveryInterval) {
clearInterval(discoveryInterval);
}
}
MeasurementUnitDiscovery.prototype.scanForUnits = function () {
this.scanLocalAreaNetworkHosts(function (error, ip, mac) {
if (error) {
this.logError(error);
} else if (ip && !mac) {
this.logDebug("Ignoring host with unknown MAC address.");
} else {
this.getVendorForMac(mac, function (err, vendor) {
if (err) {
this.logError(ip, err);
} else if (vendor) {
if ((-1 < vendor.indexOf("ALFA, INC.")) ||
(-1 < vendor.indexOf("Wilibox Deliberant Group LLC"))) {
this.logDebug("Vendor match found for host "
+ ip + ": \"" + vendor + "\"");
this.testConnection(ip, mac);
}
else {
this.logDebug("Ignoring host " + ip + " with vendor " + vendor + ".");
}
} else {
this.logDebug("No vendor found for IP " + ip + "; ignoring host.");
}
}.bind(this));
}
}.bind(this));
}
/**
* Searches the ARP entries for MAC addresses in the same subnet as the computer the code is running on.
*
* @param callback Invokes the callback for each identified MAC address. First parameter is an error,
* second the ip address, third the host.
*/
MeasurementUnitDiscovery.prototype.scanLocalAreaNetworkHosts = function (callback) {
var arp = require('node-arp');
var network = require('network');
network.get_active_interface(function (err, obj) {
if (err) {
this.logError(err);
callback(err, false, false);
} else {
var myIp = obj.ip_address;
this.logDebug("My IP address determined as " + myIp);
var subnet = myIp.substring(0, myIp.lastIndexOf(".") + 1);
this.logDebug("Scanning for hosts in IP range " + subnet + "1 to " + subnet + "254.");
for (var i = 1; i < 255; i++) {
(function () {
var currentIP = subnet + i;
if (currentIP != myIp) {
arp.getMAC(currentIP, function (err, mac) {
if (err) {
this.logError(currentIP, err);
} else {
if (mac !== undefined && mac && ("(incomplete)" != mac) && ("eth0" != mac)) {
this.logDebug ("MAC address found for IP " + currentIP + ": " + mac);
callback(false, currentIP, mac);
}
}
}.bind(this));
}
}.bind(this))();
}
}
}.bind(this))
}
/**
* Retrieves the vendor name for a MAC address.
* @param mac The MAC address to look up.
* @param callback Invoked with return values with first parameter an error, second the vendor name.
*/
MeasurementUnitDiscovery.prototype.getVendorForMac = function (mac, callback) {
this.logDebug("Looking up vendor for MAC address " + mac + ".");
var cashedVendor = vendors[mac];
if (cashedVendor || ("" == cashedVendor)) {
this.logDebug("Retrieved vendor '" + cashedVendor + "' from vendor cache.");
if ("" !== cashedVendor) {
callback(false, cashedVendor);
} else {
callback(false, false);
}
} else {
var data = "";
var options = {
hostname: 'www.macvendorlookup.com',
port: 443,
path: '/api/v2/' + mac,
method: 'GET',
rejectUnauthorized: false
};
if (!https) {
https = require('https');
}
var req = https.request(options, function (res) {
res.on('data', function (chunk) {
data += chunk;
}.bind(this));
res.on('end', function () {
var vendor;
if ("" !== data) {
var vendorLookup = JSON.parse(data);
if ((vendorLookup !== undefined && vendorLookup) &&
(vendorLookup[0] !== undefined && vendorLookup[0]) &&
(vendorLookup[0].company !== undefined && vendorLookup[0].company)) {
vendor = vendorLookup[0].company;
this.logDebug("Vendor for MAC address " + mac + " identified as " + vendor + ".");
vendors[mac] = vendor;
callback(false, vendor);
}
} else {
this.logDebug("Vendor for MAC address " + mac + " not identified.");
vendors[mac] = "";
callback(false, false);
}
}.bind(this));
}.bind(this));
req.end();
req.on('error', function (e) {
this.logError("MAC based vendor lookup failed for MAC address " + mac + ".", e);
callback(e, false);
}.bind(this));
}
}
MeasurementUnitDiscovery.prototype.testConnection = function (ip, mac) {
pollUnitState.call(this, ip, mac, function (error, unitStatus) {
if (error) {
this.logError("Skipping host " + ip + " in Temperature@lert auto discovery due to an error.", error);
} else {
try {
var discoveredDevice = new MeasurementUnit();
discoveredDevice.configuration = {};
discoveredDevice.configuration.host = unitStatus.host;
discoveredDevice.configuration.deviceName = unitStatus.deviceName;
discoveredDevice.configuration.interval = 10000;
discoveredDevice.uuid = unitStatus.mac;
discoveredDevice.actors = [];
var currentPort;
for (var n in unitStatus.ports) {
currentPort = unitStatus.ports[n];
if (currentPort.type === "temperature") {
if (!sensorLibrary) {
sensorLibrary = require("./default-units/temperatureSensor")
}
var temperatureSensor = new sensorLibrary.create();
temperatureSensor.uuiid = discoveredDevice.configuration.uuid + "port" + currentPort.number;
temperatureSensor.id = "port" + currentPort.number;
temperatureSensor.label = currentPort.name;
temperatureSensor.type = "TemperatureSensor";
temperatureSensor.configuration = {
id: "" + currentPort.number,
portNumber: currentPort.number,
name: currentPort.name,
type: currentPort.type,
};
temperatureSensor.state = {};
discoveredDevice.actors.push(temperatureSensor);
}
}
this.logDebug("Discovered Temperature@lert unit with name " +
discoveredDevice.configuration.deviceName + " at IP address " +
discoveredDevice.configuration.host + " with " + discoveredDevice.actors.length + " ports.");
this.advertiseDevice(discoveredDevice);
} catch (e) {
this.logError("Error creating Temperature@lert device.", e, unitStatus, discoveredDevice, e.stack);
}
}
}.bind(this));
}
}
/**
*
*/
function MeasurementUnit() {
/**
*
*/
MeasurementUnit.prototype.start = function () {
var deferred = q.defer();
this.logDebug("Unit started");
if ((this.configuration.interval > 1000) && (this.configuration.interval < 60000)) {
this.logDebug("Applying interval of " + this.configuration.interval + " as configured.");
} else {
this.logDebug("Configured interval of " + this.configuration.interval + " out of range; changing " +
"to 10000 (10s).");
this.configuration.interval = 10000;
}
this.intervals = [];
this.simulationIntervals = [];
this.state = {temperature1: 0, temperature2: 0, temperature3: 0, temperature4: 0};
if (this.isSimulated()) {
this.simulationIntervals.push(setInterval(function () {
this.state.temperature1 = new Date().getTime() % 4;
this.state.temperature2 = new Date().getTime() % 3;
this.state.temperature3 = new Date().getTime() % 2;
this.state.temperature4 = new Date().getTime() % 5;
this.publishStateChange();
}.bind(this), this.configuration.interval));
deferred.resolve();
} else {
pollUnitState.call(this, this.configuration.host, this.configuration.mac, function (error, unitState) {
this.updateSensors(unitState);
}.bind(this));
this.intervals.push(setInterval(function () {
pollUnitState.call(this, this.configuration.host, this.configuration.mac, function (error, unitState) {
this.updateSensors(unitState);
}.bind(this))
}.bind(this), this.configuration.interval));
deferred.resolve();
}
return deferred.promise;
};
/**
*
*/
MeasurementUnit.prototype.stop = function () {
for (var interval in this.intervals) {
clearInterval(interval);
}
for (var interval in this.simulationIntervals) {
clearInterval(interval);
}
};
/**
*
* @param unitState
*/
MeasurementUnit.prototype.updateSensors = function (unitState) {
try {
var currentPort;
for (var n in this.actors) {
currentPort = unitState.ports[this.actors[n].configuration.portNumber];
if (currentPort) {
this.actors[n].updateReading(currentPort);
}
}
} catch (e) {
this.logError("Error reading temperature.", e);
}
}
/**
*
*/
MeasurementUnit.prototype.url = function (path) {
return "http://" + this.configuration.host + "/xmlfeed.rb";
};
/**
*
*/
MeasurementUnit.prototype.setState = function (state) {
this.state = state;
};
/**
*
*/
MeasurementUnit.prototype.getState = function () {
return this.state;
};
}