ljmmm-parse
Version:
Library for parsing LabJack Modbus Map Markup documents.
420 lines (368 loc) • 14.1 kB
JavaScript
/**
* Micro-library to read LabJack MODBUS Map Markup language documents.
*
* Micro-library that supports the reading of documents or fields within larger
* documents that contain LJMMM strings. These strings likely define a series
* of MODBUS map registers (http://www.modbus.org/). See
* https://bitbucket.org/labjack/ljm_constants for the LJMMM specification.
*
* @author Sam Pottinger (samnsparky, http://gleap.org)
* @maintainer Chris Johnson (chrisjohn404, https://github.com/chrisJohn404)
* @license MIT
**/
var async = require('async');
var extend = require('./extend.js').extend;
var sprintf = require('sprintf-js');
// var util = require('util'); // Consider switching to node's util.format function.
// Sizes in number of MODBUS registers (2 bytes each).
var DATA_TYPE_SIZES = {
UINT64: 4,
INT32: 2,
STRING: 2,
UINT16: 1,
UINT32: 2,
FLOAT32: 2
};
var LUA_INTEGER_TYPES = {
UINT16: 0,
UINT32: 1,
INT32: 2,
FLOAT32: 3,
STRING: 98,
BYTE: 99,
UINT64: -1,
}
// TODO: This is slow and lazy would have been preferred. However, need an
// alternative for those pesky synchronous options.
/**
* Make a numerical range synchronously and return as an array.
*
* @param {number} startNum The first number in the range.
* @param {number} endNum The last number to include in the range non-inclusive.
* In other words, the last number to include in the range + 1.
* @param {number} increment The number value to increase by in each step or,
* in other words, the numerical difference between subsequent elements in
* the range.
* @return {array} Resulting range as an array of number.
**/
function makeRange (startNum, endNum, increment) {
var retVal = [];
for (var i=startNum; i<endNum; i+=increment) {
retVal.push(i);
}
return retVal;
}
/**
* Get the size of the data of a given type in registers.
*
* Determine the size of the data of a given data type as indicated by a type
* name. The size will be reported in MODBUS registers, each of which is two
* bytes. Unknown data types are returned as -1 registers.
*
* @param {String} typeName The name of the type to get the data type size for.
* @return {Number} The number of registers that values of the given data type
* take up. -1 is returned if the type could not be found.
**/
function getTypeRegSize(typeName)
{
if(DATA_TYPE_SIZES[typeName] === undefined)
return -1;
return DATA_TYPE_SIZES[typeName];
}
/**
* Get the Lua Integer type constant for a given data type.
*
* @param {String} typeName The name of the type to get the Lua Int. Type for.
* @return {Number} The Lua type number that relates to the given type. -1 is
* returned if the type could not be found.
**/
function getLuaTypeInt(typeName) {
if(LUA_INTEGER_TYPES[typeName] === undefined) {
return -1;
} else {
return LUA_INTEGER_TYPES[typeName];
}
}
/**
* Enumerates / interprets an LJMMM field.
*
* @param {String} name The field to interpret as an LJMMM string.
* @param {function} onError The function to call if an error is encountered
* during expansion. Should take string argument describing the error. If
* not provided, the corresponding error will be thrown.
* @param {function} onSuccess The function to call after the LJMMM field has
* been interpreted. The callback should take a single argument which
* will be an Array of String or, in other words, the expansion / result of
* the interpretation of the LJMMM field. If not provided, that argument
* will be returned from this function and this acts synchronously.
**/
exports.expandLJMMMName = function(name, onError, onSuccess)
{
var ljmmmRegex = /^(.*)\#\((\d+)\:(\d+)\:?(\d+)?\)(.*)$/;
var values = name.match(ljmmmRegex);
if(values === null) {
if (onSuccess === undefined) {
return [name];
} else {
onSuccess([name]);
}
}
var before = values[1];
var startNum = Number(values[2]);
var endNum = Number(values[3]);
var increment = values[4];
var after = values[5];
var regNums;
if (increment === undefined) {
regNums = makeRange(startNum, endNum+1, 1);
} else {
increment = Number(increment);
regNums = makeRange(startNum, endNum+1, increment);
}
var fullyQualifiedNames;
fullyQualifiedNames = regNums.map(function(regNum){
return sprintf.sprintf('%s%d%s', before, regNum, after);
// return util.format('%s%d%s', before, regNum, after);
});
if (onSuccess !== undefined)
onSuccess(fullyQualifiedNames);
return fullyQualifiedNames;
};
/**
* Expand an entry's name and alname fields as LJMMM, enumerating appropriately.
*
* @param {Object} entry An Object containing information about a register or
* set of registers.
* @param {function} onError The function to call if an error is encountered
* during expansion. Should take a string argument describing the error.
* @param {function} onSuccess The function to call after enumerating. Will be
* given an Array of Array of String that results from interpreting the
* name of the provided entry as an LJMMM field, enumerating and creating
* the appropriate entries when interpreting that field. Each sub-Array is
* the result of expanding an original name.
**/
exports.expandLJMMMNameAndAltName = function (entry, onError, onSuccess)
{
var originalNames = entry.altnames;
if (originalNames === undefined)
originalNames = [];
originalNames.push(entry.name);
async.map(
originalNames,
function (name, callback) {
exports.expandLJMMMName(
name,
function (err) { callback(err, null); },
function (names) { callback(null, names); }
);
},
function (err, results) {
if (err) {
onError(err);
return;
}
onSuccess(results);
}
);
};
/**
* Interpret an entry's name field as LJMMM, enumerating as appropriate.
*
* @param {Object} entry An Object containing information about a register or
* set of registers.
* @param {function} onError The function to call if an error is encountered
* during expansion. Should take a string argument describing the error.
* @param {function} onSuccess The function to call after enumerating. Will be
* given an Array of Object that results from interpreting the name
* of the provided entry as an LJMMM field, enumerating and creating the
* appropriate entries when interpreting that field.
**/
exports.expandLJMMMEntry = function(entry, onError, onSuccess)
{
exports.expandLJMMMNameAndAltName(
entry,
onError,
function (nameSets) {
var address;
var numNames;
var names;
var expandedEntries = [];
var regTypeSize = getTypeRegSize(entry.type);
var luaType = getLuaTypeInt(entry.type);
var numNameSets = nameSets.length;
for (var i=0; i<numNameSets; i++) {
names = nameSets[i];
numNames = names.length;
address = entry.address;
for (var j=0; j<numNames; j++) {
var name = names[j];
var newEntry = extend({}, entry);
newEntry.name = name;
newEntry.address = address;
newEntry.group = entry.name;
newEntry.luaTypeInt = luaType;
address += regTypeSize;
delete newEntry.altnames;
expandedEntries.push(newEntry);
}
}
onSuccess(expandedEntries);
}
);
};
/**
* Interpret an entry's name field as LJMMM synchronously.
*
* @param {Object} entry An Object containing information about a register or
* set of registers.
* @return {Array} An Array of Object that results from interpreting the name
* of the provided entry as an LJMMM field, enumerating and creating the
* appropriate entries when interpreting that field.
**/
exports.expandLJMMMEntrySync = function(entry)
{
var names;
var numNameSets;
var numAltNames;
var nameSets = [];
var expandedEntries = [];
var address = entry.address;
var regTypeSize = getTypeRegSize(entry.type);
var luaType = getLuaTypeInt(entry.type);
if (entry.altnames !== undefined)
numAltNames = entry.altnames.length
nameSets.push(exports.expandLJMMMName(entry.name));
for (var i=0; i<numAltNames; i++) {
nameSets.push(exports.expandLJMMMName(entry.altnames[i]));
}
numNameSets = nameSets.length;
for (var i=0; i<numNameSets; i++) {
names = nameSets[i];
numNames = names.length;
address = entry.address;
var numNames = names.length;
for (var j=0; j<numNames; j++) {
var name = names[j];
var newEntry = extend({}, entry);
newEntry.name = name;
newEntry.address = address;
newEntry.group = entry.name;
newEntry.luaTypeInt = luaType;
address += regTypeSize;
delete newEntry.altnames;
expandedEntries.push(newEntry);
}
}
return expandedEntries;
};
/**
* Interpret an entry's name field as LJMMM synchronously and don't expand the
* altname. Simply leave it as an attribute.
*
* @param {Object} entry An Object containing information about a register or
* set of registers.
* @return {Array} An Array of Object that results from interpreting the name
* of the provided entry as an LJMMM field, enumerating and creating the
* appropriate entries when interpreting that field.
**/
exports.expandPrimaryLJMMMEntrySync = function(entry)
{
var names;
var numNameSets;
var nameSets = [];
var expandedEntries = [];
var address = entry.address;
var regTypeSize = getTypeRegSize(entry.type);
var luaType = getLuaTypeInt(entry.type);
nameSets.push(exports.expandLJMMMName(entry.name));
numNameSets = nameSets.length;
for (var i=0; i<numNameSets; i++) {
names = nameSets[i];
numNames = names.length;
address = entry.address;
var numNames = names.length;
for (var j=0; j<numNames; j++) {
var name = names[j];
var newEntry = extend({}, entry);
newEntry.name = name;
newEntry.address = address;
newEntry.group = entry.name;
newEntry.luaTypeInt = luaType;
address += regTypeSize;
expandedEntries.push(newEntry);
}
}
return expandedEntries;
};
/**
* Interpret an the names of entries field as LJMMM, enumerating as appropriate.
*
* @param {array} entry An array of objects containing information about a
* register or set of registers.
* @param {function} onError The function to call if an error is encountered
* during expansion. Should take a string argument describing the error.
* @param {function} onSuccess The function to call after enumerating. Will be
* given an Array of Object that results from interpreting the name
* of the provided entry as an LJMMM field, enumerating and creating the
* appropriate entries when interpreting that field.
**/
exports.expandLJMMMEntries = function(entries, onError, onSuccess)
{
var retEntries = [];
async.each(
entries,
function (entry, callback) {
exports.expandLJMMMEntry(entry, callback, function(newEntries) {
retEntries.push.apply(retEntries, newEntries);
callback();
});
},
function (err) {
if(err !== null && err !== undefined)
onError(err);
else
onSuccess(retEntries);
}
);
};
/**
* Interpret an the names of entries field as LJMMM synchronously.
*
* @param {array} entry An array of objects containing information about a
* register or set of registers.
* @return {Array} An Array of Object that results from interpreting the name
* of the provided entry as an LJMMM field, enumerating and creating the
* appropriate entries when interpreting that field.
**/
exports.expandLJMMMEntriesSync = function(entries, onError, onSuccess)
{
var retEntries = [];
var numEntries = entries.length;
for(var i=0; i<numEntries; i++)
{
var newEntries = exports.expandLJMMMEntrySync(entries[i]);
retEntries.push.apply(retEntries, newEntries);
}
return retEntries;
};
/**
* Interpret an the names of entries field as LJMMM synchronously. Only expand
* the primary entries, leave the altnames attribute as an attribute.
*
* @param {array} entry An array of objects containing information about a
* register or set of registers.
* @return {Array} An Array of Object that results from interpreting the name
* of the provided entry as an LJMMM field, enumerating and creating the
* appropriate entries when interpreting that field.
**/
exports.expandPrimaryLJMMMEntriesSync = function(entries, onError, onSuccess)
{
var retEntries = [];
var numEntries = entries.length;
for(var i=0; i<numEntries; i++)
{
var newEntries = exports.expandPrimaryLJMMMEntrySync(entries[i]);
retEntries.push.apply(retEntries, newEntries);
}
return retEntries;
};