@eflexsystems/node-open-protocol
Version:
A library to interface with Power Tools using the Atlas Copco Open Protocol
1,177 lines (959 loc) • 35.7 kB
JavaScript
//@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 EventEmitter = require('events');
const LinkLayer = require('../src/linkLayer.js');
const helpers = require("./helpers.js");
const midGroupList = require("./midGroups.json");
const midData = require("./midData.json");
const constants = require("./constants.json");
const midRequest = require("./midRequest.json");
const midCommand = require("./midCommand.json");
const midReply = require("./midReply.json");
const mids = helpers.getMids();
var debug = util.debuglog('open-protocol');
const SUBSCRIBE = "subscribe";
const COMMAND = "command";
const REQUEST = "request";
const MANUAL = "manual";
const GENERIC = "generic";
//Status Connect
const CONN_NOT_CONNECT = 0;
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);
}
});
});
}
function maybePromisify(ref, method, mid, opts, cb) {
if (cb === undefined) {
if (typeof opts === "function") {
cb = opts;
opts = {};
} else {
return promisify(ref, method, mid, opts);
}
}
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");
}
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;
//Generic Mode
this.useGenerics = opts.genericMode || false;
//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.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.keepAliveTimer = undefined;
// this.receiverKeepAliveTimer = undefined;
this.onClose = false;
this.stream = opts.stream;
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"));
});
}
/**
* @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;
}
} else {
revision = this.defaultRevisions["1"];
}
this.statusConnection = CONN_CONNECTING;
midSend = {
mid: 1,
revision: revision
};
this.ll.write(midSend);
};
let receivedReply = (data) => {
debug("SessionControlClient receivedReply", data);
this.ll.finishCycle();
this.emit("data", data);
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;
}
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);
}
return;
}
if (data.mid === 2) {
/*
Disabling revisions check because our simulator always respond Revision == "00 "
if (data.revision !== midSend.revision) {
if (this.defaultRevisions["1"] !== undefined) {
this.emit("error", new Error(`[Session Control Client] [Connect] invalid revision MID[1], Revision [${midSend.revision}]`));
return;
}
let newPosition = this.autoRevision["1"].position + 1;
this.autoRevision["1"].value = mids[2].revision()[newPosition];
this.autoRevision["1"].position = newPosition;
sendMidOne();
return;
}
*/
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));
process.nextTick(() => {
this.emit("connect", 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);
});
});
}
}
/**
* @description This method destroys the connection and cleans all states of SessionControlClient.
* @param {Error} [err]
*/
close(err) {
debug("SessionControlClient close", err);
if (this.onClose) {
return;
}
clearTimeout(this.keepAliveTimer);
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.autoRevision = {};
// handles Node versions older than 8.x
if(typeof this.ll.destroy === 'function'){
this.ll.destroy();
} else {
this.ll._destroy();
}
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;
}
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;
}
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;
}
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;
}
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();
}
/**
* @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;
}
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();
}
/**
* @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();
}
}
/**
* @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);
}
/**
* @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;
}
return revision || 0;
}
/**
* @private
*/
_sendKeepAlive() {
debug('SessionControlClient _sendKeepAlive');
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();
}
});
}
/**
* @private
* @param {*} data
*/
_onDataLinkLayer(data) {
debug('SessionControlClient _onDataLinkLayer');
// Call callback of Link Layer
this.ll.finishCycle();
// Send data to treatment
this._receiverData(data);
}
_receiverData(data) {
debug('SessionControlClient _receiverData', data);
this.emit("data", data);
if (!this.midInProcess) {
return;
}
if (data.mid === 5) {
//Positive acknowledge
this.midInProcess.doCallback(null, data);
this.inOperation = false;
this._sendingProcess();
return;
}
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;
}
if (this.midInProcess.type === MANUAL) {
this.midInProcess.doCallback(null, data);
return;
}
let receivedMid = data.mid.toString();
let dataGroup = midData[receivedMid];
let replyGroup = midReply[receivedMid];
if (dataGroup !== undefined) {
this.emit(dataGroup, data);
let obj = {
key: dataGroup,
data
};
this.emit("__SubscribeData__", obj);
if (!this.useLinkLayer) {
this.ll.write({
mid: midGroupList[dataGroup].ack,
isAck: true
});
return;
}
}
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;
}
}
/**
* @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);
}
this._sendKeepAlive();
this.inOperation = false;
this._sendingProcess();
}
}
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;