UNPKG

labjack-nodejs

Version:

nodejs library for using the LabJackM library

1,578 lines (1,441 loc) 80.5 kB
/** * Wraps an LJM handle with all LabJack device functionality. * * @author Chris Johnson (chrisjohn404, LabJack Corp.) **/ const ref = require('ref-napi'); const util = require('util'); const driverLib = require('./driver_wrapper'); const ljnDriverLib = require('./driver'); const allocBuffer = require('allocate_buffer').allocBuffer; const jsonConstants = require('ljswitchboard-modbus_map'); const driver_const = require('ljswitchboard-ljm_driver_constants'); // var ARCH_INT_NUM_BYTES = 4; // var ARCH_DOUBLE_NUM_BYTES = 8; // var ARCH_POINTER_SIZE = { // 'ia32': 4, // 'x64': 8, // 'arm': 4 // }[process.arch] var ARCH_INT_NUM_BYTES = driver_const.ARCH_INT_NUM_BYTES; var ARCH_DOUBLE_NUM_BYTES = driver_const.ARCH_DOUBLE_NUM_BYTES; var ARCH_POINTER_SIZE = driver_const.ARCH_POINTER_SIZE; /** * Create a new DriverOperationError object. * * Create a new DriverOperationError which encapsulates the code of an error * encountered within driver code. This is an error thrown by LJM, not from * labjack-nodejs. * * @param {Number} code The raw / uinterpreted error code from LJM. **/ function DriverOperationError(code) { this.code = code; this.message = code; } util.inherits(DriverOperationError, Error); DriverOperationError.prototype.name = 'Driver Operation Error - device'; /** * Create a new DriverInterfaceError object. * * Create a new DriverInterfaceError which encapsulates the code of an error * encountered while communicating with the driver. This is an error thrown by * labjack-nodejs, not within driver code. * * @param {String/Number} description The integer error code or string * description of the error encountered. **/ function DriverInterfaceError(description) { this.description = description; this.message = description; } util.inherits(DriverInterfaceError, Error); DriverInterfaceError.prototype.name = 'Driver Interface Error - device'; function buildAsyncError(code, message, errFrame) { let errorInfo = ljm_mm.getErrorInfo(code); let error = { code: code, string: errorInfo.string, description: errorInfo.description, }; if(typeof(message) !== 'undefined') { this.message = message.toString(); } if(typeof(errFrame) !== 'undefined') { this.errFrame = errFrame; } return error; } // A numeric that keeps track of the number of devices that have been created; let numCreatedDevices = 0; exports.getNumCreatedDevices = function() { return numCreatedDevices; }; /** * Create a new LabJack device handle wrapper. * * @constructor for a LabJack device in .js */ exports.labjack = function (ljmOverride) { if(typeof(ljmOverride) !== 'undefined') { this.ljm = ljmOverride; } else { this.ljm = driverLib.getDriver(); } this.ljnd = new ljnDriverLib.ljmDriver(this.ljm); numCreatedDevices += 1; this.handle = null; this.deviceType = null; //Saves the Constants object this.constants = jsonConstants.getConstants(); this.errors = this.constants.errorsByName; this.isHandleValid = false; function wrapUserFunction(userFunction) { function persistentFunction(a, b, c, d) { try { if(self.isHandleValid) { if(userFunction) { userFunction(a, b, c, d); } else { console.error('function not defined', arguments); } } else { console.error('Preventing execution of callback when handle is not valid,', typeof(userFunction)); console.trace(); } } catch(err) { console.error('Error calling userFunction', err); } }; return persistentFunction; }; function wrapOpenCloseCallbacks(userFunction) { function persistentFunction(a, b, c, d) { if(userFunction) { userFunction(a, b, c, d); } else { console.error('function not defined', arguments); } }; return persistentFunction; }; //****************************************************************************** //************************* Opening A Device *********************************** //****************************************************************************** /** * Opens a new devDriverInterfaceErrorice if it isn't already connected. * * @param {Number/String} deviceType Optional integer or String parameter * from driver_const.js file defining which model of LabJack to open. * If not specified, defaults to "LJM_dtANY". * @param {Number/String} connectionType Optional integer or String * parameter from driver_const.js file defining how to connect to * the device (ex: USB, Ethernet, WiFi). If not specified, defaults to * "LJ_ctANY". * @param {String} identifier Optional parameter that allows selective * opening of a device based on a unique identifier (either string IP * address or integer serialNumber). If not specified, defaults to * "LJM_idANY". * @param {function} onError function Callback for error-case. * @param {function} onSuccess function Callback for success-case. * @throws {DriverInterfaceError} if there are any un-recoverable errors **/ this.open = function() { //Variables to save information to allowing for open(onError, onSuccess) let deviceType, connectionType, identifier, onError, onSuccess; //Determine how open() was used if(arguments.length == 2) { //If there are two args, aka open(onError, onSuccess) call let argA = typeof(arguments[0]); let argB = typeof(arguments[1]); //Make sure the first two arg's are onError and onSuccess if((argA == "function") && (argB == "function")) { deviceType = "LJM_dtANY"; connectionType = "LJM_ctANY"; identifier = "LJM_idANY"; onError = arguments[0];//move the onError function onSuccess = arguments[1];//move the onSuccess function } else { throw new DriverInterfaceError("Invalid Open Call"); } } else if(arguments.length == 5) { //Save the various input parameters deviceType = arguments[0]; connectionType = arguments[1]; identifier = arguments[2]; onError = arguments[3]; onSuccess = arguments[4]; } else { throw new DriverInterfaceError("Invalid Open Call"); } onError = wrapOpenCloseCallbacks(onError); onSuccess = wrapOpenCloseCallbacks(onSuccess); //Complete Asynchronous function-call, //Make sure we aren't already connected to a device if(self.handle === null) { //Create variables for the ffi call let refDeviceHandle = new ref.alloc(ref.types.int,1); let output; //Get the type's of the inputs let dtType = isNaN(deviceType); let ctType = isNaN(connectionType); let idType = isNaN(identifier); if(dtType) { dtType = "string"; } else { deviceType = parseInt(deviceType, 10); dtType = "number"; } if(ctType) { ctType = "string"; } else { connectionType = parseInt(connectionType, 10); ctType = "number"; } if(idType) { idType = "string"; } else { // Handle the serial number being parsed as a number identifier = identifier.toString(); idType = "string"; } //Function for handling the ffi callback function handleResponse(err, res) { if(err) { return onError('Weird Error open', err); } //Check for no errors if(res === 0) { //Save the handle & other information to the // device class self.handle = refDeviceHandle.readInt32LE(0); self.deviceType = deviceType; self.connectionType = connectionType; self.identifier = identifier; self.isHandleValid = true; return onSuccess(); } else { //Make sure that the handle, deviceType // & connectionType are still null self.handle = null; self.deviceType = null; self.connectionType = null; self.identifier = null; return onError(res); } }; //Determine which LJM function to call if((dtType=="number")&&(ctType=="number")&&(idType=="string")) { //call LJM_Open() using the ffi async call output = self.ljm.LJM_Open.async( deviceType, connectionType, identifier, refDeviceHandle, handleResponse ); } else if((dtType=="number")&&(ctType=="string")&&(idType=="string")) { // convert connectionType to a number connectionType = driver_const.connectionTypes[connectionType]; //call LJM_OpenS() using the ffi async call output = self.ljm.LJM_Open.async( deviceType, connectionType, identifier, refDeviceHandle, handleResponse ); } else if((dtType=="string")&&(ctType=="number")&&(idType=="string")) { // convert deviceType to a number deviceType = driver_const.deviceTypes[deviceType]; //call LJM_OpenS() using the ffi async call output = self.ljm.LJM_Open.async( deviceType, connectionType, identifier, refDeviceHandle, handleResponse ); } else if((dtType=="string")&&(ctType=="string")&&(idType=="string")) { //call LJM_OpenS() using the ffi async call output = self.ljm.LJM_OpenS.async( deviceType, connectionType, identifier, refDeviceHandle, handleResponse ); } else { //If there were no applicable LJM function calls, throw an error throw new DriverInterfaceError("Un-Handled Variable Types: "+dtType+ctType+idType+deviceType.toString()+connectionType.toString()+identifier.toString()); } } else { setImmediate(function() { onError(driver_const.LJME_DEVICE_ALREADY_OPEN); }); } }; /** * Opens a new devDriverInterfaceErrorice if it isn't already connected. * * @param {Number/String} deviceType Optional integer or String parameter * from driver_const.js file defining which model of LabJack to open. * If not specified, defaults to "LJM_dtANY". * @param {Number/String} connectionType Optional integer or String * parameter from driver_const.js file defining how to connect to * the device (ex: USB, Ethernet, WiFi). If not specified, defaults to * "LJ_ctANY". * @param {String} identifier Optional parameter that allows selective * opening of a device based on a unique identifier (either string IP * address or integer serialNumber). If not specified, defaults to * "LJM_idANY". * @return {number} 0 on success, LJM_ Error Number on error. * @throws {DriverInterfaceError} If there is a driver wrapper-error * @throws {DriverOperationError} If there is an LJM reported error **/ this.openSync = function(deviceType, connectionType, identifier) { //Determine how open() was used if(arguments.length === 0) { //If there are two args, aka open() call deviceType = "LJM_dtANY"; connectionType = "LJM_ctANY"; identifier = "LJM_idANY"; } else if(arguments.length == 3) { //Save the various input parameters deviceType = arguments[0]; connectionType = arguments[1]; identifier = arguments[2]; } else { throw new DriverInterfaceError("Invalid Open Call"); } //Complete Synchronous function-call, //Make sure we aren't already connected to a device if(self.handle === null) { //Create variables for the ffi call var refDeviceHandle = new ref.alloc(ref.types.int,1); var output; //Get the type's of the inputs var dtType = isNaN(deviceType); var ctType = isNaN(connectionType); var idType = isNaN(identifier); if(dtType) { dtType = "string"; } else { deviceType = parseInt(deviceType, 10); dtType = "number"; } if(ctType) { ctType = "string"; } else { connectionType = parseInt(connectionType, 10); ctType = "number"; } if(idType) { idType = "string"; } else { // Handle the serial number being parsed as a number identifier = identifier.toString(); idType = "string"; } //Determine which LJM function to call if((dtType=="number")&&(ctType=="number")&&(idType=="string")) { //call LJM_Open() using the ffi async call output = self.ljm.LJM_Open( deviceType, connectionType, identifier, refDeviceHandle ); } else if((dtType=="number")&&(ctType=="string")&&(idType=="string")) { // Convert connectionType to a number connectionType = driver_const.connectionTypes[connectionType]; //call LJM_OpenS() using the ffi async call output = self.ljm.LJM_Open( deviceType, connectionType, identifier, refDeviceHandle, handleResponse ); } else if((dtType=="string")&&(ctType=="number")&&(idType=="string")) { // convert deviceType to a number deviceType = driver_const.deviceTypes[deviceType]; //call LJM_OpenS() using the ffi async call output = self.ljm.LJM_Open( deviceType, connectionType, identifier, refDeviceHandle, handleResponse ); } else if((dtType=="string")&&(ctType=="string")&&(idType=="string")) { //call LJM_OpenS() using the ffi async call output = self.ljm.LJM_OpenS( deviceType, connectionType, identifier, refDeviceHandle ); } else { //If there were no applicable LJM function calls, throw an error throw new DriverInterfaceError("Un-Handled Variable Types: "+dtType+ctType+idType+deviceType.toString()+connectionType.toString()+identifier.toString()); } //Determine whether or not the Open call was successful if(output === 0) { //Save the handle & other information to the // device class self.handle = refDeviceHandle.readInt32LE(0); self.deviceType = deviceType; self.connectionType = connectionType; self.identifier = identifier; self.isHandleValid = true; return output; } else { //Make sure that the handle, deviceType // & connectionType are still null self.handle = null; self.deviceType = null; self.connectionType = null; self.identifier = null; //Report an error throw new DriverOperationError(output); return output; } } else { return driver_const.LJME_DEVICE_ALREADY_OPEN; } }; //****************************************************************************** //****************** Communicating With A Device ****************************** //****************************************************************************** /** * Retrieves device information about this device. * * Retrieves device metadata information about the device whose handle this * object encapsulates. Returns a DeviceInfo object with the following * structure: * * { * deviceType {number} A device model type corresponding to a constant * in LabJackM.h. * connectionType {number} A constant from LabJackM.h that defines * connection medium the driver is using to communicate with the * device. * serialNumber {number} The device unique integer serial number. * ipAddress {string} The IP address the device is located at. Defaults * to all zeros if not connected by the network (0.0.0.0). * port {number} The port at which the device is connected. * maxBytesPerMB {number} The maximum payload in bytes that the * driver can send to the device over the current connection * medium. * } * * @param {function} onError function Callback for error-case that takes a * single number or String argument. * @param {function} onSuccess function Callback for success-case that takes * a single Object argument. * @return {Object} DeviceInfo **/ this.getHandleInfo = function(onError, onSuccess) { //Check to make sure a device has been opened if(self.checkStatus(onError)) { return 1;} // Wrap user onError and onSuccess functions to prevent un-wanted // callback executions. onError = wrapUserFunction(onError); onSuccess = wrapUserFunction(onSuccess); return self.ljnd.LJM_GetHandleInfo(self.handle, onError, onSuccess); }; /** * Retrieves device information about this device. * * Retrieves device metadata information about the device whose handle this * object encapsulates. Returns a DeviceInfo object with the following * structure: * * { * deviceType {number} A device model type corresponding to a constant * in LabJackM.h. * connectionType {number} A constant from LabJackM.h that defines * connection medium the driver is using to communicate with the * device. * serialNumber {number} The device unique integer serial number. * ipAddress {string} The IP address the device is located at. Defaults * to all zeros if not connected by the network (0.0.0.0). * port {number} The port at which the device is connected. * maxBytesPerMB {number} The maximum payload in bytes that the * driver can send to the device over the current connection * medium. * } * * @return {Object/Number} Object conforming to the DeviceInfo structure as * defined above. * @throws {Error} Thrown if any errors are discovered. **/ this.getHandleInfoSync = function() { console.log('Calling getHandleInfo sync') //Check to make sure a device has been opened self.checkStatus(); return self.ljnd.LJM_GetHandleInfoSync(self.handle); }; /** * Performs an asynchronous LJM_ReadRaw function call with the LJM driver. * * Reads the value of a modbus register without interpreting that value * before returning. * * @param {Array} data An appropriately sized number array indicating how * many bytes should be received from the LJM driver. * @param {function} onError function to be called when successful. * @param {function} onSuccess function to be called when an error occurs. **/ this.readRaw = function(data, onError, onSuccess) { //Check to make sure a device has been opened if (self.checkStatus(onError)) { return; } // Wrap user onError and onSuccess functions to prevent un-wanted // callback executions. onError = wrapUserFunction(onError); onSuccess = wrapUserFunction(onSuccess); return self.ljnd.LJM_ReadRaw(self.handle, data, onError, onSuccess); }; /** * The synchronous version of readRaw. * * @param {Array} data an appropriately sized number array indicating how * many bytes should be received from the LJM driver. * @return {Array} Number Array of data returned from the LJM_ReadRaw * function. * @throws {DriverInterfaceError} If input args aren't correct. * @throws {DriverOperationError} If an LJM error occurs. */ this.readRawSync = function(data) { //Check to make sure a device has been opened self.checkStatus(); return self.ljnd.LJM_ReadRawSync(self.handle, data); }; /** * Asynchronously reads a single modbus address. * * @param {Number/String} address Either an integer register address or a * String name compatible with self.LJM. * @param {function} onError Takes a single argument represenging * the LJM-Error, either an integer error number or a String. * @param {Number/String} onSuccess Callback taking a single argument: an * array containging data (as number or string elements based on data * type) requested from the device. * @throws {DriverInterfaceError} If the input args aren't correct. */ this.read = function(address, onError, onSuccess) { //Check to make sure a device has been opened if (self.checkStatus(onError)) { return; } // Wrap user onError and onSuccess functions to prevent un-wanted // callback executions. onError = wrapUserFunction(onError); onSuccess = wrapUserFunction(onSuccess); return self.ljnd.LJN_Read(self.handle, address, onError, onSuccess); }; /** * Function synchronously reads a single modbus address. * * @param {number / String} address LJM-address or name to read. * @return {number / String} Data retrieved from device or error code. * @throws {DriverInterfaceError} If input args aren't correct * @throws {DriverOperationError} If an LJM error occurs */ this.readSync = function(address) { //Check to make sure a device has been opened self.checkStatus(); return self.ljnd.LJN_ReadSync(self.handle, address); }; /** * Asynchronously reads a single modbus buffer address. * * @param {Number/String} address Either an integer register address or a * String name compatible with self.LJM. * @param {Number} number The number of bytes to read from the device. * @param {function} onError Takes a single argument represenging * the LJM-Error, either an integer error number or a String. * @param {Number/String} onSuccess Callback taking a single argument: an * array containging data (as number or string elements based on data * type) requested from the device. * @throws {DriverInterfaceError} If the input args aren't correct. **/ this.readArray = function(address, numReads, onError, onSuccess) { //Check to make sure a device has been opened if (self.checkStatus(onError)) { return; } // Wrap user onError and onSuccess functions to prevent un-wanted // callback executions. onError = wrapUserFunction(onError); onSuccess = wrapUserFunction(onSuccess); return self.ljnd.LJN_readArray(self.handle, address, numReads, onError, onSuccess); }; /** * Synchronously reads a single modbus buffer address. * * @param {Number/String} address Either an integer register address or a * String name compatible with LJM. * @param {Number} number The number of bytes to read from the device. * @throws {DriverInterfaceError} If the input args aren't correct. **/ this.readArraySync = function(address, numReads) { //Check to make sure a device has been opened self.checkStatus(); return self.ljnd.LJN_readArraySync(self.handle, address, numReads); }; /** * Read many addresses asynchronously. * * Function performs LJM_eReadNames and LJM_eReadAddresses driver calls * when given certain arguments. If addresses is type: number-array then it * calls LJM_eReadAddresses and, if addresses is type string-array, then it * calls LJM_eReadNames. * * @param {Array} addresses Collection of names or addresses to read. * An Array of String elements will be interepreted as a collection * of names while an Array of Number elements will be interepreted * as a collection of addresses. * @param {function} onError Function to call if an error is encountered * while performing the read. Should take a single string parameter * describing the error encountered. * @param {function} onSuccess Function to call if this operation completes * successfully. Not called if an error is encountered. This function * should take a single arugment of type array containing elements of * type number. * @throws {DriverInterfaceError} Thrown if addresses argument is not an * Array containing at least 1 element. **/ this.readMany = function(addresses, onError, onSuccess) { // TODO: Clean this up. //Check to make sure a device has been opened if ( self.checkStatus(onError) ) { return; } // Wrap user onError and onSuccess functions to prevent un-wanted // callback executions. onError = wrapUserFunction(onError); onSuccess = wrapUserFunction(onSuccess); return self.ljnd.readMany(self.handle, addresses, onError, onSuccess); }; /** * Read many addresses. Synchronous version of readMany. * * Function performs LJM_eReadNames and LJM_eReadAddresses driver calls * when given certain arguments. If addresses is type: number-array then it * calls LJM_eReadAddresses and, if addresses is type string-array, then it * calls LJM_eReadNames. * * @param {Array} addresses Collection of names or addresses to read. * An Array of String elements will be interepreted as a collection * of names while an Array of Number elements will be interepreted * as a collection of addresses. * @return {Array} Array of double register values. * @throws {DriverInterfaceError} Thrown if addresses argument is not an * Array containing at least 1 element. **/ this.readManySync = function(addresses) { //Check to make sure a device has been opened self.checkStatus(); return self.ljnd.readManySync(self.handle, addresses); }; /** * Writes a request to the device without interpretation. * * @param {Array} data The data (Array of double) to write to the device. * @param {function} onError Function to call if an error is encountered * during operation. Should take a single parameter describing the * error encountered. * @param {function} onSuccess Function tao call after the operation * finishes. Called with a single parameter: Buffer of double. */ this.writeRaw = function(data, onError, onSuccess) { //Check to make sure a device has been opened if ( self.checkStatus(onError) ) { return; } // Wrap user onError and onSuccess functions to prevent un-wanted // callback executions. onError = wrapUserFunction(onError); onSuccess = wrapUserFunction(onSuccess); if ( typeof(data[0]) != "number" ) { console.log('WriteRaw-Err, data not a number-array'); } var aData = allocBuffer(data.length); for ( var i = 0; i < data.length; i++ ) { aData.writeUInt8(data[i], i); } errorResult = self.ljm.LJM_WriteRaw.async( self.handle, aData, data.length, function (err, res){ if(err) { return onError('Weird Error writeRaw', err); } if ( res === 0 ) { return onSuccess(aData); } else { return onError(res); } } ); }; /** * This function performs an synchronous writeRaw LJM function. * * @param {array} data Data to be written to the device. Should be an array * of number. * @return {array} The data read from the device, should be an array of * number. * @throws {DriverInterfaceError} If input args aren't correct * @throws {DriverOperationError} If an LJM error occurs */ this.writeRawSync = function(data) { //Check to make sure a device has been opened self.checkStatus(); if ( typeof(data[0] ) != "number" ) { console.log('WriteRaw-Err, data not a number-array'); } //Define data buffer var aData = allocBuffer(data.length); for ( var i = 0; i < data.length; i++ ) { aData.writeUInt8(data[i], i); } errorResult = self.ljm.LJM_WriteRaw( self.handle, aData, data.length ); if ( errorResult === 0 ) { return aData; } else { throw new DriverInterfaceError(res); } }; /** * Writes a single modbus register. * * Function performs an asynchronous write command using either the * LJM_eWriteName, LJM_eWriteAddress, LJM_eWriteNameString, or * LJM_eWriteAddressString function. * * @param {String/number} address The register being written to. The * function interprets a number as an address and a string as a * register name. * @param {String/number} value The data to write to the selected register. * @param {function} onError Function called when an error occurs. Should * take a single argument: a string description of the error * encountered. * @param {function} onSuccess Function called upon finishing successfully. */ this.write = function(address, value, onError, onSuccess) { //Check to make sure a device has been opened if ( self.checkStatus(onError) ) { return; } // Wrap user onError and onSuccess functions to prevent un-wanted // callback executions. onError = wrapUserFunction(onError); onSuccess = wrapUserFunction(onSuccess); var strBuffer; var info; //Decision making for address type (string or number) if ( isNaN(address) ) { info = self.constants.getAddressInfo(address, 'W'); //Decision making for LJM-address return type, number or string if ( (info.directionValid == 1) && (info.type != 98) ) { //Execute LJM command errorResult = self.ljm.LJM_eWriteName.async( self.handle, address, value, function(err, res) { if(err) { return onError('Weird Error write-1', err); } if ( res === 0 ) { return onSuccess(); } else { return onError(res); } } ); return 0; } else if ( (info.directionValid == 1) && (info.type == 98) ) { //Allocate space for the string to be written strBuffer = allocBuffer(driver_const.LJM_MAX_STRING_SIZE); //Fill the write-string if ( value.length <= driver_const.LJM_MAX_STRING_SIZE ) { strBuffer.write(value, 0, value.length, 'utf8'); } else { onError("String is to long"); return 0; } //Execute LJM command errorResult = self.ljm.LJM_eWriteNameString.async( self.handle, address, strBuffer, function(err, res){ if(err) { return onError('Weird Error write-2', err); } if ( res === 0 ) { return onSuccess(); } else { return onError(res); } } ); return 0; } else { //ERROR!! address is not valid, wrong direction or invalid addr. if ( info.type == -1 ) { return onError("Invalid Address"); } else if (info.directionValid === 0) { return onError("Invalid Write Attempt"); } } } else if (!isNaN(address)) { info = self.constants.getAddressInfo(address, 'W'); if ( (info.directionValid == 1) && (info.type != 98) ) { //Execute LJM command errorResult = self.ljm.LJM_eWriteAddress.async( self.handle, address, info.type, value, function(err, res){ if(err) { return onError('Weird Error write-3', err); } if ( res === 0 ) { return onSuccess(); } else { return onError(res); } } ); return 0; } else if ( (info.directionValid == 1) && (info.type == 98) ) { //Allocate space for the string to be written strBuffer = allocBuffer(driver_const.LJM_MAX_STRING_SIZE); //Fill the write-string if(value.length <= driver_const.LJM_MAX_STRING_SIZE) { strBuffer.write(value, 0, value.length, 'utf8'); } else { onError("String is to long"); return 0; } //Execute LJM command errorResult = self.ljm.LJM_eWriteAddressString.async( self.handle, address, strBuffer, function(err, res){ if(err) { return onError('Weird Error write', err); } if ( res === 0 ) { return onSuccess(); } else { return onError(res); } } ); return 0; } else { //ERROR!! address is not valid, wrong direction or invalid addr. if(info.type == -1) { return onError("Invalid Address"); } else if (info.directionValid === 0) { return onError("Invalid Write Attempt"); } } } else { return onError("Invalid Arguments"); } }; /** * Synchronous version of write. * * Function performs a synchronous write command using either the * LJM_eWriteName, LJM_eWriteAddress, LJM_eWriteNameString, or * LJM_eWriteAddressString function. * * @param {String/number} address The register being written to. The * function interprets a number as an address and a string as a * register name. * @param {String/number} value The data to write to the selected register. * @return {String/number} Will return 0 on error and, on success, the data * provided by the device as a number or string. * @throws {DriverInterfaceError} If an error has been detected before * calling the LJM function. * @throws {DriverOperationError} If LJM reports an error has occured. */ this.writeSync = function (address, value) { //Check to make sure a device has been opened self.checkStatus(); var errorResult; var strBuffer; var info; //Decision making for address type (string or number) if ( isNaN(address) ) { info = self.constants.getAddressInfo(address, 'W'); //Decision making for LJM-address return type, number or string if ( (info.directionValid == 1) && (info.type != 98) ) { //Execute LJM command errorResult = self.ljm.LJM_eWriteName( self.handle, address, value ); } else if( (info.directionValid == 1) && (info.type == 98) ) { //Allocate space for the string to be written strBuffer = allocBuffer(driver_const.LJM_MAX_STRING_SIZE); //Fill the write-string if ( value.length <= driver_const.LJM_MAX_STRING_SIZE ) { strBuffer.write(value, 0, value.length, 'utf8'); } else { throw new DriverInterfaceError("String is to long"); return "string is to long"; } //Execute LJM command errorResult = self.ljm.LJM_eWriteNameString( self.handle, address, strBuffer ); } else { //ERROR!! address is not valid, wrong direction or invalid addr. if ( info.type == -1 ) { throw new DriverInterfaceError("Invalid Address"); return "Invalid Address"; } else if (info.directionValid === 0) { throw new DriverInterfaceError("Invalid Write Attempt"); return "Invalid Write Attempt"; } } } else if( !isNaN(address) ) { info = self.constants.getAddressInfo(address, 'W'); if ( (info.directionValid == 1) && (info.type != 98) ) { //Execute LJM command errorResult = self.ljm.LJM_eWriteAddress( self.handle, address, info.type, value ); } else if ( (info.directionValid == 1) && (info.type == 98) ) { //Allocate space for the string to be written strBuffer = allocBuffer(driver_const.LJM_MAX_STRING_SIZE); //Fill the write-string if ( value.length <= driver_const.LJM_MAX_STRING_SIZE ) { strBuffer.write(value, 0, value.length, 'utf8'); } else { throw new DriverInterfaceError("String is to long"); return "string is to long"; } //Execute LJM command errorResult = self.ljm.LJM_eWriteAddressString( self.handle, address, strBuffer ); } else { //ERROR!! address is not valid, wrong direction or invalid addr. if ( info.type == -1 ) { throw new DriverInterfaceError("Invalid Address"); return "Invalid Address"; } else if ( info.directionValid === 0 ) { throw new DriverInterfaceError("Invalid Write Attempt"); return "Invalid Write Attempt"; } } } else { throw new DriverInterfaceError("Invalid Arguments"); return "Invalid Arguments"; } if ( errorResult === 0 ) { return errorResult; } else { throw new DriverOperationError(errorResult); return errorResult; } }; /** * A helper function for the writeArray and writeArraySync function to parse * or interpret the data to be written. */ var innerFormatAndValidateArrayData = function(address, writeData) { var writeInfo = { 'isValid': false, 'message': 'Unknown Reason', // Data to be written 'address': undefined, 'type': undefined, 'numValues': undefined, 'aValues': undefined, 'errorAddress': undefined, }; var info = self.constants.getAddressInfo(address, 'W'); var isDirectionValid = info.directionValid == 1; var isBufferRegister = false; if(info.data) { if(info.data.isBuffer) { isBufferRegister = true; } } if (isDirectionValid && isBufferRegister) { writeInfo.isValid = true; // Save info writeInfo.address = info.address; writeInfo.type = info.type; var errorVal = new ref.alloc('int',1); errorVal.fill(0); writeInfo.errorAddress = errorVal; // Variable declarations: var aValues, offset, i; // Check to see if the input-data is of the type "buffer" if(Buffer.isBuffer(writeData)) { writeInfo.isValid = false; writeInfo.message = 'Buffer type is not supported'; } else if(Array.isArray(writeData)) { writeInfo.numValues = writeData.length; aValues = allocBuffer(writeData.length * ARCH_DOUBLE_NUM_BYTES); offset = 0; for(i = 0; i < writeData.length; i++) { aValues.writeDoubleLE(writeData[i], offset); offset += ARCH_DOUBLE_NUM_BYTES; } writeInfo.aValues = aValues; } else if((typeof(writeData) === 'string') || (writeData instanceof String)) { writeInfo.numValues = writeData.length; aValues = allocBuffer(writeData.length * ARCH_DOUBLE_NUM_BYTES); offset = 0; for(i = 0; i < writeData.length; i++) { aValues.writeDoubleLE(writeData.charCodeAt(i), offset); offset += ARCH_DOUBLE_NUM_BYTES; } writeInfo.aValues = aValues; } else { // Un-supported type writeInfo.isValid = false; writeInfo.message = 'Invalid data type being written: ' + typeof(writeData) + '.'; } writeInfo.numValues = writeData.length; } else { writeInfo.isValid = false; if (info.type == -1) { writeInfo.message = 'Invalid Address'; } else if (info.directionValid === 0) { writeInfo.message = 'Invalid Read Attempt'; } else if (!isBufferRegister) { writeInfo.message = 'Tried to read an array from a register that is not a buffer'; } } return writeInfo; }; /** * Asynchronously write a single modbus buffer address. * * @param {Number/String} address Either an integer register address or a * String name compatible with LJM. * @param {Number} number The number of bytes to read from the device. * @param {function} onError Takes a single argument represenging * the LJM-Error, either an integer error number or a String. * @param {Number/String} onSuccess Callback taking a single argument: an * array containging data (as number or string elements based on data * type) requested from the device. * @throws {DriverInterfaceError} If the input args aren't correct. **/ this.writeArray = function(address, writeData, onError, onSuccess) { //Check to make sure a device has been opened if (self.checkStatus(onError)) { return; } // Wrap user onError and onSuccess functions to prevent un-wanted // callback executions. onError = wrapUserFunction(onError); onSuccess = wrapUserFunction(onSuccess); var writeInfo = innerFormatAndValidateArrayData(address, writeData); if(writeInfo.isValid) { // Return Variable var errorResult; var writeAddress = writeInfo.address; var type = writeInfo.type; var numValues = writeInfo.numValues; var aValues = writeInfo.aValues; var errorAddress = writeInfo.errorAddress; // Call the LJM function errorResult = self.ljm.LJM_eWriteAddressArray.async( self.handle, writeAddress, type, numValues, aValues, errorAddress, function(err, res) { if(err) { return onError('Weird Error readAddress', err); } if ( (res === 0) ) { return onSuccess(); } else { return onError({retError: res, errFrame: errorAddress.deref()}); } } ); } else { onError(writeInfo.message); return -1; } }; /** * Synchronously reads a single modbus buffer address. * * @param {Number/String} address Either an integer register address or a * String name compatible with LJM. * @param {Number} number The number of bytes to read from the device. * @throws {DriverInterfaceError} If the input args aren't correct. **/ this.writeArraySync = function(address, writeData) { //Check to make sure a device has been opened self.checkStatus(); var output; var retData; var writeInfo = innerFormatAndValidateArrayData(address, writeData); if(writeInfo.isValid) { var writeAddress = writeInfo.address; var type = writeInfo.type; var numValues = writeInfo.numValues; var aValues = writeInfo.aValues; var errorAddress = writeInfo.errorAddress; // Call the LJM function output = self.ljm.LJM_eWriteAddressArray( self.handle, writeAddress, type, numValues, aValues, errorAddress ); if (output === 0) { // Read was successful. } } else { throw new DriverInterfaceError(writeInfo.message); } if (output === 0) { return retData; } else { throw new DriverOperationError(output); } }; /** * Writes values to many registers at once. * * Asynchronously calls the LJM functions: LJM_eWriteAddresses and * LJM_eWriteNames given appropriate input variables. * * @param {array} addresses Array of registers to write to. This function * interprets a collection of numbers as a collection of addresses * but interprets a string as a collection of register names. * @param {array} values An array of values to write. Should * correspond one to one with the registers idenified in addresses. * Only number values allowed in this array and client code should use * write / writeSync if needing to write a String. * @param {function} onError Function called when finished with an error. * Should take an object with attributes retError (with a string * error message) and errorFrame (counter indicating on which frame * the error was encountered). * @param {function} onSuccess Function called when finished successfully. * This function will not pass any arugments to this callback. */ this.writeMany = function (addresses, values, onError, onSuccess) { //Check to make sure a device has been opened if ( self.checkStatus(onError) ) { return; } // Wrap user onError and onSuccess functions to prevent un-wanted // callback executions. onError = wrapUserFunction(onError); onSuccess = wrapUserFunction(onSuccess); //Check to make sure the two array's are of the same length if ( addresses.length != values.length ) { onError('Length of addresses & values must be equal'); return; } //Check to make sure that the values array is filled with numbers if ( typeof(values[0]) != 'number' ) { onError('values must be of type number-array'); return; } //Perform universal buffer allocations. var length = addresses.length; var aValues = allocBuffer(ARCH_DOUBLE_NUM_BYTES * length); var errors = new ref.alloc('int',1); //Clear the buffers errors.fill(0); var errorResult; var i = 0; var offset; var dataOffset; //Decide whether to perform address-number or address-name operation. if ( isNaN(addresses[0]) ) { //Perform necessary string buffer allocations. offset = 0; dataOffset = 0; //Declare aNames array buffer var aNames = allocBuffer(ARCH_POINTER_SIZE * length); for ( i = 0; i < length; i++ ) { //Append the value to the aValues array aValues.writeDoubleLE(values[i], dataOffset); //Declare buffer for string-address var buf = allocBuffer(addresses[i].length + 1); //Save the string to the buffer ref.writeCString(buf, 0, addresses[i]); //Write the pointer of the buffer to the aNames array ref.writePointer(aNames, offset, buf); //Increment the offset counter offset+=ARCH_POINTER_SIZE; dataOffset+=ARCH_DOUBLE_NUM_BYTES; } //Execute LJM command. errorResult = self.ljm.LJM_eWriteNames.async( self.handle, length, aNames, aValues, errors, function(err, res){ if(err) { return onError('Weird Error writeMany-1', err); } if ( (res === 0) ) { return onSuccess(); } else { return onError({retError:res, errFrame:errors.deref()}); } } ); return 0; } else if ( !isNaN(addresses[0]) ) { //Perform necessary number buffer allocations. var addrBuff = allocBuffer(ARCH_INT_NUM_BYTES * length); var addrTypeBuff = allocBuffer(ARCH_INT_NUM_BYTES * length); var inValidOperation = 0; var info; offset = 0; var offsetD = 0; for ( i = 0; i < length; i++ ) { info = self.constants.getAddressInfo(addresses[i], 'W'); if ( info.directionValid == 1 ) { addrTypeBuff.writeInt32LE(info.type,offset); addrBuff.writeInt32LE(addresses[i],offset); aValues.writeDoubleLE(values[i],offsetD); offset += ARCH_INT_NUM_BYTES; offsetD += ARCH_DOUBLE_NUM_BYTES; } else { if ( info.type == -1 ) { onError({retError:"Invalid Address", errFrame:i}); } else if (info.directionValid === 0) { onError({retError:"Invalid Read Attempt", errFrame:i}); } else { onError({retError:"Unexpected Error", errFrame:i}); } return; } } //Execute LJM command. errorResult = self.ljm.LJM_eWriteAddresses.async( self.handle, length, addrBuff, addrTypeBuff, aValues, errors, function(err, res){ if(err) { return onError('Weird Error writeMany-2', err); } if ( (res === 0) ) { return onSuccess(); } else { return onError({retError:res, errFrame:errors.deref()}); } } ); return 0; } else { onError('Invalid Array-type, must be number-array or string-array'); return; } }; /** * Synchronous version of writeMany. * * Synchronously calls the LJM functions: LJM_eWriteAddresses and * LJM_eWriteNames given appropriate input variables. * * @param {array} addresses Array of registers to write to. This function * interprets a collection of numbers as a collection of addresses * but interprets a string as a collection of register names. * @param {array} values An array of values to write. Should * correspond one to one with the registers idenified in addresses. * Only number values allowed in this array and client code should use * write / writeSync if needing to write a String. * @return {number/String} 0 on success, string on error. * @throws {DriverInterfaceError} Thrown if there is an error produced * before calling the LJM function. * @throws {DriverOperationError} Thrown if there is an error produced * during the LJM driver function. */ this.writeManySync = function (addresses, values) { //Check to make sure a device has been opened. self.checkStatus(); //Check to make sure the two array's are of the same length if ( addresses.length != values.length ) { throw new DriverInterfaceError( 'Length of Addresses & Values must be equal' ); return 'Length of Addresses & Values must be equal'; } //Check to make sure that the values array is filled with numbers if ( typeof(values[0]) != 'number' ) { throw new DriverInterfaceError( 'Values must be of type number-array' ); return 'Values must be of type number-array'; } //Perform universal buffer allocations. var length = addresses.length; var aValues = allocBuffer(ARCH_DOUBLE_NUM_BYTES * length); var errors = new ref.alloc('int',1); //Clear the buffers errors.fill(0); var errorResult; var i; var offset; //Decide whether to perform address-number or address-name operation. if ( isNaN(addresses[0]) ) { //Perform necessary string buffer allocations. i = 0; offset = 0; var dataOffset = 0; //Declare aNames array buffer var aNames = allocBuffer(ARCH_POINTER_SIZE * length); for ( i = 0; i < length; i++ ) { //Append the value to the aValues array aValues.writeDoubleLE(values[i], dataOffset); //Declare buffer for string-address var buf = allocBuffer(addresses[i].length + 1); //Save the string to the buffer ref.writeCString(buf, 0, addresses[i]); //Write the pointer of the buffer to the aNames array ref.writePointer(aNames, offset, buf); //Increment the offset counter offset+=ARCH_POINTER_SIZE; dataOffset+=ARCH_DOUBLE_NUM_BYTES; } //Execute LJM function. errorResult = self.ljm.LJM_eWriteNames( self.handle, length, aNames, aValues, errors ); } else if (!isNaN(addresses[0])) { //Perform necessary number buffer allocations. var addrBuff = allocBuffer(ARCH_INT_NUM_BYTES * length); var addrTypeBuff = allocBuffer(ARCH_INT_NUM_BYTES * length); var inValidOperation = 0; var info; offset=0; var offsetD = 0; i = 0; for ( i = 0; i < length; i++ ) { info = self.constants.getAddressInfo(addresses[i], 'W'); if ( info.directionValid == 1 ) { addrTypeBuff.writeInt32LE(info.type,offset); addrBuff.writeInt32LE(addresses[i],offset); aValues.writeDoubleLE(values[i],offsetD); offset += ARCH_INT_NUM_BYTES; offsetD += ARCH_DOUBLE_NUM_BYTES; } else { if ( info.type == -1 ) { throw new DriverInterfaceError( { retError:"Invalid Address", errFrame:i } ); return {retError:"Invalid Address", errFrame:i}; } else if (info.directionValid === 0) { throw new DriverInterfaceError( { retError:"Invalid Write Attempt", errFrame:i } ); return {retError:"Invalid Write Attempt", errFrame:i}; } else { throw new DriverInterfaceError( { retError:"Unexpected Error", errFrame:i } ); return {retError:"Unexepcted Error", errFrame:i}; } } } //Execute LJM command. errorResult = self.ljm.LJM_eWriteAddresses( self.handle, length, addrBuff, addrTypeBuff, aValues, errors ); } else { throw new DriverInterfaceError( 'Invalid Array-type, must be number-array or string-array' ); return 'Invalid Array-type, must be number-array or string-array'; } if(errorResult === 0) { return errorResult; } else { throw new DriverOperationError( { retError:errorResult, errFrame:errors.deref()} ); return {retError:errorResult, errFrame:errors.deref()}; } }; /** * Helper function for building an array of data read from the driver. * * Helper function for returning the proper array of data when using the * rwMany function call. Throws out written data & builds an array of only * data read by the driver. * * @param {number} numFrames the number of frames sent. * @param {array} numValues The values array provided by the user. All * elements should be number. * @param {array} directions Array of read/write directions. Each element * should be a number indicating the direction of the operation (read * or write). * @param {Buffer} aValues Data passed back by the LJM driver. * @return {array} Array of data that was read back from the driver. */ this.populateRWManyArray = function (numFrames, numValues, directions, aValues) { var returnArray = []; var offset = 0; for (var i = 0; i < numFrames; i++) { for (var j = 0; j < numValues[i]; j++) { if (directions[i] == driver_const.LJM_READ) { returnArray.push(aValues.readDoubleLE(offset)); } offset += ARCH_DOUBLE_NUM_BYTES; } } return return