diff --git a/CHANGELOG.md b/CHANGELOG.md index 1328c31..f47ba0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,11 @@ ## Changelog -#### v1.1.1 +#### v1.1.2 - Switches to GPL v3 licensing model + + +#### v1.1.1 + - Implemented parsing of MID 0900 #### v1.1.0 diff --git a/index.js b/index.js index 32180b4..4c5d9a7 100644 --- a/index.js +++ b/index.js @@ -1,71 +1,72 @@ -//@ts-check -/* - Copyright: (c) 2018-2020, Smart-Tech Controle e Automação - GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) -*/ - -const OpenProtocolParser = require("./src/openProtocolParser.js"); -const OpenProtocalSerializer = require("./src/openProtocolSerializer.js"); -const MIDParser = require("./src/MIDParser.js"); -const MIDSerializer = require("./src/MIDSerializer.js"); -const helpers = require("./src/helpers.js"); -const SessionControlClient = require("./src/sessionControlClient.js"); - -const midGroups = require("./src/midGroups.json"); -const midCommand = require("./src/midCommand.json"); -const midrequest = require("./src/midRequest.json"); - -const net = require("net"); - -function createClient(port, host, opts, connectionListener) { - - if (connectionListener === undefined) { - if (typeof opts === "function") { - connectionListener = opts; - opts = {}; - } else { - connectionListener = () => { - }; - } - } - - opts = opts || {}; - - let socket = net.createConnection(port, host, () => { - socket.setTimeout(0); - client.connect(connectionListener); - }); - - socket.setTimeout(20000); - - socket.once("timeout", () => onTimeout()); - - function onTimeout() { - let e = new Error("Socket Timeout"); - e.code = "SOCKET_TIMEOUT"; - e.address = host; - e.port = port; - client.emit("error", e); - } - - opts.stream = socket; - - let client = new SessionControlClient(opts); - - return client; -} - -module.exports = { - constants: { - subscribes: midGroups, - commands: midCommand, - requests: midrequest - }, - OpenProtocolParser, - OpenProtocalSerializer, - SessionControlClient, - MIDParser, - MIDSerializer, - helpers, - createClient +//@ts-check +/* + Copyright: (c) 2023, Alejandro de la Mata Chico + Copyright: (c) 2018-2020, Smart-Tech Controle e Automação + GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) +*/ + +const OpenProtocolParser = require("./src/openProtocolParser.js"); +const OpenProtocalSerializer = require("./src/openProtocolSerializer.js"); +const MIDParser = require("./src/MIDParser.js"); +const MIDSerializer = require("./src/MIDSerializer.js"); +const helpers = require("./src/helpers.js"); +const SessionControlClient = require("./src/sessionControlClient.js"); + +const midGroups = require("./src/midGroups.json"); +const midCommand = require("./src/midCommand.json"); +const midrequest = require("./src/midRequest.json"); + +const net = require("net"); + +function createClient(port, host, opts, connectionListener) { + + if (connectionListener === undefined) { + if (typeof opts === "function") { + connectionListener = opts; + opts = {}; + } else { + connectionListener = () => { + }; + } + } + + opts = opts || {}; + + let socket = net.createConnection(port, host, () => { + socket.setTimeout(0); + client.connect(connectionListener); + }); + + socket.setTimeout(20000); + + socket.once("timeout", () => onTimeout()); + + function onTimeout() { + let e = new Error("Socket Timeout"); + e.code = "SOCKET_TIMEOUT"; + e.address = host; + e.port = port; + client.emit("error", e); + } + + opts.stream = socket; + + let client = new SessionControlClient(opts); + + return client; +} + +module.exports = { + constants: { + subscribes: midGroups, + commands: midCommand, + requests: midrequest + }, + OpenProtocolParser, + OpenProtocalSerializer, + SessionControlClient, + MIDParser, + MIDSerializer, + helpers, + createClient }; \ No newline at end of file diff --git a/package.json b/package.json index cd285f4..0e912ad 100644 --- a/package.json +++ b/package.json @@ -1,39 +1,39 @@ -{ - "name": "node-open-protocol", - "version": "1.1.1", - "description": "A library to interface with Power Tools using the Atlas Copco Open Protocol", - "main": "index.js", - "directories": { - "doc": "doc" - }, - "scripts": { - "test": "nyc --reporter=html --reporter=text --reporter=text-summary mocha --timeout=3000 test/**/**.spec.js", - "doc": "jsdoc -d docs/jsdoc/ -r -R README.md src/" - }, - "repository": { - "type": "git", - "url": "https://github.com/netsmarttech/node-open-protocol" - }, - "keywords": [ - "hardware", - "ethernet", - "industrial", - "communication", - "controller", - "atlas-copco", - "power-focus", - "power-macs", - "stanley", - "desoutter", - "cleco", - "estic" - ], - "author": "SmartTech", - "license": "GPL-3.0-or-later", - "devDependencies": { - "chai": "^4.2.0", - "jsdoc": "^3.6.6", - "mocha": "^8.2.0", - "nyc": "^15.1.0" - } -} +{ + "name": "node-open-protocol-extended", + "version": "1.1.2", + "description": "A library to interface with Power Tools using the Atlas Copco Open Protocol", + "main": "index.js", + "directories": { + "doc": "doc" + }, + "scripts": { + "test": "nyc --reporter=html --reporter=text --reporter=text-summary mocha --timeout=3000 test/**/**.spec.js", + "doc": "jsdoc -d docs/jsdoc/ -r -R README.md src/" + }, + "repository": { + "type": "git", + "url": "https://github.com/alexmc1510/node-open-protocol-extended.git" + }, + "keywords": [ + "hardware", + "ethernet", + "industrial", + "communication", + "controller", + "atlas-copco", + "power-focus", + "power-macs", + "stanley", + "desoutter", + "cleco", + "estic" + ], + "author": "SmartTech & Alejandro de la Mata", + "license": "GPL-3.0-or-later", + "devDependencies": { + "chai": "^4.2.0", + "jsdoc": "^3.6.6", + "mocha": "^8.2.0", + "nyc": "^15.1.0" + } +} diff --git a/src/helpers.js b/src/helpers.js index f5b224b..d8a09f3 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,462 +1,566 @@ -//@ts-check -/* - Copyright: (c) 2018-2020, Smart-Tech Controle e Automação - GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) -*/ - -const fs = require("fs"); -const path = require("path"); -const codes = require('./constants.json'); -const encoding = codes.defaultEncoder; - -let midList; - -/** - * @description Converts a string or a number to a string with a determined @param size length. - * @param {number|String} n the element to be padded - * @param {number} size the desired length of the string - * @param {number} base the base used to convert @param n to a string when it's a number - * @param {String} [elm='0'] the character used to fill the empty positions - * @param {boolean} [trimLeft=false] whether we should remove the left side of the string if it's bigger than the size - * @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); -} - -/** - * @description Converts a string or a number to a string with a determined @param size length. - * @param {number|String} n the element to be padded - * @param {number} size the desired length of the string - * @param {number} base the base used to convert @param n to a string when it's a number - * @param {String} [elm='0'] the character used to fill the empty positions - * @param {boolean} [trimLeft=false] whether we should remove the left side of the string if it's bigger than the size - * @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')); -} - -/** - * @description This method returns all implemented MIDs. The implemented MIDs must be saved in "/node-open-protocol/src/mid". - * @returns {Array} - */ -function getMids() { - - if (midList) { - return midList; - } - - midList = []; - - const listFiles = fs.readdirSync(path.join(__dirname, ".", "mid")); - - listFiles.forEach((file) => { - - if (path.extname(file) !== ".js") { - return; - } - - midList[Number(path.basename(file, ".js"))] = require("./mid/" + file); - - }); - - return midList; -} - -/** - * @description This method serializes a field in [parameter] where, - * the values are read from [message.payload[parameter]], check type with [type] and - * add in [buffer] in position [position.value] with length [length]. - * - * 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 - * @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`)); - 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; - } - - 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; -} - -/** - * @description This method performs the serialization of key, the value to be serialized in [buffer] - * comes from [key] on [position.value] with length [length]. - * The [key] must be a Number. - * - * 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 - * @returns {boolean} - */ -function serializerKey(message, buffer, key, length, position, cb) { - - position.value -= length; - - if (isNaN(key)) { - cb(new Error(`[Serializer] MID[${message.mid}] key invalid [${key}]`)); - return false; - } - - buffer.write(padLeft(key, length), position.value, encoding); - - return true; -} - -/** - * @description This method perform the extraction of [parameter], the value extracted comes from [buffer] from - * the position [position.value] with length [parameterlength] and being converted to [parameterType], this - * value is add in [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. - * - * @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 {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; - - } - - position.value = parameterLength; - - return true; -} - -/** - * @description This method checks the key of [parameter], the value extracted comes from [buffer] from - * the position [keyPosition.value] with length [keylength], this value is compared with [key]. - * - * 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 - * @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; -} - -/** - * @description This method performs a check if in [position.value] of [buffer] the value is [NUL]. - * The return of this function is boolean, true: the value is [NUL] 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 {object} position - * @param {Function} 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; -} - -/** - * @description This method performs the extraction of the structure [Data Field], 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 34 - * - * @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; - - message.payload[parameter].push(dataFields); - - control += 1; - } - } - return true; -} - -/** - * @description This method performs the extraction of the structure [Resolution Field], 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 {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 = {}; - - 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; - - message.payload[parameter].push(resolutionFields); - - control += 1; - } - } - return true; -} - -module.exports = { - getMids, - testNul, - padLeft, - padRight, - processKey, - processParser, - processDataFields, - processResolutionFields: processResolutionFields, - serializerField, - serializerKey +//@ts-check +/* + Copyright: (c) 2023, Alejandro de la Mata Chico + Copyright: (c) 2018-2020, Smart-Tech Controle e Automação + GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) +*/ + +const fs = require("fs"); +const path = require("path"); +const codes = require('./constants.json'); +const encoding = codes.defaultEncoder; + +let midList; + +/** + * @description Converts a string or a number to a string with a determined @param size length. + * @param {number|String} n the element to be padded + * @param {number} size the desired length of the string + * @param {number} base the base used to convert @param n to a string when it's a number + * @param {String} [elm='0'] the character used to fill the empty positions + * @param {boolean} [trimLeft=false] whether we should remove the left side of the string if it's bigger than the size + * @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); +} + +/** + * @description Converts a string or a number to a string with a determined @param size length. + * @param {number|String} n the element to be padded + * @param {number} size the desired length of the string + * @param {number} base the base used to convert @param n to a string when it's a number + * @param {String} [elm='0'] the character used to fill the empty positions + * @param {boolean} [trimLeft=false] whether we should remove the left side of the string if it's bigger than the size + * @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')); +} + +/** + * @description This method returns all implemented MIDs. The implemented MIDs must be saved in "/node-open-protocol/src/mid". + * @returns {Array} + */ +function getMids() { + + if (midList) { + return midList; + } + + midList = []; + + const listFiles = fs.readdirSync(path.join(__dirname, ".", "mid")); + + listFiles.forEach((file) => { + + if (path.extname(file) !== ".js") { + return; + } + + midList[Number(path.basename(file, ".js"))] = require("./mid/" + file); + + }); + + return midList; +} + +/** + * @description This method serializes a field in [parameter] where, + * the values are read from [message.payload[parameter]], check type with [type] and + * add in [buffer] in position [position.value] with length [length]. + * + * 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 + * @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`)); + 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; + } + + 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; +} + +/** + * @description This method performs the serialization of key, the value to be serialized in [buffer] + * comes from [key] on [position.value] with length [length]. + * The [key] must be a Number. + * + * 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 + * @returns {boolean} + */ +function serializerKey(message, buffer, key, length, position, cb) { + + position.value -= length; + + if (isNaN(key)) { + cb(new Error(`[Serializer] MID[${message.mid}] key invalid [${key}]`)); + return false; + } + + buffer.write(padLeft(key, length), position.value, encoding); + + return true; +} + +/** + * @description This method perform the extraction of [parameter], the value extracted comes from [buffer] from + * the position [position.value] with length [parameterlength] and being converted to [parameterType], this + * value is add in [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. + * + * @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 {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(); + if (parameter === 'unit') { + message.payload.unitName = codes.UNIT[message.payload[parameter]] || ""; + } + 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; + + } + + position.value = parameterLength; + + return true; +} + +/** + * @description This method checks the key of [parameter], the value extracted comes from [buffer] from + * the position [keyPosition.value] with length [keylength], this value is compared with [key]. + * + * 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 + * @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; +} + +/** + * @description This method performs a check if in [position.value] of [buffer] the value is [NUL]. + * The return of this function is boolean, true: the value is [NUL] 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 {object} position + * @param {Function} 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; +} + +/** + * @description This method performs the extraction of the structure [Data Field], 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 34 + * + * @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 === "") { + if (length === 0) { + dataValue = 0; + } + else { + 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); + + control += 1; + } + } + return true; +} + +/** + * @description This method performs the extraction of the structure [Resolution Field], 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 {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 = {}; + + 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 === "") { + 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 === "" || isNaN(Number(timeValue)) || Number(timeValue) < 0) { + cb(new Error(`invalid value, mid: ${message.mid}, parameter: ${parameter}, payload: ${message.payload}`)); + return false; + } + resolutionFields.timeValue = Number(timeValue); + position.value += length; + + message.payload[parameter].push(resolutionFields); + + control += 1; + } + } + return true; +} + +/** + * @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; + } + + firstPropertyWithGivenValue('Coefficient', message.payload.fieldData); + + function toTimestamp(strDate){ + var datum = new Date(strDate); + return datum; + } + + + 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; + } + + 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 ((traceSample.value & 0x8000) > 0) { + traceSample.value = traceSample.value - 0x10000; + } + + traceSample.value = traceSample.value * coefficient; + + traceSample.timeStamp.setTime(traceSample.timeStamp.getTime() + (timeValue * multiplier * control)); + + message.payload[parameter].push(traceSample); + + position.value += 2; + control += 1; + } + } + return true; +} + +module.exports = { + getMids, + testNul, + padLeft, + padRight, + processKey, + processParser, + processDataFields, + processResolutionFields: processResolutionFields, + processTraceSamples, + serializerField, + serializerKey }; \ No newline at end of file diff --git a/src/mid/0900.js b/src/mid/0900.js new file mode 100644 index 0000000..77df9e5 --- /dev/null +++ b/src/mid/0900.js @@ -0,0 +1,208 @@ +//@ts-check +/* + Copyright: (c) 2023, Alejandro de la Mata Chico + Copyright: (c) 2018-2020, Smart-Tech Controle e Automação + GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) +*/ + +/* + 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 +}; \ No newline at end of file diff --git a/src/midData.json b/src/midData.json index 471408b..64f166a 100644 --- a/src/midData.json +++ b/src/midData.json @@ -1,24 +1,25 @@ -{ - "15": "psetSelected", - "52": "vin", - "61": "lastTightening", - "71": "alarm", - "74": "alarmAcknowledged", - "76": "alarmStatus", - "22": "lockAtBatchDoneUpload", - "35": "jobInfo", - "91": "multiSpindleStatus", - "101": "multiSpindleResult", - "106": "lastPowerMACSTighteningResultStationData", - "121": "jobLineControl", - "152": "multipleIdentifierResultParts", - "211": "statusExternallyMonitoredInputs", - "217": "relayFunction", - "221": "digitalInputFunction", - "242": "userData", - "251": "selectorSocketInfo", - "262": "toolTagID", - "401": "automaticManualMode", - "421": "openProtocolCommandsDisabled", - "501": "motorTuningResultData" +{ + "15": "psetSelected", + "52": "vin", + "61": "lastTightening", + "71": "alarm", + "74": "alarmAcknowledged", + "76": "alarmStatus", + "22": "lockAtBatchDoneUpload", + "35": "jobInfo", + "91": "multiSpindleStatus", + "101": "multiSpindleResult", + "106": "lastPowerMACSTighteningResultStationData", + "121": "jobLineControl", + "152": "multipleIdentifierResultParts", + "211": "statusExternallyMonitoredInputs", + "217": "relayFunction", + "221": "digitalInputFunction", + "242": "userData", + "251": "selectorSocketInfo", + "262": "toolTagID", + "401": "automaticManualMode", + "421": "openProtocolCommandsDisabled", + "501": "motorTuningResultData", + "900": "traceData" } \ No newline at end of file diff --git a/src/midGroups.json b/src/midGroups.json index 50a26ee..2b757ac 100644 --- a/src/midGroups.json +++ b/src/midGroups.json @@ -1,152 +1,159 @@ -{ - "psetSelected": { - "data": 15, - "subscribe": 14, - "unsubscribe": 17, - "ack": 16, - "generic": true - }, - "lockAtBatchDoneUpload": { - "data": 22, - "subscribe": 21, - "unsubscribe": 24, - "ack": 23, - "generic": true - }, - "jobInfo": { - "data": 35, - "subscribe": 34, - "unsubscribe": 37, - "ack": 36, - "generic": true - }, - "vin": { - "data": 52, - "subscribe": 51, - "unsubscribe": 54, - "ack": 53, - "generic": true - }, - "lastTightening": { - "data": 61, - "subscribe": 60, - "unsubscribe": 63, - "ack": 62, - "generic": true - }, - "alarm": { - "data": 71, - "subscribe": 70, - "unsubscribe": 73, - "ack": 72, - "generic": true - }, - "alarmStatus":{ - "data": 76, - "ack": 77, - "outList": true - }, - "alarmAcknowledged":{ - "data": 74, - "ack": 75, - "outList": true - }, - "multiSpindleStatus": { - "data": 91, - "subscribe": 90, - "unsubscribe": 93, - "ack": 92, - "generic": true - }, - "multiSpindleResult": { - "data": 101, - "subscribe": 100, - "unsubscribe": 103, - "ack": 102, - "generic": true - }, - "lastPowerMACSTighteningResultStationData": { - "data": 106, - "subscribe": 105, - "unsubscribe": 109, - "ack": 108, - "generic": true - }, - "jobLineControl": { - "data": 121, - "subscribe": 120, - "unsubscribe": 126, - "ack": 125, - "generic": true - }, - "multipleIdentifierResultParts": { - "data": 152, - "subscribe": 151, - "unsubscribe": 154, - "ack": 153, - "generic": true - }, - "statusExternallyMonitoredInputs": { - "data": 211, - "subscribe": 210, - "unsubscribe": 213, - "ack": 212, - "generic": true - }, - "relayFunction": { - "data": 217, - "subscribe": 216, - "unsubscribe": 219, - "ack": 218, - "generic": true - }, - "digitalInputFunction": { - "data": 221, - "subscribe": 220, - "unsubscribe": 223, - "ack": 222, - "generic": true - }, - "userData": { - "data": 242, - "subscribe": 241, - "unsubscribe": 244, - "ack": 243, - "generic": true - }, - "selectorSocketInfo": { - "data": 251, - "subscribe": 250, - "unsubscribe": 253, - "ack": 252, - "generic": true - }, - "toolTagID": { - "data": 262, - "subscribe": 261, - "unsubscribe": 264, - "ack": 263, - "generic": true - }, - "automaticManualMode": { - "data": 401, - "subscribe": 400, - "unsubscribe": 403, - "ack": 402, - "generic": true - }, - "openProtocolCommandsDisabled": { - "data": 421, - "subscribe": 420, - "unsubscribe": 423, - "ack": 422, - "generic": true - }, - "motorTuningResultData": { - "data": 501, - "subscribe": 500, - "unsubscribe": 503, - "ack": 502, - "generic": true - } +{ + "psetSelected": { + "data": 15, + "subscribe": 14, + "unsubscribe": 17, + "ack": 16, + "generic": true + }, + "lockAtBatchDoneUpload": { + "data": 22, + "subscribe": 21, + "unsubscribe": 24, + "ack": 23, + "generic": true + }, + "jobInfo": { + "data": 35, + "subscribe": 34, + "unsubscribe": 37, + "ack": 36, + "generic": true + }, + "vin": { + "data": 52, + "subscribe": 51, + "unsubscribe": 54, + "ack": 53, + "generic": true + }, + "lastTightening": { + "data": 61, + "subscribe": 60, + "unsubscribe": 63, + "ack": 62, + "generic": true + }, + "alarm": { + "data": 71, + "subscribe": 70, + "unsubscribe": 73, + "ack": 72, + "generic": true + }, + "alarmStatus":{ + "data": 76, + "ack": 77, + "outList": true + }, + "alarmAcknowledged":{ + "data": 74, + "ack": 75, + "outList": true + }, + "multiSpindleStatus": { + "data": 91, + "subscribe": 90, + "unsubscribe": 93, + "ack": 92, + "generic": true + }, + "multiSpindleResult": { + "data": 101, + "subscribe": 100, + "unsubscribe": 103, + "ack": 102, + "generic": true + }, + "lastPowerMACSTighteningResultStationData": { + "data": 106, + "subscribe": 105, + "unsubscribe": 109, + "ack": 108, + "generic": true + }, + "jobLineControl": { + "data": 121, + "subscribe": 120, + "unsubscribe": 126, + "ack": 125, + "generic": true + }, + "multipleIdentifierResultParts": { + "data": 152, + "subscribe": 151, + "unsubscribe": 154, + "ack": 153, + "generic": true + }, + "statusExternallyMonitoredInputs": { + "data": 211, + "subscribe": 210, + "unsubscribe": 213, + "ack": 212, + "generic": true + }, + "relayFunction": { + "data": 217, + "subscribe": 216, + "unsubscribe": 219, + "ack": 218, + "generic": true + }, + "digitalInputFunction": { + "data": 221, + "subscribe": 220, + "unsubscribe": 223, + "ack": 222, + "generic": true + }, + "userData": { + "data": 242, + "subscribe": 241, + "unsubscribe": 244, + "ack": 243, + "generic": true + }, + "selectorSocketInfo": { + "data": 251, + "subscribe": 250, + "unsubscribe": 253, + "ack": 252, + "generic": true + }, + "toolTagID": { + "data": 262, + "subscribe": 261, + "unsubscribe": 264, + "ack": 263, + "generic": true + }, + "automaticManualMode": { + "data": 401, + "subscribe": 400, + "unsubscribe": 403, + "ack": 402, + "generic": true + }, + "openProtocolCommandsDisabled": { + "data": 421, + "subscribe": 420, + "unsubscribe": 423, + "ack": 422, + "generic": true + }, + "motorTuningResultData": { + "data": 501, + "subscribe": 500, + "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 b0a6ca3..8dcf241 100644 --- a/src/openProtocolParser.js +++ b/src/openProtocolParser.js @@ -1,246 +1,251 @@ -//@ts-check -/* - Copyright: (c) 2018-2020, Smart-Tech Controle e Automação - GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) -*/ - -const util = require('util'); -const { Transform } = require('stream'); - -const constants = require("./constants.json"); - -const encodingOP = constants.defaultEncoder; - -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"); - } - - _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 (isNaN(length) || length < 1 || length > 9999) { - - let e = new Error(`Invalid length [${length}]`); - e.errno = constants.ERROR_LINKLAYER.INVALID_LENGTH; - - cb(e); - - debug("OpenProtocolParser _transform err-length:", ptr, chunk); - return; - } - - if (chunk.length < (ptr + length + 1)) { - this._nBuffer = chunk.slice(ptr); - cb(); - return; - } - - 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; - } - - ptr += 4; - - let mid = chunk.toString(encodingOP, ptr, ptr + 4); - obj.mid = Number(mid); - - 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; - - let revision = chunk.toString(encodingOP, ptr, ptr + 3); - - if (revision === " ") { - revision = 1; - } - - obj.revision = Number(revision); - - 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; - } - - if (obj.revision === 0) { - obj.revision = 1; - } - - ptr += 3; - - let noAck = chunk.toString(encodingOP, ptr, ptr + 1); - - 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; - } - - obj.noAck = Boolean(obj.noAck); - - ptr += 1; - - let stationID = chunk.toString(encodingOP, ptr, ptr + 2); - - if (stationID === " ") { - stationID = 1; - } - - 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 (obj.stationID === 0) { - obj.stationID = 1; - } - - ptr += 2; - - let spindleID = chunk.toString(encodingOP, ptr, ptr + 2); - - if (spindleID === " ") { - spindleID = 1; - } - - 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 (obj.spindleID === 0) { - obj.spindleID = 1; - } - - ptr += 2; - - let sequenceNumber = chunk.toString(encodingOP, ptr, ptr + 2); - - if (sequenceNumber === " ") { - sequenceNumber = 0; - } - - 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; - } - - ptr += 2; - - let messageParts = chunk.toString(encodingOP, ptr, ptr + 1); - - if (messageParts === " ") { - messageParts = 0; - } - - 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; - } - - ptr += 1; - - let messageNumber = chunk.toString(encodingOP, ptr, ptr + 1); - - if (messageNumber === " ") { - messageNumber = 0; - } - - 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; - } - - ptr += 1; - - obj.payload = chunk.slice(ptr, (ptr + length - 20)); - - ptr += (length - 20) + 1; - - if (this.rawData) { - obj._raw = chunk.slice(startPtr, ptr); - } - - this.push(obj); - } - cb(); - } - - _destroy() { - //no-op, needed to handle older node versions - } -} - -module.exports = OpenProtocolParser; +//@ts-check +/* + Copyright: (c) 2023, Alejandro de la Mata Chico + Copyright: (c) 2018-2020, Smart-Tech Controle e Automação + GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) +*/ + +const util = require('util'); +const { Transform } = require('stream'); + +const constants = require("./constants.json"); + +const encodingOP = constants.defaultEncoder; + +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"); + } + + _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 (isNaN(length) || length < 1 || length > 9999) { + + let e = new Error(`Invalid length [${length}]`); + e.errno = constants.ERROR_LINKLAYER.INVALID_LENGTH; + + cb(e); + + debug("OpenProtocolParser _transform err-length:", ptr, chunk); + return; + } + + if (chunk.length < (ptr + length + 1)) { + this._nBuffer = chunk.slice(ptr); + cb(); + return; + } + + 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; + } + + ptr += 4; + + let mid = chunk.toString(encodingOP, ptr, ptr + 4); + obj.mid = Number(mid); + + 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; + + let revision = chunk.toString(encodingOP, ptr, ptr + 3); + + if (revision === " ") { + revision = 1; + } + + obj.revision = Number(revision); + + 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; + } + + if (obj.revision === 0) { + obj.revision = 1; + } + + ptr += 3; + + let noAck = chunk.toString(encodingOP, ptr, ptr + 1); + + 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; + } + + obj.noAck = Boolean(obj.noAck); + + ptr += 1; + + let stationID = chunk.toString(encodingOP, ptr, ptr + 2); + + if (stationID === " ") { + stationID = 1; + } + + 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 (obj.stationID === 0) { + obj.stationID = 1; + } + + ptr += 2; + + let spindleID = chunk.toString(encodingOP, ptr, ptr + 2); + + if (spindleID === " ") { + spindleID = 1; + } + + 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 (obj.spindleID === 0) { + obj.spindleID = 1; + } + + ptr += 2; + + let sequenceNumber = chunk.toString(encodingOP, ptr, ptr + 2); + + if (sequenceNumber === " ") { + sequenceNumber = 0; + } + + 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; + } + + ptr += 2; + + let messageParts = chunk.toString(encodingOP, ptr, ptr + 1); + + if (messageParts === " ") { + messageParts = 0; + } + + 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; + } + + ptr += 1; + + let messageNumber = chunk.toString(encodingOP, ptr, ptr + 1); + + if (messageNumber === " ") { + messageNumber = 0; + } + + 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; + } + + ptr += 1; + + obj.payload = chunk.slice(ptr, (ptr + length - 20)); + + ptr += (length - 20) + 1; + + if (obj.mid === 900) { + ptr = ptr - 1; + } + + if (this.rawData) { + obj._raw = chunk.slice(startPtr, ptr); + } + + this.push(obj); + } + cb(); + } + + _destroy() { + //no-op, needed to handle older node versions + } +} + +module.exports = OpenProtocolParser; diff --git a/src/openProtocolSerializer.js b/src/openProtocolSerializer.js index 1073678..73e5823 100644 --- a/src/openProtocolSerializer.js +++ b/src/openProtocolSerializer.js @@ -1,168 +1,181 @@ -//@ts-check -/* - Copyright: (c) 2018-2020, Smart-Tech Controle e Automação - GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) -*/ - -const util = require('util'); -const { Transform } = require('stream'); - -const constants = require("./constants.json"); -const encodingOP = constants.defaultEncoder; - -const helpers = require("./helpers.js"); -const pad = helpers.padLeft; - -var debug = util.debuglog('open-protocol'); - -/** - * @class - * @name Header - * @param {object} Header - * @param {number} Header.mid The MID describes how to interpret the message. - * @param {number} Header.revision The MID Revision is unique per MID and is used in case different versions are available for the same MID. - * @param {boolean} Header.noAck The No Ack Flag is used when setting a subscription. - * @param {number} Header.stationID The station the message is addressed to in the case of controller with multi-station configuration. - * @param {number} Header.spindleID The spindle the message is addressed to in the case several spindles are connected to the same controller. - * @param {number} Header.sequenceNumber For acknowledging on “Link Level” with MIDs 0997 and 0998. - * @param {number} Header.messageParts Linking function can be up to 9 = possible to send 9*9999 bytes messages. ~ 90 kB. - * @param {number} Header.messageNumber Linking function, can be 1- 9 at message length > 9999. - * @param {buffer | string} Header.payload the user's data - */ - -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 === 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(); - } - - _destroy() { - //no-op, needed to handle older node versions - } -} - -module.exports = OpenProtocolSerializer; +//@ts-check +/* + Copyright: (c) 2023 Alejandro de la Mata Chico + Copyright: (c) 2018-2020, Smart-Tech Controle e Automação + GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) +*/ + +const util = require('util'); +const { Transform } = require('stream'); + +const constants = require("./constants.json"); +const encodingOP = constants.defaultEncoder; + +const helpers = require("./helpers.js"); +const pad = helpers.padLeft; + +var debug = util.debuglog('open-protocol'); + +/** + * @class + * @name Header + * @param {object} Header + * @param {number} Header.mid The MID describes how to interpret the message. + * @param {number} Header.revision The MID Revision is unique per MID and is used in case different versions are available for the same MID. + * @param {boolean} Header.noAck The No Ack Flag is used when setting a subscription. + * @param {number} Header.stationID The station the message is addressed to in the case of controller with multi-station configuration. + * @param {number} Header.spindleID The spindle the message is addressed to in the case several spindles are connected to the same controller. + * @param {number} Header.sequenceNumber For acknowledging on “Link Level” with MIDs 0997 and 0998. + * @param {number} Header.messageParts Linking function can be up to 9 = possible to send 9*9999 bytes messages. ~ 90 kB. + * @param {number} Header.messageNumber Linking function, can be 1- 9 at message length > 9999. + * @param {buffer | string} Header.payload the user's data + */ + +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.spindleID = 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); + + 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 = 0; + chunk.messageParts = Number(chunk.messageParts); + } else if (chunk.messageParts === undefined) { + chunk.messageParts = "\n"; + } else { + 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 = 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;