diff --git a/src/MIDParser.js b/src/MIDParser.js index c5b10bc..2c244fe 100644 --- a/src/MIDParser.js +++ b/src/MIDParser.js @@ -16,8 +16,8 @@ "use strict"; /*jshint esversion: 6, node: true*/ -const util = require('util'); -const { Transform } = require('stream'); +const util = require("util"); +const { Transform } = require("stream"); const helpers = require("./helpers.js"); const mids = helpers.getMids(); @@ -25,64 +25,59 @@ const mids = helpers.getMids(); const constants = require("./constants.json"); const encodingOP = constants.defaultEncoder; -var debug = util.debuglog('open-protocol'); +var debug = util.debuglog("open-protocol"); class MIDParser extends Transform { + /** + * @class MIDParser + * @description This class performs the parsing of a MID body. + * This transforms MID.payload (Buffer) in a MID.payload (Object). + * This class uses the implemented MIDs in 'node-open-protocol/src/mid' for parsing MIDs. + * In case of a not implemented MID, MID.payload is converted in to a String. + * @param opts parameters to Transform stream + */ + constructor(opts) { + debug("new MIDParser"); + + opts = opts || {}; + + opts.writableObjectMode = true; + opts.readableObjectMode = true; + + super(opts); + } + + _transform(chunk, encoding, cb) { + debug("new MIDParser _transform", chunk); + + if (mids[chunk.mid]) { + mids[chunk.mid].parser(chunk, null, (err, data) => { + if (err) { + cb(new Error(`Error on parser [${err}]`)); + debug("new MIDParser _transform err-parser", chunk, err); + return; + } - /** - * @class MIDParser - * @description This class performs the parsing of a MID body. - * This transforms MID.payload (Buffer) in a MID.payload (Object). - * This class uses the implemented MIDs in 'node-open-protocol/src/mid' for parsing MIDs. - * In case of a not implemented MID, MID.payload is converted in to a String. - * @param opts parameters to Transform stream - */ - constructor(opts) { - debug("new MIDParser"); - - opts = opts || {}; - - opts.writableObjectMode = true; - opts.readableObjectMode = true; - - super(opts); - } - - _transform(chunk, encoding, cb) { - debug("new MIDParser _transform", chunk); - - if(mids[chunk.mid]){ - - mids[chunk.mid].parser(chunk, null, (err, data) => { - - if(err){ - cb(new Error(`Error on parser [${err}]`)); - debug("new MIDParser _transform err-parser", chunk, err); - return; - } - - this.push(data); - cb(); - }); - - }else{ - - if(!Buffer.isBuffer(chunk.payload)){ - cb(new Error(`Error on parser - invalid payload MID [${chunk.mid}]`)); - debug("new MIDParser _transform err-invalid_payload_MID", chunk); - return; - } + this.push(data); + cb(); + }); + } else { + if (!Buffer.isBuffer(chunk.payload)) { + cb(new Error(`Error on parser - invalid payload MID [${chunk.mid}]`)); + debug("new MIDParser _transform err-invalid_payload_MID", chunk); + return; + } - chunk.payload = chunk.payload.toString(encodingOP); + chunk.payload = chunk.payload.toString(encodingOP); - this.push(chunk); - cb(); - } + this.push(chunk); + cb(); } + } - _destroy() { - //no-op, needed to handle older node versions - } + _destroy() { + //no-op, needed to handle older node versions + } } module.exports = MIDParser; diff --git a/src/MIDSerializer.js b/src/MIDSerializer.js index f16c3a8..1964014 100644 --- a/src/MIDSerializer.js +++ b/src/MIDSerializer.js @@ -47,9 +47,9 @@ class MIDSerializer extends Transform { debug("MIDSerializer _transform", chunk); if(mids[chunk.mid]){ - + mids[chunk.mid].serializer(chunk, null, (err, data) => { - + if(err){ cb(new Error(`Error on serializer [${err}]`)); debug('MIDSerializer _transform err-serializer', chunk, err); @@ -57,9 +57,9 @@ class MIDSerializer extends Transform { } this.push(data); - cb(); + cb(); }); - + }else{ if(chunk.payload === undefined){ diff --git a/src/constants.json b/src/constants.json index 09a22da..9ce614e 100644 --- a/src/constants.json +++ b/src/constants.json @@ -260,11 +260,11 @@ }, "UNIT": { "000": "No unit", - "001": "N-m", + "001": "N·m", "002": "ft lbf", - "003": "cN-m", - "004": "kN-m", - "005": "MN-m", + "003": "cN·m", + "004": "kN·m", + "005": "MN·m", "006": "in lbf", "007": "Kpm", "008": "Kfcnm", @@ -337,6 +337,14 @@ "924": "ozf / ms", "925": "MN / ms" }, + "TRACE_TYPE": { + "1": "Angle", + "2": "Torque", + "3": "Current", + "4": "Gradient", + "5": "Stroke", + "6": "Force" + }, "ERROR": { "00": "No Error", "01": "Invalid data", @@ -435,4 +443,4 @@ "INCONSISTENCY_MESSAGE_NUMBER": 4 }, "defaultEncoder" : "ascii" -} \ No newline at end of file +} diff --git a/src/helpers.js b/src/helpers.js index 3204105..1d060b9 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -18,7 +18,7 @@ const fs = require("fs"); const path = require("path"); -const codes = require('./constants.json'); +const codes = require("./constants.json"); const encoding = codes.defaultEncoder; let midList; @@ -33,9 +33,9 @@ let midList; * @returns {String} */ function padLeft(n, size, base, elm, trimLeft) { - n = n.toString(base || 10); - n = trimLeft ? n.substring(n.length - size) : n.substring(0, size); - return new Array(size - n.length + 1).join(elm || '0').concat(n); + n = n.toString(base || 10); + n = trimLeft ? n.substring(n.length - size) : n.substring(0, size); + return new Array(size - n.length + 1).join(elm || "0").concat(n); } /** @@ -48,9 +48,9 @@ function padLeft(n, size, base, elm, trimLeft) { * @returns {String} */ function padRight(n, size, base, elm, trimLeft) { - n = n.toString(base || 10); - n = trimLeft ? n.substring(n.length - size) : n.substring(0, size); - return n.concat(new Array(size - n.length + 1).join(elm || '0')); + n = n.toString(base || 10); + n = trimLeft ? n.substring(n.length - size) : n.substring(0, size); + return n.concat(new Array(size - n.length + 1).join(elm || "0")); } /** @@ -58,26 +58,23 @@ function padRight(n, size, base, elm, trimLeft) { * @returns {Array} */ function getMids() { + if (midList) { + return midList; + } - if (midList) { - return midList; - } - - midList = []; - - const listFiles = fs.readdirSync(path.join(__dirname, ".", "mid")); - - listFiles.forEach((file) => { + midList = []; - if (path.extname(file) !== ".js") { - return; - } + const listFiles = fs.readdirSync(path.join(__dirname, ".", "mid")); - midList[Number(path.basename(file, ".js"))] = require("./mid/" + file); + listFiles.forEach((file) => { + if (path.extname(file) !== ".js") { + return; + } - }); + midList[Number(path.basename(file, ".js"))] = require("./mid/" + file); + }); - return midList; + return midList; } /** @@ -88,50 +85,75 @@ function getMids() { * The [cb] function is called in cases of an error, sending the error as parameter. * The return of this function is a boolean, true: the process without errors or false: the process with an error. * - * @param {object} message - * @param {buffer} buffer - * @param {string} parameter - * @param {string} type - * @param {number} length - * @param {object} position - * @param {Function} cb + * @param {object} message + * @param {buffer} buffer + * @param {string} parameter + * @param {string} type + * @param {number} length + * @param {object} position + * @param {Function} cb * @returns {boolean} */ -function serializerField(message, buffer, parameter, type, length, position, cb) { - - position.value -= length; - - if (message.payload[parameter] === undefined) { - cb(new Error(`[Serializer] MID[${message.mid}] parameter [${parameter}] not exist`)); +function serializerField( + message, + buffer, + parameter, + type, + length, + position, + cb +) { + position.value -= length; + + if (message.payload[parameter] === undefined) { + cb( + new Error( + `[Serializer] MID[${message.mid}] parameter [${parameter}] not exist` + ) + ); + return false; + } + + switch (type) { + case "string": + buffer.write( + padRight(message.payload[parameter], length, 10, " "), + position.value, + encoding + ); + break; + + case "rawString": + buffer.write( + padRight(message.payload[parameter], length, 10, " "), + position.value, + encoding + ); + break; + + case "number": + if (isNaN(message.payload[parameter])) { + cb( + new Error( + `[Serializer] MID[${message.mid}] - type invalid isNaN - parameter: [${parameter}] value: [${message.payload[parameter]}] ` + ) + ); return false; - } + } - switch (type) { + buffer.write( + padLeft(message.payload[parameter], length), + position.value, + encoding + ); + break; - case "string": - buffer.write(padRight(message.payload[parameter], length, 10, " "), position.value, encoding); - break; + default: + cb(new Error(`[Serializer] MID[${message.mid}] - type is not defined`)); + return false; + } - case "rawString": - buffer.write(padRight(message.payload[parameter], length, 10, " "), position.value, encoding); - break; - - case "number": - - if (isNaN(message.payload[parameter])) { - cb(new Error(`[Serializer] MID[${message.mid}] - type invalid isNaN - parameter: [${parameter}] value: [${message.payload[parameter]}] `)); - return false; - } - - buffer.write(padLeft(message.payload[parameter], length), position.value, encoding); - break; - - default: - cb(new Error(`[Serializer] MID[${message.mid}] - type is not defined`)); - return false; - } - - return true; + return true; } /** @@ -142,26 +164,25 @@ function serializerField(message, buffer, parameter, type, length, position, cb) * The [cb] function is called in cases of error, sending the error as parameter. * The return of this function is boolean, true: the process without errors or false: the process with an error. * - * @param {object} message - * @param {buffer} buffer - * @param {number} key - * @param {number} length - * @param {object} position - * @param {Function} cb + * @param {object} message + * @param {buffer} buffer + * @param {number} key + * @param {number} length + * @param {object} position + * @param {Function} cb * @returns {boolean} */ function serializerKey(message, buffer, key, length, position, cb) { + position.value -= length; - position.value -= length; + if (isNaN(key)) { + cb(new Error(`[Serializer] MID[${message.mid}] key invalid [${key}]`)); + return false; + } - if (isNaN(key)) { - cb(new Error(`[Serializer] MID[${message.mid}] key invalid [${key}]`)); - return false; - } - - buffer.write(padLeft(key, length), position.value, encoding); + buffer.write(padLeft(key, length), position.value, encoding); - return true; + return true; } /** @@ -175,47 +196,76 @@ function serializerKey(message, buffer, key, length, position, cb) { * @param {object} message Object in use for update * @param {buffer} buffer Buffer with content for extracting information * @param {string} parameter Name of parameter extracted - * @param {string} parameterType Type of information extracted "string" | "rawString" | "number" + * @param {string} parameterType Type of information extracted "string" | "rawString" | "number" * @param {number} parameterLength Size of information extracted * @param {object} position Position on buffer this information {value: position} * @param {Function} cb * @returns {boolean} status process */ -function processParser(message, buffer, parameter, parameterType, parameterLength, position, cb) { - - let length = parameterLength; - parameterLength = position.value + parameterLength; - - switch (parameterType) { - case "string": - message.payload[parameter] = buffer.toString(encoding, position.value, parameterLength).trim(); - break; - - case "rawString": - message.payload[parameter] = buffer.toString(encoding, position.value, parameterLength); - if (message.payload[parameter].length !== length) { - cb(new Error(`invalid value, mid: ${message.mid}, parameter: ${parameter}, payload: ${message.payload}`)); - return false; - } - break; - - case "number": - message.payload[parameter] = Number(buffer.toString(encoding, position.value, parameterLength)); - if (isNaN(message.payload[parameter])) { - cb(new Error(`invalid value, mid: ${message.mid}, parameter: ${parameter}, payload: ${message.payload}`)); - return false; - } - break; - - default: - cb(new Error(`invalid parameterType`)); - return false; +function processParser( + message, + buffer, + parameter, + parameterType, + parameterLength, + position, + cb +) { + let length = parameterLength; + parameterLength = position.value + parameterLength; + + switch (parameterType) { + case "string": + message.payload[parameter] = buffer + .toString(encoding, position.value, parameterLength) + .trim(); + if (parameter === "unit") { + message.payload.unitName = + codes.UNIT[message.payload[parameter]] || "Unknown"; + } + break; + + case "rawString": + message.payload[parameter] = buffer.toString( + encoding, + position.value, + parameterLength + ); + if (message.payload[parameter].length !== length) { + cb( + new Error( + `invalid value, mid: ${message.mid}, parameter: ${parameter}, payload: ${message.payload}` + ) + ); + return false; + } + break; + + case "number": + message.payload[parameter] = Number( + buffer.toString(encoding, position.value, parameterLength) + ); + if (isNaN(message.payload[parameter])) { + cb( + new Error( + `invalid value, mid: ${message.mid}, parameter: ${parameter}, payload: ${message.payload}` + ) + ); + return false; + } else if (parameter === "traceType") { + message.payload.traceTypeName = + codes.TRACE_TYPE[message.payload[parameter]] || "Unknown"; + } + break; - } + default: + cb(new Error(`invalid parameterType`)); + return false; + } - position.value = parameterLength; + position.value = parameterLength; - return true; + return true; } /** @@ -225,29 +275,46 @@ function processParser(message, buffer, parameter, parameterType, parameterLengt * The return of this function is boolean, true: the value extracted is equal [key] or false: case not. * The [cb] function is called in cases of error, sending the error as parameter. * - * @param {object} object - * @param {buffer} buffer - * @param {string} parameter - * @param {number} key - * @param {number} keyLength - * @param {number} keyPosition - * @param {Function} cb + * @param {object} object + * @param {buffer} buffer + * @param {string} parameter + * @param {number} key + * @param {number} keyLength + * @param {number} keyPosition + * @param {Function} cb * @returns {boolean} */ -function processKey(object, buffer, parameter, key, keyLength, keyPosition, cb) { - - keyLength = keyPosition.value + keyLength; - - let receiver = Number(buffer.toString(encoding, keyPosition.value, keyLength)); - - if (receiver !== key) { - cb(new Error(`invalid key, mid: ${object.mid}, parameter: ${parameter}, expect: ${key}, receiver: ${receiver} payload: ${JSON.stringify(object.payload)}`)); - return false; - } - - keyPosition.value = keyLength; - - return true; +function processKey( + object, + buffer, + parameter, + key, + keyLength, + keyPosition, + cb +) { + keyLength = keyPosition.value + keyLength; + + let receiver = Number( + buffer.toString(encoding, keyPosition.value, keyLength) + ); + + if (receiver !== key) { + cb( + new Error( + `invalid key, mid: ${ + object.mid + }, parameter: ${parameter}, expect: ${key}, receiver: ${receiver} payload: ${JSON.stringify( + object.payload + )}` + ) + ); + return false; + } + + keyPosition.value = keyLength; + + return true; } /** @@ -264,15 +331,18 @@ function processKey(object, buffer, parameter, key, keyLength, keyPosition, cb) * @returns {boolean} */ function testNul(object, buffer, parameter, position, cb) { - - if (buffer[position.value] !== 0) { - cb(new Error(`invalid value, mid: ${object.mid}, parameter: ${parameter}, payload: ${object.payload}`)); - return false; - } - - position.value += 1; - - return true; + if (buffer[position.value] !== 0) { + cb( + new Error( + `invalid value, mid: ${object.mid}, parameter: ${parameter}, payload: ${object.payload}` + ) + ); + return false; + } + + position.value += 1; + + return true; } /** @@ -281,91 +351,128 @@ function testNul(object, buffer, parameter, position, cb) { * * The [cb] function is called in cases of error, sending the error as parameter. * The return of this function is boolean, true: the process without errors or false: the process with an error. - * + * * @see Specification OpenProtocol_Specification_R_2_8_0_9836 4415 01.pdf Page 34 - * - * @param {object} message - * @param {buffer} buffer - * @param {string} parameter - * @param {number} count - * @param {object} position - * @param {Function} cb + * + * @param {object} message + * @param {buffer} buffer + * @param {string} parameter + * @param {number} count + * @param {object} position + * @param {Function} cb * @returns {boolean} */ function processDataFields(message, buffer, parameter, count, position, cb) { + let control = 0; + + if (count > 0) { + message.payload[parameter] = []; + + while (control < count) { + let dataFields = {}; + + let parameterID = buffer + .toString(encoding, position.value, position.value + 5) + .trim(); + + if ( + parameterID === "" || + isNaN(Number(parameterID)) || + Number(parameterID) < 0 + ) { + cb( + new Error( + `invalid value, mid: ${message.mid}, parameter: ${parameter} - parameterID, payload: ${message.payload}` + ) + ); + return false; + } + dataFields.parameterID = parameterID; + dataFields.parameterName = codes.PID[parameterID] || ""; + position.value += 5; + + let length = Number( + buffer.toString(encoding, position.value, position.value + 3) + ); + + if (isNaN(length) || length < 0) { + cb( + new Error( + `invalid value, mid: ${message.mid}, parameter: ${parameter} - length, payload: ${message.payload}` + ) + ); + return false; + } + dataFields.length = length; + position.value += 3; + + let dataType = Number( + buffer.toString(encoding, position.value, position.value + 2) + ); + + if (isNaN(dataType) || dataType < 0) { + cb( + new Error( + `invalid value, mid: ${message.mid}, parameter: ${parameter} - dataType, payload: ${message.payload}` + ) + ); + return false; + } + dataFields.dataType = dataType; + position.value += 2; + + let unit = buffer + .toString(encoding, position.value, position.value + 3) + .trim(); + + if (unit === "" || isNaN(Number(unit)) || Number(unit) < 0) { + cb( + new Error( + `invalid value, mid: ${message.mid}, parameter: ${parameter} - unit, payload: ${message.payload}` + ) + ); + return false; + } + dataFields.unit = unit; + dataFields.unitName = codes.UNIT[unit] || ""; + position.value += 3; + + let stepNumber = Number( + buffer.toString(encoding, position.value, position.value + 4) + ); + + if (isNaN(stepNumber) || stepNumber < 0) { + cb( + new Error( + `invalid value, mid: ${message.mid}, parameter: ${parameter} - stepNumber, payload: ${message.payload}` + ) + ); + return false; + } + dataFields.stepNumber = stepNumber; + position.value += 4; + + let dataValue = buffer + .toString(encoding, position.value, position.value + length) + .trim(); + + if (dataValue === "") { + cb( + new Error( + `invalid value, mid: ${message.mid}, parameter: ${parameter} - dataValue, payload: ${message.payload}` + ) + ); + return false; + } + dataFields.dataValue = dataValue; + position.value += length; - let control = 0; - - if (count > 0) { - - message.payload[parameter] = []; - - while (control < count) { - - let dataFields = {}; - - let parameterID = buffer.toString(encoding, position.value, position.value + 5).trim(); - - if (parameterID === "" || isNaN(Number(parameterID)) || Number(parameterID) < 0) { - cb(new Error(`invalid value, mid: ${message.mid}, parameter: ${parameter} - parameterID, payload: ${message.payload}`)); - return false; - } - dataFields.parameterID = parameterID; - dataFields.parameterName = codes.PID[parameterID] || ""; - position.value += 5; - - let length = Number(buffer.toString(encoding, position.value, position.value + 3)); - - if (isNaN(length) || length < 0) { - cb(new Error(`invalid value, mid: ${message.mid}, parameter: ${parameter} - length, payload: ${message.payload}`)); - return false; - } - dataFields.length = length; - position.value += 3; - - let dataType = Number(buffer.toString(encoding, position.value, position.value + 2)); - - if (isNaN(dataType) || dataType < 0) { - cb(new Error(`invalid value, mid: ${message.mid}, parameter: ${parameter} - dataType, payload: ${message.payload}`)); - return false; - } - dataFields.dataType = dataType; - position.value += 2; - - let unit = buffer.toString(encoding, position.value, position.value + 3).trim(); - - if (unit === "" || isNaN(Number(unit)) || Number(unit) < 0) { - cb(new Error(`invalid value, mid: ${message.mid}, parameter: ${parameter} - unit, payload: ${message.payload}`)); - return false; - } - dataFields.unit = unit; - dataFields.unitName = codes.UNIT[unit] || ""; - position.value += 3; - - let stepNumber = Number(buffer.toString(encoding, position.value, position.value + 4)); - - if (isNaN(stepNumber) || stepNumber < 0) { - cb(new Error(`invalid value, mid: ${message.mid}, parameter: ${parameter} - stepNumber, payload: ${message.payload}`)); - return false; - } - dataFields.stepNumber = stepNumber; - position.value += 4; - - let dataValue = buffer.toString(encoding, position.value, position.value + length).trim(); - - if (dataValue === "") { - cb(new Error(`invalid value, mid: ${message.mid}, parameter: ${parameter} - dataValue, payload: ${message.payload}`)); - return false; - } - dataFields.dataValue = dataValue; - position.value += length; - - message.payload[parameter].push(dataFields); + message.payload[parameter].push(dataFields); - control += 1; - } + control += 1; } - return true; + } + return true; } /** @@ -376,99 +483,245 @@ function processDataFields(message, buffer, parameter, count, position, cb) { * The return of this function is boolean, true: the process without errors or false: the process with an error. * * @see Specification OpenProtocol_Specification_R_2_8_0_9836 4415 01.pdf Page 260 - * - * @param {object} message - * @param {buffer} buffer - * @param {string} parameter - * @param {number} count - * @param {object} position - * @param {function} cb + * + * @param {object} message + * @param {buffer} buffer + * @param {string} parameter + * @param {number} count + * @param {object} position + * @param {function} cb * @returns {boolean} */ -function processResolutionFields(message, buffer, parameter, count, position, cb) { - - let control = 0; - - if (count > 0) { - - message.payload[parameter] = []; - - while (control < count) { - - let resolutionFields = {}; +function processResolutionFields( + message, + buffer, + parameter, + count, + position, + cb +) { + let control = 0; + + if (count > 0) { + message.payload[parameter] = []; + + while (control < count) { + let resolutionFields = {}; + + let firstIndex = Number( + buffer.toString(encoding, position.value, position.value + 5) + ); + + if (isNaN(firstIndex) || firstIndex < 0) { + cb( + new Error( + `invalid value, mid: ${message.mid}, parameter: ${parameter}, payload: ${message.payload}` + ) + ); + return false; + } + resolutionFields.firstIndex = firstIndex; + position.value += 5; + + let lastIndex = Number( + buffer.toString(encoding, position.value, position.value + 5) + ); + + if (isNaN(lastIndex) || lastIndex < 0) { + cb( + new Error( + `invalid value, mid: ${message.mid}, parameter: ${parameter}, payload: ${message.payload}` + ) + ); + return false; + } + resolutionFields.lastIndex = lastIndex; + position.value += 5; + + let length = Number( + buffer.toString(encoding, position.value, position.value + 3) + ); + + if (isNaN(length) || length < 0) { + cb( + new Error( + `invalid value, mid: ${message.mid}, parameter: ${parameter}, payload: ${message.payload}` + ) + ); + return false; + } + resolutionFields.length = length; + position.value += 3; + + let dataType = Number( + buffer.toString(encoding, position.value, position.value + 2) + ); + + if (isNaN(dataType) || dataType < 0) { + cb( + new Error( + `invalid value, mid: ${message.mid}, parameter: ${parameter}, payload: ${message.payload}` + ) + ); + return false; + } + resolutionFields.dataType = dataType; + position.value += 2; + + let unit = buffer + .toString(encoding, position.value, position.value + 3) + .trim(); + + if (unit === "" || isNaN(Number(unit)) || Number(unit) < 0) { + cb( + new Error( + `invalid value, mid: ${message.mid}, parameter: ${parameter}, payload: ${message.payload}` + ) + ); + return false; + } + resolutionFields.unit = unit; + resolutionFields.unitName = codes.UNIT[unit] || ""; + position.value += 3; + + let timeValue = buffer + .toString(encoding, position.value, position.value + length) + .trim(); + + if (timeValue === "") { + cb( + new Error( + `invalid value, mid: ${message.mid}, parameter: ${parameter}, payload: ${message.payload}` + ) + ); + return false; + } + resolutionFields.timeValue = timeValue; + position.value += length; - let firstIndex = Number(buffer.toString(encoding, position.value, position.value + 5)); + message.payload[parameter].push(resolutionFields); - if (isNaN(firstIndex) || firstIndex < 0) { - cb(new Error(`invalid value, mid: ${message.mid}, parameter: ${parameter}, payload: ${message.payload}`)); - return false; - } - resolutionFields.firstIndex = firstIndex; - position.value += 5; - - let lastIndex = Number(buffer.toString(encoding, position.value, position.value + 5)); + control += 1; + } + } + return true; +} - if (isNaN(lastIndex) || lastIndex < 0) { - cb(new Error(`invalid value, mid: ${message.mid}, parameter: ${parameter}, payload: ${message.payload}`)); - return false; - } - resolutionFields.lastIndex = lastIndex; - position.value += 5; - - let length = Number(buffer.toString(encoding, position.value, position.value + 3)); +/** + * @description This method performs the extraction of the trace, is perform [count] times, + * from the position [position.value], these structures are stored in an array on [message.payload[parameter]]. + * + * The [cb] function is called in cases of error, sending the error as parameter. + * The return of this function is boolean, true: the process without errors or false: the process with an error. + * + * @see Specification OpenProtocol_Specification_R_2_8_0_9836 4415 01.pdf Page 260 + * + * @param {object} message + * @param {buffer} buffer + * @param {string} parameter + * @param {number} count + * @param {object} position + * @param {string} timeStamp + * @param {number} timeValue + * @param {string} unit + * @param {function} cb + * @returns {boolean} + */ +function processTraceSamples( + message, + buffer, + parameter, + count, + position, + timeStamp, + timeValue, + unit, + cb +) { + let control = 0; + let coefficient = 0; + message.payload[parameter] = []; + + if (count > 0) { + function firstPropertyWithGivenValue(value, object) { + for (var key in object) { + if (object[key].parameterName === value) + if (object[key].parameterID === "02213") { + coefficient = 1 / object[key].dataValue; + } else if (object[key].parameterID === "02214") { + coefficient = object[key].dataValue; + } else { + cb( + new Error( + `invalid value, mid: ${message.mid}, parameter: ${object[key].parameterID}, payload: ${object[key].dataValue}` + ) + ); + return false; + } + } + return coefficient; + } - if (isNaN(length) || length < 0) { - cb(new Error(`invalid value, mid: ${message.mid}, parameter: ${parameter}, payload: ${message.payload}`)); - return false; - } - resolutionFields.length = length; - position.value += 3; + firstPropertyWithGivenValue("Coefficient", message.payload.fieldData); - let dataType = Number(buffer.toString(encoding, position.value, position.value + 2)); + function toTimestamp(strDate) { + var datum = new Date(strDate); + return datum; + } - if (isNaN(dataType) || dataType < 0) { - cb(new Error(`invalid value, mid: ${message.mid}, parameter: ${parameter}, payload: ${message.payload}`)); - return false; - } - resolutionFields.dataType = dataType; - position.value += 2; + let multiplier = 0; + + if (unit === "200") { + multiplier = 1000; // ms + } else if (unit === "201") { + multiplier = 60000; // ms + } else if (unit === "202") { + multiplier = 1; // ms + } else if (unit === "203") { + multiplier = 3600000; // ms + } else { + multiplier = 1; + } - let unit = buffer.toString(encoding, position.value, position.value + 3).trim(); + while (control < count) { + let traceSample = {}; + traceSample.timeStamp = toTimestamp(timeStamp); + traceSample.value = buffer.toString( + "hex", + position.value, + position.value + 2 + ); + traceSample.value = parseInt(traceSample.value, 16); - if (unit === "" || isNaN(Number(unit)) || Number(unit) < 0) { - cb(new Error(`invalid value, mid: ${message.mid}, parameter: ${parameter}, payload: ${message.payload}`)); - return false; - } - resolutionFields.unit = unit; - resolutionFields.unitName = codes.UNIT[unit] || ""; - position.value += 3; + if ((traceSample.value & 0x8000) > 0) { + traceSample.value = traceSample.value - 0x10000; + } - let timeValue = buffer.toString(encoding, position.value, position.value + length).trim(); + traceSample.value = traceSample.value * coefficient; - if (timeValue === "") { - cb(new Error(`invalid value, mid: ${message.mid}, parameter: ${parameter}, payload: ${message.payload}`)); - return false; - } - resolutionFields.timeValue = timeValue; - position.value += length; + traceSample.timeStamp.setTime( + traceSample.timeStamp.getTime() + timeValue * multiplier * control + ); - message.payload[parameter].push(resolutionFields); + message.payload[parameter].push(traceSample); - control += 1; - } + position.value += 2; + control += 1; } - return true; + } + return true; } module.exports = { - getMids, - testNul, - padLeft, - padRight, - processKey, - processParser, - processDataFields, - processResolutionFields: processResolutionFields, - serializerField, - serializerKey -}; \ No newline at end of file + getMids, + testNul, + padLeft, + padRight, + processKey, + processParser, + processDataFields, + processResolutionFields: processResolutionFields, + processTraceSamples, + serializerField, + serializerKey, +}; diff --git a/src/linkLayer.js b/src/linkLayer.js index e02b46e..2519dfb 100644 --- a/src/linkLayer.js +++ b/src/linkLayer.js @@ -16,7 +16,7 @@ "use strict"; /*jshint esversion: 6, node: true*/ -const util = require('util'); +const util = require("util"); const { Duplex } = require("stream"); const OpenProtocolParser = require("./openProtocolParser"); @@ -25,7 +25,7 @@ const MIDParser = require("./MIDParser"); const MIDSerializer = require("./MIDSerializer"); const constants = require("./constants.json"); -var debug = util.debuglog('open-protocol'); +var debug = util.debuglog("open-protocol"); const POSITIVE_ACK = 9997; const NEGATIVE_ACK = 9998; @@ -34,488 +34,523 @@ const NEGATIVE_ACK = 9998; * This class is responsible for the controller of the link layer of OpenProtocol */ class LinkLayer extends Duplex { + /** + * Create a new object LinkLayer + * @throws {error} + * @param {object} opts + * @param {stream} opts.stream + * @param {number} opts.timeOut + * @param {number} opts.retryTimes + * @param {boolean} opts.rawData + * @param {boolean} opts.disableMidParsing + */ + constructor(opts) { + debug("new LinkLayer", opts); + + opts = opts || {}; + opts.readableObjectMode = true; + opts.writableObjectMode = true; + + super(opts); + + if (opts.stream === undefined) { + debug("LinkLayer constructor err-socket-undefined"); + throw new Error("[LinkLayer] Socket is undefined"); + } - /** - * Create a new object LinkLayer - * @throws {error} - * @param {object} opts - * @param {stream} opts.stream - * @param {number} opts.timeOut - * @param {number} opts.retryTimes - * @param {boolean} opts.rawData - * @param {boolean} opts.disableMidParsing - */ - constructor(opts) { - debug("new LinkLayer", opts); - - opts = opts || {}; - opts.readableObjectMode = true; - opts.writableObjectMode = true; - - super(opts); - - if (opts.stream === undefined) { - debug("LinkLayer constructor err-socket-undefined"); - throw new Error("[LinkLayer] Socket is undefined"); - } - - //Create instances of manipulators - this.opParser = new OpenProtocolParser({ - rawData: opts.rawData - }); - this.opSerializer = new OpenProtocolSerializer(); - this.midParser = new MIDParser(); - this.midSerializer = new MIDSerializer(); - //Create instances of manipulators - - this.stream = opts.stream; - this.timeOut = opts.timeOut || 3000; - this.retryTimes = opts.retryTimes || 3; + //Create instances of manipulators + this.opParser = new OpenProtocolParser({ + rawData: opts.rawData, + }); + this.opSerializer = new OpenProtocolSerializer(); + this.midParser = new MIDParser(); + this.midSerializer = new MIDSerializer(); + //Create instances of manipulators + + this.stream = opts.stream; + this.timeOut = opts.timeOut || 3000; + this.retryTimes = opts.retryTimes || 3; + + //Raw Data + this.rawData = opts.rawData || false; + + //Disable MID Parsing + this.disableMidParsing = opts.disableMidParsing || {}; + + this.linkLayerActive = false; + this.partsOfMessage = []; + this.receiverMessageInParts = 0; + this.numberMessageReceived = 1; + + this.lastMessageReceived = { + mid: 0, + sequenceNumber: 0, + }; + + this.stream.pause(); + + //Errors + this.midSerializer.on("error", (err) => this._onErrorSerializer(err)); + this.opSerializer.on("error", (err) => this._onErrorSerializer(err)); + + //TODO + //Verificar outra tratativa + this.opParser.on("error", (err) => this._onErrorParser(err)); + this.midParser.on("error", (err) => this._onErrorParser(err)); + //Errors + + //SEND DATA + this.midSerializer.on("data", (data) => this._onDataMidSerializer(data)); + this.opSerializer.on("data", (data) => this._onDataOpSerializer(data)); + //SEND DATA + + //RECEIVER DATA + this.stream.on("data", (data) => this._onDataStream(data)); + this.opParser.on("data", (data) => this._onDataOpParser(data)); + this.midParser.on("data", (data) => this._onDataMidParser(data)); + //RECEIVER DATA + } + + _onErrorSerializer(err) { + debug("LinkLayer _onErrorSerializer", err); + + if (this.linkLayerActive) { + this.sequenceNumber--; + } - //Raw Data - this.rawData = opts.rawData || false; + if (this.callbackWrite) { + function doCallback(cb) { + process.nextTick(() => cb()); + } - //Disable MID Parsing - this.disableMidParsing = opts.disableMidParsing || {}; + doCallback(this.callbackWrite); - this.linkLayerActive = false; - this.partsOfMessage = []; - this.receiverMessageInParts = 0; - this.numberMessageReceived = 1; - - this.lastMessageReceived = { - mid: 0, - sequenceNumber: 0 - }; - - this.stream.pause(); - - //Errors - this.midSerializer.on("error", (err) => this._onErrorSerializer(err)); - this.opSerializer.on("error", (err) => this._onErrorSerializer(err)); - - //TODO - //Verificar outra tratativa - this.opParser.on("error", (err) => this._onErrorParser(err)); - this.midParser.on("error", (err) => this._onErrorParser(err)); - //Errors - - //SEND DATA - this.midSerializer.on("data", (data) => this._onDataMidSerializer(data)); - this.opSerializer.on("data", (data) => this._onDataOpSerializer(data)); - //SEND DATA - - //RECEIVER DATA - this.stream.on("data", (data) => this._onDataStream(data)); - this.opParser.on("data", (data) => this._onDataOpParser(data)); - this.midParser.on("data", (data) => this._onDataMidParser(data)); - //RECEIVER DATA + this.callbackWrite = undefined; } - _onErrorSerializer(err) { - debug("LinkLayer _onErrorSerializer", err); - - if (this.linkLayerActive) { - this.sequenceNumber--; - } + this.emit("errorSerializer", err); - if (this.callbackWrite) { - function doCallback(cb) { - process.nextTick(() => cb()); - } + return; + } - doCallback(this.callbackWrite); + _onErrorParser(err) { + debug("LinkLayer _onErrorParser", err); + this.emit("error", err); + return; + } - this.callbackWrite = undefined; - } + _onDataMidSerializer(data) { + debug("LinkLayer _onDataMidSerializer", data); - this.emit("errorSerializer", err); - - return; + if (data.mid !== NEGATIVE_ACK && data.mid !== POSITIVE_ACK && !data.isAck) { + clearTimeout(this.timer); + this.timer = setTimeout(() => this._resendMid(), this.timeOut); } - _onErrorParser(err) { - debug("LinkLayer _onErrorParser", err); - this.emit("error", err); + this.messageParts = 0; + let length = data.payload.length; + + //Multi Parts + if (length > 9979) { + let msgPart = 1; + let parts = length / 9979; + parts = Math.ceil(parts); + data.messageParts = parts; + this.messageParts = parts; + + if (parts > 9) { + this.emit( + "error", + new Error( + `[LinkLayer] number of messages > 9, MID[${data.mid}], length buffer [${length}]` + ) + ); + debug("LinkLayer _onDataMidSerializer err_parts_9", parts); return; - } - - _onDataMidSerializer(data) { - debug("LinkLayer _onDataMidSerializer", data); - - if (data.mid !== NEGATIVE_ACK && data.mid !== POSITIVE_ACK && !data.isAck) { - - clearTimeout(this.timer); - this.timer = setTimeout(() => this._resendMid(), this.timeOut); - } - - this.messageParts = 0; - let length = data.payload.length; - - //Multi Parts - if (length > 9979) { - let msgPart = 1; - let parts = length / 9979; - parts = Math.ceil(parts); - data.messageParts = parts; - this.messageParts = parts; - - if (parts > 9) { - this.emit("error", new Error(`[LinkLayer] number of messages > 9, MID[${data.mid}], length buffer [${length}]`)); - debug("LinkLayer _onDataMidSerializer err_parts_9", parts); - return; - } - - let fullPayload = data.payload; - - while (fullPayload.length > 0) { + } - if (fullPayload.length > 9979) { - data.payload = fullPayload.slice(0, 9979); - fullPayload = fullPayload.slice(9979); - } else { - data.payload = fullPayload; - fullPayload = Buffer.from(""); - } + let fullPayload = data.payload; - data.messageNumber = msgPart; - msgPart += 1; - - this.message = data; - this.opSerializer.write(data); - } - - return; + while (fullPayload.length > 0) { + if (fullPayload.length > 9979) { + data.payload = fullPayload.slice(0, 9979); + fullPayload = fullPayload.slice(9979); + } else { + data.payload = fullPayload; + fullPayload = Buffer.from(""); } - if (data.mid !== POSITIVE_ACK && data.mid !== NEGATIVE_ACK && !data.isAck) { - this.message = data; - } + data.messageNumber = msgPart; + msgPart += 1; + this.message = data; this.opSerializer.write(data); - } + } - _onDataOpSerializer(data) { - debug("LinkLayer _onDataOpSerializer", data); - this.stream.write(data); + return; } - _onDataStream(data) { - debug("LinkLayer _onDataStream", data); - this.opParser.write(data); + if (data.mid !== POSITIVE_ACK && data.mid !== NEGATIVE_ACK && !data.isAck) { + this.message = data; } - _onDataOpParser(data) { - debug("LinkLayer _onDataOpParser", data); - - let duplicateMsg = false; - - if (this.linkLayerActive) { - if (this.lastMessageReceived.mid === data.mid && this.lastMessageReceived.sequenceNumber === data.sequenceNumber) { - duplicateMsg = true; - this.sequenceNumberPartner -= 1; - } - } - - if (data.messageParts !== 0 || this.receiverMessageInParts !== 0) { + this.opSerializer.write(data); + } - this.receiverMessageInParts = data.messageParts; + _onDataOpSerializer(data) { + debug("LinkLayer _onDataOpSerializer", data); + this.stream.write(data); + } - if (data.messageNumber !== this.numberMessageReceived) { + _onDataStream(data) { + debug("LinkLayer _onDataStream", data); + this.opParser.write(data); + } - if (this.linkLayerActive) { - this._sendLinkLayer(NEGATIVE_ACK, data.sequenceNumber, { - midNumber: data.mid, - errorCode: constants.ERROR_LINKLAYER.INCONSISTENCY_MESSAGE_NUMBER - }); - } + _onDataOpParser(data) { + debug("LinkLayer _onDataOpParser", data); - this.emit("error", new Error(`[LinkLayer] inconsistency message number, MID[${data.mid}]`)); - debug("LinkLayer _onDataOpParser err_inconsistency_message_number", data); + let duplicateMsg = false; - return; - } - - this.partsOfMessage.push(data.payload); + if (this.linkLayerActive) { + if ( + this.lastMessageReceived.mid === data.mid && + this.lastMessageReceived.sequenceNumber === data.sequenceNumber + ) { + duplicateMsg = true; + this.sequenceNumberPartner -= 1; + } + } - if (this.receiverMessageInParts === this.numberMessageReceived) { - data.payload = Buffer.concat(this.partsOfMessage); - this.receiverMessageInParts = 0; - this.numberMessageReceived = 1; - this.lastMessageReceived = data; + if (data.messageParts !== 0 || this.receiverMessageInParts !== 0) { + this.receiverMessageInParts = data.messageParts; - if (!duplicateMsg) { + if (data.messageNumber !== this.numberMessageReceived) { + if (this.linkLayerActive) { + this._sendLinkLayer(NEGATIVE_ACK, data.sequenceNumber, { + midNumber: data.mid, + errorCode: constants.ERROR_LINKLAYER.INCONSISTENCY_MESSAGE_NUMBER, + }); + } - if (this.disableMidParsing[data.mid] && (data.mid !== POSITIVE_ACK && data.mid !== NEGATIVE_ACK)) { + this.emit( + "error", + new Error( + `[LinkLayer] inconsistency message number, MID[${data.mid}]` + ) + ); + debug( + "LinkLayer _onDataOpParser err_inconsistency_message_number", + data + ); - if (!this.push(data)) { - this.stream.pause(); - return; - } - } else { - this.midParser.write(data); - } - } - return; - } - this.numberMessageReceived += 1; - return; - } + return; + } - if (this.linkLayerActive) { - if (data.sequenceNumber !== 0) { - if (data.mid === POSITIVE_ACK || data.mid === NEGATIVE_ACK) { - if (data.sequenceNumber !== (this.sequenceNumber)) { - this.emit("error", new Error(`[LinkLayer] sequence number invalid, MID[${data.mid}], received[${data.sequenceNumber}], expected[${this.sequenceNumber}]`)); - debug('LinkLayer _onDataOpParser err_sequence_number_ack_invalid', data.mid, data.sequenceNumber, this.sequenceNumber); - return; - } - } else { - - if (this.sequenceNumberPartner) { - if (this.sequenceNumberPartner === 99) { - this.sequenceNumberPartner = 0; - } - - if (data.sequenceNumber !== (this.sequenceNumberPartner + 1)) { - this._sendLinkLayer(NEGATIVE_ACK, data.sequenceNumber, { - midNumber: data.mid, - errorCode: constants.ERROR_LINKLAYER.INVALID_SEQUENCE_NUMBER - }); - - this.emit("error", new Error(`[LinkLayer] sequence number invalid, MID[${data.mid}]`)); - debug('LinkLayer _onDataOpParser err_sequence_number_invalid', data.mid, data.sequenceNumber, this.sequenceNumberPartner); - return; - } - } - - this.sequenceNumberPartner = data.sequenceNumber; - this._sendLinkLayer(POSITIVE_ACK, data.sequenceNumber, { - midNumber: data.mid - }); - } - } - } + this.partsOfMessage.push(data.payload); + if (this.receiverMessageInParts === this.numberMessageReceived) { + data.payload = Buffer.concat(this.partsOfMessage); + this.receiverMessageInParts = 0; + this.numberMessageReceived = 1; this.lastMessageReceived = data; if (!duplicateMsg) { - if (this.disableMidParsing[data.mid] && (data.mid !== POSITIVE_ACK && data.mid !== NEGATIVE_ACK)) { - - if (!this.push(data)) { - this.stream.pause(); - return; - } - } else { - this.midParser.write(data); + if ( + this.disableMidParsing[data.mid] && + data.mid !== POSITIVE_ACK && + data.mid !== NEGATIVE_ACK + ) { + if (!this.push(data)) { + this.stream.pause(); + return; } - + } else { + this.midParser.write(data); + } } + return; + } + this.numberMessageReceived += 1; + return; } - _onDataMidParser(data) { - debug("LinkLayer _onDataMidParser", data); - - clearTimeout(this.timer); - + if (this.linkLayerActive) { + if (data.sequenceNumber !== 0) { if (data.mid === POSITIVE_ACK || data.mid === NEGATIVE_ACK) { - this._receiverLinkLayer(data); + if (data.sequenceNumber !== this.sequenceNumber) { + this.emit( + "error", + new Error( + `[LinkLayer] sequence number invalid, MID[${data.mid}], received[${data.sequenceNumber}], expected[${this.sequenceNumber}]` + ) + ); + debug( + "LinkLayer _onDataOpParser err_sequence_number_ack_invalid", + data.mid, + data.sequenceNumber, + this.sequenceNumber + ); return; - } - - if (!this.push(data)) { - this.stream.pause(); - return; - } - } - - _write(msg, encoding, callback) { - debug("LinkLayer _write", msg); - - this.callbackWrite = callback; - this.resentTimes = 0; - - if (this.linkLayerActive) { - msg.sequenceNumber = this.sequenceNumber; - this.sequenceNumber += 1; + } + } else { + if (this.sequenceNumberPartner) { + if (this.sequenceNumberPartner === 99) { + this.sequenceNumberPartner = 0; + } - if (this.sequenceNumber > 99) { - this.sequenceNumber = 1; + if (data.sequenceNumber !== this.sequenceNumberPartner + 1) { + this._sendLinkLayer(NEGATIVE_ACK, data.sequenceNumber, { + midNumber: data.mid, + errorCode: constants.ERROR_LINKLAYER.INVALID_SEQUENCE_NUMBER, + }); + + this.emit( + "error", + new Error( + `[LinkLayer] sequence number invalid, MID[${data.mid}]` + ) + ); + debug( + "LinkLayer _onDataOpParser err_sequence_number_invalid", + data.mid, + data.sequenceNumber, + this.sequenceNumberPartner + ); + return; } - } + } - // if this is an ack, callback immediately - if (msg.isAck) { - clearTimeout(this.timer); - process.nextTick(() => { - this.callbackWrite = null; - callback(); - }); + this.sequenceNumberPartner = data.sequenceNumber; + this._sendLinkLayer(POSITIVE_ACK, data.sequenceNumber, { + midNumber: data.mid, + }); } - - this.midSerializer.write(msg); + } } - _read(size) { - debug("LinkLayer _read", size); + this.lastMessageReceived = data; - if (this.stream.isPaused()) { - this.stream.resume(); + if (!duplicateMsg) { + if ( + this.disableMidParsing[data.mid] && + data.mid !== POSITIVE_ACK && + data.mid !== NEGATIVE_ACK + ) { + if (!this.push(data)) { + this.stream.pause(); + return; } + } else { + this.midParser.write(data); + } } + } - _destroy() { - debug("LinkLayer _destroy"); + _onDataMidParser(data) { + debug("LinkLayer _onDataMidParser", data); - clearTimeout(this.timer); + clearTimeout(this.timer); - function destroyStream(stream){ - // handles Node versions older than 8.x - if (typeof stream.destroy === 'function') { - stream.destroy(); - } else { - stream._destroy(); - } - } + if (data.mid === POSITIVE_ACK || data.mid === NEGATIVE_ACK) { + this._receiverLinkLayer(data); + return; + } - destroyStream(this.opParser); - destroyStream(this.opSerializer); - destroyStream(this.midParser); - destroyStream(this.midSerializer); + if (!this.push(data)) { + this.stream.pause(); + return; } + } - finishCycle(err) { - debug("LinkLayer finishCycle", err); + _write(msg, encoding, callback) { + debug("LinkLayer _write", msg); - if (this.callbackWrite) { - this.callbackWrite(err); - this.callbackWrite = undefined; - } - } + this.callbackWrite = callback; + this.resentTimes = 0; - /** - * Enable LinkLayer - */ - activateLinkLayer() { - debug("LinkLayer activateLinkLayer"); + if (this.linkLayerActive) { + msg.sequenceNumber = this.sequenceNumber; + this.sequenceNumber += 1; - this.linkLayerActive = true; + if (this.sequenceNumber > 99) { this.sequenceNumber = 1; + } } - /** - * Disable LinkLayer - */ - deactivateLinkLayer() { - debug("LinkLayer deactivateLinkLayer"); - - this.linkLayerActive = false; - clearTimeout(this.timer); + // if this is an ack, callback immediately + if (msg.isAck) { + clearTimeout(this.timer); + process.nextTick(() => { + this.callbackWrite = null; + callback(); + }); } - /** - * @private - * @param {*} data - */ - _receiverLinkLayer(data) { - debug("LinkLayer _receiverLinkLayer", data); - - clearTimeout(this.timer); - - if (data.mid === NEGATIVE_ACK || data.payload.midNumber !== this.message.mid || data.sequenceNumber !== this.sequenceNumber) { - - let err = new Error(`incorrect fields of MID, MID[${data.payload.midNumber}] - Error code [${data.payload.errorCode}] -` + - ` Expect MID[${this.message.mid}] - Expect SequenceNumber [${this.sequenceNumber}] - Current SequenceNumber [${data.sequenceNumber}]`); + this.midSerializer.write(msg); + } - if (this.callbackWrite) { + _read(size) { + debug("LinkLayer _read", size); - function doCallback(cb, err) { - process.nextTick(() => cb(err)); - } - - doCallback(this.callbackWrite, err); - - this.callbackWrite = undefined; + if (this.stream.isPaused()) { + this.stream.resume(); + } + } - } else { - - debug('LinkLayer _receiverLinkLayer err-incorrect_fields_MID', this.message.mid, this.sequenceNumber); - this.emit("error", err); - } - return; - } + _destroy() { + debug("LinkLayer _destroy"); - this.message = {}; + clearTimeout(this.timer); - if (this.callbackWrite) { + function destroyStream(stream) { + // handles Node versions older than 8.x + if (typeof stream.destroy === "function") { + stream.destroy(); + } else { + stream._destroy(); + } + } - function doCallback(cb) { - process.nextTick(() => cb()); - } + destroyStream(this.opParser); + destroyStream(this.opSerializer); + destroyStream(this.midParser); + destroyStream(this.midSerializer); + } - doCallback(this.callbackWrite); + finishCycle(err) { + debug("LinkLayer finishCycle", err); - this.callbackWrite = undefined; - } + if (this.callbackWrite) { + this.callbackWrite(err); + this.callbackWrite = undefined; } - - /** - * @private - * @param {*} mid - * @param {*} sequenceNumber - * @param {*} payload - */ - _sendLinkLayer(mid, sequenceNumber, payload) { - debug("LinkLayer _sendLinkLayer", mid, sequenceNumber, payload); - - if (sequenceNumber === 99) { - sequenceNumber = 0; + } + + /** + * Enable LinkLayer + */ + activateLinkLayer() { + debug("LinkLayer activateLinkLayer"); + + this.linkLayerActive = true; + this.sequenceNumber = 1; + } + + /** + * Disable LinkLayer + */ + deactivateLinkLayer() { + debug("LinkLayer deactivateLinkLayer"); + + this.linkLayerActive = false; + clearTimeout(this.timer); + } + + /** + * @private + * @param {*} data + */ + _receiverLinkLayer(data) { + debug("LinkLayer _receiverLinkLayer", data); + + clearTimeout(this.timer); + + if ( + data.mid === NEGATIVE_ACK || + data.payload.midNumber !== this.message.mid || + data.sequenceNumber !== this.sequenceNumber + ) { + let err = new Error( + `incorrect fields of MID, MID[${data.payload.midNumber}] - Error code [${data.payload.errorCode}] -` + + ` Expect MID[${this.message.mid}] - Expect SequenceNumber [${this.sequenceNumber}] - Current SequenceNumber [${data.sequenceNumber}]` + ); + + if (this.callbackWrite) { + function doCallback(cb, err) { + process.nextTick(() => cb(err)); } - let msg = { - mid: mid, - sequenceNumber: (sequenceNumber + 1), - payload - }; + doCallback(this.callbackWrite, err); - this.midSerializer.write(msg); + this.callbackWrite = undefined; + } else { + debug( + "LinkLayer _receiverLinkLayer err-incorrect_fields_MID", + this.message.mid, + this.sequenceNumber + ); + this.emit("error", err); + } + return; } - /** - * @private - */ - _resendMid() { - debug("LinkLayer _resendMid"); + this.message = {}; - clearTimeout(this.timer); + if (this.callbackWrite) { + function doCallback(cb) { + process.nextTick(() => cb()); + } - if (this.resentTimes < this.retryTimes) { - this.timer = setTimeout(() => this._resendMid(), this.timeOut); - this.opSerializer.write(this.message); - this.resentTimes += 1; + doCallback(this.callbackWrite); - } else { + this.callbackWrite = undefined; + } + } + + /** + * @private + * @param {*} mid + * @param {*} sequenceNumber + * @param {*} payload + */ + _sendLinkLayer(mid, sequenceNumber, payload) { + debug("LinkLayer _sendLinkLayer", mid, sequenceNumber, payload); + + if (sequenceNumber === 99) { + sequenceNumber = 0; + } - let err = new Error(`[LinkLayer] timeout send MID[${this.message.mid}]`); + let msg = { + mid: mid, + sequenceNumber: sequenceNumber + 1, + payload, + }; - this.resentTimes = 0; + this.midSerializer.write(msg); + } - if (this.callbackWrite) { + /** + * @private + */ + _resendMid() { + debug("LinkLayer _resendMid"); - function doCallback(cb, err) { - process.nextTick(() => cb(err)); - } + clearTimeout(this.timer); - doCallback(this.callbackWrite, err); + if (this.resentTimes < this.retryTimes) { + this.timer = setTimeout(() => this._resendMid(), this.timeOut); + this.opSerializer.write(this.message); + this.resentTimes += 1; + } else { + let err = new Error(`[LinkLayer] timeout send MID[${this.message.mid}]`); - this.callbackWrite = undefined; + this.resentTimes = 0; - } else { - debug('LinkLayer _resendMid err-timeout_send_MID', this.message.mid); - this.emit("error", err); - } + if (this.callbackWrite) { + function doCallback(cb, err) { + process.nextTick(() => cb(err)); } + + doCallback(this.callbackWrite, err); + + this.callbackWrite = undefined; + } else { + debug("LinkLayer _resendMid err-timeout_send_MID", this.message.mid); + this.emit("error", err); + } } + } } module.exports = LinkLayer; diff --git a/src/mid/0900.js b/src/mid/0900.js new file mode 100644 index 0000000..c93202d --- /dev/null +++ b/src/mid/0900.js @@ -0,0 +1,277 @@ +//@ts-check +/* + Copyright 2018 Smart-Tech Controle e Automação + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +"use strict"; +/*jshint esversion: 6, node: true*/ + +/* + MID 0900 + [xxxxxxxxxx] [xxx...xxx] [xxx] [[][][][][]] [xx] [xx] [xxx] + (10) 0-9 (19) 10-28 (3) 29-31 (n) 32-n (2) n (2) n (3) n + resultID timeStamp numberPID dataFields traceType transducerType unit + + [xxx] [[][][][][]] [xxxxx] [0x00] [xx] + (3) n (n) n (5) n (1) n (2) n + numberResolution resolutionFields numberTrace NUL character traceSample + + Data fields + [xxxxx] [xxx] [xx] [xxx] [xxxx] [xxx...xxx] + (5) 0-4 (3) 5-7 (2) 8-9 (3) 10-12 (4) 13-16 (n) 17-n + parameterID lenght dataType unit stepNumber dataValue + + Resolution fields + [xxxxx] [xxxxx] [xxx] [xx] [xxx] [xxx...xxx] + (5) 0-4 (5) 5-9 (3) 10-12 (2) 13-14 (3) 15-17 (n) 18-n + firstIndex lastIndex length dataType unit timeValue +*/ +/* + payload: { + resultID: {number} + timeStamp: {string} + numberPID: {number} + dataFields: {object} + traceType: {number} + transducerType: {number} + unit: {number} + numberResolution: {number} + resolutionFields: {object} + numberTrace: {number} + traceSample: {string} + } + + dataField: { + parameterID: {number} + lenght: {number} + dataType: {number} + unit: {number} + stepNumber: {number} + dataValue: {string} + } + + resolutionField:{ + firstIndex: {number} + lastIndex: {number} + length: {number} + dataType: {number} + unit: {number} + timeValue: {string} + } +*/ + +/** + * @class + * @name MID0900 + * @param {object} MID0900_1.payload REV. 1 + * @param {number} MID0900_1.payload.resultID + * @param {string} MID0900_1.payload.timeStamp + * @param {number} MID0900_1.payload.numberPID + * @param {object} MID0900_1.payload.dataFields + * @param {number} MID0900_1.payload.traceType + * @param {number} MID0900_1.payload.transducerType + * @param {number} MID0900_1.payload.unit + * @param {number} MID0900_1.payload.numberResolution + * @param {object} MID0900_1.payload.resolutionFields + * @param {number} MID0900_1.payload.numberTrace + * @param {string} MID0900_1.payload.traceSample + + * @param {object} MID0900_1.dataField + * @param {number} MID0900_1.dataField.parameterID + * @param {number} MID0900_1.dataField.lenght + * @param {number} MID0900_1.dataField.dataType + * @param {number} MID0900_1.dataField.unit + * @param {number} MID0900_1.dataField.stepNumber + * @param {string} MID0900_1.dataField.dataValue + * + * @param {object} MID0900_1.resolutionField + * @param {number} MID0900_1.resolutionField.firstIndex + * @param {number} MID0900_1.resolutionField.lastIndex + * @param {number} MID0900_1.resolutionField.length + * @param {number} MID0900_1.resolutionField.dataType + * @param {number} MID0900_1.resolutionField.unit + * @param {string} MID0900_2.resolutionField.timeValue + */ + +const helpers = require("../helpers.js"); +const testNul = helpers.testNul; +const processParser = helpers.processParser; +const serializerField = helpers.serializerField; +const processDataFields = helpers.processDataFields; +const processResolutionFields = helpers.processResolutionFields; +const processTraceSamples = helpers.processTraceSamples; + +function parser(msg, opts, cb) { + let buffer = msg.payload; + msg.payload = {}; + + let traceLength = buffer.length; + + var position = { value: 0 }; + + switch (msg.revision) { + case 1: + processParser(msg, buffer, "resultID", "number", 10, position, cb) && + processParser(msg, buffer, "timeStamp", "string", 19, position, cb) && + processParser(msg, buffer, "numberPID", "number", 3, position, cb) && + processDataFields( + msg, + buffer, + "fieldPID", + msg.payload.numberPID, + position, + cb + ) && + processParser(msg, buffer, "traceType", "number", 2, position, cb) && + processParser( + msg, + buffer, + "transducerType", + "number", + 2, + position, + cb + ) && + processParser(msg, buffer, "unit", "string", 3, position, cb) && + processParser(msg, buffer, "numberData", "number", 3, position, cb) && + processDataFields( + msg, + buffer, + "fieldData", + msg.payload.numberData, + position, + cb + ) && + processParser( + msg, + buffer, + "numberResolution", + "number", + 3, + position, + cb + ) && + processResolutionFields( + msg, + buffer, + "resolutionFields", + msg.payload.numberResolution, + position, + cb + ) && + processParser(msg, buffer, "numberTrace", "number", 5, position, cb) && + testNul(msg, buffer, "char nul", position, cb) && + processTraceSamples( + msg, + buffer, + "sampleTrace", + msg.payload.numberTrace, + position, + msg.payload.timeStamp, + msg.payload.resolutionFields[0].timeValue, + msg.payload.resolutionFields[0].unit, + cb + ) && + cb(null, msg); + + break; + } +} + +function serializer(msg, opts, cb) { + let buf; + let statusprocess = false; + + let position = { + value: 0, + }; + + if (msg.isAck) { + msg.mid = 5; + let buf = Buffer.from("0900"); + msg.payload = buf; + cb(null, msg); + return; + } + + msg.revision = msg.revision || 1; + + // Automatic subscription to last 3 curves: Angle, Torque and Current. Payload not needed. + /* { + msg.payload.midNumber = 0900; + msg.payload.dataLength = 41; + msg.payload.extraData = "00000000000000000000000000000003001002003"; + msg.payload.revision = 1; + msg.payload.midNumber = 0900; + }*/ + + switch (msg.revision) { + case 1: + msg.mid = 8; + + // Automatic subscription to last 3 curves: Angle, Torque and Current. Payload not needed. + if ( + (msg.payload.midNumber || + msg.payload.dataLength || + msg.payload.extraData || + msg.payload.revision) === undefined + ) { + buf = Buffer.from("09000014100000000000000000000000000000003001002003"); + } else { + buf = Buffer.alloc(9 + msg.payload.dataLength); + position.value = 9 + msg.payload.dataLength; + statusprocess = + serializerField( + msg, + buf, + "extraData", + "string", + msg.payload.dataLength, + position, + cb + ) && + serializerField(msg, buf, "dataLength", "number", 2, position, cb) && + serializerField(msg, buf, "revision", "number", 3, position, cb) && + serializerField(msg, buf, "midNumber", "number", 4, position, cb); + + if (!statusprocess) { + return; + } + } + + msg.payload = buf; + + cb(null, msg); + + break; + + default: + cb( + new Error( + `[Serializer MID${msg.mid}] invalid revision [${msg.revision}]` + ) + ); + break; + } +} + +function revision() { + return [1]; +} + +module.exports = { + parser, + serializer, + revision, +}; diff --git a/src/mid/0900.js.sus b/src/mid/0900.js.sus deleted file mode 100644 index c021bf5..0000000 --- a/src/mid/0900.js.sus +++ /dev/null @@ -1,213 +0,0 @@ -/* - Copyright 2018 Smart-Tech Controle e Automação - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -"use strict"; -/*jshint esversion: 6, node: true*/ - -/* - MID 0900 - [xxxxxxxxxx] [xxx...xxx] [xxx] [[][][][][]] [xx] [xx] [xxx] - (10) 0-9 (19) 10-28 (3) 29-31 (n) 32-n (2) n (2) n (3) n - resultID timeStamp numberPID dataFields traceType transducerType unit - - [xxx] [[][][][][]] [xxxxx] [0x00] [xx] - (3) n (n) n (5) n (1) n (2) n - numberResolution resolutionFields numberTrace NUL character traceSample - - Data fields - [xxxxx] [xxx] [xx] [xxx] [xxxx] [xxx...xxx] - (5) 0-4 (3) 5-7 (2) 8-9 (3) 10-12 (4) 13-16 (n) 17-n - parameterID lenght dataType unit stepNumber dataValue - - Resolution fields - [xxxxx] [xxxxx] [xxx] [xx] [xxx] [xxx...xxx] - (5) 0-4 (5) 5-9 (3) 10-12 (2) 13-14 (3) 15-17 (n) 18-n - firstIndex lastIndex length dataType unit timeValue -*/ -/* - payload: { - resultID: {number} - timeStamp: {string} - numberPID: {number} - dataFields: {object} - traceType: {number} - transducerType: {number} - unit: {number} - numberResolution: {number} - resolutionFields: {object} - numberTrace: {number} - traceSample: {string} - } - - dataField: { - parameterID: {number} - lenght: {number} - dataType: {number} - unit: {number} - stepNumber: {number} - dataValue: {string} - } - - resolutionField:{ - firstIndex: {number} - lastIndex: {number} - length: {number} - dataType: {number} - unit: {number} - timeValue: {string} - } -*/ - -const helpers = require("../helpers.js"); -const padLeft = helpers.padLeft; -const padRight = helpers.padRight; -const testNul = helpers.testNul; -const processParser = helpers.processParser; -const processDataFields = helpers.processDataFields; -const processResolutionFields = helpers.processResolutionFields; - -function parser(msg, opts, cb){ - - let buffer = msg.payload; - msg.payload = {}; - - var position = {value: 0}; - - switch(msg.revision){ - - case 1: - - processParser(msg, buffer, "resultID", "number", 10, position, cb) && - processParser(msg, buffer, "timeStamp", "string", 19, position, cb) && - processParser(msg, buffer, "numberPID", "number", 3, position, cb) && - processDataFields(msg, buffer, "fieldPID", msg.payload.numberPID, position, cb) && - processParser(msg, buffer, "traceType", "number", 2, position, cb) && - processParser(msg, buffer, "transducerType", "number", 2, position, cb) && - processParser(msg, buffer, "unit", "string", 3, position, cb) && - processParser(msg, buffer, "numberData", "number", 3, position, cb) && - processDataFields(msg, buffer, "fieldData", msg.payload.numberData, position, cb) && - processParser(msg, buffer, "numberResolution", "number", 3, position, cb) && - processResolutionFields(msg, buffer, "resolutionFields", msg.payload.numberResolution, position, cb) && - processParser(msg, buffer, "numberTrace", "number", 5, position, cb) && - testNul(msg, buffer, "char nul", position, cb) && - processParser(msg, buffer, "traceSample", "string", 2, position, cb) && - cb(null, msg); - - break; - } -} - -function serializer(msg, opts, cb){ - - let info = msg.payload; - let buf; - - switch(msg.revision){ - - case 1: - - //Calc alloc - //Message Base (50) - //fieldPID - //fieldData (17 + length) x numberPID - //resolutionFields (18 + length) x numberResolution - - let size = 50; - - if(info.dataFields.length > 0){ - for(let x = 0; x < info.dataFields.length; x++){ - size += info.dataFields[x].length; - } - } - size += (17 * info.dataFields.length); - - if(info.resolutionFields.length > 0){ - for(let x = 0; x < info.resolutionFields.length; x++){ - size += info.resolutionFields[x].length; - } - } - size += (18 * info.resolutionFields.length); - - buf = Buffer.alloc(size); - - let pos = 0; - - buf.write(padLeft(info.resultID, 10), pos, "ascii"); - pos += 10; - - buf.write(padRight(info.timeStamp, 19, 10, " "), pos, "ascii"); - pos += 19; - - buf.write(padLeft(info.numberPID, 3), pos, "ascii"); - pos += 3; - - //info.dataFields - if(info.numberPID > 0){ - for(let x = 0; x < info.numberPID; x++){ - - buf.write(padLeft(info.dataFields[x].parameterID, 5), pos, "ascii"); - pos += 5; - - buf.write(padLeft(info.dataFields[x].length, 3), pos, "ascii"); - pos += 3; - - buf.write(padLeft(info.dataFields[x].dataType, 2), pos, "ascii"); - pos += 2; - - buf.write(padLeft(info.dataFields[x].unit, 3), pos, "ascii"); - pos += 3; - - buf.write(padLeft(info.dataFields[x].stepNumber, 4), pos, "ascii"); - pos += 4; - - buf.write(padRight(info.dataValue, info.dataFields[x].length, 10, " "), pos, "ascii"); - pos += info.dataFields[x].length; - - } - } - - buf.write(padLeft(info.traceType, 2), pos, "ascii"); - pos += 2; - - buf.write(padLeft(info.transducerType, 2), pos, "ascii"); - pos += 2; - - buf.write(padLeft(info.unit, 3), pos, "ascii"); - pos += 3; - - buf.write(padLeft(info.numberResolution, 3), pos, "ascii"); - pos += 3; - - - - - - - - - - break; - - } - - msg.payload = buf; - - cb(null, msg); -} - -module.exports = { - parser, - serializer -}; \ No newline at end of file diff --git a/src/midCommand.json b/src/midCommand.json index 74df6e0..fd1d413 100644 --- a/src/midCommand.json +++ b/src/midCommand.json @@ -97,7 +97,7 @@ }, "resetDigitalInputFunction": { "request": 225 - }, + }, "userDataDownload": { "request": 240, "reply": 242 @@ -132,4 +132,4 @@ "selectMode": { "request": 2606 } -} \ No newline at end of file +} diff --git a/src/midData.json b/src/midData.json index 471408b..f4ffa33 100644 --- a/src/midData.json +++ b/src/midData.json @@ -20,5 +20,6 @@ "262": "toolTagID", "401": "automaticManualMode", "421": "openProtocolCommandsDisabled", - "501": "motorTuningResultData" -} \ No newline at end of file + "501": "motorTuningResultData", + "900": "traceData" +} diff --git a/src/midGroups.json b/src/midGroups.json index 50a26ee..7f38f39 100644 --- a/src/midGroups.json +++ b/src/midGroups.json @@ -64,7 +64,7 @@ "unsubscribe": 103, "ack": 102, "generic": true - }, + }, "lastPowerMACSTighteningResultStationData": { "data": 106, "subscribe": 105, @@ -148,5 +148,12 @@ "unsubscribe": 503, "ack": 502, "generic": true + }, + "traceData": { + "data": 900, + "subscribe": 900, + "unsubscribe": 9, + "ack": 900, + "generic": true } -} \ No newline at end of file +} diff --git a/src/openProtocolParser.js b/src/openProtocolParser.js index f01ecb7..da07071 100644 --- a/src/openProtocolParser.js +++ b/src/openProtocolParser.js @@ -16,243 +16,255 @@ "use strict"; /*jshint esversion: 6, node: true*/ -const util = require('util'); -const { Transform } = require('stream'); +const util = require("util"); +const { Transform } = require("stream"); const constants = require("./constants.json"); const encodingOP = constants.defaultEncoder; -var debug = util.debuglog('open-protocol'); +var debug = util.debuglog("open-protocol"); class OpenProtocolParser extends Transform { - - /** - * @class OpenProtocolParser - * @description This class performs the parsing of the MID header. - * This transforms MID (Buffer) in MID (Object). - * @param {Object} opts an object with the option passed to the constructor - */ - constructor(opts) { - opts = opts || {}; - opts.readableObjectMode = true; - opts.decodeStrings = true; - - super(opts); - - this.rawData = opts.rawData || false; - this._nBuffer = null; - debug("new OpenProtocolParser"); + /** + * @class OpenProtocolParser + * @description This class performs the parsing of the MID header. + * This transforms MID (Buffer) in MID (Object). + * @param {Object} opts an object with the option passed to the constructor + */ + constructor(opts) { + opts = opts || {}; + opts.readableObjectMode = true; + opts.decodeStrings = true; + + super(opts); + + this.rawData = opts.rawData || false; + this._nBuffer = null; + debug("new OpenProtocolParser"); + } + + _transform(chunk, encoding, cb) { + debug("OpenProtocolParser _transform", chunk); + + let ptr = 0; + + if (this._nBuffer !== null) { + chunk = Buffer.concat([this._nBuffer, chunk]); + this._nBuffer = null; } - _transform(chunk, encoding, cb) { - debug("OpenProtocolParser _transform", chunk); - - let ptr = 0; - - if (this._nBuffer !== null) { - chunk = Buffer.concat([this._nBuffer, chunk]); - this._nBuffer = null; - } - - if (chunk.length < 20) { - this._nBuffer = chunk; - cb(); - return; - } - - while (ptr < chunk.length) { - - let obj = {}; - let startPtr = ptr; - - let length = chunk.toString(encodingOP, ptr, ptr + 4); - - length = Number(length); + if (chunk.length < 20) { + this._nBuffer = chunk; + cb(); + return; + } - if (isNaN(length) || length < 1 || length > 9999) { + while (ptr < chunk.length) { + let obj = {}; + let startPtr = ptr; - let e = new Error(`Invalid length [${length}]`); - e.errno = constants.ERROR_LINKLAYER.INVALID_LENGTH; + let length = chunk.toString(encodingOP, ptr, ptr + 4); - cb(e); + length = Number(length); - debug("OpenProtocolParser _transform err-length:", ptr, chunk); - return; - } + if (isNaN(length) || length < 1 || length > 9999) { + let e = new Error(`Invalid length [${length}]`); + e.errno = constants.ERROR_LINKLAYER.INVALID_LENGTH; - if (chunk.length < (ptr + length + 1)) { - this._nBuffer = chunk.slice(ptr); - cb(); - return; - } + cb(e); - if (chunk[ptr + length] !== 0) { - let e = new Error(`Invalid message [${chunk.toString()}]`); - e.errno = constants.ERROR_LINKLAYER.INVALID_LENGTH; - cb(e); - debug("OpenProtocolParser _transform err-message:", ptr, chunk); - return; - } + debug("OpenProtocolParser _transform err-length:", ptr, chunk); + return; + } - ptr += 4; + let mid = chunk.toString(encodingOP, ptr + 4, ptr + 8); + obj.mid = Number(mid); - let mid = chunk.toString(encodingOP, ptr, ptr + 4); - obj.mid = Number(mid); + // For MID 0900 only, incoming data does not have a null terminator. + if ( + chunk.length < ptr + length + 1 && + !(obj.mid === 900 && chunk.length === length) + ) { + this._nBuffer = chunk.slice(ptr); + cb(); + return; + } + if (chunk[chunk.length - 1] !== 0 && obj.mid !== 900) { + let e = new Error(`Invalid message [${chunk.toString()}]`); + e.errno = constants.ERROR_LINKLAYER.INVALID_LENGTH; + cb(e); + debug("OpenProtocolParser _transform err-message:", ptr, chunk); + return; + } - if (isNaN(obj.mid) || obj.mid < 1 || obj.mid > 9999) { - cb(new Error(`Invalid MID [${mid}]`)); - debug("OpenProtocolParser _transform err-mid:", ptr, chunk); - return; - } + ptr += 4; - ptr += 4; + if (isNaN(obj.mid) || obj.mid < 1 || obj.mid > 9999) { + cb(new Error(`Invalid MID [${mid}]`)); + debug("OpenProtocolParser _transform err-mid:", ptr, chunk); + return; + } - let revision = chunk.toString(encodingOP, ptr, ptr + 3); + ptr += 4; - if (revision === " ") { - revision = 1; - } + let revision = chunk.toString(encodingOP, ptr, ptr + 3); - obj.revision = Number(revision); + if (revision === " ") { + revision = 1; + } - if (isNaN(obj.revision) || obj.revision < 0 || obj.revision > 999) { - let e = new Error(`Invalid revision [${revision}]`); - e.errno = constants.ERROR_LINKLAYER.INVALID_REVISION; - e.obj = obj; - cb(e); - debug("OpenProtocolParser _transform err-revision:", ptr, chunk); - return; - } + obj.revision = Number(revision); - if (obj.revision === 0) { - obj.revision = 1; - } + if (isNaN(obj.revision) || obj.revision < 0 || obj.revision > 999) { + let e = new Error(`Invalid revision [${revision}]`); + e.errno = constants.ERROR_LINKLAYER.INVALID_REVISION; + e.obj = obj; + cb(e); + debug("OpenProtocolParser _transform err-revision:", ptr, chunk); + return; + } - ptr += 3; + if (obj.revision === 0) { + obj.revision = 1; + } - let noAck = chunk.toString(encodingOP, ptr, ptr + 1); + ptr += 3; - if (noAck === " ") { - noAck = 0; - } + let noAck = chunk.toString(encodingOP, ptr, ptr + 1); - obj.noAck = Number(noAck); + if (noAck === " ") { + noAck = 0; + } + obj.noAck = Number(noAck); - if (isNaN(obj.noAck) || obj.noAck < 0 || obj.noAck > 1) { - cb(new Error(`Invalid no ack [${obj.noAck}]`)); - debug("OpenProtocolParser _transform err-no-ack:", ptr, chunk); - return; - } + if (isNaN(obj.noAck) || obj.noAck < 0 || obj.noAck > 1) { + cb(new Error(`Invalid no ack [${obj.noAck}]`)); + debug("OpenProtocolParser _transform err-no-ack:", ptr, chunk); + return; + } - obj.noAck = Boolean(obj.noAck); + obj.noAck = Boolean(obj.noAck); - ptr += 1; + ptr += 1; - let stationID = chunk.toString(encodingOP, ptr, ptr + 2); + let stationID = chunk.toString(encodingOP, ptr, ptr + 2); - if (stationID === " ") { - stationID = 1; - } + if (stationID === " ") { + stationID = 1; + } - obj.stationID = Number(stationID); + obj.stationID = Number(stationID); - if (isNaN(obj.stationID) || obj.stationID < 0 || obj.stationID > 99) { - cb(new Error(`Invalid station id [${obj.stationID}]`)); - debug("OpenProtocolParser _transform err-station-id:", ptr, chunk); - return; - } + if (isNaN(obj.stationID) || obj.stationID < 0 || obj.stationID > 99) { + cb(new Error(`Invalid station id [${obj.stationID}]`)); + debug("OpenProtocolParser _transform err-station-id:", ptr, chunk); + return; + } - if (obj.stationID === 0) { - obj.stationID = 1; - } + if (obj.stationID === 0) { + obj.stationID = 1; + } - ptr += 2; + ptr += 2; - let spindleID = chunk.toString(encodingOP, ptr, ptr + 2); + let spindleID = chunk.toString(encodingOP, ptr, ptr + 2); - if (spindleID === " ") { - spindleID = 1; - } + if (spindleID === " ") { + spindleID = 1; + } - obj.spindleID = Number(spindleID); + obj.spindleID = Number(spindleID); - if (isNaN(obj.spindleID) || obj.spindleID < 0 || obj.spindleID > 99) { - cb(new Error(`Invalid spindle id [${obj.spindleID}]`)); - debug("OpenProtocolParser _transform err-spindle-id:", ptr, chunk); - return; - } + if (isNaN(obj.spindleID) || obj.spindleID < 0 || obj.spindleID > 99) { + cb(new Error(`Invalid spindle id [${obj.spindleID}]`)); + debug("OpenProtocolParser _transform err-spindle-id:", ptr, chunk); + return; + } - if (obj.spindleID === 0) { - obj.spindleID = 1; - } + if (obj.spindleID === 0) { + obj.spindleID = 1; + } - ptr += 2; + ptr += 2; - let sequenceNumber = chunk.toString(encodingOP, ptr, ptr + 2); + let sequenceNumber = chunk.toString(encodingOP, ptr, ptr + 2); - if (sequenceNumber === " ") { - sequenceNumber = 0; - } + if (sequenceNumber === " ") { + sequenceNumber = 0; + } - obj.sequenceNumber = Number(sequenceNumber); + obj.sequenceNumber = Number(sequenceNumber); - if (isNaN(obj.sequenceNumber) || obj.sequenceNumber < 0 || obj.sequenceNumber > 99) { - cb(new Error(`Invalid sequence number [${obj.sequenceNumber}]`)); - debug("OpenProtocolParser _transform err-sequence-number:", ptr, chunk); - return; - } + if ( + isNaN(obj.sequenceNumber) || + obj.sequenceNumber < 0 || + obj.sequenceNumber > 99 + ) { + cb(new Error(`Invalid sequence number [${obj.sequenceNumber}]`)); + debug("OpenProtocolParser _transform err-sequence-number:", ptr, chunk); + return; + } - ptr += 2; + ptr += 2; - let messageParts = chunk.toString(encodingOP, ptr, ptr + 1); + let messageParts = chunk.toString(encodingOP, ptr, ptr + 1); - if (messageParts === " ") { - messageParts = 0; - } + if (messageParts === " ") { + messageParts = 0; + } - obj.messageParts = Number(messageParts); + obj.messageParts = Number(messageParts); - if (isNaN(obj.messageParts) || obj.messageParts < 0 || obj.messageParts > 9) { - cb(new Error(`Invalid message parts [${obj.messageParts}]`)); - debug("OpenProtocolParser _transform err-message-parts:", ptr, chunk); - return; - } + if ( + isNaN(obj.messageParts) || + obj.messageParts < 0 || + obj.messageParts > 9 + ) { + cb(new Error(`Invalid message parts [${obj.messageParts}]`)); + debug("OpenProtocolParser _transform err-message-parts:", ptr, chunk); + return; + } - ptr += 1; + ptr += 1; - let messageNumber = chunk.toString(encodingOP, ptr, ptr + 1); + let messageNumber = chunk.toString(encodingOP, ptr, ptr + 1); - if (messageNumber === " ") { - messageNumber = 0; - } + if (messageNumber === " ") { + messageNumber = 0; + } - obj.messageNumber = Number(messageNumber); + obj.messageNumber = Number(messageNumber); + if ( + isNaN(obj.messageNumber) || + obj.messageNumber < 0 || + obj.messageNumber > 9 + ) { + cb(new Error(`Invalid message number [${obj.messageNumber}]`)); + debug("OpenProtocolParser _transform err-message-number:", ptr, chunk); + return; + } - if (isNaN(obj.messageNumber) || obj.messageNumber < 0 || obj.messageNumber > 9) { - cb(new Error(`Invalid message number [${obj.messageNumber}]`)); - debug("OpenProtocolParser _transform err-message-number:", ptr, chunk); - return; - } + ptr += 1; - ptr += 1; + obj.payload = chunk.slice(ptr, ptr + length - 20); - obj.payload = chunk.slice(ptr, (ptr + length - 20)); + ptr += length - 20 + 1; - ptr += (length - 20) + 1; + if (mid == 900) ptr = ptr - 1; - if (this.rawData) { - obj._raw = chunk.slice(startPtr, ptr); - } + if (this.rawData) { + obj._raw = chunk.slice(startPtr, ptr); + } - this.push(obj); - } - cb(); + this.push(obj); } + cb(); + } - _destroy() { - //no-op, needed to handle older node versions - } + _destroy() { + //no-op, needed to handle older node versions + } } module.exports = OpenProtocolParser; diff --git a/src/openProtocolSerializer.js b/src/openProtocolSerializer.js index f5bee63..9bba3d8 100644 --- a/src/openProtocolSerializer.js +++ b/src/openProtocolSerializer.js @@ -16,8 +16,8 @@ "use strict"; /*jshint esversion: 6, node: true*/ -const util = require('util'); -const { Transform } = require('stream'); +const util = require("util"); +const { Transform } = require("stream"); const constants = require("./constants.json"); const encodingOP = constants.defaultEncoder; @@ -25,7 +25,7 @@ const encodingOP = constants.defaultEncoder; const helpers = require("./helpers.js"); const pad = helpers.padLeft; -var debug = util.debuglog('open-protocol'); +var debug = util.debuglog("open-protocol"); /** * @class @@ -43,138 +43,165 @@ var debug = util.debuglog('open-protocol'); */ class OpenProtocolSerializer extends Transform { + /** + * @class OpenProtocolSerializer + * @description This class performs the serialization of the MID header. + * This transforms MID (object) in MID (Buffer). + * @param {Object} opts an object with the option passed to the constructor + */ + constructor(opts) { + opts = opts || {}; + opts.writableObjectMode = true; + super(opts); + debug("new openProtocolSerializer"); + } + + _transform(chunk, encoding, cb) { + debug("openProtocolSerializer _transform", chunk); + + chunk.mid = Number(chunk.mid); + + if (isNaN(chunk.mid) || chunk.mid < 1 || chunk.mid > 9999) { + cb(new Error(`Invalid MID [${chunk.mid}]`)); + debug("openProtocolSerializer _transform err-mid:", chunk); + return; + } + + if ( + chunk.revision === " " || + chunk.revision === 0 || + chunk.revision === undefined + ) { + chunk.revision = 1; + } + + chunk.revision = Number(chunk.revision); + + if (isNaN(chunk.revision) || chunk.revision < 0 || chunk.revision > 999) { + cb(new Error(`Invalid revision [${chunk.revision}]`)); + debug("openProtocolSerializer _transform err-revision:", chunk); + return; + } + + if (chunk.stationID === " ") { + chunk.stationID = 1; + chunk.stationID = Number(chunk.stationID); + } else if (chunk.stationID === undefined) { + chunk.stationID = "\n" + "\n"; + } else { + chunk.stationID = Number(chunk.stationID); + } + + if (isNaN(chunk.stationID) || chunk.stationID < 0 || chunk.stationID > 99) { + cb(new Error(`Invalid stationID [${chunk.stationID}]`)); + debug("openProtocolSerializer _transform err-stationID:", chunk); + return; + } + + if (chunk.spindleID === " ") { + chunk.spindleID = 1; + chunk.spindleID = Number(chunk.spindleID); + } else if (chunk.spindleID === undefined) { + chunk.spindleID = "\n" + "\n"; + } else { + chunk.spindleID = Number(chunk.spindleID); + } + + if (isNaN(chunk.spindleID) || chunk.spindleID < 0 || chunk.spindleID > 99) { + cb(new Error(`Invalid spindleID [${chunk.spindleID}]`)); + debug("openProtocolSerializer _transform err-spindleID:", chunk); + return; + } + + if (chunk.sequenceNumber === " " || chunk.sequenceNumber === undefined) { + chunk.sequenceNumber = 0; + } + + chunk.sequenceNumber = Number(chunk.sequenceNumber); - /** - * @class OpenProtocolSerializer - * @description This class performs the serialization of the MID header. - * This transforms MID (object) in MID (Buffer). - * @param {Object} opts an object with the option passed to the constructor - */ - constructor(opts) { - opts = opts || {}; - opts.writableObjectMode = true; - super(opts); - debug("new openProtocolSerializer"); + if ( + isNaN(chunk.sequenceNumber) || + chunk.sequenceNumber < 0 || + chunk.sequenceNumber > 99 + ) { + cb(new Error(`Invalid sequenceNumber [${chunk.sequenceNumber}]`)); + debug("openProtocolSerializer _transform err-sequenceNumber:", chunk); + return; } - _transform(chunk, encoding, cb) { - debug("openProtocolSerializer _transform", chunk); - - chunk.mid = Number(chunk.mid); - - if (isNaN(chunk.mid) || chunk.mid < 1 || chunk.mid > 9999) { - cb(new Error(`Invalid MID [${chunk.mid}]`)); - debug("openProtocolSerializer _transform err-mid:", chunk); - return; - } - - if (chunk.revision === " " || chunk.revision === 0 || chunk.revision === undefined) { - chunk.revision = 1; - } - - chunk.revision = Number(chunk.revision); - - if (isNaN(chunk.revision) || chunk.revision < 0 || chunk.revision > 999) { - cb(new Error(`Invalid revision [${chunk.revision}]`)); - debug("openProtocolSerializer _transform err-revision:", chunk); - return; - } - - if (chunk.stationID === " " ||chunk.stationID === undefined) { - chunk.stationID = 1; - } - - chunk.stationID = Number(chunk.stationID); - - if (isNaN(chunk.stationID) || chunk.stationID < 0 || chunk.stationID > 99) { - cb(new Error(`Invalid stationID [${chunk.stationID}]`)); - debug("openProtocolSerializer _transform err-stationID:", chunk); - return; - } - - if (chunk.spindleID === " " ||chunk.spindleID === undefined) { - chunk.spindleID = 1; - } - - chunk.spindleID = Number(chunk.spindleID); - - if (isNaN(chunk.spindleID) || chunk.spindleID < 0 || chunk.spindleID > 99) { - cb(new Error(`Invalid spindleID [${chunk.spindleID}]`)); - debug("openProtocolSerializer _transform err-spindleID:", chunk); - return; - } - - if (chunk.sequenceNumber === " " || chunk.sequenceNumber === undefined) { - chunk.sequenceNumber = 0; - } - - chunk.sequenceNumber = Number(chunk.sequenceNumber); - - if (isNaN(chunk.sequenceNumber) || chunk.sequenceNumber < 0 || chunk.sequenceNumber > 99) { - cb(new Error(`Invalid sequenceNumber [${chunk.sequenceNumber}]`)); - debug("openProtocolSerializer _transform err-sequenceNumber:", chunk); - return; - } - - if (chunk.messageParts === " " || chunk.messageParts === undefined) { - chunk.messageParts = 0; - } - - chunk.messageParts = Number(chunk.messageParts); - - if (isNaN(chunk.messageParts) || chunk.messageParts < 0 || chunk.messageParts > 9) { - cb(new Error(`Invalid messageParts [${chunk.messageParts}]`)); - debug("openProtocolSerializer _transform err-messageParts:", chunk); - return; - } - - if (chunk.messageNumber === " " || chunk.messageNumber === undefined) { - chunk.messageNumber = 0; - } - - chunk.messageNumber = Number(chunk.messageNumber); - - if (isNaN(chunk.messageNumber) || chunk.messageNumber < 0 || chunk.messageNumber > 9) { - cb(new Error(`Invalid messageNumber [${chunk.messageNumber}]`)); - debug("openProtocolSerializer _transform err-messageNumber:", chunk); - return; - } - - if(chunk.payload === undefined){ - chunk.payload = ""; - } - - if (!Buffer.isBuffer(chunk.payload) && typeof chunk.payload !== "string") { - cb(new Error(`Invalid payload [${chunk.payload}]`)); - debug("openProtocolSerializer _transform err-payload:", chunk); - return; - } - - let sizePayload = chunk.payload.length; - let sizeMessage = 21 + sizePayload; - let buf = Buffer.alloc(sizeMessage); - - buf.write(pad(sizeMessage - 1, 4), 0, 4, encodingOP); - buf.write(pad(chunk.mid, 4), 4, 4, encodingOP); - buf.write(pad(chunk.revision, 3), 8, encodingOP); - buf.write(chunk.noAck ? '1' : '0', 11, encodingOP); - buf.write(pad(chunk.stationID, 2), 12, encodingOP); - buf.write(pad(chunk.spindleID, 2), 14, encodingOP); - buf.write(pad(chunk.sequenceNumber, 2), 16, encodingOP); - buf.write(pad(chunk.messageParts, 1), 18, encodingOP); - buf.write(pad(chunk.messageNumber, 1), 19, encodingOP); - buf.write(chunk.payload.toString(encodingOP), 20, encodingOP); - buf.write("\u0000", sizeMessage, encodingOP); - - debug("openProtocolSerializer _transform publish", buf); - this.push(buf); - - cb(); + if (chunk.messageParts === " ") { + chunk.messageParts = 0; + chunk.messageParts = Number(chunk.messageParts); + } else if (chunk.messageParts === undefined) { + chunk.messageParts = "\n"; + } else { + chunk.messageParts = Number(chunk.messageParts); } - _destroy() { - //no-op, needed to handle older node versions + if ( + isNaN(chunk.messageParts) || + chunk.messageParts < 0 || + chunk.messageParts > 9 + ) { + cb(new Error(`Invalid messageParts [${chunk.messageParts}]`)); + debug("openProtocolSerializer _transform err-messageParts:", chunk); + return; } + + if (chunk.messageNumber === " ") { + chunk.messageNumber = 0; + chunk.messageNumber = Number(chunk.messageNumber); + } else if (chunk.messageNumber === undefined) { + chunk.messageNumber = "\n"; + } else { + chunk.messageNumber = Number(chunk.messageNumber); + } + + if ( + isNaN(chunk.messageNumber) || + chunk.messageNumber < 0 || + chunk.messageNumber > 9 + ) { + cb(new Error(`Invalid messageNumber [${chunk.messageNumber}]`)); + debug("openProtocolSerializer _transform err-messageNumber:", chunk); + return; + } + + if (chunk.payload === undefined) { + chunk.payload = ""; + } + + if (!Buffer.isBuffer(chunk.payload) && typeof chunk.payload !== "string") { + cb(new Error(`Invalid payload [${chunk.payload}]`)); + debug("openProtocolSerializer _transform err-payload:", chunk); + return; + } + + let sizePayload = chunk.payload.length; + let sizeMessage = 21 + sizePayload; + let buf = Buffer.alloc(sizeMessage); + + buf.write(pad(sizeMessage - 1, 4), 0, 4, encodingOP); + buf.write(pad(chunk.mid, 4), 4, 4, encodingOP); + buf.write(pad(chunk.revision, 3), 8, encodingOP); + buf.write(chunk.noAck ? "1" : "0", 11, encodingOP); + buf.write(pad(chunk.stationID, 2), 12, encodingOP); + buf.write(pad(chunk.spindleID, 2), 14, encodingOP); + buf.write(pad(chunk.sequenceNumber, 2), 16, encodingOP); + buf.write(pad(chunk.messageParts, 1), 18, encodingOP); + buf.write(pad(chunk.messageNumber, 1), 19, encodingOP); + buf.write(chunk.payload.toString(encodingOP), 20, encodingOP); + buf.write("\u0000", sizeMessage, encodingOP); + + debug("openProtocolSerializer _transform publish", buf); + this.push(buf); + + cb(); + } + + _destroy() { + //no-op, needed to handle older node versions + } } module.exports = OpenProtocolSerializer; diff --git a/src/sessionControlClient.js b/src/sessionControlClient.js index 06f5151..5a06da9 100644 --- a/src/sessionControlClient.js +++ b/src/sessionControlClient.js @@ -16,10 +16,10 @@ "use strict"; /*jshint esversion: 6, node: true*/ -const util = require('util'); +const util = require("util"); -const EventEmitter = require('events'); -const LinkLayer = require('../src/linkLayer.js'); +const EventEmitter = require("events"); +const LinkLayer = require("../src/linkLayer.js"); const helpers = require("./helpers.js"); const midGroupList = require("./midGroups.json"); const midData = require("./midData.json"); @@ -30,7 +30,7 @@ const midReply = require("./midReply.json"); const mids = helpers.getMids(); -var debug = util.debuglog('open-protocol'); +var debug = util.debuglog("open-protocol"); const SUBSCRIBE = "subscribe"; const COMMAND = "command"; @@ -44,298 +44,302 @@ const CONN_CONNECTING = 1; const CONN_CONNECTED = 2; function promisify(ref, method, mid, opts) { - return new Promise(function (resolve, reject) { - return ref[method](mid, opts, (err, data) => { - if (err) { - reject(err); - } else { - resolve(data); - } - }); + return new Promise(function (resolve, reject) { + return ref[method](mid, opts, (err, data) => { + if (err) { + reject(err); + } else { + resolve(data); + } }); + }); } function maybePromisify(ref, method, mid, opts, cb) { - if (cb === undefined) { - if (typeof opts === "function") { - cb = opts; - opts = {}; - } else { - return promisify(ref, method, mid, opts); - } + if (cb === undefined) { + if (typeof opts === "function") { + cb = opts; + opts = {}; + } else { + return promisify(ref, method, mid, opts); } + } - return ref[method](mid, opts, cb); + return ref[method](mid, opts, cb); } class SessionControlClient extends EventEmitter { + /** + * @event SessionControlClient#connect + * @property {Object} data MID 0002 - Reply of the connection + */ + + /** + * @event SessionControlClient#data + * @property {Object} data All MID's received of the controller + */ + + /** + * @event SessionControlClient#dataGroup + * @property {Object} data data of subscribe + * + * @example + * + * //Subscribe lastTightening + * sessionControlClient.subscribe("lastTightening"); + * + * sessionControlClient.on("lastTightening", (data) => { + * console.log("Received MID 0061 / data of subscribed [lastTightening]", data); + * }); + * + * //Subscribe vin vehicleIdNumber + * sessionControlClient.subscribe("vin"); + * + * sessionControlClient.on("vin", (data) => { + * console.log("Received MID 0052 / data of subscribed [vin]", data); + * }); + * + */ + + /** + * @event SessionControlClient#error + * @property {Error} err + */ + + /** + * @event SessionControlClient#close + * @property {Error} [err] + */ + + /** + * @throws {error} + * @param {*} opts + * @param {object} [opts.defaultRevisions = {}] + * @param {boolean} [opts.linkLayerActivate] true = activate LinkLayer / false = not activate LinkLayer / undefined = autoNegotiation LinkLayer + * @param {boolean} [opts.genericMode] true activate / false or undefined not activate + * @param {number} [opts.keepAlive = 10000] + * + * @param {stream} opts.stream + * @param {boolean} [opts.rawData] + * @param {object} [opts.disableMidParsing = {}] + * @param {number} [opts.timeOut = 3000] + * @param {number} [opts.retryTimes = 3] + * + * @example + * // Instantiate SessionControlClient with default values + * * + * const OP = require("open-protocol"); + * + * // Create a socket + * const net = require("net"); + * let socket = net.createConnection(port, host); + * + * let opts = { + * stream: socket + * } + * + * let sessionControlClient = new OP.SessionControlClient(opts); + * + * // Add a listener for the connect event + * sessionControlClient.on("connect", (data) => { + * console.log("Connected"); + * console.log("MID of reply", data); + * }); + * + * // Perform protocol connection + * sessionControlClient.connect(); + * + * @example + * + * // The SessionControlClient can also be instantiated in the library base, + * // in this case, the library performs the connection and return a SessionControlClient ready. + * + * const OP = require("open-protocol"); + * + * let ipController = "127.0.0.1"; + * let portController = 4545; + * let optsSessionControl = {}; + * + * let sessionControlClient = OP.createClient(portController, ipController, optsSessionControl, (data) => { + * console.log("Connected"); + * console.log("MID of reply", data); + * }); + * + */ + constructor(opts) { + debug("new SessionControlClient"); + + super(); + + opts = opts || {}; + + if (opts.stream === undefined) { + debug("SessionControlClient constructor err_stream_undefined"); + throw new Error("[Session Control Client] stream undefined"); + } - /** - * @event SessionControlClient#connect - * @property {Object} data MID 0002 - Reply of the connection - */ - - /** - * @event SessionControlClient#data - * @property {Object} data All MID's received of the controller - */ - - /** - * @event SessionControlClient#dataGroup - * @property {Object} data data of subscribe - * - * @example - * - * //Subscribe lastTightening - * sessionControlClient.subscribe("lastTightening"); - * - * sessionControlClient.on("lastTightening", (data) => { - * console.log("Received MID 0061 / data of subscribed [lastTightening]", data); - * }); - * - * //Subscribe vin vehicleIdNumber - * sessionControlClient.subscribe("vin"); - * - * sessionControlClient.on("vin", (data) => { - * console.log("Received MID 0052 / data of subscribed [vin]", data); - * }); - * - */ - - /** - * @event SessionControlClient#error - * @property {Error} err - */ - - /** - * @event SessionControlClient#close - * @property {Error} [err] - */ - - /** - * @throws {error} - * @param {*} opts - * @param {object} [opts.defaultRevisions = {}] - * @param {boolean} [opts.linkLayerActivate] true = activate LinkLayer / false = not activate LinkLayer / undefined = autoNegotiation LinkLayer - * @param {boolean} [opts.genericMode] true activate / false or undefined not activate - * @param {number} [opts.keepAlive = 10000] - * - * @param {stream} opts.stream - * @param {boolean} [opts.rawData] - * @param {object} [opts.disableMidParsing = {}] - * @param {number} [opts.timeOut = 3000] - * @param {number} [opts.retryTimes = 3] - * - * @example - * // Instantiate SessionControlClient with default values - * * - * const OP = require("open-protocol"); - * - * // Create a socket - * const net = require("net"); - * let socket = net.createConnection(port, host); - * - * let opts = { - * stream: socket - * } - * - * let sessionControlClient = new OP.SessionControlClient(opts); - * - * // Add a listener for the connect event - * sessionControlClient.on("connect", (data) => { - * console.log("Connected"); - * console.log("MID of reply", data); - * }); - * - * // Perform protocol connection - * sessionControlClient.connect(); - * - * @example - * - * // The SessionControlClient can also be instantiated in the library base, - * // in this case, the library performs the connection and return a SessionControlClient ready. - * - * const OP = require("open-protocol"); - * - * let ipController = "127.0.0.1"; - * let portController = 4545; - * let optsSessionControl = {}; - * - * let sessionControlClient = OP.createClient(portController, ipController, optsSessionControl, (data) => { - * console.log("Connected"); - * console.log("MID of reply", data); - * }); - * - */ - constructor(opts) { - debug("new SessionControlClient"); - - super(); - - opts = opts || {}; - - if (opts.stream === undefined) { - debug("SessionControlClient constructor err_stream_undefined"); - throw new Error("[Session Control Client] stream undefined"); - } - - this.defaultRevisions = opts.defaultRevisions || {}; + this.defaultRevisions = opts.defaultRevisions || {}; - //LinkLayer - //If true activate Link Layer - //If false not activate Link Layer - //If undefined autoNegotiation Link Layer - this.useLinkLayer = opts.linkLayerActivate; + //LinkLayer + //If true activate Link Layer + //If false not activate Link Layer + //If undefined autoNegotiation Link Layer + this.useLinkLayer = opts.linkLayerActivate; - //Generic Mode - this.useGenerics = opts.genericMode || false; + //Generic Mode + this.useGenerics = opts.genericMode || false; - //Keep Alive - this.keepAlive = opts.keepAlive || 10000; + //Keep Alive + this.keepAlive = opts.keepAlive || 10000; - this.ll = new LinkLayer({ - stream: opts.stream, - timeOut: opts.timeOut, - retryTimes: opts.retryTimes, - rawData: opts.rawData, - disableMidParsing: opts.disableMidParsing - }); + this.ll = new LinkLayer({ + stream: opts.stream, + timeOut: opts.timeOut, + retryTimes: opts.retryTimes, + rawData: opts.rawData, + disableMidParsing: opts.disableMidParsing, + }); - this.ll.on("error", (err) => this._onErrorLinkLayer(err)); + this.ll.on("error", (err) => this._onErrorLinkLayer(err)); - this.changeRevisionGeneric = false; - this.autoRevision = {}; - this.midInProcess = undefined; - this.inOperation = false; - this.changeRevision = false; - this.midQueue = []; - this.statusConnection = CONN_NOT_CONNECT; - this.controllerData = null; + this.changeRevisionGeneric = false; + this.autoRevision = {}; + this.midInProcess = undefined; + this.inOperation = false; + this.changeRevision = false; + this.midQueue = []; + this.statusConnection = CONN_NOT_CONNECT; + this.controllerData = null; - this.keepAliveTimer = undefined; - // this.receiverKeepAliveTimer = undefined; + this.keepAliveTimer = undefined; + // this.receiverKeepAliveTimer = undefined; - this.onClose = false; + this.onClose = false; - this.stream = opts.stream; + this.stream = opts.stream; - this.stream.on("error", (err) => { - debug("SessionControlClient stream_error", err); - this.emit("error", err); - this.close(err); - }); + this.stream.on("error", (err) => { + debug("SessionControlClient stream_error", err); + this.emit("error", err); + this.close(err); + }); - this.stream.on("close", () => { - debug("SessionControlClient stream_close"); - this.close(new Error("Stream Close")); + this.stream.on("close", () => { + debug("SessionControlClient stream_close"); + this.close(new Error("Stream Close")); + }); + } + + /** + * @description Check if the connection with the controller is active. + * @return {Boolean} + */ + isConnected() { + return this.statusConnection === CONN_CONNECTED; + } + + /** + * @description Check if the connection uses LinkLayer + * @return {Boolean} + */ + isLinkLayerActive() { + return !!this.useLinkLayer; + } + + /** + * @description This method makes a connection with the controller. + * If add a callback function, it will add as listener of connect @event. + * + * @param {function} cb function of callback + */ + connect(cb) { + debug("SessionControlClient connect"); + + let midSend = {}; + + if (this.connected) { + if (typeof cb === "function") { + return process.nextTick(cb, this.controllerData); + } else { + return new Promise(function (resolve) { + resolve(this.controllerData); }); + } } - /** - * @description Check if the connection with the controller is active. - * @return {Boolean} - */ - isConnected() { - return this.statusConnection === CONN_CONNECTED; - } - - /** - * @description Check if the connection uses LinkLayer - * @return {Boolean} - */ - isLinkLayerActive() { - return !!this.useLinkLayer; - } - - /** - * @description This method makes a connection with the controller. - * If add a callback function, it will add as listener of connect @event. - * - * @param {function} cb function of callback - */ - connect(cb) { - debug("SessionControlClient connect"); - - let midSend = {}; - - if (this.connected) { - if (typeof cb === 'function') { - return process.nextTick(cb, this.controllerData); - } else { - return new Promise(function (resolve) { - resolve(this.controllerData); - }); - } - } - - let revision = 0; - - let sendMidOne = () => { - debug("SessionControlClient sendMidOne"); - - if (this.defaultRevisions["1"] === undefined) { - - if (this.autoRevision["1"] === undefined) { - - revision = mids[2].revision()[0]; - - this.autoRevision["1"] = { - value: revision, - position: 0 - }; - - } else { - revision = this.autoRevision["1"].value; - } + let revision = 0; - } else { - revision = this.defaultRevisions["1"]; - } + let sendMidOne = () => { + debug("SessionControlClient sendMidOne"); - this.statusConnection = CONN_CONNECTING; + if (this.defaultRevisions["1"] === undefined) { + if (this.autoRevision["1"] === undefined) { + revision = mids[2].revision()[0]; - midSend = { - mid: 1, - revision: revision - }; + this.autoRevision["1"] = { + value: revision, + position: 0, + }; + } else { + revision = this.autoRevision["1"].value; + } + } else { + revision = this.defaultRevisions["1"]; + } - this.ll.write(midSend); - }; + this.statusConnection = CONN_CONNECTING; - let receivedReply = (data) => { - debug("SessionControlClient receivedReply", data); + midSend = { + mid: 1, + revision: revision, + }; - this.ll.finishCycle(); + this.ll.write(midSend); + }; - this.emit("data", data); + let receivedReply = (data) => { + debug("SessionControlClient receivedReply", data); - if (data.mid === 4) { + this.ll.finishCycle(); - if (data.payload.midNumber !== 1) { - let e = new Error(`[Session Control Client] [Connect] invalid acknowledge, expect MID[1], received MID[${data.payload.midNumber}]`); - debug("SessionControlClient connect err_invalid_acknowledge", data); - this.emit("error", e); - return; - } + this.emit("data", data); - if (data.payload.errorCode === 97 && this.defaultRevisions["1"] === undefined) { - let newPosition = this.autoRevision["1"].position + 1; - this.autoRevision["1"].value = mids[2].revision()[newPosition]; - this.autoRevision["1"].position = newPosition; - sendMidOne(); - } else { - let errorCode = helpers.padLeft(data.payload.errorCode, 2); - let e = new Error(`[Session Control Client] [Connect] negative acknowledge, MID[${data.payload.midNumber}], Error [${constants.ERROR[errorCode]}]`); - debug("SessionControlClient connect err_negative_acknowledge", data.payload.errorCode, this.defaultRevisions["1"]); - this.emit("error", e); - } + if (data.mid === 4) { + if (data.payload.midNumber !== 1) { + let e = new Error( + `[Session Control Client] [Connect] invalid acknowledge, expect MID[1], received MID[${data.payload.midNumber}]` + ); + debug("SessionControlClient connect err_invalid_acknowledge", data); + this.emit("error", e); + return; + } - return; - } + if ( + data.payload.errorCode === 97 && + this.defaultRevisions["1"] === undefined + ) { + let newPosition = this.autoRevision["1"].position + 1; + this.autoRevision["1"].value = mids[2].revision()[newPosition]; + this.autoRevision["1"].position = newPosition; + sendMidOne(); + } else { + let errorCode = helpers.padLeft(data.payload.errorCode, 2); + let e = new Error( + `[Session Control Client] [Connect] negative acknowledge, MID[${data.payload.midNumber}], Error [${constants.ERROR[errorCode]}]` + ); + debug( + "SessionControlClient connect err_negative_acknowledge", + data.payload.errorCode, + this.defaultRevisions["1"] + ); + this.emit("error", e); + } - if (data.mid === 2) { + return; + } - /* + if (data.mid === 2) { + /* Disabling revisions check because our simulator always respond Revision == "00 " @@ -355,834 +359,878 @@ class SessionControlClient extends EventEmitter { } */ - this.ll.removeAllListeners(); - - this.statusConnection = CONN_CONNECTED; - this.controllerData = data; - - this.ll.on("data", (data) => this._onDataLinkLayer(data)); - this.ll.on("error", (err) => this._onErrorLinkLayer(err)); - this.ll.on("errorSerializer", (err) => this._onErrorSerializer(err)); - this.ll.on("errorParser", (err) => this._onErrorParser(err)); + this.ll.removeAllListeners(); - process.nextTick(() => { - this.emit("connect", data); - }); + this.statusConnection = CONN_CONNECTED; + this.controllerData = data; - if (this.useLinkLayer === undefined) { - if (data.payload.sequenceNumberSupport === 1) { - this.ll.activateLinkLayer(); - this.useLinkLayer = true; - } else { - this.ll.deactivateLinkLayer(); - this.useLinkLayer = false; - } - - } else if (this.useLinkLayer) { - if (data.payload.sequenceNumberSupport !== 1 || data.payload.linkingHandlingSupport !== 1) { - this.emit("error", new Error("[Session Control Client] [Force Link Layer] controller does not support link layer")); - debug("SessionControlClient connect err_controller_not_support_link_layer", this.useLinkLayer, data.payload.sequenceNumberSupport, data.payload.linkingHandlingSupport); - return; - } - this.ll.activateLinkLayer(); - - } else { - this.ll.deactivateLinkLayer(); - } - - clearTimeout(this.keepAliveTimer); - this.keepAliveTimer = setTimeout(() => this._sendKeepAlive(), this.keepAlive); - - this.onClose = false; - this.inOperation = false; - - this._sendingProcess(); - } - }; - - sendMidOne(); - - this.ll.on("data", (data) => receivedReply(data)); - - if (typeof cb === 'function') { - return this.once('connect', cb); - } else { - return new Promise((resolve, reject) => { - this.once('connect', (data) => { - resolve(data); - }); - this.once('error', (err) => { - reject(err); - }); - }); - } - } + this.ll.on("data", (data) => this._onDataLinkLayer(data)); + this.ll.on("error", (err) => this._onErrorLinkLayer(err)); + this.ll.on("errorSerializer", (err) => this._onErrorSerializer(err)); + this.ll.on("errorParser", (err) => this._onErrorParser(err)); - /** - * @description This method destroys the connection and cleans all states of SessionControlClient. - * @param {Error} [err] - */ - close(err) { - debug("SessionControlClient close", err); + process.nextTick(() => { + this.emit("connect", data); + }); - if (this.onClose) { + if (this.useLinkLayer === undefined) { + if (data.payload.sequenceNumberSupport === 1) { + this.ll.activateLinkLayer(); + this.useLinkLayer = true; + } else { + this.ll.deactivateLinkLayer(); + this.useLinkLayer = false; + } + } else if (this.useLinkLayer) { + if ( + data.payload.sequenceNumberSupport !== 1 || + data.payload.linkingHandlingSupport !== 1 + ) { + this.emit( + "error", + new Error( + "[Session Control Client] [Force Link Layer] controller does not support link layer" + ) + ); + debug( + "SessionControlClient connect err_controller_not_support_link_layer", + this.useLinkLayer, + data.payload.sequenceNumberSupport, + data.payload.linkingHandlingSupport + ); return; + } + this.ll.activateLinkLayer(); + } else { + this.ll.deactivateLinkLayer(); } clearTimeout(this.keepAliveTimer); + this.keepAliveTimer = setTimeout( + () => this._sendKeepAlive(), + this.keepAlive + ); - this.onClose = true; - this.statusConnection = CONN_NOT_CONNECT; - - if (this.midQueue && this.midQueue.length > 0) { - let e = new Error("service unavailable"); - this.midQueue.forEach(item => { - process.nextTick(() => item.doCallback(e)); - }); - } - - this.midInProcess = null; - this.midQueue = []; + this.onClose = false; + this.inOperation = false; - this.autoRevision = {}; + this._sendingProcess(); + } + }; - // handles Node versions older than 8.x - if(typeof this.ll.destroy === 'function'){ - this.ll.destroy(); - } else { - this.ll._destroy(); - } - this.stream.end(); + sendMidOne(); - this.emit("close", err); - } + this.ll.on("data", (data) => receivedReply(data)); - /** - * @description This method makes generic call, here it is possible to send a not implemented MID. - * If add only [midNumber] the message will have default a body and revision = 1, for additional body settings, - * add the [opts] object. The [cb] function is called in case of an error, sending the error as parameter. The incoming - * data from these calls will be then emitted as events, that can be listened by using the `on` method. - * - * @example - * // Example of a call, adding only [midNumber]. - * // In this case the client does not receives a feedback and the sent message go with midNumber and default body. - * - * sessionControlClient.sendMid(1); - * - * //result - * { - * mid: 1, - * revision: 1, - * payload: "" - * } - * - * @example - * - * // Example of a call, adding [midNumber] and body values with [opts]. - * // In this case the client does not receives a feedback and the sent message will have midNumber, body values of opts and - * // others fields with default values. - * - * let opts = { - * revision: 4, - * payload: "Test" - * } - * - * sessionControlClient.sendMid(1, opts); - * - * //result - * { - * mid: 1, - * revision: 4, - * payload: "Test" - * } - * - * @example - * - * // Example of a complete call, adding [midNumber], body values with [opts] and a callback function. - * // In this case the client, receives a feedback with status of the call and the is sent message will have midNumber, body - * // values of opts and others fields with default values. - * - * let opts = { - * revision: 4, - * payload: "Test" - * } - * - * sessionControlClient.on("data", (data) => { - * console.log("Data received", data); - * }); - * - * sessionControlClient.sendMid(1, opts, (err) => { - * - * if (err) { - * console.log("an error has occurred", err); - * return; - * } - * - * }); - * - * //result - * { - * mid: 1, - * revision: 4, - * payload: "Test" - * } - * - * @param {Number} midNumber - * @param {Object} [opts] - * @param {Function} [cb] - */ - sendMid(midNumber, opts, cb) { - return maybePromisify(this, "_sendMid", midNumber, opts, cb); + if (typeof cb === "function") { + return this.once("connect", cb); + } else { + return new Promise((resolve, reject) => { + this.once("connect", (data) => { + resolve(data); + }); + this.once("error", (err) => { + reject(err); + }); + }); } + } - /** - * @private - * @param {*} midNumber - * @param {*} opts - * @param {*} cb - */ - _sendMid(midNumber, opts, cb) { - debug("SessionControlClient _sendMid", midNumber, opts); - - let mid = opts || {}; - mid.payload = mid.payload || ""; + /** + * @description This method destroys the connection and cleans all states of SessionControlClient. + * @param {Error} [err] + */ + close(err) { + debug("SessionControlClient close", err); - mid.mid = midNumber; - - this.midQueue.push(new Message(mid, cb, MANUAL)); - this._sendingProcess(); + if (this.onClose) { + return; } - /** - * @description This method makes a request call, it uses [midGroup] as key for call. - * If adding only [midGroup] the message will have a default body, for additional body settings, - * add the [opts] object. The [cb] function is called in cases of an error, sending the error as - * parameter and in success cases sending the MID of the reply. - * - * @param {String} midGroup - * @param {Object} [opts] - * @param {Function} [cb] - */ - request(midGroup, opts, cb) { - return maybePromisify(this, "_request", midGroup, opts, cb); - } - - /** - * @private - * @param {*} midGroup - * @param {*} opts - * @param {*} cb - */ - _request(midGroup, opts, cb) { - debug("SessionControlClient _request", midGroup, opts); - - if (midRequest[midGroup] === undefined) { - let err = new Error(`[Session Control Client] [Request] invalid midGroup [${midGroup}]`); - debug("SessionControlClient _request err_invalid_midGroup"); - cb(err); - return; - } - - let mid = opts || {}; - - let type = REQUEST; - - if (this.useGenerics && midRequest[midGroup].generic) { + clearTimeout(this.keepAliveTimer); - mid.mid = 6; - type = GENERIC; + this.onClose = true; + this.statusConnection = CONN_NOT_CONNECT; - let midNumber = midRequest[midGroup].request; - let revision = mid.revision || this._calcRevision(midNumber, REQUEST, midGroup, true); - let dataLength = mid.dataLength || 0; - let extraData = mid.extraData || ""; + if (this.midQueue && this.midQueue.length > 0) { + let e = new Error("service unavailable"); + this.midQueue.forEach((item) => { + process.nextTick(() => item.doCallback(e)); + }); + } - mid.payload = { - midNumber, - revision, - dataLength, - extraData - }; + this.midInProcess = null; + this.midQueue = []; - } else { - mid.payload = mid.payload || ""; - mid.mid = midRequest[midGroup].request; - } + this.autoRevision = {}; - this.midQueue.push(new Message(mid, cb, type, midGroup)); - - this._sendingProcess(); + // handles Node versions older than 8.x + if (typeof this.ll.destroy === "function") { + this.ll.destroy(); + } else { + this.ll._destroy(); } - - /** - * @description This method makes a command call, it uses [midGroup] as key for call. - * If adding only [midGroup] the message will have a default body, for additional body settings, - * add the [opts] object. The [cb] function is called in case of an error, sending the error as - * parameter and in success cases sending the MID of reply. - * - * @param {String} midGroup - * @param {Object} [opts] - * @param {Function} [cb] - */ - command(midGroup, opts, cb) { - return maybePromisify(this, "_command", midGroup, opts, cb); + this.stream.end(); + + this.emit("close", err); + } + + /** + * @description This method makes generic call, here it is possible to send a not implemented MID. + * If add only [midNumber] the message will have default a body and revision = 1, for additional body settings, + * add the [opts] object. The [cb] function is called in case of an error, sending the error as parameter. The incoming + * data from these calls will be then emitted as events, that can be listened by using the `on` method. + * + * @example + * // Example of a call, adding only [midNumber]. + * // In this case the client does not receives a feedback and the sent message go with midNumber and default body. + * + * sessionControlClient.sendMid(1); + * + * //result + * { + * mid: 1, + * revision: 1, + * payload: "" + * } + * + * @example + * + * // Example of a call, adding [midNumber] and body values with [opts]. + * // In this case the client does not receives a feedback and the sent message will have midNumber, body values of opts and + * // others fields with default values. + * + * let opts = { + * revision: 4, + * payload: "Test" + * } + * + * sessionControlClient.sendMid(1, opts); + * + * //result + * { + * mid: 1, + * revision: 4, + * payload: "Test" + * } + * + * @example + * + * // Example of a complete call, adding [midNumber], body values with [opts] and a callback function. + * // In this case the client, receives a feedback with status of the call and the is sent message will have midNumber, body + * // values of opts and others fields with default values. + * + * let opts = { + * revision: 4, + * payload: "Test" + * } + * + * sessionControlClient.on("data", (data) => { + * console.log("Data received", data); + * }); + * + * sessionControlClient.sendMid(1, opts, (err) => { + * + * if (err) { + * console.log("an error has occurred", err); + * return; + * } + * + * }); + * + * //result + * { + * mid: 1, + * revision: 4, + * payload: "Test" + * } + * + * @param {Number} midNumber + * @param {Object} [opts] + * @param {Function} [cb] + */ + sendMid(midNumber, opts, cb) { + return maybePromisify(this, "_sendMid", midNumber, opts, cb); + } + + /** + * @private + * @param {*} midNumber + * @param {*} opts + * @param {*} cb + */ + _sendMid(midNumber, opts, cb) { + debug("SessionControlClient _sendMid", midNumber, opts); + + let mid = opts || {}; + mid.payload = mid.payload || ""; + + mid.mid = midNumber; + + this.midQueue.push(new Message(mid, cb, MANUAL)); + this._sendingProcess(); + } + + /** + * @description This method makes a request call, it uses [midGroup] as key for call. + * If adding only [midGroup] the message will have a default body, for additional body settings, + * add the [opts] object. The [cb] function is called in cases of an error, sending the error as + * parameter and in success cases sending the MID of the reply. + * + * @param {String} midGroup + * @param {Object} [opts] + * @param {Function} [cb] + */ + request(midGroup, opts, cb) { + return maybePromisify(this, "_request", midGroup, opts, cb); + } + + /** + * @private + * @param {*} midGroup + * @param {*} opts + * @param {*} cb + */ + _request(midGroup, opts, cb) { + debug("SessionControlClient _request", midGroup, opts); + + if (midRequest[midGroup] === undefined) { + let err = new Error( + `[Session Control Client] [Request] invalid midGroup [${midGroup}]` + ); + debug("SessionControlClient _request err_invalid_midGroup"); + cb(err); + return; } - /** - * @private - * @param {*} midGroup - * @param {*} opts - * @param {*} cb - */ - _command(midGroup, opts, cb) { - debug("SessionControlClient _command", midGroup, opts); - - if (midCommand[midGroup] === undefined) { - let err = new Error(`[Session Control Client] [Command] invalid midGroup [${midGroup}]`); - debug("SessionControlClient _command err_invalid_midGroup"); - cb(err); - return; - } - - let mid = opts || {}; - - let type = COMMAND; - - mid.payload = mid.payload || ""; - mid.mid = midCommand[midGroup].request; - - - this.midQueue.push(new Message(mid, cb, type, midGroup)); - this._sendingProcess(); + let mid = opts || {}; + + let type = REQUEST; + + if (this.useGenerics && midRequest[midGroup].generic) { + mid.mid = 6; + type = GENERIC; + + let midNumber = midRequest[midGroup].request; + let revision = + mid.revision || this._calcRevision(midNumber, REQUEST, midGroup, true); + let dataLength = mid.dataLength || 0; + let extraData = mid.extraData || ""; + + mid.payload = { + midNumber, + revision, + dataLength, + extraData, + }; + } else { + mid.payload = mid.payload || ""; + mid.mid = midRequest[midGroup].request; } - /** - * @description This method makes a subscribe call, it uses [midGroup] as key for call. - * If adding only [midGroup] the message will have a default body, for additional body settings, - * add the [opts] object. The [cb] function is called in case of an error, sending the error as - * parameter and in success cases sending the MID of the reply. - * Data MIDs will be passed by the event named [midGroup]. - * - * @see {@link /docs/MIDsGroups.md} The keys for use as [midGroup]. - * - * @example - * //Subscribe lastTightening - * sessionControlClient.subscribe("lastTightening"); - * - * //Listening lastTightening - * sessionControlClient.on("lastTightening", (data) => { - * console.log("Receive MID 0061 / Data of subscribe lastTightening", data); - * }); - * - * @fires SessionControlClient#dataGroup - * - * @param {String} midGroup - * @param {Object} [opts] - * @param {Function} [cb] - */ - subscribe(midGroup, opts, cb) { - return maybePromisify(this, "_subscribe", midGroup, opts, cb); + this.midQueue.push(new Message(mid, cb, type, midGroup)); + + this._sendingProcess(); + } + + /** + * @description This method makes a command call, it uses [midGroup] as key for call. + * If adding only [midGroup] the message will have a default body, for additional body settings, + * add the [opts] object. The [cb] function is called in case of an error, sending the error as + * parameter and in success cases sending the MID of reply. + * + * @param {String} midGroup + * @param {Object} [opts] + * @param {Function} [cb] + */ + command(midGroup, opts, cb) { + return maybePromisify(this, "_command", midGroup, opts, cb); + } + + /** + * @private + * @param {*} midGroup + * @param {*} opts + * @param {*} cb + */ + _command(midGroup, opts, cb) { + debug("SessionControlClient _command", midGroup, opts); + + if (midCommand[midGroup] === undefined) { + let err = new Error( + `[Session Control Client] [Command] invalid midGroup [${midGroup}]` + ); + debug("SessionControlClient _command err_invalid_midGroup"); + cb(err); + return; } - /** - * @private - * @param {*} midGroup - * @param {*} opts - * @param {*} cb - */ - _subscribe(midGroup, opts, cb) { - debug("SessionControlClient _subscribe", midGroup, opts); - - if (midGroupList[midGroup] === undefined) { - let err = new Error(`[Session Control Client] [Subscribe] invalid midGroup [${midGroup}]`); - debug("SessionControlClient _subscribe err_invalid_midGroup"); - cb(err); - return; - } - - let mid = opts || {}; - - let type = SUBSCRIBE; - - if (this.useGenerics && midGroupList[midGroup].generic) { - - mid.mid = 8; - type = GENERIC; - - let midNumber = mid.midNumber || midGroupList[midGroup].subscribe; - let revision = mid.revision || this._calcRevision(midNumber, SUBSCRIBE, midGroup, true); - let dataLength = mid.dataLength || 0; - let extraData = mid.extraData || ""; - - mid.payload = { - midNumber, - revision, - dataLength, - extraData - }; - - } else { - mid.payload = mid.payload || ""; - mid.mid = midGroupList[midGroup].subscribe; - } - - this.midQueue.push(new Message(mid, cb, type, midGroup)); - this._sendingProcess(); + let mid = opts || {}; + + let type = COMMAND; + + mid.payload = mid.payload || ""; + mid.mid = midCommand[midGroup].request; + + this.midQueue.push(new Message(mid, cb, type, midGroup)); + this._sendingProcess(); + } + + /** + * @description This method makes a subscribe call, it uses [midGroup] as key for call. + * If adding only [midGroup] the message will have a default body, for additional body settings, + * add the [opts] object. The [cb] function is called in case of an error, sending the error as + * parameter and in success cases sending the MID of the reply. + * Data MIDs will be passed by the event named [midGroup]. + * + * @see {@link /docs/MIDsGroups.md} The keys for use as [midGroup]. + * + * @example + * //Subscribe lastTightening + * sessionControlClient.subscribe("lastTightening"); + * + * //Listening lastTightening + * sessionControlClient.on("lastTightening", (data) => { + * console.log("Receive MID 0061 / Data of subscribe lastTightening", data); + * }); + * + * @fires SessionControlClient#dataGroup + * + * @param {String} midGroup + * @param {Object} [opts] + * @param {Function} [cb] + */ + subscribe(midGroup, opts, cb) { + return maybePromisify(this, "_subscribe", midGroup, opts, cb); + } + + /** + * @private + * @param {*} midGroup + * @param {*} opts + * @param {*} cb + */ + _subscribe(midGroup, opts, cb) { + debug("SessionControlClient _subscribe", midGroup, opts); + + if (midGroupList[midGroup] === undefined) { + let err = new Error( + `[Session Control Client] [Subscribe] invalid midGroup [${midGroup}]` + ); + debug("SessionControlClient _subscribe err_invalid_midGroup"); + cb(err); + return; } - /** - * @description This method makes an unsubscribe call, it uses [midGroup] as key for call. - * If adding only [midGroup] the message will have a default body, for additional body settings, - * add the [opts] object. The [cb] function is called in case of the error, sending the error as - * parameter and in success cases sending the MID of reply. - * - * @param {String} midGroup - * @param {Object} [opts] - * @param {Function} [cb] - */ - unsubscribe(midGroup, opts, cb) { - return maybePromisify(this, "_unsubscribe", midGroup, opts, cb); + let mid = opts || {}; + + let type = SUBSCRIBE; + + if (this.useGenerics && midGroupList[midGroup].generic) { + mid.mid = 8; + type = GENERIC; + + let midNumber = mid.midNumber || midGroupList[midGroup].subscribe; + let revision = + mid.revision || + this._calcRevision(midNumber, SUBSCRIBE, midGroup, true); + let dataLength = mid.dataLength || 0; + let extraData = mid.extraData || ""; + + mid.payload = { + midNumber, + revision, + dataLength, + extraData, + }; + } else { + mid.payload = mid.payload || ""; + mid.mid = midGroupList[midGroup].subscribe; } - /** - * @private - * @param {*} midGroup - * @param {*} opts - * @param {*} cb - */ - _unsubscribe(midGroup, opts, cb) { - debug("SessionControlClient _unsubscribe", midGroup, opts); - - if (midGroupList[midGroup] === undefined) { - let err = new Error(`[Session Control Client] [Unsubscribe] invalid groupMid [${midGroup}]`); - debug("SessionControlClient _unsubscribe err_invalid_midGroup"); - cb(err); - return; - } - - if (cb === undefined) { - if (typeof opts === "function") { - cb = opts; - opts = {}; - } else { - cb = () => {}; - } - } - - let mid = opts || {}; - - let type = SUBSCRIBE; - - if (this.useGenerics && midGroupList[midGroup].generic) { - - mid.mid = 9; - type = GENERIC; - - let midNumber = mid.midNumber || midGroupList[midGroup].unsubscribe; - let revision = mid.revision || this._calcRevision(midNumber, SUBSCRIBE, midGroup, true); - let dataLength = mid.dataLength || 0; - let extraData = mid.extraData || ""; - - mid.payload = { - midNumber, - revision, - dataLength, - extraData - }; - - } else { - mid.payload = mid.payload || ""; - mid.mid = midGroupList[midGroup].unsubscribe; - } - - this.midQueue.push(new Message(mid, cb, type, midGroup)); - this._sendingProcess(); + this.midQueue.push(new Message(mid, cb, type, midGroup)); + this._sendingProcess(); + } + + /** + * @description This method makes an unsubscribe call, it uses [midGroup] as key for call. + * If adding only [midGroup] the message will have a default body, for additional body settings, + * add the [opts] object. The [cb] function is called in case of the error, sending the error as + * parameter and in success cases sending the MID of reply. + * + * @param {String} midGroup + * @param {Object} [opts] + * @param {Function} [cb] + */ + unsubscribe(midGroup, opts, cb) { + return maybePromisify(this, "_unsubscribe", midGroup, opts, cb); + } + + /** + * @private + * @param {*} midGroup + * @param {*} opts + * @param {*} cb + */ + _unsubscribe(midGroup, opts, cb) { + debug("SessionControlClient _unsubscribe", midGroup, opts); + + if (midGroupList[midGroup] === undefined) { + let err = new Error( + `[Session Control Client] [Unsubscribe] invalid groupMid [${midGroup}]` + ); + debug("SessionControlClient _unsubscribe err_invalid_midGroup"); + cb(err); + return; } - /** - * @private - */ - _sendingProcess() { - debug("SessionControlClient _sendingProcess"); - - if (this.onClose) { - if (this.midQueue.length > 0) { - let e = new Error("unavailable service"); - - this.midQueue.forEach(item => { - process.nextTick(() => item.doCallback(e)); - }); - } - return; - } - - if (this.inOperation || this.statusConnection !== CONN_CONNECTED) { - return; - } - - if (this.midQueue.length > 0) { - this.inOperation = true; - this.midInProcess = this.midQueue.shift(); - this._transmitMid(); - } + if (cb === undefined) { + if (typeof opts === "function") { + cb = opts; + opts = {}; + } else { + cb = () => {}; + } } - /** - * @private - */ - _transmitMid() { - debug("SessionControlClient _transmitMid", this.midInProcess); - - if (!this.changeRevision) { - this.midInProcess.midRevision = this._calcRevision(); - } else { - if (!this.changeRevisionGeneric) { - this.midInProcess.midRevision = this._calcRevision(); - } else { - this.midInProcess.genericRevision = this._calcRevision(); - } - } - - if (this.midInProcess.midRevision === 0) { - this.inOperation = false; - this._sendingProcess(); - return; - } - - clearTimeout(this.keepAliveTimer); - - this.keepAliveTimer = setTimeout(() => this._sendKeepAlive(), this.keepAlive); - - this.ll.write(this.midInProcess.mid); + let mid = opts || {}; + + let type = SUBSCRIBE; + + if (this.useGenerics && midGroupList[midGroup].generic) { + mid.mid = 9; + type = GENERIC; + + let midNumber = mid.midNumber || midGroupList[midGroup].unsubscribe; + let revision = + mid.revision || + this._calcRevision(midNumber, SUBSCRIBE, midGroup, true); + let dataLength = mid.dataLength || 0; + let extraData = mid.extraData || ""; + + mid.payload = { + midNumber, + revision, + dataLength, + extraData, + }; + } else { + mid.payload = mid.payload || ""; + mid.mid = midGroupList[midGroup].unsubscribe; } - /** - * @private - * @param {*} mid - * @param {*} type - * @param {*} group - * @param {*} local - */ - _calcRevision(mid, type, group, local) { - debug("SessionControlClient _calcRevision", mid, type, group, local); - - let revision = 0; - let midReference; - - if (this.midInProcess !== undefined) { - mid = mid || this.midInProcess.midNumber; - type = type || this.midInProcess.type; - group = group || this.midInProcess.group; - } - - if (this.changeRevision) { - - if (!this.changeRevisionGeneric) { - - if (this.midInProcess.baseMidRevision !== undefined || this.defaultRevisions[mid] !== undefined || this.autoRevision[mid].reference === 0) { - let e = new Error(`[Session Control Client] invalid revision, MID[${mid}], Revision [${this.autoRevision[mid].value}]`); - debug('SessionControlClient _calcRevision err_invalid_revision', this.midInProcess.baseMidRevision, this.defaultRevisions[mid], this.autoRevision[mid].reference); - this.midInProcess.doCallback(e, null); - return 0; - } - - } else { - - mid = this.midInProcess.baseGenericMid; - - if (!this.autoRevision[mid] || this.autoRevision[mid].reference === 0) { - let e = new Error(`[Session Control Client] invalid generic revision, MID[${mid}], Revision [${this.autoRevision[mid].value}]`); - debug('SessionControlClient _calcRevision err_generic_revision', mid, this.autoRevision[mid]); - this.midInProcess.doCallback(e, null); - return 0; - } - } - - if (type === SUBSCRIBE) { - midReference = this.autoRevision[mid].reference; - } - - if (type === REQUEST) { - midReference = this.autoRevision[mid].reference; - } - - let position = this.autoRevision[mid].position + 1; - revision = mids[midReference].revision()[position]; - - this.autoRevision[mid] = { - value: revision, - position: position, - reference: midReference - }; - - this.changeRevision = false; - this.changeRevisionGeneric = false; - - return revision || 0; - } - - if (local === true || this.midInProcess.baseMidRevision === undefined) { - - if (this.defaultRevisions[mid] === undefined) { - - if (this.autoRevision[mid] === undefined) { - - if (type === SUBSCRIBE) { - midReference = midGroupList[group].data; - } - - if (type === REQUEST) { - midReference = midRequest[group].reply; - } - - if (mids[midReference] === undefined) { - revision = 1; - midReference = 0; - } else { - revision = mids[midReference].revision()[0]; - } - - this.autoRevision[mid] = { - value: revision, - position: 0, - reference: midReference - }; - - } else { - revision = this.autoRevision[mid].value; - } - - } else { - revision = this.defaultRevisions[mid]; - } - - } else { - revision = this.midInProcess.midRevision; - } + this.midQueue.push(new Message(mid, cb, type, midGroup)); + this._sendingProcess(); + } - return revision || 0; - } + /** + * @private + */ + _sendingProcess() { + debug("SessionControlClient _sendingProcess"); - /** - * @private - */ - _sendKeepAlive() { - debug('SessionControlClient _sendKeepAlive'); + if (this.onClose) { + if (this.midQueue.length > 0) { + let e = new Error("unavailable service"); - if (this.onClose) { - clearTimeout(this.keepAliveTimer); - return; - } - - clearTimeout(this.keepAliveTimer); - this.keepAliveTimer = setTimeout(() => this._sendKeepAlive(), this.keepAlive); - - this.request("keepAlive", (err) => { - if (err) { - debug('SessionControlClient _sendKeepAlive response-error', err); - clearTimeout(this.keepAliveTimer); - this.close(); - } + this.midQueue.forEach((item) => { + process.nextTick(() => item.doCallback(e)); }); + } + return; } - /** - * @private - * @param {*} data - */ - _onDataLinkLayer(data) { - debug('SessionControlClient _onDataLinkLayer'); - - // Call callback of Link Layer - this.ll.finishCycle(); + if (this.inOperation || this.statusConnection !== CONN_CONNECTED) { + return; + } - // Send data to treatment - this._receiverData(data); + if (this.midQueue.length > 0) { + this.inOperation = true; + this.midInProcess = this.midQueue.shift(); + this._transmitMid(); + } + } + + /** + * @private + */ + _transmitMid() { + debug("SessionControlClient _transmitMid", this.midInProcess); + + if (!this.changeRevision) { + this.midInProcess.midRevision = this._calcRevision(); + } else { + if (!this.changeRevisionGeneric) { + this.midInProcess.midRevision = this._calcRevision(); + } else { + this.midInProcess.genericRevision = this._calcRevision(); + } } - _receiverData(data) { - debug('SessionControlClient _receiverData', data); + if (this.midInProcess.midRevision === 0) { + this.inOperation = false; + this._sendingProcess(); + return; + } - this.emit("data", data); + clearTimeout(this.keepAliveTimer); + + this.keepAliveTimer = setTimeout( + () => this._sendKeepAlive(), + this.keepAlive + ); + + this.ll.write(this.midInProcess.mid); + } + + /** + * @private + * @param {*} mid + * @param {*} type + * @param {*} group + * @param {*} local + */ + _calcRevision(mid, type, group, local) { + debug("SessionControlClient _calcRevision", mid, type, group, local); + + let revision = 0; + let midReference; + + if (this.midInProcess !== undefined) { + mid = mid || this.midInProcess.midNumber; + type = type || this.midInProcess.type; + group = group || this.midInProcess.group; + } - if (!this.midInProcess) { - return; + if (this.changeRevision) { + if (!this.changeRevisionGeneric) { + if ( + this.midInProcess.baseMidRevision !== undefined || + this.defaultRevisions[mid] !== undefined || + this.autoRevision[mid].reference === 0 + ) { + let e = new Error( + `[Session Control Client] invalid revision, MID[${mid}], Revision [${this.autoRevision[mid].value}]` + ); + debug( + "SessionControlClient _calcRevision err_invalid_revision", + this.midInProcess.baseMidRevision, + this.defaultRevisions[mid], + this.autoRevision[mid].reference + ); + this.midInProcess.doCallback(e, null); + return 0; } - - if (data.mid === 5) { - //Positive acknowledge - this.midInProcess.doCallback(null, data); - this.inOperation = false; - this._sendingProcess(); - return; + } else { + mid = this.midInProcess.baseGenericMid; + + if (!this.autoRevision[mid] || this.autoRevision[mid].reference === 0) { + let e = new Error( + `[Session Control Client] invalid generic revision, MID[${mid}], Revision [${this.autoRevision[mid].value}]` + ); + debug( + "SessionControlClient _calcRevision err_generic_revision", + mid, + this.autoRevision[mid] + ); + this.midInProcess.doCallback(e, null); + return 0; } + } - if (data.mid === 4) { + if (type === SUBSCRIBE) { + midReference = this.autoRevision[mid].reference; + } - // Reference MID - let midNumber = data.payload.midNumber; + if (type === REQUEST) { + midReference = this.autoRevision[mid].reference; + } - // Verify that the mid referenced in the response is equal to the mid sent. - if (midNumber !== this.midInProcess.midNumber) { - let err = new Error(`[Session Control Client] invalid acknowledge, expect MID[${this.midInProcess.midNumber}], received MID[${midNumber}]`); - debug('SessionControlClient _receiverData err-invalid_acknowledge', midNumber, this.midInProcess.midNumber); - this.midInProcess.doCallback(err); - this.inOperation = false; - this._sendingProcess(); - return; - } + let position = this.autoRevision[mid].position + 1; + revision = mids[midReference].revision()[position]; - if ((midNumber === 6 || midNumber === 8 || midNumber === 9) && this.useGenerics) { + this.autoRevision[mid] = { + value: revision, + position: position, + reference: midReference, + }; - let errorCode = data.payload.errorCode; + this.changeRevision = false; + this.changeRevisionGeneric = false; - //Error 74: Subscribed MID Revision unsupported - //Error 76: Requested MID Revision unsupported - if (errorCode === 74 || errorCode === 76) { - this.changeRevision = true; - this.changeRevisionGeneric = true; - this._transmitMid(); - return; - } - } - - //Error 97: MID revision unsupported - if (data.mid === 4 && data.payload.errorCode === 97) { - this.changeRevision = true; - this._transmitMid(); - return; - } - - let errorCode = helpers.padLeft(data.payload.errorCode, 2); - let err = new Error(`[Session Control Client] negative acknowledge, MID[${midNumber}], Error[${constants.ERROR[errorCode]}]`); - debug('SessionControlClient _receiverData err_negative_acknowledge', midNumber, errorCode); - this.midInProcess.doCallback(err); - this.inOperation = false; - this._sendingProcess(); - return; + return revision || 0; + } + if (local === true || this.midInProcess.baseMidRevision === undefined) { + if (this.defaultRevisions[mid] === undefined) { + if (this.autoRevision[mid] === undefined) { + if (type === SUBSCRIBE) { + midReference = midGroupList[group].data; + } + + if (type === REQUEST) { + midReference = midRequest[group].reply; + } + + if (mids[midReference] === undefined) { + revision = 1; + midReference = 0; + } else { + revision = mids[midReference].revision()[0]; + } + + this.autoRevision[mid] = { + value: revision, + position: 0, + reference: midReference, + }; + } else { + revision = this.autoRevision[mid].value; } + } else { + revision = this.defaultRevisions[mid]; + } + } else { + revision = this.midInProcess.midRevision; + } - if (this.midInProcess.type === MANUAL) { - this.midInProcess.doCallback(null, data); - return; - } + return revision || 0; + } - let receivedMid = data.mid.toString(); - let dataGroup = midData[receivedMid]; - let replyGroup = midReply[receivedMid]; + /** + * @private + */ + _sendKeepAlive() { + debug("SessionControlClient _sendKeepAlive"); - if (dataGroup !== undefined) { + if (this.onClose) { + clearTimeout(this.keepAliveTimer); + return; + } - this.emit(dataGroup, data); + clearTimeout(this.keepAliveTimer); + this.keepAliveTimer = setTimeout( + () => this._sendKeepAlive(), + this.keepAlive + ); - let obj = { - key: dataGroup, - data - }; + this.request("keepAlive", (err) => { + if (err) { + debug("SessionControlClient _sendKeepAlive response-error", err); + clearTimeout(this.keepAliveTimer); + this.close(); + } + }); + } - this.emit("__SubscribeData__", obj); + /** + * @private + * @param {*} data + */ + _onDataLinkLayer(data) { + debug("SessionControlClient _onDataLinkLayer"); + // Call callback of Link Layer + this.ll.finishCycle(); - if (!this.useLinkLayer) { - this.ll.write({ - mid: midGroupList[dataGroup].ack, - isAck: true - }); - return; - } - } + // Send data to treatment + this._receiverData(data); + } - if (replyGroup !== undefined) { + _receiverData(data) { + debug("SessionControlClient _receiverData", data); - if (replyGroup === this.midInProcess.group) { - this.midInProcess.doCallback(null, data); - } else { - let err = new Error(`[Session Control Client] invalid reply, expect MID[${this.midInProcess.group}], received [${replyGroup}]`); - debug('SessionControlClient _receiverData err_invalid_reply', replyGroup, this.midInProcess.group); - this.midInProcess.doCallback(err); - } + this.emit("data", data); - this.inOperation = false; - this._sendingProcess(); - return; - } + if (!this.midInProcess) { + return; } - /** - * @private - * @param {*} err - */ - _onErrorLinkLayer(err) { - debug('SessionControlClient _onErrorLinkLayer', err); - this.close(err); + if (data.mid === 5) { + //Positive acknowledge + this.midInProcess.doCallback(null, data); + this.inOperation = false; + this._sendingProcess(); + return; } - /** - * @private - * @param {*} err - */ - _onErrorSerializer(err) { - debug('SessionControlClient _onErrorSerializer', err); - - if (this.midInProcess) { - this.midInProcess.doCallback(err); - } - - this._sendKeepAlive(); + if (data.mid === 4) { + // Reference MID + let midNumber = data.payload.midNumber; + + // Verify that the mid referenced in the response is equal to the mid sent. + if (midNumber !== this.midInProcess.midNumber) { + let err = new Error( + `[Session Control Client] invalid acknowledge, expect MID[${this.midInProcess.midNumber}], received MID[${midNumber}]` + ); + debug( + "SessionControlClient _receiverData err-invalid_acknowledge", + midNumber, + this.midInProcess.midNumber + ); + this.midInProcess.doCallback(err); this.inOperation = false; this._sendingProcess(); + return; + } + + if ( + (midNumber === 6 || midNumber === 8 || midNumber === 9) && + this.useGenerics + ) { + let errorCode = data.payload.errorCode; + + //Error 74: Subscribed MID Revision unsupported + //Error 76: Requested MID Revision unsupported + if (errorCode === 74 || errorCode === 76) { + this.changeRevision = true; + this.changeRevisionGeneric = true; + this._transmitMid(); + return; + } + } + + //Error 97: MID revision unsupported + if (data.mid === 4 && data.payload.errorCode === 97) { + this.changeRevision = true; + this._transmitMid(); + return; + } + + let errorCode = helpers.padLeft(data.payload.errorCode, 2); + let err = new Error( + `[Session Control Client] negative acknowledge, MID[${midNumber}], Error[${constants.ERROR[errorCode]}]` + ); + debug( + "SessionControlClient _receiverData err_negative_acknowledge", + midNumber, + errorCode + ); + this.midInProcess.doCallback(err); + this.inOperation = false; + this._sendingProcess(); + return; } -} - -class Message { - - constructor(mid, callback, type, group) { - debug('SessionControlClient new Message'); - - this._mid = mid; - this._callback = callback; - this._type = type; - this._group = group; - this._baseMid = Object.assign({}, this._mid); + if (this.midInProcess.type === MANUAL) { + this.midInProcess.doCallback(null, data); + return; } - get mid() { - return this._mid; - } + let receivedMid = data.mid.toString(); + let dataGroup = midData[receivedMid]; + let replyGroup = midReply[receivedMid]; - get type() { - return this._type; - } + if (dataGroup !== undefined) { + this.emit(dataGroup, data); - get group() { - return this._group; - } + let obj = { + key: dataGroup, + data, + }; - get midNumber() { - return this._mid.mid; - } + this.emit("__SubscribeData__", obj); - get midRevision() { - return this._mid.revision; + if (!this.useLinkLayer) { + this.ll.write({ + mid: midGroupList[dataGroup].ack, + isAck: true, + }); + return; + } } - get baseMidRevision() { - return this._baseMid.revision; + if (replyGroup !== undefined) { + if (replyGroup === this.midInProcess.group) { + this.midInProcess.doCallback(null, data); + } else { + let err = new Error( + `[Session Control Client] invalid reply, expect MID[${this.midInProcess.group}], received [${replyGroup}]` + ); + debug( + "SessionControlClient _receiverData err_invalid_reply", + replyGroup, + this.midInProcess.group + ); + this.midInProcess.doCallback(err); + } + + this.inOperation = false; + this._sendingProcess(); + return; } - - set midRevision(revision) { - this._mid.revision = revision; + } + + /** + * @private + * @param {*} err + */ + _onErrorLinkLayer(err) { + debug("SessionControlClient _onErrorLinkLayer", err); + this.close(err); + } + + /** + * @private + * @param {*} err + */ + _onErrorSerializer(err) { + debug("SessionControlClient _onErrorSerializer", err); + + if (this.midInProcess) { + this.midInProcess.doCallback(err); } - doCallback(err, data) { + this._sendKeepAlive(); + this.inOperation = false; + this._sendingProcess(); + } +} - if (this._callback !== undefined) { - this._callback(err, data); - this._callback = undefined; - } +class Message { + constructor(mid, callback, type, group) { + debug("SessionControlClient new Message"); + + this._mid = mid; + this._callback = callback; + this._type = type; + this._group = group; + this._baseMid = Object.assign({}, this._mid); + } + + get mid() { + return this._mid; + } + + get type() { + return this._type; + } + + get group() { + return this._group; + } + + get midNumber() { + return this._mid.mid; + } + + get midRevision() { + return this._mid.revision; + } + + get baseMidRevision() { + return this._baseMid.revision; + } + + set midRevision(revision) { + this._mid.revision = revision; + } + + doCallback(err, data) { + if (this._callback !== undefined) { + this._callback(err, data); + this._callback = undefined; } - + } } module.exports = SessionControlClient;