/**
* DS18B20 temperature sensor module.
* @module mc-tempsensor
* @see module:mc-tempsensor
* @author Oscar Djupfeldt
*/
var fs = require('fs');
var path = require('path');
var winston = require('winston');
var sensorDirectory = '/sys/bus/w1/devices/';
var logDirectory = './logs';
var converters = [];
var input;
var settings = {defaultPath: true, installKernelMod: false};
if (!fs.existsSync(logDirectory)) {
fs.mkdir(logDirectory);
}
var logger = new (winston.Logger)({
transports: [
new (winston.transports.Console)(),
new (winston.transports.File)({ name:'tempSensor', filename: logDirectory + '/tempSensor.log' })
]
});
var modprobe = function(error, stdout, stderr) {
if (error) {
logger.error('MODPROB ERROR: ', error);
logger.error('MODPROB STDERR: ', stderr);
}
};
/**
* Reads the raw data from the sensors.
*
* @function readTemp
* @param {function} callback Function to call when all sensor data has bean read or failed
* @throws {Error} 'Callback function required' Thrown if no callback function is provided
*/
var readTemp = function(callback) {
if (!callback || typeof callback !== 'function') {
throw new Error('Callback function required');
}
var response = [];
for (var i in input) {
try {
var path;
if (settings.defaultPath) {
fs.accessSync(sensorDirectory + input[i] + '/w1_slave');
path = sensorDirectory + input[i] + '/w1_slave';
} else {
fs.accessSync(input[i]);
path = input[i];
}
var data = fs.readFileSync(path, 'utf8');
response.push(data);
} catch (Error) {
response.push({error: Error});
}
}
callback(undefined, response);
};
/**
* Parses the raw data from the sensors.
*
* @function parseTemp
* @param {array} input An array containing the data to parse
* @returns {array} An array with parsed data
*/
var parseTemp = function(input) {
var response = [];
for (var i in input) {
var data = input[i];
if (data.error) {
response.push({error: data.error});
continue;
}
var crc = data.match(/(crc=)[a-z0-9]*/g)[0];
crc = crc.split('=')[1];
var available = data.match(/([A-Z])\w+/g)[0];
var temperature = 'n/a';
if (available === 'YES') {
if (data.match(/(t=)[0-9]{5}/g)) {
temperature = data.match(/(t=)[0-9]{5}/g)[0];
} else if (data.match(/(t=)[0-9]{4}/g)) {
temperature = data.match(/(t=)[0-9]{4}/g)[0];
} else if (data.match(/(t=)[0-9]{3}/g)) {
temperature = data.match(/(t=)[0-9]{3}/g)[0];
}
temperature = temperature.split('=')[1];
temperature = parseInt(temperature);
}
var temp = {
crc: crc,
available: available,
temperature: {
raw: temperature
},
time: Date.now()
};
for (var name in converters) {
temp.temperature[name] = converters[name](temperature);
}
response.push(temp);
}
return response;
};
/**
* Adds a temperature scale converter function. Conversion functions should accept one parameter and return a value
* representing the converted temperature. The input to the function is the raw temperature from the DS18B20 sensor,
* an integer value that is 1000 * X, where X is the temperature in Celcius with four decimals.
*
* @function readAndParse
* @param {function} callback Function that is called when readAndParse finishes
* @throws {Error} 'Callback function required' Thrown if no callback function is provided
*/
var readAndParse = function(callback) {
if (!callback || typeof callback !== 'function') {
throw new Error('Callback function required');
}
readTemp(function (err, data) {
if (!err) {
var temp = parseTemp(data);
callback(undefined, temp);
} else {
err.tempSensorMessage = 'Error when reading temperature';
logger.error(err.tempSensorMessage, err);
callback(err);
}
});
};
var getDirectories = function(srcpath) {
return fs.readdirSync(srcpath).filter(file => fs.lstatSync(path.join(srcpath, file)).isDirectory());
};
/**
* Adds a temperature scale converter function. Conversion functions should accept one parameter and return a value
* representing the converted temperature. The input to the function is the raw temperature from the DS18B20 sensor,
* an integer value that is 1000 * X, where X is the temperature in Celcius with four decimals.
*
* @function addConverter
* @param {string} name Name of the converter, this is used to identify the output of {@link parseTemp}
* @param {function} converterFunc Function implementing the conversion
* @throws {Error} 'Name must be provided' Thrown if no name is provided
* @throws {Error} 'Converter function was not a function' Thrown if converterFunc is not a function
*/
var addConverter = function(name, converterFunc) {
if (!name) {
throw new Error('Name must be provided');
}
if (typeof converterFunc === 'function') {
converters[name] = converterFunc;
} else {
throw new Error('Converter function was not a function');
}
};
/**
* Initializes the temp sensor. The module can use one or more available DS18B20 sensors. Depending on the type of
* parameter one, different behaviors are achieved. Passing a string is equivalent to passing a {string} {array} with one
* element. Passing {undefined} is equivalent to passing a {string} {array} with all installed sensors.
*
* @function init
* @param {array} sensors Sensors to use, can be either a string, an array of strings or undefined. If settings.defaultPath
* is set to true, the values are the ID of the sensor, otherwise the full path to the sensor output.
* @param {object} newSettings object representing changes to settings
* @param {boolean} newSettings.defaultPath Use default path or not. Defaults to true, prepends '/sys/bus/w1/devices/'
* and appends '/w1_slave' to the sensor. If set to false, the values of sensor is left untouched.
* @param {boolean} newSettings.installKernelMod Defaults to false. If set to true, the kernel modules w1-gpio and
* w1-therm are installed when init is called.
* @param {function} callback Function to be called when init is finished
*/
var init = function(sensors, newSettings, callback) {
if (newSettings) {
settings = newSettings;
}
if (!sensors) {
settings.defaultPath = true;
input = getDirectories(sensorDirectory);
} else if (typeof sensors === 'string') {
input = [sensors];
} else {
input = sensors;
}
if (settings.installKernelMod) {
exec('modprobe w1-gpio', modprobe);
exec('modprobe w1-therm', modprobe);
}
if (callback) {
callback(undefined);
}
};
var convertToCelcius = function(raw) {
return raw/1000;
};
var convertToFahrenheit = function(raw) {
var c = convertToCelcius(raw);
return c * (9/5) + 32;
};
addConverter('celcius', convertToCelcius);
addConverter('fahrenheit', convertToFahrenheit);
module.exports = {
init: init,
readTemp: readTemp,
parseTemp: parseTemp,
readAndParse: readAndParse,
addConverter: addConverter,
logDirectory: logDirectory
};