diff --git a/README.md b/README.md index 9ad7360..421cdde 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,14 @@ let options = { retryTimes: 3, // A list of MIDs for which we'll not parse the payload - disableMidParsing: {} + disableMidParsing: {}, + + // Weather to send the stationID and spindleID header fields as blanks, + // which is the only value Desoutter controllers accept + //true: enforce activation + //false: enforce deactivation + //undefined: auto detect from the supplier code of the MID 0002 reply + desoutterCompatibilityMode: undefined } let controllerIp = "127.0.0.1"; diff --git a/src/linkLayer.js b/src/linkLayer.js index baef51c..f29550f 100644 --- a/src/linkLayer.js +++ b/src/linkLayer.js @@ -44,6 +44,7 @@ class LinkLayer extends Duplex { * @param {number} opts.retryTimes * @param {boolean} opts.rawData * @param {boolean} opts.disableMidParsing + * @param {boolean} opts.desoutterCompatibilityMode */ constructor(opts) { debug("new LinkLayer", opts); @@ -114,6 +115,15 @@ class LinkLayer extends Duplex { //RECEIVER DATA } + /** + * @description Enables or disables the Desoutter compatibility mode at runtime. + * @param {boolean} enabled + */ + setDesoutterCompatibilityMode(enabled) { + debug("LinkLayer setDesoutterCompatibilityMode", enabled); + this.opSerializer.setDesoutterCompatibilityMode(enabled); + } + _onErrorSerializer(err) { debug("LinkLayer _onErrorSerializer", err); diff --git a/src/openProtocolSerializer.js b/src/openProtocolSerializer.js index 7a4d277..0e8672b 100644 --- a/src/openProtocolSerializer.js +++ b/src/openProtocolSerializer.js @@ -58,6 +58,17 @@ class OpenProtocolSerializer extends Transform { this.desoutterCompatibilityMode = opts.desoutterCompatibilityMode || false; } + /** + * @description Enables or disables the Desoutter compatibility mode at runtime. + * In this mode the stationID and spindleID header fields are serialized as + * blanks, as Desoutter controllers reject any other value. + * @param {boolean} enabled + */ + setDesoutterCompatibilityMode(enabled) { + debug("openProtocolSerializer setDesoutterCompatibilityMode", enabled); + this.desoutterCompatibilityMode = !!enabled; + } + _transform(chunk, encoding, cb) { debug("openProtocolSerializer _transform", chunk); diff --git a/src/sessionControlClient.js b/src/sessionControlClient.js index ac46ad3..a2a5fdc 100644 --- a/src/sessionControlClient.js +++ b/src/sessionControlClient.js @@ -38,6 +38,9 @@ const REQUEST = "request"; const MANUAL = "manual"; const GENERIC = "generic"; +//Supplier code prefix reported by Desoutter controllers on MID 2 +const DESOUTTER_SUPPLIER_PREFIX = "DE"; + //Status Connect const CONN_NOT_CONNECT = 0; const CONN_CONNECTING = 1; @@ -125,6 +128,7 @@ class SessionControlClient extends EventEmitter { * @param {object} [opts.disableMidParsing = {}] * @param {number} [opts.timeOut = 3000] * @param {number} [opts.retryTimes = 3] + * @param {boolean} [opts.desoutterCompatibilityMode] true = always send blank stationID/spindleID / false = never / undefined = auto-detect from the supplier code of the MID 2 reply * * @example * // Instantiate SessionControlClient with default values @@ -202,6 +206,11 @@ class SessionControlClient extends EventEmitter { desoutterCompatibilityMode: opts.desoutterCompatibilityMode, }); + //Desoutter compatibility mode + //If the caller didn't take an explicit position, we auto-detect it on + //the MID 2 reply of the handshake. An explicit value is always honored. + this.autoDetectDesoutter = opts.desoutterCompatibilityMode === undefined; + this.ll.on("error", (err) => this._onErrorLinkLayer(err)); this.changeRevisionGeneric = false; @@ -248,6 +257,42 @@ class SessionControlClient extends EventEmitter { return !!this.useLinkLayer; } + /** + * @description Enables or disables the Desoutter compatibility mode at runtime. + * In this mode the stationID and spindleID header fields are sent as blanks, + * as Desoutter controllers reject any other value. + * @param {boolean} enabled + */ + setDesoutterCompatibilityMode(enabled) { + debug("SessionControlClient setDesoutterCompatibilityMode", enabled); + this.ll.setDesoutterCompatibilityMode(enabled); + } + + /** + * @description Turns on the Desoutter compatibility mode if the controller + * identified itself as a Desoutter one on the MID 2 reply. Never turns it + * off, so that an explicitly requested mode can't be undone by a controller + * that reports an unexpected supplier code. + * @private + * @param {object} data the MID 2 message + */ + _detectDesoutter(data) { + if (!this.autoDetectDesoutter) { + return; + } + + let supplierCode = data.payload && data.payload.supplierCode; + + if (typeof supplierCode !== "string") { + return; + } + + if (supplierCode.trim().toUpperCase().startsWith(DESOUTTER_SUPPLIER_PREFIX)) { + debug("SessionControlClient _detectDesoutter detected", supplierCode); + this.setDesoutterCompatibilityMode(true); + } + } + /** * @description This method makes a connection with the controller. * If add a callback function, it will add as listener of connect @event. @@ -358,6 +403,8 @@ class SessionControlClient extends EventEmitter { this.ll.removeAllListeners(); + this._detectDesoutter(data); + this.statusConnection = CONN_CONNECTED; this.controllerData = data; diff --git a/test/openProtocolSerialzer.spec.js b/test/openProtocolSerialzer.spec.js index b90e90b..04da398 100644 --- a/test/openProtocolSerialzer.spec.js +++ b/test/openProtocolSerialzer.spec.js @@ -275,4 +275,51 @@ describe("Open Protocol Serializer", () => { }); }); + it('should toggle the desoutter compatibility mode at runtime', (done) => { + let serializer = new OpenProtocolSerializer(); + let received = []; + + //00230240001001010000250[Null] + const normal = Buffer.from('00230240001001010000250'); + //002302400010 0000250[Null] + const desoutter = Buffer.from('002302400010 0000250'); + + let write = () => serializer.write({ + mid: 240, + revision: 1, + noAck: false, + stationID: 1, + spindleID: 1, + sequenceNumber: 0, + messageParts: 0, + messageNumber: 0, + payload: "250" + }); + + serializer.on('data', (data) => { + received.push(data); + + switch (received.length) { + case 1: + expect(data).to.be.deep.equal(normal); + serializer.setDesoutterCompatibilityMode(true); + write(); + break; + + case 2: + expect(data).to.be.deep.equal(desoutter); + serializer.setDesoutterCompatibilityMode(false); + write(); + break; + + case 3: + expect(data).to.be.deep.equal(normal); + done(); + break; + } + }); + + write(); + }); + }); diff --git a/test/sessionControlClient.spec.js b/test/sessionControlClient.spec.js index 6da43a4..bf2ce7c 100644 --- a/test/sessionControlClient.spec.js +++ b/test/sessionControlClient.spec.js @@ -1059,4 +1059,105 @@ describe("Session Control Client", () => { sessionControlClient.connect(); }); + it("Should enable the desoutter compatibility mode when the supplier code starts with DE", (done) => { + + let writes = []; + let sessionControlClient; + + let stream = createStreamHelper((data) => { + + writes.push(data); + + if (writes.length === 1) { + //MID 2 revision 2, supplier code "DE3" + stream.push(Buffer.from("00620002002000000000010001020103Airbag1 04DE3\u0000")); + return; + } + + if (writes.length === 2) { + expect(data.toString("ascii", 12, 16)).to.be.equal(" "); + sessionControlClient.close(); + done(); + } + }); + + sessionControlClient = new SessionControlClient({ + stream: stream + }); + + sessionControlClient.on("connect", (data) => { + expect(data.payload.supplierCode).to.be.equal("DE3"); + sessionControlClient.sendMid(9999, {}, () => {}); + }); + + sessionControlClient.connect(); + }); + + it("Should keep sending the stationID and spindleID for a non desoutter supplier code", (done) => { + + let writes = []; + let sessionControlClient; + + let stream = createStreamHelper((data) => { + + writes.push(data); + + if (writes.length === 1) { + //MID 2 revision 2, supplier code "AC " + stream.push(Buffer.from("00620002002000000000010001020103Airbag1 04AC \u0000")); + return; + } + + if (writes.length === 2) { + expect(data.toString("ascii", 12, 16)).to.be.equal("0101"); + sessionControlClient.close(); + done(); + } + }); + + sessionControlClient = new SessionControlClient({ + stream: stream + }); + + sessionControlClient.on("connect", () => { + sessionControlClient.sendMid(9999, {}, () => {}); + }); + + sessionControlClient.connect(); + }); + + it("Should not auto detect the desoutter compatibility mode when it is explicitly disabled", (done) => { + + let writes = []; + let sessionControlClient; + + let stream = createStreamHelper((data) => { + + writes.push(data); + + if (writes.length === 1) { + //MID 2 revision 2, supplier code "DE3" + stream.push(Buffer.from("00620002002000000000010001020103Airbag1 04DE3\u0000")); + return; + } + + if (writes.length === 2) { + expect(data.toString("ascii", 12, 16)).to.be.equal("0101"); + sessionControlClient.close(); + done(); + } + }); + + sessionControlClient = new SessionControlClient({ + stream: stream, + desoutterCompatibilityMode: false + }); + + sessionControlClient.on("connect", () => { + sessionControlClient.sendMid(9999, {}, () => {}); + }); + + sessionControlClient.connect(); + }); + });